pi-subagents 0.34.0 → 0.35.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/CHANGELOG.md +83 -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 +98 -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
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { existsSync, unlinkSync } from "node:fs";
|
|
7
|
+
import * as path from "node:path";
|
|
7
8
|
import type { Message } from "@earendil-works/pi-ai";
|
|
8
9
|
import type { AgentConfig } from "../../agents/agents.ts";
|
|
9
10
|
import {
|
|
@@ -51,7 +52,8 @@ import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
|
|
|
51
52
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
52
53
|
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
|
|
53
54
|
import { readStructuredOutput } from "../shared/structured-output.ts";
|
|
54
|
-
import {
|
|
55
|
+
import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
|
|
56
|
+
import { captureSingleOutputSnapshot, extractChildWrittenOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
55
57
|
import {
|
|
56
58
|
buildModelCandidates,
|
|
57
59
|
formatModelAttemptNote,
|
|
@@ -68,9 +70,18 @@ import {
|
|
|
68
70
|
shouldEscalateMutatingFailures,
|
|
69
71
|
summarizeRecentMutatingFailures,
|
|
70
72
|
} from "../shared/long-running-guard.ts";
|
|
71
|
-
import { acceptanceFailureMessage, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
72
|
-
import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState,
|
|
73
|
+
import { acceptanceFailureMessage, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
74
|
+
import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, turnBudgetDecision, turnBudgetDeferredNote, turnBudgetDeferredState, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from "../shared/turn-budget.ts";
|
|
73
75
|
import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
|
|
76
|
+
import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
|
|
77
|
+
import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, projectChildLifecycle, type ChildLifecycleAction, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
|
|
78
|
+
import {
|
|
79
|
+
acceptChildWatchdogEvent,
|
|
80
|
+
childWatchdogIsActive,
|
|
81
|
+
isChildWatchdogStatusEvent,
|
|
82
|
+
resolveChildWatchdogConfig,
|
|
83
|
+
type ChildWatchdogStateSnapshot,
|
|
84
|
+
} from "../../watchdog/child-status.ts";
|
|
74
85
|
|
|
75
86
|
const artifactOutputByResult = new WeakMap<SingleResult, string>();
|
|
76
87
|
const acceptanceOutputByResult = new WeakMap<SingleResult, string>();
|
|
@@ -102,16 +113,14 @@ function resolveAttemptTimeout(options: RunSyncOptions): { timeoutMs: number; re
|
|
|
102
113
|
};
|
|
103
114
|
}
|
|
104
115
|
|
|
105
|
-
function
|
|
116
|
+
function buildPendingAcceptanceLedger(acceptance: ResolvedAcceptanceConfig): AcceptanceLedger {
|
|
106
117
|
return {
|
|
107
|
-
status:
|
|
118
|
+
status: "pending",
|
|
108
119
|
explicit: acceptance.explicit,
|
|
109
120
|
effectiveAcceptance: acceptance,
|
|
110
121
|
inferredReason: acceptance.inferredReason,
|
|
111
122
|
criteria: acceptance.criteria,
|
|
112
|
-
runtimeChecks:
|
|
113
|
-
? []
|
|
114
|
-
: [{ id: input.id, status: "failed", message: input.message }],
|
|
123
|
+
runtimeChecks: [],
|
|
115
124
|
verifyRuns: [],
|
|
116
125
|
};
|
|
117
126
|
}
|
|
@@ -187,7 +196,16 @@ async function runSingleAttempt(
|
|
|
187
196
|
): Promise<SingleResult> {
|
|
188
197
|
const effectiveThinking = options.thinkingOverride ?? agent.thinking;
|
|
189
198
|
const modelArg = applyThinkingSuffix(model, effectiveThinking, options.thinkingOverride !== undefined);
|
|
190
|
-
const
|
|
199
|
+
const watchdogConfig = resolveWatchdogConfig(options.cwd ?? runtimeCwd);
|
|
200
|
+
const childWatchdog = watchdogConfig.ok
|
|
201
|
+
? resolveChildWatchdogConfig({
|
|
202
|
+
config: watchdogConfig.config,
|
|
203
|
+
agent: agent.name,
|
|
204
|
+
runId: options.runId,
|
|
205
|
+
childIndex: options.index ?? 0,
|
|
206
|
+
})
|
|
207
|
+
: undefined;
|
|
208
|
+
const { args, env: sharedEnv, tempDir, toolDiagnosticPath } = buildPiArgs({
|
|
191
209
|
baseArgs: ["--mode", "json", "-p"],
|
|
192
210
|
task,
|
|
193
211
|
sessionEnabled: shared.sessionEnabled,
|
|
@@ -218,6 +236,8 @@ async function runSingleAttempt(
|
|
|
218
236
|
parentSessionId: options.parentSessionId,
|
|
219
237
|
structuredOutput: options.structuredOutput,
|
|
220
238
|
toolBudget: options.toolBudget,
|
|
239
|
+
childWatchdog,
|
|
240
|
+
waitToolEnabled: options.waitToolEnabled,
|
|
221
241
|
});
|
|
222
242
|
|
|
223
243
|
const result: SingleResult = {
|
|
@@ -271,6 +291,7 @@ async function runSingleAttempt(
|
|
|
271
291
|
result.progress = progress;
|
|
272
292
|
const attemptTimeout = resolveAttemptTimeout(options);
|
|
273
293
|
if (attemptTimeout?.remainingMs === 0) {
|
|
294
|
+
cleanupTempDir(tempDir);
|
|
274
295
|
result.exitCode = 1;
|
|
275
296
|
result.timedOut = true;
|
|
276
297
|
result.error = attemptTimeout.message;
|
|
@@ -296,7 +317,6 @@ async function runSingleAttempt(
|
|
|
296
317
|
windowsHide: true,
|
|
297
318
|
});
|
|
298
319
|
const jsonlWriter = createJsonlWriter(shared.jsonlPath, proc.stdout);
|
|
299
|
-
let buf = "";
|
|
300
320
|
let processClosed = false;
|
|
301
321
|
let settled = false;
|
|
302
322
|
let detached = false;
|
|
@@ -311,6 +331,7 @@ async function runSingleAttempt(
|
|
|
311
331
|
let turnBudgetSoftReached = false;
|
|
312
332
|
let turnBudgetTerminationTimer: NodeJS.Timeout | undefined;
|
|
313
333
|
let turnBudgetHardKillTimer: NodeJS.Timeout | undefined;
|
|
334
|
+
let protocolHardKillTimer: NodeJS.Timeout | undefined;
|
|
314
335
|
const clearTurnBudgetTimers = () => {
|
|
315
336
|
if (turnBudgetTerminationTimer) {
|
|
316
337
|
clearTimeout(turnBudgetTerminationTimer);
|
|
@@ -358,8 +379,22 @@ async function runSingleAttempt(
|
|
|
358
379
|
let childExited = false;
|
|
359
380
|
let forcedTerminationSignal = false;
|
|
360
381
|
let cleanTerminalAssistantStopReceived = false;
|
|
382
|
+
let agentSettledReceived = false;
|
|
361
383
|
let finalDrainTimer: NodeJS.Timeout | undefined;
|
|
362
384
|
let finalHardKillTimer: NodeJS.Timeout | undefined;
|
|
385
|
+
let watchdogTailTimer: NodeJS.Timeout | undefined;
|
|
386
|
+
let childWatchdogState: ChildWatchdogStateSnapshot | undefined;
|
|
387
|
+
const updateChildWatchdogState = (snapshot: ChildWatchdogStateSnapshot): void => {
|
|
388
|
+
childWatchdogState = snapshot;
|
|
389
|
+
result.watchdog = snapshot;
|
|
390
|
+
progress.watchdog = snapshot;
|
|
391
|
+
};
|
|
392
|
+
const clearWatchdogTailTimer = () => {
|
|
393
|
+
if (watchdogTailTimer) {
|
|
394
|
+
clearTimeout(watchdogTailTimer);
|
|
395
|
+
watchdogTailTimer = undefined;
|
|
396
|
+
}
|
|
397
|
+
};
|
|
363
398
|
const clearFinalDrainTimers = () => {
|
|
364
399
|
if (finalDrainTimer) {
|
|
365
400
|
clearTimeout(finalDrainTimer);
|
|
@@ -371,14 +406,18 @@ async function runSingleAttempt(
|
|
|
371
406
|
}
|
|
372
407
|
};
|
|
373
408
|
const startFinalDrain = () => {
|
|
409
|
+
if (childWatchdogIsActive(childWatchdogState)) {
|
|
410
|
+
armWatchdogTail();
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
374
413
|
if (childExited || finalDrainTimer || settled || processClosed || detached) return;
|
|
375
414
|
finalDrainTimer = setTimeout(() => {
|
|
376
415
|
if (settled || processClosed || detached) return;
|
|
377
416
|
const termSent = trySignalChild(proc, "SIGTERM");
|
|
378
417
|
if (!termSent) return;
|
|
379
418
|
forcedTerminationSignal = true;
|
|
380
|
-
if (!cleanTerminalAssistantStopReceived && !assistantError) {
|
|
381
|
-
result.error = result.error ?? `Subagent process did not exit within ${FINAL_STOP_GRACE_MS}ms after its
|
|
419
|
+
if (!cleanTerminalAssistantStopReceived && !agentSettledReceived && !assistantError) {
|
|
420
|
+
result.error = result.error ?? `Subagent process did not exit within ${FINAL_STOP_GRACE_MS}ms after its terminal event. Forcing termination.`;
|
|
382
421
|
}
|
|
383
422
|
finalHardKillTimer = setTimeout(() => {
|
|
384
423
|
if (settled || processClosed || detached) return;
|
|
@@ -388,13 +427,45 @@ async function runSingleAttempt(
|
|
|
388
427
|
}, FINAL_STOP_GRACE_MS);
|
|
389
428
|
finalDrainTimer.unref?.();
|
|
390
429
|
};
|
|
430
|
+
function armWatchdogTail(): void {
|
|
431
|
+
if ((!cleanTerminalAssistantStopReceived && !agentSettledReceived) || watchdogTailTimer || settled || processClosed || detached) return;
|
|
432
|
+
watchdogTailTimer = setTimeout(() => {
|
|
433
|
+
watchdogTailTimer = undefined;
|
|
434
|
+
updateChildWatchdogState({
|
|
435
|
+
phase: "stale",
|
|
436
|
+
seq: (childWatchdogState?.seq ?? 0) + 1,
|
|
437
|
+
lastUpdate: Date.now(),
|
|
438
|
+
followUpPending: false,
|
|
439
|
+
reason: "child watchdog tail timeout",
|
|
440
|
+
timedOut: true,
|
|
441
|
+
});
|
|
442
|
+
startFinalDrain();
|
|
443
|
+
fireUpdate();
|
|
444
|
+
}, childWatchdog?.watchdogTailTimeoutMs ?? 120_000);
|
|
445
|
+
watchdogTailTimer.unref?.();
|
|
446
|
+
}
|
|
447
|
+
const applyChildLifecycle = (action: ChildLifecycleAction): void => {
|
|
448
|
+
if (action === "cancel-drain") {
|
|
449
|
+
clearFinalDrainTimers();
|
|
450
|
+
clearWatchdogTailTimer();
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (action === "start-drain") startFinalDrain();
|
|
454
|
+
};
|
|
391
455
|
|
|
392
456
|
const unsubscribeIntercomDetach = options.intercomEvents?.on?.(INTERCOM_DETACH_REQUEST_EVENT, (payload) => {
|
|
393
|
-
if (!options.allowIntercomDetach || detached || processClosed
|
|
457
|
+
if (!options.allowIntercomDetach || detached || processClosed) return;
|
|
394
458
|
if (!payload || typeof payload !== "object") return;
|
|
395
|
-
const
|
|
459
|
+
const event = payload as { requestId?: unknown; runId?: unknown; agent?: unknown; childIndex?: unknown };
|
|
460
|
+
const requestId = event.requestId;
|
|
396
461
|
if (typeof requestId !== "string" || requestId.length === 0) return;
|
|
397
|
-
|
|
462
|
+
const hasRoute = event.runId !== undefined || event.agent !== undefined || event.childIndex !== undefined;
|
|
463
|
+
if (hasRoute) {
|
|
464
|
+
if (typeof event.runId === "string" && event.runId !== options.runId) return;
|
|
465
|
+
if (typeof event.agent === "string" && event.agent !== agent.name) return;
|
|
466
|
+
if (typeof event.childIndex === "number" && event.childIndex !== (options.index ?? 0)) return;
|
|
467
|
+
} else if (!intercomStarted) return;
|
|
468
|
+
options.intercomEvents?.emit(INTERCOM_DETACH_RESPONSE_EVENT, { requestId, accepted: true, runId: options.runId, agent: agent.name, childIndex: options.index ?? 0 });
|
|
398
469
|
detachForIntercom();
|
|
399
470
|
});
|
|
400
471
|
|
|
@@ -402,9 +473,14 @@ async function runSingleAttempt(
|
|
|
402
473
|
if (settled) return;
|
|
403
474
|
settled = true;
|
|
404
475
|
clearFinalDrainTimers();
|
|
476
|
+
clearWatchdogTailTimer();
|
|
405
477
|
clearStdioGuard();
|
|
406
478
|
clearTimeoutTimers();
|
|
407
479
|
clearTurnBudgetTimers();
|
|
480
|
+
if (protocolHardKillTimer) {
|
|
481
|
+
clearTimeout(protocolHardKillTimer);
|
|
482
|
+
protocolHardKillTimer = undefined;
|
|
483
|
+
}
|
|
408
484
|
if (activityTimer) {
|
|
409
485
|
clearInterval(activityTimer);
|
|
410
486
|
activityTimer = undefined;
|
|
@@ -504,7 +580,7 @@ async function runSingleAttempt(
|
|
|
504
580
|
turnBudgetHardKillTimer.unref?.();
|
|
505
581
|
};
|
|
506
582
|
|
|
507
|
-
const updateTurnBudget = (turnCount: number, terminalAssistantStop: boolean) => {
|
|
583
|
+
const updateTurnBudget = (turnCount: number, terminalAssistantStop: boolean, toolWorkActiveOrStarting: boolean) => {
|
|
508
584
|
const budget = options.turnBudget;
|
|
509
585
|
if (!budget || result.timedOut || result.turnBudgetExceeded) return;
|
|
510
586
|
if (turnCount < budget.maxTurns) {
|
|
@@ -516,10 +592,17 @@ async function runSingleAttempt(
|
|
|
516
592
|
result.wrapUpRequested = true;
|
|
517
593
|
appendRecentOutput(progress, [turnBudgetSoftNote(budget, turnCount)]);
|
|
518
594
|
}
|
|
519
|
-
|
|
520
|
-
if (
|
|
521
|
-
|
|
595
|
+
const decision = turnBudgetDecision(budget, turnCount, terminalAssistantStop, toolWorkActiveOrStarting);
|
|
596
|
+
if (decision === "defer") {
|
|
597
|
+
result.turnBudget = turnBudgetDeferredState(
|
|
598
|
+
budget,
|
|
599
|
+
turnCount,
|
|
600
|
+
result.turnBudget?.terminationDeferredAtTurn,
|
|
601
|
+
);
|
|
602
|
+
return;
|
|
522
603
|
}
|
|
604
|
+
result.turnBudget = turnBudgetState(budget, turnCount, false);
|
|
605
|
+
if (decision === "abort") requestTurnBudgetAbort(turnCount);
|
|
523
606
|
};
|
|
524
607
|
|
|
525
608
|
const updateActivityState = (now: number): boolean => {
|
|
@@ -528,6 +611,7 @@ async function runSingleAttempt(
|
|
|
528
611
|
config: controlConfig,
|
|
529
612
|
startedAt: startTime,
|
|
530
613
|
lastActivityAt: progress.lastActivityAt,
|
|
614
|
+
currentTool: progress.currentTool,
|
|
531
615
|
now,
|
|
532
616
|
});
|
|
533
617
|
if (idleState === "needs_attention") {
|
|
@@ -569,15 +653,39 @@ async function runSingleAttempt(
|
|
|
569
653
|
const processLine = (line: string) => {
|
|
570
654
|
if (!line.trim()) return;
|
|
571
655
|
jsonlWriter.writeLine(line);
|
|
572
|
-
let evt: { type?: string; message?: Message; toolName?: string; args?: unknown };
|
|
656
|
+
let evt: { type?: string; message?: Message; toolName?: string; args?: unknown; willRetry?: unknown };
|
|
573
657
|
try {
|
|
574
|
-
evt = JSON.parse(line) as { type?: string; message?: Message; toolName?: string; args?: unknown };
|
|
658
|
+
evt = JSON.parse(line) as { type?: string; message?: Message; toolName?: string; args?: unknown; willRetry?: unknown };
|
|
575
659
|
} catch {
|
|
576
660
|
shared.transcriptWriter?.writeStdoutLine(line);
|
|
577
661
|
// Non-JSON stdout lines are expected; only structured events are parsed.
|
|
578
662
|
return;
|
|
579
663
|
}
|
|
580
664
|
shared.transcriptWriter?.writeChildEvent(evt);
|
|
665
|
+
if (evt.type === "agent_settled") agentSettledReceived = true;
|
|
666
|
+
applyChildLifecycle(projectChildLifecycle(evt));
|
|
667
|
+
|
|
668
|
+
if (isChildWatchdogStatusEvent(evt)) {
|
|
669
|
+
if (!childWatchdog) return;
|
|
670
|
+
const next = acceptChildWatchdogEvent({
|
|
671
|
+
current: childWatchdogState,
|
|
672
|
+
event: evt,
|
|
673
|
+
runId: options.runId,
|
|
674
|
+
agent: agent.name,
|
|
675
|
+
childIndex: options.index ?? 0,
|
|
676
|
+
});
|
|
677
|
+
if (!next) return;
|
|
678
|
+
updateChildWatchdogState(next);
|
|
679
|
+
if (childWatchdogIsActive(next)) {
|
|
680
|
+
clearFinalDrainTimers();
|
|
681
|
+
armWatchdogTail();
|
|
682
|
+
} else {
|
|
683
|
+
clearWatchdogTailTimer();
|
|
684
|
+
if (cleanTerminalAssistantStopReceived || agentSettledReceived) startFinalDrain();
|
|
685
|
+
}
|
|
686
|
+
fireUpdate();
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
581
689
|
|
|
582
690
|
const now = Date.now();
|
|
583
691
|
progress.durationMs = now - startTime;
|
|
@@ -588,11 +696,8 @@ async function runSingleAttempt(
|
|
|
588
696
|
const toolArgs = evt.args && typeof evt.args === "object" && !Array.isArray(evt.args)
|
|
589
697
|
? evt.args as Record<string, unknown>
|
|
590
698
|
: {};
|
|
591
|
-
let shouldDetachForBlockingIntercom = false;
|
|
592
699
|
if (options.allowIntercomDetach && (evt.toolName === "intercom" || evt.toolName === "contact_supervisor")) {
|
|
593
700
|
intercomStarted = true;
|
|
594
|
-
shouldDetachForBlockingIntercom = (evt.toolName === "intercom" && toolArgs.action === "ask")
|
|
595
|
-
|| (evt.toolName === "contact_supervisor" && (toolArgs.reason === "need_decision" || toolArgs.reason === "interview_request"));
|
|
596
701
|
}
|
|
597
702
|
progress.toolCount++;
|
|
598
703
|
if (options.toolBudget) {
|
|
@@ -606,9 +711,6 @@ async function runSingleAttempt(
|
|
|
606
711
|
observedMutationAttempt = observedMutationAttempt || mutates;
|
|
607
712
|
pendingToolResult = { tool: evt.toolName ?? "tool", path: progress.currentPath, mutates, startedAt: now };
|
|
608
713
|
fireUpdate();
|
|
609
|
-
if (shouldDetachForBlockingIntercom && !detached && !processClosed) {
|
|
610
|
-
detachForIntercom();
|
|
611
|
-
}
|
|
612
714
|
}
|
|
613
715
|
|
|
614
716
|
if (evt.type === "tool_execution_end") {
|
|
@@ -635,7 +737,7 @@ async function runSingleAttempt(
|
|
|
635
737
|
const hasToolCall = Array.isArray(evt.message.content)
|
|
636
738
|
&& evt.message.content.some((part) => (part as { type?: string }).type === "toolCall");
|
|
637
739
|
const terminalAssistantStop = stopReason === "stop" && !hasToolCall;
|
|
638
|
-
updateTurnBudget(result.usage.turns, terminalAssistantStop);
|
|
740
|
+
updateTurnBudget(result.usage.turns, terminalAssistantStop, hasToolCall || Boolean(progress.currentTool));
|
|
639
741
|
const u = evt.message.usage;
|
|
640
742
|
if (u) {
|
|
641
743
|
result.usage.input += u.input || 0;
|
|
@@ -653,7 +755,7 @@ async function runSingleAttempt(
|
|
|
653
755
|
if (terminalAssistantStop) {
|
|
654
756
|
if (!evt.message.errorMessage && assistantText.trim()) assistantError = undefined;
|
|
655
757
|
cleanTerminalAssistantStopReceived ||= !evt.message.errorMessage;
|
|
656
|
-
|
|
758
|
+
applyChildLifecycle(projectChildLifecycle(evt, true));
|
|
657
759
|
}
|
|
658
760
|
}
|
|
659
761
|
updateActivityState(now);
|
|
@@ -731,17 +833,36 @@ async function runSingleAttempt(
|
|
|
731
833
|
timeoutTimer.unref?.();
|
|
732
834
|
}
|
|
733
835
|
|
|
734
|
-
|
|
836
|
+
const stderrTail = createBoundedByteTail();
|
|
837
|
+
const failProtocol = (limit: ProtocolOutputLimit): void => {
|
|
838
|
+
if (result.protocolError) return;
|
|
839
|
+
result.protocolError = limit;
|
|
840
|
+
result.error = formatProtocolOutputLimit(limit);
|
|
841
|
+
progress.status = "failed";
|
|
842
|
+
progress.error = result.error;
|
|
843
|
+
progress.durationMs = Date.now() - startTime;
|
|
844
|
+
fireUpdate();
|
|
845
|
+
if (!childExited) {
|
|
846
|
+
trySignalChild(proc, "SIGTERM");
|
|
847
|
+
protocolHardKillTimer = setTimeout(() => {
|
|
848
|
+
if (!childExited) trySignalChild(proc, "SIGKILL");
|
|
849
|
+
}, 3000);
|
|
850
|
+
protocolHardKillTimer.unref?.();
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
const stdoutReader = createBoundedLineReader({ onLine: processLine, onLimit: failProtocol });
|
|
854
|
+
const stderrReader = createBoundedLineReader({
|
|
855
|
+
stream: "stderr",
|
|
856
|
+
maxPendingLineBytes: MAX_CHILD_STDERR_BYTES,
|
|
857
|
+
onLine: (line) => shared.transcriptWriter?.writeStderrLine(line),
|
|
858
|
+
onLimit: (limit) => shared.transcriptWriter?.writeStderrLine(formatProtocolOutputLimit(limit)),
|
|
859
|
+
});
|
|
735
860
|
|
|
736
861
|
const clearStdioGuard = attachPostExitStdioGuard(proc, { idleMs: 2000, hardMs: 8000 });
|
|
737
|
-
proc.stdout.on("data", (
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
lines.forEach(processLine);
|
|
742
|
-
});
|
|
743
|
-
proc.stderr.on("data", (d) => {
|
|
744
|
-
stderrBuf += d.toString();
|
|
862
|
+
proc.stdout.on("data", (chunk: Buffer) => stdoutReader.push(chunk));
|
|
863
|
+
proc.stderr.on("data", (chunk: Buffer) => {
|
|
864
|
+
stderrTail.push(chunk);
|
|
865
|
+
stderrReader.push(chunk);
|
|
745
866
|
});
|
|
746
867
|
proc.on("exit", () => {
|
|
747
868
|
childExited = true;
|
|
@@ -753,38 +874,67 @@ async function runSingleAttempt(
|
|
|
753
874
|
void jsonlWriter.close().catch(() => {
|
|
754
875
|
// JSONL artifact flush is best effort.
|
|
755
876
|
});
|
|
877
|
+
const toolDiagnosticError = readChildToolDiagnosticError(toolDiagnosticPath);
|
|
756
878
|
cleanupTempDir(tempDir);
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
879
|
+
stdoutReader.end();
|
|
880
|
+
stderrReader.end();
|
|
881
|
+
const stderr = stderrTail.text();
|
|
882
|
+
let closeError = result.error ?? toolDiagnosticError ?? assistantError;
|
|
883
|
+
const forcedDrainAfterFinalSuccess = forcedTerminationSignal && (cleanTerminalAssistantStopReceived || agentSettledReceived) && !closeError;
|
|
884
|
+
if (code !== 0 && stderr.trim() && !closeError && !forcedDrainAfterFinalSuccess) {
|
|
885
|
+
closeError = stderr.trim();
|
|
763
886
|
}
|
|
764
887
|
const finalCode = forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (code ?? 1) : (code ?? 0);
|
|
765
888
|
if (detached) {
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
889
|
+
const recoveredProgress = snapshotProgress(progress);
|
|
890
|
+
const recoveredResult = snapshotResult(result, recoveredProgress);
|
|
891
|
+
if (!recoveredResult.error && closeError) recoveredResult.error = closeError;
|
|
892
|
+
recoveredResult.exitCode = recoveredResult.error && finalCode === 0 ? 1 : finalCode;
|
|
893
|
+
recoveredProgress.status = recoveredResult.exitCode === 0 ? "completed" : "failed";
|
|
894
|
+
recoveredProgress.durationMs = Date.now() - startTime;
|
|
895
|
+
if (recoveredResult.error) recoveredProgress.error = recoveredResult.error;
|
|
896
|
+
recoveredResult.progressSummary = {
|
|
897
|
+
toolCount: recoveredProgress.toolCount,
|
|
898
|
+
tokens: recoveredProgress.tokens,
|
|
899
|
+
durationMs: recoveredProgress.durationMs,
|
|
774
900
|
};
|
|
775
|
-
const
|
|
776
|
-
|
|
777
|
-
|
|
901
|
+
const acceptanceOutput = getFinalOutput(recoveredResult.messages ?? []).trim()
|
|
902
|
+
|| recoveredResult.error
|
|
903
|
+
|| recoveredResult.finalOutput
|
|
904
|
+
|| "Detached child exited without final output.";
|
|
905
|
+
acceptanceOutputByResult.set(recoveredResult, acceptanceOutput);
|
|
906
|
+
let fullOutput = stripAcceptanceReport(acceptanceOutput);
|
|
907
|
+
fullOutput = fullOutput.trim() || recoveredResult.error || recoveredResult.finalOutput || "Detached child exited without final output.";
|
|
908
|
+
recoveredResult.outputMode = options.outputMode ?? "inline";
|
|
909
|
+
if (options.outputPath && recoveredResult.exitCode === 0) {
|
|
910
|
+
const resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, shared.outputSnapshot);
|
|
911
|
+
fullOutput = stripAcceptanceReport(resolvedOutput.fullOutput);
|
|
912
|
+
recoveredResult.savedOutputPath = resolvedOutput.savedPath;
|
|
913
|
+
recoveredResult.outputSaveError = resolvedOutput.saveError;
|
|
914
|
+
if (resolvedOutput.savedPath) {
|
|
915
|
+
recoveredResult.outputReference = formatSavedOutputReference(resolvedOutput.savedPath, fullOutput);
|
|
916
|
+
} else {
|
|
917
|
+
recoveredResult.exitCode = 1;
|
|
918
|
+
recoveredResult.error = `Output file was not finalized after detached child exit: ${resolvedOutput.saveError ?? options.outputPath}`;
|
|
919
|
+
recoveredProgress.status = "failed";
|
|
920
|
+
recoveredProgress.error = recoveredResult.error;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
recoveredResult.finalOutput = options.outputMode === "file-only" && recoveredResult.savedOutputPath && recoveredResult.outputReference
|
|
924
|
+
? recoveredResult.outputReference.message
|
|
925
|
+
: fullOutput;
|
|
926
|
+
if (recoveredResult.artifactPaths && options.artifactConfig?.enabled !== false && options.artifactConfig?.includeOutput !== false) {
|
|
778
927
|
try {
|
|
779
|
-
writeArtifact(
|
|
928
|
+
writeArtifact(recoveredResult.artifactPaths.outputPath, fullOutput);
|
|
780
929
|
} catch {
|
|
781
930
|
// Detached children may outlive test/temp cleanup; recovered status is best-effort.
|
|
782
931
|
}
|
|
783
932
|
}
|
|
784
|
-
options.onDetachedExit?.(
|
|
933
|
+
options.onDetachedExit?.(recoveredResult);
|
|
785
934
|
finish(-2);
|
|
786
935
|
return;
|
|
787
936
|
}
|
|
937
|
+
if (!result.error && closeError) result.error = closeError;
|
|
788
938
|
processClosed = true;
|
|
789
939
|
finish(finalCode);
|
|
790
940
|
});
|
|
@@ -795,7 +945,8 @@ async function runSingleAttempt(
|
|
|
795
945
|
// JSONL artifact flush is best effort.
|
|
796
946
|
});
|
|
797
947
|
cleanupTempDir(tempDir);
|
|
798
|
-
|
|
948
|
+
stdoutReader.end();
|
|
949
|
+
stderrReader.end();
|
|
799
950
|
if (!result.error) {
|
|
800
951
|
result.error = error instanceof Error ? error.message : String(error);
|
|
801
952
|
}
|
|
@@ -805,10 +956,6 @@ async function runSingleAttempt(
|
|
|
805
956
|
if (options.signal) {
|
|
806
957
|
const kill = () => {
|
|
807
958
|
if (processClosed || detached) return;
|
|
808
|
-
if (options.allowIntercomDetach && intercomStarted && !detached) {
|
|
809
|
-
detachForIntercom();
|
|
810
|
-
return;
|
|
811
|
-
}
|
|
812
959
|
proc.kill("SIGTERM");
|
|
813
960
|
setTimeout(() => !proc.killed && proc.kill("SIGKILL"), 3000);
|
|
814
961
|
};
|
|
@@ -861,8 +1008,12 @@ async function runSingleAttempt(
|
|
|
861
1008
|
return result;
|
|
862
1009
|
}
|
|
863
1010
|
if (result.detached) {
|
|
864
|
-
result.exitCode =
|
|
865
|
-
result.finalOutput = "Detached for intercom coordination.";
|
|
1011
|
+
result.exitCode = -2;
|
|
1012
|
+
result.finalOutput = "Detached for intercom coordination before task completion.";
|
|
1013
|
+
result.outputMode = options.outputMode ?? "inline";
|
|
1014
|
+
if (options.outputPath) {
|
|
1015
|
+
result.outputSaveError = "Output file was not finalized because the subagent detached for intercom coordination.";
|
|
1016
|
+
}
|
|
866
1017
|
return result;
|
|
867
1018
|
}
|
|
868
1019
|
|
|
@@ -928,6 +1079,9 @@ async function runSingleAttempt(
|
|
|
928
1079
|
: timeoutMessage;
|
|
929
1080
|
} else if (result.turnBudgetExceeded && result.turnBudget) {
|
|
930
1081
|
fullOutput = formatTurnBudgetOutput(turnBudgetExceededMessage(result.turnBudget, result.turnBudget.turnCount), fullOutput);
|
|
1082
|
+
} else if (result.turnBudget?.outcome === "termination-deferred") {
|
|
1083
|
+
const note = turnBudgetDeferredNote(result.turnBudget, result.turnBudget.terminationDeferredAtTurn ?? result.turnBudget.turnCount);
|
|
1084
|
+
fullOutput = fullOutput.trim() ? `${note}\n\n${fullOutput}` : note;
|
|
931
1085
|
} else if (result.wrapUpRequested && result.turnBudget?.outcome === "wrap-up-requested") {
|
|
932
1086
|
const note = turnBudgetSoftNote(result.turnBudget, result.turnBudget.wrapUpRequestedAtTurn ?? result.turnBudget.turnCount);
|
|
933
1087
|
fullOutput = fullOutput.trim() ? `${note}\n\n${fullOutput}` : note;
|
|
@@ -1028,6 +1182,7 @@ export async function runSync(
|
|
|
1028
1182
|
const effectiveAcceptance = resolveEffectiveAcceptance({
|
|
1029
1183
|
explicit: options.acceptance,
|
|
1030
1184
|
agentName,
|
|
1185
|
+
acceptanceRole: agent.acceptanceRole,
|
|
1031
1186
|
task,
|
|
1032
1187
|
mode: options.acceptanceContext?.mode ?? "single",
|
|
1033
1188
|
async: options.acceptanceContext?.async,
|
|
@@ -1039,7 +1194,13 @@ export async function runSync(
|
|
|
1039
1194
|
const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
|
|
1040
1195
|
const skillNames = options.skills ?? agent.skills ?? [];
|
|
1041
1196
|
const skillCwd = options.cwd ?? runtimeCwd;
|
|
1042
|
-
const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(
|
|
1197
|
+
const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(
|
|
1198
|
+
skillNames,
|
|
1199
|
+
skillCwd,
|
|
1200
|
+
runtimeCwd,
|
|
1201
|
+
agent.skillPath,
|
|
1202
|
+
agent.filePath ? path.dirname(agent.filePath) : skillCwd,
|
|
1203
|
+
);
|
|
1043
1204
|
if (skillNames.some((skill) => skill.trim() === "pi-subagents") && missingSkills.includes("pi-subagents")) {
|
|
1044
1205
|
return {
|
|
1045
1206
|
agent: agentName,
|
|
@@ -1059,7 +1220,7 @@ export async function runSync(
|
|
|
1059
1220
|
if (memoryInjection) {
|
|
1060
1221
|
systemPrompt = systemPrompt ? `${systemPrompt}\n\n${memoryInjection}` : memoryInjection;
|
|
1061
1222
|
}
|
|
1062
|
-
systemPrompt = injectOutputPathSystemPrompt(systemPrompt, options.outputPath);
|
|
1223
|
+
systemPrompt = injectOutputPathSystemPrompt(systemPrompt, options.outputPath, agent);
|
|
1063
1224
|
|
|
1064
1225
|
const candidates = buildModelCandidates(
|
|
1065
1226
|
options.modelOverride ?? agent.model,
|
|
@@ -1100,12 +1261,73 @@ export async function runSync(
|
|
|
1100
1261
|
}
|
|
1101
1262
|
}
|
|
1102
1263
|
|
|
1264
|
+
const persistResultMetadata = (target: SingleResult): void => {
|
|
1265
|
+
if (!artifactPathsResult || options.artifactConfig?.enabled === false || options.artifactConfig?.includeMetadata === false) return;
|
|
1266
|
+
writeMetadata(artifactPathsResult.metadataPath, {
|
|
1267
|
+
runId: options.runId,
|
|
1268
|
+
agent: agentName,
|
|
1269
|
+
task,
|
|
1270
|
+
exitCode: target.exitCode,
|
|
1271
|
+
usage: target.usage,
|
|
1272
|
+
model: target.model,
|
|
1273
|
+
attemptedModels: target.attemptedModels,
|
|
1274
|
+
modelAttempts: target.modelAttempts,
|
|
1275
|
+
durationMs: target.progressSummary?.durationMs,
|
|
1276
|
+
toolCount: target.progressSummary?.toolCount,
|
|
1277
|
+
error: target.error,
|
|
1278
|
+
acceptance: target.acceptance,
|
|
1279
|
+
...(transcriptWriter ? { transcriptPath: artifactPathsResult.transcriptPath } : {}),
|
|
1280
|
+
transcriptError: target.transcriptError,
|
|
1281
|
+
skills: target.skills,
|
|
1282
|
+
skillsWarning: target.skillsWarning,
|
|
1283
|
+
timestamp: Date.now(),
|
|
1284
|
+
});
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
const detachedAwareOptions: RunSyncOptions = options.onDetachedExit
|
|
1288
|
+
? {
|
|
1289
|
+
...options,
|
|
1290
|
+
onDetachedExit: (recoveredResult) => {
|
|
1291
|
+
void (async () => {
|
|
1292
|
+
const childWrittenOutput = options.outputPath
|
|
1293
|
+
? extractChildWrittenOutput(recoveredResult.messages, options.outputPath, options.cwd ?? runtimeCwd)
|
|
1294
|
+
: undefined;
|
|
1295
|
+
recoveredResult.acceptance = await evaluateAcceptance({
|
|
1296
|
+
acceptance: effectiveAcceptance,
|
|
1297
|
+
output: acceptanceOutputByResult.get(recoveredResult) ?? recoveredResult.finalOutput ?? "",
|
|
1298
|
+
fileOutput: childWrittenOutput !== undefined && options.outputPath
|
|
1299
|
+
? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === "file-only" }
|
|
1300
|
+
: undefined,
|
|
1301
|
+
cwd: options.cwd ?? runtimeCwd,
|
|
1302
|
+
});
|
|
1303
|
+
const acceptanceFailure = acceptanceFailureMessage(recoveredResult.acceptance);
|
|
1304
|
+
stripAcceptanceReportsFromMessages(recoveredResult.messages);
|
|
1305
|
+
if (acceptanceFailure && recoveredResult.acceptance.explicit && recoveredResult.exitCode === 0) {
|
|
1306
|
+
recoveredResult.exitCode = 1;
|
|
1307
|
+
recoveredResult.error = recoveredResult.error ? `${recoveredResult.error}\n${acceptanceFailure}` : acceptanceFailure;
|
|
1308
|
+
if (recoveredResult.progress) {
|
|
1309
|
+
recoveredResult.progress.status = "failed";
|
|
1310
|
+
recoveredResult.progress.error = recoveredResult.error;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
persistResultMetadata(recoveredResult);
|
|
1314
|
+
options.onDetachedExit?.(recoveredResult);
|
|
1315
|
+
})().catch((error) => {
|
|
1316
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1317
|
+
recoveredResult.exitCode = 1;
|
|
1318
|
+
recoveredResult.error = recoveredResult.error ? `${recoveredResult.error}\nAcceptance evaluation failed: ${message}` : `Acceptance evaluation failed: ${message}`;
|
|
1319
|
+
options.onDetachedExit?.(recoveredResult);
|
|
1320
|
+
});
|
|
1321
|
+
},
|
|
1322
|
+
}
|
|
1323
|
+
: options;
|
|
1324
|
+
|
|
1103
1325
|
let lastResult: SingleResult | undefined;
|
|
1104
1326
|
const modelsToTry = candidates.length > 0 ? candidates : [undefined];
|
|
1105
1327
|
for (let i = 0; i < modelsToTry.length; i++) {
|
|
1106
1328
|
const candidate = modelsToTry[i];
|
|
1107
1329
|
const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
|
|
1108
|
-
const result = await runSingleAttempt(runtimeCwd, agent, taskWithAcceptance, candidate,
|
|
1330
|
+
const result = await runSingleAttempt(runtimeCwd, agent, taskWithAcceptance, candidate, detachedAwareOptions, {
|
|
1109
1331
|
sessionEnabled,
|
|
1110
1332
|
systemPrompt,
|
|
1111
1333
|
resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
|
|
@@ -1132,7 +1354,7 @@ export async function runSync(
|
|
|
1132
1354
|
usage: { ...result.usage },
|
|
1133
1355
|
};
|
|
1134
1356
|
modelAttempts.push(attempt);
|
|
1135
|
-
if (result.timedOut || result.turnBudgetExceeded) {
|
|
1357
|
+
if (result.detached || result.timedOut || result.turnBudgetExceeded) {
|
|
1136
1358
|
break;
|
|
1137
1359
|
}
|
|
1138
1360
|
if (attemptSucceeded) {
|
|
@@ -1176,27 +1398,6 @@ export async function runSync(
|
|
|
1176
1398
|
if (options.artifactConfig?.includeOutput !== false) {
|
|
1177
1399
|
writeArtifact(artifactPathsResult.outputPath, artifactOutputByResult.get(result) ?? result.finalOutput ?? "");
|
|
1178
1400
|
}
|
|
1179
|
-
if (options.artifactConfig?.includeMetadata !== false) {
|
|
1180
|
-
writeMetadata(artifactPathsResult.metadataPath, {
|
|
1181
|
-
runId: options.runId,
|
|
1182
|
-
agent: agentName,
|
|
1183
|
-
task,
|
|
1184
|
-
exitCode: result.exitCode,
|
|
1185
|
-
usage: result.usage,
|
|
1186
|
-
model: result.model,
|
|
1187
|
-
attemptedModels: result.attemptedModels,
|
|
1188
|
-
modelAttempts: result.modelAttempts,
|
|
1189
|
-
durationMs: result.progressSummary?.durationMs,
|
|
1190
|
-
toolCount: result.progressSummary?.toolCount,
|
|
1191
|
-
error: result.error,
|
|
1192
|
-
...(transcriptWriter ? { transcriptPath: artifactPathsResult.transcriptPath } : {}),
|
|
1193
|
-
transcriptError: result.transcriptError,
|
|
1194
|
-
skills: result.skills,
|
|
1195
|
-
skillsWarning: result.skillsWarning,
|
|
1196
|
-
timestamp: Date.now(),
|
|
1197
|
-
});
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
1401
|
if (options.maxOutput) {
|
|
1201
1402
|
const config = { ...DEFAULT_MAX_OUTPUT, ...options.maxOutput };
|
|
1202
1403
|
const truncationResult = truncateOutput(result.finalOutput ?? "", config, artifactPathsResult.outputPath);
|
|
@@ -1215,15 +1416,27 @@ export async function runSync(
|
|
|
1215
1416
|
if (sessionFile) result.sessionFile = sessionFile;
|
|
1216
1417
|
}
|
|
1217
1418
|
|
|
1218
|
-
|
|
1219
|
-
?
|
|
1220
|
-
:
|
|
1221
|
-
|
|
1222
|
-
|
|
1419
|
+
const childWrittenOutput = options.outputPath
|
|
1420
|
+
? extractChildWrittenOutput(result.messages, options.outputPath, options.cwd ?? runtimeCwd)
|
|
1421
|
+
: undefined;
|
|
1422
|
+
if (result.detached) {
|
|
1423
|
+
result.acceptance = buildPendingAcceptanceLedger(effectiveAcceptance);
|
|
1424
|
+
} else if (result.stopped) {
|
|
1425
|
+
result.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: "stopped", message: "Acceptance was not evaluated because the subagent was stopped." });
|
|
1426
|
+
} else if (result.timedOut) {
|
|
1427
|
+
result.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: "timeout", message: "Acceptance was not evaluated because the subagent timed out." });
|
|
1428
|
+
} else if (result.turnBudgetExceeded) {
|
|
1429
|
+
result.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: "turn-budget", message: "Acceptance was not evaluated because the subagent exceeded its turn budget." });
|
|
1430
|
+
} else {
|
|
1431
|
+
result.acceptance = await evaluateAcceptance({
|
|
1223
1432
|
acceptance: effectiveAcceptance,
|
|
1224
1433
|
output: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? "",
|
|
1434
|
+
fileOutput: childWrittenOutput !== undefined && options.outputPath
|
|
1435
|
+
? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === "file-only" }
|
|
1436
|
+
: undefined,
|
|
1225
1437
|
cwd: options.cwd ?? runtimeCwd,
|
|
1226
1438
|
});
|
|
1439
|
+
}
|
|
1227
1440
|
const acceptanceFailure = acceptanceFailureMessage(result.acceptance);
|
|
1228
1441
|
stripAcceptanceReportsFromMessages(result.messages);
|
|
1229
1442
|
if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted && !result.timedOut) {
|
|
@@ -1234,6 +1447,7 @@ export async function runSync(
|
|
|
1234
1447
|
result.progress.error = result.error;
|
|
1235
1448
|
}
|
|
1236
1449
|
}
|
|
1450
|
+
persistResultMetadata(result);
|
|
1237
1451
|
|
|
1238
1452
|
return result;
|
|
1239
1453
|
}
|