// What a thread is doing, from the watch's point of view. Ported from T3 Code's // `packages/shared/src/agentAwareness.ts` (MIT), which drives T3's own mobile notifications. import type { ThreadShell } from "./t3/types.ts"; export type Phase = | "starting" | "running" | "waiting_for_approval" | "waiting_for_input" | "completed" | "failed"; type AwarenessFields = Pick< ThreadShell, | "activityRunStatus" | "lineage" | "latestRunStopRequested" | "pendingBackgroundTasks" | "pendingRuntimeRequest" | "settledOverride" | "status" >; /** * 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. */ export function threadPhase(thread: AwarenessFields): Phase | null { if (thread.lineage.relationshipToParent === "subagent") return null; const phase = resolvePhase(thread); if (phase === null) return null; if (phase === "completed" && thread.latestRunStopRequested === true) return null; if ( thread.settledOverride === "settled" && (phase === "completed" || phase === "failed") && !backgroundWorkHoldsCompletion(thread.pendingBackgroundTasks ?? []) ) { return null; } return phase; } function resolvePhase(thread: AwarenessFields): Phase | null { const runStatus = thread.activityRunStatus ?? thread.status; const request = thread.pendingRuntimeRequest; if ( request?.kind === "user_input" && (request.blocking !== false || !(runStatus === "preparing" || runStatus === "starting" || runStatus === "running")) ) { return "waiting_for_input"; } if (request && request.kind !== "user_input" && request.kind !== "auth_refresh") { return "waiting_for_approval"; } switch (runStatus) { case "preparing": case "starting": return "starting"; case "running": case "waiting": return "running"; case "completed": return backgroundWorkHoldsCompletion(thread.pendingBackgroundTasks ?? []) ? "running" : "completed"; case "failed": return "failed"; default: return null; } } // Commands and monitors are as often a dev server or its watchdog as a wait, so they hold // completion only when the agent marks them awaited. function backgroundWorkHoldsCompletion(tasks: NonNullable): boolean { return tasks.some((task) => task.awaited === true || task.kind === "subagent" || task.kind === "background_task"); }