Gateway: hide delegated threads unless they wait on the user
Gateway / check (push) Successful in 10s
Gateway / image (push) Successful in 22s

This commit is contained in:
gib committed 2026-11-01 19:54:08 -05:00
1 parent 221622b574
commit 442036b22f
3 files changed
+15 -2

No files matched your search

+4 -1
View File
@@ -14,6 +14,7 @@ export type Phase =
type AwarenessFields = Pick< type AwarenessFields = Pick<
ThreadShell, ThreadShell,
| "activityRunStatus" | "activityRunStatus"
| "delegatedFrom"
| "lineage" | "lineage"
| "latestRunStopRequested" | "latestRunStopRequested"
| "pendingBackgroundTasks" | "pendingBackgroundTasks"
@@ -24,12 +25,14 @@ type AwarenessFields = Pick<
/** /**
* The phase to show for a thread, or null when the watch should not show it: subagent children, * The phase to show for a thread, or null when the watch should not show it: subagent children,
* threads that never ran or were stopped, and finished work the user already settled. * threads that never ran or were stopped, and finished work the user already settled. Delegated
* threads show only while they wait on the user, so their approvals still reach the watch.
*/ */
export function threadPhase(thread: AwarenessFields): Phase | null { export function threadPhase(thread: AwarenessFields): Phase | null {
if (thread.lineage.relationshipToParent === "subagent") return null; if (thread.lineage.relationshipToParent === "subagent") return null;
const phase = resolvePhase(thread); const phase = resolvePhase(thread);
if (phase === null) return null; if (phase === null) return null;
if (thread.delegatedFrom && phase !== "waiting_for_approval" && phase !== "waiting_for_input") return null;
if (phase === "completed" && thread.latestRunStopRequested === true) return null; if (phase === "completed" && thread.latestRunStopRequested === true) return null;
if ( if (
thread.settledOverride === "settled" && thread.settledOverride === "settled" &&
+3 -1
View File
@@ -68,7 +68,9 @@ export interface ThreadShell {
lastError?: string | null; lastError?: string | null;
modelSelection: ModelSelection; modelSelection: ModelSelection;
runtimeMode?: string; runtimeMode?: string;
lineage: { relationshipToParent: Open<"subagent"> | null }; lineage: { relationshipToParent: Open<"fork" | "subagent"> | null };
/** Set on threads an agent started with delegate_task, which T3 does not mark as subagents. */
delegatedFrom?: { environmentId: string; threadId: string; title: string };
settledOverride?: Open<"settled"> | null; settledOverride?: Open<"settled"> | null;
} }
+8
View File
@@ -95,4 +95,12 @@ describe("threadPhase", () => {
it("hides threads that never ran", () => { it("hides threads that never ran", () => {
expect(threadPhase(thread({ status: "idle" }))).toBeNull(); 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");
});
}); });