@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
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import * as fs from "node:fs";
|
|
3
4
|
import * as path from "node:path";
|
|
4
5
|
import { pathToFileURL } from "node:url";
|
|
5
6
|
import type { Message } from "@earendil-works/pi-ai";
|
|
6
7
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
7
8
|
import { createChildTranscriptWriter, type ChildTranscriptWriter } from "../../shared/child-transcript.ts";
|
|
8
|
-
import { consumeInterruptRequest, deliverInterruptRequest, deliverTimeoutRequest, enqueueStepSteer, stepSteerInboxDir, watchAsyncControlInbox, type SteerRequest } from "./control-channel.ts";
|
|
9
|
-
import { appendJsonl as appendRawJsonl, getArtifactPaths } from "../../shared/artifacts.ts";
|
|
10
|
-
import {
|
|
11
|
-
import { captureSingleOutputSnapshot, finalizeSingleOutput, formatSavedOutputReference, resolveSingleOutput, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
9
|
+
import { closeSteerInbox, consumeInterruptRequest, consumeSteerRequests, deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest, enqueueStepSteer, steerAcksDir, steerCapabilityPath, stepSteerInboxDir, watchAsyncControlInbox, type SteerAck, type SteerCapability, type SteerRequest } from "./control-channel.ts";
|
|
10
|
+
import { appendJsonl as appendRawJsonl, formatOutputArtifactContent, getArtifactPaths } from "../../shared/artifacts.ts";
|
|
11
|
+
import { PI_CODING_AGENT_PACKAGE, getPiSpawnCommand, resolveInstalledPiPackageRoot } from "../shared/pi-spawn.ts";
|
|
12
|
+
import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, injectOutputPathSystemPrompt, injectSingleOutputInstruction, resolveSingleOutput, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
12
13
|
import {
|
|
13
14
|
type ActivityState,
|
|
14
15
|
type ArtifactConfig,
|
|
@@ -28,6 +29,9 @@ import {
|
|
|
28
29
|
type TurnBudgetState,
|
|
29
30
|
type Usage,
|
|
30
31
|
type WorkflowGraphSnapshot,
|
|
32
|
+
type SteeringStatus,
|
|
33
|
+
type SteeringTargetState,
|
|
34
|
+
type SteeringTargetStatus,
|
|
31
35
|
DEFAULT_MAX_OUTPUT,
|
|
32
36
|
type MaxOutputConfig,
|
|
33
37
|
SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
@@ -54,14 +58,17 @@ import {
|
|
|
54
58
|
DEFAULT_GLOBAL_CONCURRENCY_LIMIT,
|
|
55
59
|
Semaphore,
|
|
56
60
|
} from "../shared/parallel-utils.ts";
|
|
57
|
-
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
|
|
61
|
+
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
|
|
58
62
|
import { outputEntryFromAsyncResult, resolveOutputReferences } from "../shared/chain-outputs.ts";
|
|
59
63
|
import { createStructuredOutputRuntime, readStructuredOutput } from "../shared/structured-output.ts";
|
|
64
|
+
import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
|
|
60
65
|
import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection } from "../shared/dynamic-fanout.ts";
|
|
61
66
|
import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent } from "../shared/nested-events.ts";
|
|
62
67
|
import { formatModelAttemptNote, isRetryableModelFailure } from "../shared/model-fallback.ts";
|
|
68
|
+
import { markProcessTerminalCandidateLeaseRelease, writeProcessTerminalCandidate, type ProcessTerminalCandidate } from "./process-terminal.ts";
|
|
69
|
+
import { createSteeringStatus, recordSteeringRequest, steeringStatus, terminalSteeringNoticeState, updateSteeringTarget } from "./steering.ts";
|
|
63
70
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
64
|
-
import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput, readStatus } from "../../shared/utils.ts";
|
|
71
|
+
import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.ts";
|
|
65
72
|
import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
|
|
66
73
|
import {
|
|
67
74
|
createMutatingFailureState,
|
|
@@ -86,13 +93,29 @@ import {
|
|
|
86
93
|
type WorktreeSetup,
|
|
87
94
|
} from "../shared/worktree.ts";
|
|
88
95
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
96
|
+
import { launchBindingDigest } from "../../shared/launch-contract.ts";
|
|
89
97
|
import { writeInitialProgressFile } from "../../shared/settings.ts";
|
|
90
98
|
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
91
|
-
import { acceptanceFailureMessage, aggregateAcceptanceReport, evaluateAcceptance, formatAcceptancePrompt, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
99
|
+
import { acceptanceFailureMessage, aggregateAcceptanceReport, buildSkippedAcceptanceLedger, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
100
|
+
import { attachContractProjections, isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
92
101
|
import { waitForImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
93
102
|
import { appendRunnerStepsToStatus, consumeChainAppendRequests, countPendingChainAppendRequests } from "./chain-append.ts";
|
|
94
|
-
import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState,
|
|
103
|
+
import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, turnBudgetDecision, turnBudgetDeferredNote, turnBudgetDeferredState, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from "../shared/turn-budget.ts";
|
|
95
104
|
import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
|
|
105
|
+
import { formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup } from "../shared/parallel-handoff.ts";
|
|
106
|
+
import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
|
|
107
|
+
import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, projectChildLifecycle, type ChildLifecycleAction, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
|
|
108
|
+
import { acquireSessionLease, type SessionLeaseRequest } from "../shared/session-lease.ts";
|
|
109
|
+
import { decodeSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
110
|
+
import {
|
|
111
|
+
CHILD_WATCHDOG_CONFIG_ENV,
|
|
112
|
+
acceptChildWatchdogEvent,
|
|
113
|
+
childWatchdogIsActive,
|
|
114
|
+
decodeChildWatchdogConfig,
|
|
115
|
+
isChildWatchdogStatusEvent,
|
|
116
|
+
resolveChildWatchdogConfig,
|
|
117
|
+
type ChildWatchdogStateSnapshot,
|
|
118
|
+
} from "../../watchdog/child-status.ts";
|
|
96
119
|
|
|
97
120
|
interface SubagentRunConfig {
|
|
98
121
|
id: string;
|
|
@@ -126,19 +149,29 @@ interface SubagentRunConfig {
|
|
|
126
149
|
deadlineAt?: number;
|
|
127
150
|
turnBudget?: ResolvedTurnBudget;
|
|
128
151
|
toolBudget?: ResolvedToolBudget;
|
|
152
|
+
revivalLease?: SessionLeaseRequest;
|
|
153
|
+
revivalLeaseToken?: string;
|
|
129
154
|
/** Global cap on simultaneously-running subagent tasks within this run. */
|
|
130
155
|
globalConcurrencyLimit?: number;
|
|
156
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
157
|
+
launchContractDigest?: string;
|
|
158
|
+
runnerProcessInstanceId?: string;
|
|
131
159
|
}
|
|
132
160
|
|
|
133
161
|
interface StepResult {
|
|
134
162
|
agent: string;
|
|
163
|
+
context?: "fresh" | "fork";
|
|
164
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
165
|
+
capabilityAudit?: import("../shared/capability-ceiling.ts").SubagentCapabilityAudit;
|
|
135
166
|
output: string;
|
|
136
167
|
error?: string;
|
|
168
|
+
protocolError?: ProtocolOutputLimit;
|
|
137
169
|
success: boolean;
|
|
138
170
|
exitCode?: number | null;
|
|
139
171
|
skipped?: boolean;
|
|
140
172
|
interrupted?: boolean;
|
|
141
173
|
timedOut?: boolean;
|
|
174
|
+
stopped?: boolean;
|
|
142
175
|
turnBudget?: TurnBudgetState;
|
|
143
176
|
turnBudgetExceeded?: boolean;
|
|
144
177
|
wrapUpRequested?: boolean;
|
|
@@ -154,10 +187,18 @@ interface StepResult {
|
|
|
154
187
|
truncated?: boolean;
|
|
155
188
|
transcriptPath?: string;
|
|
156
189
|
transcriptError?: string;
|
|
190
|
+
agentContract?: import("../../shared/types.ts").AgentContract;
|
|
191
|
+
launchContractDigest?: string;
|
|
192
|
+
execution?: import("../../shared/types.ts").ExecutionProjection;
|
|
193
|
+
review?: import("../../shared/types.ts").ReviewProjection;
|
|
194
|
+
effects?: import("../../shared/types.ts").EffectsProjection;
|
|
157
195
|
structuredOutput?: unknown;
|
|
158
196
|
structuredOutputPath?: string;
|
|
159
197
|
structuredOutputSchemaPath?: string;
|
|
160
198
|
acceptance?: import("../../shared/types.ts").AcceptanceLedger;
|
|
199
|
+
watchdog?: import("../../shared/types.ts").ChildWatchdogProgress;
|
|
200
|
+
writerProcesses?: Array<{ processInstanceId: string; kind: "pi-writer"; attempt: number; closeObservedAt: number; exitCode: number | null; signal: string | null }>;
|
|
201
|
+
writerAttemptCount?: number;
|
|
161
202
|
}
|
|
162
203
|
|
|
163
204
|
const ASYNC_INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2";
|
|
@@ -235,6 +276,15 @@ function shouldPersistChildEvent(event: Record<string, unknown>): boolean {
|
|
|
235
276
|
return event.type !== "message_update";
|
|
236
277
|
}
|
|
237
278
|
|
|
279
|
+
function isBlockingSupervisorTool(toolName: string | undefined, args: unknown): boolean {
|
|
280
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) return false;
|
|
281
|
+
if (toolName === "contact_supervisor") {
|
|
282
|
+
const reason = (args as Record<string, unknown>).reason;
|
|
283
|
+
return reason === "need_decision" || reason === "interview_request";
|
|
284
|
+
}
|
|
285
|
+
return toolName === "intercom" && (args as Record<string, unknown>).action === "ask";
|
|
286
|
+
}
|
|
287
|
+
|
|
238
288
|
function findLatestSessionFile(sessionDir: string): string | null {
|
|
239
289
|
try {
|
|
240
290
|
const files = fs
|
|
@@ -254,19 +304,6 @@ function emptyUsage(): Usage {
|
|
|
254
304
|
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
|
|
255
305
|
}
|
|
256
306
|
|
|
257
|
-
function usageFromAttempts(attempts: ModelAttempt[] | undefined): Usage {
|
|
258
|
-
const usage = emptyUsage();
|
|
259
|
-
for (const attempt of attempts ?? []) {
|
|
260
|
-
usage.input += attempt.usage?.input ?? 0;
|
|
261
|
-
usage.output += attempt.usage?.output ?? 0;
|
|
262
|
-
usage.cacheRead += attempt.usage?.cacheRead ?? 0;
|
|
263
|
-
usage.cacheWrite += attempt.usage?.cacheWrite ?? 0;
|
|
264
|
-
usage.cost += attempt.usage?.cost ?? 0;
|
|
265
|
-
usage.turns += attempt.usage?.turns ?? 0;
|
|
266
|
-
}
|
|
267
|
-
return usage;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
307
|
function tokenUsageFromAttempts(attempts: ModelAttempt[] | undefined): TokenUsage | null {
|
|
271
308
|
if (!attempts || attempts.length === 0) return null;
|
|
272
309
|
let input = 0;
|
|
@@ -304,11 +341,13 @@ function appendRecentStepOutput(step: RunnerStatusStep, lines: string[]): void {
|
|
|
304
341
|
}
|
|
305
342
|
}
|
|
306
343
|
|
|
307
|
-
function
|
|
308
|
-
|
|
309
|
-
const hasToolCall = Array.isArray(message.content)
|
|
344
|
+
function assistantStartsToolCall(message: Message): boolean {
|
|
345
|
+
return Array.isArray(message.content)
|
|
310
346
|
&& message.content.some((part) => (part as { type?: string }).type === "toolCall");
|
|
311
|
-
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function isTerminalAssistantStop(message: Message): boolean {
|
|
350
|
+
return (message as { stopReason?: string }).stopReason === "stop" && !assistantStartsToolCall(message);
|
|
312
351
|
}
|
|
313
352
|
|
|
314
353
|
function resetStepLiveDetail(step: RunnerStatusStep): void {
|
|
@@ -348,6 +387,7 @@ interface ChildEvent {
|
|
|
348
387
|
message?: ChildMessage;
|
|
349
388
|
toolName?: string;
|
|
350
389
|
args?: Record<string, unknown>;
|
|
390
|
+
willRetry?: unknown;
|
|
351
391
|
}
|
|
352
392
|
|
|
353
393
|
interface RunPiStreamingResult {
|
|
@@ -357,15 +397,21 @@ interface RunPiStreamingResult {
|
|
|
357
397
|
usage: Usage;
|
|
358
398
|
model?: string;
|
|
359
399
|
error?: string;
|
|
400
|
+
protocolError?: ProtocolOutputLimit;
|
|
360
401
|
finalOutput: string;
|
|
361
402
|
interrupted?: boolean;
|
|
362
403
|
timedOut?: boolean;
|
|
404
|
+
stopped?: boolean;
|
|
363
405
|
turnBudget?: TurnBudgetState;
|
|
364
406
|
turnBudgetExceeded?: boolean;
|
|
365
407
|
wrapUpRequested?: boolean;
|
|
366
408
|
toolBudget?: ToolBudgetState;
|
|
367
409
|
toolBudgetBlocked?: boolean;
|
|
368
410
|
observedMutationAttempt?: boolean;
|
|
411
|
+
watchdog?: ChildWatchdogStateSnapshot;
|
|
412
|
+
processInstanceId: string;
|
|
413
|
+
processCloseObservedAt?: number;
|
|
414
|
+
processSignal?: string | null;
|
|
369
415
|
}
|
|
370
416
|
|
|
371
417
|
function runPiStreaming(
|
|
@@ -382,12 +428,17 @@ function runPiStreaming(
|
|
|
382
428
|
transcriptWriter?: ChildTranscriptWriter,
|
|
383
429
|
registerTimeout?: (interrupt: (() => void) | undefined) => void,
|
|
384
430
|
timeoutMessage?: string,
|
|
431
|
+
registerStop?: (stop: (() => void) | undefined) => void,
|
|
432
|
+
stopMessage?: string,
|
|
385
433
|
registerTurnBudgetAbort?: (abort: ((message: string, state?: TurnBudgetState) => void) | undefined) => void,
|
|
434
|
+
onWriterProcess?: (writer: { state: "none" | "spawning" } | { state: "running"; pid: number }) => void,
|
|
386
435
|
): Promise<RunPiStreamingResult> {
|
|
387
436
|
return new Promise((resolve) => {
|
|
437
|
+
const processInstanceId = randomUUID();
|
|
438
|
+
onWriterProcess?.({ state: "spawning" });
|
|
388
439
|
const outputStream = fs.createWriteStream(outputFile, { flags: "w" });
|
|
389
440
|
const spawnEnv = { ...process.env, ...(env ?? {}), ...getSubagentDepthEnv(maxSubagentDepth) };
|
|
390
|
-
const spawnSpec =
|
|
441
|
+
const spawnSpec = getPiSpawnCommand(args, {
|
|
391
442
|
...(piPackageRoot ? { piPackageRoot } : {}),
|
|
392
443
|
...(piArgv1 ? { argv1: piArgv1 } : {}),
|
|
393
444
|
});
|
|
@@ -397,21 +448,35 @@ function runPiStreaming(
|
|
|
397
448
|
env: spawnEnv,
|
|
398
449
|
windowsHide: true,
|
|
399
450
|
});
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
let stderrBuf = "";
|
|
451
|
+
const stderrTail = createBoundedByteTail();
|
|
452
|
+
const rawStdoutTail = createBoundedByteTail();
|
|
403
453
|
const messages: Message[] = [];
|
|
404
454
|
const usage = emptyUsage();
|
|
405
455
|
let model: string | undefined;
|
|
406
|
-
let
|
|
456
|
+
let writerRegistrationError: string | undefined;
|
|
457
|
+
if (typeof child.pid === "number") {
|
|
458
|
+
try {
|
|
459
|
+
onWriterProcess?.({ state: "running", pid: child.pid });
|
|
460
|
+
} catch (writerError) {
|
|
461
|
+
writerRegistrationError = `Failed to record revived Pi writer ownership: ${writerError instanceof Error ? writerError.message : String(writerError)}`;
|
|
462
|
+
trySignalChild(child, "SIGKILL");
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
let error: string | undefined = writerRegistrationError;
|
|
407
466
|
let assistantError: string | undefined;
|
|
408
467
|
let interrupted = false;
|
|
409
468
|
let timedOut = false;
|
|
469
|
+
let stopped = false;
|
|
410
470
|
let turnBudgetExceeded = false;
|
|
411
471
|
let turnBudgetMessage: string | undefined;
|
|
412
472
|
let turnBudget: TurnBudgetState | undefined;
|
|
413
473
|
let observedMutationAttempt = false;
|
|
414
|
-
const
|
|
474
|
+
const childWatchdogConfig = decodeChildWatchdogConfig(env?.[CHILD_WATCHDOG_CONFIG_ENV]);
|
|
475
|
+
let childWatchdogState: ChildWatchdogStateSnapshot | undefined;
|
|
476
|
+
let applyChildLifecycle = (_action: ChildLifecycleAction): void => {};
|
|
477
|
+
const updateChildWatchdogState = (snapshot: ChildWatchdogStateSnapshot): void => {
|
|
478
|
+
childWatchdogState = snapshot;
|
|
479
|
+
};
|
|
415
480
|
|
|
416
481
|
const writeOutputLine = (line: string) => {
|
|
417
482
|
if (!line.trim()) return;
|
|
@@ -449,7 +514,7 @@ function runPiStreaming(
|
|
|
449
514
|
try {
|
|
450
515
|
event = JSON.parse(line) as ChildEvent;
|
|
451
516
|
} catch {
|
|
452
|
-
|
|
517
|
+
rawStdoutTail.push(`${line}\n`);
|
|
453
518
|
writeOutputLine(line);
|
|
454
519
|
appendChildLine("subagent.child.stdout", line);
|
|
455
520
|
return;
|
|
@@ -457,6 +522,38 @@ function runPiStreaming(
|
|
|
457
522
|
|
|
458
523
|
appendChildEvent(event);
|
|
459
524
|
transcriptWriter?.writeChildEvent(event);
|
|
525
|
+
if (event.type === "agent_settled") agentSettledReceived = true;
|
|
526
|
+
applyChildLifecycle(projectChildLifecycle(event));
|
|
527
|
+
|
|
528
|
+
if (isChildWatchdogStatusEvent(event)) {
|
|
529
|
+
if (!childWatchdogConfig) return;
|
|
530
|
+
const next = acceptChildWatchdogEvent({
|
|
531
|
+
current: childWatchdogState,
|
|
532
|
+
event,
|
|
533
|
+
runId: childEventContext?.runId,
|
|
534
|
+
agent: childEventContext?.agent,
|
|
535
|
+
childIndex: childEventContext?.stepIndex,
|
|
536
|
+
});
|
|
537
|
+
if (!next) return;
|
|
538
|
+
updateChildWatchdogState(next);
|
|
539
|
+
onChildEvent?.(event);
|
|
540
|
+
if (childWatchdogIsActive(next)) {
|
|
541
|
+
if (finalDrainTimer) {
|
|
542
|
+
clearTimeout(finalDrainTimer);
|
|
543
|
+
finalDrainTimer = undefined;
|
|
544
|
+
}
|
|
545
|
+
if (finalHardKillTimer) {
|
|
546
|
+
clearTimeout(finalHardKillTimer);
|
|
547
|
+
finalHardKillTimer = undefined;
|
|
548
|
+
}
|
|
549
|
+
armWatchdogTail();
|
|
550
|
+
} else {
|
|
551
|
+
clearWatchdogTailTimer();
|
|
552
|
+
if (cleanTerminalAssistantStopReceived || agentSettledReceived) startFinalDrain();
|
|
553
|
+
}
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
|
|
460
557
|
onChildEvent?.(event);
|
|
461
558
|
|
|
462
559
|
if (event.type === "tool_execution_start" && event.toolName) {
|
|
@@ -486,24 +583,11 @@ function runPiStreaming(
|
|
|
486
583
|
if (isTerminalAssistantStop(event.message)) {
|
|
487
584
|
if (!event.message.errorMessage && extractTextFromContent(event.message.content).trim()) assistantError = undefined;
|
|
488
585
|
cleanTerminalAssistantStopReceived ||= !event.message.errorMessage;
|
|
489
|
-
|
|
586
|
+
applyChildLifecycle(projectChildLifecycle(event, true));
|
|
490
587
|
}
|
|
491
588
|
}
|
|
492
589
|
};
|
|
493
590
|
|
|
494
|
-
const processStderrText = (text: string) => {
|
|
495
|
-
stderr += text;
|
|
496
|
-
stderrBuf += text;
|
|
497
|
-
outputStream.write(text);
|
|
498
|
-
if (!childEventContext) return;
|
|
499
|
-
const lines = stderrBuf.split("\n");
|
|
500
|
-
stderrBuf = lines.pop() || "";
|
|
501
|
-
for (const line of lines) {
|
|
502
|
-
if (!line.trim()) continue;
|
|
503
|
-
appendChildLine("subagent.child.stderr", line);
|
|
504
|
-
}
|
|
505
|
-
};
|
|
506
|
-
|
|
507
591
|
// Guard both cases that can leave the parent waiting on `close` forever:
|
|
508
592
|
// a lingering stdio holder after `exit`, or a child that never exits.
|
|
509
593
|
const FINAL_STOP_GRACE_MS = 1000;
|
|
@@ -512,35 +596,68 @@ function runPiStreaming(
|
|
|
512
596
|
let childExited = false;
|
|
513
597
|
let forcedTerminationSignal = false;
|
|
514
598
|
let cleanTerminalAssistantStopReceived = false;
|
|
599
|
+
let agentSettledReceived = false;
|
|
515
600
|
let finalDrainTimer: NodeJS.Timeout | undefined;
|
|
516
601
|
let finalHardKillTimer: NodeJS.Timeout | undefined;
|
|
602
|
+
let watchdogTailTimer: NodeJS.Timeout | undefined;
|
|
517
603
|
let timeoutHardKillTimer: NodeJS.Timeout | undefined;
|
|
518
604
|
let turnBudgetTerminationTimer: NodeJS.Timeout | undefined;
|
|
519
605
|
let turnBudgetHardKillTimer: NodeJS.Timeout | undefined;
|
|
606
|
+
let protocolHardKillTimer: NodeJS.Timeout | undefined;
|
|
607
|
+
let protocolError: ProtocolOutputLimit | undefined;
|
|
520
608
|
let settled = false;
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
609
|
+
applyChildLifecycle = (action: ChildLifecycleAction): void => {
|
|
610
|
+
if (action === "cancel-drain") {
|
|
611
|
+
if (finalDrainTimer) {
|
|
612
|
+
clearTimeout(finalDrainTimer);
|
|
613
|
+
finalDrainTimer = undefined;
|
|
614
|
+
}
|
|
615
|
+
if (finalHardKillTimer) {
|
|
616
|
+
clearTimeout(finalHardKillTimer);
|
|
617
|
+
finalHardKillTimer = undefined;
|
|
618
|
+
}
|
|
619
|
+
clearWatchdogTailTimer();
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
if (action === "start-drain") startFinalDrain();
|
|
623
|
+
};
|
|
624
|
+
const failProtocol = (limit: ProtocolOutputLimit): void => {
|
|
625
|
+
if (protocolError) return;
|
|
626
|
+
protocolError = limit;
|
|
627
|
+
error = formatProtocolOutputLimit(limit);
|
|
628
|
+
if (!childExited) {
|
|
629
|
+
trySignalChild(child, "SIGTERM");
|
|
630
|
+
protocolHardKillTimer = setTimeout(() => {
|
|
631
|
+
if (!settled) trySignalChild(child, "SIGKILL");
|
|
632
|
+
}, 3000);
|
|
633
|
+
protocolHardKillTimer.unref?.();
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
const stdoutReader = createBoundedLineReader({ onLine: processStdoutLine, onLimit: failProtocol });
|
|
637
|
+
const stderrReader = createBoundedLineReader({
|
|
638
|
+
stream: "stderr",
|
|
639
|
+
maxPendingLineBytes: MAX_CHILD_STDERR_BYTES,
|
|
640
|
+
onLine: (line) => appendChildLine("subagent.child.stderr", line),
|
|
641
|
+
onLimit: (limit) => appendChildLine("subagent.child.stderr", formatProtocolOutputLimit(limit)),
|
|
528
642
|
});
|
|
529
|
-
|
|
643
|
+
const clearStdioGuard = attachPostExitStdioGuard(child, { idleMs: 2000, hardMs: 8000 });
|
|
644
|
+
child.stdout.on("data", (chunk: Buffer) => stdoutReader.push(chunk));
|
|
530
645
|
child.stderr.on("data", (chunk: Buffer) => {
|
|
531
|
-
|
|
646
|
+
stderrTail.push(chunk);
|
|
647
|
+
stderrReader.push(chunk);
|
|
648
|
+
outputStream.write(chunk);
|
|
532
649
|
});
|
|
533
650
|
registerInterrupt?.(() => {
|
|
534
|
-
if (settled || timedOut) return;
|
|
651
|
+
if (settled || timedOut || stopped) return;
|
|
535
652
|
interrupted = true;
|
|
536
653
|
if (!error) error = "Interrupted. Waiting for explicit next action.";
|
|
537
654
|
trySignalChild(child, "SIGINT");
|
|
538
655
|
setTimeout(() => {
|
|
539
|
-
if (!settled && !timedOut) trySignalChild(child, "SIGTERM");
|
|
656
|
+
if (!settled && !timedOut && !stopped) trySignalChild(child, "SIGTERM");
|
|
540
657
|
}, 1000).unref?.();
|
|
541
658
|
});
|
|
542
659
|
registerTimeout?.(() => {
|
|
543
|
-
if (settled || timedOut) return;
|
|
660
|
+
if (settled || timedOut || stopped) return;
|
|
544
661
|
timedOut = true;
|
|
545
662
|
interrupted = false;
|
|
546
663
|
error = timeoutMessage ?? "Subagent timed out.";
|
|
@@ -550,8 +667,19 @@ function runPiStreaming(
|
|
|
550
667
|
}, TIMEOUT_HARD_KILL_MS);
|
|
551
668
|
timeoutHardKillTimer.unref?.();
|
|
552
669
|
});
|
|
670
|
+
registerStop?.(() => {
|
|
671
|
+
if (settled || timedOut || stopped) return;
|
|
672
|
+
stopped = true;
|
|
673
|
+
interrupted = false;
|
|
674
|
+
error = stopMessage ?? "Subagent stopped by user.";
|
|
675
|
+
trySignalChild(child, "SIGTERM");
|
|
676
|
+
timeoutHardKillTimer = setTimeout(() => {
|
|
677
|
+
if (!settled) trySignalChild(child, "SIGKILL");
|
|
678
|
+
}, TIMEOUT_HARD_KILL_MS);
|
|
679
|
+
timeoutHardKillTimer.unref?.();
|
|
680
|
+
});
|
|
553
681
|
registerTurnBudgetAbort?.((message, state) => {
|
|
554
|
-
if (settled || timedOut || turnBudgetExceeded) return;
|
|
682
|
+
if (settled || timedOut || stopped || turnBudgetExceeded) return;
|
|
555
683
|
turnBudgetExceeded = true;
|
|
556
684
|
turnBudgetMessage = message;
|
|
557
685
|
turnBudget = state;
|
|
@@ -559,11 +687,11 @@ function runPiStreaming(
|
|
|
559
687
|
error = message;
|
|
560
688
|
trySignalChild(child, "SIGINT");
|
|
561
689
|
turnBudgetTerminationTimer = setTimeout(() => {
|
|
562
|
-
if (!settled && !timedOut) trySignalChild(child, "SIGTERM");
|
|
690
|
+
if (!settled && !timedOut && !stopped) trySignalChild(child, "SIGTERM");
|
|
563
691
|
}, 1000);
|
|
564
692
|
turnBudgetTerminationTimer.unref?.();
|
|
565
693
|
turnBudgetHardKillTimer = setTimeout(() => {
|
|
566
|
-
if (!settled && !timedOut) trySignalChild(child, "SIGKILL");
|
|
694
|
+
if (!settled && !timedOut && !stopped) trySignalChild(child, "SIGKILL");
|
|
567
695
|
}, 4000);
|
|
568
696
|
turnBudgetHardKillTimer.unref?.();
|
|
569
697
|
});
|
|
@@ -576,6 +704,7 @@ function runPiStreaming(
|
|
|
576
704
|
clearTimeout(finalHardKillTimer);
|
|
577
705
|
finalHardKillTimer = undefined;
|
|
578
706
|
}
|
|
707
|
+
clearWatchdogTailTimer();
|
|
579
708
|
if (timeoutHardKillTimer) {
|
|
580
709
|
clearTimeout(timeoutHardKillTimer);
|
|
581
710
|
timeoutHardKillTimer = undefined;
|
|
@@ -588,16 +717,24 @@ function runPiStreaming(
|
|
|
588
717
|
clearTimeout(turnBudgetHardKillTimer);
|
|
589
718
|
turnBudgetHardKillTimer = undefined;
|
|
590
719
|
}
|
|
720
|
+
if (protocolHardKillTimer) {
|
|
721
|
+
clearTimeout(protocolHardKillTimer);
|
|
722
|
+
protocolHardKillTimer = undefined;
|
|
723
|
+
}
|
|
591
724
|
};
|
|
592
725
|
function startFinalDrain(): void {
|
|
726
|
+
if (childWatchdogIsActive(childWatchdogState)) {
|
|
727
|
+
armWatchdogTail();
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
593
730
|
if (childExited || finalDrainTimer || settled) return;
|
|
594
731
|
finalDrainTimer = setTimeout(() => {
|
|
595
732
|
if (settled) return;
|
|
596
733
|
const termSent = trySignalChild(child, "SIGTERM");
|
|
597
734
|
if (!termSent) return;
|
|
598
735
|
forcedTerminationSignal = true;
|
|
599
|
-
if (!cleanTerminalAssistantStopReceived && !error && !assistantError) {
|
|
600
|
-
error = `Subagent process did not exit within ${FINAL_STOP_GRACE_MS}ms after its
|
|
736
|
+
if (!cleanTerminalAssistantStopReceived && !agentSettledReceived && !error && !assistantError) {
|
|
737
|
+
error = `Subagent process did not exit within ${FINAL_STOP_GRACE_MS}ms after its terminal event. Forcing termination.`;
|
|
601
738
|
}
|
|
602
739
|
finalHardKillTimer = setTimeout(() => {
|
|
603
740
|
if (settled) return;
|
|
@@ -607,63 +744,108 @@ function runPiStreaming(
|
|
|
607
744
|
}, FINAL_STOP_GRACE_MS);
|
|
608
745
|
finalDrainTimer.unref?.();
|
|
609
746
|
}
|
|
747
|
+
function clearWatchdogTailTimer(): void {
|
|
748
|
+
if (watchdogTailTimer) {
|
|
749
|
+
clearTimeout(watchdogTailTimer);
|
|
750
|
+
watchdogTailTimer = undefined;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
function armWatchdogTail(): void {
|
|
754
|
+
if ((!cleanTerminalAssistantStopReceived && !agentSettledReceived) || watchdogTailTimer || settled) return;
|
|
755
|
+
watchdogTailTimer = setTimeout(() => {
|
|
756
|
+
watchdogTailTimer = undefined;
|
|
757
|
+
updateChildWatchdogState({
|
|
758
|
+
phase: "stale",
|
|
759
|
+
seq: (childWatchdogState?.seq ?? 0) + 1,
|
|
760
|
+
lastUpdate: Date.now(),
|
|
761
|
+
followUpPending: false,
|
|
762
|
+
reason: "child watchdog tail timeout",
|
|
763
|
+
timedOut: true,
|
|
764
|
+
});
|
|
765
|
+
startFinalDrain();
|
|
766
|
+
}, childWatchdogConfig?.watchdogTailTimeoutMs ?? 120_000);
|
|
767
|
+
watchdogTailTimer.unref?.();
|
|
768
|
+
}
|
|
610
769
|
child.on("exit", () => {
|
|
611
770
|
childExited = true;
|
|
612
771
|
clearDrainTimers();
|
|
613
772
|
});
|
|
614
773
|
child.on("close", (exitCode, signal) => {
|
|
615
774
|
settled = true;
|
|
775
|
+
const processCloseObservedAt = Date.now();
|
|
776
|
+
try {
|
|
777
|
+
onWriterProcess?.({ state: "none" });
|
|
778
|
+
} catch {
|
|
779
|
+
// The runner still owns and releases the lease during finalization.
|
|
780
|
+
}
|
|
616
781
|
registerInterrupt?.(undefined);
|
|
617
782
|
registerTimeout?.(undefined);
|
|
783
|
+
registerStop?.(undefined);
|
|
618
784
|
registerTurnBudgetAbort?.(undefined);
|
|
619
785
|
clearDrainTimers();
|
|
620
786
|
clearStdioGuard();
|
|
621
|
-
|
|
622
|
-
|
|
787
|
+
stdoutReader.end();
|
|
788
|
+
stderrReader.end();
|
|
623
789
|
outputStream.end();
|
|
624
|
-
const
|
|
790
|
+
const stderr = stderrTail.text();
|
|
791
|
+
const finalOutput = getFinalOutput(messages) || rawStdoutTail.text().trim();
|
|
625
792
|
const finalError = error ?? assistantError;
|
|
626
|
-
const forcedDrainAfterFinalSuccess = forcedTerminationSignal && cleanTerminalAssistantStopReceived && !finalError;
|
|
793
|
+
const forcedDrainAfterFinalSuccess = forcedTerminationSignal && (cleanTerminalAssistantStopReceived || agentSettledReceived) && !finalError;
|
|
627
794
|
resolve({
|
|
628
795
|
stderr,
|
|
629
|
-
exitCode: timedOut ? 1 : turnBudgetExceeded ? 1 : interrupted || forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (exitCode ?? 1) : exitCode,
|
|
796
|
+
exitCode: timedOut || stopped ? 1 : turnBudgetExceeded ? 1 : interrupted || forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (exitCode ?? 1) : exitCode,
|
|
630
797
|
messages,
|
|
631
798
|
usage,
|
|
632
799
|
model,
|
|
633
|
-
error: timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? turnBudgetMessage : interrupted || forcedDrainAfterFinalSuccess ? undefined : finalError,
|
|
634
|
-
|
|
800
|
+
error: stopped ? (stopMessage ?? "Subagent stopped by user.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? turnBudgetMessage : interrupted || forcedDrainAfterFinalSuccess ? undefined : finalError,
|
|
801
|
+
protocolError,
|
|
802
|
+
finalOutput: (timedOut || stopped) && !finalOutput.trim() ? (stopped ? stopMessage ?? "Subagent stopped by user." : timeoutMessage ?? "Subagent timed out.") : finalOutput,
|
|
635
803
|
interrupted,
|
|
636
804
|
timedOut,
|
|
805
|
+
stopped,
|
|
637
806
|
turnBudget,
|
|
638
807
|
turnBudgetExceeded,
|
|
639
|
-
wrapUpRequested: turnBudget?.outcome === "wrap-up-requested" || turnBudgetExceeded || undefined,
|
|
808
|
+
wrapUpRequested: turnBudget?.outcome === "wrap-up-requested" || turnBudget?.outcome === "termination-deferred" || turnBudgetExceeded || undefined,
|
|
640
809
|
observedMutationAttempt,
|
|
810
|
+
watchdog: childWatchdogState,
|
|
811
|
+
processInstanceId,
|
|
812
|
+
processCloseObservedAt,
|
|
813
|
+
processSignal: signal,
|
|
641
814
|
});
|
|
642
815
|
});
|
|
643
816
|
|
|
644
817
|
child.on("error", (spawnError) => {
|
|
645
818
|
settled = true;
|
|
819
|
+
try {
|
|
820
|
+
onWriterProcess?.({ state: "none" });
|
|
821
|
+
} catch {
|
|
822
|
+
// The runner still owns and releases the lease during finalization.
|
|
823
|
+
}
|
|
646
824
|
registerInterrupt?.(undefined);
|
|
647
825
|
registerTimeout?.(undefined);
|
|
826
|
+
registerStop?.(undefined);
|
|
648
827
|
registerTurnBudgetAbort?.(undefined);
|
|
649
828
|
clearDrainTimers();
|
|
650
829
|
clearStdioGuard();
|
|
830
|
+
stdoutReader.end();
|
|
831
|
+
stderrReader.end();
|
|
651
832
|
outputStream.end();
|
|
652
|
-
const
|
|
833
|
+
const stderr = stderrTail.text();
|
|
834
|
+
const finalOutput = getFinalOutput(messages) || rawStdoutTail.text().trim();
|
|
653
835
|
const spawnErrorMessage = spawnError instanceof Error ? spawnError.message : String(spawnError);
|
|
654
|
-
resolve({ stderr, exitCode: 1, messages, usage, model, error: timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? turnBudgetMessage : error ?? assistantError ?? spawnErrorMessage, finalOutput: timedOut && !finalOutput.trim() ? (timeoutMessage ?? "Subagent timed out.") : finalOutput, timedOut, turnBudget, turnBudgetExceeded, wrapUpRequested: turnBudget?.outcome === "wrap-up-requested" || turnBudgetExceeded || undefined, observedMutationAttempt });
|
|
836
|
+
resolve({ stderr, exitCode: 1, messages, usage, model, error: stopped ? (stopMessage ?? "Subagent stopped by user.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? turnBudgetMessage : error ?? assistantError ?? spawnErrorMessage, protocolError, finalOutput: (timedOut || stopped) && !finalOutput.trim() ? (stopped ? stopMessage ?? "Subagent stopped by user." : timeoutMessage ?? "Subagent timed out.") : finalOutput, timedOut, stopped, turnBudget, turnBudgetExceeded, wrapUpRequested: turnBudget?.outcome === "wrap-up-requested" || turnBudget?.outcome === "termination-deferred" || turnBudgetExceeded || undefined, observedMutationAttempt, watchdog: childWatchdogState, processInstanceId });
|
|
655
837
|
});
|
|
656
838
|
});
|
|
657
839
|
}
|
|
658
840
|
|
|
659
|
-
function
|
|
660
|
-
const root =
|
|
841
|
+
function resolvePiPackageRootFallback(): string {
|
|
842
|
+
const root = resolveInstalledPiPackageRoot();
|
|
661
843
|
if (root) return root;
|
|
662
|
-
throw new Error(`Could not resolve ${
|
|
844
|
+
throw new Error(`Could not resolve ${PI_CODING_AGENT_PACKAGE} package root`);
|
|
663
845
|
}
|
|
664
846
|
|
|
665
847
|
async function exportSessionHtml(sessionFile: string, outputDir: string, piPackageRoot?: string): Promise<string> {
|
|
666
|
-
const pkgRoot = piPackageRoot ??
|
|
848
|
+
const pkgRoot = piPackageRoot ?? resolvePiPackageRootFallback();
|
|
667
849
|
const exportModulePath = path.join(pkgRoot, "dist", "core", "export-html", "index.js");
|
|
668
850
|
const moduleUrl = pathToFileURL(exportModulePath).href;
|
|
669
851
|
const mod = await import(moduleUrl);
|
|
@@ -777,20 +959,27 @@ interface SingleStepContext {
|
|
|
777
959
|
flatStepCount: number;
|
|
778
960
|
outputFile: string;
|
|
779
961
|
steerInboxDir?: string;
|
|
962
|
+
steerCapabilityPath?: string;
|
|
963
|
+
steerAckDir?: string;
|
|
780
964
|
transcriptPath?: string;
|
|
781
965
|
piPackageRoot?: string;
|
|
782
966
|
piArgv1?: string;
|
|
783
967
|
registerInterrupt?: (interrupt: (() => void) | undefined) => void;
|
|
784
968
|
registerTimeout?: (interrupt: (() => void) | undefined) => void;
|
|
969
|
+
registerStop?: (stop: (() => void) | undefined) => void;
|
|
785
970
|
registerTurnBudgetAbort?: (abort: ((message: string, state?: TurnBudgetState) => void) | undefined) => void;
|
|
786
971
|
timeoutSignal?: AbortSignal;
|
|
972
|
+
stopSignal?: AbortSignal;
|
|
787
973
|
timeoutMessage?: string;
|
|
974
|
+
stopMessage?: string;
|
|
788
975
|
turnBudget?: ResolvedTurnBudget;
|
|
789
976
|
childIntercomTarget?: string;
|
|
790
977
|
orchestratorIntercomTarget?: string;
|
|
791
978
|
nestedRoute?: NestedRouteInfo;
|
|
979
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
792
980
|
onAttemptStart?: (attempt: { model?: string; thinking?: string }) => void;
|
|
793
981
|
onChildEvent?: (event: ChildEvent) => void;
|
|
982
|
+
onWriterProcess?: (writer: { state: "none" | "spawning" } | { state: "running"; pid: number }) => void;
|
|
794
983
|
skipAcceptance?: () => boolean;
|
|
795
984
|
}
|
|
796
985
|
|
|
@@ -800,6 +989,9 @@ async function runSingleStep(
|
|
|
800
989
|
ctx: SingleStepContext,
|
|
801
990
|
): Promise<{
|
|
802
991
|
agent: string;
|
|
992
|
+
context?: "fresh" | "fork";
|
|
993
|
+
agentContract?: import("../../shared/types.ts").AgentContract;
|
|
994
|
+
launchContractDigest?: string;
|
|
803
995
|
output: string;
|
|
804
996
|
exitCode: number | null;
|
|
805
997
|
error?: string;
|
|
@@ -811,6 +1003,7 @@ async function runSingleStep(
|
|
|
811
1003
|
transcriptError?: string;
|
|
812
1004
|
interrupted?: boolean;
|
|
813
1005
|
timedOut?: boolean;
|
|
1006
|
+
stopped?: boolean;
|
|
814
1007
|
turnBudget?: TurnBudgetState;
|
|
815
1008
|
turnBudgetExceeded?: boolean;
|
|
816
1009
|
wrapUpRequested?: boolean;
|
|
@@ -819,13 +1012,18 @@ async function runSingleStep(
|
|
|
819
1012
|
sessionFile?: string;
|
|
820
1013
|
intercomTarget?: string;
|
|
821
1014
|
completionGuardTriggered?: boolean;
|
|
1015
|
+
effects?: import("../../shared/types.ts").EffectsProjection;
|
|
1016
|
+
execution?: import("../../shared/types.ts").ExecutionProjection;
|
|
1017
|
+
review?: import("../../shared/types.ts").ReviewProjection;
|
|
822
1018
|
structuredOutput?: unknown;
|
|
823
1019
|
structuredOutputPath?: string;
|
|
824
1020
|
structuredOutputSchemaPath?: string;
|
|
825
1021
|
acceptance?: import("../../shared/types.ts").AcceptanceLedger;
|
|
1022
|
+
writerAttemptCount?: number;
|
|
826
1023
|
}> {
|
|
827
1024
|
if (step.importAsyncRoot) {
|
|
828
1025
|
let importTimedOut = false;
|
|
1026
|
+
let importStopped = false;
|
|
829
1027
|
ctx.registerTimeout?.(() => {
|
|
830
1028
|
importTimedOut = true;
|
|
831
1029
|
let pid: number | undefined;
|
|
@@ -840,36 +1038,54 @@ async function runSingleStep(
|
|
|
840
1038
|
// The parent runner's own timeout result is authoritative for the attached step.
|
|
841
1039
|
}
|
|
842
1040
|
});
|
|
1041
|
+
ctx.registerStop?.(() => {
|
|
1042
|
+
importStopped = true;
|
|
1043
|
+
let pid: number | undefined;
|
|
1044
|
+
try {
|
|
1045
|
+
pid = readStatus(step.importAsyncRoot!.asyncDir)?.pid;
|
|
1046
|
+
} catch {
|
|
1047
|
+
pid = undefined;
|
|
1048
|
+
}
|
|
1049
|
+
try {
|
|
1050
|
+
deliverStopRequest({ asyncDir: step.importAsyncRoot!.asyncDir, pid, source: "ancestor-stop" });
|
|
1051
|
+
} catch {
|
|
1052
|
+
// The parent runner's own stopped result is authoritative for the attached step.
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
843
1055
|
try {
|
|
844
1056
|
const imported = await waitForImportedAsyncRoot(step.importAsyncRoot, {
|
|
845
|
-
shouldAbort: () => importTimedOut || ctx.timeoutSignal?.aborted === true || ctx.skipAcceptance?.() === true,
|
|
846
|
-
timeoutMessage: ctx.timeoutMessage,
|
|
1057
|
+
shouldAbort: () => importTimedOut || importStopped || ctx.timeoutSignal?.aborted === true || ctx.stopSignal?.aborted === true || ctx.skipAcceptance?.() === true,
|
|
1058
|
+
timeoutMessage: importStopped || ctx.stopSignal?.aborted === true ? ctx.stopMessage : ctx.timeoutMessage,
|
|
847
1059
|
});
|
|
848
1060
|
try {
|
|
849
1061
|
fs.writeFileSync(ctx.outputFile, imported.output, "utf-8");
|
|
850
1062
|
} catch {
|
|
851
1063
|
// Output files are observability only for imported roots.
|
|
852
1064
|
}
|
|
853
|
-
const
|
|
1065
|
+
const stopped = importStopped || imported.stopped === true || ctx.stopSignal?.aborted === true;
|
|
1066
|
+
const timedOut = !stopped && (importTimedOut || imported.timedOut === true || ctx.timeoutSignal?.aborted === true || ctx.skipAcceptance?.() === true);
|
|
1067
|
+
const message = stopped ? ctx.stopMessage ?? "Subagent stopped by user." : ctx.timeoutMessage ?? "Subagent timed out.";
|
|
854
1068
|
return {
|
|
855
1069
|
agent: imported.agent,
|
|
856
|
-
output: timedOut
|
|
857
|
-
exitCode: timedOut ? 1 : imported.exitCode,
|
|
858
|
-
error: timedOut
|
|
1070
|
+
output: timedOut || stopped ? message : imported.output,
|
|
1071
|
+
exitCode: timedOut || stopped ? 1 : imported.exitCode,
|
|
1072
|
+
error: timedOut || stopped ? message : imported.error,
|
|
859
1073
|
timedOut: timedOut ? true : undefined,
|
|
1074
|
+
stopped: stopped ? true : undefined,
|
|
860
1075
|
sessionFile: imported.sessionFile,
|
|
861
1076
|
intercomTarget: imported.intercomTarget,
|
|
862
1077
|
model: imported.model,
|
|
863
1078
|
attemptedModels: imported.attemptedModels,
|
|
864
1079
|
modelAttempts: imported.modelAttempts,
|
|
865
1080
|
totalCost: imported.totalCost,
|
|
866
|
-
structuredOutput: timedOut ? undefined : imported.structuredOutput,
|
|
867
|
-
structuredOutputPath: timedOut ? undefined : imported.structuredOutputPath,
|
|
868
|
-
structuredOutputSchemaPath: timedOut ? undefined : imported.structuredOutputSchemaPath,
|
|
869
|
-
acceptance: timedOut ? undefined : imported.acceptance,
|
|
1081
|
+
structuredOutput: timedOut || stopped ? undefined : imported.structuredOutput,
|
|
1082
|
+
structuredOutputPath: timedOut || stopped ? undefined : imported.structuredOutputPath,
|
|
1083
|
+
structuredOutputSchemaPath: timedOut || stopped ? undefined : imported.structuredOutputSchemaPath,
|
|
1084
|
+
acceptance: timedOut || stopped ? undefined : imported.acceptance,
|
|
870
1085
|
};
|
|
871
1086
|
} finally {
|
|
872
1087
|
ctx.registerTimeout?.(undefined);
|
|
1088
|
+
ctx.registerStop?.(undefined);
|
|
873
1089
|
}
|
|
874
1090
|
}
|
|
875
1091
|
|
|
@@ -878,10 +1094,10 @@ async function runSingleStep(
|
|
|
878
1094
|
: undefined);
|
|
879
1095
|
const placeholderRegex = new RegExp(ctx.placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
|
|
880
1096
|
let task = step.task.replace(placeholderRegex, () => ctx.previousOutput);
|
|
881
|
-
task = resolveOutputReferences(task, ctx.outputs
|
|
1097
|
+
if (ctx.outputs) task = resolveOutputReferences(task, ctx.outputs);
|
|
882
1098
|
const taskForCompletionGuard = task;
|
|
883
1099
|
if (step.effectiveAcceptance) {
|
|
884
|
-
const acceptancePrompt = formatAcceptancePrompt(step.effectiveAcceptance);
|
|
1100
|
+
const acceptancePrompt = formatAcceptancePrompt(step.effectiveAcceptance, { reportOptional: isAgentContractV1(step.agentContract) });
|
|
885
1101
|
if (acceptancePrompt) task = `${task}\n${acceptancePrompt}`;
|
|
886
1102
|
}
|
|
887
1103
|
const sessionEnabled = Boolean(step.sessionFile) || ctx.sessionEnabled;
|
|
@@ -915,7 +1131,10 @@ async function runSingleStep(
|
|
|
915
1131
|
? [step.model]
|
|
916
1132
|
: [undefined];
|
|
917
1133
|
const attemptedModels: string[] = [];
|
|
1134
|
+
let capabilityAudit: import("../shared/capability-ceiling.ts").SubagentCapabilityAudit | undefined;
|
|
918
1135
|
const modelAttempts: ModelAttempt[] = [];
|
|
1136
|
+
const writerProcesses: Array<{ processInstanceId: string; kind: "pi-writer"; attempt: number; closeObservedAt: number; exitCode: number | null; signal: string | null }> = [];
|
|
1137
|
+
let writerAttemptCount = 0;
|
|
919
1138
|
const attemptNotes: string[] = [];
|
|
920
1139
|
const eventsPath = path.join(path.dirname(ctx.outputFile), "events.jsonl");
|
|
921
1140
|
let finalResult: RunPiStreamingResult | undefined;
|
|
@@ -924,6 +1143,7 @@ async function runSingleStep(
|
|
|
924
1143
|
let turnBudget = ctx.turnBudget ? initialTurnBudgetState(ctx.turnBudget) : undefined;
|
|
925
1144
|
let toolBudget = step.toolBudget ? initialToolBudgetState(step.toolBudget) : undefined;
|
|
926
1145
|
let toolBudgetBlocked = false;
|
|
1146
|
+
let actualLaunchContractDigest = step.launchContractDigest;
|
|
927
1147
|
|
|
928
1148
|
for (let index = 0; index < candidates.length; index++) {
|
|
929
1149
|
if (ctx.timeoutSignal?.aborted || ctx.skipAcceptance?.()) break;
|
|
@@ -937,7 +1157,16 @@ async function runSingleStep(
|
|
|
937
1157
|
// Missing/stale structured-output files are handled after the child exits.
|
|
938
1158
|
}
|
|
939
1159
|
}
|
|
940
|
-
const
|
|
1160
|
+
const watchdogConfig = resolveWatchdogConfig(step.cwd ?? ctx.cwd);
|
|
1161
|
+
const childWatchdog = watchdogConfig.ok
|
|
1162
|
+
? resolveChildWatchdogConfig({
|
|
1163
|
+
config: watchdogConfig.config,
|
|
1164
|
+
agent: step.agent,
|
|
1165
|
+
runId: ctx.id,
|
|
1166
|
+
childIndex: ctx.flatIndex,
|
|
1167
|
+
})
|
|
1168
|
+
: undefined;
|
|
1169
|
+
const { args, env, tempDir, toolDiagnosticPath, capabilityAudit: attemptCapabilityAudit } = buildPiArgs({
|
|
941
1170
|
parentSessionId: step.parentSessionId,
|
|
942
1171
|
baseArgs: ["--offline", "--mode", "json", "-p"],
|
|
943
1172
|
task,
|
|
@@ -954,6 +1183,7 @@ async function runSingleStep(
|
|
|
954
1183
|
systemPrompt: appendTurnBudgetSystemPrompt(step.systemPrompt ?? "", ctx.turnBudget),
|
|
955
1184
|
systemPromptMode: step.systemPromptMode,
|
|
956
1185
|
mcpDirectTools: step.mcpDirectTools,
|
|
1186
|
+
capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
|
|
957
1187
|
cwd: step.cwd ?? ctx.cwd,
|
|
958
1188
|
promptFileStem: step.agent,
|
|
959
1189
|
intercomSessionName: ctx.childIntercomTarget,
|
|
@@ -966,9 +1196,46 @@ async function runSingleStep(
|
|
|
966
1196
|
parentRootRunId: ctx.nestedRoute?.rootRunId,
|
|
967
1197
|
parentCapabilityToken: ctx.nestedRoute?.capabilityToken,
|
|
968
1198
|
steerInboxDir: ctx.steerInboxDir,
|
|
1199
|
+
steerCapabilityPath: ctx.steerCapabilityPath,
|
|
1200
|
+
steerAckDir: ctx.steerAckDir,
|
|
969
1201
|
structuredOutput: effectiveStructuredOutput,
|
|
970
1202
|
toolBudget: step.toolBudget,
|
|
1203
|
+
childWatchdog,
|
|
1204
|
+
waitToolEnabled: step.waitToolEnabled,
|
|
971
1205
|
});
|
|
1206
|
+
if (step.definitionDigest) {
|
|
1207
|
+
const toolPlan = resolvePiLaunchToolPlan({
|
|
1208
|
+
tools: step.tools,
|
|
1209
|
+
extensions: step.extensions,
|
|
1210
|
+
subagentOnlyExtensions: step.subagentOnlyExtensions,
|
|
1211
|
+
mcpDirectTools: step.mcpDirectTools,
|
|
1212
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
1213
|
+
requireReadTool: Boolean(step.skills?.length),
|
|
1214
|
+
structuredOutput: Boolean(effectiveStructuredOutput),
|
|
1215
|
+
capabilityCeiling: step.capabilityCeiling ?? ctx.capabilityCeiling,
|
|
1216
|
+
inheritedCapabilityCeiling: decodeSubagentCapabilityCeiling(process.env[SUBAGENT_CAPABILITY_CEILING_ENV]),
|
|
1217
|
+
});
|
|
1218
|
+
actualLaunchContractDigest = launchBindingDigest({
|
|
1219
|
+
definitionDigest: step.definitionDigest,
|
|
1220
|
+
task: step.launchBindingTask ?? task,
|
|
1221
|
+
...(candidate ? { model: candidate } : {}),
|
|
1222
|
+
modelCandidates: candidates,
|
|
1223
|
+
...(resolveEffectiveThinking(candidate, step.thinking) ? { thinking: resolveEffectiveThinking(candidate, step.thinking) } : {}),
|
|
1224
|
+
systemPrompt: appendTurnBudgetSystemPrompt(step.systemPrompt ?? "", ctx.turnBudget),
|
|
1225
|
+
systemPromptMode: step.systemPromptMode,
|
|
1226
|
+
inheritProjectContext: step.inheritProjectContext,
|
|
1227
|
+
inheritSkills: step.inheritSkills,
|
|
1228
|
+
skills: step.skills,
|
|
1229
|
+
tools: toolPlan.effectiveToolAllowlist,
|
|
1230
|
+
extensions: toolPlan.extensionArgs,
|
|
1231
|
+
mcpDirectTools: toolPlan.effectiveMcpTools,
|
|
1232
|
+
...(step.outputPath ? { outputPath: step.outputPath } : {}),
|
|
1233
|
+
...(step.outputMode ? { outputMode: step.outputMode } : {}),
|
|
1234
|
+
...(step.structuredOutputSchema ? { structuredOutputSchema: step.structuredOutputSchema } : {}),
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
capabilityAudit = attemptCapabilityAudit;
|
|
1238
|
+
writerAttemptCount += 1;
|
|
972
1239
|
const run = await runPiStreaming(
|
|
973
1240
|
args,
|
|
974
1241
|
step.cwd ?? ctx.cwd,
|
|
@@ -983,8 +1250,21 @@ async function runSingleStep(
|
|
|
983
1250
|
transcriptWriter,
|
|
984
1251
|
ctx.registerTimeout,
|
|
985
1252
|
ctx.timeoutMessage,
|
|
1253
|
+
ctx.registerStop,
|
|
1254
|
+
ctx.stopMessage,
|
|
986
1255
|
ctx.registerTurnBudgetAbort,
|
|
1256
|
+
ctx.onWriterProcess,
|
|
987
1257
|
);
|
|
1258
|
+
if (run.processCloseObservedAt !== undefined) {
|
|
1259
|
+
writerProcesses.push({
|
|
1260
|
+
processInstanceId: run.processInstanceId,
|
|
1261
|
+
kind: "pi-writer",
|
|
1262
|
+
attempt: index,
|
|
1263
|
+
closeObservedAt: run.processCloseObservedAt,
|
|
1264
|
+
exitCode: run.exitCode,
|
|
1265
|
+
signal: run.processSignal ?? null,
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
988
1268
|
if (run.turnBudget) turnBudget = run.turnBudget;
|
|
989
1269
|
else if (ctx.turnBudget) {
|
|
990
1270
|
const assistantMessages = run.messages.filter((message) => message.role === "assistant");
|
|
@@ -993,26 +1273,38 @@ async function runSingleStep(
|
|
|
993
1273
|
if (turnCount > 0 && turnCount < ctx.turnBudget.maxTurns) {
|
|
994
1274
|
turnBudget = { ...ctx.turnBudget, outcome: "within-budget", turnCount };
|
|
995
1275
|
} else if (turnCount >= ctx.turnBudget.maxTurns) {
|
|
996
|
-
|
|
1276
|
+
const decision = turnBudgetDecision(
|
|
997
1277
|
ctx.turnBudget,
|
|
998
1278
|
turnCount,
|
|
999
|
-
|
|
1279
|
+
lastAssistantMessage ? isTerminalAssistantStop(lastAssistantMessage) : false,
|
|
1280
|
+
lastAssistantMessage ? assistantStartsToolCall(lastAssistantMessage) : false,
|
|
1000
1281
|
);
|
|
1282
|
+
turnBudget = decision === "defer"
|
|
1283
|
+
? turnBudgetDeferredState(ctx.turnBudget, turnCount)
|
|
1284
|
+
: turnBudgetState(ctx.turnBudget, turnCount, decision === "abort");
|
|
1001
1285
|
}
|
|
1002
1286
|
}
|
|
1287
|
+
const toolAvailabilityError = run.exitCode === 0 && !run.error
|
|
1288
|
+
? readChildToolDiagnosticError(toolDiagnosticPath)
|
|
1289
|
+
: undefined;
|
|
1003
1290
|
cleanupTempDir(tempDir);
|
|
1004
1291
|
|
|
1005
|
-
const hiddenError = run.exitCode === 0 && !run.error ? detectSubagentError(run.messages) : null;
|
|
1292
|
+
const hiddenError = run.exitCode === 0 && !run.error && !toolAvailabilityError ? detectSubagentError(run.messages) : null;
|
|
1006
1293
|
const missingStructuredOutput = effectiveStructuredOutput
|
|
1007
1294
|
? !fs.existsSync(effectiveStructuredOutput.outputPath)
|
|
1008
1295
|
: false;
|
|
1009
|
-
const emptyOutputError = run.exitCode === 0
|
|
1296
|
+
const emptyOutputError = run.exitCode === 0
|
|
1297
|
+
&& !run.error
|
|
1298
|
+
&& !toolAvailabilityError
|
|
1299
|
+
&& !run.finalOutput.trim()
|
|
1300
|
+
&& (!effectiveStructuredOutput || missingStructuredOutput)
|
|
1301
|
+
&& (!hiddenError?.hasError || hasEmptyTerminalAssistantResponse(run.messages))
|
|
1010
1302
|
? "Subagent produced no output (possible model cold-start or empty response)."
|
|
1011
1303
|
: undefined;
|
|
1012
1304
|
let structuredOutput: unknown;
|
|
1013
1305
|
let structuredError: string | undefined;
|
|
1014
|
-
if (effectiveStructuredOutput && run.exitCode === 0 && !run.error && !hiddenError?.hasError && !emptyOutputError) {
|
|
1015
|
-
const structured = readStructuredOutput({
|
|
1306
|
+
if (effectiveStructuredOutput && run.exitCode === 0 && !run.error && !toolAvailabilityError && !hiddenError?.hasError && !emptyOutputError) {
|
|
1307
|
+
const structured = await readStructuredOutput({
|
|
1016
1308
|
schema: effectiveStructuredOutput.schema,
|
|
1017
1309
|
schemaPath: effectiveStructuredOutput.schemaPath,
|
|
1018
1310
|
outputPath: effectiveStructuredOutput.outputPath,
|
|
@@ -1020,7 +1312,8 @@ async function runSingleStep(
|
|
|
1020
1312
|
if (structured.error) structuredError = structured.error;
|
|
1021
1313
|
else structuredOutput = structured.value;
|
|
1022
1314
|
}
|
|
1023
|
-
const
|
|
1315
|
+
const completionGuardEnabled = isAgentContractV1(step.agentContract) ? step.completionGuard === true : step.completionGuard !== false;
|
|
1316
|
+
const completionGuard = run.exitCode === 0 && !run.error && !toolAvailabilityError && !hiddenError?.hasError && !emptyOutputError && completionGuardEnabled
|
|
1024
1317
|
? evaluateCompletionMutationGuard({
|
|
1025
1318
|
agent: step.agent,
|
|
1026
1319
|
task: taskForCompletionGuard,
|
|
@@ -1030,27 +1323,33 @@ async function runSingleStep(
|
|
|
1030
1323
|
})
|
|
1031
1324
|
: undefined;
|
|
1032
1325
|
const completionGuardTriggered = completionGuard?.triggered === true && !run.observedMutationAttempt;
|
|
1033
|
-
const
|
|
1326
|
+
const fileMutationEffect = completionGuard
|
|
1327
|
+
? {
|
|
1328
|
+
status: completionGuard.expectedMutation ? completionGuardTriggered ? "missing" as const : "observed" as const : "not-applicable" as const,
|
|
1329
|
+
expected: completionGuard.expectedMutation,
|
|
1330
|
+
attempted: completionGuard.attemptedMutation || run.observedMutationAttempt === true,
|
|
1331
|
+
...(completionGuardTriggered ? { message: "Subagent completed without making edits for an implementation task." } : {}),
|
|
1332
|
+
}
|
|
1333
|
+
: undefined;
|
|
1334
|
+
const completionGuardError = completionGuardTriggered && !isAgentContractV1(step.agentContract)
|
|
1034
1335
|
? "Subagent completed without making edits for an implementation task.\nIt appears to have returned planning or scratchpad output instead of applying changes."
|
|
1035
1336
|
: undefined;
|
|
1036
|
-
const effectiveExitCode = completionGuardTriggered
|
|
1337
|
+
const effectiveExitCode = toolAvailabilityError || (completionGuardTriggered && !isAgentContractV1(step.agentContract)) || structuredError || emptyOutputError
|
|
1037
1338
|
? 1
|
|
1038
|
-
:
|
|
1039
|
-
? 1
|
|
1040
|
-
: hiddenError?.hasError
|
|
1339
|
+
: hiddenError?.hasError
|
|
1041
1340
|
? (hiddenError.exitCode ?? 1)
|
|
1042
|
-
:
|
|
1341
|
+
: run.error && run.exitCode === 0
|
|
1043
1342
|
? 1
|
|
1044
|
-
: run.
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
const error = completionGuardError
|
|
1343
|
+
: run.exitCode;
|
|
1344
|
+
const error = toolAvailabilityError
|
|
1345
|
+
?? completionGuardError
|
|
1048
1346
|
?? structuredError
|
|
1347
|
+
?? emptyOutputError
|
|
1049
1348
|
?? (hiddenError?.hasError
|
|
1050
1349
|
? hiddenError.details
|
|
1051
1350
|
? `${hiddenError.errorType} failed (exit ${effectiveExitCode}): ${hiddenError.details}`
|
|
1052
1351
|
: `${hiddenError.errorType} failed with exit code ${effectiveExitCode}`
|
|
1053
|
-
:
|
|
1352
|
+
: run.error || (run.exitCode !== 0 && run.stderr.trim() ? run.stderr.trim() : undefined));
|
|
1054
1353
|
const attempt: ModelAttempt = {
|
|
1055
1354
|
model: candidate ?? run.model ?? step.model ?? "default",
|
|
1056
1355
|
success: effectiveExitCode === 0 && !error,
|
|
@@ -1068,9 +1367,9 @@ async function runSingleStep(
|
|
|
1068
1367
|
toolBudgetBlocked = Boolean(blockedMessage);
|
|
1069
1368
|
toolBudget = toolBudgetState(step.toolBudget, toolMessages.length, blockedMessage ? (blockedMessage as { toolName?: string }).toolName : undefined);
|
|
1070
1369
|
}
|
|
1071
|
-
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput } as RunPiStreamingResult & { structuredOutput?: unknown };
|
|
1370
|
+
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput, ...(step.agentContract ? { agentContract: step.agentContract } : {}), ...(fileMutationEffect ? { effects: { fileMutation: fileMutationEffect } } : {}) } as RunPiStreamingResult & { structuredOutput?: unknown; agentContract?: import("../../shared/types.ts").AgentContract; effects?: import("../../shared/types.ts").EffectsProjection };
|
|
1072
1371
|
if (run.turnBudgetExceeded) break;
|
|
1073
|
-
if (run.timedOut || ctx.timeoutSignal?.aborted || ctx.skipAcceptance?.()) break;
|
|
1372
|
+
if (run.stopped || run.timedOut || ctx.timeoutSignal?.aborted || ctx.stopSignal?.aborted || ctx.skipAcceptance?.()) break;
|
|
1074
1373
|
if (attempt.success || completionGuardTriggered) break;
|
|
1075
1374
|
if (!isRetryableModelFailure(error) || index === candidates.length - 1) break;
|
|
1076
1375
|
attemptNotes.push(formatModelAttemptNote(attempt, candidates[index + 1]));
|
|
@@ -1081,21 +1380,29 @@ async function runSingleStep(
|
|
|
1081
1380
|
const resolvedOutput = step.outputPath && finalResult?.exitCode === 0
|
|
1082
1381
|
? resolveSingleOutput(step.outputPath, outputForPersistence, finalOutputSnapshot)
|
|
1083
1382
|
: { fullOutput: outputForPersistence };
|
|
1084
|
-
const output = resolvedOutput.fullOutput;
|
|
1383
|
+
const output = stripAcceptanceReport(resolvedOutput.fullOutput);
|
|
1085
1384
|
const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, output) : undefined;
|
|
1086
1385
|
let outputForSummary = output;
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
if (
|
|
1386
|
+
if (attemptNotes.length > 0) {
|
|
1387
|
+
outputForSummary = `${attemptNotes.join("\n")}\n\n${outputForSummary}`.trim();
|
|
1388
|
+
}
|
|
1389
|
+
if (finalResult?.stopped && !outputForSummary.trim()) {
|
|
1390
|
+
outputForSummary = ctx.stopMessage ?? "Subagent stopped by user.";
|
|
1391
|
+
} else if (!finalResult?.timedOut && !finalResult?.stopped && finalResult?.turnBudgetExceeded && turnBudget) {
|
|
1091
1392
|
outputForSummary = formatTurnBudgetOutput(turnBudgetExceededMessage(turnBudget, turnBudget.turnCount), outputForSummary);
|
|
1092
|
-
} else if (!finalResult?.timedOut && turnBudget?.outcome === "
|
|
1393
|
+
} else if (!finalResult?.timedOut && !finalResult?.stopped && turnBudget?.outcome === "termination-deferred") {
|
|
1394
|
+
const note = turnBudgetDeferredNote(turnBudget, turnBudget.terminationDeferredAtTurn ?? turnBudget.turnCount);
|
|
1395
|
+
outputForSummary = outputForSummary.trim() ? `${note}\n\n${outputForSummary}` : note;
|
|
1396
|
+
} else if (!finalResult?.timedOut && !finalResult?.stopped && turnBudget?.outcome === "wrap-up-requested") {
|
|
1093
1397
|
const note = turnBudgetSoftNote(turnBudget, turnBudget.wrapUpRequestedAtTurn ?? turnBudget.turnCount);
|
|
1094
1398
|
outputForSummary = outputForSummary.trim() ? `${note}\n\n${outputForSummary}` : note;
|
|
1095
1399
|
}
|
|
1096
1400
|
const outputForAcceptance = rawOutput;
|
|
1097
|
-
|
|
1098
|
-
|
|
1401
|
+
const childWrittenOutput = step.outputPath
|
|
1402
|
+
? extractChildWrittenOutput(finalResult?.messages, step.outputPath, step.cwd ?? ctx.cwd)
|
|
1403
|
+
: undefined;
|
|
1404
|
+
const finalizedOutput = finalizeSingleOutput({
|
|
1405
|
+
fullOutput: outputForSummary,
|
|
1099
1406
|
outputPath: step.outputPath,
|
|
1100
1407
|
outputMode: step.outputMode,
|
|
1101
1408
|
exitCode: finalResult?.exitCode ?? 1,
|
|
@@ -1104,32 +1411,52 @@ async function runSingleStep(
|
|
|
1104
1411
|
saveError: resolvedOutput.saveError,
|
|
1105
1412
|
});
|
|
1106
1413
|
outputForSummary = finalizedOutput.displayOutput;
|
|
1107
|
-
const acceptance = step.effectiveAcceptance && !finalResult?.turnBudgetExceeded && !ctx.timeoutSignal?.aborted && !ctx.skipAcceptance?.()
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1414
|
+
const acceptance = step.effectiveAcceptance && !finalResult?.stopped && !finalResult?.turnBudgetExceeded && !ctx.timeoutSignal?.aborted && !ctx.stopSignal?.aborted && !ctx.skipAcceptance?.()
|
|
1415
|
+
? await evaluateAcceptance({
|
|
1416
|
+
acceptance: step.effectiveAcceptance,
|
|
1417
|
+
output: outputForAcceptance,
|
|
1418
|
+
fileOutput: childWrittenOutput !== undefined && step.outputPath
|
|
1419
|
+
? { content: childWrittenOutput, path: step.outputPath, authoritative: step.outputMode === "file-only" }
|
|
1420
|
+
: undefined,
|
|
1421
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
1422
|
+
signal: combinedAbortSignal([ctx.timeoutSignal, ctx.stopSignal]),
|
|
1423
|
+
abortMessage: ctx.stopSignal?.aborted ? ctx.stopMessage ?? "Subagent stopped by user." : ctx.timeoutMessage ?? "Subagent timed out.",
|
|
1424
|
+
reportOptional: isAgentContractV1(step.agentContract),
|
|
1425
|
+
})
|
|
1115
1426
|
: undefined;
|
|
1116
|
-
const
|
|
1427
|
+
const stoppedAfterAcceptance = finalResult?.stopped === true || ctx.stopSignal?.aborted === true;
|
|
1428
|
+
const timedOutAfterAcceptance = !stoppedAfterAcceptance && (finalResult?.timedOut === true || ctx.timeoutSignal?.aborted === true);
|
|
1117
1429
|
const turnBudgetExceeded = finalResult?.turnBudgetExceeded === true;
|
|
1118
|
-
const effectiveAcceptance =
|
|
1430
|
+
const effectiveAcceptance = step.effectiveAcceptance
|
|
1431
|
+
? stoppedAfterAcceptance
|
|
1432
|
+
? buildSkippedAcceptanceLedger(step.effectiveAcceptance, { id: "stopped", message: "Acceptance was not evaluated because the subagent was stopped." })
|
|
1433
|
+
: timedOutAfterAcceptance
|
|
1434
|
+
? buildSkippedAcceptanceLedger(step.effectiveAcceptance, { id: "timeout", message: "Acceptance was not evaluated because the subagent timed out." })
|
|
1435
|
+
: turnBudgetExceeded
|
|
1436
|
+
? buildSkippedAcceptanceLedger(step.effectiveAcceptance, { id: "turn-budget", message: "Acceptance was not evaluated because the subagent exceeded its turn budget." })
|
|
1437
|
+
: acceptance
|
|
1438
|
+
: undefined;
|
|
1119
1439
|
const acceptanceFailure = effectiveAcceptance ? acceptanceFailureMessage(effectiveAcceptance) : undefined;
|
|
1120
|
-
const acceptanceCanFailRun = acceptanceFailure && effectiveAcceptance?.explicit && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted && !timedOutAfterAcceptance && !turnBudgetExceeded;
|
|
1121
|
-
const effectiveFinalExitCode = timedOutAfterAcceptance || turnBudgetExceeded ? 1 : acceptanceCanFailRun ? 1 : finalResult?.exitCode ?? 1;
|
|
1122
|
-
const effectiveFinalError =
|
|
1123
|
-
? ctx.
|
|
1124
|
-
:
|
|
1125
|
-
?
|
|
1126
|
-
:
|
|
1127
|
-
?
|
|
1128
|
-
:
|
|
1440
|
+
const acceptanceCanFailRun = acceptanceFailure && effectiveAcceptance?.explicit && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted && !timedOutAfterAcceptance && !stoppedAfterAcceptance && !turnBudgetExceeded && !isAgentContractV1(step.agentContract);
|
|
1441
|
+
const effectiveFinalExitCode = timedOutAfterAcceptance || stoppedAfterAcceptance || turnBudgetExceeded ? 1 : acceptanceCanFailRun ? 1 : finalResult?.exitCode ?? 1;
|
|
1442
|
+
const effectiveFinalError = stoppedAfterAcceptance
|
|
1443
|
+
? ctx.stopMessage ?? "Subagent stopped by user."
|
|
1444
|
+
: timedOutAfterAcceptance
|
|
1445
|
+
? ctx.timeoutMessage ?? "Subagent timed out."
|
|
1446
|
+
: turnBudgetExceeded
|
|
1447
|
+
? finalResult?.error ?? (turnBudget ? turnBudgetExceededMessage(turnBudget, turnBudget.turnCount) : "Subagent exceeded turn budget.")
|
|
1448
|
+
: acceptanceCanFailRun
|
|
1449
|
+
? (finalResult?.error ? `${finalResult.error}\n${acceptanceFailure}` : acceptanceFailure)
|
|
1450
|
+
: finalResult?.error;
|
|
1129
1451
|
|
|
1130
1452
|
if (artifactPaths && ctx.artifactConfig?.enabled !== false) {
|
|
1131
1453
|
if (ctx.artifactConfig?.includeOutput !== false) {
|
|
1132
|
-
fs.writeFileSync(artifactPaths.outputPath,
|
|
1454
|
+
fs.writeFileSync(artifactPaths.outputPath, formatOutputArtifactContent({
|
|
1455
|
+
output,
|
|
1456
|
+
error: effectiveFinalError,
|
|
1457
|
+
transcriptPath: transcriptWriter ? artifactPaths.transcriptPath : undefined,
|
|
1458
|
+
metadataPath: ctx.artifactConfig?.includeMetadata === false ? undefined : artifactPaths.metadataPath,
|
|
1459
|
+
}), "utf-8");
|
|
1133
1460
|
}
|
|
1134
1461
|
if (ctx.artifactConfig?.includeMetadata !== false) {
|
|
1135
1462
|
fs.writeFileSync(
|
|
@@ -1142,6 +1469,10 @@ async function runSingleStep(
|
|
|
1142
1469
|
model: finalResult?.model,
|
|
1143
1470
|
attemptedModels: attemptedModels.length > 0 ? attemptedModels : undefined,
|
|
1144
1471
|
modelAttempts,
|
|
1472
|
+
error: effectiveFinalError,
|
|
1473
|
+
acceptance: effectiveAcceptance,
|
|
1474
|
+
...(capabilityAudit ? { capabilityCeiling: capabilityAudit.ceiling, capabilityAudit } : {}),
|
|
1475
|
+
launchContractDigest: actualLaunchContractDigest,
|
|
1145
1476
|
...(transcriptWriter ? { transcriptPath: artifactPaths.transcriptPath } : {}),
|
|
1146
1477
|
transcriptError: transcriptWriter?.getError(),
|
|
1147
1478
|
skills: step.skills,
|
|
@@ -1152,11 +1483,15 @@ async function runSingleStep(
|
|
|
1152
1483
|
}
|
|
1153
1484
|
}
|
|
1154
1485
|
|
|
1155
|
-
|
|
1486
|
+
const result = {
|
|
1156
1487
|
agent: step.agent,
|
|
1488
|
+
context: step.context,
|
|
1489
|
+
...(step.agentContract ? { agentContract: step.agentContract } : {}),
|
|
1490
|
+
launchContractDigest: actualLaunchContractDigest,
|
|
1157
1491
|
output: outputForSummary,
|
|
1158
1492
|
exitCode: effectiveFinalExitCode,
|
|
1159
1493
|
error: effectiveFinalError,
|
|
1494
|
+
protocolError: finalResult?.protocolError,
|
|
1160
1495
|
sessionFile: step.sessionFile,
|
|
1161
1496
|
intercomTarget: ctx.childIntercomTarget,
|
|
1162
1497
|
model: finalResult?.model,
|
|
@@ -1166,25 +1501,45 @@ async function runSingleStep(
|
|
|
1166
1501
|
artifactPaths,
|
|
1167
1502
|
transcriptPath: transcriptWriter ? artifactPaths?.transcriptPath : undefined,
|
|
1168
1503
|
transcriptError: transcriptWriter?.getError(),
|
|
1169
|
-
interrupted: timedOutAfterAcceptance || turnBudgetExceeded ? false : finalResult?.interrupted,
|
|
1504
|
+
interrupted: timedOutAfterAcceptance || stoppedAfterAcceptance || turnBudgetExceeded ? false : finalResult?.interrupted,
|
|
1170
1505
|
timedOut: timedOutAfterAcceptance ? true : finalResult?.timedOut,
|
|
1506
|
+
stopped: stoppedAfterAcceptance ? true : finalResult?.stopped,
|
|
1171
1507
|
turnBudget,
|
|
1172
1508
|
turnBudgetExceeded: turnBudgetExceeded || undefined,
|
|
1173
|
-
wrapUpRequested: finalResult?.wrapUpRequested || turnBudget?.outcome === "wrap-up-requested" || turnBudgetExceeded || undefined,
|
|
1509
|
+
wrapUpRequested: finalResult?.wrapUpRequested || turnBudget?.outcome === "wrap-up-requested" || turnBudget?.outcome === "termination-deferred" || turnBudgetExceeded || undefined,
|
|
1174
1510
|
toolBudget,
|
|
1175
1511
|
toolBudgetBlocked: toolBudgetBlocked || undefined,
|
|
1176
1512
|
completionGuardTriggered: completionGuardTriggeredFinal,
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1513
|
+
...((finalResult as (RunPiStreamingResult & { effects?: import("../../shared/types.ts").EffectsProjection }) | undefined)?.effects ? { effects: (finalResult as RunPiStreamingResult & { effects?: import("../../shared/types.ts").EffectsProjection }).effects } : {}),
|
|
1514
|
+
structuredOutput: timedOutAfterAcceptance || stoppedAfterAcceptance || turnBudgetExceeded ? undefined : (finalResult as (RunPiStreamingResult & { structuredOutput?: unknown }) | undefined)?.structuredOutput,
|
|
1515
|
+
structuredOutputPath: timedOutAfterAcceptance || stoppedAfterAcceptance || turnBudgetExceeded ? undefined : effectiveStructuredOutput?.outputPath,
|
|
1516
|
+
structuredOutputSchemaPath: timedOutAfterAcceptance || stoppedAfterAcceptance || turnBudgetExceeded ? undefined : effectiveStructuredOutput?.schemaPath,
|
|
1180
1517
|
acceptance: effectiveAcceptance,
|
|
1518
|
+
watchdog: finalResult?.watchdog,
|
|
1519
|
+
...(capabilityAudit ? { capabilityCeiling: capabilityAudit.ceiling, capabilityAudit } : {}),
|
|
1520
|
+
writerProcesses,
|
|
1521
|
+
writerAttemptCount,
|
|
1181
1522
|
};
|
|
1523
|
+
return isAgentContractV1(step.agentContract) ? attachContractProjections(result as unknown as import("../../shared/types.ts").SingleResult) as unknown as typeof result : result;
|
|
1182
1524
|
}
|
|
1183
1525
|
|
|
1184
1526
|
type RunnerStatusStep = NonNullable<AsyncStatus["steps"]>[number] & {
|
|
1185
1527
|
exitCode?: number | null;
|
|
1186
1528
|
};
|
|
1187
1529
|
|
|
1530
|
+
function appendCapabilityCeilingAppliedEvent(eventsPath: string, runId: string, stepIndex: number, agent: string, result: StepResult): void {
|
|
1531
|
+
if (!result.capabilityCeiling) return;
|
|
1532
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
1533
|
+
type: "subagent.capability-ceiling.applied",
|
|
1534
|
+
ts: Date.now(),
|
|
1535
|
+
runId,
|
|
1536
|
+
stepIndex,
|
|
1537
|
+
agent,
|
|
1538
|
+
capabilityCeiling: result.capabilityCeiling,
|
|
1539
|
+
...(result.capabilityAudit ? { capabilityAudit: result.capabilityAudit } : {}),
|
|
1540
|
+
}));
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1188
1543
|
type RunnerStatusPayload = Omit<AsyncStatus, "steps" | "parallelGroups" | "pid" | "cwd" | "currentStep" | "chainStepCount" | "lastUpdate"> & {
|
|
1189
1544
|
pid: number;
|
|
1190
1545
|
cwd: string;
|
|
@@ -1220,7 +1575,7 @@ function markParallelGroupSetupFailure(input: {
|
|
|
1220
1575
|
input.statusPayload.steps[flatTaskIndex].endedAt = input.failedAt;
|
|
1221
1576
|
input.statusPayload.steps[flatTaskIndex].durationMs = 0;
|
|
1222
1577
|
input.statusPayload.steps[flatTaskIndex].exitCode = 1;
|
|
1223
|
-
input.results.push({ agent: input.group.parallel[taskIndex].agent, output: input.setupError, success: false, exitCode: 1, sessionFile: input.group.parallel[taskIndex].sessionFile });
|
|
1578
|
+
input.results.push({ agent: input.group.parallel[taskIndex].agent, context: input.group.parallel[taskIndex].context, output: input.setupError, success: false, exitCode: 1, sessionFile: input.group.parallel[taskIndex].sessionFile });
|
|
1224
1579
|
}
|
|
1225
1580
|
input.statusPayload.currentStep = input.groupStartFlatIndex;
|
|
1226
1581
|
input.statusPayload.lastUpdate = input.failedAt;
|
|
@@ -1285,19 +1640,15 @@ function prepareParallelTaskRun(
|
|
|
1285
1640
|
};
|
|
1286
1641
|
}
|
|
1287
1642
|
|
|
1288
|
-
function
|
|
1289
|
-
|
|
1290
|
-
worktreeSetup: WorktreeSetup | undefined,
|
|
1643
|
+
function captureParallelWorktreeDiffs(
|
|
1644
|
+
worktreeSetup: WorktreeSetup,
|
|
1291
1645
|
asyncDir: string,
|
|
1292
1646
|
stepIndex: number,
|
|
1293
1647
|
group: Extract<RunnerStep, { parallel: SubagentStep[] }>,
|
|
1294
|
-
): string {
|
|
1295
|
-
if (!worktreeSetup) return previousOutput;
|
|
1648
|
+
): { diffs: ReturnType<typeof diffWorktrees>; summary: string } {
|
|
1296
1649
|
const diffsDir = path.join(asyncDir, "worktree-diffs", `step-${stepIndex}`);
|
|
1297
1650
|
const diffs = diffWorktrees(worktreeSetup, group.parallel.map((task) => task.agent), diffsDir);
|
|
1298
|
-
|
|
1299
|
-
if (!diffSummary) return previousOutput;
|
|
1300
|
-
return `${previousOutput}\n\n${diffSummary}`;
|
|
1651
|
+
return { diffs, summary: formatWorktreeDiffSummary(diffs) };
|
|
1301
1652
|
}
|
|
1302
1653
|
|
|
1303
1654
|
function ensureParallelProgressFile(cwd: string, group: Extract<RunnerStep, { parallel: SubagentStep[] }>): void {
|
|
@@ -1325,7 +1676,26 @@ function resolveAsyncStepTranscriptPath(input: {
|
|
|
1325
1676
|
|
|
1326
1677
|
type SingleStepResult = Awaited<ReturnType<typeof runSingleStep>>;
|
|
1327
1678
|
|
|
1328
|
-
|
|
1679
|
+
function combinedAbortSignal(signals: Array<AbortSignal | undefined>): AbortSignal | undefined {
|
|
1680
|
+
const activeSignals = signals.filter((signal): signal is AbortSignal => Boolean(signal));
|
|
1681
|
+
if (activeSignals.length === 0) return undefined;
|
|
1682
|
+
if (activeSignals.length === 1) return activeSignals[0];
|
|
1683
|
+
const controller = new AbortController();
|
|
1684
|
+
const abort = (): void => controller.abort();
|
|
1685
|
+
for (const signal of activeSignals) {
|
|
1686
|
+
if (signal.aborted) {
|
|
1687
|
+
abort();
|
|
1688
|
+
break;
|
|
1689
|
+
}
|
|
1690
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
1691
|
+
}
|
|
1692
|
+
return controller.signal;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
async function runSubagent(
|
|
1696
|
+
config: SubagentRunConfig,
|
|
1697
|
+
onWriterProcess?: (writer: { state: "none" | "spawning" } | { state: "running"; pid: number }) => void,
|
|
1698
|
+
): Promise<void> {
|
|
1329
1699
|
const { id, steps, resultPath, cwd, placeholder, taskIndex, totalTasks, maxOutput, artifactsDir, artifactConfig } =
|
|
1330
1700
|
config;
|
|
1331
1701
|
const globalSemaphore = new Semaphore(config.globalConcurrencyLimit ?? DEFAULT_GLOBAL_CONCURRENCY_LIMIT);
|
|
@@ -1341,16 +1711,21 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1341
1711
|
const controlConfig = config.controlConfig ?? DEFAULT_CONTROL_CONFIG;
|
|
1342
1712
|
const activeChildInterrupts = new Map<number, () => void>();
|
|
1343
1713
|
const activeChildTimeouts = new Map<number, () => void>();
|
|
1714
|
+
const activeChildStops = new Map<number, () => void>();
|
|
1344
1715
|
const activeChildTurnBudgetAborts = new Map<number, (message: string, state?: TurnBudgetState) => void>();
|
|
1345
1716
|
const pendingStepSteers: SteerRequest[] = [];
|
|
1717
|
+
const steeringCapabilities = new Map<number, SteerCapability>();
|
|
1346
1718
|
let interrupted = false;
|
|
1347
1719
|
let currentActivityState: ActivityState | undefined;
|
|
1348
1720
|
let activityTimer: NodeJS.Timeout | undefined;
|
|
1349
1721
|
let timeoutTimer: NodeJS.Timeout | undefined;
|
|
1350
1722
|
let timedOut = false;
|
|
1723
|
+
let stopped = false;
|
|
1351
1724
|
let turnBudgetExceeded = false;
|
|
1352
1725
|
const timeoutMessage = config.timeoutMs !== undefined ? `Subagent timed out after ${config.timeoutMs}ms.` : undefined;
|
|
1726
|
+
const stopMessage = "Subagent stopped by user.";
|
|
1353
1727
|
const timeoutAbortController = new AbortController();
|
|
1728
|
+
const stopAbortController = new AbortController();
|
|
1354
1729
|
let previousCumulativeTokens: TokenUsage = { input: 0, output: 0, total: 0 };
|
|
1355
1730
|
let latestSessionFile: string | undefined;
|
|
1356
1731
|
|
|
@@ -1368,10 +1743,14 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1368
1743
|
const transcriptPath = resolveAsyncStepTranscriptPath({ artifactsDir, artifactConfig, runId: id, agent: task.agent, flatIndex: taskFlatIndex, flatStepCount: initialFlatStepCount });
|
|
1369
1744
|
initialStatusSteps.push({
|
|
1370
1745
|
agent: task.agent,
|
|
1746
|
+
...(task.context ? { context: task.context } : {}),
|
|
1371
1747
|
phase: task.phase,
|
|
1372
1748
|
label: task.label,
|
|
1373
1749
|
outputName: task.outputName,
|
|
1374
1750
|
structured: task.structured,
|
|
1751
|
+
...(task.agentContract ? { agentContract: task.agentContract } : {}),
|
|
1752
|
+
...(task.launchContractDigest ? { launchContractDigest: task.launchContractDigest } : {}),
|
|
1753
|
+
...(task.capabilityCeiling ? { capabilityCeiling: task.capabilityCeiling } : {}),
|
|
1375
1754
|
status: "pending",
|
|
1376
1755
|
...(task.toolBudget ? { toolBudget: initialToolBudgetState(task.toolBudget) } : {}),
|
|
1377
1756
|
...(task.sessionFile ? { sessionFile: task.sessionFile } : {}),
|
|
@@ -1389,10 +1768,13 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1389
1768
|
parallelGroups.push({ start: flatStepCount, count: 1, stepIndex });
|
|
1390
1769
|
initialStatusSteps.push({
|
|
1391
1770
|
agent: `expand:${step.parallel.agent}`,
|
|
1771
|
+
...(step.parallel.context ? { context: step.parallel.context } : {}),
|
|
1392
1772
|
phase: step.phase ?? step.parallel.phase,
|
|
1393
1773
|
label: step.label ?? step.parallel.label ?? `Dynamic fanout (${step.collect.as})`,
|
|
1394
1774
|
outputName: step.collect.as,
|
|
1395
1775
|
structured: Boolean(step.collect.outputSchema),
|
|
1776
|
+
...(step.agentContract ? { agentContract: step.agentContract } : {}),
|
|
1777
|
+
...(step.capabilityCeiling ? { capabilityCeiling: step.capabilityCeiling } : {}),
|
|
1396
1778
|
status: "pending",
|
|
1397
1779
|
...(step.parallel.toolBudget ? { toolBudget: initialToolBudgetState(step.parallel.toolBudget) } : {}),
|
|
1398
1780
|
recentTools: [],
|
|
@@ -1404,10 +1786,14 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1404
1786
|
const transcriptPath = resolveAsyncStepTranscriptPath({ artifactsDir, artifactConfig, runId: id, agent: step.agent, flatIndex: stepFlatIndex, flatStepCount: initialFlatStepCount });
|
|
1405
1787
|
initialStatusSteps.push({
|
|
1406
1788
|
agent: step.agent,
|
|
1789
|
+
...(step.context ? { context: step.context } : {}),
|
|
1407
1790
|
phase: step.phase,
|
|
1408
1791
|
label: step.label,
|
|
1409
1792
|
outputName: step.outputName,
|
|
1410
1793
|
structured: step.structured,
|
|
1794
|
+
...(step.agentContract ? { agentContract: step.agentContract } : {}),
|
|
1795
|
+
...(step.launchContractDigest ? { launchContractDigest: step.launchContractDigest } : {}),
|
|
1796
|
+
...(step.capabilityCeiling ? { capabilityCeiling: step.capabilityCeiling } : {}),
|
|
1411
1797
|
status: "pending",
|
|
1412
1798
|
...(step.toolBudget ? { toolBudget: initialToolBudgetState(step.toolBudget) } : {}),
|
|
1413
1799
|
...(step.sessionFile ? { sessionFile: step.sessionFile } : {}),
|
|
@@ -1425,12 +1811,19 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1425
1811
|
const sessionEnabled = Boolean(config.sessionDir)
|
|
1426
1812
|
|| shareEnabled
|
|
1427
1813
|
|| flatSteps.some((step) => Boolean(step.sessionFile));
|
|
1814
|
+
if (config.runnerProcessInstanceId) {
|
|
1815
|
+
for (const step of initialStatusSteps) {
|
|
1816
|
+
step.processTerminal = { version: 1, state: "pending", runId: id, runnerProcessInstanceId: config.runnerProcessInstanceId };
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1428
1819
|
const statusPayload: RunnerStatusPayload = {
|
|
1429
1820
|
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
1430
1821
|
runId: id,
|
|
1431
1822
|
...(config.sessionId ? { sessionId: config.sessionId } : {}),
|
|
1432
1823
|
mode: config.resultMode ?? (flatSteps.length > 1 ? "chain" : "single"),
|
|
1824
|
+
...(config.nestedRoute ? { isNested: true } : {}),
|
|
1433
1825
|
state: "running",
|
|
1826
|
+
steering: createSteeringStatus(),
|
|
1434
1827
|
lastActivityAt: overallStartTime,
|
|
1435
1828
|
startedAt: overallStartTime,
|
|
1436
1829
|
lastUpdate: overallStartTime,
|
|
@@ -1444,6 +1837,9 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1444
1837
|
chainStepCount: steps.length,
|
|
1445
1838
|
parallelGroups,
|
|
1446
1839
|
workflowGraph: config.workflowGraph,
|
|
1840
|
+
...(config.launchContractDigest ? { launchContractDigest: config.launchContractDigest } : {}),
|
|
1841
|
+
...(config.capabilityCeiling ? { capabilityCeiling: config.capabilityCeiling } : {}),
|
|
1842
|
+
...(config.runnerProcessInstanceId ? { processTerminal: { version: 1 as const, state: "pending" as const, runId: id, runnerProcessInstanceId: config.runnerProcessInstanceId } } : {}),
|
|
1447
1843
|
steps: initialStatusSteps,
|
|
1448
1844
|
artifactsDir,
|
|
1449
1845
|
sessionDir: config.sessionDir,
|
|
@@ -1477,9 +1873,9 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1477
1873
|
const refreshWorkflowGraph = (): void => {
|
|
1478
1874
|
if (!config.workflowGraph) return;
|
|
1479
1875
|
const graph = structuredClone(statusPayload.workflowGraph ?? config.workflowGraph);
|
|
1480
|
-
const normalize = (status: RunnerStatusStep["status"]): "pending" | "running" | "completed" | "failed" | "paused" | "detached" => {
|
|
1876
|
+
const normalize = (status: RunnerStatusStep["status"]): "pending" | "running" | "completed" | "failed" | "paused" | "stopped" | "detached" => {
|
|
1481
1877
|
if (status === "complete" || status === "completed") return "completed";
|
|
1482
|
-
if (status === "running" || status === "failed" || status === "paused" || status === "pending") return status;
|
|
1878
|
+
if (status === "running" || status === "failed" || status === "paused" || status === "stopped" || status === "pending") return status;
|
|
1483
1879
|
return "pending";
|
|
1484
1880
|
};
|
|
1485
1881
|
const updateNode = (node: NonNullable<typeof graph.nodes>[number]): void => {
|
|
@@ -1496,10 +1892,11 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1496
1892
|
if (node.children?.length) {
|
|
1497
1893
|
if (node.children.every((child) => child.status === "completed")) node.status = "completed";
|
|
1498
1894
|
else if (node.children.some((child) => child.status === "running")) node.status = "running";
|
|
1895
|
+
else if (node.children.some((child) => child.status === "stopped")) node.status = "stopped";
|
|
1499
1896
|
else if (node.children.some((child) => child.status === "failed")) node.status = "failed";
|
|
1500
1897
|
else if (node.children.some((child) => child.status === "paused")) node.status = "paused";
|
|
1501
1898
|
}
|
|
1502
|
-
if (node.error) node.status = "failed";
|
|
1899
|
+
if (node.error && node.status !== "stopped") node.status = "failed";
|
|
1503
1900
|
};
|
|
1504
1901
|
for (const node of graph.nodes) updateNode(node);
|
|
1505
1902
|
statusPayload.workflowGraph = graph;
|
|
@@ -1525,6 +1922,14 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1525
1922
|
activeChildTimeouts.set(flatIndex, interrupt);
|
|
1526
1923
|
if (timedOut) interrupt();
|
|
1527
1924
|
};
|
|
1925
|
+
const registerStepStop = (flatIndex: number, stop: (() => void) | undefined): void => {
|
|
1926
|
+
if (!stop) {
|
|
1927
|
+
activeChildStops.delete(flatIndex);
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
activeChildStops.set(flatIndex, stop);
|
|
1931
|
+
if (stopped) stop();
|
|
1932
|
+
};
|
|
1528
1933
|
const registerStepTurnBudgetAbort = (flatIndex: number, abort: ((message: string, state?: TurnBudgetState) => void) | undefined): void => {
|
|
1529
1934
|
if (!abort) {
|
|
1530
1935
|
activeChildTurnBudgetAborts.delete(flatIndex);
|
|
@@ -1538,6 +1943,9 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1538
1943
|
const timeoutActiveChildren = (): void => {
|
|
1539
1944
|
for (const interrupt of [...activeChildTimeouts.values()]) interrupt();
|
|
1540
1945
|
};
|
|
1946
|
+
const stopActiveChildren = (): void => {
|
|
1947
|
+
for (const stop of [...activeChildStops.values()]) stop();
|
|
1948
|
+
};
|
|
1541
1949
|
const nestedRuns = function* (children: NestedRunSummary[] | undefined): Generator<NestedRunSummary> {
|
|
1542
1950
|
for (const child of children ?? []) {
|
|
1543
1951
|
yield child;
|
|
@@ -1576,6 +1984,37 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1576
1984
|
}
|
|
1577
1985
|
}
|
|
1578
1986
|
};
|
|
1987
|
+
const stopNestedAsyncDescendants = (): void => {
|
|
1988
|
+
if (!config.nestedRoute) return;
|
|
1989
|
+
let registry: ReturnType<typeof projectNestedEvents>;
|
|
1990
|
+
try {
|
|
1991
|
+
registry = projectNestedEvents(config.nestedRoute);
|
|
1992
|
+
} catch (error) {
|
|
1993
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
1994
|
+
type: "subagent.nested.stop_failed",
|
|
1995
|
+
ts: Date.now(),
|
|
1996
|
+
runId: id,
|
|
1997
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1998
|
+
}));
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
for (const run of nestedRuns(registry.children)) {
|
|
2002
|
+
if (run.state !== "running" && run.state !== "queued") continue;
|
|
2003
|
+
const nestedAsyncDir = run.asyncDir ?? resolveNestedAsyncDir(config.nestedRoute.rootRunId, run);
|
|
2004
|
+
if (!nestedAsyncDir) continue;
|
|
2005
|
+
try {
|
|
2006
|
+
deliverStopRequest({ asyncDir: nestedAsyncDir, pid: run.pid, source: "ancestor-stop" });
|
|
2007
|
+
} catch (error) {
|
|
2008
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
2009
|
+
type: "subagent.nested.stop_failed",
|
|
2010
|
+
ts: Date.now(),
|
|
2011
|
+
runId: id,
|
|
2012
|
+
targetRunId: run.id,
|
|
2013
|
+
message: error instanceof Error ? error.message : String(error),
|
|
2014
|
+
}));
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
};
|
|
1579
2018
|
const timeoutNestedAsyncDescendants = (): void => {
|
|
1580
2019
|
if (!config.nestedRoute) return;
|
|
1581
2020
|
let registry: ReturnType<typeof projectNestedEvents>;
|
|
@@ -1607,19 +2046,29 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1607
2046
|
}
|
|
1608
2047
|
}
|
|
1609
2048
|
};
|
|
1610
|
-
const pausedStepResult = (agent: string): SingleStepResult => ({
|
|
2049
|
+
const pausedStepResult = (agent: string, context?: "fresh" | "fork"): SingleStepResult => ({
|
|
1611
2050
|
agent,
|
|
2051
|
+
context,
|
|
1612
2052
|
output: "Paused after interrupt. Waiting for explicit next action.",
|
|
1613
2053
|
exitCode: 0,
|
|
1614
2054
|
interrupted: true,
|
|
1615
2055
|
});
|
|
1616
|
-
const timedOutStepResult = (agent: string): SingleStepResult => ({
|
|
2056
|
+
const timedOutStepResult = (agent: string, context?: "fresh" | "fork"): SingleStepResult => ({
|
|
1617
2057
|
agent,
|
|
2058
|
+
context,
|
|
1618
2059
|
output: timeoutMessage ?? "Subagent timed out.",
|
|
1619
2060
|
error: timeoutMessage ?? "Subagent timed out.",
|
|
1620
2061
|
exitCode: 1,
|
|
1621
2062
|
timedOut: true,
|
|
1622
2063
|
});
|
|
2064
|
+
const stoppedStepResult = (agent: string, context?: "fresh" | "fork"): SingleStepResult => ({
|
|
2065
|
+
agent,
|
|
2066
|
+
context,
|
|
2067
|
+
output: stopMessage,
|
|
2068
|
+
error: stopMessage,
|
|
2069
|
+
exitCode: 1,
|
|
2070
|
+
stopped: true,
|
|
2071
|
+
});
|
|
1623
2072
|
const consumePendingAppendRequests = (): void => {
|
|
1624
2073
|
if (statusPayload.mode !== "chain" || statusPayload.state !== "running") return;
|
|
1625
2074
|
const requests = consumeChainAppendRequests(asyncDir);
|
|
@@ -1659,7 +2108,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1659
2108
|
}));
|
|
1660
2109
|
}
|
|
1661
2110
|
};
|
|
1662
|
-
const markDynamicGraphGroup = (stepIndex: number, status: "completed" | "failed" | "running", error?: string, acceptance?: import("../../shared/types.ts").AcceptanceLedger): void => {
|
|
2111
|
+
const markDynamicGraphGroup = (stepIndex: number, status: "completed" | "failed" | "running" | "stopped", error?: string, acceptance?: import("../../shared/types.ts").AcceptanceLedger): void => {
|
|
1663
2112
|
const groupNode = statusPayload.workflowGraph?.nodes.find((node) => node.id === `step-${stepIndex}`);
|
|
1664
2113
|
if (!groupNode) return;
|
|
1665
2114
|
groupNode.status = status;
|
|
@@ -1684,6 +2133,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1684
2133
|
const activeLongRunningSteps = new Set<number>();
|
|
1685
2134
|
const mutatingFailureStates = initialStatusSteps.map(() => createMutatingFailureState());
|
|
1686
2135
|
const pendingToolResults: Array<{ tool: string; path?: string; mutates: boolean; startedAt?: number } | undefined> = initialStatusSteps.map(() => undefined);
|
|
2136
|
+
const supervisorAttentionSteps = new Map<number, ActivityState | undefined>();
|
|
1687
2137
|
const mutatingFailureWindowMs = 5 * 60_000;
|
|
1688
2138
|
const appendControlEvent = (event: ReturnType<typeof buildControlEvent>) => {
|
|
1689
2139
|
if (!controlConfig.enabled) return;
|
|
@@ -1714,6 +2164,15 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1714
2164
|
statusPayload.currentToolStartedAt = activeStep?.currentToolStartedAt;
|
|
1715
2165
|
statusPayload.currentPath = activeStep?.currentPath;
|
|
1716
2166
|
};
|
|
2167
|
+
const syncAggregateActivityState = (): void => {
|
|
2168
|
+
const nextRunState = statusPayload.steps.some((step) => step.activityState === "needs_attention")
|
|
2169
|
+
? "needs_attention"
|
|
2170
|
+
: statusPayload.steps.some((step) => step.activityState === "active_long_running")
|
|
2171
|
+
? "active_long_running"
|
|
2172
|
+
: undefined;
|
|
2173
|
+
currentActivityState = nextRunState;
|
|
2174
|
+
statusPayload.activityState = nextRunState;
|
|
2175
|
+
};
|
|
1717
2176
|
const maybeEmitActiveLongRunning = (flatIndex: number, now: number): boolean => {
|
|
1718
2177
|
if (!controlConfig.enabled || activeLongRunningSteps.has(flatIndex)) return false;
|
|
1719
2178
|
const step = statusPayload.steps[flatIndex];
|
|
@@ -1750,48 +2209,125 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1750
2209
|
appendControlEvent(event);
|
|
1751
2210
|
return true;
|
|
1752
2211
|
};
|
|
2212
|
+
const steeringMarkerPath = (requestId: string): string => path.join(asyncDir, "control", "steer-recovery", `${Buffer.from(requestId).toString("base64url")}.json`);
|
|
2213
|
+
const markSteeringAttention = (index: number): void => {
|
|
2214
|
+
const step = statusPayload.steps[index];
|
|
2215
|
+
if (step) step.activityState = "needs_attention";
|
|
2216
|
+
statusPayload.activityState = "needs_attention";
|
|
2217
|
+
};
|
|
2218
|
+
const emitSteeringEvent = (type: string, request: SteerRequest, index?: number, extra: Record<string, unknown> = {}): void => {
|
|
2219
|
+
appendJsonl(eventsPath, JSON.stringify({ type, ts: Date.now(), runId: id, requestId: request.id, ...(index !== undefined ? { index } : {}), ...extra }));
|
|
2220
|
+
};
|
|
2221
|
+
const emitSteeringNotice = (requestId: string, state: "failed" | "partial" | "recovered", message: string): void => {
|
|
2222
|
+
appendJsonl(eventsPath, JSON.stringify({ type: "subagent.steering.notice", ts: Date.now(), runId: id, requestId, state, message, ...(config.sessionId ? { currentSessionId: config.sessionId } : {}) }));
|
|
2223
|
+
};
|
|
2224
|
+
const recordSteeringLifecycle = (request: SteerRequest, targets: Array<{ index: number; state: SteeringTargetState; reason?: string }>): void => {
|
|
2225
|
+
const lifecycle = steeringStatus(statusPayload);
|
|
2226
|
+
recordSteeringRequest(lifecycle, { id: request.id, requestedAt: request.ts, source: request.source, message: request.message, targets });
|
|
2227
|
+
for (const target of targets) {
|
|
2228
|
+
const step = statusPayload.steps[target.index];
|
|
2229
|
+
if (!step) continue;
|
|
2230
|
+
step.steering ??= createSteeringStatus();
|
|
2231
|
+
recordSteeringRequest(step.steering, { id: request.id, requestedAt: request.ts, source: request.source, message: request.message, targets: [target] });
|
|
2232
|
+
}
|
|
2233
|
+
};
|
|
2234
|
+
const updateSteeringLifecycleTarget = (
|
|
2235
|
+
requestId: string,
|
|
2236
|
+
index: number,
|
|
2237
|
+
state: SteeringTargetState,
|
|
2238
|
+
now: number,
|
|
2239
|
+
fields: Pick<SteeringTargetStatus, "reason" | "replacementRunId"> = {},
|
|
2240
|
+
): SteeringTargetStatus | undefined => {
|
|
2241
|
+
const updated = updateSteeringTarget(steeringStatus(statusPayload), requestId, index, state, now, fields);
|
|
2242
|
+
const step = statusPayload.steps[index];
|
|
2243
|
+
if (step?.steering) updateSteeringTarget(step.steering, requestId, index, state, now, fields);
|
|
2244
|
+
return updated;
|
|
2245
|
+
};
|
|
2246
|
+
const emitTerminalSteeringNotice = (requestId: string, failureMessage: string): void => {
|
|
2247
|
+
const state = terminalSteeringNoticeState(steeringStatus(statusPayload), requestId);
|
|
2248
|
+
if (state === "partial") emitSteeringNotice(requestId, "partial", `Steering partially delivered for run ${id}.`);
|
|
2249
|
+
else if (state === "failed") emitSteeringNotice(requestId, "failed", failureMessage);
|
|
2250
|
+
};
|
|
1753
2251
|
const deliverSteerRequest = (request: SteerRequest): void => {
|
|
1754
|
-
if (statusPayload.state !== "running")
|
|
2252
|
+
if (statusPayload.state !== "running") {
|
|
2253
|
+
const reason = `run became ${statusPayload.state} before steering request was consumed`;
|
|
2254
|
+
const indexes = request.targetIndex !== undefined
|
|
2255
|
+
? [request.targetIndex]
|
|
2256
|
+
: request.targetIndexes?.length
|
|
2257
|
+
? request.targetIndexes
|
|
2258
|
+
: statusPayload.steps.map((_, index) => index);
|
|
2259
|
+
const targets = indexes.map((index) => ({ index, state: "failed" as const, reason }));
|
|
2260
|
+
recordSteeringLifecycle(request, targets);
|
|
2261
|
+
emitSteeringEvent("subagent.steer.requested", request, undefined, { targets });
|
|
2262
|
+
for (const target of targets) emitSteeringEvent("subagent.steer.failed", request, target.index, { reason });
|
|
2263
|
+
emitTerminalSteeringNotice(request.id, `Steering failed for run ${id}: ${reason}.`);
|
|
2264
|
+
statusPayload.lastUpdate = Date.now();
|
|
2265
|
+
writeStatusPayload();
|
|
2266
|
+
return;
|
|
2267
|
+
}
|
|
1755
2268
|
const runningIndexes = statusPayload.steps
|
|
1756
2269
|
.map((step, index) => ({ step, index }))
|
|
1757
2270
|
.filter(({ step }) => step.status === "running")
|
|
1758
2271
|
.map(({ index }) => index);
|
|
1759
|
-
const targets = request.targetIndex !== undefined
|
|
2272
|
+
const targets = request.targetIndex !== undefined
|
|
2273
|
+
? [request.targetIndex]
|
|
2274
|
+
: request.targetIndexes?.length
|
|
2275
|
+
? request.targetIndexes
|
|
2276
|
+
: runningIndexes.length > 0
|
|
2277
|
+
? runningIndexes
|
|
2278
|
+
: statusPayload.mode === "single" && statusPayload.steps[0]?.status === "pending"
|
|
2279
|
+
? [0]
|
|
2280
|
+
: [];
|
|
1760
2281
|
const now = Date.now();
|
|
1761
|
-
const
|
|
1762
|
-
const rejected: Array<{ index: number; reason: string }> = [];
|
|
1763
|
-
for (const index of targets) {
|
|
2282
|
+
const targetStates = targets.map((index) => {
|
|
1764
2283
|
const step = statusPayload.steps[index];
|
|
1765
|
-
if (!step) {
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
2284
|
+
if (!step) return { index, state: "failed" as const, reason: "child index out of range" };
|
|
2285
|
+
if (step.status === "pending") return { index, state: "scheduled" as const };
|
|
2286
|
+
if (step.status !== "running") return { index, state: "failed" as const, reason: `child is ${step.status}` };
|
|
2287
|
+
if (steeringCapabilities.get(index)?.supported === false) return { index, state: "failed" as const, reason: "child Pi session does not support steering" };
|
|
2288
|
+
return { index, state: "routed" as const };
|
|
2289
|
+
});
|
|
2290
|
+
recordSteeringLifecycle(request, targetStates);
|
|
2291
|
+
emitSteeringEvent("subagent.steer.requested", request, undefined, { targets: targetStates });
|
|
2292
|
+
for (const target of targetStates) {
|
|
2293
|
+
if (target.state === "routed") {
|
|
2294
|
+
try {
|
|
2295
|
+
enqueueStepSteer(asyncDir, target.index, request);
|
|
2296
|
+
updateSteeringLifecycleTarget(request.id, target.index, "routed", now);
|
|
2297
|
+
emitSteeringEvent("subagent.steer.routed", request, target.index);
|
|
2298
|
+
} catch (error) {
|
|
2299
|
+
markSteeringAttention(target.index);
|
|
2300
|
+
updateSteeringLifecycleTarget(request.id, target.index, "failed", now, { reason: error instanceof Error ? error.message : String(error) });
|
|
2301
|
+
emitSteeringEvent("subagent.steer.failed", request, target.index, { reason: error instanceof Error ? error.message : String(error) });
|
|
2302
|
+
}
|
|
2303
|
+
} else if (target.state === "failed") {
|
|
2304
|
+
markSteeringAttention(target.index);
|
|
2305
|
+
emitSteeringEvent("subagent.steer.failed", request, target.index, { reason: target.reason });
|
|
2306
|
+
} else {
|
|
2307
|
+
emitSteeringEvent("subagent.steer.scheduled", request, target.index);
|
|
1772
2308
|
}
|
|
1773
|
-
enqueueStepSteer(asyncDir, index, request);
|
|
1774
|
-
step.steerCount = (step.steerCount ?? 0) + 1;
|
|
1775
|
-
step.lastSteerAt = now;
|
|
1776
|
-
accepted.push(index);
|
|
1777
2309
|
}
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
2310
|
+
emitTerminalSteeringNotice(request.id, `Steering failed for run ${id}: no requested child remained steerable.`);
|
|
2311
|
+
statusPayload.lastUpdate = now;
|
|
2312
|
+
writeStatusPayload();
|
|
2313
|
+
};
|
|
2314
|
+
const consumeSteerAck = (ack: SteerAck): void => {
|
|
2315
|
+
const lifecycle = steeringStatus(statusPayload);
|
|
2316
|
+
const request = lifecycle.recent.find((candidate) => candidate.id === ack.requestId);
|
|
2317
|
+
if (!request || !request.targets.some((target) => target.index === ack.index)) return;
|
|
2318
|
+
const late = fs.existsSync(steeringMarkerPath(ack.requestId));
|
|
2319
|
+
const now = Date.now();
|
|
2320
|
+
if (ack.state === "delivered") {
|
|
2321
|
+
updateSteeringLifecycleTarget(ack.requestId, ack.index, late ? "late" : "delivered", now, { reason: late ? "acknowledged after recovery commit" : undefined });
|
|
2322
|
+
emitSteeringEvent("subagent.steer.delivered", { type: "steer", id: ack.requestId, ts: now, message: ack.message }, ack.index, { late, message: ack.message });
|
|
2323
|
+
} else {
|
|
2324
|
+
markSteeringAttention(ack.index);
|
|
2325
|
+
updateSteeringLifecycleTarget(ack.requestId, ack.index, "failed", now, { reason: ack.message });
|
|
2326
|
+
emitSteeringEvent("subagent.steer.failed", { type: "steer", id: ack.requestId, ts: now, message: ack.message }, ack.index, { reason: ack.message });
|
|
1783
2327
|
}
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
runId: id,
|
|
1788
|
-
requestId: request.id,
|
|
1789
|
-
message: request.message,
|
|
1790
|
-
...(request.source ? { source: request.source } : {}),
|
|
1791
|
-
...(request.targetIndex !== undefined ? { targetIndex: request.targetIndex } : {}),
|
|
1792
|
-
acceptedIndexes: accepted,
|
|
1793
|
-
...(rejected.length ? { rejected } : {}),
|
|
1794
|
-
}));
|
|
2328
|
+
emitTerminalSteeringNotice(ack.requestId, `Steering failed for run ${id}: ${ack.message}`);
|
|
2329
|
+
statusPayload.lastUpdate = now;
|
|
2330
|
+
writeStatusPayload();
|
|
1795
2331
|
};
|
|
1796
2332
|
const flushPendingStepSteers = (flatIndex: number): void => {
|
|
1797
2333
|
const remaining: SteerRequest[] = [];
|
|
@@ -1810,25 +2346,51 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1810
2346
|
statusPayload.lastUpdate = now;
|
|
1811
2347
|
writeStatusPayload();
|
|
1812
2348
|
};
|
|
1813
|
-
const updateStepTurnBudget = (
|
|
2349
|
+
const updateStepTurnBudget = (
|
|
2350
|
+
flatIndex: number,
|
|
2351
|
+
turnCount: number,
|
|
2352
|
+
now: number,
|
|
2353
|
+
terminalAssistantStop: boolean,
|
|
2354
|
+
toolWorkActiveOrStarting: boolean,
|
|
2355
|
+
): void => {
|
|
1814
2356
|
const budget = config.turnBudget;
|
|
1815
2357
|
const step = statusPayload.steps[flatIndex];
|
|
1816
|
-
if (!budget || !step || timedOut || turnBudgetExceeded || step.turnBudgetExceeded) return;
|
|
2358
|
+
if (!budget || !step || timedOut || stopped || turnBudgetExceeded || step.turnBudgetExceeded) return;
|
|
1817
2359
|
if (turnCount < budget.maxTurns) {
|
|
1818
2360
|
const state: TurnBudgetState = { ...budget, outcome: "within-budget", turnCount };
|
|
1819
2361
|
step.turnBudget = state;
|
|
1820
2362
|
statusPayload.turnBudget = state;
|
|
1821
2363
|
return;
|
|
1822
2364
|
}
|
|
1823
|
-
const state = turnBudgetState(budget, turnCount, false);
|
|
1824
|
-
step.turnBudget = state;
|
|
1825
|
-
statusPayload.turnBudget = state;
|
|
1826
2365
|
if (!step.wrapUpRequested) {
|
|
1827
2366
|
step.wrapUpRequested = true;
|
|
1828
2367
|
statusPayload.wrapUpRequested = true;
|
|
1829
2368
|
appendRecentStepOutput(step, [turnBudgetSoftNote(budget, turnCount)]);
|
|
1830
2369
|
}
|
|
1831
|
-
|
|
2370
|
+
const decision = turnBudgetDecision(budget, turnCount, terminalAssistantStop, toolWorkActiveOrStarting);
|
|
2371
|
+
if (decision === "defer") {
|
|
2372
|
+
const firstDeferredTurn = step.turnBudget?.terminationDeferredAtTurn;
|
|
2373
|
+
const deferredState = turnBudgetDeferredState(budget, turnCount, firstDeferredTurn);
|
|
2374
|
+
step.turnBudget = deferredState;
|
|
2375
|
+
statusPayload.turnBudget = deferredState;
|
|
2376
|
+
if (firstDeferredTurn === undefined) {
|
|
2377
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
2378
|
+
type: "subagent.step.turn_budget_deferred",
|
|
2379
|
+
ts: now,
|
|
2380
|
+
runId: id,
|
|
2381
|
+
stepIndex: flatIndex,
|
|
2382
|
+
agent: step.agent,
|
|
2383
|
+
turnCount,
|
|
2384
|
+
maxTurns: budget.maxTurns,
|
|
2385
|
+
graceTurns: budget.graceTurns,
|
|
2386
|
+
}));
|
|
2387
|
+
}
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
const state = turnBudgetState(budget, turnCount, false);
|
|
2391
|
+
step.turnBudget = state;
|
|
2392
|
+
statusPayload.turnBudget = state;
|
|
2393
|
+
if (decision !== "abort") return;
|
|
1832
2394
|
const exceededState = turnBudgetState(budget, turnCount, true);
|
|
1833
2395
|
const message = turnBudgetExceededMessage(budget, turnCount);
|
|
1834
2396
|
step.turnBudget = exceededState;
|
|
@@ -1849,6 +2411,22 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1849
2411
|
if (!step) return;
|
|
1850
2412
|
const now = Date.now();
|
|
1851
2413
|
statusPayload.currentStep = flatIndex;
|
|
2414
|
+
if (isChildWatchdogStatusEvent(event)) {
|
|
2415
|
+
const next = acceptChildWatchdogEvent({
|
|
2416
|
+
current: step.watchdog,
|
|
2417
|
+
event,
|
|
2418
|
+
runId: id,
|
|
2419
|
+
agent: step.agent,
|
|
2420
|
+
childIndex: flatIndex,
|
|
2421
|
+
});
|
|
2422
|
+
if (!next) return;
|
|
2423
|
+
step.watchdog = next;
|
|
2424
|
+
step.lastActivityAt = now;
|
|
2425
|
+
statusPayload.lastActivityAt = now;
|
|
2426
|
+
statusPayload.lastUpdate = now;
|
|
2427
|
+
writeStatusPayload();
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
1852
2430
|
if (event.type === "tool_execution_start" && event.toolName) {
|
|
1853
2431
|
const mutates = isMutatingTool(event.toolName, event.args);
|
|
1854
2432
|
const currentPath = resolveCurrentPath(event.toolName, event.args);
|
|
@@ -1865,15 +2443,45 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1865
2443
|
pendingToolResults[flatIndex] = { tool: event.toolName, path: currentPath, mutates, startedAt: now };
|
|
1866
2444
|
statusPayload.toolCount = (statusPayload.toolCount ?? 0) + 1;
|
|
1867
2445
|
syncTopLevelCurrentTool();
|
|
2446
|
+
if (controlConfig.enabled && isBlockingSupervisorTool(event.toolName, event.args) && step.activityState !== "needs_attention") {
|
|
2447
|
+
const previous = step.activityState;
|
|
2448
|
+
step.activityState = "needs_attention";
|
|
2449
|
+
supervisorAttentionSteps.set(flatIndex, previous);
|
|
2450
|
+
currentActivityState = "needs_attention";
|
|
2451
|
+
statusPayload.activityState = "needs_attention";
|
|
2452
|
+
appendControlEvent(buildControlEvent({
|
|
2453
|
+
type: "needs_attention",
|
|
2454
|
+
from: previous,
|
|
2455
|
+
to: "needs_attention",
|
|
2456
|
+
runId: id,
|
|
2457
|
+
agent: step.agent,
|
|
2458
|
+
index: flatIndex,
|
|
2459
|
+
ts: now,
|
|
2460
|
+
message: `${step.agent} is waiting for a supervisor reply`,
|
|
2461
|
+
reason: "supervisor_request",
|
|
2462
|
+
turns: step.turnCount,
|
|
2463
|
+
tokens: step.tokens?.total,
|
|
2464
|
+
toolCount: step.toolCount,
|
|
2465
|
+
currentTool: step.currentTool,
|
|
2466
|
+
currentToolDurationMs: 0,
|
|
2467
|
+
currentPath: step.currentPath,
|
|
2468
|
+
}));
|
|
2469
|
+
}
|
|
1868
2470
|
} else if (event.type === "tool_execution_end") {
|
|
1869
2471
|
if (step.currentTool) {
|
|
1870
2472
|
step.recentTools ??= [];
|
|
1871
2473
|
step.recentTools.push({ tool: step.currentTool, args: step.currentToolArgs || "", endMs: now });
|
|
1872
2474
|
}
|
|
2475
|
+
const supervisorPreviousActivity = supervisorAttentionSteps.get(flatIndex);
|
|
2476
|
+
const clearedSupervisorAttention = supervisorAttentionSteps.delete(flatIndex);
|
|
1873
2477
|
step.currentTool = undefined;
|
|
1874
2478
|
step.currentToolArgs = undefined;
|
|
1875
2479
|
step.currentToolStartedAt = undefined;
|
|
1876
2480
|
step.currentPath = undefined;
|
|
2481
|
+
if (clearedSupervisorAttention && step.activityState === "needs_attention") {
|
|
2482
|
+
step.activityState = supervisorPreviousActivity;
|
|
2483
|
+
syncAggregateActivityState();
|
|
2484
|
+
}
|
|
1877
2485
|
syncTopLevelCurrentTool();
|
|
1878
2486
|
} else if (event.type === "tool_result_end" && event.message) {
|
|
1879
2487
|
const toolSnapshot = pendingToolResults[flatIndex];
|
|
@@ -1938,7 +2546,13 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1938
2546
|
statusPayload.totalTokens = { input: totalInput + input, output: totalOutput + output, total: totalInput + totalOutput + input + output };
|
|
1939
2547
|
}
|
|
1940
2548
|
statusPayload.turnCount = Math.max(statusPayload.turnCount ?? 0, step.turnCount);
|
|
1941
|
-
updateStepTurnBudget(
|
|
2549
|
+
updateStepTurnBudget(
|
|
2550
|
+
flatIndex,
|
|
2551
|
+
step.turnCount,
|
|
2552
|
+
now,
|
|
2553
|
+
isTerminalAssistantStop(event.message),
|
|
2554
|
+
assistantStartsToolCall(event.message) || Boolean(step.currentTool),
|
|
2555
|
+
);
|
|
1942
2556
|
}
|
|
1943
2557
|
syncTopLevelCurrentTool();
|
|
1944
2558
|
step.lastActivityAt = now;
|
|
@@ -1964,6 +2578,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1964
2578
|
config: controlConfig,
|
|
1965
2579
|
startedAt: step.startedAt ?? overallStartTime,
|
|
1966
2580
|
lastActivityAt,
|
|
2581
|
+
currentTool: step.currentTool,
|
|
1967
2582
|
now,
|
|
1968
2583
|
});
|
|
1969
2584
|
if (idleState === "needs_attention") {
|
|
@@ -2039,8 +2654,40 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2039
2654
|
interruptNestedAsyncDescendants();
|
|
2040
2655
|
interruptActiveChildren();
|
|
2041
2656
|
};
|
|
2657
|
+
const stopRunner = () => {
|
|
2658
|
+
if (stopped || timedOut || interrupted || statusPayload.state !== "running") return;
|
|
2659
|
+
stopped = true;
|
|
2660
|
+
const now = Date.now();
|
|
2661
|
+
statusPayload.state = "stopped";
|
|
2662
|
+
statusPayload.stopped = true;
|
|
2663
|
+
statusPayload.error = stopMessage;
|
|
2664
|
+
currentActivityState = undefined;
|
|
2665
|
+
statusPayload.activityState = undefined;
|
|
2666
|
+
statusPayload.lastUpdate = now;
|
|
2667
|
+
for (const step of statusPayload.steps) {
|
|
2668
|
+
if (step.status !== "running" && step.status !== "pending") continue;
|
|
2669
|
+
step.status = "stopped";
|
|
2670
|
+
step.error = stopMessage;
|
|
2671
|
+
step.exitCode = 1;
|
|
2672
|
+
step.stopped = true;
|
|
2673
|
+
step.activityState = undefined;
|
|
2674
|
+
step.endedAt = now;
|
|
2675
|
+
step.durationMs = step.startedAt ? now - step.startedAt : 0;
|
|
2676
|
+
step.lastActivityAt = now;
|
|
2677
|
+
}
|
|
2678
|
+
writeStatusPayload();
|
|
2679
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
2680
|
+
type: "subagent.run.stopped",
|
|
2681
|
+
ts: now,
|
|
2682
|
+
runId: id,
|
|
2683
|
+
message: stopMessage,
|
|
2684
|
+
}));
|
|
2685
|
+
stopAbortController.abort();
|
|
2686
|
+
stopNestedAsyncDescendants();
|
|
2687
|
+
stopActiveChildren();
|
|
2688
|
+
};
|
|
2042
2689
|
const timeoutRunner = () => {
|
|
2043
|
-
if (timedOut || interrupted || statusPayload.state !== "running") return;
|
|
2690
|
+
if (timedOut || stopped || interrupted || statusPayload.state !== "running") return;
|
|
2044
2691
|
timedOut = true;
|
|
2045
2692
|
const now = Date.now();
|
|
2046
2693
|
const message = timeoutMessage ?? "Subagent timed out.";
|
|
@@ -2081,12 +2728,36 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2081
2728
|
const disposeControlInbox = watchAsyncControlInbox(asyncDir, {
|
|
2082
2729
|
onInterrupt: interruptRunner,
|
|
2083
2730
|
onTimeout: timeoutRunner,
|
|
2731
|
+
onStop: stopRunner,
|
|
2084
2732
|
onSteer: (request) => {
|
|
2085
2733
|
const targetStep = request.targetIndex !== undefined ? statusPayload.steps[request.targetIndex] : undefined;
|
|
2086
|
-
if (targetStep?.status === "pending")
|
|
2087
|
-
|
|
2088
|
-
|
|
2734
|
+
if (targetStep?.status === "pending") {
|
|
2735
|
+
deliverSteerRequest(request);
|
|
2736
|
+
pendingStepSteers.push(request);
|
|
2737
|
+
} else if (request.targetIndexes !== undefined || request.targetIndex !== undefined || statusPayload.steps.some((step) => step.status === "running")) {
|
|
2738
|
+
deliverSteerRequest(request);
|
|
2739
|
+
} else {
|
|
2740
|
+
deliverSteerRequest(request);
|
|
2741
|
+
pendingStepSteers.push(request);
|
|
2742
|
+
}
|
|
2743
|
+
},
|
|
2744
|
+
onSteerCapability: (capability) => {
|
|
2745
|
+
steeringCapabilities.set(capability.index, capability);
|
|
2746
|
+
if (!capability.supported) {
|
|
2747
|
+
const now = Date.now();
|
|
2748
|
+
const lifecycle = steeringStatus(statusPayload);
|
|
2749
|
+
for (const request of lifecycle.recent) {
|
|
2750
|
+
if (!request.targets.some((target) => target.index === capability.index && (target.state === "routed" || target.state === "scheduled"))) continue;
|
|
2751
|
+
markSteeringAttention(capability.index);
|
|
2752
|
+
updateSteeringLifecycleTarget(request.id, capability.index, "failed", now, { reason: "child Pi session does not support steering" });
|
|
2753
|
+
emitSteeringEvent("subagent.steer.failed", { type: "steer", id: request.id, ts: request.requestedAt, message: "child Pi session does not support steering" }, capability.index, { reason: "child Pi session does not support steering" });
|
|
2754
|
+
emitTerminalSteeringNotice(request.id, `Steering failed for run ${id}: child ${capability.index} does not support steering.`);
|
|
2755
|
+
}
|
|
2756
|
+
statusPayload.lastUpdate = now;
|
|
2757
|
+
writeStatusPayload();
|
|
2758
|
+
}
|
|
2089
2759
|
},
|
|
2760
|
+
onSteerAck: consumeSteerAck,
|
|
2090
2761
|
});
|
|
2091
2762
|
if (config.deadlineAt !== undefined) {
|
|
2092
2763
|
const remainingMs = Math.max(0, config.deadlineAt - Date.now());
|
|
@@ -2110,7 +2781,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2110
2781
|
let stepCursor = 0;
|
|
2111
2782
|
|
|
2112
2783
|
while (true) {
|
|
2113
|
-
if (interrupted || timedOut || turnBudgetExceeded) break;
|
|
2784
|
+
if (interrupted || timedOut || stopped || turnBudgetExceeded) break;
|
|
2114
2785
|
consumePendingAppendRequests();
|
|
2115
2786
|
if (stepCursor >= steps.length) break;
|
|
2116
2787
|
const stepIndex = stepCursor++;
|
|
@@ -2121,7 +2792,10 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2121
2792
|
let materialized: ReturnType<typeof materializeDynamicParallelStep>;
|
|
2122
2793
|
try {
|
|
2123
2794
|
materialized = materializeDynamicParallelStep(step as Parameters<typeof materializeDynamicParallelStep>[0], outputs, stepIndex, { maxItems: config.dynamicFanoutMaxItems, allowRunnerFields: true });
|
|
2124
|
-
if (materialized.
|
|
2795
|
+
if (materialized.parallel.length > 1 && step.parallel.outputPath && !step.parallel.namespaceOutputPath) {
|
|
2796
|
+
throw new DynamicFanoutError(`Dynamic chain step ${stepIndex + 1} materialized ${materialized.parallel.length} items that resolve output to the same path: ${step.parallel.outputPath}. Remove the explicit output path or use an inherited relative agent output so each item can be isolated.`);
|
|
2797
|
+
}
|
|
2798
|
+
if (materialized.collectedOnEmpty) await validateDynamicCollection(step.collect.outputSchema, materialized.collectedOnEmpty);
|
|
2125
2799
|
} catch (error) {
|
|
2126
2800
|
const now = Date.now();
|
|
2127
2801
|
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
@@ -2140,10 +2814,21 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2140
2814
|
statusPayload.lastUpdate = now;
|
|
2141
2815
|
markDynamicGraphGroup(stepIndex, "failed", message);
|
|
2142
2816
|
writeStatusPayload();
|
|
2143
|
-
results.push({ agent: step.parallel.agent, output: message, error: message, success: false, exitCode: 1 });
|
|
2817
|
+
results.push({ agent: step.parallel.agent, context: step.parallel.context, output: message, error: message, success: false, exitCode: 1 });
|
|
2144
2818
|
break;
|
|
2145
2819
|
}
|
|
2146
2820
|
|
|
2821
|
+
const effectiveDynamicGroupAcceptance = resolveEffectiveAcceptance({
|
|
2822
|
+
explicit: step.acceptanceInput,
|
|
2823
|
+
agentName: step.parallel.agent,
|
|
2824
|
+
acceptanceRole: step.acceptanceRole,
|
|
2825
|
+
task: materialized.parallel.map((task) => task.task ?? step.parallel.task).join("\n") || step.parallel.task,
|
|
2826
|
+
mode: config.mode,
|
|
2827
|
+
async: true,
|
|
2828
|
+
dynamicGroup: true,
|
|
2829
|
+
agentContract: step.agentContract,
|
|
2830
|
+
});
|
|
2831
|
+
|
|
2147
2832
|
if (materialized.parallel.length === 0) {
|
|
2148
2833
|
const now = Date.now();
|
|
2149
2834
|
const collection = materialized.collectedOnEmpty ?? [];
|
|
@@ -2162,37 +2847,41 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2162
2847
|
placeholder.durationMs = 0;
|
|
2163
2848
|
}
|
|
2164
2849
|
previousOutput = "Dynamic fanout produced 0 results.";
|
|
2165
|
-
const groupAcceptance =
|
|
2850
|
+
const groupAcceptance = effectiveDynamicGroupAcceptance.explicit && !timedOut && !stopped
|
|
2166
2851
|
? await evaluateAcceptance({
|
|
2167
|
-
acceptance:
|
|
2852
|
+
acceptance: effectiveDynamicGroupAcceptance,
|
|
2168
2853
|
output: "",
|
|
2169
2854
|
report: aggregateAcceptanceReport({
|
|
2170
2855
|
results: [],
|
|
2171
2856
|
notes: "Dynamic fanout produced 0 results.",
|
|
2172
2857
|
}),
|
|
2173
2858
|
cwd,
|
|
2174
|
-
signal: timeoutAbortController.signal,
|
|
2175
|
-
abortMessage: timeoutMessage ?? "Subagent timed out.",
|
|
2859
|
+
signal: combinedAbortSignal([timeoutAbortController.signal, stopAbortController.signal]),
|
|
2860
|
+
abortMessage: stopAbortController.signal.aborted ? stopMessage : timeoutMessage ?? "Subagent timed out.",
|
|
2861
|
+
reportOptional: isAgentContractV1(step.agentContract),
|
|
2176
2862
|
})
|
|
2177
2863
|
: undefined;
|
|
2178
|
-
const
|
|
2179
|
-
const
|
|
2864
|
+
const groupStopped = stopped || stopAbortController.signal.aborted;
|
|
2865
|
+
const groupTimedOut = !groupStopped && (timedOut || timeoutAbortController.signal.aborted);
|
|
2866
|
+
const effectiveGroupAcceptance = groupTimedOut || groupStopped ? undefined : groupAcceptance;
|
|
2180
2867
|
if (placeholder && effectiveGroupAcceptance) placeholder.acceptance = effectiveGroupAcceptance;
|
|
2181
|
-
const groupAcceptanceFailure = effectiveGroupAcceptance ? acceptanceFailureMessage(effectiveGroupAcceptance) : undefined;
|
|
2182
|
-
if (groupTimedOut || groupAcceptanceFailure) {
|
|
2183
|
-
const errorMessage = groupTimedOut ? timeoutMessage ?? "Subagent timed out." : groupAcceptanceFailure!;
|
|
2184
|
-
statusPayload.state = "failed";
|
|
2868
|
+
const groupAcceptanceFailure = effectiveGroupAcceptance && (!isAgentContractV1(step.agentContract) || step.gateOn === "acceptance") ? acceptanceFailureMessage(effectiveGroupAcceptance) : undefined;
|
|
2869
|
+
if (groupTimedOut || groupStopped || groupAcceptanceFailure) {
|
|
2870
|
+
const errorMessage = groupStopped ? stopMessage : groupTimedOut ? timeoutMessage ?? "Subagent timed out." : groupAcceptanceFailure!;
|
|
2871
|
+
statusPayload.state = groupStopped ? "stopped" : "failed";
|
|
2185
2872
|
statusPayload.error = errorMessage;
|
|
2873
|
+
statusPayload.stopped = groupStopped ? true : statusPayload.stopped;
|
|
2186
2874
|
if (placeholder) {
|
|
2187
|
-
placeholder.status = "failed";
|
|
2875
|
+
placeholder.status = groupStopped ? "stopped" : "failed";
|
|
2188
2876
|
placeholder.error = errorMessage;
|
|
2189
2877
|
placeholder.exitCode = 1;
|
|
2190
2878
|
placeholder.timedOut = groupTimedOut ? true : undefined;
|
|
2879
|
+
placeholder.stopped = groupStopped ? true : undefined;
|
|
2191
2880
|
}
|
|
2192
|
-
markDynamicGraphGroup(stepIndex, "failed", errorMessage, effectiveGroupAcceptance);
|
|
2881
|
+
markDynamicGraphGroup(stepIndex, groupStopped ? "stopped" : "failed", errorMessage, effectiveGroupAcceptance);
|
|
2193
2882
|
statusPayload.lastUpdate = Date.now();
|
|
2194
2883
|
writeStatusPayload();
|
|
2195
|
-
results.push({ agent: step.parallel.agent, output: errorMessage, error: errorMessage, success: false, exitCode: 1, timedOut: groupTimedOut ? true : undefined, acceptance: effectiveGroupAcceptance });
|
|
2884
|
+
results.push({ agent: step.parallel.agent, context: step.parallel.context, output: errorMessage, error: errorMessage, success: false, exitCode: 1, timedOut: groupTimedOut ? true : undefined, stopped: groupStopped ? true : undefined, acceptance: effectiveGroupAcceptance });
|
|
2196
2885
|
break;
|
|
2197
2886
|
}
|
|
2198
2887
|
flatIndex++;
|
|
@@ -2206,9 +2895,26 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2206
2895
|
const thinkingOverride = step.thinkingOverrides?.[itemIndex];
|
|
2207
2896
|
const model = thinkingOverride ? applyThinkingSuffix(step.parallel.model, thinkingOverride, true) : step.parallel.model;
|
|
2208
2897
|
const thinking = thinkingOverride ? resolveEffectiveThinking(model, thinkingOverride) : undefined;
|
|
2898
|
+
const outputPath = step.parallel.namespaceOutputPath && step.parallel.outputPath
|
|
2899
|
+
? path.join(path.dirname(step.parallel.outputPath), `dynamic-${stepIndex}`, `${itemIndex}-${step.parallel.agent}`, path.basename(step.parallel.outputPath))
|
|
2900
|
+
: step.parallel.outputPath;
|
|
2901
|
+
const taskText = task.task ?? step.parallel.task;
|
|
2902
|
+
const materializedTask = step.parallel.namespaceOutputPath ? injectSingleOutputInstruction(taskText, outputPath, step.parallel) : taskText;
|
|
2209
2903
|
return {
|
|
2210
2904
|
...step.parallel,
|
|
2211
|
-
task:
|
|
2905
|
+
task: materializedTask,
|
|
2906
|
+
effectiveAcceptance: resolveEffectiveAcceptance({
|
|
2907
|
+
explicit: step.parallel.acceptanceInput,
|
|
2908
|
+
agentName: step.parallel.agent,
|
|
2909
|
+
acceptanceRole: step.parallel.acceptanceRole,
|
|
2910
|
+
task: materializedTask,
|
|
2911
|
+
mode: config.mode,
|
|
2912
|
+
async: true,
|
|
2913
|
+
dynamic: step.parallel.acceptanceInput === undefined,
|
|
2914
|
+
agentContract: step.parallel.agentContract ?? step.agentContract,
|
|
2915
|
+
}),
|
|
2916
|
+
systemPrompt: step.parallel.namespaceOutputPath ? injectOutputPathSystemPrompt(step.parallel.systemPrompt ?? "", outputPath, step.parallel) : step.parallel.systemPrompt,
|
|
2917
|
+
outputPath,
|
|
2212
2918
|
label: task.label ?? step.parallel.label,
|
|
2213
2919
|
...(step.sessionFiles?.[itemIndex] ? { sessionFile: step.sessionFiles[itemIndex] } : {}),
|
|
2214
2920
|
...(thinkingOverride ? {
|
|
@@ -2225,10 +2931,13 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2225
2931
|
const transcriptPath = resolveAsyncStepTranscriptPath({ artifactsDir, artifactConfig, runId: id, agent: task.agent, flatIndex: groupStartFlatIndex + itemIndex, flatStepCount: dynamicFlatStepCount });
|
|
2226
2932
|
return {
|
|
2227
2933
|
agent: task.agent,
|
|
2934
|
+
...(task.context ? { context: task.context } : {}),
|
|
2228
2935
|
phase: task.phase ?? step.phase,
|
|
2229
2936
|
label: task.label,
|
|
2230
2937
|
outputName: undefined,
|
|
2231
2938
|
structured: Boolean(task.structuredOutputSchema),
|
|
2939
|
+
...(task.agentContract ? { agentContract: task.agentContract } : {}),
|
|
2940
|
+
...(task.capabilityCeiling ? { capabilityCeiling: task.capabilityCeiling } : {}),
|
|
2232
2941
|
status: "pending",
|
|
2233
2942
|
...(task.sessionFile ? { sessionFile: task.sessionFile } : {}),
|
|
2234
2943
|
...(transcriptPath ? { transcriptPath } : {}),
|
|
@@ -2288,8 +2997,9 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2288
2997
|
let aborted = false;
|
|
2289
2998
|
const parallelResults = await mapConcurrent(dynamicSteps, concurrency, async (task, taskIdx) => {
|
|
2290
2999
|
const fi = groupStartFlatIndex + taskIdx;
|
|
2291
|
-
if (timedOut) return timedOutStepResult(task.agent);
|
|
2292
|
-
if (
|
|
3000
|
+
if (timedOut) return timedOutStepResult(task.agent, task.context);
|
|
3001
|
+
if (stopped) return stoppedStepResult(task.agent, task.context);
|
|
3002
|
+
if (interrupted) return pausedStepResult(task.agent, task.context);
|
|
2293
3003
|
if (aborted && failFast) {
|
|
2294
3004
|
const skippedAt = Date.now();
|
|
2295
3005
|
statusPayload.steps[fi].status = "failed";
|
|
@@ -2300,7 +3010,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2300
3010
|
statusPayload.steps[fi].exitCode = -1;
|
|
2301
3011
|
statusPayload.lastUpdate = skippedAt;
|
|
2302
3012
|
writeStatusPayload();
|
|
2303
|
-
return { agent: task.agent, output: "(skipped — fail-fast)", exitCode: -1 as number | null, skipped: true };
|
|
3013
|
+
return { agent: task.agent, context: task.context, output: "(skipped — fail-fast)", exitCode: -1 as number | null, skipped: true };
|
|
2304
3014
|
}
|
|
2305
3015
|
const taskStartTime = Date.now();
|
|
2306
3016
|
statusPayload.currentStep = fi;
|
|
@@ -2324,28 +3034,37 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2324
3034
|
flatIndex: fi, flatStepCount: Math.max(statusPayload.steps.length, 1),
|
|
2325
3035
|
outputFile: path.join(asyncDir, `output-${fi}.log`),
|
|
2326
3036
|
steerInboxDir: stepSteerInboxDir(asyncDir, fi),
|
|
3037
|
+
steerCapabilityPath: steerCapabilityPath(asyncDir, fi),
|
|
3038
|
+
steerAckDir: steerAcksDir(asyncDir, fi),
|
|
2327
3039
|
piPackageRoot: config.piPackageRoot,
|
|
2328
3040
|
piArgv1: config.piArgv1,
|
|
2329
3041
|
childIntercomTarget: config.childIntercomTargets?.[fi],
|
|
2330
3042
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
2331
3043
|
nestedRoute: config.nestedRoute,
|
|
3044
|
+
capabilityCeiling: config.capabilityCeiling,
|
|
2332
3045
|
registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
|
|
2333
3046
|
registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
|
|
3047
|
+
registerStop: (stop) => registerStepStop(fi, stop),
|
|
2334
3048
|
registerTurnBudgetAbort: (abort) => registerStepTurnBudgetAbort(fi, abort),
|
|
2335
3049
|
timeoutSignal: timeoutAbortController.signal,
|
|
3050
|
+
stopSignal: stopAbortController.signal,
|
|
2336
3051
|
timeoutMessage,
|
|
3052
|
+
stopMessage,
|
|
2337
3053
|
turnBudget: config.turnBudget,
|
|
2338
3054
|
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking),
|
|
2339
3055
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
2340
|
-
|
|
3056
|
+
onWriterProcess,
|
|
3057
|
+
skipAcceptance: () => timedOut || stopped,
|
|
2341
3058
|
});
|
|
2342
3059
|
const taskEndTime = Date.now();
|
|
2343
3060
|
const childInterrupted = singleResult.interrupted === true;
|
|
2344
|
-
|
|
3061
|
+
const childStopped = singleResult.stopped === true;
|
|
3062
|
+
statusPayload.steps[fi].status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
2345
3063
|
statusPayload.steps[fi].endedAt = taskEndTime;
|
|
2346
3064
|
statusPayload.steps[fi].durationMs = taskEndTime - taskStartTime;
|
|
2347
|
-
statusPayload.steps[fi].exitCode = timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
|
|
3065
|
+
statusPayload.steps[fi].exitCode = stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
|
|
2348
3066
|
statusPayload.steps[fi].timedOut = timedOut || singleResult.timedOut ? true : undefined;
|
|
3067
|
+
statusPayload.steps[fi].stopped = stopped || childStopped ? true : undefined;
|
|
2349
3068
|
statusPayload.steps[fi].turnBudget = singleResult.turnBudget;
|
|
2350
3069
|
statusPayload.steps[fi].turnBudgetExceeded = singleResult.turnBudgetExceeded;
|
|
2351
3070
|
statusPayload.steps[fi].wrapUpRequested = singleResult.wrapUpRequested;
|
|
@@ -2361,35 +3080,51 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2361
3080
|
statusPayload.steps[fi].attemptedModels = singleResult.attemptedModels;
|
|
2362
3081
|
statusPayload.steps[fi].modelAttempts = singleResult.modelAttempts;
|
|
2363
3082
|
statusPayload.steps[fi].totalCost = singleResult.totalCost;
|
|
2364
|
-
statusPayload.steps[fi].error = timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error;
|
|
3083
|
+
statusPayload.steps[fi].error = stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error;
|
|
2365
3084
|
statusPayload.steps[fi].transcriptPath = singleResult.transcriptPath ?? statusPayload.steps[fi].transcriptPath;
|
|
2366
3085
|
statusPayload.steps[fi].transcriptError = singleResult.transcriptError;
|
|
3086
|
+
statusPayload.steps[fi].agentContract = singleResult.agentContract;
|
|
3087
|
+
statusPayload.steps[fi].launchContractDigest = singleResult.launchContractDigest;
|
|
3088
|
+
statusPayload.steps[fi].effects = singleResult.effects;
|
|
3089
|
+
statusPayload.steps[fi].execution = singleResult.execution;
|
|
3090
|
+
statusPayload.steps[fi].review = singleResult.review;
|
|
2367
3091
|
statusPayload.steps[fi].structuredOutput = singleResult.structuredOutput;
|
|
2368
3092
|
statusPayload.steps[fi].structuredOutputPath = singleResult.structuredOutputPath;
|
|
2369
3093
|
statusPayload.steps[fi].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
2370
3094
|
statusPayload.steps[fi].acceptance = singleResult.acceptance;
|
|
3095
|
+
statusPayload.steps[fi].watchdog = singleResult.watchdog;
|
|
3096
|
+
statusPayload.steps[fi].capabilityCeiling = singleResult.capabilityCeiling;
|
|
3097
|
+
statusPayload.steps[fi].capabilityAudit = singleResult.capabilityAudit;
|
|
3098
|
+
if (singleResult.capabilityCeiling) statusPayload.capabilityCeiling = singleResult.capabilityCeiling;
|
|
3099
|
+
if (singleResult.capabilityAudit) statusPayload.capabilityAudit = singleResult.capabilityAudit;
|
|
2371
3100
|
statusPayload.lastUpdate = taskEndTime;
|
|
2372
3101
|
writeStatusPayload();
|
|
3102
|
+
appendCapabilityCeilingAppliedEvent(eventsPath, id, fi, task.agent, singleResult);
|
|
2373
3103
|
appendJsonl(eventsPath, JSON.stringify({
|
|
2374
|
-
type: timedOut ? "subagent.step.failed" : childInterrupted ? "subagent.step.paused" : singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
3104
|
+
type: stopped || childStopped ? "subagent.step.stopped" : timedOut ? "subagent.step.failed" : childInterrupted ? "subagent.step.paused" : singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
2375
3105
|
ts: taskEndTime, runId: id, stepIndex: fi, agent: task.agent,
|
|
2376
|
-
exitCode: timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode, durationMs: taskEndTime - taskStartTime,
|
|
3106
|
+
exitCode: stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode, durationMs: taskEndTime - taskStartTime,
|
|
2377
3107
|
}));
|
|
2378
3108
|
if (singleResult.exitCode !== 0 && failFast) aborted = true;
|
|
2379
|
-
return timedOut ? { ...singleResult, output: timeoutMessage ?? "Subagent timed out.", error: timeoutMessage ?? "Subagent timed out.", exitCode: 1, interrupted: false, timedOut: true, skipped: false } : { ...singleResult, skipped: false };
|
|
3109
|
+
return stopped || childStopped ? { ...singleResult, output: stopMessage, error: stopMessage, exitCode: 1, interrupted: false, timedOut: false, stopped: true, skipped: false } : timedOut ? { ...singleResult, output: timeoutMessage ?? "Subagent timed out.", error: timeoutMessage ?? "Subagent timed out.", exitCode: 1, interrupted: false, timedOut: true, skipped: false } : { ...singleResult, skipped: false };
|
|
2380
3110
|
}, globalSemaphore);
|
|
2381
3111
|
|
|
2382
3112
|
flatIndex += dynamicSteps.length;
|
|
2383
3113
|
for (const pr of parallelResults) {
|
|
2384
3114
|
results.push({
|
|
2385
3115
|
agent: pr.agent,
|
|
3116
|
+
context: pr.context,
|
|
3117
|
+
agentContract: pr.agentContract,
|
|
3118
|
+
launchContractDigest: pr.launchContractDigest,
|
|
2386
3119
|
output: pr.output,
|
|
2387
3120
|
error: pr.error,
|
|
2388
|
-
|
|
3121
|
+
protocolError: pr.protocolError,
|
|
3122
|
+
success: pr.stopped !== true && pr.interrupted !== true && pr.exitCode === 0,
|
|
2389
3123
|
exitCode: pr.interrupted === true ? 0 : pr.exitCode,
|
|
2390
3124
|
skipped: pr.skipped,
|
|
2391
3125
|
interrupted: pr.interrupted,
|
|
2392
3126
|
timedOut: pr.timedOut,
|
|
3127
|
+
stopped: pr.stopped,
|
|
2393
3128
|
turnBudget: pr.turnBudget,
|
|
2394
3129
|
turnBudgetExceeded: pr.turnBudgetExceeded,
|
|
2395
3130
|
wrapUpRequested: pr.wrapUpRequested,
|
|
@@ -2404,17 +3139,34 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2404
3139
|
artifactPaths: pr.artifactPaths,
|
|
2405
3140
|
transcriptPath: pr.transcriptPath,
|
|
2406
3141
|
transcriptError: pr.transcriptError,
|
|
3142
|
+
effects: pr.effects,
|
|
3143
|
+
execution: pr.execution,
|
|
3144
|
+
review: pr.review,
|
|
2407
3145
|
structuredOutput: pr.structuredOutput,
|
|
2408
3146
|
structuredOutputPath: pr.structuredOutputPath,
|
|
2409
3147
|
structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
|
|
2410
3148
|
acceptance: pr.acceptance,
|
|
3149
|
+
watchdog: pr.watchdog,
|
|
3150
|
+
capabilityCeiling: pr.capabilityCeiling,
|
|
3151
|
+
capabilityAudit: pr.capabilityAudit,
|
|
2411
3152
|
});
|
|
2412
3153
|
}
|
|
2413
3154
|
const collection = collectDynamicResults(step as Parameters<typeof collectDynamicResults>[0], materialized.items, parallelResults);
|
|
2414
3155
|
const failures = parallelResults.filter((result) => result.exitCode !== 0 && result.exitCode !== -1);
|
|
2415
|
-
|
|
3156
|
+
const acceptanceFailures = parallelResults
|
|
3157
|
+
.map((result, originalIndex) => ({ result, originalIndex, task: dynamicSteps[originalIndex] }))
|
|
3158
|
+
.filter(({ result, task }) => isAgentContractV1(task?.agentContract ?? step.agentContract) && task?.gateOn === "acceptance" && result.acceptance?.status === "rejected");
|
|
3159
|
+
if (acceptanceFailures.length > 0) {
|
|
3160
|
+
const message = acceptanceFailures
|
|
3161
|
+
.map(({ result, originalIndex }) => `Dynamic item ${originalIndex + 1} (${result.agent}, key ${materialized.items[originalIndex]?.key ?? originalIndex}) acceptance rejected: ${acceptanceFailureMessage(result.acceptance) ?? "acceptance rejected"}`)
|
|
3162
|
+
.join("\n");
|
|
3163
|
+
results.push({ agent: step.parallel.agent, context: step.parallel.context, output: message, error: message, success: false, exitCode: 1, structuredOutput: collection });
|
|
3164
|
+
statusPayload.error = message;
|
|
3165
|
+
markDynamicGraphGroup(stepIndex, "failed", message);
|
|
3166
|
+
}
|
|
3167
|
+
if (failures.length === 0 && acceptanceFailures.length === 0) {
|
|
2416
3168
|
try {
|
|
2417
|
-
validateDynamicCollection(step.collect.outputSchema, collection);
|
|
3169
|
+
await validateDynamicCollection(step.collect.outputSchema, collection);
|
|
2418
3170
|
outputs[step.collect.as] = {
|
|
2419
3171
|
text: JSON.stringify(collection),
|
|
2420
3172
|
structured: collection,
|
|
@@ -2422,24 +3174,26 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2422
3174
|
stepIndex,
|
|
2423
3175
|
};
|
|
2424
3176
|
statusPayload.outputs = outputs;
|
|
2425
|
-
const groupAcceptance =
|
|
3177
|
+
const groupAcceptance = !timedOut && !stopped
|
|
2426
3178
|
? await evaluateAcceptance({
|
|
2427
|
-
acceptance:
|
|
3179
|
+
acceptance: effectiveDynamicGroupAcceptance,
|
|
2428
3180
|
output: "",
|
|
2429
3181
|
report: aggregateAcceptanceReport({
|
|
2430
3182
|
results: parallelResults,
|
|
2431
3183
|
notes: `Dynamic fanout collected ${collection.length} result(s) into ${step.collect.as}.`,
|
|
2432
3184
|
}),
|
|
2433
3185
|
cwd,
|
|
2434
|
-
signal: timeoutAbortController.signal,
|
|
2435
|
-
abortMessage: timeoutMessage ?? "Subagent timed out.",
|
|
3186
|
+
signal: combinedAbortSignal([timeoutAbortController.signal, stopAbortController.signal]),
|
|
3187
|
+
abortMessage: stopAbortController.signal.aborted ? stopMessage : timeoutMessage ?? "Subagent timed out.",
|
|
3188
|
+
reportOptional: isAgentContractV1(step.agentContract),
|
|
2436
3189
|
})
|
|
2437
3190
|
: undefined;
|
|
2438
|
-
const
|
|
2439
|
-
const
|
|
2440
|
-
const
|
|
2441
|
-
const
|
|
2442
|
-
|
|
3191
|
+
const groupStopped = stopped || stopAbortController.signal.aborted;
|
|
3192
|
+
const groupTimedOut = !groupStopped && (timedOut || timeoutAbortController.signal.aborted);
|
|
3193
|
+
const effectiveGroupAcceptance = groupTimedOut || groupStopped ? undefined : groupAcceptance;
|
|
3194
|
+
const groupAcceptanceFailure = effectiveDynamicGroupAcceptance.explicit && effectiveGroupAcceptance && (!isAgentContractV1(step.agentContract) || step.gateOn === "acceptance") ? acceptanceFailureMessage(effectiveGroupAcceptance) : undefined;
|
|
3195
|
+
const groupError = groupStopped ? stopMessage : groupTimedOut ? timeoutMessage ?? "Subagent timed out." : groupAcceptanceFailure;
|
|
3196
|
+
markDynamicGraphGroup(stepIndex, groupError ? groupStopped ? "stopped" : "failed" : "completed", groupError, effectiveGroupAcceptance);
|
|
2443
3197
|
if (groupError) {
|
|
2444
3198
|
results.push({
|
|
2445
3199
|
agent: step.parallel.agent,
|
|
@@ -2448,14 +3202,16 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2448
3202
|
success: false,
|
|
2449
3203
|
exitCode: 1,
|
|
2450
3204
|
timedOut: groupTimedOut ? true : undefined,
|
|
3205
|
+
stopped: groupStopped ? true : undefined,
|
|
2451
3206
|
structuredOutput: collection,
|
|
2452
3207
|
acceptance: effectiveGroupAcceptance,
|
|
2453
3208
|
});
|
|
2454
3209
|
statusPayload.error = groupError;
|
|
3210
|
+
statusPayload.stopped = groupStopped ? true : statusPayload.stopped;
|
|
2455
3211
|
}
|
|
2456
3212
|
} catch (error) {
|
|
2457
3213
|
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
2458
|
-
results.push({ agent: step.parallel.agent, output: message, error: message, success: false, exitCode: 1, structuredOutput: collection });
|
|
3214
|
+
results.push({ agent: step.parallel.agent, context: step.parallel.context, output: message, error: message, success: false, exitCode: 1, structuredOutput: collection });
|
|
2459
3215
|
statusPayload.error = message;
|
|
2460
3216
|
markDynamicGraphGroup(stepIndex, "failed", message);
|
|
2461
3217
|
}
|
|
@@ -2475,7 +3231,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2475
3231
|
ts: Date.now(),
|
|
2476
3232
|
runId: id,
|
|
2477
3233
|
stepIndex,
|
|
2478
|
-
success: failures.length === 0,
|
|
3234
|
+
success: failures.length === 0 && acceptanceFailures.length === 0,
|
|
2479
3235
|
}));
|
|
2480
3236
|
if (failures.length > 0) markDynamicGraphGroup(stepIndex, "failed", failures[0]?.error ?? "Dynamic fanout child failed.");
|
|
2481
3237
|
statusPayload.lastUpdate = Date.now();
|
|
@@ -2491,6 +3247,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2491
3247
|
const groupStartFlatIndex = flatIndex;
|
|
2492
3248
|
let aborted = false;
|
|
2493
3249
|
let worktreeSetup: WorktreeSetup | undefined;
|
|
3250
|
+
let worktreeFinalized = false;
|
|
2494
3251
|
if (group.worktree) {
|
|
2495
3252
|
const worktreeTaskCwdConflict = findWorktreeTaskCwdConflict(group.parallel, cwd);
|
|
2496
3253
|
if (worktreeTaskCwdConflict) {
|
|
@@ -2559,8 +3316,9 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2559
3316
|
concurrency,
|
|
2560
3317
|
async (task, taskIdx) => {
|
|
2561
3318
|
const fi = groupStartFlatIndex + taskIdx;
|
|
2562
|
-
if (timedOut) return timedOutStepResult(task.agent);
|
|
2563
|
-
if (
|
|
3319
|
+
if (timedOut) return timedOutStepResult(task.agent, task.context);
|
|
3320
|
+
if (stopped) return stoppedStepResult(task.agent, task.context);
|
|
3321
|
+
if (interrupted) return pausedStepResult(task.agent, task.context);
|
|
2564
3322
|
if (aborted && failFast) {
|
|
2565
3323
|
const skippedAt = Date.now();
|
|
2566
3324
|
statusPayload.steps[fi].status = "failed";
|
|
@@ -2575,7 +3333,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2575
3333
|
appendJsonl(eventsPath, JSON.stringify({
|
|
2576
3334
|
type: "subagent.step.failed", ts: skippedAt, runId: id, stepIndex: fi, agent: task.agent, exitCode: -1, durationMs: 0,
|
|
2577
3335
|
}));
|
|
2578
|
-
return { agent: task.agent, output: "(skipped — fail-fast)", exitCode: -1 as number | null, skipped: true };
|
|
3336
|
+
return { agent: task.agent, context: task.context, output: "(skipped — fail-fast)", exitCode: -1 as number | null, skipped: true };
|
|
2579
3337
|
}
|
|
2580
3338
|
|
|
2581
3339
|
const taskStartTime = Date.now();
|
|
@@ -2611,20 +3369,27 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2611
3369
|
flatIndex: fi, flatStepCount: Math.max(statusPayload.steps.length, 1),
|
|
2612
3370
|
outputFile: path.join(asyncDir, `output-${fi}.log`),
|
|
2613
3371
|
steerInboxDir: stepSteerInboxDir(asyncDir, fi),
|
|
3372
|
+
steerCapabilityPath: steerCapabilityPath(asyncDir, fi),
|
|
3373
|
+
steerAckDir: steerAcksDir(asyncDir, fi),
|
|
2614
3374
|
piPackageRoot: config.piPackageRoot,
|
|
2615
3375
|
piArgv1: config.piArgv1,
|
|
2616
3376
|
childIntercomTarget: config.childIntercomTargets?.[fi],
|
|
2617
3377
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
2618
3378
|
nestedRoute: config.nestedRoute,
|
|
3379
|
+
capabilityCeiling: config.capabilityCeiling,
|
|
2619
3380
|
registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
|
|
2620
3381
|
registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
|
|
3382
|
+
registerStop: (stop) => registerStepStop(fi, stop),
|
|
2621
3383
|
registerTurnBudgetAbort: (abort) => registerStepTurnBudgetAbort(fi, abort),
|
|
2622
3384
|
timeoutSignal: timeoutAbortController.signal,
|
|
3385
|
+
stopSignal: stopAbortController.signal,
|
|
2623
3386
|
timeoutMessage,
|
|
3387
|
+
stopMessage,
|
|
2624
3388
|
turnBudget: config.turnBudget,
|
|
2625
3389
|
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking),
|
|
2626
3390
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
2627
|
-
|
|
3391
|
+
onWriterProcess,
|
|
3392
|
+
skipAcceptance: () => timedOut || stopped,
|
|
2628
3393
|
});
|
|
2629
3394
|
if (task.sessionFile) {
|
|
2630
3395
|
latestSessionFile = task.sessionFile;
|
|
@@ -2633,12 +3398,14 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2633
3398
|
const taskEndTime = Date.now();
|
|
2634
3399
|
const taskDuration = taskEndTime - taskStartTime;
|
|
2635
3400
|
const childInterrupted = singleResult.interrupted === true;
|
|
3401
|
+
const childStopped = singleResult.stopped === true;
|
|
2636
3402
|
|
|
2637
|
-
statusPayload.steps[fi].status = timedOut ? "failed" : childInterrupted ? "paused" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
3403
|
+
statusPayload.steps[fi].status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
2638
3404
|
statusPayload.steps[fi].endedAt = taskEndTime;
|
|
2639
3405
|
statusPayload.steps[fi].durationMs = taskDuration;
|
|
2640
|
-
statusPayload.steps[fi].exitCode = timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
|
|
3406
|
+
statusPayload.steps[fi].exitCode = stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
|
|
2641
3407
|
statusPayload.steps[fi].timedOut = timedOut || singleResult.timedOut ? true : undefined;
|
|
3408
|
+
statusPayload.steps[fi].stopped = stopped || childStopped ? true : undefined;
|
|
2642
3409
|
statusPayload.steps[fi].turnBudget = singleResult.turnBudget;
|
|
2643
3410
|
statusPayload.steps[fi].turnBudgetExceeded = singleResult.turnBudgetExceeded;
|
|
2644
3411
|
statusPayload.steps[fi].wrapUpRequested = singleResult.wrapUpRequested;
|
|
@@ -2654,20 +3421,30 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2654
3421
|
statusPayload.steps[fi].attemptedModels = singleResult.attemptedModels;
|
|
2655
3422
|
statusPayload.steps[fi].modelAttempts = singleResult.modelAttempts;
|
|
2656
3423
|
statusPayload.steps[fi].totalCost = singleResult.totalCost;
|
|
2657
|
-
statusPayload.steps[fi].error = timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error;
|
|
3424
|
+
statusPayload.steps[fi].error = stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error;
|
|
2658
3425
|
statusPayload.steps[fi].transcriptPath = singleResult.transcriptPath ?? statusPayload.steps[fi].transcriptPath;
|
|
2659
3426
|
statusPayload.steps[fi].transcriptError = singleResult.transcriptError;
|
|
3427
|
+
statusPayload.steps[fi].agentContract = singleResult.agentContract;
|
|
3428
|
+
statusPayload.steps[fi].effects = singleResult.effects;
|
|
3429
|
+
statusPayload.steps[fi].execution = singleResult.execution;
|
|
3430
|
+
statusPayload.steps[fi].review = singleResult.review;
|
|
2660
3431
|
statusPayload.steps[fi].structuredOutput = singleResult.structuredOutput;
|
|
2661
3432
|
statusPayload.steps[fi].structuredOutputPath = singleResult.structuredOutputPath;
|
|
2662
3433
|
statusPayload.steps[fi].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
2663
3434
|
statusPayload.steps[fi].acceptance = singleResult.acceptance;
|
|
3435
|
+
statusPayload.steps[fi].watchdog = singleResult.watchdog;
|
|
3436
|
+
statusPayload.steps[fi].capabilityCeiling = singleResult.capabilityCeiling;
|
|
3437
|
+
statusPayload.steps[fi].capabilityAudit = singleResult.capabilityAudit;
|
|
3438
|
+
if (singleResult.capabilityCeiling) statusPayload.capabilityCeiling = singleResult.capabilityCeiling;
|
|
3439
|
+
if (singleResult.capabilityAudit) statusPayload.capabilityAudit = singleResult.capabilityAudit;
|
|
2664
3440
|
statusPayload.lastUpdate = taskEndTime;
|
|
2665
3441
|
writeStatusPayload();
|
|
3442
|
+
appendCapabilityCeilingAppliedEvent(eventsPath, id, fi, task.agent, singleResult);
|
|
2666
3443
|
|
|
2667
3444
|
appendJsonl(eventsPath, JSON.stringify({
|
|
2668
|
-
type: timedOut ? "subagent.step.failed" : childInterrupted ? "subagent.step.paused" : singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
3445
|
+
type: stopped || childStopped ? "subagent.step.stopped" : timedOut ? "subagent.step.failed" : childInterrupted ? "subagent.step.paused" : singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
2669
3446
|
ts: taskEndTime, runId: id, stepIndex: fi, agent: task.agent,
|
|
2670
|
-
exitCode: timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode, durationMs: taskDuration,
|
|
3447
|
+
exitCode: stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode, durationMs: taskDuration,
|
|
2671
3448
|
}));
|
|
2672
3449
|
if (singleResult.completionGuardTriggered) {
|
|
2673
3450
|
const event = buildControlEvent({
|
|
@@ -2684,7 +3461,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2684
3461
|
}
|
|
2685
3462
|
|
|
2686
3463
|
if (singleResult.exitCode !== 0 && failFast) aborted = true;
|
|
2687
|
-
return timedOut ? { ...singleResult, output: timeoutMessage ?? "Subagent timed out.", error: timeoutMessage ?? "Subagent timed out.", exitCode: 1, interrupted: false, timedOut: true, skipped: false } : { ...singleResult, skipped: false };
|
|
3464
|
+
return stopped || childStopped ? { ...singleResult, output: stopMessage, error: stopMessage, exitCode: 1, interrupted: false, timedOut: false, stopped: true, skipped: false } : timedOut ? { ...singleResult, output: timeoutMessage ?? "Subagent timed out.", error: timeoutMessage ?? "Subagent timed out.", exitCode: 1, interrupted: false, timedOut: true, skipped: false } : { ...singleResult, skipped: false };
|
|
2688
3465
|
},
|
|
2689
3466
|
globalSemaphore,
|
|
2690
3467
|
);
|
|
@@ -2712,13 +3489,18 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2712
3489
|
for (const pr of parallelResults) {
|
|
2713
3490
|
results.push({
|
|
2714
3491
|
agent: pr.agent,
|
|
3492
|
+
context: pr.context,
|
|
3493
|
+
agentContract: pr.agentContract,
|
|
3494
|
+
launchContractDigest: pr.launchContractDigest,
|
|
2715
3495
|
output: pr.output,
|
|
2716
3496
|
error: pr.error,
|
|
2717
|
-
|
|
3497
|
+
protocolError: pr.protocolError,
|
|
3498
|
+
success: pr.stopped !== true && pr.interrupted !== true && pr.exitCode === 0,
|
|
2718
3499
|
exitCode: pr.interrupted === true ? 0 : pr.exitCode,
|
|
2719
3500
|
skipped: pr.skipped,
|
|
2720
3501
|
interrupted: pr.interrupted,
|
|
2721
3502
|
timedOut: pr.timedOut,
|
|
3503
|
+
stopped: pr.stopped,
|
|
2722
3504
|
turnBudget: pr.turnBudget,
|
|
2723
3505
|
turnBudgetExceeded: pr.turnBudgetExceeded,
|
|
2724
3506
|
wrapUpRequested: pr.wrapUpRequested,
|
|
@@ -2733,12 +3515,16 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2733
3515
|
artifactPaths: pr.artifactPaths,
|
|
2734
3516
|
transcriptPath: pr.transcriptPath,
|
|
2735
3517
|
transcriptError: pr.transcriptError,
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
3518
|
+
effects: pr.effects,
|
|
3519
|
+
execution: pr.execution,
|
|
3520
|
+
review: pr.review,
|
|
3521
|
+
structuredOutput: pr.structuredOutput,
|
|
3522
|
+
structuredOutputPath: pr.structuredOutputPath,
|
|
3523
|
+
structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
|
|
3524
|
+
acceptance: pr.acceptance,
|
|
3525
|
+
watchdog: pr.watchdog,
|
|
3526
|
+
});
|
|
3527
|
+
}
|
|
2742
3528
|
for (let t = 0; t < group.parallel.length; t++) {
|
|
2743
3529
|
const outputName = group.parallel[t]?.outputName;
|
|
2744
3530
|
if (outputName) outputs[outputName] = outputEntryFromAsyncResult({
|
|
@@ -2751,29 +3537,70 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2751
3537
|
|
|
2752
3538
|
previousOutput = aggregateParallelOutputs(
|
|
2753
3539
|
parallelResults.map((r) => ({
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
3540
|
+
agent: r.agent,
|
|
3541
|
+
output: r.output,
|
|
3542
|
+
exitCode: r.exitCode,
|
|
3543
|
+
error: r.error,
|
|
3544
|
+
model: r.model,
|
|
3545
|
+
attemptedModels: r.attemptedModels,
|
|
3546
|
+
})),
|
|
2761
3547
|
);
|
|
2762
|
-
|
|
3548
|
+
if (worktreeSetup) {
|
|
3549
|
+
const captured = captureParallelWorktreeDiffs(worktreeSetup, asyncDir, stepIndex, group);
|
|
3550
|
+
if (captured.summary) previousOutput = `${previousOutput}\n\n${captured.summary}`;
|
|
3551
|
+
worktreeFinalized = true;
|
|
3552
|
+
const cleanup = cleanupWorktrees(worktreeSetup);
|
|
3553
|
+
try {
|
|
3554
|
+
statusPayload.parallelHandoff = writeParallelHandoffGroup({
|
|
3555
|
+
manifestPath: parallelHandoffPath(asyncDir),
|
|
3556
|
+
runId: id,
|
|
3557
|
+
mode: (config.resultMode ?? statusPayload.mode) === "parallel" ? "parallel" : "chain",
|
|
3558
|
+
source: "async",
|
|
3559
|
+
cwd,
|
|
3560
|
+
stepIndex,
|
|
3561
|
+
flatStartIndex: groupStartFlatIndex,
|
|
3562
|
+
setup: worktreeSetup,
|
|
3563
|
+
diffs: captured.diffs,
|
|
3564
|
+
cleanup,
|
|
3565
|
+
results: parallelResults.map((result) => ({
|
|
3566
|
+
agent: result.agent,
|
|
3567
|
+
status: result.stopped ? "stopped" : result.interrupted ? "paused" : result.exitCode === 0 ? "completed" : "failed",
|
|
3568
|
+
summary: result.output || result.error || "(no output)",
|
|
3569
|
+
...(result.artifactPaths?.outputPath ? { outputPath: result.artifactPaths.outputPath } : {}),
|
|
3570
|
+
...(result.structuredOutput !== undefined ? { structuredOutput: result.structuredOutput } : {}),
|
|
3571
|
+
...(result.structuredOutputPath ? { structuredOutputPath: result.structuredOutputPath } : {}),
|
|
3572
|
+
...(result.sessionFile ? { sessionPath: result.sessionFile } : {}),
|
|
3573
|
+
})),
|
|
3574
|
+
});
|
|
3575
|
+
previousOutput = `${previousOutput}\n\n${formatParallelHandoffReference(statusPayload.parallelHandoff)}`;
|
|
3576
|
+
} catch (error) {
|
|
3577
|
+
previousOutput = `${previousOutput}\n\n${formatParallelHandoffError(error)}`;
|
|
3578
|
+
}
|
|
3579
|
+
writeStatusPayload();
|
|
3580
|
+
}
|
|
2763
3581
|
|
|
2764
3582
|
appendJsonl(eventsPath, JSON.stringify({
|
|
2765
3583
|
type: "subagent.parallel.completed",
|
|
2766
3584
|
ts: Date.now(),
|
|
2767
3585
|
runId: id,
|
|
2768
3586
|
stepIndex,
|
|
2769
|
-
success: parallelResults.every((r) => r.exitCode === 0 || r.exitCode === -1)
|
|
3587
|
+
success: parallelResults.every((r) => r.exitCode === 0 || r.exitCode === -1)
|
|
3588
|
+
&& parallelResults.every((result, index) => !(isAgentContractV1(group.parallel[index]?.agentContract) && group.parallel[index]?.gateOn === "acceptance" && result.acceptance?.status === "rejected")),
|
|
2770
3589
|
}));
|
|
2771
3590
|
|
|
3591
|
+
const acceptanceGateFailure = parallelResults
|
|
3592
|
+
.map((result, index) => ({ result, index, task: group.parallel[index] }))
|
|
3593
|
+
.find(({ result, task }) => isAgentContractV1(task?.agentContract) && task?.gateOn === "acceptance" && result.acceptance?.status === "rejected");
|
|
3594
|
+
if (acceptanceGateFailure) {
|
|
3595
|
+
statusPayload.error = acceptanceFailureMessage(acceptanceGateFailure.result.acceptance) ?? "Parallel acceptance gate rejected the step.";
|
|
3596
|
+
writeStatusPayload();
|
|
3597
|
+
break;
|
|
3598
|
+
}
|
|
2772
3599
|
if (parallelResults.some((r) => r.exitCode !== 0 && r.exitCode !== -1)) {
|
|
2773
3600
|
break;
|
|
2774
3601
|
}
|
|
2775
3602
|
} finally {
|
|
2776
|
-
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
3603
|
+
if (worktreeSetup && !worktreeFinalized) cleanupWorktrees(worktreeSetup);
|
|
2777
3604
|
}
|
|
2778
3605
|
} else {
|
|
2779
3606
|
const seqStep = step as SubagentStep;
|
|
@@ -2802,38 +3629,50 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2802
3629
|
flushPendingStepSteers(flatIndex);
|
|
2803
3630
|
const singleResult = await runSingleStep(seqStep, {
|
|
2804
3631
|
previousOutput, placeholder, cwd, sessionEnabled,
|
|
2805
|
-
outputs,
|
|
3632
|
+
outputs: statusPayload.mode === "single" ? undefined : outputs,
|
|
2806
3633
|
sessionDir: config.sessionDir,
|
|
2807
3634
|
artifactsDir, artifactConfig, id,
|
|
2808
3635
|
flatIndex, flatStepCount: Math.max(statusPayload.steps.length, 1),
|
|
2809
3636
|
outputFile: path.join(asyncDir, `output-${flatIndex}.log`),
|
|
2810
3637
|
steerInboxDir: stepSteerInboxDir(asyncDir, flatIndex),
|
|
3638
|
+
steerCapabilityPath: steerCapabilityPath(asyncDir, flatIndex),
|
|
3639
|
+
steerAckDir: steerAcksDir(asyncDir, flatIndex),
|
|
2811
3640
|
piPackageRoot: config.piPackageRoot,
|
|
2812
3641
|
piArgv1: config.piArgv1,
|
|
2813
3642
|
childIntercomTarget: config.childIntercomTargets?.[flatIndex],
|
|
2814
3643
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
2815
3644
|
nestedRoute: config.nestedRoute,
|
|
3645
|
+
capabilityCeiling: config.capabilityCeiling,
|
|
2816
3646
|
registerInterrupt: (interrupt) => registerStepInterrupt(flatIndex, interrupt),
|
|
2817
3647
|
registerTimeout: (interrupt) => registerStepTimeout(flatIndex, interrupt),
|
|
3648
|
+
registerStop: (stop) => registerStepStop(flatIndex, stop),
|
|
2818
3649
|
registerTurnBudgetAbort: (abort) => registerStepTurnBudgetAbort(flatIndex, abort),
|
|
2819
3650
|
timeoutSignal: timeoutAbortController.signal,
|
|
3651
|
+
stopSignal: stopAbortController.signal,
|
|
2820
3652
|
timeoutMessage,
|
|
3653
|
+
stopMessage,
|
|
2821
3654
|
turnBudget: config.turnBudget,
|
|
2822
3655
|
onAttemptStart: (attempt) => updateStepModel(flatIndex, attempt.model, attempt.thinking),
|
|
2823
3656
|
onChildEvent: (event) => updateStepFromChildEvent(flatIndex, event),
|
|
2824
|
-
|
|
3657
|
+
onWriterProcess,
|
|
3658
|
+
skipAcceptance: () => timedOut || stopped,
|
|
2825
3659
|
});
|
|
2826
3660
|
if (seqStep.sessionFile) {
|
|
2827
3661
|
latestSessionFile = seqStep.sessionFile;
|
|
2828
3662
|
}
|
|
2829
3663
|
|
|
2830
3664
|
previousOutput = singleResult.output;
|
|
3665
|
+
const childStopped = singleResult.stopped === true;
|
|
2831
3666
|
results.push({
|
|
2832
3667
|
agent: singleResult.agent,
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
3668
|
+
context: singleResult.context,
|
|
3669
|
+
agentContract: singleResult.agentContract,
|
|
3670
|
+
launchContractDigest: singleResult.launchContractDigest,
|
|
3671
|
+
output: stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.output,
|
|
3672
|
+
error: stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error,
|
|
3673
|
+
protocolError: singleResult.protocolError,
|
|
3674
|
+
success: !stopped && !childStopped && !timedOut && singleResult.interrupted !== true && singleResult.exitCode === 0,
|
|
3675
|
+
exitCode: stopped || childStopped ? 1 : timedOut ? 1 : singleResult.interrupted === true ? 0 : singleResult.exitCode,
|
|
2837
3676
|
sessionFile: singleResult.sessionFile,
|
|
2838
3677
|
intercomTarget: singleResult.intercomTarget,
|
|
2839
3678
|
model: singleResult.model,
|
|
@@ -2843,12 +3682,19 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2843
3682
|
artifactPaths: singleResult.artifactPaths,
|
|
2844
3683
|
transcriptPath: singleResult.transcriptPath,
|
|
2845
3684
|
transcriptError: singleResult.transcriptError,
|
|
3685
|
+
effects: singleResult.effects,
|
|
3686
|
+
execution: singleResult.execution,
|
|
3687
|
+
review: singleResult.review,
|
|
2846
3688
|
structuredOutput: singleResult.structuredOutput,
|
|
2847
3689
|
structuredOutputPath: singleResult.structuredOutputPath,
|
|
2848
3690
|
structuredOutputSchemaPath: singleResult.structuredOutputSchemaPath,
|
|
2849
3691
|
acceptance: singleResult.acceptance,
|
|
3692
|
+
watchdog: singleResult.watchdog,
|
|
3693
|
+
capabilityCeiling: singleResult.capabilityCeiling,
|
|
3694
|
+
capabilityAudit: singleResult.capabilityAudit,
|
|
2850
3695
|
interrupted: singleResult.interrupted,
|
|
2851
3696
|
timedOut: timedOut || singleResult.timedOut ? true : undefined,
|
|
3697
|
+
stopped: stopped || childStopped ? true : undefined,
|
|
2852
3698
|
turnBudget: singleResult.turnBudget,
|
|
2853
3699
|
turnBudgetExceeded: singleResult.turnBudgetExceeded,
|
|
2854
3700
|
wrapUpRequested: singleResult.wrapUpRequested,
|
|
@@ -2887,11 +3733,12 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2887
3733
|
|
|
2888
3734
|
const stepEndTime = Date.now();
|
|
2889
3735
|
const childInterrupted = singleResult.interrupted === true;
|
|
2890
|
-
statusPayload.steps[flatIndex].status = timedOut ? "failed" : childInterrupted ? "paused" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
3736
|
+
statusPayload.steps[flatIndex].status = stopped || childStopped ? "stopped" : timedOut ? "failed" : childInterrupted ? "paused" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
2891
3737
|
statusPayload.steps[flatIndex].endedAt = stepEndTime;
|
|
2892
3738
|
statusPayload.steps[flatIndex].durationMs = stepEndTime - stepStartTime;
|
|
2893
|
-
statusPayload.steps[flatIndex].exitCode = timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
|
|
3739
|
+
statusPayload.steps[flatIndex].exitCode = stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
|
|
2894
3740
|
statusPayload.steps[flatIndex].timedOut = timedOut || singleResult.timedOut ? true : undefined;
|
|
3741
|
+
statusPayload.steps[flatIndex].stopped = stopped || childStopped ? true : undefined;
|
|
2895
3742
|
statusPayload.steps[flatIndex].turnBudget = singleResult.turnBudget;
|
|
2896
3743
|
statusPayload.steps[flatIndex].turnBudgetExceeded = singleResult.turnBudgetExceeded;
|
|
2897
3744
|
statusPayload.steps[flatIndex].wrapUpRequested = singleResult.wrapUpRequested;
|
|
@@ -2907,27 +3754,37 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2907
3754
|
statusPayload.steps[flatIndex].attemptedModels = singleResult.attemptedModels;
|
|
2908
3755
|
statusPayload.steps[flatIndex].modelAttempts = singleResult.modelAttempts;
|
|
2909
3756
|
statusPayload.steps[flatIndex].totalCost = singleResult.totalCost;
|
|
2910
|
-
statusPayload.steps[flatIndex].error = timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error;
|
|
3757
|
+
statusPayload.steps[flatIndex].error = stopped || childStopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error;
|
|
2911
3758
|
statusPayload.steps[flatIndex].transcriptPath = singleResult.transcriptPath ?? statusPayload.steps[flatIndex].transcriptPath;
|
|
2912
3759
|
statusPayload.steps[flatIndex].transcriptError = singleResult.transcriptError;
|
|
3760
|
+
statusPayload.steps[flatIndex].agentContract = singleResult.agentContract;
|
|
3761
|
+
statusPayload.steps[flatIndex].effects = singleResult.effects;
|
|
3762
|
+
statusPayload.steps[flatIndex].execution = singleResult.execution;
|
|
3763
|
+
statusPayload.steps[flatIndex].review = singleResult.review;
|
|
2913
3764
|
statusPayload.steps[flatIndex].structuredOutput = singleResult.structuredOutput;
|
|
2914
3765
|
statusPayload.steps[flatIndex].structuredOutputPath = singleResult.structuredOutputPath;
|
|
2915
3766
|
statusPayload.steps[flatIndex].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
2916
3767
|
statusPayload.steps[flatIndex].acceptance = singleResult.acceptance;
|
|
3768
|
+
statusPayload.steps[flatIndex].watchdog = singleResult.watchdog;
|
|
3769
|
+
statusPayload.steps[flatIndex].capabilityCeiling = singleResult.capabilityCeiling;
|
|
3770
|
+
statusPayload.steps[flatIndex].capabilityAudit = singleResult.capabilityAudit;
|
|
3771
|
+
if (singleResult.capabilityCeiling) statusPayload.capabilityCeiling = singleResult.capabilityCeiling;
|
|
3772
|
+
if (singleResult.capabilityAudit) statusPayload.capabilityAudit = singleResult.capabilityAudit;
|
|
2917
3773
|
if (stepTokens) {
|
|
2918
3774
|
statusPayload.steps[flatIndex].tokens = stepTokens;
|
|
2919
3775
|
statusPayload.totalTokens = { ...previousCumulativeTokens };
|
|
2920
3776
|
}
|
|
2921
3777
|
statusPayload.lastUpdate = stepEndTime;
|
|
2922
3778
|
writeStatusPayload();
|
|
3779
|
+
appendCapabilityCeilingAppliedEvent(eventsPath, id, flatIndex, seqStep.agent, singleResult);
|
|
2923
3780
|
|
|
2924
3781
|
appendJsonl(eventsPath, JSON.stringify({
|
|
2925
|
-
type: timedOut ? "subagent.step.failed" : childInterrupted ? "subagent.step.paused" : singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
3782
|
+
type: stopped || childStopped ? "subagent.step.stopped" : timedOut ? "subagent.step.failed" : childInterrupted ? "subagent.step.paused" : singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
2926
3783
|
ts: stepEndTime,
|
|
2927
3784
|
runId: id,
|
|
2928
3785
|
stepIndex: flatIndex,
|
|
2929
3786
|
agent: seqStep.agent,
|
|
2930
|
-
exitCode: timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode,
|
|
3787
|
+
exitCode: stopped || childStopped ? 1 : timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode,
|
|
2931
3788
|
durationMs: stepEndTime - stepStartTime,
|
|
2932
3789
|
tokens: stepTokens,
|
|
2933
3790
|
}));
|
|
@@ -2946,6 +3803,11 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2946
3803
|
}
|
|
2947
3804
|
|
|
2948
3805
|
flatIndex++;
|
|
3806
|
+
if (isAgentContractV1(seqStep.agentContract) && seqStep.gateOn === "acceptance" && singleResult.acceptance?.status === "rejected") {
|
|
3807
|
+
statusPayload.error = acceptanceFailureMessage(singleResult.acceptance) ?? "Chain acceptance gate rejected the step.";
|
|
3808
|
+
writeStatusPayload();
|
|
3809
|
+
break;
|
|
3810
|
+
}
|
|
2949
3811
|
if (singleResult.exitCode !== 0) {
|
|
2950
3812
|
break;
|
|
2951
3813
|
}
|
|
@@ -2972,16 +3834,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2972
3834
|
costUsd: sum.costUsd + (result.totalCost?.costUsd ?? 0),
|
|
2973
3835
|
}), { inputTokens: 0, outputTokens: 0, costUsd: 0 });
|
|
2974
3836
|
const finalTotalCost = totalCost.inputTokens > 0 || totalCost.outputTokens > 0 || totalCost.costUsd > 0 ? totalCost : undefined;
|
|
2975
|
-
const totalChildUsage = results.reduce<Usage>((sum, result) => {
|
|
2976
|
-
const usage = usageFromAttempts(result.modelAttempts);
|
|
2977
|
-
sum.input += usage.input;
|
|
2978
|
-
sum.output += usage.output;
|
|
2979
|
-
sum.cacheRead += usage.cacheRead;
|
|
2980
|
-
sum.cacheWrite += usage.cacheWrite;
|
|
2981
|
-
sum.cost += usage.cost;
|
|
2982
|
-
sum.turns += usage.turns;
|
|
2983
|
-
return sum;
|
|
2984
|
-
}, emptyUsage());
|
|
2985
3837
|
const finalFlatAgents = statusPayload.steps.map((step) => step.agent);
|
|
2986
3838
|
const agentName = finalFlatAgents.length === 1
|
|
2987
3839
|
? finalFlatAgents[0]!
|
|
@@ -3026,11 +3878,29 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
3026
3878
|
clearTimeout(timeoutTimer);
|
|
3027
3879
|
timeoutTimer = undefined;
|
|
3028
3880
|
}
|
|
3881
|
+
statusPayload.state = stopped ? "stopped" : timedOut || turnBudgetExceeded || statusPayload.error ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed";
|
|
3882
|
+
closeSteerInbox(asyncDir, statusPayload.state);
|
|
3029
3883
|
disposeControlInbox();
|
|
3884
|
+
for (const request of consumeSteerRequests(asyncDir)) deliverSteerRequest(request);
|
|
3030
3885
|
const effectiveSessionFile = sessionFile ?? latestSessionFile;
|
|
3886
|
+
const steeringLifecycle = steeringStatus(statusPayload);
|
|
3887
|
+
for (const request of steeringLifecycle.recent) {
|
|
3888
|
+
let changed = false;
|
|
3889
|
+
for (const target of request.targets) {
|
|
3890
|
+
if (target.state !== "scheduled" && target.state !== "routed") continue;
|
|
3891
|
+
changed = true;
|
|
3892
|
+
updateSteeringLifecycleTarget(request.id, target.index, "failed", Date.now(), { reason: "child terminated before steering delivery" });
|
|
3893
|
+
markSteeringAttention(target.index);
|
|
3894
|
+
emitSteeringEvent("subagent.steer.failed", { type: "steer", id: request.id, ts: request.requestedAt, message: "child terminated before steering delivery" }, target.index, { reason: "child terminated before steering delivery" });
|
|
3895
|
+
}
|
|
3896
|
+
if (changed) emitTerminalSteeringNotice(request.id, `Steering failed for run ${id}: child terminated before delivery.`);
|
|
3897
|
+
}
|
|
3031
3898
|
const runEndedAt = Date.now();
|
|
3032
|
-
statusPayload.state = timedOut || turnBudgetExceeded ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed";
|
|
3033
3899
|
statusPayload.activityState = undefined;
|
|
3900
|
+
if (stopped) {
|
|
3901
|
+
statusPayload.stopped = true;
|
|
3902
|
+
statusPayload.error = stopMessage;
|
|
3903
|
+
}
|
|
3034
3904
|
if (timedOut) {
|
|
3035
3905
|
statusPayload.timedOut = true;
|
|
3036
3906
|
statusPayload.error = timeoutMessage ?? "Subagent timed out.";
|
|
@@ -3091,9 +3961,9 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
3091
3961
|
id,
|
|
3092
3962
|
agent: agentName,
|
|
3093
3963
|
mode: resultMode,
|
|
3094
|
-
success: !timedOut && !turnBudgetExceeded && !interrupted && results.every((r) => r.success),
|
|
3095
|
-
state: timedOut || turnBudgetExceeded ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed",
|
|
3096
|
-
summary: timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? (statusPayload.error ?? "Subagent exceeded turn budget.") : interrupted ? "Paused after interrupt. Waiting for explicit next action." : summary,
|
|
3964
|
+
success: !stopped && !timedOut && !turnBudgetExceeded && !interrupted && results.every((r) => r.success),
|
|
3965
|
+
state: stopped ? "stopped" : timedOut || turnBudgetExceeded ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed",
|
|
3966
|
+
summary: stopped ? stopMessage : timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? (statusPayload.error ?? "Subagent exceeded turn budget.") : interrupted ? "Paused after interrupt. Waiting for explicit next action." : summary,
|
|
3097
3967
|
...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),
|
|
3098
3968
|
...(config.deadlineAt !== undefined ? { deadlineAt: config.deadlineAt } : {}),
|
|
3099
3969
|
...(statusPayload.turnBudget ? { turnBudget: statusPayload.turnBudget } : {}),
|
|
@@ -3101,15 +3971,18 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
3101
3971
|
...(statusPayload.wrapUpRequested ? { wrapUpRequested: true } : {}),
|
|
3102
3972
|
...(statusPayload.toolBudget ? { toolBudget: statusPayload.toolBudget } : {}),
|
|
3103
3973
|
...(statusPayload.toolBudgetBlocked ? { toolBudgetBlocked: true } : {}),
|
|
3104
|
-
...(timedOut ? { timedOut: true, error: timeoutMessage ?? "Subagent timed out." } : turnBudgetExceeded ? { error: statusPayload.error ?? "Subagent exceeded turn budget." } : {}),
|
|
3974
|
+
...(stopped ? { stopped: true, error: stopMessage } : timedOut ? { timedOut: true, error: timeoutMessage ?? "Subagent timed out." } : turnBudgetExceeded ? { error: statusPayload.error ?? "Subagent exceeded turn budget." } : {}),
|
|
3105
3975
|
results: results.map((r) => ({
|
|
3106
3976
|
agent: r.agent,
|
|
3977
|
+
context: r.context,
|
|
3107
3978
|
output: r.output,
|
|
3108
3979
|
error: r.error,
|
|
3980
|
+
protocolError: r.protocolError,
|
|
3109
3981
|
success: r.success,
|
|
3110
3982
|
skipped: r.skipped || undefined,
|
|
3111
3983
|
interrupted: r.interrupted || undefined,
|
|
3112
3984
|
timedOut: r.timedOut || undefined,
|
|
3985
|
+
stopped: r.stopped || undefined,
|
|
3113
3986
|
turnBudget: r.turnBudget,
|
|
3114
3987
|
turnBudgetExceeded: r.turnBudgetExceeded || undefined,
|
|
3115
3988
|
wrapUpRequested: r.wrapUpRequested || undefined,
|
|
@@ -3125,23 +3998,34 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
3125
3998
|
truncated: r.truncated,
|
|
3126
3999
|
transcriptPath: r.transcriptPath,
|
|
3127
4000
|
transcriptError: r.transcriptError,
|
|
4001
|
+
agentContract: r.agentContract,
|
|
4002
|
+
launchContractDigest: r.launchContractDigest,
|
|
4003
|
+
execution: r.execution,
|
|
4004
|
+
review: r.review,
|
|
4005
|
+
effects: r.effects,
|
|
3128
4006
|
structuredOutput: r.structuredOutput,
|
|
3129
4007
|
structuredOutputPath: r.structuredOutputPath,
|
|
3130
4008
|
structuredOutputSchemaPath: r.structuredOutputSchemaPath,
|
|
3131
4009
|
acceptance: r.acceptance,
|
|
4010
|
+
watchdog: r.watchdog,
|
|
4011
|
+
capabilityCeiling: r.capabilityCeiling,
|
|
4012
|
+
capabilityAudit: r.capabilityAudit,
|
|
3132
4013
|
})),
|
|
3133
4014
|
outputs,
|
|
3134
4015
|
workflowGraph: statusPayload.workflowGraph,
|
|
3135
|
-
|
|
4016
|
+
parallelHandoff: statusPayload.parallelHandoff,
|
|
4017
|
+
capabilityCeiling: statusPayload.capabilityCeiling,
|
|
4018
|
+
capabilityAudit: statusPayload.capabilityAudit,
|
|
4019
|
+
exitCode: stopped || timedOut || turnBudgetExceeded ? 1 : interrupted || results.every((r) => r.success) ? 0 : 1,
|
|
3136
4020
|
timestamp: runEndedAt,
|
|
3137
4021
|
durationMs: runEndedAt - overallStartTime,
|
|
3138
4022
|
totalTokens: statusPayload.totalTokens,
|
|
3139
|
-
totalChildUsage,
|
|
3140
4023
|
totalCost: finalTotalCost,
|
|
3141
4024
|
truncated,
|
|
3142
4025
|
artifactsDir,
|
|
3143
4026
|
cwd,
|
|
3144
4027
|
asyncDir,
|
|
4028
|
+
launchContractDigest: config.launchContractDigest,
|
|
3145
4029
|
sessionId: config.sessionId,
|
|
3146
4030
|
sessionFile: effectiveSessionFile,
|
|
3147
4031
|
intercomTarget: config.controlIntercomTarget,
|
|
@@ -3154,6 +4038,118 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
3154
4038
|
} catch (err) {
|
|
3155
4039
|
console.error(`Failed to write result file ${resultPath}:`, err);
|
|
3156
4040
|
}
|
|
4041
|
+
if (config.runnerProcessInstanceId) {
|
|
4042
|
+
const writers: Record<string, Array<{ processInstanceId: string; kind: "pi-writer"; attempt: number; closeObservedAt: number; exitCode: number | null; signal: string | null }>> = {};
|
|
4043
|
+
const expectedWriters: Record<string, number> = {};
|
|
4044
|
+
for (const [index, result] of results.entries()) {
|
|
4045
|
+
writers[String(index)] = result.writerProcesses ?? [];
|
|
4046
|
+
expectedWriters[String(index)] = result.writerAttemptCount ?? 0;
|
|
4047
|
+
}
|
|
4048
|
+
const candidate: ProcessTerminalCandidate = {
|
|
4049
|
+
version: 1,
|
|
4050
|
+
runId: id,
|
|
4051
|
+
runnerProcessInstanceId: config.runnerProcessInstanceId,
|
|
4052
|
+
writers,
|
|
4053
|
+
expectedWriters,
|
|
4054
|
+
...(config.revivalLease?.sessionFile ? { sessionFile: config.revivalLease.sessionFile } : {}),
|
|
4055
|
+
...(config.revivalLeaseToken ? { revivalLeaseToken: config.revivalLeaseToken } : {}),
|
|
4056
|
+
};
|
|
4057
|
+
try {
|
|
4058
|
+
writeProcessTerminalCandidate(asyncDir, candidate);
|
|
4059
|
+
} catch (error) {
|
|
4060
|
+
console.error(`Failed to write process-terminal candidate for '${id}':`, error);
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
|
|
4065
|
+
async function waitForStartupControl(
|
|
4066
|
+
controlPath: string,
|
|
4067
|
+
token: string,
|
|
4068
|
+
action: "ack" | "proceed",
|
|
4069
|
+
timeoutMs = 30_000,
|
|
4070
|
+
): Promise<void> {
|
|
4071
|
+
const deadline = Date.now() + timeoutMs;
|
|
4072
|
+
while (Date.now() <= deadline) {
|
|
4073
|
+
if (fs.existsSync(controlPath)) {
|
|
4074
|
+
let payload: { action?: unknown; token?: unknown };
|
|
4075
|
+
try {
|
|
4076
|
+
payload = JSON.parse(fs.readFileSync(controlPath, "utf-8")) as { action?: unknown; token?: unknown };
|
|
4077
|
+
} catch (error) {
|
|
4078
|
+
throw new Error(`Failed to read runner startup control '${controlPath}': ${error instanceof Error ? error.message : String(error)}`);
|
|
4079
|
+
}
|
|
4080
|
+
if (payload.token !== token) throw new Error("Runner startup control token does not match the acquired session lease.");
|
|
4081
|
+
if (payload.action === action) return;
|
|
4082
|
+
if (payload.action !== "ack" && payload.action !== "proceed") throw new Error("Runner startup control action is invalid.");
|
|
4083
|
+
}
|
|
4084
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
4085
|
+
}
|
|
4086
|
+
throw new Error(`Timed out after ${timeoutMs}ms waiting for runner startup control '${action}'.`);
|
|
4087
|
+
}
|
|
4088
|
+
|
|
4089
|
+
async function runConfiguredSubagent(config: SubagentRunConfig): Promise<void> {
|
|
4090
|
+
let lease: ReturnType<typeof acquireSessionLease> | undefined;
|
|
4091
|
+
let startupCommitted = config.revivalLease === undefined;
|
|
4092
|
+
const startupPath = path.join(config.asyncDir, "runner-startup.json");
|
|
4093
|
+
const startupAckPath = path.join(config.asyncDir, "runner-startup-ack.json");
|
|
4094
|
+
const startupProceedPath = path.join(config.asyncDir, "runner-startup-proceed.json");
|
|
4095
|
+
const releaseOnExit = (): void => {
|
|
4096
|
+
try {
|
|
4097
|
+
lease?.release();
|
|
4098
|
+
} catch {
|
|
4099
|
+
// Exit cleanup is best effort; a dead-owner lease is reclaimed on the next revival.
|
|
4100
|
+
}
|
|
4101
|
+
};
|
|
4102
|
+
process.once("exit", releaseOnExit);
|
|
4103
|
+
try {
|
|
4104
|
+
if (config.revivalLease) {
|
|
4105
|
+
lease = acquireSessionLease(config.revivalLease);
|
|
4106
|
+
config.revivalLeaseToken = lease.owner.token;
|
|
4107
|
+
writeAtomicJson(startupPath, { state: "ready", token: lease.owner.token, pid: process.pid, owner: lease.owner });
|
|
4108
|
+
await waitForStartupControl(startupAckPath, lease.owner.token, "ack");
|
|
4109
|
+
writeAtomicJson(startupPath, { state: "acknowledged", token: lease.owner.token, pid: process.pid });
|
|
4110
|
+
await waitForStartupControl(startupProceedPath, lease.owner.token, "proceed");
|
|
4111
|
+
startupCommitted = true;
|
|
4112
|
+
for (const controlPath of [startupAckPath, startupProceedPath]) {
|
|
4113
|
+
try {
|
|
4114
|
+
fs.rmSync(controlPath, { force: true });
|
|
4115
|
+
} catch {
|
|
4116
|
+
// Startup control cleanup is best effort after the parent commits the run.
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4120
|
+
await runSubagent(config, lease ? (writer) => lease!.updateWriter(writer) : undefined);
|
|
4121
|
+
} catch (error) {
|
|
4122
|
+
if (config.revivalLease && !startupCommitted) {
|
|
4123
|
+
try {
|
|
4124
|
+
writeAtomicJson(startupPath, { state: "error", pid: process.pid, error: error instanceof Error ? error.message : String(error) });
|
|
4125
|
+
} catch {
|
|
4126
|
+
// The parent will time out and terminate this runner if the handshake cannot be written.
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
throw error;
|
|
4130
|
+
} finally {
|
|
4131
|
+
process.off("exit", releaseOnExit);
|
|
4132
|
+
if (lease) {
|
|
4133
|
+
let acknowledged = false;
|
|
4134
|
+
try {
|
|
4135
|
+
acknowledged = lease.release();
|
|
4136
|
+
} catch (error) {
|
|
4137
|
+
console.error("Failed to release session revival lease:", error);
|
|
4138
|
+
}
|
|
4139
|
+
try {
|
|
4140
|
+
markProcessTerminalCandidateLeaseRelease(config.asyncDir, lease.owner.token, acknowledged);
|
|
4141
|
+
} catch (error) {
|
|
4142
|
+
console.error("Failed to record session revival lease release:", error);
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
}
|
|
4147
|
+
|
|
4148
|
+
function startConfiguredSubagent(config: SubagentRunConfig): void {
|
|
4149
|
+
runConfiguredSubagent(config).catch((runErr) => {
|
|
4150
|
+
console.error("Subagent runner error:", runErr);
|
|
4151
|
+
process.exit(1);
|
|
4152
|
+
});
|
|
3157
4153
|
}
|
|
3158
4154
|
|
|
3159
4155
|
const configArg = process.argv[2];
|
|
@@ -3166,10 +4162,7 @@ if (configArg) {
|
|
|
3166
4162
|
} catch {
|
|
3167
4163
|
// Temp config cleanup is best effort.
|
|
3168
4164
|
}
|
|
3169
|
-
|
|
3170
|
-
console.error("Subagent runner error:", runErr);
|
|
3171
|
-
process.exit(1);
|
|
3172
|
-
});
|
|
4165
|
+
startConfiguredSubagent(config);
|
|
3173
4166
|
} catch (err) {
|
|
3174
4167
|
console.error("Subagent runner error:", err);
|
|
3175
4168
|
process.exit(1);
|
|
@@ -3183,10 +4176,7 @@ if (configArg) {
|
|
|
3183
4176
|
process.stdin.on("end", () => {
|
|
3184
4177
|
try {
|
|
3185
4178
|
const config = JSON.parse(input) as SubagentRunConfig;
|
|
3186
|
-
|
|
3187
|
-
console.error("Subagent runner error:", runErr);
|
|
3188
|
-
process.exit(1);
|
|
3189
|
-
});
|
|
4179
|
+
startConfiguredSubagent(config);
|
|
3190
4180
|
} catch (err) {
|
|
3191
4181
|
console.error("Subagent runner error:", err);
|
|
3192
4182
|
process.exit(1);
|