@selesai/code 0.5.19 → 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-intercom/broker/spawn.test.ts +31 -0
- package/dist/extensions/pi-intercom/broker/spawn.ts +5 -3
- 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 +2 -1
|
@@ -3,17 +3,20 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
5
5
|
import type { ExtensionAPI, ExtensionContext } from "@selesai/code";
|
|
6
|
-
import {
|
|
6
|
+
import type { AgentConfig, AgentScope } from "../../agents/agents.ts";
|
|
7
7
|
import { getArtifactsDir, getProjectChainRunsDir } from "../../shared/artifacts.ts";
|
|
8
8
|
import { ChainClarifyComponent, type ChainClarifyResult } from "./chain-clarify.ts";
|
|
9
9
|
import { toModelInfo, type ModelInfo } from "../../shared/model-info.ts";
|
|
10
10
|
import { executeChain } from "./chain-execution.ts";
|
|
11
|
+
import { beginForegroundChild, finishForegroundChild, updateForegroundChild } from "./foreground-control.ts";
|
|
11
12
|
import { resolveExecutionAgentScope } from "../../agents/agent-scope.ts";
|
|
12
13
|
import { handleManagementAction } from "../../agents/agent-management.ts";
|
|
13
14
|
import { buildDoctorReport } from "../../extension/doctor.ts";
|
|
14
15
|
import { clearPendingForegroundControlNotices } from "../../extension/control-notices.ts";
|
|
15
16
|
import { runSync } from "./execution.ts";
|
|
16
|
-
import {
|
|
17
|
+
import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/tool-actions.ts";
|
|
18
|
+
import type { MainWatchdogRuntime } from "../../watchdog/runtime.ts";
|
|
19
|
+
import { buildModelCandidates, resolveEffectiveSubagentModel, resolveModelCandidate } from "../shared/model-fallback.ts";
|
|
17
20
|
import type { ModelScopeConfig } from "../shared/model-scope.ts";
|
|
18
21
|
import { aggregateParallelOutputs } from "../shared/parallel-utils.ts";
|
|
19
22
|
import { recordRun } from "../shared/run-history.ts";
|
|
@@ -36,27 +39,34 @@ import { buildAsyncRunnerSteps, executeAsyncChain, executeAsyncSingle, formatAsy
|
|
|
36
39
|
import type { ScheduledRunAction } from "../background/scheduled-runs.ts";
|
|
37
40
|
import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
|
|
38
41
|
import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
|
|
39
|
-
import {
|
|
40
|
-
import { createForkContextResolver } from "../../shared/fork-context.ts";
|
|
42
|
+
import { validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
43
|
+
import { createForkContextResolver, forkedChildRequiresThinkingOff } from "../../shared/fork-context.ts";
|
|
41
44
|
import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
|
|
42
45
|
import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
|
|
43
46
|
import { formatControlIntercomMessage, formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
|
|
44
|
-
import {
|
|
47
|
+
import { resolveTurnBudgetConfig } from "../shared/turn-budget.ts";
|
|
48
|
+
import { formatSpawnBudget, getSpawnBudgetSnapshot, grantSpawnBudget, preflightSpawnBudget, preflightSpawnBudgetGrant, reserveSpawnBudget } from "../shared/spawn-budget.ts";
|
|
45
49
|
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
50
|
+
import { intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
51
|
+
import { isAgentContractV1 } from "../shared/agent-contract.ts";
|
|
46
52
|
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
53
|
+
import { cleanupStructuredOutputRuntime, createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
47
54
|
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
|
|
48
55
|
import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
|
|
56
|
+
import { formatParallelHandoffError, formatParallelHandoffReference, parallelHandoffPath, writeParallelHandoffGroup } from "../shared/parallel-handoff.ts";
|
|
57
|
+
import { summarizeContextModes, type ContextMode, type ContextSummary } from "../shared/context-mode.ts";
|
|
49
58
|
import {
|
|
50
59
|
attachNestedChildrenToResultChildren,
|
|
51
60
|
buildSubagentResultIntercomPayload,
|
|
52
|
-
deliverSubagentIntercomMessageEvent,
|
|
53
61
|
deliverSubagentResultIntercomEvent,
|
|
54
62
|
formatSubagentResultReceipt,
|
|
55
63
|
resolveSubagentResultStatus,
|
|
56
64
|
stripDetailsOutputsForIntercomReceipt,
|
|
57
65
|
} from "../../intercom/result-intercom.ts";
|
|
58
|
-
import {
|
|
59
|
-
import { deliverInterruptRequest, requestAsyncSteer } from "../background/control-channel.ts";
|
|
66
|
+
import { applySteeringRecoveryAgentConfig, buildRevivedAsyncTask, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
|
|
67
|
+
import { deliverInterruptRequest, deliverStopRequest, requestAsyncSteer } from "../background/control-channel.ts";
|
|
68
|
+
import { waitForSteeringAction } from "../background/steering.ts";
|
|
69
|
+
import { steerAsyncRun } from "./async-steering-action.ts";
|
|
60
70
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
61
71
|
import { resolveAsyncRootResultPath } from "../background/chain-root-attachment.ts";
|
|
62
72
|
import { attachRootChildrenToSteps, createNestedRoute, readNestedControlResults, resolveInheritedNestedRouteFromEnv, resolveNestedAsyncDir, resolveNestedParentAddressFromEnv, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent, type NestedRunResolutionScope } from "../shared/nested-events.ts";
|
|
@@ -75,14 +85,18 @@ import {
|
|
|
75
85
|
} from "../shared/worktree.ts";
|
|
76
86
|
import {
|
|
77
87
|
type AgentProgress,
|
|
88
|
+
type AsyncStatus,
|
|
78
89
|
type AcceptanceInput,
|
|
90
|
+
type AgentContract,
|
|
79
91
|
type ArtifactConfig,
|
|
80
92
|
type ArtifactPaths,
|
|
81
93
|
type ControlConfig,
|
|
82
94
|
type ControlEvent,
|
|
83
95
|
type Details,
|
|
84
96
|
type ExtensionConfig,
|
|
97
|
+
type ForegroundRunControl,
|
|
85
98
|
type IntercomEventBus,
|
|
99
|
+
type JsonSchemaObject,
|
|
86
100
|
type MaxOutputConfig,
|
|
87
101
|
type NestedRouteInfo,
|
|
88
102
|
type NestedRunSummary,
|
|
@@ -96,12 +110,13 @@ import {
|
|
|
96
110
|
type SubagentState,
|
|
97
111
|
ASYNC_DIR,
|
|
98
112
|
DEFAULT_ARTIFACT_CONFIG,
|
|
113
|
+
DEFAULT_FORK_PREAMBLE,
|
|
99
114
|
RESULTS_DIR,
|
|
100
115
|
SUBAGENT_ACTIONS,
|
|
101
116
|
SUBAGENT_CONTROL_EVENT,
|
|
102
117
|
SUBAGENT_CONTROL_INTERCOM_EVENT,
|
|
118
|
+
SUBAGENT_FOREGROUND_COMPLETE_EVENT,
|
|
103
119
|
checkSubagentDepth,
|
|
104
|
-
resolveMaxSubagentSpawnsPerSession,
|
|
105
120
|
resolveTopLevelParallelConcurrency,
|
|
106
121
|
resolveTopLevelParallelMaxTasks,
|
|
107
122
|
resolveChildMaxSubagentDepth,
|
|
@@ -109,8 +124,7 @@ import {
|
|
|
109
124
|
wrapForkTask,
|
|
110
125
|
} from "../../shared/types.ts";
|
|
111
126
|
|
|
112
|
-
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset"]);
|
|
113
|
-
|
|
127
|
+
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure"]);
|
|
114
128
|
interface TaskParam {
|
|
115
129
|
agent: string;
|
|
116
130
|
task: string;
|
|
@@ -122,7 +136,9 @@ interface TaskParam {
|
|
|
122
136
|
progress?: boolean;
|
|
123
137
|
model?: string;
|
|
124
138
|
skill?: string | string[] | boolean;
|
|
139
|
+
outputSchema?: JsonSchemaObject;
|
|
125
140
|
acceptance?: AcceptanceInput;
|
|
141
|
+
agentContract?: AgentContract;
|
|
126
142
|
toolBudget?: ToolBudgetConfig;
|
|
127
143
|
}
|
|
128
144
|
|
|
@@ -137,15 +153,19 @@ export interface SubagentParamsLike {
|
|
|
137
153
|
agent?: string;
|
|
138
154
|
task?: string;
|
|
139
155
|
message?: string;
|
|
156
|
+
steeringRecovery?: boolean;
|
|
140
157
|
chain?: ChainStep[];
|
|
141
158
|
tasks?: TaskParam[];
|
|
142
159
|
concurrency?: number;
|
|
143
160
|
worktree?: boolean;
|
|
144
161
|
context?: "fresh" | "fork";
|
|
145
162
|
async?: boolean;
|
|
163
|
+
foregroundOnly?: boolean;
|
|
146
164
|
timeoutMs?: number;
|
|
147
165
|
maxRuntimeMs?: number;
|
|
148
166
|
turnBudget?: TurnBudgetConfig;
|
|
167
|
+
/** Internal-only strict turn-boundary enforcement for versioned foreground delegation. */
|
|
168
|
+
enforceHardTurnLimit?: boolean;
|
|
149
169
|
toolBudget?: ToolBudgetConfig;
|
|
150
170
|
clarify?: boolean;
|
|
151
171
|
share?: boolean;
|
|
@@ -156,14 +176,20 @@ export interface SubagentParamsLike {
|
|
|
156
176
|
artifacts?: boolean;
|
|
157
177
|
includeProgress?: boolean;
|
|
158
178
|
model?: string;
|
|
179
|
+
thinking?: string | false;
|
|
180
|
+
scope?: string;
|
|
181
|
+
target?: string;
|
|
159
182
|
skill?: string | string[] | boolean;
|
|
160
183
|
output?: string | boolean;
|
|
161
184
|
outputMode?: "inline" | "file-only";
|
|
185
|
+
outputSchema?: JsonSchemaObject;
|
|
162
186
|
agentScope?: unknown;
|
|
163
187
|
chainDir?: string;
|
|
164
188
|
acceptance?: AcceptanceInput;
|
|
189
|
+
agentContract?: AgentContract;
|
|
165
190
|
schedule?: string;
|
|
166
191
|
scheduleName?: string;
|
|
192
|
+
additional?: number;
|
|
167
193
|
}
|
|
168
194
|
|
|
169
195
|
interface ExecutorDeps {
|
|
@@ -171,7 +197,9 @@ interface ExecutorDeps {
|
|
|
171
197
|
state: SubagentState;
|
|
172
198
|
config: ExtensionConfig;
|
|
173
199
|
asyncByDefault: boolean;
|
|
200
|
+
waitToolEnabled?: boolean;
|
|
174
201
|
handleScheduledRunAction?: (params: SubagentParamsLike, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>;
|
|
202
|
+
watchdog?: MainWatchdogRuntime;
|
|
175
203
|
tempArtifactsDir: string;
|
|
176
204
|
getSubagentSessionRoot: (parentSessionFile: string | null) => string;
|
|
177
205
|
expandTilde: (p: string) => string;
|
|
@@ -180,6 +208,9 @@ interface ExecutorDeps {
|
|
|
180
208
|
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
181
209
|
}
|
|
182
210
|
|
|
211
|
+
type ForkSessionFileForTask = (agentName: string, idx?: number, modelOverride?: string) => string | undefined;
|
|
212
|
+
type ForkThinkingOverrideForTask = (agentName: string, idx?: number, modelOverride?: string) => AgentConfig["thinking"] | undefined;
|
|
213
|
+
|
|
183
214
|
interface ExecutionContextData {
|
|
184
215
|
params: SubagentParamsLike;
|
|
185
216
|
effectiveCwd: string;
|
|
@@ -192,8 +223,8 @@ interface ExecutionContextData {
|
|
|
192
223
|
sessionRoot: string;
|
|
193
224
|
sessionDirForIndex: (idx?: number) => string;
|
|
194
225
|
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
195
|
-
sessionFileForTask:
|
|
196
|
-
thinkingOverrideForTask:
|
|
226
|
+
sessionFileForTask: ForkSessionFileForTask;
|
|
227
|
+
thinkingOverrideForTask: ForkThinkingOverrideForTask;
|
|
197
228
|
artifactConfig: ArtifactConfig;
|
|
198
229
|
artifactsDir: string;
|
|
199
230
|
backgroundRequestedWhileClarifying: boolean;
|
|
@@ -205,9 +236,12 @@ interface ExecutionContextData {
|
|
|
205
236
|
deadlineAt?: number;
|
|
206
237
|
turnBudget?: ResolvedTurnBudget;
|
|
207
238
|
toolBudget?: ResolvedToolBudget;
|
|
239
|
+
allowZeroToolBudget?: boolean;
|
|
208
240
|
configToolBudget?: ResolvedToolBudget;
|
|
209
241
|
contextPolicy: AgentDefaultContextPolicy;
|
|
210
242
|
modelScope?: ModelScopeConfig;
|
|
243
|
+
parentSessionId: string | null;
|
|
244
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
211
245
|
}
|
|
212
246
|
|
|
213
247
|
function resolveRequestedCwd(runtimeCwd: string, requestedCwd: string | undefined): string {
|
|
@@ -263,22 +297,34 @@ function trustedSessionRootsForStatus(ctx: ExtensionContext, deps: ExecutorDeps)
|
|
|
263
297
|
return [...new Set(roots)];
|
|
264
298
|
}
|
|
265
299
|
|
|
266
|
-
function
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
return
|
|
300
|
+
function spawnBudgetErrorResult(message: string, mode: "single" | "parallel" | "chain"): AgentToolResult<Details> {
|
|
301
|
+
return {
|
|
302
|
+
content: [{ type: "text", text: message }],
|
|
303
|
+
isError: true,
|
|
304
|
+
details: { mode, results: [] },
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function withSpawnBudgetStatus(
|
|
309
|
+
result: AgentToolResult<Details>,
|
|
310
|
+
state: SubagentState,
|
|
311
|
+
config: ExtensionConfig,
|
|
312
|
+
sessionId: string | null,
|
|
313
|
+
): AgentToolResult<Details> {
|
|
314
|
+
const spawnBudget = getSpawnBudgetSnapshot(state, config, sessionId);
|
|
315
|
+
return {
|
|
316
|
+
...result,
|
|
317
|
+
content: result.content.map((item, index) => index === 0 && item.type === "text"
|
|
318
|
+
? { ...item, text: `${formatSpawnBudget(spawnBudget)}\n${item.text}` }
|
|
319
|
+
: item),
|
|
320
|
+
details: { ...result.details, spawnBudget },
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function hasActiveSubagentChildren(state: SubagentState): boolean {
|
|
325
|
+
if (state.subagentInProgress || state.foregroundControls.size > 0) return true;
|
|
326
|
+
const isActive = (status: string) => status === "queued" || status === "running";
|
|
327
|
+
return [...state.asyncJobs.values(), ...(state.fleetJobs?.values() ?? [])].some((job) => isActive(job.status));
|
|
282
328
|
}
|
|
283
329
|
|
|
284
330
|
function countRequestedSubagentSpawns(params: SubagentParamsLike, config: ExtensionConfig): number {
|
|
@@ -315,13 +361,28 @@ function foregroundStatusResult(control: SubagentState["foregroundControls"] ext
|
|
|
315
361
|
function trimRememberedForegroundRuns(state: SubagentState): void {
|
|
316
362
|
if (!state.foregroundRuns) return;
|
|
317
363
|
while (state.foregroundRuns.size > 50) {
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
364
|
+
const oldestTerminal = [...state.foregroundRuns.values()]
|
|
365
|
+
.filter((run) => !run.children.some((child) => child.status === "detached"))
|
|
366
|
+
.sort((left, right) => left.updatedAt - right.updatedAt)[0];
|
|
367
|
+
if (!oldestTerminal) break;
|
|
368
|
+
state.foregroundRuns.delete(oldestTerminal.runId);
|
|
321
369
|
}
|
|
322
370
|
}
|
|
323
371
|
|
|
324
|
-
function
|
|
372
|
+
function foregroundChildActivityFromProgress(progress: SingleResult["progress"] | undefined) {
|
|
373
|
+
return {
|
|
374
|
+
...(progress?.activityState ? { activityState: progress.activityState } : {}),
|
|
375
|
+
...(progress?.lastActivityAt !== undefined ? { lastActivityAt: progress.lastActivityAt } : {}),
|
|
376
|
+
...(progress?.currentTool ? { currentTool: progress.currentTool } : {}),
|
|
377
|
+
...(progress?.currentToolStartedAt !== undefined ? { currentToolStartedAt: progress.currentToolStartedAt } : {}),
|
|
378
|
+
...(progress?.currentPath ? { currentPath: progress.currentPath } : {}),
|
|
379
|
+
...(progress?.turnCount !== undefined ? { turnCount: progress.turnCount } : {}),
|
|
380
|
+
...(progress?.tokens !== undefined ? { tokens: progress.tokens } : {}),
|
|
381
|
+
...(progress?.toolCount !== undefined ? { toolCount: progress.toolCount } : {}),
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function rememberForegroundRun(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; results: SingleResult[] }): void {
|
|
325
386
|
state.foregroundRuns ??= new Map();
|
|
326
387
|
const previous = state.foregroundRuns.get(input.runId);
|
|
327
388
|
const updatedAt = Date.now();
|
|
@@ -329,20 +390,30 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
329
390
|
runId: input.runId,
|
|
330
391
|
mode: input.mode,
|
|
331
392
|
cwd: input.cwd,
|
|
393
|
+
...(input.sessionId ? { sessionId: input.sessionId } : {}),
|
|
332
394
|
updatedAt,
|
|
333
395
|
children: input.results.map((result, index) => {
|
|
334
396
|
const child = {
|
|
335
397
|
agent: result.agent,
|
|
336
398
|
index,
|
|
399
|
+
...(result.context ? { context: result.context } : {}),
|
|
337
400
|
status: resolveSubagentResultStatus({ exitCode: result.exitCode, interrupted: result.interrupted, detached: result.detached }),
|
|
401
|
+
...foregroundChildActivityFromProgress(result.progress),
|
|
338
402
|
updatedAt,
|
|
339
403
|
...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {}),
|
|
404
|
+
...(result.error ? { error: result.error } : {}),
|
|
340
405
|
...(result.finalOutput ? { finalOutput: result.finalOutput } : {}),
|
|
406
|
+
...(result.outputMode ? { outputMode: result.outputMode } : {}),
|
|
407
|
+
...(result.savedOutputPath ? { savedOutputPath: result.savedOutputPath } : {}),
|
|
408
|
+
...(result.outputSaveError ? { outputSaveError: result.outputSaveError } : {}),
|
|
341
409
|
...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
|
|
342
410
|
...(result.artifactPaths ? { artifactPaths: result.artifactPaths } : {}),
|
|
343
411
|
...(result.transcriptPath ? { transcriptPath: result.transcriptPath } : {}),
|
|
344
412
|
...(result.transcriptError ? { transcriptError: result.transcriptError } : {}),
|
|
345
413
|
...(result.detachedReason ? { detachedReason: result.detachedReason } : {}),
|
|
414
|
+
...(result.acceptance ? { acceptance: result.acceptance } : {}),
|
|
415
|
+
...(result.capabilityCeiling ? { capabilityCeiling: result.capabilityCeiling } : {}),
|
|
416
|
+
...(result.capabilityAudit ? { capabilityAudit: result.capabilityAudit } : {}),
|
|
346
417
|
};
|
|
347
418
|
const recovered = previous?.children[index];
|
|
348
419
|
return child.status === "detached" && recovered && recovered.status !== "detached" ? recovered : child;
|
|
@@ -351,12 +422,35 @@ function rememberForegroundRun(state: SubagentState, input: { runId: string; mod
|
|
|
351
422
|
trimRememberedForegroundRuns(state);
|
|
352
423
|
}
|
|
353
424
|
|
|
354
|
-
function
|
|
425
|
+
function applyControlEventToRememberedForegroundRun(state: SubagentState, event: ControlEvent): void {
|
|
426
|
+
const run = state.foregroundRuns?.get(event.runId);
|
|
427
|
+
if (!run) return;
|
|
428
|
+
const index = event.index ?? (run.children.length === 1 ? run.children[0]?.index : undefined);
|
|
429
|
+
if (index === undefined) return;
|
|
430
|
+
const child = run.children[index];
|
|
431
|
+
if (!child || child.status !== "detached") return;
|
|
432
|
+
const updatedAt = event.ts;
|
|
433
|
+
run.updatedAt = updatedAt;
|
|
434
|
+
run.children[index] = {
|
|
435
|
+
...child,
|
|
436
|
+
activityState: event.to,
|
|
437
|
+
updatedAt,
|
|
438
|
+
...(event.elapsedMs !== undefined ? { lastActivityAt: event.ts - event.elapsedMs } : {}),
|
|
439
|
+
...(event.currentTool ? { currentTool: event.currentTool } : {}),
|
|
440
|
+
...(event.currentToolDurationMs !== undefined ? { currentToolStartedAt: event.ts - event.currentToolDurationMs } : {}),
|
|
441
|
+
...(event.currentPath ? { currentPath: event.currentPath } : {}),
|
|
442
|
+
...(event.turns !== undefined ? { turnCount: event.turns } : {}),
|
|
443
|
+
...(event.tokens !== undefined ? { tokens: event.tokens } : {}),
|
|
444
|
+
...(event.toolCount !== undefined ? { toolCount: event.toolCount } : {}),
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function updateRememberedForegroundChild(state: SubagentState, input: { runId: string; mode: "single" | "parallel" | "chain"; cwd: string; sessionId: string | null; index: number; result: SingleResult; events: IntercomEventBus }): void {
|
|
355
449
|
state.foregroundRuns ??= new Map();
|
|
356
450
|
const updatedAt = Date.now();
|
|
357
451
|
let run = state.foregroundRuns.get(input.runId);
|
|
358
452
|
if (!run) {
|
|
359
|
-
run = { runId: input.runId, mode: input.mode, cwd: input.cwd, updatedAt, children: [] };
|
|
453
|
+
run = { runId: input.runId, mode: input.mode, cwd: input.cwd, ...(input.sessionId ? { sessionId: input.sessionId } : {}), updatedAt, children: [] };
|
|
360
454
|
state.foregroundRuns.set(input.runId, run);
|
|
361
455
|
}
|
|
362
456
|
run.updatedAt = updatedAt;
|
|
@@ -365,38 +459,83 @@ function updateRememberedForegroundChild(state: SubagentState, input: { runId: s
|
|
|
365
459
|
...child,
|
|
366
460
|
agent: input.result.agent,
|
|
367
461
|
index: input.index,
|
|
368
|
-
|
|
462
|
+
...(input.result.context ? { context: input.result.context } : {}),
|
|
463
|
+
status: input.result.acceptance?.status === "rejected"
|
|
464
|
+
? "failed"
|
|
465
|
+
: resolveSubagentResultStatus({ exitCode: input.result.exitCode, interrupted: input.result.interrupted, detached: false }),
|
|
466
|
+
...foregroundChildActivityFromProgress(input.result.progress),
|
|
369
467
|
updatedAt,
|
|
370
468
|
...(input.result.exitCode !== undefined ? { exitCode: input.result.exitCode } : {}),
|
|
469
|
+
...(input.result.error ? { error: input.result.error } : {}),
|
|
371
470
|
...(input.result.finalOutput ? { finalOutput: input.result.finalOutput } : {}),
|
|
471
|
+
outputMode: input.result.outputMode,
|
|
472
|
+
savedOutputPath: input.result.savedOutputPath,
|
|
473
|
+
outputSaveError: input.result.outputSaveError,
|
|
372
474
|
...(input.result.sessionFile ? { sessionFile: input.result.sessionFile } : {}),
|
|
373
475
|
...(input.result.artifactPaths ? { artifactPaths: input.result.artifactPaths } : {}),
|
|
374
476
|
...(input.result.transcriptPath ? { transcriptPath: input.result.transcriptPath } : {}),
|
|
375
477
|
...(input.result.transcriptError ? { transcriptError: input.result.transcriptError } : {}),
|
|
376
478
|
...(input.result.detachedReason ? { detachedReason: input.result.detachedReason } : {}),
|
|
479
|
+
...(input.result.acceptance ? { acceptance: input.result.acceptance } : {}),
|
|
480
|
+
...(input.result.capabilityCeiling ? { capabilityCeiling: input.result.capabilityCeiling } : {}),
|
|
481
|
+
...(input.result.capabilityAudit ? { capabilityAudit: input.result.capabilityAudit } : {}),
|
|
377
482
|
};
|
|
378
483
|
trimRememberedForegroundRuns(state);
|
|
484
|
+
const output = getSingleResultOutput(input.result).trim();
|
|
485
|
+
const success = input.result.exitCode === 0 && input.result.acceptance?.status !== "rejected";
|
|
486
|
+
const summary = !success && input.result.error
|
|
487
|
+
? `${input.result.error}${output ? `\n\nOutput:\n${output}` : ""}`
|
|
488
|
+
: output || input.result.error || "Detached child exited without final output.";
|
|
489
|
+
// A detached callback may outlive its extension runtime. Stale sessions are
|
|
490
|
+
// intentionally dropped rather than routed through a replacement runtime.
|
|
491
|
+
if (!input.sessionId || input.sessionId !== state.currentSessionId) return;
|
|
492
|
+
input.events.emit(SUBAGENT_FOREGROUND_COMPLETE_EVENT, {
|
|
493
|
+
id: `${input.runId}:${input.index}`,
|
|
494
|
+
runId: input.runId,
|
|
495
|
+
source: "foreground",
|
|
496
|
+
mode: input.mode,
|
|
497
|
+
agent: input.result.agent,
|
|
498
|
+
success,
|
|
499
|
+
summary,
|
|
500
|
+
exitCode: input.result.exitCode,
|
|
501
|
+
state: success ? "complete" : "failed",
|
|
502
|
+
timestamp: updatedAt,
|
|
503
|
+
cwd: input.cwd,
|
|
504
|
+
sessionFile: input.result.sessionFile,
|
|
505
|
+
sessionId: input.sessionId,
|
|
506
|
+
taskIndex: input.index,
|
|
507
|
+
});
|
|
379
508
|
}
|
|
380
509
|
|
|
381
|
-
function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState): { runId: string; mode: "single" | "parallel" | "chain"; state: "complete"; agent: string; index: number;
|
|
510
|
+
function resolveForegroundResumeTarget(params: SubagentParamsLike, state: SubagentState): { runId: string; mode: "single" | "parallel" | "chain"; state: "complete"; agent: string; index: number; cwd: string; sessionFile: string; capabilityCeiling?: ResolvedSubagentCapabilityCeiling } | undefined {
|
|
382
511
|
const requested = (params.id ?? params.runId)?.trim();
|
|
383
|
-
if (!requested || !state.foregroundRuns?.size) return undefined;
|
|
384
|
-
const
|
|
385
|
-
const
|
|
512
|
+
if (!requested || !state.foregroundRuns?.size || !state.currentSessionId) return undefined;
|
|
513
|
+
const sessionRuns = [...state.foregroundRuns.values()].filter((run) => run.sessionId === state.currentSessionId);
|
|
514
|
+
const direct = sessionRuns.find((run) => run.runId === requested);
|
|
515
|
+
const matches = direct ? [direct] : sessionRuns.filter((run) => run.runId.startsWith(requested));
|
|
386
516
|
if (matches.length === 0) return undefined;
|
|
387
517
|
if (matches.length > 1) throw new Error(`Ambiguous foreground run id prefix '${requested}' matched: ${matches.map((run) => run.runId).join(", ")}. Provide a longer id.`);
|
|
388
518
|
const run = matches[0]!;
|
|
519
|
+
if (run.children.some((child) => child.status === "detached")) throw new Error(`Foreground run '${run.runId}' is detached for intercom coordination and cannot be revived safely while any child may still be live. Reply to the supervisor request first, then wait with subagent_wait({ id: "${run.runId}" }); use status to recover the result and do not launch a replacement while it remains detached.`);
|
|
389
520
|
if (run.children.length > 1 && params.index === undefined) throw new Error(`Foreground run '${run.runId}' has ${run.children.length} children. Provide index to choose one.`);
|
|
390
521
|
const index = params.index ?? 0;
|
|
391
522
|
if (!Number.isInteger(index)) throw new Error(`Foreground run '${run.runId}' index must be an integer.`);
|
|
392
523
|
if (index < 0 || index >= run.children.length) throw new Error(`Foreground run '${run.runId}' has ${run.children.length} children. Index ${index} is out of range.`);
|
|
393
524
|
const child = run.children[index]!;
|
|
394
|
-
if (child.status === "detached") throw new Error(`Foreground run '${run.runId}' child ${index} is detached for intercom coordination and cannot be revived safely from the remembered foreground state. Reply to the supervisor request first; after the child exits, start a fresh follow-up if needed.`);
|
|
395
525
|
if (!child.sessionFile) throw new Error(`Foreground run '${run.runId}' child ${index} does not have a persisted session file to resume from.`);
|
|
396
526
|
if (path.extname(child.sessionFile) !== ".jsonl") throw new Error(`Foreground run '${run.runId}' child ${index} session file must be a .jsonl file: ${child.sessionFile}`);
|
|
397
527
|
const sessionFile = path.resolve(child.sessionFile);
|
|
398
528
|
if (!fs.existsSync(sessionFile)) throw new Error(`Foreground run '${run.runId}' child ${index} session file does not exist: ${child.sessionFile}`);
|
|
399
|
-
return {
|
|
529
|
+
return {
|
|
530
|
+
runId: run.runId,
|
|
531
|
+
mode: run.mode,
|
|
532
|
+
state: "complete",
|
|
533
|
+
agent: child.agent,
|
|
534
|
+
index,
|
|
535
|
+
cwd: run.cwd,
|
|
536
|
+
sessionFile,
|
|
537
|
+
...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
|
|
538
|
+
};
|
|
400
539
|
}
|
|
401
540
|
|
|
402
541
|
type AsyncResumeSourceTarget = ReturnType<typeof resolveAsyncResumeTarget> & { source: "async" };
|
|
@@ -408,9 +547,9 @@ type NestedResumeSourceTarget = {
|
|
|
408
547
|
state: "complete" | "failed" | "paused";
|
|
409
548
|
agent: string;
|
|
410
549
|
index: number;
|
|
411
|
-
intercomTarget: string;
|
|
412
550
|
cwd?: string;
|
|
413
551
|
sessionFile: string;
|
|
552
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
414
553
|
};
|
|
415
554
|
type ResumeSourceTarget = AsyncResumeSourceTarget | ForegroundResumeSourceTarget | NestedResumeSourceTarget;
|
|
416
555
|
|
|
@@ -449,7 +588,13 @@ function resolveResumeTarget(params: SubagentParamsLike, state: SubagentState, o
|
|
|
449
588
|
foregroundError = error;
|
|
450
589
|
}
|
|
451
590
|
try {
|
|
452
|
-
asyncTarget = {
|
|
591
|
+
asyncTarget = {
|
|
592
|
+
source: "async",
|
|
593
|
+
...resolveAsyncResumeTarget(params, {}, {
|
|
594
|
+
requireSessionFile: options.asyncRequireSessionFile,
|
|
595
|
+
sessionId: state.currentSessionId ?? undefined,
|
|
596
|
+
}),
|
|
597
|
+
};
|
|
453
598
|
} catch (error) {
|
|
454
599
|
asyncError = error;
|
|
455
600
|
}
|
|
@@ -481,6 +626,7 @@ function getAsyncInterruptTarget(
|
|
|
481
626
|
state: SubagentState,
|
|
482
627
|
runId: string | undefined,
|
|
483
628
|
location?: { asyncDir: string | null; resolvedId?: string },
|
|
629
|
+
options: { fallbackToNewest?: boolean } = {},
|
|
484
630
|
): { asyncId: string; asyncDir: string } | undefined {
|
|
485
631
|
if (location?.asyncDir) {
|
|
486
632
|
return {
|
|
@@ -491,6 +637,7 @@ function getAsyncInterruptTarget(
|
|
|
491
637
|
if (runId) {
|
|
492
638
|
const direct = state.asyncJobs.get(runId);
|
|
493
639
|
if (direct) return { asyncId: direct.asyncId, asyncDir: direct.asyncDir };
|
|
640
|
+
if (options.fallbackToNewest === false) return undefined;
|
|
494
641
|
}
|
|
495
642
|
let newest: { asyncId: string; asyncDir: string; updatedAt: number } | undefined;
|
|
496
643
|
for (const job of state.asyncJobs.values()) {
|
|
@@ -567,64 +714,48 @@ function interruptAsyncRun(
|
|
|
567
714
|
}
|
|
568
715
|
}
|
|
569
716
|
|
|
570
|
-
function
|
|
571
|
-
state: SubagentState
|
|
572
|
-
runId: string
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
717
|
+
function stopAsyncRun(
|
|
718
|
+
state: SubagentState,
|
|
719
|
+
runId: string | undefined,
|
|
720
|
+
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean,
|
|
721
|
+
location?: { asyncDir: string | null; resolvedId?: string },
|
|
722
|
+
): AgentToolResult<Details> | null {
|
|
723
|
+
const target = getAsyncInterruptTarget(state, runId, location, { fallbackToNewest: false });
|
|
724
|
+
if (!target) return null;
|
|
725
|
+
const status = reconcileAsyncRun(target.asyncDir, { kill }).status;
|
|
726
|
+
if (state.currentSessionId && status?.sessionId !== state.currentSessionId) {
|
|
579
727
|
return {
|
|
580
|
-
content: [{ type: "text", text: `Async run '${
|
|
728
|
+
content: [{ type: "text", text: `Async run '${target.asyncId}' was not found in the active session.` }],
|
|
581
729
|
isError: true,
|
|
582
730
|
details: { mode: "management", results: [] },
|
|
583
731
|
};
|
|
584
732
|
}
|
|
585
|
-
const status = reconcileAsyncRun(input.location.asyncDir, { kill: input.kill }).status;
|
|
586
733
|
if (!status || (status.state !== "running" && status.state !== "queued")) {
|
|
587
734
|
return {
|
|
588
|
-
content: [{ type: "text", text: `
|
|
735
|
+
content: [{ type: "text", text: `No running or queued async run was found for '${runId ?? "current"}'.` }],
|
|
589
736
|
isError: true,
|
|
590
737
|
details: { mode: "management", results: [] },
|
|
591
738
|
};
|
|
592
739
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
details: { mode: "management", results: [] },
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
const targetStep = steps[input.index];
|
|
603
|
-
if (targetStep && targetStep.status !== "running" && targetStep.status !== "pending") {
|
|
604
|
-
return {
|
|
605
|
-
content: [{ type: "text", text: `Async run '${status.runId}' child ${input.index} is ${targetStep.status} and cannot be steered.` }],
|
|
606
|
-
isError: true,
|
|
607
|
-
details: { mode: "management", results: [] },
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
} else {
|
|
611
|
-
const running = steps.filter((step) => step.status === "running");
|
|
612
|
-
if (running.length === 0 && steps.length > 1) {
|
|
613
|
-
return {
|
|
614
|
-
content: [{ type: "text", text: `Async run '${status.runId}' has no running child yet. Provide index to steer a queued child.` }],
|
|
615
|
-
isError: true,
|
|
616
|
-
details: { mode: "management", results: [] },
|
|
617
|
-
};
|
|
740
|
+
try {
|
|
741
|
+
deliverStopRequest({ asyncDir: target.asyncDir, pid: typeof status.pid === "number" ? status.pid : undefined, kill, source: "stop-action" });
|
|
742
|
+
const tracked = state.asyncJobs.get(target.asyncId);
|
|
743
|
+
if (tracked) {
|
|
744
|
+
tracked.activityState = undefined;
|
|
745
|
+
tracked.updatedAt = Date.now();
|
|
618
746
|
}
|
|
747
|
+
return {
|
|
748
|
+
content: [{ type: "text", text: `Stop requested for async run ${target.asyncId}.` }],
|
|
749
|
+
details: { mode: "management", results: [] },
|
|
750
|
+
};
|
|
751
|
+
} catch (error) {
|
|
752
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
753
|
+
return {
|
|
754
|
+
content: [{ type: "text", text: `Failed to stop async run ${target.asyncId}: ${message}` }],
|
|
755
|
+
isError: true,
|
|
756
|
+
details: { mode: "management", results: [] },
|
|
757
|
+
};
|
|
619
758
|
}
|
|
620
|
-
requestAsyncSteer(input.location.asyncDir, { message: input.message, targetIndex: input.index, source: "steer-action" });
|
|
621
|
-
const tracked = input.state.asyncJobs.get(status.runId);
|
|
622
|
-
if (tracked) tracked.updatedAt = Date.now();
|
|
623
|
-
const childText = input.index !== undefined ? ` child ${input.index}` : " running child";
|
|
624
|
-
return {
|
|
625
|
-
content: [{ type: "text", text: `Steering queued for async run ${status.runId}${childText}. Delivery requires a live Pi child session that supports mid-run steering.` }],
|
|
626
|
-
details: { mode: "management", results: [] },
|
|
627
|
-
};
|
|
628
759
|
}
|
|
629
760
|
|
|
630
761
|
function duplicateNames(names: string[]): string[] {
|
|
@@ -754,6 +885,7 @@ function appendStepToAsyncChain(input: {
|
|
|
754
885
|
currentModelProvider: input.ctx.model?.provider,
|
|
755
886
|
currentModel: input.ctx.model,
|
|
756
887
|
modelScope: discoveredForAppend.modelScope,
|
|
888
|
+
interactive: input.ctx.hasUI,
|
|
757
889
|
};
|
|
758
890
|
const built = buildAsyncRunnerSteps(resolved.id, {
|
|
759
891
|
chain: wrapChainTasksForFork(input.params.chain, contextPolicy),
|
|
@@ -766,8 +898,11 @@ function appendStepToAsyncChain(input: {
|
|
|
766
898
|
chainSkills,
|
|
767
899
|
dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
|
|
768
900
|
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
901
|
+
waitToolEnabled: input.deps.waitToolEnabled,
|
|
902
|
+
contextForAgent: contextPolicy.contextForAgent,
|
|
769
903
|
asyncDir: resolved.location.asyncDir,
|
|
770
904
|
validateOutputBindings: false,
|
|
905
|
+
capabilityCeiling: intersectSubagentCapabilityCeilings(status.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(asyncCtx.currentSessionId)),
|
|
771
906
|
});
|
|
772
907
|
if ("error" in built) {
|
|
773
908
|
return {
|
|
@@ -858,6 +993,7 @@ function validateNestedSessionFile(run: NestedRunSummary, trustedSessionRoots: s
|
|
|
858
993
|
function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "nested" }, trustedSessionRoots: string[]): NestedResumeSourceTarget {
|
|
859
994
|
const run = match.match.run;
|
|
860
995
|
if (run.state === "running" || run.state === "queued") throw new Error(`Nested run '${run.id}' is live; route the follow-up to the owner process instead.`);
|
|
996
|
+
if (run.state === "stopped") throw new Error(`Nested run '${run.id}' was stopped and cannot be resumed. Start a new run instead.`);
|
|
861
997
|
const agent = nestedRunAgent(run);
|
|
862
998
|
if (!agent) throw new Error(`Could not determine child agent for nested run '${run.id}'.`);
|
|
863
999
|
const state = run.state === "complete" || run.state === "failed" || run.state === "paused" ? run.state : "failed";
|
|
@@ -869,9 +1005,9 @@ function resolveNestedResumeTarget(match: ResolvedSubagentRunId & { kind: "neste
|
|
|
869
1005
|
state,
|
|
870
1006
|
agent,
|
|
871
1007
|
index: 0,
|
|
872
|
-
intercomTarget: resolveSubagentIntercomTarget(run.id, agent, 0),
|
|
873
1008
|
cwd: asyncDir ? path.dirname(asyncDir) : undefined,
|
|
874
1009
|
sessionFile: validateNestedSessionFile(run, trustedSessionRoots),
|
|
1010
|
+
...(run.capabilityCeiling ? { capabilityCeiling: run.capabilityCeiling } : {}),
|
|
875
1011
|
};
|
|
876
1012
|
}
|
|
877
1013
|
|
|
@@ -913,7 +1049,7 @@ function directNestedAsyncInterrupt(target: ResolvedSubagentRunId & { kind: "nes
|
|
|
913
1049
|
}
|
|
914
1050
|
}
|
|
915
1051
|
|
|
916
|
-
function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; index?: number }): AgentToolResult<Details> | undefined {
|
|
1052
|
+
async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; index?: number; signal?: AbortSignal }): Promise<AgentToolResult<Details> | undefined> {
|
|
917
1053
|
const run = input.target.match.run;
|
|
918
1054
|
const asyncDir = resolveNestedAsyncDir(input.target.match.rootRunId, run);
|
|
919
1055
|
if (!asyncDir) return undefined;
|
|
@@ -925,8 +1061,32 @@ function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & { kind:
|
|
|
925
1061
|
const step = steps[input.index];
|
|
926
1062
|
if (step && step.status !== "running" && step.status !== "pending") return { content: [{ type: "text", text: `Nested async run ${run.id} child ${input.index} is ${step.status} and cannot be steered.` }], isError: true, details: { mode: "management", results: [] } };
|
|
927
1063
|
}
|
|
928
|
-
|
|
929
|
-
|
|
1064
|
+
const runningIndexes = steps
|
|
1065
|
+
.map((step, index) => step.status === "running" ? index : undefined)
|
|
1066
|
+
.filter((index): index is number => index !== undefined);
|
|
1067
|
+
const effectiveTargetIndex = input.index ?? (status.mode === "single" && runningIndexes.length === 0 && steps[0]?.status === "pending" ? 0 : undefined);
|
|
1068
|
+
const targetIndexes = effectiveTargetIndex !== undefined ? [effectiveTargetIndex] : runningIndexes;
|
|
1069
|
+
if (targetIndexes.length === 0) return { content: [{ type: "text", text: `Nested async run ${run.id} has no running child to steer.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1070
|
+
const requestId = randomUUID();
|
|
1071
|
+
try {
|
|
1072
|
+
requestAsyncSteer(asyncDir, {
|
|
1073
|
+
message: input.message,
|
|
1074
|
+
...(effectiveTargetIndex !== undefined ? { targetIndex: effectiveTargetIndex } : { targetIndexes }),
|
|
1075
|
+
source: "nested-steer",
|
|
1076
|
+
id: requestId,
|
|
1077
|
+
});
|
|
1078
|
+
} catch (error) {
|
|
1079
|
+
return { content: [{ type: "text", text: `Failed to queue steering for nested async run ${run.id}: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "management", results: [] } };
|
|
1080
|
+
}
|
|
1081
|
+
const targets = targetIndexes.map((index) => ({ index, state: steps[index]?.status === "pending" ? "scheduled" as const : "pending" as const }));
|
|
1082
|
+
if (targets.every((target) => target.state === "scheduled")) {
|
|
1083
|
+
const scheduled = { requestId, state: "scheduled" as const, sourceRunId: run.id, targets };
|
|
1084
|
+
return { content: [{ type: "text", text: `Steering scheduled for nested async run ${run.id} (request ${requestId}).` }], details: { mode: "management", results: [], steering: scheduled } };
|
|
1085
|
+
}
|
|
1086
|
+
const waited = await waitForSteeringAction({ asyncDir, sourceRunId: run.id, requestId, timeoutMs: 3_000, signal: input.signal });
|
|
1087
|
+
const result = waited ?? { requestId, state: "pending" as const, sourceRunId: run.id, targets };
|
|
1088
|
+
const stateText = result.state === "delivered" ? "delivered" : result.state === "failed" ? "failed" : result.state === "partial" ? "partial" : "pending";
|
|
1089
|
+
return { content: [{ type: "text", text: `Steering ${stateText} for nested async run ${run.id} (request ${requestId}).` }], ...(result.state === "failed" || result.state === "partial" ? { isError: true } : {}), details: { mode: "management", results: [], steering: result } };
|
|
930
1090
|
}
|
|
931
1091
|
|
|
932
1092
|
async function interruptNestedRun(target: ResolvedSubagentRunId & { kind: "nested" }): Promise<AgentToolResult<Details>> {
|
|
@@ -948,10 +1108,10 @@ async function resumeLiveNestedRun(input: { target: ResolvedSubagentRunId & { ki
|
|
|
948
1108
|
return { content: [{ type: "text", text: `Nested run ${run.id} appears live but its owner route is not reachable. Wait for completion, then retry action='resume'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
949
1109
|
}
|
|
950
1110
|
|
|
951
|
-
function steerNestedRun(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; index?: number }): AgentToolResult<Details
|
|
1111
|
+
async function steerNestedRun(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; index?: number; signal?: AbortSignal }): Promise<AgentToolResult<Details>> {
|
|
952
1112
|
const run = input.target.match.run;
|
|
953
1113
|
if (run.state !== "running" && run.state !== "queued") return { content: [{ type: "text", text: `Nested run ${run.id} is ${run.state} and cannot be steered.` }], isError: true, details: { mode: "management", results: [] } };
|
|
954
|
-
const direct = directNestedAsyncSteer(input);
|
|
1114
|
+
const direct = await directNestedAsyncSteer(input);
|
|
955
1115
|
if (direct) return direct;
|
|
956
1116
|
return { content: [{ type: "text", text: `Nested run ${run.id} is not a live async Pi child session with a steering inbox. action='steer' cannot target foreground nested runs.` }], isError: true, details: { mode: "management", results: [] } };
|
|
957
1117
|
}
|
|
@@ -961,6 +1121,7 @@ async function resumeAsyncRun(input: {
|
|
|
961
1121
|
requestCwd: string;
|
|
962
1122
|
ctx: ExtensionContext;
|
|
963
1123
|
deps: ExecutorDeps;
|
|
1124
|
+
absoluteDeadlineAt?: number;
|
|
964
1125
|
}): Promise<AgentToolResult<Details>> {
|
|
965
1126
|
const followUp = (input.params.message ?? input.params.task ?? "").trim();
|
|
966
1127
|
const attachChain = (input.params.chain?.length ?? 0) > 0 ? input.params.chain as ChainStep[] : undefined;
|
|
@@ -971,6 +1132,15 @@ async function resumeAsyncRun(input: {
|
|
|
971
1132
|
details: { mode: "management", results: [] },
|
|
972
1133
|
};
|
|
973
1134
|
}
|
|
1135
|
+
const acceptanceErrors = validateExecutionAcceptance(input.params);
|
|
1136
|
+
if (acceptanceErrors.length > 0) {
|
|
1137
|
+
return {
|
|
1138
|
+
content: [{ type: "text", text: `Cannot resume: ${acceptanceErrors.join(" ")}` }],
|
|
1139
|
+
isError: true,
|
|
1140
|
+
details: { mode: "management", results: [] },
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
input.deps.state.currentSessionId = resolveCurrentSessionId(input.ctx.sessionManager);
|
|
974
1144
|
|
|
975
1145
|
let target: ResumeSourceTarget;
|
|
976
1146
|
const parentSessionFile = input.ctx.sessionManager.getSessionFile() ?? null;
|
|
@@ -1009,34 +1179,14 @@ async function resumeAsyncRun(input: {
|
|
|
1009
1179
|
}
|
|
1010
1180
|
|
|
1011
1181
|
if (target.kind === "live" && !attachChain) {
|
|
1012
|
-
const interrupt = interruptLiveAsyncResumeTarget({
|
|
1013
|
-
target,
|
|
1014
|
-
state: input.deps.state,
|
|
1015
|
-
kill: input.deps.kill,
|
|
1016
|
-
resultsDir: RESULTS_DIR,
|
|
1017
|
-
});
|
|
1018
|
-
if (!interrupt.ok) {
|
|
1019
|
-
return {
|
|
1020
|
-
content: [{ type: "text", text: interrupt.message }],
|
|
1021
|
-
isError: true,
|
|
1022
|
-
details: { mode: "management", results: [] },
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
const delivered = await deliverSubagentIntercomMessageEvent(
|
|
1026
|
-
input.deps.pi.events,
|
|
1027
|
-
target.intercomTarget,
|
|
1028
|
-
`Follow-up for async run ${target.runId} (${target.agent}):\n\n${followUp}`,
|
|
1029
|
-
500,
|
|
1030
|
-
{ source: "async-resume", runId: target.runId, agent: target.agent, index: target.index },
|
|
1031
|
-
);
|
|
1032
|
-
if (delivered) {
|
|
1033
|
-
return {
|
|
1034
|
-
content: [{ type: "text", text: [`Interrupted live async child, then delivered follow-up.`, `Run: ${target.runId}`, `Intercom target: ${target.intercomTarget}`].join("\n") }],
|
|
1035
|
-
details: { mode: "management", results: [] },
|
|
1036
|
-
};
|
|
1037
|
-
}
|
|
1038
1182
|
return {
|
|
1039
|
-
content: [{
|
|
1183
|
+
content: [{
|
|
1184
|
+
type: "text",
|
|
1185
|
+
text: [
|
|
1186
|
+
`Async child '${target.runId}' index ${target.index} is still running. action='resume' only revives paused, completed, or failed children.`,
|
|
1187
|
+
`Send live input with subagent({ action: "steer", id: "${target.runId}", index: ${target.index}, message: "..." }).`,
|
|
1188
|
+
].join("\n"),
|
|
1189
|
+
}],
|
|
1040
1190
|
isError: true,
|
|
1041
1191
|
details: { mode: "management", results: [] },
|
|
1042
1192
|
};
|
|
@@ -1066,7 +1216,18 @@ async function resumeAsyncRun(input: {
|
|
|
1066
1216
|
const agents = intercomBridge.active
|
|
1067
1217
|
? discoveredAgents.map((agent) => applyIntercomBridgeToAgent(agent, intercomBridge))
|
|
1068
1218
|
: discoveredAgents;
|
|
1069
|
-
const
|
|
1219
|
+
const recoveryDescriptor = "recoveryDescriptor" in target ? target.recoveryDescriptor : undefined;
|
|
1220
|
+
const discoveredAgentConfig = agents.find((agent) => agent.name === target.agent);
|
|
1221
|
+
const agentConfig: AgentConfig | undefined = discoveredAgentConfig ?? (recoveryDescriptor ? {
|
|
1222
|
+
name: recoveryDescriptor.agent,
|
|
1223
|
+
description: "Persisted async recovery contract",
|
|
1224
|
+
systemPrompt: "",
|
|
1225
|
+
systemPromptMode: recoveryDescriptor.systemPromptMode,
|
|
1226
|
+
inheritProjectContext: recoveryDescriptor.inheritProjectContext,
|
|
1227
|
+
inheritSkills: recoveryDescriptor.inheritSkills,
|
|
1228
|
+
source: "project",
|
|
1229
|
+
filePath: recoveryDescriptor.agentFilePath ?? path.join(recoveryDescriptor.cwd, ".pi-subagents-recovery-agent"),
|
|
1230
|
+
} : undefined);
|
|
1070
1231
|
if (!agentConfig) {
|
|
1071
1232
|
return {
|
|
1072
1233
|
content: [{ type: "text", text: `Unknown agent for resume: ${target.agent}` }],
|
|
@@ -1091,14 +1252,17 @@ async function resumeAsyncRun(input: {
|
|
|
1091
1252
|
};
|
|
1092
1253
|
}
|
|
1093
1254
|
const runId = randomUUID().slice(0, 8);
|
|
1094
|
-
const artifactConfig: ArtifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false };
|
|
1255
|
+
const artifactConfig: ArtifactConfig = { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir };
|
|
1095
1256
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1096
1257
|
const contextPolicy = resolveExplicitContextPolicy(input.params);
|
|
1258
|
+
const workflowTask = (input.params.task ?? followUp) || undefined;
|
|
1259
|
+
const goal = resolveAsyncEventGoal(workflowTask, attachChain);
|
|
1097
1260
|
const chain = wrapChainTasksForFork(attachChain, contextPolicy);
|
|
1098
1261
|
const normalized = normalizeSkillInput(input.params.skill);
|
|
1099
1262
|
const result = executeAsyncChain(runId, {
|
|
1100
1263
|
chain,
|
|
1101
|
-
task:
|
|
1264
|
+
task: workflowTask,
|
|
1265
|
+
goal,
|
|
1102
1266
|
attachRoot: {
|
|
1103
1267
|
runId: target.runId,
|
|
1104
1268
|
asyncDir: target.asyncDir ?? path.join(ASYNC_DIR, target.runId),
|
|
@@ -1116,17 +1280,20 @@ async function resumeAsyncRun(input: {
|
|
|
1116
1280
|
currentModelProvider: input.ctx.model?.provider,
|
|
1117
1281
|
currentModel: input.ctx.model,
|
|
1118
1282
|
modelScope,
|
|
1283
|
+
interactive: input.ctx.hasUI,
|
|
1119
1284
|
},
|
|
1120
1285
|
availableModels,
|
|
1121
1286
|
cwd: effectiveCwd,
|
|
1122
1287
|
maxOutput: input.params.maxOutput,
|
|
1123
|
-
artifactsDir: getArtifactsDir(parentSessionFile, effectiveCwd),
|
|
1288
|
+
artifactsDir: getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir),
|
|
1124
1289
|
artifactConfig,
|
|
1125
1290
|
shareEnabled: input.params.share === true,
|
|
1126
1291
|
sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile),
|
|
1127
1292
|
chainSkills: normalized === false ? [] : (normalized ?? []),
|
|
1293
|
+
agentContract: input.params.agentContract,
|
|
1128
1294
|
dynamicFanoutMaxItems: input.deps.config.chain?.dynamicFanout?.maxItems,
|
|
1129
1295
|
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1296
|
+
waitToolEnabled: input.deps.waitToolEnabled,
|
|
1130
1297
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
1131
1298
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
1132
1299
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
@@ -1134,6 +1301,7 @@ async function resumeAsyncRun(input: {
|
|
|
1134
1301
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
1135
1302
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1136
1303
|
globalConcurrencyLimit: input.deps.config.globalConcurrencyLimit,
|
|
1304
|
+
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
|
|
1137
1305
|
});
|
|
1138
1306
|
if (result.isError) return result;
|
|
1139
1307
|
const attachedId = result.details.asyncId ?? runId;
|
|
@@ -1144,17 +1312,19 @@ async function resumeAsyncRun(input: {
|
|
|
1144
1312
|
result.details.asyncDir ? `Async dir: ${result.details.asyncDir}` : undefined,
|
|
1145
1313
|
`Status if needed: subagent({ action: "status", id: "${attachedId}" })`,
|
|
1146
1314
|
].filter((line): line is string => Boolean(line));
|
|
1147
|
-
return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n")) }], details: result.details };
|
|
1315
|
+
return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n"), input.ctx.hasUI) }], details: result.details };
|
|
1148
1316
|
}
|
|
1149
1317
|
|
|
1150
1318
|
const runId = randomUUID().slice(0, 8);
|
|
1151
|
-
const
|
|
1152
|
-
const
|
|
1319
|
+
const recoveryAgentConfig = recoveryDescriptor ? applySteeringRecoveryAgentConfig(agentConfig, recoveryDescriptor) : agentConfig;
|
|
1320
|
+
const artifactConfig: ArtifactConfig = recoveryDescriptor?.artifactConfig ?? { ...DEFAULT_ARTIFACT_CONFIG, enabled: input.params.artifacts !== false, dir: input.deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir };
|
|
1321
|
+
const artifactsDir = recoveryDescriptor?.artifactsDir ?? getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
|
|
1153
1322
|
const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1154
1323
|
const result = executeAsyncSingle(runId, {
|
|
1155
1324
|
agent: target.agent,
|
|
1156
1325
|
task: buildRevivedAsyncTask(target, followUp),
|
|
1157
|
-
|
|
1326
|
+
goal: followUp,
|
|
1327
|
+
agentConfig: recoveryAgentConfig,
|
|
1158
1328
|
ctx: {
|
|
1159
1329
|
pi: input.deps.pi,
|
|
1160
1330
|
cwd: input.requestCwd,
|
|
@@ -1163,23 +1333,45 @@ async function resumeAsyncRun(input: {
|
|
|
1163
1333
|
currentModelProvider: input.ctx.model?.provider,
|
|
1164
1334
|
currentModel: input.ctx.model,
|
|
1165
1335
|
modelScope,
|
|
1336
|
+
interactive: input.ctx.hasUI,
|
|
1166
1337
|
},
|
|
1167
1338
|
cwd: effectiveCwd,
|
|
1168
|
-
maxOutput: input.params.maxOutput,
|
|
1339
|
+
maxOutput: input.params.maxOutput ?? recoveryDescriptor?.maxOutput,
|
|
1169
1340
|
artifactsDir,
|
|
1170
1341
|
artifactConfig,
|
|
1171
|
-
shareEnabled: input.params.share === true,
|
|
1342
|
+
shareEnabled: recoveryDescriptor?.share ?? input.params.share === true,
|
|
1172
1343
|
sessionRoot: input.deps.getSubagentSessionRoot(parentSessionFile),
|
|
1344
|
+
...(recoveryDescriptor?.sessionDir ? { sessionDir: recoveryDescriptor.sessionDir } : {}),
|
|
1173
1345
|
sessionFile: target.sessionFile,
|
|
1346
|
+
revivalLease: {
|
|
1347
|
+
sessionFile: target.sessionFile,
|
|
1348
|
+
runId,
|
|
1349
|
+
sourceRunId: target.runId,
|
|
1350
|
+
...(input.deps.state.currentSessionId ? { parentSessionId: input.deps.state.currentSessionId } : {}),
|
|
1351
|
+
},
|
|
1352
|
+
modelOverride: input.params.model ?? recoveryDescriptor?.model ?? target.model,
|
|
1353
|
+
thinkingOverride: input.params.model ? undefined : recoveryDescriptor?.thinking ?? target.thinking,
|
|
1174
1354
|
outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
|
|
1175
|
-
maxSubagentDepth: resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1355
|
+
maxSubagentDepth: recoveryDescriptor?.maxSubagentDepth ?? resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth),
|
|
1356
|
+
waitToolEnabled: input.deps.waitToolEnabled,
|
|
1176
1357
|
worktreeSetupHook: input.deps.config.worktreeSetupHook,
|
|
1177
1358
|
worktreeSetupHookTimeoutMs: input.deps.config.worktreeSetupHookTimeoutMs,
|
|
1178
1359
|
worktreeBaseDir: input.deps.config.worktreeBaseDir,
|
|
1179
|
-
controlConfig: resolveControlConfig(input.deps.config.control, input.params.control),
|
|
1360
|
+
controlConfig: recoveryDescriptor?.controlConfig ?? resolveControlConfig(input.deps.config.control, input.params.control),
|
|
1180
1361
|
controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
|
|
1181
1362
|
childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
|
|
1182
1363
|
availableModels,
|
|
1364
|
+
output: recoveryDescriptor?.outputPath,
|
|
1365
|
+
outputMode: recoveryDescriptor?.outputMode,
|
|
1366
|
+
...(recoveryDescriptor?.agentContract ? { agentContract: recoveryDescriptor.agentContract } : {}),
|
|
1367
|
+
...(recoveryDescriptor?.structuredOutputSchema ? { structuredOutputSchema: recoveryDescriptor.structuredOutputSchema } : {}),
|
|
1368
|
+
...(recoveryDescriptor?.skills ? { skills: [...recoveryDescriptor.skills] } : {}),
|
|
1369
|
+
...(recoveryDescriptor?.acceptance !== undefined && input.params.acceptance === undefined ? { acceptance: recoveryDescriptor.acceptance } : {}),
|
|
1370
|
+
...(input.params.timeoutMs !== undefined ? { timeoutMs: input.params.timeoutMs } : {}),
|
|
1371
|
+
...(input.absoluteDeadlineAt !== undefined ? { absoluteDeadlineAt: input.absoluteDeadlineAt } : {}),
|
|
1372
|
+
...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
|
|
1373
|
+
...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
|
|
1374
|
+
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, recoveryDescriptor?.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
|
|
1183
1375
|
});
|
|
1184
1376
|
if (result.isError) return result;
|
|
1185
1377
|
|
|
@@ -1195,7 +1387,7 @@ async function resumeAsyncRun(input: {
|
|
|
1195
1387
|
revivedTarget ? `Intercom target: ${revivedTarget} (if registered)` : undefined,
|
|
1196
1388
|
`Status if needed: subagent({ action: "status", id: "${revivedId}" })`,
|
|
1197
1389
|
].filter((line): line is string => Boolean(line));
|
|
1198
|
-
return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n")) }], details: result.details };
|
|
1390
|
+
return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n"), input.ctx.hasUI) }], details: result.details };
|
|
1199
1391
|
}
|
|
1200
1392
|
|
|
1201
1393
|
function resultSummaryForIntercom(result: SingleResult): string {
|
|
@@ -1213,19 +1405,22 @@ function formatFailedSingleRunOutput(result: SingleResult, displayOutput: string
|
|
|
1213
1405
|
if (output && output !== error.trim()) {
|
|
1214
1406
|
lines.push("", "Output:", output);
|
|
1215
1407
|
}
|
|
1216
|
-
if (result.artifactPaths?.outputPath) {
|
|
1408
|
+
if (result.artifactPaths?.outputPath && fs.existsSync(result.artifactPaths.outputPath)) {
|
|
1217
1409
|
lines.push("", `Output artifact: ${result.artifactPaths.outputPath}`);
|
|
1218
1410
|
}
|
|
1219
1411
|
return lines.join("\n");
|
|
1220
1412
|
}
|
|
1221
1413
|
|
|
1222
|
-
function createForegroundControlNotifier(data: Pick<ExecutionContextData, "controlConfig" | "intercomBridge">, deps: Pick<ExecutorDeps, "pi">): (event: ControlEvent) => void {
|
|
1223
|
-
return (event) =>
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1414
|
+
function createForegroundControlNotifier(data: Pick<ExecutionContextData, "controlConfig" | "intercomBridge">, deps: Pick<ExecutorDeps, "pi" | "state">): (event: ControlEvent) => void {
|
|
1415
|
+
return (event) => {
|
|
1416
|
+
applyControlEventToRememberedForegroundRun(deps.state, event);
|
|
1417
|
+
emitControlNotification({
|
|
1418
|
+
pi: deps.pi,
|
|
1419
|
+
controlConfig: data.controlConfig,
|
|
1420
|
+
intercomBridge: data.intercomBridge,
|
|
1421
|
+
event,
|
|
1422
|
+
});
|
|
1423
|
+
};
|
|
1229
1424
|
}
|
|
1230
1425
|
|
|
1231
1426
|
async function emitForegroundResultIntercom(input: {
|
|
@@ -1236,6 +1431,7 @@ async function emitForegroundResultIntercom(input: {
|
|
|
1236
1431
|
results: SingleResult[];
|
|
1237
1432
|
chainSteps?: number;
|
|
1238
1433
|
nestedChildren?: NestedRunSummary[];
|
|
1434
|
+
parallelHandoff?: Details["parallelHandoff"];
|
|
1239
1435
|
}): Promise<ReturnType<typeof buildSubagentResultIntercomPayload> | null> {
|
|
1240
1436
|
if (!input.intercomBridge.active || !input.intercomBridge.orchestratorTarget) return null;
|
|
1241
1437
|
const children = input.results.flatMap((result, index) => result.detached ? [] : [{
|
|
@@ -1259,6 +1455,7 @@ async function emitForegroundResultIntercom(input: {
|
|
|
1259
1455
|
source: "foreground",
|
|
1260
1456
|
children: attachNestedChildrenToResultChildren(input.runId, children, input.nestedChildren),
|
|
1261
1457
|
...(typeof input.chainSteps === "number" ? { chainSteps: input.chainSteps } : {}),
|
|
1458
|
+
...(input.parallelHandoff ? { parallelHandoff: input.parallelHandoff } : {}),
|
|
1262
1459
|
});
|
|
1263
1460
|
const delivered = await deliverSubagentResultIntercomEvent(input.pi.events, payload);
|
|
1264
1461
|
if (!delivered) return null;
|
|
@@ -1281,6 +1478,7 @@ async function maybeBuildForegroundIntercomReceipt(input: {
|
|
|
1281
1478
|
results: input.details.results,
|
|
1282
1479
|
...(typeof input.details.totalSteps === "number" ? { chainSteps: input.details.totalSteps } : {}),
|
|
1283
1480
|
...(input.nestedChildren?.length ? { nestedChildren: input.nestedChildren } : {}),
|
|
1481
|
+
...(input.details.parallelHandoff ? { parallelHandoff: input.details.parallelHandoff } : {}),
|
|
1284
1482
|
});
|
|
1285
1483
|
if (!payload) return null;
|
|
1286
1484
|
return {
|
|
@@ -1413,25 +1611,6 @@ function validateExecutionChainBindings(params: SubagentParamsLike, dynamicFanou
|
|
|
1413
1611
|
return null;
|
|
1414
1612
|
}
|
|
1415
1613
|
|
|
1416
|
-
function validateExecutionAcceptance(params: SubagentParamsLike): string[] {
|
|
1417
|
-
const errors: string[] = [];
|
|
1418
|
-
errors.push(...validateAcceptanceInput(params.acceptance, "acceptance"));
|
|
1419
|
-
for (const [index, task] of (params.tasks ?? []).entries()) {
|
|
1420
|
-
errors.push(...validateAcceptanceInput(task.acceptance, `tasks[${index}].acceptance`));
|
|
1421
|
-
}
|
|
1422
|
-
for (const [stepIndex, step] of (params.chain ?? []).entries()) {
|
|
1423
|
-
errors.push(...validateAcceptanceInput((step as { acceptance?: unknown }).acceptance, `chain[${stepIndex}].acceptance`));
|
|
1424
|
-
if (isParallelStep(step)) {
|
|
1425
|
-
for (const [taskIndex, task] of step.parallel.entries()) {
|
|
1426
|
-
errors.push(...validateAcceptanceInput(task.acceptance, `chain[${stepIndex}].parallel[${taskIndex}].acceptance`));
|
|
1427
|
-
}
|
|
1428
|
-
} else if (isDynamicParallelStep(step)) {
|
|
1429
|
-
errors.push(...validateAcceptanceInput(step.parallel.acceptance, `chain[${stepIndex}].parallel.acceptance`));
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
return errors;
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
1614
|
function getRequestedModeLabel(params: SubagentParamsLike): Details["mode"] {
|
|
1436
1615
|
if ((params.chain?.length ?? 0) > 0) return "chain";
|
|
1437
1616
|
if ((params.tasks?.length ?? 0) > 0) return "parallel";
|
|
@@ -1441,7 +1620,8 @@ function getRequestedModeLabel(params: SubagentParamsLike): Details["mode"] {
|
|
|
1441
1620
|
|
|
1442
1621
|
interface AgentDefaultContextPolicy {
|
|
1443
1622
|
params: SubagentParamsLike;
|
|
1444
|
-
contextForAgent(agentName: string):
|
|
1623
|
+
contextForAgent(agentName: string): ContextMode;
|
|
1624
|
+
contextSummary?: ContextSummary;
|
|
1445
1625
|
usesFork: boolean;
|
|
1446
1626
|
}
|
|
1447
1627
|
|
|
@@ -1450,12 +1630,15 @@ function resolveAgentDefaultContextPolicy(params: SubagentParamsLike, agents: Ag
|
|
|
1450
1630
|
return resolveExplicitContextPolicy(params);
|
|
1451
1631
|
}
|
|
1452
1632
|
const byName = new Map(agents.map((agent) => [agent.name, agent]));
|
|
1453
|
-
const contextForAgent = (agentName: string):
|
|
1633
|
+
const contextForAgent = (agentName: string): ContextMode =>
|
|
1454
1634
|
byName.get(agentName)?.defaultContext === "fork" ? "fork" : "fresh";
|
|
1455
|
-
const
|
|
1635
|
+
const requestedAgentNames = collectRequestedAgentNames(params);
|
|
1636
|
+
const contextSummary = summarizeContextModes(requestedAgentNames.map((name) => contextForAgent(name)));
|
|
1637
|
+
const usesFork = contextSummary === "fork" || contextSummary === "mixed";
|
|
1456
1638
|
return {
|
|
1457
|
-
params
|
|
1639
|
+
params,
|
|
1458
1640
|
contextForAgent,
|
|
1641
|
+
contextSummary,
|
|
1459
1642
|
usesFork,
|
|
1460
1643
|
};
|
|
1461
1644
|
}
|
|
@@ -1465,6 +1648,7 @@ function resolveExplicitContextPolicy(params: SubagentParamsLike): AgentDefaultC
|
|
|
1465
1648
|
return {
|
|
1466
1649
|
params,
|
|
1467
1650
|
contextForAgent: () => context,
|
|
1651
|
+
contextSummary: context,
|
|
1468
1652
|
usesFork: context === "fork",
|
|
1469
1653
|
};
|
|
1470
1654
|
}
|
|
@@ -1481,8 +1665,12 @@ function shouldForkAgent(contextPolicy: AgentDefaultContextPolicy, agentName: st
|
|
|
1481
1665
|
return contextPolicy.contextForAgent(agentName) === "fork";
|
|
1482
1666
|
}
|
|
1483
1667
|
|
|
1668
|
+
function summarizeResultContext(details: Details, fallback: ContextSummary | undefined): ContextSummary | undefined {
|
|
1669
|
+
return summarizeContextModes(details.results.map((result) => result.context)) ?? fallback;
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1484
1672
|
function buildRequestedModeError(params: SubagentParamsLike, message: string): AgentToolResult<Details> {
|
|
1485
|
-
return
|
|
1673
|
+
return withResolvedContext(
|
|
1486
1674
|
{
|
|
1487
1675
|
content: [{ type: "text", text: message }],
|
|
1488
1676
|
isError: true,
|
|
@@ -1492,6 +1680,25 @@ function buildRequestedModeError(params: SubagentParamsLike, message: string): A
|
|
|
1492
1680
|
);
|
|
1493
1681
|
}
|
|
1494
1682
|
|
|
1683
|
+
function applySingleAgentLaunchDefaults(params: SubagentParamsLike, agents: AgentConfig[]): SubagentParamsLike {
|
|
1684
|
+
if ((params.chain?.length ?? 0) > 0 || (params.tasks?.length ?? 0) > 0 || !params.agent) return params;
|
|
1685
|
+
const agent = agents.find((candidate) => candidate.name === params.agent);
|
|
1686
|
+
if (!agent) return params;
|
|
1687
|
+
return {
|
|
1688
|
+
...params,
|
|
1689
|
+
...(params.async === undefined && agent.defaultAsync !== undefined ? { async: agent.defaultAsync } : {}),
|
|
1690
|
+
...(params.timeoutMs === undefined && params.maxRuntimeMs === undefined && agent.defaultTimeoutMs !== undefined
|
|
1691
|
+
? { timeoutMs: agent.defaultTimeoutMs }
|
|
1692
|
+
: {}),
|
|
1693
|
+
...(params.turnBudget === undefined && agent.defaultTurnBudget !== undefined
|
|
1694
|
+
? { turnBudget: agent.defaultTurnBudget }
|
|
1695
|
+
: {}),
|
|
1696
|
+
...(params.acceptance === undefined && agent.defaultAcceptance !== undefined
|
|
1697
|
+
? { acceptance: agent.defaultAcceptance }
|
|
1698
|
+
: {}),
|
|
1699
|
+
};
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1495
1702
|
function resolveForegroundTimeout(params: SubagentParamsLike): { timeoutMs?: number; error?: string } {
|
|
1496
1703
|
const rawTimeout = params.timeoutMs;
|
|
1497
1704
|
const rawMaxRuntime = params.maxRuntimeMs;
|
|
@@ -1508,22 +1715,12 @@ function resolveForegroundTimeout(params: SubagentParamsLike): { timeoutMs?: num
|
|
|
1508
1715
|
return { timeoutMs: rawTimeout ?? rawMaxRuntime };
|
|
1509
1716
|
}
|
|
1510
1717
|
|
|
1511
|
-
function
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
}
|
|
1518
|
-
const graceTurns = raw.graceTurns ?? DEFAULT_TURN_BUDGET_GRACE_TURNS;
|
|
1519
|
-
if (typeof graceTurns !== "number" || !Number.isInteger(graceTurns) || graceTurns < 0) {
|
|
1520
|
-
return { error: "turnBudget.graceTurns must be an integer >= 0." };
|
|
1521
|
-
}
|
|
1522
|
-
return { turnBudget: { maxTurns: raw.maxTurns, graceTurns } };
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
function resolveToolBudget(raw: unknown, label = "toolBudget"): { toolBudget?: ResolvedToolBudget; error?: string } {
|
|
1526
|
-
const resolved = validateToolBudgetConfig(raw, label);
|
|
1718
|
+
function resolveToolBudget(
|
|
1719
|
+
raw: unknown,
|
|
1720
|
+
label = "toolBudget",
|
|
1721
|
+
options?: { minimumHard?: 0 | 1 },
|
|
1722
|
+
): { toolBudget?: ResolvedToolBudget; error?: string } {
|
|
1723
|
+
const resolved = validateToolBudgetConfig(raw, label, options);
|
|
1527
1724
|
return { toolBudget: resolved.budget, error: resolved.error };
|
|
1528
1725
|
}
|
|
1529
1726
|
|
|
@@ -1593,35 +1790,50 @@ function normalizeRepeatedParallelCounts(params: SubagentParamsLike): { params?:
|
|
|
1593
1790
|
return { params };
|
|
1594
1791
|
}
|
|
1595
1792
|
|
|
1596
|
-
function
|
|
1793
|
+
function withResolvedContext(
|
|
1597
1794
|
result: AgentToolResult<Details>,
|
|
1598
|
-
|
|
1795
|
+
fallback: ContextSummary | undefined,
|
|
1599
1796
|
): AgentToolResult<Details> {
|
|
1600
|
-
if (
|
|
1797
|
+
if (!result.details) return result;
|
|
1798
|
+
const context = summarizeResultContext(result.details, fallback);
|
|
1799
|
+
if (!context) return result;
|
|
1601
1800
|
return {
|
|
1602
1801
|
...result,
|
|
1603
1802
|
details: {
|
|
1604
1803
|
...result.details,
|
|
1605
|
-
context
|
|
1804
|
+
context,
|
|
1606
1805
|
},
|
|
1607
1806
|
};
|
|
1608
1807
|
}
|
|
1609
1808
|
|
|
1610
|
-
function
|
|
1809
|
+
function withForkThinkingNotes(
|
|
1810
|
+
result: AgentToolResult<Details>,
|
|
1811
|
+
downgrades: Map<number, string>,
|
|
1812
|
+
): AgentToolResult<Details> {
|
|
1813
|
+
if (downgrades.size === 0) return result;
|
|
1814
|
+
const children = [...downgrades.entries()]
|
|
1815
|
+
.sort(([a], [b]) => a - b)
|
|
1816
|
+
.map(([index, agent]) => `${agent} (child ${index})`)
|
|
1817
|
+
.join(", ");
|
|
1818
|
+
const note = `Note: fork context forced thinking off for ${children}. The forked transcript contained signed Anthropic thinking blocks that were sanitized, and Anthropic children cannot resume such a transcript with thinking enabled. Use context: "fresh" when an Anthropic child needs thinking.`;
|
|
1819
|
+
return { ...result, content: [...result.content, { type: "text", text: note }] };
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
function toExecutionErrorResult(params: SubagentParamsLike, error: unknown, contextSummary?: ContextSummary): AgentToolResult<Details> {
|
|
1611
1823
|
const message = error instanceof Error ? error.message : String(error);
|
|
1612
|
-
return
|
|
1824
|
+
return withResolvedContext(
|
|
1613
1825
|
{
|
|
1614
1826
|
content: [{ type: "text", text: message }],
|
|
1615
1827
|
isError: true,
|
|
1616
1828
|
details: { mode: getRequestedModeLabel(params), results: [] },
|
|
1617
1829
|
},
|
|
1618
|
-
|
|
1830
|
+
contextSummary,
|
|
1619
1831
|
);
|
|
1620
1832
|
}
|
|
1621
1833
|
|
|
1622
1834
|
function collectChainSessionFiles(
|
|
1623
1835
|
chain: ChainStep[],
|
|
1624
|
-
sessionFileForTask:
|
|
1836
|
+
sessionFileForTask: ForkSessionFileForTask,
|
|
1625
1837
|
dynamicFanoutMaxItems?: number,
|
|
1626
1838
|
): (string | undefined)[] {
|
|
1627
1839
|
const sessionFiles: (string | undefined)[] = [];
|
|
@@ -1629,7 +1841,7 @@ function collectChainSessionFiles(
|
|
|
1629
1841
|
for (const step of chain) {
|
|
1630
1842
|
if (isParallelStep(step)) {
|
|
1631
1843
|
for (const task of step.parallel) {
|
|
1632
|
-
sessionFiles.push(sessionFileForTask(task.agent, flatIndex));
|
|
1844
|
+
sessionFiles.push(sessionFileForTask(task.agent, flatIndex, task.model));
|
|
1633
1845
|
flatIndex++;
|
|
1634
1846
|
}
|
|
1635
1847
|
continue;
|
|
@@ -1637,12 +1849,13 @@ function collectChainSessionFiles(
|
|
|
1637
1849
|
if (isDynamicParallelStep(step)) {
|
|
1638
1850
|
const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
|
|
1639
1851
|
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) {
|
|
1640
|
-
sessionFiles.push(sessionFileForTask(step.parallel.agent, flatIndex));
|
|
1852
|
+
sessionFiles.push(sessionFileForTask(step.parallel.agent, flatIndex, step.parallel.model));
|
|
1641
1853
|
flatIndex++;
|
|
1642
1854
|
}
|
|
1643
1855
|
continue;
|
|
1644
1856
|
}
|
|
1645
|
-
|
|
1857
|
+
const sequential = step as SequentialStep;
|
|
1858
|
+
sessionFiles.push(sessionFileForTask(sequential.agent, flatIndex, sequential.model));
|
|
1646
1859
|
flatIndex++;
|
|
1647
1860
|
}
|
|
1648
1861
|
return sessionFiles;
|
|
@@ -1650,7 +1863,7 @@ function collectChainSessionFiles(
|
|
|
1650
1863
|
|
|
1651
1864
|
function collectChainThinkingOverrides(
|
|
1652
1865
|
chain: ChainStep[],
|
|
1653
|
-
thinkingOverrideForTask:
|
|
1866
|
+
thinkingOverrideForTask: ForkThinkingOverrideForTask,
|
|
1654
1867
|
dynamicFanoutMaxItems?: number,
|
|
1655
1868
|
): (AgentConfig["thinking"] | undefined)[] {
|
|
1656
1869
|
const thinkingOverrides: (AgentConfig["thinking"] | undefined)[] = [];
|
|
@@ -1658,7 +1871,7 @@ function collectChainThinkingOverrides(
|
|
|
1658
1871
|
for (const step of chain) {
|
|
1659
1872
|
if (isParallelStep(step)) {
|
|
1660
1873
|
for (const task of step.parallel) {
|
|
1661
|
-
thinkingOverrides.push(thinkingOverrideForTask(task.agent, flatIndex));
|
|
1874
|
+
thinkingOverrides.push(thinkingOverrideForTask(task.agent, flatIndex, task.model));
|
|
1662
1875
|
flatIndex++;
|
|
1663
1876
|
}
|
|
1664
1877
|
continue;
|
|
@@ -1666,17 +1879,42 @@ function collectChainThinkingOverrides(
|
|
|
1666
1879
|
if (isDynamicParallelStep(step)) {
|
|
1667
1880
|
const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
|
|
1668
1881
|
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) {
|
|
1669
|
-
thinkingOverrides.push(thinkingOverrideForTask(step.parallel.agent, flatIndex));
|
|
1882
|
+
thinkingOverrides.push(thinkingOverrideForTask(step.parallel.agent, flatIndex, step.parallel.model));
|
|
1670
1883
|
flatIndex++;
|
|
1671
1884
|
}
|
|
1672
1885
|
continue;
|
|
1673
1886
|
}
|
|
1674
|
-
|
|
1887
|
+
const sequential = step as SequentialStep;
|
|
1888
|
+
thinkingOverrides.push(thinkingOverrideForTask(sequential.agent, flatIndex, sequential.model));
|
|
1675
1889
|
flatIndex++;
|
|
1676
1890
|
}
|
|
1677
1891
|
return thinkingOverrides;
|
|
1678
1892
|
}
|
|
1679
1893
|
|
|
1894
|
+
function firstChainAgent(chain: ChainStep[]): string | undefined {
|
|
1895
|
+
const first = chain[0];
|
|
1896
|
+
if (!first) return undefined;
|
|
1897
|
+
if (isParallelStep(first)) return first.parallel[0]?.agent;
|
|
1898
|
+
if (isDynamicParallelStep(first)) return first.parallel.agent;
|
|
1899
|
+
return (first as SequentialStep).agent;
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
function firstRawChainTask(chain: ChainStep[]): string | undefined {
|
|
1903
|
+
const first = chain[0];
|
|
1904
|
+
if (!first) return undefined;
|
|
1905
|
+
if (isParallelStep(first)) return first.parallel[0]?.task;
|
|
1906
|
+
if (isDynamicParallelStep(first)) return first.parallel.task;
|
|
1907
|
+
return (first as SequentialStep).task;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
function resolveAsyncEventGoal(workflowTask: string | undefined, rawChain: ChainStep[], unwrapForkFallback = false): string {
|
|
1911
|
+
if (workflowTask?.trim()) return workflowTask;
|
|
1912
|
+
const fallback = firstRawChainTask(rawChain) || "";
|
|
1913
|
+
if (!unwrapForkFallback) return fallback;
|
|
1914
|
+
const forkPrefix = `${DEFAULT_FORK_PREAMBLE}\n\nTask:\n`;
|
|
1915
|
+
return fallback.startsWith(forkPrefix) ? fallback.slice(forkPrefix.length) : fallback;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1680
1918
|
function wrapChainTasksForFork(chain: ChainStep[], contextPolicy: AgentDefaultContextPolicy): ChainStep[] {
|
|
1681
1919
|
return chain.map((step, stepIndex) => {
|
|
1682
1920
|
if (isParallelStep(step)) {
|
|
@@ -1714,17 +1952,17 @@ function wrapChainTasksForFork(chain: ChainStep[], contextPolicy: AgentDefaultCo
|
|
|
1714
1952
|
function preflightForkSessionsForStaticTasks(
|
|
1715
1953
|
params: SubagentParamsLike,
|
|
1716
1954
|
contextPolicy: AgentDefaultContextPolicy,
|
|
1717
|
-
sessionFileForTask:
|
|
1955
|
+
sessionFileForTask: ForkSessionFileForTask,
|
|
1718
1956
|
dynamicFanoutMaxItems?: number,
|
|
1719
1957
|
): void {
|
|
1720
1958
|
if (!contextPolicy.usesFork) return;
|
|
1721
1959
|
if (params.agent) {
|
|
1722
|
-
if (shouldForkAgent(contextPolicy, params.agent)) sessionFileForTask(params.agent, 0);
|
|
1960
|
+
if (shouldForkAgent(contextPolicy, params.agent)) sessionFileForTask(params.agent, 0, params.model);
|
|
1723
1961
|
return;
|
|
1724
1962
|
}
|
|
1725
1963
|
if (params.tasks) {
|
|
1726
1964
|
params.tasks.forEach((task, index) => {
|
|
1727
|
-
if (shouldForkAgent(contextPolicy, task.agent)) sessionFileForTask(task.agent, index);
|
|
1965
|
+
if (shouldForkAgent(contextPolicy, task.agent)) sessionFileForTask(task.agent, index, task.model);
|
|
1728
1966
|
});
|
|
1729
1967
|
return;
|
|
1730
1968
|
}
|
|
@@ -1733,7 +1971,7 @@ function preflightForkSessionsForStaticTasks(
|
|
|
1733
1971
|
for (const step of params.chain) {
|
|
1734
1972
|
if (isParallelStep(step)) {
|
|
1735
1973
|
for (const task of step.parallel) {
|
|
1736
|
-
if (shouldForkAgent(contextPolicy, task.agent)) sessionFileForTask(task.agent, flatIndex);
|
|
1974
|
+
if (shouldForkAgent(contextPolicy, task.agent)) sessionFileForTask(task.agent, flatIndex, task.model);
|
|
1737
1975
|
flatIndex++;
|
|
1738
1976
|
}
|
|
1739
1977
|
continue;
|
|
@@ -1741,13 +1979,13 @@ function preflightForkSessionsForStaticTasks(
|
|
|
1741
1979
|
if (isDynamicParallelStep(step)) {
|
|
1742
1980
|
const maxItems = step.expand.maxItems ?? dynamicFanoutMaxItems ?? 0;
|
|
1743
1981
|
if (shouldForkAgent(contextPolicy, step.parallel.agent)) {
|
|
1744
|
-
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) sessionFileForTask(step.parallel.agent, flatIndex + itemIndex);
|
|
1982
|
+
for (let itemIndex = 0; itemIndex < maxItems; itemIndex++) sessionFileForTask(step.parallel.agent, flatIndex + itemIndex, step.parallel.model);
|
|
1745
1983
|
}
|
|
1746
1984
|
flatIndex += maxItems;
|
|
1747
1985
|
continue;
|
|
1748
1986
|
}
|
|
1749
1987
|
const sequential = step as SequentialStep;
|
|
1750
|
-
if (shouldForkAgent(contextPolicy, sequential.agent)) sessionFileForTask(sequential.agent, flatIndex);
|
|
1988
|
+
if (shouldForkAgent(contextPolicy, sequential.agent)) sessionFileForTask(sequential.agent, flatIndex, sequential.model);
|
|
1751
1989
|
flatIndex++;
|
|
1752
1990
|
}
|
|
1753
1991
|
}
|
|
@@ -1814,6 +2052,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1814
2052
|
currentModelProvider: ctx.model?.provider,
|
|
1815
2053
|
currentModel: ctx.model,
|
|
1816
2054
|
modelScope: data.modelScope,
|
|
2055
|
+
interactive: ctx.hasUI,
|
|
1817
2056
|
};
|
|
1818
2057
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
1819
2058
|
const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth);
|
|
@@ -1822,22 +2061,20 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1822
2061
|
const childIntercomTarget = intercomBridge.active ? (agent: string, index: number) => resolveSubagentIntercomTarget(id, agent, index) : undefined;
|
|
1823
2062
|
|
|
1824
2063
|
if (hasTasks && params.tasks) {
|
|
1825
|
-
const agentConfigs = params.tasks.map((task) => agents.find((agent) => agent.name === task.agent));
|
|
1826
|
-
const modelOverrides = params.tasks.map((task, index) =>
|
|
1827
|
-
resolveSubagentModelOverride(task.model ?? agentConfigs[index]?.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope, source: task.model ? "explicit" : "inherited" }),
|
|
1828
|
-
);
|
|
1829
2064
|
const skillOverrides = params.tasks.map((task) => normalizeSkillInput(task.skill));
|
|
1830
2065
|
const parallelTasks = params.tasks.map((task, index) => ({
|
|
1831
2066
|
agent: task.agent,
|
|
1832
2067
|
task: shouldForkAgent(contextPolicy, task.agent) ? wrapForkTask(task.task) : task.task,
|
|
1833
2068
|
cwd: task.cwd,
|
|
1834
|
-
...(
|
|
2069
|
+
...(task.model !== undefined ? { model: task.model } : {}),
|
|
1835
2070
|
...(skillOverrides[index] !== undefined ? { skill: skillOverrides[index] } : {}),
|
|
1836
|
-
...(task.output
|
|
2071
|
+
...(task.output !== undefined && task.output !== true ? { output: task.output } : {}),
|
|
1837
2072
|
...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
|
|
1838
2073
|
...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
|
|
1839
2074
|
...(task.progress !== undefined ? { progress: task.progress } : {}),
|
|
1840
2075
|
...(task.toolBudget !== undefined ? { toolBudget: task.toolBudget } : {}),
|
|
2076
|
+
...(task.outputSchema !== undefined ? { outputSchema: task.outputSchema } : {}),
|
|
2077
|
+
...(task.agentContract !== undefined ? { agentContract: task.agentContract } : {}),
|
|
1841
2078
|
...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
|
|
1842
2079
|
}));
|
|
1843
2080
|
return executeAsyncChain(id, {
|
|
@@ -1847,6 +2084,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1847
2084
|
worktree: params.worktree,
|
|
1848
2085
|
}],
|
|
1849
2086
|
resultMode: "parallel",
|
|
2087
|
+
goal: params.tasks[0]?.task ?? "",
|
|
1850
2088
|
agents,
|
|
1851
2089
|
ctx: asyncCtx,
|
|
1852
2090
|
availableModels,
|
|
@@ -1857,13 +2095,16 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1857
2095
|
shareEnabled,
|
|
1858
2096
|
sessionRoot,
|
|
1859
2097
|
chainSkills: [],
|
|
1860
|
-
sessionFilesByFlatIndex: params.tasks.map((task, index) => sessionFileForTask(task.agent, index)),
|
|
1861
|
-
thinkingOverridesByFlatIndex: params.tasks.map((task, index) => thinkingOverrideForTask(task.agent, index)),
|
|
2098
|
+
sessionFilesByFlatIndex: params.tasks.map((task, index) => sessionFileForTask(task.agent, index, task.model)),
|
|
2099
|
+
thinkingOverridesByFlatIndex: params.tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, task.model)),
|
|
2100
|
+
contextForAgent: contextPolicy.contextForAgent,
|
|
1862
2101
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2102
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
1863
2103
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1864
2104
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1865
2105
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
1866
2106
|
controlConfig,
|
|
2107
|
+
agentContract: params.agentContract,
|
|
1867
2108
|
controlIntercomTarget,
|
|
1868
2109
|
childIntercomTarget,
|
|
1869
2110
|
nestedRoute,
|
|
@@ -1871,6 +2112,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1871
2112
|
turnBudget: data.turnBudget,
|
|
1872
2113
|
toolBudget: data.toolBudget,
|
|
1873
2114
|
configToolBudget: data.configToolBudget,
|
|
2115
|
+
capabilityCeiling: data.capabilityCeiling,
|
|
1874
2116
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
1875
2117
|
});
|
|
1876
2118
|
}
|
|
@@ -1878,10 +2120,12 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1878
2120
|
if (hasChain && params.chain) {
|
|
1879
2121
|
const normalized = normalizeSkillInput(params.skill);
|
|
1880
2122
|
const chainSkills = normalized === false ? [] : (normalized ?? []);
|
|
1881
|
-
const
|
|
2123
|
+
const rawChain = params.chain as ChainStep[];
|
|
2124
|
+
const chain = wrapChainTasksForFork(rawChain, contextPolicy);
|
|
1882
2125
|
return executeAsyncChain(id, {
|
|
1883
2126
|
chain,
|
|
1884
2127
|
task: params.task,
|
|
2128
|
+
goal: resolveAsyncEventGoal(params.task, rawChain),
|
|
1885
2129
|
agents,
|
|
1886
2130
|
ctx: asyncCtx,
|
|
1887
2131
|
availableModels,
|
|
@@ -1894,12 +2138,15 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1894
2138
|
chainSkills,
|
|
1895
2139
|
sessionFilesByFlatIndex: collectChainSessionFiles(chain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
1896
2140
|
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(chain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2141
|
+
contextForAgent: contextPolicy.contextForAgent,
|
|
1897
2142
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
1898
2143
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2144
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
1899
2145
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1900
2146
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1901
2147
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
1902
2148
|
controlConfig,
|
|
2149
|
+
agentContract: params.agentContract,
|
|
1903
2150
|
controlIntercomTarget,
|
|
1904
2151
|
childIntercomTarget,
|
|
1905
2152
|
nestedRoute,
|
|
@@ -1907,6 +2154,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1907
2154
|
turnBudget: data.turnBudget,
|
|
1908
2155
|
toolBudget: data.toolBudget,
|
|
1909
2156
|
configToolBudget: data.configToolBudget,
|
|
2157
|
+
capabilityCeiling: data.capabilityCeiling,
|
|
1910
2158
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
1911
2159
|
});
|
|
1912
2160
|
}
|
|
@@ -1926,10 +2174,11 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1926
2174
|
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
1927
2175
|
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
1928
2176
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
|
|
1929
|
-
const modelOverride =
|
|
2177
|
+
const modelOverride = resolveEffectiveSubagentModel(params.model as string | undefined, a.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope });
|
|
1930
2178
|
return executeAsyncSingle(id, {
|
|
1931
2179
|
agent: params.agent!,
|
|
1932
2180
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(params.task ?? "") : (params.task ?? ""),
|
|
2181
|
+
goal: params.task ?? "",
|
|
1933
2182
|
agentConfig: a,
|
|
1934
2183
|
ctx: asyncCtx,
|
|
1935
2184
|
availableModels,
|
|
@@ -1939,14 +2188,16 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1939
2188
|
artifactConfig,
|
|
1940
2189
|
shareEnabled,
|
|
1941
2190
|
sessionRoot,
|
|
1942
|
-
sessionFile: sessionFileForTask(params.agent!, 0),
|
|
2191
|
+
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
2192
|
+
context: contextPolicy.contextForAgent(params.agent!),
|
|
1943
2193
|
skills,
|
|
1944
2194
|
output: effectiveOutput,
|
|
1945
2195
|
outputMode: effectiveOutputMode,
|
|
1946
2196
|
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
1947
2197
|
modelOverride,
|
|
1948
|
-
thinkingOverride: thinkingOverrideForTask(params.agent!, 0),
|
|
2198
|
+
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
1949
2199
|
maxSubagentDepth,
|
|
2200
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
1950
2201
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
1951
2202
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
1952
2203
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
@@ -1954,11 +2205,14 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
1954
2205
|
controlIntercomTarget,
|
|
1955
2206
|
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
|
|
1956
2207
|
nestedRoute,
|
|
2208
|
+
agentContract: params.agentContract,
|
|
2209
|
+
structuredOutputSchema: params.outputSchema,
|
|
1957
2210
|
acceptance: params.acceptance,
|
|
1958
2211
|
timeoutMs: data.timeoutMs,
|
|
1959
2212
|
turnBudget: data.turnBudget,
|
|
1960
2213
|
toolBudget: data.toolBudget,
|
|
1961
2214
|
configToolBudget: data.configToolBudget,
|
|
2215
|
+
capabilityCeiling: data.capabilityCeiling,
|
|
1962
2216
|
});
|
|
1963
2217
|
}
|
|
1964
2218
|
|
|
@@ -2007,6 +2261,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2007
2261
|
sessionFileForIndex,
|
|
2008
2262
|
sessionFileForTask,
|
|
2009
2263
|
thinkingOverrideForTask,
|
|
2264
|
+
contextForAgent: contextPolicy.contextForAgent,
|
|
2010
2265
|
artifactsDir,
|
|
2011
2266
|
artifactConfig,
|
|
2012
2267
|
includeProgress: params.includeProgress,
|
|
@@ -2014,6 +2269,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2014
2269
|
onUpdate,
|
|
2015
2270
|
onControlEvent,
|
|
2016
2271
|
controlConfig,
|
|
2272
|
+
agentContract: params.agentContract,
|
|
2017
2273
|
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
|
|
2018
2274
|
orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
2019
2275
|
foregroundControl,
|
|
@@ -2028,10 +2284,11 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2028
2284
|
timeoutMs: data.timeoutMs,
|
|
2029
2285
|
deadlineAt: data.deadlineAt,
|
|
2030
2286
|
turnBudget: data.turnBudget,
|
|
2031
|
-
onDetachedExit: (index, result) => updateRememberedForegroundChild(deps.state, { runId, mode: "chain", cwd: effectiveCwd, index, result }),
|
|
2287
|
+
onDetachedExit: (index, result) => updateRememberedForegroundChild(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, index, result, events: deps.pi.events }),
|
|
2032
2288
|
toolBudget: data.toolBudget,
|
|
2033
2289
|
configToolBudget: data.configToolBudget,
|
|
2034
2290
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2291
|
+
capabilityCeiling: data.capabilityCeiling,
|
|
2035
2292
|
});
|
|
2036
2293
|
|
|
2037
2294
|
if (chainResult.requestedAsync) {
|
|
@@ -2051,11 +2308,15 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2051
2308
|
currentModelProvider: ctx.model?.provider,
|
|
2052
2309
|
currentModel: ctx.model,
|
|
2053
2310
|
modelScope: data.modelScope,
|
|
2311
|
+
interactive: ctx.hasUI,
|
|
2054
2312
|
};
|
|
2055
|
-
const
|
|
2313
|
+
const rawAsyncChain = chainResult.requestedAsync.chain;
|
|
2314
|
+
const asyncChain = wrapChainTasksForFork(rawAsyncChain, contextPolicy);
|
|
2315
|
+
const firstAgent = firstChainAgent(rawAsyncChain);
|
|
2056
2316
|
return executeAsyncChain(id, {
|
|
2057
2317
|
chain: asyncChain,
|
|
2058
2318
|
task: params.task,
|
|
2319
|
+
goal: resolveAsyncEventGoal(params.task, rawAsyncChain, firstAgent ? shouldForkAgent(contextPolicy, firstAgent) : false),
|
|
2059
2320
|
agents,
|
|
2060
2321
|
ctx: asyncCtx,
|
|
2061
2322
|
availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
|
|
@@ -2068,12 +2329,15 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2068
2329
|
chainSkills: chainResult.requestedAsync.chainSkills,
|
|
2069
2330
|
sessionFilesByFlatIndex: collectChainSessionFiles(asyncChain, sessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2070
2331
|
thinkingOverridesByFlatIndex: collectChainThinkingOverrides(asyncChain, thinkingOverrideForTask, deps.config.chain?.dynamicFanout?.maxItems),
|
|
2332
|
+
contextForAgent: contextPolicy.contextForAgent,
|
|
2071
2333
|
dynamicFanoutMaxItems: deps.config.chain?.dynamicFanout?.maxItems,
|
|
2072
2334
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2335
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
2073
2336
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2074
2337
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2075
2338
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
2076
2339
|
controlConfig,
|
|
2340
|
+
agentContract: params.agentContract,
|
|
2077
2341
|
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
2078
2342
|
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
2079
2343
|
nestedRoute: data.nestedRoute,
|
|
@@ -2081,6 +2345,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2081
2345
|
turnBudget: data.turnBudget,
|
|
2082
2346
|
toolBudget: data.toolBudget,
|
|
2083
2347
|
configToolBudget: data.configToolBudget,
|
|
2348
|
+
capabilityCeiling: data.capabilityCeiling,
|
|
2084
2349
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2085
2350
|
});
|
|
2086
2351
|
}
|
|
@@ -2092,7 +2357,7 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2092
2357
|
rawChainDetails.totalCost = sumResultsCost(rawChainDetails.results);
|
|
2093
2358
|
}
|
|
2094
2359
|
const chainDetails = rawChainDetails ? compactForegroundDetails(rawChainDetails) : undefined;
|
|
2095
|
-
if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, results: chainDetails.results });
|
|
2360
|
+
if (chainDetails) rememberForegroundRun(deps.state, { runId, mode: "chain", cwd: effectiveCwd, sessionId: data.parentSessionId, results: chainDetails.results });
|
|
2096
2361
|
const intercomReceipt = chainDetails && !chainDetails.results.some((result) => result.interrupted || result.detached)
|
|
2097
2362
|
? await maybeBuildForegroundIntercomReceipt({
|
|
2098
2363
|
pi: deps.pi,
|
|
@@ -2117,16 +2382,19 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
2117
2382
|
interface ForegroundParallelRunInput {
|
|
2118
2383
|
tasks: TaskParam[];
|
|
2119
2384
|
taskTexts: string[];
|
|
2385
|
+
taskDescriptions: string[];
|
|
2120
2386
|
agents: AgentConfig[];
|
|
2121
2387
|
ctx: ExtensionContext;
|
|
2122
2388
|
state: SubagentState;
|
|
2123
2389
|
intercomEvents: IntercomEventBus;
|
|
2390
|
+
parentSessionId: string | null;
|
|
2391
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
2124
2392
|
signal: AbortSignal;
|
|
2125
2393
|
runId: string;
|
|
2126
2394
|
sessionDirForIndex: (idx?: number) => string | undefined;
|
|
2127
2395
|
sessionFileForIndex: (idx?: number) => string | undefined;
|
|
2128
|
-
sessionFileForTask:
|
|
2129
|
-
thinkingOverrideForTask:
|
|
2396
|
+
sessionFileForTask: ForkSessionFileForTask;
|
|
2397
|
+
thinkingOverrideForTask: ForkThinkingOverrideForTask;
|
|
2130
2398
|
shareEnabled: boolean;
|
|
2131
2399
|
artifactConfig: ArtifactConfig;
|
|
2132
2400
|
artifactsDir: string;
|
|
@@ -2135,12 +2403,14 @@ interface ForegroundParallelRunInput {
|
|
|
2135
2403
|
paramsCwd: string;
|
|
2136
2404
|
progressDir: string;
|
|
2137
2405
|
maxSubagentDepths: number[];
|
|
2406
|
+
waitToolEnabled?: boolean;
|
|
2138
2407
|
availableModels: ModelInfo[];
|
|
2139
2408
|
modelScope?: ModelScopeConfig;
|
|
2140
2409
|
modelOverrides: (string | undefined)[];
|
|
2141
2410
|
behaviors: Array<ReturnType<typeof resolveStepBehavior>>;
|
|
2142
2411
|
firstProgressIndex: number;
|
|
2143
2412
|
controlConfig: ResolvedControlConfig;
|
|
2413
|
+
contextPolicy: AgentDefaultContextPolicy;
|
|
2144
2414
|
onControlEvent?: (event: ControlEvent) => void;
|
|
2145
2415
|
childIntercomTarget?: (agent: string, index: number) => string | undefined;
|
|
2146
2416
|
orchestratorIntercomTarget?: string;
|
|
@@ -2155,8 +2425,8 @@ interface ForegroundParallelRunInput {
|
|
|
2155
2425
|
deadlineAt?: number;
|
|
2156
2426
|
turnBudget?: ResolvedTurnBudget;
|
|
2157
2427
|
toolBudgets: (ResolvedToolBudget | undefined)[];
|
|
2428
|
+
agentContract?: AgentContract;
|
|
2158
2429
|
}
|
|
2159
|
-
|
|
2160
2430
|
function buildParallelModeError(message: string): AgentToolResult<Details> {
|
|
2161
2431
|
return {
|
|
2162
2432
|
content: [{ type: "text", text: message }],
|
|
@@ -2229,15 +2499,52 @@ function resolveParallelTaskCwd(
|
|
|
2229
2499
|
return resolveChildCwd(paramsCwd, task.cwd);
|
|
2230
2500
|
}
|
|
2231
2501
|
|
|
2232
|
-
function
|
|
2233
|
-
worktreeSetup: WorktreeSetup
|
|
2234
|
-
artifactsDir: string
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2502
|
+
function finalizeParallelWorktreeHandoff(input: {
|
|
2503
|
+
worktreeSetup: WorktreeSetup;
|
|
2504
|
+
artifactsDir: string;
|
|
2505
|
+
runId: string;
|
|
2506
|
+
cwd: string;
|
|
2507
|
+
tasks: TaskParam[];
|
|
2508
|
+
results: SingleResult[];
|
|
2509
|
+
}): { suffix: string; reference?: NonNullable<Details["parallelHandoff"]> } {
|
|
2510
|
+
const diffsDir = path.join(input.artifactsDir, "worktree-diffs", input.runId);
|
|
2511
|
+
const diffs = diffWorktrees(input.worktreeSetup, input.tasks.map((task) => task.agent), diffsDir);
|
|
2512
|
+
const cleanup = cleanupWorktrees(input.worktreeSetup);
|
|
2513
|
+
const diffSummary = formatWorktreeDiffSummary(diffs);
|
|
2514
|
+
try {
|
|
2515
|
+
const reference = writeParallelHandoffGroup({
|
|
2516
|
+
manifestPath: parallelHandoffPath(input.artifactsDir, input.runId),
|
|
2517
|
+
runId: input.runId,
|
|
2518
|
+
mode: "parallel",
|
|
2519
|
+
source: "foreground",
|
|
2520
|
+
cwd: input.cwd,
|
|
2521
|
+
stepIndex: 0,
|
|
2522
|
+
flatStartIndex: 0,
|
|
2523
|
+
setup: input.worktreeSetup,
|
|
2524
|
+
diffs,
|
|
2525
|
+
cleanup,
|
|
2526
|
+
results: input.results.map((result) => ({
|
|
2527
|
+
agent: result.agent,
|
|
2528
|
+
status: resolveSubagentResultStatus({
|
|
2529
|
+
exitCode: result.exitCode,
|
|
2530
|
+
interrupted: result.interrupted,
|
|
2531
|
+
detached: result.detached,
|
|
2532
|
+
state: result.stopped ? "stopped" : undefined,
|
|
2533
|
+
}),
|
|
2534
|
+
summary: resultSummaryForIntercom(result),
|
|
2535
|
+
...(result.artifactPaths?.outputPath ? { outputPath: result.artifactPaths.outputPath } : {}),
|
|
2536
|
+
...(result.structuredOutput !== undefined ? { structuredOutput: result.structuredOutput } : {}),
|
|
2537
|
+
...(result.structuredOutputPath ? { structuredOutputPath: result.structuredOutputPath } : {}),
|
|
2538
|
+
...(result.sessionFile ? { sessionPath: result.sessionFile } : {}),
|
|
2539
|
+
})),
|
|
2540
|
+
});
|
|
2541
|
+
return {
|
|
2542
|
+
suffix: [diffSummary, formatParallelHandoffReference(reference)].filter(Boolean).join("\n\n"),
|
|
2543
|
+
reference,
|
|
2544
|
+
};
|
|
2545
|
+
} catch (error) {
|
|
2546
|
+
return { suffix: [diffSummary, formatParallelHandoffError(error)].filter(Boolean).join("\n\n") };
|
|
2547
|
+
}
|
|
2241
2548
|
}
|
|
2242
2549
|
|
|
2243
2550
|
function findDuplicateParallelOutputPath(input: {
|
|
@@ -2268,9 +2575,8 @@ function findDuplicateParallelOutputPath(input: {
|
|
|
2268
2575
|
async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Promise<SingleResult[]> {
|
|
2269
2576
|
// Pre-warm fork session files sequentially before concurrent dispatch to avoid
|
|
2270
2577
|
// races where multiple workers simultaneously try to branch the same parent session.
|
|
2271
|
-
// sessionFileForIndex caches results, so these calls return instantly inside mapConcurrent.
|
|
2272
2578
|
for (let i = 0; i < input.tasks.length; i++) {
|
|
2273
|
-
input.
|
|
2579
|
+
input.sessionFileForTask(input.tasks[i]!.agent, i, input.modelOverrides[i]);
|
|
2274
2580
|
}
|
|
2275
2581
|
return mapConcurrent(input.tasks, input.concurrencyLimit, async (task, index) => {
|
|
2276
2582
|
const behavior = input.behaviors[index];
|
|
@@ -2283,27 +2589,31 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2283
2589
|
? buildChainInstructions({ ...behavior, output: false, reads: false }, input.progressDir, index === input.firstProgressIndex)
|
|
2284
2590
|
: { prefix: "", suffix: "" };
|
|
2285
2591
|
const outputPath = resolveSingleOutputPath(behavior?.output, input.ctx.cwd, taskCwd, input.outputBaseDir);
|
|
2592
|
+
const agentConfig = input.agents.find((agent) => agent.name === task.agent);
|
|
2286
2593
|
const taskText = injectSingleOutputInstruction(
|
|
2287
2594
|
`${readInstructions.prefix}${input.taskTexts[index]!}${progressInstructions.suffix}`,
|
|
2288
2595
|
outputPath,
|
|
2596
|
+
agentConfig,
|
|
2289
2597
|
);
|
|
2290
2598
|
const interruptController = new AbortController();
|
|
2291
2599
|
if (input.foregroundControl) {
|
|
2292
|
-
input.foregroundControl
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
};
|
|
2600
|
+
beginForegroundChild(input.foregroundControl, {
|
|
2601
|
+
index,
|
|
2602
|
+
agent: task.agent,
|
|
2603
|
+
description: input.taskDescriptions[index],
|
|
2604
|
+
interrupt: () => {
|
|
2605
|
+
if (interruptController.signal.aborted) return false;
|
|
2606
|
+
interruptController.abort();
|
|
2607
|
+
return true;
|
|
2608
|
+
},
|
|
2609
|
+
});
|
|
2303
2610
|
}
|
|
2304
|
-
const
|
|
2611
|
+
const structuredRuntime = task.outputSchema
|
|
2612
|
+
? createStructuredOutputRuntime(task.outputSchema, path.join(input.artifactsDir, "structured-output", input.runId))
|
|
2613
|
+
: undefined;
|
|
2305
2614
|
return runSync(input.ctx.cwd, input.agents, task.agent, taskText, {
|
|
2306
2615
|
parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
|
|
2616
|
+
context: input.contextPolicy.contextForAgent(task.agent),
|
|
2307
2617
|
cwd: taskCwd,
|
|
2308
2618
|
signal: input.signal,
|
|
2309
2619
|
interruptSignal: interruptController.signal,
|
|
@@ -2312,7 +2622,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2312
2622
|
runId: input.runId,
|
|
2313
2623
|
index,
|
|
2314
2624
|
sessionDir: input.sessionDirForIndex(index),
|
|
2315
|
-
sessionFile: input.sessionFileForTask(task.agent, index),
|
|
2625
|
+
sessionFile: input.sessionFileForTask(task.agent, index, input.modelOverrides[index]),
|
|
2316
2626
|
share: input.shareEnabled,
|
|
2317
2627
|
artifactsDir: input.artifactConfig.enabled ? input.artifactsDir : undefined,
|
|
2318
2628
|
artifactConfig: input.artifactConfig,
|
|
@@ -2320,18 +2630,22 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2320
2630
|
outputPath,
|
|
2321
2631
|
outputMode: behavior?.outputMode,
|
|
2322
2632
|
maxSubagentDepth: input.maxSubagentDepths[index],
|
|
2633
|
+
waitToolEnabled: input.waitToolEnabled,
|
|
2634
|
+
capabilityCeiling: input.capabilityCeiling,
|
|
2323
2635
|
controlConfig: input.controlConfig,
|
|
2324
2636
|
onControlEvent: input.onControlEvent,
|
|
2325
|
-
onDetachedExit: (result) => updateRememberedForegroundChild(input.state, { runId: input.runId, mode: "parallel", cwd: taskCwd, index, result }),
|
|
2637
|
+
onDetachedExit: (result) => updateRememberedForegroundChild(input.state, { runId: input.runId, mode: "parallel", cwd: taskCwd, sessionId: input.parentSessionId, index, result, events: input.intercomEvents }),
|
|
2326
2638
|
intercomSessionName: input.childIntercomTarget?.(task.agent, index),
|
|
2327
2639
|
orchestratorIntercomTarget: input.orchestratorIntercomTarget,
|
|
2328
2640
|
nestedRoute: input.foregroundControl?.nestedRoute,
|
|
2329
2641
|
modelOverride: input.modelOverrides[index],
|
|
2330
|
-
thinkingOverride: input.thinkingOverrideForTask(task.agent, index),
|
|
2642
|
+
thinkingOverride: input.thinkingOverrideForTask(task.agent, index, input.modelOverrides[index]),
|
|
2331
2643
|
availableModels: input.availableModels,
|
|
2332
2644
|
preferredModelProvider: input.ctx.model?.provider,
|
|
2333
2645
|
modelScope: input.modelScope,
|
|
2334
2646
|
skills: effectiveSkills === false ? [] : effectiveSkills,
|
|
2647
|
+
structuredOutput: structuredRuntime,
|
|
2648
|
+
agentContract: task.agentContract ?? input.agentContract,
|
|
2335
2649
|
acceptance: task.acceptance,
|
|
2336
2650
|
acceptanceContext: { mode: "parallel" },
|
|
2337
2651
|
timeoutMs: input.timeoutMs,
|
|
@@ -2343,18 +2657,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2343
2657
|
const stepResults = progressUpdate.details?.results || [];
|
|
2344
2658
|
const stepProgress = progressUpdate.details?.progress || [];
|
|
2345
2659
|
if (input.foregroundControl && stepProgress.length > 0) {
|
|
2346
|
-
|
|
2347
|
-
input.foregroundControl.currentAgent = task.agent;
|
|
2348
|
-
input.foregroundControl.currentIndex = index;
|
|
2349
|
-
input.foregroundControl.currentActivityState = current?.activityState;
|
|
2350
|
-
input.foregroundControl.lastActivityAt = current?.lastActivityAt;
|
|
2351
|
-
input.foregroundControl.currentTool = current?.currentTool;
|
|
2352
|
-
input.foregroundControl.currentToolStartedAt = current?.currentToolStartedAt;
|
|
2353
|
-
input.foregroundControl.currentPath = current?.currentPath;
|
|
2354
|
-
input.foregroundControl.turnCount = current?.turnCount;
|
|
2355
|
-
input.foregroundControl.tokens = current?.tokens;
|
|
2356
|
-
input.foregroundControl.toolCount = current?.toolCount;
|
|
2357
|
-
input.foregroundControl.updatedAt = Date.now();
|
|
2660
|
+
updateForegroundChild(input.foregroundControl, index, stepProgress[0]);
|
|
2358
2661
|
}
|
|
2359
2662
|
if (stepResults.length > 0) input.liveResults[index] = stepResults[0];
|
|
2360
2663
|
if (stepProgress.length > 0) input.liveProgress[index] = stepProgress[0];
|
|
@@ -2373,10 +2676,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
2373
2676
|
}
|
|
2374
2677
|
: undefined,
|
|
2375
2678
|
}).finally(() => {
|
|
2376
|
-
if (input.foregroundControl
|
|
2377
|
-
input.foregroundControl.interrupt = undefined;
|
|
2378
|
-
input.foregroundControl.updatedAt = Date.now();
|
|
2379
|
-
}
|
|
2679
|
+
if (input.foregroundControl) finishForegroundChild(input.foregroundControl, index);
|
|
2380
2680
|
});
|
|
2381
2681
|
}, input.globalSemaphore);
|
|
2382
2682
|
}
|
|
@@ -2453,15 +2753,15 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2453
2753
|
normalizeSkillInput(t.skill),
|
|
2454
2754
|
);
|
|
2455
2755
|
const behaviorOverrides: StepOverrides[] = tasks.map((task, index) => ({
|
|
2456
|
-
...(task.output !== undefined
|
|
2756
|
+
...(task.output !== undefined && task.output !== true ? { output: task.output } : {}),
|
|
2457
2757
|
...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
|
|
2458
2758
|
...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
|
|
2459
2759
|
...(task.progress !== undefined ? { progress: task.progress } : {}),
|
|
2460
2760
|
...(skillOverrides[index] !== undefined ? { skills: skillOverrides[index] } : {}),
|
|
2461
|
-
...(task.model ? { model: task.model } : {}),
|
|
2761
|
+
...(task.model !== undefined ? { model: task.model } : {}),
|
|
2462
2762
|
}));
|
|
2463
2763
|
const modelOverrides: (string | undefined)[] = tasks.map((_, i) =>
|
|
2464
|
-
|
|
2764
|
+
resolveEffectiveSubagentModel(behaviorOverrides[i]?.model, agentConfigs[i]?.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope }),
|
|
2465
2765
|
);
|
|
2466
2766
|
|
|
2467
2767
|
if (params.clarify === true && ctx.hasUI) {
|
|
@@ -2495,8 +2795,8 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2495
2795
|
taskTexts = result.templates;
|
|
2496
2796
|
for (let i = 0; i < result.behaviorOverrides.length; i++) {
|
|
2497
2797
|
const override = result.behaviorOverrides[i];
|
|
2498
|
-
if (override?.model) {
|
|
2499
|
-
modelOverrides[i] =
|
|
2798
|
+
if (override?.model !== undefined) {
|
|
2799
|
+
modelOverrides[i] = resolveEffectiveSubagentModel(override.model, agentConfigs[i]?.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope });
|
|
2500
2800
|
behaviorOverrides[i]!.model = override.model;
|
|
2501
2801
|
}
|
|
2502
2802
|
if (override?.output !== undefined) behaviorOverrides[i]!.output = override.output;
|
|
@@ -2525,6 +2825,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2525
2825
|
currentModelProvider: ctx.model?.provider,
|
|
2526
2826
|
currentModel: ctx.model,
|
|
2527
2827
|
modelScope: data.modelScope,
|
|
2828
|
+
interactive: ctx.hasUI,
|
|
2528
2829
|
};
|
|
2529
2830
|
const parallelTasks = tasks.map((t, i) => {
|
|
2530
2831
|
const taskText = shouldForkAgent(contextPolicy, t.agent) ? wrapForkTask(taskTexts[i]!) : taskTexts[i]!;
|
|
@@ -2533,19 +2834,22 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2533
2834
|
agent: t.agent,
|
|
2534
2835
|
task: taskText,
|
|
2535
2836
|
cwd: t.cwd,
|
|
2536
|
-
...(
|
|
2837
|
+
...(behaviorOverrides[i]?.model !== undefined ? { model: behaviorOverrides[i]!.model } : {}),
|
|
2537
2838
|
...(skillOverrides[i] !== undefined ? { skill: skillOverrides[i] } : {}),
|
|
2538
2839
|
...(behaviorOverrides[i]?.output !== undefined ? { output: behaviorOverrides[i]!.output } : {}),
|
|
2539
2840
|
...(behaviorOverrides[i]?.outputMode !== undefined ? { outputMode: behaviorOverrides[i]!.outputMode } : {}),
|
|
2540
2841
|
...(behaviorOverrides[i]?.reads !== undefined ? { reads: behaviorOverrides[i]!.reads } : {}),
|
|
2541
2842
|
...(progress !== undefined ? { progress } : {}),
|
|
2542
2843
|
...(t.toolBudget !== undefined ? { toolBudget: t.toolBudget } : {}),
|
|
2844
|
+
...(t.outputSchema !== undefined ? { outputSchema: t.outputSchema } : {}),
|
|
2543
2845
|
...(t.acceptance !== undefined ? { acceptance: t.acceptance } : {}),
|
|
2846
|
+
...(t.agentContract !== undefined ? { agentContract: t.agentContract } : {}),
|
|
2544
2847
|
};
|
|
2545
2848
|
});
|
|
2546
2849
|
return executeAsyncChain(id, {
|
|
2547
2850
|
chain: [{ parallel: parallelTasks, concurrency: parallelConcurrency, worktree: params.worktree }],
|
|
2548
2851
|
resultMode: "parallel",
|
|
2852
|
+
goal: taskTexts[0] ?? "",
|
|
2549
2853
|
agents,
|
|
2550
2854
|
ctx: asyncCtx,
|
|
2551
2855
|
availableModels,
|
|
@@ -2556,23 +2860,35 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2556
2860
|
shareEnabled,
|
|
2557
2861
|
sessionRoot,
|
|
2558
2862
|
chainSkills: [],
|
|
2559
|
-
sessionFilesByFlatIndex: tasks.map((task, index) => sessionFileForTask(task.agent, index)),
|
|
2560
|
-
thinkingOverridesByFlatIndex: tasks.map((task, index) => thinkingOverrideForTask(task.agent, index)),
|
|
2863
|
+
sessionFilesByFlatIndex: tasks.map((task, index) => sessionFileForTask(task.agent, index, modelOverrides[index])),
|
|
2864
|
+
thinkingOverridesByFlatIndex: tasks.map((task, index) => thinkingOverrideForTask(task.agent, index, modelOverrides[index])),
|
|
2865
|
+
contextForAgent: contextPolicy.contextForAgent,
|
|
2561
2866
|
maxSubagentDepth: currentMaxSubagentDepth,
|
|
2867
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
2562
2868
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2563
2869
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2564
2870
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
2565
2871
|
controlConfig,
|
|
2872
|
+
agentContract: params.agentContract,
|
|
2566
2873
|
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
2567
2874
|
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
2875
|
+
nestedRoute: data.nestedRoute,
|
|
2568
2876
|
timeoutMs: data.timeoutMs,
|
|
2569
2877
|
turnBudget: data.turnBudget,
|
|
2878
|
+
toolBudget: data.toolBudget,
|
|
2879
|
+
configToolBudget: data.configToolBudget,
|
|
2570
2880
|
globalConcurrencyLimit: deps.config.globalConcurrencyLimit,
|
|
2571
2881
|
});
|
|
2572
2882
|
}
|
|
2573
2883
|
}
|
|
2574
2884
|
|
|
2575
|
-
const behaviors =
|
|
2885
|
+
const behaviors = tasks.map((task, index) => {
|
|
2886
|
+
let behavior = suppressProgressForReadOnlyTask(resolveStepBehavior(agentConfigs[index]!, behaviorOverrides[index]!), taskTexts[index]);
|
|
2887
|
+
if (behaviorOverrides[index]?.output === undefined && typeof behavior.output === "string" && !path.isAbsolute(behavior.output)) {
|
|
2888
|
+
behavior = { ...behavior, output: path.join("parallel-0", `${index}-${task.agent}`, behavior.output) };
|
|
2889
|
+
}
|
|
2890
|
+
return behavior;
|
|
2891
|
+
});
|
|
2576
2892
|
const firstProgressIndex = behaviors.findIndex((behavior) => behavior.progress);
|
|
2577
2893
|
const liveResults: (SingleResult | undefined)[] = new Array(tasks.length).fill(undefined);
|
|
2578
2894
|
const liveProgress: (AgentProgress | undefined)[] = new Array(tasks.length).fill(undefined);
|
|
@@ -2588,6 +2904,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2588
2904
|
);
|
|
2589
2905
|
if (errorResult) return errorResult;
|
|
2590
2906
|
|
|
2907
|
+
let worktreeFinalized = false;
|
|
2591
2908
|
try {
|
|
2592
2909
|
const outputBaseDir = path.join(artifactsDir, "outputs", runId);
|
|
2593
2910
|
const duplicateOutputError = findDuplicateParallelOutputPath({
|
|
@@ -2610,6 +2927,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2610
2927
|
const parallelProgressDir = path.join(artifactsDir, "progress", runId);
|
|
2611
2928
|
if (parallelProgressPrecreated) writeInitialProgressFile(parallelProgressDir);
|
|
2612
2929
|
|
|
2930
|
+
const taskDescriptions = taskTexts.map((taskText) => taskText.trim());
|
|
2613
2931
|
for (let i = 0; i < taskTexts.length; i++) {
|
|
2614
2932
|
if (shouldForkAgent(contextPolicy, tasks[i]!.agent)) taskTexts[i] = wrapForkTask(taskTexts[i]!);
|
|
2615
2933
|
}
|
|
@@ -2618,10 +2936,13 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2618
2936
|
const results = await runForegroundParallelTasks({
|
|
2619
2937
|
tasks,
|
|
2620
2938
|
taskTexts,
|
|
2939
|
+
taskDescriptions,
|
|
2621
2940
|
agents,
|
|
2622
2941
|
ctx,
|
|
2623
2942
|
state: deps.state,
|
|
2624
2943
|
intercomEvents: deps.pi.events,
|
|
2944
|
+
parentSessionId: data.parentSessionId,
|
|
2945
|
+
capabilityCeiling: data.capabilityCeiling,
|
|
2625
2946
|
signal,
|
|
2626
2947
|
runId,
|
|
2627
2948
|
sessionDirForIndex,
|
|
@@ -2641,6 +2962,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2641
2962
|
behaviors,
|
|
2642
2963
|
firstProgressIndex: parallelProgressPrecreated ? -1 : firstProgressIndex,
|
|
2643
2964
|
controlConfig,
|
|
2965
|
+
contextPolicy,
|
|
2644
2966
|
onControlEvent,
|
|
2645
2967
|
childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(runId, agent, index) : undefined,
|
|
2646
2968
|
orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
@@ -2648,6 +2970,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2648
2970
|
concurrencyLimit: parallelConcurrency,
|
|
2649
2971
|
globalSemaphore: new Semaphore(deps.config.globalConcurrencyLimit ?? DEFAULT_GLOBAL_CONCURRENCY_LIMIT),
|
|
2650
2972
|
maxSubagentDepths,
|
|
2973
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
2651
2974
|
liveResults,
|
|
2652
2975
|
liveProgress,
|
|
2653
2976
|
onUpdate,
|
|
@@ -2656,6 +2979,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2656
2979
|
deadlineAt,
|
|
2657
2980
|
turnBudget: data.turnBudget,
|
|
2658
2981
|
toolBudgets,
|
|
2982
|
+
agentContract: params.agentContract,
|
|
2659
2983
|
});
|
|
2660
2984
|
for (let i = 0; i < results.length; i++) {
|
|
2661
2985
|
const run = results[i]!;
|
|
@@ -2671,6 +2995,11 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2671
2995
|
updateForegroundNestedProjection(foregroundControl);
|
|
2672
2996
|
attachRootChildrenToSteps(runId, results, foregroundControl.nestedChildren);
|
|
2673
2997
|
}
|
|
2998
|
+
let handoff: ReturnType<typeof finalizeParallelWorktreeHandoff> | undefined;
|
|
2999
|
+
if (worktreeSetup) {
|
|
3000
|
+
worktreeFinalized = true;
|
|
3001
|
+
handoff = finalizeParallelWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: effectiveCwd, tasks, results });
|
|
3002
|
+
}
|
|
2674
3003
|
const interrupted = results.find((result) => result.interrupted);
|
|
2675
3004
|
const details = compactForegroundDetails({
|
|
2676
3005
|
mode: "parallel",
|
|
@@ -2680,8 +3009,9 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2680
3009
|
artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
|
|
2681
3010
|
totalChildUsage: sumResultsUsage(results),
|
|
2682
3011
|
totalCost: sumResultsCost(results),
|
|
3012
|
+
...(handoff?.reference ? { parallelHandoff: handoff.reference } : {}),
|
|
2683
3013
|
});
|
|
2684
|
-
rememberForegroundRun(deps.state, { runId, mode: "parallel", cwd: effectiveCwd, results: details.results });
|
|
3014
|
+
rememberForegroundRun(deps.state, { runId, mode: "parallel", cwd: effectiveCwd, sessionId: data.parentSessionId, results: details.results });
|
|
2685
3015
|
if (interrupted) {
|
|
2686
3016
|
return {
|
|
2687
3017
|
content: [{ type: "text", text: `Parallel run paused after interrupt (${interrupted.agent}). Waiting for explicit next action.` }],
|
|
@@ -2692,7 +3022,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2692
3022
|
const detached = detachedIndex >= 0 ? results[detachedIndex] : undefined;
|
|
2693
3023
|
if (detached) {
|
|
2694
3024
|
return {
|
|
2695
|
-
content: [{ type: "text", text: `Parallel run detached for intercom coordination (${detached.agent}). Reply to the supervisor request first.
|
|
3025
|
+
content: [{ type: "text", text: `Parallel run detached for intercom coordination (${detached.agent}). Reply to the supervisor request first, then wait with subagent_wait({ id: "${runId}" }). Use subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.` }],
|
|
2696
3026
|
details,
|
|
2697
3027
|
};
|
|
2698
3028
|
}
|
|
@@ -2713,7 +3043,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2713
3043
|
};
|
|
2714
3044
|
}
|
|
2715
3045
|
|
|
2716
|
-
const worktreeSuffix =
|
|
3046
|
+
const worktreeSuffix = handoff?.suffix ?? "";
|
|
2717
3047
|
const ok = results.filter((result) => result.exitCode === 0).length;
|
|
2718
3048
|
const downgradeNote = backgroundRequestedWhileClarifying ? " (background requested, but clarify kept this run foreground)" : "";
|
|
2719
3049
|
const aggregatedOutput = aggregateParallelOutputs(
|
|
@@ -2737,7 +3067,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
2737
3067
|
details,
|
|
2738
3068
|
};
|
|
2739
3069
|
} finally {
|
|
2740
|
-
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
3070
|
+
if (worktreeSetup && !worktreeFinalized) cleanupWorktrees(worktreeSetup);
|
|
2741
3071
|
}
|
|
2742
3072
|
}
|
|
2743
3073
|
|
|
@@ -2773,17 +3103,18 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2773
3103
|
};
|
|
2774
3104
|
}
|
|
2775
3105
|
const effectiveToolBudget = resolveEffectiveToolBudget({ runBudget: data.toolBudget, agentBudget: agentConfig.toolBudget, configBudget: data.configToolBudget });
|
|
2776
|
-
if (effectiveToolBudget.error) return toExecutionErrorResult(params, new Error(effectiveToolBudget.error));
|
|
3106
|
+
if (effectiveToolBudget.error) return toExecutionErrorResult(params, new Error(effectiveToolBudget.error), data.contextPolicy.contextSummary);
|
|
2777
3107
|
|
|
2778
3108
|
const currentProvider = ctx.model?.provider;
|
|
2779
3109
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map(toModelInfo);
|
|
2780
3110
|
let task = params.task ?? "";
|
|
2781
|
-
let modelOverride: string | undefined =
|
|
2782
|
-
|
|
3111
|
+
let modelOverride: string | undefined = resolveEffectiveSubagentModel(
|
|
3112
|
+
params.model as string | undefined,
|
|
3113
|
+
agentConfig.model,
|
|
2783
3114
|
ctx.model,
|
|
2784
3115
|
availableModels,
|
|
2785
3116
|
currentProvider,
|
|
2786
|
-
{ scope: data.modelScope
|
|
3117
|
+
{ scope: data.modelScope },
|
|
2787
3118
|
);
|
|
2788
3119
|
let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
|
|
2789
3120
|
const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
|
|
@@ -2820,7 +3151,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2820
3151
|
|
|
2821
3152
|
task = result.templates[0]!;
|
|
2822
3153
|
const override = result.behaviorOverrides[0];
|
|
2823
|
-
if (override?.model) modelOverride =
|
|
3154
|
+
if (override?.model !== undefined) modelOverride = resolveEffectiveSubagentModel(override.model, agentConfig.model, ctx.model, availableModels, currentProvider, { scope: data.modelScope });
|
|
2824
3155
|
if (override?.output !== undefined) effectiveOutput = normalizeSingleOutputOverride(override.output, agentConfig.output);
|
|
2825
3156
|
if (override?.skills !== undefined) skillOverride = override.skills;
|
|
2826
3157
|
|
|
@@ -2841,10 +3172,12 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2841
3172
|
currentModelProvider: ctx.model?.provider,
|
|
2842
3173
|
currentModel: ctx.model,
|
|
2843
3174
|
modelScope: data.modelScope,
|
|
3175
|
+
interactive: ctx.hasUI,
|
|
2844
3176
|
};
|
|
2845
3177
|
return executeAsyncSingle(id, {
|
|
2846
3178
|
agent: params.agent!,
|
|
2847
3179
|
task: shouldForkAgent(contextPolicy, params.agent!) ? wrapForkTask(task) : task,
|
|
3180
|
+
goal: task,
|
|
2848
3181
|
agentConfig,
|
|
2849
3182
|
ctx: asyncCtx,
|
|
2850
3183
|
availableModels,
|
|
@@ -2854,23 +3187,30 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2854
3187
|
artifactConfig,
|
|
2855
3188
|
shareEnabled,
|
|
2856
3189
|
sessionRoot,
|
|
2857
|
-
sessionFile: sessionFileForTask(params.agent!, 0),
|
|
3190
|
+
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
3191
|
+
context: contextPolicy.contextForAgent(params.agent!),
|
|
2858
3192
|
skills: skillOverride === false ? [] : skillOverride,
|
|
2859
3193
|
output: effectiveOutput,
|
|
2860
3194
|
outputMode: effectiveOutputMode,
|
|
2861
3195
|
outputBaseDir: resolveSingleRunOutputBaseDir(deps, artifactsDir, id),
|
|
2862
3196
|
modelOverride,
|
|
2863
|
-
thinkingOverride: thinkingOverrideForTask(params.agent!, 0),
|
|
3197
|
+
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
2864
3198
|
maxSubagentDepth,
|
|
3199
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
2865
3200
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
2866
3201
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
2867
3202
|
worktreeBaseDir: deps.config.worktreeBaseDir,
|
|
2868
3203
|
controlConfig,
|
|
2869
3204
|
controlIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
2870
3205
|
childIntercomTarget: data.intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(id, agent, index) : undefined,
|
|
3206
|
+
nestedRoute: data.nestedRoute,
|
|
3207
|
+
agentContract: params.agentContract,
|
|
3208
|
+
structuredOutputSchema: params.outputSchema,
|
|
3209
|
+
acceptance: params.acceptance,
|
|
2871
3210
|
timeoutMs: data.timeoutMs,
|
|
2872
3211
|
turnBudget: data.turnBudget,
|
|
2873
3212
|
toolBudget: effectiveToolBudget.toolBudget,
|
|
3213
|
+
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
2874
3214
|
});
|
|
2875
3215
|
}
|
|
2876
3216
|
}
|
|
@@ -2884,7 +3224,10 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2884
3224
|
if (validationError) {
|
|
2885
3225
|
return { content: [{ type: "text", text: validationError }], isError: true, details: { mode: "single", results: [] } };
|
|
2886
3226
|
}
|
|
2887
|
-
|
|
3227
|
+
const structuredRuntime = params.outputSchema
|
|
3228
|
+
? createStructuredOutputRuntime(params.outputSchema, artifactConfig.enabled ? path.join(artifactsDir, "structured-output", runId) : undefined)
|
|
3229
|
+
: undefined;
|
|
3230
|
+
task = injectSingleOutputInstruction(task, outputPath, agentConfig);
|
|
2888
3231
|
|
|
2889
3232
|
let effectiveSkills: string[] | undefined;
|
|
2890
3233
|
if (skillOverride === false) {
|
|
@@ -2895,90 +3238,77 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2895
3238
|
const interruptController = new AbortController();
|
|
2896
3239
|
const foregroundControl = deps.state.foregroundControls.get(runId);
|
|
2897
3240
|
if (foregroundControl) {
|
|
2898
|
-
foregroundControl
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
};
|
|
3241
|
+
beginForegroundChild(foregroundControl, {
|
|
3242
|
+
index: 0,
|
|
3243
|
+
agent: params.agent!,
|
|
3244
|
+
description: foregroundControl.description,
|
|
3245
|
+
interrupt: () => {
|
|
3246
|
+
if (interruptController.signal.aborted) return false;
|
|
3247
|
+
interruptController.abort();
|
|
3248
|
+
return true;
|
|
3249
|
+
},
|
|
3250
|
+
});
|
|
2909
3251
|
}
|
|
2910
3252
|
|
|
2911
3253
|
const forwardSingleUpdate = onUpdate
|
|
2912
3254
|
? (update: AgentToolResult<Details>) => {
|
|
2913
|
-
if (foregroundControl)
|
|
2914
|
-
const firstProgress = update.details?.progress?.[0];
|
|
2915
|
-
foregroundControl.currentAgent = params.agent;
|
|
2916
|
-
foregroundControl.currentIndex = firstProgress?.index ?? 0;
|
|
2917
|
-
foregroundControl.currentActivityState = firstProgress?.activityState;
|
|
2918
|
-
foregroundControl.lastActivityAt = firstProgress?.lastActivityAt;
|
|
2919
|
-
foregroundControl.currentTool = firstProgress?.currentTool;
|
|
2920
|
-
foregroundControl.currentToolStartedAt = firstProgress?.currentToolStartedAt;
|
|
2921
|
-
foregroundControl.currentPath = firstProgress?.currentPath;
|
|
2922
|
-
foregroundControl.turnCount = firstProgress?.turnCount;
|
|
2923
|
-
foregroundControl.tokens = firstProgress?.tokens;
|
|
2924
|
-
foregroundControl.toolCount = firstProgress?.toolCount;
|
|
2925
|
-
foregroundControl.updatedAt = Date.now();
|
|
2926
|
-
}
|
|
3255
|
+
if (foregroundControl) updateForegroundChild(foregroundControl, 0, update.details?.progress?.[0]);
|
|
2927
3256
|
onUpdate(update);
|
|
2928
3257
|
}
|
|
2929
3258
|
: undefined;
|
|
2930
3259
|
|
|
2931
3260
|
const deadlineAt = data.deadlineAt ?? (data.timeoutMs !== undefined ? Date.now() + data.timeoutMs : undefined);
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
cwd
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
3261
|
+
let r: Awaited<ReturnType<typeof runSync>>;
|
|
3262
|
+
try {
|
|
3263
|
+
r = await runSync(ctx.cwd, agents, params.agent!, task, {
|
|
3264
|
+
parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
|
|
3265
|
+
context: data.contextPolicy.contextForAgent(params.agent!),
|
|
3266
|
+
cwd: effectiveCwd,
|
|
3267
|
+
signal,
|
|
3268
|
+
interruptSignal: interruptController.signal,
|
|
3269
|
+
allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
3270
|
+
intercomEvents: deps.pi.events,
|
|
3271
|
+
runId,
|
|
3272
|
+
sessionDir: sessionDirForIndex(0),
|
|
3273
|
+
sessionFile: sessionFileForTask(params.agent!, 0, modelOverride),
|
|
3274
|
+
share: shareEnabled,
|
|
3275
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
3276
|
+
artifactConfig,
|
|
3277
|
+
maxOutput: params.maxOutput,
|
|
3278
|
+
outputPath,
|
|
3279
|
+
outputMode: effectiveOutputMode,
|
|
3280
|
+
maxSubagentDepth,
|
|
3281
|
+
waitToolEnabled: deps.waitToolEnabled,
|
|
3282
|
+
onUpdate: forwardSingleUpdate,
|
|
3283
|
+
controlConfig,
|
|
3284
|
+
onControlEvent,
|
|
3285
|
+
intercomSessionName: childIntercomTarget,
|
|
3286
|
+
orchestratorIntercomTarget: data.intercomBridge.active ? data.intercomBridge.orchestratorTarget : undefined,
|
|
3287
|
+
nestedRoute: foregroundControl?.nestedRoute,
|
|
3288
|
+
index: 0,
|
|
3289
|
+
modelOverride,
|
|
3290
|
+
thinkingOverride: thinkingOverrideForTask(params.agent!, 0, modelOverride),
|
|
3291
|
+
availableModels,
|
|
3292
|
+
preferredModelProvider: currentProvider,
|
|
3293
|
+
modelScope: data.modelScope,
|
|
3294
|
+
skills: effectiveSkills,
|
|
3295
|
+
structuredOutput: structuredRuntime,
|
|
3296
|
+
agentContract: params.agentContract,
|
|
3297
|
+
acceptance: params.acceptance,
|
|
3298
|
+
acceptanceContext: { mode: "single" },
|
|
3299
|
+
onDetachedExit: (result) => updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events }),
|
|
3300
|
+
timeoutMs: data.timeoutMs,
|
|
3301
|
+
deadlineAt,
|
|
3302
|
+
turnBudget: data.turnBudget,
|
|
3303
|
+
enforceHardTurnLimit: params.enforceHardTurnLimit,
|
|
3304
|
+
toolBudget: effectiveToolBudget.toolBudget,
|
|
3305
|
+
capabilityCeiling: data.capabilityCeiling,
|
|
3306
|
+
allowZeroToolBudget: data.allowZeroToolBudget && effectiveToolBudget.toolBudget === data.toolBudget,
|
|
3307
|
+
});
|
|
3308
|
+
} finally {
|
|
3309
|
+
if (!artifactConfig.enabled) cleanupStructuredOutputRuntime(structuredRuntime);
|
|
2981
3310
|
}
|
|
3311
|
+
if (foregroundControl) finishForegroundChild(foregroundControl, 0);
|
|
2982
3312
|
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
|
|
2983
3313
|
|
|
2984
3314
|
if (r.progress) allProgress.push(r.progress);
|
|
@@ -3011,7 +3341,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3011
3341
|
totalChildUsage: sumResultsUsage([r]),
|
|
3012
3342
|
totalCost: sumResultsCost([r]),
|
|
3013
3343
|
});
|
|
3014
|
-
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: effectiveCwd, results: details.results });
|
|
3344
|
+
rememberForegroundRun(deps.state, { runId, mode: "single", cwd: effectiveCwd, sessionId: data.parentSessionId, results: details.results });
|
|
3015
3345
|
|
|
3016
3346
|
if (!r.detached && !r.interrupted) {
|
|
3017
3347
|
if (foregroundControl) updateForegroundNestedProjection(foregroundControl);
|
|
@@ -3034,7 +3364,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
3034
3364
|
|
|
3035
3365
|
if (r.detached) {
|
|
3036
3366
|
return {
|
|
3037
|
-
content: [{ type: "text", text: `Detached for intercom coordination: ${params.agent}. Reply to the supervisor request first.
|
|
3367
|
+
content: [{ type: "text", text: `Detached for intercom coordination: ${params.agent}. Reply to the supervisor request first, then wait with subagent_wait({ id: "${runId}" }). Use subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.` }],
|
|
3038
3368
|
details,
|
|
3039
3369
|
};
|
|
3040
3370
|
}
|
|
@@ -3098,7 +3428,21 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3098
3428
|
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
3099
3429
|
ctx: ExtensionContext,
|
|
3100
3430
|
) => Promise<AgentToolResult<Details>>;
|
|
3431
|
+
/**
|
|
3432
|
+
* Correlated extension-to-extension delegation owns its request IDs and
|
|
3433
|
+
* cancellation controllers, so independent requests may execute concurrently.
|
|
3434
|
+
* The ordinary model-facing tool keeps the one-foreground-call-per-turn guard.
|
|
3435
|
+
*/
|
|
3436
|
+
executeDelegated: (
|
|
3437
|
+
id: string,
|
|
3438
|
+
params: SubagentParamsLike,
|
|
3439
|
+
signal: AbortSignal,
|
|
3440
|
+
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
3441
|
+
ctx: ExtensionContext,
|
|
3442
|
+
) => Promise<AgentToolResult<Details>>;
|
|
3101
3443
|
} {
|
|
3444
|
+
const delegatedThinkingOverrides = new WeakMap<object, AgentConfig["thinking"]>();
|
|
3445
|
+
const delegatedZeroToolBudgets = new WeakSet<object>();
|
|
3102
3446
|
const execute = async (
|
|
3103
3447
|
_id: string,
|
|
3104
3448
|
params: SubagentParamsLike,
|
|
@@ -3106,6 +3450,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3106
3450
|
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
3107
3451
|
ctx: ExtensionContext,
|
|
3108
3452
|
): Promise<AgentToolResult<Details>> => {
|
|
3453
|
+
const delegatedThinkingOverride = delegatedThinkingOverrides.get(params);
|
|
3454
|
+
const allowZeroToolBudget = delegatedZeroToolBudgets.has(params);
|
|
3109
3455
|
deps.state.baseCwd = ctx.cwd;
|
|
3110
3456
|
deps.state.foregroundRuns ??= new Map();
|
|
3111
3457
|
deps.state.foregroundControls ??= new Map();
|
|
@@ -3115,6 +3461,80 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3115
3461
|
const paramsWithResolvedCwd = requestParams.cwd === undefined ? requestParams : { ...requestParams, cwd: requestCwd };
|
|
3116
3462
|
const action = paramsWithResolvedCwd.action;
|
|
3117
3463
|
if (action) {
|
|
3464
|
+
if ((WATCHDOG_TOOL_ACTIONS as readonly string[]).includes(action)) {
|
|
3465
|
+
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
3466
|
+
return {
|
|
3467
|
+
content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
|
|
3468
|
+
isError: true,
|
|
3469
|
+
details: { mode: "management" as const, results: [] },
|
|
3470
|
+
};
|
|
3471
|
+
}
|
|
3472
|
+
return handleWatchdogToolAction(action, paramsWithResolvedCwd, ctx, deps.watchdog);
|
|
3473
|
+
}
|
|
3474
|
+
if (action === "grant-spawn-budget") {
|
|
3475
|
+
if (deps.allowMutatingManagementActions === false || !ctx.hasUI) {
|
|
3476
|
+
return {
|
|
3477
|
+
content: [{ type: "text", text: "Action 'grant-spawn-budget' is available only from the root interactive parent session." }],
|
|
3478
|
+
isError: true,
|
|
3479
|
+
details: { mode: "management", results: [] },
|
|
3480
|
+
};
|
|
3481
|
+
}
|
|
3482
|
+
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
3483
|
+
if (!deps.state.currentSessionId) {
|
|
3484
|
+
return {
|
|
3485
|
+
content: [{ type: "text", text: "Action 'grant-spawn-budget' requires an active parent session id." }],
|
|
3486
|
+
isError: true,
|
|
3487
|
+
details: { mode: "management", results: [] },
|
|
3488
|
+
};
|
|
3489
|
+
}
|
|
3490
|
+
if (hasActiveSubagentChildren(deps.state)) {
|
|
3491
|
+
const spawnBudget = getSpawnBudgetSnapshot(deps.state, deps.config, deps.state.currentSessionId);
|
|
3492
|
+
return {
|
|
3493
|
+
content: [{ type: "text", text: "Spawn budget grants are rejected while current-session children are queued or running. Wait for them to settle, then retry the explicit grant." }],
|
|
3494
|
+
isError: true,
|
|
3495
|
+
details: { mode: "management", results: [], spawnBudget },
|
|
3496
|
+
};
|
|
3497
|
+
}
|
|
3498
|
+
const sessionId = deps.state.currentSessionId;
|
|
3499
|
+
const additional = paramsWithResolvedCwd.additional ?? Number.NaN;
|
|
3500
|
+
const preview = preflightSpawnBudgetGrant(deps.state, deps.config, sessionId, additional);
|
|
3501
|
+
if (preview.error) {
|
|
3502
|
+
return {
|
|
3503
|
+
content: [{ type: "text", text: preview.error }],
|
|
3504
|
+
isError: true,
|
|
3505
|
+
details: { mode: "management", results: [], spawnBudget: preview.snapshot },
|
|
3506
|
+
};
|
|
3507
|
+
}
|
|
3508
|
+
const confirmed = await ctx.ui.confirm(
|
|
3509
|
+
"Grant subagent spawn budget?",
|
|
3510
|
+
`Add ${additional} launches to this logical session?\n\n${formatSpawnBudget(preview.snapshot)}\n\nUsage is not reset. Compaction keeps the same budget; a new parent session starts a fresh one.`,
|
|
3511
|
+
);
|
|
3512
|
+
if (!confirmed) {
|
|
3513
|
+
return {
|
|
3514
|
+
content: [{ type: "text", text: "Spawn budget grant canceled; no capacity was added." }],
|
|
3515
|
+
details: { mode: "management", results: [], spawnBudget: preview.snapshot },
|
|
3516
|
+
};
|
|
3517
|
+
}
|
|
3518
|
+
const currentBudget = getSpawnBudgetSnapshot(deps.state, deps.config, deps.state.currentSessionId);
|
|
3519
|
+
if (
|
|
3520
|
+
resolveCurrentSessionId(ctx.sessionManager) !== sessionId
|
|
3521
|
+
|| hasActiveSubagentChildren(deps.state)
|
|
3522
|
+
|| currentBudget.used !== preview.snapshot.used
|
|
3523
|
+
|| currentBudget.granted !== preview.snapshot.granted
|
|
3524
|
+
) {
|
|
3525
|
+
return {
|
|
3526
|
+
content: [{ type: "text", text: "Spawn budget grant was not applied because the session, budget, or active-child state changed while confirmation was open." }],
|
|
3527
|
+
isError: true,
|
|
3528
|
+
details: { mode: "management", results: [], spawnBudget: currentBudget },
|
|
3529
|
+
};
|
|
3530
|
+
}
|
|
3531
|
+
const granted = grantSpawnBudget(deps.state, deps.config, sessionId, additional);
|
|
3532
|
+
return {
|
|
3533
|
+
content: [{ type: "text", text: granted.error ?? `Spawn budget grant applied: +${additional}. ${formatSpawnBudget(granted.snapshot)}` }],
|
|
3534
|
+
...(granted.error ? { isError: true } : {}),
|
|
3535
|
+
details: { mode: "management", results: [], spawnBudget: granted.snapshot },
|
|
3536
|
+
};
|
|
3537
|
+
}
|
|
3118
3538
|
if (action === "doctor") {
|
|
3119
3539
|
let currentSessionFile: string | null = null;
|
|
3120
3540
|
let currentSessionId = deps.state.currentSessionId;
|
|
@@ -3131,6 +3551,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3131
3551
|
} catch (error) {
|
|
3132
3552
|
if (!sessionError) sessionError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
3133
3553
|
}
|
|
3554
|
+
const spawnBudget = getSpawnBudgetSnapshot(deps.state, deps.config, currentSessionId);
|
|
3134
3555
|
return {
|
|
3135
3556
|
content: [{
|
|
3136
3557
|
type: "text",
|
|
@@ -3147,15 +3568,22 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3147
3568
|
expandTilde: deps.expandTilde,
|
|
3148
3569
|
}),
|
|
3149
3570
|
}],
|
|
3150
|
-
details: { mode: "management", results: [] },
|
|
3571
|
+
details: { mode: "management", results: [], spawnBudget },
|
|
3151
3572
|
};
|
|
3152
3573
|
}
|
|
3153
3574
|
if (action === "status") {
|
|
3575
|
+
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
3576
|
+
const withBudget = (result: AgentToolResult<Details>) => withSpawnBudgetStatus(
|
|
3577
|
+
result,
|
|
3578
|
+
deps.state,
|
|
3579
|
+
deps.config,
|
|
3580
|
+
deps.state.currentSessionId,
|
|
3581
|
+
);
|
|
3154
3582
|
const targetRunId = paramsWithResolvedCwd.id ?? paramsWithResolvedCwd.runId;
|
|
3155
3583
|
const nestedScope = nestedResolutionScopeForExecutor(deps);
|
|
3156
3584
|
const sessionRoots = trustedSessionRootsForStatus(ctx, deps);
|
|
3157
3585
|
if (paramsWithResolvedCwd.view === "fleet") {
|
|
3158
|
-
return inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedScope, sessionRoots });
|
|
3586
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedScope, sessionRoots }));
|
|
3159
3587
|
}
|
|
3160
3588
|
if (targetRunId) {
|
|
3161
3589
|
try {
|
|
@@ -3164,34 +3592,35 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3164
3592
|
const foreground = getForegroundControl(deps.state, resolved.id);
|
|
3165
3593
|
if (foreground) {
|
|
3166
3594
|
if (paramsWithResolvedCwd.view === "transcript") {
|
|
3167
|
-
return {
|
|
3595
|
+
return withBudget({
|
|
3168
3596
|
content: [{ type: "text", text: "Live foreground transcript is already visible in the expanded running subagent result. Persisted session transcript becomes inspectable after the foreground run completes when sessions are enabled." }],
|
|
3169
3597
|
details: { mode: "management", results: [] },
|
|
3170
|
-
};
|
|
3598
|
+
});
|
|
3171
3599
|
}
|
|
3172
|
-
return foregroundStatusResult(foreground);
|
|
3600
|
+
return withBudget(foregroundStatusResult(foreground));
|
|
3173
3601
|
}
|
|
3174
3602
|
}
|
|
3175
3603
|
} catch (error) {
|
|
3176
3604
|
const message = error instanceof Error ? error.message : String(error);
|
|
3177
|
-
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
3605
|
+
return withBudget({ content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } });
|
|
3178
3606
|
}
|
|
3179
3607
|
} else {
|
|
3180
3608
|
const foreground = getForegroundControl(deps.state, undefined);
|
|
3181
|
-
if (foreground && paramsWithResolvedCwd.view !== "transcript") return foregroundStatusResult(foreground);
|
|
3609
|
+
if (foreground && paramsWithResolvedCwd.view !== "transcript") return withBudget(foregroundStatusResult(foreground));
|
|
3182
3610
|
if (foreground && paramsWithResolvedCwd.view === "transcript") {
|
|
3183
|
-
return {
|
|
3611
|
+
return withBudget({
|
|
3184
3612
|
content: [{ type: "text", text: "Live foreground transcript is already visible in the expanded running subagent result. Pass an async run id to inspect a background transcript." }],
|
|
3185
3613
|
details: { mode: "management", results: [] },
|
|
3186
|
-
};
|
|
3614
|
+
});
|
|
3187
3615
|
}
|
|
3188
3616
|
}
|
|
3189
|
-
return inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedScope, sessionRoots });
|
|
3617
|
+
return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, { state: deps.state, nested: nestedScope, sessionRoots }));
|
|
3190
3618
|
}
|
|
3191
3619
|
if (action === "resume") {
|
|
3192
3620
|
return resumeAsyncRun({ params: paramsWithResolvedCwd, requestCwd, ctx, deps });
|
|
3193
3621
|
}
|
|
3194
3622
|
if (action === "steer") {
|
|
3623
|
+
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
3195
3624
|
const message = (paramsWithResolvedCwd.message ?? paramsWithResolvedCwd.task ?? "").trim();
|
|
3196
3625
|
if (!message) return { content: [{ type: "text", text: "action='steer' requires message." }], isError: true, details: { mode: "management", results: [] } };
|
|
3197
3626
|
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
@@ -3199,7 +3628,22 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3199
3628
|
try {
|
|
3200
3629
|
const location = resolveAsyncRunLocation(paramsWithResolvedCwd, ASYNC_DIR, RESULTS_DIR);
|
|
3201
3630
|
const runId = location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir);
|
|
3202
|
-
return steerAsyncRun({
|
|
3631
|
+
return steerAsyncRun({
|
|
3632
|
+
state: deps.state,
|
|
3633
|
+
runId,
|
|
3634
|
+
message,
|
|
3635
|
+
index: paramsWithResolvedCwd.index,
|
|
3636
|
+
kill: deps.kill,
|
|
3637
|
+
location,
|
|
3638
|
+
signal,
|
|
3639
|
+
...(paramsWithResolvedCwd.steeringRecovery === false
|
|
3640
|
+
? {}
|
|
3641
|
+
: {
|
|
3642
|
+
recover: ({ absoluteDeadlineAt, ...limits }) =>
|
|
3643
|
+
resumeAsyncRun({ params: { ...limits, action: "resume", id: runId, message }, requestCwd, ctx, deps, absoluteDeadlineAt }),
|
|
3644
|
+
}
|
|
3645
|
+
),
|
|
3646
|
+
});
|
|
3203
3647
|
} catch (error) {
|
|
3204
3648
|
const text = error instanceof Error ? error.message : String(error);
|
|
3205
3649
|
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -3213,10 +3657,31 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3213
3657
|
const text = error instanceof Error ? error.message : String(error);
|
|
3214
3658
|
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
3215
3659
|
}
|
|
3216
|
-
if (resolved?.kind === "nested") return steerNestedRun({ target: resolved, message, index: paramsWithResolvedCwd.index });
|
|
3660
|
+
if (resolved?.kind === "nested") return steerNestedRun({ target: resolved, message, index: paramsWithResolvedCwd.index, signal });
|
|
3217
3661
|
if (resolved?.kind === "foreground") return { content: [{ type: "text", text: "action='steer' currently supports live async Pi child sessions only; use action='interrupt' or action='resume' for foreground runs." }], isError: true, details: { mode: "management", results: [] } };
|
|
3218
3662
|
if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
3219
|
-
return steerAsyncRun({
|
|
3663
|
+
return steerAsyncRun({
|
|
3664
|
+
state: deps.state,
|
|
3665
|
+
runId: resolved.id,
|
|
3666
|
+
message,
|
|
3667
|
+
index: paramsWithResolvedCwd.index,
|
|
3668
|
+
kill: deps.kill,
|
|
3669
|
+
location: resolved.location,
|
|
3670
|
+
signal,
|
|
3671
|
+
...(paramsWithResolvedCwd.steeringRecovery === false
|
|
3672
|
+
? {}
|
|
3673
|
+
: {
|
|
3674
|
+
recover: ({ absoluteDeadlineAt, ...limits }) =>
|
|
3675
|
+
resumeAsyncRun({
|
|
3676
|
+
params: { ...limits, action: "resume", id: resolved!.id, message },
|
|
3677
|
+
requestCwd,
|
|
3678
|
+
ctx,
|
|
3679
|
+
deps,
|
|
3680
|
+
absoluteDeadlineAt,
|
|
3681
|
+
}),
|
|
3682
|
+
}
|
|
3683
|
+
),
|
|
3684
|
+
});
|
|
3220
3685
|
}
|
|
3221
3686
|
if (action === "append-step") {
|
|
3222
3687
|
return appendStepToAsyncChain({ params: paramsWithResolvedCwd, requestCwd, ctx, deps });
|
|
@@ -3231,6 +3696,40 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3231
3696
|
}
|
|
3232
3697
|
return deps.handleScheduledRunAction(paramsWithResolvedCwd, ctx);
|
|
3233
3698
|
}
|
|
3699
|
+
if (action === "stop") {
|
|
3700
|
+
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
3701
|
+
let resolved: ResolvedSubagentRunId | undefined;
|
|
3702
|
+
if (paramsWithResolvedCwd.dir) {
|
|
3703
|
+
try {
|
|
3704
|
+
const location = resolveAsyncRunLocation(paramsWithResolvedCwd, ASYNC_DIR, RESULTS_DIR);
|
|
3705
|
+
return stopAsyncRun(deps.state, location.resolvedId ?? targetRunId ?? path.basename(location.asyncDir ?? paramsWithResolvedCwd.dir), deps.kill, location);
|
|
3706
|
+
} catch (error) {
|
|
3707
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
3708
|
+
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
if (!targetRunId) return { content: [{ type: "text", text: "action='stop' requires id or dir." }], isError: true, details: { mode: "management", results: [] } };
|
|
3712
|
+
try {
|
|
3713
|
+
resolved = resolveSubagentRunId(targetRunId, { state: deps.state, nested: nestedResolutionScopeForExecutor(deps) });
|
|
3714
|
+
} catch (error) {
|
|
3715
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3716
|
+
return { content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } };
|
|
3717
|
+
}
|
|
3718
|
+
if (resolved?.kind === "nested") return { content: [{ type: "text", text: "action='stop' supports current-session top-level async runs only." }], isError: true, details: { mode: "management", results: [] } };
|
|
3719
|
+
if (resolved?.kind === "foreground") return { content: [{ type: "text", text: "action='stop' supports async runs only. Use action='interrupt' for foreground runs." }], isError: true, details: { mode: "management", results: [] } };
|
|
3720
|
+
const stopResult = stopAsyncRun(
|
|
3721
|
+
deps.state,
|
|
3722
|
+
resolved?.kind === "async" ? resolved.id : targetRunId,
|
|
3723
|
+
deps.kill,
|
|
3724
|
+
resolved?.kind === "async" ? resolved.location : undefined,
|
|
3725
|
+
);
|
|
3726
|
+
if (stopResult) return stopResult;
|
|
3727
|
+
return {
|
|
3728
|
+
content: [{ type: "text", text: "No stoppable async run found in this session." }],
|
|
3729
|
+
isError: true,
|
|
3730
|
+
details: { mode: "management", results: [] },
|
|
3731
|
+
};
|
|
3732
|
+
}
|
|
3234
3733
|
if (action === "interrupt") {
|
|
3235
3734
|
const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
|
|
3236
3735
|
let resolved: ResolvedSubagentRunId | undefined;
|
|
@@ -3320,11 +3819,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3320
3819
|
depth,
|
|
3321
3820
|
deps.config.forceTopLevelAsync === true,
|
|
3322
3821
|
);
|
|
3323
|
-
const
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3822
|
+
const runToolBudget = resolveToolBudget(
|
|
3823
|
+
effectiveParams.toolBudget,
|
|
3824
|
+
"toolBudget",
|
|
3825
|
+
allowZeroToolBudget ? { minimumHard: 0 } : undefined,
|
|
3826
|
+
);
|
|
3328
3827
|
if (runToolBudget.error) return buildRequestedModeError(effectiveParams, runToolBudget.error);
|
|
3329
3828
|
const configToolBudget = resolveToolBudget(deps.config.toolBudget, "config.toolBudget");
|
|
3330
3829
|
if (configToolBudget.error) return buildRequestedModeError(effectiveParams, configToolBudget.error);
|
|
@@ -3336,12 +3835,17 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3336
3835
|
const discovered = deps.discoverAgents(effectiveCwd, scope);
|
|
3337
3836
|
const discoveredAgents = discovered.agents;
|
|
3338
3837
|
const modelScope = discovered.modelScope;
|
|
3838
|
+
effectiveParams = applySingleAgentLaunchDefaults(effectiveParams, discoveredAgents);
|
|
3839
|
+
const foregroundTimeout = resolveForegroundTimeout(effectiveParams);
|
|
3840
|
+
if (foregroundTimeout.error) return buildRequestedModeError(effectiveParams, foregroundTimeout.error);
|
|
3841
|
+
const turnBudget = resolveTurnBudgetConfig(effectiveParams.turnBudget ?? deps.config.turnBudget);
|
|
3842
|
+
if (turnBudget.error) return buildRequestedModeError(effectiveParams, turnBudget.error);
|
|
3339
3843
|
const contextPolicy = resolveAgentDefaultContextPolicy(effectiveParams, discoveredAgents);
|
|
3340
3844
|
effectiveParams = contextPolicy.params;
|
|
3341
3845
|
const sessionName = resolveIntercomSessionTarget(deps.pi.getSessionName(), ctx.sessionManager.getSessionId());
|
|
3342
3846
|
const intercomBridge = resolveIntercomBridge({
|
|
3343
3847
|
config: deps.config.intercomBridge,
|
|
3344
|
-
context: effectiveParams.context,
|
|
3848
|
+
context: effectiveParams.context ?? (contextPolicy.usesFork ? "fork" : undefined),
|
|
3345
3849
|
orchestratorTarget: sessionName,
|
|
3346
3850
|
});
|
|
3347
3851
|
const agents = intercomBridge.active
|
|
@@ -3370,14 +3874,51 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3370
3874
|
);
|
|
3371
3875
|
if (validationError) return validationError;
|
|
3372
3876
|
|
|
3877
|
+
const foregroundMode: "single" | "parallel" | "chain" = hasChain ? "chain" : hasTasks ? "parallel" : "single";
|
|
3878
|
+
const requestedSpawns = countRequestedSubagentSpawns(effectiveParams, deps.config);
|
|
3879
|
+
const spawnPreflight = preflightSpawnBudget(
|
|
3880
|
+
deps.state,
|
|
3881
|
+
deps.config,
|
|
3882
|
+
deps.state.currentSessionId,
|
|
3883
|
+
requestedSpawns,
|
|
3884
|
+
);
|
|
3885
|
+
if (spawnPreflight.error) return spawnBudgetErrorResult(spawnPreflight.error, foregroundMode);
|
|
3886
|
+
|
|
3373
3887
|
let forkSessionFileForIndex: (idx?: number) => string | undefined = () => undefined;
|
|
3374
3888
|
let forkThinkingOverrideForIndex: (idx?: number) => AgentConfig["thinking"] | undefined = () => undefined;
|
|
3889
|
+
let prepareForkThinking = (_agentName: string, _index: number, _modelOverride?: string): void => {};
|
|
3890
|
+
const forkThinkingRequirements = new Map<number, boolean>();
|
|
3891
|
+
const forkThinkingDowngrades = new Map<number, string>();
|
|
3375
3892
|
try {
|
|
3376
|
-
const
|
|
3893
|
+
const forkAvailableModels = contextPolicy.usesFork ? ctx.modelRegistry.getAvailable().map(toModelInfo) : [];
|
|
3894
|
+
prepareForkThinking = (agentName, index, modelOverride) => {
|
|
3895
|
+
const agentConfig = agents.find((agent) => agent.name === agentName);
|
|
3896
|
+
const primaryModel = resolveEffectiveSubagentModel(
|
|
3897
|
+
modelOverride,
|
|
3898
|
+
agentConfig?.model,
|
|
3899
|
+
ctx.model,
|
|
3900
|
+
forkAvailableModels,
|
|
3901
|
+
ctx.model?.provider,
|
|
3902
|
+
);
|
|
3903
|
+
const candidates = buildModelCandidates(
|
|
3904
|
+
primaryModel,
|
|
3905
|
+
agentConfig?.fallbackModels,
|
|
3906
|
+
forkAvailableModels,
|
|
3907
|
+
ctx.model?.provider,
|
|
3908
|
+
);
|
|
3909
|
+
forkThinkingRequirements.set(
|
|
3910
|
+
index,
|
|
3911
|
+
candidates.length === 0
|
|
3912
|
+
|| candidates.some((candidate) => forkedChildRequiresThinkingOff(candidate, forkAvailableModels, ctx.model?.provider)),
|
|
3913
|
+
);
|
|
3914
|
+
};
|
|
3915
|
+
const forkContextResolver = createForkContextResolver(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined, {
|
|
3916
|
+
forceThinkingOffForIndex: (index) => forkThinkingRequirements.get(index) ?? true,
|
|
3917
|
+
});
|
|
3377
3918
|
forkSessionFileForIndex = forkContextResolver.sessionFileForIndex;
|
|
3378
3919
|
forkThinkingOverrideForIndex = forkContextResolver.thinkingOverrideForIndex;
|
|
3379
3920
|
} catch (error) {
|
|
3380
|
-
return toExecutionErrorResult(effectiveParams, error);
|
|
3921
|
+
return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
|
|
3381
3922
|
}
|
|
3382
3923
|
const requestedAsync = effectiveParams.async ?? deps.asyncByDefault;
|
|
3383
3924
|
const backgroundRequestedWhileClarifying = (hasChain || hasTasks) && requestedAsync && effectiveParams.clarify === true;
|
|
@@ -3387,8 +3928,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3387
3928
|
const artifactConfig: ArtifactConfig = {
|
|
3388
3929
|
...DEFAULT_ARTIFACT_CONFIG,
|
|
3389
3930
|
enabled: effectiveParams.artifacts !== false,
|
|
3931
|
+
dir: deps.config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir,
|
|
3390
3932
|
};
|
|
3391
|
-
const artifactsDir = getArtifactsDir(parentSessionFile, effectiveCwd);
|
|
3933
|
+
const artifactsDir = getArtifactsDir(parentSessionFile, effectiveCwd, artifactConfig.dir);
|
|
3392
3934
|
|
|
3393
3935
|
let sessionRoot: string;
|
|
3394
3936
|
if (effectiveParams.sessionDir) {
|
|
@@ -3406,39 +3948,48 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3406
3948
|
return toExecutionErrorResult(
|
|
3407
3949
|
effectiveParams,
|
|
3408
3950
|
new Error(`Failed to create session directory '${sessionRoot}': ${message}`),
|
|
3951
|
+
contextPolicy.contextSummary,
|
|
3409
3952
|
);
|
|
3410
3953
|
}
|
|
3411
3954
|
const sessionDirForIndex = (idx?: number) =>
|
|
3412
3955
|
path.join(sessionRoot, `run-${idx ?? 0}`);
|
|
3413
|
-
const forkSessionFileForTask = (agentName
|
|
3414
|
-
shouldForkAgent(contextPolicy, agentName)
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3956
|
+
const forkSessionFileForTask: ForkSessionFileForTask = (agentName, idx = 0, modelOverride) => {
|
|
3957
|
+
if (!shouldForkAgent(contextPolicy, agentName)) return undefined;
|
|
3958
|
+
prepareForkThinking(agentName, idx, modelOverride);
|
|
3959
|
+
return forkSessionFileForIndex(idx);
|
|
3960
|
+
};
|
|
3961
|
+
const forkThinkingOverrideForTask: ForkThinkingOverrideForTask = (agentName, idx = 0, modelOverride) => {
|
|
3962
|
+
if (!shouldForkAgent(contextPolicy, agentName)) return delegatedThinkingOverride;
|
|
3963
|
+
prepareForkThinking(agentName, idx, modelOverride);
|
|
3964
|
+
const override = forkThinkingOverrideForIndex(idx);
|
|
3965
|
+
if (override === "off") forkThinkingDowngrades.set(idx, agentName);
|
|
3966
|
+
return override ?? delegatedThinkingOverride;
|
|
3967
|
+
};
|
|
3968
|
+
const childSessionFileForTask: ForkSessionFileForTask = (agentName, idx, modelOverride) =>
|
|
3969
|
+
forkSessionFileForTask(agentName, idx, modelOverride) ?? path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
3419
3970
|
const childSessionFileForIndex = (idx?: number) =>
|
|
3420
3971
|
path.join(sessionDirForIndex(idx), "session.jsonl");
|
|
3421
3972
|
try {
|
|
3422
|
-
|
|
3973
|
+
if (!(effectiveParams.clarify === true && ctx.hasUI)) {
|
|
3974
|
+
preflightForkSessionsForStaticTasks(effectiveParams, contextPolicy, forkSessionFileForTask, deps.config.chain?.dynamicFanout?.maxItems);
|
|
3975
|
+
}
|
|
3423
3976
|
} catch (error) {
|
|
3424
|
-
return toExecutionErrorResult(effectiveParams, error);
|
|
3977
|
+
return toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary);
|
|
3425
3978
|
}
|
|
3426
3979
|
const chainBindingsError = validateExecutionChainBindings(effectiveParams, deps.config.chain?.dynamicFanout?.maxItems);
|
|
3427
|
-
if (chainBindingsError) return chainBindingsError;
|
|
3980
|
+
if (chainBindingsError) return withResolvedContext(chainBindingsError, contextPolicy.contextSummary);
|
|
3428
3981
|
|
|
3429
3982
|
const onUpdateWithContext = onUpdate
|
|
3430
|
-
? (r: AgentToolResult<Details>) => onUpdate(
|
|
3983
|
+
? (r: AgentToolResult<Details>) => onUpdate(withResolvedContext(r, contextPolicy.contextSummary))
|
|
3431
3984
|
: undefined;
|
|
3432
3985
|
|
|
3433
|
-
const
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
});
|
|
3441
|
-
if (spawnLimitError) return spawnLimitError;
|
|
3986
|
+
const reservation = reserveSpawnBudget(
|
|
3987
|
+
deps.state,
|
|
3988
|
+
deps.config,
|
|
3989
|
+
deps.state.currentSessionId,
|
|
3990
|
+
requestedSpawns,
|
|
3991
|
+
);
|
|
3992
|
+
if (reservation.error) return spawnBudgetErrorResult(reservation.error, foregroundMode);
|
|
3442
3993
|
|
|
3443
3994
|
const execData: ExecutionContextData = {
|
|
3444
3995
|
params: effectiveParams,
|
|
@@ -3464,21 +4015,30 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3464
4015
|
timeoutMs: foregroundTimeout.timeoutMs,
|
|
3465
4016
|
turnBudget: turnBudget.turnBudget,
|
|
3466
4017
|
toolBudget: runToolBudget.toolBudget,
|
|
4018
|
+
allowZeroToolBudget,
|
|
3467
4019
|
configToolBudget: configToolBudget.toolBudget,
|
|
3468
4020
|
contextPolicy,
|
|
3469
4021
|
modelScope,
|
|
4022
|
+
parentSessionId: deps.state.currentSessionId,
|
|
4023
|
+
capabilityCeiling: resolveCurrentSubagentCapabilityCeiling(deps.state.currentSessionId ?? undefined),
|
|
3470
4024
|
};
|
|
3471
4025
|
|
|
3472
|
-
const
|
|
4026
|
+
const foregroundDescription = effectiveParams.task?.trim()
|
|
4027
|
+
|| effectiveParams.tasks?.[0]?.task?.trim()
|
|
4028
|
+
|| (effectiveParams.chain ? firstRawChainTask(effectiveParams.chain)?.trim() : undefined);
|
|
4029
|
+
const foregroundControl: ForegroundRunControl | undefined = effectiveAsync
|
|
3473
4030
|
? undefined
|
|
3474
4031
|
: {
|
|
3475
4032
|
runId,
|
|
3476
4033
|
mode: foregroundMode,
|
|
3477
4034
|
startedAt: Date.now(),
|
|
3478
4035
|
updatedAt: Date.now(),
|
|
4036
|
+
cwd: effectiveCwd,
|
|
3479
4037
|
currentAgent: undefined,
|
|
3480
4038
|
currentIndex: undefined,
|
|
4039
|
+
description: foregroundDescription,
|
|
3481
4040
|
currentActivityState: undefined,
|
|
4041
|
+
activeChildren: new Map(),
|
|
3482
4042
|
nestedRoute,
|
|
3483
4043
|
interrupt: undefined,
|
|
3484
4044
|
};
|
|
@@ -3493,10 +4053,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3493
4053
|
const details = result?.details;
|
|
3494
4054
|
const state = type === "subagent.nested.started"
|
|
3495
4055
|
? "running"
|
|
3496
|
-
:
|
|
3497
|
-
? "
|
|
3498
|
-
: details?.results.some((child) => child.
|
|
3499
|
-
? "
|
|
4056
|
+
: details?.results.some((child) => child.interrupted || child.detached)
|
|
4057
|
+
? "paused"
|
|
4058
|
+
: result?.isError || details?.results.some((child) => child.exitCode !== 0)
|
|
4059
|
+
? "failed"
|
|
3500
4060
|
: "complete";
|
|
3501
4061
|
const errorText = result?.isError
|
|
3502
4062
|
? result.content.find((item) => item.type === "text")?.text
|
|
@@ -3536,7 +4096,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3536
4096
|
...(errorText ? { error: errorText } : {}),
|
|
3537
4097
|
...(details?.results.length ? { steps: details.results.map((child) => ({
|
|
3538
4098
|
agent: child.agent,
|
|
3539
|
-
status: child.interrupted ? "paused" : child.exitCode === 0 ? "complete" : "failed",
|
|
4099
|
+
status: child.interrupted || child.detached ? "paused" : child.exitCode === 0 ? "complete" : "failed",
|
|
3540
4100
|
...(child.sessionFile ? { sessionFile: child.sessionFile } : {}),
|
|
3541
4101
|
...(child.error ? { error: child.error } : {}),
|
|
3542
4102
|
})) } : {}),
|
|
@@ -3550,7 +4110,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3550
4110
|
let nestedForegroundStarted = false;
|
|
3551
4111
|
try {
|
|
3552
4112
|
const asyncResult = runAsyncPath(execData, deps);
|
|
3553
|
-
if (asyncResult) return
|
|
4113
|
+
if (asyncResult) return withResolvedContext(withForkThinkingNotes(asyncResult, forkThinkingDowngrades), contextPolicy.contextSummary);
|
|
3554
4114
|
if (foregroundControl) {
|
|
3555
4115
|
writeNestedForegroundEvent("subagent.nested.started");
|
|
3556
4116
|
nestedForegroundStarted = true;
|
|
@@ -3558,20 +4118,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3558
4118
|
if (hasChain && effectiveParams.chain) {
|
|
3559
4119
|
const result = await runChainPath(execData, deps);
|
|
3560
4120
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
3561
|
-
return
|
|
4121
|
+
return withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary);
|
|
3562
4122
|
}
|
|
3563
4123
|
if (hasTasks && effectiveParams.tasks) {
|
|
3564
4124
|
const result = await runParallelPath(execData, deps);
|
|
3565
4125
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
3566
|
-
return
|
|
4126
|
+
return withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary);
|
|
3567
4127
|
}
|
|
3568
4128
|
if (hasSingle) {
|
|
3569
4129
|
const result = await runSinglePath(execData, deps);
|
|
3570
4130
|
writeNestedForegroundEvent("subagent.nested.completed", result);
|
|
3571
|
-
return
|
|
4131
|
+
return withResolvedContext(withForkThinkingNotes(result, forkThinkingDowngrades), contextPolicy.contextSummary);
|
|
3572
4132
|
}
|
|
3573
4133
|
} catch (error) {
|
|
3574
|
-
const errorResult = toExecutionErrorResult(effectiveParams, error);
|
|
4134
|
+
const errorResult = withForkThinkingNotes(toExecutionErrorResult(effectiveParams, error, contextPolicy.contextSummary), forkThinkingDowngrades);
|
|
3575
4135
|
if (nestedForegroundStarted) writeNestedForegroundEvent("subagent.nested.completed", errorResult);
|
|
3576
4136
|
return errorResult;
|
|
3577
4137
|
} finally {
|
|
@@ -3584,11 +4144,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3584
4144
|
}
|
|
3585
4145
|
}
|
|
3586
4146
|
|
|
3587
|
-
return
|
|
4147
|
+
return withResolvedContext({
|
|
3588
4148
|
content: [{ type: "text", text: "Invalid params" }],
|
|
3589
4149
|
isError: true,
|
|
3590
4150
|
details: { mode: "single" as const, results: [] },
|
|
3591
|
-
},
|
|
4151
|
+
}, contextPolicy.contextSummary);
|
|
3592
4152
|
};
|
|
3593
4153
|
|
|
3594
4154
|
const executeWithSingleDispatchGuard = async (
|
|
@@ -3600,6 +4160,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3600
4160
|
): Promise<AgentToolResult<Details>> => {
|
|
3601
4161
|
const requestParams = omitExecutionModeActionAlias(params);
|
|
3602
4162
|
if (requestParams.action) return execute(id, requestParams, signal, onUpdate, ctx);
|
|
4163
|
+
const { depth } = checkSubagentDepth(deps.config.maxSubagentDepth);
|
|
4164
|
+
const dispatchParams = applyForceTopLevelAsyncOverride(requestParams, depth, deps.config.forceTopLevelAsync === true);
|
|
4165
|
+
const runsForeground = dispatchParams.clarify === true || (dispatchParams.async ?? deps.asyncByDefault) !== true;
|
|
4166
|
+
if (!runsForeground) return execute(id, requestParams, signal, onUpdate, ctx);
|
|
3603
4167
|
if (deps.state.subagentInProgress === true) return duplicateSubagentCallResult(requestParams);
|
|
3604
4168
|
deps.state.subagentInProgress = true;
|
|
3605
4169
|
try {
|
|
@@ -3609,5 +4173,26 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
3609
4173
|
}
|
|
3610
4174
|
};
|
|
3611
4175
|
|
|
3612
|
-
|
|
4176
|
+
const executeDelegated = async (
|
|
4177
|
+
id: string,
|
|
4178
|
+
params: SubagentParamsLike,
|
|
4179
|
+
signal: AbortSignal,
|
|
4180
|
+
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
4181
|
+
ctx: ExtensionContext,
|
|
4182
|
+
): Promise<AgentToolResult<Details>> => {
|
|
4183
|
+
const delegatedParams = { ...params };
|
|
4184
|
+
const privateParams = delegatedParams as SubagentParamsLike & {
|
|
4185
|
+
delegatedThinkingOverride?: AgentConfig["thinking"];
|
|
4186
|
+
delegatedAllowZeroToolBudget?: true;
|
|
4187
|
+
};
|
|
4188
|
+
const thinkingOverride = privateParams.delegatedThinkingOverride;
|
|
4189
|
+
const allowZeroToolBudget = privateParams.delegatedAllowZeroToolBudget === true;
|
|
4190
|
+
delete privateParams.delegatedThinkingOverride;
|
|
4191
|
+
delete privateParams.delegatedAllowZeroToolBudget;
|
|
4192
|
+
if (thinkingOverride !== undefined) delegatedThinkingOverrides.set(delegatedParams, thinkingOverride);
|
|
4193
|
+
if (allowZeroToolBudget) delegatedZeroToolBudgets.add(delegatedParams);
|
|
4194
|
+
return execute(id, delegatedParams, signal, onUpdate, ctx);
|
|
4195
|
+
};
|
|
4196
|
+
|
|
4197
|
+
return { execute: executeWithSingleDispatchGuard, executeDelegated };
|
|
3613
4198
|
}
|