@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/src/inspector.ts DELETED
@@ -1,369 +0,0 @@
1
- /**
2
- * Pure view-model builder for the /subagents-inspect overlay.
3
- *
4
- * Joins three read-only sources into one snapshot:
5
- * - monitor (live widget rows: status/usage/activity),
6
- * - runtime.threads (thread state + control phase), and
7
- * - inspectorStore (append-only trajectory + bounded transcript + retained
8
- * snapshots for finished/parked threads whose monitor rows are gone).
9
- *
10
- * The overlay component renders this snapshot directly and never mutates
11
- * runtime state; building it must therefore be side-effect free.
12
- */
13
-
14
- import { stripVTControlCharacters } from "node:util";
15
- import { formatUsageCompact, monitor, type RunStatus } from "./monitor.ts";
16
- import { emptyUsage, type UsageStats } from "./rpc-run.ts";
17
- import type { SubagentRuntime, ThreadState } from "./runtime.ts";
18
- import { inspectorStore, type TrajectoryEvent } from "./trajectory.ts";
19
-
20
- export interface InspectorRunItem {
21
- id: number;
22
- agent: string;
23
- label: string;
24
- status: RunStatus;
25
- /** Thread state text ("running", "parked", "completed", ...) or the monitor
26
- * status label for threads without a control record (chain internals). */
27
- stateText: string;
28
- generation?: number;
29
- elapsedMs?: number;
30
- }
31
-
32
- export interface InspectorToolEntry {
33
- tool: string;
34
- summary?: string;
35
- isError?: boolean;
36
- running?: boolean;
37
- at: number;
38
- }
39
-
40
- export interface InspectorThreadView {
41
- id: number;
42
- agent: string;
43
- label: string;
44
- task: string;
45
- status: RunStatus;
46
- stateText: string;
47
- /** Control phase of the current attempt (queued/starting/running/...). */
48
- phase?: string;
49
- generation?: number;
50
- elapsedMs?: number;
51
- startedAt?: number;
52
- endedAt?: number;
53
- model?: string;
54
- /** Ordered candidate refs from the dispatch record (primary first). */
55
- modelChain: string[];
56
- modelFallbackFrom?: string;
57
- thinking?: string;
58
- isolation?: "shared" | "worktree";
59
- integrationStatus?: string;
60
- integrationApplied?: boolean;
61
- originalCwd?: string;
62
- isolationCwd?: string;
63
- integrationWorktreePath?: string;
64
- integrationPatchPath?: string;
65
- integrationError?: string;
66
- forkedFromRunId?: number;
67
- forkChildRunIds: number[];
68
- usage: UsageStats;
69
- toolCount: number;
70
- activity?: string;
71
- currentTool?: string;
72
- tools: InspectorToolEntry[];
73
- trajectory: readonly TrajectoryEvent[];
74
- trajectoryTotal: number;
75
- transcript: { text: string; textTruncated: boolean; thinking: string; thinkingTruncated: boolean };
76
- }
77
-
78
- export interface InspectorSnapshot {
79
- now: number;
80
- items: InspectorRunItem[];
81
- detail?: InspectorThreadView;
82
- }
83
-
84
- const TOOL_LIMIT = 6;
85
- const TRAJECTORY_LIMIT = 10;
86
-
87
- function monitorRunsSafe(): ReturnType<typeof monitor.getRuns> {
88
- try {
89
- return monitor.getRuns();
90
- } catch {
91
- return [];
92
- }
93
- }
94
-
95
- function threadStatusToRunStatus(state: ThreadState): RunStatus {
96
- switch (state) {
97
- case "queued":
98
- case "resuming":
99
- return "queued";
100
- case "running":
101
- return "running";
102
- case "steering":
103
- return "steering";
104
- case "interrupting":
105
- return "interrupting";
106
- case "parked":
107
- return "parked";
108
- case "completed":
109
- return "done";
110
- case "failed":
111
- case "stopped":
112
- return "failed";
113
- }
114
- }
115
-
116
- export function buildInspectorSnapshot(options: {
117
- runtime: Pick<SubagentRuntime, "threads">;
118
- selectedId?: number;
119
- now?: number;
120
- toolLimit?: number;
121
- trajectoryLimit?: number;
122
- }): InspectorSnapshot {
123
- const now = options.now ?? Date.now();
124
- const toolLimit = options.toolLimit ?? TOOL_LIMIT;
125
- const trajectoryLimit = options.trajectoryLimit ?? TRAJECTORY_LIMIT;
126
- const monitorRuns = monitorRunsSafe();
127
-
128
- // Union of the three sources keyed by id.
129
- const ids = new Set<number>();
130
- for (const id of options.runtime.threads.keys()) ids.add(id);
131
- for (const state of inspectorStore.all()) ids.add(state.runId);
132
- for (const run of monitorRuns) ids.add(run.id);
133
-
134
- const items: InspectorRunItem[] = [];
135
- let detail: InspectorThreadView | undefined;
136
-
137
- for (const id of [...ids].sort((a, b) => a - b)) {
138
- const monitorRun = monitorRuns.find((r) => r.id === id);
139
- const thread = options.runtime.threads.get(id);
140
- const inspect = inspectorStore.find(id);
141
-
142
- const latestSettled = [...(inspect?.trajectory.getGenerationEvents() ?? [])]
143
- .reverse()
144
- .find((event): event is Extract<TrajectoryEvent, { kind: "settled" }> => event.kind === "settled");
145
- const trajectoryStatus = latestSettled
146
- ? latestSettled.status === "done" ? "done" : "failed"
147
- : undefined;
148
- const agent = monitorRun?.agent ?? thread?.agentName ?? inspect?.agent ?? "?";
149
- const label = monitorRun?.label ?? inspect?.label ?? "";
150
- const task = monitorRun?.task ?? thread?.task ?? inspect?.task ?? "";
151
- const status: RunStatus = monitorRun?.status ?? (thread
152
- ? threadStatusToRunStatus(thread.state)
153
- : trajectoryStatus ?? statusFromText(inspect?.status));
154
- const stateText = thread?.state ?? monitorRun?.status ?? latestSettled?.status ?? inspect?.status ?? "queued";
155
- const startedAt = monitorRun?.startedAt ?? inspect?.startedAt;
156
- const endedAt = monitorRun?.endedAt ?? inspect?.endedAt ?? inspect?.trajectory.summary().endedAt;
157
- const elapsedMs = startedAt === undefined ? undefined : Math.max(0, (endedAt ?? now) - startedAt);
158
-
159
- items.push({ id, agent, label, status, stateText, generation: thread?.generation ?? inspect?.generation, elapsedMs });
160
-
161
- if (options.selectedId === id) {
162
- detail = buildDetail({
163
- id,
164
- agent,
165
- label,
166
- task,
167
- status,
168
- stateText,
169
- startedAt,
170
- endedAt,
171
- elapsedMs,
172
- monitorRun,
173
- thread,
174
- inspect,
175
- toolLimit,
176
- trajectoryLimit,
177
- });
178
- }
179
- }
180
-
181
- return { now, items, detail };
182
- }
183
-
184
- function statusFromText(text: string | undefined): RunStatus {
185
- switch (text) {
186
- case "done":
187
- case "completed":
188
- return "done";
189
- case "failed":
190
- case "stopped":
191
- return "failed";
192
- case "running":
193
- return "running";
194
- case "steering":
195
- return "steering";
196
- case "interrupting":
197
- return "interrupting";
198
- case "parked":
199
- return "parked";
200
- default:
201
- return "queued";
202
- }
203
- }
204
-
205
- function buildDetail(input: {
206
- id: number;
207
- agent: string;
208
- label: string;
209
- task: string;
210
- status: RunStatus;
211
- stateText: string;
212
- startedAt?: number;
213
- endedAt?: number;
214
- elapsedMs?: number;
215
- monitorRun?: ReturnType<typeof monitor.getRuns>[number];
216
- thread?: {
217
- state: ThreadState;
218
- generation: number;
219
- control: { getPhase(): string };
220
- isolation: "shared" | "worktree";
221
- cwd: string;
222
- executionCwd: string;
223
- worktree?: { worktreePath: string; patchPath: string; state: string };
224
- forkedFromRunId?: number;
225
- forkChildRunIds: number[];
226
- };
227
- inspect?: ReturnType<typeof inspectorStore.find>;
228
- toolLimit: number;
229
- trajectoryLimit: number;
230
- }): InspectorThreadView {
231
- const { monitorRun, thread, inspect } = input;
232
- const genEvents = inspect?.trajectory.getGenerationEvents() ?? [];
233
- const allEvents = inspect?.trajectory.getEvents() ?? [];
234
-
235
- // Model chain: primary first, then the recorded pool fallbacks. The actual
236
- // model currently running is the trajectory summary's latest, or the live
237
- // monitor model. modelFallbackFrom marks a pool advancement.
238
- const dispatchEvent = [...genEvents].reverse().find((e): e is Extract<TrajectoryEvent, { kind: "dispatch" }> => e.kind === "dispatch");
239
- const chain: string[] = [];
240
- if (dispatchEvent?.model) chain.push(dispatchEvent.model);
241
- for (const ref of dispatchEvent?.pool ?? []) if (ref && !chain.includes(ref)) chain.push(ref);
242
- const model = monitorRun?.model ?? inspect?.trajectory.summary().model ?? inspect?.model;
243
- const fallbackFrom = monitorRun?.modelFallbackFrom ?? inspect?.trajectory.summary().modelFallbackFrom;
244
- if (model && !chain.includes(model)) chain.push(model);
245
-
246
- // Recent tool entries from the current-generation trajectory.
247
- const tools: InspectorToolEntry[] = [];
248
- for (let i = genEvents.length - 1; i >= 0 && tools.length < input.toolLimit; i--) {
249
- const e = genEvents[i];
250
- if (e.kind === "tool_start") {
251
- // Correlate parallel calls by Pi's stable toolCallId. Name matching is
252
- // retained only for legacy events that predate id capture.
253
- const ended = genEvents.slice(i + 1).find((later) =>
254
- later.kind === "tool_end" &&
255
- (e.toolCallId
256
- ? later.toolCallId === e.toolCallId
257
- : later.toolCallId === undefined && later.tool === e.tool),
258
- );
259
- tools.unshift({
260
- tool: e.tool,
261
- summary: e.summary || undefined,
262
- isError: ended && ended.kind === "tool_end" ? ended.isError : undefined,
263
- running: ended === undefined,
264
- at: e.at,
265
- });
266
- }
267
- }
268
-
269
- const trajectory = genEvents.slice(-input.trajectoryLimit);
270
- const usage = monitorRun?.usage ?? inspect?.runInfo?.usage ?? emptyUsage();
271
- const toolCount = monitorRun?.toolCount ?? inspect?.runInfo?.toolCount ?? inspect?.trajectory.summary().toolCount ?? 0;
272
- const trajectorySummary = inspect?.trajectory.summary();
273
- const lastWorktreeEvent = [...allEvents]
274
- .reverse()
275
- .find((event): event is Extract<TrajectoryEvent, { kind: "worktree" }> => event.kind === "worktree");
276
- const isolation = thread?.isolation ?? monitorRun?.isolation ?? trajectorySummary?.isolation;
277
-
278
- return {
279
- id: input.id,
280
- agent: input.agent,
281
- label: input.label,
282
- task: input.task,
283
- status: input.status,
284
- stateText: input.stateText,
285
- phase: thread?.control.getPhase(),
286
- generation: thread?.generation ?? (genEvents.length > 0 ? inspect?.generation : undefined),
287
- elapsedMs: input.elapsedMs,
288
- startedAt: input.startedAt,
289
- endedAt: input.endedAt,
290
- model: model ?? undefined,
291
- modelChain: chain,
292
- modelFallbackFrom: fallbackFrom,
293
- thinking: monitorRun?.thinking ?? trajectorySummary?.thinking ?? inspect?.thinking,
294
- isolation,
295
- integrationStatus: monitorRun?.integrationStatus ?? trajectorySummary?.integrationStatus ?? thread?.worktree?.state,
296
- integrationApplied: lastWorktreeEvent?.integrated,
297
- originalCwd: thread?.cwd ?? trajectorySummary?.originalCwd,
298
- isolationCwd: thread?.executionCwd ?? trajectorySummary?.isolationCwd,
299
- integrationWorktreePath: lastWorktreeEvent?.worktreePath ?? (thread?.worktree?.state === "retained" ? thread.worktree.worktreePath : undefined),
300
- integrationPatchPath: lastWorktreeEvent?.patchPath ?? (thread?.worktree?.state === "retained" ? thread.worktree.patchPath : undefined),
301
- integrationError: lastWorktreeEvent?.error,
302
- forkedFromRunId: thread?.forkedFromRunId ?? trajectorySummary?.forkedFromRunId,
303
- forkChildRunIds: [...(thread?.forkChildRunIds ?? trajectorySummary?.forkChildRunIds ?? [])],
304
- usage,
305
- toolCount,
306
- activity: monitorRun?.activity ?? inspect?.runInfo?.activity ?? inspect?.trajectory.summary().activity,
307
- currentTool: monitorRun?.currentTool ?? inspect?.runInfo?.currentTool ?? inspect?.trajectory.summary().currentTool,
308
- tools,
309
- trajectory,
310
- trajectoryTotal: allEvents.length,
311
- transcript: inspect?.transcript.snapshot() ?? { text: "", textTruncated: false, thinking: "", thinkingTruncated: false },
312
- };
313
- }
314
-
315
- /** One-line event text for the trajectory pane (plain, pre-theme). */
316
- export function formatTrajectoryEvent(event: TrajectoryEvent): string {
317
- return stripVTControlCharacters(formatTrajectoryEventUnsafe(event));
318
- }
319
-
320
- function formatTrajectoryEventUnsafe(event: TrajectoryEvent): string {
321
- switch (event.kind) {
322
- case "dispatch":
323
- return `dispatch ${event.agent}${event.resumed ? " (resumed)" : ""}${event.model ? ` · ${event.model}` : ""}`;
324
- case "status":
325
- return `status: ${event.status}`;
326
- case "candidate":
327
- return event.fallbackFrom ? `model: ${event.model ?? "?"} (pool fallback from ${event.fallbackFrom})` : `model: ${event.model ?? "?"}`;
328
- case "retry":
329
- return `retry${event.reason ? `: ${event.reason}` : ""}`;
330
- case "steer":
331
- return `steer: ${compactOne(event.instruction, 60)}`;
332
- case "retarget":
333
- return `retarget: ${compactOne(event.objective, 60)}`;
334
- case "park":
335
- return "parked at checkpoint";
336
- case "resume":
337
- return event.objective ? `resume: ${compactOne(event.objective, 60)}` : "resume from retained context";
338
- case "fork":
339
- return event.runId === event.sourceRunId
340
- ? `forked child #${event.childRunId}${event.objective ? `: ${compactOne(event.objective, 48)}` : ""}`
341
- : `forked from #${event.sourceRunId}${event.objective ? `: ${compactOne(event.objective, 48)}` : ""}`;
342
- case "stop":
343
- return event.reason ? `stop: ${compactOne(event.reason, 60)}` : "stopped";
344
- case "worktree":
345
- return event.status === "created"
346
- ? `worktree created${event.isolationCwd ? ` · ${compactOne(event.isolationCwd, 48)}` : ""}`
347
- : event.status === "retained" && event.integrated
348
- ? `worktree cleanup failed after apply${event.error ? `: ${compactOne(event.error, 48)}` : ""}`
349
- : `worktree ${event.status}${event.error ? `: ${compactOne(event.error, 48)}` : ""}`;
350
- case "settled":
351
- return `settled: ${event.status}${event.model ? ` · ${event.model}` : ""}${event.integrationStatus ? ` · worktree ${event.integrationStatus}` : ""}`;
352
- case "tool_start":
353
- return `→ ${event.tool}${event.summary ? ` ${compactOne(event.summary, 48)}` : ""}`;
354
- case "tool_end":
355
- return `${e2(event)}${event.tool}`;
356
- case "usage":
357
- return `usage: ${formatUsageCompact(event.usage) || "—"}`;
358
- }
359
- }
360
-
361
- function e2(event: Extract<TrajectoryEvent, { kind: "tool_end" }>): string {
362
- return event.isError ? "✗ " : "✓ ";
363
- }
364
-
365
- function compactOne(text: string, max: number): string {
366
- const oneLine = stripVTControlCharacters(text).replace(/\s+/g, " ").trim();
367
- const chars = [...oneLine];
368
- return chars.length > max ? `${chars.slice(0, max - 1).join("")}…` : oneLine;
369
- }