pi-subagents 0.38.0 → 0.40.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 +38 -0
- package/README.md +85 -13
- package/agents/worker.md +1 -0
- package/package.json +1 -1
- package/prompts/review-loop.md +1 -1
- package/skills/pi-subagents/SKILL.md +4 -3
- package/skills/pi-subagents/references/execution-controls.md +22 -3
- package/skills/pi-subagents/references/management-authoring-rpc.md +5 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +17 -3
- package/src/agents/agent-management.ts +71 -30
- package/src/agents/agent-serializer.ts +4 -0
- package/src/agents/agents.ts +60 -1
- package/src/api/preflight.ts +16 -12
- package/src/extension/chain-validation.ts +17 -1
- package/src/extension/rpc.ts +2 -0
- package/src/extension/schemas.ts +16 -3
- package/src/extension/tool-description.ts +6 -5
- package/src/intercom/result-intercom.ts +33 -4
- package/src/runs/background/async-execution.ts +63 -16
- package/src/runs/background/async-job-tracker.ts +5 -1
- package/src/runs/background/async-resume.ts +1 -0
- package/src/runs/background/async-status.ts +18 -2
- package/src/runs/background/chain-append.ts +48 -5
- package/src/runs/background/control-channel.ts +60 -0
- package/src/runs/background/notify.ts +26 -4
- package/src/runs/background/result-watcher.ts +20 -3
- package/src/runs/background/run-status.ts +29 -3
- package/src/runs/background/subagent-runner.ts +302 -18
- package/src/runs/foreground/async-stop-action.ts +65 -0
- package/src/runs/foreground/chain-execution.ts +143 -43
- package/src/runs/foreground/execution.ts +384 -202
- package/src/runs/foreground/foreground-control.ts +30 -0
- package/src/runs/foreground/subagent-executor.ts +314 -77
- package/src/runs/shared/capability-ceiling.ts +51 -19
- package/src/runs/shared/chain-outputs.ts +3 -1
- package/src/runs/shared/dynamic-fanout.ts +1 -1
- package/src/runs/shared/nested-events.ts +97 -20
- package/src/runs/shared/parallel-utils.ts +43 -12
- package/src/runs/shared/pi-args.ts +45 -3
- package/src/runs/shared/process-signal.ts +19 -0
- package/src/runs/shared/run-history.ts +45 -9
- package/src/runs/shared/runtime-acknowledged-extensions.ts +71 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +30 -0
- package/src/runs/shared/usage-budget.ts +65 -0
- package/src/runs/shared/workflow-graph.ts +26 -1
- package/src/shared/settings.ts +17 -1
- package/src/shared/types.ts +124 -8
- package/src/shared/utils.ts +12 -1
- package/src/slash/slash-commands.ts +43 -1
- package/src/slash/slash-live-state.ts +5 -3
- package/src/tui/fleet-status.ts +12 -2
- package/src/tui/fleet.ts +181 -8
- package/src/tui/render.ts +4 -2
- package/src/watchdog/register-main.ts +14 -7
- package/src/watchdog/review.ts +4 -3
- package/src/watchdog/runtime.ts +170 -17
- package/src/watchdog/scope.ts +62 -0
- package/src/watchdog/settings.ts +41 -1
- package/src/watchdog/types.ts +10 -0
|
@@ -53,6 +53,13 @@ export interface StopRequest {
|
|
|
53
53
|
reason?: string;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
export interface CheckpointDecisionRequest {
|
|
57
|
+
type: "approve-checkpoint" | "reject-checkpoint";
|
|
58
|
+
ts?: number;
|
|
59
|
+
source?: string;
|
|
60
|
+
reason?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
56
63
|
export interface SteerRequest {
|
|
57
64
|
type: "steer";
|
|
58
65
|
id: string;
|
|
@@ -110,6 +117,14 @@ export function stopRequestPath(asyncDir: string): string {
|
|
|
110
117
|
return path.join(controlInboxDir(asyncDir), "stop.json");
|
|
111
118
|
}
|
|
112
119
|
|
|
120
|
+
export function approveCheckpointRequestPath(asyncDir: string): string {
|
|
121
|
+
return path.join(controlInboxDir(asyncDir), "approve-checkpoint.json");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function rejectCheckpointRequestPath(asyncDir: string): string {
|
|
125
|
+
return path.join(controlInboxDir(asyncDir), "reject-checkpoint.json");
|
|
126
|
+
}
|
|
127
|
+
|
|
113
128
|
/** Directory of parent-to-runner steering requests. */
|
|
114
129
|
export function steerRequestsDir(asyncDir: string): string {
|
|
115
130
|
return path.join(controlInboxDir(asyncDir), STEER_REQUESTS_DIR);
|
|
@@ -254,6 +269,18 @@ export function requestAsyncStop(
|
|
|
254
269
|
return requestPath;
|
|
255
270
|
}
|
|
256
271
|
|
|
272
|
+
export function requestAsyncCheckpointDecision(
|
|
273
|
+
asyncDir: string,
|
|
274
|
+
type: CheckpointDecisionRequest["type"],
|
|
275
|
+
payload: Omit<CheckpointDecisionRequest, "type"> = {},
|
|
276
|
+
deps: { now?: () => number } = {},
|
|
277
|
+
): string {
|
|
278
|
+
const requestPath = type === "approve-checkpoint" ? approveCheckpointRequestPath(asyncDir) : rejectCheckpointRequestPath(asyncDir);
|
|
279
|
+
const request: CheckpointDecisionRequest = { ...payload, ts: payload.ts ?? deps.now?.() ?? Date.now(), type };
|
|
280
|
+
writeAtomicJson(requestPath, request);
|
|
281
|
+
return requestPath;
|
|
282
|
+
}
|
|
283
|
+
|
|
257
284
|
export function requestAsyncSteer(
|
|
258
285
|
asyncDir: string,
|
|
259
286
|
payload: { message: string; targetIndex?: number; targetIndexes?: number[]; source?: string; id?: string; ts?: number },
|
|
@@ -457,6 +484,21 @@ export function consumeStopRequest(
|
|
|
457
484
|
return true;
|
|
458
485
|
}
|
|
459
486
|
|
|
487
|
+
export function consumeCheckpointDecisionRequest(
|
|
488
|
+
asyncDir: string,
|
|
489
|
+
fsImpl: Pick<typeof fs, "existsSync" | "rmSync"> = fs,
|
|
490
|
+
): "approved" | "rejected" | undefined {
|
|
491
|
+
if (fsImpl.existsSync(rejectCheckpointRequestPath(asyncDir))) {
|
|
492
|
+
try { fsImpl.rmSync(rejectCheckpointRequestPath(asyncDir), { force: true, recursive: true }); } catch {}
|
|
493
|
+
return "rejected";
|
|
494
|
+
}
|
|
495
|
+
if (fsImpl.existsSync(approveCheckpointRequestPath(asyncDir))) {
|
|
496
|
+
try { fsImpl.rmSync(approveCheckpointRequestPath(asyncDir), { force: true, recursive: true }); } catch {}
|
|
497
|
+
return "approved";
|
|
498
|
+
}
|
|
499
|
+
return undefined;
|
|
500
|
+
}
|
|
501
|
+
|
|
460
502
|
/**
|
|
461
503
|
* Parent side: portable interrupt = authoritative file request + best-effort OS
|
|
462
504
|
* signal. The signal is only a latency optimization on Unix; ENOSYS on Windows
|
|
@@ -513,6 +555,21 @@ export function deliverStopRequest(input: {
|
|
|
513
555
|
requestAsyncStop(input.asyncDir, input.source ? { source: input.source } : {}, { now: input.now });
|
|
514
556
|
}
|
|
515
557
|
|
|
558
|
+
export function deliverCheckpointDecisionRequest(input: {
|
|
559
|
+
asyncDir: string;
|
|
560
|
+
decision: "approved" | "rejected";
|
|
561
|
+
now?: () => number;
|
|
562
|
+
source?: string;
|
|
563
|
+
reason?: string;
|
|
564
|
+
}): void {
|
|
565
|
+
requestAsyncCheckpointDecision(
|
|
566
|
+
input.asyncDir,
|
|
567
|
+
input.decision === "approved" ? "approve-checkpoint" : "reject-checkpoint",
|
|
568
|
+
{ ...(input.source ? { source: input.source } : {}), ...(input.reason ? { reason: input.reason } : {}) },
|
|
569
|
+
{ now: input.now },
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
516
573
|
/**
|
|
517
574
|
* Runner side: watch the control inbox and route interrupt requests into
|
|
518
575
|
* `onInterrupt`. Uses `fs.watch` when available plus an interval poll as a
|
|
@@ -526,6 +583,7 @@ export function watchAsyncControlInbox(
|
|
|
526
583
|
onTimeout?: () => void;
|
|
527
584
|
onStop?: () => void;
|
|
528
585
|
onSteer?: (request: SteerRequest) => void;
|
|
586
|
+
onCheckpointDecision?: (decision: "approved" | "rejected") => void;
|
|
529
587
|
onSteerCapability?: (capability: SteerCapability) => void;
|
|
530
588
|
onSteerAck?: (ack: SteerAck) => void;
|
|
531
589
|
pollIntervalMs?: number;
|
|
@@ -549,6 +607,8 @@ export function watchAsyncControlInbox(
|
|
|
549
607
|
if (consumeStopRequest(asyncDir, fsImpl)) opts.onStop?.();
|
|
550
608
|
if (consumeTimeoutRequest(asyncDir, fsImpl)) opts.onTimeout?.();
|
|
551
609
|
if (consumeInterruptRequest(asyncDir, fsImpl)) opts.onInterrupt();
|
|
610
|
+
const checkpointDecision = consumeCheckpointDecisionRequest(asyncDir, fsImpl);
|
|
611
|
+
if (checkpointDecision) opts.onCheckpointDecision?.(checkpointDecision);
|
|
552
612
|
for (const request of consumeSteerRequests(asyncDir, fsImpl)) opts.onSteer?.(request);
|
|
553
613
|
for (const capability of consumeSteerCapabilities(asyncDir, fsImpl)) opts.onSteerCapability?.(capability);
|
|
554
614
|
for (const ack of consumeSteerAcks(asyncDir, fsImpl)) opts.onSteerAck?.(ack);
|
|
@@ -15,10 +15,11 @@ import {
|
|
|
15
15
|
resolveCompletionBatchConfig,
|
|
16
16
|
} from "./completion-batcher.ts";
|
|
17
17
|
import { SUBAGENT_ASYNC_COMPLETE_EVENT, SUBAGENT_FOREGROUND_COMPLETE_EVENT, type ParallelHandoffReference, type SubagentState } from "../../shared/types.ts";
|
|
18
|
+
import { isUnexplainedProcessSignal } from "../shared/process-signal.ts";
|
|
18
19
|
|
|
19
20
|
export interface SubagentNotifyDetails {
|
|
20
21
|
agent: string;
|
|
21
|
-
status: "completed" | "failed" | "paused";
|
|
22
|
+
status: "completed" | "failed" | "paused" | "stopped";
|
|
22
23
|
source?: "async" | "foreground";
|
|
23
24
|
taskInfo?: string;
|
|
24
25
|
resultPreview: string;
|
|
@@ -37,6 +38,21 @@ export interface CompletionNotification {
|
|
|
37
38
|
summary?: string;
|
|
38
39
|
exitCode?: number;
|
|
39
40
|
state?: string;
|
|
41
|
+
processSignal?: string | null;
|
|
42
|
+
interrupted?: boolean;
|
|
43
|
+
timedOut?: boolean;
|
|
44
|
+
stopped?: boolean;
|
|
45
|
+
turnBudgetExceeded?: boolean;
|
|
46
|
+
results?: Array<{
|
|
47
|
+
status?: string;
|
|
48
|
+
success?: boolean;
|
|
49
|
+
exitCode?: number | null;
|
|
50
|
+
processSignal?: string | null;
|
|
51
|
+
interrupted?: boolean;
|
|
52
|
+
timedOut?: boolean;
|
|
53
|
+
stopped?: boolean;
|
|
54
|
+
turnBudgetExceeded?: boolean;
|
|
55
|
+
}>;
|
|
40
56
|
timestamp?: number;
|
|
41
57
|
durationMs?: number;
|
|
42
58
|
cwd?: string;
|
|
@@ -92,7 +108,7 @@ export function formatSingleCompletion(details: SubagentNotifyDetails): string {
|
|
|
92
108
|
|
|
93
109
|
export function parseSubagentNotifyContent(content: string): SubagentNotifyDetails | undefined {
|
|
94
110
|
const lines = content.split("\n");
|
|
95
|
-
const match = (lines[0] ?? "").match(/^(Background task|Detached foreground task) (completed|failed|paused): \*\*(.+?)\*\*(?:\s+(\([^)]*\)))?$/);
|
|
111
|
+
const match = (lines[0] ?? "").match(/^(Background task|Detached foreground task) (completed|failed|paused|stopped): \*\*(.+?)\*\*(?:\s+(\([^)]*\)))?$/);
|
|
96
112
|
if (!match) return undefined;
|
|
97
113
|
const body = lines.slice(2);
|
|
98
114
|
let sessionIndex = -1;
|
|
@@ -179,12 +195,18 @@ function completionBatchKey(result: CompletionNotification): string {
|
|
|
179
195
|
export function buildCompletionDetails(result: CompletionNotification): SubagentNotifyDetails {
|
|
180
196
|
const agent = result.agent ?? "unknown";
|
|
181
197
|
const summary = typeof result.summary === "string" ? result.summary : "";
|
|
182
|
-
const
|
|
198
|
+
const stopped = result.stopped === true
|
|
199
|
+
|| result.state === "stopped"
|
|
200
|
+
|| (result.success !== true && result.exitCode !== 0 && isUnexplainedProcessSignal(result))
|
|
201
|
+
|| result.results?.some((child) => child.stopped === true
|
|
202
|
+
|| child.status === "stopped"
|
|
203
|
+
|| (child.success !== true && child.exitCode !== 0 && isUnexplainedProcessSignal(child))) === true;
|
|
204
|
+
const paused = !stopped && !result.success && (
|
|
183
205
|
result.exitCode === 0
|
|
184
206
|
|| result.state === "paused"
|
|
185
207
|
|| summary.startsWith("Paused after interrupt.")
|
|
186
208
|
);
|
|
187
|
-
const status = paused ? "paused" : result.success ? "completed" : "failed";
|
|
209
|
+
const status = stopped ? "stopped" : paused ? "paused" : result.success ? "completed" : "failed";
|
|
188
210
|
const taskInfo =
|
|
189
211
|
result.taskIndex !== undefined && result.totalTasks !== undefined
|
|
190
212
|
? ` (${result.taskIndex + 1}/${result.totalTasks})`
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type NestedRunSummary,
|
|
9
9
|
type ParallelHandoffReference,
|
|
10
10
|
type SubagentResultIntercomChild,
|
|
11
|
+
type SubagentOutputState,
|
|
11
12
|
type SubagentState,
|
|
12
13
|
} from "../../shared/types.ts";
|
|
13
14
|
import {
|
|
@@ -45,10 +46,15 @@ type ResultWatcherDeps = {
|
|
|
45
46
|
type ResultFileChild = {
|
|
46
47
|
agent?: string;
|
|
47
48
|
output?: string;
|
|
49
|
+
outputState?: SubagentOutputState;
|
|
48
50
|
error?: string;
|
|
49
51
|
success?: boolean;
|
|
50
52
|
state?: string;
|
|
53
|
+
interrupted?: boolean;
|
|
54
|
+
timedOut?: boolean;
|
|
51
55
|
stopped?: boolean;
|
|
56
|
+
turnBudgetExceeded?: boolean;
|
|
57
|
+
processSignal?: string | null;
|
|
52
58
|
sessionFile?: string;
|
|
53
59
|
artifactPaths?: { outputPath?: string };
|
|
54
60
|
intercomTarget?: string;
|
|
@@ -174,9 +180,9 @@ export function createResultWatcher(
|
|
|
174
180
|
const hasResultChildren = Array.isArray(data.results) && data.results.length > 0;
|
|
175
181
|
const resultChildren: ResultFileChild[] = hasResultChildren
|
|
176
182
|
? data.results!
|
|
177
|
-
: [{ agent: data.agent ?? undefined, output: data.summary, success: data.success }];
|
|
183
|
+
: [{ agent: data.agent ?? undefined, output: data.summary, outputState: "unknown", success: data.success }];
|
|
178
184
|
const normalizedChildren = attachNestedChildrenToResultChildren(runId, resultChildren.map((result = {}, index): SubagentResultIntercomChild => {
|
|
179
|
-
const baseOutput = result.output ?? data.summary;
|
|
185
|
+
const baseOutput = hasResultChildren ? result.output : result.output ?? data.summary;
|
|
180
186
|
const hasRealOutput = typeof baseOutput === "string" && baseOutput.trim().length > 0;
|
|
181
187
|
const output = hasRealOutput ? baseOutput : "(no output)";
|
|
182
188
|
const summary = result.success === false && result.error
|
|
@@ -193,7 +199,18 @@ export function createResultWatcher(
|
|
|
193
199
|
: undefined;
|
|
194
200
|
return {
|
|
195
201
|
agent: result.agent ?? data.agent ?? `step-${index + 1}`,
|
|
196
|
-
status: resolveSubagentResultStatus({
|
|
202
|
+
status: resolveSubagentResultStatus({
|
|
203
|
+
success: result.success,
|
|
204
|
+
state: childState,
|
|
205
|
+
interrupted: result.interrupted,
|
|
206
|
+
timedOut: result.timedOut,
|
|
207
|
+
stopped: result.stopped,
|
|
208
|
+
turnBudgetExceeded: result.turnBudgetExceeded,
|
|
209
|
+
processSignal: result.processSignal,
|
|
210
|
+
}),
|
|
211
|
+
outputState: result.outputState === "present" || result.outputState === "absent" || result.outputState === "unknown"
|
|
212
|
+
? result.outputState
|
|
213
|
+
: "unknown",
|
|
197
214
|
summary,
|
|
198
215
|
index,
|
|
199
216
|
artifactPath: result.artifactPaths?.outputPath,
|
|
@@ -8,6 +8,7 @@ import { formatModelThinking } from "../../shared/formatters.ts";
|
|
|
8
8
|
import { formatActivityLabel } from "../../shared/status-format.ts";
|
|
9
9
|
import { ASYNC_DIR, RESULTS_DIR, type AsyncStatus, type Details, type ForegroundResumeRun, type NestedRunSummary, type SteeringStatus, type SubagentState } from "../../shared/types.ts";
|
|
10
10
|
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
11
|
+
import { resolveSubagentResultStatus } from "../../intercom/result-intercom.ts";
|
|
11
12
|
import { readProcessTerminal, sanitizeProcessTerminal } from "./process-terminal.ts";
|
|
12
13
|
import { resolveAsyncRunLocation } from "./async-resume.ts";
|
|
13
14
|
import { resolveSubagentRunId } from "./run-id-resolver.ts";
|
|
@@ -39,6 +40,11 @@ function hasExistingSessionFile(value: unknown): value is string {
|
|
|
39
40
|
return typeof value === "string" && fs.existsSync(value);
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
function formatCheckpointGuidance(runId: string | undefined, checkpoint: AsyncStatus["checkpoint"] | undefined): string | undefined {
|
|
44
|
+
if (!runId || !checkpoint || checkpoint.status !== "pending") return undefined;
|
|
45
|
+
return `Checkpoint: ${checkpoint.name}${checkpoint.message ? ` — ${checkpoint.message}` : ""}\nApprove: subagent({ action: "approve-checkpoint", id: "${runId}" })\nReject: subagent({ action: "reject-checkpoint", id: "${runId}" })`;
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
function formatResumeGuidance(runId: string | undefined, children: Array<{ agent?: unknown; sessionFile?: unknown }>, fallbackSessionFile?: unknown, options: { stopped?: boolean } = {}): string {
|
|
43
49
|
if (options.stopped) return "Resume: unavailable; stopped runs are not resumable. Start a new run instead.";
|
|
44
50
|
const knownChildren = children
|
|
@@ -126,6 +132,12 @@ function formatRememberedForegroundStatus(run: ForegroundResumeRun): string {
|
|
|
126
132
|
else lines.push(`Transcript: subagent({ action: "status", id: "${run.runId}", index: 0, view: "transcript" })`);
|
|
127
133
|
const detached = run.children.some((child) => child.status === "detached");
|
|
128
134
|
const resumable = run.children.find((child) => hasExistingSessionFile(child.sessionFile));
|
|
135
|
+
if (run.checkpoint?.status === "pending") {
|
|
136
|
+
lines.push(`Checkpoint: ${run.checkpoint.name}${run.checkpoint.message ? ` — ${run.checkpoint.message}` : ""}`);
|
|
137
|
+
lines.push(`Approve: subagent({ action: "approve-checkpoint", id: "${run.runId}" })`);
|
|
138
|
+
lines.push(`Reject: subagent({ action: "reject-checkpoint", id: "${run.runId}" })`);
|
|
139
|
+
return lines.join("\n");
|
|
140
|
+
}
|
|
129
141
|
if (detached) {
|
|
130
142
|
lines.push(`Recovery: reply to the supervisor request first, then wait with subagent_wait({ id: "${run.runId}" }); do not resume or launch a replacement while any child remains detached.`);
|
|
131
143
|
} else if (resumable) {
|
|
@@ -403,7 +415,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
403
415
|
if (status.sessionFile) lines.push(`Session: ${status.sessionFile}`);
|
|
404
416
|
if (status.state === "running") lines.push(`Steer running child: subagent({ action: "steer", id: "${status.runId}", message: "..." })`);
|
|
405
417
|
if (status.state !== "running") {
|
|
406
|
-
lines.push(formatResumeGuidance(status.runId, status.steps ?? [], status.sessionFile, { stopped: status.state === "stopped" || status.stopped === true }));
|
|
418
|
+
lines.push(formatCheckpointGuidance(status.runId, status.checkpoint) ?? formatResumeGuidance(status.runId, status.steps ?? [], status.sessionFile, { stopped: status.state === "stopped" || status.stopped === true }));
|
|
407
419
|
}
|
|
408
420
|
if (fs.existsSync(logPath)) lines.push(`Log: ${logPath}`);
|
|
409
421
|
if (fs.existsSync(eventsPath)) lines.push(`Events: ${eventsPath}`);
|
|
@@ -415,7 +427,7 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
415
427
|
if (resultPath) {
|
|
416
428
|
try {
|
|
417
429
|
const raw = fs.readFileSync(resultPath, "utf-8");
|
|
418
|
-
const data = JSON.parse(raw) as { id?: string; runId?: string; agent?: string; success?: boolean; summary?: string; output?: string; exitCode?: number; state?: string; sessionFile?: string; parallelHandoff?: { path?: string }; results?: Array<{ agent?: string; output?: string; summary?: string; sessionFile?: string; state?: string; success?: boolean; exitCode?: number | null }> };
|
|
430
|
+
const data = JSON.parse(raw) as { id?: string; runId?: 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; output?: string; summary?: string; sessionFile?: string; state?: string; success?: boolean; exitCode?: number | null; stopped?: boolean; timedOut?: boolean; turnBudgetExceeded?: boolean; interrupted?: boolean; processSignal?: string | null }> };
|
|
419
431
|
if (params.view === "transcript") {
|
|
420
432
|
try {
|
|
421
433
|
return { content: [{ type: "text", text: formatAsyncResultTranscript(data, resultPath, { index: params.index, lines: params.lines }) }], details: { mode: "single", results: [] } };
|
|
@@ -424,7 +436,21 @@ export function inspectSubagentStatus(params: RunStatusParams, deps: RunStatusDe
|
|
|
424
436
|
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "single", results: [] } };
|
|
425
437
|
}
|
|
426
438
|
}
|
|
427
|
-
const
|
|
439
|
+
const childStatuses = Array.isArray(data.results)
|
|
440
|
+
? data.results.map((child) => resolveSubagentResultStatus({
|
|
441
|
+
success: child.success,
|
|
442
|
+
state: child.state,
|
|
443
|
+
interrupted: child.interrupted,
|
|
444
|
+
timedOut: child.timedOut,
|
|
445
|
+
stopped: child.stopped,
|
|
446
|
+
turnBudgetExceeded: child.turnBudgetExceeded,
|
|
447
|
+
processSignal: child.processSignal,
|
|
448
|
+
exitCode: typeof child.exitCode === "number" ? child.exitCode : undefined,
|
|
449
|
+
}))
|
|
450
|
+
: [];
|
|
451
|
+
const status = data.state === "stopped" || data.stopped === true || childStatuses.includes("stopped")
|
|
452
|
+
? "stopped"
|
|
453
|
+
: data.success ? "complete" : data.state === "paused" || data.exitCode === 0 ? "paused" : "failed";
|
|
428
454
|
const runId = data.runId ?? data.id ?? resolvedId;
|
|
429
455
|
const lines = [`Run: ${runId}`, `State: ${status}`, `Result: ${resultPath}`];
|
|
430
456
|
if (data.parallelHandoff?.path) lines.push(`Parallel handoff: ${data.parallelHandoff.path}`);
|