@selesai/code 0.5.28 → 0.6.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 +24 -0
- package/README.md +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +9 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +1 -1
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/credential-print.d.ts +23 -0
- package/dist/cli/credential-print.d.ts.map +1 -0
- package/dist/cli/credential-print.js +117 -0
- package/dist/cli/credential-print.js.map +1 -0
- package/dist/cli/startup-ui.d.ts.map +1 -1
- package/dist/cli/startup-ui.js +1 -1
- package/dist/cli/startup-ui.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +3 -0
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session.d.ts +12 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +20 -14
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +11 -3
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +11 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +14 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +10 -0
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +1 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/llama/provider.d.ts.map +1 -1
- package/dist/core/llama/provider.js +8 -3
- package/dist/core/llama/provider.js.map +1 -1
- package/dist/core/model-config.d.ts +30 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +6 -0
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +2 -2
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts +1 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +20 -3
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts +2 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +5 -4
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +13 -6
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +24 -11
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/resource-loader.d.ts +15 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +66 -9
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +19 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/system-prompt.test.d.ts +2 -0
- package/dist/core/system-prompt.test.d.ts.map +1 -0
- package/dist/core/system-prompt.test.js +89 -0
- package/dist/core/system-prompt.test.js.map +1 -0
- package/dist/core/tools/bash.d.ts +2 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +34 -5
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +3 -1
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/defaults/models.json +13 -45
- package/dist/defaults/settings.json +1 -2
- package/dist/extensions/copy-turn.test.ts +131 -0
- package/dist/extensions/copy-turn.ts +6 -1
- package/dist/extensions/package.json +0 -1
- package/dist/extensions/pi-intercom/CHANGELOG.md +249 -0
- package/dist/extensions/pi-intercom/README.md +102 -39
- package/dist/extensions/pi-intercom/broker/broker.ts +1233 -36
- package/dist/extensions/pi-intercom/broker/client.test.ts +83 -0
- package/dist/extensions/pi-intercom/broker/client.ts +315 -12
- package/dist/extensions/pi-intercom/broker/extension-state.ts +186 -0
- package/dist/extensions/pi-intercom/broker/extension.test.ts +387 -0
- package/dist/extensions/pi-intercom/broker/framing.test.ts +114 -0
- package/dist/extensions/pi-intercom/broker/framing.ts +82 -24
- package/dist/extensions/pi-intercom/broker/paths.test.ts +153 -0
- package/dist/extensions/pi-intercom/broker/paths.ts +117 -8
- package/dist/extensions/pi-intercom/broker/runtime-claim.test.ts +34 -0
- package/dist/extensions/pi-intercom/broker/runtime-claim.ts +21 -0
- package/dist/extensions/pi-intercom/broker/spawn.test.ts +160 -23
- package/dist/extensions/pi-intercom/broker/spawn.ts +113 -27
- package/dist/extensions/pi-intercom/config.test.ts +93 -0
- package/dist/extensions/pi-intercom/config.ts +55 -6
- package/dist/extensions/pi-intercom/cwd.test.ts +40 -0
- package/dist/extensions/pi-intercom/cwd.ts +31 -0
- package/dist/extensions/pi-intercom/extension-api.ts +44 -0
- package/dist/extensions/pi-intercom/format-context.test.ts +31 -0
- package/dist/extensions/pi-intercom/format-context.ts +32 -0
- package/dist/extensions/pi-intercom/index.ts +742 -145
- package/dist/extensions/pi-intercom/intercom.integration.test.ts +2646 -0
- package/dist/extensions/pi-intercom/package.json +15 -5
- package/dist/extensions/pi-intercom/reply-tracker.test.ts +134 -0
- package/dist/extensions/pi-intercom/reply-tracker.ts +31 -13
- package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +13 -11
- package/dist/extensions/pi-intercom/test/inline-message.test.ts +184 -0
- package/dist/extensions/pi-intercom/test/overlay-width.test.ts +66 -0
- package/dist/extensions/pi-intercom/types.ts +94 -4
- package/dist/extensions/pi-intercom/ui/compose.ts +8 -4
- package/dist/extensions/pi-intercom/ui/inline-message.ts +61 -25
- package/dist/extensions/pi-intercom/ui/session-list.ts +7 -3
- package/dist/extensions/pi-subagents/CHANGELOG.md +88 -0
- package/dist/extensions/pi-subagents/LICENSE +21 -0
- package/dist/extensions/pi-subagents/README.md +158 -69
- package/dist/extensions/pi-subagents/agents/architect.md +5 -4
- package/dist/extensions/pi-subagents/agents/builder.md +6 -4
- package/dist/extensions/pi-subagents/agents/commentator.md +3 -2
- package/dist/extensions/pi-subagents/agents/explorer.md +3 -2
- package/dist/extensions/pi-subagents/agents/recapper.md +3 -2
- package/dist/extensions/pi-subagents/agents/researcher.md +4 -3
- package/dist/extensions/pi-subagents/package-lock.json +2 -2
- package/dist/extensions/pi-subagents/package.json +3 -3
- package/dist/extensions/pi-subagents/prompts/review-loop.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +22 -988
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +257 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +431 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +144 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +281 -0
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +125 -37
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +4 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +118 -9
- package/dist/extensions/pi-subagents/src/agents/skills.ts +14 -12
- package/dist/extensions/pi-subagents/src/agents/task-aware-routing.ts +125 -0
- package/dist/extensions/pi-subagents/src/api/delegation.ts +3 -0
- package/dist/extensions/pi-subagents/src/api/preflight.ts +17 -13
- package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +17 -1
- package/dist/extensions/pi-subagents/src/extension/index.ts +22 -8
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +248 -6
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +33 -8
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +32 -14
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +9 -4
- package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +33 -4
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +86 -21
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +24 -13
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +7 -5
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -2
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +48 -5
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +68 -1
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +56 -5
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +92 -11
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +29 -3
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +388 -29
- package/dist/extensions/pi-subagents/src/runs/foreground/async-stop-action.ts +65 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +3 -3
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +214 -60
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +546 -252
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +42 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +553 -154
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +19 -13
- package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +51 -19
- package/dist/extensions/pi-subagents/src/runs/shared/chain-outputs.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +44 -11
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +8 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +97 -20
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +43 -12
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +52 -4
- package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +19 -0
- package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +45 -9
- package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +71 -0
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +63 -9
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +31 -1
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +101 -0
- package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +22 -1
- package/dist/extensions/pi-subagents/src/runs/shared/usage-budget.ts +65 -0
- package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +26 -1
- package/dist/extensions/pi-subagents/src/shared/settings.ts +17 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +196 -12
- package/dist/extensions/pi-subagents/src/shared/utils.ts +102 -5
- package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +10 -1
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +46 -4
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +5 -3
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +87 -24
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +182 -9
- package/dist/extensions/pi-subagents/src/tui/render.ts +55 -34
- package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +14 -7
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +5 -4
- package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +170 -17
- package/dist/extensions/pi-subagents/src/watchdog/scope.ts +62 -0
- package/dist/extensions/pi-subagents/src/watchdog/settings.ts +41 -1
- package/dist/extensions/pi-subagents/src/watchdog/types.ts +10 -0
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +113 -8
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +419 -47
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +84 -0
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +4 -2
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +51 -2
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +62 -22
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +48 -0
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +8 -6
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +231 -19
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +48 -7
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +44 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +199 -17
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +898 -16
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +112 -23
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +17 -5
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +2 -0
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +18 -2
- package/dist/extensions/pi-subagents/test/support/register-loader.mjs +3 -3
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +3 -1
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +198 -6
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +161 -1
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +47 -2
- package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +60 -0
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +8 -0
- package/dist/extensions/pi-subagents/test/unit/builtin-agent-documentation.test.ts +63 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +136 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +6 -0
- package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +48 -48
- package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +23 -0
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +28 -2
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +176 -8
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +164 -5
- package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +41 -1
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +17 -12
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +18 -2
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +20 -1
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +30 -2
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +39 -2
- package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +22 -0
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +103 -0
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +18 -0
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +48 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +13 -1
- package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +29 -4
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +223 -4
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +30 -0
- package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +52 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +46 -2
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +91 -1
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +78 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +41 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +77 -0
- package/dist/extensions/pi-subagents/test/unit/task-aware-routing.test.ts +213 -0
- package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +26 -1
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +61 -10
- package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +210 -1
- package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +33 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +32 -0
- package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +43 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts +3 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +10 -2
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts +1 -2
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +16 -46
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +4 -1
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +22 -13
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/external-editor.d.ts +12 -0
- package/dist/modes/interactive/external-editor.d.ts.map +1 -0
- package/dist/modes/interactive/external-editor.js +37 -0
- package/dist/modes/interactive/external-editor.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +72 -64
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +14 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/skills/ponytail/SKILL.md +1 -3
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +19 -8
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +1 -1
- package/dist/utils/version-check.js.map +1 -1
- package/docs/compaction.md +1 -1
- package/docs/custom-provider.md +14 -5
- package/docs/environment-variables.md +86 -0
- package/docs/extensions.md +15 -5
- package/docs/index.md +1 -0
- package/docs/models.md +9 -2
- package/docs/plans/subagent-delegation/phase-0-correctness.md +265 -0
- package/docs/plans/subagent-delegation/phase-1-behavioral-contract.md +486 -0
- package/docs/plans/subagent-delegation/phase-2-context-controls.md +282 -0
- package/docs/plans/subagent-delegation/phase-3-advisory-routing.md +362 -0
- package/docs/plans/subagent-delegation/phase-4-optional-enforcement.md +381 -0
- package/docs/providers.md +21 -2
- package/docs/rpc.md +23 -6
- package/docs/session-format.md +2 -0
- package/docs/settings.md +1 -1
- package/docs/usage.md +0 -13
- package/examples/extensions/custom-compaction.ts +5 -2
- package/examples/extensions/custom-provider-anthropic/index.ts +9 -3
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/handoff.ts +11 -3
- package/examples/extensions/message-renderer.ts +3 -3
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/summarize.ts +5 -2
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/12-full-control.ts +3 -1
- package/package.json +16 -7
- package/dist/extensions/caveman/caveman-instructions.cjs +0 -11
- package/dist/extensions/caveman/index.js +0 -118
- package/dist/extensions/caveman/package.json +0 -8
- package/dist/extensions/caveman/test/extension.test.js +0 -203
- package/dist/extensions/caveman/test/helpers.test.js +0 -58
- package/dist/skills/caveman/SKILL.md +0 -50
|
@@ -45,6 +45,9 @@ import {
|
|
|
45
45
|
hasEmptyTerminalAssistantResponse,
|
|
46
46
|
extractToolArgsPreview,
|
|
47
47
|
extractTextFromContent,
|
|
48
|
+
boundStreamedRecentTools,
|
|
49
|
+
boundStreamedRecentOutput,
|
|
50
|
+
boundStreamedToolCalls,
|
|
48
51
|
} from "../../shared/utils.ts";
|
|
49
52
|
import { buildSkillInjection, resolveSkillsWithFallback } from "../../agents/skills.ts";
|
|
50
53
|
import { buildAgentMemoryInjection } from "../../agents/agent-memory.ts";
|
|
@@ -52,17 +55,26 @@ import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
|
|
|
52
55
|
import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
|
|
53
56
|
import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
|
|
54
57
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
55
|
-
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
|
|
56
|
-
import {
|
|
58
|
+
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
|
|
59
|
+
import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.ts";
|
|
60
|
+
import { assertAgentAllowedByCapabilityCeiling, decodeSubagentCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV } from "../shared/capability-ceiling.ts";
|
|
57
61
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
58
62
|
import { MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput } from "../shared/structured-output.ts";
|
|
63
|
+
import { formatProcessSignalError, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
|
|
59
64
|
import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
|
|
60
|
-
import { captureSingleOutputSnapshot, extractChildWrittenOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
65
|
+
import { captureSingleOutputSnapshot, extractChildWrittenOutput, formatBoundedPersistenceFallback, formatSavedOutputReference, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
61
66
|
import {
|
|
62
67
|
buildModelCandidates,
|
|
63
68
|
formatModelAttemptNote,
|
|
64
69
|
isRetryableModelFailure,
|
|
65
70
|
} from "../shared/model-fallback.ts";
|
|
71
|
+
import {
|
|
72
|
+
SUBAGENT_STARTUP_RETRY_DELAYS_MS,
|
|
73
|
+
formatSubagentStartupRetryExhaustedError,
|
|
74
|
+
formatSubagentStartupRetryNote,
|
|
75
|
+
isRetryableSubagentStartupFailure,
|
|
76
|
+
waitForSubagentStartupRetry,
|
|
77
|
+
} from "../shared/subagent-startup-retry.ts";
|
|
66
78
|
import {
|
|
67
79
|
createMutatingFailureState,
|
|
68
80
|
didMutatingToolFail,
|
|
@@ -111,6 +123,47 @@ function sumUsage(target: Usage, source: Usage): void {
|
|
|
111
123
|
target.turns += source.turns;
|
|
112
124
|
}
|
|
113
125
|
|
|
126
|
+
function persistSingleResultMetadata(input: {
|
|
127
|
+
metadataPath?: string;
|
|
128
|
+
enabled: boolean;
|
|
129
|
+
runId?: string;
|
|
130
|
+
agent: string;
|
|
131
|
+
task: string;
|
|
132
|
+
result: SingleResult;
|
|
133
|
+
}): void {
|
|
134
|
+
if (!input.enabled || !input.metadataPath) return;
|
|
135
|
+
const target = input.result;
|
|
136
|
+
writeMetadata(input.metadataPath, {
|
|
137
|
+
runId: input.runId,
|
|
138
|
+
agent: input.agent,
|
|
139
|
+
task: input.task,
|
|
140
|
+
exitCode: target.exitCode,
|
|
141
|
+
processSignal: target.processSignal,
|
|
142
|
+
usage: target.usage,
|
|
143
|
+
model: target.model,
|
|
144
|
+
attemptedModels: target.attemptedModels,
|
|
145
|
+
modelAttempts: target.modelAttempts,
|
|
146
|
+
durationMs: target.progressSummary?.durationMs,
|
|
147
|
+
toolCount: target.progressSummary?.toolCount,
|
|
148
|
+
error: target.error,
|
|
149
|
+
agentContract: target.agentContract,
|
|
150
|
+
launchContractDigest: target.launchContractDigest,
|
|
151
|
+
launchResolvedExtensions: target.launchResolvedExtensions,
|
|
152
|
+
runtimeAcknowledgedExtensions: target.runtimeAcknowledgedExtensions,
|
|
153
|
+
execution: target.execution,
|
|
154
|
+
acceptance: target.acceptance,
|
|
155
|
+
capabilityCeiling: target.capabilityCeiling,
|
|
156
|
+
capabilityAudit: target.capabilityAudit,
|
|
157
|
+
review: target.review,
|
|
158
|
+
effects: target.effects,
|
|
159
|
+
transcriptPath: target.transcriptPath,
|
|
160
|
+
transcriptError: target.transcriptError,
|
|
161
|
+
skills: target.skills,
|
|
162
|
+
skillsWarning: target.skillsWarning,
|
|
163
|
+
timestamp: Date.now(),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
114
167
|
function formatTimeoutMessage(timeoutMs: number): string {
|
|
115
168
|
return `Subagent timed out after ${timeoutMs}ms.`;
|
|
116
169
|
}
|
|
@@ -138,6 +191,21 @@ function buildPendingAcceptanceLedger(acceptance: ResolvedAcceptanceConfig): Acc
|
|
|
138
191
|
};
|
|
139
192
|
}
|
|
140
193
|
|
|
194
|
+
function buildInterruptedAcceptanceLedger(acceptance: ResolvedAcceptanceConfig): AcceptanceLedger {
|
|
195
|
+
const pending = buildPendingAcceptanceLedger(acceptance);
|
|
196
|
+
if (acceptance.level === "none") {
|
|
197
|
+
pending.status = "not-required";
|
|
198
|
+
pending.evidenceStatus = "not-required";
|
|
199
|
+
} else {
|
|
200
|
+
pending.runtimeChecks = [{
|
|
201
|
+
id: "interrupted",
|
|
202
|
+
status: "not-applicable",
|
|
203
|
+
message: "Acceptance was not evaluated because the subagent was interrupted.",
|
|
204
|
+
}];
|
|
205
|
+
}
|
|
206
|
+
return pending;
|
|
207
|
+
}
|
|
208
|
+
|
|
141
209
|
function appendRecentOutput(progress: AgentProgress, lines: string[]): void {
|
|
142
210
|
if (lines.length === 0) return;
|
|
143
211
|
progress.recentOutput.push(...lines.filter((line) => line.trim()));
|
|
@@ -161,8 +229,8 @@ function snapshotProgress(progress: AgentProgress): AgentProgress {
|
|
|
161
229
|
return {
|
|
162
230
|
...progress,
|
|
163
231
|
skills: progress.skills ? [...progress.skills] : undefined,
|
|
164
|
-
recentTools: progress.recentTools
|
|
165
|
-
recentOutput:
|
|
232
|
+
recentTools: boundStreamedRecentTools(progress.recentTools),
|
|
233
|
+
recentOutput: boundStreamedRecentOutput(progress.recentOutput),
|
|
166
234
|
};
|
|
167
235
|
}
|
|
168
236
|
|
|
@@ -188,6 +256,20 @@ function snapshotResult(result: SingleResult, progress: AgentProgress): SingleRe
|
|
|
188
256
|
};
|
|
189
257
|
}
|
|
190
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Streaming variant of snapshotResult for `onUpdate` progress events: it drops the
|
|
261
|
+
* unbounded `messages` transcript in favour of compact tool-call summaries so a
|
|
262
|
+
* single `tool_execution_update` line stays well under the child-stdout protocol
|
|
263
|
+
* cap (`MAX_CHILD_PENDING_LINE_BYTES`). Non-streaming consumers such as
|
|
264
|
+
* foreground detach receipts and terminal consumers use snapshotResult directly.
|
|
265
|
+
*/
|
|
266
|
+
function snapshotStreamResult(result: SingleResult, progress: AgentProgress): SingleResult {
|
|
267
|
+
const snapshot = snapshotResult(result, progress);
|
|
268
|
+
snapshot.messages = undefined;
|
|
269
|
+
snapshot.toolCalls = boundStreamedToolCalls(result);
|
|
270
|
+
return snapshot;
|
|
271
|
+
}
|
|
272
|
+
|
|
191
273
|
async function runSingleAttempt(
|
|
192
274
|
runtimeCwd: string,
|
|
193
275
|
agent: AgentConfig,
|
|
@@ -220,7 +302,7 @@ async function runSingleAttempt(
|
|
|
220
302
|
childIndex: options.index ?? 0,
|
|
221
303
|
})
|
|
222
304
|
: undefined;
|
|
223
|
-
const { args, env: sharedEnv, tempDir, toolDiagnosticPath, capabilityAudit } = buildPiArgs({
|
|
305
|
+
const { args, env: sharedEnv, tempDir, toolDiagnosticPath, runtimeAcknowledgedExtensionsPath, capabilityAudit } = buildPiArgs({
|
|
224
306
|
baseArgs: ["--offline", "--mode", "json", "-p"],
|
|
225
307
|
task,
|
|
226
308
|
sessionEnabled: shared.sessionEnabled,
|
|
@@ -268,7 +350,9 @@ async function runSingleAttempt(
|
|
|
268
350
|
structuredOutput: Boolean(options.structuredOutput),
|
|
269
351
|
capabilityCeiling: options.capabilityCeiling,
|
|
270
352
|
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
353
|
+
agentName: agent.name,
|
|
271
354
|
});
|
|
355
|
+
const launchResolvedExtensions = projectLaunchResolvedChildExtensions(toolPlan);
|
|
272
356
|
const launchContractDigest = launchBindingDigest({
|
|
273
357
|
definitionDigest: agentDefinitionDigest(agent),
|
|
274
358
|
task: shared.originalTask ?? task,
|
|
@@ -288,11 +372,14 @@ async function runSingleAttempt(
|
|
|
288
372
|
...(options.structuredOutput ? { structuredOutputSchema: options.structuredOutput.schema } : {}),
|
|
289
373
|
});
|
|
290
374
|
const result: SingleResult = withRunContext({
|
|
375
|
+
index: options.index ?? 0,
|
|
291
376
|
agent: agent.name,
|
|
292
377
|
task: shared.originalTask ?? task,
|
|
293
378
|
...(options.agentContract ? { agentContract: options.agentContract } : {}),
|
|
294
379
|
launchContractDigest,
|
|
380
|
+
launchResolvedExtensions,
|
|
295
381
|
exitCode: 0,
|
|
382
|
+
outputState: "absent",
|
|
296
383
|
messages: [],
|
|
297
384
|
usage: emptyUsage(),
|
|
298
385
|
model: modelArg,
|
|
@@ -337,6 +424,10 @@ async function runSingleAttempt(
|
|
|
337
424
|
recentOutput: [...shared.attemptNotes],
|
|
338
425
|
toolCount: 0,
|
|
339
426
|
tokens: 0,
|
|
427
|
+
...(modelArg ? { model: modelArg } : {}),
|
|
428
|
+
...(resolvedThinking ? { thinking: resolvedThinking } : {}),
|
|
429
|
+
inputTokens: 0,
|
|
430
|
+
outputTokens: 0,
|
|
340
431
|
durationMs: 0,
|
|
341
432
|
lastActivityAt: startTime,
|
|
342
433
|
};
|
|
@@ -371,7 +462,7 @@ async function runSingleAttempt(
|
|
|
371
462
|
});
|
|
372
463
|
const jsonlWriter = createJsonlWriter(shared.jsonlPath, proc.stdout);
|
|
373
464
|
let processClosed = false;
|
|
374
|
-
let
|
|
465
|
+
let lifecycleFinished = false;
|
|
375
466
|
let detached = false;
|
|
376
467
|
let intercomStarted = false;
|
|
377
468
|
let assistantError: string | undefined;
|
|
@@ -410,19 +501,43 @@ async function runSingleAttempt(
|
|
|
410
501
|
}
|
|
411
502
|
};
|
|
412
503
|
|
|
413
|
-
const
|
|
414
|
-
detached
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
504
|
+
const detachForeground = (reason: string): boolean => {
|
|
505
|
+
if (detached || processClosed || lifecycleFinished || options.signal?.aborted) return false;
|
|
506
|
+
const receiptProgress = snapshotProgress(progress);
|
|
507
|
+
receiptProgress.status = "detached";
|
|
508
|
+
receiptProgress.durationMs = Date.now() - startTime;
|
|
509
|
+
const receipt = snapshotResult(result, receiptProgress);
|
|
510
|
+
receipt.exitCode = -2;
|
|
511
|
+
receipt.detached = true;
|
|
512
|
+
receipt.detachedReason = reason;
|
|
513
|
+
receipt.finalOutput = reason === "intercom coordination"
|
|
514
|
+
? "Detached for intercom coordination before task completion."
|
|
515
|
+
: reason === "user request"
|
|
516
|
+
? "Detached at user request before task completion."
|
|
517
|
+
: `Detached for ${reason} before task completion.`;
|
|
518
|
+
receipt.outputMode = options.outputMode ?? "inline";
|
|
519
|
+
if (options.outputPath) {
|
|
520
|
+
receipt.outputSaveError = reason === "user request"
|
|
521
|
+
? "Output file was not finalized because the subagent detached at user request."
|
|
522
|
+
: `Output file was not finalized because the subagent detached for ${reason}.`;
|
|
523
|
+
}
|
|
524
|
+
receipt.progressSummary = {
|
|
525
|
+
toolCount: receiptProgress.toolCount,
|
|
526
|
+
tokens: receiptProgress.tokens,
|
|
527
|
+
durationMs: receiptProgress.durationMs,
|
|
424
528
|
};
|
|
425
|
-
|
|
529
|
+
// The attempt is not detached until the outer coordinator has accepted and
|
|
530
|
+
// synchronously published the receipt. A persistence/callback failure must
|
|
531
|
+
// leave this attempt attached and abortable rather than orphaning it.
|
|
532
|
+
let accepted = false;
|
|
533
|
+
try {
|
|
534
|
+
accepted = options.onDetachReceipt?.(receipt) === true;
|
|
535
|
+
} catch {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
if (!accepted) return false;
|
|
539
|
+
detached = true;
|
|
540
|
+
return true;
|
|
426
541
|
};
|
|
427
542
|
|
|
428
543
|
// If the child emits a terminal assistant stop but never exits,
|
|
@@ -463,9 +578,9 @@ async function runSingleAttempt(
|
|
|
463
578
|
armWatchdogTail();
|
|
464
579
|
return;
|
|
465
580
|
}
|
|
466
|
-
if (childExited || finalDrainTimer ||
|
|
581
|
+
if (childExited || finalDrainTimer || lifecycleFinished || processClosed) return;
|
|
467
582
|
finalDrainTimer = setTimeout(() => {
|
|
468
|
-
if (
|
|
583
|
+
if (lifecycleFinished || processClosed) return;
|
|
469
584
|
const termSent = trySignalChild(proc, "SIGTERM");
|
|
470
585
|
if (!termSent) return;
|
|
471
586
|
forcedTerminationSignal = true;
|
|
@@ -473,7 +588,7 @@ async function runSingleAttempt(
|
|
|
473
588
|
result.error = result.error ?? `Subagent process did not exit within ${FINAL_STOP_GRACE_MS}ms after its terminal event. Forcing termination.`;
|
|
474
589
|
}
|
|
475
590
|
finalHardKillTimer = setTimeout(() => {
|
|
476
|
-
if (
|
|
591
|
+
if (lifecycleFinished || processClosed) return;
|
|
477
592
|
forcedTerminationSignal = trySignalChild(proc, "SIGKILL") || forcedTerminationSignal;
|
|
478
593
|
}, HARD_KILL_MS);
|
|
479
594
|
finalHardKillTimer.unref?.();
|
|
@@ -481,7 +596,7 @@ async function runSingleAttempt(
|
|
|
481
596
|
finalDrainTimer.unref?.();
|
|
482
597
|
};
|
|
483
598
|
function armWatchdogTail(): void {
|
|
484
|
-
if ((!cleanTerminalAssistantStopReceived && !agentSettledReceived) || watchdogTailTimer ||
|
|
599
|
+
if ((!cleanTerminalAssistantStopReceived && !agentSettledReceived) || watchdogTailTimer || lifecycleFinished || processClosed) return;
|
|
485
600
|
watchdogTailTimer = setTimeout(() => {
|
|
486
601
|
watchdogTailTimer = undefined;
|
|
487
602
|
updateChildWatchdogState({
|
|
@@ -507,7 +622,7 @@ async function runSingleAttempt(
|
|
|
507
622
|
};
|
|
508
623
|
|
|
509
624
|
const unsubscribeIntercomDetach = options.intercomEvents?.on?.(INTERCOM_DETACH_REQUEST_EVENT, (payload) => {
|
|
510
|
-
if (!options.allowIntercomDetach ||
|
|
625
|
+
if (!options.allowIntercomDetach || processClosed) return;
|
|
511
626
|
if (!payload || typeof payload !== "object") return;
|
|
512
627
|
const event = payload as { requestId?: unknown; runId?: unknown; agent?: unknown; childIndex?: unknown };
|
|
513
628
|
const requestId = event.requestId;
|
|
@@ -518,13 +633,13 @@ async function runSingleAttempt(
|
|
|
518
633
|
if (typeof event.agent === "string" && event.agent !== agent.name) return;
|
|
519
634
|
if (typeof event.childIndex === "number" && event.childIndex !== (options.index ?? 0)) return;
|
|
520
635
|
} else if (!intercomStarted) return;
|
|
521
|
-
|
|
522
|
-
|
|
636
|
+
const accepted = detachForeground("intercom coordination");
|
|
637
|
+
options.intercomEvents?.emit(INTERCOM_DETACH_RESPONSE_EVENT, { requestId, accepted, runId: options.runId, agent: agent.name, childIndex: options.index ?? 0 });
|
|
523
638
|
});
|
|
524
639
|
|
|
525
640
|
const finish = (code: number) => {
|
|
526
|
-
if (
|
|
527
|
-
|
|
641
|
+
if (lifecycleFinished) return;
|
|
642
|
+
lifecycleFinished = true;
|
|
528
643
|
clearFinalDrainTimers();
|
|
529
644
|
clearWatchdogTailTimer();
|
|
530
645
|
clearStdioGuard();
|
|
@@ -609,7 +724,7 @@ async function runSingleAttempt(
|
|
|
609
724
|
};
|
|
610
725
|
const requestTurnBudgetAbort = (turnCount: number) => {
|
|
611
726
|
const budget = options.turnBudget;
|
|
612
|
-
if (!budget || result.timedOut || result.turnBudgetExceeded || interruptedByControl || processClosed ||
|
|
727
|
+
if (!budget || result.timedOut || result.turnBudgetExceeded || interruptedByControl || processClosed || lifecycleFinished) return;
|
|
613
728
|
const message = turnBudgetExceededMessage(budget, turnCount);
|
|
614
729
|
result.turnBudgetExceeded = true;
|
|
615
730
|
result.wrapUpRequested = true;
|
|
@@ -622,12 +737,12 @@ async function runSingleAttempt(
|
|
|
622
737
|
fireUpdate();
|
|
623
738
|
trySignalChild(proc, "SIGINT");
|
|
624
739
|
turnBudgetTerminationTimer = setTimeout(() => {
|
|
625
|
-
if (processClosed ||
|
|
740
|
+
if (processClosed || lifecycleFinished || result.timedOut) return;
|
|
626
741
|
trySignalChild(proc, "SIGTERM");
|
|
627
742
|
}, 1000);
|
|
628
743
|
turnBudgetTerminationTimer.unref?.();
|
|
629
744
|
turnBudgetHardKillTimer = setTimeout(() => {
|
|
630
|
-
if (processClosed ||
|
|
745
|
+
if (processClosed || lifecycleFinished || result.timedOut) return;
|
|
631
746
|
trySignalChild(proc, "SIGKILL");
|
|
632
747
|
}, 4000);
|
|
633
748
|
turnBudgetHardKillTimer.unref?.();
|
|
@@ -683,7 +798,7 @@ async function runSingleAttempt(
|
|
|
683
798
|
const emitUpdateSnapshot = (text: string) => {
|
|
684
799
|
if (!options.onUpdate || processClosed) return;
|
|
685
800
|
const progressSnapshot = snapshotProgress(progress);
|
|
686
|
-
const resultSnapshot =
|
|
801
|
+
const resultSnapshot = snapshotStreamResult(result, progressSnapshot);
|
|
687
802
|
const controlEvents = drainPendingControlEvents();
|
|
688
803
|
options.onUpdate({
|
|
689
804
|
content: [{ type: "text", text }],
|
|
@@ -703,6 +818,7 @@ async function runSingleAttempt(
|
|
|
703
818
|
emitUpdateSnapshot(output || "(running...)");
|
|
704
819
|
};
|
|
705
820
|
|
|
821
|
+
const rawStdoutTail = createBoundedByteTail();
|
|
706
822
|
const processLine = (line: string) => {
|
|
707
823
|
if (!line.trim()) return;
|
|
708
824
|
jsonlWriter.writeLine(line);
|
|
@@ -710,6 +826,7 @@ async function runSingleAttempt(
|
|
|
710
826
|
try {
|
|
711
827
|
evt = JSON.parse(line) as { type?: string; message?: Message; toolName?: string; args?: unknown; willRetry?: unknown };
|
|
712
828
|
} catch {
|
|
829
|
+
rawStdoutTail.push(`${line}\n`);
|
|
713
830
|
shared.transcriptWriter?.writeStdoutLine(line);
|
|
714
831
|
// Non-JSON stdout lines are expected; only structured events are parsed.
|
|
715
832
|
return;
|
|
@@ -805,8 +922,13 @@ async function runSingleAttempt(
|
|
|
805
922
|
result.usage.cacheWrite += u.cacheWrite || 0;
|
|
806
923
|
result.usage.cost += u.cost?.total || 0;
|
|
807
924
|
progress.tokens = result.usage.input + result.usage.output;
|
|
925
|
+
progress.inputTokens = result.usage.input;
|
|
926
|
+
progress.outputTokens = result.usage.output;
|
|
927
|
+
}
|
|
928
|
+
if (evt.message.model) {
|
|
929
|
+
progress.model = evt.message.model;
|
|
930
|
+
if (!result.model) result.model = evt.message.model;
|
|
808
931
|
}
|
|
809
|
-
if (!result.model && evt.message.model) result.model = evt.message.model;
|
|
810
932
|
if (evt.message.errorMessage) assistantError = evt.message.errorMessage;
|
|
811
933
|
const assistantText = extractTextFromContent(evt.message.content);
|
|
812
934
|
appendRecentOutput(progress, assistantText.split("\n").slice(-10));
|
|
@@ -857,7 +979,7 @@ async function runSingleAttempt(
|
|
|
857
979
|
|
|
858
980
|
if (controlConfig.enabled) {
|
|
859
981
|
activityTimer = setInterval(() => {
|
|
860
|
-
if (processClosed ||
|
|
982
|
+
if (processClosed || lifecycleFinished) return;
|
|
861
983
|
const now = Date.now();
|
|
862
984
|
if (updateActivityState(now)) {
|
|
863
985
|
progress.durationMs = now - startTime;
|
|
@@ -869,7 +991,7 @@ async function runSingleAttempt(
|
|
|
869
991
|
|
|
870
992
|
if (attemptTimeout) {
|
|
871
993
|
timeoutTimer = setTimeout(() => {
|
|
872
|
-
if (processClosed ||
|
|
994
|
+
if (processClosed || lifecycleFinished || interruptedByControl) return;
|
|
873
995
|
result.timedOut = true;
|
|
874
996
|
result.error = attemptTimeout.message;
|
|
875
997
|
result.finalOutput = attemptTimeout.message;
|
|
@@ -879,12 +1001,12 @@ async function runSingleAttempt(
|
|
|
879
1001
|
fireUpdate();
|
|
880
1002
|
trySignalChild(proc, "SIGINT");
|
|
881
1003
|
timeoutTerminationTimer = setTimeout(() => {
|
|
882
|
-
if (processClosed ||
|
|
1004
|
+
if (processClosed || lifecycleFinished) return;
|
|
883
1005
|
trySignalChild(proc, "SIGTERM");
|
|
884
1006
|
}, 1000);
|
|
885
1007
|
timeoutTerminationTimer.unref?.();
|
|
886
1008
|
timeoutHardKillTimer = setTimeout(() => {
|
|
887
|
-
if (processClosed ||
|
|
1009
|
+
if (processClosed || lifecycleFinished) return;
|
|
888
1010
|
trySignalChild(proc, "SIGKILL");
|
|
889
1011
|
}, 4000);
|
|
890
1012
|
timeoutHardKillTimer.unref?.();
|
|
@@ -928,76 +1050,46 @@ async function runSingleAttempt(
|
|
|
928
1050
|
clearFinalDrainTimers();
|
|
929
1051
|
});
|
|
930
1052
|
proc.on("close", (code, signal) => {
|
|
1053
|
+
if (lifecycleFinished) return;
|
|
1054
|
+
processClosed = true;
|
|
931
1055
|
clearFinalDrainTimers();
|
|
932
1056
|
clearStdioGuard();
|
|
933
1057
|
void jsonlWriter.close().catch(() => {
|
|
934
1058
|
// JSONL artifact flush is best effort.
|
|
935
1059
|
});
|
|
936
1060
|
const toolDiagnosticError = readChildToolDiagnosticError(toolDiagnosticPath);
|
|
1061
|
+
result.runtimeAcknowledgedExtensions = readRuntimeAcknowledgedExtensions(runtimeAcknowledgedExtensionsPath);
|
|
937
1062
|
cleanupTempDir(tempDir);
|
|
938
1063
|
stdoutReader.end();
|
|
939
1064
|
stderrReader.end();
|
|
940
1065
|
const stderr = stderrTail.text();
|
|
1066
|
+
const rawStdout = rawStdoutTail.text();
|
|
941
1067
|
let closeError = result.error ?? toolDiagnosticError ?? assistantError;
|
|
942
|
-
const forcedDrainAfterFinalSuccess = forcedTerminationSignal && (cleanTerminalAssistantStopReceived || agentSettledReceived) && !closeError;
|
|
1068
|
+
const forcedDrainAfterFinalSuccess = Boolean(forcedTerminationSignal || signal) && (cleanTerminalAssistantStopReceived || agentSettledReceived) && !closeError;
|
|
1069
|
+
if (signal) result.processSignal = signal;
|
|
1070
|
+
if (!closeError && isUnexplainedProcessSignal({
|
|
1071
|
+
processSignal: signal,
|
|
1072
|
+
interrupted: result.interrupted,
|
|
1073
|
+
timedOut: result.timedOut,
|
|
1074
|
+
stopped: result.stopped,
|
|
1075
|
+
turnBudgetExceeded: result.turnBudgetExceeded,
|
|
1076
|
+
forcedDrainAfterFinalSuccess,
|
|
1077
|
+
})) {
|
|
1078
|
+
closeError = formatProcessSignalError(signal!);
|
|
1079
|
+
}
|
|
1080
|
+
if (code !== 0 && rawStdout.trim() && !closeError && !forcedDrainAfterFinalSuccess) {
|
|
1081
|
+
closeError = rawStdout.trim();
|
|
1082
|
+
}
|
|
943
1083
|
if (code !== 0 && stderr.trim() && !closeError && !forcedDrainAfterFinalSuccess) {
|
|
944
1084
|
closeError = stderr.trim();
|
|
945
1085
|
}
|
|
946
1086
|
const finalCode = forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (code ?? 1) : (code ?? 0);
|
|
947
|
-
if (detached) {
|
|
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,
|
|
959
|
-
};
|
|
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) {
|
|
986
|
-
try {
|
|
987
|
-
writeArtifact(recoveredResult.artifactPaths.outputPath, fullOutput);
|
|
988
|
-
} catch {
|
|
989
|
-
// Detached children may outlive test/temp cleanup; recovered status is best-effort.
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
options.onDetachedExit?.(recoveredResult);
|
|
993
|
-
finish(-2);
|
|
994
|
-
return;
|
|
995
|
-
}
|
|
996
1087
|
if (!result.error && closeError) result.error = closeError;
|
|
997
|
-
processClosed = true;
|
|
998
1088
|
finish(finalCode);
|
|
999
1089
|
});
|
|
1000
1090
|
proc.on("error", (error) => {
|
|
1091
|
+
if (lifecycleFinished) return;
|
|
1092
|
+
processClosed = true;
|
|
1001
1093
|
clearFinalDrainTimers();
|
|
1002
1094
|
clearStdioGuard();
|
|
1003
1095
|
void jsonlWriter.close().catch(() => {
|
|
@@ -1014,7 +1106,7 @@ async function runSingleAttempt(
|
|
|
1014
1106
|
|
|
1015
1107
|
if (options.signal) {
|
|
1016
1108
|
const kill = () => {
|
|
1017
|
-
if (processClosed ||
|
|
1109
|
+
if (processClosed || lifecycleFinished) return;
|
|
1018
1110
|
proc.kill("SIGTERM");
|
|
1019
1111
|
setTimeout(() => !proc.killed && proc.kill("SIGKILL"), 3000);
|
|
1020
1112
|
};
|
|
@@ -1027,7 +1119,7 @@ async function runSingleAttempt(
|
|
|
1027
1119
|
|
|
1028
1120
|
if (options.interruptSignal) {
|
|
1029
1121
|
const interrupt = () => {
|
|
1030
|
-
if (processClosed ||
|
|
1122
|
+
if (processClosed || lifecycleFinished) return;
|
|
1031
1123
|
if (result.timedOut) return;
|
|
1032
1124
|
interruptedByControl = true;
|
|
1033
1125
|
clearTimeoutTimers();
|
|
@@ -1039,7 +1131,7 @@ async function runSingleAttempt(
|
|
|
1039
1131
|
fireUpdate();
|
|
1040
1132
|
trySignalChild(proc, "SIGINT");
|
|
1041
1133
|
setTimeout(() => {
|
|
1042
|
-
if (
|
|
1134
|
+
if (lifecycleFinished || processClosed) return;
|
|
1043
1135
|
trySignalChild(proc, "SIGTERM");
|
|
1044
1136
|
}, 1000).unref?.();
|
|
1045
1137
|
};
|
|
@@ -1049,6 +1141,16 @@ async function runSingleAttempt(
|
|
|
1049
1141
|
removeInterruptListener = () => options.interruptSignal?.removeEventListener("abort", interrupt);
|
|
1050
1142
|
}
|
|
1051
1143
|
}
|
|
1144
|
+
|
|
1145
|
+
// Publish only after every callback and cleanup guard captured by detach or
|
|
1146
|
+
// later lifecycle events has initialized. Consumers may invoke synchronously.
|
|
1147
|
+
try {
|
|
1148
|
+
options.onDetachReady?.((reason = "user request") => detachForeground(reason));
|
|
1149
|
+
} catch (error) {
|
|
1150
|
+
// A consumer callback is advisory. Keep the child attached and observable
|
|
1151
|
+
// rather than rejecting this attempt and orphaning its live process.
|
|
1152
|
+
appendRecentOutput(progress, [`Foreground detach callback failed: ${error instanceof Error ? error.message : String(error)}`]);
|
|
1153
|
+
}
|
|
1052
1154
|
});
|
|
1053
1155
|
result.exitCode = exitCode;
|
|
1054
1156
|
if (interruptedByControl) {
|
|
@@ -1066,16 +1168,6 @@ async function runSingleAttempt(
|
|
|
1066
1168
|
};
|
|
1067
1169
|
return result;
|
|
1068
1170
|
}
|
|
1069
|
-
if (result.detached) {
|
|
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
|
-
}
|
|
1076
|
-
return result;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
1171
|
if (result.error && result.exitCode === 0) {
|
|
1080
1172
|
result.exitCode = 1;
|
|
1081
1173
|
}
|
|
@@ -1137,6 +1229,7 @@ async function runSingleAttempt(
|
|
|
1137
1229
|
|
|
1138
1230
|
const acceptanceOutput = getFinalOutput(result.messages ?? []);
|
|
1139
1231
|
let fullOutput = stripAcceptanceReport(acceptanceOutput);
|
|
1232
|
+
result.outputState = fullOutput.trim() || result.structuredOutput !== undefined ? "present" : "absent";
|
|
1140
1233
|
if (result.timedOut) {
|
|
1141
1234
|
const timeoutMessage = formatTimeoutMessage(options.timeoutMs ?? 0);
|
|
1142
1235
|
fullOutput = fullOutput.trim()
|
|
@@ -1189,26 +1282,41 @@ async function runSingleAttempt(
|
|
|
1189
1282
|
reason: "completion_guard",
|
|
1190
1283
|
}));
|
|
1191
1284
|
}
|
|
1192
|
-
|
|
1285
|
+
// Persist whenever an output path is configured and meaningful child output
|
|
1286
|
+
// exists, not only for successful runs: failed children with output keep a
|
|
1287
|
+
// readable result file and a saved-output reference instead of raw inline
|
|
1288
|
+
// output. Synthetic failures without output (startup errors, empty responses)
|
|
1289
|
+
// stay outputState "absent" and are not promoted to a bogus saved reference.
|
|
1290
|
+
if (options.outputPath && (fullOutput.trim() || result.exitCode === 0)) {
|
|
1193
1291
|
const resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, shared.outputSnapshot);
|
|
1194
1292
|
fullOutput = stripAcceptanceReport(resolvedOutput.fullOutput);
|
|
1195
1293
|
result.savedOutputPath = resolvedOutput.savedPath;
|
|
1196
1294
|
result.outputSaveError = resolvedOutput.saveError;
|
|
1197
1295
|
if (resolvedOutput.savedPath) {
|
|
1198
1296
|
result.outputReference = formatSavedOutputReference(resolvedOutput.savedPath, fullOutput);
|
|
1297
|
+
if (result.outputState === "absent") result.outputState = "unknown";
|
|
1199
1298
|
}
|
|
1200
1299
|
}
|
|
1201
1300
|
artifactOutputByResult.set(result, fullOutput);
|
|
1202
1301
|
acceptanceOutputByResult.set(result, acceptanceOutput);
|
|
1203
1302
|
result.outputMode = options.outputMode ?? "inline";
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1303
|
+
const hasSavedReference = result.savedOutputPath && result.outputReference ? true : false;
|
|
1304
|
+
result.finalOutput = hasSavedReference && (options.outputMode === "file-only" || result.exitCode !== 0)
|
|
1305
|
+
? result.outputReference!.message
|
|
1306
|
+
: result.outputSaveError && options.outputPath
|
|
1307
|
+
? formatBoundedPersistenceFallback({
|
|
1308
|
+
error: result.outputSaveError,
|
|
1309
|
+
outputPath: options.outputPath,
|
|
1310
|
+
fullOutput,
|
|
1311
|
+
exitCode: result.exitCode,
|
|
1312
|
+
processError: result.error,
|
|
1313
|
+
})
|
|
1314
|
+
: fullOutput;
|
|
1207
1315
|
result.controlEvents = allControlEvents.length ? allControlEvents : undefined;
|
|
1208
1316
|
if (options.onUpdate) {
|
|
1209
1317
|
const finalText = result.finalOutput || result.error || "(no output)";
|
|
1210
1318
|
const progressSnapshot = snapshotProgress(progress);
|
|
1211
|
-
const resultSnapshot =
|
|
1319
|
+
const resultSnapshot = snapshotStreamResult(result, progressSnapshot);
|
|
1212
1320
|
options.onUpdate({
|
|
1213
1321
|
content: [{ type: "text", text: finalText }],
|
|
1214
1322
|
details: {
|
|
@@ -1225,7 +1333,7 @@ async function runSingleAttempt(
|
|
|
1225
1333
|
/**
|
|
1226
1334
|
* Run a subagent synchronously (blocking until complete)
|
|
1227
1335
|
*/
|
|
1228
|
-
|
|
1336
|
+
async function runSyncCompletion(
|
|
1229
1337
|
runtimeCwd: string,
|
|
1230
1338
|
agents: AgentConfig[],
|
|
1231
1339
|
agentName: string,
|
|
@@ -1234,11 +1342,12 @@ export async function runSync(
|
|
|
1234
1342
|
): Promise<SingleResult> {
|
|
1235
1343
|
options = {
|
|
1236
1344
|
...options,
|
|
1237
|
-
capabilityCeiling: options.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(options.parentSessionId),
|
|
1345
|
+
capabilityCeiling: intersectSubagentCapabilityCeilings(options.capabilityCeiling ?? resolveCurrentSubagentCapabilityCeiling(options.parentSessionId), decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV])),
|
|
1238
1346
|
};
|
|
1239
1347
|
const agent = agents.find((a) => a.name === agentName);
|
|
1240
1348
|
if (!agent) {
|
|
1241
1349
|
return withRunContext({
|
|
1350
|
+
index: options.index ?? 0,
|
|
1242
1351
|
agent: agentName,
|
|
1243
1352
|
task,
|
|
1244
1353
|
exitCode: 1,
|
|
@@ -1247,9 +1356,23 @@ export async function runSync(
|
|
|
1247
1356
|
error: `Unknown agent: ${agentName}`,
|
|
1248
1357
|
}, options.context);
|
|
1249
1358
|
}
|
|
1359
|
+
try {
|
|
1360
|
+
assertAgentAllowedByCapabilityCeiling(agent.name, options.capabilityCeiling);
|
|
1361
|
+
} catch (error) {
|
|
1362
|
+
return withRunContext({
|
|
1363
|
+
agent: agent.name,
|
|
1364
|
+
task,
|
|
1365
|
+
exitCode: 1,
|
|
1366
|
+
messages: [],
|
|
1367
|
+
usage: emptyUsage(),
|
|
1368
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1369
|
+
...(options.capabilityCeiling ? { capabilityCeiling: options.capabilityCeiling } : {}),
|
|
1370
|
+
}, options.context);
|
|
1371
|
+
}
|
|
1250
1372
|
const outputModeValidationError = validateFileOnlyOutputMode(options.outputMode, options.outputPath, `Single run (${agentName})`);
|
|
1251
1373
|
if (outputModeValidationError) {
|
|
1252
1374
|
return withRunContext({
|
|
1375
|
+
index: options.index ?? 0,
|
|
1253
1376
|
agent: agentName,
|
|
1254
1377
|
task,
|
|
1255
1378
|
exitCode: 1,
|
|
@@ -1286,6 +1409,7 @@ export async function runSync(
|
|
|
1286
1409
|
);
|
|
1287
1410
|
if (skillNames.some((skill) => skill.trim() === "pi-subagents") && missingSkills.includes("pi-subagents")) {
|
|
1288
1411
|
return withRunContext({
|
|
1412
|
+
index: options.index ?? 0,
|
|
1289
1413
|
agent: agentName,
|
|
1290
1414
|
task,
|
|
1291
1415
|
exitCode: 1,
|
|
@@ -1345,121 +1469,149 @@ export async function runSync(
|
|
|
1345
1469
|
}
|
|
1346
1470
|
|
|
1347
1471
|
const persistResultMetadata = (target: SingleResult): void => {
|
|
1348
|
-
|
|
1349
|
-
|
|
1472
|
+
persistSingleResultMetadata({
|
|
1473
|
+
metadataPath: artifactPathsResult?.metadataPath,
|
|
1474
|
+
enabled: options.artifactConfig?.enabled !== false && options.artifactConfig?.includeMetadata !== false,
|
|
1350
1475
|
runId: options.runId,
|
|
1351
1476
|
agent: agentName,
|
|
1352
1477
|
task,
|
|
1353
|
-
|
|
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(),
|
|
1478
|
+
result: target,
|
|
1374
1479
|
});
|
|
1375
1480
|
};
|
|
1376
1481
|
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1482
|
+
let intercomDetached = false;
|
|
1483
|
+
let detachedReason: string | undefined;
|
|
1484
|
+
const attemptOptions: RunSyncOptions = {
|
|
1485
|
+
...options,
|
|
1486
|
+
onDetachReceipt: (receipt) => {
|
|
1487
|
+
receipt.acceptance = buildPendingAcceptanceLedger(effectiveAcceptance);
|
|
1488
|
+
try {
|
|
1489
|
+
persistResultMetadata(receipt);
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
receipt.metadataSaveError = error instanceof Error ? error.message : String(error);
|
|
1492
|
+
}
|
|
1493
|
+
const accepted = options.onDetachReceipt?.(receipt) === true;
|
|
1494
|
+
if (accepted) {
|
|
1495
|
+
detachedReason = receipt.detachedReason;
|
|
1496
|
+
if (receipt.detachedReason === "intercom coordination") intercomDetached = true;
|
|
1497
|
+
}
|
|
1498
|
+
return accepted;
|
|
1499
|
+
},
|
|
1500
|
+
};
|
|
1501
|
+
let lastResult: SingleResult | undefined;
|
|
1502
|
+
const modelsToTry = candidates.length > 0 ? candidates : [undefined];
|
|
1503
|
+
modelAttemptsLoop: for (let modelIndex = 0; modelIndex < modelsToTry.length; modelIndex++) {
|
|
1504
|
+
const candidate = modelsToTry[modelIndex];
|
|
1505
|
+
for (let startupAttemptIndex = 0; ; startupAttemptIndex++) {
|
|
1506
|
+
const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
|
|
1507
|
+
const result = await runSingleAttempt(runtimeCwd, agent, taskWithAcceptance, candidate, attemptOptions, {
|
|
1508
|
+
sessionEnabled,
|
|
1509
|
+
systemPrompt,
|
|
1510
|
+
resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
|
|
1511
|
+
skillsWarning: missingSkills.length > 0 ? `Skills not found: ${missingSkills.join(", ")}` : undefined,
|
|
1512
|
+
jsonlPath,
|
|
1513
|
+
artifactPaths: artifactPathsResult,
|
|
1514
|
+
transcriptWriter,
|
|
1515
|
+
attemptNotes,
|
|
1516
|
+
modelCandidates: candidates
|
|
1517
|
+
.map((modelCandidate) => applyThinkingSuffix(modelCandidate, options.thinkingOverride ?? agent.thinking, options.thinkingOverride !== undefined))
|
|
1518
|
+
.filter((modelCandidate): modelCandidate is string => Boolean(modelCandidate)),
|
|
1519
|
+
outputSnapshot,
|
|
1520
|
+
originalTask: task,
|
|
1521
|
+
});
|
|
1522
|
+
lastResult = result;
|
|
1523
|
+
if (startupAttemptIndex === 0) {
|
|
1524
|
+
if (result.model) attemptedModels.push(result.model);
|
|
1525
|
+
else if (candidate) attemptedModels.push(candidate);
|
|
1526
|
+
}
|
|
1527
|
+
sumUsage(aggregateUsage, result.usage);
|
|
1528
|
+
totalToolCount += result.progressSummary?.toolCount ?? 0;
|
|
1529
|
+
totalDurationMs += result.progressSummary?.durationMs ?? 0;
|
|
1530
|
+
const attemptSucceeded = result.exitCode === 0 && !result.error;
|
|
1531
|
+
const attempt: ModelAttempt = {
|
|
1532
|
+
model: result.model ?? candidate ?? agent.model ?? "default",
|
|
1533
|
+
success: attemptSucceeded,
|
|
1534
|
+
exitCode: result.exitCode,
|
|
1535
|
+
error: result.error,
|
|
1536
|
+
usage: { ...result.usage },
|
|
1537
|
+
};
|
|
1538
|
+
modelAttempts.push(attempt);
|
|
1539
|
+
// Preserve the legacy intercom handoff contract: once this logical run has
|
|
1540
|
+
// been handed to a supervisor, terminating that attempt must not launch a
|
|
1541
|
+
// startup retry or model fallback. Explicit user detach retains fallback.
|
|
1542
|
+
if (intercomDetached || result.timedOut || result.turnBudgetExceeded) break modelAttemptsLoop;
|
|
1543
|
+
if (attemptSucceeded) break modelAttemptsLoop;
|
|
1544
|
+
|
|
1545
|
+
const startupFailure = isRetryableSubagentStartupFailure({
|
|
1546
|
+
exitCode: result.exitCode,
|
|
1547
|
+
error: result.error,
|
|
1548
|
+
finalOutput: result.finalOutput,
|
|
1549
|
+
messageCount: result.messages?.length ?? 0,
|
|
1550
|
+
toolCount: result.progressSummary?.toolCount ?? 0,
|
|
1551
|
+
usage: result.usage,
|
|
1552
|
+
durationMs: result.progressSummary?.durationMs ?? Number.POSITIVE_INFINITY,
|
|
1553
|
+
protocolError: result.protocolError,
|
|
1554
|
+
processSignal: result.processSignal,
|
|
1555
|
+
detached: result.detached,
|
|
1556
|
+
interrupted: result.interrupted,
|
|
1557
|
+
timedOut: result.timedOut,
|
|
1558
|
+
stopped: result.stopped,
|
|
1559
|
+
turnBudgetExceeded: result.turnBudgetExceeded,
|
|
1560
|
+
});
|
|
1561
|
+
const retryDelayMs = SUBAGENT_STARTUP_RETRY_DELAYS_MS[startupAttemptIndex];
|
|
1562
|
+
if (startupFailure && retryDelayMs !== undefined) {
|
|
1563
|
+
const retryNote = formatSubagentStartupRetryNote({
|
|
1564
|
+
model: attempt.model,
|
|
1565
|
+
attempt: startupAttemptIndex + 1,
|
|
1566
|
+
maxAttempts: SUBAGENT_STARTUP_RETRY_DELAYS_MS.length + 1,
|
|
1567
|
+
delayMs: retryDelayMs,
|
|
1568
|
+
});
|
|
1569
|
+
const shouldRetry = await waitForSubagentStartupRetry(retryDelayMs, [options.signal, options.interruptSignal]);
|
|
1570
|
+
if (!shouldRetry) {
|
|
1571
|
+
if (options.interruptSignal?.aborted) {
|
|
1572
|
+
result.exitCode = 0;
|
|
1573
|
+
result.interrupted = true;
|
|
1574
|
+
result.error = undefined;
|
|
1575
|
+
result.finalOutput = "Interrupted. Waiting for explicit next action.";
|
|
1576
|
+
if (result.progress) {
|
|
1577
|
+
result.progress.status = "running";
|
|
1578
|
+
result.progress.error = undefined;
|
|
1402
1579
|
}
|
|
1580
|
+
} else {
|
|
1581
|
+
const cancellationError = "Subagent startup retry cancelled before relaunch.";
|
|
1582
|
+
result.error = cancellationError;
|
|
1583
|
+
result.finalOutput = cancellationError;
|
|
1584
|
+
attempt.error = cancellationError;
|
|
1585
|
+
if (result.progress) result.progress.error = cancellationError;
|
|
1403
1586
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1587
|
+
break modelAttemptsLoop;
|
|
1588
|
+
}
|
|
1589
|
+
attempt.error = retryNote;
|
|
1590
|
+
attemptNotes.push(retryNote);
|
|
1591
|
+
continue;
|
|
1592
|
+
}
|
|
1593
|
+
if (startupFailure) {
|
|
1594
|
+
const startupError = formatSubagentStartupRetryExhaustedError({
|
|
1595
|
+
model: attempt.model,
|
|
1596
|
+
attempts: startupAttemptIndex + 1,
|
|
1412
1597
|
});
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
systemPrompt,
|
|
1425
|
-
resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
|
|
1426
|
-
skillsWarning: missingSkills.length > 0 ? `Skills not found: ${missingSkills.join(", ")}` : undefined,
|
|
1427
|
-
jsonlPath,
|
|
1428
|
-
artifactPaths: artifactPathsResult,
|
|
1429
|
-
transcriptWriter,
|
|
1430
|
-
attemptNotes,
|
|
1431
|
-
modelCandidates: candidates.map((candidate) => applyThinkingSuffix(candidate, options.thinkingOverride ?? agent.thinking, options.thinkingOverride !== undefined)),
|
|
1432
|
-
outputSnapshot,
|
|
1433
|
-
originalTask: task,
|
|
1434
|
-
});
|
|
1435
|
-
lastResult = result;
|
|
1436
|
-
if (result.model) attemptedModels.push(result.model);
|
|
1437
|
-
else if (candidate) attemptedModels.push(candidate);
|
|
1438
|
-
sumUsage(aggregateUsage, result.usage);
|
|
1439
|
-
totalToolCount += result.progressSummary?.toolCount ?? 0;
|
|
1440
|
-
totalDurationMs += result.progressSummary?.durationMs ?? 0;
|
|
1441
|
-
const attemptSucceeded = result.exitCode === 0 && !result.error;
|
|
1442
|
-
const attempt: ModelAttempt = {
|
|
1443
|
-
model: result.model ?? candidate ?? agent.model ?? "default",
|
|
1444
|
-
success: attemptSucceeded,
|
|
1445
|
-
exitCode: result.exitCode,
|
|
1446
|
-
error: result.error,
|
|
1447
|
-
usage: { ...result.usage },
|
|
1448
|
-
};
|
|
1449
|
-
modelAttempts.push(attempt);
|
|
1450
|
-
if (result.detached || result.timedOut || result.turnBudgetExceeded) {
|
|
1451
|
-
break;
|
|
1452
|
-
}
|
|
1453
|
-
if (attemptSucceeded) {
|
|
1454
|
-
break;
|
|
1455
|
-
}
|
|
1456
|
-
if (!isRetryableModelFailure(result.error) || i === modelsToTry.length - 1) {
|
|
1598
|
+
result.error = startupError;
|
|
1599
|
+
result.finalOutput = startupError;
|
|
1600
|
+
if (result.progress) {
|
|
1601
|
+
result.progress.error = startupError;
|
|
1602
|
+
result.progress.status = "failed";
|
|
1603
|
+
}
|
|
1604
|
+
attempt.error = startupError;
|
|
1605
|
+
break modelAttemptsLoop;
|
|
1606
|
+
}
|
|
1607
|
+
if (!isRetryableModelFailure(result.error) || modelIndex === modelsToTry.length - 1) break modelAttemptsLoop;
|
|
1608
|
+
attemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[modelIndex + 1]));
|
|
1457
1609
|
break;
|
|
1458
1610
|
}
|
|
1459
|
-
attemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[i + 1]));
|
|
1460
1611
|
}
|
|
1461
1612
|
|
|
1462
1613
|
const result = withRunContext(lastResult ?? {
|
|
1614
|
+
index: options.index ?? 0,
|
|
1463
1615
|
agent: agentName,
|
|
1464
1616
|
task,
|
|
1465
1617
|
exitCode: 1,
|
|
@@ -1476,35 +1628,33 @@ export async function runSync(
|
|
|
1476
1628
|
tokens: aggregateUsage.input + aggregateUsage.output,
|
|
1477
1629
|
durationMs: totalDurationMs,
|
|
1478
1630
|
};
|
|
1479
|
-
if (attemptNotes.length > 0 && result.progress) {
|
|
1480
|
-
result.progress.recentOutput = [...attemptNotes, ...result.progress.recentOutput];
|
|
1481
|
-
if (result.progress.recentOutput.length > 50) {
|
|
1482
|
-
result.progress.recentOutput.splice(50);
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
1631
|
if (transcriptWriter) result.transcriptPath = artifactPathsResult?.transcriptPath;
|
|
1487
1632
|
if (transcriptWriter?.getError()) result.transcriptError = transcriptWriter.getError();
|
|
1488
1633
|
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1634
|
+
try {
|
|
1635
|
+
if (artifactPathsResult && options.artifactConfig?.enabled !== false) {
|
|
1636
|
+
result.artifactPaths = artifactPathsResult;
|
|
1637
|
+
if (options.artifactConfig?.includeOutput !== false) {
|
|
1638
|
+
writeArtifact(artifactPathsResult.outputPath, formatOutputArtifactContent({
|
|
1639
|
+
output: artifactOutputByResult.get(result) ?? result.finalOutput ?? "",
|
|
1640
|
+
error: result.error,
|
|
1641
|
+
transcriptPath: result.transcriptPath,
|
|
1642
|
+
metadataPath: options.artifactConfig?.includeMetadata === false ? undefined : artifactPathsResult.metadataPath,
|
|
1643
|
+
}));
|
|
1644
|
+
}
|
|
1645
|
+
if (options.maxOutput) {
|
|
1646
|
+
const config = { ...DEFAULT_MAX_OUTPUT, ...options.maxOutput };
|
|
1647
|
+
const truncationResult = truncateOutput(result.finalOutput ?? "", config, artifactPathsResult.outputPath);
|
|
1648
|
+
if (truncationResult.truncated) result.truncation = truncationResult;
|
|
1649
|
+
}
|
|
1650
|
+
} else if (options.maxOutput) {
|
|
1500
1651
|
const config = { ...DEFAULT_MAX_OUTPUT, ...options.maxOutput };
|
|
1501
|
-
const truncationResult = truncateOutput(result.finalOutput ?? "", config
|
|
1652
|
+
const truncationResult = truncateOutput(result.finalOutput ?? "", config);
|
|
1502
1653
|
if (truncationResult.truncated) result.truncation = truncationResult;
|
|
1503
1654
|
}
|
|
1504
|
-
}
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1507
|
-
if (truncationResult.truncated) result.truncation = truncationResult;
|
|
1655
|
+
} catch (error) {
|
|
1656
|
+
const message = `Artifact output post-processing failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
1657
|
+
result.outputSaveError = result.outputSaveError ? `${result.outputSaveError}\n${message}` : message;
|
|
1508
1658
|
}
|
|
1509
1659
|
|
|
1510
1660
|
if (options.sessionFile && (existsSync(options.sessionFile) || result.messages?.length)) {
|
|
@@ -1517,28 +1667,36 @@ export async function runSync(
|
|
|
1517
1667
|
const childWrittenOutput = options.outputPath
|
|
1518
1668
|
? extractChildWrittenOutput(result.messages, options.outputPath, options.cwd ?? runtimeCwd)
|
|
1519
1669
|
: undefined;
|
|
1520
|
-
|
|
1521
|
-
result.
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
:
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1670
|
+
try {
|
|
1671
|
+
if (result.interrupted && detachedReason === "user request") {
|
|
1672
|
+
// Only an accepted user-detach receipt needs a non-rejecting terminal
|
|
1673
|
+
// ledger. Attached and background execution retain their baseline
|
|
1674
|
+
// acceptance behavior.
|
|
1675
|
+
result.acceptance = buildInterruptedAcceptanceLedger(effectiveAcceptance);
|
|
1676
|
+
} else if (result.stopped) {
|
|
1677
|
+
result.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: "stopped", message: "Acceptance was not evaluated because the subagent was stopped." });
|
|
1678
|
+
} else if (result.timedOut) {
|
|
1679
|
+
result.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: "timeout", message: "Acceptance was not evaluated because the subagent timed out." });
|
|
1680
|
+
} else if (result.turnBudgetExceeded) {
|
|
1681
|
+
result.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: "turn-budget", message: "Acceptance was not evaluated because the subagent exceeded its turn budget." });
|
|
1682
|
+
} else {
|
|
1683
|
+
result.acceptance = await evaluateAcceptance({
|
|
1684
|
+
acceptance: effectiveAcceptance,
|
|
1685
|
+
output: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? "",
|
|
1686
|
+
fileOutput: childWrittenOutput !== undefined && options.outputPath
|
|
1687
|
+
? { content: childWrittenOutput, path: options.outputPath, authoritative: options.outputMode === "file-only" }
|
|
1688
|
+
: undefined,
|
|
1689
|
+
cwd: options.cwd ?? runtimeCwd,
|
|
1690
|
+
reportOptional: isAgentContractV1(options.agentContract),
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
1693
|
+
} catch (error) {
|
|
1694
|
+
const message = `Acceptance evaluation failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
1695
|
+
result.acceptance = buildSkippedAcceptanceLedger(effectiveAcceptance, { id: "acceptance-evaluation", message });
|
|
1538
1696
|
}
|
|
1539
1697
|
const acceptanceFailure = acceptanceFailureMessage(result.acceptance);
|
|
1540
1698
|
stripAcceptanceReportsFromMessages(result.messages);
|
|
1541
|
-
if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.
|
|
1699
|
+
if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.interrupted && !result.timedOut && !isAgentContractV1(options.agentContract)) {
|
|
1542
1700
|
result.exitCode = 1;
|
|
1543
1701
|
result.error = result.error ? `${result.error}\n${acceptanceFailure}` : acceptanceFailure;
|
|
1544
1702
|
if (result.progress) {
|
|
@@ -1547,7 +1705,143 @@ export async function runSync(
|
|
|
1547
1705
|
}
|
|
1548
1706
|
}
|
|
1549
1707
|
if (isAgentContractV1(options.agentContract)) attachContractProjections(result);
|
|
1550
|
-
|
|
1708
|
+
try {
|
|
1709
|
+
persistResultMetadata(result);
|
|
1710
|
+
} catch (error) {
|
|
1711
|
+
result.metadataSaveError = error instanceof Error ? error.message : String(error);
|
|
1712
|
+
}
|
|
1551
1713
|
|
|
1552
1714
|
return result;
|
|
1553
1715
|
}
|
|
1716
|
+
|
|
1717
|
+
/**
|
|
1718
|
+
* Runs the authoritative completion pipeline independently from the foreground
|
|
1719
|
+
* receipt. Detachment is same-runtime only: it does not adopt or daemonize work.
|
|
1720
|
+
*/
|
|
1721
|
+
export async function runSync(
|
|
1722
|
+
runtimeCwd: string,
|
|
1723
|
+
agents: AgentConfig[],
|
|
1724
|
+
agentName: string,
|
|
1725
|
+
task: string,
|
|
1726
|
+
options: RunSyncOptions,
|
|
1727
|
+
): Promise<SingleResult> {
|
|
1728
|
+
// Capture the strict contract before consumer-owned objects can be mutated
|
|
1729
|
+
// after a detached receipt is published.
|
|
1730
|
+
const strictContract = isAgentContractV1(options.agentContract);
|
|
1731
|
+
let detachedReason: string | undefined;
|
|
1732
|
+
let publishedReceipt: SingleResult | undefined;
|
|
1733
|
+
let activeDetachAttempt: ((reason?: string) => boolean) | undefined;
|
|
1734
|
+
let detachReadyPublished = false;
|
|
1735
|
+
let resolveReceipt!: (result: SingleResult) => void;
|
|
1736
|
+
const receipt = new Promise<SingleResult>((resolve) => { resolveReceipt = resolve; });
|
|
1737
|
+
const originSignal = options.signal;
|
|
1738
|
+
const originController = new AbortController();
|
|
1739
|
+
const forwardOriginAbort = () => {
|
|
1740
|
+
if (!detachedReason) originController.abort(originSignal?.reason);
|
|
1741
|
+
};
|
|
1742
|
+
if (originSignal?.aborted) forwardOriginAbort();
|
|
1743
|
+
else originSignal?.addEventListener("abort", forwardOriginAbort, { once: true });
|
|
1744
|
+
|
|
1745
|
+
const completion = runSyncCompletion(runtimeCwd, agents, agentName, task, {
|
|
1746
|
+
...options,
|
|
1747
|
+
signal: originController.signal,
|
|
1748
|
+
onDetachedExit: undefined,
|
|
1749
|
+
onDetachReceipt: (detachedReceipt) => {
|
|
1750
|
+
if (detachedReason || publishedReceipt) return false;
|
|
1751
|
+
// Keep the authoritative result, fallback snapshot, and caller receipt
|
|
1752
|
+
// separately owned while the completion pipeline remains live.
|
|
1753
|
+
publishedReceipt = structuredClone(detachedReceipt);
|
|
1754
|
+
const callerReceipt = structuredClone(detachedReceipt);
|
|
1755
|
+
// A strict contract was already validated before detach; normalize private
|
|
1756
|
+
// and caller snapshots to the only safely known version.
|
|
1757
|
+
publishedReceipt.agentContract = strictContract ? { version: 1 } : undefined;
|
|
1758
|
+
callerReceipt.agentContract = strictContract ? { version: 1 } : undefined;
|
|
1759
|
+
detachedReason = detachedReceipt.detachedReason ?? "user request";
|
|
1760
|
+
resolveReceipt(callerReceipt);
|
|
1761
|
+
return true;
|
|
1762
|
+
},
|
|
1763
|
+
onDetachReady: (detachAttempt) => {
|
|
1764
|
+
activeDetachAttempt = detachAttempt;
|
|
1765
|
+
if (detachReadyPublished) return;
|
|
1766
|
+
detachReadyPublished = true;
|
|
1767
|
+
options.onDetachReady?.((reason = "user request") => {
|
|
1768
|
+
if (detachedReason || originController.signal.aborted) return false;
|
|
1769
|
+
return activeDetachAttempt?.(reason) ?? false;
|
|
1770
|
+
});
|
|
1771
|
+
},
|
|
1772
|
+
});
|
|
1773
|
+
|
|
1774
|
+
const authoritativeCompletion = completion.catch((error: unknown) => {
|
|
1775
|
+
if (!publishedReceipt || !detachedReason) throw error;
|
|
1776
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1777
|
+
const failureMessage = `Detached completion pipeline failed after receipt: ${message}`;
|
|
1778
|
+
const failedProgress: AgentProgress = publishedReceipt.progress
|
|
1779
|
+
? { ...publishedReceipt.progress, status: "failed", error: failureMessage }
|
|
1780
|
+
: {
|
|
1781
|
+
index: options.index ?? 0,
|
|
1782
|
+
agent: publishedReceipt.agent,
|
|
1783
|
+
status: "failed",
|
|
1784
|
+
task: publishedReceipt.task,
|
|
1785
|
+
recentTools: [],
|
|
1786
|
+
recentOutput: [],
|
|
1787
|
+
toolCount: publishedReceipt.progressSummary?.toolCount ?? 0,
|
|
1788
|
+
tokens: publishedReceipt.progressSummary?.tokens ?? 0,
|
|
1789
|
+
durationMs: publishedReceipt.progressSummary?.durationMs ?? 0,
|
|
1790
|
+
error: failureMessage,
|
|
1791
|
+
};
|
|
1792
|
+
const failedResult: SingleResult = {
|
|
1793
|
+
...publishedReceipt,
|
|
1794
|
+
detached: undefined,
|
|
1795
|
+
detachedReason,
|
|
1796
|
+
exitCode: 1,
|
|
1797
|
+
error: failureMessage,
|
|
1798
|
+
finalOutput: failureMessage,
|
|
1799
|
+
progress: failedProgress,
|
|
1800
|
+
progressSummary: {
|
|
1801
|
+
toolCount: failedProgress.toolCount,
|
|
1802
|
+
tokens: failedProgress.tokens,
|
|
1803
|
+
durationMs: failedProgress.durationMs,
|
|
1804
|
+
},
|
|
1805
|
+
acceptance: publishedReceipt.acceptance
|
|
1806
|
+
? buildSkippedAcceptanceLedger(publishedReceipt.acceptance.effectiveAcceptance, { id: "completion-pipeline", message: failureMessage })
|
|
1807
|
+
: undefined,
|
|
1808
|
+
};
|
|
1809
|
+
if (strictContract) attachContractProjections(failedResult);
|
|
1810
|
+
try {
|
|
1811
|
+
// Replace the provisional detach receipt metadata with the authoritative
|
|
1812
|
+
// terminal failure. Persistence remains best-effort and cannot orphan work.
|
|
1813
|
+
persistSingleResultMetadata({
|
|
1814
|
+
metadataPath: failedResult.artifactPaths?.metadataPath,
|
|
1815
|
+
enabled: options.artifactConfig?.enabled !== false && options.artifactConfig?.includeMetadata !== false,
|
|
1816
|
+
runId: options.runId,
|
|
1817
|
+
agent: failedResult.agent,
|
|
1818
|
+
task: failedResult.task,
|
|
1819
|
+
result: failedResult,
|
|
1820
|
+
});
|
|
1821
|
+
} catch (metadataError) {
|
|
1822
|
+
failedResult.metadataSaveError = metadataError instanceof Error ? metadataError.message : String(metadataError);
|
|
1823
|
+
}
|
|
1824
|
+
return failedResult;
|
|
1825
|
+
});
|
|
1826
|
+
|
|
1827
|
+
let terminalCallbackInvoked = false;
|
|
1828
|
+
void authoritativeCompletion.then((terminalResult) => {
|
|
1829
|
+
if (!detachedReason || terminalCallbackInvoked) return;
|
|
1830
|
+
terminalCallbackInvoked = true;
|
|
1831
|
+
terminalResult.detached = undefined;
|
|
1832
|
+
terminalResult.detachedReason = detachedReason;
|
|
1833
|
+
try {
|
|
1834
|
+
options.onDetachedExit?.(terminalResult);
|
|
1835
|
+
} catch {
|
|
1836
|
+
// The authoritative result has settled. Consumer callback failures are
|
|
1837
|
+
// contained here; each consumer owns cleanup through its own finally block.
|
|
1838
|
+
}
|
|
1839
|
+
}).catch(() => {
|
|
1840
|
+
// Attached completion rejection remains observable through the returned
|
|
1841
|
+
// promise without becoming an unhandled side-channel rejection.
|
|
1842
|
+
}).finally(() => {
|
|
1843
|
+
originSignal?.removeEventListener("abort", forwardOriginAbort);
|
|
1844
|
+
});
|
|
1845
|
+
|
|
1846
|
+
return Promise.race([authoritativeCompletion, receipt]);
|
|
1847
|
+
}
|