@north-light/crouter 0.3.312 → 0.3.313
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/api/client.d.ts +27 -30
- package/dist/api/client.js +80 -67
- package/dist/api/dto/broker-ops.d.ts +10 -20
- package/dist/api/dto/canvas.d.ts +1 -10
- package/dist/api/dto/health.d.ts +113 -0
- package/dist/api/dto/human-requests.d.ts +14 -3
- package/dist/api/dto/human.d.ts +2 -48
- package/dist/api/dto/human.js +3 -2
- package/dist/api/dto/inbox.d.ts +14 -0
- package/dist/api/dto/mail.d.ts +43 -0
- package/dist/api/dto/nodes.d.ts +11 -8
- package/dist/api/dto/recovery.d.ts +96 -0
- package/dist/api/dto/reviews.d.ts +2 -2
- package/dist/api/routes.d.ts +9 -6
- package/dist/api/routes.js +11 -11
- package/dist/build-root.d.ts +4 -0
- package/dist/build-root.js +11 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/integration/provider-rotation.test.ts +35 -47
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +12 -5
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +12 -5
- package/dist/cli.js +10 -1
- package/dist/clients/__tests__/helpers/human-daemon.d.ts +11 -0
- package/dist/clients/__tests__/helpers/human-daemon.js +60 -0
- package/dist/clients/attach/chrome/canvas-panels.js +1 -1
- package/dist/clients/attach/chrome/inbox-strip.d.ts +3 -3
- package/dist/clients/attach/chrome/inbox-strip.js +17 -9
- package/dist/clients/attach/chrome/ticket-panel.d.ts +2 -2
- package/dist/clients/attach/chrome/ticket-panel.js +77 -46
- package/dist/clients/attach/overlays/graph.js +6 -9
- package/dist/clients/attach/render/chat-view.d.ts +5 -4
- package/dist/clients/attach/render/chat-view.js +22 -13
- package/dist/clients/attach/render/page-block.d.ts +11 -7
- package/dist/clients/attach/render/page-block.js +64 -55
- package/dist/clients/attach/viewer.js +746 -735
- package/dist/clients/inbox/__tests__/integration/inbox-controller.test.js +56 -56
- package/dist/clients/inbox/__tests__/integration/stale-row-activation.test.js +45 -32
- package/dist/clients/inbox/controller.d.ts +42 -14
- package/dist/clients/inbox/controller.js +240 -124
- package/dist/clients/inbox/dto-adapt.d.ts +28 -0
- package/dist/clients/inbox/dto-adapt.js +93 -0
- package/dist/clients/inbox/page-adapter.d.ts +5 -4
- package/dist/clients/inbox/page-adapter.js +12 -12
- package/dist/clients/inbox/resolve.d.ts +10 -5
- package/dist/clients/inbox/resolve.js +13 -12
- package/dist/clients/inbox/review/review-client.d.ts +0 -1
- package/dist/clients/inbox/review/review-client.js +0 -4
- package/dist/clients/inbox/review-adapter.d.ts +2 -1
- package/dist/clients/inbox/review-adapter.js +3 -5
- package/dist/clients/inbox/surface.d.ts +2 -2
- package/dist/clients/inbox/surface.js +19 -8
- package/dist/clients/inbox/tui.d.ts +3 -3
- package/dist/commands/__tests__/human.test.js +31 -15
- package/dist/commands/__tests__/integration/dashboard-snapshot.test.js +36 -11
- package/dist/commands/{sys/__tests__ → __tests__/integration}/migrate.test.js +142 -36
- package/dist/commands/__tests__/revive-now-gate.test.js +2 -0
- package/dist/commands/api-client.d.ts +5 -4
- package/dist/commands/api-client.js +13 -7
- package/dist/commands/canvas-history/shared.js +1 -1
- package/dist/commands/canvas-history.js +1 -1
- package/dist/commands/canvas-roster.js +1 -1
- package/dist/commands/canvas.js +1 -2
- package/dist/commands/human/feedback.js +20 -17
- package/dist/commands/human/prompts.js +45 -36
- package/dist/commands/human/queue.js +73 -123
- package/dist/commands/human/review.js +6 -16
- package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +2 -0
- package/dist/commands/memory/__tests__/memory-list-and-lint-ignore.test.js +2 -0
- package/dist/commands/memory/__tests__/repository-root-lint.test.js +2 -0
- package/dist/commands/node-inspect-artifacts.js +1 -1
- package/dist/commands/node-lifecycle-revive.d.ts +2 -2
- package/dist/commands/node-lifecycle-revive.js +9 -19
- package/dist/commands/pkg/plugin-manage.js +0 -1
- package/dist/commands/profile/show.js +4 -12
- package/dist/commands/surface-tmux-spread.js +1 -5
- package/dist/commands/sys/__tests__/config-model-ladders.test.js +23 -30
- package/dist/commands/sys/__tests__/sync-project-guidance.test.js +37 -11
- package/dist/commands/sys/branch.d.ts +2 -0
- package/dist/commands/sys/branch.js +19 -0
- package/dist/commands/sys/config.js +106 -33
- package/dist/commands/sys/daemon.d.ts +2 -0
- package/dist/commands/sys/daemon.js +130 -7
- package/dist/commands/sys/doctor.js +5 -15
- package/dist/commands/sys/migrate.js +21 -92
- package/dist/commands/sys/setup-settings-runner.js +4 -2
- package/dist/commands/sys.js +2 -15
- package/dist/core/__tests__/boot-fault-attempt.test.js +23 -1
- package/dist/core/__tests__/boot.test.js +32 -25
- package/dist/core/__tests__/broker-launch-candidates.test.js +49 -0
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +149 -176
- package/dist/core/__tests__/canvas-inbox-watcher-naming.test.js +59 -10
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +76 -20
- package/dist/core/__tests__/canvas.test.js +18 -47
- package/dist/core/__tests__/child-death-wake.test.js +29 -7
- package/dist/core/__tests__/cli-help-contract.test.js +2 -0
- package/dist/core/__tests__/close.test.js +29 -10
- package/dist/core/__tests__/collectible.test.js +2 -1
- package/dist/core/__tests__/config-change-delta.test.js +2 -0
- package/dist/core/__tests__/context-intro.test.js +13 -1
- package/dist/core/__tests__/cron-armed-context-failure.test.js +17 -12
- package/dist/core/__tests__/cron-held-settlement.test.js +21 -3
- package/dist/core/__tests__/cron-node-sink-parked-root.test.js +29 -10
- package/dist/core/__tests__/cron-self-cancel-delivery.test.js +14 -2
- package/dist/core/__tests__/cron-timeout-descendants.test.js +13 -1
- package/dist/core/__tests__/daemon-boot.test.js +51 -14
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +1 -1
- package/dist/core/__tests__/fixtures/fake-engine.js +8 -3
- package/dist/core/__tests__/focuses.test.js +10 -5
- package/dist/core/__tests__/helpers/harness.js +50 -18
- package/dist/core/__tests__/history-inbox.test.js +12 -16
- package/dist/core/__tests__/human-action-delivery.test.js +48 -28
- package/dist/core/__tests__/human-actions.test.js +7 -3
- package/dist/core/__tests__/inline-memory-refs.test.js +7 -2
- package/dist/core/__tests__/integration/broker-fork-seam.test.js +37 -10
- package/dist/core/__tests__/integration/broker-sdk-wiring.test.js +42 -0
- package/dist/core/__tests__/integration/broker-snapshot-history.test.js +42 -29
- package/dist/core/__tests__/integration/broker-wedge-supervision.test.js +36 -35
- package/dist/core/__tests__/integration/command-plugins.test.js +15 -6
- package/dist/core/__tests__/integration/deferred-no-wake.test.js +113 -92
- package/dist/core/__tests__/integration/host-teardown-process-group.test.js +9 -5
- package/dist/core/__tests__/integration/lifecycle-hooks.test.js +16 -3
- package/dist/core/__tests__/integration/plugin-revalidate.test.js +4 -0
- package/dist/core/__tests__/integration/profile-pause.test.js +4 -4
- package/dist/core/__tests__/integration/refresh-stall-recycle.test.js +9 -12
- package/dist/core/__tests__/integration/revive.test.js +67 -21
- package/dist/core/__tests__/integration/spawn-root.test.js +51 -8
- package/dist/core/__tests__/integration/subscription-delivery.test.js +33 -21
- package/dist/core/__tests__/integration/worktree-land.test.js +5 -2
- package/dist/core/__tests__/integration/worktree-reap.test.js +15 -1
- package/dist/core/__tests__/kickoff.test.js +36 -39
- package/dist/core/__tests__/lifecycle-terminal-reasons.test.js +2 -1
- package/dist/core/__tests__/lifecycle.test.js +2 -1
- package/dist/core/__tests__/live-lifecycle-prompt.test.js +13 -1
- package/dist/core/__tests__/memory-resolver-precedence.test.js +2 -0
- package/dist/core/__tests__/migration.test.js +145 -31
- package/dist/core/__tests__/model-pin-durability.test.js +4 -1
- package/dist/core/__tests__/model-routes-config.test.js +13 -5
- package/dist/core/__tests__/node-outcome.test.js +93 -26
- package/dist/core/__tests__/on-read-crouter-home-fence.test.js +2 -1
- package/dist/core/__tests__/on-read-dedup-resume.test.js +2 -1
- package/dist/core/__tests__/on-read-nested-store.test.js +2 -1
- package/dist/core/__tests__/passive-subscription.test.js +50 -157
- package/dist/core/__tests__/plugin-kinds.test.js +6 -4
- package/dist/core/__tests__/plugin-link-symlinked-scope-root.test.js +6 -1
- package/dist/core/__tests__/plugin-page-components.test.js +6 -4
- package/dist/core/__tests__/preview-registry-sync.test.js +2 -0
- package/dist/core/__tests__/profile-project-memory-delivery.test.js +4 -1
- package/dist/core/__tests__/prune.test.js +11 -5
- package/dist/core/__tests__/push-final-guard.test.js +14 -7
- package/dist/core/__tests__/relaunch-root.test.js +30 -2
- package/dist/core/__tests__/remote-canvas-target.test.js +7 -1
- package/dist/core/__tests__/repository-root-identity.test.js +4 -0
- package/dist/core/__tests__/review-model-floor.test.js +6 -3
- package/dist/core/__tests__/revive-all.test.js +13 -3
- package/dist/core/__tests__/revive-capacity.test.js +26 -4
- package/dist/core/__tests__/revive-parked-fresh.test.js +31 -8
- package/dist/core/__tests__/seam/broker-attach-stream.test.js +1 -1
- package/dist/core/__tests__/seam/broker-cap-freeze.test.js +4 -3
- package/dist/core/__tests__/seam/broker-crash-teardown.test.js +11 -14
- package/dist/core/__tests__/seam/broker-provider-retry.test.js +85 -71
- package/dist/core/__tests__/seam/dormancy-release.test.js +33 -19
- package/dist/core/__tests__/seam/held-deferred-human-prompt.test.js +7 -5
- package/dist/core/__tests__/seam/inbox-reference-guidance.test.js +20 -6
- package/dist/core/__tests__/seam/node-outcome-delivery.test.js +27 -3
- package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +2 -1
- package/dist/core/__tests__/stop-guard.test.js +2 -1
- package/dist/core/__tests__/subscription-pool-credential-guard.test.js +4 -3
- package/dist/core/__tests__/warm-claim-preference-snapshot.test.js +18 -4
- package/dist/core/__tests__/yield-ensures-daemon.test.js +2 -1
- package/dist/core/bootstrap.js +2 -0
- package/dist/core/broker-client/__tests__/transport-socket.test.js +51 -6
- package/dist/core/broker-client/transport-socket.d.ts +1 -0
- package/dist/core/broker-client/transport-socket.js +13 -5
- package/dist/core/canvas/__tests__/attention.test.js +29 -35
- package/dist/core/canvas/__tests__/history-transcript.test.js +6 -6
- package/dist/core/canvas/__tests__/render-remote.test.js +25 -9
- package/dist/core/canvas/attention.d.ts +2 -5
- package/dist/core/canvas/attention.js +15 -64
- package/dist/core/canvas/boot.d.ts +6 -7
- package/dist/core/canvas/boot.js +5 -9
- package/dist/core/canvas/canvas.d.ts +13 -109
- package/dist/core/canvas/canvas.js +140 -401
- package/dist/core/canvas/crons.d.ts +2 -0
- package/dist/core/canvas/crons.js +16 -15
- package/dist/core/canvas/daemon-owner.js +10 -0
- package/dist/core/canvas/db.d.ts +2 -2
- package/dist/core/canvas/db.js +2 -18
- package/dist/core/canvas/deliveries.d.ts +104 -0
- package/dist/core/canvas/deliveries.js +354 -0
- package/dist/core/canvas/executions.d.ts +29 -0
- package/dist/core/canvas/executions.js +68 -0
- package/dist/core/canvas/history.js +35 -35
- package/dist/core/canvas/index.d.ts +3 -1
- package/dist/core/canvas/index.js +3 -1
- package/dist/core/canvas/migrations.js +20 -12
- package/dist/core/canvas/nav-model.js +1 -3
- package/dist/core/canvas/nav-render.d.ts +4 -4
- package/dist/core/canvas/nav-render.js +11 -21
- package/dist/core/canvas/node-sql-migration.d.ts +3 -0
- package/dist/core/canvas/node-sql-migration.js +261 -0
- package/dist/core/canvas/paths.d.ts +2 -16
- package/dist/core/canvas/paths.js +2 -31
- package/dist/core/canvas/recovery.d.ts +76 -0
- package/dist/core/canvas/recovery.js +588 -0
- package/dist/core/canvas/remote-canvas-source.js +4 -0
- package/dist/core/canvas/render-source.d.ts +16 -27
- package/dist/core/canvas/render-source.js +16 -45
- package/dist/core/canvas/sessions.d.ts +33 -0
- package/dist/core/canvas/sessions.js +78 -0
- package/dist/core/canvas/types.d.ts +33 -33
- package/dist/core/canvas/types.js +2 -5
- package/dist/core/canvas/waits.d.ts +18 -0
- package/dist/core/canvas/waits.js +70 -0
- package/dist/core/canvas/worktrees.d.ts +40 -0
- package/dist/core/canvas/worktrees.js +93 -0
- package/dist/core/config.d.ts +17 -12
- package/dist/core/config.js +106 -155
- package/dist/core/feed/__tests__/inbox-migrate.test.js +27 -31
- package/dist/core/feed/feed.d.ts +0 -6
- package/dist/core/feed/feed.js +68 -56
- package/dist/core/feed/inbox.d.ts +7 -81
- package/dist/core/feed/inbox.js +46 -378
- package/dist/core/feed/messages.d.ts +58 -0
- package/dist/core/feed/messages.js +217 -0
- package/dist/core/fs-utils.d.ts +4 -0
- package/dist/core/fs-utils.js +21 -1
- package/dist/core/human/answer.d.ts +1 -1
- package/dist/core/human/answer.js +1 -1
- package/dist/core/human/claim.d.ts +1 -1
- package/dist/core/human/claim.js +2 -4
- package/dist/core/human/component-docs.js +1 -1
- package/dist/core/human/feedback-companion.js +3 -2
- package/dist/core/human/feedback.d.ts +5 -8
- package/dist/core/human/feedback.js +57 -85
- package/dist/core/human/page-authoring.d.ts +30 -0
- package/dist/core/human/page-authoring.js +19 -0
- package/dist/core/human/requests.d.ts +136 -0
- package/dist/core/human/requests.js +515 -0
- package/dist/core/human/root.d.ts +0 -7
- package/dist/core/human/root.js +0 -17
- package/dist/core/human/scan.d.ts +2 -7
- package/dist/core/human/scan.js +48 -132
- package/dist/core/human/summary.d.ts +1 -1
- package/dist/core/human/summary.js +1 -1
- package/dist/core/human/tickets.d.ts +23 -83
- package/dist/core/human/tickets.js +92 -243
- package/dist/core/human/types.d.ts +5 -1
- package/dist/core/installed-plugins.js +8 -3
- package/dist/core/keybindings/__tests__/resolve.test.js +19 -17
- package/dist/core/locked-file.d.ts +6 -3
- package/dist/core/locked-file.js +36 -9
- package/dist/core/memory/extensions.js +2 -0
- package/dist/core/preview-registry.js +0 -1
- package/dist/core/profiles/__tests__/env-store.test.js +2 -0
- package/dist/core/profiles/default-binding.d.ts +0 -4
- package/dist/core/profiles/default-binding.js +12 -58
- package/dist/core/profiles/env-store.js +8 -3
- package/dist/core/profiles/manifest.d.ts +2 -37
- package/dist/core/profiles/manifest.js +35 -161
- package/dist/core/profiles/select.js +3 -0
- package/dist/core/review/__tests__/capture-origin.test.js +13 -2
- package/dist/core/review/__tests__/comments.test.js +28 -10
- package/dist/core/review/__tests__/remap.test.js +28 -10
- package/dist/core/review/__tests__/stage-identity.test.js +17 -7
- package/dist/core/review/birth.d.ts +1 -1
- package/dist/core/review/birth.js +34 -5
- package/dist/core/review/comments.js +1 -1
- package/dist/core/review/document.js +1 -1
- package/dist/core/review/project.d.ts +5 -1
- package/dist/core/review/project.js +14 -36
- package/dist/core/review/realize.d.ts +3 -6
- package/dist/core/review/realize.js +51 -168
- package/dist/core/review/remap.js +1 -1
- package/dist/core/review/stage.d.ts +3 -2
- package/dist/core/review/stage.js +2 -3
- package/dist/core/review/store.d.ts +3 -21
- package/dist/core/review/store.js +140 -186
- package/dist/core/review/types.d.ts +0 -1
- package/dist/core/runtime/__tests__/install-id-cache-scoping.test.js +9 -5
- package/dist/core/runtime/__tests__/spawn-env.test.js +8 -9
- package/dist/core/runtime/broker/auth-reload.js +9 -10
- package/dist/core/runtime/broker/daemon-ops.d.ts +25 -8
- package/dist/core/runtime/broker/daemon-ops.js +42 -9
- package/dist/core/runtime/broker/extension-abort.d.ts +3 -0
- package/dist/core/runtime/broker/extension-abort.js +11 -0
- package/dist/core/runtime/broker/fault-retry.d.ts +15 -6
- package/dist/core/runtime/broker/fault-retry.js +142 -95
- package/dist/core/runtime/broker/inbox.d.ts +4 -32
- package/dist/core/runtime/broker/inbox.js +4 -92
- package/dist/core/runtime/broker/rebind.d.ts +1 -0
- package/dist/core/runtime/broker/rebind.js +21 -12
- package/dist/core/runtime/broker-persona-guidance.d.ts +1 -1
- package/dist/core/runtime/broker-persona-guidance.js +2 -13
- package/dist/core/runtime/broker.js +28 -22
- package/dist/core/runtime/close.d.ts +3 -9
- package/dist/core/runtime/close.js +16 -27
- package/dist/core/runtime/fault.d.ts +3 -93
- package/dist/core/runtime/fault.js +9 -419
- package/dist/core/runtime/kickoff.d.ts +3 -6
- package/dist/core/runtime/kickoff.js +12 -18
- package/dist/core/runtime/launch.js +1 -1
- package/dist/core/runtime/lifecycle.js +40 -7
- package/dist/core/runtime/placement.js +2 -2
- package/dist/core/runtime/recycle.js +8 -2
- package/dist/core/runtime/reset.js +22 -6
- package/dist/core/runtime/revive.js +226 -218
- package/dist/core/runtime/spawn.js +14 -3
- package/dist/core/scoped-state/db.d.ts +7 -0
- package/dist/core/scoped-state/db.js +92 -0
- package/dist/core/scoped-state/migrate.d.ts +11 -0
- package/dist/core/scoped-state/migrate.js +198 -0
- package/dist/core/scoped-state/paths.d.ts +9 -0
- package/dist/core/scoped-state/paths.js +27 -0
- package/dist/core/scoped-state/profiles.d.ts +12 -0
- package/dist/core/scoped-state/profiles.js +62 -0
- package/dist/core/scoped-state/providers.d.ts +16 -0
- package/dist/core/scoped-state/providers.js +8 -0
- package/dist/core/scoped-state/schema.d.ts +3 -0
- package/dist/core/scoped-state/schema.js +26 -0
- package/dist/core/scoped-state/settings.d.ts +28 -0
- package/dist/core/scoped-state/settings.js +82 -0
- package/dist/core/subscription-state.d.ts +29 -48
- package/dist/core/subscription-state.js +176 -712
- package/dist/core/worktree-close.js +4 -4
- package/dist/core/worktree-mutation-async.js +2 -2
- package/dist/core/worktree-sweep.js +11 -16
- package/dist/core/worktree.js +6 -12
- package/dist/daemon/__tests__/autostart-storm.test.js +1 -39
- package/dist/daemon/__tests__/helpers/source-daemon.d.ts +2 -2
- package/dist/daemon/__tests__/helpers/source-daemon.js +7 -6
- package/dist/daemon/__tests__/integration/migration-startup.test.js +59 -26
- package/dist/daemon/__tests__/node-deadline-lane.test.js +2 -1
- package/dist/daemon/__tests__/node-outcome-birth-invariants.test.js +39 -15
- package/dist/daemon/__tests__/node-outcome-delivery.test.js +39 -10
- package/dist/daemon/__tests__/reconciler-signature.test.js +2 -2
- package/dist/daemon/__tests__/subtree-filter-freshness.test.js +2 -2
- package/dist/daemon/api/__tests__/broker-settle-park.test.js +17 -12
- package/dist/daemon/api/__tests__/canvas-snapshot-fields.test.js +27 -5
- package/dist/daemon/api/__tests__/model-config.test.js +8 -8
- package/dist/daemon/api/__tests__/node-create-description.test.js +22 -5
- package/dist/daemon/api/__tests__/node-model-validation.test.js +21 -1
- package/dist/daemon/api/__tests__/nodes-activity-query.test.js +10 -12
- package/dist/daemon/api/__tests__/profile-launch-gates.test.js +4 -1
- package/dist/daemon/api/__tests__/profile-metadata-route.test.js +4 -1
- package/dist/daemon/api/__tests__/reopen-delivery.test.js +26 -8
- package/dist/daemon/api/__tests__/seam/api-server.test.js +24 -32
- package/dist/daemon/api/__tests__/seam/b10-attach-modelauth.test.js +4 -1
- package/dist/daemon/api/__tests__/seam/leaf-api-parity.test.js +5 -4
- package/dist/daemon/api/__tests__/seam/profile-delete.test.js +27 -41
- package/dist/daemon/api/__tests__/submit-result.test.js +3 -2
- package/dist/daemon/api/bridge.js +1 -1
- package/dist/daemon/api/handlers/bash-jobs.js +6 -7
- package/dist/daemon/api/handlers/broker-ops.js +47 -84
- package/dist/daemon/api/handlers/broker-recovery.d.ts +4 -0
- package/dist/daemon/api/handlers/broker-recovery.js +251 -0
- package/dist/daemon/api/handlers/canvas.js +2 -44
- package/dist/daemon/api/handlers/daemon.js +41 -0
- package/dist/daemon/api/handlers/feedback-comments.js +2 -2
- package/dist/daemon/api/handlers/health.js +10 -5
- package/dist/daemon/api/handlers/human-requests.js +168 -188
- package/dist/daemon/api/handlers/human.js +3 -151
- package/dist/daemon/api/handlers/inbox-page.d.ts +4 -0
- package/dist/daemon/api/handlers/inbox-page.js +89 -0
- package/dist/daemon/api/handlers/inbox.js +21 -120
- package/dist/daemon/api/handlers/messages.d.ts +1 -0
- package/dist/daemon/api/handlers/messages.js +73 -15
- package/dist/daemon/api/handlers/node-outcomes.js +58 -13
- package/dist/daemon/api/handlers/nodes.js +7 -13
- package/dist/daemon/api/handlers/reviews.js +4 -29
- package/dist/daemon/api/map.d.ts +25 -3
- package/dist/daemon/api/map.js +84 -14
- package/dist/daemon/api/server.js +68 -9
- package/dist/daemon/control.d.ts +18 -0
- package/dist/daemon/cron/disposition.d.ts +2 -0
- package/dist/daemon/cron/disposition.js +8 -5
- package/dist/daemon/cron/settle.js +37 -13
- package/dist/daemon/cron/sinks.d.ts +18 -1
- package/dist/daemon/cron/sinks.js +47 -93
- package/dist/daemon/crtrd-cli.js +4 -19
- package/dist/daemon/crtrd.d.ts +2 -0
- package/dist/daemon/crtrd.js +206 -81
- package/dist/daemon/fleet.js +5 -6
- package/dist/daemon/human/finish.d.ts +12 -24
- package/dist/daemon/human/finish.js +88 -285
- package/dist/daemon/human/settle.d.ts +40 -0
- package/dist/daemon/human/settle.js +89 -0
- package/dist/daemon/human/sweep.d.ts +20 -5
- package/dist/daemon/human/sweep.js +56 -60
- package/dist/daemon/manage.d.ts +21 -16
- package/dist/daemon/manage.js +85 -51
- package/dist/daemon/messaging/node-message.d.ts +1 -1
- package/dist/daemon/messaging/node-message.js +9 -12
- package/dist/daemon/park-activity.js +3 -3
- package/dist/daemon/reconcilers/bash-deadline.js +6 -7
- package/dist/daemon/reconcilers/broker-supervision.d.ts +1 -1
- package/dist/daemon/reconcilers/broker-supervision.js +41 -19
- package/dist/daemon/reconcilers/controller-death.js +6 -18
- package/dist/daemon/reconcilers/delivery-lane.d.ts +9 -0
- package/dist/daemon/reconcilers/delivery-lane.js +130 -0
- package/dist/daemon/reconcilers/live-obligation.js +3 -3
- package/dist/daemon/reconcilers/node-deadline.js +1 -1
- package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.d.ts +1 -1
- package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +10 -12
- package/dist/daemon/reconcilers/node-lifecycle/tick.js +5 -5
- package/dist/daemon/reconcilers/node-lifecycle/wants-execution.js +1 -1
- package/dist/daemon/reconcilers/pending-review-submit.js +3 -3
- package/dist/daemon/reconcilers/storage-maintenance.d.ts +2 -8
- package/dist/daemon/reconcilers/storage-maintenance.js +13 -43
- package/dist/daemon/review/companion.js +5 -11
- package/dist/daemon/review/deliver.d.ts +1 -5
- package/dist/daemon/review/deliver.js +3 -52
- package/dist/daemon/review/finish.d.ts +2 -27
- package/dist/daemon/review/finish.js +79 -263
- package/dist/daemon/review/sweep.d.ts +3 -10
- package/dist/daemon/review/sweep.js +13 -131
- package/dist/daemon/startup-policy.d.ts +0 -5
- package/dist/daemon/startup-policy.js +0 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/migrations/__tests__/activation-concurrency.test.js +3 -2
- package/dist/migrations/__tests__/activation.test.js +9 -6
- package/dist/migrations/activation.d.ts +3 -2
- package/dist/migrations/activation.js +2 -4
- package/dist/migrations/corpus.d.ts +2 -5
- package/dist/migrations/corpus.js +9 -20
- package/dist/migrations/runner.d.ts +3 -1
- package/dist/migrations/runner.js +6 -2
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +21 -89
- package/dist/pi-extensions/__tests__/canvas-goal-capture-envelope.test.js +4 -2
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +51 -33
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +16 -2
- package/dist/pi-extensions/__tests__/pre-command-gate.test.js +13 -1
- package/dist/pi-extensions/canvas-inbox-watcher.js +98 -244
- package/dist/pi-extensions/canvas-passive-context.d.ts +4 -8
- package/dist/pi-extensions/canvas-passive-context.js +85 -37
- package/dist/pi-extensions/canvas-stophook.js +47 -31
- package/dist/shared/env.d.ts +1 -0
- package/dist/shared/env.js +3 -0
- package/package.json +1 -1
- package/runtime.lock.json +5 -5
- package/dist/clients/attach/__tests__/page-block.test.js +0 -54
- package/dist/clients/inbox/review-visibility.d.ts +0 -5
- package/dist/clients/inbox/review-visibility.js +0 -19
- package/dist/commands/canvas-rebuild-index.d.ts +0 -2
- package/dist/commands/canvas-rebuild-index.js +0 -46
- package/dist/core/__tests__/human-cancel-guard.test.js +0 -110
- package/dist/core/__tests__/human-deliver.test.d.ts +0 -1
- package/dist/core/__tests__/human-deliver.test.js +0 -256
- package/dist/core/__tests__/human-node-not-supervised.test.d.ts +0 -1
- package/dist/core/__tests__/human-node-not-supervised.test.js +0 -89
- package/dist/core/__tests__/integration/human-deliver-e2e.test.d.ts +0 -1
- package/dist/core/__tests__/integration/human-deliver-e2e.test.js +0 -131
- package/dist/core/__tests__/phase4-review-store.test.d.ts +0 -1
- package/dist/core/__tests__/phase4-review-store.test.js +0 -144
- package/dist/core/__tests__/watchdog-abort-arms-retry.test.d.ts +0 -1
- package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +0 -110
- package/dist/core/canvas/human-deliveries.d.ts +0 -39
- package/dist/core/canvas/human-deliveries.js +0 -75
- package/dist/core/canvas/node-outcome-deliveries.d.ts +0 -64
- package/dist/core/canvas/node-outcome-deliveries.js +0 -159
- package/dist/core/feed/passive.d.ts +0 -3
- package/dist/core/feed/passive.js +0 -53
- package/dist/core/human/__tests__/inbox-cancel-worker.d.ts +0 -1
- package/dist/core/human/__tests__/inbox-cancel-worker.js +0 -7
- package/dist/core/human/__tests__/inbox-claim-worker.d.ts +0 -1
- package/dist/core/human/__tests__/inbox-claim-worker.js +0 -17
- package/dist/core/human/__tests__/inbox-stale-claim-worker.d.ts +0 -1
- package/dist/core/human/__tests__/inbox-stale-claim-worker.js +0 -8
- package/dist/core/human/__tests__/integration/inbox-core.test.d.ts +0 -1
- package/dist/core/human/__tests__/integration/inbox-core.test.js +0 -145
- package/dist/core/human/__tests__/page-tickets.test.d.ts +0 -1
- package/dist/core/human/__tests__/page-tickets.test.js +0 -161
- package/dist/core/review/ticket-filter.d.ts +0 -17
- package/dist/core/review/ticket-filter.js +0 -21
- package/dist/core/runtime/broker/passive.d.ts +0 -17
- package/dist/core/runtime/broker/passive.js +0 -44
- package/dist/core/runtime/busy.d.ts +0 -46
- package/dist/core/runtime/busy.js +0 -150
- package/dist/daemon/__tests__/startup-block-marker.test.d.ts +0 -1
- package/dist/daemon/__tests__/startup-block-marker.test.js +0 -86
- package/dist/daemon/api/__tests__/review-terminal-conflict.test.d.ts +0 -1
- package/dist/daemon/api/__tests__/review-terminal-conflict.test.js +0 -98
- package/dist/daemon/human/deliver-action.d.ts +0 -3
- package/dist/daemon/human/deliver-action.js +0 -9
- package/dist/daemon/node-outcome/deliver-outcome.d.ts +0 -3
- package/dist/daemon/node-outcome/deliver-outcome.js +0 -9
- package/dist/daemon/reconcilers/human-delivery-lane.d.ts +0 -10
- package/dist/daemon/reconcilers/human-delivery-lane.js +0 -41
- package/dist/daemon/reconcilers/node-outcome-delivery-lane.d.ts +0 -10
- package/dist/daemon/reconcilers/node-outcome-delivery-lane.js +0 -41
- package/dist/daemon/startup-block-marker.d.ts +0 -25
- package/dist/daemon/startup-block-marker.js +0 -99
- /package/dist/{clients/attach/__tests__/page-block.test.d.ts → api/dto/mail.js} +0 -0
- /package/dist/{commands/sys/__tests__/migrate.test.d.ts → api/dto/recovery.js} +0 -0
- /package/dist/{core/__tests__/human-cancel-guard.test.d.ts → commands/__tests__/integration/migrate.test.d.ts} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { openDb } from './db.js';
|
|
1
2
|
export type CronScope = 'profile' | 'global';
|
|
2
3
|
export type CronOverlap = 'skip' | 'queue' | 'replace';
|
|
3
4
|
export type CronOnOutput = 'silent' | 'on-failure' | 'always' | 'on-change';
|
|
@@ -297,4 +298,5 @@ export declare function runningCrons(): Cron[];
|
|
|
297
298
|
* written: a cron canceled while its run was in flight is already gone (and
|
|
298
299
|
* its history cascaded), so a settling run no-ops with `false` instead of
|
|
299
300
|
* violating the FK. */
|
|
301
|
+
export declare function recordCronRunLocked(db: ReturnType<typeof openDb>, run: CronRunRecord): boolean;
|
|
300
302
|
export declare function recordCronRun(run: CronRunRecord): boolean;
|
|
@@ -484,20 +484,21 @@ export function runningCrons() {
|
|
|
484
484
|
* written: a cron canceled while its run was in flight is already gone (and
|
|
485
485
|
* its history cascaded), so a settling run no-ops with `false` instead of
|
|
486
486
|
* violating the FK. */
|
|
487
|
+
export function recordCronRunLocked(db, run) {
|
|
488
|
+
if (db.prepare('SELECT 1 FROM crons WHERE cron_id = ?').get(run.cron_id) === undefined) {
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
db.prepare(`INSERT INTO cron_runs (
|
|
492
|
+
run_id, cron_id, started, finished, duration_ms, exit_code,
|
|
493
|
+
stdout_head, stderr_head, delivered
|
|
494
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(run.run_id, run.cron_id, run.started, run.finished, run.duration_ms, run.exit_code, run.stdout_head, run.stderr_head, run.delivered);
|
|
495
|
+
db.prepare(`DELETE FROM cron_runs
|
|
496
|
+
WHERE cron_id = ? AND run_id NOT IN (
|
|
497
|
+
SELECT run_id FROM cron_runs WHERE cron_id = ?
|
|
498
|
+
ORDER BY started DESC, run_id DESC LIMIT ?
|
|
499
|
+
)`).run(run.cron_id, run.cron_id, CRON_RUN_RING);
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
487
502
|
export function recordCronRun(run) {
|
|
488
|
-
return withCanvasWrite((db) =>
|
|
489
|
-
if (db.prepare('SELECT 1 FROM crons WHERE cron_id = ?').get(run.cron_id) === undefined) {
|
|
490
|
-
return false;
|
|
491
|
-
}
|
|
492
|
-
db.prepare(`INSERT INTO cron_runs (
|
|
493
|
-
run_id, cron_id, started, finished, duration_ms, exit_code,
|
|
494
|
-
stdout_head, stderr_head, delivered
|
|
495
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(run.run_id, run.cron_id, run.started, run.finished, run.duration_ms, run.exit_code, run.stdout_head, run.stderr_head, run.delivered);
|
|
496
|
-
db.prepare(`DELETE FROM cron_runs
|
|
497
|
-
WHERE cron_id = ? AND run_id NOT IN (
|
|
498
|
-
SELECT run_id FROM cron_runs WHERE cron_id = ?
|
|
499
|
-
ORDER BY started DESC, run_id DESC LIMIT ?
|
|
500
|
-
)`).run(run.cron_id, run.cron_id, CRON_RUN_RING);
|
|
501
|
-
return true;
|
|
502
|
-
});
|
|
503
|
+
return withCanvasWrite((db) => recordCronRunLocked(db, run));
|
|
503
504
|
}
|
|
@@ -62,6 +62,15 @@ function parseClaim(raw) {
|
|
|
62
62
|
return null;
|
|
63
63
|
return { pid: pid, pid_identity, token };
|
|
64
64
|
}
|
|
65
|
+
/** Bootstrap only `canvas_meta`; daemon startup owns explicit schema migration. */
|
|
66
|
+
function ensureOwnerTable() {
|
|
67
|
+
openDb().exec(`
|
|
68
|
+
CREATE TABLE IF NOT EXISTS canvas_meta (
|
|
69
|
+
key TEXT PRIMARY KEY,
|
|
70
|
+
value TEXT NOT NULL
|
|
71
|
+
)
|
|
72
|
+
`);
|
|
73
|
+
}
|
|
65
74
|
/** Read the incumbent's exact serialized `value` for `DAEMON_OWNER_META_KEY`
|
|
66
75
|
* as a plain snapshot — no transaction. `undefined` means the key is
|
|
67
76
|
* currently absent. */
|
|
@@ -95,6 +104,7 @@ export function claimDaemonOwnership(token) {
|
|
|
95
104
|
}
|
|
96
105
|
const claim = { pid: process.pid, pid_identity: ownIdentity, token };
|
|
97
106
|
const serializedClaim = serializeClaim(claim);
|
|
107
|
+
ensureOwnerTable();
|
|
98
108
|
for (;;) {
|
|
99
109
|
const snapshot = readSnapshot();
|
|
100
110
|
if (snapshot === undefined) {
|
package/dist/core/canvas/db.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DatabaseSync } from 'node:sqlite';
|
|
2
2
|
export { migrate, MIGRATIONS } from './migrations.js';
|
|
3
|
-
/** Open (or reuse) the canvas db at the current `CRTR_HOME`,
|
|
4
|
-
*
|
|
3
|
+
/** Open (or reuse) the canvas db at the current `CRTR_HOME`, configuring WAL
|
|
4
|
+
* but never changing its schema. Keyed by path so tests with distinct homes get
|
|
5
5
|
* independent handles.
|
|
6
6
|
*
|
|
7
7
|
* A cache hit trusts the cached handle outright: `crtrd` is this process's
|
package/dist/core/canvas/db.js
CHANGED
|
@@ -1,21 +1,6 @@
|
|
|
1
|
-
// canvas.db — connection management only. Opens (or reuses) the WAL-mode
|
|
2
|
-
// handle, applies pending schema migrations on first open, and serializes
|
|
3
|
-
// writes under one transaction boundary. sqlite in WAL mode so the many
|
|
4
|
-
// concurrent writers a sisyphus-grade swarm produces don't contend on a
|
|
5
|
-
// single locked JSON.
|
|
6
|
-
//
|
|
7
|
-
// Node rows are a rebuildable index over each node's meta.json (the source of
|
|
8
|
-
// truth). The `subscribes_to` edges are the one genuinely-mutable part no meta
|
|
9
|
-
// owns, so the db is authoritative for them.
|
|
10
|
-
//
|
|
11
|
-
// The versioned schema chain itself (`MIGRATIONS`, `migrate()`) lives in
|
|
12
|
-
// `./migrations.js` — see that file's header for how this repo's three
|
|
13
|
-
// migration homes divide. Re-exported here so every existing import of
|
|
14
|
-
// `migrate`/`MIGRATIONS` from this module keeps working.
|
|
15
1
|
import { DatabaseSync } from 'node:sqlite';
|
|
16
2
|
import { appendFileSync } from 'node:fs';
|
|
17
3
|
import { canvasDbPath, ensureHome } from './paths.js';
|
|
18
|
-
import { migrate } from './migrations.js';
|
|
19
4
|
export { migrate, MIGRATIONS } from './migrations.js';
|
|
20
5
|
const handles = new Map();
|
|
21
6
|
// V-2 open-ledger instrumentation
|
|
@@ -45,8 +30,8 @@ function recordOpenLedger() {
|
|
|
45
30
|
/* instrumentation only — never let a ledger write break a db open */
|
|
46
31
|
}
|
|
47
32
|
}
|
|
48
|
-
/** Open (or reuse) the canvas db at the current `CRTR_HOME`,
|
|
49
|
-
*
|
|
33
|
+
/** Open (or reuse) the canvas db at the current `CRTR_HOME`, configuring WAL
|
|
34
|
+
* but never changing its schema. Keyed by path so tests with distinct homes get
|
|
50
35
|
* independent handles.
|
|
51
36
|
*
|
|
52
37
|
* A cache hit trusts the cached handle outright: `crtrd` is this process's
|
|
@@ -78,7 +63,6 @@ export function openDb() {
|
|
|
78
63
|
// Load-bearing as of migration v4: the edges→nodes FK (ON DELETE CASCADE)
|
|
79
64
|
// needs this ON at the deleting connection so a node delete reaps its edges.
|
|
80
65
|
db.exec('PRAGMA foreign_keys = ON;');
|
|
81
|
-
migrate(db);
|
|
82
66
|
handles.set(path, db);
|
|
83
67
|
return db;
|
|
84
68
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import type { DeliveryFailure, DeliverySettlement as ProcessDeliverySettlement } from './delivery-contract.js';
|
|
3
|
+
export type DeliveryState = 'armed' | 'pending' | 'handed_off' | 'acknowledged' | 'permanent_failed';
|
|
4
|
+
export type DeliveryDestinationKind = 'node_inbox' | 'external_action';
|
|
5
|
+
export type DeliveryUrgency = 'critical' | 'urgent' | 'normal' | 'deferred';
|
|
6
|
+
export interface ArmedExternalActionInput {
|
|
7
|
+
destinationId: string;
|
|
8
|
+
adapter: Record<string, unknown>;
|
|
9
|
+
idempotencyKey: string;
|
|
10
|
+
}
|
|
11
|
+
export interface DeliveryRecord {
|
|
12
|
+
deliveryId: string;
|
|
13
|
+
messageId: string | null;
|
|
14
|
+
destinationKind: DeliveryDestinationKind;
|
|
15
|
+
destinationId: string;
|
|
16
|
+
urgency: DeliveryUrgency;
|
|
17
|
+
wake: 0 | 1;
|
|
18
|
+
state: DeliveryState;
|
|
19
|
+
attempts: number;
|
|
20
|
+
nextAttemptAt: number | null;
|
|
21
|
+
claimedAt: number | null;
|
|
22
|
+
claimOwner: string | null;
|
|
23
|
+
acknowledgedAt: number | null;
|
|
24
|
+
failedAt: number | null;
|
|
25
|
+
lastFailure: DeliveryFailure | null;
|
|
26
|
+
adapter: Record<string, unknown> | null;
|
|
27
|
+
effect: Record<string, unknown> | null;
|
|
28
|
+
idempotencyKey: string | null;
|
|
29
|
+
canceledAt: number | null;
|
|
30
|
+
createdAt: number;
|
|
31
|
+
updatedAt: number;
|
|
32
|
+
}
|
|
33
|
+
export interface ClaimedNodeMail extends DeliveryRecord {
|
|
34
|
+
message: {
|
|
35
|
+
messageId: string;
|
|
36
|
+
seq: number;
|
|
37
|
+
operationId: string;
|
|
38
|
+
createdAt: string;
|
|
39
|
+
senderNodeId: string | null;
|
|
40
|
+
senderName: string | null;
|
|
41
|
+
kind: 'update' | 'urgent' | 'final' | 'message' | 'completed';
|
|
42
|
+
label: string;
|
|
43
|
+
bodyRef: string | null;
|
|
44
|
+
bodyJson: unknown | null;
|
|
45
|
+
disposition: 'human-answer' | 'human-canceled' | null;
|
|
46
|
+
};
|
|
47
|
+
handoff: 'fresh' | 'inherited';
|
|
48
|
+
}
|
|
49
|
+
/** Claim one broker channel. A refreshing or stale broker receives hold and changes nothing. */
|
|
50
|
+
export declare function claimNodeMail(nodeId: string, executionId: string, channel: 'wake' | 'passive', now?: number): {
|
|
51
|
+
action: 'claimed' | 'hold';
|
|
52
|
+
deliveries: ClaimedNodeMail[];
|
|
53
|
+
};
|
|
54
|
+
/** Pre-launch wake claim. It uses the same state transition but deliberately does not reject refresh intent. */
|
|
55
|
+
export declare function claimKickoffMail(nodeId: string, executionId: string, now?: number): ClaimedNodeMail[];
|
|
56
|
+
export type DeliverySettlement = {
|
|
57
|
+
state: 'acknowledged';
|
|
58
|
+
} | {
|
|
59
|
+
state: 'released';
|
|
60
|
+
} | {
|
|
61
|
+
state: 'permanent_failed';
|
|
62
|
+
failure: DeliveryFailure;
|
|
63
|
+
} | {
|
|
64
|
+
state: 'pending';
|
|
65
|
+
nextAttemptAt: number;
|
|
66
|
+
failure: DeliveryFailure;
|
|
67
|
+
};
|
|
68
|
+
/** The one execution/attempt/owner fence for node mail and external actions. */
|
|
69
|
+
export declare function settleDelivery(deliveryId: string, attempts: number, claimOwner: string, settlement: DeliverySettlement | ProcessDeliverySettlement, now?: number): boolean;
|
|
70
|
+
/** Broker acknowledgement additionally checks the live execution/refresh guards before the per-row fence. */
|
|
71
|
+
export declare function acknowledgeNodeMail(nodeId: string, executionId: string, deliveries: Array<{
|
|
72
|
+
deliveryId: string;
|
|
73
|
+
attempts: number;
|
|
74
|
+
disposition: 'acknowledged' | 'released';
|
|
75
|
+
}>, now?: number): {
|
|
76
|
+
action: 'acknowledged' | 'hold';
|
|
77
|
+
results: Array<{
|
|
78
|
+
deliveryId: string;
|
|
79
|
+
outcome: 'acknowledged' | 'released' | 'stale';
|
|
80
|
+
}>;
|
|
81
|
+
};
|
|
82
|
+
/** The immutable action document belonging to one external-action obligation. */
|
|
83
|
+
export declare function messageBodyForDelivery(deliveryId: string): unknown | null;
|
|
84
|
+
/** Read the one external-action obligation with this caller-owned identity. */
|
|
85
|
+
export declare function readExternalActionDelivery(idempotencyKey: string): DeliveryRecord | null;
|
|
86
|
+
export declare function dueExternalActionDeliveries(now: number): DeliveryRecord[];
|
|
87
|
+
export declare function claimExternalActionDelivery(deliveryId: string, now: number, claimOwner: string): DeliveryRecord | null;
|
|
88
|
+
export declare function resetHandedOffDeliveries(now: number): number;
|
|
89
|
+
/** Reset mail held by an execution that is no longer this node's one live execution. */
|
|
90
|
+
export declare function resetStaleExecutionMail(now: number): number;
|
|
91
|
+
export declare function hasUnseenWakeMail(nodeId: string): boolean;
|
|
92
|
+
export declare function hasWakeMailSince(nodeId: string, since: number): boolean;
|
|
93
|
+
export declare function acknowledgedNodeMailCount(nodeId: string): number;
|
|
94
|
+
/** Register an outcome's frozen external destination before an outcome message exists. */
|
|
95
|
+
export declare function armExternalActionDelivery(db: DatabaseSync, input: ArmedExternalActionInput, now?: number): void;
|
|
96
|
+
/** Attach a newly written immutable outcome message and make its registered action due. */
|
|
97
|
+
export declare function activateArmedExternalActionDelivery(db: DatabaseSync, input: {
|
|
98
|
+
idempotencyKey: string;
|
|
99
|
+
label: string;
|
|
100
|
+
bodyJson: string;
|
|
101
|
+
supersede: boolean;
|
|
102
|
+
}, now?: number): boolean;
|
|
103
|
+
/** Create the messaging schema and import every legacy mailbox/action/wait row. The caller owns the encompassing migration transaction. */
|
|
104
|
+
export declare function migrateMessagingState(db: DatabaseSync): void;
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { openDb, withCanvasWrite } from './db.js';
|
|
4
|
+
import { inboxPath, passivePath } from './paths.js';
|
|
5
|
+
function deliveryFrom(row) {
|
|
6
|
+
return {
|
|
7
|
+
deliveryId: row.delivery_id,
|
|
8
|
+
messageId: row.message_id,
|
|
9
|
+
destinationKind: row.destination_kind,
|
|
10
|
+
destinationId: row.destination_id,
|
|
11
|
+
urgency: row.urgency,
|
|
12
|
+
wake: row.wake === 1 ? 1 : 0,
|
|
13
|
+
state: row.state,
|
|
14
|
+
attempts: row.attempts,
|
|
15
|
+
nextAttemptAt: row.next_attempt_at,
|
|
16
|
+
claimedAt: row.claimed_at,
|
|
17
|
+
claimOwner: row.claim_owner,
|
|
18
|
+
acknowledgedAt: row.acknowledged_at,
|
|
19
|
+
failedAt: row.failed_at,
|
|
20
|
+
lastFailure: row.last_failure_json === null ? null : JSON.parse(row.last_failure_json),
|
|
21
|
+
adapter: row.adapter_json === null ? null : JSON.parse(row.adapter_json),
|
|
22
|
+
effect: row.effect_json === null ? null : JSON.parse(row.effect_json),
|
|
23
|
+
idempotencyKey: row.idempotency_key,
|
|
24
|
+
canceledAt: row.canceled_at,
|
|
25
|
+
createdAt: row.created_at,
|
|
26
|
+
updatedAt: row.updated_at,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function executionIsLive(db, nodeId, executionId, rejectRefresh) {
|
|
30
|
+
const row = db.prepare(`SELECT 1 FROM nodes n JOIN executions e ON e.node_id = n.node_id
|
|
31
|
+
WHERE n.node_id = ? AND e.execution_id = ? AND e.state != 'exited'${rejectRefresh ? " AND n.intent IS NOT 'refresh'" : ''}`).get(nodeId, executionId);
|
|
32
|
+
return row !== undefined;
|
|
33
|
+
}
|
|
34
|
+
function nodeMailRows(db, nodeId, executionId, wake, freshIds) {
|
|
35
|
+
const rows = db.prepare(`SELECT d.*, m.message_id AS m_message_id, m.seq, m.operation_id, m.created_at AS message_created_at,
|
|
36
|
+
m.sender_node_id, m.sender_name, m.kind, m.label, m.body_ref, m.body_json, m.disposition,
|
|
37
|
+
CASE WHEN d.state = 'handed_off' THEN 'inherited' ELSE 'fresh' END AS handoff
|
|
38
|
+
FROM deliveries d JOIN messages m ON m.message_id = d.message_id
|
|
39
|
+
WHERE d.destination_kind = 'node_inbox' AND d.destination_id = ? AND d.wake = ?
|
|
40
|
+
AND d.canceled_at IS NULL
|
|
41
|
+
AND (d.state = 'pending' OR (d.state = 'handed_off' AND d.claim_owner = ?))
|
|
42
|
+
ORDER BY m.seq`).all(nodeId, wake, executionId);
|
|
43
|
+
return rows.map((row) => ({
|
|
44
|
+
...deliveryFrom(row),
|
|
45
|
+
message: {
|
|
46
|
+
messageId: row['m_message_id'],
|
|
47
|
+
seq: Number(row['seq']),
|
|
48
|
+
operationId: row['operation_id'],
|
|
49
|
+
createdAt: row['message_created_at'],
|
|
50
|
+
senderNodeId: row['sender_node_id'],
|
|
51
|
+
senderName: row['sender_name'],
|
|
52
|
+
kind: row['kind'],
|
|
53
|
+
label: row['label'],
|
|
54
|
+
bodyRef: row['body_ref'],
|
|
55
|
+
bodyJson: row['body_json'] === null ? null : JSON.parse(row['body_json']),
|
|
56
|
+
disposition: row['disposition'],
|
|
57
|
+
},
|
|
58
|
+
handoff: freshIds?.has(row.delivery_id) === true ? 'fresh' : row['handoff'],
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
/** Claim one broker channel. A refreshing or stale broker receives hold and changes nothing. */
|
|
62
|
+
export function claimNodeMail(nodeId, executionId, channel, now = Date.now()) {
|
|
63
|
+
return withCanvasWrite((db) => {
|
|
64
|
+
if (!executionIsLive(db, nodeId, executionId, true))
|
|
65
|
+
return { action: 'hold', deliveries: [] };
|
|
66
|
+
const wake = channel === 'wake' ? 1 : 0;
|
|
67
|
+
const freshIds = new Set(db.prepare(`SELECT delivery_id FROM deliveries
|
|
68
|
+
WHERE destination_kind = 'node_inbox' AND destination_id = ? AND wake = ? AND state = 'pending' AND canceled_at IS NULL`).all(nodeId, wake).map((row) => row.delivery_id));
|
|
69
|
+
db.prepare(`UPDATE deliveries SET state = 'handed_off', attempts = attempts + 1, claim_owner = ?, claimed_at = ?, updated_at = ?
|
|
70
|
+
WHERE destination_kind = 'node_inbox' AND destination_id = ? AND wake = ? AND state = 'pending' AND canceled_at IS NULL`).run(executionId, now, now, nodeId, wake);
|
|
71
|
+
return { action: 'claimed', deliveries: nodeMailRows(db, nodeId, executionId, wake, freshIds) };
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Pre-launch wake claim. It uses the same state transition but deliberately does not reject refresh intent. */
|
|
75
|
+
export function claimKickoffMail(nodeId, executionId, now = Date.now()) {
|
|
76
|
+
return withCanvasWrite((db) => {
|
|
77
|
+
if (!executionIsLive(db, nodeId, executionId, false))
|
|
78
|
+
throw new Error(`execution ${executionId} is not live for node ${nodeId}`);
|
|
79
|
+
const freshIds = new Set(db.prepare(`SELECT delivery_id FROM deliveries
|
|
80
|
+
WHERE destination_kind = 'node_inbox' AND destination_id = ? AND wake = 1 AND state = 'pending' AND canceled_at IS NULL`).all(nodeId).map((row) => row.delivery_id));
|
|
81
|
+
db.prepare(`UPDATE deliveries SET state = 'handed_off', attempts = attempts + 1, claim_owner = ?, claimed_at = ?, updated_at = ?
|
|
82
|
+
WHERE destination_kind = 'node_inbox' AND destination_id = ? AND wake = 1 AND state = 'pending' AND canceled_at IS NULL`).run(executionId, now, now, nodeId);
|
|
83
|
+
return nodeMailRows(db, nodeId, executionId, 1, freshIds);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// Convert the existing subprocess contract at the one shared fence. The old names do not escape this module.
|
|
87
|
+
function normalizedSettlement(settlement) {
|
|
88
|
+
if (settlement.state === 'accepted')
|
|
89
|
+
return { state: 'acknowledged' };
|
|
90
|
+
return settlement;
|
|
91
|
+
}
|
|
92
|
+
/** The one execution/attempt/owner fence for node mail and external actions. */
|
|
93
|
+
export function settleDelivery(deliveryId, attempts, claimOwner, settlement, now = Date.now()) {
|
|
94
|
+
const next = normalizedSettlement(settlement);
|
|
95
|
+
const failure = next.state === 'pending' || next.state === 'permanent_failed' ? JSON.stringify(next.failure) : null;
|
|
96
|
+
const state = next.state === 'released' ? 'pending' : next.state;
|
|
97
|
+
const nextAttemptAt = next.state === 'pending' ? next.nextAttemptAt : state === 'pending' ? now : null;
|
|
98
|
+
return withCanvasWrite((db) => Number(db.prepare(`UPDATE deliveries
|
|
99
|
+
SET state = ?, next_attempt_at = ?, claimed_at = NULL, claim_owner = NULL,
|
|
100
|
+
acknowledged_at = ?, failed_at = ?, last_failure_json = ?, updated_at = ?
|
|
101
|
+
WHERE delivery_id = ? AND state = 'handed_off' AND attempts = ? AND claim_owner = ?`).run(state, nextAttemptAt, state === 'acknowledged' ? now : null, state === 'permanent_failed' ? now : null, failure, now, deliveryId, attempts, claimOwner).changes) === 1);
|
|
102
|
+
}
|
|
103
|
+
/** Broker acknowledgement additionally checks the live execution/refresh guards before the per-row fence. */
|
|
104
|
+
export function acknowledgeNodeMail(nodeId, executionId, deliveries, now = Date.now()) {
|
|
105
|
+
return withCanvasWrite((db) => {
|
|
106
|
+
if (!executionIsLive(db, nodeId, executionId, true))
|
|
107
|
+
return { action: 'hold', results: [] };
|
|
108
|
+
const results = deliveries.map((delivery) => {
|
|
109
|
+
const owned = db.prepare("SELECT 1 FROM deliveries WHERE delivery_id = ? AND destination_kind = 'node_inbox' AND destination_id = ?").get(delivery.deliveryId, nodeId) !== undefined;
|
|
110
|
+
return {
|
|
111
|
+
deliveryId: delivery.deliveryId,
|
|
112
|
+
outcome: owned && settleDelivery(delivery.deliveryId, delivery.attempts, executionId, { state: delivery.disposition }, now)
|
|
113
|
+
? delivery.disposition
|
|
114
|
+
: 'stale',
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
return { action: 'acknowledged', results };
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/** The immutable action document belonging to one external-action obligation. */
|
|
121
|
+
export function messageBodyForDelivery(deliveryId) {
|
|
122
|
+
const row = openDb().prepare(`SELECT m.body_json FROM deliveries d JOIN messages m ON m.message_id = d.message_id
|
|
123
|
+
WHERE d.delivery_id = ? AND d.destination_kind = 'external_action'`).get(deliveryId);
|
|
124
|
+
return row === undefined || row.body_json === null ? null : JSON.parse(row.body_json);
|
|
125
|
+
}
|
|
126
|
+
/** Read the one external-action obligation with this caller-owned identity. */
|
|
127
|
+
export function readExternalActionDelivery(idempotencyKey) {
|
|
128
|
+
const row = openDb().prepare("SELECT * FROM deliveries WHERE destination_kind = 'external_action' AND idempotency_key = ?").get(idempotencyKey);
|
|
129
|
+
return row === undefined ? null : deliveryFrom(row);
|
|
130
|
+
}
|
|
131
|
+
export function dueExternalActionDeliveries(now) {
|
|
132
|
+
return openDb().prepare(`SELECT * FROM deliveries
|
|
133
|
+
WHERE destination_kind = 'external_action' AND state = 'pending' AND canceled_at IS NULL AND next_attempt_at <= ?
|
|
134
|
+
ORDER BY next_attempt_at, created_at, delivery_id`).all(now).map(deliveryFrom);
|
|
135
|
+
}
|
|
136
|
+
export function claimExternalActionDelivery(deliveryId, now, claimOwner) {
|
|
137
|
+
return withCanvasWrite((db) => {
|
|
138
|
+
const result = db.prepare(`UPDATE deliveries SET state = 'handed_off', attempts = attempts + 1, claimed_at = ?, claim_owner = ?, updated_at = ?
|
|
139
|
+
WHERE delivery_id = ? AND destination_kind = 'external_action' AND state = 'pending' AND canceled_at IS NULL AND next_attempt_at <= ?`).run(now, claimOwner, now, deliveryId, now);
|
|
140
|
+
if (Number(result.changes) === 0)
|
|
141
|
+
return null;
|
|
142
|
+
return deliveryFrom(db.prepare('SELECT * FROM deliveries WHERE delivery_id = ?').get(deliveryId));
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
export function resetHandedOffDeliveries(now) {
|
|
146
|
+
return withCanvasWrite((db) => Number(db.prepare(`UPDATE deliveries SET state = 'pending', next_attempt_at = CASE WHEN destination_kind = 'external_action' THEN ? ELSE NULL END,
|
|
147
|
+
claimed_at = NULL, claim_owner = NULL, updated_at = ? WHERE state = 'handed_off'`).run(now, now).changes));
|
|
148
|
+
}
|
|
149
|
+
/** Reset mail held by an execution that is no longer this node's one live execution. */
|
|
150
|
+
export function resetStaleExecutionMail(now) {
|
|
151
|
+
return withCanvasWrite((db) => Number(db.prepare(`UPDATE deliveries AS d SET state = 'pending', claimed_at = NULL, claim_owner = NULL, updated_at = ?
|
|
152
|
+
WHERE d.destination_kind = 'node_inbox' AND d.state = 'handed_off'
|
|
153
|
+
AND NOT EXISTS (
|
|
154
|
+
SELECT 1 FROM executions e WHERE e.node_id = d.destination_id AND e.execution_id = d.claim_owner AND e.state != 'exited'
|
|
155
|
+
)`).run(now).changes));
|
|
156
|
+
}
|
|
157
|
+
export function hasUnseenWakeMail(nodeId) {
|
|
158
|
+
return openDb().prepare(`SELECT 1 FROM deliveries WHERE destination_kind = 'node_inbox' AND destination_id = ? AND wake = 1
|
|
159
|
+
AND canceled_at IS NULL AND urgency != 'deferred' AND state IN ('pending', 'handed_off') LIMIT 1`).get(nodeId) !== undefined;
|
|
160
|
+
}
|
|
161
|
+
export function hasWakeMailSince(nodeId, since) {
|
|
162
|
+
return openDb().prepare(`SELECT 1 FROM deliveries WHERE destination_kind = 'node_inbox' AND destination_id = ? AND wake = 1
|
|
163
|
+
AND urgency != 'deferred' AND created_at >= ? LIMIT 1`).get(nodeId, since) !== undefined;
|
|
164
|
+
}
|
|
165
|
+
export function acknowledgedNodeMailCount(nodeId) {
|
|
166
|
+
return Number(openDb().prepare("SELECT COUNT(*) AS count FROM deliveries WHERE destination_kind = 'node_inbox' AND destination_id = ? AND acknowledged_at IS NOT NULL").get(nodeId).count);
|
|
167
|
+
}
|
|
168
|
+
/** Register an outcome's frozen external destination before an outcome message exists. */
|
|
169
|
+
export function armExternalActionDelivery(db, input, now = Date.now()) {
|
|
170
|
+
const deliveryId = newId();
|
|
171
|
+
const result = db.prepare(`INSERT INTO deliveries
|
|
172
|
+
(delivery_id, message_id, destination_kind, destination_id, urgency, wake, state, adapter_json, idempotency_key, created_at, updated_at)
|
|
173
|
+
VALUES (?, NULL, 'external_action', ?, 'normal', 0, 'armed', ?, ?, ?, ?)
|
|
174
|
+
ON CONFLICT(idempotency_key) WHERE idempotency_key IS NOT NULL DO UPDATE SET
|
|
175
|
+
destination_id = excluded.destination_id, adapter_json = excluded.adapter_json, updated_at = excluded.updated_at
|
|
176
|
+
WHERE deliveries.state = 'armed'`).run(deliveryId, input.destinationId, JSON.stringify(input.adapter), input.idempotencyKey, now, now);
|
|
177
|
+
if (Number(result.changes) === 0)
|
|
178
|
+
throw new Error(`external action ${input.idempotencyKey} is already active`);
|
|
179
|
+
}
|
|
180
|
+
/** Attach a newly written immutable outcome message and make its registered action due. */
|
|
181
|
+
export function activateArmedExternalActionDelivery(db, input, now = Date.now()) {
|
|
182
|
+
const row = db.prepare("SELECT state FROM deliveries WHERE destination_kind = 'external_action' AND idempotency_key = ?").get(input.idempotencyKey);
|
|
183
|
+
if (row === undefined || (!input.supersede && row.state !== 'armed'))
|
|
184
|
+
return false;
|
|
185
|
+
let body;
|
|
186
|
+
try {
|
|
187
|
+
body = JSON.parse(input.bodyJson);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
throw new Error(`external action ${input.idempotencyKey} has invalid outcome document: ${error instanceof Error ? error.message : String(error)}`);
|
|
191
|
+
}
|
|
192
|
+
const messageId = newId();
|
|
193
|
+
const seq = Number(db.prepare('SELECT COALESCE(MAX(seq), 0) AS seq FROM messages').get().seq) + 1;
|
|
194
|
+
db.prepare(`INSERT INTO messages
|
|
195
|
+
(message_id, seq, operation_id, created_at, sender_node_id, sender_name, kind, label, body_ref, body_json, disposition)
|
|
196
|
+
VALUES (?, ?, ?, ?, NULL, NULL, 'completed', ?, NULL, ?, NULL)`).run(messageId, seq, newId(), new Date(now).toISOString(), input.label, JSON.stringify(body));
|
|
197
|
+
if (input.supersede) {
|
|
198
|
+
db.prepare(`UPDATE deliveries SET message_id = ?, state = 'pending', attempts = 0, next_attempt_at = ?, claimed_at = NULL, claim_owner = NULL,
|
|
199
|
+
acknowledged_at = NULL, failed_at = NULL, last_failure_json = NULL, updated_at = ?
|
|
200
|
+
WHERE destination_kind = 'external_action' AND idempotency_key = ?`).run(messageId, now, now, input.idempotencyKey);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
db.prepare(`UPDATE deliveries SET message_id = ?, state = 'pending', next_attempt_at = ?, updated_at = ?
|
|
204
|
+
WHERE destination_kind = 'external_action' AND idempotency_key = ? AND state = 'armed'`).run(messageId, now, now, input.idempotencyKey);
|
|
205
|
+
}
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
function newId() {
|
|
209
|
+
let id;
|
|
210
|
+
do
|
|
211
|
+
id = randomBytes(16).toString('hex');
|
|
212
|
+
while (id === '00000000000000000000000000000000');
|
|
213
|
+
return id;
|
|
214
|
+
}
|
|
215
|
+
function readJsonLines(path) {
|
|
216
|
+
if (!existsSync(path))
|
|
217
|
+
return [];
|
|
218
|
+
const raw = readFileSync(path, 'utf8');
|
|
219
|
+
if (raw.trim() === '')
|
|
220
|
+
return [];
|
|
221
|
+
return raw.split('\n').filter((line) => line.trim() !== '').map((line, index) => {
|
|
222
|
+
let entry;
|
|
223
|
+
try {
|
|
224
|
+
entry = JSON.parse(line);
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
throw new Error(`invalid mailbox JSON at ${path}:${index + 1}: ${error.message}`);
|
|
228
|
+
}
|
|
229
|
+
if (!/^(?!0{32}$)[0-9a-f]{32}$/.test(entry.entry_id) || !/^(?!0{32}$)[0-9a-f]{32}$/.test(entry.operation_id)) {
|
|
230
|
+
throw new Error(`invalid mailbox identity at ${path}:${index + 1}`);
|
|
231
|
+
}
|
|
232
|
+
if (typeof entry.ts !== 'string' || typeof entry.label !== 'string')
|
|
233
|
+
throw new Error(`incomplete mailbox entry at ${path}:${index + 1}`);
|
|
234
|
+
return entry;
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function readCanceled(path) {
|
|
238
|
+
if (!existsSync(path))
|
|
239
|
+
return new Set();
|
|
240
|
+
return new Set(readFileSync(path, 'utf8').split('\n').map((line) => line.trim()).filter(Boolean));
|
|
241
|
+
}
|
|
242
|
+
/** Create the messaging schema and import every legacy mailbox/action/wait row. The caller owns the encompassing migration transaction. */
|
|
243
|
+
export function migrateMessagingState(db) {
|
|
244
|
+
db.exec(`
|
|
245
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
246
|
+
message_id TEXT PRIMARY KEY,
|
|
247
|
+
seq INTEGER NOT NULL UNIQUE,
|
|
248
|
+
operation_id TEXT NOT NULL,
|
|
249
|
+
created_at TEXT NOT NULL,
|
|
250
|
+
sender_node_id TEXT,
|
|
251
|
+
sender_name TEXT,
|
|
252
|
+
kind TEXT NOT NULL CHECK (kind IN ('update', 'urgent', 'final', 'message', 'completed')),
|
|
253
|
+
label TEXT NOT NULL,
|
|
254
|
+
body_ref TEXT,
|
|
255
|
+
body_json TEXT,
|
|
256
|
+
disposition TEXT CHECK (disposition IS NULL OR disposition IN ('human-answer', 'human-canceled'))
|
|
257
|
+
);
|
|
258
|
+
CREATE TABLE IF NOT EXISTS deliveries (
|
|
259
|
+
delivery_id TEXT PRIMARY KEY,
|
|
260
|
+
message_id TEXT REFERENCES messages(message_id),
|
|
261
|
+
destination_kind TEXT NOT NULL CHECK (destination_kind IN ('node_inbox', 'external_action')),
|
|
262
|
+
destination_id TEXT NOT NULL,
|
|
263
|
+
urgency TEXT NOT NULL CHECK (urgency IN ('critical', 'urgent', 'normal', 'deferred')),
|
|
264
|
+
wake INTEGER NOT NULL CHECK (wake IN (0, 1)),
|
|
265
|
+
state TEXT NOT NULL CHECK (state IN ('armed', 'pending', 'handed_off', 'acknowledged', 'permanent_failed')),
|
|
266
|
+
attempts INTEGER NOT NULL DEFAULT 0 CHECK (attempts >= 0),
|
|
267
|
+
next_attempt_at INTEGER,
|
|
268
|
+
claimed_at INTEGER,
|
|
269
|
+
claim_owner TEXT,
|
|
270
|
+
acknowledged_at INTEGER,
|
|
271
|
+
failed_at INTEGER,
|
|
272
|
+
last_failure_json TEXT,
|
|
273
|
+
adapter_json TEXT,
|
|
274
|
+
effect_json TEXT,
|
|
275
|
+
idempotency_key TEXT,
|
|
276
|
+
canceled_at INTEGER,
|
|
277
|
+
created_at INTEGER NOT NULL,
|
|
278
|
+
updated_at INTEGER NOT NULL,
|
|
279
|
+
CHECK (state = 'armed' OR message_id IS NOT NULL)
|
|
280
|
+
);
|
|
281
|
+
CREATE UNIQUE INDEX IF NOT EXISTS messages_seq ON messages(seq);
|
|
282
|
+
CREATE INDEX IF NOT EXISTS deliveries_node_inbox ON deliveries(destination_id, wake, state, created_at) WHERE destination_kind = 'node_inbox';
|
|
283
|
+
CREATE INDEX IF NOT EXISTS deliveries_external_due ON deliveries(state, next_attempt_at) WHERE destination_kind = 'external_action';
|
|
284
|
+
CREATE INDEX IF NOT EXISTS deliveries_handed_off ON deliveries(claim_owner) WHERE state = 'handed_off';
|
|
285
|
+
CREATE INDEX IF NOT EXISTS deliveries_message ON deliveries(message_id);
|
|
286
|
+
CREATE UNIQUE INDEX IF NOT EXISTS deliveries_idempotency ON deliveries(idempotency_key) WHERE idempotency_key IS NOT NULL;
|
|
287
|
+
CREATE TABLE IF NOT EXISTS waits (
|
|
288
|
+
wait_id TEXT PRIMARY KEY,
|
|
289
|
+
node_id TEXT NOT NULL REFERENCES nodes(node_id) ON DELETE CASCADE,
|
|
290
|
+
kind TEXT NOT NULL CHECK (kind = 'controller'),
|
|
291
|
+
target_id TEXT NOT NULL,
|
|
292
|
+
generation INTEGER NOT NULL,
|
|
293
|
+
created_at INTEGER NOT NULL,
|
|
294
|
+
satisfied_at INTEGER
|
|
295
|
+
);
|
|
296
|
+
CREATE UNIQUE INDEX IF NOT EXISTS waits_live ON waits(node_id) WHERE satisfied_at IS NULL;
|
|
297
|
+
CREATE INDEX IF NOT EXISTS waits_node ON waits(node_id);
|
|
298
|
+
`);
|
|
299
|
+
const mail = [];
|
|
300
|
+
for (const row of db.prepare('SELECT node_id FROM nodes').all()) {
|
|
301
|
+
const nodeId = row.node_id;
|
|
302
|
+
const inbox = readJsonLines(inboxPath(nodeId));
|
|
303
|
+
const cursor = existsSync(`${inboxPath(nodeId)}.cursor`) ? readFileSync(`${inboxPath(nodeId)}.cursor`, 'utf8').trim() : '';
|
|
304
|
+
const cursorIndex = inbox.findIndex((entry) => entry.entry_id === cursor);
|
|
305
|
+
const canceled = readCanceled(`${inboxPath(nodeId)}.canceled`);
|
|
306
|
+
inbox.forEach((entry, physicalIndex) => mail.push({ nodeId, wake: 1, source: 'inbox', fileRank: 0, physicalIndex, entry, acknowledged: cursorIndex >= 0 && physicalIndex <= cursorIndex, canceled: canceled.has(entry.entry_id), seq: 0 }));
|
|
307
|
+
for (const [fileRank, path] of [[0, `${passivePath(nodeId)}.draining`], [1, passivePath(nodeId)]]) {
|
|
308
|
+
readJsonLines(path).forEach((entry, physicalIndex) => mail.push({ nodeId, wake: 0, source: 'passive', fileRank, physicalIndex, entry, acknowledged: false, canceled: false, seq: 0 }));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
mail.sort((a, b) => a.entry.ts.localeCompare(b.entry.ts) || a.nodeId.localeCompare(b.nodeId) || a.fileRank - b.fileRank || a.physicalIndex - b.physicalIndex);
|
|
312
|
+
mail.forEach((entry, index) => { entry.seq = index + 1; });
|
|
313
|
+
const groups = new Map();
|
|
314
|
+
for (const entry of mail) {
|
|
315
|
+
const key = `${entry.nodeId}\0${entry.source}`;
|
|
316
|
+
const group = groups.get(key) ?? [];
|
|
317
|
+
group.push(entry);
|
|
318
|
+
groups.set(key, group);
|
|
319
|
+
}
|
|
320
|
+
for (const group of groups.values()) {
|
|
321
|
+
const available = group.map((entry) => entry.seq).sort((a, b) => a - b);
|
|
322
|
+
group.sort((a, b) => a.fileRank - b.fileRank || a.physicalIndex - b.physicalIndex).forEach((entry, index) => { entry.seq = available[index]; });
|
|
323
|
+
}
|
|
324
|
+
const insertMessage = db.prepare(`INSERT INTO messages (message_id, seq, operation_id, created_at, sender_node_id, sender_name, kind, label, body_ref, body_json, disposition) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
325
|
+
const insertDelivery = db.prepare(`INSERT INTO deliveries (delivery_id, message_id, destination_kind, destination_id, urgency, wake, state, attempts, acknowledged_at, canceled_at, created_at, updated_at) VALUES (?, ?, 'node_inbox', ?, ?, ?, ?, 0, ?, ?, ?, ?)`);
|
|
326
|
+
for (const imported of mail) {
|
|
327
|
+
const entry = imported.entry;
|
|
328
|
+
insertMessage.run(entry.entry_id, imported.seq, entry.operation_id, entry.ts, entry.from, entry.from_name ?? null, entry.kind, entry.label, entry.ref ?? null, entry.data === undefined ? null : JSON.stringify(entry.data), entry.disposition ?? null);
|
|
329
|
+
const timestamp = Date.parse(entry.ts);
|
|
330
|
+
const now = Number.isFinite(timestamp) ? timestamp : 0;
|
|
331
|
+
insertDelivery.run(newId(), entry.entry_id, imported.nodeId, entry.tier, imported.wake, imported.acknowledged ? 'acknowledged' : 'pending', imported.acknowledged ? now : null, imported.canceled ? now : null, now, now);
|
|
332
|
+
}
|
|
333
|
+
const actionRows = db.prepare('SELECT * FROM human_action_deliveries').all();
|
|
334
|
+
for (const row of actionRows) {
|
|
335
|
+
const messageId = newId();
|
|
336
|
+
insertMessage.run(messageId, nextSeq(db), newId(), new Date(row.created_at).toISOString(), null, null, 'completed', row.request_id, null, row.completion_json, null);
|
|
337
|
+
const state = row.state === 'running' ? 'handed_off' : row.state === 'accepted' ? 'acknowledged' : row.state;
|
|
338
|
+
db.prepare(`INSERT INTO deliveries (delivery_id, message_id, destination_kind, destination_id, urgency, wake, state, attempts, next_attempt_at, claimed_at, claim_owner, acknowledged_at, failed_at, last_failure_json, adapter_json, idempotency_key, created_at, updated_at) VALUES (?, ?, 'external_action', 'human_action', 'normal', 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(newId(), messageId, state, row.attempt, row.next_attempt_at, row.claimed_at, row.claim_owner, row.accepted_at, row.permanent_failed_at, row.last_failure_json, JSON.stringify({ adapter: 'subprocess', argv: JSON.parse(row.argv_json), cwd: row.cwd }), `human-action:${row.request_id}`, row.created_at, row.updated_at);
|
|
339
|
+
}
|
|
340
|
+
const outcomeRows = db.prepare('SELECT * FROM node_outcome_deliveries').all();
|
|
341
|
+
for (const row of outcomeRows) {
|
|
342
|
+
const messageId = row.document_json === null ? null : newId();
|
|
343
|
+
if (messageId !== null)
|
|
344
|
+
insertMessage.run(messageId, nextSeq(db), newId(), new Date(row.created_at).toISOString(), null, null, 'completed', row.node_id, null, row.document_json, null);
|
|
345
|
+
const state = row.document_json === null ? 'armed' : row.state === 'running' ? 'handed_off' : row.state === 'accepted' ? 'acknowledged' : row.state;
|
|
346
|
+
db.prepare(`INSERT INTO deliveries (delivery_id, message_id, destination_kind, destination_id, urgency, wake, state, attempts, next_attempt_at, claimed_at, claim_owner, acknowledged_at, failed_at, last_failure_json, adapter_json, idempotency_key, created_at, updated_at) VALUES (?, ?, 'external_action', ?, 'normal', 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(newId(), messageId, row.action_name, state, row.attempt, row.next_attempt_at, row.claimed_at, row.claim_owner, row.accepted_at, row.permanent_failed_at, row.last_failure_json, JSON.stringify({ adapter: 'subprocess', argv: JSON.parse(row.argv_json), cwd: row.cwd, config_origin: row.config_origin, payload: row.payload_json === null ? null : JSON.parse(row.payload_json) }), `node-outcome:${row.node_id}`, row.created_at, row.updated_at);
|
|
347
|
+
}
|
|
348
|
+
const waiting = db.prepare('SELECT node_id, waiting_for, waiting_for_generation FROM nodes WHERE waiting_for IS NOT NULL').all();
|
|
349
|
+
for (const row of waiting)
|
|
350
|
+
db.prepare("INSERT INTO waits (wait_id, node_id, kind, target_id, generation, created_at, satisfied_at) VALUES (?, ?, 'controller', ?, ?, ?, NULL)").run(newId(), row.node_id, row.waiting_for, row.waiting_for_generation, Date.now());
|
|
351
|
+
}
|
|
352
|
+
function nextSeq(db) {
|
|
353
|
+
return Number(db.prepare('SELECT COALESCE(MAX(seq), 0) AS seq FROM messages').get().seq) + 1;
|
|
354
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface Execution {
|
|
2
|
+
executionId: string;
|
|
3
|
+
nodeId: string;
|
|
4
|
+
daemonEpoch: string | null;
|
|
5
|
+
pid: number | null;
|
|
6
|
+
processIdentity: string | null;
|
|
7
|
+
state: 'launching' | 'running' | 'exited';
|
|
8
|
+
startedAt: string;
|
|
9
|
+
endedAt: string | null;
|
|
10
|
+
exitReason: string | null;
|
|
11
|
+
}
|
|
12
|
+
export declare function liveExecutionForNode(nodeId: string): Execution | null;
|
|
13
|
+
export declare function beginExecution(nodeId: string, input: {
|
|
14
|
+
daemonEpoch: string | null;
|
|
15
|
+
}): string;
|
|
16
|
+
/** Returns shutdown when a late broker references an execution no longer live. */
|
|
17
|
+
export declare function recordExecutionPid(executionId: string, nodeId: string, pid: number): 'recorded' | 'shutdown';
|
|
18
|
+
export declare function endExecution(executionId: string, input: {
|
|
19
|
+
reason: string;
|
|
20
|
+
}): boolean;
|
|
21
|
+
export declare function abortExecution(executionId: string, input: {
|
|
22
|
+
reason: string;
|
|
23
|
+
}): boolean;
|
|
24
|
+
export declare function endLiveExecution(nodeId: string, input: {
|
|
25
|
+
reason: string;
|
|
26
|
+
}): boolean;
|
|
27
|
+
export declare function migrateLegacyExecutionIdentities(captureIdentity?: (pid: number) => string | undefined): {
|
|
28
|
+
migrated: string[];
|
|
29
|
+
};
|