@selesai/code 0.5.20 → 0.5.21
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/dist/extensions/handoff-new.test.ts +8 -9
- package/dist/extensions/handoff-new.ts +6 -8
- package/dist/extensions/pi-subagents/CHANGELOG.md +1367 -0
- package/dist/extensions/pi-subagents/README.md +602 -220
- package/dist/extensions/pi-subagents/banner.png +0 -0
- package/dist/extensions/pi-subagents/index.ts +1 -0
- package/dist/extensions/pi-subagents/install.mjs +3 -3
- package/dist/extensions/pi-subagents/package-lock.json +3405 -0
- package/dist/extensions/pi-subagents/package.json +26 -14
- package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
- package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +3 -3
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +7 -7
- package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
- package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
- package/dist/extensions/pi-subagents/prompts/review-loop.md +13 -11
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +267 -176
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +89 -16
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +19 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +218 -120
- package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +67 -13
- package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
- package/dist/extensions/pi-subagents/src/agents/skills.ts +25 -12
- package/dist/extensions/pi-subagents/src/api/background-work.ts +197 -0
- package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +17 -0
- package/dist/extensions/pi-subagents/src/api/delegation.ts +285 -0
- package/dist/extensions/pi-subagents/src/api/preflight.ts +399 -0
- package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +165 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +7 -1
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +15 -0
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -1
- package/dist/extensions/pi-subagents/src/extension/index.ts +113 -157
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +41 -4
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +45 -15
- package/dist/extensions/pi-subagents/src/extension/steering-notices.ts +35 -0
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -15
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -3
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +225 -29
- package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +11 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -10
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +452 -62
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +60 -9
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +198 -52
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +119 -21
- package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +67 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +16 -8
- package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +6 -4
- package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +2 -11
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +260 -13
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +32 -6
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +171 -90
- package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +280 -0
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +149 -88
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +14 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +33 -17
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +9 -1
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +38 -10
- package/dist/extensions/pi-subagents/src/runs/background/steering.ts +237 -0
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +1297 -307
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +610 -0
- package/dist/extensions/pi-subagents/src/runs/background/top-level-async.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +36 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +26 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +230 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +22 -6
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +228 -140
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +452 -138
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +90 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1001 -416
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +443 -132
- package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +38 -0
- package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +178 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +121 -0
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +41 -93
- package/dist/extensions/pi-subagents/src/runs/shared/context-mode.ts +44 -0
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +11 -9
- package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +36 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +44 -7
- package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +4 -1
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +154 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +18 -0
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +174 -54
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +38 -31
- package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +90 -5
- package/dist/extensions/pi-subagents/src/runs/shared/session-lease.ts +299 -0
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +61 -6
- package/dist/extensions/pi-subagents/src/runs/shared/spawn-budget.ts +128 -0
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +112 -7
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +14 -6
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +149 -36
- package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +181 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +83 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +11 -5
- package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +50 -4
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +63 -14
- package/dist/extensions/pi-subagents/src/shared/accessible-dir.ts +25 -0
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +37 -7
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +18 -43
- package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +52 -0
- package/dist/extensions/pi-subagents/src/shared/env.ts +16 -0
- package/dist/extensions/pi-subagents/src/shared/file-system-retry.ts +47 -0
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +28 -3
- package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +123 -0
- package/dist/extensions/pi-subagents/src/shared/model-info.ts +8 -5
- package/dist/extensions/pi-subagents/src/shared/settings.ts +9 -1
- package/dist/extensions/pi-subagents/src/shared/status-format.ts +7 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +522 -51
- package/dist/extensions/pi-subagents/src/shared/utils.ts +59 -53
- package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +604 -0
- package/dist/extensions/pi-subagents/src/slash/delegation-json.ts +108 -0
- package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +249 -0
- package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +353 -345
- package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +2 -2
- package/dist/extensions/pi-subagents/src/slash/selector.ts +147 -0
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +265 -23
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +2 -2
- package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +428 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +362 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +472 -0
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +664 -0
- package/dist/extensions/pi-subagents/src/tui/render.ts +115 -31
- package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +220 -0
- package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +205 -0
- package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +123 -0
- package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +532 -0
- package/dist/extensions/pi-subagents/src/watchdog/model-selection.ts +167 -0
- package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +117 -0
- package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +433 -0
- package/dist/extensions/pi-subagents/src/watchdog/render.ts +54 -0
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +298 -0
- package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +714 -0
- package/dist/extensions/pi-subagents/src/watchdog/settings.ts +528 -0
- package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +155 -0
- package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +161 -0
- package/dist/extensions/pi-subagents/src/watchdog/types.ts +188 -0
- package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +73 -0
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +130 -2
- package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +439 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1632 -61
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +54 -1
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +112 -0
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +98 -1
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +179 -9
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +16 -9
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +6 -5
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +589 -9
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +528 -21
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +227 -3
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +235 -7
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +172 -2
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1502 -18
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +993 -4
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +7 -1
- package/dist/extensions/pi-subagents/test/support/helpers.ts +32 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +59 -2
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +32 -6
- package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +25 -24
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +59 -35
- package/dist/extensions/pi-subagents/test/support/session-lease-child.mjs +42 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +703 -69
- package/dist/extensions/pi-subagents/test/unit/accessible-dir.test.ts +82 -0
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
- package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +478 -19
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +228 -7
- package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +198 -78
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +15 -1
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +102 -7
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +439 -6
- package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +41 -1
- package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +79 -0
- package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +285 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +84 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +68 -0
- package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +326 -0
- package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +82 -0
- package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +46 -4
- package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +3 -1
- package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +9 -17
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +175 -3
- package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +10 -5
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +126 -6
- package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +219 -0
- package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +913 -0
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +13 -2
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +351 -0
- package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +280 -0
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +575 -0
- package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +65 -0
- package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +94 -1
- package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +23 -0
- package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +157 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +367 -5
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +11 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +36 -0
- package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +25 -3
- package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +341 -10
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +16 -0
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +52 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +168 -7
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +85 -7
- package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +175 -0
- package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +243 -16
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +79 -36
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +174 -56
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +322 -0
- package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +204 -0
- package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +20 -12
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +117 -4
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +40 -7
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +48 -1
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +21 -3
- package/dist/extensions/pi-subagents/test/unit/selector.test.ts +57 -0
- package/dist/extensions/pi-subagents/test/unit/session-lease.test.ts +326 -0
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +101 -2
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +111 -1
- package/dist/extensions/pi-subagents/test/unit/spawn-budget.test.ts +124 -0
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +6 -4
- package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +7 -0
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +397 -0
- package/dist/extensions/pi-subagents/test/unit/steering-notices.test.ts +58 -0
- package/dist/extensions/pi-subagents/test/unit/steering.test.ts +173 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +25 -4
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +390 -25
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +837 -0
- package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +86 -0
- package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +47 -1
- package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +56 -10
- package/dist/extensions/pi-subagents/test/unit/watchdog-change-signature.test.ts +387 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +110 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-emission-guard.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +144 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-model-selection.test.ts +121 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +76 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +327 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +863 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +290 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +91 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-turn-delta.test.ts +130 -0
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +32 -4
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +5 -1
- package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +30 -0
- package/dist/extensions/question/batch.ts +39 -0
- package/dist/extensions/question/dialog-adapter.ts +23 -1
- package/dist/extensions/question/index.ts +237 -7
- package/dist/extensions/question/schemas.ts +28 -15
- package/dist/extensions/question/tests/batch.test.ts +26 -0
- package/dist/extensions/question/tests/ui-protocol.test.ts +23 -3
- package/dist/extensions/question/tui-adapter.ts +2 -3
- package/dist/extensions/question/types.ts +37 -0
- package/dist/extensions/question/ui-protocol.ts +3 -3
- package/dist/skills/batch-grill-me/SKILL.md +3 -1
- package/package.json +1 -1
|
@@ -4,10 +4,12 @@
|
|
|
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 {
|
|
10
11
|
ensureArtifactsDir,
|
|
12
|
+
formatOutputArtifactContent,
|
|
11
13
|
getArtifactPaths,
|
|
12
14
|
writeArtifact,
|
|
13
15
|
writeMetadata,
|
|
@@ -40,18 +42,22 @@ import {
|
|
|
40
42
|
getFinalOutput,
|
|
41
43
|
findLatestSessionFile,
|
|
42
44
|
detectSubagentError,
|
|
45
|
+
hasEmptyTerminalAssistantResponse,
|
|
43
46
|
extractToolArgsPreview,
|
|
44
47
|
extractTextFromContent,
|
|
45
48
|
} from "../../shared/utils.ts";
|
|
46
49
|
import { buildSkillInjection, resolveSkillsWithFallback } from "../../agents/skills.ts";
|
|
47
50
|
import { buildAgentMemoryInjection } from "../../agents/agent-memory.ts";
|
|
48
51
|
import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
|
|
49
|
-
import {
|
|
52
|
+
import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
|
|
50
53
|
import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
|
|
51
54
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
52
|
-
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
|
|
53
|
-
import {
|
|
54
|
-
import {
|
|
55
|
+
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
|
|
56
|
+
import { decodeSubagentCapabilityCeiling, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV } from "../shared/capability-ceiling.ts";
|
|
57
|
+
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
58
|
+
import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput } from "../shared/structured-output.ts";
|
|
59
|
+
import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
|
|
60
|
+
import { captureSingleOutputSnapshot, extractChildWrittenOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
55
61
|
import {
|
|
56
62
|
buildModelCandidates,
|
|
57
63
|
formatModelAttemptNote,
|
|
@@ -68,9 +74,20 @@ import {
|
|
|
68
74
|
shouldEscalateMutatingFailures,
|
|
69
75
|
summarizeRecentMutatingFailures,
|
|
70
76
|
} from "../shared/long-running-guard.ts";
|
|
71
|
-
import { acceptanceFailureMessage, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
72
|
-
import {
|
|
77
|
+
import { acceptanceFailureMessage, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
78
|
+
import { attachContractProjections, isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
79
|
+
import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, turnBudgetDecision, turnBudgetDeferredNote, turnBudgetDeferredState, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from "../shared/turn-budget.ts";
|
|
73
80
|
import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
|
|
81
|
+
import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
|
|
82
|
+
import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
83
|
+
import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, projectChildLifecycle, type ChildLifecycleAction, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
|
|
84
|
+
import {
|
|
85
|
+
acceptChildWatchdogEvent,
|
|
86
|
+
childWatchdogIsActive,
|
|
87
|
+
isChildWatchdogStatusEvent,
|
|
88
|
+
resolveChildWatchdogConfig,
|
|
89
|
+
type ChildWatchdogStateSnapshot,
|
|
90
|
+
} from "../../watchdog/child-status.ts";
|
|
74
91
|
|
|
75
92
|
const artifactOutputByResult = new WeakMap<SingleResult, string>();
|
|
76
93
|
const acceptanceOutputByResult = new WeakMap<SingleResult, string>();
|
|
@@ -79,6 +96,12 @@ function emptyUsage(): Usage {
|
|
|
79
96
|
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
|
|
80
97
|
}
|
|
81
98
|
|
|
99
|
+
function withRunContext<T extends SingleResult>(result: T, context: RunSyncOptions["context"]): T {
|
|
100
|
+
if (!context) return result;
|
|
101
|
+
result.context = context;
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
82
105
|
function sumUsage(target: Usage, source: Usage): void {
|
|
83
106
|
target.input += source.input;
|
|
84
107
|
target.output += source.output;
|
|
@@ -102,16 +125,15 @@ function resolveAttemptTimeout(options: RunSyncOptions): { timeoutMs: number; re
|
|
|
102
125
|
};
|
|
103
126
|
}
|
|
104
127
|
|
|
105
|
-
function
|
|
128
|
+
function buildPendingAcceptanceLedger(acceptance: ResolvedAcceptanceConfig): AcceptanceLedger {
|
|
106
129
|
return {
|
|
107
|
-
status:
|
|
130
|
+
status: "pending",
|
|
131
|
+
evidenceStatus: "pending",
|
|
108
132
|
explicit: acceptance.explicit,
|
|
109
133
|
effectiveAcceptance: acceptance,
|
|
110
134
|
inferredReason: acceptance.inferredReason,
|
|
111
135
|
criteria: acceptance.criteria,
|
|
112
|
-
runtimeChecks:
|
|
113
|
-
? []
|
|
114
|
-
: [{ id: input.id, status: "failed", message: input.message }],
|
|
136
|
+
runtimeChecks: [],
|
|
115
137
|
verifyRuns: [],
|
|
116
138
|
};
|
|
117
139
|
}
|
|
@@ -176,6 +198,7 @@ async function runSingleAttempt(
|
|
|
176
198
|
sessionEnabled: boolean;
|
|
177
199
|
systemPrompt: string;
|
|
178
200
|
resolvedSkillNames?: string[];
|
|
201
|
+
modelCandidates?: string[];
|
|
179
202
|
skillsWarning?: string;
|
|
180
203
|
jsonlPath?: string;
|
|
181
204
|
artifactPaths?: ArtifactPaths;
|
|
@@ -187,7 +210,17 @@ async function runSingleAttempt(
|
|
|
187
210
|
): Promise<SingleResult> {
|
|
188
211
|
const effectiveThinking = options.thinkingOverride ?? agent.thinking;
|
|
189
212
|
const modelArg = applyThinkingSuffix(model, effectiveThinking, options.thinkingOverride !== undefined);
|
|
190
|
-
const
|
|
213
|
+
const resolvedThinking = resolveEffectiveThinking(modelArg, effectiveThinking);
|
|
214
|
+
const watchdogConfig = resolveWatchdogConfig(options.cwd ?? runtimeCwd);
|
|
215
|
+
const childWatchdog = watchdogConfig.ok
|
|
216
|
+
? resolveChildWatchdogConfig({
|
|
217
|
+
config: watchdogConfig.config,
|
|
218
|
+
agent: agent.name,
|
|
219
|
+
runId: options.runId,
|
|
220
|
+
childIndex: options.index ?? 0,
|
|
221
|
+
})
|
|
222
|
+
: undefined;
|
|
223
|
+
const { args, env: sharedEnv, tempDir, toolDiagnosticPath, capabilityAudit } = buildPiArgs({
|
|
191
224
|
baseArgs: ["--offline", "--mode", "json", "-p"],
|
|
192
225
|
task,
|
|
193
226
|
sessionEnabled: shared.sessionEnabled,
|
|
@@ -218,22 +251,61 @@ async function runSingleAttempt(
|
|
|
218
251
|
parentSessionId: options.parentSessionId,
|
|
219
252
|
structuredOutput: options.structuredOutput,
|
|
220
253
|
toolBudget: options.toolBudget,
|
|
254
|
+
allowZeroToolBudget: options.allowZeroToolBudget,
|
|
255
|
+
childWatchdog,
|
|
256
|
+
waitToolEnabled: options.waitToolEnabled,
|
|
257
|
+
capabilityCeiling: options.capabilityCeiling,
|
|
221
258
|
});
|
|
222
259
|
|
|
223
|
-
const
|
|
260
|
+
const effectiveSystemPrompt = appendTurnBudgetSystemPrompt(shared.systemPrompt, options.turnBudget);
|
|
261
|
+
const toolPlan = resolvePiLaunchToolPlan({
|
|
262
|
+
tools: agent.tools,
|
|
263
|
+
extensions: agent.extensions,
|
|
264
|
+
subagentOnlyExtensions: agent.subagentOnlyExtensions,
|
|
265
|
+
mcpDirectTools: agent.mcpDirectTools,
|
|
266
|
+
cwd: options.cwd ?? runtimeCwd,
|
|
267
|
+
requireReadTool: Boolean(shared.resolvedSkillNames?.length),
|
|
268
|
+
structuredOutput: Boolean(options.structuredOutput),
|
|
269
|
+
capabilityCeiling: options.capabilityCeiling,
|
|
270
|
+
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
271
|
+
});
|
|
272
|
+
const launchContractDigest = launchBindingDigest({
|
|
273
|
+
definitionDigest: agentDefinitionDigest(agent),
|
|
274
|
+
task: shared.originalTask ?? task,
|
|
275
|
+
...(modelArg ? { model: modelArg } : {}),
|
|
276
|
+
modelCandidates: shared.modelCandidates,
|
|
277
|
+
...(resolvedThinking ? { thinking: resolvedThinking } : {}),
|
|
278
|
+
systemPrompt: effectiveSystemPrompt,
|
|
279
|
+
systemPromptMode: agent.systemPromptMode,
|
|
280
|
+
inheritProjectContext: agent.inheritProjectContext,
|
|
281
|
+
inheritSkills: agent.inheritSkills,
|
|
282
|
+
skills: shared.resolvedSkillNames ?? [],
|
|
283
|
+
tools: toolPlan.effectiveToolAllowlist,
|
|
284
|
+
extensions: toolPlan.extensionArgs,
|
|
285
|
+
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
286
|
+
...(options.outputPath ? { outputPath: options.outputPath } : {}),
|
|
287
|
+
outputMode: options.outputMode ?? "inline",
|
|
288
|
+
...(options.structuredOutput ? { structuredOutputSchema: options.structuredOutput.schema } : {}),
|
|
289
|
+
});
|
|
290
|
+
const result: SingleResult = withRunContext({
|
|
224
291
|
agent: agent.name,
|
|
225
292
|
task: shared.originalTask ?? task,
|
|
293
|
+
...(options.agentContract ? { agentContract: options.agentContract } : {}),
|
|
294
|
+
launchContractDigest,
|
|
226
295
|
exitCode: 0,
|
|
227
296
|
messages: [],
|
|
228
297
|
usage: emptyUsage(),
|
|
229
298
|
model: modelArg,
|
|
299
|
+
...(resolvedThinking ? { thinking: resolvedThinking } : {}),
|
|
230
300
|
artifactPaths: shared.artifactPaths,
|
|
231
301
|
transcriptPath: shared.transcriptWriter ? shared.artifactPaths?.transcriptPath : undefined,
|
|
232
302
|
skills: shared.resolvedSkillNames,
|
|
233
303
|
skillsWarning: shared.skillsWarning,
|
|
234
304
|
...(options.turnBudget ? { turnBudget: initialTurnBudgetState(options.turnBudget) } : {}),
|
|
235
305
|
...(options.toolBudget ? { toolBudget: initialToolBudgetState(options.toolBudget) } : {}),
|
|
236
|
-
|
|
306
|
+
...(options.capabilityCeiling ? { capabilityCeiling: options.capabilityCeiling } : {}),
|
|
307
|
+
...(capabilityAudit ? { capabilityAudit } : {}),
|
|
308
|
+
}, options.context);
|
|
237
309
|
const startTime = Date.now();
|
|
238
310
|
if (options.structuredOutput) {
|
|
239
311
|
try {
|
|
@@ -271,6 +343,7 @@ async function runSingleAttempt(
|
|
|
271
343
|
result.progress = progress;
|
|
272
344
|
const attemptTimeout = resolveAttemptTimeout(options);
|
|
273
345
|
if (attemptTimeout?.remainingMs === 0) {
|
|
346
|
+
cleanupTempDir(tempDir);
|
|
274
347
|
result.exitCode = 1;
|
|
275
348
|
result.timedOut = true;
|
|
276
349
|
result.error = attemptTimeout.message;
|
|
@@ -286,9 +359,10 @@ async function runSingleAttempt(
|
|
|
286
359
|
}
|
|
287
360
|
const spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth) };
|
|
288
361
|
let observedMutationAttempt = false;
|
|
362
|
+
let structuredOutputToolInvoked = false;
|
|
289
363
|
|
|
290
364
|
const exitCode = await new Promise<number>((resolve) => {
|
|
291
|
-
const spawnSpec =
|
|
365
|
+
const spawnSpec = getPiSpawnCommand(args);
|
|
292
366
|
const proc = spawn(spawnSpec.command, spawnSpec.args, {
|
|
293
367
|
cwd: options.cwd ?? runtimeCwd,
|
|
294
368
|
env: spawnEnv,
|
|
@@ -296,7 +370,6 @@ async function runSingleAttempt(
|
|
|
296
370
|
windowsHide: true,
|
|
297
371
|
});
|
|
298
372
|
const jsonlWriter = createJsonlWriter(shared.jsonlPath, proc.stdout);
|
|
299
|
-
let buf = "";
|
|
300
373
|
let processClosed = false;
|
|
301
374
|
let settled = false;
|
|
302
375
|
let detached = false;
|
|
@@ -311,6 +384,7 @@ async function runSingleAttempt(
|
|
|
311
384
|
let turnBudgetSoftReached = false;
|
|
312
385
|
let turnBudgetTerminationTimer: NodeJS.Timeout | undefined;
|
|
313
386
|
let turnBudgetHardKillTimer: NodeJS.Timeout | undefined;
|
|
387
|
+
let protocolHardKillTimer: NodeJS.Timeout | undefined;
|
|
314
388
|
const clearTurnBudgetTimers = () => {
|
|
315
389
|
if (turnBudgetTerminationTimer) {
|
|
316
390
|
clearTimeout(turnBudgetTerminationTimer);
|
|
@@ -358,8 +432,22 @@ async function runSingleAttempt(
|
|
|
358
432
|
let childExited = false;
|
|
359
433
|
let forcedTerminationSignal = false;
|
|
360
434
|
let cleanTerminalAssistantStopReceived = false;
|
|
435
|
+
let agentSettledReceived = false;
|
|
361
436
|
let finalDrainTimer: NodeJS.Timeout | undefined;
|
|
362
437
|
let finalHardKillTimer: NodeJS.Timeout | undefined;
|
|
438
|
+
let watchdogTailTimer: NodeJS.Timeout | undefined;
|
|
439
|
+
let childWatchdogState: ChildWatchdogStateSnapshot | undefined;
|
|
440
|
+
const updateChildWatchdogState = (snapshot: ChildWatchdogStateSnapshot): void => {
|
|
441
|
+
childWatchdogState = snapshot;
|
|
442
|
+
result.watchdog = snapshot;
|
|
443
|
+
progress.watchdog = snapshot;
|
|
444
|
+
};
|
|
445
|
+
const clearWatchdogTailTimer = () => {
|
|
446
|
+
if (watchdogTailTimer) {
|
|
447
|
+
clearTimeout(watchdogTailTimer);
|
|
448
|
+
watchdogTailTimer = undefined;
|
|
449
|
+
}
|
|
450
|
+
};
|
|
363
451
|
const clearFinalDrainTimers = () => {
|
|
364
452
|
if (finalDrainTimer) {
|
|
365
453
|
clearTimeout(finalDrainTimer);
|
|
@@ -371,14 +459,18 @@ async function runSingleAttempt(
|
|
|
371
459
|
}
|
|
372
460
|
};
|
|
373
461
|
const startFinalDrain = () => {
|
|
462
|
+
if (childWatchdogIsActive(childWatchdogState)) {
|
|
463
|
+
armWatchdogTail();
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
374
466
|
if (childExited || finalDrainTimer || settled || processClosed || detached) return;
|
|
375
467
|
finalDrainTimer = setTimeout(() => {
|
|
376
468
|
if (settled || processClosed || detached) return;
|
|
377
469
|
const termSent = trySignalChild(proc, "SIGTERM");
|
|
378
470
|
if (!termSent) return;
|
|
379
471
|
forcedTerminationSignal = true;
|
|
380
|
-
if (!cleanTerminalAssistantStopReceived && !assistantError) {
|
|
381
|
-
result.error = result.error ?? `Subagent process did not exit within ${FINAL_STOP_GRACE_MS}ms after its
|
|
472
|
+
if (!cleanTerminalAssistantStopReceived && !agentSettledReceived && !assistantError) {
|
|
473
|
+
result.error = result.error ?? `Subagent process did not exit within ${FINAL_STOP_GRACE_MS}ms after its terminal event. Forcing termination.`;
|
|
382
474
|
}
|
|
383
475
|
finalHardKillTimer = setTimeout(() => {
|
|
384
476
|
if (settled || processClosed || detached) return;
|
|
@@ -388,13 +480,45 @@ async function runSingleAttempt(
|
|
|
388
480
|
}, FINAL_STOP_GRACE_MS);
|
|
389
481
|
finalDrainTimer.unref?.();
|
|
390
482
|
};
|
|
483
|
+
function armWatchdogTail(): void {
|
|
484
|
+
if ((!cleanTerminalAssistantStopReceived && !agentSettledReceived) || watchdogTailTimer || settled || processClosed || detached) return;
|
|
485
|
+
watchdogTailTimer = setTimeout(() => {
|
|
486
|
+
watchdogTailTimer = undefined;
|
|
487
|
+
updateChildWatchdogState({
|
|
488
|
+
phase: "stale",
|
|
489
|
+
seq: (childWatchdogState?.seq ?? 0) + 1,
|
|
490
|
+
lastUpdate: Date.now(),
|
|
491
|
+
followUpPending: false,
|
|
492
|
+
reason: "child watchdog tail timeout",
|
|
493
|
+
timedOut: true,
|
|
494
|
+
});
|
|
495
|
+
startFinalDrain();
|
|
496
|
+
fireUpdate();
|
|
497
|
+
}, childWatchdog?.watchdogTailTimeoutMs ?? 120_000);
|
|
498
|
+
watchdogTailTimer.unref?.();
|
|
499
|
+
}
|
|
500
|
+
const applyChildLifecycle = (action: ChildLifecycleAction): void => {
|
|
501
|
+
if (action === "cancel-drain") {
|
|
502
|
+
clearFinalDrainTimers();
|
|
503
|
+
clearWatchdogTailTimer();
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
if (action === "start-drain") startFinalDrain();
|
|
507
|
+
};
|
|
391
508
|
|
|
392
509
|
const unsubscribeIntercomDetach = options.intercomEvents?.on?.(INTERCOM_DETACH_REQUEST_EVENT, (payload) => {
|
|
393
|
-
if (!options.allowIntercomDetach || detached || processClosed
|
|
510
|
+
if (!options.allowIntercomDetach || detached || processClosed) return;
|
|
394
511
|
if (!payload || typeof payload !== "object") return;
|
|
395
|
-
const
|
|
512
|
+
const event = payload as { requestId?: unknown; runId?: unknown; agent?: unknown; childIndex?: unknown };
|
|
513
|
+
const requestId = event.requestId;
|
|
396
514
|
if (typeof requestId !== "string" || requestId.length === 0) return;
|
|
397
|
-
|
|
515
|
+
const hasRoute = event.runId !== undefined || event.agent !== undefined || event.childIndex !== undefined;
|
|
516
|
+
if (hasRoute) {
|
|
517
|
+
if (typeof event.runId === "string" && event.runId !== options.runId) return;
|
|
518
|
+
if (typeof event.agent === "string" && event.agent !== agent.name) return;
|
|
519
|
+
if (typeof event.childIndex === "number" && event.childIndex !== (options.index ?? 0)) return;
|
|
520
|
+
} else if (!intercomStarted) return;
|
|
521
|
+
options.intercomEvents?.emit(INTERCOM_DETACH_RESPONSE_EVENT, { requestId, accepted: true, runId: options.runId, agent: agent.name, childIndex: options.index ?? 0 });
|
|
398
522
|
detachForIntercom();
|
|
399
523
|
});
|
|
400
524
|
|
|
@@ -402,9 +526,14 @@ async function runSingleAttempt(
|
|
|
402
526
|
if (settled) return;
|
|
403
527
|
settled = true;
|
|
404
528
|
clearFinalDrainTimers();
|
|
529
|
+
clearWatchdogTailTimer();
|
|
405
530
|
clearStdioGuard();
|
|
406
531
|
clearTimeoutTimers();
|
|
407
532
|
clearTurnBudgetTimers();
|
|
533
|
+
if (protocolHardKillTimer) {
|
|
534
|
+
clearTimeout(protocolHardKillTimer);
|
|
535
|
+
protocolHardKillTimer = undefined;
|
|
536
|
+
}
|
|
408
537
|
if (activityTimer) {
|
|
409
538
|
clearInterval(activityTimer);
|
|
410
539
|
activityTimer = undefined;
|
|
@@ -504,7 +633,7 @@ async function runSingleAttempt(
|
|
|
504
633
|
turnBudgetHardKillTimer.unref?.();
|
|
505
634
|
};
|
|
506
635
|
|
|
507
|
-
const updateTurnBudget = (turnCount: number, terminalAssistantStop: boolean) => {
|
|
636
|
+
const updateTurnBudget = (turnCount: number, terminalAssistantStop: boolean, toolWorkActiveOrStarting: boolean) => {
|
|
508
637
|
const budget = options.turnBudget;
|
|
509
638
|
if (!budget || result.timedOut || result.turnBudgetExceeded) return;
|
|
510
639
|
if (turnCount < budget.maxTurns) {
|
|
@@ -516,10 +645,17 @@ async function runSingleAttempt(
|
|
|
516
645
|
result.wrapUpRequested = true;
|
|
517
646
|
appendRecentOutput(progress, [turnBudgetSoftNote(budget, turnCount)]);
|
|
518
647
|
}
|
|
519
|
-
|
|
520
|
-
if (
|
|
521
|
-
|
|
648
|
+
const decision = turnBudgetDecision(budget, turnCount, terminalAssistantStop, toolWorkActiveOrStarting, options.enforceHardTurnLimit);
|
|
649
|
+
if (decision === "defer") {
|
|
650
|
+
result.turnBudget = turnBudgetDeferredState(
|
|
651
|
+
budget,
|
|
652
|
+
turnCount,
|
|
653
|
+
result.turnBudget?.terminationDeferredAtTurn,
|
|
654
|
+
);
|
|
655
|
+
return;
|
|
522
656
|
}
|
|
657
|
+
result.turnBudget = turnBudgetState(budget, turnCount, false);
|
|
658
|
+
if (decision === "abort") requestTurnBudgetAbort(turnCount);
|
|
523
659
|
};
|
|
524
660
|
|
|
525
661
|
const updateActivityState = (now: number): boolean => {
|
|
@@ -528,6 +664,7 @@ async function runSingleAttempt(
|
|
|
528
664
|
config: controlConfig,
|
|
529
665
|
startedAt: startTime,
|
|
530
666
|
lastActivityAt: progress.lastActivityAt,
|
|
667
|
+
currentTool: progress.currentTool,
|
|
531
668
|
now,
|
|
532
669
|
});
|
|
533
670
|
if (idleState === "needs_attention") {
|
|
@@ -562,22 +699,46 @@ async function runSingleAttempt(
|
|
|
562
699
|
const fireUpdate = () => {
|
|
563
700
|
if (!options.onUpdate || processClosed) return;
|
|
564
701
|
progress.durationMs = Date.now() - startTime;
|
|
565
|
-
const output = (result.timedOut || result.turnBudgetExceeded) && result.finalOutput ? result.finalOutput : getFinalOutput(result.messages);
|
|
702
|
+
const output = (result.timedOut || result.turnBudgetExceeded) && result.finalOutput ? result.finalOutput : getFinalOutput(result.messages ?? []);
|
|
566
703
|
emitUpdateSnapshot(output || "(running...)");
|
|
567
704
|
};
|
|
568
705
|
|
|
569
706
|
const processLine = (line: string) => {
|
|
570
707
|
if (!line.trim()) return;
|
|
571
708
|
jsonlWriter.writeLine(line);
|
|
572
|
-
let evt: { type?: string; message?: Message; toolName?: string; args?: unknown };
|
|
709
|
+
let evt: { type?: string; message?: Message; toolName?: string; args?: unknown; willRetry?: unknown };
|
|
573
710
|
try {
|
|
574
|
-
evt = JSON.parse(line) as { type?: string; message?: Message; toolName?: string; args?: unknown };
|
|
711
|
+
evt = JSON.parse(line) as { type?: string; message?: Message; toolName?: string; args?: unknown; willRetry?: unknown };
|
|
575
712
|
} catch {
|
|
576
713
|
shared.transcriptWriter?.writeStdoutLine(line);
|
|
577
714
|
// Non-JSON stdout lines are expected; only structured events are parsed.
|
|
578
715
|
return;
|
|
579
716
|
}
|
|
580
717
|
shared.transcriptWriter?.writeChildEvent(evt);
|
|
718
|
+
if (evt.type === "agent_settled") agentSettledReceived = true;
|
|
719
|
+
applyChildLifecycle(projectChildLifecycle(evt));
|
|
720
|
+
|
|
721
|
+
if (isChildWatchdogStatusEvent(evt)) {
|
|
722
|
+
if (!childWatchdog) return;
|
|
723
|
+
const next = acceptChildWatchdogEvent({
|
|
724
|
+
current: childWatchdogState,
|
|
725
|
+
event: evt,
|
|
726
|
+
runId: options.runId,
|
|
727
|
+
agent: agent.name,
|
|
728
|
+
childIndex: options.index ?? 0,
|
|
729
|
+
});
|
|
730
|
+
if (!next) return;
|
|
731
|
+
updateChildWatchdogState(next);
|
|
732
|
+
if (childWatchdogIsActive(next)) {
|
|
733
|
+
clearFinalDrainTimers();
|
|
734
|
+
armWatchdogTail();
|
|
735
|
+
} else {
|
|
736
|
+
clearWatchdogTailTimer();
|
|
737
|
+
if (cleanTerminalAssistantStopReceived || agentSettledReceived) startFinalDrain();
|
|
738
|
+
}
|
|
739
|
+
fireUpdate();
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
581
742
|
|
|
582
743
|
const now = Date.now();
|
|
583
744
|
progress.durationMs = now - startTime;
|
|
@@ -588,11 +749,9 @@ async function runSingleAttempt(
|
|
|
588
749
|
const toolArgs = evt.args && typeof evt.args === "object" && !Array.isArray(evt.args)
|
|
589
750
|
? evt.args as Record<string, unknown>
|
|
590
751
|
: {};
|
|
591
|
-
|
|
752
|
+
if (options.structuredOutput && evt.toolName === "structured_output") structuredOutputToolInvoked = true;
|
|
592
753
|
if (options.allowIntercomDetach && (evt.toolName === "intercom" || evt.toolName === "contact_supervisor")) {
|
|
593
754
|
intercomStarted = true;
|
|
594
|
-
shouldDetachForBlockingIntercom = (evt.toolName === "intercom" && toolArgs.action === "ask")
|
|
595
|
-
|| (evt.toolName === "contact_supervisor" && (toolArgs.reason === "need_decision" || toolArgs.reason === "interview_request"));
|
|
596
755
|
}
|
|
597
756
|
progress.toolCount++;
|
|
598
757
|
if (options.toolBudget) {
|
|
@@ -606,9 +765,6 @@ async function runSingleAttempt(
|
|
|
606
765
|
observedMutationAttempt = observedMutationAttempt || mutates;
|
|
607
766
|
pendingToolResult = { tool: evt.toolName ?? "tool", path: progress.currentPath, mutates, startedAt: now };
|
|
608
767
|
fireUpdate();
|
|
609
|
-
if (shouldDetachForBlockingIntercom && !detached && !processClosed) {
|
|
610
|
-
detachForIntercom();
|
|
611
|
-
}
|
|
612
768
|
}
|
|
613
769
|
|
|
614
770
|
if (evt.type === "tool_execution_end") {
|
|
@@ -627,15 +783,20 @@ async function runSingleAttempt(
|
|
|
627
783
|
}
|
|
628
784
|
|
|
629
785
|
if (evt.type === "message_end" && evt.message) {
|
|
630
|
-
result.messages
|
|
786
|
+
result.messages!.push(evt.message);
|
|
631
787
|
if (evt.message.role === "assistant") {
|
|
632
788
|
result.usage.turns++;
|
|
633
789
|
progress.turnCount = result.usage.turns;
|
|
634
790
|
const stopReason = (evt.message as { stopReason?: string }).stopReason;
|
|
635
|
-
const
|
|
636
|
-
|
|
791
|
+
const toolCalls = Array.isArray(evt.message.content)
|
|
792
|
+
? evt.message.content.filter((part) => (part as { type?: string }).type === "toolCall")
|
|
793
|
+
: [];
|
|
794
|
+
const hasToolCall = toolCalls.length > 0;
|
|
637
795
|
const terminalAssistantStop = stopReason === "stop" && !hasToolCall;
|
|
638
|
-
|
|
796
|
+
const terminalStructuredOutputCall = Boolean(options.structuredOutput)
|
|
797
|
+
&& toolCalls.length === 1
|
|
798
|
+
&& (toolCalls[0] as { name?: string }).name === "structured_output";
|
|
799
|
+
updateTurnBudget(result.usage.turns, terminalAssistantStop || terminalStructuredOutputCall, hasToolCall || Boolean(progress.currentTool));
|
|
639
800
|
const u = evt.message.usage;
|
|
640
801
|
if (u) {
|
|
641
802
|
result.usage.input += u.input || 0;
|
|
@@ -653,7 +814,7 @@ async function runSingleAttempt(
|
|
|
653
814
|
if (terminalAssistantStop) {
|
|
654
815
|
if (!evt.message.errorMessage && assistantText.trim()) assistantError = undefined;
|
|
655
816
|
cleanTerminalAssistantStopReceived ||= !evt.message.errorMessage;
|
|
656
|
-
|
|
817
|
+
applyChildLifecycle(projectChildLifecycle(evt, true));
|
|
657
818
|
}
|
|
658
819
|
}
|
|
659
820
|
updateActivityState(now);
|
|
@@ -661,7 +822,7 @@ async function runSingleAttempt(
|
|
|
661
822
|
}
|
|
662
823
|
|
|
663
824
|
if (evt.type === "tool_result_end" && evt.message) {
|
|
664
|
-
result.messages
|
|
825
|
+
result.messages!.push(evt.message);
|
|
665
826
|
const resultText = extractTextFromContent(evt.message.content);
|
|
666
827
|
if (options.toolBudget && pendingToolResult && resultText.includes("Tool budget hard limit reached")) {
|
|
667
828
|
result.toolBudgetBlocked = true;
|
|
@@ -731,17 +892,36 @@ async function runSingleAttempt(
|
|
|
731
892
|
timeoutTimer.unref?.();
|
|
732
893
|
}
|
|
733
894
|
|
|
734
|
-
|
|
895
|
+
const stderrTail = createBoundedByteTail();
|
|
896
|
+
const failProtocol = (limit: ProtocolOutputLimit): void => {
|
|
897
|
+
if (result.protocolError) return;
|
|
898
|
+
result.protocolError = limit;
|
|
899
|
+
result.error = formatProtocolOutputLimit(limit);
|
|
900
|
+
progress.status = "failed";
|
|
901
|
+
progress.error = result.error;
|
|
902
|
+
progress.durationMs = Date.now() - startTime;
|
|
903
|
+
fireUpdate();
|
|
904
|
+
if (!childExited) {
|
|
905
|
+
trySignalChild(proc, "SIGTERM");
|
|
906
|
+
protocolHardKillTimer = setTimeout(() => {
|
|
907
|
+
if (!childExited) trySignalChild(proc, "SIGKILL");
|
|
908
|
+
}, 3000);
|
|
909
|
+
protocolHardKillTimer.unref?.();
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
const stdoutReader = createBoundedLineReader({ onLine: processLine, onLimit: failProtocol });
|
|
913
|
+
const stderrReader = createBoundedLineReader({
|
|
914
|
+
stream: "stderr",
|
|
915
|
+
maxPendingLineBytes: MAX_CHILD_STDERR_BYTES,
|
|
916
|
+
onLine: (line) => shared.transcriptWriter?.writeStderrLine(line),
|
|
917
|
+
onLimit: (limit) => shared.transcriptWriter?.writeStderrLine(formatProtocolOutputLimit(limit)),
|
|
918
|
+
});
|
|
735
919
|
|
|
736
920
|
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();
|
|
921
|
+
proc.stdout.on("data", (chunk: Buffer) => stdoutReader.push(chunk));
|
|
922
|
+
proc.stderr.on("data", (chunk: Buffer) => {
|
|
923
|
+
stderrTail.push(chunk);
|
|
924
|
+
stderrReader.push(chunk);
|
|
745
925
|
});
|
|
746
926
|
proc.on("exit", () => {
|
|
747
927
|
childExited = true;
|
|
@@ -753,38 +933,67 @@ async function runSingleAttempt(
|
|
|
753
933
|
void jsonlWriter.close().catch(() => {
|
|
754
934
|
// JSONL artifact flush is best effort.
|
|
755
935
|
});
|
|
936
|
+
const toolDiagnosticError = readChildToolDiagnosticError(toolDiagnosticPath);
|
|
756
937
|
cleanupTempDir(tempDir);
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
938
|
+
stdoutReader.end();
|
|
939
|
+
stderrReader.end();
|
|
940
|
+
const stderr = stderrTail.text();
|
|
941
|
+
let closeError = result.error ?? toolDiagnosticError ?? assistantError;
|
|
942
|
+
const forcedDrainAfterFinalSuccess = forcedTerminationSignal && (cleanTerminalAssistantStopReceived || agentSettledReceived) && !closeError;
|
|
943
|
+
if (code !== 0 && stderr.trim() && !closeError && !forcedDrainAfterFinalSuccess) {
|
|
944
|
+
closeError = stderr.trim();
|
|
763
945
|
}
|
|
764
946
|
const finalCode = forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (code ?? 1) : (code ?? 0);
|
|
765
947
|
if (detached) {
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
948
|
+
const recoveredProgress = snapshotProgress(progress);
|
|
949
|
+
const recoveredResult = snapshotResult(result, recoveredProgress);
|
|
950
|
+
if (!recoveredResult.error && closeError) recoveredResult.error = closeError;
|
|
951
|
+
recoveredResult.exitCode = recoveredResult.error && finalCode === 0 ? 1 : finalCode;
|
|
952
|
+
recoveredProgress.status = recoveredResult.exitCode === 0 ? "completed" : "failed";
|
|
953
|
+
recoveredProgress.durationMs = Date.now() - startTime;
|
|
954
|
+
if (recoveredResult.error) recoveredProgress.error = recoveredResult.error;
|
|
955
|
+
recoveredResult.progressSummary = {
|
|
956
|
+
toolCount: recoveredProgress.toolCount,
|
|
957
|
+
tokens: recoveredProgress.tokens,
|
|
958
|
+
durationMs: recoveredProgress.durationMs,
|
|
774
959
|
};
|
|
775
|
-
const
|
|
776
|
-
|
|
777
|
-
|
|
960
|
+
const acceptanceOutput = getFinalOutput(recoveredResult.messages ?? []).trim()
|
|
961
|
+
|| recoveredResult.error
|
|
962
|
+
|| recoveredResult.finalOutput
|
|
963
|
+
|| "Detached child exited without final output.";
|
|
964
|
+
acceptanceOutputByResult.set(recoveredResult, acceptanceOutput);
|
|
965
|
+
let fullOutput = stripAcceptanceReport(acceptanceOutput);
|
|
966
|
+
fullOutput = fullOutput.trim() || recoveredResult.error || recoveredResult.finalOutput || "Detached child exited without final output.";
|
|
967
|
+
recoveredResult.outputMode = options.outputMode ?? "inline";
|
|
968
|
+
if (options.outputPath && recoveredResult.exitCode === 0) {
|
|
969
|
+
const resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, shared.outputSnapshot);
|
|
970
|
+
fullOutput = stripAcceptanceReport(resolvedOutput.fullOutput);
|
|
971
|
+
recoveredResult.savedOutputPath = resolvedOutput.savedPath;
|
|
972
|
+
recoveredResult.outputSaveError = resolvedOutput.saveError;
|
|
973
|
+
if (resolvedOutput.savedPath) {
|
|
974
|
+
recoveredResult.outputReference = formatSavedOutputReference(resolvedOutput.savedPath, fullOutput);
|
|
975
|
+
} else {
|
|
976
|
+
recoveredResult.exitCode = 1;
|
|
977
|
+
recoveredResult.error = `Output file was not finalized after detached child exit: ${resolvedOutput.saveError ?? options.outputPath}`;
|
|
978
|
+
recoveredProgress.status = "failed";
|
|
979
|
+
recoveredProgress.error = recoveredResult.error;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
recoveredResult.finalOutput = options.outputMode === "file-only" && recoveredResult.savedOutputPath && recoveredResult.outputReference
|
|
983
|
+
? recoveredResult.outputReference.message
|
|
984
|
+
: fullOutput;
|
|
985
|
+
if (recoveredResult.artifactPaths && options.artifactConfig?.enabled !== false && options.artifactConfig?.includeOutput !== false) {
|
|
778
986
|
try {
|
|
779
|
-
writeArtifact(
|
|
987
|
+
writeArtifact(recoveredResult.artifactPaths.outputPath, fullOutput);
|
|
780
988
|
} catch {
|
|
781
989
|
// Detached children may outlive test/temp cleanup; recovered status is best-effort.
|
|
782
990
|
}
|
|
783
991
|
}
|
|
784
|
-
options.onDetachedExit?.(
|
|
992
|
+
options.onDetachedExit?.(recoveredResult);
|
|
785
993
|
finish(-2);
|
|
786
994
|
return;
|
|
787
995
|
}
|
|
996
|
+
if (!result.error && closeError) result.error = closeError;
|
|
788
997
|
processClosed = true;
|
|
789
998
|
finish(finalCode);
|
|
790
999
|
});
|
|
@@ -795,7 +1004,8 @@ async function runSingleAttempt(
|
|
|
795
1004
|
// JSONL artifact flush is best effort.
|
|
796
1005
|
});
|
|
797
1006
|
cleanupTempDir(tempDir);
|
|
798
|
-
|
|
1007
|
+
stdoutReader.end();
|
|
1008
|
+
stderrReader.end();
|
|
799
1009
|
if (!result.error) {
|
|
800
1010
|
result.error = error instanceof Error ? error.message : String(error);
|
|
801
1011
|
}
|
|
@@ -805,10 +1015,6 @@ async function runSingleAttempt(
|
|
|
805
1015
|
if (options.signal) {
|
|
806
1016
|
const kill = () => {
|
|
807
1017
|
if (processClosed || detached) return;
|
|
808
|
-
if (options.allowIntercomDetach && intercomStarted && !detached) {
|
|
809
|
-
detachForIntercom();
|
|
810
|
-
return;
|
|
811
|
-
}
|
|
812
1018
|
proc.kill("SIGTERM");
|
|
813
1019
|
setTimeout(() => !proc.killed && proc.kill("SIGKILL"), 3000);
|
|
814
1020
|
};
|
|
@@ -861,8 +1067,12 @@ async function runSingleAttempt(
|
|
|
861
1067
|
return result;
|
|
862
1068
|
}
|
|
863
1069
|
if (result.detached) {
|
|
864
|
-
result.exitCode =
|
|
865
|
-
result.finalOutput = "Detached for intercom coordination.";
|
|
1070
|
+
result.exitCode = -2;
|
|
1071
|
+
result.finalOutput = "Detached for intercom coordination before task completion.";
|
|
1072
|
+
result.outputMode = options.outputMode ?? "inline";
|
|
1073
|
+
if (options.outputPath) {
|
|
1074
|
+
result.outputSaveError = "Output file was not finalized because the subagent detached for intercom coordination.";
|
|
1075
|
+
}
|
|
866
1076
|
return result;
|
|
867
1077
|
}
|
|
868
1078
|
|
|
@@ -870,37 +1080,43 @@ async function runSingleAttempt(
|
|
|
870
1080
|
result.exitCode = 1;
|
|
871
1081
|
}
|
|
872
1082
|
if (result.exitCode === 0 && !result.error) {
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
result.exitCode = errInfo.exitCode ?? 1;
|
|
876
|
-
result.error = errInfo.details
|
|
877
|
-
? `${errInfo.errorType} failed (exit ${errInfo.exitCode}): ${errInfo.details}`
|
|
878
|
-
: `${errInfo.errorType} failed with exit code ${errInfo.exitCode}`;
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
if (result.exitCode === 0 && !result.error) {
|
|
882
|
-
const finalText = getFinalOutput(result.messages);
|
|
1083
|
+
const messages = result.messages ?? [];
|
|
1084
|
+
const finalText = getFinalOutput(messages);
|
|
883
1085
|
const missingStructuredOutput = options.structuredOutput
|
|
884
1086
|
? !existsSync(options.structuredOutput.outputPath)
|
|
885
1087
|
: false;
|
|
886
|
-
|
|
1088
|
+
const errInfo = detectSubagentError(messages);
|
|
1089
|
+
const missingOutput = !finalText?.trim() && (!options.structuredOutput || missingStructuredOutput);
|
|
1090
|
+
if (missingOutput && (!errInfo.hasError || hasEmptyTerminalAssistantResponse(messages))) {
|
|
887
1091
|
result.exitCode = 1;
|
|
888
1092
|
result.error = "Subagent produced no output (possible model cold-start or empty response).";
|
|
1093
|
+
} else if (errInfo.hasError) {
|
|
1094
|
+
result.exitCode = errInfo.exitCode ?? 1;
|
|
1095
|
+
result.error = errInfo.details
|
|
1096
|
+
? `${errInfo.errorType} failed (exit ${errInfo.exitCode}): ${errInfo.details}`
|
|
1097
|
+
: `${errInfo.errorType} failed with exit code ${errInfo.exitCode}`;
|
|
889
1098
|
}
|
|
890
1099
|
}
|
|
891
1100
|
if (options.structuredOutput && result.exitCode === 0 && !result.error) {
|
|
892
|
-
const structured = readStructuredOutput({
|
|
893
|
-
schema: options.structuredOutput.schema,
|
|
894
|
-
schemaPath: options.structuredOutput.schemaPath,
|
|
895
|
-
outputPath: options.structuredOutput.outputPath,
|
|
896
|
-
});
|
|
897
1101
|
result.structuredOutputSchemaPath = options.structuredOutput.schemaPath;
|
|
898
1102
|
result.structuredOutputPath = options.structuredOutput.outputPath;
|
|
899
|
-
if (
|
|
1103
|
+
if (!structuredOutputToolInvoked) {
|
|
900
1104
|
result.exitCode = 1;
|
|
901
|
-
result.error =
|
|
1105
|
+
result.error = MISSING_STRUCTURED_OUTPUT_CALL_ERROR;
|
|
1106
|
+
result.structuredOutputFailed = true;
|
|
902
1107
|
} else {
|
|
903
|
-
|
|
1108
|
+
const structured = await readStructuredOutput({
|
|
1109
|
+
schema: options.structuredOutput.schema,
|
|
1110
|
+
schemaPath: options.structuredOutput.schemaPath,
|
|
1111
|
+
outputPath: options.structuredOutput.outputPath,
|
|
1112
|
+
});
|
|
1113
|
+
if (structured.error) {
|
|
1114
|
+
result.exitCode = 1;
|
|
1115
|
+
result.error = structured.error;
|
|
1116
|
+
result.structuredOutputFailed = true;
|
|
1117
|
+
} else {
|
|
1118
|
+
result.structuredOutput = structured.value;
|
|
1119
|
+
}
|
|
904
1120
|
}
|
|
905
1121
|
}
|
|
906
1122
|
|
|
@@ -919,7 +1135,7 @@ async function runSingleAttempt(
|
|
|
919
1135
|
durationMs: progress.durationMs,
|
|
920
1136
|
};
|
|
921
1137
|
|
|
922
|
-
const acceptanceOutput = getFinalOutput(result.messages);
|
|
1138
|
+
const acceptanceOutput = getFinalOutput(result.messages ?? []);
|
|
923
1139
|
let fullOutput = stripAcceptanceReport(acceptanceOutput);
|
|
924
1140
|
if (result.timedOut) {
|
|
925
1141
|
const timeoutMessage = formatTimeoutMessage(options.timeoutMs ?? 0);
|
|
@@ -928,20 +1144,36 @@ async function runSingleAttempt(
|
|
|
928
1144
|
: timeoutMessage;
|
|
929
1145
|
} else if (result.turnBudgetExceeded && result.turnBudget) {
|
|
930
1146
|
fullOutput = formatTurnBudgetOutput(turnBudgetExceededMessage(result.turnBudget, result.turnBudget.turnCount), fullOutput);
|
|
1147
|
+
} else if (result.turnBudget?.outcome === "termination-deferred") {
|
|
1148
|
+
const note = turnBudgetDeferredNote(result.turnBudget, result.turnBudget.terminationDeferredAtTurn ?? result.turnBudget.turnCount);
|
|
1149
|
+
fullOutput = fullOutput.trim() ? `${note}\n\n${fullOutput}` : note;
|
|
931
1150
|
} else if (result.wrapUpRequested && result.turnBudget?.outcome === "wrap-up-requested") {
|
|
932
1151
|
const note = turnBudgetSoftNote(result.turnBudget, result.turnBudget.wrapUpRequestedAtTurn ?? result.turnBudget.turnCount);
|
|
933
1152
|
fullOutput = fullOutput.trim() ? `${note}\n\n${fullOutput}` : note;
|
|
934
1153
|
}
|
|
935
|
-
const
|
|
1154
|
+
const completionGuardEnabled = isAgentContractV1(options.agentContract) ? agent.completionGuard === true : agent.completionGuard !== false;
|
|
1155
|
+
const completionGuard = result.exitCode === 0 && !result.error && completionGuardEnabled
|
|
936
1156
|
? evaluateCompletionMutationGuard({
|
|
937
1157
|
agent: agent.name,
|
|
938
1158
|
task: shared.originalTask ?? task,
|
|
939
|
-
messages: result.messages,
|
|
1159
|
+
messages: result.messages ?? [],
|
|
940
1160
|
tools: agent.tools,
|
|
941
1161
|
mcpDirectTools: agent.mcpDirectTools,
|
|
942
1162
|
})
|
|
943
1163
|
: undefined;
|
|
944
|
-
|
|
1164
|
+
const completionGuardTriggered = completionGuard?.triggered === true && !observedMutationAttempt;
|
|
1165
|
+
if (completionGuard) {
|
|
1166
|
+
result.effects = {
|
|
1167
|
+
...(result.effects ?? {}),
|
|
1168
|
+
fileMutation: {
|
|
1169
|
+
status: completionGuard.expectedMutation ? completionGuardTriggered ? "missing" : "observed" : "not-applicable",
|
|
1170
|
+
expected: completionGuard.expectedMutation,
|
|
1171
|
+
attempted: completionGuard.attemptedMutation || observedMutationAttempt,
|
|
1172
|
+
...(completionGuardTriggered ? { message: "Subagent completed without making edits for an implementation task." } : {}),
|
|
1173
|
+
},
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
if (completionGuardTriggered && !isAgentContractV1(options.agentContract)) {
|
|
945
1177
|
result.exitCode = 1;
|
|
946
1178
|
result.error = "Subagent completed without making edits for an implementation task.\nIt appears to have returned planning or scratchpad output instead of applying changes.";
|
|
947
1179
|
progress.status = "failed";
|
|
@@ -1000,20 +1232,24 @@ export async function runSync(
|
|
|
1000
1232
|
task: string,
|
|
1001
1233
|
options: RunSyncOptions,
|
|
1002
1234
|
): Promise<SingleResult> {
|
|
1235
|
+
options = {
|
|
1236
|
+
...options,
|
|
1237
|
+
capabilityCeiling: options.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(options.parentSessionId),
|
|
1238
|
+
};
|
|
1003
1239
|
const agent = agents.find((a) => a.name === agentName);
|
|
1004
1240
|
if (!agent) {
|
|
1005
|
-
return {
|
|
1241
|
+
return withRunContext({
|
|
1006
1242
|
agent: agentName,
|
|
1007
1243
|
task,
|
|
1008
1244
|
exitCode: 1,
|
|
1009
1245
|
messages: [],
|
|
1010
1246
|
usage: emptyUsage(),
|
|
1011
1247
|
error: `Unknown agent: ${agentName}`,
|
|
1012
|
-
};
|
|
1248
|
+
}, options.context);
|
|
1013
1249
|
}
|
|
1014
1250
|
const outputModeValidationError = validateFileOnlyOutputMode(options.outputMode, options.outputPath, `Single run (${agentName})`);
|
|
1015
1251
|
if (outputModeValidationError) {
|
|
1016
|
-
return {
|
|
1252
|
+
return withRunContext({
|
|
1017
1253
|
agent: agentName,
|
|
1018
1254
|
task,
|
|
1019
1255
|
exitCode: 1,
|
|
@@ -1021,34 +1257,42 @@ export async function runSync(
|
|
|
1021
1257
|
usage: emptyUsage(),
|
|
1022
1258
|
outputMode: options.outputMode,
|
|
1023
1259
|
error: outputModeValidationError,
|
|
1024
|
-
};
|
|
1260
|
+
}, options.context);
|
|
1025
1261
|
}
|
|
1026
1262
|
|
|
1027
1263
|
const shareEnabled = options.share === true;
|
|
1028
1264
|
const effectiveAcceptance = resolveEffectiveAcceptance({
|
|
1029
1265
|
explicit: options.acceptance,
|
|
1030
1266
|
agentName,
|
|
1267
|
+
acceptanceRole: agent.acceptanceRole,
|
|
1031
1268
|
task,
|
|
1032
1269
|
mode: options.acceptanceContext?.mode ?? "single",
|
|
1033
1270
|
async: options.acceptanceContext?.async,
|
|
1034
1271
|
dynamic: options.acceptanceContext?.dynamic,
|
|
1035
1272
|
dynamicGroup: options.acceptanceContext?.dynamicGroup,
|
|
1273
|
+
agentContract: options.agentContract,
|
|
1036
1274
|
});
|
|
1037
|
-
const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance);
|
|
1275
|
+
const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance, { reportOptional: isAgentContractV1(options.agentContract) });
|
|
1038
1276
|
const taskWithAcceptance = acceptancePrompt ? `${task}\n${acceptancePrompt}` : task;
|
|
1039
1277
|
const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
|
|
1040
1278
|
const skillNames = options.skills ?? agent.skills ?? [];
|
|
1041
1279
|
const skillCwd = options.cwd ?? runtimeCwd;
|
|
1042
|
-
const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(
|
|
1280
|
+
const { resolved: resolvedSkills, missing: missingSkills } = resolveSkillsWithFallback(
|
|
1281
|
+
skillNames,
|
|
1282
|
+
skillCwd,
|
|
1283
|
+
runtimeCwd,
|
|
1284
|
+
agent.skillPath,
|
|
1285
|
+
agent.filePath ? path.dirname(agent.filePath) : skillCwd,
|
|
1286
|
+
);
|
|
1043
1287
|
if (skillNames.some((skill) => skill.trim() === "pi-subagents") && missingSkills.includes("pi-subagents")) {
|
|
1044
|
-
return {
|
|
1288
|
+
return withRunContext({
|
|
1045
1289
|
agent: agentName,
|
|
1046
1290
|
task,
|
|
1047
1291
|
exitCode: 1,
|
|
1048
1292
|
messages: [],
|
|
1049
1293
|
usage: emptyUsage(),
|
|
1050
1294
|
error: "Skills not found: pi-subagents",
|
|
1051
|
-
};
|
|
1295
|
+
}, options.context);
|
|
1052
1296
|
}
|
|
1053
1297
|
let systemPrompt = agent.systemPrompt?.trim() || "";
|
|
1054
1298
|
if (resolvedSkills.length > 0) {
|
|
@@ -1059,7 +1303,7 @@ export async function runSync(
|
|
|
1059
1303
|
if (memoryInjection) {
|
|
1060
1304
|
systemPrompt = systemPrompt ? `${systemPrompt}\n\n${memoryInjection}` : memoryInjection;
|
|
1061
1305
|
}
|
|
1062
|
-
systemPrompt = injectOutputPathSystemPrompt(systemPrompt, options.outputPath);
|
|
1306
|
+
systemPrompt = injectOutputPathSystemPrompt(systemPrompt, options.outputPath, agent);
|
|
1063
1307
|
|
|
1064
1308
|
const candidates = buildModelCandidates(
|
|
1065
1309
|
options.modelOverride ?? agent.model,
|
|
@@ -1100,12 +1344,82 @@ export async function runSync(
|
|
|
1100
1344
|
}
|
|
1101
1345
|
}
|
|
1102
1346
|
|
|
1347
|
+
const persistResultMetadata = (target: SingleResult): void => {
|
|
1348
|
+
if (!artifactPathsResult || options.artifactConfig?.enabled === false || options.artifactConfig?.includeMetadata === false) return;
|
|
1349
|
+
writeMetadata(artifactPathsResult.metadataPath, {
|
|
1350
|
+
runId: options.runId,
|
|
1351
|
+
agent: agentName,
|
|
1352
|
+
task,
|
|
1353
|
+
exitCode: target.exitCode,
|
|
1354
|
+
usage: target.usage,
|
|
1355
|
+
model: target.model,
|
|
1356
|
+
attemptedModels: target.attemptedModels,
|
|
1357
|
+
modelAttempts: target.modelAttempts,
|
|
1358
|
+
durationMs: target.progressSummary?.durationMs,
|
|
1359
|
+
toolCount: target.progressSummary?.toolCount,
|
|
1360
|
+
error: target.error,
|
|
1361
|
+
agentContract: target.agentContract,
|
|
1362
|
+
launchContractDigest: target.launchContractDigest,
|
|
1363
|
+
execution: target.execution,
|
|
1364
|
+
acceptance: target.acceptance,
|
|
1365
|
+
capabilityCeiling: target.capabilityCeiling,
|
|
1366
|
+
capabilityAudit: target.capabilityAudit,
|
|
1367
|
+
review: target.review,
|
|
1368
|
+
effects: target.effects,
|
|
1369
|
+
...(transcriptWriter ? { transcriptPath: artifactPathsResult.transcriptPath } : {}),
|
|
1370
|
+
transcriptError: target.transcriptError,
|
|
1371
|
+
skills: target.skills,
|
|
1372
|
+
skillsWarning: target.skillsWarning,
|
|
1373
|
+
timestamp: Date.now(),
|
|
1374
|
+
});
|
|
1375
|
+
};
|
|
1376
|
+
|
|
1377
|
+
const detachedAwareOptions: RunSyncOptions = options.onDetachedExit
|
|
1378
|
+
? {
|
|
1379
|
+
...options,
|
|
1380
|
+
onDetachedExit: (recoveredResult) => {
|
|
1381
|
+
void (async () => {
|
|
1382
|
+
const childWrittenOutput = options.outputPath
|
|
1383
|
+
? extractChildWrittenOutput(recoveredResult.messages, options.outputPath, options.cwd ?? runtimeCwd)
|
|
1384
|
+
: undefined;
|
|
1385
|
+
recoveredResult.acceptance = await evaluateAcceptance({
|
|
1386
|
+
acceptance: effectiveAcceptance,
|
|
1387
|
+
output: acceptanceOutputByResult.get(recoveredResult) ?? recoveredResult.finalOutput ?? "",
|
|
1388
|
+
fileOutput: childWrittenOutput !== undefined && options.outputPath
|
|
1389
|
+
? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === "file-only" }
|
|
1390
|
+
: undefined,
|
|
1391
|
+
cwd: options.cwd ?? runtimeCwd,
|
|
1392
|
+
reportOptional: isAgentContractV1(options.agentContract),
|
|
1393
|
+
});
|
|
1394
|
+
const acceptanceFailure = acceptanceFailureMessage(recoveredResult.acceptance);
|
|
1395
|
+
stripAcceptanceReportsFromMessages(recoveredResult.messages);
|
|
1396
|
+
if (acceptanceFailure && recoveredResult.acceptance.explicit && recoveredResult.exitCode === 0 && !isAgentContractV1(options.agentContract)) {
|
|
1397
|
+
recoveredResult.exitCode = 1;
|
|
1398
|
+
recoveredResult.error = recoveredResult.error ? `${recoveredResult.error}\n${acceptanceFailure}` : acceptanceFailure;
|
|
1399
|
+
if (recoveredResult.progress) {
|
|
1400
|
+
recoveredResult.progress.status = "failed";
|
|
1401
|
+
recoveredResult.progress.error = recoveredResult.error;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
if (isAgentContractV1(options.agentContract)) attachContractProjections(recoveredResult);
|
|
1405
|
+
persistResultMetadata(recoveredResult);
|
|
1406
|
+
options.onDetachedExit?.(recoveredResult);
|
|
1407
|
+
})().catch((error) => {
|
|
1408
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1409
|
+
recoveredResult.exitCode = 1;
|
|
1410
|
+
recoveredResult.error = recoveredResult.error ? `${recoveredResult.error}\nAcceptance evaluation failed: ${message}` : `Acceptance evaluation failed: ${message}`;
|
|
1411
|
+
options.onDetachedExit?.(recoveredResult);
|
|
1412
|
+
});
|
|
1413
|
+
},
|
|
1414
|
+
}
|
|
1415
|
+
: options;
|
|
1416
|
+
|
|
1103
1417
|
let lastResult: SingleResult | undefined;
|
|
1104
1418
|
const modelsToTry = candidates.length > 0 ? candidates : [undefined];
|
|
1105
1419
|
for (let i = 0; i < modelsToTry.length; i++) {
|
|
1106
1420
|
const candidate = modelsToTry[i];
|
|
1107
1421
|
const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
|
|
1108
|
-
const result = await runSingleAttempt(runtimeCwd, agent, taskWithAcceptance, candidate,
|
|
1422
|
+
const result = await runSingleAttempt(runtimeCwd, agent, taskWithAcceptance, candidate, detachedAwareOptions, {
|
|
1109
1423
|
sessionEnabled,
|
|
1110
1424
|
systemPrompt,
|
|
1111
1425
|
resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
|
|
@@ -1114,6 +1428,7 @@ export async function runSync(
|
|
|
1114
1428
|
artifactPaths: artifactPathsResult,
|
|
1115
1429
|
transcriptWriter,
|
|
1116
1430
|
attemptNotes,
|
|
1431
|
+
modelCandidates: candidates.map((candidate) => applyThinkingSuffix(candidate, options.thinkingOverride ?? agent.thinking, options.thinkingOverride !== undefined)),
|
|
1117
1432
|
outputSnapshot,
|
|
1118
1433
|
originalTask: task,
|
|
1119
1434
|
});
|
|
@@ -1132,7 +1447,7 @@ export async function runSync(
|
|
|
1132
1447
|
usage: { ...result.usage },
|
|
1133
1448
|
};
|
|
1134
1449
|
modelAttempts.push(attempt);
|
|
1135
|
-
if (result.timedOut || result.turnBudgetExceeded) {
|
|
1450
|
+
if (result.detached || result.timedOut || result.turnBudgetExceeded) {
|
|
1136
1451
|
break;
|
|
1137
1452
|
}
|
|
1138
1453
|
if (attemptSucceeded) {
|
|
@@ -1144,14 +1459,14 @@ export async function runSync(
|
|
|
1144
1459
|
attemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[i + 1]));
|
|
1145
1460
|
}
|
|
1146
1461
|
|
|
1147
|
-
const result = lastResult ?? {
|
|
1462
|
+
const result = withRunContext(lastResult ?? {
|
|
1148
1463
|
agent: agentName,
|
|
1149
1464
|
task,
|
|
1150
1465
|
exitCode: 1,
|
|
1151
1466
|
messages: [],
|
|
1152
1467
|
usage: emptyUsage(),
|
|
1153
1468
|
error: "Subagent did not produce a result.",
|
|
1154
|
-
} satisfies SingleResult;
|
|
1469
|
+
} satisfies SingleResult, options.context);
|
|
1155
1470
|
|
|
1156
1471
|
result.usage = aggregateUsage;
|
|
1157
1472
|
result.attemptedModels = attemptedModels.length > 0 ? attemptedModels : undefined;
|
|
@@ -1174,29 +1489,13 @@ export async function runSync(
|
|
|
1174
1489
|
if (artifactPathsResult && options.artifactConfig?.enabled !== false) {
|
|
1175
1490
|
result.artifactPaths = artifactPathsResult;
|
|
1176
1491
|
if (options.artifactConfig?.includeOutput !== false) {
|
|
1177
|
-
writeArtifact(artifactPathsResult.outputPath,
|
|
1178
|
-
|
|
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,
|
|
1492
|
+
writeArtifact(artifactPathsResult.outputPath, formatOutputArtifactContent({
|
|
1493
|
+
output: artifactOutputByResult.get(result) ?? result.finalOutput ?? "",
|
|
1191
1494
|
error: result.error,
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
skillsWarning: result.skillsWarning,
|
|
1196
|
-
timestamp: Date.now(),
|
|
1197
|
-
});
|
|
1495
|
+
transcriptPath: result.transcriptPath,
|
|
1496
|
+
metadataPath: options.artifactConfig?.includeMetadata === false ? undefined : artifactPathsResult.metadataPath,
|
|
1497
|
+
}));
|
|
1198
1498
|
}
|
|
1199
|
-
|
|
1200
1499
|
if (options.maxOutput) {
|
|
1201
1500
|
const config = { ...DEFAULT_MAX_OUTPUT, ...options.maxOutput };
|
|
1202
1501
|
const truncationResult = truncateOutput(result.finalOutput ?? "", config, artifactPathsResult.outputPath);
|
|
@@ -1215,18 +1514,31 @@ export async function runSync(
|
|
|
1215
1514
|
if (sessionFile) result.sessionFile = sessionFile;
|
|
1216
1515
|
}
|
|
1217
1516
|
|
|
1218
|
-
|
|
1219
|
-
?
|
|
1220
|
-
:
|
|
1221
|
-
|
|
1222
|
-
|
|
1517
|
+
const childWrittenOutput = options.outputPath
|
|
1518
|
+
? extractChildWrittenOutput(result.messages, options.outputPath, options.cwd ?? runtimeCwd)
|
|
1519
|
+
: undefined;
|
|
1520
|
+
if (result.detached) {
|
|
1521
|
+
result.acceptance = buildPendingAcceptanceLedger(effectiveAcceptance);
|
|
1522
|
+
} else if (result.stopped) {
|
|
1523
|
+
result.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: "stopped", message: "Acceptance was not evaluated because the subagent was stopped." });
|
|
1524
|
+
} else if (result.timedOut) {
|
|
1525
|
+
result.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: "timeout", message: "Acceptance was not evaluated because the subagent timed out." });
|
|
1526
|
+
} else if (result.turnBudgetExceeded) {
|
|
1527
|
+
result.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: "turn-budget", message: "Acceptance was not evaluated because the subagent exceeded its turn budget." });
|
|
1528
|
+
} else {
|
|
1529
|
+
result.acceptance = await evaluateAcceptance({
|
|
1223
1530
|
acceptance: effectiveAcceptance,
|
|
1224
1531
|
output: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? "",
|
|
1532
|
+
fileOutput: childWrittenOutput !== undefined && options.outputPath
|
|
1533
|
+
? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === "file-only" }
|
|
1534
|
+
: undefined,
|
|
1225
1535
|
cwd: options.cwd ?? runtimeCwd,
|
|
1536
|
+
reportOptional: isAgentContractV1(options.agentContract),
|
|
1226
1537
|
});
|
|
1538
|
+
}
|
|
1227
1539
|
const acceptanceFailure = acceptanceFailureMessage(result.acceptance);
|
|
1228
1540
|
stripAcceptanceReportsFromMessages(result.messages);
|
|
1229
|
-
if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted && !result.timedOut) {
|
|
1541
|
+
if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted && !result.timedOut && !isAgentContractV1(options.agentContract)) {
|
|
1230
1542
|
result.exitCode = 1;
|
|
1231
1543
|
result.error = result.error ? `${result.error}\n${acceptanceFailure}` : acceptanceFailure;
|
|
1232
1544
|
if (result.progress) {
|
|
@@ -1234,6 +1546,8 @@ export async function runSync(
|
|
|
1234
1546
|
result.progress.error = result.error;
|
|
1235
1547
|
}
|
|
1236
1548
|
}
|
|
1549
|
+
if (isAgentContractV1(options.agentContract)) attachContractProjections(result);
|
|
1550
|
+
persistResultMetadata(result);
|
|
1237
1551
|
|
|
1238
1552
|
return result;
|
|
1239
1553
|
}
|