@selesai/code 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/diagnostics.d.ts +1 -0
- package/dist/core/diagnostics.d.ts.map +1 -1
- package/dist/core/diagnostics.js.map +1 -1
- package/dist/core/package-manager.d.ts +10 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +93 -8
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/package-manager.test.d.ts +2 -0
- package/dist/core/package-manager.test.d.ts.map +1 -0
- package/dist/core/package-manager.test.js +20 -0
- package/dist/core/package-manager.test.js.map +1 -0
- package/dist/core/resource-loader.d.ts +3 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +10 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/extensions/caveman/caveman-instructions.cjs +39 -0
- package/dist/extensions/caveman/index.js +116 -0
- package/dist/extensions/caveman/package.json +8 -0
- package/dist/extensions/caveman/test/extension.test.js +185 -0
- package/dist/extensions/caveman/test/helpers.test.js +67 -0
- package/dist/extensions/pi-intercom/broker/broker.ts +2 -3
- package/dist/extensions/pi-intercom/broker/paths.ts +10 -5
- package/dist/extensions/pi-intercom/broker/spawn.ts +2 -3
- package/dist/extensions/pi-intercom/config.ts +2 -2
- package/dist/extensions/pi-intercom/index.ts +2 -2
- package/dist/extensions/pi-intercom/package.json +2 -2
- package/dist/extensions/pi-intercom/ui/compose.ts +3 -3
- package/dist/extensions/pi-intercom/ui/inline-message.ts +3 -3
- package/dist/extensions/pi-intercom/ui/session-list.ts +3 -3
- package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +2 -1
- package/dist/extensions/pi-powerline-footer/index.ts +9 -16
- package/dist/extensions/pi-powerline-footer/welcome.ts +23 -16
- package/dist/extensions/pi-powerline-footer/working-vibes.ts +2 -9
- package/dist/extensions/pi-rewind-hook/index.ts +1 -1
- package/dist/extensions/pi-rewind-hook/package.json +1 -1
- package/dist/extensions/pi-subagents/README.md +224 -52
- package/dist/extensions/pi-subagents/install.mjs +93 -0
- package/dist/extensions/pi-subagents/package.json +4 -5
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +1 -1
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +76 -13
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +179 -2
- package/dist/extensions/pi-subagents/src/agents/agent-memory.ts +254 -0
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +11 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +193 -19
- package/dist/extensions/pi-subagents/src/agents/chain-serializer.ts +27 -2
- package/dist/extensions/pi-subagents/src/extension/config.ts +27 -4
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +1 -7
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -2
- package/dist/extensions/pi-subagents/src/extension/index.ts +73 -71
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +369 -0
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +54 -10
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +200 -0
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +21 -253
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +519 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +195 -105
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +88 -2
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +67 -10
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +34 -4
- package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +166 -0
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +156 -1
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +515 -0
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +161 -44
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +1 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +3 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +167 -6
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +514 -0
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +28 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +873 -204
- package/dist/extensions/pi-subagents/src/runs/background/wait.ts +353 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +123 -27
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +174 -70
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +569 -81
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +45 -22
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +172 -21
- package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +128 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +89 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +50 -1
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +35 -4
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +63 -45
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +125 -5
- package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +74 -0
- package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +52 -0
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +28 -5
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +16 -1
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +15 -2
- package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +212 -0
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +133 -22
- package/dist/extensions/pi-subagents/src/shared/settings.ts +3 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +197 -4
- package/dist/extensions/pi-subagents/src/shared/utils.ts +100 -15
- package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +330 -0
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +135 -4
- package/dist/extensions/pi-subagents/src/tui/render.ts +32 -12
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +94 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2772 -0
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +1107 -0
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +463 -0
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +274 -0
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1535 -0
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +242 -0
- package/dist/extensions/pi-subagents/test/integration/doctor-executor.test.ts +128 -0
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +252 -0
- package/dist/extensions/pi-subagents/test/integration/foreground-result-size.test.ts +188 -0
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +1306 -0
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +1033 -0
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +397 -0
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +583 -0
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +691 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +892 -0
- package/dist/extensions/pi-subagents/test/integration/session-tokens.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1824 -0
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1284 -0
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +127 -0
- package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +299 -0
- package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +37 -0
- package/dist/extensions/pi-subagents/test/support/helpers.ts +189 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +335 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +129 -0
- package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +184 -0
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +285 -0
- package/dist/extensions/pi-subagents/test/support/register-loader.mjs +15 -0
- package/dist/extensions/pi-subagents/test/support/ts-loader.mjs +108 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +421 -0
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +213 -0
- package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +343 -0
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +1199 -0
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +556 -0
- package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +420 -0
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +579 -0
- package/dist/extensions/pi-subagents/test/unit/agent-scope.test.ts +20 -0
- package/dist/extensions/pi-subagents/test/unit/agent-selection.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +24 -0
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +86 -0
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +221 -0
- package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +98 -0
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +285 -0
- package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +112 -0
- package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +206 -0
- package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +159 -0
- package/dist/extensions/pi-subagents/test/unit/chain-serializer.test.ts +229 -0
- package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +225 -0
- package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +101 -0
- package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +228 -0
- package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +36 -0
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +152 -0
- package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +72 -0
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +327 -0
- package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +161 -0
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +159 -0
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +217 -0
- package/dist/extensions/pi-subagents/test/unit/extra-agent-dirs.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/file-coalescer.test.ts +68 -0
- package/dist/extensions/pi-subagents/test/unit/foreground-tool-call-compaction.test.ts +78 -0
- package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +415 -0
- package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +126 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +271 -0
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +173 -0
- package/dist/extensions/pi-subagents/test/unit/jsonl-writer.test.ts +119 -0
- package/dist/extensions/pi-subagents/test/unit/live-async-resume.test.ts +170 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +346 -0
- package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +87 -0
- package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +145 -0
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +162 -0
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +470 -0
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +329 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +319 -0
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +70 -0
- package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +247 -0
- package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +113 -0
- package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +94 -0
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +803 -0
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +209 -0
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +238 -0
- package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +118 -0
- package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +299 -0
- package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +408 -0
- package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +134 -0
- package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +257 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +209 -0
- package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +226 -0
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +294 -0
- package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +147 -0
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +997 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +478 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +562 -0
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +213 -0
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +438 -0
- package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +54 -0
- package/dist/extensions/pi-subagents/test/unit/slash-chain-groups.test.ts +464 -0
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +255 -0
- package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +19 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +245 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +517 -0
- package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +72 -0
- package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +52 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +237 -0
- package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +60 -0
- package/dist/extensions/pi-subagents/test/unit/ts-loader.test.ts +33 -0
- package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +202 -0
- package/dist/extensions/pi-subagents/test/unit/types-fork-preamble.test.ts +23 -0
- package/dist/extensions/pi-subagents/test/unit/wait.test.ts +398 -0
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-graph.test.ts +134 -0
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +479 -0
- package/dist/extensions/pi-web-agent/src/changelog-notice.ts +5 -2
- package/dist/extensions/pi-web-agent/src/presentation/config-store.ts +6 -4
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +4 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.js +19 -5
- package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
- package/dist/skills/caveman/SKILL.md +49 -0
- package/dist/skills/handoff-text/SKILL.md +13 -0
- package/docs/docs.json +4 -0
- package/docs/extensions.md +2 -0
- package/docs/settings.md +15 -0
- package/docs/shared-host-extensions.md +109 -0
- package/package.json +1 -1
|
@@ -4,7 +4,8 @@ import * as path from "node:path";
|
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import type { Message } from "@earendil-works/pi-ai";
|
|
6
6
|
import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
7
|
-
import {
|
|
7
|
+
import { createChildTranscriptWriter, type ChildTranscriptWriter } from "../../shared/child-transcript.ts";
|
|
8
|
+
import { consumeInterruptRequest, deliverInterruptRequest, deliverTimeoutRequest, enqueueStepSteer, stepSteerInboxDir, watchAsyncControlInbox, type SteerRequest } from "./control-channel.ts";
|
|
8
9
|
import { appendJsonl as appendRawJsonl, getArtifactPaths } from "../../shared/artifacts.ts";
|
|
9
10
|
import { SELESAI_PACKAGE, getSelesaiSpawnCommand, resolveInstalledSelesaiPackageRoot } from "../shared/pi-spawn.ts";
|
|
10
11
|
import { captureSingleOutputSnapshot, finalizeSingleOutput, formatSavedOutputReference, resolveSingleOutput, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
@@ -15,14 +16,21 @@ import {
|
|
|
15
16
|
type AsyncParallelGroupStatus,
|
|
16
17
|
type AsyncStatus,
|
|
17
18
|
type ChainOutputMap,
|
|
19
|
+
type CostSummary,
|
|
18
20
|
type ModelAttempt,
|
|
19
21
|
type NestedRouteInfo,
|
|
22
|
+
type NestedRunSummary,
|
|
20
23
|
type ResolvedControlConfig,
|
|
24
|
+
type ResolvedTurnBudget,
|
|
25
|
+
type ResolvedToolBudget,
|
|
21
26
|
type SubagentRunMode,
|
|
27
|
+
type ToolBudgetState,
|
|
28
|
+
type TurnBudgetState,
|
|
22
29
|
type Usage,
|
|
23
30
|
type WorkflowGraphSnapshot,
|
|
24
31
|
DEFAULT_MAX_OUTPUT,
|
|
25
32
|
type MaxOutputConfig,
|
|
33
|
+
SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
26
34
|
truncateOutput,
|
|
27
35
|
getSubagentDepthEnv,
|
|
28
36
|
} from "../../shared/types.ts";
|
|
@@ -43,15 +51,17 @@ import {
|
|
|
43
51
|
mapConcurrent,
|
|
44
52
|
aggregateParallelOutputs,
|
|
45
53
|
MAX_PARALLEL_CONCURRENCY,
|
|
54
|
+
DEFAULT_GLOBAL_CONCURRENCY_LIMIT,
|
|
55
|
+
Semaphore,
|
|
46
56
|
} from "../shared/parallel-utils.ts";
|
|
47
|
-
import { buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
|
|
57
|
+
import { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
|
|
48
58
|
import { outputEntryFromAsyncResult, resolveOutputReferences } from "../shared/chain-outputs.ts";
|
|
49
59
|
import { createStructuredOutputRuntime, readStructuredOutput } from "../shared/structured-output.ts";
|
|
50
60
|
import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection } from "../shared/dynamic-fanout.ts";
|
|
51
|
-
import { nestedSummaryFromAsyncStatus, writeNestedEvent } from "../shared/nested-events.ts";
|
|
61
|
+
import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent } from "../shared/nested-events.ts";
|
|
52
62
|
import { formatModelAttemptNote, isRetryableModelFailure } from "../shared/model-fallback.ts";
|
|
53
63
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
54
|
-
import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput } from "../../shared/utils.ts";
|
|
64
|
+
import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput, readStatus } from "../../shared/utils.ts";
|
|
55
65
|
import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
|
|
56
66
|
import {
|
|
57
67
|
createMutatingFailureState,
|
|
@@ -81,7 +91,8 @@ import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts
|
|
|
81
91
|
import { acceptanceFailureMessage, aggregateAcceptanceReport, evaluateAcceptance, formatAcceptancePrompt, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
82
92
|
import { waitForImportedAsyncRoot } from "./chain-root-attachment.ts";
|
|
83
93
|
import { appendRunnerStepsToStatus, consumeChainAppendRequests, countPendingChainAppendRequests } from "./chain-append.ts";
|
|
84
|
-
import {
|
|
94
|
+
import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, shouldAbortForTurnBudget, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from "../shared/turn-budget.ts";
|
|
95
|
+
import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
|
|
85
96
|
|
|
86
97
|
interface SubagentRunConfig {
|
|
87
98
|
id: string;
|
|
@@ -102,6 +113,7 @@ interface SubagentRunConfig {
|
|
|
102
113
|
piArgv1?: string;
|
|
103
114
|
worktreeSetupHook?: string;
|
|
104
115
|
worktreeSetupHookTimeoutMs?: number;
|
|
116
|
+
worktreeBaseDir?: string;
|
|
105
117
|
controlConfig?: ResolvedControlConfig;
|
|
106
118
|
controlIntercomTarget?: string;
|
|
107
119
|
childIntercomTargets?: Array<string | undefined>;
|
|
@@ -110,6 +122,12 @@ interface SubagentRunConfig {
|
|
|
110
122
|
workflowGraph?: WorkflowGraphSnapshot;
|
|
111
123
|
nestedRoute?: NestedRouteInfo;
|
|
112
124
|
nestedSelf?: { parentRunId: string; parentStepIndex?: number; depth: number; path?: Array<{ runId: string; stepIndex?: number; agent?: string }> };
|
|
125
|
+
timeoutMs?: number;
|
|
126
|
+
deadlineAt?: number;
|
|
127
|
+
turnBudget?: ResolvedTurnBudget;
|
|
128
|
+
toolBudget?: ResolvedToolBudget;
|
|
129
|
+
/** Global cap on simultaneously-running subagent tasks within this run. */
|
|
130
|
+
globalConcurrencyLimit?: number;
|
|
113
131
|
}
|
|
114
132
|
|
|
115
133
|
interface StepResult {
|
|
@@ -119,13 +137,23 @@ interface StepResult {
|
|
|
119
137
|
success: boolean;
|
|
120
138
|
exitCode?: number | null;
|
|
121
139
|
skipped?: boolean;
|
|
140
|
+
interrupted?: boolean;
|
|
141
|
+
timedOut?: boolean;
|
|
142
|
+
turnBudget?: TurnBudgetState;
|
|
143
|
+
turnBudgetExceeded?: boolean;
|
|
144
|
+
wrapUpRequested?: boolean;
|
|
145
|
+
toolBudget?: ToolBudgetState;
|
|
146
|
+
toolBudgetBlocked?: boolean;
|
|
122
147
|
sessionFile?: string;
|
|
123
148
|
intercomTarget?: string;
|
|
124
149
|
model?: string;
|
|
125
150
|
attemptedModels?: string[];
|
|
126
151
|
modelAttempts?: ModelAttempt[];
|
|
152
|
+
totalCost?: CostSummary;
|
|
127
153
|
artifactPaths?: ArtifactPaths;
|
|
128
154
|
truncated?: boolean;
|
|
155
|
+
transcriptPath?: string;
|
|
156
|
+
transcriptError?: string;
|
|
129
157
|
structuredOutput?: unknown;
|
|
130
158
|
structuredOutputPath?: string;
|
|
131
159
|
structuredOutputSchemaPath?: string;
|
|
@@ -238,6 +266,21 @@ function tokenUsageFromAttempts(attempts: ModelAttempt[] | undefined): TokenUsag
|
|
|
238
266
|
return total > 0 ? { input, output, total } : null;
|
|
239
267
|
}
|
|
240
268
|
|
|
269
|
+
function costSummaryFromAttempts(attempts: ModelAttempt[] | undefined): CostSummary | undefined {
|
|
270
|
+
if (!attempts || attempts.length === 0) return undefined;
|
|
271
|
+
let inputTokens = 0;
|
|
272
|
+
let outputTokens = 0;
|
|
273
|
+
let costUsd = 0;
|
|
274
|
+
for (const attempt of attempts) {
|
|
275
|
+
inputTokens += attempt.usage?.input ?? 0;
|
|
276
|
+
outputTokens += attempt.usage?.output ?? 0;
|
|
277
|
+
costUsd += attempt.usage?.cost ?? 0;
|
|
278
|
+
}
|
|
279
|
+
return inputTokens > 0 || outputTokens > 0 || costUsd > 0
|
|
280
|
+
? { inputTokens, outputTokens, costUsd }
|
|
281
|
+
: undefined;
|
|
282
|
+
}
|
|
283
|
+
|
|
241
284
|
function appendRecentStepOutput(step: RunnerStatusStep, lines: string[]): void {
|
|
242
285
|
const nonEmpty = lines.filter((line) => line.trim());
|
|
243
286
|
if (nonEmpty.length === 0) return;
|
|
@@ -248,6 +291,13 @@ function appendRecentStepOutput(step: RunnerStatusStep, lines: string[]): void {
|
|
|
248
291
|
}
|
|
249
292
|
}
|
|
250
293
|
|
|
294
|
+
function isTerminalAssistantStop(message: Message): boolean {
|
|
295
|
+
const stopReason = (message as { stopReason?: string }).stopReason;
|
|
296
|
+
const hasToolCall = Array.isArray(message.content)
|
|
297
|
+
&& message.content.some((part) => (part as { type?: string }).type === "toolCall");
|
|
298
|
+
return stopReason === "stop" && !hasToolCall;
|
|
299
|
+
}
|
|
300
|
+
|
|
251
301
|
function resetStepLiveDetail(step: RunnerStatusStep): void {
|
|
252
302
|
step.currentTool = undefined;
|
|
253
303
|
step.currentToolArgs = undefined;
|
|
@@ -262,7 +312,6 @@ interface ChildEventContext {
|
|
|
262
312
|
runId: string;
|
|
263
313
|
stepIndex: number;
|
|
264
314
|
agent: string;
|
|
265
|
-
markers?: SpawnTimingMarkers;
|
|
266
315
|
}
|
|
267
316
|
|
|
268
317
|
interface ChildUsage {
|
|
@@ -297,6 +346,12 @@ interface RunPiStreamingResult {
|
|
|
297
346
|
error?: string;
|
|
298
347
|
finalOutput: string;
|
|
299
348
|
interrupted?: boolean;
|
|
349
|
+
timedOut?: boolean;
|
|
350
|
+
turnBudget?: TurnBudgetState;
|
|
351
|
+
turnBudgetExceeded?: boolean;
|
|
352
|
+
wrapUpRequested?: boolean;
|
|
353
|
+
toolBudget?: ToolBudgetState;
|
|
354
|
+
toolBudgetBlocked?: boolean;
|
|
300
355
|
observedMutationAttempt?: boolean;
|
|
301
356
|
}
|
|
302
357
|
|
|
@@ -311,21 +366,18 @@ function runPiStreaming(
|
|
|
311
366
|
childEventContext?: ChildEventContext,
|
|
312
367
|
registerInterrupt?: (interrupt: (() => void) | undefined) => void,
|
|
313
368
|
onChildEvent?: (event: ChildEvent) => void,
|
|
369
|
+
transcriptWriter?: ChildTranscriptWriter,
|
|
370
|
+
registerTimeout?: (interrupt: (() => void) | undefined) => void,
|
|
371
|
+
timeoutMessage?: string,
|
|
372
|
+
registerTurnBudgetAbort?: (abort: ((message: string, state?: TurnBudgetState) => void) | undefined) => void,
|
|
314
373
|
): Promise<RunPiStreamingResult> {
|
|
315
374
|
return new Promise((resolve) => {
|
|
316
375
|
const outputStream = fs.createWriteStream(outputFile, { flags: "w" });
|
|
317
376
|
const spawnEnv = { ...process.env, ...(env ?? {}), ...getSubagentDepthEnv(maxSubagentDepth) };
|
|
318
|
-
const markers = childEventContext ? createSpawnTimingMarkers() : undefined;
|
|
319
|
-
if (markers) {
|
|
320
|
-
emitSpawnTiming({ eventsPath: childEventContext.eventsPath, marker: "spawn_start", markers, runId: childEventContext.runId, stepIndex: childEventContext.stepIndex, agent: childEventContext.agent });
|
|
321
|
-
}
|
|
322
377
|
const spawnSpec = getSelesaiSpawnCommand(args, {
|
|
323
378
|
...(piPackageRoot ? { piPackageRoot } : {}),
|
|
324
379
|
...(piArgv1 ? { argv1: piArgv1 } : {}),
|
|
325
380
|
});
|
|
326
|
-
if (markers) {
|
|
327
|
-
emitSpawnTiming({ eventsPath: childEventContext.eventsPath, marker: "spawn_command_resolved", markers, runId: childEventContext.runId, stepIndex: childEventContext.stepIndex, agent: childEventContext.agent });
|
|
328
|
-
}
|
|
329
381
|
const child = spawn(spawnSpec.command, spawnSpec.args, {
|
|
330
382
|
cwd,
|
|
331
383
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -341,23 +393,13 @@ function runPiStreaming(
|
|
|
341
393
|
let error: string | undefined;
|
|
342
394
|
let assistantError: string | undefined;
|
|
343
395
|
let interrupted = false;
|
|
396
|
+
let timedOut = false;
|
|
397
|
+
let turnBudgetExceeded = false;
|
|
398
|
+
let turnBudgetMessage: string | undefined;
|
|
399
|
+
let turnBudget: TurnBudgetState | undefined;
|
|
344
400
|
let observedMutationAttempt = false;
|
|
345
|
-
let firstEventEmitted = false;
|
|
346
|
-
let firstJsonlEventEmitted = false;
|
|
347
401
|
const rawStdoutLines: string[] = [];
|
|
348
402
|
|
|
349
|
-
const emitChildTiming = (marker: import("../shared/spawn-timing.ts").SpawnTimingMarker) => {
|
|
350
|
-
if (!markers || !childEventContext) return;
|
|
351
|
-
emitSpawnTiming({
|
|
352
|
-
eventsPath: childEventContext.eventsPath,
|
|
353
|
-
marker,
|
|
354
|
-
markers,
|
|
355
|
-
runId: childEventContext.runId,
|
|
356
|
-
stepIndex: childEventContext.stepIndex,
|
|
357
|
-
agent: childEventContext.agent,
|
|
358
|
-
});
|
|
359
|
-
};
|
|
360
|
-
|
|
361
403
|
const writeOutputLine = (line: string) => {
|
|
362
404
|
if (!line.trim()) return;
|
|
363
405
|
outputStream.write(`${line}\n`);
|
|
@@ -384,6 +426,8 @@ function runPiStreaming(
|
|
|
384
426
|
|
|
385
427
|
const appendChildLine = (type: "subagent.child.stdout" | "subagent.child.stderr", line: string) => {
|
|
386
428
|
appendChildEvent({ type, line });
|
|
429
|
+
if (type === "subagent.child.stdout") transcriptWriter?.writeStdoutLine(line);
|
|
430
|
+
else transcriptWriter?.writeStderrLine(line);
|
|
387
431
|
};
|
|
388
432
|
|
|
389
433
|
const processStdoutLine = (line: string) => {
|
|
@@ -391,10 +435,6 @@ function runPiStreaming(
|
|
|
391
435
|
let event: ChildEvent;
|
|
392
436
|
try {
|
|
393
437
|
event = JSON.parse(line) as ChildEvent;
|
|
394
|
-
if (!firstJsonlEventEmitted) {
|
|
395
|
-
firstJsonlEventEmitted = true;
|
|
396
|
-
emitChildTiming("child_first_jsonl_event");
|
|
397
|
-
}
|
|
398
438
|
} catch {
|
|
399
439
|
rawStdoutLines.push(line);
|
|
400
440
|
writeOutputLine(line);
|
|
@@ -403,6 +443,7 @@ function runPiStreaming(
|
|
|
403
443
|
}
|
|
404
444
|
|
|
405
445
|
appendChildEvent(event);
|
|
446
|
+
transcriptWriter?.writeChildEvent(event);
|
|
406
447
|
onChildEvent?.(event);
|
|
407
448
|
|
|
408
449
|
if (event.type === "tool_execution_start" && event.toolName) {
|
|
@@ -429,10 +470,7 @@ function runPiStreaming(
|
|
|
429
470
|
usage.cacheWrite += eventUsage.cacheWrite ?? 0;
|
|
430
471
|
usage.cost += eventUsage.cost?.total ?? 0;
|
|
431
472
|
}
|
|
432
|
-
|
|
433
|
-
const hasToolCall = Array.isArray(event.message.content)
|
|
434
|
-
&& event.message.content.some((part) => (part as { type?: string }).type === "toolCall");
|
|
435
|
-
if (stopReason === "stop" && !hasToolCall) {
|
|
473
|
+
if (isTerminalAssistantStop(event.message)) {
|
|
436
474
|
if (!event.message.errorMessage && extractTextFromContent(event.message.content).trim()) assistantError = undefined;
|
|
437
475
|
cleanTerminalAssistantStopReceived ||= !event.message.errorMessage;
|
|
438
476
|
startFinalDrain();
|
|
@@ -457,18 +495,18 @@ function runPiStreaming(
|
|
|
457
495
|
// a lingering stdio holder after `exit`, or a child that never exits.
|
|
458
496
|
const FINAL_STOP_GRACE_MS = 1000;
|
|
459
497
|
const HARD_KILL_MS = 3000;
|
|
498
|
+
const TIMEOUT_HARD_KILL_MS = 3000;
|
|
460
499
|
let childExited = false;
|
|
461
500
|
let forcedTerminationSignal = false;
|
|
462
501
|
let cleanTerminalAssistantStopReceived = false;
|
|
463
502
|
let finalDrainTimer: NodeJS.Timeout | undefined;
|
|
464
503
|
let finalHardKillTimer: NodeJS.Timeout | undefined;
|
|
504
|
+
let timeoutHardKillTimer: NodeJS.Timeout | undefined;
|
|
505
|
+
let turnBudgetTerminationTimer: NodeJS.Timeout | undefined;
|
|
506
|
+
let turnBudgetHardKillTimer: NodeJS.Timeout | undefined;
|
|
465
507
|
let settled = false;
|
|
466
508
|
const clearStdioGuard = attachPostExitStdioGuard(child, { idleMs: 2000, hardMs: 8000 });
|
|
467
509
|
child.stdout.on("data", (chunk: Buffer) => {
|
|
468
|
-
if (!firstEventEmitted) {
|
|
469
|
-
firstEventEmitted = true;
|
|
470
|
-
emitChildTiming("child_first_event");
|
|
471
|
-
}
|
|
472
510
|
const text = chunk.toString();
|
|
473
511
|
stdoutBuf += text;
|
|
474
512
|
const lines = stdoutBuf.split("\n");
|
|
@@ -477,21 +515,45 @@ function runPiStreaming(
|
|
|
477
515
|
});
|
|
478
516
|
|
|
479
517
|
child.stderr.on("data", (chunk: Buffer) => {
|
|
480
|
-
if (!firstEventEmitted) {
|
|
481
|
-
firstEventEmitted = true;
|
|
482
|
-
emitChildTiming("child_first_event");
|
|
483
|
-
}
|
|
484
518
|
processStderrText(chunk.toString());
|
|
485
519
|
});
|
|
486
520
|
registerInterrupt?.(() => {
|
|
487
|
-
if (settled) return;
|
|
521
|
+
if (settled || timedOut) return;
|
|
488
522
|
interrupted = true;
|
|
489
523
|
if (!error) error = "Interrupted. Waiting for explicit next action.";
|
|
490
524
|
trySignalChild(child, "SIGINT");
|
|
491
525
|
setTimeout(() => {
|
|
492
|
-
if (!settled) trySignalChild(child, "SIGTERM");
|
|
526
|
+
if (!settled && !timedOut) trySignalChild(child, "SIGTERM");
|
|
493
527
|
}, 1000).unref?.();
|
|
494
528
|
});
|
|
529
|
+
registerTimeout?.(() => {
|
|
530
|
+
if (settled || timedOut) return;
|
|
531
|
+
timedOut = true;
|
|
532
|
+
interrupted = false;
|
|
533
|
+
error = timeoutMessage ?? "Subagent timed out.";
|
|
534
|
+
trySignalChild(child, "SIGTERM");
|
|
535
|
+
timeoutHardKillTimer = setTimeout(() => {
|
|
536
|
+
if (!settled) trySignalChild(child, "SIGKILL");
|
|
537
|
+
}, TIMEOUT_HARD_KILL_MS);
|
|
538
|
+
timeoutHardKillTimer.unref?.();
|
|
539
|
+
});
|
|
540
|
+
registerTurnBudgetAbort?.((message, state) => {
|
|
541
|
+
if (settled || timedOut || turnBudgetExceeded) return;
|
|
542
|
+
turnBudgetExceeded = true;
|
|
543
|
+
turnBudgetMessage = message;
|
|
544
|
+
turnBudget = state;
|
|
545
|
+
interrupted = false;
|
|
546
|
+
error = message;
|
|
547
|
+
trySignalChild(child, "SIGINT");
|
|
548
|
+
turnBudgetTerminationTimer = setTimeout(() => {
|
|
549
|
+
if (!settled && !timedOut) trySignalChild(child, "SIGTERM");
|
|
550
|
+
}, 1000);
|
|
551
|
+
turnBudgetTerminationTimer.unref?.();
|
|
552
|
+
turnBudgetHardKillTimer = setTimeout(() => {
|
|
553
|
+
if (!settled && !timedOut) trySignalChild(child, "SIGKILL");
|
|
554
|
+
}, 4000);
|
|
555
|
+
turnBudgetHardKillTimer.unref?.();
|
|
556
|
+
});
|
|
495
557
|
const clearDrainTimers = () => {
|
|
496
558
|
if (finalDrainTimer) {
|
|
497
559
|
clearTimeout(finalDrainTimer);
|
|
@@ -501,6 +563,18 @@ function runPiStreaming(
|
|
|
501
563
|
clearTimeout(finalHardKillTimer);
|
|
502
564
|
finalHardKillTimer = undefined;
|
|
503
565
|
}
|
|
566
|
+
if (timeoutHardKillTimer) {
|
|
567
|
+
clearTimeout(timeoutHardKillTimer);
|
|
568
|
+
timeoutHardKillTimer = undefined;
|
|
569
|
+
}
|
|
570
|
+
if (turnBudgetTerminationTimer) {
|
|
571
|
+
clearTimeout(turnBudgetTerminationTimer);
|
|
572
|
+
turnBudgetTerminationTimer = undefined;
|
|
573
|
+
}
|
|
574
|
+
if (turnBudgetHardKillTimer) {
|
|
575
|
+
clearTimeout(turnBudgetHardKillTimer);
|
|
576
|
+
turnBudgetHardKillTimer = undefined;
|
|
577
|
+
}
|
|
504
578
|
};
|
|
505
579
|
function startFinalDrain(): void {
|
|
506
580
|
if (childExited || finalDrainTimer || settled) return;
|
|
@@ -527,6 +601,8 @@ function runPiStreaming(
|
|
|
527
601
|
child.on("close", (exitCode, signal) => {
|
|
528
602
|
settled = true;
|
|
529
603
|
registerInterrupt?.(undefined);
|
|
604
|
+
registerTimeout?.(undefined);
|
|
605
|
+
registerTurnBudgetAbort?.(undefined);
|
|
530
606
|
clearDrainTimers();
|
|
531
607
|
clearStdioGuard();
|
|
532
608
|
if (stdoutBuf.trim()) processStdoutLine(stdoutBuf);
|
|
@@ -537,13 +613,17 @@ function runPiStreaming(
|
|
|
537
613
|
const forcedDrainAfterFinalSuccess = forcedTerminationSignal && cleanTerminalAssistantStopReceived && !finalError;
|
|
538
614
|
resolve({
|
|
539
615
|
stderr,
|
|
540
|
-
exitCode: interrupted || forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (exitCode ?? 1) : exitCode,
|
|
616
|
+
exitCode: timedOut ? 1 : turnBudgetExceeded ? 1 : interrupted || forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (exitCode ?? 1) : exitCode,
|
|
541
617
|
messages,
|
|
542
618
|
usage,
|
|
543
619
|
model,
|
|
544
|
-
error: interrupted || forcedDrainAfterFinalSuccess ? undefined : finalError,
|
|
545
|
-
finalOutput,
|
|
620
|
+
error: timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? turnBudgetMessage : interrupted || forcedDrainAfterFinalSuccess ? undefined : finalError,
|
|
621
|
+
finalOutput: timedOut && !finalOutput.trim() ? (timeoutMessage ?? "Subagent timed out.") : finalOutput,
|
|
546
622
|
interrupted,
|
|
623
|
+
timedOut,
|
|
624
|
+
turnBudget,
|
|
625
|
+
turnBudgetExceeded,
|
|
626
|
+
wrapUpRequested: turnBudget?.outcome === "wrap-up-requested" || turnBudgetExceeded || undefined,
|
|
547
627
|
observedMutationAttempt,
|
|
548
628
|
});
|
|
549
629
|
});
|
|
@@ -551,12 +631,14 @@ function runPiStreaming(
|
|
|
551
631
|
child.on("error", (spawnError) => {
|
|
552
632
|
settled = true;
|
|
553
633
|
registerInterrupt?.(undefined);
|
|
634
|
+
registerTimeout?.(undefined);
|
|
635
|
+
registerTurnBudgetAbort?.(undefined);
|
|
554
636
|
clearDrainTimers();
|
|
555
637
|
clearStdioGuard();
|
|
556
638
|
outputStream.end();
|
|
557
639
|
const finalOutput = getFinalOutput(messages) || rawStdoutLines.join("\n").trim();
|
|
558
640
|
const spawnErrorMessage = spawnError instanceof Error ? spawnError.message : String(spawnError);
|
|
559
|
-
resolve({ stderr, exitCode: 1, messages, usage, model, error: error ?? assistantError ?? spawnErrorMessage, finalOutput, observedMutationAttempt });
|
|
641
|
+
resolve({ stderr, exitCode: 1, messages, usage, model, error: timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? turnBudgetMessage : error ?? assistantError ?? spawnErrorMessage, finalOutput: timedOut && !finalOutput.trim() ? (timeoutMessage ?? "Subagent timed out.") : finalOutput, timedOut, turnBudget, turnBudgetExceeded, wrapUpRequested: turnBudget?.outcome === "wrap-up-requested" || turnBudgetExceeded || undefined, observedMutationAttempt });
|
|
560
642
|
});
|
|
561
643
|
});
|
|
562
644
|
}
|
|
@@ -681,14 +763,22 @@ interface SingleStepContext {
|
|
|
681
763
|
flatIndex: number;
|
|
682
764
|
flatStepCount: number;
|
|
683
765
|
outputFile: string;
|
|
766
|
+
steerInboxDir?: string;
|
|
767
|
+
transcriptPath?: string;
|
|
684
768
|
piPackageRoot?: string;
|
|
685
769
|
piArgv1?: string;
|
|
686
770
|
registerInterrupt?: (interrupt: (() => void) | undefined) => void;
|
|
771
|
+
registerTimeout?: (interrupt: (() => void) | undefined) => void;
|
|
772
|
+
registerTurnBudgetAbort?: (abort: ((message: string, state?: TurnBudgetState) => void) | undefined) => void;
|
|
773
|
+
timeoutSignal?: AbortSignal;
|
|
774
|
+
timeoutMessage?: string;
|
|
775
|
+
turnBudget?: ResolvedTurnBudget;
|
|
687
776
|
childIntercomTarget?: string;
|
|
688
777
|
orchestratorIntercomTarget?: string;
|
|
689
778
|
nestedRoute?: NestedRouteInfo;
|
|
690
779
|
onAttemptStart?: (attempt: { model?: string; thinking?: string }) => void;
|
|
691
780
|
onChildEvent?: (event: ChildEvent) => void;
|
|
781
|
+
skipAcceptance?: () => boolean;
|
|
692
782
|
}
|
|
693
783
|
|
|
694
784
|
/** Run a single pi agent step, returning output and metadata */
|
|
@@ -704,7 +794,15 @@ async function runSingleStep(
|
|
|
704
794
|
attemptedModels?: string[];
|
|
705
795
|
modelAttempts?: ModelAttempt[];
|
|
706
796
|
artifactPaths?: ArtifactPaths;
|
|
797
|
+
transcriptPath?: string;
|
|
798
|
+
transcriptError?: string;
|
|
707
799
|
interrupted?: boolean;
|
|
800
|
+
timedOut?: boolean;
|
|
801
|
+
turnBudget?: TurnBudgetState;
|
|
802
|
+
turnBudgetExceeded?: boolean;
|
|
803
|
+
wrapUpRequested?: boolean;
|
|
804
|
+
toolBudget?: ToolBudgetState;
|
|
805
|
+
toolBudgetBlocked?: boolean;
|
|
708
806
|
sessionFile?: string;
|
|
709
807
|
intercomTarget?: string;
|
|
710
808
|
completionGuardTriggered?: boolean;
|
|
@@ -714,27 +812,52 @@ async function runSingleStep(
|
|
|
714
812
|
acceptance?: import("../../shared/types.ts").AcceptanceLedger;
|
|
715
813
|
}> {
|
|
716
814
|
if (step.importAsyncRoot) {
|
|
717
|
-
|
|
815
|
+
let importTimedOut = false;
|
|
816
|
+
ctx.registerTimeout?.(() => {
|
|
817
|
+
importTimedOut = true;
|
|
818
|
+
let pid: number | undefined;
|
|
819
|
+
try {
|
|
820
|
+
pid = readStatus(step.importAsyncRoot!.asyncDir)?.pid;
|
|
821
|
+
} catch {
|
|
822
|
+
pid = undefined;
|
|
823
|
+
}
|
|
824
|
+
try {
|
|
825
|
+
deliverTimeoutRequest({ asyncDir: step.importAsyncRoot!.asyncDir, pid, source: "ancestor-timeout" });
|
|
826
|
+
} catch {
|
|
827
|
+
// The parent runner's own timeout result is authoritative for the attached step.
|
|
828
|
+
}
|
|
829
|
+
});
|
|
718
830
|
try {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
831
|
+
const imported = await waitForImportedAsyncRoot(step.importAsyncRoot, {
|
|
832
|
+
shouldAbort: () => importTimedOut || ctx.timeoutSignal?.aborted === true || ctx.skipAcceptance?.() === true,
|
|
833
|
+
timeoutMessage: ctx.timeoutMessage,
|
|
834
|
+
});
|
|
835
|
+
try {
|
|
836
|
+
fs.writeFileSync(ctx.outputFile, imported.output, "utf-8");
|
|
837
|
+
} catch {
|
|
838
|
+
// Output files are observability only for imported roots.
|
|
839
|
+
}
|
|
840
|
+
const timedOut = importTimedOut || imported.timedOut === true || ctx.timeoutSignal?.aborted === true || ctx.skipAcceptance?.() === true;
|
|
841
|
+
return {
|
|
842
|
+
agent: imported.agent,
|
|
843
|
+
output: timedOut ? ctx.timeoutMessage ?? "Subagent timed out." : imported.output,
|
|
844
|
+
exitCode: timedOut ? 1 : imported.exitCode,
|
|
845
|
+
error: timedOut ? ctx.timeoutMessage ?? "Subagent timed out." : imported.error,
|
|
846
|
+
timedOut: timedOut ? true : undefined,
|
|
847
|
+
sessionFile: imported.sessionFile,
|
|
848
|
+
intercomTarget: imported.intercomTarget,
|
|
849
|
+
model: imported.model,
|
|
850
|
+
attemptedModels: imported.attemptedModels,
|
|
851
|
+
modelAttempts: imported.modelAttempts,
|
|
852
|
+
totalCost: imported.totalCost,
|
|
853
|
+
structuredOutput: timedOut ? undefined : imported.structuredOutput,
|
|
854
|
+
structuredOutputPath: timedOut ? undefined : imported.structuredOutputPath,
|
|
855
|
+
structuredOutputSchemaPath: timedOut ? undefined : imported.structuredOutputSchemaPath,
|
|
856
|
+
acceptance: timedOut ? undefined : imported.acceptance,
|
|
857
|
+
};
|
|
858
|
+
} finally {
|
|
859
|
+
ctx.registerTimeout?.(undefined);
|
|
722
860
|
}
|
|
723
|
-
return {
|
|
724
|
-
agent: imported.agent,
|
|
725
|
-
output: imported.output,
|
|
726
|
-
exitCode: imported.exitCode,
|
|
727
|
-
error: imported.error,
|
|
728
|
-
sessionFile: imported.sessionFile,
|
|
729
|
-
intercomTarget: imported.intercomTarget,
|
|
730
|
-
model: imported.model,
|
|
731
|
-
attemptedModels: imported.attemptedModels,
|
|
732
|
-
modelAttempts: imported.modelAttempts,
|
|
733
|
-
structuredOutput: imported.structuredOutput,
|
|
734
|
-
structuredOutputPath: imported.structuredOutputPath,
|
|
735
|
-
structuredOutputSchemaPath: imported.structuredOutputSchemaPath,
|
|
736
|
-
acceptance: imported.acceptance,
|
|
737
|
-
};
|
|
738
861
|
}
|
|
739
862
|
|
|
740
863
|
const effectiveStructuredOutput = step.structuredOutput ?? (step.structuredOutputSchema
|
|
@@ -752,6 +875,7 @@ async function runSingleStep(
|
|
|
752
875
|
const sessionDir = step.sessionFile ? undefined : ctx.sessionDir;
|
|
753
876
|
|
|
754
877
|
let artifactPaths: ArtifactPaths | undefined;
|
|
878
|
+
let transcriptWriter: ChildTranscriptWriter | undefined;
|
|
755
879
|
if (ctx.artifactsDir && ctx.artifactConfig?.enabled !== false) {
|
|
756
880
|
const index = ctx.flatStepCount > 1 ? ctx.flatIndex : undefined;
|
|
757
881
|
artifactPaths = getArtifactPaths(ctx.artifactsDir, ctx.id, step.agent, index);
|
|
@@ -759,7 +883,18 @@ async function runSingleStep(
|
|
|
759
883
|
if (ctx.artifactConfig?.includeInput !== false) {
|
|
760
884
|
fs.writeFileSync(artifactPaths.inputPath, `# Task for ${step.agent}\n\n${task}`, "utf-8");
|
|
761
885
|
}
|
|
886
|
+
if (ctx.artifactConfig?.includeTranscript !== false) {
|
|
887
|
+
transcriptWriter = createChildTranscriptWriter({
|
|
888
|
+
transcriptPath: artifactPaths.transcriptPath,
|
|
889
|
+
source: "async",
|
|
890
|
+
runId: ctx.id,
|
|
891
|
+
agent: step.agent,
|
|
892
|
+
childIndex: ctx.flatIndex,
|
|
893
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
894
|
+
});
|
|
895
|
+
}
|
|
762
896
|
}
|
|
897
|
+
transcriptWriter?.writeInitialUserMessage(task);
|
|
763
898
|
|
|
764
899
|
const candidates = step.modelCandidates && step.modelCandidates.length > 0
|
|
765
900
|
? step.modelCandidates
|
|
@@ -773,8 +908,12 @@ async function runSingleStep(
|
|
|
773
908
|
let finalResult: RunPiStreamingResult | undefined;
|
|
774
909
|
let finalOutputSnapshot: SingleOutputSnapshot | undefined;
|
|
775
910
|
let completionGuardTriggeredFinal = false;
|
|
911
|
+
let turnBudget = ctx.turnBudget ? initialTurnBudgetState(ctx.turnBudget) : undefined;
|
|
912
|
+
let toolBudget = step.toolBudget ? initialToolBudgetState(step.toolBudget) : undefined;
|
|
913
|
+
let toolBudgetBlocked = false;
|
|
776
914
|
|
|
777
915
|
for (let index = 0; index < candidates.length; index++) {
|
|
916
|
+
if (ctx.timeoutSignal?.aborted || ctx.skipAcceptance?.()) break;
|
|
778
917
|
const candidate = candidates[index];
|
|
779
918
|
ctx.onAttemptStart?.({ model: candidate, thinking: resolveEffectiveThinking(candidate, step.thinking) });
|
|
780
919
|
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
@@ -799,7 +938,7 @@ async function runSingleStep(
|
|
|
799
938
|
tools: step.tools,
|
|
800
939
|
extensions: step.extensions,
|
|
801
940
|
subagentOnlyExtensions: step.subagentOnlyExtensions,
|
|
802
|
-
systemPrompt: step.systemPrompt,
|
|
941
|
+
systemPrompt: appendTurnBudgetSystemPrompt(step.systemPrompt ?? "", ctx.turnBudget),
|
|
803
942
|
systemPromptMode: step.systemPromptMode,
|
|
804
943
|
mcpDirectTools: step.mcpDirectTools,
|
|
805
944
|
cwd: step.cwd ?? ctx.cwd,
|
|
@@ -813,7 +952,9 @@ async function runSingleStep(
|
|
|
813
952
|
parentControlInbox: ctx.nestedRoute?.controlInbox,
|
|
814
953
|
parentRootRunId: ctx.nestedRoute?.rootRunId,
|
|
815
954
|
parentCapabilityToken: ctx.nestedRoute?.capabilityToken,
|
|
955
|
+
steerInboxDir: ctx.steerInboxDir,
|
|
816
956
|
structuredOutput: effectiveStructuredOutput,
|
|
957
|
+
toolBudget: step.toolBudget,
|
|
817
958
|
});
|
|
818
959
|
const run = await runPiStreaming(
|
|
819
960
|
args,
|
|
@@ -826,13 +967,38 @@ async function runSingleStep(
|
|
|
826
967
|
{ eventsPath, runId: ctx.id, stepIndex: ctx.flatIndex, agent: step.agent },
|
|
827
968
|
ctx.registerInterrupt,
|
|
828
969
|
ctx.onChildEvent,
|
|
970
|
+
transcriptWriter,
|
|
971
|
+
ctx.registerTimeout,
|
|
972
|
+
ctx.timeoutMessage,
|
|
973
|
+
ctx.registerTurnBudgetAbort,
|
|
829
974
|
);
|
|
975
|
+
if (run.turnBudget) turnBudget = run.turnBudget;
|
|
976
|
+
else if (ctx.turnBudget) {
|
|
977
|
+
const assistantMessages = run.messages.filter((message) => message.role === "assistant");
|
|
978
|
+
const turnCount = assistantMessages.length;
|
|
979
|
+
const lastAssistantMessage = assistantMessages.at(-1);
|
|
980
|
+
if (turnCount > 0 && turnCount < ctx.turnBudget.maxTurns) {
|
|
981
|
+
turnBudget = { ...ctx.turnBudget, outcome: "within-budget", turnCount };
|
|
982
|
+
} else if (turnCount >= ctx.turnBudget.maxTurns) {
|
|
983
|
+
turnBudget = turnBudgetState(
|
|
984
|
+
ctx.turnBudget,
|
|
985
|
+
turnCount,
|
|
986
|
+
shouldAbortForTurnBudget(ctx.turnBudget, turnCount, lastAssistantMessage ? isTerminalAssistantStop(lastAssistantMessage) : false),
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
830
990
|
cleanupTempDir(tempDir);
|
|
831
991
|
|
|
832
992
|
const hiddenError = run.exitCode === 0 && !run.error ? detectSubagentError(run.messages) : null;
|
|
993
|
+
const missingStructuredOutput = effectiveStructuredOutput
|
|
994
|
+
? !fs.existsSync(effectiveStructuredOutput.outputPath)
|
|
995
|
+
: false;
|
|
996
|
+
const emptyOutputError = run.exitCode === 0 && !run.error && !hiddenError?.hasError && !run.finalOutput.trim() && (!effectiveStructuredOutput || missingStructuredOutput)
|
|
997
|
+
? "Subagent produced no output (possible model cold-start or empty response)."
|
|
998
|
+
: undefined;
|
|
833
999
|
let structuredOutput: unknown;
|
|
834
1000
|
let structuredError: string | undefined;
|
|
835
|
-
if (effectiveStructuredOutput && run.exitCode === 0 && !run.error && !hiddenError?.hasError) {
|
|
1001
|
+
if (effectiveStructuredOutput && run.exitCode === 0 && !run.error && !hiddenError?.hasError && !emptyOutputError) {
|
|
836
1002
|
const structured = readStructuredOutput({
|
|
837
1003
|
schema: effectiveStructuredOutput.schema,
|
|
838
1004
|
schemaPath: effectiveStructuredOutput.schemaPath,
|
|
@@ -841,7 +1007,7 @@ async function runSingleStep(
|
|
|
841
1007
|
if (structured.error) structuredError = structured.error;
|
|
842
1008
|
else structuredOutput = structured.value;
|
|
843
1009
|
}
|
|
844
|
-
const completionGuard = run.exitCode === 0 && !run.error && !hiddenError?.hasError && step.completionGuard !== false
|
|
1010
|
+
const completionGuard = run.exitCode === 0 && !run.error && !hiddenError?.hasError && !emptyOutputError && step.completionGuard !== false
|
|
845
1011
|
? evaluateCompletionMutationGuard({
|
|
846
1012
|
agent: step.agent,
|
|
847
1013
|
task: taskForCompletionGuard,
|
|
@@ -860,16 +1026,18 @@ async function runSingleStep(
|
|
|
860
1026
|
? 1
|
|
861
1027
|
: hiddenError?.hasError
|
|
862
1028
|
? (hiddenError.exitCode ?? 1)
|
|
863
|
-
:
|
|
1029
|
+
: emptyOutputError
|
|
864
1030
|
? 1
|
|
865
|
-
: run.exitCode
|
|
1031
|
+
: run.error && run.exitCode === 0
|
|
1032
|
+
? 1
|
|
1033
|
+
: run.exitCode;
|
|
866
1034
|
const error = completionGuardError
|
|
867
1035
|
?? structuredError
|
|
868
1036
|
?? (hiddenError?.hasError
|
|
869
1037
|
? hiddenError.details
|
|
870
1038
|
? `${hiddenError.errorType} failed (exit ${effectiveExitCode}): ${hiddenError.details}`
|
|
871
1039
|
: `${hiddenError.errorType} failed with exit code ${effectiveExitCode}`
|
|
872
|
-
: run.error || (run.exitCode !== 0 && run.stderr.trim() ? run.stderr.trim() : undefined));
|
|
1040
|
+
: emptyOutputError ?? (run.error || (run.exitCode !== 0 && run.stderr.trim() ? run.stderr.trim() : undefined)));
|
|
873
1041
|
const attempt: ModelAttempt = {
|
|
874
1042
|
model: candidate ?? run.model ?? step.model ?? "default",
|
|
875
1043
|
success: effectiveExitCode === 0 && !error,
|
|
@@ -881,7 +1049,15 @@ async function runSingleStep(
|
|
|
881
1049
|
if (candidate) attemptedModels.push(candidate);
|
|
882
1050
|
completionGuardTriggeredFinal = completionGuardTriggered;
|
|
883
1051
|
finalOutputSnapshot = outputSnapshot;
|
|
1052
|
+
if (step.toolBudget) {
|
|
1053
|
+
const toolMessages = run.messages.filter((message) => message.role === "toolResult");
|
|
1054
|
+
const blockedMessage = toolMessages.find((message) => extractTextFromContent(message.content).includes("Tool budget hard limit reached"));
|
|
1055
|
+
toolBudgetBlocked = Boolean(blockedMessage);
|
|
1056
|
+
toolBudget = toolBudgetState(step.toolBudget, toolMessages.length, blockedMessage ? (blockedMessage as { toolName?: string }).toolName : undefined);
|
|
1057
|
+
}
|
|
884
1058
|
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput } as RunPiStreamingResult & { structuredOutput?: unknown };
|
|
1059
|
+
if (run.turnBudgetExceeded) break;
|
|
1060
|
+
if (run.timedOut || ctx.timeoutSignal?.aborted || ctx.skipAcceptance?.()) break;
|
|
885
1061
|
if (attempt.success || completionGuardTriggered) break;
|
|
886
1062
|
if (!isRetryableModelFailure(error) || index === candidates.length - 1) break;
|
|
887
1063
|
attemptNotes.push(formatModelAttemptNote(attempt, candidates[index + 1]));
|
|
@@ -898,6 +1074,12 @@ async function runSingleStep(
|
|
|
898
1074
|
if (attemptNotes.length > 0) {
|
|
899
1075
|
outputForSummary = `${attemptNotes.join("\n")}\n\n${outputForSummary}`.trim();
|
|
900
1076
|
}
|
|
1077
|
+
if (!finalResult?.timedOut && finalResult?.turnBudgetExceeded && turnBudget) {
|
|
1078
|
+
outputForSummary = formatTurnBudgetOutput(turnBudgetExceededMessage(turnBudget, turnBudget.turnCount), outputForSummary);
|
|
1079
|
+
} else if (!finalResult?.timedOut && turnBudget?.outcome === "wrap-up-requested") {
|
|
1080
|
+
const note = turnBudgetSoftNote(turnBudget, turnBudget.wrapUpRequestedAtTurn ?? turnBudget.turnCount);
|
|
1081
|
+
outputForSummary = outputForSummary.trim() ? `${note}\n\n${outputForSummary}` : note;
|
|
1082
|
+
}
|
|
901
1083
|
const outputForAcceptance = rawOutput;
|
|
902
1084
|
const finalizedOutput = finalizeSingleOutput({
|
|
903
1085
|
fullOutput: outputForSummary,
|
|
@@ -909,19 +1091,28 @@ async function runSingleStep(
|
|
|
909
1091
|
saveError: resolvedOutput.saveError,
|
|
910
1092
|
});
|
|
911
1093
|
outputForSummary = finalizedOutput.displayOutput;
|
|
912
|
-
const acceptance = step.effectiveAcceptance
|
|
1094
|
+
const acceptance = step.effectiveAcceptance && !finalResult?.turnBudgetExceeded && !ctx.timeoutSignal?.aborted && !ctx.skipAcceptance?.()
|
|
913
1095
|
? await evaluateAcceptance({
|
|
914
1096
|
acceptance: step.effectiveAcceptance,
|
|
915
1097
|
output: outputForAcceptance,
|
|
916
1098
|
cwd: step.cwd ?? ctx.cwd,
|
|
1099
|
+
signal: ctx.timeoutSignal,
|
|
1100
|
+
abortMessage: ctx.timeoutMessage ?? "Subagent timed out.",
|
|
917
1101
|
})
|
|
918
1102
|
: undefined;
|
|
919
|
-
const
|
|
920
|
-
const
|
|
921
|
-
const
|
|
922
|
-
const
|
|
923
|
-
|
|
924
|
-
|
|
1103
|
+
const timedOutAfterAcceptance = finalResult?.timedOut === true || ctx.timeoutSignal?.aborted === true || ctx.skipAcceptance?.() === true;
|
|
1104
|
+
const turnBudgetExceeded = finalResult?.turnBudgetExceeded === true;
|
|
1105
|
+
const effectiveAcceptance = timedOutAfterAcceptance || turnBudgetExceeded ? undefined : acceptance;
|
|
1106
|
+
const acceptanceFailure = effectiveAcceptance ? acceptanceFailureMessage(effectiveAcceptance) : undefined;
|
|
1107
|
+
const acceptanceCanFailRun = acceptanceFailure && effectiveAcceptance?.explicit && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted && !timedOutAfterAcceptance && !turnBudgetExceeded;
|
|
1108
|
+
const effectiveFinalExitCode = timedOutAfterAcceptance || turnBudgetExceeded ? 1 : acceptanceCanFailRun ? 1 : finalResult?.exitCode ?? 1;
|
|
1109
|
+
const effectiveFinalError = timedOutAfterAcceptance
|
|
1110
|
+
? ctx.timeoutMessage ?? "Subagent timed out."
|
|
1111
|
+
: turnBudgetExceeded
|
|
1112
|
+
? finalResult?.error ?? (turnBudget ? turnBudgetExceededMessage(turnBudget, turnBudget.turnCount) : "Subagent exceeded turn budget.")
|
|
1113
|
+
: acceptanceCanFailRun
|
|
1114
|
+
? (finalResult?.error ? `${finalResult.error}\n${acceptanceFailure}` : acceptanceFailure)
|
|
1115
|
+
: finalResult?.error;
|
|
925
1116
|
|
|
926
1117
|
if (artifactPaths && ctx.artifactConfig?.enabled !== false) {
|
|
927
1118
|
if (ctx.artifactConfig?.includeOutput !== false) {
|
|
@@ -938,6 +1129,8 @@ async function runSingleStep(
|
|
|
938
1129
|
model: finalResult?.model,
|
|
939
1130
|
attemptedModels: attemptedModels.length > 0 ? attemptedModels : undefined,
|
|
940
1131
|
modelAttempts,
|
|
1132
|
+
...(transcriptWriter ? { transcriptPath: artifactPaths.transcriptPath } : {}),
|
|
1133
|
+
transcriptError: transcriptWriter?.getError(),
|
|
941
1134
|
skills: step.skills,
|
|
942
1135
|
timestamp: Date.now(),
|
|
943
1136
|
}, null, 2),
|
|
@@ -956,13 +1149,22 @@ async function runSingleStep(
|
|
|
956
1149
|
model: finalResult?.model,
|
|
957
1150
|
attemptedModels: attemptedModels.length > 0 ? attemptedModels : undefined,
|
|
958
1151
|
modelAttempts,
|
|
1152
|
+
totalCost: costSummaryFromAttempts(modelAttempts),
|
|
959
1153
|
artifactPaths,
|
|
960
|
-
|
|
1154
|
+
transcriptPath: transcriptWriter ? artifactPaths?.transcriptPath : undefined,
|
|
1155
|
+
transcriptError: transcriptWriter?.getError(),
|
|
1156
|
+
interrupted: timedOutAfterAcceptance || turnBudgetExceeded ? false : finalResult?.interrupted,
|
|
1157
|
+
timedOut: timedOutAfterAcceptance ? true : finalResult?.timedOut,
|
|
1158
|
+
turnBudget,
|
|
1159
|
+
turnBudgetExceeded: turnBudgetExceeded || undefined,
|
|
1160
|
+
wrapUpRequested: finalResult?.wrapUpRequested || turnBudget?.outcome === "wrap-up-requested" || turnBudgetExceeded || undefined,
|
|
1161
|
+
toolBudget,
|
|
1162
|
+
toolBudgetBlocked: toolBudgetBlocked || undefined,
|
|
961
1163
|
completionGuardTriggered: completionGuardTriggeredFinal,
|
|
962
|
-
structuredOutput: (finalResult as (RunPiStreamingResult & { structuredOutput?: unknown }) | undefined)?.structuredOutput,
|
|
963
|
-
structuredOutputPath: effectiveStructuredOutput?.outputPath,
|
|
964
|
-
structuredOutputSchemaPath: effectiveStructuredOutput?.schemaPath,
|
|
965
|
-
acceptance,
|
|
1164
|
+
structuredOutput: timedOutAfterAcceptance || turnBudgetExceeded ? undefined : (finalResult as (RunPiStreamingResult & { structuredOutput?: unknown }) | undefined)?.structuredOutput,
|
|
1165
|
+
structuredOutputPath: timedOutAfterAcceptance || turnBudgetExceeded ? undefined : effectiveStructuredOutput?.outputPath,
|
|
1166
|
+
structuredOutputSchemaPath: timedOutAfterAcceptance || turnBudgetExceeded ? undefined : effectiveStructuredOutput?.schemaPath,
|
|
1167
|
+
acceptance: effectiveAcceptance,
|
|
966
1168
|
};
|
|
967
1169
|
}
|
|
968
1170
|
|
|
@@ -1091,9 +1293,29 @@ function ensureParallelProgressFile(cwd: string, group: Extract<RunnerStep, { pa
|
|
|
1091
1293
|
writeInitialProgressFile(cwd);
|
|
1092
1294
|
}
|
|
1093
1295
|
|
|
1094
|
-
|
|
1296
|
+
function resolveAsyncStepTranscriptPath(input: {
|
|
1297
|
+
artifactsDir?: string;
|
|
1298
|
+
artifactConfig?: Partial<ArtifactConfig>;
|
|
1299
|
+
runId: string;
|
|
1300
|
+
agent: string;
|
|
1301
|
+
flatIndex: number;
|
|
1302
|
+
flatStepCount: number;
|
|
1303
|
+
}): string | undefined {
|
|
1304
|
+
if (!input.artifactsDir || input.artifactConfig?.enabled === false || input.artifactConfig?.includeTranscript === false) return undefined;
|
|
1305
|
+
return getArtifactPaths(
|
|
1306
|
+
input.artifactsDir,
|
|
1307
|
+
input.runId,
|
|
1308
|
+
input.agent,
|
|
1309
|
+
input.flatStepCount > 1 ? input.flatIndex : undefined,
|
|
1310
|
+
).transcriptPath;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
type SingleStepResult = Awaited<ReturnType<typeof runSingleStep>>;
|
|
1314
|
+
|
|
1315
|
+
async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
1095
1316
|
const { id, steps, resultPath, cwd, placeholder, taskIndex, totalTasks, maxOutput, artifactsDir, artifactConfig } =
|
|
1096
1317
|
config;
|
|
1318
|
+
const globalSemaphore = new Semaphore(config.globalConcurrencyLimit ?? DEFAULT_GLOBAL_CONCURRENCY_LIMIT);
|
|
1097
1319
|
let previousOutput = "";
|
|
1098
1320
|
const outputs: ChainOutputMap = {};
|
|
1099
1321
|
const results: StepResult[] = [];
|
|
@@ -1104,13 +1326,23 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1104
1326
|
const eventsPath = path.join(asyncDir, "events.jsonl");
|
|
1105
1327
|
const logPath = path.join(asyncDir, `subagent-log-${id}.md`);
|
|
1106
1328
|
const controlConfig = config.controlConfig ?? DEFAULT_CONTROL_CONFIG;
|
|
1107
|
-
|
|
1329
|
+
const activeChildInterrupts = new Map<number, () => void>();
|
|
1330
|
+
const activeChildTimeouts = new Map<number, () => void>();
|
|
1331
|
+
const activeChildTurnBudgetAborts = new Map<number, (message: string, state?: TurnBudgetState) => void>();
|
|
1332
|
+
const pendingStepSteers: SteerRequest[] = [];
|
|
1108
1333
|
let interrupted = false;
|
|
1109
1334
|
let currentActivityState: ActivityState | undefined;
|
|
1110
1335
|
let activityTimer: NodeJS.Timeout | undefined;
|
|
1336
|
+
let timeoutTimer: NodeJS.Timeout | undefined;
|
|
1337
|
+
let timedOut = false;
|
|
1338
|
+
let turnBudgetExceeded = false;
|
|
1339
|
+
const timeoutMessage = config.timeoutMs !== undefined ? `Subagent timed out after ${config.timeoutMs}ms.` : undefined;
|
|
1340
|
+
const timeoutAbortController = new AbortController();
|
|
1111
1341
|
let previousCumulativeTokens: TokenUsage = { input: 0, output: 0, total: 0 };
|
|
1112
1342
|
let latestSessionFile: string | undefined;
|
|
1113
1343
|
|
|
1344
|
+
const flatSteps = flattenSteps(steps);
|
|
1345
|
+
const initialFlatStepCount = flatSteps.length;
|
|
1114
1346
|
const parallelGroups: Array<{ start: number; count: number; stepIndex: number }> = [];
|
|
1115
1347
|
const initialStatusSteps: RunnerStatusStep[] = [];
|
|
1116
1348
|
let flatStepCount = 0;
|
|
@@ -1119,6 +1351,8 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1119
1351
|
if (isParallelGroup(step)) {
|
|
1120
1352
|
parallelGroups.push({ start: flatStepCount, count: step.parallel.length, stepIndex });
|
|
1121
1353
|
for (const task of step.parallel) {
|
|
1354
|
+
const taskFlatIndex = flatStepCount;
|
|
1355
|
+
const transcriptPath = resolveAsyncStepTranscriptPath({ artifactsDir, artifactConfig, runId: id, agent: task.agent, flatIndex: taskFlatIndex, flatStepCount: initialFlatStepCount });
|
|
1122
1356
|
initialStatusSteps.push({
|
|
1123
1357
|
agent: task.agent,
|
|
1124
1358
|
phase: task.phase,
|
|
@@ -1126,7 +1360,9 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1126
1360
|
outputName: task.outputName,
|
|
1127
1361
|
structured: task.structured,
|
|
1128
1362
|
status: "pending",
|
|
1363
|
+
...(task.toolBudget ? { toolBudget: initialToolBudgetState(task.toolBudget) } : {}),
|
|
1129
1364
|
...(task.sessionFile ? { sessionFile: task.sessionFile } : {}),
|
|
1365
|
+
...(transcriptPath ? { transcriptPath } : {}),
|
|
1130
1366
|
skills: task.skills,
|
|
1131
1367
|
model: task.model,
|
|
1132
1368
|
thinking: task.thinking,
|
|
@@ -1134,8 +1370,8 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1134
1370
|
recentTools: [],
|
|
1135
1371
|
recentOutput: [],
|
|
1136
1372
|
});
|
|
1373
|
+
flatStepCount++;
|
|
1137
1374
|
}
|
|
1138
|
-
flatStepCount += step.parallel.length;
|
|
1139
1375
|
} else if (isDynamicRunnerGroup(step)) {
|
|
1140
1376
|
parallelGroups.push({ start: flatStepCount, count: 1, stepIndex });
|
|
1141
1377
|
initialStatusSteps.push({
|
|
@@ -1145,11 +1381,14 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1145
1381
|
outputName: step.collect.as,
|
|
1146
1382
|
structured: Boolean(step.collect.outputSchema),
|
|
1147
1383
|
status: "pending",
|
|
1384
|
+
...(step.parallel.toolBudget ? { toolBudget: initialToolBudgetState(step.parallel.toolBudget) } : {}),
|
|
1148
1385
|
recentTools: [],
|
|
1149
1386
|
recentOutput: [],
|
|
1150
1387
|
});
|
|
1151
1388
|
flatStepCount++;
|
|
1152
1389
|
} else {
|
|
1390
|
+
const stepFlatIndex = flatStepCount;
|
|
1391
|
+
const transcriptPath = resolveAsyncStepTranscriptPath({ artifactsDir, artifactConfig, runId: id, agent: step.agent, flatIndex: stepFlatIndex, flatStepCount: initialFlatStepCount });
|
|
1153
1392
|
initialStatusSteps.push({
|
|
1154
1393
|
agent: step.agent,
|
|
1155
1394
|
phase: step.phase,
|
|
@@ -1157,7 +1396,9 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1157
1396
|
outputName: step.outputName,
|
|
1158
1397
|
structured: step.structured,
|
|
1159
1398
|
status: "pending",
|
|
1399
|
+
...(step.toolBudget ? { toolBudget: initialToolBudgetState(step.toolBudget) } : {}),
|
|
1160
1400
|
...(step.sessionFile ? { sessionFile: step.sessionFile } : {}),
|
|
1401
|
+
...(transcriptPath ? { transcriptPath } : {}),
|
|
1161
1402
|
skills: step.skills,
|
|
1162
1403
|
model: step.model,
|
|
1163
1404
|
thinking: step.thinking,
|
|
@@ -1168,11 +1409,11 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1168
1409
|
flatStepCount++;
|
|
1169
1410
|
}
|
|
1170
1411
|
}
|
|
1171
|
-
const flatSteps = flattenSteps(steps);
|
|
1172
1412
|
const sessionEnabled = Boolean(config.sessionDir)
|
|
1173
1413
|
|| shareEnabled
|
|
1174
1414
|
|| flatSteps.some((step) => Boolean(step.sessionFile));
|
|
1175
1415
|
const statusPayload: RunnerStatusPayload = {
|
|
1416
|
+
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
1176
1417
|
runId: id,
|
|
1177
1418
|
...(config.sessionId ? { sessionId: config.sessionId } : {}),
|
|
1178
1419
|
mode: config.resultMode ?? (flatSteps.length > 1 ? "chain" : "single"),
|
|
@@ -1180,6 +1421,10 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1180
1421
|
lastActivityAt: overallStartTime,
|
|
1181
1422
|
startedAt: overallStartTime,
|
|
1182
1423
|
lastUpdate: overallStartTime,
|
|
1424
|
+
...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),
|
|
1425
|
+
...(config.deadlineAt !== undefined ? { deadlineAt: config.deadlineAt } : {}),
|
|
1426
|
+
...(config.turnBudget ? { turnBudget: initialTurnBudgetState(config.turnBudget) } : {}),
|
|
1427
|
+
...(config.toolBudget ? { toolBudget: initialToolBudgetState(config.toolBudget) } : {}),
|
|
1183
1428
|
pid: process.pid,
|
|
1184
1429
|
cwd,
|
|
1185
1430
|
currentStep: 0,
|
|
@@ -1251,6 +1496,117 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1251
1496
|
writeAtomicJson(statusPath, statusPayload);
|
|
1252
1497
|
emitNestedSelfEvent(statusPayload.state === "running" || statusPayload.state === "queued" ? "subagent.nested.updated" : "subagent.nested.completed");
|
|
1253
1498
|
};
|
|
1499
|
+
const registerStepInterrupt = (flatIndex: number, interrupt: (() => void) | undefined): void => {
|
|
1500
|
+
if (!interrupt) {
|
|
1501
|
+
activeChildInterrupts.delete(flatIndex);
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
activeChildInterrupts.set(flatIndex, interrupt);
|
|
1505
|
+
if (interrupted) interrupt();
|
|
1506
|
+
};
|
|
1507
|
+
const registerStepTimeout = (flatIndex: number, interrupt: (() => void) | undefined): void => {
|
|
1508
|
+
if (!interrupt) {
|
|
1509
|
+
activeChildTimeouts.delete(flatIndex);
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
activeChildTimeouts.set(flatIndex, interrupt);
|
|
1513
|
+
if (timedOut) interrupt();
|
|
1514
|
+
};
|
|
1515
|
+
const registerStepTurnBudgetAbort = (flatIndex: number, abort: ((message: string, state?: TurnBudgetState) => void) | undefined): void => {
|
|
1516
|
+
if (!abort) {
|
|
1517
|
+
activeChildTurnBudgetAborts.delete(flatIndex);
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
activeChildTurnBudgetAborts.set(flatIndex, abort);
|
|
1521
|
+
};
|
|
1522
|
+
const interruptActiveChildren = (): void => {
|
|
1523
|
+
for (const interrupt of [...activeChildInterrupts.values()]) interrupt();
|
|
1524
|
+
};
|
|
1525
|
+
const timeoutActiveChildren = (): void => {
|
|
1526
|
+
for (const interrupt of [...activeChildTimeouts.values()]) interrupt();
|
|
1527
|
+
};
|
|
1528
|
+
const nestedRuns = function* (children: NestedRunSummary[] | undefined): Generator<NestedRunSummary> {
|
|
1529
|
+
for (const child of children ?? []) {
|
|
1530
|
+
yield child;
|
|
1531
|
+
yield* nestedRuns(child.children);
|
|
1532
|
+
yield* nestedRuns(child.steps?.flatMap((step) => step.children ?? []));
|
|
1533
|
+
}
|
|
1534
|
+
};
|
|
1535
|
+
const interruptNestedAsyncDescendants = (): void => {
|
|
1536
|
+
if (!config.nestedRoute) return;
|
|
1537
|
+
let registry: ReturnType<typeof projectNestedEvents>;
|
|
1538
|
+
try {
|
|
1539
|
+
registry = projectNestedEvents(config.nestedRoute);
|
|
1540
|
+
} catch (error) {
|
|
1541
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
1542
|
+
type: "subagent.nested.interrupt_failed",
|
|
1543
|
+
ts: Date.now(),
|
|
1544
|
+
runId: id,
|
|
1545
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1546
|
+
}));
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
for (const run of nestedRuns(registry.children)) {
|
|
1550
|
+
if (run.state !== "running" && run.state !== "queued") continue;
|
|
1551
|
+
const nestedAsyncDir = run.asyncDir ?? resolveNestedAsyncDir(config.nestedRoute.rootRunId, run);
|
|
1552
|
+
if (!nestedAsyncDir) continue;
|
|
1553
|
+
try {
|
|
1554
|
+
deliverInterruptRequest({ asyncDir: nestedAsyncDir, pid: run.pid, source: "ancestor-interrupt" });
|
|
1555
|
+
} catch (error) {
|
|
1556
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
1557
|
+
type: "subagent.nested.interrupt_failed",
|
|
1558
|
+
ts: Date.now(),
|
|
1559
|
+
runId: id,
|
|
1560
|
+
targetRunId: run.id,
|
|
1561
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1562
|
+
}));
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
};
|
|
1566
|
+
const timeoutNestedAsyncDescendants = (): void => {
|
|
1567
|
+
if (!config.nestedRoute) return;
|
|
1568
|
+
let registry: ReturnType<typeof projectNestedEvents>;
|
|
1569
|
+
try {
|
|
1570
|
+
registry = projectNestedEvents(config.nestedRoute);
|
|
1571
|
+
} catch (error) {
|
|
1572
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
1573
|
+
type: "subagent.nested.timeout_failed",
|
|
1574
|
+
ts: Date.now(),
|
|
1575
|
+
runId: id,
|
|
1576
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1577
|
+
}));
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
for (const run of nestedRuns(registry.children)) {
|
|
1581
|
+
if (run.state !== "running" && run.state !== "queued") continue;
|
|
1582
|
+
const nestedAsyncDir = run.asyncDir ?? resolveNestedAsyncDir(config.nestedRoute.rootRunId, run);
|
|
1583
|
+
if (!nestedAsyncDir) continue;
|
|
1584
|
+
try {
|
|
1585
|
+
deliverTimeoutRequest({ asyncDir: nestedAsyncDir, pid: run.pid, source: "ancestor-timeout" });
|
|
1586
|
+
} catch (error) {
|
|
1587
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
1588
|
+
type: "subagent.nested.timeout_failed",
|
|
1589
|
+
ts: Date.now(),
|
|
1590
|
+
runId: id,
|
|
1591
|
+
targetRunId: run.id,
|
|
1592
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1593
|
+
}));
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
};
|
|
1597
|
+
const pausedStepResult = (agent: string): SingleStepResult => ({
|
|
1598
|
+
agent,
|
|
1599
|
+
output: "Paused after interrupt. Waiting for explicit next action.",
|
|
1600
|
+
exitCode: 0,
|
|
1601
|
+
interrupted: true,
|
|
1602
|
+
});
|
|
1603
|
+
const timedOutStepResult = (agent: string): SingleStepResult => ({
|
|
1604
|
+
agent,
|
|
1605
|
+
output: timeoutMessage ?? "Subagent timed out.",
|
|
1606
|
+
error: timeoutMessage ?? "Subagent timed out.",
|
|
1607
|
+
exitCode: 1,
|
|
1608
|
+
timedOut: true,
|
|
1609
|
+
});
|
|
1254
1610
|
const consumePendingAppendRequests = (): void => {
|
|
1255
1611
|
if (statusPayload.mode !== "chain" || statusPayload.state !== "running") return;
|
|
1256
1612
|
const requests = consumeChainAppendRequests(asyncDir);
|
|
@@ -1381,6 +1737,58 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1381
1737
|
appendControlEvent(event);
|
|
1382
1738
|
return true;
|
|
1383
1739
|
};
|
|
1740
|
+
const deliverSteerRequest = (request: SteerRequest): void => {
|
|
1741
|
+
if (statusPayload.state !== "running") return;
|
|
1742
|
+
const runningIndexes = statusPayload.steps
|
|
1743
|
+
.map((step, index) => ({ step, index }))
|
|
1744
|
+
.filter(({ step }) => step.status === "running")
|
|
1745
|
+
.map(({ index }) => index);
|
|
1746
|
+
const targets = request.targetIndex !== undefined ? [request.targetIndex] : runningIndexes;
|
|
1747
|
+
const now = Date.now();
|
|
1748
|
+
const accepted: number[] = [];
|
|
1749
|
+
const rejected: Array<{ index: number; reason: string }> = [];
|
|
1750
|
+
for (const index of targets) {
|
|
1751
|
+
const step = statusPayload.steps[index];
|
|
1752
|
+
if (!step) {
|
|
1753
|
+
rejected.push({ index, reason: "child index out of range" });
|
|
1754
|
+
continue;
|
|
1755
|
+
}
|
|
1756
|
+
if (step.status !== "running") {
|
|
1757
|
+
rejected.push({ index, reason: `child is ${step.status}` });
|
|
1758
|
+
continue;
|
|
1759
|
+
}
|
|
1760
|
+
enqueueStepSteer(asyncDir, index, request);
|
|
1761
|
+
step.steerCount = (step.steerCount ?? 0) + 1;
|
|
1762
|
+
step.lastSteerAt = now;
|
|
1763
|
+
accepted.push(index);
|
|
1764
|
+
}
|
|
1765
|
+
if (accepted.length > 0) {
|
|
1766
|
+
statusPayload.steerCount = (statusPayload.steerCount ?? 0) + accepted.length;
|
|
1767
|
+
statusPayload.lastSteerAt = now;
|
|
1768
|
+
statusPayload.lastUpdate = now;
|
|
1769
|
+
writeStatusPayload();
|
|
1770
|
+
}
|
|
1771
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
1772
|
+
type: "subagent.steer.requested",
|
|
1773
|
+
ts: now,
|
|
1774
|
+
runId: id,
|
|
1775
|
+
requestId: request.id,
|
|
1776
|
+
message: request.message,
|
|
1777
|
+
...(request.source ? { source: request.source } : {}),
|
|
1778
|
+
...(request.targetIndex !== undefined ? { targetIndex: request.targetIndex } : {}),
|
|
1779
|
+
acceptedIndexes: accepted,
|
|
1780
|
+
...(rejected.length ? { rejected } : {}),
|
|
1781
|
+
}));
|
|
1782
|
+
};
|
|
1783
|
+
const flushPendingStepSteers = (flatIndex: number): void => {
|
|
1784
|
+
const remaining: SteerRequest[] = [];
|
|
1785
|
+
for (const request of pendingStepSteers.splice(0)) {
|
|
1786
|
+
if (request.targetIndex === undefined) deliverSteerRequest({ ...request, targetIndex: flatIndex });
|
|
1787
|
+
else if (request.targetIndex === flatIndex) deliverSteerRequest(request);
|
|
1788
|
+
else remaining.push(request);
|
|
1789
|
+
}
|
|
1790
|
+
pendingStepSteers.push(...remaining);
|
|
1791
|
+
};
|
|
1384
1792
|
const updateStepModel = (flatIndex: number, model: string | undefined, thinking: string | undefined, now = Date.now()): void => {
|
|
1385
1793
|
const step = statusPayload.steps[flatIndex];
|
|
1386
1794
|
if (!step) return;
|
|
@@ -1389,6 +1797,40 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1389
1797
|
statusPayload.lastUpdate = now;
|
|
1390
1798
|
writeStatusPayload();
|
|
1391
1799
|
};
|
|
1800
|
+
const updateStepTurnBudget = (flatIndex: number, turnCount: number, now: number, terminalAssistantStop: boolean): void => {
|
|
1801
|
+
const budget = config.turnBudget;
|
|
1802
|
+
const step = statusPayload.steps[flatIndex];
|
|
1803
|
+
if (!budget || !step || timedOut || turnBudgetExceeded || step.turnBudgetExceeded) return;
|
|
1804
|
+
if (turnCount < budget.maxTurns) {
|
|
1805
|
+
const state: TurnBudgetState = { ...budget, outcome: "within-budget", turnCount };
|
|
1806
|
+
step.turnBudget = state;
|
|
1807
|
+
statusPayload.turnBudget = state;
|
|
1808
|
+
return;
|
|
1809
|
+
}
|
|
1810
|
+
const state = turnBudgetState(budget, turnCount, false);
|
|
1811
|
+
step.turnBudget = state;
|
|
1812
|
+
statusPayload.turnBudget = state;
|
|
1813
|
+
if (!step.wrapUpRequested) {
|
|
1814
|
+
step.wrapUpRequested = true;
|
|
1815
|
+
statusPayload.wrapUpRequested = true;
|
|
1816
|
+
appendRecentStepOutput(step, [turnBudgetSoftNote(budget, turnCount)]);
|
|
1817
|
+
}
|
|
1818
|
+
if (!shouldAbortForTurnBudget(budget, turnCount, terminalAssistantStop)) return;
|
|
1819
|
+
const exceededState = turnBudgetState(budget, turnCount, true);
|
|
1820
|
+
const message = turnBudgetExceededMessage(budget, turnCount);
|
|
1821
|
+
step.turnBudget = exceededState;
|
|
1822
|
+
step.turnBudgetExceeded = true;
|
|
1823
|
+
step.wrapUpRequested = true;
|
|
1824
|
+
step.error = message;
|
|
1825
|
+
turnBudgetExceeded = true;
|
|
1826
|
+
statusPayload.turnBudget = exceededState;
|
|
1827
|
+
statusPayload.turnBudgetExceeded = true;
|
|
1828
|
+
statusPayload.wrapUpRequested = true;
|
|
1829
|
+
statusPayload.error = message;
|
|
1830
|
+
statusPayload.lastUpdate = now;
|
|
1831
|
+
appendJsonl(eventsPath, JSON.stringify({ type: "subagent.step.turn_budget_exceeded", ts: now, runId: id, stepIndex: flatIndex, agent: step.agent, turnCount, maxTurns: budget.maxTurns, graceTurns: budget.graceTurns, message }));
|
|
1832
|
+
activeChildTurnBudgetAborts.get(flatIndex)?.(message, exceededState);
|
|
1833
|
+
};
|
|
1392
1834
|
const updateStepFromChildEvent = (flatIndex: number, event: ChildEvent): void => {
|
|
1393
1835
|
const step = statusPayload.steps[flatIndex];
|
|
1394
1836
|
if (!step) return;
|
|
@@ -1398,6 +1840,11 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1398
1840
|
const mutates = isMutatingTool(event.toolName, event.args);
|
|
1399
1841
|
const currentPath = resolveCurrentPath(event.toolName, event.args);
|
|
1400
1842
|
step.toolCount = (step.toolCount ?? 0) + 1;
|
|
1843
|
+
const configuredToolBudget = flatSteps[flatIndex]?.toolBudget;
|
|
1844
|
+
if (configuredToolBudget) {
|
|
1845
|
+
step.toolBudget = toolBudgetState(configuredToolBudget, step.toolCount);
|
|
1846
|
+
statusPayload.toolBudget = step.toolBudget;
|
|
1847
|
+
}
|
|
1401
1848
|
step.currentTool = event.toolName;
|
|
1402
1849
|
step.currentToolArgs = extractToolArgsPreview(event.args ?? {});
|
|
1403
1850
|
step.currentToolStartedAt = now;
|
|
@@ -1419,6 +1866,15 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1419
1866
|
const toolSnapshot = pendingToolResults[flatIndex];
|
|
1420
1867
|
pendingToolResults[flatIndex] = undefined;
|
|
1421
1868
|
const resultText = extractTextFromContent(event.message.content);
|
|
1869
|
+
if (toolSnapshot && resultText.includes("Tool budget hard limit reached")) {
|
|
1870
|
+
const configuredToolBudget = flatSteps[flatIndex]?.toolBudget;
|
|
1871
|
+
if (configuredToolBudget) {
|
|
1872
|
+
step.toolBudget = toolBudgetState(configuredToolBudget, step.toolCount ?? 0, toolSnapshot.tool);
|
|
1873
|
+
step.toolBudgetBlocked = true;
|
|
1874
|
+
statusPayload.toolBudget = step.toolBudget;
|
|
1875
|
+
statusPayload.toolBudgetBlocked = true;
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1422
1878
|
appendRecentStepOutput(step, resultText.split("\n").slice(-10));
|
|
1423
1879
|
if (toolSnapshot?.mutates && didMutatingToolFail(resultText)) {
|
|
1424
1880
|
const state = mutatingFailureStates[flatIndex]!;
|
|
@@ -1469,6 +1925,7 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1469
1925
|
statusPayload.totalTokens = { input: totalInput + input, output: totalOutput + output, total: totalInput + totalOutput + input + output };
|
|
1470
1926
|
}
|
|
1471
1927
|
statusPayload.turnCount = Math.max(statusPayload.turnCount ?? 0, step.turnCount);
|
|
1928
|
+
updateStepTurnBudget(flatIndex, step.turnCount, now, isTerminalAssistantStop(event.message));
|
|
1472
1929
|
}
|
|
1473
1930
|
syncTopLevelCurrentTool();
|
|
1474
1931
|
step.lastActivityAt = now;
|
|
@@ -1566,17 +2023,68 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1566
2023
|
ts: now,
|
|
1567
2024
|
runId: id,
|
|
1568
2025
|
}));
|
|
1569
|
-
|
|
2026
|
+
interruptNestedAsyncDescendants();
|
|
2027
|
+
interruptActiveChildren();
|
|
2028
|
+
};
|
|
2029
|
+
const timeoutRunner = () => {
|
|
2030
|
+
if (timedOut || interrupted || statusPayload.state !== "running") return;
|
|
2031
|
+
timedOut = true;
|
|
2032
|
+
const now = Date.now();
|
|
2033
|
+
const message = timeoutMessage ?? "Subagent timed out.";
|
|
2034
|
+
statusPayload.state = "failed";
|
|
2035
|
+
statusPayload.timedOut = true;
|
|
2036
|
+
statusPayload.error = message;
|
|
2037
|
+
currentActivityState = undefined;
|
|
2038
|
+
statusPayload.activityState = undefined;
|
|
2039
|
+
statusPayload.lastUpdate = now;
|
|
2040
|
+
for (const step of statusPayload.steps) {
|
|
2041
|
+
if (step.status !== "running" && step.status !== "pending") continue;
|
|
2042
|
+
step.status = "failed";
|
|
2043
|
+
step.error = message;
|
|
2044
|
+
step.exitCode = 1;
|
|
2045
|
+
step.timedOut = true;
|
|
2046
|
+
step.activityState = undefined;
|
|
2047
|
+
step.endedAt = now;
|
|
2048
|
+
step.durationMs = step.startedAt ? now - step.startedAt : 0;
|
|
2049
|
+
step.lastActivityAt = now;
|
|
2050
|
+
}
|
|
2051
|
+
writeStatusPayload();
|
|
2052
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
2053
|
+
type: "subagent.run.timed_out",
|
|
2054
|
+
ts: now,
|
|
2055
|
+
runId: id,
|
|
2056
|
+
timeoutMs: config.timeoutMs,
|
|
2057
|
+
deadlineAt: config.deadlineAt,
|
|
2058
|
+
message,
|
|
2059
|
+
}));
|
|
2060
|
+
timeoutAbortController.abort();
|
|
2061
|
+
timeoutNestedAsyncDescendants();
|
|
2062
|
+
timeoutActiveChildren();
|
|
1570
2063
|
};
|
|
1571
2064
|
process.on(ASYNC_INTERRUPT_SIGNAL, interruptRunner);
|
|
1572
|
-
// Portable control inbox: the parent drops
|
|
1573
|
-
// it cannot deliver
|
|
1574
|
-
//
|
|
1575
|
-
const disposeControlInbox = watchAsyncControlInbox(asyncDir, {
|
|
2065
|
+
// Portable control inbox: the parent drops control request files here when
|
|
2066
|
+
// it cannot deliver OS signals (e.g. ENOSYS on Windows) or when steering a
|
|
2067
|
+
// live child. Interrupts still route into the same graceful interruptRunner().
|
|
2068
|
+
const disposeControlInbox = watchAsyncControlInbox(asyncDir, {
|
|
2069
|
+
onInterrupt: interruptRunner,
|
|
2070
|
+
onTimeout: timeoutRunner,
|
|
2071
|
+
onSteer: (request) => {
|
|
2072
|
+
const targetStep = request.targetIndex !== undefined ? statusPayload.steps[request.targetIndex] : undefined;
|
|
2073
|
+
if (targetStep?.status === "pending") pendingStepSteers.push(request);
|
|
2074
|
+
else if (request.targetIndex !== undefined || statusPayload.steps.some((step) => step.status === "running")) deliverSteerRequest(request);
|
|
2075
|
+
else pendingStepSteers.push(request);
|
|
2076
|
+
},
|
|
2077
|
+
});
|
|
2078
|
+
if (config.deadlineAt !== undefined) {
|
|
2079
|
+
const remainingMs = Math.max(0, config.deadlineAt - Date.now());
|
|
2080
|
+
timeoutTimer = setTimeout(timeoutRunner, remainingMs);
|
|
2081
|
+
timeoutTimer.unref?.();
|
|
2082
|
+
}
|
|
1576
2083
|
appendJsonl(
|
|
1577
2084
|
eventsPath,
|
|
1578
2085
|
JSON.stringify({
|
|
1579
2086
|
type: "subagent.run.started",
|
|
2087
|
+
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
1580
2088
|
ts: overallStartTime,
|
|
1581
2089
|
runId: id,
|
|
1582
2090
|
mode: statusPayload.mode,
|
|
@@ -1589,7 +2097,7 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1589
2097
|
let stepCursor = 0;
|
|
1590
2098
|
|
|
1591
2099
|
while (true) {
|
|
1592
|
-
if (interrupted) break;
|
|
2100
|
+
if (interrupted || timedOut || turnBudgetExceeded) break;
|
|
1593
2101
|
consumePendingAppendRequests();
|
|
1594
2102
|
if (stepCursor >= steps.length) break;
|
|
1595
2103
|
const stepIndex = stepCursor++;
|
|
@@ -1641,7 +2149,7 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1641
2149
|
placeholder.durationMs = 0;
|
|
1642
2150
|
}
|
|
1643
2151
|
previousOutput = "Dynamic fanout produced 0 results.";
|
|
1644
|
-
const groupAcceptance = step.effectiveAcceptance?.explicit
|
|
2152
|
+
const groupAcceptance = step.effectiveAcceptance?.explicit && !timedOut
|
|
1645
2153
|
? await evaluateAcceptance({
|
|
1646
2154
|
acceptance: step.effectiveAcceptance,
|
|
1647
2155
|
output: "",
|
|
@@ -1650,39 +2158,59 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1650
2158
|
notes: "Dynamic fanout produced 0 results.",
|
|
1651
2159
|
}),
|
|
1652
2160
|
cwd,
|
|
2161
|
+
signal: timeoutAbortController.signal,
|
|
2162
|
+
abortMessage: timeoutMessage ?? "Subagent timed out.",
|
|
1653
2163
|
})
|
|
1654
2164
|
: undefined;
|
|
1655
|
-
|
|
1656
|
-
const
|
|
1657
|
-
if (
|
|
2165
|
+
const groupTimedOut = timedOut || timeoutAbortController.signal.aborted;
|
|
2166
|
+
const effectiveGroupAcceptance = groupTimedOut ? undefined : groupAcceptance;
|
|
2167
|
+
if (placeholder && effectiveGroupAcceptance) placeholder.acceptance = effectiveGroupAcceptance;
|
|
2168
|
+
const groupAcceptanceFailure = effectiveGroupAcceptance ? acceptanceFailureMessage(effectiveGroupAcceptance) : undefined;
|
|
2169
|
+
if (groupTimedOut || groupAcceptanceFailure) {
|
|
2170
|
+
const errorMessage = groupTimedOut ? timeoutMessage ?? "Subagent timed out." : groupAcceptanceFailure!;
|
|
1658
2171
|
statusPayload.state = "failed";
|
|
1659
|
-
statusPayload.error =
|
|
2172
|
+
statusPayload.error = errorMessage;
|
|
1660
2173
|
if (placeholder) {
|
|
1661
2174
|
placeholder.status = "failed";
|
|
1662
|
-
placeholder.error =
|
|
2175
|
+
placeholder.error = errorMessage;
|
|
1663
2176
|
placeholder.exitCode = 1;
|
|
2177
|
+
placeholder.timedOut = groupTimedOut ? true : undefined;
|
|
1664
2178
|
}
|
|
1665
|
-
markDynamicGraphGroup(stepIndex, "failed",
|
|
1666
|
-
statusPayload.lastUpdate = now;
|
|
2179
|
+
markDynamicGraphGroup(stepIndex, "failed", errorMessage, effectiveGroupAcceptance);
|
|
2180
|
+
statusPayload.lastUpdate = Date.now();
|
|
1667
2181
|
writeStatusPayload();
|
|
1668
|
-
results.push({ agent: step.parallel.agent, output:
|
|
2182
|
+
results.push({ agent: step.parallel.agent, output: errorMessage, error: errorMessage, success: false, exitCode: 1, timedOut: groupTimedOut ? true : undefined, acceptance: effectiveGroupAcceptance });
|
|
1669
2183
|
break;
|
|
1670
2184
|
}
|
|
1671
2185
|
flatIndex++;
|
|
1672
2186
|
statusPayload.lastUpdate = now;
|
|
1673
|
-
markDynamicGraphGroup(stepIndex, "completed", undefined,
|
|
2187
|
+
markDynamicGraphGroup(stepIndex, "completed", undefined, effectiveGroupAcceptance);
|
|
1674
2188
|
writeStatusPayload();
|
|
1675
2189
|
continue;
|
|
1676
2190
|
}
|
|
1677
2191
|
|
|
1678
|
-
const dynamicSteps = materialized.parallel.map((task, itemIndex) =>
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
2192
|
+
const dynamicSteps = materialized.parallel.map((task, itemIndex) => {
|
|
2193
|
+
const thinkingOverride = step.thinkingOverrides?.[itemIndex];
|
|
2194
|
+
const model = thinkingOverride ? applyThinkingSuffix(step.parallel.model, thinkingOverride, true) : step.parallel.model;
|
|
2195
|
+
const thinking = thinkingOverride ? resolveEffectiveThinking(model, thinkingOverride) : undefined;
|
|
2196
|
+
return {
|
|
2197
|
+
...step.parallel,
|
|
2198
|
+
task: task.task ?? step.parallel.task,
|
|
2199
|
+
label: task.label ?? step.parallel.label,
|
|
2200
|
+
...(step.sessionFiles?.[itemIndex] ? { sessionFile: step.sessionFiles[itemIndex] } : {}),
|
|
2201
|
+
...(thinkingOverride ? {
|
|
2202
|
+
...(model ? { model } : {}),
|
|
2203
|
+
...(thinking ? { thinking } : {}),
|
|
2204
|
+
...(step.parallel.modelCandidates ? { modelCandidates: step.parallel.modelCandidates.map((candidate) => applyThinkingSuffix(candidate, thinkingOverride, true)) } : {}),
|
|
2205
|
+
} : {}),
|
|
2206
|
+
structuredOutput: undefined,
|
|
2207
|
+
structuredOutputSchema: step.parallel.structuredOutputSchema ?? step.parallel.structuredOutput?.schema,
|
|
2208
|
+
};
|
|
2209
|
+
});
|
|
2210
|
+
const dynamicFlatStepCount = Math.max(statusPayload.steps.length - 1 + dynamicSteps.length, 1);
|
|
2211
|
+
const dynamicStatusSteps: RunnerStatusStep[] = dynamicSteps.map((task, itemIndex) => {
|
|
2212
|
+
const transcriptPath = resolveAsyncStepTranscriptPath({ artifactsDir, artifactConfig, runId: id, agent: task.agent, flatIndex: groupStartFlatIndex + itemIndex, flatStepCount: dynamicFlatStepCount });
|
|
2213
|
+
return {
|
|
1686
2214
|
agent: task.agent,
|
|
1687
2215
|
phase: task.phase ?? step.phase,
|
|
1688
2216
|
label: task.label,
|
|
@@ -1690,13 +2218,15 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1690
2218
|
structured: Boolean(task.structuredOutputSchema),
|
|
1691
2219
|
status: "pending",
|
|
1692
2220
|
...(task.sessionFile ? { sessionFile: task.sessionFile } : {}),
|
|
2221
|
+
...(transcriptPath ? { transcriptPath } : {}),
|
|
1693
2222
|
skills: task.skills,
|
|
1694
2223
|
model: task.model,
|
|
1695
2224
|
thinking: task.thinking,
|
|
1696
2225
|
attemptedModels: task.modelCandidates && task.modelCandidates.length > 0 ? task.modelCandidates : task.model ? [task.model] : undefined,
|
|
1697
2226
|
recentTools: [],
|
|
1698
2227
|
recentOutput: [],
|
|
1699
|
-
}
|
|
2228
|
+
};
|
|
2229
|
+
});
|
|
1700
2230
|
statusPayload.steps.splice(groupStartFlatIndex, 1, ...dynamicStatusSteps);
|
|
1701
2231
|
if (config.childIntercomTargets) {
|
|
1702
2232
|
config.childIntercomTargets = statusPayload.steps.map((statusStep, index) => resolveSubagentIntercomTarget(id, statusStep.agent, index));
|
|
@@ -1745,6 +2275,8 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1745
2275
|
let aborted = false;
|
|
1746
2276
|
const parallelResults = await mapConcurrent(dynamicSteps, concurrency, async (task, taskIdx) => {
|
|
1747
2277
|
const fi = groupStartFlatIndex + taskIdx;
|
|
2278
|
+
if (timedOut) return timedOutStepResult(task.agent);
|
|
2279
|
+
if (interrupted) return pausedStepResult(task.agent);
|
|
1748
2280
|
if (aborted && failFast) {
|
|
1749
2281
|
const skippedAt = Date.now();
|
|
1750
2282
|
statusPayload.steps[fi].status = "failed";
|
|
@@ -1770,6 +2302,7 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1770
2302
|
statusPayload.lastUpdate = taskStartTime;
|
|
1771
2303
|
writeStatusPayload();
|
|
1772
2304
|
appendJsonl(eventsPath, JSON.stringify({ type: "subagent.step.started", ts: taskStartTime, runId: id, stepIndex: fi, agent: task.agent }));
|
|
2305
|
+
flushPendingStepSteers(fi);
|
|
1773
2306
|
const singleResult = await runSingleStep(task, {
|
|
1774
2307
|
previousOutput, placeholder, cwd, sessionEnabled,
|
|
1775
2308
|
outputs,
|
|
@@ -1777,27 +2310,47 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1777
2310
|
artifactsDir, artifactConfig, id,
|
|
1778
2311
|
flatIndex: fi, flatStepCount: Math.max(statusPayload.steps.length, 1),
|
|
1779
2312
|
outputFile: path.join(asyncDir, `output-${fi}.log`),
|
|
2313
|
+
steerInboxDir: stepSteerInboxDir(asyncDir, fi),
|
|
1780
2314
|
piPackageRoot: config.piPackageRoot,
|
|
1781
2315
|
piArgv1: config.piArgv1,
|
|
1782
2316
|
childIntercomTarget: config.childIntercomTargets?.[fi],
|
|
1783
2317
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
1784
2318
|
nestedRoute: config.nestedRoute,
|
|
1785
|
-
registerInterrupt: (interrupt) =>
|
|
1786
|
-
|
|
1787
|
-
|
|
2319
|
+
registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
|
|
2320
|
+
registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
|
|
2321
|
+
registerTurnBudgetAbort: (abort) => registerStepTurnBudgetAbort(fi, abort),
|
|
2322
|
+
timeoutSignal: timeoutAbortController.signal,
|
|
2323
|
+
timeoutMessage,
|
|
2324
|
+
turnBudget: config.turnBudget,
|
|
1788
2325
|
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking),
|
|
1789
2326
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
2327
|
+
skipAcceptance: () => timedOut,
|
|
1790
2328
|
});
|
|
1791
2329
|
const taskEndTime = Date.now();
|
|
1792
|
-
|
|
2330
|
+
const childInterrupted = singleResult.interrupted === true;
|
|
2331
|
+
statusPayload.steps[fi].status = timedOut ? "failed" : childInterrupted ? "paused" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
1793
2332
|
statusPayload.steps[fi].endedAt = taskEndTime;
|
|
1794
2333
|
statusPayload.steps[fi].durationMs = taskEndTime - taskStartTime;
|
|
1795
|
-
statusPayload.steps[fi].exitCode = singleResult.exitCode;
|
|
2334
|
+
statusPayload.steps[fi].exitCode = timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
|
|
2335
|
+
statusPayload.steps[fi].timedOut = timedOut || singleResult.timedOut ? true : undefined;
|
|
2336
|
+
statusPayload.steps[fi].turnBudget = singleResult.turnBudget;
|
|
2337
|
+
statusPayload.steps[fi].turnBudgetExceeded = singleResult.turnBudgetExceeded;
|
|
2338
|
+
statusPayload.steps[fi].wrapUpRequested = singleResult.wrapUpRequested;
|
|
2339
|
+
statusPayload.steps[fi].toolBudget = singleResult.toolBudget;
|
|
2340
|
+
statusPayload.steps[fi].toolBudgetBlocked = singleResult.toolBudgetBlocked;
|
|
2341
|
+
if (singleResult.toolBudget) statusPayload.toolBudget = singleResult.toolBudget;
|
|
2342
|
+
if (singleResult.toolBudgetBlocked) statusPayload.toolBudgetBlocked = true;
|
|
2343
|
+
if (singleResult.turnBudget) statusPayload.turnBudget = singleResult.turnBudget;
|
|
2344
|
+
if (singleResult.turnBudgetExceeded) statusPayload.turnBudgetExceeded = true;
|
|
2345
|
+
if (singleResult.wrapUpRequested) statusPayload.wrapUpRequested = true;
|
|
1796
2346
|
statusPayload.steps[fi].model = singleResult.model;
|
|
1797
2347
|
statusPayload.steps[fi].thinking = resolveEffectiveThinking(singleResult.model, statusPayload.steps[fi].thinking);
|
|
1798
2348
|
statusPayload.steps[fi].attemptedModels = singleResult.attemptedModels;
|
|
1799
2349
|
statusPayload.steps[fi].modelAttempts = singleResult.modelAttempts;
|
|
1800
|
-
statusPayload.steps[fi].
|
|
2350
|
+
statusPayload.steps[fi].totalCost = singleResult.totalCost;
|
|
2351
|
+
statusPayload.steps[fi].error = timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error;
|
|
2352
|
+
statusPayload.steps[fi].transcriptPath = singleResult.transcriptPath ?? statusPayload.steps[fi].transcriptPath;
|
|
2353
|
+
statusPayload.steps[fi].transcriptError = singleResult.transcriptError;
|
|
1801
2354
|
statusPayload.steps[fi].structuredOutput = singleResult.structuredOutput;
|
|
1802
2355
|
statusPayload.steps[fi].structuredOutputPath = singleResult.structuredOutputPath;
|
|
1803
2356
|
statusPayload.steps[fi].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
@@ -1805,13 +2358,13 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1805
2358
|
statusPayload.lastUpdate = taskEndTime;
|
|
1806
2359
|
writeStatusPayload();
|
|
1807
2360
|
appendJsonl(eventsPath, JSON.stringify({
|
|
1808
|
-
type: singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
2361
|
+
type: timedOut ? "subagent.step.failed" : childInterrupted ? "subagent.step.paused" : singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
1809
2362
|
ts: taskEndTime, runId: id, stepIndex: fi, agent: task.agent,
|
|
1810
|
-
exitCode: singleResult.exitCode, durationMs: taskEndTime - taskStartTime,
|
|
2363
|
+
exitCode: timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode, durationMs: taskEndTime - taskStartTime,
|
|
1811
2364
|
}));
|
|
1812
2365
|
if (singleResult.exitCode !== 0 && failFast) aborted = true;
|
|
1813
|
-
return { ...singleResult, skipped: false };
|
|
1814
|
-
});
|
|
2366
|
+
return timedOut ? { ...singleResult, output: timeoutMessage ?? "Subagent timed out.", error: timeoutMessage ?? "Subagent timed out.", exitCode: 1, interrupted: false, timedOut: true, skipped: false } : { ...singleResult, skipped: false };
|
|
2367
|
+
}, globalSemaphore);
|
|
1815
2368
|
|
|
1816
2369
|
flatIndex += dynamicSteps.length;
|
|
1817
2370
|
for (const pr of parallelResults) {
|
|
@@ -1819,15 +2372,25 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1819
2372
|
agent: pr.agent,
|
|
1820
2373
|
output: pr.output,
|
|
1821
2374
|
error: pr.error,
|
|
1822
|
-
success: pr.exitCode === 0,
|
|
1823
|
-
exitCode: pr.exitCode,
|
|
2375
|
+
success: pr.interrupted !== true && pr.exitCode === 0,
|
|
2376
|
+
exitCode: pr.interrupted === true ? 0 : pr.exitCode,
|
|
1824
2377
|
skipped: pr.skipped,
|
|
2378
|
+
interrupted: pr.interrupted,
|
|
2379
|
+
timedOut: pr.timedOut,
|
|
2380
|
+
turnBudget: pr.turnBudget,
|
|
2381
|
+
turnBudgetExceeded: pr.turnBudgetExceeded,
|
|
2382
|
+
wrapUpRequested: pr.wrapUpRequested,
|
|
2383
|
+
toolBudget: pr.toolBudget,
|
|
2384
|
+
toolBudgetBlocked: pr.toolBudgetBlocked,
|
|
1825
2385
|
sessionFile: pr.sessionFile,
|
|
1826
2386
|
intercomTarget: pr.intercomTarget,
|
|
1827
2387
|
model: pr.model,
|
|
1828
2388
|
attemptedModels: pr.attemptedModels,
|
|
1829
2389
|
modelAttempts: pr.modelAttempts,
|
|
2390
|
+
totalCost: pr.totalCost,
|
|
1830
2391
|
artifactPaths: pr.artifactPaths,
|
|
2392
|
+
transcriptPath: pr.transcriptPath,
|
|
2393
|
+
transcriptError: pr.transcriptError,
|
|
1831
2394
|
structuredOutput: pr.structuredOutput,
|
|
1832
2395
|
structuredOutputPath: pr.structuredOutputPath,
|
|
1833
2396
|
structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
|
|
@@ -1846,7 +2409,7 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1846
2409
|
stepIndex,
|
|
1847
2410
|
};
|
|
1848
2411
|
statusPayload.outputs = outputs;
|
|
1849
|
-
const groupAcceptance = step.effectiveAcceptance
|
|
2412
|
+
const groupAcceptance = step.effectiveAcceptance && !timedOut
|
|
1850
2413
|
? await evaluateAcceptance({
|
|
1851
2414
|
acceptance: step.effectiveAcceptance,
|
|
1852
2415
|
output: "",
|
|
@@ -1855,21 +2418,27 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1855
2418
|
notes: `Dynamic fanout collected ${collection.length} result(s) into ${step.collect.as}.`,
|
|
1856
2419
|
}),
|
|
1857
2420
|
cwd,
|
|
2421
|
+
signal: timeoutAbortController.signal,
|
|
2422
|
+
abortMessage: timeoutMessage ?? "Subagent timed out.",
|
|
1858
2423
|
})
|
|
1859
2424
|
: undefined;
|
|
1860
|
-
const
|
|
1861
|
-
|
|
1862
|
-
|
|
2425
|
+
const groupTimedOut = timedOut || timeoutAbortController.signal.aborted;
|
|
2426
|
+
const effectiveGroupAcceptance = groupTimedOut ? undefined : groupAcceptance;
|
|
2427
|
+
const groupAcceptanceFailure = effectiveGroupAcceptance ? acceptanceFailureMessage(effectiveGroupAcceptance) : undefined;
|
|
2428
|
+
const groupError = groupTimedOut ? timeoutMessage ?? "Subagent timed out." : groupAcceptanceFailure;
|
|
2429
|
+
markDynamicGraphGroup(stepIndex, groupError ? "failed" : "completed", groupError, effectiveGroupAcceptance);
|
|
2430
|
+
if (groupError) {
|
|
1863
2431
|
results.push({
|
|
1864
2432
|
agent: step.parallel.agent,
|
|
1865
|
-
output:
|
|
1866
|
-
error:
|
|
2433
|
+
output: groupError,
|
|
2434
|
+
error: groupError,
|
|
1867
2435
|
success: false,
|
|
1868
2436
|
exitCode: 1,
|
|
2437
|
+
timedOut: groupTimedOut ? true : undefined,
|
|
1869
2438
|
structuredOutput: collection,
|
|
1870
|
-
acceptance:
|
|
2439
|
+
acceptance: effectiveGroupAcceptance,
|
|
1871
2440
|
});
|
|
1872
|
-
statusPayload.error =
|
|
2441
|
+
statusPayload.error = groupError;
|
|
1873
2442
|
}
|
|
1874
2443
|
} catch (error) {
|
|
1875
2444
|
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
@@ -1935,6 +2504,7 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1935
2504
|
setupHook: config.worktreeSetupHook
|
|
1936
2505
|
? { hookPath: config.worktreeSetupHook, timeoutMs: config.worktreeSetupHookTimeoutMs }
|
|
1937
2506
|
: undefined,
|
|
2507
|
+
baseDir: config.worktreeBaseDir,
|
|
1938
2508
|
});
|
|
1939
2509
|
} catch (error) {
|
|
1940
2510
|
const setupError = error instanceof Error ? error.message : String(error);
|
|
@@ -1976,6 +2546,8 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1976
2546
|
concurrency,
|
|
1977
2547
|
async (task, taskIdx) => {
|
|
1978
2548
|
const fi = groupStartFlatIndex + taskIdx;
|
|
2549
|
+
if (timedOut) return timedOutStepResult(task.agent);
|
|
2550
|
+
if (interrupted) return pausedStepResult(task.agent);
|
|
1979
2551
|
if (aborted && failFast) {
|
|
1980
2552
|
const skippedAt = Date.now();
|
|
1981
2553
|
statusPayload.steps[fi].status = "failed";
|
|
@@ -2016,6 +2588,7 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2016
2588
|
? path.join(config.sessionDir, `parallel-${taskIdx}`)
|
|
2017
2589
|
: undefined;
|
|
2018
2590
|
const { taskForRun, taskCwd } = prepareParallelTaskRun(task, cwd, worktreeSetup, taskIdx);
|
|
2591
|
+
flushPendingStepSteers(fi);
|
|
2019
2592
|
|
|
2020
2593
|
const singleResult = await runSingleStep(taskForRun, {
|
|
2021
2594
|
previousOutput, placeholder, cwd: taskCwd, sessionEnabled,
|
|
@@ -2024,16 +2597,21 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2024
2597
|
artifactsDir, artifactConfig, id,
|
|
2025
2598
|
flatIndex: fi, flatStepCount: Math.max(statusPayload.steps.length, 1),
|
|
2026
2599
|
outputFile: path.join(asyncDir, `output-${fi}.log`),
|
|
2600
|
+
steerInboxDir: stepSteerInboxDir(asyncDir, fi),
|
|
2027
2601
|
piPackageRoot: config.piPackageRoot,
|
|
2028
2602
|
piArgv1: config.piArgv1,
|
|
2029
2603
|
childIntercomTarget: config.childIntercomTargets?.[fi],
|
|
2030
2604
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
2031
2605
|
nestedRoute: config.nestedRoute,
|
|
2032
|
-
registerInterrupt: (interrupt) =>
|
|
2033
|
-
|
|
2034
|
-
|
|
2606
|
+
registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
|
|
2607
|
+
registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
|
|
2608
|
+
registerTurnBudgetAbort: (abort) => registerStepTurnBudgetAbort(fi, abort),
|
|
2609
|
+
timeoutSignal: timeoutAbortController.signal,
|
|
2610
|
+
timeoutMessage,
|
|
2611
|
+
turnBudget: config.turnBudget,
|
|
2035
2612
|
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking),
|
|
2036
2613
|
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
2614
|
+
skipAcceptance: () => timedOut,
|
|
2037
2615
|
});
|
|
2038
2616
|
if (task.sessionFile) {
|
|
2039
2617
|
latestSessionFile = task.sessionFile;
|
|
@@ -2041,16 +2619,31 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2041
2619
|
|
|
2042
2620
|
const taskEndTime = Date.now();
|
|
2043
2621
|
const taskDuration = taskEndTime - taskStartTime;
|
|
2622
|
+
const childInterrupted = singleResult.interrupted === true;
|
|
2044
2623
|
|
|
2045
|
-
statusPayload.steps[fi].status = singleResult.exitCode === 0 ? "complete" : "failed";
|
|
2624
|
+
statusPayload.steps[fi].status = timedOut ? "failed" : childInterrupted ? "paused" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
2046
2625
|
statusPayload.steps[fi].endedAt = taskEndTime;
|
|
2047
2626
|
statusPayload.steps[fi].durationMs = taskDuration;
|
|
2048
|
-
statusPayload.steps[fi].exitCode = singleResult.exitCode;
|
|
2627
|
+
statusPayload.steps[fi].exitCode = timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
|
|
2628
|
+
statusPayload.steps[fi].timedOut = timedOut || singleResult.timedOut ? true : undefined;
|
|
2629
|
+
statusPayload.steps[fi].turnBudget = singleResult.turnBudget;
|
|
2630
|
+
statusPayload.steps[fi].turnBudgetExceeded = singleResult.turnBudgetExceeded;
|
|
2631
|
+
statusPayload.steps[fi].wrapUpRequested = singleResult.wrapUpRequested;
|
|
2632
|
+
statusPayload.steps[fi].toolBudget = singleResult.toolBudget;
|
|
2633
|
+
statusPayload.steps[fi].toolBudgetBlocked = singleResult.toolBudgetBlocked;
|
|
2634
|
+
if (singleResult.toolBudget) statusPayload.toolBudget = singleResult.toolBudget;
|
|
2635
|
+
if (singleResult.toolBudgetBlocked) statusPayload.toolBudgetBlocked = true;
|
|
2636
|
+
if (singleResult.turnBudget) statusPayload.turnBudget = singleResult.turnBudget;
|
|
2637
|
+
if (singleResult.turnBudgetExceeded) statusPayload.turnBudgetExceeded = true;
|
|
2638
|
+
if (singleResult.wrapUpRequested) statusPayload.wrapUpRequested = true;
|
|
2049
2639
|
statusPayload.steps[fi].model = singleResult.model;
|
|
2050
2640
|
statusPayload.steps[fi].thinking = resolveEffectiveThinking(singleResult.model, statusPayload.steps[fi].thinking);
|
|
2051
2641
|
statusPayload.steps[fi].attemptedModels = singleResult.attemptedModels;
|
|
2052
2642
|
statusPayload.steps[fi].modelAttempts = singleResult.modelAttempts;
|
|
2053
|
-
statusPayload.steps[fi].
|
|
2643
|
+
statusPayload.steps[fi].totalCost = singleResult.totalCost;
|
|
2644
|
+
statusPayload.steps[fi].error = timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error;
|
|
2645
|
+
statusPayload.steps[fi].transcriptPath = singleResult.transcriptPath ?? statusPayload.steps[fi].transcriptPath;
|
|
2646
|
+
statusPayload.steps[fi].transcriptError = singleResult.transcriptError;
|
|
2054
2647
|
statusPayload.steps[fi].structuredOutput = singleResult.structuredOutput;
|
|
2055
2648
|
statusPayload.steps[fi].structuredOutputPath = singleResult.structuredOutputPath;
|
|
2056
2649
|
statusPayload.steps[fi].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
@@ -2059,9 +2652,9 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2059
2652
|
writeStatusPayload();
|
|
2060
2653
|
|
|
2061
2654
|
appendJsonl(eventsPath, JSON.stringify({
|
|
2062
|
-
type: singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
2655
|
+
type: timedOut ? "subagent.step.failed" : childInterrupted ? "subagent.step.paused" : singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
2063
2656
|
ts: taskEndTime, runId: id, stepIndex: fi, agent: task.agent,
|
|
2064
|
-
exitCode: singleResult.exitCode, durationMs: taskDuration,
|
|
2657
|
+
exitCode: timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode, durationMs: taskDuration,
|
|
2065
2658
|
}));
|
|
2066
2659
|
if (singleResult.completionGuardTriggered) {
|
|
2067
2660
|
const event = buildControlEvent({
|
|
@@ -2078,8 +2671,9 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2078
2671
|
}
|
|
2079
2672
|
|
|
2080
2673
|
if (singleResult.exitCode !== 0 && failFast) aborted = true;
|
|
2081
|
-
return { ...singleResult, skipped: false };
|
|
2674
|
+
return timedOut ? { ...singleResult, output: timeoutMessage ?? "Subagent timed out.", error: timeoutMessage ?? "Subagent timed out.", exitCode: 1, interrupted: false, timedOut: true, skipped: false } : { ...singleResult, skipped: false };
|
|
2082
2675
|
},
|
|
2676
|
+
globalSemaphore,
|
|
2083
2677
|
);
|
|
2084
2678
|
|
|
2085
2679
|
flatIndex += group.parallel.length;
|
|
@@ -2107,15 +2701,25 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2107
2701
|
agent: pr.agent,
|
|
2108
2702
|
output: pr.output,
|
|
2109
2703
|
error: pr.error,
|
|
2110
|
-
success: pr.exitCode === 0,
|
|
2111
|
-
exitCode: pr.exitCode,
|
|
2704
|
+
success: pr.interrupted !== true && pr.exitCode === 0,
|
|
2705
|
+
exitCode: pr.interrupted === true ? 0 : pr.exitCode,
|
|
2112
2706
|
skipped: pr.skipped,
|
|
2707
|
+
interrupted: pr.interrupted,
|
|
2708
|
+
timedOut: pr.timedOut,
|
|
2709
|
+
turnBudget: pr.turnBudget,
|
|
2710
|
+
turnBudgetExceeded: pr.turnBudgetExceeded,
|
|
2711
|
+
wrapUpRequested: pr.wrapUpRequested,
|
|
2712
|
+
toolBudget: pr.toolBudget,
|
|
2713
|
+
toolBudgetBlocked: pr.toolBudgetBlocked,
|
|
2113
2714
|
sessionFile: pr.sessionFile,
|
|
2114
2715
|
intercomTarget: pr.intercomTarget,
|
|
2115
2716
|
model: pr.model,
|
|
2116
2717
|
attemptedModels: pr.attemptedModels,
|
|
2117
2718
|
modelAttempts: pr.modelAttempts,
|
|
2719
|
+
totalCost: pr.totalCost,
|
|
2118
2720
|
artifactPaths: pr.artifactPaths,
|
|
2721
|
+
transcriptPath: pr.transcriptPath,
|
|
2722
|
+
transcriptError: pr.transcriptError,
|
|
2119
2723
|
structuredOutput: pr.structuredOutput,
|
|
2120
2724
|
structuredOutputPath: pr.structuredOutputPath,
|
|
2121
2725
|
structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
|
|
@@ -2182,6 +2786,7 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2182
2786
|
agent: seqStep.agent,
|
|
2183
2787
|
}));
|
|
2184
2788
|
|
|
2789
|
+
flushPendingStepSteers(flatIndex);
|
|
2185
2790
|
const singleResult = await runSingleStep(seqStep, {
|
|
2186
2791
|
previousOutput, placeholder, cwd, sessionEnabled,
|
|
2187
2792
|
outputs,
|
|
@@ -2189,16 +2794,21 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2189
2794
|
artifactsDir, artifactConfig, id,
|
|
2190
2795
|
flatIndex, flatStepCount: Math.max(statusPayload.steps.length, 1),
|
|
2191
2796
|
outputFile: path.join(asyncDir, `output-${flatIndex}.log`),
|
|
2797
|
+
steerInboxDir: stepSteerInboxDir(asyncDir, flatIndex),
|
|
2192
2798
|
piPackageRoot: config.piPackageRoot,
|
|
2193
2799
|
piArgv1: config.piArgv1,
|
|
2194
2800
|
childIntercomTarget: config.childIntercomTargets?.[flatIndex],
|
|
2195
2801
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
2196
2802
|
nestedRoute: config.nestedRoute,
|
|
2197
|
-
registerInterrupt: (interrupt) =>
|
|
2198
|
-
|
|
2199
|
-
|
|
2803
|
+
registerInterrupt: (interrupt) => registerStepInterrupt(flatIndex, interrupt),
|
|
2804
|
+
registerTimeout: (interrupt) => registerStepTimeout(flatIndex, interrupt),
|
|
2805
|
+
registerTurnBudgetAbort: (abort) => registerStepTurnBudgetAbort(flatIndex, abort),
|
|
2806
|
+
timeoutSignal: timeoutAbortController.signal,
|
|
2807
|
+
timeoutMessage,
|
|
2808
|
+
turnBudget: config.turnBudget,
|
|
2200
2809
|
onAttemptStart: (attempt) => updateStepModel(flatIndex, attempt.model, attempt.thinking),
|
|
2201
2810
|
onChildEvent: (event) => updateStepFromChildEvent(flatIndex, event),
|
|
2811
|
+
skipAcceptance: () => timedOut,
|
|
2202
2812
|
});
|
|
2203
2813
|
if (seqStep.sessionFile) {
|
|
2204
2814
|
latestSessionFile = seqStep.sessionFile;
|
|
@@ -2207,20 +2817,30 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2207
2817
|
previousOutput = singleResult.output;
|
|
2208
2818
|
results.push({
|
|
2209
2819
|
agent: singleResult.agent,
|
|
2210
|
-
output: singleResult.output,
|
|
2211
|
-
error: singleResult.error,
|
|
2212
|
-
success: singleResult.exitCode === 0,
|
|
2213
|
-
exitCode: singleResult.exitCode,
|
|
2820
|
+
output: timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.output,
|
|
2821
|
+
error: timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error,
|
|
2822
|
+
success: !timedOut && singleResult.interrupted !== true && singleResult.exitCode === 0,
|
|
2823
|
+
exitCode: timedOut ? 1 : singleResult.interrupted === true ? 0 : singleResult.exitCode,
|
|
2214
2824
|
sessionFile: singleResult.sessionFile,
|
|
2215
2825
|
intercomTarget: singleResult.intercomTarget,
|
|
2216
2826
|
model: singleResult.model,
|
|
2217
2827
|
attemptedModels: singleResult.attemptedModels,
|
|
2218
2828
|
modelAttempts: singleResult.modelAttempts,
|
|
2829
|
+
totalCost: singleResult.totalCost,
|
|
2219
2830
|
artifactPaths: singleResult.artifactPaths,
|
|
2831
|
+
transcriptPath: singleResult.transcriptPath,
|
|
2832
|
+
transcriptError: singleResult.transcriptError,
|
|
2220
2833
|
structuredOutput: singleResult.structuredOutput,
|
|
2221
2834
|
structuredOutputPath: singleResult.structuredOutputPath,
|
|
2222
2835
|
structuredOutputSchemaPath: singleResult.structuredOutputSchemaPath,
|
|
2223
2836
|
acceptance: singleResult.acceptance,
|
|
2837
|
+
interrupted: singleResult.interrupted,
|
|
2838
|
+
timedOut: timedOut || singleResult.timedOut ? true : undefined,
|
|
2839
|
+
turnBudget: singleResult.turnBudget,
|
|
2840
|
+
turnBudgetExceeded: singleResult.turnBudgetExceeded,
|
|
2841
|
+
wrapUpRequested: singleResult.wrapUpRequested,
|
|
2842
|
+
toolBudget: singleResult.toolBudget,
|
|
2843
|
+
toolBudgetBlocked: singleResult.toolBudgetBlocked,
|
|
2224
2844
|
});
|
|
2225
2845
|
if (seqStep.outputName) {
|
|
2226
2846
|
outputs[seqStep.outputName] = outputEntryFromAsyncResult({
|
|
@@ -2253,15 +2873,30 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2253
2873
|
}
|
|
2254
2874
|
|
|
2255
2875
|
const stepEndTime = Date.now();
|
|
2256
|
-
|
|
2876
|
+
const childInterrupted = singleResult.interrupted === true;
|
|
2877
|
+
statusPayload.steps[flatIndex].status = timedOut ? "failed" : childInterrupted ? "paused" : singleResult.exitCode === 0 ? "complete" : "failed";
|
|
2257
2878
|
statusPayload.steps[flatIndex].endedAt = stepEndTime;
|
|
2258
2879
|
statusPayload.steps[flatIndex].durationMs = stepEndTime - stepStartTime;
|
|
2259
|
-
statusPayload.steps[flatIndex].exitCode = singleResult.exitCode;
|
|
2880
|
+
statusPayload.steps[flatIndex].exitCode = timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode;
|
|
2881
|
+
statusPayload.steps[flatIndex].timedOut = timedOut || singleResult.timedOut ? true : undefined;
|
|
2882
|
+
statusPayload.steps[flatIndex].turnBudget = singleResult.turnBudget;
|
|
2883
|
+
statusPayload.steps[flatIndex].turnBudgetExceeded = singleResult.turnBudgetExceeded;
|
|
2884
|
+
statusPayload.steps[flatIndex].wrapUpRequested = singleResult.wrapUpRequested;
|
|
2885
|
+
statusPayload.steps[flatIndex].toolBudget = singleResult.toolBudget;
|
|
2886
|
+
statusPayload.steps[flatIndex].toolBudgetBlocked = singleResult.toolBudgetBlocked;
|
|
2887
|
+
if (singleResult.toolBudget) statusPayload.toolBudget = singleResult.toolBudget;
|
|
2888
|
+
if (singleResult.toolBudgetBlocked) statusPayload.toolBudgetBlocked = true;
|
|
2889
|
+
if (singleResult.turnBudget) statusPayload.turnBudget = singleResult.turnBudget;
|
|
2890
|
+
if (singleResult.turnBudgetExceeded) statusPayload.turnBudgetExceeded = true;
|
|
2891
|
+
if (singleResult.wrapUpRequested) statusPayload.wrapUpRequested = true;
|
|
2260
2892
|
statusPayload.steps[flatIndex].model = singleResult.model;
|
|
2261
2893
|
statusPayload.steps[flatIndex].thinking = resolveEffectiveThinking(singleResult.model, statusPayload.steps[flatIndex].thinking);
|
|
2262
2894
|
statusPayload.steps[flatIndex].attemptedModels = singleResult.attemptedModels;
|
|
2263
2895
|
statusPayload.steps[flatIndex].modelAttempts = singleResult.modelAttempts;
|
|
2264
|
-
statusPayload.steps[flatIndex].
|
|
2896
|
+
statusPayload.steps[flatIndex].totalCost = singleResult.totalCost;
|
|
2897
|
+
statusPayload.steps[flatIndex].error = timedOut ? (timeoutMessage ?? "Subagent timed out.") : singleResult.error;
|
|
2898
|
+
statusPayload.steps[flatIndex].transcriptPath = singleResult.transcriptPath ?? statusPayload.steps[flatIndex].transcriptPath;
|
|
2899
|
+
statusPayload.steps[flatIndex].transcriptError = singleResult.transcriptError;
|
|
2265
2900
|
statusPayload.steps[flatIndex].structuredOutput = singleResult.structuredOutput;
|
|
2266
2901
|
statusPayload.steps[flatIndex].structuredOutputPath = singleResult.structuredOutputPath;
|
|
2267
2902
|
statusPayload.steps[flatIndex].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
@@ -2274,12 +2909,12 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2274
2909
|
writeStatusPayload();
|
|
2275
2910
|
|
|
2276
2911
|
appendJsonl(eventsPath, JSON.stringify({
|
|
2277
|
-
type: singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
2912
|
+
type: timedOut ? "subagent.step.failed" : childInterrupted ? "subagent.step.paused" : singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
2278
2913
|
ts: stepEndTime,
|
|
2279
2914
|
runId: id,
|
|
2280
2915
|
stepIndex: flatIndex,
|
|
2281
2916
|
agent: seqStep.agent,
|
|
2282
|
-
exitCode: singleResult.exitCode,
|
|
2917
|
+
exitCode: timedOut ? 1 : childInterrupted ? 0 : singleResult.exitCode,
|
|
2283
2918
|
durationMs: stepEndTime - stepStartTime,
|
|
2284
2919
|
tokens: stepTokens,
|
|
2285
2920
|
}));
|
|
@@ -2318,6 +2953,12 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2318
2953
|
}
|
|
2319
2954
|
|
|
2320
2955
|
const resultMode = config.resultMode ?? statusPayload.mode;
|
|
2956
|
+
const totalCost = results.reduce<CostSummary>((sum, result) => ({
|
|
2957
|
+
inputTokens: sum.inputTokens + (result.totalCost?.inputTokens ?? 0),
|
|
2958
|
+
outputTokens: sum.outputTokens + (result.totalCost?.outputTokens ?? 0),
|
|
2959
|
+
costUsd: sum.costUsd + (result.totalCost?.costUsd ?? 0),
|
|
2960
|
+
}), { inputTokens: 0, outputTokens: 0, costUsd: 0 });
|
|
2961
|
+
const finalTotalCost = totalCost.inputTokens > 0 || totalCost.outputTokens > 0 || totalCost.costUsd > 0 ? totalCost : undefined;
|
|
2321
2962
|
const finalFlatAgents = statusPayload.steps.map((step) => step.agent);
|
|
2322
2963
|
const agentName = finalFlatAgents.length === 1
|
|
2323
2964
|
? finalFlatAgents[0]!
|
|
@@ -2358,14 +2999,27 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2358
2999
|
clearInterval(activityTimer);
|
|
2359
3000
|
activityTimer = undefined;
|
|
2360
3001
|
}
|
|
3002
|
+
if (timeoutTimer) {
|
|
3003
|
+
clearTimeout(timeoutTimer);
|
|
3004
|
+
timeoutTimer = undefined;
|
|
3005
|
+
}
|
|
2361
3006
|
disposeControlInbox();
|
|
2362
3007
|
const effectiveSessionFile = sessionFile ?? latestSessionFile;
|
|
2363
3008
|
const runEndedAt = Date.now();
|
|
2364
|
-
statusPayload.state = interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed";
|
|
3009
|
+
statusPayload.state = timedOut || turnBudgetExceeded ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed";
|
|
2365
3010
|
statusPayload.activityState = undefined;
|
|
3011
|
+
if (timedOut) {
|
|
3012
|
+
statusPayload.timedOut = true;
|
|
3013
|
+
statusPayload.error = timeoutMessage ?? "Subagent timed out.";
|
|
3014
|
+
}
|
|
3015
|
+
if (turnBudgetExceeded && !statusPayload.error) {
|
|
3016
|
+
const budget = statusPayload.turnBudget;
|
|
3017
|
+
statusPayload.error = budget ? turnBudgetExceededMessage(budget, budget.turnCount) : "Subagent exceeded turn budget.";
|
|
3018
|
+
}
|
|
2366
3019
|
statusPayload.endedAt = runEndedAt;
|
|
2367
3020
|
statusPayload.lastUpdate = runEndedAt;
|
|
2368
3021
|
statusPayload.sessionFile = effectiveSessionFile;
|
|
3022
|
+
statusPayload.totalCost = finalTotalCost;
|
|
2369
3023
|
statusPayload.shareUrl = shareUrl;
|
|
2370
3024
|
statusPayload.gistUrl = gistUrl;
|
|
2371
3025
|
statusPayload.shareError = shareError;
|
|
@@ -2380,10 +3034,13 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2380
3034
|
eventsPath,
|
|
2381
3035
|
JSON.stringify({
|
|
2382
3036
|
type: "subagent.run.completed",
|
|
3037
|
+
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
2383
3038
|
ts: runEndedAt,
|
|
2384
3039
|
runId: id,
|
|
2385
3040
|
status: statusPayload.state,
|
|
2386
3041
|
durationMs: runEndedAt - overallStartTime,
|
|
3042
|
+
totalTokens: statusPayload.totalTokens,
|
|
3043
|
+
totalCost: finalTotalCost,
|
|
2387
3044
|
}),
|
|
2388
3045
|
);
|
|
2389
3046
|
writeRunLog(logPath, {
|
|
@@ -2407,25 +3064,44 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2407
3064
|
|
|
2408
3065
|
try {
|
|
2409
3066
|
writeAtomicJson(resultPath, {
|
|
3067
|
+
lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
|
|
2410
3068
|
id,
|
|
2411
3069
|
agent: agentName,
|
|
2412
3070
|
mode: resultMode,
|
|
2413
|
-
success: !interrupted && results.every((r) => r.success),
|
|
2414
|
-
state: interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed",
|
|
2415
|
-
summary: interrupted ? "Paused after interrupt. Waiting for explicit next action." : summary,
|
|
3071
|
+
success: !timedOut && !turnBudgetExceeded && !interrupted && results.every((r) => r.success),
|
|
3072
|
+
state: timedOut || turnBudgetExceeded ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed",
|
|
3073
|
+
summary: timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? (statusPayload.error ?? "Subagent exceeded turn budget.") : interrupted ? "Paused after interrupt. Waiting for explicit next action." : summary,
|
|
3074
|
+
...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),
|
|
3075
|
+
...(config.deadlineAt !== undefined ? { deadlineAt: config.deadlineAt } : {}),
|
|
3076
|
+
...(statusPayload.turnBudget ? { turnBudget: statusPayload.turnBudget } : {}),
|
|
3077
|
+
...(statusPayload.turnBudgetExceeded ? { turnBudgetExceeded: true } : {}),
|
|
3078
|
+
...(statusPayload.wrapUpRequested ? { wrapUpRequested: true } : {}),
|
|
3079
|
+
...(statusPayload.toolBudget ? { toolBudget: statusPayload.toolBudget } : {}),
|
|
3080
|
+
...(statusPayload.toolBudgetBlocked ? { toolBudgetBlocked: true } : {}),
|
|
3081
|
+
...(timedOut ? { timedOut: true, error: timeoutMessage ?? "Subagent timed out." } : turnBudgetExceeded ? { error: statusPayload.error ?? "Subagent exceeded turn budget." } : {}),
|
|
2416
3082
|
results: results.map((r) => ({
|
|
2417
3083
|
agent: r.agent,
|
|
2418
3084
|
output: r.output,
|
|
2419
3085
|
error: r.error,
|
|
2420
3086
|
success: r.success,
|
|
2421
3087
|
skipped: r.skipped || undefined,
|
|
3088
|
+
interrupted: r.interrupted || undefined,
|
|
3089
|
+
timedOut: r.timedOut || undefined,
|
|
3090
|
+
turnBudget: r.turnBudget,
|
|
3091
|
+
turnBudgetExceeded: r.turnBudgetExceeded || undefined,
|
|
3092
|
+
wrapUpRequested: r.wrapUpRequested || undefined,
|
|
3093
|
+
toolBudget: r.toolBudget,
|
|
3094
|
+
toolBudgetBlocked: r.toolBudgetBlocked || undefined,
|
|
2422
3095
|
sessionFile: r.sessionFile,
|
|
2423
3096
|
intercomTarget: r.intercomTarget,
|
|
2424
3097
|
model: r.model,
|
|
2425
3098
|
attemptedModels: r.attemptedModels,
|
|
2426
3099
|
modelAttempts: r.modelAttempts,
|
|
3100
|
+
totalCost: r.totalCost,
|
|
2427
3101
|
artifactPaths: r.artifactPaths,
|
|
2428
3102
|
truncated: r.truncated,
|
|
3103
|
+
transcriptPath: r.transcriptPath,
|
|
3104
|
+
transcriptError: r.transcriptError,
|
|
2429
3105
|
structuredOutput: r.structuredOutput,
|
|
2430
3106
|
structuredOutputPath: r.structuredOutputPath,
|
|
2431
3107
|
structuredOutputSchemaPath: r.structuredOutputSchemaPath,
|
|
@@ -2433,9 +3109,11 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2433
3109
|
})),
|
|
2434
3110
|
outputs,
|
|
2435
3111
|
workflowGraph: statusPayload.workflowGraph,
|
|
2436
|
-
exitCode: interrupted || results.every((r) => r.success) ? 0 : 1,
|
|
3112
|
+
exitCode: timedOut || turnBudgetExceeded ? 1 : interrupted || results.every((r) => r.success) ? 0 : 1,
|
|
2437
3113
|
timestamp: runEndedAt,
|
|
2438
3114
|
durationMs: runEndedAt - overallStartTime,
|
|
3115
|
+
totalTokens: statusPayload.totalTokens,
|
|
3116
|
+
totalCost: finalTotalCost,
|
|
2439
3117
|
truncated,
|
|
2440
3118
|
artifactsDir,
|
|
2441
3119
|
cwd,
|
|
@@ -2454,49 +3132,40 @@ export async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2454
3132
|
}
|
|
2455
3133
|
}
|
|
2456
3134
|
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
const config = JSON.parse(configJson) as SubagentRunConfig;
|
|
3135
|
+
const configArg = process.argv[2];
|
|
3136
|
+
if (configArg) {
|
|
2460
3137
|
try {
|
|
2461
|
-
fs.
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
return import.meta.url === pathToFileURL(process.argv[1] ?? "").href;
|
|
2471
|
-
} catch {
|
|
2472
|
-
return false;
|
|
2473
|
-
}
|
|
2474
|
-
}
|
|
2475
|
-
|
|
2476
|
-
if (!runnerConfigPath && isRunnerMainModule()) {
|
|
2477
|
-
const configArg = process.argv[2];
|
|
2478
|
-
if (configArg) {
|
|
2479
|
-
runSubagentFromConfigFile(configArg).catch((err) => {
|
|
2480
|
-
console.error("Subagent runner error:", err);
|
|
3138
|
+
const configJson = fs.readFileSync(configArg, "utf-8");
|
|
3139
|
+
const config = JSON.parse(configJson) as SubagentRunConfig;
|
|
3140
|
+
try {
|
|
3141
|
+
fs.unlinkSync(configArg);
|
|
3142
|
+
} catch {
|
|
3143
|
+
// Temp config cleanup is best effort.
|
|
3144
|
+
}
|
|
3145
|
+
runSubagent(config).catch((runErr) => {
|
|
3146
|
+
console.error("Subagent runner error:", runErr);
|
|
2481
3147
|
process.exit(1);
|
|
2482
3148
|
});
|
|
2483
|
-
}
|
|
2484
|
-
|
|
2485
|
-
process.
|
|
2486
|
-
process.stdin.on("data", (chunk) => {
|
|
2487
|
-
input += chunk;
|
|
2488
|
-
});
|
|
2489
|
-
process.stdin.on("end", () => {
|
|
2490
|
-
try {
|
|
2491
|
-
const config = JSON.parse(input) as SubagentRunConfig;
|
|
2492
|
-
runSubagent(config).catch((err) => {
|
|
2493
|
-
console.error("Subagent runner error:", err);
|
|
2494
|
-
process.exit(1);
|
|
2495
|
-
});
|
|
2496
|
-
} catch (err) {
|
|
2497
|
-
console.error("Subagent runner error:", err);
|
|
2498
|
-
process.exit(1);
|
|
2499
|
-
}
|
|
2500
|
-
});
|
|
3149
|
+
} catch (err) {
|
|
3150
|
+
console.error("Subagent runner error:", err);
|
|
3151
|
+
process.exit(1);
|
|
2501
3152
|
}
|
|
3153
|
+
} else {
|
|
3154
|
+
let input = "";
|
|
3155
|
+
process.stdin.setEncoding("utf-8");
|
|
3156
|
+
process.stdin.on("data", (chunk) => {
|
|
3157
|
+
input += chunk;
|
|
3158
|
+
});
|
|
3159
|
+
process.stdin.on("end", () => {
|
|
3160
|
+
try {
|
|
3161
|
+
const config = JSON.parse(input) as SubagentRunConfig;
|
|
3162
|
+
runSubagent(config).catch((runErr) => {
|
|
3163
|
+
console.error("Subagent runner error:", runErr);
|
|
3164
|
+
process.exit(1);
|
|
3165
|
+
});
|
|
3166
|
+
} catch (err) {
|
|
3167
|
+
console.error("Subagent runner error:", err);
|
|
3168
|
+
process.exit(1);
|
|
3169
|
+
}
|
|
3170
|
+
});
|
|
2502
3171
|
}
|