forked from pingdotgg/t3code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathagentAwareness.ts
More file actions
152 lines (143 loc) · 4.53 KB
/
Copy pathagentAwareness.ts
File metadata and controls
152 lines (143 loc) · 4.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import type {
EnvironmentId,
OrchestrationProjectShell,
OrchestrationThreadShell,
ThreadId,
} from "@t3tools/contracts";
export type AgentAwarenessPhase =
| "starting"
| "running"
| "waiting_for_approval"
| "waiting_for_input"
| "completed"
| "failed"
| "stale";
export interface AgentAwarenessState {
readonly environmentId: EnvironmentId;
readonly threadId: ThreadId;
readonly projectTitle: string;
readonly threadTitle: string;
readonly phase: AgentAwarenessPhase;
readonly headline: string;
readonly detail?: string;
readonly modelTitle: string;
readonly updatedAt: string;
readonly deepLink: string;
}
export interface ProjectThreadAwarenessInput {
readonly environmentId: EnvironmentId;
readonly project: Pick<OrchestrationProjectShell, "title">;
readonly thread: Pick<
OrchestrationThreadShell,
| "id"
| "title"
| "modelSelection"
| "session"
| "latestTurn"
| "updatedAt"
| "hasPendingApprovals"
| "hasPendingUserInput"
>;
}
export function buildAgentAwarenessDeepLink(input: {
readonly environmentId: EnvironmentId;
readonly threadId: ThreadId;
}): string {
return `/threads/${encodeURIComponent(input.environmentId)}/${encodeURIComponent(input.threadId)}`;
}
export function projectThreadAwareness(
input: ProjectThreadAwarenessInput,
): AgentAwarenessState | null {
const { environmentId, project, thread } = input;
const phase = resolveThreadAwarenessPhase(thread);
if (!phase) {
return null;
}
const detail = detailForPhase(phase, thread);
return {
environmentId,
threadId: thread.id,
projectTitle: project.title,
threadTitle: thread.title,
phase,
headline: headlineForPhase(phase),
...(detail === undefined ? {} : { detail }),
modelTitle: thread.modelSelection.model,
updatedAt: thread.updatedAt,
deepLink: buildAgentAwarenessDeepLink({ environmentId, threadId: thread.id }),
};
}
function resolveThreadAwarenessPhase(
thread: ProjectThreadAwarenessInput["thread"],
): AgentAwarenessPhase | null {
if (thread.hasPendingApprovals) {
return "waiting_for_approval";
}
if (thread.hasPendingUserInput) {
return "waiting_for_input";
}
if (thread.session?.status === "error" || thread.latestTurn?.state === "error") {
return "failed";
}
if (thread.session?.status === "starting") {
return "starting";
}
if (thread.session?.status === "running" || thread.latestTurn?.state === "running") {
return "running";
}
if (thread.latestTurn?.state === "completed") {
return "completed";
}
// A turn that finished can still read as "interrupted" here: session
// teardown settles still-running turns by session status, and that write
// can race the turn.completed one. completedAt survives the race — a turn
// that has a completion timestamp finished, whatever the state column says.
// Without this, quick finish-then-teardown threads resolve to null
// persistently and get tombstoned instead of published as completed.
if (thread.latestTurn?.state === "interrupted" && thread.latestTurn.completedAt !== null) {
return "completed";
}
// Threads whose turns never produce a checkpoint (no code changes) have no
// materialized latestTurn in the shell at all, and the session-set
// projection clears latest_turn_id the moment the session settles. The
// session status is then the only surviving completion signal: a live
// session at "ready"/"idle" with nothing pending and nothing running means
// the agent finished and is waiting for the next prompt — Done.
if (thread.session?.status === "ready" || thread.session?.status === "idle") {
return "completed";
}
return null;
}
function headlineForPhase(phase: AgentAwarenessPhase): string {
switch (phase) {
case "starting":
return "Starting agent";
case "running":
return "Agent is working";
case "waiting_for_approval":
return "Approval needed";
case "waiting_for_input":
return "Waiting for input";
case "completed":
return "Agent finished";
case "failed":
return "Agent failed";
case "stale":
return "Update delayed";
}
}
function detailForPhase(
phase: AgentAwarenessPhase,
thread: ProjectThreadAwarenessInput["thread"],
): string | undefined {
if (phase === "failed") {
return thread.session?.lastError ?? undefined;
}
if (phase === "completed") {
return "Review the completed task.";
}
if (phase === "running" && thread.session?.providerName) {
return `${thread.session.providerName} is active.`;
}
return undefined;
}