129 lines
5.2 KiB
TypeScript
129 lines
5.2 KiB
TypeScript
// Cases ported from T3 Code's agentAwareness.test.ts, so the gateway notifies when T3's own app would.
|
|
import { describe, expect, it } from "bun:test";
|
|
|
|
import { isSnoozed, threadPhase } from "../src/awareness.ts";
|
|
import type { ThreadShell } from "../src/t3/types.ts";
|
|
|
|
const thread = (overrides: Partial<ThreadShell> = {}): ThreadShell => ({
|
|
id: "thread-2",
|
|
projectId: "project-1",
|
|
title: "Integrate orchestration",
|
|
createdAt: "2026-05-22T12:00:00.000Z",
|
|
updatedAt: "2026-05-22T12:00:00.000Z",
|
|
status: "running",
|
|
activeRunId: "run-1",
|
|
pendingRuntimeRequest: null,
|
|
modelSelection: { instanceId: "codex", model: "gpt-5.4" },
|
|
lineage: { relationshipToParent: null },
|
|
settledOverride: null,
|
|
...overrides,
|
|
});
|
|
|
|
const request = (kind: string, blocking?: boolean): ThreadShell["pendingRuntimeRequest"] => ({
|
|
id: `request-${kind}`,
|
|
kind,
|
|
createdAt: "2026-05-22T12:00:00.000Z",
|
|
...(blocking === undefined ? {} : { blocking }),
|
|
});
|
|
|
|
describe("threadPhase", () => {
|
|
it("does not announce completion after a manual stop", () => {
|
|
expect(threadPhase(thread({ status: "completed" }))).toBe("completed");
|
|
expect(threadPhase(thread({ status: "completed", latestRunStopRequested: true }))).toBeNull();
|
|
});
|
|
|
|
it.each(["running", "completed", "failed"])("hides %s subagent activity", (status) => {
|
|
expect(threadPhase(thread({ status, lineage: { relationshipToParent: "subagent" } }))).toBeNull();
|
|
});
|
|
|
|
it("keeps an older activity run visible over a newer cancelled run", () => {
|
|
expect(threadPhase(thread({ status: "cancelled", activityRunStatus: "running" }))).toBe("running");
|
|
});
|
|
|
|
it.each(["completed", "failed"])("hides settled %s work", (status) => {
|
|
expect(threadPhase(thread({ status, settledOverride: "settled" }))).toBeNull();
|
|
});
|
|
|
|
it.each([
|
|
["starting", "starting"],
|
|
["running", "running"],
|
|
["waiting", "running"],
|
|
] as const)("keeps settled %s work visible", (status, phase) => {
|
|
expect(threadPhase(thread({ status, settledOverride: "settled" }))).toBe(phase);
|
|
});
|
|
|
|
it.each([
|
|
["user_input", "waiting_for_input"],
|
|
["command", "waiting_for_approval"],
|
|
] as const)("keeps a settled terminal thread with a pending %s request", (kind, phase) => {
|
|
expect(
|
|
threadPhase(thread({ status: "completed", settledOverride: "settled", pendingRuntimeRequest: request(kind) })),
|
|
).toBe(phase);
|
|
});
|
|
|
|
it("keeps settled work held open by an awaited monitor", () => {
|
|
expect(
|
|
threadPhase(
|
|
thread({
|
|
status: "completed",
|
|
settledOverride: "settled",
|
|
pendingBackgroundTasks: [{ kind: "monitor", awaited: true }],
|
|
}),
|
|
),
|
|
).toBe("running");
|
|
});
|
|
|
|
it.each([
|
|
["only a dev server", "completed", [{ kind: "command" }]],
|
|
["a dev server's watchdog monitor", "completed", [{ kind: "monitor" }]],
|
|
["a command the agent waits on", "running", [{ kind: "command", awaited: true }]],
|
|
["a dev server and a subagent", "running", [{ kind: "command" }, { kind: "subagent" }]],
|
|
] as const)("reports a completed run waiting on %s as %s", (_case, phase, tasks) => {
|
|
expect(threadPhase(thread({ status: "completed", pendingBackgroundTasks: [...tasks] }))).toBe(phase);
|
|
});
|
|
|
|
it("reports an async question as input only after the working run completes", () => {
|
|
const asking = thread({ pendingRuntimeRequest: request("user_input", false) });
|
|
expect(threadPhase(asking)).toBe("running");
|
|
expect(threadPhase({ ...asking, status: "completed" })).toBe("waiting_for_input");
|
|
});
|
|
|
|
it("does not present authentication refreshes as approvals", () => {
|
|
expect(threadPhase(thread({ pendingRuntimeRequest: request("auth_refresh") }))).toBe("running");
|
|
});
|
|
|
|
it("hides threads that never ran", () => {
|
|
expect(threadPhase(thread({ status: "idle" }))).toBeNull();
|
|
});
|
|
|
|
// Not from upstream: delegate_task children carry no subagent lineage, only delegatedFrom.
|
|
it("shows delegated threads only while they wait on the user", () => {
|
|
const delegatedFrom = { environmentId: "env-desktop", threadId: "thread-1", title: "Parent" };
|
|
expect(threadPhase(thread({ delegatedFrom }))).toBeNull();
|
|
expect(threadPhase(thread({ delegatedFrom, status: "completed" }))).toBeNull();
|
|
expect(threadPhase(thread({ delegatedFrom, pendingRuntimeRequest: request("command") }))).toBe("waiting_for_approval");
|
|
});
|
|
});
|
|
|
|
describe("isSnoozed", () => {
|
|
const now = Date.parse("2026-11-01T12:00:00.000Z");
|
|
const snoozed = {
|
|
snoozedUntil: "2026-11-01T18:00:00.000Z",
|
|
snoozedAt: "2026-11-01T10:00:00.000Z",
|
|
latestRunCompletedAt: "2026-11-01T09:00:00.000Z",
|
|
status: "completed",
|
|
pendingRuntimeRequest: null,
|
|
} as const;
|
|
|
|
it("hides a thread until its time, as T3 does", () => {
|
|
expect(isSnoozed(snoozed, now)).toBe(true);
|
|
expect(isSnoozed({ ...snoozed, snoozedUntil: "2026-11-01T11:00:00.000Z" }, now)).toBe(false);
|
|
});
|
|
|
|
it("wakes a thread that waits on someone or failed after the snooze", () => {
|
|
expect(isSnoozed({ ...snoozed, pendingRuntimeRequest: { id: "r", kind: "command", createdAt: "" } }, now)).toBe(false);
|
|
expect(isSnoozed({ ...snoozed, status: "failed", latestRunCompletedAt: "2026-11-01T11:00:00.000Z" }, now)).toBe(false);
|
|
expect(isSnoozed({ ...snoozed, status: "failed" }, now)).toBe(true);
|
|
});
|
|
});
|