@adhdev/daemon-core 0.9.82-rc.49 → 0.9.82-rc.490
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boot/daemon-lifecycle.d.ts +9 -0
- package/dist/boot/process-hardening.d.ts +50 -0
- package/dist/build-info.d.ts +37 -0
- package/dist/chat/source-machine.d.ts +166 -0
- package/dist/chat/source-resolver.d.ts +104 -0
- package/dist/chat/subscription-updates.d.ts +1 -0
- package/dist/cli-adapter-types.d.ts +68 -2
- package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
- package/dist/cli-adapters/cli-state-engine.d.ts +220 -0
- package/dist/cli-adapters/provider-cli-adapter.d.ts +137 -64
- package/dist/cli-adapters/provider-cli-parse.d.ts +5 -0
- package/dist/cli-adapters/provider-cli-runtime.d.ts +34 -0
- package/dist/cli-adapters/provider-cli-shared.d.ts +74 -0
- package/dist/cli-adapters/pty-write-chunking.d.ts +34 -0
- package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
- package/dist/cli-adapters/resolve-executable.d.ts +69 -0
- package/dist/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +6 -3
- package/dist/cli-adapters/terminal-backends/types.d.ts +10 -2
- package/dist/cli-adapters/terminal-screen.d.ts +4 -11
- package/dist/commands/chat-commands-debug-bundle.d.ts +14 -0
- package/dist/commands/chat-commands-read.d.ts +23 -0
- package/dist/commands/chat-commands-scope.d.ts +39 -0
- package/dist/commands/chat-commands-shared.d.ts +33 -0
- package/dist/commands/chat-commands-write.d.ts +14 -0
- package/dist/commands/chat-commands.d.ts +10 -23
- package/dist/commands/cli-manager.d.ts +71 -1
- package/dist/commands/handler.d.ts +125 -0
- package/dist/commands/high-family/index.d.ts +3 -0
- package/dist/commands/high-family/mesh-coordinator-launch.d.ts +2 -0
- package/dist/commands/high-family/mesh-events.d.ts +2 -0
- package/dist/commands/high-family/mesh-status.d.ts +2 -0
- package/dist/commands/high-family/types.d.ts +64 -0
- package/dist/commands/low-family/coordinator-prompt.d.ts +9 -0
- package/dist/commands/low-family/daemon-lifecycle.d.ts +2 -0
- package/dist/commands/low-family/diagnostics.d.ts +2 -0
- package/dist/commands/low-family/index.d.ts +3 -0
- package/dist/commands/low-family/mesh-ledger.d.ts +10 -0
- package/dist/commands/low-family/mesh-node-logs.d.ts +2 -0
- package/dist/commands/low-family/notification.d.ts +2 -0
- package/dist/commands/low-family/refine-config.d.ts +2 -0
- package/dist/commands/low-family/session-host.d.ts +2 -0
- package/dist/commands/low-family/spec-providerdev.d.ts +11 -0
- package/dist/commands/low-family/status-meta.d.ts +2 -0
- package/dist/commands/low-family/types.d.ts +33 -0
- package/dist/commands/med-family/cli-agent.d.ts +2 -0
- package/dist/commands/med-family/fast-forward.d.ts +2 -0
- package/dist/commands/med-family/ide.d.ts +10 -0
- package/dist/commands/med-family/index.d.ts +3 -0
- package/dist/commands/med-family/mesh-crud.d.ts +48 -0
- package/dist/commands/med-family/mesh-host-pairing.d.ts +2 -0
- package/dist/commands/med-family/mesh-queue.d.ts +2 -0
- package/dist/commands/med-family/mesh-restart.d.ts +2 -0
- package/dist/commands/med-family/types.d.ts +152 -0
- package/dist/commands/mesh-coordinator.d.ts +85 -1
- package/dist/commands/router-aggregate-status.d.ts +24 -0
- package/dist/commands/router-mesh-session-owner.d.ts +59 -0
- package/dist/commands/router-refine.d.ts +151 -0
- package/dist/commands/router-worktree-cleanup.d.ts +133 -0
- package/dist/commands/router.d.ts +203 -15
- package/dist/commands/upgrade-helper.d.ts +41 -1
- package/dist/config/chat-history.d.ts +11 -0
- package/dist/config/config.d.ts +19 -0
- package/dist/config/mesh-config.d.ts +65 -0
- package/dist/config/mesh-json-config.d.ts +199 -0
- package/dist/config/registry-resolver.d.ts +54 -0
- package/dist/config/repo-settings.d.ts +77 -0
- package/dist/config/state-store.d.ts +30 -0
- package/dist/daemon/dev-server.d.ts +0 -2
- package/dist/detection/cli-detector.d.ts +17 -0
- package/dist/detection/ide-detector.d.ts +13 -0
- package/dist/detection/win32-ide-version.d.ts +37 -0
- package/dist/git/change-impact-config.d.ts +159 -0
- package/dist/git/git-commands.d.ts +26 -2
- package/dist/git/git-diff.d.ts +6 -0
- package/dist/git/git-executor.d.ts +11 -0
- package/dist/git/git-status.d.ts +80 -0
- package/dist/git/git-types.d.ts +2 -50
- package/dist/git/git-worktree.d.ts +71 -1
- package/dist/git/index.d.ts +3 -1
- package/dist/index.d.ts +70 -17
- package/dist/index.js +53532 -19073
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +53283 -18989
- package/dist/index.mjs.map +1 -1
- package/dist/installer.d.ts +1 -4
- package/dist/ipc/local-ipc-server.d.ts +91 -0
- package/dist/launch.d.ts +1 -1
- package/dist/logging/async-batch-writer.d.ts +10 -0
- package/dist/logging/debug-config.d.ts +16 -0
- package/dist/logging/log-redactor.d.ts +24 -0
- package/dist/logging/log-tail-reader.d.ts +81 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/mesh/contracts.d.ts +256 -0
- package/dist/mesh/coordinator-prompt.d.ts +76 -0
- package/dist/mesh/coordinator-registry.d.ts +59 -0
- package/dist/mesh/mesh-active-work.d.ts +203 -0
- package/dist/mesh/mesh-clone-grace.d.ts +8 -0
- package/dist/mesh/mesh-completion-synthesis.d.ts +4 -0
- package/dist/mesh/mesh-coordinator-config.d.ts +21 -0
- package/dist/mesh/mesh-delivery-policy.d.ts +104 -0
- package/dist/mesh/mesh-event-classify.d.ts +7 -0
- package/dist/mesh/mesh-event-forwarding.d.ts +58 -0
- package/dist/mesh/mesh-event-trace.d.ts +21 -0
- package/dist/mesh/mesh-events-coordinator.d.ts +4 -0
- package/dist/mesh/mesh-events-pending.d.ts +258 -0
- package/dist/mesh/mesh-events-stale.d.ts +61 -0
- package/dist/mesh/mesh-events-utils.d.ts +75 -0
- package/dist/mesh/mesh-events.d.ts +6 -49
- package/dist/mesh/mesh-fast-forward.d.ts +81 -0
- package/dist/mesh/mesh-host-ownership.d.ts +21 -1
- package/dist/mesh/mesh-init.d.ts +128 -0
- package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -4
- package/dist/mesh/mesh-ledger.d.ts +139 -1
- package/dist/mesh/mesh-magi-status.d.ts +63 -0
- package/dist/mesh/mesh-missions.d.ts +281 -0
- package/dist/mesh/mesh-node-identity.d.ts +310 -0
- package/dist/mesh/mesh-queue-assignment.d.ts +162 -0
- package/dist/mesh/mesh-reconcile-acked-hold.d.ts +17 -0
- package/dist/mesh/mesh-reconcile-config.d.ts +6 -0
- package/dist/mesh/mesh-reconcile-identity.d.ts +6 -0
- package/dist/mesh/mesh-reconcile-loop.d.ts +84 -0
- package/dist/mesh/mesh-reconcile-v2-backstop.d.ts +16 -0
- package/dist/mesh/mesh-refine-batch.d.ts +68 -0
- package/dist/mesh/mesh-refine-gates.d.ts +457 -0
- package/dist/mesh/mesh-refine-status.d.ts +62 -0
- package/dist/mesh/mesh-remote-event-pull.d.ts +15 -0
- package/dist/mesh/mesh-review-inbox.d.ts +90 -0
- package/dist/mesh/mesh-routing.d.ts +70 -0
- package/dist/mesh/mesh-runtime-store.d.ts +567 -0
- package/dist/mesh/mesh-scheduling-runtime.d.ts +78 -0
- package/dist/mesh/mesh-task-inflight.d.ts +46 -0
- package/dist/mesh/mesh-task-stats.d.ts +49 -0
- package/dist/mesh/mesh-unresolved-forward-outbox.d.ts +34 -0
- package/dist/mesh/mesh-warmup-deadline.d.ts +68 -0
- package/dist/mesh/mesh-work-queue.d.ts +392 -1
- package/dist/mesh/preview-freshness.d.ts +28 -0
- package/dist/mesh/refine-config.d.ts +97 -0
- package/dist/mesh/worktree-bootstrap-config.d.ts +219 -0
- package/dist/providers/acp-provider-instance.d.ts +5 -0
- package/dist/providers/approval-utils.d.ts +46 -0
- package/dist/providers/chat-message-normalization.d.ts +58 -1
- package/dist/providers/cli-provider-effect-format.d.ts +30 -0
- package/dist/providers/cli-provider-history-dedup.d.ts +17 -0
- package/dist/providers/cli-provider-input-prompt.d.ts +12 -0
- package/dist/providers/cli-provider-instance-types.d.ts +45 -0
- package/dist/providers/cli-provider-instance.d.ts +426 -30
- package/dist/providers/cli-provider-status-helpers.d.ts +46 -0
- package/dist/providers/cli-provider-transcript-merge.d.ts +7 -0
- package/dist/providers/contracts.d.ts +196 -6
- package/dist/providers/external-sources.d.ts +71 -0
- package/dist/providers/manual-attendance.d.ts +79 -0
- package/dist/providers/native-history/antigravity-claim-registry.d.ts +39 -0
- package/dist/providers/native-history/antigravity-cli-transcript.d.ts +137 -0
- package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
- package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
- package/dist/providers/native-history/constants.d.ts +12 -0
- package/dist/providers/native-history/dispatcher.d.ts +44 -0
- package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
- package/dist/providers/native-history/index.d.ts +13 -0
- package/dist/providers/provider-instance-manager.d.ts +30 -0
- package/dist/providers/provider-instance.d.ts +31 -1
- package/dist/providers/provider-loader.d.ts +42 -5
- package/dist/providers/provider-trust.d.ts +31 -0
- package/dist/providers/read-chat-contract.d.ts +29 -0
- package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
- package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +86 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
- package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
- package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
- package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
- package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
- package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
- package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
- package/dist/providers/sdk/v1/index.d.ts +30 -0
- package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
- package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
- package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
- package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
- package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
- package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
- package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
- package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
- package/dist/providers/spec/adapter.d.ts +98 -0
- package/dist/providers/spec/cli-adapter.d.ts +223 -0
- package/dist/providers/spec/evaluator.d.ts +45 -0
- package/dist/providers/spec/fsm-driver.d.ts +449 -0
- package/dist/providers/spec/fsm-evaluator.d.ts +93 -0
- package/dist/providers/spec/fsm-loader.d.ts +14 -0
- package/dist/providers/spec/fsm-types.d.ts +202 -0
- package/dist/providers/spec/native-history-executor.d.ts +41 -0
- package/dist/providers/spec/pre-launch-trust.d.ts +16 -0
- package/dist/providers/spec/route.d.ts +4 -0
- package/dist/providers/spec/types.d.ts +244 -0
- package/dist/providers/status-monitor.d.ts +7 -7
- package/dist/providers/transcript-v2.d.ts +176 -0
- package/dist/providers/types/interactive-prompt.d.ts +84 -0
- package/dist/providers/working-dir.d.ts +17 -0
- package/dist/repo-mesh-types.d.ts +616 -12
- package/dist/runtime-defaults.d.ts +2 -0
- package/dist/session-host/managed-host.d.ts +64 -0
- package/dist/session-host/runtime-surface.d.ts +10 -16
- package/dist/sessions/registry.d.ts +26 -0
- package/dist/shared-types-extra.d.ts +2 -4
- package/dist/shared-types.d.ts +110 -55
- package/dist/status/chat-tail-hot-sessions.d.ts +40 -0
- package/dist/status/normalize.d.ts +1 -1
- package/dist/status/normalize.js +1 -0
- package/dist/status/normalize.js.map +1 -1
- package/dist/status/normalize.mjs +1 -0
- package/dist/status/normalize.mjs.map +1 -1
- package/dist/status/reporter.d.ts +2 -0
- package/dist/status/snapshot.d.ts +27 -0
- package/dist/system/hash.d.ts +8 -0
- package/dist/system/load-better-sqlite3.d.ts +21 -0
- package/dist/types.d.ts +20 -6
- package/package.json +11 -4
- package/src/agent-stream/poller.ts +2 -3
- package/src/agent-stream/provider-adapter.ts +1 -1
- package/src/boot/daemon-lifecycle.ts +80 -14
- package/src/boot/process-hardening.ts +89 -0
- package/src/build-info.ts +73 -0
- package/src/chat/source-machine.ts +534 -0
- package/src/chat/source-resolver.ts +0 -0
- package/src/chat/subscription-updates.ts +20 -1
- package/src/cli-adapter-types.d.ts +3 -1
- package/src/cli-adapter-types.ts +68 -2
- package/src/cli-adapters/cli-script-runner.ts +421 -0
- package/src/cli-adapters/cli-state-engine.ts +1282 -0
- package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
- package/src/cli-adapters/provider-cli-adapter.ts +1048 -1143
- package/src/cli-adapters/provider-cli-parse.d.ts +2 -0
- package/src/cli-adapters/provider-cli-parse.ts +19 -0
- package/src/cli-adapters/provider-cli-runtime.ts +56 -7
- package/src/cli-adapters/provider-cli-shared.d.ts +4 -0
- package/src/cli-adapters/provider-cli-shared.ts +121 -11
- package/src/cli-adapters/pty-transport.ts +2 -1
- package/src/cli-adapters/pty-write-chunking.ts +106 -0
- package/src/cli-adapters/raw-terminal-io.ts +252 -0
- package/src/cli-adapters/resolve-executable.ts +204 -0
- package/src/cli-adapters/session-host-transport.ts +2 -1
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +63 -29
- package/src/cli-adapters/terminal-backends/types.ts +10 -3
- package/src/cli-adapters/terminal-screen.ts +16 -81
- package/src/commands/chat-commands-debug-bundle.ts +398 -0
- package/src/commands/chat-commands-read.ts +2788 -0
- package/src/commands/chat-commands-scope.ts +54 -0
- package/src/commands/chat-commands-shared.ts +114 -0
- package/src/commands/chat-commands-write.ts +891 -0
- package/src/commands/chat-commands.ts +19 -1841
- package/src/commands/cli-manager.d.ts +2 -0
- package/src/commands/cli-manager.ts +724 -27
- package/src/commands/handler.ts +865 -2
- package/src/commands/high-family/index.ts +28 -0
- package/src/commands/high-family/mesh-coordinator-launch.ts +696 -0
- package/src/commands/high-family/mesh-events.ts +102 -0
- package/src/commands/high-family/mesh-status.ts +857 -0
- package/src/commands/high-family/types.ts +80 -0
- package/src/commands/low-family/coordinator-prompt.ts +125 -0
- package/src/commands/low-family/daemon-lifecycle.ts +120 -0
- package/src/commands/low-family/diagnostics.ts +57 -0
- package/src/commands/low-family/index.ts +37 -0
- package/src/commands/low-family/mesh-ledger.ts +62 -0
- package/src/commands/low-family/mesh-node-logs.ts +87 -0
- package/src/commands/low-family/notification.ts +116 -0
- package/src/commands/low-family/refine-config.ts +106 -0
- package/src/commands/low-family/session-host.ts +282 -0
- package/src/commands/low-family/spec-providerdev.ts +217 -0
- package/src/commands/low-family/status-meta.ts +112 -0
- package/src/commands/low-family/types.ts +39 -0
- package/src/commands/med-family/cli-agent.ts +270 -0
- package/src/commands/med-family/fast-forward.ts +230 -0
- package/src/commands/med-family/ide.ts +163 -0
- package/src/commands/med-family/index.ts +37 -0
- package/src/commands/med-family/mesh-crud.ts +1268 -0
- package/src/commands/med-family/mesh-host-pairing.ts +234 -0
- package/src/commands/med-family/mesh-queue.ts +167 -0
- package/src/commands/med-family/mesh-restart.ts +92 -0
- package/src/commands/med-family/types.ts +153 -0
- package/src/commands/mesh-coordinator.ts +334 -124
- package/src/commands/router-aggregate-status.ts +209 -0
- package/src/commands/router-mesh-session-owner.ts +114 -0
- package/src/commands/router-refine.ts +1794 -0
- package/src/commands/router-worktree-cleanup.ts +870 -0
- package/src/commands/router.ts +567 -4149
- package/src/commands/stream-commands.ts +8 -0
- package/src/commands/upgrade-helper.ts +310 -45
- package/src/config/chat-history.ts +517 -24
- package/src/config/config.ts +30 -0
- package/src/config/mesh-config.ts +300 -24
- package/src/config/mesh-json-config.ts +376 -0
- package/src/config/recent-activity.ts +8 -2
- package/src/config/registry-resolver.ts +100 -0
- package/src/config/repo-settings.ts +111 -0
- package/src/config/state-store.ts +55 -0
- package/src/daemon/dev-auto-implement.ts +3 -2
- package/src/daemon/dev-cli-debug.ts +10 -1
- package/src/daemon/dev-server.ts +0 -541
- package/src/detection/cli-detector.ts +94 -9
- package/src/detection/ide-detector.ts +55 -16
- package/src/detection/win32-ide-version.ts +106 -0
- package/src/git/change-impact-config.ts +354 -0
- package/src/git/git-commands.ts +112 -16
- package/src/git/git-diff.ts +81 -11
- package/src/git/git-executor.ts +12 -0
- package/src/git/git-status.ts +822 -48
- package/src/git/git-types.ts +14 -62
- package/src/git/git-worktree.ts +261 -4
- package/src/git/index.ts +17 -0
- package/src/index.ts +190 -13
- package/src/installer.d.ts +1 -1
- package/src/installer.ts +8 -6
- package/src/ipc/local-ipc-server.ts +278 -0
- package/src/launch.d.ts +1 -1
- package/src/launch.ts +37 -28
- package/src/logging/async-batch-writer.ts +55 -0
- package/src/logging/command-log.ts +7 -5
- package/src/logging/debug-config.ts +25 -0
- package/src/logging/debug-trace.ts +7 -2
- package/src/logging/log-redactor.ts +100 -0
- package/src/logging/log-tail-reader.ts +341 -0
- package/src/logging/logger.ts +14 -7
- package/src/mesh/contracts.ts +510 -0
- package/src/mesh/coordinator-prompt.ts +627 -34
- package/src/mesh/coordinator-registry.ts +121 -0
- package/src/mesh/mesh-active-work.ts +706 -0
- package/src/mesh/mesh-clone-grace.ts +68 -0
- package/src/mesh/mesh-completion-synthesis.ts +416 -0
- package/src/mesh/mesh-coordinator-config.ts +97 -0
- package/src/mesh/mesh-delivery-policy.ts +284 -0
- package/src/mesh/mesh-event-classify.ts +78 -0
- package/src/mesh/mesh-event-forwarding.ts +2030 -0
- package/src/mesh/mesh-event-trace.ts +67 -0
- package/src/mesh/mesh-events-coordinator.ts +32 -0
- package/src/mesh/mesh-events-pending.ts +1548 -0
- package/src/mesh/mesh-events-stale.ts +452 -0
- package/src/mesh/mesh-events-utils.ts +443 -0
- package/src/mesh/mesh-events.ts +43 -1035
- package/src/mesh/mesh-fast-forward.ts +856 -0
- package/src/mesh/mesh-host-ownership.ts +41 -3
- package/src/mesh/mesh-init.ts +350 -0
- package/src/mesh/mesh-ledger-reconciliation.ts +12 -7
- package/src/mesh/mesh-ledger.ts +897 -102
- package/src/mesh/mesh-magi-status.ts +223 -0
- package/src/mesh/mesh-missions.ts +695 -0
- package/src/mesh/mesh-node-identity.ts +2065 -0
- package/src/mesh/mesh-queue-assignment.ts +2498 -0
- package/src/mesh/mesh-reconcile-acked-hold.ts +230 -0
- package/src/mesh/mesh-reconcile-config.ts +66 -0
- package/src/mesh/mesh-reconcile-identity.ts +103 -0
- package/src/mesh/mesh-reconcile-loop.ts +1696 -0
- package/src/mesh/mesh-reconcile-v2-backstop.ts +62 -0
- package/src/mesh/mesh-refine-batch.ts +205 -0
- package/src/mesh/mesh-refine-gates.ts +1792 -0
- package/src/mesh/mesh-refine-status.ts +231 -0
- package/src/mesh/mesh-remote-event-pull.ts +279 -0
- package/src/mesh/mesh-review-inbox.ts +308 -0
- package/src/mesh/mesh-routing.ts +291 -0
- package/src/mesh/mesh-runtime-store.ts +2292 -0
- package/src/mesh/mesh-scheduling-runtime.ts +198 -0
- package/src/mesh/mesh-task-inflight.ts +70 -0
- package/src/mesh/mesh-task-stats.ts +161 -0
- package/src/mesh/mesh-unresolved-forward-outbox.ts +215 -0
- package/src/mesh/mesh-warmup-deadline.ts +152 -0
- package/src/mesh/mesh-work-queue.ts +1489 -145
- package/src/mesh/preview-freshness.ts +163 -0
- package/src/mesh/refine-config.ts +129 -12
- package/src/mesh/worktree-bootstrap-config.ts +599 -0
- package/src/providers/acp-provider-instance.ts +43 -10
- package/src/providers/approval-utils.d.ts +5 -0
- package/src/providers/approval-utils.ts +100 -6
- package/src/providers/chat-message-normalization.ts +126 -4
- package/src/providers/cli-provider-effect-format.ts +53 -0
- package/src/providers/cli-provider-history-dedup.ts +75 -0
- package/src/providers/cli-provider-input-prompt.ts +133 -0
- package/src/providers/cli-provider-instance-types.ts +131 -0
- package/src/providers/cli-provider-instance.ts +2771 -499
- package/src/providers/cli-provider-status-helpers.ts +123 -0
- package/src/providers/cli-provider-transcript-merge.ts +114 -0
- package/src/providers/contracts.d.ts +55 -0
- package/src/providers/contracts.ts +207 -6
- package/src/providers/extension-provider-instance.ts +12 -7
- package/src/providers/external-sources.ts +218 -0
- package/src/providers/ide-provider-instance.ts +35 -12
- package/src/providers/manual-attendance.ts +105 -0
- package/src/providers/native-history/antigravity-claim-registry.ts +138 -0
- package/src/providers/native-history/antigravity-cli-transcript.ts +1275 -0
- package/src/providers/native-history/claude-cli-transcript.ts +396 -0
- package/src/providers/native-history/codex-cli-transcript.ts +479 -0
- package/src/providers/native-history/constants.ts +19 -0
- package/src/providers/native-history/dispatcher.ts +617 -0
- package/src/providers/native-history/hermes-cli-transcript.ts +312 -0
- package/src/providers/native-history/index.ts +30 -0
- package/src/providers/provider-instance-manager.ts +71 -0
- package/src/providers/provider-instance.ts +24 -1
- package/src/providers/provider-loader.ts +700 -55
- package/src/providers/provider-schema.ts +93 -14
- package/src/providers/provider-trust.ts +114 -0
- package/src/providers/read-chat-contract.ts +76 -16
- package/src/providers/sdk/README.md +49 -0
- package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
- package/src/providers/sdk/v1/builders/cli/detect-status.ts +313 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
- package/src/providers/sdk/v1/builders/cli/parse-approval.ts +261 -0
- package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
- package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
- package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
- package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
- package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
- package/src/providers/sdk/v1/index.ts +152 -0
- package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
- package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
- package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +538 -0
- package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
- package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
- package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
- package/src/providers/sdk/v1/types/cli/index.ts +365 -0
- package/src/providers/sdk/v1/types/common/index.ts +229 -0
- package/src/providers/sdk/v1/validators/index.ts +19 -0
- package/src/providers/sdk/v1/validators/manifest.ts +110 -0
- package/src/providers/sdk/v1/validators/taint.ts +309 -0
- package/src/providers/spec/adapter.ts +246 -0
- package/src/providers/spec/cli-adapter.ts +1064 -0
- package/src/providers/spec/evaluator.ts +407 -0
- package/src/providers/spec/fsm-driver.ts +1528 -0
- package/src/providers/spec/fsm-evaluator.ts +290 -0
- package/src/providers/spec/fsm-loader.ts +128 -0
- package/src/providers/spec/fsm-types.ts +301 -0
- package/src/providers/spec/native-history-executor.ts +1241 -0
- package/src/providers/spec/pre-launch-trust.ts +104 -0
- package/src/providers/spec/route.ts +51 -0
- package/src/providers/spec/types.ts +284 -0
- package/src/providers/status-monitor.d.ts +7 -7
- package/src/providers/status-monitor.ts +37 -22
- package/src/providers/transcript-v2.ts +567 -0
- package/src/providers/types/interactive-prompt.ts +536 -0
- package/src/providers/version-archive.ts +64 -24
- package/src/providers/working-dir.ts +23 -0
- package/src/repo-mesh-types.ts +861 -14
- package/src/runtime-defaults.ts +39 -0
- package/src/session-host/managed-host.ts +218 -0
- package/src/session-host/runtime-surface.ts +20 -80
- package/src/sessions/registry.ts +44 -0
- package/src/shared-types-extra.ts +2 -4
- package/src/shared-types.d.ts +8 -0
- package/src/shared-types.ts +148 -55
- package/src/status/builders.ts +69 -6
- package/src/status/chat-tail-hot-sessions.ts +117 -2
- package/src/status/normalize.ts +2 -0
- package/src/status/reporter.ts +19 -1
- package/src/status/snapshot.ts +100 -27
- package/src/system/hash.ts +23 -0
- package/src/system/host-memory.ts +29 -12
- package/src/system/load-better-sqlite3.ts +68 -0
- package/src/types.ts +26 -6
- package/dist/cli-adapters/terminal-backends/xterm-backend.d.ts +0 -17
- package/dist/mesh/mesh-sync.d.ts +0 -53
- package/src/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +0 -16
- package/src/cli-adapters/terminal-backends/xterm-backend.ts +0 -97
- package/src/mesh/mesh-sync.ts +0 -111
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
import type { MeshLedgerEntry } from './mesh-ledger.js';
|
|
2
|
+
import { appendLedgerEntry } from './mesh-ledger.js';
|
|
3
|
+
import type { MeshWorkQueueEntry, DirectDispatchRecord } from './mesh-work-queue.js';
|
|
4
|
+
import { deleteDirectDispatchesByTaskId } from './mesh-work-queue.js';
|
|
5
|
+
import { meshNodeIdMatches, daemonIdsEquivalent, sessionIdsEquivalent } from '@adhdev/mesh-shared';
|
|
6
|
+
|
|
7
|
+
export type MeshActiveWorkSource = 'queue' | 'direct';
|
|
8
|
+
export type MeshActiveWorkStatus = 'pending' | 'assigned' | 'generating' | 'idle' | 'failed' | 'awaiting_approval';
|
|
9
|
+
|
|
10
|
+
export interface MeshActiveWorkRecord {
|
|
11
|
+
taskId: string;
|
|
12
|
+
source: MeshActiveWorkSource;
|
|
13
|
+
status: MeshActiveWorkStatus;
|
|
14
|
+
nodeId?: string;
|
|
15
|
+
sessionId?: string;
|
|
16
|
+
providerType?: string;
|
|
17
|
+
taskTitle: string;
|
|
18
|
+
taskSummary: string;
|
|
19
|
+
message?: string;
|
|
20
|
+
taskMode?: string;
|
|
21
|
+
createdAt: string;
|
|
22
|
+
updatedAt: string;
|
|
23
|
+
dispatchedAt?: string;
|
|
24
|
+
elapsedMs: number;
|
|
25
|
+
terminal?: boolean;
|
|
26
|
+
terminalKind?: string;
|
|
27
|
+
terminalAt?: string;
|
|
28
|
+
staleReason?: string;
|
|
29
|
+
/**
|
|
30
|
+
* When true, the session targeted by this direct dispatch still exists in the live mesh
|
|
31
|
+
* but did not transition to generating — the dispatch was not acknowledged by the provider.
|
|
32
|
+
* Distinct from historical/orphaned stale entries where the node/session is gone.
|
|
33
|
+
*/
|
|
34
|
+
staleDispatchUnacknowledged?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface MeshActiveWorkSummary {
|
|
38
|
+
totalActiveCount: number;
|
|
39
|
+
queueActiveCount: number;
|
|
40
|
+
directActiveCount: number;
|
|
41
|
+
awaitingApprovalCount: number;
|
|
42
|
+
generatingCount: number;
|
|
43
|
+
failedCount: number;
|
|
44
|
+
idleCount: number;
|
|
45
|
+
sourceCounts: Record<MeshActiveWorkSource, number>;
|
|
46
|
+
statusCounts: Record<MeshActiveWorkStatus, number>;
|
|
47
|
+
staleDirectCount: number;
|
|
48
|
+
/**
|
|
49
|
+
* Count of stale direct entries where the target session still exists in the live mesh
|
|
50
|
+
* but never transitioned to generating — these are fresh dispatch failures, not historical orphans.
|
|
51
|
+
* Requires immediate recovery: launch a fresh session and retry the task.
|
|
52
|
+
*/
|
|
53
|
+
staleDirectUnacknowledgedCount?: number;
|
|
54
|
+
/**
|
|
55
|
+
* When staleDirectCount > 0, this note clarifies what kind of stale records exist.
|
|
56
|
+
* Fresh unacknowledged dispatches carry an actionable recovery note; orphaned historical
|
|
57
|
+
* entries carry the "historical evidence only" note. Both may coexist.
|
|
58
|
+
*/
|
|
59
|
+
staleDirectNote?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface MeshStaleDirectWorkSummary {
|
|
63
|
+
count: number;
|
|
64
|
+
sampleLimit: number;
|
|
65
|
+
sample: Array<Pick<MeshActiveWorkRecord, 'taskId' | 'status' | 'nodeId' | 'sessionId' | 'taskTitle' | 'createdAt' | 'staleReason'>>;
|
|
66
|
+
reasonCounts: Record<string, number>;
|
|
67
|
+
detailHint: string;
|
|
68
|
+
note?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface BuildMeshActiveWorkOptions {
|
|
72
|
+
meshId: string;
|
|
73
|
+
queue?: MeshWorkQueueEntry[];
|
|
74
|
+
ledgerEntries?: MeshLedgerEntry[];
|
|
75
|
+
/**
|
|
76
|
+
* Active direct dispatches from MeshRuntimeStore. When provided, these are used instead of
|
|
77
|
+
* scanning ledger entries for direct dispatches — eliminates the O(n_ledger) scan.
|
|
78
|
+
* Falls back to ledger scanning when not provided.
|
|
79
|
+
*/
|
|
80
|
+
directDispatches?: DirectDispatchRecord[];
|
|
81
|
+
nodes?: any[];
|
|
82
|
+
now?: number;
|
|
83
|
+
/** Include terminal direct rows (idle/failed) for handoff/recent-work surfaces. Defaults false. */
|
|
84
|
+
includeTerminalDirect?: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const DIRECT_DISPATCH_VIA = new Set(['p2p_direct', 'local_direct', 'mesh_send_task']);
|
|
88
|
+
const TERMINAL_LEDGER_KINDS = new Set(['task_completed', 'task_failed', 'task_stalled']);
|
|
89
|
+
|
|
90
|
+
function readString(value: unknown): string | undefined {
|
|
91
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function summarizeMessage(message: string): { title: string; summary: string } {
|
|
95
|
+
const oneLine = message.replace(/\s+/g, ' ').trim();
|
|
96
|
+
const title = oneLine.length > 96 ? `${oneLine.slice(0, 93)}...` : oneLine;
|
|
97
|
+
return { title: title || '(untitled task)', summary: oneLine };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function elapsedSince(value: string | undefined, now: number): number {
|
|
101
|
+
const started = value ? new Date(value).getTime() : Number.NaN;
|
|
102
|
+
return Number.isFinite(started) ? Math.max(0, now - started) : 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function sessionStatusFromNodes(nodes: any[] | undefined, nodeId?: string, sessionId?: string): { status?: MeshActiveWorkStatus; staleReason?: string } {
|
|
106
|
+
if (!Array.isArray(nodes)) return {};
|
|
107
|
+
if (!nodeId) return { staleReason: 'direct task has no node id' };
|
|
108
|
+
const node = nodes.find(item => meshNodeIdMatches(item, nodeId));
|
|
109
|
+
if (!node) return { staleReason: 'direct task node is no longer in the live mesh' };
|
|
110
|
+
if (!sessionId) return {};
|
|
111
|
+
const candidates: any[] = [];
|
|
112
|
+
for (const value of [
|
|
113
|
+
node.sessions,
|
|
114
|
+
node.activeSessions,
|
|
115
|
+
node.active_sessions,
|
|
116
|
+
node.activeSessionDetails,
|
|
117
|
+
node.active_session_details,
|
|
118
|
+
node.sessionDetails,
|
|
119
|
+
node.session_details,
|
|
120
|
+
node.lastProbe?.sessions,
|
|
121
|
+
node.last_probe?.sessions,
|
|
122
|
+
node.lastProbe?.status?.sessions,
|
|
123
|
+
node.last_probe?.status?.sessions,
|
|
124
|
+
]) {
|
|
125
|
+
if (Array.isArray(value)) candidates.push(...value);
|
|
126
|
+
}
|
|
127
|
+
for (const value of [node.activeSession, node.active_session, node.currentSession, node.current_session, node.runtimeSession, node.runtime_session, node.session]) {
|
|
128
|
+
if (value && typeof value === 'object') candidates.push(value);
|
|
129
|
+
}
|
|
130
|
+
const session = candidates.find(item => {
|
|
131
|
+
if (typeof item === 'string') return sessionIdsEquivalent(item, sessionId);
|
|
132
|
+
const id = readString(item?.id) || readString(item?.sessionId) || readString(item?.session_id) || readString(item?.runtimeSessionId) || readString(item?.instanceId);
|
|
133
|
+
return sessionIdsEquivalent(id, sessionId);
|
|
134
|
+
});
|
|
135
|
+
if (!session) return { staleReason: 'direct task session is not present in live session records' };
|
|
136
|
+
if (typeof session === 'string') return {};
|
|
137
|
+
const raw = `${readString(session.status) || ''} ${readString(session.lifecycle) || ''} ${readString(session.state) || ''} ${readString(session.activeChat?.status) || ''}`.toLowerCase();
|
|
138
|
+
if (raw.includes('approval')) return { status: 'awaiting_approval' };
|
|
139
|
+
if (raw.includes('generating') || raw.includes('running') || raw.includes('busy')) return { status: 'generating' };
|
|
140
|
+
if (raw.includes('failed') || raw.includes('stopped') || raw.includes('terminated') || raw.includes('exited')) return { status: 'failed' };
|
|
141
|
+
if (raw.includes('idle') || raw.includes('waiting_input') || raw.includes('ready')) return { status: 'idle' };
|
|
142
|
+
return {};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isDirectDispatch(entry: MeshLedgerEntry): boolean {
|
|
146
|
+
if (entry.kind !== 'task_dispatched') return false;
|
|
147
|
+
const payload = entry.payload || {};
|
|
148
|
+
if (payload.source === 'direct') return true;
|
|
149
|
+
const via = readString(payload.via);
|
|
150
|
+
return Boolean(via && DIRECT_DISPATCH_VIA.has(via) && payload.source !== 'queue');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function directDispatchTaskId(entry: MeshLedgerEntry): string {
|
|
154
|
+
return readString(entry.payload?.taskId) || entry.id;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function terminalMatchesDispatch(terminal: MeshLedgerEntry, dispatch: MeshLedgerEntry, taskId: string): boolean {
|
|
158
|
+
const terminalTaskId = readString(terminal.payload?.taskId);
|
|
159
|
+
if (terminalTaskId && terminalTaskId === taskId) return true;
|
|
160
|
+
if (terminalTaskId && terminalTaskId !== taskId) return false;
|
|
161
|
+
if (dispatch.sessionId && sessionIdsEquivalent(terminal.sessionId, dispatch.sessionId)) return true;
|
|
162
|
+
// Node ids can carry interchangeable daemon-id forms (bare `mach_X` vs
|
|
163
|
+
// `daemon_mach_X`); compare under the canonical machine core, not raw `===`.
|
|
164
|
+
return Boolean(dispatch.nodeId && daemonIdsEquivalent(terminal.nodeId, dispatch.nodeId) && !dispatch.sessionId);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function statusFromTerminal(entry: MeshLedgerEntry): MeshActiveWorkStatus {
|
|
168
|
+
if (entry.kind === 'task_approval_needed') return 'awaiting_approval';
|
|
169
|
+
if (entry.kind === 'task_completed') return 'idle';
|
|
170
|
+
return 'failed';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const LEDGER_ONLY_STALE_REASON =
|
|
174
|
+
'direct task dispatch has no provider acknowledgement, transcript append, or active runtime transition';
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Single source of truth for classifying whether a direct dispatch is ledger-only
|
|
178
|
+
* stale (dispatched but never acknowledged: no provider ack, no transcript append,
|
|
179
|
+
* no runtime transition) and whether that staleness is "fresh unacknowledged"
|
|
180
|
+
* (target session still live) vs. an orphaned historical record.
|
|
181
|
+
*
|
|
182
|
+
* Previously this predicate was inlined in all three dispatch-classification blocks
|
|
183
|
+
* (MeshRuntimeStore path, remote-ledger path, full-ledger path) and had drifted —
|
|
184
|
+
* e.g. one block gated `isIdleUnacknowledged` on `!isTerminal` and another did not.
|
|
185
|
+
* Extracting it keeps the three callers byte-for-byte consistent. Status derivation
|
|
186
|
+
* stays per-block: it genuinely differs by data source and is passed in via `status`.
|
|
187
|
+
*/
|
|
188
|
+
function classifyDirectDispatch(params: {
|
|
189
|
+
/** Final classified status of the dispatch. */
|
|
190
|
+
status: MeshActiveWorkStatus;
|
|
191
|
+
/** True when a real terminal row applies (completed/failed/stale); excludes approval_needed. Gates the stale verdict. */
|
|
192
|
+
isTerminalRow: boolean;
|
|
193
|
+
/** True when any terminal-derived status exists (incl. approval_needed). Used to detect "no transition". */
|
|
194
|
+
hasTerminalStatus: boolean;
|
|
195
|
+
/** Live session status from the mesh nodes, if any. */
|
|
196
|
+
liveStatus?: string;
|
|
197
|
+
/** Live stale reason from the mesh nodes, if any. */
|
|
198
|
+
liveStaleReason?: string;
|
|
199
|
+
/** Whether the dispatch targeted an already-idle session. */
|
|
200
|
+
dispatchedToIdleSession: boolean;
|
|
201
|
+
}): { ledgerOnlyStaleReason: string | undefined; isFreshUnacknowledged: boolean } {
|
|
202
|
+
const { status, isTerminalRow, hasTerminalStatus, liveStatus, liveStaleReason, dispatchedToIdleSession } = params;
|
|
203
|
+
const isNoTransition = !hasTerminalStatus && !liveStatus;
|
|
204
|
+
const isIdleUnacknowledged = status === 'idle';
|
|
205
|
+
const ledgerOnlyStaleReason =
|
|
206
|
+
!isTerminalRow && (isIdleUnacknowledged || isNoTransition || (dispatchedToIdleSession && isIdleUnacknowledged))
|
|
207
|
+
? LEDGER_ONLY_STALE_REASON
|
|
208
|
+
: undefined;
|
|
209
|
+
const isFreshUnacknowledged = Boolean(ledgerOnlyStaleReason && !liveStaleReason);
|
|
210
|
+
return { ledgerOnlyStaleReason, isFreshUnacknowledged };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Build a direct-dispatch MeshActiveWorkRecord from a `task_dispatched` ledger entry,
|
|
215
|
+
* matching it against the terminal ledger entries and live mesh nodes. Shared by the
|
|
216
|
+
* remote-ledger scan (inside the MeshRuntimeStore branch) and the full-ledger scan
|
|
217
|
+
* (standalone branch) — those two loops were previously byte-identical except for a
|
|
218
|
+
* single `dbTaskIds.has(taskId)` skip guard that stays in the caller. Returns the record
|
|
219
|
+
* plus `terminalRow` so the caller can route it into terminal/stale/active buckets.
|
|
220
|
+
*/
|
|
221
|
+
function buildLedgerDirectDispatchRecord(
|
|
222
|
+
dispatch: MeshLedgerEntry,
|
|
223
|
+
ctx: { terminals: MeshLedgerEntry[]; nodes: any[] | undefined; now: number },
|
|
224
|
+
): { record: MeshActiveWorkRecord; terminalRow: boolean } {
|
|
225
|
+
const taskId = directDispatchTaskId(dispatch);
|
|
226
|
+
const matching = ctx.terminals
|
|
227
|
+
.filter(entry => new Date(entry.timestamp).getTime() >= new Date(dispatch.timestamp).getTime())
|
|
228
|
+
.filter(entry => terminalMatchesDispatch(entry, dispatch, taskId));
|
|
229
|
+
// APPROVAL-Q1-REALTIME (stale level state): prefer a REAL terminal (task_completed /
|
|
230
|
+
// task_failed) over an earlier task_approval_needed for the same dispatch. An approval
|
|
231
|
+
// that was subsequently resolved — the worker went on to complete or fail — must NOT keep
|
|
232
|
+
// the node pinned to awaiting_approval, which would falsely tell the coordinator (via
|
|
233
|
+
// mesh_status/read_chat) the worker is still blocked (the UX inversion this fix avoids).
|
|
234
|
+
// Among real terminals the earliest still wins (unchanged); approval-needed is selected
|
|
235
|
+
// only when no real terminal followed it. `terminals` is sorted ascending, so `.find`
|
|
236
|
+
// returns the earliest real terminal.
|
|
237
|
+
const terminal = matching.find(entry => entry.kind !== 'task_approval_needed') || matching[0];
|
|
238
|
+
const terminalStatus = terminal ? statusFromTerminal(terminal) : undefined;
|
|
239
|
+
const live = sessionStatusFromNodes(ctx.nodes, dispatch.nodeId, dispatch.sessionId);
|
|
240
|
+
const status = terminalStatus || live.status || 'assigned';
|
|
241
|
+
const terminalRow = Boolean(terminal && terminal.kind !== 'task_approval_needed');
|
|
242
|
+
const { ledgerOnlyStaleReason, isFreshUnacknowledged } = classifyDirectDispatch({
|
|
243
|
+
status,
|
|
244
|
+
isTerminalRow: terminalRow,
|
|
245
|
+
hasTerminalStatus: Boolean(terminalStatus),
|
|
246
|
+
liveStatus: live.status,
|
|
247
|
+
liveStaleReason: live.staleReason,
|
|
248
|
+
dispatchedToIdleSession: dispatch.payload?.dispatchedToIdleSession === true,
|
|
249
|
+
});
|
|
250
|
+
const message = readString(dispatch.payload?.message) || readString(dispatch.payload?.summary) || '';
|
|
251
|
+
const { title, summary } = summarizeMessage(message);
|
|
252
|
+
const record: MeshActiveWorkRecord = {
|
|
253
|
+
taskId,
|
|
254
|
+
source: 'direct',
|
|
255
|
+
status,
|
|
256
|
+
nodeId: dispatch.nodeId,
|
|
257
|
+
sessionId: dispatch.sessionId,
|
|
258
|
+
providerType: dispatch.providerType || readString(dispatch.payload?.providerType),
|
|
259
|
+
taskTitle: readString(dispatch.payload?.taskTitle) || title,
|
|
260
|
+
taskSummary: readString(dispatch.payload?.taskSummary) || summary,
|
|
261
|
+
message,
|
|
262
|
+
taskMode: readString(dispatch.payload?.taskMode),
|
|
263
|
+
createdAt: dispatch.timestamp,
|
|
264
|
+
updatedAt: terminal?.timestamp || dispatch.timestamp,
|
|
265
|
+
dispatchedAt: dispatch.timestamp,
|
|
266
|
+
elapsedMs: elapsedSince(dispatch.timestamp, ctx.now),
|
|
267
|
+
terminal: terminalRow,
|
|
268
|
+
terminalKind: terminal?.kind,
|
|
269
|
+
terminalAt: terminal?.timestamp,
|
|
270
|
+
staleReason: live.staleReason || ledgerOnlyStaleReason,
|
|
271
|
+
...(isFreshUnacknowledged ? { staleDispatchUnacknowledged: true } : {}),
|
|
272
|
+
};
|
|
273
|
+
return { record, terminalRow };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* One session awaiting an approval decision — the derived row `mesh_list_pending_approvals`
|
|
278
|
+
* returns and the UI approvals inbox renders. A thin projection of the `awaiting_approval`
|
|
279
|
+
* MeshActiveWorkRecord: it carries exactly what a coordinator needs to route a follow-up
|
|
280
|
+
* mesh_approve(node_id, session_id) and what the inbox needs to label the item. No new
|
|
281
|
+
* store or data source — derived from the same buildMeshActiveWork records.
|
|
282
|
+
*/
|
|
283
|
+
export interface MeshPendingApproval {
|
|
284
|
+
nodeId?: string;
|
|
285
|
+
sessionId?: string;
|
|
286
|
+
providerType?: string;
|
|
287
|
+
taskId: string;
|
|
288
|
+
taskTitle: string;
|
|
289
|
+
/** Always 'awaiting_approval' — kept explicit so consumers can render/assert on it. */
|
|
290
|
+
status: 'awaiting_approval';
|
|
291
|
+
/** ISO timestamp the underlying task was created/dispatched — the "waiting since" anchor. */
|
|
292
|
+
waitingSince: string;
|
|
293
|
+
/** Milliseconds the record has been outstanding (elapsed from dispatch/create). */
|
|
294
|
+
waitingMs: number;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Derive the mesh-wide pending-approval inbox from already-built active-work records.
|
|
299
|
+
* Pure filter+projection over `status === 'awaiting_approval'` — no new store, no probe;
|
|
300
|
+
* the caller supplies the records (typically buildMeshActiveWork(...).activeWork so the
|
|
301
|
+
* enumeration reuses the exact classification `mesh_status` already computes). Records
|
|
302
|
+
* without a node/session are skipped: an approval that cannot be routed to a live
|
|
303
|
+
* node+session via mesh_approve is not actionable inbox content.
|
|
304
|
+
*/
|
|
305
|
+
export function collectPendingApprovals(activeWork: MeshActiveWorkRecord[]): MeshPendingApproval[] {
|
|
306
|
+
const approvals: MeshPendingApproval[] = [];
|
|
307
|
+
for (const record of activeWork) {
|
|
308
|
+
if (record.status !== 'awaiting_approval') continue;
|
|
309
|
+
if (!record.nodeId || !record.sessionId) continue;
|
|
310
|
+
approvals.push({
|
|
311
|
+
nodeId: record.nodeId,
|
|
312
|
+
sessionId: record.sessionId,
|
|
313
|
+
providerType: record.providerType,
|
|
314
|
+
taskId: record.taskId,
|
|
315
|
+
taskTitle: record.taskTitle,
|
|
316
|
+
status: 'awaiting_approval',
|
|
317
|
+
waitingSince: record.dispatchedAt || record.createdAt,
|
|
318
|
+
waitingMs: record.elapsedMs,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
// Longest-waiting first — the coordinator/inbox should address the most-stalled approval first.
|
|
322
|
+
approvals.sort((a, b) => b.waitingMs - a.waitingMs);
|
|
323
|
+
return approvals;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function buildMeshActiveWorkSummary(activeWork: MeshActiveWorkRecord[]): MeshActiveWorkSummary {
|
|
327
|
+
const statusCounts: Record<MeshActiveWorkStatus, number> = {
|
|
328
|
+
pending: 0,
|
|
329
|
+
assigned: 0,
|
|
330
|
+
generating: 0,
|
|
331
|
+
idle: 0,
|
|
332
|
+
failed: 0,
|
|
333
|
+
awaiting_approval: 0,
|
|
334
|
+
};
|
|
335
|
+
const sourceCounts: Record<MeshActiveWorkSource, number> = { queue: 0, direct: 0 };
|
|
336
|
+
for (const item of activeWork) {
|
|
337
|
+
sourceCounts[item.source] += 1;
|
|
338
|
+
statusCounts[item.status] += 1;
|
|
339
|
+
}
|
|
340
|
+
const staleDirectCount = activeWork.filter(item => item.source === 'direct' && item.staleReason).length;
|
|
341
|
+
const staleDirectUnacknowledgedCount = activeWork.filter(item => item.source === 'direct' && item.staleDispatchUnacknowledged).length;
|
|
342
|
+
return {
|
|
343
|
+
totalActiveCount: activeWork.length,
|
|
344
|
+
queueActiveCount: sourceCounts.queue,
|
|
345
|
+
directActiveCount: sourceCounts.direct,
|
|
346
|
+
awaitingApprovalCount: statusCounts.awaiting_approval,
|
|
347
|
+
generatingCount: statusCounts.generating,
|
|
348
|
+
failedCount: statusCounts.failed,
|
|
349
|
+
idleCount: statusCounts.idle,
|
|
350
|
+
sourceCounts,
|
|
351
|
+
statusCounts,
|
|
352
|
+
staleDirectCount,
|
|
353
|
+
...(staleDirectUnacknowledgedCount > 0 ? { staleDirectUnacknowledgedCount } : {}),
|
|
354
|
+
...(staleDirectCount > 0 ? { staleDirectNote: 'Stale direct records are orphaned ledger entries whose node/session no longer exists. They are historical recovery evidence only — not active or unresolved work. The queue (source: queue) is authoritative for pending/assigned tasks.' } : {}),
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
export function buildMeshActiveWork(opts: BuildMeshActiveWorkOptions): { activeWork: MeshActiveWorkRecord[]; staleDirectWork: MeshActiveWorkRecord[]; staleDirectWorkNote?: string; terminalDirectWork: MeshActiveWorkRecord[]; summary: MeshActiveWorkSummary } {
|
|
360
|
+
const now = opts.now ?? Date.now();
|
|
361
|
+
const records: MeshActiveWorkRecord[] = [];
|
|
362
|
+
const staleDirectWork: MeshActiveWorkRecord[] = [];
|
|
363
|
+
const terminalDirectWork: MeshActiveWorkRecord[] = [];
|
|
364
|
+
|
|
365
|
+
for (const task of opts.queue || []) {
|
|
366
|
+
if (task.status !== 'pending' && task.status !== 'assigned') continue;
|
|
367
|
+
const { title, summary } = summarizeMessage(task.message || '');
|
|
368
|
+
records.push({
|
|
369
|
+
taskId: task.id,
|
|
370
|
+
source: 'queue',
|
|
371
|
+
status: task.status,
|
|
372
|
+
nodeId: task.assignedNodeId || task.targetNodeId,
|
|
373
|
+
sessionId: task.assignedSessionId || task.targetSessionId,
|
|
374
|
+
taskTitle: title,
|
|
375
|
+
taskSummary: summary,
|
|
376
|
+
message: task.message,
|
|
377
|
+
taskMode: task.taskMode,
|
|
378
|
+
createdAt: task.createdAt,
|
|
379
|
+
updatedAt: task.updatedAt,
|
|
380
|
+
dispatchedAt: task.dispatchTimestamp,
|
|
381
|
+
elapsedMs: elapsedSince(task.dispatchTimestamp || task.createdAt, now),
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// When MeshRuntimeStore direct dispatches are provided, use them for LOCAL dispatches (O(1) indexed).
|
|
386
|
+
// ALSO scan ledger for remote dispatches (P2P) whose taskIds are not in MeshRuntimeStore — these
|
|
387
|
+
// are never written to the local MeshRuntimeStore since they're dispatched from a remote daemon.
|
|
388
|
+
if (opts.directDispatches !== undefined) {
|
|
389
|
+
const dbTaskIds = new Set(opts.directDispatches.map(d => d.taskId));
|
|
390
|
+
for (const dispatch of opts.directDispatches) {
|
|
391
|
+
const live = sessionStatusFromNodes(opts.nodes, dispatch.nodeId ?? undefined, dispatch.sessionId ?? undefined);
|
|
392
|
+
const dbStatus = dispatch.status; // 'dispatched' | 'acked' | 'completed' | 'failed' | 'stale'
|
|
393
|
+
const isTerminal = dbStatus === 'completed' || dbStatus === 'failed' || dbStatus === 'stale';
|
|
394
|
+
const status: MeshActiveWorkStatus = isTerminal
|
|
395
|
+
? (dbStatus === 'completed' ? 'idle' : 'failed')
|
|
396
|
+
: live.status || (dbStatus === 'acked' ? 'generating' : 'assigned');
|
|
397
|
+
const { ledgerOnlyStaleReason, isFreshUnacknowledged } = classifyDirectDispatch({
|
|
398
|
+
status,
|
|
399
|
+
isTerminalRow: isTerminal,
|
|
400
|
+
hasTerminalStatus: isTerminal,
|
|
401
|
+
liveStatus: live.status,
|
|
402
|
+
liveStaleReason: live.staleReason,
|
|
403
|
+
dispatchedToIdleSession: dispatch.dispatchedToIdleSession === true,
|
|
404
|
+
});
|
|
405
|
+
const { title, summary } = summarizeMessage(dispatch.message || '');
|
|
406
|
+
const record: MeshActiveWorkRecord = {
|
|
407
|
+
taskId: dispatch.taskId,
|
|
408
|
+
source: 'direct',
|
|
409
|
+
status,
|
|
410
|
+
nodeId: dispatch.nodeId ?? undefined,
|
|
411
|
+
sessionId: dispatch.sessionId ?? undefined,
|
|
412
|
+
providerType: dispatch.providerType ?? undefined,
|
|
413
|
+
taskTitle: title,
|
|
414
|
+
taskSummary: summary,
|
|
415
|
+
message: dispatch.message,
|
|
416
|
+
taskMode: dispatch.taskMode ?? undefined,
|
|
417
|
+
createdAt: dispatch.dispatchedAt,
|
|
418
|
+
updatedAt: dispatch.updatedAt,
|
|
419
|
+
dispatchedAt: dispatch.dispatchedAt,
|
|
420
|
+
elapsedMs: elapsedSince(dispatch.dispatchedAt, now),
|
|
421
|
+
terminal: isTerminal,
|
|
422
|
+
terminalKind: isTerminal ? (dbStatus === 'completed' ? 'task_completed' : 'task_failed') : undefined,
|
|
423
|
+
terminalAt: isTerminal ? dispatch.updatedAt : undefined,
|
|
424
|
+
staleReason: live.staleReason || ledgerOnlyStaleReason,
|
|
425
|
+
...(isFreshUnacknowledged ? { staleDispatchUnacknowledged: true } : {}),
|
|
426
|
+
};
|
|
427
|
+
if (isTerminal) {
|
|
428
|
+
terminalDirectWork.push(record);
|
|
429
|
+
if (opts.includeTerminalDirect !== true) continue;
|
|
430
|
+
}
|
|
431
|
+
if ((live.staleReason || ledgerOnlyStaleReason) && !isTerminal) {
|
|
432
|
+
staleDirectWork.push(record);
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
records.push(record);
|
|
436
|
+
}
|
|
437
|
+
// Also scan ledger for remote dispatches (via p2p_direct) whose taskIds are NOT in MeshRuntimeStore.
|
|
438
|
+
// Remote daemons write their own local MeshRuntimeStore; this coordinator's MeshRuntimeStore only has local dispatches.
|
|
439
|
+
const ledgerEntries = (opts.ledgerEntries || []).slice().sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
440
|
+
const terminals = ledgerEntries.filter(entry => TERMINAL_LEDGER_KINDS.has(entry.kind) || entry.kind === 'task_approval_needed');
|
|
441
|
+
for (const dispatch of ledgerEntries.filter(isDirectDispatch)) {
|
|
442
|
+
if (dbTaskIds.has(directDispatchTaskId(dispatch))) continue; // already covered by MeshRuntimeStore path above
|
|
443
|
+
const { record, terminalRow } = buildLedgerDirectDispatchRecord(dispatch, { terminals, nodes: opts.nodes, now });
|
|
444
|
+
if (terminalRow) {
|
|
445
|
+
terminalDirectWork.push(record);
|
|
446
|
+
if (opts.includeTerminalDirect !== true) continue;
|
|
447
|
+
}
|
|
448
|
+
if (record.staleReason && !terminalRow) {
|
|
449
|
+
staleDirectWork.push(record);
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
records.push(record);
|
|
453
|
+
}
|
|
454
|
+
} else {
|
|
455
|
+
// Full ledger scan: no MeshRuntimeStore direct dispatches available (standalone mode or empty).
|
|
456
|
+
const ledgerEntries = (opts.ledgerEntries || []).slice().sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
457
|
+
const terminals = ledgerEntries.filter(entry => TERMINAL_LEDGER_KINDS.has(entry.kind) || entry.kind === 'task_approval_needed');
|
|
458
|
+
for (const dispatch of ledgerEntries.filter(isDirectDispatch)) {
|
|
459
|
+
const { record, terminalRow } = buildLedgerDirectDispatchRecord(dispatch, { terminals, nodes: opts.nodes, now });
|
|
460
|
+
if (terminalRow) {
|
|
461
|
+
terminalDirectWork.push(record);
|
|
462
|
+
if (opts.includeTerminalDirect !== true) continue;
|
|
463
|
+
}
|
|
464
|
+
if (record.staleReason && !terminalRow) {
|
|
465
|
+
staleDirectWork.push(record);
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
records.push(record);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
records.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
473
|
+
staleDirectWork.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
474
|
+
terminalDirectWork.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
475
|
+
const summary = buildMeshActiveWorkSummary(records);
|
|
476
|
+
summary.staleDirectCount = staleDirectWork.length;
|
|
477
|
+
const unacknowledgedCount = staleDirectWork.filter(r => r.staleDispatchUnacknowledged).length;
|
|
478
|
+
if (unacknowledgedCount > 0) {
|
|
479
|
+
summary.staleDirectUnacknowledgedCount = unacknowledgedCount;
|
|
480
|
+
}
|
|
481
|
+
// Build a note that accurately describes what's in the stale set.
|
|
482
|
+
// When there are fresh unacknowledged entries (node/session still live, session never started),
|
|
483
|
+
// use the actionable recovery note instead of the misleading "historical evidence only" note.
|
|
484
|
+
const staleDirectWorkNote = staleDirectWork.length > 0
|
|
485
|
+
? unacknowledgedCount > 0 && unacknowledgedCount === staleDirectWork.length
|
|
486
|
+
? `${unacknowledgedCount} direct dispatch(es) were not acknowledged by the target session — the session received the agent_command but never transitioned to generating. This is a fresh dispatch failure, not historical noise. Recovery: launch a fresh session on the same node and retry the task, or use mesh_enqueue_task for queue-based assignment.`
|
|
487
|
+
: unacknowledgedCount > 0
|
|
488
|
+
? `${unacknowledgedCount} of ${staleDirectWork.length} stale direct record(s) are fresh unacknowledged dispatch failures (session still live but never transitioned to generating); the rest are orphaned historical entries whose node/session no longer exists. Fresh unacknowledged dispatches need recovery: launch a fresh session and retry. Orphaned entries are historical evidence only — not active or unresolved work.`
|
|
489
|
+
: 'These are orphaned ledger entries whose original node or session no longer exists in the live mesh. They are historical/recovery evidence only — not active or unresolved work. Do not treat staleDirectCount as a status mismatch; use the queue (source: queue) as authoritative for pending/assigned tasks.'
|
|
490
|
+
: undefined;
|
|
491
|
+
if (staleDirectWorkNote) {
|
|
492
|
+
summary.staleDirectNote = staleDirectWorkNote;
|
|
493
|
+
}
|
|
494
|
+
return { activeWork: records, staleDirectWork, staleDirectWorkNote, terminalDirectWork, summary };
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* staleReason strings (produced by sessionStatusFromNodes above) that indicate the original
|
|
499
|
+
* node/session is GONE from the live mesh — i.e. the staleDirect record is an orphaned ledger
|
|
500
|
+
* artifact, not active or recoverable work. These are the only reasons safe to prune from the
|
|
501
|
+
* active staleDirect surface. The "no provider acknowledgement" reason is deliberately excluded:
|
|
502
|
+
* those entries have a still-live node/session (staleDispatchUnacknowledged) and represent
|
|
503
|
+
* recoverable dispatch failures, never orphans.
|
|
504
|
+
*/
|
|
505
|
+
export const PRUNABLE_ORPHAN_STALE_REASONS: ReadonlySet<string> = new Set([
|
|
506
|
+
'direct task node is no longer in the live mesh',
|
|
507
|
+
'direct task session is not present in live session records',
|
|
508
|
+
'direct task has no node id',
|
|
509
|
+
]);
|
|
510
|
+
|
|
511
|
+
export type StaleDirectPruneClassification = 'prunable_orphan' | 'prunable_terminal' | 'preserve_unacknowledged' | 'preserve_active';
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Classify a direct-work record for the staleDirect prune path. Pure function — the prune tool
|
|
515
|
+
* uses this so the safety rules (never touch active work or recoverable unacknowledged dispatches)
|
|
516
|
+
* live next to the staleReason producers and are independently testable.
|
|
517
|
+
*/
|
|
518
|
+
export function classifyStaleDirectForPrune(
|
|
519
|
+
record: Pick<MeshActiveWorkRecord, 'staleReason' | 'staleDispatchUnacknowledged' | 'terminal'>,
|
|
520
|
+
opts: { includeTerminal?: boolean } = {},
|
|
521
|
+
): StaleDirectPruneClassification {
|
|
522
|
+
if (record.staleDispatchUnacknowledged === true) return 'preserve_unacknowledged';
|
|
523
|
+
if (record.terminal === true) return opts.includeTerminal ? 'prunable_terminal' : 'preserve_active';
|
|
524
|
+
if (record.staleReason && PRUNABLE_ORPHAN_STALE_REASONS.has(record.staleReason)) return 'prunable_orphan';
|
|
525
|
+
return 'preserve_active';
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Outcome of one staleDirect prune pass. Pure data — callers (the MCP tool, the
|
|
530
|
+
* daemon reconcile loop) format/log this however they need. The MCP tool wraps it in
|
|
531
|
+
* its JSON response; the reconcile loop logs prunedCount when > 0.
|
|
532
|
+
*/
|
|
533
|
+
export interface StaleDirectPruneResult {
|
|
534
|
+
mode: 'execute' | 'dry_run';
|
|
535
|
+
includeTerminal: boolean;
|
|
536
|
+
/** Total staleDirect (+terminal when included) candidates surfaced this pass. */
|
|
537
|
+
candidateCount: number;
|
|
538
|
+
/** Records classified prunable AND (when minAgeMs > 0) old enough to auto-prune. */
|
|
539
|
+
prunable: MeshActiveWorkRecord[];
|
|
540
|
+
prunedCount: number;
|
|
541
|
+
/** Prunable by classification but younger than the age gate — only populated when minAgeMs > 0. */
|
|
542
|
+
skippedTooYoung: MeshActiveWorkRecord[];
|
|
543
|
+
preservedUnacknowledged: MeshActiveWorkRecord[];
|
|
544
|
+
/** Prunable orphans/terminals with no store-backed row to delete (ledger-only audit). */
|
|
545
|
+
preservedLedgerOnly: MeshActiveWorkRecord[];
|
|
546
|
+
preservedNotOrphan: MeshActiveWorkRecord[];
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export interface PruneStaleDirectDispatchesOptions {
|
|
550
|
+
meshId: string;
|
|
551
|
+
/** Active direct dispatches from MeshRuntimeStore (getActiveDirectDispatches). */
|
|
552
|
+
directDispatches: DirectDispatchRecord[];
|
|
553
|
+
/** Ledger tail used to attribute remote/terminal dispatches (readLedgerEntries). */
|
|
554
|
+
ledgerEntries?: MeshLedgerEntry[];
|
|
555
|
+
queue?: MeshWorkQueueEntry[];
|
|
556
|
+
/** Live mesh nodes (decorated with live session details) — drives orphan detection. */
|
|
557
|
+
nodes?: any[];
|
|
558
|
+
/** When true, actually delete + append the audit ledger entry. Default false (dry run). */
|
|
559
|
+
execute?: boolean;
|
|
560
|
+
/** Include terminal (idle/failed) direct rows as prune candidates. Default false. */
|
|
561
|
+
includeTerminal?: boolean;
|
|
562
|
+
/**
|
|
563
|
+
* Minimum age (ms, measured from createdAt/dispatchedAt) before a prunable orphan is
|
|
564
|
+
* eligible. 0 (default) prunes immediately regardless of age — the manual prune behavior.
|
|
565
|
+
* The daemon auto-prune passes a conservative threshold so a node/session that is only
|
|
566
|
+
* transiently invisible is never pruned on the spot.
|
|
567
|
+
*/
|
|
568
|
+
minAgeMs?: number;
|
|
569
|
+
/** Audit source string written into the direct_dispatch_pruned ledger payload. */
|
|
570
|
+
source?: string;
|
|
571
|
+
now?: number;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Shared staleDirect prune core. Single source of truth for the prune decision + the
|
|
576
|
+
* mutation (store-row delete + audit-ledger append) used by BOTH the manual MCP tool
|
|
577
|
+
* (mesh_prune_stale_direct, minAgeMs=0) and the daemon reconcile loop's auto-prune
|
|
578
|
+
* PHASE (minAgeMs > 0). Pure decision logic via buildMeshActiveWork + classifyStaleDirectForPrune;
|
|
579
|
+
* the only side effects (on execute) are deleteDirectDispatchesByTaskId and a single
|
|
580
|
+
* direct_dispatch_pruned ledger entry — never touching the append-only audit history of the
|
|
581
|
+
* pruned dispatches themselves.
|
|
582
|
+
*
|
|
583
|
+
* Safety rules (identical for manual + auto):
|
|
584
|
+
* - Only records classified as staleDirectWork against the CURRENT live mesh are eligible.
|
|
585
|
+
* - Of those, only orphans (node/session gone) — and terminals when includeTerminal — are prunable.
|
|
586
|
+
* Fresh unacknowledged dispatch failures (node/session still live) are always preserved.
|
|
587
|
+
* - Only store-backed rows (taskId present in MeshRuntimeStore) are deleted; ledger-only remote
|
|
588
|
+
* entries are preserved.
|
|
589
|
+
* - When minAgeMs > 0, a prunable orphan younger than the gate is held back (skippedTooYoung).
|
|
590
|
+
* This applies ONLY to the auto path; the manual path passes minAgeMs=0 (immediate).
|
|
591
|
+
*
|
|
592
|
+
* Idempotent: a deleted row no longer appears in getActiveDirectDispatches, so a second pass
|
|
593
|
+
* over the same orphan finds nothing to prune.
|
|
594
|
+
*/
|
|
595
|
+
export function pruneStaleDirectDispatches(opts: PruneStaleDirectDispatchesOptions): StaleDirectPruneResult {
|
|
596
|
+
const now = opts.now ?? Date.now();
|
|
597
|
+
const includeTerminal = opts.includeTerminal === true;
|
|
598
|
+
const execute = opts.execute === true;
|
|
599
|
+
const minAgeMs = Math.max(0, opts.minAgeMs ?? 0);
|
|
600
|
+
|
|
601
|
+
const activeWorkEvidence = buildMeshActiveWork({
|
|
602
|
+
meshId: opts.meshId,
|
|
603
|
+
queue: opts.queue,
|
|
604
|
+
ledgerEntries: opts.ledgerEntries,
|
|
605
|
+
directDispatches: opts.directDispatches,
|
|
606
|
+
nodes: opts.nodes,
|
|
607
|
+
now,
|
|
608
|
+
includeTerminalDirect: includeTerminal,
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
const candidates = [
|
|
612
|
+
...activeWorkEvidence.staleDirectWork,
|
|
613
|
+
...(includeTerminal ? activeWorkEvidence.terminalDirectWork : []),
|
|
614
|
+
];
|
|
615
|
+
// Only prune store-backed dispatch rows (taskIds present in MeshRuntimeStore). Ledger-only
|
|
616
|
+
// remote entries have no store row to delete and are pure audit history — leave them alone.
|
|
617
|
+
const storeTaskIds = new Set(opts.directDispatches.map(d => d.taskId));
|
|
618
|
+
|
|
619
|
+
const prunable: MeshActiveWorkRecord[] = [];
|
|
620
|
+
const skippedTooYoung: MeshActiveWorkRecord[] = [];
|
|
621
|
+
const preservedUnacknowledged: MeshActiveWorkRecord[] = [];
|
|
622
|
+
const preservedLedgerOnly: MeshActiveWorkRecord[] = [];
|
|
623
|
+
const preservedNotOrphan: MeshActiveWorkRecord[] = [];
|
|
624
|
+
for (const record of candidates) {
|
|
625
|
+
const classification = classifyStaleDirectForPrune(record, { includeTerminal });
|
|
626
|
+
if (classification === 'preserve_unacknowledged') {
|
|
627
|
+
preservedUnacknowledged.push(record);
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
if (classification === 'preserve_active') {
|
|
631
|
+
preservedNotOrphan.push(record);
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
// prunable_orphan | prunable_terminal — only delete store-backed rows; ledger-only remote
|
|
635
|
+
// entries have no store row to delete and are pure audit history.
|
|
636
|
+
if (!storeTaskIds.has(record.taskId)) {
|
|
637
|
+
preservedLedgerOnly.push(record);
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
// Age gate (auto path only): hold back orphans that are too fresh — a node/session that is
|
|
641
|
+
// only transiently invisible must not be pruned the instant it disappears.
|
|
642
|
+
if (minAgeMs > 0) {
|
|
643
|
+
const ageRef = record.dispatchedAt || record.createdAt;
|
|
644
|
+
const ageMs = elapsedSince(ageRef, now);
|
|
645
|
+
if (ageMs < minAgeMs) {
|
|
646
|
+
skippedTooYoung.push(record);
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
prunable.push(record);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
let prunedCount = 0;
|
|
654
|
+
if (execute && prunable.length) {
|
|
655
|
+
prunedCount = deleteDirectDispatchesByTaskId(opts.meshId, prunable.map(r => r.taskId));
|
|
656
|
+
appendLedgerEntry(opts.meshId, {
|
|
657
|
+
kind: 'direct_dispatch_pruned',
|
|
658
|
+
payload: {
|
|
659
|
+
source: opts.source || 'prune_stale_direct',
|
|
660
|
+
prunedCount,
|
|
661
|
+
taskIds: prunable.map(r => r.taskId),
|
|
662
|
+
reasons: Array.from(new Set(prunable.map(r => r.staleReason || (r.terminal ? 'terminal' : 'unknown')))),
|
|
663
|
+
},
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
return {
|
|
668
|
+
mode: execute ? 'execute' : 'dry_run',
|
|
669
|
+
includeTerminal,
|
|
670
|
+
candidateCount: candidates.length,
|
|
671
|
+
prunable,
|
|
672
|
+
prunedCount,
|
|
673
|
+
skippedTooYoung,
|
|
674
|
+
preservedUnacknowledged,
|
|
675
|
+
preservedLedgerOnly,
|
|
676
|
+
preservedNotOrphan,
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
export function buildCompactStaleDirectWorkSummary(
|
|
681
|
+
staleDirectWork: MeshActiveWorkRecord[],
|
|
682
|
+
opts: { sampleLimit?: number; detailHint?: string; note?: string } = {},
|
|
683
|
+
): MeshStaleDirectWorkSummary {
|
|
684
|
+
const sampleLimit = Math.max(0, Math.min(10, Math.floor(opts.sampleLimit ?? 3)));
|
|
685
|
+
const reasonCounts: Record<string, number> = {};
|
|
686
|
+
for (const entry of staleDirectWork) {
|
|
687
|
+
const reason = entry.staleReason || 'unknown';
|
|
688
|
+
reasonCounts[reason] = (reasonCounts[reason] || 0) + 1;
|
|
689
|
+
}
|
|
690
|
+
return {
|
|
691
|
+
count: staleDirectWork.length,
|
|
692
|
+
sampleLimit,
|
|
693
|
+
sample: staleDirectWork.slice(0, sampleLimit).map(entry => ({
|
|
694
|
+
taskId: entry.taskId,
|
|
695
|
+
status: entry.status,
|
|
696
|
+
nodeId: entry.nodeId,
|
|
697
|
+
sessionId: entry.sessionId,
|
|
698
|
+
taskTitle: entry.taskTitle,
|
|
699
|
+
createdAt: entry.createdAt,
|
|
700
|
+
staleReason: entry.staleReason,
|
|
701
|
+
})),
|
|
702
|
+
reasonCounts,
|
|
703
|
+
detailHint: opts.detailHint || 'Stale direct records are historical recovery evidence only. Use mesh_task_history for full ledger details, or request includeStaleDirectWorkDetails when supported by the caller.',
|
|
704
|
+
...(opts.note ? { note: opts.note } : {}),
|
|
705
|
+
};
|
|
706
|
+
}
|