Files
wristcode/gateway/src/awareness.ts
T

77 lines
2.5 KiB
TypeScript

// 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<ThreadShell["pendingBackgroundTasks"]>): boolean {
return tasks.some((task) => task.awaited === true || task.kind === "subagent" || task.kind === "background_task");
}