pi-subagents 0.57.0 → 0.59.0
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/CHANGELOG.md +104 -0
- package/docs/agents.md +18 -9
- package/docs/configuration.md +4 -4
- package/docs/extension-api.md +40 -1
- package/docs/models.md +27 -4
- package/docs/observability.md +1 -1
- package/docs/tool-reference.md +92 -6
- package/docs/workflows.md +115 -0
- package/package.json +3 -1
- package/prompts/review-loop.md +2 -2
- package/skills/council-mode/SKILL.md +1 -1
- package/skills/pi-subagents/SKILL.md +3 -1
- package/skills/pi-subagents/references/execution-controls.md +6 -2
- package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/skills/pi-subagents/references/prompting-and-roles.md +26 -5
- package/src/agents/agent-management.ts +28 -17
- package/src/agents/agent-serializer.ts +9 -2
- package/src/agents/agents.ts +103 -27
- package/src/agents/runtime-agent-events.ts +70 -0
- package/src/agents/runtime-agent-registry.ts +19 -18
- package/src/api/agents.ts +10 -5
- package/src/api/background-work.ts +5 -1
- package/src/api/delegation.ts +0 -7
- package/src/api/preflight.ts +32 -10
- package/src/extension/fanout-child.ts +5 -3
- package/src/extension/index.ts +66 -27
- package/src/extension/public-execution.ts +15 -2
- package/src/extension/rpc.ts +35 -14
- package/src/extension/schemas.ts +37 -12
- package/src/extension/tool-description.ts +25 -6
- package/src/integrations/herdr-status.ts +5 -0
- package/src/intercom/result-intercom.ts +2 -0
- package/src/profiles/profiles.ts +5 -6
- package/src/runs/background/active-async-capacity.ts +2 -2
- package/src/runs/background/async-execution.ts +78 -46
- package/src/runs/background/async-job-tracker.ts +14 -12
- package/src/runs/background/async-resume.ts +29 -27
- package/src/runs/background/async-status-snapshot.ts +23 -261
- package/src/runs/background/async-status.ts +68 -7
- package/src/runs/background/chain-append.ts +8 -3
- package/src/runs/background/chain-root-attachment.ts +60 -8
- package/src/runs/background/fleet-view.ts +21 -11
- package/src/runs/background/notify.ts +184 -9
- package/src/runs/background/result-delivery-ownership.ts +45 -0
- package/src/runs/background/result-files.ts +2 -1
- package/src/runs/background/result-watcher.ts +29 -10
- package/src/runs/background/resume-guidance.ts +1 -1
- package/src/runs/background/retained-children.ts +1 -1
- package/src/runs/background/run-status.ts +18 -8
- package/src/runs/background/scheduled-runs.ts +86 -7
- package/src/runs/background/stale-run-reconciler.ts +10 -4
- package/src/runs/background/steering.ts +4 -14
- package/src/runs/background/subagent-runner.ts +389 -350
- package/src/runs/background/subagent-wait.ts +58 -10
- package/src/runs/background/terminal-run-index.ts +1 -1
- package/src/runs/background/wait-completions.ts +22 -1
- package/src/runs/background/wait-config.ts +23 -9
- package/src/runs/background/wait-tool.ts +9 -2
- package/src/runs/foreground/async-steering-action.ts +2 -2
- package/src/runs/foreground/execution.ts +134 -121
- package/src/runs/foreground/foreground-control.ts +3 -0
- package/src/runs/foreground/foreground-history.ts +1 -0
- package/src/runs/foreground/subagent-executor.ts +527 -257
- package/src/runs/foreground/workflow-detach-reconcile.ts +130 -120
- package/src/runs/shared/abort-recovery.ts +119 -0
- package/src/runs/shared/async-status-projection.ts +463 -0
- package/src/runs/shared/child-identity.ts +19 -4
- package/src/runs/shared/child-launch-plan.ts +151 -0
- package/src/runs/shared/completion-evidence.ts +89 -0
- package/src/runs/shared/completion-guard.ts +5 -4
- package/src/runs/shared/dynamic-fanout.ts +3 -3
- package/src/runs/shared/fast-mode-extension.ts +5 -5
- package/src/runs/shared/host-step-status.ts +230 -0
- package/src/runs/shared/lane-metadata.ts +105 -0
- package/src/runs/shared/launch-cwd.ts +16 -0
- package/src/runs/shared/long-running-guard.ts +2 -1
- package/src/runs/shared/mcp-config-sources.ts +422 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +221 -158
- package/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
- package/src/runs/shared/model-exclusions.ts +12 -0
- package/src/runs/shared/model-fallback.ts +25 -6
- package/src/runs/shared/nested-events.ts +6 -2
- package/src/runs/shared/nested-render.ts +7 -3
- package/src/runs/shared/parallel-handoff.ts +419 -7
- package/src/runs/shared/parallel-utils.ts +15 -1
- package/src/runs/shared/pi-args.ts +81 -8
- package/src/runs/shared/single-output.ts +44 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +98 -13
- package/src/runs/shared/worktree-cleanup-plan.ts +847 -0
- package/src/runs/shared/worktree.ts +18 -0
- package/src/shared/child-session-name.ts +46 -0
- package/src/shared/extension-context.ts +24 -0
- package/src/shared/formatters.ts +11 -2
- package/src/shared/launch-contract.ts +5 -1
- package/src/shared/settings.ts +9 -103
- package/src/shared/types.ts +240 -30
- package/src/shared/utils.ts +41 -84
- package/src/slash/delegation-adapters.ts +1 -8
- package/src/slash/delegation-request.ts +0 -4
- package/src/slash/slash-bridge.ts +1 -2
- package/src/slash/slash-commands.ts +369 -90
- package/src/slash/slash-live-state.ts +22 -11
- package/src/slash/subagents-admin.ts +3 -0
- package/src/tui/fleet-status.ts +125 -74
- package/src/tui/fleet.ts +11 -5
- package/src/tui/render.ts +330 -33
- package/src/watchdog/turn-delta.ts +1 -1
- package/src/workflows/chat-progress.ts +6 -3
- package/src/workflows/host-command.ts +230 -0
- package/src/workflows/scripted-workflow.ts +496 -35
- package/src/workflows/workflow-child-summary.ts +9 -5
- package/src/workflows/workflow-preflight.ts +270 -0
- package/src/workflows/workflow-receipt.ts +79 -5
- package/src/workflows/workflow-settlement.ts +246 -0
- package/src/runs/shared/turn-budget.ts +0 -98
|
@@ -15,7 +15,21 @@ import {
|
|
|
15
15
|
resolveCompletionBatchConfig,
|
|
16
16
|
} from "./completion-batcher.ts";
|
|
17
17
|
import { SUBAGENT_ASYNC_COMPLETE_EVENT, SUBAGENT_FOREGROUND_COMPLETE_EVENT, type ParallelHandoffReference, type ScheduleOrigin, type SubagentState } from "../../shared/types.ts";
|
|
18
|
+
import { safeTerminalText } from "../../shared/display-text.ts";
|
|
19
|
+
import { resolveSubagentResultStatus } from "../../intercom/result-intercom.ts";
|
|
18
20
|
import { isUnexplainedProcessSignal } from "../shared/process-signal.ts";
|
|
21
|
+
import type { ResultDeliveryOwnership } from "./result-delivery-ownership.ts";
|
|
22
|
+
|
|
23
|
+
export interface SubagentNotifyChildOutput {
|
|
24
|
+
workflowKey?: string;
|
|
25
|
+
runId?: string;
|
|
26
|
+
agent?: string;
|
|
27
|
+
status: string;
|
|
28
|
+
savedOutputPath?: string;
|
|
29
|
+
preview: string;
|
|
30
|
+
previewTruncated?: boolean;
|
|
31
|
+
previewUnavailableReason?: string;
|
|
32
|
+
}
|
|
19
33
|
|
|
20
34
|
export interface SubagentNotifyDetails {
|
|
21
35
|
agent: string;
|
|
@@ -26,6 +40,7 @@ export interface SubagentNotifyDetails {
|
|
|
26
40
|
durationMs?: number;
|
|
27
41
|
workflowRunId?: string;
|
|
28
42
|
childRuns?: Array<{ runId: string; workflowKey?: string; agent?: string; status?: string }>;
|
|
43
|
+
childOutputs?: SubagentNotifyChildOutput[];
|
|
29
44
|
reconciledFromDetachedChild?: string;
|
|
30
45
|
sessionLabel?: string;
|
|
31
46
|
sessionValue?: string;
|
|
@@ -56,7 +71,14 @@ export interface CompletionNotification {
|
|
|
56
71
|
workflowKey?: string;
|
|
57
72
|
agent?: string;
|
|
58
73
|
status?: string;
|
|
74
|
+
state?: string;
|
|
59
75
|
success?: boolean;
|
|
76
|
+
output?: string;
|
|
77
|
+
structuredOutput?: unknown;
|
|
78
|
+
outputState?: "present" | "absent" | "unknown";
|
|
79
|
+
outputReference?: string | { path?: string };
|
|
80
|
+
artifactPaths?: { outputPath?: string };
|
|
81
|
+
detached?: boolean;
|
|
60
82
|
exitCode?: number | null;
|
|
61
83
|
processSignal?: string | null;
|
|
62
84
|
interrupted?: boolean;
|
|
@@ -91,6 +113,7 @@ export interface RegisterSubagentNotifyOptions {
|
|
|
91
113
|
batchConfig?: CompletionBatchConfig;
|
|
92
114
|
timers?: NotifyTimerApi;
|
|
93
115
|
now?: () => number;
|
|
116
|
+
ownership?: Pick<ResultDeliveryOwnership, "owns">;
|
|
94
117
|
}
|
|
95
118
|
|
|
96
119
|
export interface CompletionNotifier {
|
|
@@ -98,6 +121,115 @@ export interface CompletionNotifier {
|
|
|
98
121
|
dispose(): void;
|
|
99
122
|
}
|
|
100
123
|
|
|
124
|
+
const CHILD_OUTPUT_PREVIEW_MAX_BYTES = 4 * 1024;
|
|
125
|
+
const CHILD_OUTPUT_PREVIEW_COUNT = 8;
|
|
126
|
+
const PREVIEW_TRUNCATION_MARKER = "...[preview truncated]";
|
|
127
|
+
type CompletionChild = NonNullable<CompletionNotification["results"]>[number];
|
|
128
|
+
|
|
129
|
+
function truncateUtf8Head(value: string, maxBytes: number): { text: string; truncated: boolean } {
|
|
130
|
+
const bytes = Buffer.from(value, "utf8");
|
|
131
|
+
if (bytes.length <= maxBytes) return { text: value, truncated: false };
|
|
132
|
+
const markerBytes = Buffer.byteLength(PREVIEW_TRUNCATION_MARKER, "utf8");
|
|
133
|
+
const contentBytes = Math.max(0, maxBytes - markerBytes);
|
|
134
|
+
let end = contentBytes;
|
|
135
|
+
while (end > 0 && (bytes[end]! & 0xc0) === 0x80) end -= 1;
|
|
136
|
+
return {
|
|
137
|
+
text: `${bytes.subarray(0, end).toString("utf8")}${PREVIEW_TRUNCATION_MARKER}`,
|
|
138
|
+
truncated: true,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function boundedSafeText(value: string, maxBytes = 1_024): string {
|
|
143
|
+
return truncateUtf8Head(safeTerminalText(value).replace(/\n/g, "\\n"), maxBytes).text;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function outputPathFromReference(value: CompletionChild["outputReference"]): string | undefined {
|
|
147
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
148
|
+
if (value && typeof value === "object" && typeof value.path === "string" && value.path.trim()) return value.path.trim();
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function childSavedOutputPath(child: CompletionChild): string | undefined {
|
|
153
|
+
return outputPathFromReference(child.outputReference);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function childStatus(child: CompletionChild, workflowState?: string): string {
|
|
157
|
+
const knownStatus = child.status === "complete"
|
|
158
|
+
? "completed"
|
|
159
|
+
: child.status === "completed" || child.status === "failed" || child.status === "paused" || child.status === "stopped" || child.status === "detached"
|
|
160
|
+
? child.status
|
|
161
|
+
: undefined;
|
|
162
|
+
if (knownStatus) return knownStatus;
|
|
163
|
+
return resolveSubagentResultStatus({
|
|
164
|
+
success: child.success,
|
|
165
|
+
state: child.state ?? (child.success === undefined ? workflowState : undefined),
|
|
166
|
+
interrupted: child.interrupted,
|
|
167
|
+
detached: child.detached,
|
|
168
|
+
processSignal: child.processSignal,
|
|
169
|
+
timedOut: child.timedOut,
|
|
170
|
+
stopped: child.stopped,
|
|
171
|
+
turnBudgetExceeded: child.turnBudgetExceeded,
|
|
172
|
+
exitCode: typeof child.exitCode === "number" ? child.exitCode : undefined,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function structuredOutputText(value: unknown): string | undefined {
|
|
177
|
+
if (value === undefined) return undefined;
|
|
178
|
+
try {
|
|
179
|
+
const serialized = JSON.stringify(value, null, 2);
|
|
180
|
+
return typeof serialized === "string" ? serialized : undefined;
|
|
181
|
+
} catch {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function childInlinePreview(child: CompletionChild): { preview?: string; truncated?: boolean; unavailableReason?: string } {
|
|
187
|
+
const output = typeof child.output === "string" ? child.output : "";
|
|
188
|
+
const outputReference = childSavedOutputPath(child);
|
|
189
|
+
const referenceOnly = Boolean(outputReference && output.trim().startsWith("Output saved to:"));
|
|
190
|
+
const raw = child.outputState === "absent" || referenceOnly ? structuredOutputText(child.structuredOutput) : output || structuredOutputText(child.structuredOutput);
|
|
191
|
+
if (!raw?.trim()) {
|
|
192
|
+
return { unavailableReason: referenceOnly ? "saved output is file-only" : "no safe inline output" };
|
|
193
|
+
}
|
|
194
|
+
const safe = safeTerminalText(raw);
|
|
195
|
+
if (!safe.trim()) return { unavailableReason: "no safe inline output" };
|
|
196
|
+
const bounded = truncateUtf8Head(safe, CHILD_OUTPUT_PREVIEW_MAX_BYTES);
|
|
197
|
+
return { preview: bounded.text, ...(bounded.truncated ? { truncated: true } : {}) };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function formatChildOutputBlock(children: SubagentNotifyChildOutput[] | undefined): string | undefined {
|
|
201
|
+
if (!children?.length) return undefined;
|
|
202
|
+
const lines = ["Child outputs:"];
|
|
203
|
+
for (let index = 0; index < children.length; index++) {
|
|
204
|
+
const child = children[index]!;
|
|
205
|
+
const key = child.workflowKey ? boundedSafeText(child.workflowKey) : "unavailable";
|
|
206
|
+
const runId = child.runId ? boundedSafeText(child.runId) : "unavailable";
|
|
207
|
+
const status = boundedSafeText(child.status) || "unavailable";
|
|
208
|
+
lines.push(`- key=${key} run=${runId} status=${status}`);
|
|
209
|
+
lines.push(` Saved output: ${child.savedOutputPath ? boundedSafeText(child.savedOutputPath) : "unavailable"}`);
|
|
210
|
+
if (index >= CHILD_OUTPUT_PREVIEW_COUNT) {
|
|
211
|
+
lines.push(" Preview: unavailable (notice preview budget exceeded)");
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (!child.preview) {
|
|
215
|
+
lines.push(` Preview: unavailable (${child.previewUnavailableReason ?? "no safe inline output"})`);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
lines.push(" Preview:");
|
|
219
|
+
for (const line of child.preview.split("\n")) lines.push(` | ${line}`);
|
|
220
|
+
}
|
|
221
|
+
if (children.length > CHILD_OUTPUT_PREVIEW_COUNT) {
|
|
222
|
+
lines.push(`- ${children.length - CHILD_OUTPUT_PREVIEW_COUNT} additional child preview(s) omitted by notice budget; child run metadata is retained below.`);
|
|
223
|
+
}
|
|
224
|
+
return lines.join("\n");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function formatResultPreview(details: SubagentNotifyDetails): string {
|
|
228
|
+
const summary = details.resultPreview.trim() ? details.resultPreview : "(no output)";
|
|
229
|
+
const childOutputs = formatChildOutputBlock(details.childOutputs);
|
|
230
|
+
return childOutputs ? `${summary}\n\n${childOutputs}` : summary;
|
|
231
|
+
}
|
|
232
|
+
|
|
101
233
|
function formatSessionLine(details: SubagentNotifyDetails): string | undefined {
|
|
102
234
|
if (!details.sessionValue) return undefined;
|
|
103
235
|
return details.sessionLabel ? `${details.sessionLabel}: ${details.sessionValue}` : details.sessionValue;
|
|
@@ -129,7 +261,7 @@ export function formatSingleCompletion(details: SubagentNotifyDetails): string {
|
|
|
129
261
|
"",
|
|
130
262
|
scheduleLine,
|
|
131
263
|
scheduleLine ? "" : undefined,
|
|
132
|
-
|
|
264
|
+
formatResultPreview(details),
|
|
133
265
|
details.handoffPath ? "" : undefined,
|
|
134
266
|
details.handoffPath ? `Parallel handoff: ${details.handoffPath}` : undefined,
|
|
135
267
|
correlationLines.length && !details.handoffPath ? "" : undefined,
|
|
@@ -216,7 +348,7 @@ export function formatGroupedCompletion(details: SubagentNotifyDetails[]): strin
|
|
|
216
348
|
if (!detail) continue;
|
|
217
349
|
const sessionLine = formatSessionLine(detail);
|
|
218
350
|
blocks.push(`${index + 1}. ${detail.agent}${detail.taskInfo ?? ""}${detail.scheduleOrigin ? ` — scheduled run from ${detail.scheduleOrigin.name ?? detail.scheduleOrigin.id} (schedule ${detail.scheduleOrigin.id})` : ""}`);
|
|
219
|
-
blocks.push(
|
|
351
|
+
blocks.push(formatResultPreview(detail));
|
|
220
352
|
if (detail.handoffPath) blocks.push(`Parallel handoff: ${detail.handoffPath}`);
|
|
221
353
|
blocks.push(...formatCorrelationLines(detail));
|
|
222
354
|
if (sessionLine) blocks.push(sessionLine);
|
|
@@ -228,6 +360,8 @@ export function formatGroupedCompletion(details: SubagentNotifyDetails[]): strin
|
|
|
228
360
|
interface PendingCompletion {
|
|
229
361
|
key: string;
|
|
230
362
|
details: SubagentNotifyDetails;
|
|
363
|
+
sessionId: string;
|
|
364
|
+
completionOwnerId: unknown;
|
|
231
365
|
triggerTurn: boolean;
|
|
232
366
|
resolve(accepted: boolean): void;
|
|
233
367
|
}
|
|
@@ -271,6 +405,7 @@ export function buildCompletionDetails(result: CompletionNotification): Subagent
|
|
|
271
405
|
const paused = !stopped && !result.success && (
|
|
272
406
|
result.exitCode === 0
|
|
273
407
|
|| result.state === "paused"
|
|
408
|
+
|| result.interrupted === true
|
|
274
409
|
|| summary.startsWith("Paused after interrupt.")
|
|
275
410
|
);
|
|
276
411
|
const status = stopped ? "stopped" : paused ? "paused" : result.success ? "completed" : "failed";
|
|
@@ -286,14 +421,34 @@ export function buildCompletionDetails(result: CompletionNotification): Subagent
|
|
|
286
421
|
const rawRunId = typeof result.runId === "string" ? result.runId : typeof result.id === "string" ? result.id : undefined;
|
|
287
422
|
const workflowRunId = (result.mode === "workflow" || agent === "workflow") && rawRunId ? rawRunId : undefined;
|
|
288
423
|
const childRuns = result.results?.flatMap((child) => {
|
|
289
|
-
|
|
424
|
+
const runId = typeof child.runId === "string" && child.runId.trim() ? child.runId.trim() : undefined;
|
|
425
|
+
const workflowKey = typeof child.workflowKey === "string" && child.workflowKey.trim() ? child.workflowKey.trim() : undefined;
|
|
426
|
+
if (!runId && (!workflowRunId || !workflowKey)) return [];
|
|
290
427
|
return [{
|
|
291
|
-
runId:
|
|
292
|
-
...(
|
|
428
|
+
runId: runId ?? "unavailable",
|
|
429
|
+
...(workflowKey ? { workflowKey } : {}),
|
|
293
430
|
...(typeof child.agent === "string" ? { agent: child.agent } : {}),
|
|
294
|
-
|
|
431
|
+
status: childStatus(child, workflowRunId ? result.state : undefined),
|
|
295
432
|
}];
|
|
296
433
|
}) ?? [];
|
|
434
|
+
const childOutputs = workflowRunId && result.results?.length
|
|
435
|
+
? result.results.map((child) => {
|
|
436
|
+
const inline = childInlinePreview(child);
|
|
437
|
+
const workflowKey = typeof child.workflowKey === "string" && child.workflowKey.trim() ? child.workflowKey.trim() : undefined;
|
|
438
|
+
const runId = typeof child.runId === "string" && child.runId.trim() ? child.runId.trim() : undefined;
|
|
439
|
+
const savedOutputPath = childSavedOutputPath(child);
|
|
440
|
+
return {
|
|
441
|
+
...(workflowKey ? { workflowKey } : {}),
|
|
442
|
+
...(runId ? { runId } : {}),
|
|
443
|
+
...(typeof child.agent === "string" ? { agent: child.agent } : {}),
|
|
444
|
+
status: childStatus(child, result.state),
|
|
445
|
+
...(savedOutputPath ? { savedOutputPath } : {}),
|
|
446
|
+
...(inline.preview ? { preview: inline.preview } : { preview: "" }),
|
|
447
|
+
...(inline.truncated ? { previewTruncated: true } : {}),
|
|
448
|
+
...(inline.unavailableReason ? { previewUnavailableReason: inline.unavailableReason } : {}),
|
|
449
|
+
};
|
|
450
|
+
})
|
|
451
|
+
: undefined;
|
|
297
452
|
const reconciledFromDetachedChild = typeof result.reconciledFromDetachedChild === "string" ? result.reconciledFromDetachedChild : undefined;
|
|
298
453
|
const session =
|
|
299
454
|
result.shareUrl
|
|
@@ -318,6 +473,7 @@ export function buildCompletionDetails(result: CompletionNotification): Subagent
|
|
|
318
473
|
...(handoffPath ? { handoffPath } : {}),
|
|
319
474
|
...(workflowRunId ? { workflowRunId } : {}),
|
|
320
475
|
...(childRuns.length ? { childRuns } : {}),
|
|
476
|
+
...(childOutputs?.length ? { childOutputs } : {}),
|
|
321
477
|
...(reconciledFromDetachedChild ? { reconciledFromDetachedChild } : {}),
|
|
322
478
|
...(session ? { sessionLabel: session.label, sessionValue: session.value } : {}),
|
|
323
479
|
};
|
|
@@ -335,6 +491,10 @@ export default function registerSubagentNotify(
|
|
|
335
491
|
const batchConfig = resolveCompletionBatchConfig(options.batchConfig);
|
|
336
492
|
const batchers = new Map<string, CompletionBatcher<PendingCompletion>>();
|
|
337
493
|
let disposed = false;
|
|
494
|
+
const ownsResult = options.ownership?.owns
|
|
495
|
+
?? ((sessionId: string, completionOwnerId: unknown) => sessionId === state.currentSessionId
|
|
496
|
+
&& typeof completionOwnerId === "string"
|
|
497
|
+
&& completionOwnerId === state.completionOwnerId);
|
|
338
498
|
|
|
339
499
|
const settle = (items: PendingCompletion[], accepted: boolean) => {
|
|
340
500
|
for (const item of items) {
|
|
@@ -343,7 +503,18 @@ export default function registerSubagentNotify(
|
|
|
343
503
|
item.resolve(accepted);
|
|
344
504
|
}
|
|
345
505
|
};
|
|
346
|
-
const emit = (items: PendingCompletion[]) =>
|
|
506
|
+
const emit = (items: PendingCompletion[]) => {
|
|
507
|
+
const accepted: PendingCompletion[] = [];
|
|
508
|
+
const rejected: PendingCompletion[] = [];
|
|
509
|
+
for (const item of items) {
|
|
510
|
+
const owned = item.details.source === "foreground"
|
|
511
|
+
? item.sessionId === state.currentSessionId
|
|
512
|
+
: ownsResult(item.sessionId, item.completionOwnerId);
|
|
513
|
+
(owned ? accepted : rejected).push(item);
|
|
514
|
+
}
|
|
515
|
+
settle(rejected, false);
|
|
516
|
+
settle(accepted, sendCompletion(pi, accepted));
|
|
517
|
+
};
|
|
347
518
|
const getBatcher = (result: CompletionNotification) => {
|
|
348
519
|
const key = completionBatchKey(result);
|
|
349
520
|
let batcher = batchers.get(key);
|
|
@@ -360,8 +531,10 @@ export default function registerSubagentNotify(
|
|
|
360
531
|
};
|
|
361
532
|
|
|
362
533
|
const deliver = (result: CompletionNotification): Promise<boolean> => {
|
|
363
|
-
if (disposed || typeof result.sessionId !== "string"
|
|
364
|
-
if (result.source
|
|
534
|
+
if (disposed || typeof result.sessionId !== "string") return Promise.resolve(false);
|
|
535
|
+
if (result.source === "foreground") {
|
|
536
|
+
if (result.sessionId !== state.currentSessionId) return Promise.resolve(false);
|
|
537
|
+
} else if (!ownsResult(result.sessionId, result.completionOwnerId)) return Promise.resolve(false);
|
|
365
538
|
if (result.intercomDelivered === true) return Promise.resolve(true);
|
|
366
539
|
const key = buildCompletionKey(result, "notify");
|
|
367
540
|
const seenAt = seen.get(key);
|
|
@@ -376,6 +549,8 @@ export default function registerSubagentNotify(
|
|
|
376
549
|
const item: PendingCompletion = {
|
|
377
550
|
key,
|
|
378
551
|
details,
|
|
552
|
+
sessionId: result.sessionId,
|
|
553
|
+
completionOwnerId: result.completionOwnerId,
|
|
379
554
|
triggerTurn: result.triggerTurn !== false,
|
|
380
555
|
resolve,
|
|
381
556
|
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { SubagentState } from "../../shared/types.ts";
|
|
2
|
+
|
|
3
|
+
const MAX_CLAIMED_PREDECESSOR_SESSIONS = 8;
|
|
4
|
+
|
|
5
|
+
type ResultDeliveryState = Pick<SubagentState, "currentSessionId" | "completionOwnerId">;
|
|
6
|
+
|
|
7
|
+
export interface ResultDeliveryOwnership {
|
|
8
|
+
owns(sessionId: string, completionOwnerId: unknown): boolean;
|
|
9
|
+
claimedSessionIds(): readonly string[];
|
|
10
|
+
claimPredecessor(previousSessionFile: string | undefined, previousRuntimeSessionId: string | null): boolean;
|
|
11
|
+
clear(): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createResultDeliveryOwnership(state: ResultDeliveryState): ResultDeliveryOwnership {
|
|
15
|
+
const claimed = new Map<string, string>();
|
|
16
|
+
|
|
17
|
+
const currentOwner = (): string | undefined => typeof state.completionOwnerId === "string" && state.completionOwnerId
|
|
18
|
+
? state.completionOwnerId
|
|
19
|
+
: undefined;
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
owns(sessionId, completionOwnerId) {
|
|
23
|
+
const owner = currentOwner();
|
|
24
|
+
if (!owner || completionOwnerId !== owner) return false;
|
|
25
|
+
return sessionId === state.currentSessionId || claimed.get(sessionId) === owner;
|
|
26
|
+
},
|
|
27
|
+
claimedSessionIds() {
|
|
28
|
+
const owner = currentOwner();
|
|
29
|
+
if (!owner) return [];
|
|
30
|
+
return [...claimed].flatMap(([sessionId, claimedOwner]) => claimedOwner === owner ? [sessionId] : []);
|
|
31
|
+
},
|
|
32
|
+
claimPredecessor(previousSessionFile, previousRuntimeSessionId) {
|
|
33
|
+
const owner = currentOwner();
|
|
34
|
+
if (!owner || !previousSessionFile || !previousRuntimeSessionId) return false;
|
|
35
|
+
if (previousSessionFile !== previousRuntimeSessionId || state.currentSessionId !== previousRuntimeSessionId) return false;
|
|
36
|
+
claimed.delete(previousRuntimeSessionId);
|
|
37
|
+
claimed.set(previousRuntimeSessionId, owner);
|
|
38
|
+
while (claimed.size > MAX_CLAIMED_PREDECESSOR_SESSIONS) claimed.delete(claimed.keys().next().value!);
|
|
39
|
+
return true;
|
|
40
|
+
},
|
|
41
|
+
clear() {
|
|
42
|
+
claimed.clear();
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -343,7 +343,8 @@ export function resultPayloadPathForMissionObserverRun(resultsDir: string, runId
|
|
|
343
343
|
if (!entry || entry.runId !== runId) return undefined;
|
|
344
344
|
return resultPayloadLocationFromIndex(resultsDir, entry)?.path;
|
|
345
345
|
} catch (error) {
|
|
346
|
-
|
|
346
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
347
|
+
if (code !== "ENOENT" && code !== "ENOTDIR" && !isUnaddressableResultCandidate(error)) console.error(`Ignoring invalid async result observer index for '${runId}':`, error);
|
|
347
348
|
return undefined;
|
|
348
349
|
}
|
|
349
350
|
}
|
|
@@ -25,6 +25,7 @@ import { recordWaitCompletion } from "./wait-completions.ts";
|
|
|
25
25
|
import { MISSION_BINDING_FILE, syncMissionFromAsyncCompletion } from "../../missions/lifecycle.ts";
|
|
26
26
|
import { missionObserverResultCandidateFiles, promotePendingResultFile, removeMissionObserverIndex, removeResultIndex, resultCandidateFilesForSession, resultPayloadPathForIndexedRun, resultPayloadPathForMissionObserverRun, resultPayloadPathForSessionRun, writeAsyncResultFile, writeResultIndexForData } from "./result-files.ts";
|
|
27
27
|
import type { CompletionNotifier, CompletionNotification } from "./notify.ts";
|
|
28
|
+
import type { ResultDeliveryOwnership } from "./result-delivery-ownership.ts";
|
|
28
29
|
|
|
29
30
|
const WATCHER_RESTART_DELAY_MS = 3000;
|
|
30
31
|
const POLL_INTERVAL_MS = 3000;
|
|
@@ -60,10 +61,13 @@ type ResultWatcherDeps = {
|
|
|
60
61
|
/** Control how slow result-index scans are logged. Defaults to \"activity\". */
|
|
61
62
|
resultScanLogging?: "all" | "activity" | "off";
|
|
62
63
|
platform?: NodeJS.Platform;
|
|
64
|
+
/** Shared current/predecessor session ownership used by the notifier. */
|
|
65
|
+
ownership?: Pick<ResultDeliveryOwnership, "owns" | "claimedSessionIds">;
|
|
63
66
|
};
|
|
64
67
|
|
|
65
68
|
type ResultFileChild = {
|
|
66
69
|
agent?: string;
|
|
70
|
+
sessionName?: string;
|
|
67
71
|
output?: string;
|
|
68
72
|
structuredOutput?: unknown;
|
|
69
73
|
outputState?: SubagentOutputState;
|
|
@@ -200,6 +204,7 @@ export function createResultWatcher(
|
|
|
200
204
|
deps: ResultWatcherDeps = {},
|
|
201
205
|
): {
|
|
202
206
|
startResultWatcher: () => void;
|
|
207
|
+
transitionResultDelivery: () => void;
|
|
203
208
|
primeExistingResults: (options?: { triggerTurn?: boolean }) => void;
|
|
204
209
|
refreshResultDelivery: () => void;
|
|
205
210
|
stopResultWatcher: () => void;
|
|
@@ -218,14 +223,16 @@ export function createResultWatcher(
|
|
|
218
223
|
// The sole in-memory ownership lease. It is acquired for one active session
|
|
219
224
|
// and revoked before the watcher, queues, or callbacks are torn down.
|
|
220
225
|
let activeSessionId: string | null = null;
|
|
226
|
+
const ownsResult = deps.ownership?.owns
|
|
227
|
+
?? ((sessionId: string, completionOwnerId: unknown) => sessionId === state.currentSessionId
|
|
228
|
+
&& typeof completionOwnerId === "string"
|
|
229
|
+
&& completionOwnerId === state.completionOwnerId);
|
|
230
|
+
const claimedSessionIds = () => deps.ownership?.claimedSessionIds() ?? [];
|
|
221
231
|
|
|
222
232
|
const ownsCompletion = (sessionId: string, completionOwnerId: unknown, epoch: number) => {
|
|
223
233
|
if (!deliveryActive || epoch !== deliveryEpoch) return false;
|
|
224
234
|
if (!activeSessionId && state.currentSessionId) activeSessionId = state.currentSessionId;
|
|
225
|
-
return activeSessionId === sessionId
|
|
226
|
-
&& state.currentSessionId === sessionId
|
|
227
|
-
&& typeof completionOwnerId === "string"
|
|
228
|
-
&& completionOwnerId === state.completionOwnerId;
|
|
235
|
+
return activeSessionId === state.currentSessionId && ownsResult(sessionId, completionOwnerId);
|
|
229
236
|
};
|
|
230
237
|
|
|
231
238
|
const scheduleResult = (file: string, triggerTurn: boolean, delayMs = 0) => {
|
|
@@ -246,8 +253,11 @@ export function createResultWatcher(
|
|
|
246
253
|
const resultPayloadPath = (file: string, observed?: ReadonlySet<string>): string | undefined => {
|
|
247
254
|
if (file !== path.basename(file) || !file.endsWith(".json")) return undefined;
|
|
248
255
|
const runId = file.replace(/\.json$/i, "");
|
|
249
|
-
const
|
|
250
|
-
|
|
256
|
+
for (const sessionId of [state.currentSessionId, ...claimedSessionIds()]) {
|
|
257
|
+
if (!sessionId) continue;
|
|
258
|
+
const sessionResult = resultPayloadPathForSessionRun(resultsDir, sessionId, runId);
|
|
259
|
+
if (sessionResult) return sessionResult;
|
|
260
|
+
}
|
|
251
261
|
const observerResult = resultPayloadPathForMissionObserverRun(resultsDir, runId);
|
|
252
262
|
if (observerResult) return observerResult;
|
|
253
263
|
if (observed?.has(runId)) {
|
|
@@ -308,7 +318,7 @@ export function createResultWatcher(
|
|
|
308
318
|
// Missing identity stays on the normal parser path so malformed or legacy
|
|
309
319
|
// files keep their existing diagnostics and compatibility behavior.
|
|
310
320
|
if (!identity.sessionId) return true;
|
|
311
|
-
if (identity.sessionId
|
|
321
|
+
if (ownsResult(identity.sessionId, identity.completionOwnerId)) return true;
|
|
312
322
|
if (identity.asyncDir && fsApi.existsSync(path.join(identity.asyncDir, MISSION_BINDING_FILE))) return true;
|
|
313
323
|
if (identity.runId && (observed ?? observedRunIds()).has(identity.runId)) return true;
|
|
314
324
|
return Boolean(deps.observeCompletion && !deps.observedCompletionRunIds);
|
|
@@ -473,6 +483,7 @@ export function createResultWatcher(
|
|
|
473
483
|
: undefined;
|
|
474
484
|
return {
|
|
475
485
|
agent: result.agent ?? data.agent ?? `step-${index + 1}`,
|
|
486
|
+
...(result.sessionName ? { sessionName: result.sessionName } : {}),
|
|
476
487
|
status: resolveSubagentResultStatus({
|
|
477
488
|
success: result.success,
|
|
478
489
|
state: childState,
|
|
@@ -621,8 +632,9 @@ export function createResultWatcher(
|
|
|
621
632
|
};
|
|
622
633
|
const indexedResultCandidates = (observed: ReadonlySet<string>): string[] => {
|
|
623
634
|
const files = new Set<string>();
|
|
624
|
-
|
|
625
|
-
|
|
635
|
+
for (const sessionId of [state.currentSessionId, ...claimedSessionIds()]) {
|
|
636
|
+
if (!sessionId) continue;
|
|
637
|
+
for (const file of resultCandidateFilesForSession(resultsDir, sessionId)) files.add(file);
|
|
626
638
|
}
|
|
627
639
|
for (const runId of state.asyncJobs.keys()) files.add(`${runId}.json`);
|
|
628
640
|
for (const file of missionObserverResultCandidateFiles(resultsDir)) files.add(file);
|
|
@@ -755,6 +767,13 @@ export function createResultWatcher(
|
|
|
755
767
|
}
|
|
756
768
|
};
|
|
757
769
|
|
|
770
|
+
const transitionResultDelivery = () => {
|
|
771
|
+
deliveryActive = true;
|
|
772
|
+
activeSessionId = state.currentSessionId;
|
|
773
|
+
deliveryEpoch += 1;
|
|
774
|
+
identityCache.clear();
|
|
775
|
+
};
|
|
776
|
+
|
|
758
777
|
const stopResultWatcher = () => {
|
|
759
778
|
deliveryActive = false;
|
|
760
779
|
activeSessionId = null;
|
|
@@ -769,5 +788,5 @@ export function createResultWatcher(
|
|
|
769
788
|
identityCache.clear();
|
|
770
789
|
};
|
|
771
790
|
|
|
772
|
-
return { startResultWatcher, primeExistingResults, stopResultWatcher, refreshResultDelivery: () => { primeExistingResults(); startDemandPolling(); } };
|
|
791
|
+
return { startResultWatcher, transitionResultDelivery, primeExistingResults, stopResultWatcher, refreshResultDelivery: () => { primeExistingResults(); startDemandPolling(); } };
|
|
773
792
|
}
|
|
@@ -39,7 +39,7 @@ export function formatResumeFirstFailedRunDetail(run: AsyncRunSummary): string |
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export function formatResumeFirstFailedRunsNote(runs: AsyncRunSummary[]): string {
|
|
42
|
-
const failedRuns = runs.filter((run) => run.state === "failed");
|
|
42
|
+
const failedRuns = runs.filter((run) => run.state === "failed" || run.state === "partial");
|
|
43
43
|
const detachGuidance = failedRuns
|
|
44
44
|
.map(formatIntercomDetachGuidance)
|
|
45
45
|
.filter((guidance): guidance is string => Boolean(guidance));
|
|
@@ -27,7 +27,7 @@ export interface RetainedChild {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
function isRetainedChildState(state: AsyncRunSummary["state"]): state is RetainedChildState {
|
|
30
|
-
return state === "complete" || state === "failed" || state === "paused" || state === "stopped";
|
|
30
|
+
return state === "complete" || state === "failed" || state === "partial" || state === "paused" || state === "stopped";
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
function isTerminalStepStatus(status: AsyncRunSummary["steps"][number]["status"]): boolean {
|
|
@@ -23,6 +23,7 @@ import { attachRootChildrenToSteps, findNestedRouteForRootId, projectNestedRegis
|
|
|
23
23
|
import { readMissionBinding } from "../../missions/lifecycle.ts";
|
|
24
24
|
import { formatWorkflowJsonPreview } from "../../workflows/scripted-workflow.ts";
|
|
25
25
|
import { parseWorkflowChildSummary } from "../../workflows/workflow-child-summary.ts";
|
|
26
|
+
import { formatWorkflowPreflightPlanSummary, formatWorkflowPreflightWarningSummary } from "../../workflows/workflow-preflight.ts";
|
|
26
27
|
import { formatRunFanoutBudget, getRunFanoutBudgetSnapshot, readRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
|
|
27
28
|
import { getExternalJobProvider } from "../../api/external-job-provider.ts";
|
|
28
29
|
|
|
@@ -64,13 +65,14 @@ function formatCapacityOwner(inspect: ActiveAsyncCapacityInspection): string[] {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
function formatWorkflowDebug(status: AsyncStatus): string[] {
|
|
67
|
-
if (status.mode !== "workflow" && !status.parentWorkflowRunId && !status.workflowKey) return [];
|
|
68
|
+
if (status.mode !== "workflow" && !status.parentWorkflowRunId && !status.workflowKey && !status.lane) return [];
|
|
68
69
|
const lines = [
|
|
69
70
|
status.parentWorkflowRunId ? `Workflow parent: ${status.parentWorkflowRunId}${status.workflowKey ? ` (${status.workflowKey})` : ""}` : undefined,
|
|
70
71
|
status.mode === "workflow" ? `Workflow children: ${(status.steps ?? []).length}` : undefined,
|
|
72
|
+
status.lane ? `Lane: ${status.lane.key}${status.lane.mode ? ` (${status.lane.mode})` : ""}` : undefined,
|
|
71
73
|
].filter((line): line is string => line !== undefined);
|
|
72
74
|
for (const [index, step] of (status.steps ?? []).entries()) {
|
|
73
|
-
lines.push(` ${index + 1}. key ${step.workflowKey ?? "n/a"} · ${step
|
|
75
|
+
lines.push(` ${index + 1}. key ${step.workflowKey ?? "n/a"} · ${runStatusStepDisplayName(step)} · ${step.status} · async ${step.async === undefined ? "unknown" : step.async ? "yes" : "no"}${step.runId ? ` · run ${step.runId}` : ""}${step.lane ? ` · lane ${step.lane.key}` : ""}${step.worktreePath ? ` · worktree ${step.worktreePath} · branch ${step.branch ?? "unknown"}` : ""}`);
|
|
74
76
|
}
|
|
75
77
|
return lines;
|
|
76
78
|
}
|
|
@@ -88,6 +90,7 @@ function formatRunLifecycleDebug(input: { status: AsyncStatus; asyncDir: string;
|
|
|
88
90
|
`Mode: ${status.mode}`,
|
|
89
91
|
status.parentWorkflowRunId ? `Workflow parent: ${status.parentWorkflowRunId}` : undefined,
|
|
90
92
|
status.workflowKey ? `Workflow key: ${status.workflowKey}` : undefined,
|
|
93
|
+
status.lane ? `Lane: ${status.lane.key}${status.lane.mode ? ` (${status.lane.mode})` : ""}` : undefined,
|
|
91
94
|
`Status process terminal: ${formatProcessTerminal(overlayProcessTerminal)}`,
|
|
92
95
|
`Sidecar process terminal: ${formatProcessTerminal(sidecarProcessTerminal)}`,
|
|
93
96
|
...formatCapacityOwner(capacity),
|
|
@@ -152,7 +155,12 @@ function stepLineLabel(status: AsyncStatus, index: number): string {
|
|
|
152
155
|
return `Step ${index + 1}`;
|
|
153
156
|
}
|
|
154
157
|
|
|
158
|
+
function runStatusStepDisplayName(step: { agent: string; sessionName?: string; label?: string }): string {
|
|
159
|
+
return step.sessionName?.trim() || (step.label ? `${step.label} (${step.agent})` : step.agent);
|
|
160
|
+
}
|
|
161
|
+
|
|
155
162
|
function nestedRunDisplayName(run: NestedRunSummary): string {
|
|
163
|
+
if (run.sessionName?.trim()) return run.sessionName.trim();
|
|
156
164
|
if (run.agent) return run.agent;
|
|
157
165
|
if (run.agents?.length) return run.agents.join(", ");
|
|
158
166
|
return run.id;
|
|
@@ -197,7 +205,7 @@ function formatRememberedForegroundStatus(run: ForegroundResumeRun): string {
|
|
|
197
205
|
for (const child of run.children) {
|
|
198
206
|
const output = rememberedForegroundChildOutput(child).trim().split(/\r?\n/).find((line) => line.trim());
|
|
199
207
|
const parts = [
|
|
200
|
-
`${child.index + 1}. ${child.agent} ${child.status}`,
|
|
208
|
+
`${child.index + 1}. ${child.sessionName?.trim() || child.agent} ${child.status}`,
|
|
201
209
|
child.exitCode !== undefined ? `exit ${child.exitCode}` : undefined,
|
|
202
210
|
child.detachedReason ? `detached: ${child.detachedReason}` : undefined,
|
|
203
211
|
child.acceptance ? `acceptance: ${child.acceptance.status}` : undefined,
|
|
@@ -241,7 +249,7 @@ function formatRememberedForegroundTranscript(run: ForegroundResumeRun, options:
|
|
|
241
249
|
const lines = [
|
|
242
250
|
`Run: ${run.runId}`,
|
|
243
251
|
`State: ${child.status}`,
|
|
244
|
-
`Child: ${index} (${child.agent})`,
|
|
252
|
+
`Child: ${index} (${child.sessionName?.trim() || child.agent})`,
|
|
245
253
|
child.sessionFile ? `Session: ${child.sessionFile}` : undefined,
|
|
246
254
|
child.transcriptPath ? `Transcript: ${child.transcriptPath}` : undefined,
|
|
247
255
|
child.artifactPaths?.outputPath ? `Output: ${child.artifactPaths.outputPath}` : undefined,
|
|
@@ -277,7 +285,7 @@ function formatNestedExactStatus(rootRunId: string, run: NestedRunSummary): stri
|
|
|
277
285
|
for (const [index, step] of run.steps.entries()) {
|
|
278
286
|
const activity = step.status === "running" ? formatActivityLabel(step.lastActivityAt, step.activityState) : undefined;
|
|
279
287
|
const budget = step.turnBudget ? `, turn budget: ${step.turnBudget.turnCount}/${step.turnBudget.maxTurns}+${step.turnBudget.graceTurns} (${step.turnBudget.outcome})` : "";
|
|
280
|
-
lines.push(` ${index + 1}. ${step.agent} ${step.status}${activity ? `, ${activity}` : ""}${budget}${step.error ? `, error: ${step.error}` : ""}`);
|
|
288
|
+
lines.push(` ${index + 1}. ${step.sessionName?.trim() || step.agent} ${step.status}${activity ? `, ${activity}` : ""}${budget}${step.error ? `, error: ${step.error}` : ""}`);
|
|
281
289
|
lines.push(...formatNestedRunStatusLines(step.children, { indent: " ", commandHints: true }));
|
|
282
290
|
}
|
|
283
291
|
}
|
|
@@ -504,6 +512,8 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
504
512
|
statusActivityText ? `Activity: ${statusActivityText}` : undefined,
|
|
505
513
|
steeringText ? `Steering: ${steeringText}` : undefined,
|
|
506
514
|
`Mode: ${status.mode}`,
|
|
515
|
+
...(status.preflight ? [formatWorkflowPreflightPlanSummary(status.preflight)] : []),
|
|
516
|
+
...(status.workflow?.preflightWarnings?.length ? [formatWorkflowPreflightWarningSummary(status.workflow.preflightWarnings)] : []),
|
|
507
517
|
runFanoutBudget ? formatRunFanoutBudget(runFanoutBudget) : undefined,
|
|
508
518
|
status.parentWorkflowRunId ? `Workflow parent: ${status.parentWorkflowRunId}${status.workflowKey ? ` (${status.workflowKey})` : ""}` : undefined,
|
|
509
519
|
status.mode === "workflow" && workflowReturnPreview !== undefined ? `Return: ${workflowReturnPreview}` : undefined,
|
|
@@ -535,7 +545,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
535
545
|
const errorText = step.error ? `, error: ${step.error}` : "";
|
|
536
546
|
const acceptanceText = step.acceptance?.status ? `, acceptance: ${step.acceptance.status}` : "";
|
|
537
547
|
const budgetText = step.turnBudget ? `, turn budget: ${step.turnBudget.turnCount}/${step.turnBudget.maxTurns}+${step.turnBudget.graceTurns} (${step.turnBudget.outcome})` : "";
|
|
538
|
-
const display =
|
|
548
|
+
const display = runStatusStepDisplayName(step);
|
|
539
549
|
const phase = step.phase ? `[${step.phase}] ` : "";
|
|
540
550
|
lines.push(`${stepLineLabel(status, index)}: ${phase}${display} ${step.status}${modelText}${stepActivityText ? `, ${stepActivityText}` : ""}${steeringSuffix}${acceptanceText}${budgetText}${errorText}`);
|
|
541
551
|
if (step.runner?.type === "external-cli") {
|
|
@@ -608,7 +618,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
608
618
|
|
|
609
619
|
const workflowChildren = parseWorkflowChildSummary(status.workflowChildren);
|
|
610
620
|
if (workflowChildren && workflowChildren.workflowRunId !== status.runId) throw new Error("workflowChildren.workflowRunId does not match async status runId.");
|
|
611
|
-
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [], ...(workflowChildren ? { workflowChildren } : {}), ...(runFanoutBudget ? { runFanoutBudget } : {}), ...(processTerminal ? { lifecycleStatus: { processTerminal } } : {}) } };
|
|
621
|
+
return { content: [{ type: "text", text: lines.join("\n") }], details: { mode: "single", results: [], ...(status.preflight ? { preflight: status.preflight } : {}), ...(status.workflow?.preflightWarnings?.length ? { preflightWarnings: status.workflow.preflightWarnings } : {}), ...(workflowChildren ? { workflowChildren } : {}), ...(runFanoutBudget ? { runFanoutBudget } : {}), ...(processTerminal ? { lifecycleStatus: { processTerminal } } : {}) } };
|
|
612
622
|
}
|
|
613
623
|
}
|
|
614
624
|
|
|
@@ -622,7 +632,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
622
632
|
}
|
|
623
633
|
try {
|
|
624
634
|
const raw = fs.readFileSync(resultPath, "utf-8");
|
|
625
|
-
const data = JSON.parse(raw) as { id?: string; runId?: string; toolCallId?: string; agent?: string; success?: boolean; summary?: string; output?: string; exitCode?: number; state?: string; stopped?: boolean; timedOut?: boolean; turnBudgetExceeded?: boolean; processSignal?: string | null; sessionFile?: string; parallelHandoff?: { path?: string }; results?: Array<{ agent?: string; runId?: string; workflowKey?: string; output?: string; summary?: string; sessionFile?: string; state?: string; success?: boolean; exitCode?: number | null; stopped?: boolean; timedOut?: boolean; turnBudgetExceeded?: boolean; interrupted?: boolean; processSignal?: string | null }> };
|
|
635
|
+
const data = JSON.parse(raw) as { id?: string; runId?: string; toolCallId?: string; agent?: string; success?: boolean; summary?: string; output?: string; exitCode?: number; state?: string; stopped?: boolean; timedOut?: boolean; turnBudgetExceeded?: boolean; processSignal?: string | null; sessionFile?: string; parallelHandoff?: { path?: string }; results?: Array<{ agent?: string; sessionName?: string; runId?: string; workflowKey?: string; output?: string; summary?: string; sessionFile?: string; state?: string; success?: boolean; exitCode?: number | null; stopped?: boolean; timedOut?: boolean; turnBudgetExceeded?: boolean; interrupted?: boolean; processSignal?: string | null }> };
|
|
626
636
|
if (params.view === "transcript") {
|
|
627
637
|
try {
|
|
628
638
|
return { content: [{ type: "text", text: formatAsyncResultTranscript(data, resultPath, { index: params.index, lines: params.lines }) }], details: { mode: "single", results: [] } };
|