pi-subagents 0.34.0 → 0.35.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 +78 -9
- package/README.md +213 -32
- package/index.ts +1 -0
- package/install.mjs +1 -1
- package/package.json +23 -8
- package/prompts/review-loop.md +3 -1
- package/skills/pi-subagents/SKILL.md +87 -25
- package/src/agents/agent-management.ts +82 -15
- package/src/agents/agent-serializer.ts +19 -0
- package/src/agents/agents.ts +91 -49
- package/src/agents/frontmatter.ts +67 -13
- package/src/agents/skills.ts +25 -12
- package/src/api/background-work.ts +197 -0
- package/src/api/delegation.ts +158 -0
- package/src/extension/chain-validation.ts +165 -0
- package/src/extension/doctor.ts +15 -0
- package/src/extension/fanout-child.ts +3 -1
- package/src/extension/index.ts +65 -124
- package/src/extension/rpc.ts +10 -2
- package/src/extension/schemas.ts +18 -14
- package/src/extension/steering-notices.ts +35 -0
- package/src/extension/tool-description.ts +20 -9
- package/src/intercom/intercom-bridge.ts +3 -2
- package/src/intercom/native-supervisor-channel.ts +9 -1
- package/src/intercom/result-intercom.ts +4 -0
- package/src/runs/background/async-execution.ts +293 -44
- package/src/runs/background/async-job-tracker.ts +56 -9
- package/src/runs/background/async-resume.ts +159 -52
- package/src/runs/background/async-status.ts +25 -18
- package/src/runs/background/auto-drain.ts +67 -0
- package/src/runs/background/chain-root-attachment.ts +16 -8
- package/src/runs/background/control-channel.ts +260 -13
- package/src/runs/background/fleet-view.ts +23 -2
- package/src/runs/background/notify.ts +79 -10
- package/src/runs/background/result-watcher.ts +12 -9
- package/src/runs/background/run-id-resolver.ts +14 -2
- package/src/runs/background/run-status.ts +23 -15
- package/src/runs/background/scheduled-runs.ts +3 -0
- package/src/runs/background/stale-run-reconciler.ts +32 -10
- package/src/runs/background/steering.ts +237 -0
- package/src/runs/background/subagent-runner.ts +898 -236
- package/src/runs/background/subagent-wait.ts +484 -0
- package/src/runs/background/top-level-async.ts +2 -1
- package/src/runs/background/wait-config.ts +36 -0
- package/src/runs/background/wait-tool.ts +26 -0
- package/src/runs/foreground/async-steering-action.ts +230 -0
- package/src/runs/foreground/chain-clarify.ts +22 -6
- package/src/runs/foreground/chain-execution.ts +50 -32
- package/src/runs/foreground/execution.ts +308 -94
- package/src/runs/foreground/subagent-executor.ts +592 -268
- package/src/runs/shared/acceptance.ts +355 -97
- package/src/runs/shared/child-protocol.ts +121 -0
- package/src/runs/shared/completion-guard.ts +8 -127
- package/src/runs/shared/dynamic-fanout.ts +6 -4
- package/src/runs/shared/model-fallback.ts +36 -0
- package/src/runs/shared/nested-events.ts +9 -4
- package/src/runs/shared/nested-render.ts +4 -1
- package/src/runs/shared/parallel-utils.ts +7 -0
- package/src/runs/shared/pi-args.ts +34 -7
- package/src/runs/shared/pi-spawn.ts +18 -12
- package/src/runs/shared/session-lease.ts +279 -0
- package/src/runs/shared/single-output.ts +61 -6
- package/src/runs/shared/spawn-budget.ts +128 -0
- package/src/runs/shared/subagent-control.ts +10 -6
- package/src/runs/shared/subagent-prompt-runtime.ts +127 -26
- package/src/runs/shared/task-intent.ts +176 -0
- package/src/runs/shared/tool-availability.ts +65 -0
- package/src/runs/shared/turn-budget.ts +49 -4
- package/src/shared/atomic-json.ts +4 -1
- package/src/shared/fork-context.ts +28 -3
- package/src/shared/model-info.ts +7 -4
- package/src/shared/status-format.ts +7 -1
- package/src/shared/types.ts +203 -25
- package/src/shared/utils.ts +35 -7
- package/src/slash/delegation-adapters.ts +457 -0
- package/src/slash/delegation-request.ts +103 -0
- package/src/slash/prompt-template-bridge.ts +167 -344
- package/src/slash/slash-commands.ts +239 -6
- package/src/slash/subagents-admin.ts +428 -0
- package/src/slash/subagents-editor.ts +86 -0
- package/src/tui/fleet.ts +405 -0
- package/src/tui/render.ts +90 -16
- package/src/watchdog/change-signature.ts +127 -0
- package/src/watchdog/child-status.ts +205 -0
- package/src/watchdog/emission-guard.ts +123 -0
- package/src/watchdog/lsp-diagnostics.ts +532 -0
- package/src/watchdog/model-selection.ts +167 -0
- package/src/watchdog/register-child.ts +117 -0
- package/src/watchdog/register-main.ts +433 -0
- package/src/watchdog/render.ts +54 -0
- package/src/watchdog/review.ts +293 -0
- package/src/watchdog/runtime.ts +712 -0
- package/src/watchdog/settings.ts +528 -0
- package/src/watchdog/tool-actions.ts +155 -0
- package/src/watchdog/turn-delta.ts +161 -0
- package/src/watchdog/types.ts +188 -0
- package/src/watchdog/warning-format.ts +73 -0
- package/src/runs/background/wait.ts +0 -394
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { formatDuration, formatModelThinking, formatTokens, shortenPath } from "../../shared/formatters.ts";
|
|
4
4
|
import { formatActivityLabel, formatParallelOutcome } from "../../shared/status-format.ts";
|
|
5
|
-
import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type NestedRunSummary, type SubagentRunMode, type TokenUsage, type TurnBudgetState } from "../../shared/types.ts";
|
|
5
|
+
import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type NestedRunSummary, type SteeringStatus, type SubagentRunMode, type TokenUsage, type TurnBudgetState } from "../../shared/types.ts";
|
|
6
6
|
import { readStatus } from "../../shared/utils.ts";
|
|
7
7
|
import { attachRootChildrenToSteps, buildNestedRouteIndex, type NestedRoute, projectNestedEvents } from "../shared/nested-events.ts";
|
|
8
8
|
import { formatNestedRunStatusLines } from "../shared/nested-render.ts";
|
|
@@ -27,8 +27,7 @@ interface AsyncRunStepSummary {
|
|
|
27
27
|
recentOutput?: string[];
|
|
28
28
|
turnCount?: number;
|
|
29
29
|
toolCount?: number;
|
|
30
|
-
|
|
31
|
-
lastSteerAt?: number;
|
|
30
|
+
steering?: SteeringStatus;
|
|
32
31
|
durationMs?: number;
|
|
33
32
|
tokens?: TokenUsage;
|
|
34
33
|
totalCost?: CostSummary;
|
|
@@ -38,6 +37,7 @@ interface AsyncRunStepSummary {
|
|
|
38
37
|
attemptedModels?: string[];
|
|
39
38
|
error?: string;
|
|
40
39
|
timedOut?: boolean;
|
|
40
|
+
stopped?: boolean;
|
|
41
41
|
turnBudget?: TurnBudgetState;
|
|
42
42
|
turnBudgetExceeded?: boolean;
|
|
43
43
|
wrapUpRequested?: boolean;
|
|
@@ -48,7 +48,7 @@ export interface AsyncRunSummary {
|
|
|
48
48
|
id: string;
|
|
49
49
|
asyncDir: string;
|
|
50
50
|
sessionId?: string;
|
|
51
|
-
state: "queued" | "running" | "complete" | "failed" | "paused";
|
|
51
|
+
state: "queued" | "running" | "complete" | "failed" | "paused" | "stopped";
|
|
52
52
|
error?: string;
|
|
53
53
|
activityState?: ActivityState;
|
|
54
54
|
lastActivityAt?: number;
|
|
@@ -57,8 +57,7 @@ export interface AsyncRunSummary {
|
|
|
57
57
|
currentPath?: string;
|
|
58
58
|
turnCount?: number;
|
|
59
59
|
toolCount?: number;
|
|
60
|
-
|
|
61
|
-
lastSteerAt?: number;
|
|
60
|
+
steering?: SteeringStatus;
|
|
62
61
|
mode: SubagentRunMode;
|
|
63
62
|
cwd?: string;
|
|
64
63
|
startedAt: number;
|
|
@@ -67,6 +66,7 @@ export interface AsyncRunSummary {
|
|
|
67
66
|
timeoutMs?: number;
|
|
68
67
|
deadlineAt?: number;
|
|
69
68
|
timedOut?: boolean;
|
|
69
|
+
stopped?: boolean;
|
|
70
70
|
turnBudget?: TurnBudgetState;
|
|
71
71
|
turnBudgetExceeded?: boolean;
|
|
72
72
|
wrapUpRequested?: boolean;
|
|
@@ -178,8 +178,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
178
178
|
...(step.recentOutput ? { recentOutput: [...step.recentOutput] } : {}),
|
|
179
179
|
...(step.turnCount !== undefined ? { turnCount: step.turnCount } : {}),
|
|
180
180
|
...(step.toolCount !== undefined ? { toolCount: step.toolCount } : {}),
|
|
181
|
-
...(step.
|
|
182
|
-
...(step.lastSteerAt !== undefined ? { lastSteerAt: step.lastSteerAt } : {}),
|
|
181
|
+
...(step.steering ? { steering: step.steering } : {}),
|
|
183
182
|
...(step.durationMs !== undefined ? { durationMs: step.durationMs } : {}),
|
|
184
183
|
...(step.tokens ? { tokens: step.tokens } : {}),
|
|
185
184
|
...(step.totalCost ? { totalCost: step.totalCost } : {}),
|
|
@@ -189,6 +188,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
189
188
|
...(step.attemptedModels ? { attemptedModels: step.attemptedModels } : {}),
|
|
190
189
|
...(step.error ? { error: step.error } : {}),
|
|
191
190
|
...(step.timedOut !== undefined ? { timedOut: step.timedOut } : {}),
|
|
191
|
+
...(step.stopped !== undefined ? { stopped: step.stopped } : {}),
|
|
192
192
|
...(step.turnBudget ? { turnBudget: step.turnBudget } : {}),
|
|
193
193
|
...(step.turnBudgetExceeded !== undefined ? { turnBudgetExceeded: step.turnBudgetExceeded } : {}),
|
|
194
194
|
...(step.wrapUpRequested !== undefined ? { wrapUpRequested: step.wrapUpRequested } : {}),
|
|
@@ -209,8 +209,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
209
209
|
currentPath: status.currentPath,
|
|
210
210
|
turnCount: status.turnCount,
|
|
211
211
|
toolCount: status.toolCount,
|
|
212
|
-
|
|
213
|
-
lastSteerAt: status.lastSteerAt,
|
|
212
|
+
steering: status.steering,
|
|
214
213
|
mode: status.mode,
|
|
215
214
|
cwd: status.cwd,
|
|
216
215
|
startedAt: status.startedAt,
|
|
@@ -219,6 +218,7 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
219
218
|
...(status.timeoutMs !== undefined ? { timeoutMs: status.timeoutMs } : {}),
|
|
220
219
|
...(status.deadlineAt !== undefined ? { deadlineAt: status.deadlineAt } : {}),
|
|
221
220
|
...(status.timedOut !== undefined ? { timedOut: status.timedOut } : {}),
|
|
221
|
+
...(status.stopped !== undefined ? { stopped: status.stopped } : {}),
|
|
222
222
|
...(status.turnBudget ? { turnBudget: status.turnBudget } : {}),
|
|
223
223
|
...(status.turnBudgetExceeded !== undefined ? { turnBudgetExceeded: status.turnBudgetExceeded } : {}),
|
|
224
224
|
...(status.wrapUpRequested !== undefined ? { wrapUpRequested: status.wrapUpRequested } : {}),
|
|
@@ -237,12 +237,17 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
|
|
|
237
237
|
};
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
+
export function summarizeAsyncStatus(asyncDir: string, status: AsyncStatus & { cwd?: string }): AsyncRunSummary {
|
|
241
|
+
return statusToSummary(asyncDir, status);
|
|
242
|
+
}
|
|
243
|
+
|
|
240
244
|
function sortRuns(runs: AsyncRunSummary[]): AsyncRunSummary[] {
|
|
241
245
|
const rank = (state: AsyncRunSummary["state"]): number => {
|
|
242
246
|
switch (state) {
|
|
243
247
|
case "running": return 0;
|
|
244
248
|
case "queued": return 1;
|
|
245
249
|
case "failed": return 2;
|
|
250
|
+
case "stopped": return 2;
|
|
246
251
|
case "paused": return 2;
|
|
247
252
|
case "complete": return 3;
|
|
248
253
|
}
|
|
@@ -294,11 +299,13 @@ export function listAsyncRuns(asyncDirRoot: string, options: AsyncRunListOptions
|
|
|
294
299
|
if (options.sessionId && status.sessionId !== options.sessionId) continue;
|
|
295
300
|
const nestedWarnings: string[] = [];
|
|
296
301
|
let nestedRoute: NestedRoute | undefined;
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
+
if (options.reconcile !== false) {
|
|
303
|
+
try {
|
|
304
|
+
nestedRoute = resolveNestedRoute(status.runId || path.basename(asyncDir));
|
|
305
|
+
if (nestedRoute) reconcileNestedAsyncDescendants(nestedRoute, { resultsDir: options.resultsDir, kill: options.kill, now: options.now });
|
|
306
|
+
} catch (error) {
|
|
307
|
+
nestedWarnings.push(`Nested status unavailable: ${getErrorMessage(error)}`);
|
|
308
|
+
}
|
|
302
309
|
}
|
|
303
310
|
const summary = statusToSummary(asyncDir, status, nestedWarnings, nestedRoute);
|
|
304
311
|
runs.push(summary);
|
|
@@ -308,18 +315,18 @@ export function listAsyncRuns(asyncDirRoot: string, options: AsyncRunListOptions
|
|
|
308
315
|
return options.limit !== undefined ? sorted.slice(0, options.limit) : sorted;
|
|
309
316
|
}
|
|
310
317
|
|
|
311
|
-
function formatActivityFacts(input: { activityState?: ActivityState; lastActivityAt?: number; currentTool?: string; currentToolStartedAt?: number; currentPath?: string; turnCount?: number; toolCount?: number;
|
|
318
|
+
function formatActivityFacts(input: { activityState?: ActivityState; lastActivityAt?: number; currentTool?: string; currentToolStartedAt?: number; currentPath?: string; turnCount?: number; toolCount?: number; steering?: SteeringStatus; turnBudget?: TurnBudgetState; turnBudgetExceeded?: boolean; wrapUpRequested?: boolean }): string | undefined {
|
|
312
319
|
const facts: string[] = [];
|
|
313
320
|
if (input.currentTool && input.currentToolStartedAt !== undefined) facts.push(`tool ${input.currentTool} ${formatDuration(Math.max(0, Date.now() - input.currentToolStartedAt))}`);
|
|
314
321
|
else if (input.currentTool) facts.push(`tool ${input.currentTool}`);
|
|
315
322
|
if (input.currentPath) facts.push(shortenPath(input.currentPath));
|
|
316
323
|
if (input.turnCount !== undefined) facts.push(`${input.turnCount} turns`);
|
|
317
324
|
if (input.turnBudgetExceeded && input.turnBudget) facts.push(`turn budget exceeded ${input.turnBudget.turnCount}/${input.turnBudget.maxTurns}+${input.turnBudget.graceTurns}`);
|
|
325
|
+
else if (input.turnBudget?.outcome === "termination-deferred") facts.push(`turn-budget termination deferred ${input.turnBudget.turnCount}/${input.turnBudget.maxTurns}+${input.turnBudget.graceTurns}`);
|
|
318
326
|
else if (input.wrapUpRequested && input.turnBudget) facts.push(`wrap-up requested ${input.turnBudget.turnCount}/${input.turnBudget.maxTurns}`);
|
|
319
327
|
else if (input.turnBudget) facts.push(`turn budget ${input.turnBudget.turnCount}/${input.turnBudget.maxTurns}+${input.turnBudget.graceTurns}`);
|
|
320
328
|
if (input.toolCount !== undefined) facts.push(`${input.toolCount} tools`);
|
|
321
|
-
if (input.
|
|
322
|
-
if (typeof input.lastSteerAt === "number" && Number.isFinite(input.lastSteerAt)) facts.push(`last steer ${new Date(input.lastSteerAt).toISOString()}`);
|
|
329
|
+
if (input.steering) facts.push(`steering ${input.steering.scheduled} scheduled, ${input.steering.pending} pending, ${input.steering.delivered} delivered, ${input.steering.failed} failed, ${input.steering.recovered} recovered`);
|
|
323
330
|
const activity = formatActivityLabel(input.lastActivityAt, input.activityState);
|
|
324
331
|
return activity || facts.length ? [activity, ...facts].filter(Boolean).join(" | ") : undefined;
|
|
325
332
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import { snapshotBackgroundWork } from "../../api/background-work.ts";
|
|
3
|
+
import { ASYNC_DIR, RESULTS_DIR, type Details, type SubagentState } from "../../shared/types.ts";
|
|
4
|
+
import { listAsyncRuns } from "./async-status.ts";
|
|
5
|
+
import { waitForSubagents, type SubagentWaitDeps, type SubagentWaitParams, type WaitEventBus } from "./subagent-wait.ts";
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_AUTO_DRAIN_TIMEOUT_MS = 30 * 60 * 1000;
|
|
8
|
+
|
|
9
|
+
export interface AutoDrainDeps {
|
|
10
|
+
state: SubagentState;
|
|
11
|
+
events?: WaitEventBus;
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
now?: () => number;
|
|
14
|
+
wait?: (
|
|
15
|
+
params: SubagentWaitParams,
|
|
16
|
+
signal: AbortSignal | undefined,
|
|
17
|
+
deps: SubagentWaitDeps,
|
|
18
|
+
) => Promise<AgentToolResult<Details>>;
|
|
19
|
+
hasWork?: (sessionId: string, nowMs: number) => boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resultText(value: AgentToolResult<Details>): string {
|
|
23
|
+
return value.content.map((part) => part.type === "text" ? part.text : "").join(" ").trim();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function hasOutstandingWork(sessionId: string, nowMs: number): boolean {
|
|
27
|
+
const asyncRuns = listAsyncRuns(ASYNC_DIR, {
|
|
28
|
+
states: ["queued", "running"],
|
|
29
|
+
sessionId,
|
|
30
|
+
resultsDir: RESULTS_DIR,
|
|
31
|
+
now: () => nowMs,
|
|
32
|
+
});
|
|
33
|
+
return asyncRuns.length > 0 || snapshotBackgroundWork(sessionId, nowMs).items.length > 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Drain all work owned by the current headless session, including work added while draining. */
|
|
37
|
+
export async function drainOutstandingWork(deps: AutoDrainDeps): Promise<void> {
|
|
38
|
+
const sessionId = deps.state.currentSessionId;
|
|
39
|
+
if (!sessionId) throw new Error("Cannot auto-drain background work without an active session identity.");
|
|
40
|
+
const now = deps.now ?? Date.now;
|
|
41
|
+
const timeoutMs = deps.timeoutMs ?? DEFAULT_AUTO_DRAIN_TIMEOUT_MS;
|
|
42
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new Error("Auto-drain timeoutMs must be a positive finite number.");
|
|
43
|
+
const deadlineAt = now() + timeoutMs;
|
|
44
|
+
const hasWork = deps.hasWork ?? hasOutstandingWork;
|
|
45
|
+
const wait = deps.wait ?? waitForSubagents;
|
|
46
|
+
|
|
47
|
+
while (hasWork(sessionId, now())) {
|
|
48
|
+
const remainingMs = deadlineAt - now();
|
|
49
|
+
if (remainingMs <= 0) {
|
|
50
|
+
throw new Error(`Auto-drain timed out after ${timeoutMs}ms with background work still active in session '${sessionId}'.`);
|
|
51
|
+
}
|
|
52
|
+
const waitResult = await wait(
|
|
53
|
+
{ all: true, timeoutMs: remainingMs },
|
|
54
|
+
undefined,
|
|
55
|
+
{
|
|
56
|
+
state: deps.state,
|
|
57
|
+
events: deps.events,
|
|
58
|
+
now,
|
|
59
|
+
stopOnAttention: false,
|
|
60
|
+
failOnFailedRuns: true,
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
if (waitResult.isError) {
|
|
64
|
+
throw new Error(`Auto-drain failed for session '${sessionId}': ${resultText(waitResult) || "subagent_wait returned an error without details"}.`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -27,6 +27,7 @@ export interface ImportedAsyncRootResult {
|
|
|
27
27
|
structuredOutputSchemaPath?: string;
|
|
28
28
|
acceptance?: AcceptanceLedger;
|
|
29
29
|
timedOut?: boolean;
|
|
30
|
+
stopped?: boolean;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
interface AsyncResultFile {
|
|
@@ -35,12 +36,14 @@ interface AsyncResultFile {
|
|
|
35
36
|
summary?: string;
|
|
36
37
|
error?: string;
|
|
37
38
|
timedOut?: boolean;
|
|
39
|
+
stopped?: boolean;
|
|
38
40
|
results?: Array<{
|
|
39
41
|
agent?: string;
|
|
40
42
|
output?: string;
|
|
41
43
|
error?: string;
|
|
42
44
|
success?: boolean;
|
|
43
45
|
timedOut?: boolean;
|
|
46
|
+
stopped?: boolean;
|
|
44
47
|
sessionFile?: string;
|
|
45
48
|
intercomTarget?: string;
|
|
46
49
|
model?: string;
|
|
@@ -54,8 +57,8 @@ interface AsyncResultFile {
|
|
|
54
57
|
}>;
|
|
55
58
|
}
|
|
56
59
|
|
|
57
|
-
const TERMINAL_STATES = new Set(["complete", "failed", "paused"]);
|
|
58
|
-
const TERMINAL_STEP_STATUSES = new Set(["complete", "completed", "failed", "paused"]);
|
|
60
|
+
const TERMINAL_STATES = new Set(["complete", "failed", "paused", "stopped"]);
|
|
61
|
+
const TERMINAL_STEP_STATUSES = new Set(["complete", "completed", "failed", "paused", "stopped"]);
|
|
59
62
|
|
|
60
63
|
function readResultFile(resultPath: string): AsyncResultFile | undefined {
|
|
61
64
|
try {
|
|
@@ -79,11 +82,12 @@ function isTerminalStatus(status: AsyncStatus | null, index: number): boolean {
|
|
|
79
82
|
return TERMINAL_STATES.has(status.state);
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
function resultState(result: AsyncResultFile | undefined, child: NonNullable<AsyncResultFile["results"]>[number] | undefined): "complete" | "failed" | "paused" | undefined {
|
|
85
|
+
function resultState(result: AsyncResultFile | undefined, child: NonNullable<AsyncResultFile["results"]>[number] | undefined): "complete" | "failed" | "paused" | "stopped" | undefined {
|
|
83
86
|
if (!result) return undefined;
|
|
87
|
+
if (child?.stopped === true) return "stopped";
|
|
84
88
|
if (child?.success === true) return "complete";
|
|
85
|
-
if (child?.success === false) return result.state === "paused" ? "paused" : "failed";
|
|
86
|
-
if (result.state === "complete" || result.state === "failed" || result.state === "paused") return result.state;
|
|
89
|
+
if (child?.success === false) return result.state === "stopped" ? "stopped" : result.state === "paused" ? "paused" : "failed";
|
|
90
|
+
if (result.state === "complete" || result.state === "failed" || result.state === "paused" || result.state === "stopped") return result.state;
|
|
87
91
|
if (result.success === true) return "complete";
|
|
88
92
|
if (result.success === false) return "failed";
|
|
89
93
|
return undefined;
|
|
@@ -92,7 +96,8 @@ function resultState(result: AsyncResultFile | undefined, child: NonNullable<Asy
|
|
|
92
96
|
function outputFromTerminalStatus(root: ImportedAsyncRoot, status: AsyncStatus, step: NonNullable<AsyncStatus["steps"]>[number] | undefined): ImportedAsyncRootResult {
|
|
93
97
|
const agent = step?.agent ?? status.steps?.[root.index]?.agent ?? "subagent";
|
|
94
98
|
const timedOut = step?.timedOut === true || status.timedOut === true;
|
|
95
|
-
const
|
|
99
|
+
const stopped = step?.stopped === true || status.stopped === true || status.state === "stopped";
|
|
100
|
+
const message = step?.error ?? status.error ?? (stopped ? "Subagent stopped by user." : `Attached async root ${root.runId} ended without a result file at ${root.resultPath}.`);
|
|
96
101
|
return {
|
|
97
102
|
agent,
|
|
98
103
|
output: message,
|
|
@@ -100,6 +105,7 @@ function outputFromTerminalStatus(root: ImportedAsyncRoot, status: AsyncStatus,
|
|
|
100
105
|
exitCode: 1,
|
|
101
106
|
error: message,
|
|
102
107
|
...(timedOut ? { timedOut: true } : {}),
|
|
108
|
+
...(stopped ? { stopped: true } : {}),
|
|
103
109
|
...(step?.sessionFile ?? status.sessionFile ? { sessionFile: step?.sessionFile ?? status.sessionFile } : {}),
|
|
104
110
|
...(step?.model ? { model: step.model } : {}),
|
|
105
111
|
...(step?.attemptedModels ? { attemptedModels: step.attemptedModels } : {}),
|
|
@@ -136,8 +142,9 @@ function buildImportedResult(root: ImportedAsyncRoot, status: AsyncStatus | null
|
|
|
136
142
|
const agent = child?.agent ?? step?.agent ?? status?.steps?.[root.index]?.agent ?? "subagent";
|
|
137
143
|
const output = child?.output ?? result.summary ?? "";
|
|
138
144
|
const timedOut = child?.timedOut === true || step?.timedOut === true || result.timedOut === true || status?.timedOut === true;
|
|
139
|
-
const
|
|
140
|
-
const
|
|
145
|
+
const stopped = child?.stopped === true || step?.stopped === true || result.stopped === true || status?.stopped === true || state === "stopped";
|
|
146
|
+
const success = state === "complete" && !timedOut && !stopped;
|
|
147
|
+
const error = child?.error ?? (success ? undefined : stopped ? "Subagent stopped by user." : result.error ?? result.summary ?? status?.error ?? `Attached async root ${root.runId} did not complete successfully.`);
|
|
141
148
|
return {
|
|
142
149
|
agent,
|
|
143
150
|
output: success ? output : (output || error || ""),
|
|
@@ -145,6 +152,7 @@ function buildImportedResult(root: ImportedAsyncRoot, status: AsyncStatus | null
|
|
|
145
152
|
exitCode: success ? 0 : 1,
|
|
146
153
|
...(error ? { error } : {}),
|
|
147
154
|
...(timedOut ? { timedOut: true } : {}),
|
|
155
|
+
...(stopped ? { stopped: true } : {}),
|
|
148
156
|
...(child?.sessionFile ?? step?.sessionFile ?? status?.sessionFile ? { sessionFile: child?.sessionFile ?? step?.sessionFile ?? status?.sessionFile } : {}),
|
|
149
157
|
...(child?.intercomTarget ? { intercomTarget: child.intercomTarget } : {}),
|
|
150
158
|
...(child?.model ?? step?.model ? { model: child?.model ?? step?.model } : {}),
|