From 442036b22fd887b72e655dc873cc0541feceff87 Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Sun, 1 Nov 2026 19:54:08 -0500 Subject: [PATCH] Gateway: hide delegated threads unless they wait on the user --- gateway/src/awareness.ts | 5 ++++- gateway/src/t3/types.ts | 4 +++- gateway/test/awareness.test.ts | 8 ++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/gateway/src/awareness.ts b/gateway/src/awareness.ts index c1ae698..47e1efa 100644 --- a/gateway/src/awareness.ts +++ b/gateway/src/awareness.ts @@ -14,6 +14,7 @@ export type Phase = type AwarenessFields = Pick< ThreadShell, | "activityRunStatus" + | "delegatedFrom" | "lineage" | "latestRunStopRequested" | "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, - * 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 { if (thread.lineage.relationshipToParent === "subagent") return null; const phase = resolvePhase(thread); 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 ( thread.settledOverride === "settled" && diff --git a/gateway/src/t3/types.ts b/gateway/src/t3/types.ts index d59f995..4259958 100644 --- a/gateway/src/t3/types.ts +++ b/gateway/src/t3/types.ts @@ -68,7 +68,9 @@ export interface ThreadShell { lastError?: string | null; modelSelection: ModelSelection; 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; } diff --git a/gateway/test/awareness.test.ts b/gateway/test/awareness.test.ts index a9eed50..ad6d3d3 100644 --- a/gateway/test/awareness.test.ts +++ b/gateway/test/awareness.test.ts @@ -95,4 +95,12 @@ describe("threadPhase", () => { 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"); + }); });