@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
|
@@ -7,11 +7,13 @@ import {
|
|
|
7
7
|
type AsyncJobState,
|
|
8
8
|
type AsyncStartedEvent,
|
|
9
9
|
type ControlEvent,
|
|
10
|
+
type SteeringNotice,
|
|
10
11
|
type SubagentState,
|
|
11
12
|
POLL_INTERVAL_MS,
|
|
12
13
|
RESULTS_DIR,
|
|
13
14
|
SUBAGENT_CONTROL_EVENT,
|
|
14
15
|
SUBAGENT_CONTROL_INTERCOM_EVENT,
|
|
16
|
+
SUBAGENT_STEERING_NOTICE_EVENT,
|
|
15
17
|
} from "../../shared/types.ts";
|
|
16
18
|
import { readStatus } from "../../shared/utils.ts";
|
|
17
19
|
import { normalizeParallelGroups } from "./parallel-groups.ts";
|
|
@@ -23,6 +25,7 @@ interface AsyncJobTrackerOptions {
|
|
|
23
25
|
completionRetentionMs?: number;
|
|
24
26
|
pollIntervalMs?: number;
|
|
25
27
|
resultsDir?: string;
|
|
28
|
+
widgetEnabled?: boolean;
|
|
26
29
|
kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
27
30
|
now?: () => number;
|
|
28
31
|
}
|
|
@@ -30,9 +33,20 @@ interface AsyncJobTrackerOptions {
|
|
|
30
33
|
const CONTROL_EVENT_READ_CHUNK_BYTES = 64 * 1024;
|
|
31
34
|
const MAX_CONTROL_EVENT_LINE_BYTES = 1024 * 1024;
|
|
32
35
|
const CONTROL_EVENT_SCAN_WINDOW_BYTES = 2 * 1024 * 1024;
|
|
36
|
+
const MAX_RECENT_FLEET_JOBS = 20;
|
|
37
|
+
|
|
38
|
+
function rememberFleetJob(state: SubagentState, job: AsyncJobState): void {
|
|
39
|
+
state.fleetJobs ??= new Map();
|
|
40
|
+
state.fleetJobs.set(job.asyncId, job);
|
|
41
|
+
const terminal = [...state.fleetJobs.values()]
|
|
42
|
+
.filter((candidate) => candidate.status === "complete" || candidate.status === "failed" || candidate.status === "paused" || candidate.status === "stopped")
|
|
43
|
+
.sort((left, right) => (right.updatedAt ?? right.startedAt ?? 0) - (left.updatedAt ?? left.startedAt ?? 0));
|
|
44
|
+
for (const stale of terminal.slice(MAX_RECENT_FLEET_JOBS)) state.fleetJobs.delete(stale.asyncId);
|
|
45
|
+
}
|
|
33
46
|
|
|
34
47
|
export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: SubagentState, asyncDirRoot: string, options: AsyncJobTrackerOptions = {}): {
|
|
35
48
|
ensurePoller: () => void;
|
|
49
|
+
refreshWidget: (ctx: ExtensionContext) => void;
|
|
36
50
|
handleStarted: (data: unknown) => void;
|
|
37
51
|
handleComplete: (data: unknown) => void;
|
|
38
52
|
resetJobs: (ctx?: ExtensionContext) => void;
|
|
@@ -41,10 +55,12 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
41
55
|
const completionRetentionMs = options.completionRetentionMs ?? 10000;
|
|
42
56
|
const pollIntervalMs = options.pollIntervalMs ?? POLL_INTERVAL_MS;
|
|
43
57
|
const resultsDir = options.resultsDir ?? RESULTS_DIR;
|
|
58
|
+
const steeringNoticeSeen = new Map<string, number>();
|
|
44
59
|
const rerenderWidget = (ctx: ExtensionContext, jobs = Array.from(state.asyncJobs.values())) => {
|
|
45
|
-
renderWidget(ctx, jobs);
|
|
60
|
+
renderWidget(ctx, options.widgetEnabled === false ? [] : jobs);
|
|
46
61
|
ctx.ui.requestRender?.();
|
|
47
62
|
};
|
|
63
|
+
const refreshWidget = (ctx: ExtensionContext) => rerenderWidget(ctx);
|
|
48
64
|
const restoredControlEventCursor = (asyncDir: string) => {
|
|
49
65
|
try {
|
|
50
66
|
return fs.statSync(path.join(asyncDir, "events.jsonl")).size;
|
|
@@ -73,7 +89,10 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
73
89
|
currentPath: run.currentPath,
|
|
74
90
|
turnCount: run.turnCount,
|
|
75
91
|
toolCount: run.toolCount,
|
|
92
|
+
steering: run.steering,
|
|
76
93
|
mode: run.mode,
|
|
94
|
+
context: run.context,
|
|
95
|
+
cwd: run.cwd,
|
|
77
96
|
agents: visibleSteps.map((step) => step.agent),
|
|
78
97
|
currentStep: run.currentStep,
|
|
79
98
|
chainStepCount: run.chainStepCount,
|
|
@@ -89,6 +108,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
89
108
|
timeoutMs: run.timeoutMs,
|
|
90
109
|
deadlineAt: run.deadlineAt,
|
|
91
110
|
timedOut: run.timedOut,
|
|
111
|
+
stopped: run.stopped,
|
|
92
112
|
turnBudget: run.turnBudget,
|
|
93
113
|
turnBudgetExceeded: run.turnBudgetExceeded,
|
|
94
114
|
wrapUpRequested: run.wrapUpRequested,
|
|
@@ -144,7 +164,24 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
144
164
|
console.error(`Ignoring malformed async control event in '${eventsPath}':`, error);
|
|
145
165
|
return;
|
|
146
166
|
}
|
|
147
|
-
if (!parsed || typeof parsed !== "object"
|
|
167
|
+
if (!parsed || typeof parsed !== "object") return;
|
|
168
|
+
if ((parsed as { type?: unknown }).type === "subagent.steering.notice") {
|
|
169
|
+
const notice = parsed as Partial<SteeringNotice>;
|
|
170
|
+
if (typeof notice.requestId !== "string" || typeof notice.runId !== "string" || (notice.state !== "failed" && notice.state !== "partial" && notice.state !== "recovered") || typeof notice.message !== "string") return;
|
|
171
|
+
if (typeof state.currentSessionId === "string" && notice.currentSessionId !== state.currentSessionId) return;
|
|
172
|
+
const key = `${notice.runId}:${notice.requestId}:${notice.state}`;
|
|
173
|
+
if (steeringNoticeSeen.has(key)) return;
|
|
174
|
+
const now = Date.now();
|
|
175
|
+
steeringNoticeSeen.set(key, now);
|
|
176
|
+
if (steeringNoticeSeen.size > 200) {
|
|
177
|
+
for (const [seenKey, seenAt] of steeringNoticeSeen) {
|
|
178
|
+
if (now - seenAt > 10 * 60 * 1000 || steeringNoticeSeen.size > 200) steeringNoticeSeen.delete(seenKey);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
pi.events.emit(SUBAGENT_STEERING_NOTICE_EVENT, { ...notice, source: "async", asyncDir: job.asyncDir, noticeText: notice.message });
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if ((parsed as { type?: unknown }).type !== "subagent.control") return;
|
|
148
185
|
const record = parsed as { event?: ControlEvent; channels?: string[]; childIntercomTarget?: string; noticeText?: string; intercom?: { to?: string; message?: string } };
|
|
149
186
|
if (!record.event || !Array.isArray(record.channels)) return;
|
|
150
187
|
const payload = {
|
|
@@ -269,7 +306,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
269
306
|
if (status) {
|
|
270
307
|
const previousStatus = job.status;
|
|
271
308
|
job.status = status.state;
|
|
272
|
-
if (job.status !== "complete" && job.status !== "failed" && job.status !== "paused") cancelCleanup(job.asyncId);
|
|
309
|
+
if (job.status !== "complete" && job.status !== "failed" && job.status !== "paused" && job.status !== "stopped") cancelCleanup(job.asyncId);
|
|
273
310
|
job.sessionId = status.sessionId ?? job.sessionId;
|
|
274
311
|
job.activityState = status.activityState;
|
|
275
312
|
job.lastActivityAt = status.lastActivityAt ?? job.lastActivityAt;
|
|
@@ -278,6 +315,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
278
315
|
job.currentPath = status.currentPath;
|
|
279
316
|
job.turnCount = status.turnCount ?? job.turnCount;
|
|
280
317
|
job.toolCount = status.toolCount ?? job.toolCount;
|
|
318
|
+
job.steering = status.steering ?? job.steering;
|
|
281
319
|
job.mode = status.mode;
|
|
282
320
|
job.currentStep = status.currentStep ?? job.currentStep;
|
|
283
321
|
job.chainStepCount = status.chainStepCount ?? job.chainStepCount;
|
|
@@ -308,12 +346,16 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
308
346
|
job.timeoutMs = status.timeoutMs ?? job.timeoutMs;
|
|
309
347
|
job.deadlineAt = status.deadlineAt ?? job.deadlineAt;
|
|
310
348
|
job.timedOut = status.timedOut ?? job.timedOut;
|
|
349
|
+
job.stopped = status.stopped ?? job.stopped;
|
|
311
350
|
job.turnBudget = status.turnBudget ?? job.turnBudget;
|
|
312
351
|
job.turnBudgetExceeded = status.turnBudgetExceeded ?? job.turnBudgetExceeded;
|
|
313
352
|
job.wrapUpRequested = status.wrapUpRequested ?? job.wrapUpRequested;
|
|
314
353
|
job.sessionFile = status.sessionFile ?? job.sessionFile;
|
|
315
|
-
if (
|
|
316
|
-
|
|
354
|
+
if (job.status === "complete" || job.status === "failed" || job.status === "paused" || job.status === "stopped") {
|
|
355
|
+
rememberFleetJob(state, job);
|
|
356
|
+
if (!nestedRefreshFailed && !hasLiveNestedDescendants(job.nestedChildren) && (previousStatus !== job.status || !state.cleanupTimers.has(job.asyncId))) {
|
|
357
|
+
scheduleCleanup(job.asyncId);
|
|
358
|
+
}
|
|
317
359
|
}
|
|
318
360
|
if (widgetRenderKey(job) !== widgetStateBefore) widgetChanged = true;
|
|
319
361
|
continue;
|
|
@@ -328,6 +370,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
328
370
|
job.status = "failed";
|
|
329
371
|
job.updatedAt = Date.now();
|
|
330
372
|
}
|
|
373
|
+
rememberFleetJob(state, job);
|
|
331
374
|
if (!hasLiveNestedDescendants(job.nestedChildren) && !state.cleanupTimers.has(job.asyncId)) {
|
|
332
375
|
scheduleCleanup(job.asyncId);
|
|
333
376
|
}
|
|
@@ -356,10 +399,12 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
356
399
|
state.asyncJobs.set(info.id, {
|
|
357
400
|
asyncId: info.id,
|
|
358
401
|
asyncDir,
|
|
402
|
+
...(typeof info.cwd === "string" ? { cwd: path.resolve(info.cwd) } : {}),
|
|
359
403
|
status: "queued",
|
|
360
404
|
pid: typeof info.pid === "number" ? info.pid : undefined,
|
|
361
405
|
...(typeof info.sessionId === "string" ? { sessionId: info.sessionId } : {}),
|
|
362
406
|
mode: info.mode ?? (info.chain ? "chain" : "single"),
|
|
407
|
+
description: info.goal ?? info.task,
|
|
363
408
|
agents,
|
|
364
409
|
chainStepCount: info.chainStepCount,
|
|
365
410
|
parallelGroups: validParallelGroups,
|
|
@@ -374,6 +419,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
374
419
|
turnBudget: info.turnBudget,
|
|
375
420
|
controlEventCursor: 0,
|
|
376
421
|
});
|
|
422
|
+
rememberFleetJob(state, state.asyncJobs.get(info.id)!);
|
|
377
423
|
ensurePoller();
|
|
378
424
|
if (state.lastUiContext) {
|
|
379
425
|
rerenderWidget(state.lastUiContext);
|
|
@@ -381,14 +427,15 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
381
427
|
};
|
|
382
428
|
|
|
383
429
|
const handleComplete = (data: unknown) => {
|
|
384
|
-
const result = data as { id?: string; success?: boolean; asyncDir?: string; sessionId?: string };
|
|
430
|
+
const result = data as { id?: string; success?: boolean; state?: AsyncJobState["status"]; asyncDir?: string; sessionId?: string; stopped?: boolean };
|
|
385
431
|
if (typeof state.currentSessionId === "string" && result.sessionId !== state.currentSessionId) return;
|
|
386
432
|
const asyncId = result.id;
|
|
387
433
|
if (!asyncId) return;
|
|
388
434
|
const job = state.asyncJobs.get(asyncId);
|
|
389
435
|
let nestedRefreshFailed = false;
|
|
390
436
|
if (job) {
|
|
391
|
-
job.status = result.success ? "complete" : "failed";
|
|
437
|
+
job.status = result.state ?? (result.success ? "complete" : "failed");
|
|
438
|
+
job.stopped = result.stopped ?? job.stopped;
|
|
392
439
|
job.updatedAt = Date.now();
|
|
393
440
|
if (result.asyncDir) job.asyncDir = result.asyncDir;
|
|
394
441
|
try {
|
|
@@ -398,6 +445,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
398
445
|
console.error(`Failed to refresh nested async descendants for '${job.asyncDir}':`, error);
|
|
399
446
|
}
|
|
400
447
|
}
|
|
448
|
+
if (job) rememberFleetJob(state, job);
|
|
401
449
|
if (state.lastUiContext) {
|
|
402
450
|
rerenderWidget(state.lastUiContext);
|
|
403
451
|
}
|
|
@@ -410,6 +458,7 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
410
458
|
}
|
|
411
459
|
state.cleanupTimers.clear();
|
|
412
460
|
state.asyncJobs.clear();
|
|
461
|
+
state.fleetJobs?.clear();
|
|
413
462
|
state.foregroundControls?.clear();
|
|
414
463
|
state.lastForegroundControlId = null;
|
|
415
464
|
state.resultFileCoalescer.clear();
|
|
@@ -430,12 +479,14 @@ export function createAsyncJobTracker(pi: Pick<ExtensionAPI, "events">, state: S
|
|
|
430
479
|
return;
|
|
431
480
|
}
|
|
432
481
|
for (const run of runs) {
|
|
433
|
-
|
|
482
|
+
const job = summaryToJob(run);
|
|
483
|
+
state.asyncJobs.set(run.id, job);
|
|
484
|
+
rememberFleetJob(state, job);
|
|
434
485
|
}
|
|
435
486
|
if (runs.length === 0) return;
|
|
436
487
|
ensurePoller();
|
|
437
488
|
if (state.lastUiContext?.hasUI) rerenderWidget(state.lastUiContext);
|
|
438
489
|
};
|
|
439
490
|
|
|
440
|
-
return { ensurePoller, handleStarted, handleComplete, resetJobs, restoreActiveJobs };
|
|
491
|
+
return { ensurePoller, refreshWidget, handleStarted, handleComplete, resetJobs, restoreActiveJobs };
|
|
441
492
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { ASYNC_DIR, RESULTS_DIR, type AsyncStatus, type
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { ASYNC_DIR, RESULTS_DIR, type AcceptanceInput, type AsyncStatus, type SteeringRecoveryDescriptor } from "../../shared/types.ts";
|
|
4
|
+
import type { AgentConfig } from "../../agents/agents.ts";
|
|
5
|
+
import { validateAcceptanceInput } from "../shared/acceptance.ts";
|
|
6
|
+
import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
|
|
7
|
+
import { intersectSubagentCapabilityCeilings, parseSubagentCapabilityCeiling, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
|
|
8
|
+
import { resolveTurnBudgetConfig } from "../shared/turn-budget.ts";
|
|
6
9
|
import { reconcileAsyncRun } from "./stale-run-reconciler.ts";
|
|
7
10
|
|
|
8
|
-
export const ASYNC_RESUME_INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2";
|
|
9
|
-
|
|
10
11
|
export interface AsyncResumeParams {
|
|
11
12
|
id?: string;
|
|
12
13
|
runId?: string;
|
|
@@ -23,6 +24,7 @@ export interface AsyncResumeDeps {
|
|
|
23
24
|
|
|
24
25
|
export interface AsyncResumeOptions {
|
|
25
26
|
requireSessionFile?: boolean;
|
|
27
|
+
sessionId?: string;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export type AsyncResumeTarget = {
|
|
@@ -32,49 +34,15 @@ export type AsyncResumeTarget = {
|
|
|
32
34
|
state: AsyncStatus["state"];
|
|
33
35
|
agent: string;
|
|
34
36
|
index: number;
|
|
35
|
-
intercomTarget: string;
|
|
36
37
|
cwd?: string;
|
|
37
38
|
sessionFile?: string;
|
|
39
|
+
model?: string;
|
|
40
|
+
thinking?: string;
|
|
41
|
+
recoveryDescriptor?: SteeringRecoveryDescriptor;
|
|
42
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
43
|
+
launchContractDigest?: string;
|
|
38
44
|
};
|
|
39
45
|
|
|
40
|
-
type KillFn = (pid: number, signal?: NodeJS.Signals | 0) => boolean;
|
|
41
|
-
|
|
42
|
-
export function interruptLiveAsyncResumeTarget(input: {
|
|
43
|
-
target: AsyncResumeTarget & { kind: "live" };
|
|
44
|
-
state?: Pick<SubagentState, "asyncJobs">;
|
|
45
|
-
kill?: KillFn;
|
|
46
|
-
now?: () => number;
|
|
47
|
-
resultsDir?: string;
|
|
48
|
-
}): { ok: true; asyncId: string } | { ok: false; message: string } {
|
|
49
|
-
const asyncId = input.target.runId;
|
|
50
|
-
if (!input.target.asyncDir) {
|
|
51
|
-
return { ok: false, message: `Async run ${asyncId} is live but does not have an async directory to interrupt.` };
|
|
52
|
-
}
|
|
53
|
-
const status = reconcileAsyncRun(input.target.asyncDir, { resultsDir: input.resultsDir, kill: input.kill, now: input.now }).status;
|
|
54
|
-
if (!status || status.state !== "running" || typeof status.pid !== "number") {
|
|
55
|
-
return { ok: false, message: `Async run ${asyncId} is live but no interrupt-capable runner pid was found.` };
|
|
56
|
-
}
|
|
57
|
-
try {
|
|
58
|
-
deliverInterruptRequest({
|
|
59
|
-
asyncDir: input.target.asyncDir,
|
|
60
|
-
pid: status.pid,
|
|
61
|
-
kill: input.kill,
|
|
62
|
-
signal: ASYNC_RESUME_INTERRUPT_SIGNAL,
|
|
63
|
-
now: input.now,
|
|
64
|
-
source: "async-resume",
|
|
65
|
-
});
|
|
66
|
-
const tracked = input.state?.asyncJobs.get(asyncId);
|
|
67
|
-
if (tracked) {
|
|
68
|
-
tracked.activityState = undefined;
|
|
69
|
-
tracked.updatedAt = input.now?.() ?? Date.now();
|
|
70
|
-
}
|
|
71
|
-
return { ok: true, asyncId };
|
|
72
|
-
} catch (error) {
|
|
73
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
74
|
-
return { ok: false, message: `Failed to interrupt async run ${asyncId}: ${message}` };
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
46
|
interface AsyncResultFile {
|
|
79
47
|
id?: string;
|
|
80
48
|
runId?: string;
|
|
@@ -83,8 +51,13 @@ interface AsyncResultFile {
|
|
|
83
51
|
state?: string;
|
|
84
52
|
success?: boolean;
|
|
85
53
|
cwd?: string;
|
|
54
|
+
sessionId?: string;
|
|
86
55
|
sessionFile?: string;
|
|
87
|
-
|
|
56
|
+
model?: string;
|
|
57
|
+
thinking?: string;
|
|
58
|
+
launchContractDigest?: string;
|
|
59
|
+
capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
|
|
60
|
+
results?: Array<{ agent?: string; success?: boolean; sessionFile?: string; intercomTarget?: string; model?: string; thinking?: string; launchContractDigest?: string; capabilityCeiling?: ResolvedSubagentCapabilityCeiling }>;
|
|
88
61
|
}
|
|
89
62
|
|
|
90
63
|
export interface AsyncRunLocation {
|
|
@@ -122,9 +95,13 @@ function validateResultFile(value: unknown, resultPath: string): AsyncResultFile
|
|
|
122
95
|
const agent = validateOptionalString(child, "agent", resultPath, `results[${index}].agent`);
|
|
123
96
|
const sessionFile = validateOptionalString(child, "sessionFile", resultPath, `results[${index}].sessionFile`);
|
|
124
97
|
const intercomTarget = validateOptionalString(child, "intercomTarget", resultPath, `results[${index}].intercomTarget`);
|
|
98
|
+
const model = validateOptionalString(child, "model", resultPath, `results[${index}].model`);
|
|
99
|
+
const thinking = validateOptionalString(child, "thinking", resultPath, `results[${index}].thinking`);
|
|
100
|
+
const launchContractDigest = validateOptionalString(child, "launchContractDigest", resultPath, `results[${index}].launchContractDigest`);
|
|
101
|
+
const capabilityCeiling = child.capabilityCeiling === undefined ? undefined : parseSubagentCapabilityCeiling(child.capabilityCeiling, `async result file '${resultPath}' results[${index}].capabilityCeiling`);
|
|
125
102
|
const success = child.success;
|
|
126
103
|
if (success !== undefined && typeof success !== "boolean") throw new Error(`Invalid async result file '${resultPath}': results[${index}].success must be a boolean.`);
|
|
127
|
-
return { agent, sessionFile, intercomTarget, ...(typeof success === "boolean" ? { success } : {}) };
|
|
104
|
+
return { agent, sessionFile, intercomTarget, model, thinking, launchContractDigest, ...(capabilityCeiling ? { capabilityCeiling } : {}), ...(typeof success === "boolean" ? { success } : {}) };
|
|
128
105
|
});
|
|
129
106
|
}
|
|
130
107
|
const success = data.success;
|
|
@@ -136,7 +113,12 @@ function validateResultFile(value: unknown, resultPath: string): AsyncResultFile
|
|
|
136
113
|
mode: validateOptionalString(data, "mode", resultPath),
|
|
137
114
|
state: validateOptionalString(data, "state", resultPath),
|
|
138
115
|
cwd: validateOptionalString(data, "cwd", resultPath),
|
|
116
|
+
sessionId: validateOptionalString(data, "sessionId", resultPath),
|
|
139
117
|
sessionFile: validateOptionalString(data, "sessionFile", resultPath),
|
|
118
|
+
model: validateOptionalString(data, "model", resultPath),
|
|
119
|
+
thinking: validateOptionalString(data, "thinking", resultPath),
|
|
120
|
+
launchContractDigest: validateOptionalString(data, "launchContractDigest", resultPath),
|
|
121
|
+
...(data.capabilityCeiling === undefined ? {} : { capabilityCeiling: parseSubagentCapabilityCeiling(data.capabilityCeiling, `async result file '${resultPath}' capabilityCeiling`) }),
|
|
140
122
|
...(typeof success === "boolean" ? { success } : {}),
|
|
141
123
|
...(results ? { results } : {}),
|
|
142
124
|
};
|
|
@@ -252,7 +234,7 @@ export function resolveAsyncRunLocation(params: AsyncResumeParams, asyncDirRoot:
|
|
|
252
234
|
}
|
|
253
235
|
|
|
254
236
|
function resultState(result: AsyncResultFile): AsyncStatus["state"] {
|
|
255
|
-
if (result.state === "complete" || result.state === "failed" || result.state === "paused" || result.state === "running" || result.state === "queued") {
|
|
237
|
+
if (result.state === "complete" || result.state === "failed" || result.state === "paused" || result.state === "stopped" || result.state === "running" || result.state === "queued") {
|
|
256
238
|
return result.state;
|
|
257
239
|
}
|
|
258
240
|
return result.success ? "complete" : "failed";
|
|
@@ -264,16 +246,131 @@ function validateStatusForResume(status: AsyncStatus | null, source: string): vo
|
|
|
264
246
|
if (status.sessionId !== undefined && typeof status.sessionId !== "string") throw new Error(`Invalid async status '${source}': sessionId must be a string.`);
|
|
265
247
|
if (status.cwd !== undefined && typeof status.cwd !== "string") throw new Error(`Invalid async status '${source}': cwd must be a string.`);
|
|
266
248
|
if (status.sessionFile !== undefined && typeof status.sessionFile !== "string") throw new Error(`Invalid async status '${source}': sessionFile must be a string.`);
|
|
249
|
+
if (status.capabilityCeiling !== undefined) status.capabilityCeiling = parseSubagentCapabilityCeiling(status.capabilityCeiling, `async status '${source}' capabilityCeiling`);
|
|
267
250
|
if (status.steps !== undefined) {
|
|
268
251
|
if (!Array.isArray(status.steps)) throw new Error(`Invalid async status '${source}': steps must be an array.`);
|
|
269
252
|
status.steps.forEach((step, index) => {
|
|
270
253
|
if (!step || typeof step !== "object" || Array.isArray(step)) throw new Error(`Invalid async status '${source}': steps[${index}] must be an object.`);
|
|
271
|
-
|
|
272
|
-
if (
|
|
254
|
+
const stepRecord = step as Record<string, unknown>;
|
|
255
|
+
if (typeof stepRecord.agent !== "string") throw new Error(`Invalid async status '${source}': steps[${index}].agent must be a string.`);
|
|
256
|
+
if (stepRecord.sessionFile !== undefined && typeof stepRecord.sessionFile !== "string") throw new Error(`Invalid async status '${source}': steps[${index}].sessionFile must be a string.`);
|
|
257
|
+
if (stepRecord.model !== undefined && typeof stepRecord.model !== "string") throw new Error(`Invalid async status '${source}': steps[${index}].model must be a string.`);
|
|
258
|
+
if (stepRecord.thinking !== undefined && typeof stepRecord.thinking !== "string") throw new Error(`Invalid async status '${source}': steps[${index}].thinking must be a string.`);
|
|
259
|
+
if (stepRecord.capabilityCeiling !== undefined) stepRecord.capabilityCeiling = parseSubagentCapabilityCeiling(stepRecord.capabilityCeiling, `async status '${source}' steps[${index}].capabilityCeiling`);
|
|
273
260
|
});
|
|
274
261
|
}
|
|
275
262
|
}
|
|
276
263
|
|
|
264
|
+
function normalizeRecoveryAcceptance(value: unknown, descriptorPath: string): AcceptanceInput | undefined {
|
|
265
|
+
if (value && typeof value === "object" && !Array.isArray(value) && ("explicit" in value || "inferredReason" in value)) {
|
|
266
|
+
const { explicit, inferredReason: _inferredReason, ...publicAcceptance } = value as Record<string, unknown>;
|
|
267
|
+
if (explicit === false) return undefined;
|
|
268
|
+
value = publicAcceptance;
|
|
269
|
+
}
|
|
270
|
+
const errors = validateAcceptanceInput(value, "recoveryDescriptor.acceptance");
|
|
271
|
+
if (errors.length) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${errors.join(" ")}`);
|
|
272
|
+
return value as AcceptanceInput;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): SteeringRecoveryDescriptor | undefined {
|
|
276
|
+
if (!asyncDir) return undefined;
|
|
277
|
+
const descriptorPath = path.join(asyncDir, "recovery-descriptor.json");
|
|
278
|
+
if (!fs.existsSync(descriptorPath)) return undefined;
|
|
279
|
+
let value: unknown;
|
|
280
|
+
try {
|
|
281
|
+
value = JSON.parse(fs.readFileSync(descriptorPath, "utf-8"));
|
|
282
|
+
} catch (error) {
|
|
283
|
+
throw new Error(`Failed to parse async recovery descriptor '${descriptorPath}': ${getErrorMessage(error)}`, { cause: error instanceof Error ? error : undefined });
|
|
284
|
+
}
|
|
285
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': expected an object.`);
|
|
286
|
+
const parsed = value as Record<string, unknown>;
|
|
287
|
+
const allowedFields = new Set([
|
|
288
|
+
"version", "sourceRunId", "agentContract", "agent", "sessionFile", "cwd", "model", "fallbackModels", "thinking", "tools", "extensions",
|
|
289
|
+
"subagentOnlyExtensions", "mcpDirectTools", "systemPrompt", "systemPromptMode", "inheritProjectContext", "inheritSkills", "skills",
|
|
290
|
+
"skillPath", "agentFilePath", "completionGuard", "memory", "outputPath", "outputMode", "structuredOutputSchema", "acceptance", "sessionDir", "artifactConfig",
|
|
291
|
+
"artifactsDir", "maxOutput", "controlConfig", "absoluteDeadlineAt", "initialTurnBudget", "initialToolBudget", "maxSubagentDepth", "share", "capabilityCeiling",
|
|
292
|
+
]);
|
|
293
|
+
for (const field of Object.keys(parsed)) {
|
|
294
|
+
if (!allowedFields.has(field)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': unknown field '${field}'.`);
|
|
295
|
+
}
|
|
296
|
+
const requiredStrings = ["sourceRunId", "agent", "cwd", "systemPromptMode", "outputMode"] as const;
|
|
297
|
+
for (const field of requiredStrings) {
|
|
298
|
+
if (typeof parsed[field] !== "string" || !(parsed[field] as string).trim()) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${field} must be a non-empty string.`);
|
|
299
|
+
}
|
|
300
|
+
if (parsed.version !== 1) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': version must be 1.`);
|
|
301
|
+
if (parsed.capabilityCeiling !== undefined) parsed.capabilityCeiling = parseSubagentCapabilityCeiling(parsed.capabilityCeiling, `async recovery descriptor '${descriptorPath}' capabilityCeiling`);
|
|
302
|
+
if (parsed.agentContract !== undefined) {
|
|
303
|
+
if (!parsed.agentContract || typeof parsed.agentContract !== "object" || Array.isArray(parsed.agentContract)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': agentContract must be an object.`);
|
|
304
|
+
const contract = parsed.agentContract as Record<string, unknown>;
|
|
305
|
+
if (contract.version !== 1 || Object.keys(contract).some((key) => key !== "version")) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': agentContract must be { version: 1 }.`);
|
|
306
|
+
}
|
|
307
|
+
if (parsed.systemPromptMode !== "append" && parsed.systemPromptMode !== "replace") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': systemPromptMode is invalid.`);
|
|
308
|
+
if (parsed.outputMode !== "inline" && parsed.outputMode !== "file-only") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': outputMode is invalid.`);
|
|
309
|
+
for (const field of ["inheritProjectContext", "inheritSkills", "share"] as const) {
|
|
310
|
+
if (typeof parsed[field] !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${field} must be a boolean.`);
|
|
311
|
+
}
|
|
312
|
+
if (!Number.isInteger(parsed.maxSubagentDepth) || (parsed.maxSubagentDepth as number) < 0) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': maxSubagentDepth must be a non-negative integer.`);
|
|
313
|
+
for (const field of ["fallbackModels", "tools", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "skills", "skillPath"] as const) {
|
|
314
|
+
const item = parsed[field];
|
|
315
|
+
if (item !== undefined && (!Array.isArray(item) || item.some((entry) => typeof entry !== "string" || !entry.trim()))) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${field} must contain non-empty strings.`);
|
|
316
|
+
}
|
|
317
|
+
if (parsed.systemPrompt !== undefined && typeof parsed.systemPrompt !== "string") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': systemPrompt must be a string.`);
|
|
318
|
+
for (const field of ["sessionFile", "model", "thinking", "agentFilePath", "outputPath", "sessionDir", "artifactsDir"] as const) {
|
|
319
|
+
if (parsed[field] !== undefined && (typeof parsed[field] !== "string" || !(parsed[field] as string).trim())) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${field} must be a non-empty string.`);
|
|
320
|
+
}
|
|
321
|
+
if (parsed.completionGuard !== undefined && typeof parsed.completionGuard !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': completionGuard must be a boolean.`);
|
|
322
|
+
if (parsed.structuredOutputSchema !== undefined && (!parsed.structuredOutputSchema || typeof parsed.structuredOutputSchema !== "object" || Array.isArray(parsed.structuredOutputSchema))) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': structuredOutputSchema must be an object.`);
|
|
323
|
+
if (parsed.memory !== undefined) {
|
|
324
|
+
if (!parsed.memory || typeof parsed.memory !== "object" || Array.isArray(parsed.memory)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': memory must be an object.`);
|
|
325
|
+
const memory = parsed.memory as Record<string, unknown>;
|
|
326
|
+
if ((memory.scope !== "project" && memory.scope !== "user") || typeof memory.path !== "string" || !memory.path.trim()) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': memory is invalid.`);
|
|
327
|
+
}
|
|
328
|
+
if (parsed.absoluteDeadlineAt !== undefined && (!Number.isFinite(parsed.absoluteDeadlineAt) || (parsed.absoluteDeadlineAt as number) <= 0)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': absoluteDeadlineAt must be a positive timestamp.`);
|
|
329
|
+
if (parsed.initialTurnBudget !== undefined) {
|
|
330
|
+
const result = resolveTurnBudgetConfig(parsed.initialTurnBudget, "recoveryDescriptor.initialTurnBudget");
|
|
331
|
+
if (result.error) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${result.error}`);
|
|
332
|
+
}
|
|
333
|
+
if (parsed.initialToolBudget !== undefined) {
|
|
334
|
+
const result = validateToolBudgetConfig(parsed.initialToolBudget, "recoveryDescriptor.initialToolBudget");
|
|
335
|
+
if (result.error) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${result.error}`);
|
|
336
|
+
}
|
|
337
|
+
if (parsed.maxOutput !== undefined) {
|
|
338
|
+
if (!parsed.maxOutput || typeof parsed.maxOutput !== "object" || Array.isArray(parsed.maxOutput)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': maxOutput must be an object.`);
|
|
339
|
+
for (const field of ["bytes", "lines"] as const) {
|
|
340
|
+
const item = (parsed.maxOutput as Record<string, unknown>)[field];
|
|
341
|
+
if (item !== undefined && (!Number.isInteger(item) || (item as number) < 1)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': maxOutput.${field} must be a positive integer.`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (parsed.artifactConfig !== undefined) {
|
|
345
|
+
if (!parsed.artifactConfig || typeof parsed.artifactConfig !== "object" || Array.isArray(parsed.artifactConfig)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': artifactConfig must be an object.`);
|
|
346
|
+
const artifact = parsed.artifactConfig as Record<string, unknown>;
|
|
347
|
+
for (const field of ["enabled", "includeInput", "includeOutput", "includeJsonl", "includeMetadata"] as const) {
|
|
348
|
+
if (typeof artifact[field] !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': artifactConfig.${field} must be a boolean.`);
|
|
349
|
+
}
|
|
350
|
+
if (artifact.includeTranscript !== undefined && typeof artifact.includeTranscript !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': artifactConfig.includeTranscript must be a boolean.`);
|
|
351
|
+
if (!Number.isInteger(artifact.cleanupDays) || (artifact.cleanupDays as number) < 0) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': artifactConfig.cleanupDays must be a non-negative integer.`);
|
|
352
|
+
}
|
|
353
|
+
if (parsed.controlConfig !== undefined) {
|
|
354
|
+
if (!parsed.controlConfig || typeof parsed.controlConfig !== "object" || Array.isArray(parsed.controlConfig)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': controlConfig must be an object.`);
|
|
355
|
+
const control = parsed.controlConfig as Record<string, unknown>;
|
|
356
|
+
if (typeof control.enabled !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': controlConfig.enabled must be a boolean.`);
|
|
357
|
+
for (const field of ["needsAttentionAfterMs", "activeNoticeAfterMs", "failedToolAttemptsBeforeAttention"] as const) {
|
|
358
|
+
if (!Number.isInteger(control[field]) || (control[field] as number) < 1) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': controlConfig.${field} must be a positive integer.`);
|
|
359
|
+
}
|
|
360
|
+
for (const field of ["activeNoticeAfterTurns", "activeNoticeAfterTokens"] as const) {
|
|
361
|
+
if (control[field] !== undefined && (!Number.isInteger(control[field]) || (control[field] as number) < 1)) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': controlConfig.${field} must be a positive integer.`);
|
|
362
|
+
}
|
|
363
|
+
if (!Array.isArray(control.notifyOn) || control.notifyOn.some((item) => item !== "active_long_running" && item !== "needs_attention")) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': controlConfig.notifyOn is invalid.`);
|
|
364
|
+
if (!Array.isArray(control.notifyChannels) || control.notifyChannels.some((item) => item !== "event" && item !== "async" && item !== "intercom")) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': controlConfig.notifyChannels is invalid.`);
|
|
365
|
+
}
|
|
366
|
+
if (parsed.acceptance !== undefined) {
|
|
367
|
+
const acceptance = normalizeRecoveryAcceptance(parsed.acceptance, descriptorPath);
|
|
368
|
+
if (acceptance === undefined) delete parsed.acceptance;
|
|
369
|
+
else parsed.acceptance = acceptance;
|
|
370
|
+
}
|
|
371
|
+
return parsed as unknown as SteeringRecoveryDescriptor;
|
|
372
|
+
}
|
|
373
|
+
|
|
277
374
|
function validateResumeSessionFile(runId: string, sessionFile: string): string {
|
|
278
375
|
if (path.extname(sessionFile) !== ".jsonl") throw new Error(`Async run '${runId}' session file must be a .jsonl file: ${sessionFile}`);
|
|
279
376
|
const resolved = path.resolve(sessionFile);
|
|
@@ -295,10 +392,16 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
295
392
|
: undefined;
|
|
296
393
|
const status = reconciliation?.status ?? null;
|
|
297
394
|
validateStatusForResume(status, location.asyncDir ? path.join(location.asyncDir, "status.json") : "status.json");
|
|
395
|
+
const recoveryDescriptor = readAsyncRecoveryDescriptor(location.asyncDir);
|
|
298
396
|
const result = location.resultPath ? readResultFile(location.resultPath) : undefined;
|
|
299
397
|
const runId = status?.runId ?? result?.runId ?? result?.id ?? location.resolvedId ?? (location.asyncDir ? path.basename(location.asyncDir) : "unknown");
|
|
398
|
+
if (options.sessionId && ((status && status.sessionId !== options.sessionId) || (result && result.sessionId !== options.sessionId))) {
|
|
399
|
+
throw new Error(`Async run '${runId}' was not found in the active session.`);
|
|
400
|
+
}
|
|
401
|
+
if (recoveryDescriptor && recoveryDescriptor.sourceRunId !== runId) throw new Error(`Async run '${runId}' has a recovery descriptor for a different source run.`);
|
|
300
402
|
const state = status?.state ?? (result ? resultState(result) : undefined);
|
|
301
403
|
if (!state) throw new Error(`Status file not found for async run '${runId}'.`);
|
|
404
|
+
if (state === "stopped") throw new Error(`Async run '${runId}' was stopped and cannot be resumed. Start a new run instead.`);
|
|
302
405
|
|
|
303
406
|
const statusSteps = status?.steps ?? [];
|
|
304
407
|
const resultSteps = result?.results ?? [];
|
|
@@ -312,6 +415,7 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
312
415
|
if (requestedIndex < 0 || requestedIndex >= stepCount) throw new Error(`Async run '${runId}' has ${stepCount} children. Index ${requestedIndex} is out of range.`);
|
|
313
416
|
const selectedStep = statusSteps[requestedIndex];
|
|
314
417
|
if (selectedStep?.status === "running") {
|
|
418
|
+
const capabilityCeiling = intersectSubagentCapabilityCeilings(status?.capabilityCeiling, selectedStep.capabilityCeiling);
|
|
315
419
|
return {
|
|
316
420
|
kind: "live",
|
|
317
421
|
runId,
|
|
@@ -319,9 +423,13 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
319
423
|
state,
|
|
320
424
|
agent: selectedStep.agent,
|
|
321
425
|
index: requestedIndex,
|
|
322
|
-
intercomTarget: resolveSubagentIntercomTarget(runId, selectedStep.agent, requestedIndex),
|
|
323
426
|
cwd: status?.cwd ?? result?.cwd,
|
|
324
427
|
sessionFile: selectedStep.sessionFile ?? status?.sessionFile ?? result?.sessionFile,
|
|
428
|
+
model: selectedStep.model,
|
|
429
|
+
thinking: selectedStep.thinking,
|
|
430
|
+
launchContractDigest: selectedStep.launchContractDigest ?? result?.results?.[requestedIndex]?.launchContractDigest ?? result?.launchContractDigest,
|
|
431
|
+
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
432
|
+
...(recoveryDescriptor ? { recoveryDescriptor } : {}),
|
|
325
433
|
};
|
|
326
434
|
}
|
|
327
435
|
if (selectedStep?.status === "pending") throw new Error(`Async run '${runId}' child ${requestedIndex} is pending and has not started yet. Wait for it to run or complete before resuming.`);
|
|
@@ -334,6 +442,7 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
334
442
|
if (!selected) {
|
|
335
443
|
throw new Error(`Async run '${runId}' has ${running.length} running children. Provide index to choose one.`);
|
|
336
444
|
}
|
|
445
|
+
const capabilityCeiling = intersectSubagentCapabilityCeilings(status?.capabilityCeiling, selected.step.capabilityCeiling);
|
|
337
446
|
return {
|
|
338
447
|
kind: "live",
|
|
339
448
|
runId,
|
|
@@ -341,9 +450,13 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
341
450
|
state,
|
|
342
451
|
agent: selected.step.agent,
|
|
343
452
|
index: selected.index,
|
|
344
|
-
intercomTarget: resolveSubagentIntercomTarget(runId, selected.step.agent, selected.index),
|
|
345
453
|
cwd: status?.cwd ?? result?.cwd,
|
|
346
454
|
sessionFile: selected.step.sessionFile ?? status?.sessionFile ?? result?.sessionFile,
|
|
455
|
+
model: selected.step.model,
|
|
456
|
+
thinking: selected.step.thinking,
|
|
457
|
+
launchContractDigest: selected.step.launchContractDigest ?? result?.results?.[selected.index]?.launchContractDigest ?? result?.launchContractDigest,
|
|
458
|
+
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
459
|
+
...(recoveryDescriptor ? { recoveryDescriptor } : {}),
|
|
347
460
|
};
|
|
348
461
|
}
|
|
349
462
|
}
|
|
@@ -356,11 +469,15 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
356
469
|
if (index < 0 || index >= stepCount) throw new Error(`Async run '${runId}' has ${stepCount} children. Index ${index} is out of range.`);
|
|
357
470
|
const agent = statusSteps[index]?.agent ?? resultSteps[index]?.agent ?? result?.agent;
|
|
358
471
|
if (!agent) throw new Error(`Could not determine child agent for async run '${runId}'.`);
|
|
472
|
+
if (recoveryDescriptor && recoveryDescriptor.agent !== agent) throw new Error(`Async run '${runId}' has a recovery descriptor for '${recoveryDescriptor.agent}', not '${agent}'.`);
|
|
359
473
|
const sessionFile = statusSteps[index]?.sessionFile
|
|
360
474
|
?? resultSteps[index]?.sessionFile
|
|
361
475
|
?? (stepCount === 1 ? status?.sessionFile ?? result?.sessionFile : undefined);
|
|
362
476
|
if (!sessionFile && requireSessionFile) throw new Error(`Async run '${runId}' child ${index} does not have a persisted session file to resume from.`);
|
|
363
477
|
const resolvedSessionFile = sessionFile ? validateResumeSessionFile(runId, sessionFile) : undefined;
|
|
478
|
+
const stepModel = statusSteps[index]?.model ?? resultSteps[index]?.model ?? (stepCount === 1 ? result?.model : undefined);
|
|
479
|
+
const stepThinking = statusSteps[index]?.thinking ?? resultSteps[index]?.thinking ?? (stepCount === 1 ? result?.thinking : undefined);
|
|
480
|
+
const capabilityCeiling = intersectSubagentCapabilityCeilings(status?.capabilityCeiling, statusSteps[index]?.capabilityCeiling, result?.capabilityCeiling, resultSteps[index]?.capabilityCeiling);
|
|
364
481
|
|
|
365
482
|
return {
|
|
366
483
|
kind: "revive",
|
|
@@ -369,9 +486,38 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
|
|
|
369
486
|
state,
|
|
370
487
|
agent,
|
|
371
488
|
index,
|
|
372
|
-
intercomTarget: resolveSubagentIntercomTarget(runId, agent, index),
|
|
373
489
|
cwd: status?.cwd ?? result?.cwd,
|
|
374
490
|
...(resolvedSessionFile ? { sessionFile: resolvedSessionFile } : {}),
|
|
491
|
+
...(stepModel ? { model: stepModel } : {}),
|
|
492
|
+
...(stepThinking ? { thinking: stepThinking } : {}),
|
|
493
|
+
launchContractDigest: statusSteps[index]?.launchContractDigest ?? resultSteps[index]?.launchContractDigest ?? result?.launchContractDigest,
|
|
494
|
+
...(capabilityCeiling ? { capabilityCeiling } : {}),
|
|
495
|
+
...(recoveryDescriptor ? { recoveryDescriptor } : {}),
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export function applySteeringRecoveryAgentConfig(agentConfig: AgentConfig, descriptor: SteeringRecoveryDescriptor): AgentConfig {
|
|
500
|
+
return {
|
|
501
|
+
...agentConfig,
|
|
502
|
+
model: descriptor.model,
|
|
503
|
+
fallbackModels: descriptor.fallbackModels ? [...descriptor.fallbackModels] : undefined,
|
|
504
|
+
thinking: descriptor.thinking,
|
|
505
|
+
tools: descriptor.tools ? [...descriptor.tools] : undefined,
|
|
506
|
+
extensions: descriptor.extensions ? [...descriptor.extensions] : undefined,
|
|
507
|
+
subagentOnlyExtensions: descriptor.subagentOnlyExtensions ? [...descriptor.subagentOnlyExtensions] : undefined,
|
|
508
|
+
mcpDirectTools: descriptor.mcpDirectTools ? [...descriptor.mcpDirectTools] : undefined,
|
|
509
|
+
systemPrompt: descriptor.systemPrompt,
|
|
510
|
+
systemPromptMode: descriptor.systemPromptMode,
|
|
511
|
+
inheritProjectContext: descriptor.inheritProjectContext,
|
|
512
|
+
inheritSkills: descriptor.inheritSkills,
|
|
513
|
+
skills: descriptor.skills ? [...descriptor.skills] : undefined,
|
|
514
|
+
skillPath: descriptor.skillPath ? [...descriptor.skillPath] : undefined,
|
|
515
|
+
filePath: descriptor.agentFilePath,
|
|
516
|
+
completionGuard: descriptor.completionGuard,
|
|
517
|
+
memory: descriptor.memory ? { ...descriptor.memory } : undefined,
|
|
518
|
+
output: descriptor.outputPath,
|
|
519
|
+
toolBudget: descriptor.initialToolBudget,
|
|
520
|
+
maxSubagentDepth: descriptor.maxSubagentDepth,
|
|
375
521
|
};
|
|
376
522
|
}
|
|
377
523
|
|