@duckmind/dm-windows-x64 0.61.4 → 0.61.9
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 +211 -67
- 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/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
|
@@ -44,26 +44,43 @@ export class Semaphore {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
export async function mapConcurrent(items, limit, fn, globalSemaphore) {
|
|
47
|
+
export async function mapConcurrent(items, limit, fn, globalSemaphore, onSchedulingSettled) {
|
|
48
48
|
const safeLimit = Math.max(1, Math.floor(limit) || 1);
|
|
49
49
|
const results = new Array(items.length);
|
|
50
50
|
let next = 0;
|
|
51
|
+
let liveWorkers = Math.min(safeLimit, items.length);
|
|
52
|
+
const notifySchedulingSettled = () => {
|
|
53
|
+
try {
|
|
54
|
+
onSchedulingSettled?.();
|
|
55
|
+
} catch {}
|
|
56
|
+
};
|
|
51
57
|
async function worker(_workerIndex) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
globalSemaphore.
|
|
58
|
+
try {
|
|
59
|
+
while (next < items.length) {
|
|
60
|
+
const i = next++;
|
|
61
|
+
if (!(i in items))
|
|
62
|
+
throw new Error(`Missing parallel item at index ${i}`);
|
|
63
|
+
const item = items[i];
|
|
64
|
+
if (globalSemaphore) {
|
|
65
|
+
await globalSemaphore.acquire();
|
|
66
|
+
try {
|
|
67
|
+
results[i] = await fn(item, i);
|
|
68
|
+
} finally {
|
|
69
|
+
globalSemaphore.release();
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
results[i] = await fn(item, i);
|
|
60
73
|
}
|
|
61
|
-
} else {
|
|
62
|
-
results[i] = await fn(items[i], i);
|
|
63
74
|
}
|
|
75
|
+
} finally {
|
|
76
|
+
liveWorkers--;
|
|
77
|
+
if (liveWorkers === 0)
|
|
78
|
+
notifySchedulingSettled();
|
|
64
79
|
}
|
|
65
80
|
}
|
|
66
|
-
|
|
81
|
+
if (liveWorkers === 0)
|
|
82
|
+
notifySchedulingSettled();
|
|
83
|
+
await Promise.all(Array.from({ length: liveWorkers }, (_, wi) => worker(wi)));
|
|
67
84
|
return results;
|
|
68
85
|
}
|
|
69
86
|
export function aggregateParallelOutputs(results, headerFormat = (i, agent) => `=== Parallel Task ${i + 1} (${agent}) ===`) {
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
export const PERMISSION_POLICY_ENV = "DM_SUBAGENT_PERMISSION_POLICY";
|
|
4
|
+
export const PERMISSION_AUDIT_PATH_ENV = "DM_SUBAGENT_PERMISSION_AUDIT_PATH";
|
|
5
|
+
const INTERNAL_TOOLS = new Set(["contact_supervisor", "intercom", "bg_wait", "subagent_wait", "structured_output"]);
|
|
6
|
+
const DECISIONS = new Set(["allow", "ask", "deny"]);
|
|
7
|
+
const MAX_POLICY_BYTES = 16 * 1024;
|
|
8
|
+
const MAX_PREVIEW_BYTES = 2048;
|
|
9
|
+
const SECRET_KEY = /(?:authorization|cookie|credential|password|secret|token|api[-_]?key)/i;
|
|
10
|
+
const SECRET_VALUE = /\b(?:Bearer\s+\S+|(?:sk|ghp|github_pat|xox[baprs])[-_A-Za-z0-9]{8,})\b/gi;
|
|
11
|
+
export function redactSecretValues(value) {
|
|
12
|
+
return value.replace(SECRET_VALUE, "[redacted]");
|
|
13
|
+
}
|
|
14
|
+
export function validatePermissionRules(value, label) {
|
|
15
|
+
if (value === undefined)
|
|
16
|
+
return;
|
|
17
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
18
|
+
throw new Error(`${label} must be an object mapping tool names to allow, ask, or deny.`);
|
|
19
|
+
const result = {};
|
|
20
|
+
for (const [tool, decision] of Object.entries(value)) {
|
|
21
|
+
if (!tool.trim())
|
|
22
|
+
throw new Error(`${label} contains an empty tool name.`);
|
|
23
|
+
if (tool === "bash")
|
|
24
|
+
throw new Error(`${label}.bash is unsupported; dm-subagents leaves bash policy to dm-guard.`);
|
|
25
|
+
if (INTERNAL_TOOLS.has(tool))
|
|
26
|
+
throw new Error(`${label}.${tool} is reserved for child coordination and cannot be gated.`);
|
|
27
|
+
if (!DECISIONS.has(decision))
|
|
28
|
+
throw new Error(`${label}.${tool} must be allow, ask, or deny.`);
|
|
29
|
+
result[tool] = decision;
|
|
30
|
+
}
|
|
31
|
+
return Object.keys(result).length ? result : undefined;
|
|
32
|
+
}
|
|
33
|
+
export function validatePermissionConfig(value, label = "config.permissions") {
|
|
34
|
+
if (value === undefined)
|
|
35
|
+
return;
|
|
36
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
37
|
+
throw new Error(`${label} must be an object.`);
|
|
38
|
+
const object = value;
|
|
39
|
+
const unknown = Object.keys(object).filter((key) => key !== "rules");
|
|
40
|
+
if (unknown.length)
|
|
41
|
+
throw new Error(`${label} has unsupported fields: ${unknown.join(", ")}.`);
|
|
42
|
+
return { rules: validatePermissionRules(object.rules, `${label}.rules`) };
|
|
43
|
+
}
|
|
44
|
+
export function resolvePermissionRules(globalConfig, agentRules) {
|
|
45
|
+
const merged = { ...globalConfig?.rules ?? {}, ...agentRules ?? {} };
|
|
46
|
+
for (const [tool, decision] of Object.entries(merged))
|
|
47
|
+
if (decision === "allow")
|
|
48
|
+
delete merged[tool];
|
|
49
|
+
return Object.keys(merged).length ? merged : undefined;
|
|
50
|
+
}
|
|
51
|
+
export function permissionDecision(rules, toolName) {
|
|
52
|
+
if (toolName === "bash" || INTERNAL_TOOLS.has(toolName))
|
|
53
|
+
return "allow";
|
|
54
|
+
return rules?.[toolName] ?? "allow";
|
|
55
|
+
}
|
|
56
|
+
export function encodePermissionRules(rules) {
|
|
57
|
+
if (!rules || Object.keys(rules).length === 0)
|
|
58
|
+
return;
|
|
59
|
+
const encoded = JSON.stringify(rules);
|
|
60
|
+
if (Buffer.byteLength(encoded, "utf-8") > MAX_POLICY_BYTES)
|
|
61
|
+
throw new Error("Resolved permission policy is too large.");
|
|
62
|
+
return encoded;
|
|
63
|
+
}
|
|
64
|
+
export function decodePermissionRules(encoded) {
|
|
65
|
+
if (!encoded?.trim())
|
|
66
|
+
return;
|
|
67
|
+
return validatePermissionRules(JSON.parse(encoded), PERMISSION_POLICY_ENV);
|
|
68
|
+
}
|
|
69
|
+
function redact(value, key = "", depth = 0) {
|
|
70
|
+
if (SECRET_KEY.test(key))
|
|
71
|
+
return "[redacted]";
|
|
72
|
+
if (depth >= 3)
|
|
73
|
+
return "[truncated]";
|
|
74
|
+
if (Array.isArray(value))
|
|
75
|
+
return value.slice(0, 10).map((item) => redact(item, "", depth + 1));
|
|
76
|
+
if (value && typeof value === "object")
|
|
77
|
+
return Object.fromEntries(Object.entries(value).slice(0, 20).map(([entryKey, entryValue]) => [entryKey, redact(entryValue, entryKey, depth + 1)]));
|
|
78
|
+
if (typeof value === "string") {
|
|
79
|
+
const redacted = redactSecretValues(value);
|
|
80
|
+
return redacted.length > 500 ? `${redacted.slice(0, 500)}…` : redacted;
|
|
81
|
+
}
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
export function permissionArgsPreview(input) {
|
|
85
|
+
const serialized = JSON.stringify(redact(input));
|
|
86
|
+
if (!serialized)
|
|
87
|
+
return "{}";
|
|
88
|
+
if (Buffer.byteLength(serialized, "utf-8") <= MAX_PREVIEW_BYTES)
|
|
89
|
+
return serialized;
|
|
90
|
+
const maxContentBytes = MAX_PREVIEW_BYTES - Buffer.byteLength("…", "utf-8");
|
|
91
|
+
let preview = "";
|
|
92
|
+
let previewBytes = 0;
|
|
93
|
+
for (const character of serialized) {
|
|
94
|
+
const characterBytes = Buffer.byteLength(character, "utf-8");
|
|
95
|
+
if (previewBytes + characterBytes > maxContentBytes)
|
|
96
|
+
break;
|
|
97
|
+
preview += character;
|
|
98
|
+
previewBytes += characterBytes;
|
|
99
|
+
}
|
|
100
|
+
return `${preview}…`;
|
|
101
|
+
}
|
|
102
|
+
export function appendPermissionAudit(filePath, record) {
|
|
103
|
+
if (!filePath)
|
|
104
|
+
return;
|
|
105
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 448 });
|
|
106
|
+
fs.appendFileSync(filePath, `${JSON.stringify(record)}
|
|
107
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
108
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function formatProcessSignalError(signal) {
|
|
2
|
+
return `Subagent process terminated by signal ${signal}.`;
|
|
3
|
+
}
|
|
4
|
+
export function formatMidToolExitError(input) {
|
|
5
|
+
const terminal = [`exit ${input.exitCode ?? "unknown"}`, ...input.processSignal ? [`signal ${input.processSignal}`] : []].join(", ");
|
|
6
|
+
return `Subagent process exited during '${input.toolName}' tool execution (${terminal}) before the tool completed. Earlier assistant output is not a terminal result.`;
|
|
7
|
+
}
|
|
8
|
+
export function isOrdinaryToolForMidToolExit(toolName) {
|
|
9
|
+
return toolName !== "intercom" && toolName !== "contact_supervisor";
|
|
10
|
+
}
|
|
11
|
+
export function isUnexplainedProcessSignal(input) {
|
|
12
|
+
return Boolean(input.processSignal) && input.interrupted !== true && input.timedOut !== true && input.stopped !== true && input.turnBudgetExceeded !== true && input.forcedDrainAfterFinalSuccess !== true;
|
|
13
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS, waitForFileSystemRetry } from "../../shared/file-system-retry.js";
|
|
5
|
+
import {
|
|
6
|
+
TEMP_ROOT_DIR
|
|
7
|
+
} from "../../shared/types.js";
|
|
8
|
+
export const RUN_FANOUT_BUDGET_ENV = "DM_SUBAGENT_RUN_FANOUT_BUDGET";
|
|
9
|
+
const RUN_FANOUT_ROOT = path.join(TEMP_ROOT_DIR, "run-fanout-budgets");
|
|
10
|
+
|
|
11
|
+
export class RunFanoutLimitError extends Error {
|
|
12
|
+
rejection;
|
|
13
|
+
snapshot;
|
|
14
|
+
constructor(rejection) {
|
|
15
|
+
super(formatRunFanoutRejection(rejection));
|
|
16
|
+
this.name = "RunFanoutLimitError";
|
|
17
|
+
this.rejection = rejection;
|
|
18
|
+
this.snapshot = { used: rejection.used, limit: rejection.limit, remaining: rejection.remaining };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function safeRootRunId(rootRunId) {
|
|
22
|
+
return rootRunId.replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 120) || randomUUID();
|
|
23
|
+
}
|
|
24
|
+
function parseManifest(value) {
|
|
25
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
26
|
+
return;
|
|
27
|
+
const manifest = value;
|
|
28
|
+
if (manifest.version !== 1 || typeof manifest.rootRunId !== "string" || !manifest.rootRunId || !Number.isInteger(manifest.limit) || (manifest.limit ?? 0) <= 0 || typeof manifest.createdAt !== "number" || !Number.isFinite(manifest.createdAt))
|
|
29
|
+
return;
|
|
30
|
+
return manifest;
|
|
31
|
+
}
|
|
32
|
+
function readManifest(directory) {
|
|
33
|
+
let parsed;
|
|
34
|
+
try {
|
|
35
|
+
parsed = JSON.parse(fs.readFileSync(path.join(directory, "manifest.json"), "utf-8"));
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw new Error(`Run fan-out budget manifest is unreadable at '${directory}': ${error instanceof Error ? error.message : String(error)}`);
|
|
38
|
+
}
|
|
39
|
+
const manifest = parseManifest(parsed);
|
|
40
|
+
if (!manifest)
|
|
41
|
+
throw new Error(`Run fan-out budget manifest is invalid at '${directory}'.`);
|
|
42
|
+
return manifest;
|
|
43
|
+
}
|
|
44
|
+
function validateDirectory(directory) {
|
|
45
|
+
let realDirectory;
|
|
46
|
+
let realRoot;
|
|
47
|
+
try {
|
|
48
|
+
realDirectory = fs.realpathSync(path.resolve(directory));
|
|
49
|
+
realRoot = fs.realpathSync(path.resolve(RUN_FANOUT_ROOT));
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw new Error(`Run fan-out budget directory is unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}
|
|
53
|
+
if (realDirectory !== realRoot && !realDirectory.startsWith(`${realRoot}${path.sep}`)) {
|
|
54
|
+
throw new Error("Run fan-out budget directory resolves outside the managed budget root.");
|
|
55
|
+
}
|
|
56
|
+
return realDirectory;
|
|
57
|
+
}
|
|
58
|
+
export function createRunFanoutBudget(rootRunId, limit) {
|
|
59
|
+
if (!Number.isInteger(limit) || limit <= 0)
|
|
60
|
+
throw new Error("Run fan-out limit must be a positive integer.");
|
|
61
|
+
fs.mkdirSync(RUN_FANOUT_ROOT, { recursive: true, mode: 448 });
|
|
62
|
+
let directory;
|
|
63
|
+
do
|
|
64
|
+
directory = path.join(RUN_FANOUT_ROOT, `${safeRootRunId(rootRunId)}-${randomUUID()}`);
|
|
65
|
+
while (fs.existsSync(directory));
|
|
66
|
+
fs.mkdirSync(path.join(directory, "claims"), { recursive: true, mode: 448 });
|
|
67
|
+
const manifest = { version: 1, rootRunId, limit, createdAt: Date.now() };
|
|
68
|
+
fs.writeFileSync(path.join(directory, "manifest.json"), `${JSON.stringify(manifest)}
|
|
69
|
+
`, { mode: 384, flag: "wx" });
|
|
70
|
+
return { version: 1, rootRunId, directory, limit };
|
|
71
|
+
}
|
|
72
|
+
export function validateRunFanoutBudgetDescriptor(value) {
|
|
73
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
74
|
+
throw new Error("Run fan-out budget descriptor is missing or invalid.");
|
|
75
|
+
const descriptor = value;
|
|
76
|
+
if (descriptor.version !== 1 || typeof descriptor.rootRunId !== "string" || !descriptor.rootRunId || typeof descriptor.directory !== "string" || !descriptor.directory || !Number.isInteger(descriptor.limit) || (descriptor.limit ?? 0) <= 0 || descriptor.parentPath !== undefined && typeof descriptor.parentPath !== "string") {
|
|
77
|
+
throw new Error("Run fan-out budget descriptor is invalid.");
|
|
78
|
+
}
|
|
79
|
+
const directory = validateDirectory(descriptor.directory);
|
|
80
|
+
const manifest = readManifest(directory);
|
|
81
|
+
if (manifest.rootRunId !== descriptor.rootRunId || manifest.limit !== descriptor.limit) {
|
|
82
|
+
throw new Error("Run fan-out budget descriptor does not match its manifest.");
|
|
83
|
+
}
|
|
84
|
+
return { version: 1, rootRunId: descriptor.rootRunId, directory, limit: descriptor.limit, ...descriptor.parentPath ? { parentPath: descriptor.parentPath } : {} };
|
|
85
|
+
}
|
|
86
|
+
export function writeRunFanoutBudgetDescriptor(asyncDir, descriptor) {
|
|
87
|
+
const valid = validateRunFanoutBudgetDescriptor(descriptor);
|
|
88
|
+
fs.mkdirSync(asyncDir, { recursive: true });
|
|
89
|
+
fs.writeFileSync(path.join(asyncDir, "run-fanout-budget.json"), `${JSON.stringify(valid)}
|
|
90
|
+
`, { mode: 384 });
|
|
91
|
+
}
|
|
92
|
+
export function readRunFanoutBudgetDescriptor(asyncDir) {
|
|
93
|
+
if (!asyncDir)
|
|
94
|
+
return;
|
|
95
|
+
const descriptorPath = path.join(asyncDir, "run-fanout-budget.json");
|
|
96
|
+
if (!fs.existsSync(descriptorPath))
|
|
97
|
+
return;
|
|
98
|
+
try {
|
|
99
|
+
return validateRunFanoutBudgetDescriptor(JSON.parse(fs.readFileSync(descriptorPath, "utf-8")));
|
|
100
|
+
} catch (error) {
|
|
101
|
+
throw new Error(`Invalid persisted run fan-out budget '${descriptorPath}': ${error instanceof Error ? error.message : String(error)}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export function encodeRunFanoutBudgetDescriptor(descriptor) {
|
|
105
|
+
return Buffer.from(JSON.stringify(validateRunFanoutBudgetDescriptor(descriptor)), "utf-8").toString("base64url");
|
|
106
|
+
}
|
|
107
|
+
export function decodeRunFanoutBudgetDescriptor(encoded) {
|
|
108
|
+
if (!encoded)
|
|
109
|
+
return;
|
|
110
|
+
try {
|
|
111
|
+
return validateRunFanoutBudgetDescriptor(JSON.parse(Buffer.from(encoded, "base64url").toString("utf-8")));
|
|
112
|
+
} catch (error) {
|
|
113
|
+
throw new Error(`Invalid inherited run fan-out budget: ${error instanceof Error ? error.message : String(error)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const ADMISSION_LOCK_STALE_MS = 60000;
|
|
117
|
+
function readAdmissionLockOwner(lockPath) {
|
|
118
|
+
try {
|
|
119
|
+
const owner = JSON.parse(fs.readFileSync(path.join(lockPath, "owner.json"), "utf-8"));
|
|
120
|
+
if (Number.isSafeInteger(owner.pid) && (owner.pid ?? 0) > 0 && typeof owner.token === "string" && owner.token)
|
|
121
|
+
return owner;
|
|
122
|
+
} catch {}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
function admissionLockIsStale(lockPath) {
|
|
126
|
+
const owner = readAdmissionLockOwner(lockPath);
|
|
127
|
+
if (owner) {
|
|
128
|
+
try {
|
|
129
|
+
process.kill(owner.pid, 0);
|
|
130
|
+
return false;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
return error.code !== "EPERM";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
return Date.now() - fs.statSync(lockPath).mtimeMs > ADMISSION_LOCK_STALE_MS;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (error.code === "ENOENT")
|
|
139
|
+
return false;
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function withAdmissionLock(directory, operation) {
|
|
144
|
+
const lockPath = path.join(directory, "admission.lock");
|
|
145
|
+
const owner = { pid: process.pid, token: randomUUID() };
|
|
146
|
+
for (let attempt = 0;; attempt++) {
|
|
147
|
+
try {
|
|
148
|
+
fs.mkdirSync(lockPath, { mode: 448 });
|
|
149
|
+
try {
|
|
150
|
+
fs.writeFileSync(path.join(lockPath, "owner.json"), JSON.stringify(owner), { mode: 384 });
|
|
151
|
+
} catch (error) {
|
|
152
|
+
fs.rmSync(lockPath, { recursive: true, force: true });
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
if (error.code !== "EEXIST")
|
|
158
|
+
throw error;
|
|
159
|
+
if (admissionLockIsStale(lockPath)) {
|
|
160
|
+
const stalePath = path.join(directory, `admission.stale-${randomUUID()}`);
|
|
161
|
+
try {
|
|
162
|
+
fs.renameSync(lockPath, stalePath);
|
|
163
|
+
fs.rmSync(stalePath, { recursive: true, force: true });
|
|
164
|
+
continue;
|
|
165
|
+
} catch (reclaimError) {
|
|
166
|
+
if (reclaimError.code !== "ENOENT")
|
|
167
|
+
throw reclaimError;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const delay = DEFAULT_FILE_SYSTEM_RETRY_DELAYS_MS[attempt];
|
|
171
|
+
if (delay === undefined)
|
|
172
|
+
throw new Error(`Timed out acquiring run fan-out admission lock at '${directory}'.`);
|
|
173
|
+
waitForFileSystemRetry(delay);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
return operation();
|
|
178
|
+
} finally {
|
|
179
|
+
if (readAdmissionLockOwner(lockPath)?.token === owner.token)
|
|
180
|
+
fs.rmSync(lockPath, { recursive: true, force: true });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function claimCount(directory) {
|
|
184
|
+
const claimsDir = path.join(directory, "claims");
|
|
185
|
+
let entries;
|
|
186
|
+
try {
|
|
187
|
+
entries = fs.readdirSync(claimsDir, { withFileTypes: true });
|
|
188
|
+
} catch (error) {
|
|
189
|
+
throw new Error(`Run fan-out claims directory is unreadable at '${claimsDir}': ${error instanceof Error ? error.message : String(error)}`);
|
|
190
|
+
}
|
|
191
|
+
return entries.filter((entry) => /^\d{6}\.json$/.test(entry.name)).length;
|
|
192
|
+
}
|
|
193
|
+
export function getRunFanoutBudgetSnapshot(descriptor) {
|
|
194
|
+
const valid = validateRunFanoutBudgetDescriptor(descriptor);
|
|
195
|
+
const used = claimCount(valid.directory);
|
|
196
|
+
return { used, limit: valid.limit, remaining: Math.max(0, valid.limit - used) };
|
|
197
|
+
}
|
|
198
|
+
function qualifyRunFanoutPaths(descriptor, paths) {
|
|
199
|
+
const prefix = descriptor.parentPath?.trim();
|
|
200
|
+
return paths.map((item) => prefix ? `${prefix}/${item}` : item);
|
|
201
|
+
}
|
|
202
|
+
function commitRunFanoutBatch(descriptor, paths, commit) {
|
|
203
|
+
const valid = validateRunFanoutBudgetDescriptor(descriptor);
|
|
204
|
+
if (paths.length === 0)
|
|
205
|
+
return commit(getRunFanoutBudgetSnapshot(valid));
|
|
206
|
+
const qualified = qualifyRunFanoutPaths(valid, paths);
|
|
207
|
+
return withAdmissionLock(valid.directory, () => {
|
|
208
|
+
const before = getRunFanoutBudgetSnapshot(valid);
|
|
209
|
+
if (qualified.length > before.remaining) {
|
|
210
|
+
throw new RunFanoutLimitError({ code: "RUN_FANOUT_LIMIT", path: qualified[before.remaining] ?? qualified[0], requested: qualified.length, ...before });
|
|
211
|
+
}
|
|
212
|
+
const created = [];
|
|
213
|
+
try {
|
|
214
|
+
for (const claimPath of qualified) {
|
|
215
|
+
for (let slot = 0;slot < valid.limit; slot++) {
|
|
216
|
+
const slotPath = path.join(valid.directory, "claims", `${String(slot).padStart(6, "0")}.json`);
|
|
217
|
+
try {
|
|
218
|
+
const fd = fs.openSync(slotPath, "wx", 384);
|
|
219
|
+
created.push(slotPath);
|
|
220
|
+
try {
|
|
221
|
+
const claim = { version: 1, claimId: randomUUID(), path: claimPath, claimedAt: Date.now() };
|
|
222
|
+
fs.writeFileSync(fd, `${JSON.stringify(claim)}
|
|
223
|
+
`, "utf-8");
|
|
224
|
+
} finally {
|
|
225
|
+
fs.closeSync(fd);
|
|
226
|
+
}
|
|
227
|
+
break;
|
|
228
|
+
} catch (error) {
|
|
229
|
+
if (error.code === "EEXIST")
|
|
230
|
+
continue;
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return commit(getRunFanoutBudgetSnapshot(valid));
|
|
236
|
+
} catch (error) {
|
|
237
|
+
for (const slotPath of created) {
|
|
238
|
+
try {
|
|
239
|
+
fs.unlinkSync(slotPath);
|
|
240
|
+
} catch {}
|
|
241
|
+
}
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
export function claimRunFanoutBatch(descriptor, paths) {
|
|
247
|
+
return commitRunFanoutBatch(descriptor, paths, (snapshot) => snapshot);
|
|
248
|
+
}
|
|
249
|
+
export function claimRunFanoutBatchWithCommit(descriptor, paths, commit) {
|
|
250
|
+
return commitRunFanoutBatch(descriptor, paths, commit);
|
|
251
|
+
}
|
|
252
|
+
export function formatRunFanoutBudget(snapshot) {
|
|
253
|
+
return `Run fan-out: ${snapshot.used}/${snapshot.limit} used, ${snapshot.remaining} remaining`;
|
|
254
|
+
}
|
|
255
|
+
function formatRunFanoutRejection(rejection) {
|
|
256
|
+
return `Run fan-out limit reached at ${rejection.path} (${rejection.used}/${rejection.limit} used; ${rejection.requested} requested, ${rejection.remaining} remaining). No children from this admission group were started. Start a new top-level run or raise config.maxSubagentSpawnsPerRun.`;
|
|
257
|
+
}
|
|
@@ -1,29 +1,149 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import * as fs from "node:fs";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import { getAgentDir } from "../../shared/utils.js";
|
|
5
|
+
import { isUnexplainedProcessSignal } from "./process-signal.js";
|
|
4
6
|
const ROTATE_READ_THRESHOLD = 1200;
|
|
5
7
|
const ROTATE_KEEP = 1000;
|
|
8
|
+
const PRIVATE_DIR_MODE = 448;
|
|
9
|
+
const PRIVATE_FILE_MODE = 384;
|
|
10
|
+
const REDACTED_TASK = "[redacted]";
|
|
11
|
+
const historyFileStates = new Map;
|
|
6
12
|
function getHistoryPath() {
|
|
7
13
|
return path.join(getAgentDir(), "run-history.jsonl");
|
|
8
14
|
}
|
|
9
|
-
|
|
15
|
+
function hashTask(task) {
|
|
16
|
+
return createHash("sha256").update(task).digest("hex");
|
|
17
|
+
}
|
|
18
|
+
function hardenHistoryStorage(historyPath) {
|
|
19
|
+
const historyDir = path.dirname(historyPath);
|
|
20
|
+
fs.mkdirSync(historyDir, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
21
|
+
try {
|
|
22
|
+
if ((fs.statSync(historyDir).mode & 511) !== PRIVATE_DIR_MODE)
|
|
23
|
+
fs.chmodSync(historyDir, PRIVATE_DIR_MODE);
|
|
24
|
+
} catch {}
|
|
25
|
+
try {
|
|
26
|
+
if ((fs.statSync(historyPath).mode & 511) !== PRIVATE_FILE_MODE)
|
|
27
|
+
fs.chmodSync(historyPath, PRIVATE_FILE_MODE);
|
|
28
|
+
} catch {}
|
|
29
|
+
}
|
|
30
|
+
function sanitizeHistoryLine(line) {
|
|
31
|
+
let value;
|
|
32
|
+
try {
|
|
33
|
+
value = JSON.parse(line);
|
|
34
|
+
} catch {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (!value || typeof value !== "object")
|
|
38
|
+
return;
|
|
39
|
+
const record = value;
|
|
40
|
+
const task = typeof record.task === "string" ? record.task : "";
|
|
41
|
+
const taskHash = typeof record.taskHash === "string" && record.taskHash ? record.taskHash : task && task !== REDACTED_TASK ? hashTask(task) : undefined;
|
|
42
|
+
return JSON.stringify({
|
|
43
|
+
...record,
|
|
44
|
+
task: REDACTED_TASK,
|
|
45
|
+
...taskHash ? { taskHash } : {}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function sanitizeHistoryLines(raw) {
|
|
49
|
+
const lines = [];
|
|
50
|
+
let changed = false;
|
|
51
|
+
for (const line of raw.split(`
|
|
52
|
+
`)) {
|
|
53
|
+
const trimmed = line.trim();
|
|
54
|
+
if (!trimmed)
|
|
55
|
+
continue;
|
|
56
|
+
const sanitized = sanitizeHistoryLine(trimmed);
|
|
57
|
+
if (!sanitized) {
|
|
58
|
+
changed = true;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (sanitized !== trimmed)
|
|
62
|
+
changed = true;
|
|
63
|
+
lines.push(sanitized);
|
|
64
|
+
}
|
|
65
|
+
return { lines, changed };
|
|
66
|
+
}
|
|
67
|
+
function writePrivateHistory(historyPath, lines) {
|
|
68
|
+
fs.writeFileSync(historyPath, lines.length ? `${lines.join(`
|
|
69
|
+
`)}
|
|
70
|
+
` : "", { encoding: "utf-8", mode: PRIVATE_FILE_MODE });
|
|
71
|
+
try {
|
|
72
|
+
fs.chmodSync(historyPath, PRIVATE_FILE_MODE);
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
function rememberHistoryFile(historyPath, lineCount) {
|
|
76
|
+
const stat = fs.statSync(historyPath);
|
|
77
|
+
historyFileStates.set(historyPath, {
|
|
78
|
+
mtimeMs: stat.mtimeMs,
|
|
79
|
+
ctimeMs: stat.ctimeMs,
|
|
80
|
+
size: stat.size,
|
|
81
|
+
ino: stat.ino,
|
|
82
|
+
lineCount
|
|
83
|
+
});
|
|
84
|
+
if (historyFileStates.size > 8)
|
|
85
|
+
historyFileStates.delete(historyFileStates.keys().next().value);
|
|
86
|
+
}
|
|
87
|
+
function sanitizeHistoryFile(historyPath) {
|
|
88
|
+
let stat;
|
|
10
89
|
try {
|
|
90
|
+
stat = fs.statSync(historyPath);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (error.code === "ENOENT")
|
|
93
|
+
return 0;
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
const cached = historyFileStates.get(historyPath);
|
|
97
|
+
if (cached && cached.mtimeMs === stat.mtimeMs && cached.ctimeMs === stat.ctimeMs && cached.size === stat.size && cached.ino === stat.ino) {
|
|
98
|
+
return cached.lineCount;
|
|
99
|
+
}
|
|
100
|
+
const raw = fs.readFileSync(historyPath, "utf-8");
|
|
101
|
+
const { lines, changed } = sanitizeHistoryLines(raw);
|
|
102
|
+
if (changed)
|
|
103
|
+
writePrivateHistory(historyPath, lines);
|
|
104
|
+
rememberHistoryFile(historyPath, lines.length);
|
|
105
|
+
return lines.length;
|
|
106
|
+
}
|
|
107
|
+
function appendPrivateHistoryLine(historyPath, line) {
|
|
108
|
+
const fd = fs.openSync(historyPath, fs.constants.O_APPEND | fs.constants.O_CREAT | fs.constants.O_WRONLY, PRIVATE_FILE_MODE);
|
|
109
|
+
try {
|
|
110
|
+
fs.writeSync(fd, `${line}
|
|
111
|
+
`);
|
|
112
|
+
} finally {
|
|
113
|
+
fs.closeSync(fd);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export function recordRun(agent, task, exitCode, durationMs, terminal = {}) {
|
|
117
|
+
try {
|
|
118
|
+
const outcome = terminal.stopped ? "stopped" : terminal.interrupted ? "interrupted" : terminal.timedOut ? "timed_out" : exitCode !== 0 && isUnexplainedProcessSignal(terminal) ? "stopped" : exitCode === 0 ? "completed" : "failed";
|
|
11
119
|
const entry = {
|
|
12
120
|
agent,
|
|
13
|
-
task:
|
|
121
|
+
task: REDACTED_TASK,
|
|
122
|
+
taskHash: hashTask(task),
|
|
14
123
|
ts: Math.floor(Date.now() / 1000),
|
|
15
124
|
status: exitCode === 0 ? "ok" : "error",
|
|
125
|
+
outcome,
|
|
16
126
|
duration: durationMs,
|
|
17
127
|
...exitCode !== 0 ? { exit: exitCode } : {}
|
|
18
128
|
};
|
|
19
129
|
const historyPath = getHistoryPath();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
130
|
+
hardenHistoryStorage(historyPath);
|
|
131
|
+
let lineCount;
|
|
132
|
+
try {
|
|
133
|
+
lineCount = sanitizeHistoryFile(historyPath);
|
|
134
|
+
} catch {}
|
|
135
|
+
appendPrivateHistoryLine(historyPath, JSON.stringify(entry));
|
|
136
|
+
if (lineCount === undefined)
|
|
137
|
+
historyFileStates.delete(historyPath);
|
|
138
|
+
else
|
|
139
|
+
rememberHistoryFile(historyPath, lineCount + 1);
|
|
23
140
|
} catch {}
|
|
24
141
|
}
|
|
25
142
|
export function loadRunsForAgent(agent) {
|
|
26
143
|
const historyPath = getHistoryPath();
|
|
144
|
+
try {
|
|
145
|
+
hardenHistoryStorage(historyPath);
|
|
146
|
+
} catch {}
|
|
27
147
|
if (!fs.existsSync(historyPath))
|
|
28
148
|
return [];
|
|
29
149
|
let raw;
|
|
@@ -32,21 +152,21 @@ export function loadRunsForAgent(agent) {
|
|
|
32
152
|
} catch {
|
|
33
153
|
return [];
|
|
34
154
|
}
|
|
35
|
-
let lines = raw
|
|
36
|
-
`).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
155
|
+
let { lines, changed } = sanitizeHistoryLines(raw);
|
|
37
156
|
if (lines.length > ROTATE_READ_THRESHOLD) {
|
|
38
157
|
lines = lines.slice(-ROTATE_KEEP);
|
|
39
|
-
|
|
40
|
-
fs.writeFileSync(historyPath, `${lines.join(`
|
|
41
|
-
`)}
|
|
42
|
-
`, "utf-8");
|
|
43
|
-
} catch {}
|
|
158
|
+
changed = true;
|
|
44
159
|
}
|
|
160
|
+
try {
|
|
161
|
+
if (changed)
|
|
162
|
+
writePrivateHistory(historyPath, lines);
|
|
163
|
+
rememberHistoryFile(historyPath, lines.length);
|
|
164
|
+
} catch {}
|
|
45
165
|
return lines.map((line) => {
|
|
46
166
|
try {
|
|
47
167
|
return JSON.parse(line);
|
|
48
168
|
} catch {
|
|
49
169
|
return;
|
|
50
170
|
}
|
|
51
|
-
}).filter((entry) =>
|
|
171
|
+
}).filter((entry) => entry !== undefined && entry.agent === agent).reverse();
|
|
52
172
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
export const RUNTIME_EXTENSION_ACK_EVENT = "subagent:acknowledge-extension";
|
|
4
|
+
export const RUNTIME_EXTENSION_ACK_PATH_ENV = "DM_SUBAGENT_RUNTIME_ACKNOWLEDGED_EXTENSIONS";
|
|
5
|
+
export const MAX_RUNTIME_ACKNOWLEDGED_EXTENSION_IDS = 32;
|
|
6
|
+
export const MAX_RUNTIME_ACKNOWLEDGED_EXTENSION_ID_LENGTH = 128;
|
|
7
|
+
export const MAX_RUNTIME_ACKNOWLEDGED_EXTENSION_FILE_BYTES = 64 * 1024;
|
|
8
|
+
export function isRuntimeAcknowledgedExtensionId(value) {
|
|
9
|
+
return typeof value === "string" && value.length > 0 && value.length <= MAX_RUNTIME_ACKNOWLEDGED_EXTENSION_ID_LENGTH && /^[A-Za-z0-9._:@+-]+$/.test(value) && !value.includes("..") && !value.includes("/") && !value.includes("\\");
|
|
10
|
+
}
|
|
11
|
+
export function projectRuntimeAcknowledgedExtensions(ids) {
|
|
12
|
+
const unique = [];
|
|
13
|
+
const seen = new Set;
|
|
14
|
+
for (const id of ids) {
|
|
15
|
+
if (!isRuntimeAcknowledgedExtensionId(id) || seen.has(id))
|
|
16
|
+
continue;
|
|
17
|
+
seen.add(id);
|
|
18
|
+
unique.push(id);
|
|
19
|
+
}
|
|
20
|
+
if (unique.length === 0)
|
|
21
|
+
return;
|
|
22
|
+
return {
|
|
23
|
+
version: 1,
|
|
24
|
+
source: "child-runtime",
|
|
25
|
+
ids: unique.slice(0, MAX_RUNTIME_ACKNOWLEDGED_EXTENSION_IDS),
|
|
26
|
+
omitted: Math.max(0, unique.length - MAX_RUNTIME_ACKNOWLEDGED_EXTENSION_IDS)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function sanitizeRuntimeAcknowledgedExtensions(value) {
|
|
30
|
+
if (!value || typeof value !== "object")
|
|
31
|
+
return;
|
|
32
|
+
const raw = value;
|
|
33
|
+
if (raw.version !== 1 || raw.source !== "child-runtime" || !Array.isArray(raw.ids))
|
|
34
|
+
return;
|
|
35
|
+
const projected = projectRuntimeAcknowledgedExtensions(raw.ids);
|
|
36
|
+
if (!projected)
|
|
37
|
+
return;
|
|
38
|
+
const omitted = typeof raw.omitted === "number" && Number.isFinite(raw.omitted) ? Math.max(0, Math.floor(raw.omitted)) : 0;
|
|
39
|
+
return { ...projected, omitted: projected.omitted + omitted };
|
|
40
|
+
}
|
|
41
|
+
export function readRuntimeAcknowledgedExtensions(filePath) {
|
|
42
|
+
if (!filePath)
|
|
43
|
+
return;
|
|
44
|
+
try {
|
|
45
|
+
if (fs.statSync(filePath).size > MAX_RUNTIME_ACKNOWLEDGED_EXTENSION_FILE_BYTES)
|
|
46
|
+
return;
|
|
47
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
48
|
+
return sanitizeRuntimeAcknowledgedExtensions(parsed);
|
|
49
|
+
} catch {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function writeRuntimeAcknowledgedExtensions(filePath, ids) {
|
|
54
|
+
const projection = projectRuntimeAcknowledgedExtensions(ids);
|
|
55
|
+
try {
|
|
56
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
57
|
+
if (projection)
|
|
58
|
+
fs.writeFileSync(filePath, JSON.stringify(projection), { mode: 384 });
|
|
59
|
+
else
|
|
60
|
+
fs.rmSync(filePath, { force: true });
|
|
61
|
+
} catch {}
|
|
62
|
+
}
|