@ferris1225/pi-subagents 0.32.2 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -22
- package/agents/reviewer.md +2 -0
- package/package.json +2 -2
- package/src/agents.ts +2 -7
- package/src/announcements.ts +62 -0
- package/src/completion.ts +7 -36
- package/src/config.ts +327 -364
- package/src/dispatch.ts +1682 -1878
- package/src/fixloop.ts +0 -16
- package/src/format.ts +4 -8
- package/src/index.ts +8 -9
- package/src/models.ts +17 -39
- package/src/monitor.ts +64 -175
- package/src/rpc-run.ts +2 -41
- package/src/runtime.ts +272 -285
- package/src/session-fork.ts +0 -4
- package/src/setup.ts +4 -4
- package/src/spawn.ts +542 -562
- package/src/tools.ts +706 -748
- package/src/ui.ts +3 -7
- package/src/widget.ts +90 -178
- package/src/worktree.ts +1 -1
- package/src/inspector-panel.ts +0 -363
- package/src/inspector.ts +0 -369
- package/src/trajectory.ts +0 -503
package/src/trajectory.ts
DELETED
|
@@ -1,503 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Append-only inspector backing stores for sub-agent threads.
|
|
3
|
-
*
|
|
4
|
-
* Two structures, both owned by a single thread record ({@link TrajectoryLog} or
|
|
5
|
-
* {@link InspectRunState}):
|
|
6
|
-
*
|
|
7
|
-
* - {@link TrajectoryLog} — a typed, append-only event log covering
|
|
8
|
-
* orchestration (dispatch, model-candidate switches, retries, control actions
|
|
9
|
-
* steer/retarget/park/resume/stop) and live child activity (status transitions,
|
|
10
|
-
* tool starts/ends, usage). Events keep their generation and source timestamps
|
|
11
|
-
* forever: resume/restart clears the mutable summary fields but NEVER the
|
|
12
|
-
* event history, so the inspector can show the full story of a thread across
|
|
13
|
-
* generations. Fork and worktree events carry typed relationship/lifecycle
|
|
14
|
-
* payloads so retained source/child state remains inspectable.
|
|
15
|
-
*
|
|
16
|
-
* - {@link TranscriptBuffer} — a bounded rolling window of streamed assistant
|
|
17
|
-
* text/thinking for the CURRENT generation, dropped on restart and cleared on
|
|
18
|
-
* parent-session teardown. Render-performance aid only; it trends toward
|
|
19
|
-
* dropping old output while the trajectory log is the durable record.
|
|
20
|
-
*
|
|
21
|
-
* Aliveness back-compat: the widget's "thinking"/"responding" activity comes from
|
|
22
|
-
* forwarded {@link SubagentLiveEvent}s, which carry no delta payload. The
|
|
23
|
-
* trajectory needs the delta text itself. Rather than widening the live event
|
|
24
|
-
* union (which would silently drop interpreter results from widget consumers),
|
|
25
|
-
* the monitor fan-out handler extracts the delta text alongside forwarding and
|
|
26
|
-
* appends it here — additive, zero churn for the widget path.
|
|
27
|
-
*
|
|
28
|
-
* Secret hygiene: tool arguments are summarized verbatim by key, but obvious
|
|
29
|
-
* credential-bearing fields (token/password/authorization/apiKey/secret/...)
|
|
30
|
-
* are redacted, and values are truncated to a compact length. Arbitrary deep
|
|
31
|
-
* payloads never enter the transcript or the trajectory.
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
import { stripVTControlCharacters } from "node:util";
|
|
35
|
-
import type { UsageStats } from "./rpc-run.ts";
|
|
36
|
-
import type { IsolationMode, WorktreeFinalizationStatus } from "./worktree.ts";
|
|
37
|
-
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
// Event types
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
|
|
42
|
-
export const TRAJECTORY_VERSION = 1;
|
|
43
|
-
|
|
44
|
-
/** Typed append-only trajectory events. */
|
|
45
|
-
export type TrajectoryEvent =
|
|
46
|
-
| { v: number; runId: number; generation: number; at: number; kind: "dispatch"; agent: string; task: string; model?: string; thinking?: string; pool?: readonly string[]; vision?: boolean; resumed?: boolean; isolation?: IsolationMode; originalCwd?: string; isolationCwd?: string }
|
|
47
|
-
| { v: number; runId: number; generation: number; at: number; kind: "status"; status: string; phase?: string }
|
|
48
|
-
| { v: number; runId: number; generation: number; at: number; kind: "candidate"; model?: string; index?: number; total?: number; fallbackFrom?: string }
|
|
49
|
-
| { v: number; runId: number; generation: number; at: number; kind: "retry"; reason: string; delayMs?: number }
|
|
50
|
-
| { v: number; runId: number; generation: number; at: number; kind: "steer"; instruction: string }
|
|
51
|
-
| { v: number; runId: number; generation: number; at: number; kind: "retarget"; objective: string }
|
|
52
|
-
| { v: number; runId: number; generation: number; at: number; kind: "park" }
|
|
53
|
-
| { v: number; runId: number; generation: number; at: number; kind: "resume"; objective?: string }
|
|
54
|
-
| { v: number; runId: number; generation: number; at: number; kind: "fork"; sourceRunId: number; childRunId: number; objective?: string }
|
|
55
|
-
| { v: number; runId: number; generation: number; at: number; kind: "stop"; reason?: string }
|
|
56
|
-
| { v: number; runId: number; generation: number; at: number; kind: "worktree"; status: "created" | WorktreeFinalizationStatus; originalCwd: string; isolationCwd?: string; worktreePath?: string; patchPath?: string; integrated?: boolean; error?: string }
|
|
57
|
-
| { v: number; runId: number; generation: number; at: number; kind: "settled"; status: "done" | "failed" | "stopped"; model?: string; isolation?: IsolationMode; integrationStatus?: WorktreeFinalizationStatus }
|
|
58
|
-
| { v: number; runId: number; generation: number; at: number; kind: "tool_start"; tool: string; toolCallId?: string; summary: string }
|
|
59
|
-
| { v: number; runId: number; generation: number; at: number; kind: "tool_end"; tool: string; toolCallId?: string; isError: boolean; error?: string }
|
|
60
|
-
| { v: number; runId: number; generation: number; at: number; kind: "usage"; usage: UsageStats; model?: string };
|
|
61
|
-
|
|
62
|
-
export type TrajectoryEventKind = TrajectoryEvent["kind"];
|
|
63
|
-
|
|
64
|
-
/** Distributive Omit: per-variant event payload without the envelope fields
|
|
65
|
-
* (v/runId/generation/at are stamped by TrajectoryLog.append). */
|
|
66
|
-
type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
|
|
67
|
-
export type NewTrajectoryEvent = DistributiveOmit<TrajectoryEvent, "v" | "runId" | "generation" | "at">;
|
|
68
|
-
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
|
-
// Tool-arg safety: concise summary + redaction
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
|
|
73
|
-
/** Truncation budget for one scalar argument value inside a summary. */
|
|
74
|
-
export const TOOL_ARG_VALUE_MAX = 48;
|
|
75
|
-
/** Total budget for a summarized tool-args blob. */
|
|
76
|
-
export const TOOL_ARG_SUMMARY_MAX = 160;
|
|
77
|
-
|
|
78
|
-
const SENSITIVE_KEY_RE = /token|password|authorization|api[-_]?key|apikey|secret|credential|cookie|bearer/i;
|
|
79
|
-
const REDACTED = "<redacted>";
|
|
80
|
-
|
|
81
|
-
function isSensitiveKey(key: string): boolean {
|
|
82
|
-
return SENSITIVE_KEY_RE.test(key);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** Redact credentials embedded inside otherwise ordinary scalar fields such as
|
|
86
|
-
* `command`. Key-only filtering is insufficient for shell/header arguments. */
|
|
87
|
-
export function redactSensitiveText(value: string): string {
|
|
88
|
-
let text = stripVTControlCharacters(value);
|
|
89
|
-
text = text.replace(
|
|
90
|
-
/(\bauthorization\s*:\s*(?:bearer|basic)\s+)([^\s'"`;,]+)/giu,
|
|
91
|
-
`$1${REDACTED}`,
|
|
92
|
-
);
|
|
93
|
-
text = text.replace(
|
|
94
|
-
/(\bbearer\s+)([A-Za-z0-9._~+/=-]{6,})/giu,
|
|
95
|
-
`$1${REDACTED}`,
|
|
96
|
-
);
|
|
97
|
-
text = text.replace(
|
|
98
|
-
/(\b(?:api[-_]?key|apikey|access[-_]?token|refresh[-_]?token|token|password|passwd|secret|credential|cookie)\b\s*(?:=|:)\s*)(?:"[^"]*"|'[^']*'|[^\s;&,]+)/giu,
|
|
99
|
-
`$1${REDACTED}`,
|
|
100
|
-
);
|
|
101
|
-
text = text.replace(
|
|
102
|
-
/((?:--?(?:api[-_]?key|access[-_]?token|token|password|secret|credential))\s+)(?:"[^"]*"|'[^']*'|\S+)/giu,
|
|
103
|
-
`$1${REDACTED}`,
|
|
104
|
-
);
|
|
105
|
-
text = text.replace(
|
|
106
|
-
/\b(?:sk-[A-Za-z0-9_-]{8,}|gh[pousr]_[A-Za-z0-9]{8,}|AKIA[A-Z0-9]{16})\b/gu,
|
|
107
|
-
REDACTED,
|
|
108
|
-
);
|
|
109
|
-
return text;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function summarizeScalar(value: unknown): string | undefined {
|
|
113
|
-
if (value === null || value === undefined || value === "") return undefined;
|
|
114
|
-
let text: string;
|
|
115
|
-
if (typeof value === "string") text = value;
|
|
116
|
-
else if (typeof value === "number" || typeof value === "boolean") text = String(value);
|
|
117
|
-
else return undefined; // arrays/objects are dropped from the summary
|
|
118
|
-
const oneLine = value.toString() === "[object Object]" ? "" : redactSensitiveText(text).replace(/\s+/g, " ").trim();
|
|
119
|
-
if (!oneLine) return undefined;
|
|
120
|
-
const chars = [...oneLine];
|
|
121
|
-
return chars.length > TOOL_ARG_VALUE_MAX ? `${chars.slice(0, TOOL_ARG_VALUE_MAX - 1).join("")}…` : oneLine;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Concise, display-safe one-line summary of a tool-args payload. Only the first
|
|
126
|
-
* few scalar fields are shown; sensitive-looking values are redacted; deep
|
|
127
|
-
* structures are collapsed. Never throws. Output is width-agnostic plain text
|
|
128
|
-
* (callers truncate to their display budget with truncateToWidth).
|
|
129
|
-
*/
|
|
130
|
-
export function summarizeToolArgs(args: unknown): string {
|
|
131
|
-
if (args === null || args === undefined) return "";
|
|
132
|
-
if (typeof args !== "object") {
|
|
133
|
-
const s = summarizeScalar(args);
|
|
134
|
-
return s ? truncateSummary(s) : "";
|
|
135
|
-
}
|
|
136
|
-
const record = args as Record<string, unknown>;
|
|
137
|
-
const parts: string[] = [];
|
|
138
|
-
for (const [key, value] of Object.entries(record)) {
|
|
139
|
-
if (parts.length >= 5) {
|
|
140
|
-
parts.push("…");
|
|
141
|
-
break;
|
|
142
|
-
}
|
|
143
|
-
if (isSensitiveKey(key)) {
|
|
144
|
-
parts.push(`${key}=${REDACTED}`);
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
const scalar = summarizeScalar(value);
|
|
148
|
-
if (scalar !== undefined) parts.push(`${key}=${scalar}`);
|
|
149
|
-
else if (value !== undefined && value !== null) parts.push(`${key}={…}`);
|
|
150
|
-
}
|
|
151
|
-
return truncateSummary(parts.filter(Boolean).join(" "));
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function truncateSummary(text: string): string {
|
|
155
|
-
const chars = [...text];
|
|
156
|
-
return chars.length > TOOL_ARG_SUMMARY_MAX ? `${chars.slice(0, TOOL_ARG_SUMMARY_MAX - 1).join("")}…` : text;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// ---------------------------------------------------------------------------
|
|
160
|
-
// Bounded transcript buffer (per-generation streaming output)
|
|
161
|
-
// ---------------------------------------------------------------------------
|
|
162
|
-
|
|
163
|
-
export interface TranscriptBudget {
|
|
164
|
-
/** Max Unicode code points kept per section (not UTF-16 units/display cells). */
|
|
165
|
-
maxTextChars: number;
|
|
166
|
-
maxThinkingChars: number;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export const DEFAULT_TRANSCRIPT_BUDGET: TranscriptBudget = {
|
|
170
|
-
maxTextChars: 8_000,
|
|
171
|
-
maxThinkingChars: 2_000,
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
export class TranscriptBuffer {
|
|
175
|
-
private text = "";
|
|
176
|
-
private thinking = "";
|
|
177
|
-
private textDropped = 0;
|
|
178
|
-
private thinkingDropped = 0;
|
|
179
|
-
|
|
180
|
-
constructor(private readonly budget: TranscriptBudget = DEFAULT_TRANSCRIPT_BUDGET) {}
|
|
181
|
-
|
|
182
|
-
appendText(delta: string): void {
|
|
183
|
-
if (!delta) return;
|
|
184
|
-
const next = cap(
|
|
185
|
-
(this.text + stripVTControlCharacters(delta)).replace(/\r\n?/g, "\n"),
|
|
186
|
-
this.budget.maxTextChars,
|
|
187
|
-
);
|
|
188
|
-
this.text = next.value;
|
|
189
|
-
this.textDropped += next.dropped;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
appendThinking(delta: string): void {
|
|
193
|
-
if (!delta) return;
|
|
194
|
-
const next = cap(
|
|
195
|
-
(this.thinking + stripVTControlCharacters(delta)).replace(/\r\n?/g, "\n"),
|
|
196
|
-
this.budget.maxThinkingChars,
|
|
197
|
-
);
|
|
198
|
-
this.thinking = next.value;
|
|
199
|
-
this.thinkingDropped += next.dropped;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/** Current-generation streams; structured so the renderer can flow-wrap. */
|
|
203
|
-
snapshot(): { text: string; textTruncated: boolean; thinking: string; thinkingTruncated: boolean } {
|
|
204
|
-
return {
|
|
205
|
-
text: this.text,
|
|
206
|
-
textTruncated: this.textDropped > 0,
|
|
207
|
-
thinking: this.thinking,
|
|
208
|
-
thinkingTruncated: this.thinkingDropped > 0,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
clear(): void {
|
|
213
|
-
this.text = "";
|
|
214
|
-
this.thinking = "";
|
|
215
|
-
this.textDropped = 0;
|
|
216
|
-
this.thinkingDropped = 0;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function cap(value: string, max: number): { value: string; dropped: number } {
|
|
221
|
-
const points = [...value];
|
|
222
|
-
const limit = Math.max(0, max);
|
|
223
|
-
if (points.length <= limit) return { value, dropped: 0 };
|
|
224
|
-
const dropped = points.length - limit;
|
|
225
|
-
return { value: points.slice(dropped).join(""), dropped };
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// ---------------------------------------------------------------------------
|
|
229
|
-
// Trajectory log
|
|
230
|
-
// ---------------------------------------------------------------------------
|
|
231
|
-
|
|
232
|
-
export interface TrajectorySummaryRecord {
|
|
233
|
-
model?: string;
|
|
234
|
-
thinking?: string;
|
|
235
|
-
modelFallbackFrom?: string;
|
|
236
|
-
toolCount: number;
|
|
237
|
-
currentTool?: string;
|
|
238
|
-
activity?: string;
|
|
239
|
-
lastAt?: number;
|
|
240
|
-
endedAt?: number;
|
|
241
|
-
isolation?: IsolationMode;
|
|
242
|
-
integrationStatus?: WorktreeFinalizationStatus | "pending";
|
|
243
|
-
originalCwd?: string;
|
|
244
|
-
isolationCwd?: string;
|
|
245
|
-
forkedFromRunId?: number;
|
|
246
|
-
forkChildRunIds?: number[];
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Append-only event log for one thread id. `clearSummary()` retires the mutable
|
|
251
|
-
* latest-state fields at the start of a new generation while the event history
|
|
252
|
-
* stays. `clearAll()` (parent-session teardown only) drops everything.
|
|
253
|
-
*/
|
|
254
|
-
export class TrajectoryLog {
|
|
255
|
-
private readonly events: TrajectoryEvent[] = [];
|
|
256
|
-
/** Mutable latest-state summary; NOT append-only — reset per generation. */
|
|
257
|
-
private summaryRecord: TrajectorySummaryRecord = { toolCount: 0 };
|
|
258
|
-
|
|
259
|
-
constructor(
|
|
260
|
-
readonly runId: number,
|
|
261
|
-
private readonly notify: () => void,
|
|
262
|
-
) {}
|
|
263
|
-
|
|
264
|
-
get generation(): number {
|
|
265
|
-
return this.summaryGeneration;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/** Internal counter also stored on each event; bumped by restart(). Starts
|
|
269
|
-
* at 1 to mirror the thread's generation numbering in the runtime. */
|
|
270
|
-
private summaryGeneration = 1;
|
|
271
|
-
|
|
272
|
-
append(event: NewTrajectoryEvent, now: number = Date.now()): void {
|
|
273
|
-
const full = { v: TRAJECTORY_VERSION, runId: this.runId, generation: this.summaryGeneration, at: now, ...event } as TrajectoryEvent;
|
|
274
|
-
this.events.push(full);
|
|
275
|
-
this.applySummary(full);
|
|
276
|
-
try {
|
|
277
|
-
this.notify();
|
|
278
|
-
} catch {
|
|
279
|
-
/* observers must never break trajectory capture */
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/** All events in append order (oldest first). Generation ascends monotonically. */
|
|
284
|
-
getEvents(): readonly TrajectoryEvent[] {
|
|
285
|
-
return this.events;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/** Events of the latest generation only (oldest first). */
|
|
289
|
-
getGenerationEvents(): readonly TrajectoryEvent[] {
|
|
290
|
-
let start = this.events.length;
|
|
291
|
-
for (let i = this.events.length - 1; i >= 0; i--) {
|
|
292
|
-
if (this.events[i].generation === this.summaryGeneration) start = i;
|
|
293
|
-
else break;
|
|
294
|
-
}
|
|
295
|
-
return this.events.slice(start);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
summary(): Readonly<TrajectorySummaryRecord> {
|
|
299
|
-
return this.summaryRecord;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/** Begin a new generation: same id, bumped generation, fresh mutable summary,
|
|
303
|
-
* untouched event history. Appends no event itself — dispatch appends the
|
|
304
|
-
* dispatch/resume event with the new-generation attributes. */
|
|
305
|
-
restart(): number {
|
|
306
|
-
this.summaryGeneration += 1;
|
|
307
|
-
this.summaryRecord = { toolCount: 0 };
|
|
308
|
-
return this.summaryGeneration;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
private applySummary(event: TrajectoryEvent): void {
|
|
312
|
-
const s = this.summaryRecord;
|
|
313
|
-
s.lastAt = event.at;
|
|
314
|
-
switch (event.kind) {
|
|
315
|
-
case "dispatch":
|
|
316
|
-
if (event.model !== undefined) s.model = event.model;
|
|
317
|
-
if (event.isolation !== undefined) s.isolation = event.isolation;
|
|
318
|
-
if (event.originalCwd !== undefined) s.originalCwd = event.originalCwd;
|
|
319
|
-
if (event.isolationCwd !== undefined) s.isolationCwd = event.isolationCwd;
|
|
320
|
-
if (event.isolation === "worktree") s.integrationStatus = "pending";
|
|
321
|
-
if (event.thinking !== undefined) s.thinking = event.thinking;
|
|
322
|
-
s.modelFallbackFrom = undefined;
|
|
323
|
-
s.endedAt = undefined;
|
|
324
|
-
s.currentTool = undefined;
|
|
325
|
-
s.activity = undefined;
|
|
326
|
-
break;
|
|
327
|
-
case "candidate":
|
|
328
|
-
if (event.model !== undefined) s.model = event.model;
|
|
329
|
-
if (event.fallbackFrom !== undefined) s.modelFallbackFrom = event.fallbackFrom;
|
|
330
|
-
break;
|
|
331
|
-
case "tool_start":
|
|
332
|
-
s.toolCount += 1;
|
|
333
|
-
s.currentTool = event.tool;
|
|
334
|
-
s.activity = event.summary ? `${event.tool} ${event.summary}` : event.tool;
|
|
335
|
-
break;
|
|
336
|
-
case "tool_end":
|
|
337
|
-
if (s.currentTool === event.tool) s.currentTool = undefined;
|
|
338
|
-
break;
|
|
339
|
-
case "fork":
|
|
340
|
-
if (event.runId === event.childRunId) s.forkedFromRunId = event.sourceRunId;
|
|
341
|
-
if (event.runId === event.sourceRunId) {
|
|
342
|
-
s.forkChildRunIds ??= [];
|
|
343
|
-
if (!s.forkChildRunIds.includes(event.childRunId)) s.forkChildRunIds.push(event.childRunId);
|
|
344
|
-
}
|
|
345
|
-
break;
|
|
346
|
-
case "worktree":
|
|
347
|
-
s.isolation = "worktree";
|
|
348
|
-
s.originalCwd = event.originalCwd;
|
|
349
|
-
if (event.isolationCwd !== undefined) s.isolationCwd = event.isolationCwd;
|
|
350
|
-
if (event.status !== "created") s.integrationStatus = event.status;
|
|
351
|
-
break;
|
|
352
|
-
case "settled":
|
|
353
|
-
s.endedAt = event.at;
|
|
354
|
-
s.currentTool = undefined;
|
|
355
|
-
if (event.isolation !== undefined) s.isolation = event.isolation;
|
|
356
|
-
if (event.integrationStatus !== undefined) s.integrationStatus = event.integrationStatus;
|
|
357
|
-
break;
|
|
358
|
-
case "status":
|
|
359
|
-
s.activity = undefined;
|
|
360
|
-
if (event.status === "parked" || event.status === "done" || event.status === "failed") s.endedAt ??= event.at;
|
|
361
|
-
break;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/** Parent-session teardown: wipe summary AND history. */
|
|
366
|
-
clearAll(): void {
|
|
367
|
-
this.events.length = 0;
|
|
368
|
-
this.summaryGeneration = 1;
|
|
369
|
-
this.summaryRecord = { toolCount: 0 };
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// ---------------------------------------------------------------------------
|
|
374
|
-
// Inspector run-state projection (trajectory + transcript + retained run info)
|
|
375
|
-
// ---------------------------------------------------------------------------
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Per-thread projection the inspector reads: live trajectory + bounded
|
|
379
|
-
* transcript + retained snapshot for completed/failed/parked threads so the
|
|
380
|
-
* detail pane survives monitor-row removal. Render code must treat this as
|
|
381
|
-
* read-only.
|
|
382
|
-
*/
|
|
383
|
-
export class InspectRunState {
|
|
384
|
-
readonly trajectory: TrajectoryLog;
|
|
385
|
-
readonly transcript = new TranscriptBuffer();
|
|
386
|
-
|
|
387
|
-
agent = "";
|
|
388
|
-
task = "";
|
|
389
|
-
label = "";
|
|
390
|
-
model?: string;
|
|
391
|
-
thinking?: string;
|
|
392
|
-
status = "queued";
|
|
393
|
-
startedAt?: number;
|
|
394
|
-
endedAt?: number;
|
|
395
|
-
|
|
396
|
-
/** Present for the monitor-row lifetime and beyond (via retained snapshot). */
|
|
397
|
-
runInfo?: {
|
|
398
|
-
usage?: UsageStats;
|
|
399
|
-
toolCount?: number;
|
|
400
|
-
activity?: string;
|
|
401
|
-
currentTool?: string;
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
constructor(
|
|
405
|
-
readonly runId: number,
|
|
406
|
-
notify: () => void,
|
|
407
|
-
) {
|
|
408
|
-
this.trajectory = new TrajectoryLog(runId, notify);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
get generation(): number {
|
|
412
|
-
return this.trajectory.generation;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/** Preserve run metadata before the monitor row goes away (beginTurn sweep,
|
|
416
|
-
* finishRun removal). Only fillsAnnounce fields; the inspector's detail pane
|
|
417
|
-
* stays truthful even for finished, swept threads. */
|
|
418
|
-
retainFrom(run: {
|
|
419
|
-
agent: string;
|
|
420
|
-
task: string;
|
|
421
|
-
label?: string;
|
|
422
|
-
model?: string;
|
|
423
|
-
thinking?: string;
|
|
424
|
-
status: string;
|
|
425
|
-
startedAt?: number;
|
|
426
|
-
endedAt?: number;
|
|
427
|
-
usage?: UsageStats;
|
|
428
|
-
toolCount?: number;
|
|
429
|
-
activity?: string;
|
|
430
|
-
currentTool?: string;
|
|
431
|
-
}): void {
|
|
432
|
-
this.agent = run.agent;
|
|
433
|
-
this.task = run.task;
|
|
434
|
-
this.label = run.label ?? "";
|
|
435
|
-
this.model = run.model ?? this.model;
|
|
436
|
-
this.thinking = run.thinking ?? this.thinking;
|
|
437
|
-
this.status = run.status;
|
|
438
|
-
this.startedAt = run.startedAt;
|
|
439
|
-
this.endedAt = run.endedAt;
|
|
440
|
-
this.runInfo = {
|
|
441
|
-
usage: run.usage,
|
|
442
|
-
toolCount: run.toolCount,
|
|
443
|
-
activity: run.activity,
|
|
444
|
-
currentTool: run.currentTool,
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* Registry of inspector projections for the parent session. Survives monitor
|
|
451
|
-
* row removal so completed/failed/parked threads stay inspectable.
|
|
452
|
-
*/
|
|
453
|
-
export class InspectorStore {
|
|
454
|
-
private readonly states = new Map<number, InspectRunState>();
|
|
455
|
-
private readonly listeners = new Set<() => void>();
|
|
456
|
-
|
|
457
|
-
/** Get (creating on demand) the projection for a thread id. */
|
|
458
|
-
get(runId: number): InspectRunState {
|
|
459
|
-
let state = this.states.get(runId);
|
|
460
|
-
if (!state) {
|
|
461
|
-
state = new InspectRunState(runId, () => this.emit());
|
|
462
|
-
this.states.set(runId, state);
|
|
463
|
-
}
|
|
464
|
-
return state;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
find(runId: number): InspectRunState | undefined {
|
|
468
|
-
return this.states.get(runId);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
all(): InspectRunState[] {
|
|
472
|
-
return [...this.states.values()].sort((a, b) => a.runId - b.runId);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
subscribe(listener: () => void): () => void {
|
|
476
|
-
this.listeners.add(listener);
|
|
477
|
-
return () => {
|
|
478
|
-
this.listeners.delete(listener);
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
private emit(): void {
|
|
483
|
-
for (const listener of this.listeners) {
|
|
484
|
-
try {
|
|
485
|
-
listener();
|
|
486
|
-
} catch {
|
|
487
|
-
/* subscriber errors must not break the store */
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/** Parent-session teardown only: drop every projection and its history. */
|
|
493
|
-
clearAll(): void {
|
|
494
|
-
for (const state of this.states.values()) {
|
|
495
|
-
state.trajectory.clearAll();
|
|
496
|
-
state.transcript.clear();
|
|
497
|
-
}
|
|
498
|
-
this.states.clear();
|
|
499
|
-
this.emit();
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
export const inspectorStore = new InspectorStore();
|