@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
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { readCompletionReplay, writeCompletionReplay } from "./completion-replay.js";
|
|
3
|
+
import { fallbackResultPayloadPathForSessionRun, resultFilePath, resultPayloadPathForSessionRun } from "./result-files.js";
|
|
4
|
+
import { parseWorkflowChildSummary } from "../../workflows/workflow-child-summary.js";
|
|
5
|
+
import { projectTimeoutRecovery } from "../shared/mutation-evidence.js";
|
|
6
|
+
function asNonEmptyString(value) {
|
|
7
|
+
return typeof value === "string" && value ? value : undefined;
|
|
8
|
+
}
|
|
9
|
+
function nonNegativeNumber(value) {
|
|
10
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
11
|
+
}
|
|
12
|
+
function projectedUsage(value) {
|
|
13
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
14
|
+
return;
|
|
15
|
+
const record = value;
|
|
16
|
+
const input = nonNegativeNumber(record.input);
|
|
17
|
+
const output = nonNegativeNumber(record.output);
|
|
18
|
+
const cacheRead = nonNegativeNumber(record.cacheRead);
|
|
19
|
+
const cacheWrite = nonNegativeNumber(record.cacheWrite);
|
|
20
|
+
const cost = nonNegativeNumber(record.cost);
|
|
21
|
+
const turns = nonNegativeNumber(record.turns);
|
|
22
|
+
if (input === undefined || output === undefined || cacheRead === undefined || cacheWrite === undefined || cost === undefined || turns === undefined || !Number.isSafeInteger(turns))
|
|
23
|
+
return;
|
|
24
|
+
return { input, output, cacheRead, cacheWrite, cost, turns };
|
|
25
|
+
}
|
|
26
|
+
function errorCode(error) {
|
|
27
|
+
return typeof error === "object" && error !== null && "code" in error ? error.code : undefined;
|
|
28
|
+
}
|
|
29
|
+
function isAccessDenied(error) {
|
|
30
|
+
const code = errorCode(error);
|
|
31
|
+
return code === "EPERM" || code === "EACCES";
|
|
32
|
+
}
|
|
33
|
+
function errorMessage(error) {
|
|
34
|
+
return error instanceof Error ? error.message : String(error);
|
|
35
|
+
}
|
|
36
|
+
export function toWaitCompletion(data, runId) {
|
|
37
|
+
const results = Array.isArray(data.results) ? data.results.flatMap((entry) => {
|
|
38
|
+
if (entry === null || typeof entry !== "object")
|
|
39
|
+
return [];
|
|
40
|
+
const child = entry;
|
|
41
|
+
const outputState = child.outputState === "present" || child.outputState === "absent" || child.outputState === "unknown" ? child.outputState : undefined;
|
|
42
|
+
const artifactPaths = child.artifactPaths !== null && typeof child.artifactPaths === "object" ? child.artifactPaths : undefined;
|
|
43
|
+
const agent = asNonEmptyString(child.agent);
|
|
44
|
+
const childRunId = asNonEmptyString(child.runId);
|
|
45
|
+
const usage = projectedUsage(child.usage);
|
|
46
|
+
const sessionFile = asNonEmptyString(child.sessionFile);
|
|
47
|
+
const error = asNonEmptyString(child.error);
|
|
48
|
+
const model = asNonEmptyString(child.model);
|
|
49
|
+
const contextOverflow = child.contextOverflow === true;
|
|
50
|
+
const timeoutRecovery = projectTimeoutRecovery(child.timeoutRecovery);
|
|
51
|
+
return [{
|
|
52
|
+
...agent ? { agent } : {},
|
|
53
|
+
...childRunId ? { runId: childRunId } : {},
|
|
54
|
+
...usage ? { usage } : {},
|
|
55
|
+
...sessionFile ? { sessionFile } : {},
|
|
56
|
+
...typeof child.success === "boolean" ? { success: child.success } : {},
|
|
57
|
+
...outputState ? { outputState } : {},
|
|
58
|
+
...error ? { error } : {},
|
|
59
|
+
...model ? { model } : {},
|
|
60
|
+
...contextOverflow ? { contextOverflow: true } : {},
|
|
61
|
+
...artifactPaths ? { artifactPaths } : {},
|
|
62
|
+
...timeoutRecovery ? { timeoutRecovery } : {}
|
|
63
|
+
}];
|
|
64
|
+
}) : undefined;
|
|
65
|
+
const agent = asNonEmptyString(data.agent);
|
|
66
|
+
const mode = asNonEmptyString(data.mode);
|
|
67
|
+
const state = asNonEmptyString(data.state);
|
|
68
|
+
const workflowChildren = parseWorkflowChildSummary(data.workflowChildren);
|
|
69
|
+
if (workflowChildren && workflowChildren.workflowRunId !== runId)
|
|
70
|
+
throw new Error("workflowChildren.workflowRunId does not match its completion run id.");
|
|
71
|
+
return {
|
|
72
|
+
runId,
|
|
73
|
+
...agent ? { agent } : {},
|
|
74
|
+
...mode ? { mode } : {},
|
|
75
|
+
...state ? { state } : {},
|
|
76
|
+
...typeof data.success === "boolean" ? { success: data.success } : {},
|
|
77
|
+
...results && results.length > 0 ? { results } : {},
|
|
78
|
+
...workflowChildren ? { workflowChildren } : {}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export function recordWaitCompletion(state, runId, data, now, ttlMs, persistence) {
|
|
82
|
+
const store = state.completedResults ??= new Map;
|
|
83
|
+
for (const [key, entry] of store) {
|
|
84
|
+
if (now - entry.seenAt > ttlMs)
|
|
85
|
+
store.delete(key);
|
|
86
|
+
}
|
|
87
|
+
let completion = toWaitCompletion(data, runId);
|
|
88
|
+
if (persistence) {
|
|
89
|
+
try {
|
|
90
|
+
completion = writeCompletionReplay({
|
|
91
|
+
...persistence,
|
|
92
|
+
runId,
|
|
93
|
+
completion,
|
|
94
|
+
data,
|
|
95
|
+
now,
|
|
96
|
+
ttlMs
|
|
97
|
+
}).completion;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error(`Failed to persist completion replay for '${runId}':`, error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
store.set(runId, { seenAt: now, completion });
|
|
103
|
+
}
|
|
104
|
+
export function collectWaitCompletions(terminal, state, resultsDir) {
|
|
105
|
+
if (terminal.length === 0)
|
|
106
|
+
return;
|
|
107
|
+
const completions = [];
|
|
108
|
+
for (const run of terminal) {
|
|
109
|
+
const recorded = state.completedResults?.get(run.id);
|
|
110
|
+
if (recorded) {
|
|
111
|
+
completions.push(recorded.completion);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const publicResultPath = resultFilePath(resultsDir, run.id);
|
|
115
|
+
let resultPath = publicResultPath;
|
|
116
|
+
try {
|
|
117
|
+
resultPath = run.sessionId ? resultPayloadPathForSessionRun(resultsDir, run.sessionId, run.id) ?? publicResultPath : publicResultPath;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (!isAccessDenied(error) || !run.sessionId)
|
|
120
|
+
throw error;
|
|
121
|
+
try {
|
|
122
|
+
resultPath = fallbackResultPayloadPathForSessionRun(resultsDir, run.sessionId, run.id) ?? publicResultPath;
|
|
123
|
+
} catch (fallbackError) {
|
|
124
|
+
throw new Error(`Failed to read subagent result '${publicResultPath}': ${errorMessage(fallbackError)}`, {
|
|
125
|
+
cause: fallbackError instanceof Error ? fallbackError : undefined
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const raw = JSON.parse(fs.readFileSync(resultPath, "utf-8"));
|
|
131
|
+
completions.push(toWaitCompletion(raw, run.id));
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (errorCode(error) !== "ENOENT") {
|
|
134
|
+
throw new Error(`Failed to read subagent result '${resultPath}': ${errorMessage(error)}`, {
|
|
135
|
+
cause: error instanceof Error ? error : undefined
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
const late = state.completedResults?.get(run.id);
|
|
139
|
+
if (late) {
|
|
140
|
+
completions.push(late.completion);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
const replay = readCompletionReplay(resultsDir, run.id, { sessionId: run.sessionId });
|
|
145
|
+
if (replay)
|
|
146
|
+
completions.push(replay.completion);
|
|
147
|
+
} catch (replayError) {
|
|
148
|
+
throw new Error(`Failed to read completion replay for '${run.id}': ${errorMessage(replayError)}`, {
|
|
149
|
+
cause: replayError instanceof Error ? replayError : undefined
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return completions.length > 0 ? completions : undefined;
|
|
155
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const WAIT_TOOL_ENABLED_ENV = "DM_SUBAGENT_WAIT_TOOL_ENABLED";
|
|
2
|
+
export const WAIT_TOOL_DEFAULT_TIMEOUT_MS_ENV = "DM_SUBAGENT_WAIT_TOOL_DEFAULT_TIMEOUT_MS";
|
|
3
|
+
const TRUE_VALUES = new Set(["1", "true", "yes", "on", "enabled"]);
|
|
4
|
+
const FALSE_VALUES = new Set(["0", "false", "no", "off", "disabled"]);
|
|
5
|
+
function environmentValue(value) {
|
|
6
|
+
if (value === undefined)
|
|
7
|
+
return;
|
|
8
|
+
const normalized = value.trim().toLowerCase();
|
|
9
|
+
if (TRUE_VALUES.has(normalized))
|
|
10
|
+
return true;
|
|
11
|
+
if (FALSE_VALUES.has(normalized))
|
|
12
|
+
return false;
|
|
13
|
+
throw new Error(`${WAIT_TOOL_ENABLED_ENV} must be one of true/false, 1/0, yes/no, on/off, or enabled/disabled.`);
|
|
14
|
+
}
|
|
15
|
+
function environmentDefaultTimeoutMs(value) {
|
|
16
|
+
if (value === undefined)
|
|
17
|
+
return;
|
|
18
|
+
const parsed = Number(value);
|
|
19
|
+
if (!Number.isInteger(parsed) || parsed < 1)
|
|
20
|
+
throw new Error(`${WAIT_TOOL_DEFAULT_TIMEOUT_MS_ENV} must be a positive integer.`);
|
|
21
|
+
return parsed;
|
|
22
|
+
}
|
|
23
|
+
function configuredValue(config) {
|
|
24
|
+
if (config === undefined)
|
|
25
|
+
return {};
|
|
26
|
+
if (typeof config === "boolean")
|
|
27
|
+
return { enabled: config };
|
|
28
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
29
|
+
throw new Error("config.waitTool must be a boolean or an object with optional enabled and defaultTimeoutMs values.");
|
|
30
|
+
}
|
|
31
|
+
const { enabled, defaultTimeoutMs } = config;
|
|
32
|
+
if (enabled !== undefined && typeof enabled !== "boolean")
|
|
33
|
+
throw new Error("config.waitTool.enabled must be a boolean.");
|
|
34
|
+
if (defaultTimeoutMs !== undefined && (!Number.isInteger(defaultTimeoutMs) || defaultTimeoutMs < 1)) {
|
|
35
|
+
throw new Error("config.waitTool.defaultTimeoutMs must be a positive integer.");
|
|
36
|
+
}
|
|
37
|
+
return { enabled, ...defaultTimeoutMs !== undefined ? { defaultTimeoutMs } : {} };
|
|
38
|
+
}
|
|
39
|
+
export function resolveWaitToolConfig(config, env = process.env) {
|
|
40
|
+
const configured = configuredValue(config);
|
|
41
|
+
const envDefaultTimeoutMs = environmentDefaultTimeoutMs(env[WAIT_TOOL_DEFAULT_TIMEOUT_MS_ENV]);
|
|
42
|
+
return {
|
|
43
|
+
enabled: environmentValue(env[WAIT_TOOL_ENABLED_ENV]) ?? configured.enabled ?? true,
|
|
44
|
+
...envDefaultTimeoutMs ?? configured.defaultTimeoutMs !== undefined ? { defaultTimeoutMs: envDefaultTimeoutMs ?? configured.defaultTimeoutMs } : {}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { listAsyncRuns } from "./async-status.js";
|
|
5
|
+
import { formatResumeFirstFailedRunDetail } from "./resume-guidance.js";
|
|
6
|
+
import { readCompletionReplay } from "./completion-replay.js";
|
|
7
|
+
import { writeAtomicJson } from "../../shared/atomic-json.js";
|
|
8
|
+
import {
|
|
9
|
+
DIRS,
|
|
10
|
+
INTERCOM_DETACH_REQUEST_EVENT,
|
|
11
|
+
SUBAGENT_ASYNC_COMPLETE_EVENT,
|
|
12
|
+
SUBAGENT_CONTROL_EVENT,
|
|
13
|
+
SUBAGENT_CONTROL_INTERCOM_EVENT,
|
|
14
|
+
SUBAGENT_FOREGROUND_COMPLETE_EVENT,
|
|
15
|
+
SUBAGENT_RESULT_INTERCOM_EVENT
|
|
16
|
+
} from "../../shared/types.js";
|
|
17
|
+
const SUBSCRIPTION_VERSION = 1;
|
|
18
|
+
const RECONCILE_INTERVAL_MS = 1000;
|
|
19
|
+
const FOREIGN_SWEEP_INTERVAL_MS = 60000;
|
|
20
|
+
const FOREIGN_SWEEP_GRACE_MS = 24 * 60 * 60 * 1000;
|
|
21
|
+
function isNotFound(error) {
|
|
22
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
23
|
+
}
|
|
24
|
+
function parseRecord(value) {
|
|
25
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
26
|
+
return;
|
|
27
|
+
const record = value;
|
|
28
|
+
if (record.version !== SUBSCRIPTION_VERSION || typeof record.token !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(record.token) || typeof record.sessionId !== "string" || record.targetKind !== "async" && record.targetKind !== "foreground" || typeof record.runId !== "string" || typeof record.requestedId !== "string" || typeof record.createdAt !== "number" || typeof record.expiresAt !== "number")
|
|
29
|
+
return;
|
|
30
|
+
return record;
|
|
31
|
+
}
|
|
32
|
+
function needsAttention(run) {
|
|
33
|
+
return run.activityState === "needs_attention" || run.steps.some((step) => step.activityState === "needs_attention");
|
|
34
|
+
}
|
|
35
|
+
function subscriptionFile(dir, token) {
|
|
36
|
+
return path.join(dir, `${token}.json`);
|
|
37
|
+
}
|
|
38
|
+
export function formatWaitSubscriptions(state, now = Date.now()) {
|
|
39
|
+
const subscriptions = [...state.waitSubscriptions?.values() ?? []].sort((left, right) => left.createdAt - right.createdAt);
|
|
40
|
+
if (subscriptions.length === 0)
|
|
41
|
+
return;
|
|
42
|
+
const lines = [`Armed wait subscriptions (${subscriptions.length}):`];
|
|
43
|
+
for (const record of subscriptions) {
|
|
44
|
+
lines.push(`- ${record.token}: ${record.targetKind} run ${record.runId}, timeout in ${Math.max(0, record.expiresAt - now)}ms`);
|
|
45
|
+
}
|
|
46
|
+
return lines.join(`
|
|
47
|
+
`);
|
|
48
|
+
}
|
|
49
|
+
export function createWaitSubscriptionManager(dm, state, options = {}) {
|
|
50
|
+
const asyncDirRoot = options.asyncDirRoot ?? DIRS.async;
|
|
51
|
+
const resultsDir = options.resultsDir ?? DIRS.results;
|
|
52
|
+
const subscriptionsDir = options.subscriptionsDir ?? path.join(path.dirname(asyncDirRoot), "wait-subscriptions");
|
|
53
|
+
const now = options.now ?? Date.now;
|
|
54
|
+
const subscriptions = state.waitSubscriptions ?? new Map;
|
|
55
|
+
state.waitSubscriptions = subscriptions;
|
|
56
|
+
const unresolvedRestoredForegroundTokens = new Set;
|
|
57
|
+
let disposed = false;
|
|
58
|
+
let lastForeignSweepAt = 0;
|
|
59
|
+
const sweepExpiredForeignSubscriptions = (force = false) => {
|
|
60
|
+
const sweptAt = now();
|
|
61
|
+
if (!force && sweptAt - lastForeignSweepAt < FOREIGN_SWEEP_INTERVAL_MS)
|
|
62
|
+
return;
|
|
63
|
+
lastForeignSweepAt = sweptAt;
|
|
64
|
+
let files;
|
|
65
|
+
try {
|
|
66
|
+
files = fs.readdirSync(subscriptionsDir).filter((file) => file.endsWith(".json"));
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (!isNotFound(error))
|
|
69
|
+
console.error(`Failed to scan wait subscriptions in '${subscriptionsDir}':`, error);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const file of files) {
|
|
73
|
+
const filePath = path.join(subscriptionsDir, file);
|
|
74
|
+
let record;
|
|
75
|
+
try {
|
|
76
|
+
record = parseRecord(JSON.parse(fs.readFileSync(filePath, "utf-8")));
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error(`Ignoring invalid wait subscription '${filePath}':`, error);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (!record || file !== `${record.token}.json`)
|
|
82
|
+
continue;
|
|
83
|
+
if (record.sessionId === state.currentSessionId)
|
|
84
|
+
continue;
|
|
85
|
+
if (sweptAt < record.expiresAt + FOREIGN_SWEEP_GRACE_MS)
|
|
86
|
+
continue;
|
|
87
|
+
try {
|
|
88
|
+
fs.unlinkSync(filePath);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (!isNotFound(error))
|
|
91
|
+
console.error(`Failed to remove expired wait subscription '${filePath}':`, error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const remove = (record) => {
|
|
96
|
+
try {
|
|
97
|
+
fs.unlinkSync(subscriptionFile(subscriptionsDir, record.token));
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (!isNotFound(error))
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
subscriptions.delete(record.token);
|
|
103
|
+
unresolvedRestoredForegroundTokens.delete(record.token);
|
|
104
|
+
};
|
|
105
|
+
const settle = (record, outcome, detail, completion) => {
|
|
106
|
+
if (disposed || state.currentSessionId !== record.sessionId)
|
|
107
|
+
return;
|
|
108
|
+
try {
|
|
109
|
+
remove(record);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error(`Failed to clear wait subscription '${record.token}'; it remains armed:`, error);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
dm.sendMessage({
|
|
116
|
+
customType: "subagent-wait-subscription",
|
|
117
|
+
content: `Wait subscription ${record.token} fired for run ${record.runId}: ${outcome}. ${detail}`,
|
|
118
|
+
display: true,
|
|
119
|
+
details: {
|
|
120
|
+
token: record.token,
|
|
121
|
+
runId: record.runId,
|
|
122
|
+
outcome,
|
|
123
|
+
...completion ? { completions: [completion] } : {}
|
|
124
|
+
}
|
|
125
|
+
}, { triggerTurn: true });
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error(`Failed to deliver wait subscription '${record.token}' after clearing it:`, error);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const reconcileRecord = (record) => {
|
|
131
|
+
if (record.sessionId !== state.currentSessionId)
|
|
132
|
+
return;
|
|
133
|
+
if (now() >= record.expiresAt) {
|
|
134
|
+
settle(record, "timed out", "The targeted run may still be active; inspect its status before taking follow-up action.");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (record.targetKind === "foreground") {
|
|
138
|
+
const run = state.foregroundRuns?.get(record.runId);
|
|
139
|
+
if (!run && unresolvedRestoredForegroundTokens.has(record.token))
|
|
140
|
+
return;
|
|
141
|
+
if (run)
|
|
142
|
+
unresolvedRestoredForegroundTokens.delete(record.token);
|
|
143
|
+
if (!run || run.sessionId !== record.sessionId) {
|
|
144
|
+
settle(record, "could not be reconciled", "The remembered foreground run disappeared before completion was confirmed.");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const detached = run.children.filter((child) => child.status === "detached");
|
|
148
|
+
if (detached.some((child) => child.activityState === "needs_attention" && child.currentTool === "contact_supervisor")) {
|
|
149
|
+
settle(record, "needs attention", "Reply to the pending supervisor request or inspect the run status.");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (detached.length === 0) {
|
|
153
|
+
const failed = run.children.some((child) => child.status === "failed");
|
|
154
|
+
settle(record, failed ? "failed" : "completed", "Inspect the run status for its final output.");
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const runs = listAsyncRuns(asyncDirRoot, {
|
|
159
|
+
sessionId: record.sessionId,
|
|
160
|
+
runId: record.runId,
|
|
161
|
+
exactRunId: true,
|
|
162
|
+
resultsDir,
|
|
163
|
+
kill: options.kill,
|
|
164
|
+
now
|
|
165
|
+
});
|
|
166
|
+
const run = runs.find((candidate) => candidate.id === record.runId);
|
|
167
|
+
if (!run) {
|
|
168
|
+
settle(record, "could not be reconciled", "The exact async run disappeared before a terminal result was confirmed.");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (needsAttention(run)) {
|
|
172
|
+
settle(record, "needs attention", "Inspect the run status and answer any pending supervisor request.");
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (run.state !== "queued" && run.state !== "running") {
|
|
176
|
+
let completion;
|
|
177
|
+
try {
|
|
178
|
+
completion = readCompletionReplay(resultsDir, record.runId, { sessionId: record.sessionId, now: now() })?.completion;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error(`Failed to read completion replay for wait subscription '${record.token}':`, error);
|
|
181
|
+
}
|
|
182
|
+
const detail = formatResumeFirstFailedRunDetail(run) ?? "Inspect the run status for its final output.";
|
|
183
|
+
const archiveDetail = completion?.archivePath ? ` Completion archive: ${completion.archivePath}.` : "";
|
|
184
|
+
settle(record, run.state === "complete" ? "completed" : run.state, `${detail}${archiveDetail}`, completion);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const reconcile = () => {
|
|
188
|
+
if (disposed)
|
|
189
|
+
return;
|
|
190
|
+
sweepExpiredForeignSubscriptions();
|
|
191
|
+
if (!state.currentSessionId)
|
|
192
|
+
return;
|
|
193
|
+
for (const record of [...subscriptions.values()]) {
|
|
194
|
+
try {
|
|
195
|
+
reconcileRecord(record);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.error(`Failed to reconcile wait subscription '${record.token}':`, error);
|
|
198
|
+
settle(record, "reconciliation failed", "The targeted run could not be reconciled. Inspect its status before taking follow-up action.");
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const wakeChannels = [
|
|
203
|
+
INTERCOM_DETACH_REQUEST_EVENT,
|
|
204
|
+
SUBAGENT_ASYNC_COMPLETE_EVENT,
|
|
205
|
+
SUBAGENT_FOREGROUND_COMPLETE_EVENT,
|
|
206
|
+
SUBAGENT_CONTROL_EVENT,
|
|
207
|
+
SUBAGENT_CONTROL_INTERCOM_EVENT,
|
|
208
|
+
SUBAGENT_RESULT_INTERCOM_EVENT
|
|
209
|
+
];
|
|
210
|
+
const unsubscribes = wakeChannels.map((channel) => dm.events.on(channel, reconcile));
|
|
211
|
+
const interval = setInterval(reconcile, options.pollIntervalMs ?? RECONCILE_INTERVAL_MS);
|
|
212
|
+
interval.unref?.();
|
|
213
|
+
return {
|
|
214
|
+
arm(input) {
|
|
215
|
+
const sessionId = state.currentSessionId;
|
|
216
|
+
if (!sessionId)
|
|
217
|
+
throw new Error("A wait subscription requires an active session identity.");
|
|
218
|
+
const createdAt = now();
|
|
219
|
+
const record = {
|
|
220
|
+
version: SUBSCRIPTION_VERSION,
|
|
221
|
+
token: randomUUID(),
|
|
222
|
+
sessionId,
|
|
223
|
+
targetKind: input.targetKind,
|
|
224
|
+
runId: input.runId,
|
|
225
|
+
requestedId: input.requestedId,
|
|
226
|
+
createdAt,
|
|
227
|
+
expiresAt: createdAt + input.timeoutMs
|
|
228
|
+
};
|
|
229
|
+
fs.mkdirSync(subscriptionsDir, { recursive: true });
|
|
230
|
+
writeAtomicJson(subscriptionFile(subscriptionsDir, record.token), record);
|
|
231
|
+
subscriptions.set(record.token, record);
|
|
232
|
+
return record;
|
|
233
|
+
},
|
|
234
|
+
restore() {
|
|
235
|
+
subscriptions.clear();
|
|
236
|
+
unresolvedRestoredForegroundTokens.clear();
|
|
237
|
+
sweepExpiredForeignSubscriptions(true);
|
|
238
|
+
if (!state.currentSessionId)
|
|
239
|
+
return;
|
|
240
|
+
let files;
|
|
241
|
+
try {
|
|
242
|
+
files = fs.readdirSync(subscriptionsDir).filter((file) => file.endsWith(".json"));
|
|
243
|
+
} catch (error) {
|
|
244
|
+
if (isNotFound(error))
|
|
245
|
+
return;
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
for (const file of files) {
|
|
249
|
+
try {
|
|
250
|
+
const record = parseRecord(JSON.parse(fs.readFileSync(path.join(subscriptionsDir, file), "utf-8")));
|
|
251
|
+
if (!record || file !== `${record.token}.json`)
|
|
252
|
+
continue;
|
|
253
|
+
if (record.sessionId === state.currentSessionId) {
|
|
254
|
+
subscriptions.set(record.token, record);
|
|
255
|
+
if (record.targetKind === "foreground" && !state.foregroundRuns?.has(record.runId))
|
|
256
|
+
unresolvedRestoredForegroundTokens.add(record.token);
|
|
257
|
+
}
|
|
258
|
+
} catch (error) {
|
|
259
|
+
console.error(`Ignoring invalid wait subscription '${path.join(subscriptionsDir, file)}':`, error);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
reconcile();
|
|
263
|
+
},
|
|
264
|
+
reconcile,
|
|
265
|
+
dispose() {
|
|
266
|
+
if (disposed)
|
|
267
|
+
return;
|
|
268
|
+
disposed = true;
|
|
269
|
+
clearInterval(interval);
|
|
270
|
+
for (const unsubscribe of unsubscribes) {
|
|
271
|
+
try {
|
|
272
|
+
unsubscribe();
|
|
273
|
+
} catch {}
|
|
274
|
+
}
|
|
275
|
+
subscriptions.clear();
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { SubagentWaitParams } from "../../extension/schemas.js";
|
|
2
|
+
import { resolveWaitToolConfig, waitForSubagents } from "./subagent-wait.js";
|
|
3
|
+
import { finalizeToolResult } from "../../extension/tool-result.js";
|
|
4
|
+
export function registerWaitTool(dm, state, enabled = resolveWaitToolConfig().enabled, subscriptions, defaultTimeoutMs) {
|
|
5
|
+
const description = `Wait for background, provider, or detached work that has no native completion notification, then return.
|
|
6
|
+
|
|
7
|
+
Ordinary async subagent runs already notify this session natively when they complete or need attention. In an interactive chat, return control instead of calling this merely to wait. Use this tool for provider jobs, remembered detached foreground runs, or other background work without a native notification path. Headless runs auto-drain current-session subagent work at agent_end; use this tool only when the current turn must receive non-notifying background work results.
|
|
8
|
+
|
|
9
|
+
• { } — return when the first initially active async run or registered provider item finishes, or when a subagent needs attention.
|
|
10
|
+
• { all: true } — wait for every async run and provider item that was active when the call began.
|
|
11
|
+
• { id: "..." } — wait for one async or remembered detached foreground subagent run (id or prefix).
|
|
12
|
+
• { id: "...", nonBlocking: true } — resolve the prefix once, persist an exact-run wake subscription, and return immediately. Use this for detached work without native completion delivery; the originating interactive session wakes on completion, failure, attention, reconciliation failure, or timeout.
|
|
13
|
+
• { stopOnAttention: false } — for blocking waits only, keep waiting through idle or long-thinking attention; supervisor/contact requests still stop the wait.
|
|
14
|
+
• { timeoutMs: 600000 } — stop waiting after N ms; active work keeps running. Omitted values use waitTool.defaultTimeoutMs, then 30 minutes. Window expiry returns a non-error window_elapsed result with active work identities.
|
|
15
|
+
|
|
16
|
+
Non-blocking subscriptions are visible in subagent status and differ from disabling waitTool: waitTool.enabled=false returns immediately without registering any future wake. Provider jobs are session-scoped and identified exactly, so replacing one job with another cannot hide a completion. Provider extensions must be explicitly loaded in this process. In a child agent, keep \`bg_wait\` (or the deprecated \`subagent_wait\` compatibility alias) in the child tool allowlist and load each provider through the agent's extensions or subagentOnlyExtensions; this tool never loads providers or grants tools itself.${enabled ? "" : `
|
|
17
|
+
|
|
18
|
+
Configured behavior: bg_wait and its subagent_wait compatibility alias are disabled by config.waitTool or DM_SUBAGENT_WAIT_TOOL_ENABLED and return immediately without blocking.`}`;
|
|
19
|
+
const execute = async (_id, params, signal, onUpdate, ctx) => finalizeToolResult(await waitForSubagents(params, signal, {
|
|
20
|
+
state,
|
|
21
|
+
events: dm.events,
|
|
22
|
+
enabled,
|
|
23
|
+
...defaultTimeoutMs !== undefined ? { defaultTimeoutMs } : {},
|
|
24
|
+
onUpdate,
|
|
25
|
+
...subscriptions && ctx?.hasUI ? { subscribe: (input) => subscriptions.arm(input) } : {}
|
|
26
|
+
}));
|
|
27
|
+
const primaryTool = {
|
|
28
|
+
name: "bg_wait",
|
|
29
|
+
label: "Background Wait",
|
|
30
|
+
description,
|
|
31
|
+
parameters: SubagentWaitParams,
|
|
32
|
+
execute
|
|
33
|
+
};
|
|
34
|
+
dm.registerTool(primaryTool);
|
|
35
|
+
dm.registerTool({
|
|
36
|
+
...primaryTool,
|
|
37
|
+
name: "wait",
|
|
38
|
+
label: "Wait",
|
|
39
|
+
description: "Compatibility alias for `bg_wait`."
|
|
40
|
+
});
|
|
41
|
+
dm.registerTool({
|
|
42
|
+
...primaryTool,
|
|
43
|
+
name: "subagent_wait",
|
|
44
|
+
label: "Subagent Wait (deprecated)",
|
|
45
|
+
description: "Deprecated compatibility alias for `bg_wait`. Use `bg_wait` for background, provider, or detached work without a native completion notification. It has the same parameters and behavior."
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { writeAtomicJson } from "../../shared/atomic-json.js";
|
|
4
|
+
import { DIRS } from "../../shared/types.js";
|
|
5
|
+
import { readStatus } from "../../shared/utils.js";
|
|
6
|
+
import { updateActiveRunIndex } from "../background/active-run-index.js";
|
|
7
|
+
import { reconcileAsyncRun } from "../background/stale-run-reconciler.js";
|
|
8
|
+
import { resultFilePath } from "../background/result-files.js";
|
|
9
|
+
export function dismissRecoveredWorkflow(state, location) {
|
|
10
|
+
const asyncDir = location.asyncDir;
|
|
11
|
+
const runId = location.resolvedId ?? (asyncDir ? path.basename(asyncDir) : "requested");
|
|
12
|
+
if (!asyncDir) {
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: "text", text: `Recovered workflow '${runId}' has no disk status to dismiss.` }],
|
|
15
|
+
isError: true,
|
|
16
|
+
details: { mode: "management", results: [] }
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const status = readStatus(asyncDir);
|
|
20
|
+
if (!status || status.mode !== "workflow") {
|
|
21
|
+
return {
|
|
22
|
+
content: [{ type: "text", text: `Run '${runId}' is not a recovered workflow.` }],
|
|
23
|
+
isError: true,
|
|
24
|
+
details: { mode: "management", results: [] }
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (!state.currentSessionId || status.sessionId !== state.currentSessionId) {
|
|
28
|
+
return {
|
|
29
|
+
content: [{ type: "text", text: `Recovered workflow '${runId}' was not found in the active session.` }],
|
|
30
|
+
isError: true,
|
|
31
|
+
details: { mode: "management", results: [] }
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (state.workflowControllers?.has(status.runId) || state.workflowControllers?.has(runId)) {
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: "text", text: `Workflow '${runId}' still has a live controller and cannot be dismissed.` }],
|
|
37
|
+
isError: true,
|
|
38
|
+
details: { mode: "management", results: [] }
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (status.state !== "running") {
|
|
42
|
+
return {
|
|
43
|
+
content: [{ type: "text", text: `Recovered workflow '${runId}' is ${status.state}, not running.` }],
|
|
44
|
+
isError: true,
|
|
45
|
+
details: { mode: "management", results: [] }
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
let latestStatus = status;
|
|
49
|
+
const resultPath = resultFilePath(DIRS.results, status.runId);
|
|
50
|
+
if (fs.existsSync(resultPath)) {
|
|
51
|
+
const reconciled = reconcileAsyncRun(asyncDir).status;
|
|
52
|
+
if (reconciled && reconciled.state !== "running") {
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: "text", text: `Recovered workflow '${runId}' is ${reconciled.state}, not running.` }],
|
|
55
|
+
isError: true,
|
|
56
|
+
details: { mode: "management", results: [] }
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (reconciled)
|
|
60
|
+
latestStatus = reconciled;
|
|
61
|
+
}
|
|
62
|
+
const dismissed = { ...latestStatus, displayDismissedAt: Date.now() };
|
|
63
|
+
writeAtomicJson(path.join(asyncDir, "status.json"), dismissed);
|
|
64
|
+
const repaired = reconcileAsyncRun(asyncDir).status;
|
|
65
|
+
if (repaired && repaired.state !== "running") {
|
|
66
|
+
return {
|
|
67
|
+
content: [{ type: "text", text: `Recovered workflow '${runId}' is ${repaired.state}, not running.` }],
|
|
68
|
+
isError: true,
|
|
69
|
+
details: { mode: "management", results: [] }
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
updateActiveRunIndex(asyncDir, "complete");
|
|
73
|
+
state.asyncJobs.delete(status.runId);
|
|
74
|
+
state.asyncJobs.delete(runId);
|
|
75
|
+
state.fleetJobs?.delete(status.runId);
|
|
76
|
+
state.fleetJobs?.delete(runId);
|
|
77
|
+
return {
|
|
78
|
+
content: [{ type: "text", text: `Dismissed recovered workflow ${status.runId} from the display. No running work was terminated.` }],
|
|
79
|
+
details: { mode: "management", results: [] }
|
|
80
|
+
};
|
|
81
|
+
}
|