77 lines
2.5 KiB
TypeScript
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");
|
|
}
|