@duckmind/dm-windows-x64 0.61.5 → 0.62.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/dm.exe +0 -0
- package/extensions/.dm-extensions.json +215 -71
- package/extensions/dm-cua/bin/browser-cua.mjs +6 -1
- package/extensions/dm-cua/index.js +3 -2
- package/extensions/dm-cua/src/browser-cua-lib.mjs +69 -13
- package/extensions/dm-subagents/agents/claude-code-writer.md +15 -0
- package/extensions/dm-subagents/agents/claude-code.md +15 -0
- package/extensions/dm-subagents/agents/codex-exec-writer.md +15 -0
- package/extensions/dm-subagents/agents/codex-exec.md +15 -0
- package/extensions/dm-subagents/agents/cursor-agent-writer.md +14 -0
- package/extensions/dm-subagents/agents/cursor-agent.md +14 -0
- package/extensions/dm-subagents/agents/delegate.md +3 -2
- package/extensions/dm-subagents/agents/oracle.md +10 -5
- package/extensions/dm-subagents/agents/researcher.md +2 -2
- package/extensions/dm-subagents/agents/reviewer.md +17 -7
- package/extensions/dm-subagents/agents/scout.md +5 -5
- package/extensions/dm-subagents/agents/worker.md +6 -2
- package/extensions/dm-subagents/async-retention-discovery-worker.mjs +167 -0
- package/extensions/dm-subagents/index.js +4 -0
- package/extensions/dm-subagents/inspector-runner.mjs +10 -0
- package/extensions/dm-subagents/install.mjs +3 -2
- package/extensions/dm-subagents/package.json +2 -2
- package/extensions/dm-subagents/prompts/council.md +60 -0
- package/extensions/dm-subagents/prompts/parallel-review.md +5 -1
- package/extensions/dm-subagents/prompts/review-loop.md +13 -7
- package/extensions/dm-subagents/skills/council-mode/SKILL.md +59 -0
- package/extensions/dm-subagents/skills/council-mode/references/pass-contracts.md +150 -0
- package/extensions/dm-subagents/skills/dm-subagents/SKILL.md +96 -911
- package/extensions/dm-subagents/skills/dm-subagents/references/constraints-and-recipes.md +70 -0
- package/extensions/dm-subagents/skills/dm-subagents/references/execution-controls.md +539 -0
- package/extensions/dm-subagents/skills/dm-subagents/references/management-authoring-rpc.md +161 -0
- package/extensions/dm-subagents/skills/dm-subagents/references/multi-lane-orchestration.md +51 -0
- package/extensions/dm-subagents/skills/dm-subagents/references/prompting-and-roles.md +295 -0
- package/extensions/dm-subagents/skills/dm-subagents/references/review-and-validation.md +73 -0
- package/extensions/dm-subagents/src/agents/agent-management.js +716 -484
- package/extensions/dm-subagents/src/agents/agent-refinements.js +563 -0
- package/extensions/dm-subagents/src/agents/agent-serializer.js +70 -5
- package/extensions/dm-subagents/src/agents/agents.js +1447 -299
- package/extensions/dm-subagents/src/agents/builtin-names.js +15 -0
- package/extensions/dm-subagents/src/agents/chain-serializer.js +12 -7
- package/extensions/dm-subagents/src/agents/frontmatter.js +64 -14
- package/extensions/dm-subagents/src/agents/identity.js +1 -1
- package/extensions/dm-subagents/src/agents/proactive-skills.js +14 -11
- package/extensions/dm-subagents/src/agents/runtime-agent-events.js +49 -0
- package/extensions/dm-subagents/src/agents/runtime-agent-registry.js +412 -0
- package/extensions/dm-subagents/src/agents/skills.js +52 -35
- package/extensions/dm-subagents/src/api/agents.js +6 -0
- package/extensions/dm-subagents/src/api/background-work.js +151 -0
- package/extensions/dm-subagents/src/api/capability-ceiling.js +12 -0
- package/extensions/dm-subagents/src/api/control-channel.js +3 -0
- package/extensions/dm-subagents/src/api/delegation.js +5 -0
- package/extensions/dm-subagents/src/api/dm-args.js +3 -0
- package/extensions/dm-subagents/src/api/external-job-provider.js +137 -0
- package/extensions/dm-subagents/src/api/external-runs.js +233 -0
- package/extensions/dm-subagents/src/api/intercom-bridge.js +3 -0
- package/extensions/dm-subagents/src/api/preflight.js +322 -0
- package/extensions/dm-subagents/src/api/project-panes.js +11 -0
- package/extensions/dm-subagents/src/api/shared-types.js +4 -0
- package/extensions/dm-subagents/src/extension/config.js +175 -0
- package/extensions/dm-subagents/src/extension/control-notices.js +4 -43
- package/extensions/dm-subagents/src/extension/doctor.js +71 -17
- package/extensions/dm-subagents/src/extension/fanout-child.js +49 -32
- package/extensions/dm-subagents/src/extension/index.js +711 -265
- package/extensions/dm-subagents/src/extension/public-execution.js +114 -0
- package/extensions/dm-subagents/src/extension/rpc.js +427 -22
- package/extensions/dm-subagents/src/extension/schemas.js +158 -65
- package/extensions/dm-subagents/src/extension/steering-notices.js +23 -0
- package/extensions/dm-subagents/src/extension/subagent-guide.js +31 -0
- package/extensions/dm-subagents/src/extension/tool-description.js +92 -74
- package/extensions/dm-subagents/src/extension/tool-result.js +7 -0
- package/extensions/dm-subagents/src/inspectors/herdr/actions.js +218 -0
- package/extensions/dm-subagents/src/inspectors/herdr/client.js +123 -0
- package/extensions/dm-subagents/src/inspectors/herdr/focus.js +47 -0
- package/extensions/dm-subagents/src/inspectors/herdr/inspector-runner.js +160 -0
- package/extensions/dm-subagents/src/inspectors/herdr/project-panes.js +618 -0
- package/extensions/dm-subagents/src/inspectors/herdr/session-roots-codec.js +21 -0
- package/extensions/dm-subagents/src/inspectors/herdr/shell-command.js +15 -0
- package/extensions/dm-subagents/src/integrations/herdr-status.js +377 -0
- package/extensions/dm-subagents/src/intercom/intercom-bridge.js +17 -13
- package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +371 -79
- package/extensions/dm-subagents/src/intercom/result-intercom.js +47 -7
- package/extensions/dm-subagents/src/missions/actions.js +394 -0
- package/extensions/dm-subagents/src/missions/goal-driver.js +149 -0
- package/extensions/dm-subagents/src/missions/lifecycle.js +331 -0
- package/extensions/dm-subagents/src/missions/store.js +548 -0
- package/extensions/dm-subagents/src/missions/types.js +9 -0
- package/extensions/dm-subagents/src/missions/workflow-state.js +245 -0
- package/extensions/dm-subagents/src/policy/authority.js +37 -0
- package/extensions/dm-subagents/src/profiles/profiles.js +36 -18
- package/extensions/dm-subagents/src/runs/background/active-async-capacity.js +427 -0
- package/extensions/dm-subagents/src/runs/background/active-run-index.js +122 -0
- package/extensions/dm-subagents/src/runs/background/async-execution.js +925 -137
- package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +515 -148
- package/extensions/dm-subagents/src/runs/background/async-resume.js +378 -51
- package/extensions/dm-subagents/src/runs/background/async-retention.js +828 -0
- package/extensions/dm-subagents/src/runs/background/async-status-snapshot.js +31 -0
- package/extensions/dm-subagents/src/runs/background/async-status.js +259 -22
- package/extensions/dm-subagents/src/runs/background/auto-drain.js +46 -0
- package/extensions/dm-subagents/src/runs/background/chain-append.js +50 -15
- package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +67 -12
- package/extensions/dm-subagents/src/runs/background/completion-batcher.js +5 -1
- package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +3 -11
- package/extensions/dm-subagents/src/runs/background/completion-replay.js +245 -0
- package/extensions/dm-subagents/src/runs/background/control-channel.js +423 -36
- package/extensions/dm-subagents/src/runs/background/fleet-view.js +132 -59
- package/extensions/dm-subagents/src/runs/background/index-segment.js +38 -0
- package/extensions/dm-subagents/src/runs/background/inspect-rpc.js +373 -0
- package/extensions/dm-subagents/src/runs/background/notify.js +357 -57
- package/extensions/dm-subagents/src/runs/background/owned-process-tree.js +86 -0
- package/extensions/dm-subagents/src/runs/background/process-terminal.js +269 -0
- package/extensions/dm-subagents/src/runs/background/result-delivery-ownership.js +34 -0
- package/extensions/dm-subagents/src/runs/background/result-files.js +469 -0
- package/extensions/dm-subagents/src/runs/background/result-watcher.js +540 -75
- package/extensions/dm-subagents/src/runs/background/resume-guidance.js +44 -0
- package/extensions/dm-subagents/src/runs/background/retained-children.js +119 -0
- package/extensions/dm-subagents/src/runs/background/run-id-query.js +5 -0
- package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +93 -9
- package/extensions/dm-subagents/src/runs/background/run-status.js +303 -32
- package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +784 -376
- package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +75 -34
- package/extensions/dm-subagents/src/runs/background/steering.js +221 -0
- package/extensions/dm-subagents/src/runs/background/subagent-runner.js +3106 -844
- package/extensions/dm-subagents/src/runs/background/subagent-wait.js +529 -0
- package/extensions/dm-subagents/src/runs/background/terminal-run-index.js +106 -0
- package/extensions/dm-subagents/src/runs/background/top-level-async.js +1 -1
- package/extensions/dm-subagents/src/runs/background/wait-completions.js +155 -0
- package/extensions/dm-subagents/src/runs/background/wait-config.js +46 -0
- package/extensions/dm-subagents/src/runs/background/wait-subscriptions.js +278 -0
- package/extensions/dm-subagents/src/runs/background/wait-tool.js +47 -0
- package/extensions/dm-subagents/src/runs/foreground/async-dismiss-action.js +81 -0
- package/extensions/dm-subagents/src/runs/foreground/async-steering-action.js +245 -0
- package/extensions/dm-subagents/src/runs/foreground/async-stop-action.js +74 -0
- package/extensions/dm-subagents/src/runs/foreground/execution.js +1401 -342
- package/extensions/dm-subagents/src/runs/foreground/foreground-control.js +133 -0
- package/extensions/dm-subagents/src/runs/foreground/foreground-history.js +148 -0
- package/extensions/dm-subagents/src/runs/foreground/prompt-audit.js +139 -0
- package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +4278 -1441
- package/extensions/dm-subagents/src/runs/foreground/workflow-detach-reconcile.js +278 -0
- package/extensions/dm-subagents/src/runs/foreground/workflow-foreground-steering.js +155 -0
- package/extensions/dm-subagents/src/runs/shared/abort-recovery.js +97 -0
- package/extensions/dm-subagents/src/runs/shared/acceptance.js +597 -148
- package/extensions/dm-subagents/src/runs/shared/agent-contract.js +35 -0
- package/extensions/dm-subagents/src/runs/shared/async-status-projection.js +472 -0
- package/extensions/dm-subagents/src/runs/shared/background-process-options.js +6 -0
- package/extensions/dm-subagents/src/runs/shared/capability-ceiling.js +175 -0
- package/extensions/dm-subagents/src/runs/shared/child-identity.js +32 -0
- package/extensions/dm-subagents/src/runs/shared/child-launch-plan.js +65 -0
- package/extensions/dm-subagents/src/runs/shared/child-protocol.js +447 -0
- package/extensions/dm-subagents/src/runs/shared/claude-code-adapter.js +120 -0
- package/extensions/dm-subagents/src/runs/shared/codex-exec-adapter.js +129 -0
- package/extensions/dm-subagents/src/runs/shared/completion-evidence.js +40 -0
- package/extensions/dm-subagents/src/runs/shared/completion-guard.js +140 -83
- package/extensions/dm-subagents/src/runs/shared/context-mode.js +38 -0
- package/extensions/dm-subagents/src/runs/shared/cursor-agent-adapter.js +101 -0
- package/extensions/dm-subagents/src/runs/shared/dm-args.js +445 -72
- package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +27 -16
- package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +19 -6
- package/extensions/dm-subagents/src/runs/shared/extension-bindings.js +81 -0
- package/extensions/dm-subagents/src/runs/shared/external-cli-contract.js +134 -0
- package/extensions/dm-subagents/src/runs/shared/external-cli-preflight.js +98 -0
- package/extensions/dm-subagents/src/runs/shared/external-cli-runner.js +419 -0
- package/extensions/dm-subagents/src/runs/shared/external-job-bridge.js +404 -0
- package/extensions/dm-subagents/src/runs/shared/external-job-runner.js +334 -0
- package/extensions/dm-subagents/src/runs/shared/fast-mode-extension.js +8 -0
- package/extensions/dm-subagents/src/runs/shared/host-step-status.js +228 -0
- package/extensions/dm-subagents/src/runs/shared/lane-metadata.js +104 -0
- package/extensions/dm-subagents/src/runs/shared/launch-cwd.js +17 -0
- package/extensions/dm-subagents/src/runs/shared/llm-intent-arbiter.js +190 -0
- package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +48 -3
- package/extensions/dm-subagents/src/runs/shared/mcp-config-sources.js +387 -0
- package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +212 -137
- package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-grant.js +131 -0
- package/extensions/dm-subagents/src/runs/shared/model-exclusions.js +207 -0
- package/extensions/dm-subagents/src/runs/shared/model-fallback.js +225 -55
- package/extensions/dm-subagents/src/runs/shared/model-scope.js +85 -28
- package/extensions/dm-subagents/src/runs/shared/mutation-evidence.js +182 -0
- package/extensions/dm-subagents/src/runs/shared/nested-events.js +264 -102
- package/extensions/dm-subagents/src/runs/shared/nested-render.js +15 -5
- package/extensions/dm-subagents/src/runs/shared/orca-progress-tabs.js +505 -0
- package/extensions/dm-subagents/src/runs/shared/parallel-handoff.js +653 -0
- package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +29 -12
- package/extensions/dm-subagents/src/runs/shared/permissions.js +108 -0
- package/extensions/dm-subagents/src/runs/shared/process-signal.js +13 -0
- package/extensions/dm-subagents/src/runs/shared/run-fanout-budget.js +257 -0
- package/extensions/dm-subagents/src/runs/shared/run-history.js +133 -13
- package/extensions/dm-subagents/src/runs/shared/runtime-acknowledged-extensions.js +62 -0
- package/extensions/dm-subagents/src/runs/shared/session-lease.js +225 -0
- package/extensions/dm-subagents/src/runs/shared/single-output.js +129 -27
- package/extensions/dm-subagents/src/runs/shared/spawn-budget.js +95 -0
- package/extensions/dm-subagents/src/runs/shared/structured-output.js +129 -10
- package/extensions/dm-subagents/src/runs/shared/subagent-control.js +66 -11
- package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +548 -70
- package/extensions/dm-subagents/src/runs/shared/subagent-startup-retry.js +50 -0
- package/extensions/dm-subagents/src/runs/shared/task-intent.js +130 -0
- package/extensions/dm-subagents/src/runs/shared/tool-availability.js +59 -0
- package/extensions/dm-subagents/src/runs/shared/tool-budget.js +7 -5
- package/extensions/dm-subagents/src/runs/shared/tool-timeout.js +64 -0
- package/extensions/dm-subagents/src/runs/shared/usage-budget.js +74 -0
- package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +22 -0
- package/extensions/dm-subagents/src/runs/shared/worktree-cleanup-plan.js +721 -0
- package/extensions/dm-subagents/src/runs/shared/worktree.js +168 -19
- package/extensions/dm-subagents/src/shared/accessible-dir.js +35 -0
- package/extensions/dm-subagents/src/shared/agent-stream-options.js +3 -0
- package/extensions/dm-subagents/src/shared/artifacts.js +170 -11
- package/extensions/dm-subagents/src/shared/atomic-json.js +36 -38
- package/extensions/dm-subagents/src/shared/capacity-resilient-json.js +77 -0
- package/extensions/dm-subagents/src/shared/child-session-name.js +15 -0
- package/extensions/dm-subagents/src/shared/child-transcript.js +57 -2
- package/extensions/dm-subagents/src/shared/completion-owner.js +7 -0
- package/extensions/dm-subagents/src/shared/display-text.js +142 -0
- package/extensions/dm-subagents/src/shared/extension-context.js +17 -0
- package/extensions/dm-subagents/src/shared/file-coalescer.js +9 -0
- package/extensions/dm-subagents/src/shared/file-system-retry.js +56 -0
- package/extensions/dm-subagents/src/shared/fork-context.js +96 -33
- package/extensions/dm-subagents/src/shared/formatters.js +21 -7
- package/extensions/dm-subagents/src/shared/launch-contract.js +94 -0
- package/extensions/dm-subagents/src/shared/model-info.js +10 -5
- package/extensions/dm-subagents/src/shared/node-executable.js +19 -0
- package/extensions/dm-subagents/src/shared/prompt-resources.js +10 -0
- package/extensions/dm-subagents/src/shared/pruned-fork.js +427 -0
- package/extensions/dm-subagents/src/shared/session-file-trust.js +19 -0
- package/extensions/dm-subagents/src/shared/session-tokens.js +14 -3
- package/extensions/dm-subagents/src/shared/settings.js +39 -47
- package/extensions/dm-subagents/src/shared/shortcuts.js +16 -0
- package/extensions/dm-subagents/src/shared/status-format.js +9 -2
- package/extensions/dm-subagents/src/shared/thinking-ceiling.js +41 -0
- package/extensions/dm-subagents/src/shared/types.js +47 -7
- package/extensions/dm-subagents/src/shared/utf8.js +12 -0
- package/extensions/dm-subagents/src/shared/utils.js +151 -131
- package/extensions/dm-subagents/src/shared/watch-strategy.js +3 -0
- package/extensions/dm-subagents/src/shared/workflow-child-permit.js +84 -0
- package/extensions/dm-subagents/src/slash/delegation-adapters.js +274 -0
- package/extensions/dm-subagents/src/slash/delegation-json.js +113 -0
- package/extensions/dm-subagents/src/slash/delegation-request.js +152 -0
- package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +294 -243
- package/extensions/dm-subagents/src/slash/prompt-workflows.js +35 -73
- package/extensions/dm-subagents/src/slash/selector.js +101 -0
- package/extensions/dm-subagents/src/slash/slash-bridge.js +17 -1
- package/extensions/dm-subagents/src/slash/slash-commands.js +722 -732
- package/extensions/dm-subagents/src/slash/slash-live-state.js +37 -19
- package/extensions/dm-subagents/src/slash/subagents-admin.js +410 -0
- package/extensions/dm-subagents/src/tui/fleet-status.js +824 -0
- package/extensions/dm-subagents/src/tui/fleet-transcript.js +479 -0
- package/extensions/dm-subagents/src/tui/fleet.js +1326 -0
- package/extensions/dm-subagents/src/tui/render-helpers.js +22 -0
- package/extensions/dm-subagents/src/tui/render.js +1511 -261
- package/extensions/dm-subagents/src/watchdog/change-signature.js +220 -0
- package/extensions/dm-subagents/src/watchdog/child-status.js +151 -0
- package/extensions/dm-subagents/src/watchdog/emission-guard.js +90 -0
- package/extensions/dm-subagents/src/watchdog/lsp-diagnostics.js +484 -0
- package/extensions/dm-subagents/src/watchdog/model-selection.js +154 -0
- package/extensions/dm-subagents/src/watchdog/permission-arbiter.js +138 -0
- package/extensions/dm-subagents/src/watchdog/register-child.js +112 -0
- package/extensions/dm-subagents/src/watchdog/register-main.js +419 -0
- package/extensions/dm-subagents/src/watchdog/render.js +54 -0
- package/extensions/dm-subagents/src/watchdog/review.js +251 -0
- package/extensions/dm-subagents/src/watchdog/runtime.js +803 -0
- package/extensions/dm-subagents/src/watchdog/scope.js +56 -0
- package/extensions/dm-subagents/src/watchdog/settings.js +515 -0
- package/extensions/dm-subagents/src/watchdog/tool-actions.js +151 -0
- package/extensions/dm-subagents/src/watchdog/turn-delta.js +169 -0
- package/extensions/dm-subagents/src/watchdog/types.js +29 -0
- package/extensions/dm-subagents/src/watchdog/warning-format.js +58 -0
- package/extensions/dm-subagents/src/workflows/chat-progress.js +116 -0
- package/extensions/dm-subagents/src/workflows/host-command.js +227 -0
- package/extensions/dm-subagents/src/workflows/scripted-workflow.js +2011 -0
- package/extensions/dm-subagents/src/workflows/workflow-child-summary.js +116 -0
- package/extensions/dm-subagents/src/workflows/workflow-preflight.js +243 -0
- package/extensions/dm-subagents/src/workflows/workflow-receipt.js +387 -0
- package/extensions/dm-subagents/src/workflows/workflow-settlement.js +189 -0
- package/extensions/greedysearch-dm/bin/cdp.mjs +28 -3
- package/extensions/greedysearch-dm/bin/launch.mjs +2 -3
- package/extensions/greedysearch-dm/src/search/constants.mjs +5 -2
- package/package.json +4 -3
- package/extensions/dm-fff/package.json +0 -21
- package/extensions/dm-fff/src/index.js +0 -691
- package/extensions/dm-fff/src/query.js +0 -60
- package/extensions/dm-subagents/agents/context-builder.md +0 -46
- package/extensions/dm-subagents/agents/planner.md +0 -55
- package/extensions/dm-subagents/prompts/parallel-context-build.md +0 -55
- package/extensions/dm-subagents/prompts/parallel-handoff-plan.md +0 -61
- package/extensions/dm-subagents/src/runs/background/wait.js +0 -206
- package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +0 -1013
- package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +0 -981
- package/extensions/dm-subagents/src/runs/shared/turn-budget.js +0 -50
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { createAtomicJsonWriter } from "../../shared/atomic-json.js";
|
|
6
|
+
import { TEMP_ROOT_DIR } from "../../shared/types.js";
|
|
7
|
+
export const SESSION_LEASES_DIR = path.join(TEMP_ROOT_DIR, "session-leases");
|
|
8
|
+
|
|
9
|
+
export class SessionLeaseConflictError extends Error {
|
|
10
|
+
owner;
|
|
11
|
+
constructor(message, owner) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "SessionLeaseConflictError";
|
|
14
|
+
this.owner = owner;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function getProcessStartIdentity(pid) {
|
|
18
|
+
if (process.platform === "linux") {
|
|
19
|
+
try {
|
|
20
|
+
const stat = fs.readFileSync(`/proc/${pid}/stat`, "utf-8");
|
|
21
|
+
const commandEnd = stat.lastIndexOf(")");
|
|
22
|
+
if (commandEnd === -1)
|
|
23
|
+
return;
|
|
24
|
+
const fields = stat.slice(commandEnd + 1).trim().split(/\s+/);
|
|
25
|
+
const startTicks = fields[19];
|
|
26
|
+
return startTicks ? `linux:${startTicks}` : undefined;
|
|
27
|
+
} catch {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
function processIsAlive(pid) {
|
|
34
|
+
try {
|
|
35
|
+
process.kill(pid, 0);
|
|
36
|
+
return true;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
const code = error.code;
|
|
39
|
+
if (code === "ESRCH")
|
|
40
|
+
return false;
|
|
41
|
+
if (code === "EPERM")
|
|
42
|
+
return true;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function canonicalSessionFilePath(sessionFile) {
|
|
47
|
+
return fs.realpathSync.native(path.resolve(sessionFile));
|
|
48
|
+
}
|
|
49
|
+
export function canonicalSessionId(sessionFile) {
|
|
50
|
+
const canonical = canonicalSessionFilePath(sessionFile);
|
|
51
|
+
const key = process.platform === "win32" ? canonical.toLowerCase() : canonical;
|
|
52
|
+
return createHash("sha256").update(key).digest("hex");
|
|
53
|
+
}
|
|
54
|
+
export function sessionLeaseDir(sessionFile, rootDir = SESSION_LEASES_DIR) {
|
|
55
|
+
return path.join(rootDir, canonicalSessionId(sessionFile));
|
|
56
|
+
}
|
|
57
|
+
export function inspectSessionLease(sessionFile, rootDir = SESSION_LEASES_DIR) {
|
|
58
|
+
const canonicalSessionFile = canonicalSessionFilePath(sessionFile);
|
|
59
|
+
const canonicalSessionIdValue = canonicalSessionId(canonicalSessionFile);
|
|
60
|
+
const leaseDir = path.join(rootDir, canonicalSessionIdValue);
|
|
61
|
+
if (!fs.existsSync(leaseDir))
|
|
62
|
+
return { state: "free", canonicalSessionFile, canonicalSessionId: canonicalSessionIdValue };
|
|
63
|
+
const owner = readLeaseOwner(leaseDir);
|
|
64
|
+
return owner ? { state: "owned", canonicalSessionFile, canonicalSessionId: canonicalSessionIdValue, owner } : { state: "unreadable", canonicalSessionFile, canonicalSessionId: canonicalSessionIdValue };
|
|
65
|
+
}
|
|
66
|
+
function parseOwner(value) {
|
|
67
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
68
|
+
return;
|
|
69
|
+
const owner = value;
|
|
70
|
+
if (owner.version !== 1 || typeof owner.token !== "string" || typeof owner.canonicalSessionFile !== "string" || typeof owner.runId !== "string" || typeof owner.sourceRunId !== "string" || typeof owner.pid !== "number" || !Number.isInteger(owner.pid) || owner.pid <= 0 || typeof owner.hostname !== "string" || owner.writerState !== "none" && owner.writerState !== "spawning" && owner.writerState !== "running" || typeof owner.acquiredAt !== "string" || typeof owner.acquiredAtMs !== "number" || typeof owner.updatedAtMs !== "number")
|
|
71
|
+
return;
|
|
72
|
+
if (owner.parentSessionId !== undefined && typeof owner.parentSessionId !== "string")
|
|
73
|
+
return;
|
|
74
|
+
if (owner.processStartIdentity !== undefined && typeof owner.processStartIdentity !== "string")
|
|
75
|
+
return;
|
|
76
|
+
if (owner.writerPid !== undefined && (typeof owner.writerPid !== "number" || !Number.isInteger(owner.writerPid) || owner.writerPid <= 0))
|
|
77
|
+
return;
|
|
78
|
+
if (owner.writerProcessStartIdentity !== undefined && typeof owner.writerProcessStartIdentity !== "string")
|
|
79
|
+
return;
|
|
80
|
+
if (owner.writerState === "running" && owner.writerPid === undefined)
|
|
81
|
+
return;
|
|
82
|
+
if (owner.writerState !== "running" && (owner.writerPid !== undefined || owner.writerProcessStartIdentity !== undefined))
|
|
83
|
+
return;
|
|
84
|
+
return owner;
|
|
85
|
+
}
|
|
86
|
+
function readLeaseOwner(leaseDir) {
|
|
87
|
+
try {
|
|
88
|
+
return parseOwner(JSON.parse(fs.readFileSync(path.join(leaseDir, "owner.json"), "utf-8")));
|
|
89
|
+
} catch {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function conflictMessage(canonicalSessionFile, owner) {
|
|
94
|
+
if (!owner) {
|
|
95
|
+
return `Direct revival of session '${canonicalSessionFile}' is blocked by an existing lease with unreadable owner metadata. Refusing to reclaim it without proof that the owner is stale.`;
|
|
96
|
+
}
|
|
97
|
+
const parent = owner.parentSessionId ? `, parent session '${owner.parentSessionId}'` : "";
|
|
98
|
+
return `Direct revival of session '${canonicalSessionFile}' is already owned by run '${owner.runId}' (source run '${owner.sourceRunId}'${parent}, pid ${owner.pid} on ${owner.hostname}). Wait for that revival to finish or start a separate continuation without reusing this session file.`;
|
|
99
|
+
}
|
|
100
|
+
function processDemonstrablyGone(pid, startIdentity, options) {
|
|
101
|
+
const alive = options.isProcessAlive(pid);
|
|
102
|
+
if (alive === false)
|
|
103
|
+
return true;
|
|
104
|
+
if (alive !== true || !startIdentity)
|
|
105
|
+
return false;
|
|
106
|
+
const currentIdentity = options.getProcessStartIdentity(pid);
|
|
107
|
+
return currentIdentity !== undefined && currentIdentity !== startIdentity;
|
|
108
|
+
}
|
|
109
|
+
function demonstrablyStale(owner, options) {
|
|
110
|
+
if (owner.hostname !== options.hostname)
|
|
111
|
+
return false;
|
|
112
|
+
if (!processDemonstrablyGone(owner.pid, owner.processStartIdentity, options))
|
|
113
|
+
return false;
|
|
114
|
+
if (owner.writerState === "spawning")
|
|
115
|
+
return false;
|
|
116
|
+
if (owner.writerState === "none")
|
|
117
|
+
return true;
|
|
118
|
+
return owner.writerPid !== undefined && processDemonstrablyGone(owner.writerPid, owner.writerProcessStartIdentity, options);
|
|
119
|
+
}
|
|
120
|
+
function createLeaseDirectory(leaseDir, owner) {
|
|
121
|
+
const tempDir = `${leaseDir}.candidate-${owner.token}`;
|
|
122
|
+
fs.mkdirSync(path.dirname(leaseDir), { recursive: true, mode: 448 });
|
|
123
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
124
|
+
fs.mkdirSync(tempDir, { mode: 448 });
|
|
125
|
+
try {
|
|
126
|
+
fs.writeFileSync(path.join(tempDir, "owner.json"), JSON.stringify(owner, null, 2), { encoding: "utf-8", mode: 384 });
|
|
127
|
+
try {
|
|
128
|
+
fs.renameSync(tempDir, leaseDir);
|
|
129
|
+
return true;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (fs.existsSync(leaseDir))
|
|
132
|
+
return false;
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
} finally {
|
|
136
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export function acquireSessionLease(request, options = {}) {
|
|
140
|
+
const canonicalSessionFile = canonicalSessionFilePath(request.sessionFile);
|
|
141
|
+
const rootDir = options.rootDir ?? SESSION_LEASES_DIR;
|
|
142
|
+
const leaseDir = sessionLeaseDir(canonicalSessionFile, rootDir);
|
|
143
|
+
const now = options.now ?? Date.now;
|
|
144
|
+
const pid = options.pid ?? process.pid;
|
|
145
|
+
const hostname = options.hostname ?? os.hostname();
|
|
146
|
+
const getIdentity = options.getProcessStartIdentity ?? getProcessStartIdentity;
|
|
147
|
+
const processStartIdentity = options.processStartIdentity ?? getIdentity(pid) ?? (pid === process.pid ? `runtime:${Math.round(Date.now() - process.uptime() * 1000)}` : undefined);
|
|
148
|
+
const acquiredAtMs = now();
|
|
149
|
+
const owner = {
|
|
150
|
+
version: 1,
|
|
151
|
+
token: options.token?.() ?? randomUUID(),
|
|
152
|
+
canonicalSessionFile,
|
|
153
|
+
runId: request.runId,
|
|
154
|
+
sourceRunId: request.sourceRunId,
|
|
155
|
+
...request.parentSessionId ? { parentSessionId: request.parentSessionId } : {},
|
|
156
|
+
pid,
|
|
157
|
+
hostname,
|
|
158
|
+
...processStartIdentity ? { processStartIdentity } : {},
|
|
159
|
+
writerState: "none",
|
|
160
|
+
acquiredAt: new Date(acquiredAtMs).toISOString(),
|
|
161
|
+
acquiredAtMs,
|
|
162
|
+
updatedAtMs: acquiredAtMs
|
|
163
|
+
};
|
|
164
|
+
const staleOptions = {
|
|
165
|
+
hostname,
|
|
166
|
+
isProcessAlive: options.isProcessAlive ?? processIsAlive,
|
|
167
|
+
getProcessStartIdentity: getIdentity
|
|
168
|
+
};
|
|
169
|
+
for (let attempt = 0;attempt < 4; attempt++) {
|
|
170
|
+
if (createLeaseDirectory(leaseDir, owner)) {
|
|
171
|
+
const writeOwner = createAtomicJsonWriter();
|
|
172
|
+
return {
|
|
173
|
+
leaseDir,
|
|
174
|
+
owner,
|
|
175
|
+
updateWriter(writer) {
|
|
176
|
+
const currentOwner = readLeaseOwner(leaseDir);
|
|
177
|
+
if (!currentOwner || currentOwner.token !== owner.token) {
|
|
178
|
+
throw new Error(`Session revival lease ownership changed for run '${owner.runId}'.`);
|
|
179
|
+
}
|
|
180
|
+
const writerProcessStartIdentity = writer.state === "running" ? getIdentity(writer.pid) : undefined;
|
|
181
|
+
const nextOwner = {
|
|
182
|
+
...owner,
|
|
183
|
+
writerState: writer.state,
|
|
184
|
+
...writer.state === "running" ? { writerPid: writer.pid } : {},
|
|
185
|
+
...writerProcessStartIdentity ? { writerProcessStartIdentity } : {},
|
|
186
|
+
updatedAtMs: now()
|
|
187
|
+
};
|
|
188
|
+
delete nextOwner.writerPid;
|
|
189
|
+
delete nextOwner.writerProcessStartIdentity;
|
|
190
|
+
if (writer.state === "running") {
|
|
191
|
+
nextOwner.writerPid = writer.pid;
|
|
192
|
+
if (writerProcessStartIdentity)
|
|
193
|
+
nextOwner.writerProcessStartIdentity = writerProcessStartIdentity;
|
|
194
|
+
}
|
|
195
|
+
writeOwner(path.join(leaseDir, "owner.json"), nextOwner);
|
|
196
|
+
delete owner.writerPid;
|
|
197
|
+
delete owner.writerProcessStartIdentity;
|
|
198
|
+
Object.assign(owner, nextOwner);
|
|
199
|
+
},
|
|
200
|
+
release() {
|
|
201
|
+
const currentOwner = readLeaseOwner(leaseDir);
|
|
202
|
+
if (!currentOwner || currentOwner.token !== owner.token)
|
|
203
|
+
return false;
|
|
204
|
+
fs.rmSync(leaseDir, { recursive: true, force: true });
|
|
205
|
+
return !fs.existsSync(leaseDir);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const existingOwner = readLeaseOwner(leaseDir);
|
|
210
|
+
if (!existingOwner || !demonstrablyStale(existingOwner, staleOptions)) {
|
|
211
|
+
throw new SessionLeaseConflictError(conflictMessage(canonicalSessionFile, existingOwner), existingOwner);
|
|
212
|
+
}
|
|
213
|
+
const tombstone = `${leaseDir}.stale-${existingOwner.token.replace(/[^A-Za-z0-9._-]/g, "-")}`;
|
|
214
|
+
try {
|
|
215
|
+
fs.renameSync(leaseDir, tombstone);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
const code = error.code;
|
|
218
|
+
if (code === "ENOENT" || fs.existsSync(tombstone))
|
|
219
|
+
continue;
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const existingOwner = readLeaseOwner(leaseDir);
|
|
224
|
+
throw new SessionLeaseConflictError(conflictMessage(canonicalSessionFile, existingOwner), existingOwner);
|
|
225
|
+
}
|
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { hasMutationToolCapability } from "./completion-guard.js";
|
|
4
|
+
function missingFileStatError(error) {
|
|
5
|
+
const code = error?.code;
|
|
6
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
7
|
+
}
|
|
8
|
+
function snapshotFromStatError(error) {
|
|
9
|
+
if (missingFileStatError(error))
|
|
10
|
+
return { exists: false };
|
|
11
|
+
return { exists: false, error: error instanceof Error ? error.message : String(error) };
|
|
12
|
+
}
|
|
13
|
+
export function extractChildWrittenOutput(messages, outputPath, cwd) {
|
|
14
|
+
if (!messages?.length || !outputPath)
|
|
15
|
+
return;
|
|
16
|
+
const resolvedTarget = path.resolve(cwd ?? ".", outputPath);
|
|
17
|
+
const comparableTarget = process.platform === "win32" ? resolvedTarget.toLowerCase() : resolvedTarget;
|
|
18
|
+
const successfulCallIds = new Set;
|
|
19
|
+
for (const message of messages) {
|
|
20
|
+
if (message.role === "toolResult" && message.isError === false && typeof message.toolCallId === "string") {
|
|
21
|
+
successfulCallIds.add(message.toolCallId);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
let content;
|
|
25
|
+
for (const message of messages) {
|
|
26
|
+
if (message.role !== "assistant")
|
|
27
|
+
continue;
|
|
28
|
+
for (const part of message.content) {
|
|
29
|
+
if (part.type !== "toolCall" || part.name !== "write" || !successfulCallIds.has(part.id))
|
|
30
|
+
continue;
|
|
31
|
+
const args = typeof part.arguments === "object" && part.arguments !== null && !Array.isArray(part.arguments) ? part.arguments : {};
|
|
32
|
+
if (typeof args.path !== "string" || typeof args.content !== "string")
|
|
33
|
+
continue;
|
|
34
|
+
const resolvedWritePath = path.resolve(cwd ?? ".", args.path);
|
|
35
|
+
const comparableWritePath = process.platform === "win32" ? resolvedWritePath.toLowerCase() : resolvedWritePath;
|
|
36
|
+
if (comparableWritePath !== comparableTarget)
|
|
37
|
+
continue;
|
|
38
|
+
content = args.content;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return content;
|
|
42
|
+
}
|
|
3
43
|
export function normalizeSingleOutputOverride(output, defaultOutput) {
|
|
4
44
|
if (output === false || output === "false")
|
|
5
45
|
return false;
|
|
@@ -19,28 +59,52 @@ export function resolveSingleOutputPath(output, runtimeCwd, requestedCwd, relati
|
|
|
19
59
|
const baseCwd = requestedCwd ? path.isAbsolute(requestedCwd) ? requestedCwd : path.resolve(runtimeCwd, requestedCwd) : runtimeCwd;
|
|
20
60
|
return path.resolve(baseCwd, output);
|
|
21
61
|
}
|
|
22
|
-
function formatOutputPathInstruction(outputPath) {
|
|
62
|
+
function formatOutputPathInstruction(outputPath, capabilities) {
|
|
63
|
+
const delivery = !capabilities || hasMutationToolCapability(capabilities.tools, capabilities.mcpDirectTools) ? `Write your findings to exactly this path: ${outputPath}` : [
|
|
64
|
+
"Return the complete artifact in your final response.",
|
|
65
|
+
`The runtime will persist it to exactly this path: ${outputPath}`,
|
|
66
|
+
"Do not call contact_supervisor merely because no write-capable tool is available."
|
|
67
|
+
].join(`
|
|
68
|
+
`);
|
|
23
69
|
return [
|
|
24
|
-
|
|
70
|
+
delivery,
|
|
25
71
|
"This path is authoritative for this run.",
|
|
26
72
|
"Ignore any other output filename or output path mentioned elsewhere, including output destinations in the base agent prompt, system prompt, or task instructions."
|
|
27
73
|
].join(`
|
|
28
74
|
`);
|
|
29
75
|
}
|
|
30
|
-
export function
|
|
76
|
+
export function requestedOutputPathFromTask(task) {
|
|
77
|
+
for (const line of task.split(/\r?\n/).reverse()) {
|
|
78
|
+
const match = line.match(/^\s*(?:Write your findings to(?: exactly this path)?:|The runtime will persist it to exactly this path:)\s*(.+?)\s*$/i);
|
|
79
|
+
if (!match?.[1])
|
|
80
|
+
continue;
|
|
81
|
+
const requested = match[1].trim();
|
|
82
|
+
return requested.startsWith("`") && requested.endsWith("`") ? requested.slice(1, -1) : requested;
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
export function outputPathMappingFromTask(task, savedPath) {
|
|
87
|
+
const requestedPath = requestedOutputPathFromTask(task);
|
|
88
|
+
if (!requestedPath || !savedPath)
|
|
89
|
+
return;
|
|
90
|
+
if (path.isAbsolute(requestedPath) && path.normalize(requestedPath) === path.normalize(savedPath))
|
|
91
|
+
return;
|
|
92
|
+
return { requestedPath, savedPath };
|
|
93
|
+
}
|
|
94
|
+
export function injectSingleOutputInstruction(task, outputPath, capabilities) {
|
|
31
95
|
if (!outputPath)
|
|
32
96
|
return task;
|
|
33
97
|
return `${task}
|
|
34
98
|
|
|
35
99
|
---
|
|
36
100
|
**Output:**
|
|
37
|
-
${formatOutputPathInstruction(outputPath)}`;
|
|
101
|
+
${formatOutputPathInstruction(outputPath, capabilities)}`;
|
|
38
102
|
}
|
|
39
|
-
export function injectOutputPathSystemPrompt(systemPrompt, outputPath) {
|
|
103
|
+
export function injectOutputPathSystemPrompt(systemPrompt, outputPath, capabilities) {
|
|
40
104
|
if (!outputPath)
|
|
41
105
|
return systemPrompt;
|
|
42
106
|
const instruction = `Runtime output path override:
|
|
43
|
-
${formatOutputPathInstruction(outputPath)}`;
|
|
107
|
+
${formatOutputPathInstruction(outputPath, capabilities)}`;
|
|
44
108
|
return systemPrompt ? `${systemPrompt}
|
|
45
109
|
|
|
46
110
|
${instruction}` : instruction;
|
|
@@ -80,20 +144,60 @@ export function validateFileOnlyOutputMode(outputMode, outputPath, context) {
|
|
|
80
144
|
}
|
|
81
145
|
return;
|
|
82
146
|
}
|
|
147
|
+
export function resolveSingleOutputClaimPath(outputPath) {
|
|
148
|
+
let existing = path.resolve(outputPath);
|
|
149
|
+
const missingSegments = [];
|
|
150
|
+
while (!fs.existsSync(existing)) {
|
|
151
|
+
missingSegments.unshift(path.basename(existing));
|
|
152
|
+
const parent = path.dirname(existing);
|
|
153
|
+
if (parent === existing)
|
|
154
|
+
break;
|
|
155
|
+
existing = parent;
|
|
156
|
+
}
|
|
157
|
+
return path.join(fs.realpathSync(existing), ...missingSegments);
|
|
158
|
+
}
|
|
159
|
+
function outputClaimError(outputPath, expectedClaimPath) {
|
|
160
|
+
if (expectedClaimPath && resolveSingleOutputClaimPath(outputPath) !== expectedClaimPath)
|
|
161
|
+
return "Output path changed after it was claimed.";
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
83
164
|
export function captureSingleOutputSnapshot(outputPath) {
|
|
84
165
|
if (!outputPath)
|
|
85
166
|
return;
|
|
86
167
|
try {
|
|
87
168
|
const stat = fs.statSync(outputPath);
|
|
88
169
|
return { exists: true, mtimeMs: stat.mtimeMs, size: stat.size };
|
|
89
|
-
} catch {
|
|
90
|
-
return
|
|
170
|
+
} catch (error) {
|
|
171
|
+
return snapshotFromStatError(error);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function inspectSingleOutputChange(outputPath, beforeRun) {
|
|
175
|
+
if (beforeRun?.error)
|
|
176
|
+
return { changed: false, error: beforeRun.error };
|
|
177
|
+
try {
|
|
178
|
+
const stat = fs.statSync(outputPath);
|
|
179
|
+
return {
|
|
180
|
+
changed: !beforeRun?.exists || stat.mtimeMs !== beforeRun.mtimeMs || stat.size !== beforeRun.size
|
|
181
|
+
};
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (missingFileStatError(error))
|
|
184
|
+
return { changed: false };
|
|
185
|
+
return { changed: false, error: error instanceof Error ? error.message : String(error) };
|
|
91
186
|
}
|
|
92
187
|
}
|
|
93
|
-
function
|
|
188
|
+
export function hasSingleOutputChangedSinceSnapshot(outputPath, beforeRun) {
|
|
189
|
+
if (!outputPath)
|
|
190
|
+
return;
|
|
191
|
+
const inspected = inspectSingleOutputChange(outputPath, beforeRun);
|
|
192
|
+
return inspected.error ? undefined : inspected.changed;
|
|
193
|
+
}
|
|
194
|
+
function persistSingleOutput(outputPath, fullOutput, expectedClaimPath) {
|
|
94
195
|
if (!outputPath)
|
|
95
196
|
return {};
|
|
96
197
|
try {
|
|
198
|
+
const claimError = outputClaimError(outputPath, expectedClaimPath);
|
|
199
|
+
if (claimError)
|
|
200
|
+
return { error: claimError, fatalError: true };
|
|
97
201
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
98
202
|
fs.writeFileSync(outputPath, fullOutput, "utf-8");
|
|
99
203
|
return { savedPath: outputPath };
|
|
@@ -101,23 +205,20 @@ function persistSingleOutput(outputPath, fullOutput) {
|
|
|
101
205
|
return { error: err instanceof Error ? err.message : String(err) };
|
|
102
206
|
}
|
|
103
207
|
}
|
|
104
|
-
export function resolveSingleOutput(outputPath, fallbackOutput, beforeRun) {
|
|
208
|
+
export function resolveSingleOutput(outputPath, fallbackOutput, beforeRun, expectedClaimPath) {
|
|
105
209
|
if (!outputPath)
|
|
106
210
|
return { fullOutput: fallbackOutput };
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
saveError: `Failed to inspect output file: ${error instanceof Error ? error.message : String(error)}`
|
|
117
|
-
};
|
|
118
|
-
}
|
|
211
|
+
const claimError = outputClaimError(outputPath, expectedClaimPath);
|
|
212
|
+
if (claimError)
|
|
213
|
+
return { fullOutput: fallbackOutput, saveError: claimError, fatalError: true };
|
|
214
|
+
const changedSinceStart = inspectSingleOutputChange(outputPath, beforeRun);
|
|
215
|
+
if (changedSinceStart.error) {
|
|
216
|
+
return {
|
|
217
|
+
fullOutput: fallbackOutput,
|
|
218
|
+
saveError: `Failed to inspect output file: ${changedSinceStart.error}`
|
|
219
|
+
};
|
|
119
220
|
}
|
|
120
|
-
if (changedSinceStart) {
|
|
221
|
+
if (changedSinceStart.changed) {
|
|
121
222
|
try {
|
|
122
223
|
return { fullOutput: fs.readFileSync(outputPath, "utf-8"), savedPath: outputPath };
|
|
123
224
|
} catch (error) {
|
|
@@ -127,19 +228,20 @@ export function resolveSingleOutput(outputPath, fallbackOutput, beforeRun) {
|
|
|
127
228
|
};
|
|
128
229
|
}
|
|
129
230
|
}
|
|
130
|
-
const save = persistSingleOutput(outputPath, fallbackOutput);
|
|
231
|
+
const save = persistSingleOutput(outputPath, fallbackOutput, expectedClaimPath);
|
|
131
232
|
if (save.savedPath)
|
|
132
233
|
return { fullOutput: fallbackOutput, savedPath: save.savedPath };
|
|
133
|
-
return { fullOutput: fallbackOutput, saveError: save.error };
|
|
234
|
+
return { fullOutput: fallbackOutput, saveError: save.error, ...save.fatalError ? { fatalError: true } : {} };
|
|
134
235
|
}
|
|
135
236
|
export function finalizeSingleOutput(params) {
|
|
136
237
|
let displayOutput = params.truncatedOutput || params.fullOutput;
|
|
137
|
-
if (params.exitCode === 0 && params.savedPath) {
|
|
238
|
+
if ((params.exitCode === 0 || params.preserveSavedOutput) && params.savedPath) {
|
|
138
239
|
const outputReference = params.outputReference ?? formatSavedOutputReference(params.savedPath, params.fullOutput);
|
|
139
240
|
if (params.outputMode === "file-only") {
|
|
140
241
|
return { displayOutput: outputReference.message, savedPath: params.savedPath, outputReference };
|
|
141
242
|
}
|
|
142
|
-
displayOutput
|
|
243
|
+
if (!displayOutput.includes(outputReference.message))
|
|
244
|
+
displayOutput += `
|
|
143
245
|
|
|
144
246
|
${outputReference.message}`;
|
|
145
247
|
return { displayOutput, savedPath: params.savedPath, outputReference };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveMaxSubagentSpawnsPerSession
|
|
3
|
+
} from "../../shared/types.js";
|
|
4
|
+
const MAX_GRANT_HISTORY = 20;
|
|
5
|
+
function sessionState(state, config, sessionId) {
|
|
6
|
+
let counters = state.subagentSpawns;
|
|
7
|
+
if (!counters || counters.sessionId !== sessionId) {
|
|
8
|
+
counters = {
|
|
9
|
+
sessionId,
|
|
10
|
+
count: 0,
|
|
11
|
+
configuredLimit: resolveMaxSubagentSpawnsPerSession(config.maxSubagentSpawnsPerSession) ?? null,
|
|
12
|
+
granted: 0,
|
|
13
|
+
grantHistory: []
|
|
14
|
+
};
|
|
15
|
+
state.subagentSpawns = counters;
|
|
16
|
+
}
|
|
17
|
+
if (counters.configuredLimit === undefined) {
|
|
18
|
+
counters.configuredLimit = resolveMaxSubagentSpawnsPerSession(config.maxSubagentSpawnsPerSession) ?? null;
|
|
19
|
+
}
|
|
20
|
+
counters.granted ??= 0;
|
|
21
|
+
counters.grantHistory ??= [];
|
|
22
|
+
return counters;
|
|
23
|
+
}
|
|
24
|
+
export function getSpawnBudgetSnapshot(state, config, sessionId = state.currentSessionId) {
|
|
25
|
+
const counters = sessionState(state, config, sessionId);
|
|
26
|
+
const configuredLimit = counters.configuredLimit ?? null;
|
|
27
|
+
const granted = configuredLimit === null ? 0 : counters.granted ?? 0;
|
|
28
|
+
const limit = configuredLimit === null ? null : configuredLimit + granted;
|
|
29
|
+
return {
|
|
30
|
+
used: counters.count,
|
|
31
|
+
configuredLimit,
|
|
32
|
+
granted,
|
|
33
|
+
limit,
|
|
34
|
+
remaining: limit === null ? null : Math.max(0, limit - counters.count),
|
|
35
|
+
grantRemaining: configuredLimit === null ? null : Math.max(0, configuredLimit - granted),
|
|
36
|
+
grantHistory: [...counters.grantHistory ?? []]
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function formatSpawnBudgetSummary(snapshot) {
|
|
40
|
+
if (snapshot.limit === null)
|
|
41
|
+
return "unlimited";
|
|
42
|
+
return `${snapshot.used}/${snapshot.limit} used, ${snapshot.remaining} remaining (configured ${snapshot.configuredLimit}; granted ${snapshot.granted}; grant allowance ${snapshot.grantRemaining})`;
|
|
43
|
+
}
|
|
44
|
+
export function formatSpawnBudget(snapshot) {
|
|
45
|
+
return `Spawn budget: ${formatSpawnBudgetSummary(snapshot)}`;
|
|
46
|
+
}
|
|
47
|
+
export function preflightSpawnBudget(state, config, sessionId, requested) {
|
|
48
|
+
const snapshot = getSpawnBudgetSnapshot(state, config, sessionId);
|
|
49
|
+
if (requested <= 0 || snapshot.limit === null || requested <= (snapshot.remaining ?? 0))
|
|
50
|
+
return { snapshot };
|
|
51
|
+
return {
|
|
52
|
+
snapshot,
|
|
53
|
+
error: `Subagent spawn limit reached for this session (${snapshot.used}/${snapshot.limit} used, ${requested} requested). ${snapshot.remaining} remaining; the declared run cannot fit, so no children were started. Grant budget explicitly from the root interactive session or start a new session.`
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function reserveSpawnBudget(state, config, sessionId, requested) {
|
|
57
|
+
const checked = preflightSpawnBudget(state, config, sessionId, requested);
|
|
58
|
+
if (checked.error || requested <= 0 || checked.snapshot.limit === null)
|
|
59
|
+
return checked;
|
|
60
|
+
state.subagentSpawns.count += requested;
|
|
61
|
+
return { snapshot: getSpawnBudgetSnapshot(state, config, sessionId) };
|
|
62
|
+
}
|
|
63
|
+
export function preflightSpawnBudgetGrant(state, config, sessionId, additional) {
|
|
64
|
+
const snapshot = getSpawnBudgetSnapshot(state, config, sessionId);
|
|
65
|
+
if (!Number.isInteger(additional) || additional <= 0) {
|
|
66
|
+
return { snapshot, error: "action='grant-spawn-budget' requires additional to be a positive integer." };
|
|
67
|
+
}
|
|
68
|
+
if (snapshot.configuredLimit === null || snapshot.limit === null) {
|
|
69
|
+
return { snapshot, error: "The current session has no configured spawn cap, so it does not need a budget grant." };
|
|
70
|
+
}
|
|
71
|
+
if (additional > (snapshot.grantRemaining ?? 0)) {
|
|
72
|
+
return {
|
|
73
|
+
snapshot,
|
|
74
|
+
error: `Spawn budget grant rejected: ${additional} requested but only ${snapshot.grantRemaining} of the original configured limit remains grantable.`
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return { snapshot };
|
|
78
|
+
}
|
|
79
|
+
export function grantSpawnBudget(state, config, sessionId, additional, now = Date.now()) {
|
|
80
|
+
const checked = preflightSpawnBudgetGrant(state, config, sessionId, additional);
|
|
81
|
+
if (checked.error)
|
|
82
|
+
return checked;
|
|
83
|
+
const before = checked.snapshot;
|
|
84
|
+
if (before.limit === null) {
|
|
85
|
+
return { snapshot: before, error: "The current session has no configured spawn cap, so it does not need a budget grant." };
|
|
86
|
+
}
|
|
87
|
+
const counters = state.subagentSpawns;
|
|
88
|
+
counters.granted = (counters.granted ?? 0) + additional;
|
|
89
|
+
const limit = before.limit + additional;
|
|
90
|
+
counters.grantHistory = [
|
|
91
|
+
...counters.grantHistory ?? [],
|
|
92
|
+
{ sessionId, amount: additional, grantedAt: now, previousLimit: before.limit, limit }
|
|
93
|
+
].slice(-MAX_GRANT_HISTORY);
|
|
94
|
+
return { snapshot: getSpawnBudgetSnapshot(state, config, sessionId) };
|
|
95
|
+
}
|