99 lines
3.7 KiB
TypeScript
99 lines
3.7 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 { 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();
|
|
});
|
|
});
|