@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
|
@@ -3,9 +3,28 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { writeAtomicJson } from "../../shared/atomic-json.js";
|
|
5
5
|
import { POLL_INTERVAL_MS } from "../../shared/types.js";
|
|
6
|
-
|
|
6
|
+
import { shouldUseNativeFsWatch } from "../../shared/watch-strategy.js";
|
|
7
|
+
import { resolveWatchPath } from "../../shared/utils.js";
|
|
8
|
+
function writeJsonToExistingDir(filePath, payload) {
|
|
9
|
+
const tempPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`);
|
|
10
|
+
try {
|
|
11
|
+
fs.writeFileSync(tempPath, JSON.stringify(payload, null, 2), { encoding: "utf-8", flag: "wx" });
|
|
12
|
+
fs.renameSync(tempPath, filePath);
|
|
13
|
+
} finally {
|
|
14
|
+
fs.rmSync(tempPath, { force: true });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const CONTROL_SAFETY_POLL_INTERVAL_MS = 5000;
|
|
7
18
|
const STEER_REQUESTS_DIR = "steer-requests";
|
|
19
|
+
const STOP_REQUESTS_DIR = "stop-requests";
|
|
20
|
+
const REVIVAL_BRIEFS_DIR = "revival-briefs";
|
|
21
|
+
export const MAX_STEER_QUEUE_SIZE = 20;
|
|
8
22
|
const STEER_TARGETS_DIR = "steer-targets";
|
|
23
|
+
const STEER_CAPABILITIES_DIR = "steer-capabilities";
|
|
24
|
+
const STEER_ACKS_DIR = "steer-acks";
|
|
25
|
+
const STEER_INBOX_CLOSED_FILE = "steer-inbox-closed.json";
|
|
26
|
+
const MAX_STEER_MESSAGE_BYTES = 128 * 1024;
|
|
27
|
+
const MAX_STEER_REQUEST_ID_LENGTH = 256;
|
|
9
28
|
export function controlInboxDir(asyncDir) {
|
|
10
29
|
return path.join(asyncDir, "control");
|
|
11
30
|
}
|
|
@@ -15,20 +34,114 @@ export function interruptRequestPath(asyncDir) {
|
|
|
15
34
|
export function timeoutRequestPath(asyncDir) {
|
|
16
35
|
return path.join(controlInboxDir(asyncDir), "timeout.json");
|
|
17
36
|
}
|
|
37
|
+
export function stopRequestPath(asyncDir) {
|
|
38
|
+
return path.join(controlInboxDir(asyncDir), "stop.json");
|
|
39
|
+
}
|
|
40
|
+
export function stopRequestsDir(asyncDir) {
|
|
41
|
+
return path.join(controlInboxDir(asyncDir), STOP_REQUESTS_DIR);
|
|
42
|
+
}
|
|
18
43
|
export function steerRequestsDir(asyncDir) {
|
|
19
44
|
return path.join(controlInboxDir(asyncDir), STEER_REQUESTS_DIR);
|
|
20
45
|
}
|
|
46
|
+
export function steerInboxClosedPath(asyncDir) {
|
|
47
|
+
return path.join(controlInboxDir(asyncDir), STEER_INBOX_CLOSED_FILE);
|
|
48
|
+
}
|
|
49
|
+
export function closeSteerInbox(asyncDir, state, write = writeAtomicJson) {
|
|
50
|
+
write(steerInboxClosedPath(asyncDir), { version: 1, closedAt: Date.now(), state });
|
|
51
|
+
}
|
|
21
52
|
export function stepSteerInboxDir(asyncDir, index) {
|
|
53
|
+
assertChildIndex(index);
|
|
22
54
|
return path.join(controlInboxDir(asyncDir), STEER_TARGETS_DIR, String(index));
|
|
23
55
|
}
|
|
56
|
+
export function steerCapabilitiesDir(asyncDir) {
|
|
57
|
+
return path.join(controlInboxDir(asyncDir), STEER_CAPABILITIES_DIR);
|
|
58
|
+
}
|
|
59
|
+
export function steerCapabilityPath(asyncDir, index) {
|
|
60
|
+
assertChildIndex(index);
|
|
61
|
+
return path.join(steerCapabilitiesDir(asyncDir), `${index}.json`);
|
|
62
|
+
}
|
|
63
|
+
export function steerAcksDir(asyncDir, index) {
|
|
64
|
+
assertChildIndex(index);
|
|
65
|
+
return path.join(controlInboxDir(asyncDir), STEER_ACKS_DIR, String(index));
|
|
66
|
+
}
|
|
67
|
+
function steerAckFileName(requestId) {
|
|
68
|
+
return `${Buffer.from(requestId).toString("base64url")}.json`;
|
|
69
|
+
}
|
|
70
|
+
export function steerAckPathFromDir(dir, requestId) {
|
|
71
|
+
if (!/^[^\s]+$/.test(requestId) || requestId.length > 256)
|
|
72
|
+
throw new Error("steer acknowledgment requestId is invalid.");
|
|
73
|
+
return path.join(dir, steerAckFileName(requestId));
|
|
74
|
+
}
|
|
75
|
+
function assertChildIndex(index) {
|
|
76
|
+
if (!Number.isInteger(index) || index < 0 || index > 1e6)
|
|
77
|
+
throw new Error("child index must be a non-negative integer.");
|
|
78
|
+
}
|
|
79
|
+
function validStopChildId(childId) {
|
|
80
|
+
return typeof childId === "string" && Boolean(childId.trim()) && childId.length <= 256 && !/[\r\n]/.test(childId);
|
|
81
|
+
}
|
|
24
82
|
function steerRequestFileName(request) {
|
|
25
83
|
return `${String(request.ts).padStart(13, "0")}-${Buffer.from(request.id).toString("base64url")}.json`;
|
|
26
84
|
}
|
|
85
|
+
function stopRequestFileName(request) {
|
|
86
|
+
return `${String(request.ts ?? 0).padStart(13, "0")}-${randomUUID()}.json`;
|
|
87
|
+
}
|
|
88
|
+
function validSteerRequest(request) {
|
|
89
|
+
return request.type === "steer" && typeof request.id === "string" && /^[^\s]+$/.test(request.id) && request.id.length <= MAX_STEER_REQUEST_ID_LENGTH && typeof request.ts === "number" && Number.isFinite(request.ts) && request.ts > 0 && typeof request.message === "string" && Boolean(request.message.trim()) && Buffer.byteLength(request.message, "utf8") <= MAX_STEER_MESSAGE_BYTES && (request.mode === undefined || request.mode === "steer" || request.mode === "follow_up" || request.mode === "auto") && (request.targetIndex === undefined || Number.isInteger(request.targetIndex) && request.targetIndex >= 0 && request.targetIndex <= 1e6) && (request.targetIndexes === undefined || request.targetIndex === undefined && Array.isArray(request.targetIndexes) && request.targetIndexes.length > 0 && request.targetIndexes.length <= 1000 && request.targetIndexes.every((index) => Number.isInteger(index) && index >= 0 && index <= 1e6) && new Set(request.targetIndexes).size === request.targetIndexes.length) && (request.source === undefined || typeof request.source === "string" && Boolean(request.source.trim()) && request.source.length <= 256);
|
|
90
|
+
}
|
|
27
91
|
export function writeSteerRequestToDir(dir, request) {
|
|
92
|
+
if (!validSteerRequest(request))
|
|
93
|
+
throw new Error("steer request is malformed or exceeds transport limits.");
|
|
28
94
|
const requestPath = path.join(dir, steerRequestFileName(request));
|
|
29
95
|
writeAtomicJson(requestPath, request);
|
|
30
96
|
return requestPath;
|
|
31
97
|
}
|
|
98
|
+
export function writeSteerRequestToExistingDir(dir, request) {
|
|
99
|
+
if (!validSteerRequest(request))
|
|
100
|
+
throw new Error("steer request is malformed or exceeds transport limits.");
|
|
101
|
+
const requestPath = path.join(dir, steerRequestFileName(request));
|
|
102
|
+
writeJsonToExistingDir(requestPath, request);
|
|
103
|
+
return requestPath;
|
|
104
|
+
}
|
|
105
|
+
export function writeSteerCapabilityAt(filePath, capability) {
|
|
106
|
+
assertChildIndex(capability.index);
|
|
107
|
+
if (!Number.isInteger(capability.pid) || capability.pid <= 0)
|
|
108
|
+
throw new Error("steer capability pid must be a positive integer.");
|
|
109
|
+
if (!Number.isFinite(capability.readyAt) || capability.readyAt <= 0)
|
|
110
|
+
throw new Error("steer capability readyAt must be a finite timestamp.");
|
|
111
|
+
const record = { type: "steer-capability", protocolVersion: 1, ...capability };
|
|
112
|
+
writeAtomicJson(filePath, record);
|
|
113
|
+
return filePath;
|
|
114
|
+
}
|
|
115
|
+
export function writeSteerCapability(asyncDir, capability) {
|
|
116
|
+
return writeSteerCapabilityAt(steerCapabilityPath(asyncDir, capability.index), capability);
|
|
117
|
+
}
|
|
118
|
+
function steerAckWritePath(filePath, ack) {
|
|
119
|
+
const parsed = path.parse(filePath);
|
|
120
|
+
const stateOrder = ack.state === "queued" ? "0" : ack.state === "delivered" ? "1" : "2";
|
|
121
|
+
const timestamp = String(Math.trunc(ack.ts)).padStart(13, "0");
|
|
122
|
+
for (let suffix = 0;suffix < 1000; suffix += 1) {
|
|
123
|
+
const candidate = path.join(parsed.dir, `${parsed.name}-${timestamp}-${stateOrder}-${ack.state}${suffix === 0 ? "" : `-${suffix}`}${parsed.ext}`);
|
|
124
|
+
if (!fs.existsSync(candidate))
|
|
125
|
+
return candidate;
|
|
126
|
+
}
|
|
127
|
+
throw new Error("steer acknowledgment queue is full.");
|
|
128
|
+
}
|
|
129
|
+
export function writeSteerAckAt(filePath, ack) {
|
|
130
|
+
assertChildIndex(ack.index);
|
|
131
|
+
if (!/^[^\s]+$/.test(ack.requestId) || ack.requestId.length > 256)
|
|
132
|
+
throw new Error("steer acknowledgment requestId is invalid.");
|
|
133
|
+
if (!Number.isFinite(ack.ts) || ack.ts <= 0)
|
|
134
|
+
throw new Error("steer acknowledgment ts must be a finite timestamp.");
|
|
135
|
+
if (!ack.message.trim() || ack.message.length > 1000)
|
|
136
|
+
throw new Error("steer acknowledgment message is invalid.");
|
|
137
|
+
const record = { type: "steer-ack", protocolVersion: 1, ...ack, message: ack.message.trim() };
|
|
138
|
+
const ackPath = steerAckWritePath(filePath, ack);
|
|
139
|
+
writeAtomicJson(ackPath, record);
|
|
140
|
+
return ackPath;
|
|
141
|
+
}
|
|
142
|
+
export function writeSteerAck(asyncDir, ack) {
|
|
143
|
+
return writeSteerAckAt(path.join(steerAcksDir(asyncDir, ack.index), steerAckFileName(ack.requestId)), ack);
|
|
144
|
+
}
|
|
32
145
|
export function requestAsyncInterrupt(asyncDir, payload = {}, deps = {}) {
|
|
33
146
|
const requestPath = interruptRequestPath(asyncDir);
|
|
34
147
|
const request = { ...payload, ts: payload.ts ?? deps.now?.() ?? Date.now(), type: "interrupt" };
|
|
@@ -41,56 +154,199 @@ export function requestAsyncTimeout(asyncDir, payload = {}, deps = {}) {
|
|
|
41
154
|
writeAtomicJson(requestPath, request);
|
|
42
155
|
return requestPath;
|
|
43
156
|
}
|
|
157
|
+
export function requestAsyncStop(asyncDir, payload = {}, deps = {}) {
|
|
158
|
+
if (payload.targetIndex !== undefined)
|
|
159
|
+
assertChildIndex(payload.targetIndex);
|
|
160
|
+
if (payload.childId !== undefined && !validStopChildId(payload.childId)) {
|
|
161
|
+
throw new Error("stop childId must be a non-empty string without newlines and at most 256 characters.");
|
|
162
|
+
}
|
|
163
|
+
const request = { ...payload, ts: payload.ts ?? deps.now?.() ?? Date.now(), type: "stop" };
|
|
164
|
+
const requestPath = path.join(stopRequestsDir(asyncDir), stopRequestFileName(request));
|
|
165
|
+
writeAtomicJson(requestPath, request);
|
|
166
|
+
return requestPath;
|
|
167
|
+
}
|
|
44
168
|
export function requestAsyncSteer(asyncDir, payload, deps = {}) {
|
|
45
169
|
const message = payload.message.trim();
|
|
46
170
|
if (!message)
|
|
47
171
|
throw new Error("steer message must not be empty.");
|
|
48
|
-
if (
|
|
49
|
-
throw new Error(
|
|
172
|
+
if (Buffer.byteLength(message, "utf8") > MAX_STEER_MESSAGE_BYTES)
|
|
173
|
+
throw new Error(`steer message exceeds ${MAX_STEER_MESSAGE_BYTES} UTF-8 bytes.`);
|
|
174
|
+
if (payload.targetIndex !== undefined && (!Number.isInteger(payload.targetIndex) || payload.targetIndex < 0 || payload.targetIndex > 1e6)) {
|
|
175
|
+
throw new Error("steer targetIndex must be an integer between 0 and 1000000.");
|
|
176
|
+
}
|
|
177
|
+
if (payload.targetIndexes !== undefined && (!Array.isArray(payload.targetIndexes) || payload.targetIndex !== undefined || payload.targetIndexes.length === 0 || payload.targetIndexes.length > 1000 || payload.targetIndexes.some((index) => !Number.isInteger(index) || index < 0 || index > 1e6) || new Set(payload.targetIndexes).size !== payload.targetIndexes.length)) {
|
|
178
|
+
throw new Error("steer targetIndexes must contain 1-1000 unique non-negative integers and cannot be combined with targetIndex.");
|
|
50
179
|
}
|
|
180
|
+
const closedPath = steerInboxClosedPath(asyncDir);
|
|
181
|
+
if (fs.existsSync(closedPath))
|
|
182
|
+
throw new Error("Async run no longer accepts steering requests.");
|
|
51
183
|
const request = {
|
|
52
184
|
type: "steer",
|
|
53
185
|
id: payload.id ?? deps.randomId?.() ?? randomUUID(),
|
|
54
186
|
ts: payload.ts ?? deps.now?.() ?? Date.now(),
|
|
55
187
|
message,
|
|
188
|
+
...payload.mode && payload.mode !== "steer" ? { mode: payload.mode } : {},
|
|
56
189
|
...payload.targetIndex !== undefined ? { targetIndex: payload.targetIndex } : {},
|
|
190
|
+
...payload.targetIndexes !== undefined ? { targetIndexes: [...payload.targetIndexes] } : {},
|
|
57
191
|
...payload.source ? { source: payload.source } : {}
|
|
58
192
|
};
|
|
59
|
-
|
|
193
|
+
const requestPath = writeSteerRequestToDir(steerRequestsDir(asyncDir), request);
|
|
194
|
+
if (fs.existsSync(closedPath)) {
|
|
195
|
+
fs.rmSync(requestPath, { force: true });
|
|
196
|
+
throw new Error("Async run stopped accepting steering before the request was committed.");
|
|
197
|
+
}
|
|
198
|
+
return requestPath;
|
|
60
199
|
}
|
|
61
200
|
export function enqueueStepSteer(asyncDir, index, request) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return writeSteerRequestToDir(stepSteerInboxDir(asyncDir, index), { ...
|
|
201
|
+
assertChildIndex(index);
|
|
202
|
+
const { targetIndexes: _targetIndexes, ...singleTargetRequest } = request;
|
|
203
|
+
return writeSteerRequestToDir(stepSteerInboxDir(asyncDir, index), { ...singleTargetRequest, targetIndex: index, type: "steer" });
|
|
65
204
|
}
|
|
66
|
-
function
|
|
205
|
+
function parseSteerCapability(raw) {
|
|
67
206
|
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
68
207
|
return;
|
|
69
208
|
const input = raw;
|
|
70
|
-
if (input.type !== "steer")
|
|
209
|
+
if (input.type !== "steer-capability" || input.protocolVersion !== 1)
|
|
71
210
|
return;
|
|
72
|
-
|
|
211
|
+
const { index, pid, readyAt, supported } = input;
|
|
212
|
+
if (typeof index !== "number" || !Number.isInteger(index) || index < 0 || index > 1e6)
|
|
73
213
|
return;
|
|
74
|
-
if (typeof
|
|
214
|
+
if (typeof pid !== "number" || typeof readyAt !== "number" || !Number.isInteger(pid) || pid <= 0 || !Number.isFinite(readyAt) || readyAt <= 0 || typeof supported !== "boolean")
|
|
75
215
|
return;
|
|
76
|
-
|
|
216
|
+
return { type: "steer-capability", protocolVersion: 1, index, pid, readyAt, supported };
|
|
217
|
+
}
|
|
218
|
+
function parseSteerAck(raw) {
|
|
219
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
220
|
+
return;
|
|
221
|
+
const input = raw;
|
|
222
|
+
if (input.type !== "steer-ack" || input.protocolVersion !== 1 || typeof input.requestId !== "string" || !/^[^\s]+$/.test(input.requestId) || input.requestId.length > 256)
|
|
223
|
+
return;
|
|
224
|
+
const { index, ts, state, message } = input;
|
|
225
|
+
if (typeof index !== "number" || typeof ts !== "number" || !Number.isInteger(index) || index < 0 || index > 1e6 || !Number.isFinite(ts) || ts <= 0)
|
|
226
|
+
return;
|
|
227
|
+
if (state !== "delivered" && state !== "queued" && state !== "failed")
|
|
228
|
+
return;
|
|
229
|
+
if (input.deliveryStatus !== undefined && input.deliveryStatus !== "delivered" && input.deliveryStatus !== "queued")
|
|
230
|
+
return;
|
|
231
|
+
if (typeof message !== "string" || !message.trim() || message.length > 1000)
|
|
232
|
+
return;
|
|
233
|
+
return { type: "steer-ack", protocolVersion: 1, requestId: input.requestId, index, ts, state, ...input.deliveryStatus ? { deliveryStatus: input.deliveryStatus } : {}, message: message.trim() };
|
|
234
|
+
}
|
|
235
|
+
export function readSteerCapability(asyncDir, index) {
|
|
236
|
+
try {
|
|
237
|
+
return parseSteerCapability(JSON.parse(fs.readFileSync(steerCapabilityPath(asyncDir, index), "utf-8")));
|
|
238
|
+
} catch {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
export function consumeSteerCapabilities(asyncDir, fsImpl = fs) {
|
|
243
|
+
const dir = steerCapabilitiesDir(asyncDir);
|
|
244
|
+
if (!fsImpl.existsSync(dir))
|
|
245
|
+
return [];
|
|
246
|
+
const capabilities = [];
|
|
247
|
+
for (const entry of fsImpl.readdirSync(dir).filter((name) => /^\d+\.json$/.test(name)).sort()) {
|
|
248
|
+
try {
|
|
249
|
+
const capability = parseSteerCapability(JSON.parse(fsImpl.readFileSync(path.join(dir, entry), "utf-8")));
|
|
250
|
+
if (capability)
|
|
251
|
+
capabilities.push(capability);
|
|
252
|
+
} catch {}
|
|
253
|
+
}
|
|
254
|
+
return capabilities;
|
|
255
|
+
}
|
|
256
|
+
export function consumeSteerAckFromDir(dir, requestId, fsImpl = fs) {
|
|
257
|
+
if (!fsImpl.existsSync(dir))
|
|
258
|
+
return;
|
|
259
|
+
let entries;
|
|
260
|
+
try {
|
|
261
|
+
entries = fsImpl.readdirSync(dir).filter((name) => name.endsWith(".json")).sort();
|
|
262
|
+
} catch {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
for (const entry of entries) {
|
|
266
|
+
const target = path.join(dir, entry);
|
|
267
|
+
let ack;
|
|
268
|
+
try {
|
|
269
|
+
ack = parseSteerAck(JSON.parse(fsImpl.readFileSync(target, "utf-8")));
|
|
270
|
+
} catch {
|
|
271
|
+
ack = undefined;
|
|
272
|
+
}
|
|
273
|
+
if (ack?.requestId !== requestId)
|
|
274
|
+
continue;
|
|
275
|
+
try {
|
|
276
|
+
fsImpl.rmSync(target, { force: true });
|
|
277
|
+
} catch {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
return ack;
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
export function consumeSteerAcks(asyncDir, fsImpl = fs) {
|
|
285
|
+
const root = path.join(controlInboxDir(asyncDir), STEER_ACKS_DIR);
|
|
286
|
+
if (!fsImpl.existsSync(root))
|
|
287
|
+
return [];
|
|
288
|
+
const acks = [];
|
|
289
|
+
let indexNames;
|
|
290
|
+
try {
|
|
291
|
+
indexNames = fsImpl.readdirSync(root).filter((name) => /^\d+$/.test(name));
|
|
292
|
+
} catch {
|
|
293
|
+
return [];
|
|
294
|
+
}
|
|
295
|
+
for (const indexName of indexNames) {
|
|
296
|
+
const dir = path.join(root, indexName);
|
|
297
|
+
let entries;
|
|
298
|
+
try {
|
|
299
|
+
entries = fsImpl.readdirSync(dir).filter((name) => name.endsWith(".json")).sort();
|
|
300
|
+
} catch {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
for (const entry of entries) {
|
|
304
|
+
const target = path.join(dir, entry);
|
|
305
|
+
let ack;
|
|
306
|
+
try {
|
|
307
|
+
ack = parseSteerAck(JSON.parse(fsImpl.readFileSync(target, "utf-8")));
|
|
308
|
+
} catch {
|
|
309
|
+
ack = undefined;
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
fsImpl.rmSync(target, { force: true });
|
|
313
|
+
} catch {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (ack)
|
|
317
|
+
acks.push(ack);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return acks;
|
|
321
|
+
}
|
|
322
|
+
function parseSteerRequest(raw) {
|
|
323
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
77
324
|
return;
|
|
78
|
-
|
|
325
|
+
const input = raw;
|
|
326
|
+
if (!validSteerRequest(input))
|
|
79
327
|
return;
|
|
80
328
|
return {
|
|
81
329
|
type: "steer",
|
|
82
330
|
id: input.id.trim(),
|
|
83
331
|
ts: input.ts,
|
|
84
332
|
message: input.message.trim(),
|
|
333
|
+
...input.mode ? { mode: input.mode } : {},
|
|
85
334
|
...input.targetIndex !== undefined ? { targetIndex: input.targetIndex } : {},
|
|
335
|
+
...input.targetIndexes !== undefined ? { targetIndexes: [...input.targetIndexes] } : {},
|
|
86
336
|
...typeof input.source === "string" && input.source.trim() ? { source: input.source } : {}
|
|
87
337
|
};
|
|
88
338
|
}
|
|
89
339
|
export function consumeSteerRequestsFromDir(dir, fsImpl = fs) {
|
|
90
340
|
if (!fsImpl.existsSync(dir))
|
|
91
341
|
return [];
|
|
342
|
+
let entries;
|
|
343
|
+
try {
|
|
344
|
+
entries = fsImpl.readdirSync(dir).filter((name) => name.endsWith(".json")).sort();
|
|
345
|
+
} catch {
|
|
346
|
+
return [];
|
|
347
|
+
}
|
|
92
348
|
const requests = [];
|
|
93
|
-
for (const entry of
|
|
349
|
+
for (const entry of entries) {
|
|
94
350
|
const requestPath = path.join(dir, entry);
|
|
95
351
|
let parsed;
|
|
96
352
|
try {
|
|
@@ -111,6 +367,27 @@ export function consumeSteerRequestsFromDir(dir, fsImpl = fs) {
|
|
|
111
367
|
export function consumeSteerRequests(asyncDir, fsImpl = fs) {
|
|
112
368
|
return consumeSteerRequestsFromDir(steerRequestsDir(asyncDir), fsImpl);
|
|
113
369
|
}
|
|
370
|
+
export function queueRevivalBrief(asyncDir, request) {
|
|
371
|
+
const dir = path.join(controlInboxDir(asyncDir), REVIVAL_BRIEFS_DIR);
|
|
372
|
+
const queued = fs.existsSync(dir) ? fs.readdirSync(dir).filter((entry) => entry.endsWith(".json")).length : 0;
|
|
373
|
+
if (queued >= MAX_STEER_QUEUE_SIZE)
|
|
374
|
+
throw new Error(`Follow-up queue is full (${MAX_STEER_QUEUE_SIZE} messages).`);
|
|
375
|
+
return writeSteerRequestToDir(dir, { ...request, mode: "follow_up" });
|
|
376
|
+
}
|
|
377
|
+
export function readRevivalBriefs(asyncDir) {
|
|
378
|
+
const dir = path.join(controlInboxDir(asyncDir), REVIVAL_BRIEFS_DIR);
|
|
379
|
+
if (!fs.existsSync(dir))
|
|
380
|
+
return [];
|
|
381
|
+
return fs.readdirSync(dir).filter((entry) => entry.endsWith(".json")).sort().flatMap((entry) => {
|
|
382
|
+
const filePath = path.join(dir, entry);
|
|
383
|
+
try {
|
|
384
|
+
const request = parseSteerRequest(JSON.parse(fs.readFileSync(filePath, "utf-8")));
|
|
385
|
+
return request ? [{ request, path: filePath }] : [];
|
|
386
|
+
} catch {
|
|
387
|
+
return [];
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
114
391
|
export function consumeInterruptRequest(asyncDir, fsImpl = fs) {
|
|
115
392
|
const requestPath = interruptRequestPath(asyncDir);
|
|
116
393
|
if (!fsImpl.existsSync(requestPath))
|
|
@@ -129,25 +406,78 @@ export function consumeTimeoutRequest(asyncDir, fsImpl = fs) {
|
|
|
129
406
|
} catch {}
|
|
130
407
|
return true;
|
|
131
408
|
}
|
|
132
|
-
export function
|
|
133
|
-
|
|
134
|
-
|
|
409
|
+
export function consumeStopRequest(asyncDir, fsImpl = fs) {
|
|
410
|
+
return consumeStopRequestPayload(asyncDir, fsImpl) !== undefined;
|
|
411
|
+
}
|
|
412
|
+
function parseStopRequest(raw) {
|
|
413
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
414
|
+
return;
|
|
415
|
+
const parsed = raw;
|
|
416
|
+
if (parsed.type !== "stop")
|
|
417
|
+
return;
|
|
418
|
+
if (Object.hasOwn(parsed, "targetIndex") && !(Number.isInteger(parsed.targetIndex) && parsed.targetIndex >= 0 && parsed.targetIndex <= 1e6))
|
|
419
|
+
return;
|
|
420
|
+
if (Object.hasOwn(parsed, "childId") && !validStopChildId(parsed.childId))
|
|
421
|
+
return;
|
|
422
|
+
return {
|
|
423
|
+
type: "stop",
|
|
424
|
+
...typeof parsed.ts === "number" ? { ts: parsed.ts } : {},
|
|
425
|
+
...typeof parsed.source === "string" ? { source: parsed.source } : {},
|
|
426
|
+
...typeof parsed.reason === "string" ? { reason: parsed.reason } : {},
|
|
427
|
+
...parsed.targetIndex !== undefined ? { targetIndex: parsed.targetIndex } : {},
|
|
428
|
+
...validStopChildId(parsed.childId) ? { childId: parsed.childId } : {}
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
function consumeStopRequestFile(requestPath, fsImpl) {
|
|
432
|
+
let request;
|
|
433
|
+
try {
|
|
434
|
+
request = parseStopRequest(JSON.parse(fsImpl.readFileSync(requestPath, "utf-8")));
|
|
435
|
+
} catch {
|
|
436
|
+
request = undefined;
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
fsImpl.rmSync(requestPath, { force: true, recursive: true });
|
|
440
|
+
} catch {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
return request;
|
|
444
|
+
}
|
|
445
|
+
export function consumeStopRequestPayloads(asyncDir, fsImpl = fs) {
|
|
446
|
+
const dir = stopRequestsDir(asyncDir);
|
|
447
|
+
const requests = [];
|
|
448
|
+
if (fsImpl.existsSync(dir)) {
|
|
449
|
+
let entries;
|
|
135
450
|
try {
|
|
136
|
-
(
|
|
137
|
-
} catch
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
throw error;
|
|
451
|
+
entries = fsImpl.readdirSync(dir).filter((name) => name.endsWith(".json")).sort();
|
|
452
|
+
} catch {
|
|
453
|
+
entries = [];
|
|
454
|
+
}
|
|
455
|
+
for (const entry of entries) {
|
|
456
|
+
const request = consumeStopRequestFile(path.join(dir, entry), fsImpl);
|
|
457
|
+
if (request)
|
|
458
|
+
requests.push(request);
|
|
145
459
|
}
|
|
146
460
|
}
|
|
461
|
+
const legacyPath = stopRequestPath(asyncDir);
|
|
462
|
+
if (fsImpl.existsSync(legacyPath)) {
|
|
463
|
+
const request = consumeStopRequestFile(legacyPath, fsImpl);
|
|
464
|
+
if (request)
|
|
465
|
+
requests.push(request);
|
|
466
|
+
}
|
|
467
|
+
return requests.sort((left, right) => (left.ts ?? 0) - (right.ts ?? 0));
|
|
468
|
+
}
|
|
469
|
+
export function consumeStopRequestPayload(asyncDir, fsImpl = fs) {
|
|
470
|
+
return consumeStopRequestPayloads(asyncDir, fsImpl)[0];
|
|
471
|
+
}
|
|
472
|
+
export function deliverInterruptRequest(input) {
|
|
473
|
+
requestAsyncInterrupt(input.asyncDir, input.source ? { source: input.source } : {}, { now: input.now });
|
|
147
474
|
}
|
|
148
475
|
export function deliverTimeoutRequest(input) {
|
|
149
476
|
requestAsyncTimeout(input.asyncDir, input.source ? { source: input.source } : {}, { now: input.now });
|
|
150
477
|
}
|
|
478
|
+
export function deliverStopRequest(input) {
|
|
479
|
+
requestAsyncStop(input.asyncDir, { ...input.source ? { source: input.source } : {}, ...input.targetIndex !== undefined ? { targetIndex: input.targetIndex } : {}, ...input.childId ? { childId: input.childId } : {} }, { now: input.now });
|
|
480
|
+
}
|
|
151
481
|
export function watchAsyncControlInbox(asyncDir, opts) {
|
|
152
482
|
const fsImpl = opts.fs ?? fs;
|
|
153
483
|
const timers = opts.timers ?? { setInterval, clearInterval };
|
|
@@ -160,31 +490,88 @@ export function watchAsyncControlInbox(asyncDir, opts) {
|
|
|
160
490
|
if (disposed)
|
|
161
491
|
return;
|
|
162
492
|
try {
|
|
493
|
+
for (const stopRequest of consumeStopRequestPayloads(asyncDir, fsImpl))
|
|
494
|
+
opts.onStop?.(stopRequest);
|
|
163
495
|
if (consumeTimeoutRequest(asyncDir, fsImpl))
|
|
164
496
|
opts.onTimeout?.();
|
|
165
497
|
if (consumeInterruptRequest(asyncDir, fsImpl))
|
|
166
498
|
opts.onInterrupt();
|
|
167
499
|
for (const request of consumeSteerRequests(asyncDir, fsImpl))
|
|
168
500
|
opts.onSteer?.(request);
|
|
501
|
+
for (const capability of consumeSteerCapabilities(asyncDir, fsImpl))
|
|
502
|
+
opts.onSteerCapability?.(capability);
|
|
503
|
+
for (const ack of consumeSteerAcks(asyncDir, fsImpl))
|
|
504
|
+
opts.onSteerAck?.(ack);
|
|
169
505
|
} catch {}
|
|
170
506
|
};
|
|
171
507
|
check();
|
|
172
|
-
|
|
508
|
+
const watchers = [];
|
|
509
|
+
const watchedDirs = new Set;
|
|
510
|
+
let interval;
|
|
511
|
+
let safetyInterval;
|
|
512
|
+
const startPolling = () => {
|
|
513
|
+
if (interval || disposed)
|
|
514
|
+
return;
|
|
515
|
+
interval = timers.setInterval(check, opts.pollIntervalMs ?? POLL_INTERVAL_MS);
|
|
516
|
+
interval.unref?.();
|
|
517
|
+
};
|
|
518
|
+
const startSafetyPolling = () => {
|
|
519
|
+
if (safetyInterval || disposed)
|
|
520
|
+
return;
|
|
521
|
+
safetyInterval = timers.setInterval(check, opts.safetyPollIntervalMs ?? CONTROL_SAFETY_POLL_INTERVAL_MS);
|
|
522
|
+
safetyInterval.unref?.();
|
|
523
|
+
};
|
|
524
|
+
const watchDir = (target, create = false) => {
|
|
525
|
+
if (disposed || watchedDirs.has(target))
|
|
526
|
+
return;
|
|
527
|
+
if (create)
|
|
528
|
+
fsImpl.mkdirSync(target, { recursive: true });
|
|
529
|
+
const watcher = fsImpl.watch(resolveWatchPath(target, fsImpl.realpathSync.native), () => {
|
|
530
|
+
watchExistingSteerAckDirs();
|
|
531
|
+
check();
|
|
532
|
+
});
|
|
533
|
+
watcher.on?.("error", startPolling);
|
|
534
|
+
watchers.push(watcher);
|
|
535
|
+
watchedDirs.add(target);
|
|
536
|
+
};
|
|
537
|
+
const watchExistingSteerAckDirs = () => {
|
|
538
|
+
const ackRoot = path.join(dir, STEER_ACKS_DIR);
|
|
539
|
+
let entries;
|
|
540
|
+
try {
|
|
541
|
+
entries = fsImpl.readdirSync(ackRoot).filter((name) => /^\d+$/.test(name));
|
|
542
|
+
} catch {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
for (const entry of entries)
|
|
546
|
+
watchDir(path.join(ackRoot, entry));
|
|
547
|
+
};
|
|
173
548
|
try {
|
|
174
|
-
|
|
175
|
-
|
|
549
|
+
if (shouldUseNativeFsWatch("runner-control-inbox", opts.platform)) {
|
|
550
|
+
watchDir(dir);
|
|
551
|
+
watchDir(stopRequestsDir(asyncDir), true);
|
|
552
|
+
watchDir(steerRequestsDir(asyncDir), true);
|
|
553
|
+
watchDir(steerCapabilitiesDir(asyncDir), true);
|
|
554
|
+
watchDir(path.join(dir, STEER_ACKS_DIR), true);
|
|
555
|
+
watchExistingSteerAckDirs();
|
|
556
|
+
startSafetyPolling();
|
|
557
|
+
} else {
|
|
558
|
+
startPolling();
|
|
559
|
+
}
|
|
176
560
|
} catch {
|
|
177
|
-
|
|
561
|
+
startPolling();
|
|
178
562
|
}
|
|
179
|
-
const interval = timers.setInterval(check, opts.pollIntervalMs ?? POLL_INTERVAL_MS);
|
|
180
|
-
interval.unref?.();
|
|
181
563
|
return () => {
|
|
182
564
|
if (disposed)
|
|
183
565
|
return;
|
|
184
566
|
disposed = true;
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
567
|
+
for (const watcher of watchers) {
|
|
568
|
+
try {
|
|
569
|
+
watcher.close();
|
|
570
|
+
} catch {}
|
|
571
|
+
}
|
|
572
|
+
if (interval)
|
|
573
|
+
timers.clearInterval(interval);
|
|
574
|
+
if (safetyInterval)
|
|
575
|
+
timers.clearInterval(safetyInterval);
|
|
189
576
|
};
|
|
190
577
|
}
|