@north-light/crouter 0.3.311 → 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 +17 -5
- 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 +312 -1
- 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 +62 -19
- 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 +4 -3
- package/dist/core/__tests__/on-read-dedup-resume.test.js +8 -7
- package/dist/core/__tests__/on-read-nested-store.test.js +8 -7
- 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 +35 -32
- 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 +5 -4
- 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 +19 -5
- 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/command-hooks/artifact.d.ts +6 -0
- package/dist/core/command-hooks/artifact.js +22 -6
- package/dist/core/command-hooks/discovery.d.ts +2 -0
- package/dist/core/command-hooks/discovery.js +2 -0
- package/dist/core/command-hooks/lifecycle-catalog.d.ts +20 -2
- package/dist/core/command-hooks/lifecycle-catalog.js +7 -2
- package/dist/core/command-hooks/lifecycle-create.d.ts +4 -0
- package/dist/core/command-hooks/lifecycle-create.js +51 -0
- package/dist/core/command-hooks/schema.d.ts +2 -2
- package/dist/core/command-hooks/schema.js +5 -4
- package/dist/core/command-hooks/transport/exec-invoke.js +3 -1
- package/dist/core/command-hooks/transport/exec-lifecycle.d.ts +18 -8
- package/dist/core/command-hooks/transport/exec-lifecycle.js +89 -30
- package/dist/core/config.d.ts +17 -12
- package/dist/core/config.js +106 -155
- package/dist/core/errors.d.ts +1 -0
- package/dist/core/errors.js +4 -0
- 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 +4 -3
- 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/companion.d.ts +1 -1
- package/dist/core/review/companion.js +2 -2
- 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/nodes.d.ts +5 -1
- package/dist/core/runtime/nodes.js +26 -11
- package/dist/core/runtime/placement.js +2 -2
- package/dist/core/runtime/recycle.js +24 -14
- package/dist/core/runtime/reset.js +26 -7
- package/dist/core/runtime/revive.js +226 -217
- package/dist/core/runtime/spawn.d.ts +4 -1
- package/dist/core/runtime/spawn.js +43 -5
- package/dist/core/runtime/warm-pool.d.ts +2 -2
- package/dist/core/runtime/warm-pool.js +23 -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 +41 -17
- 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 +6 -3
- 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 +29 -43
- 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 -148
- 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.d.ts +1 -0
- package/dist/daemon/api/handlers/nodes.js +41 -42
- 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/hook-authoring.d.ts +28 -2
- package/dist/hook-authoring.js +53 -1
- package/dist/index.d.ts +3 -2
- 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,127 +1,39 @@
|
|
|
1
|
-
// The canvas data-access layer.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// Source-of-truth split: a node's meta.json is canonical for its own fields;
|
|
5
|
-
// the db row is a queryable index re-derivable from it. The subscribes_to edges
|
|
6
|
-
// are db-authoritative (mutable, many-writers — what WAL is for).
|
|
1
|
+
// The canvas data-access layer. Nodes, their sessions, executions, and managed
|
|
2
|
+
// worktrees are SQL-authoritative. Node directories hold only artifacts.
|
|
7
3
|
import { envNodeId } from '../../shared/env.js';
|
|
8
|
-
import { existsSync,
|
|
4
|
+
import { existsSync, realpathSync, rmSync, statSync } from 'node:fs';
|
|
9
5
|
import { join, resolve } from 'node:path';
|
|
10
6
|
import { openDb, withCanvasWrite } from './db.js';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/** The identity keys meta.json persists. Listed explicitly so no runtime field
|
|
20
|
-
* can ever leak onto disk even when a fully-hydrated NodeMeta is handed in. */
|
|
21
|
-
const IDENTITY_KEYS = [
|
|
22
|
-
'node_id', 'name', 'description', 'title', 'icon', 'cycles', 'created', 'cwd', 'host_kind', 'profile_id', 'kind', 'mode',
|
|
23
|
-
'lifecycle', 'persona_ack', 'parent', 'spawned_by', 'creator', 'fork_from', 'fork_source_file', 'review_binding', 'passive_default', 'pi_session_id',
|
|
24
|
-
'pi_session_file', 'cycle_pending', 'model_override',
|
|
25
|
-
'launch', 'managed_worktree',
|
|
26
|
-
];
|
|
27
|
-
/** Project any node object down to its durable-identity subset. */
|
|
28
|
-
function toIdentity(m) {
|
|
29
|
-
const out = {};
|
|
30
|
-
for (const k of IDENTITY_KEYS) {
|
|
31
|
-
if (m[k] !== undefined)
|
|
32
|
-
out[k] = m[k];
|
|
33
|
-
}
|
|
34
|
-
for (const key of ['pi_session_file', 'fork_source_file']) {
|
|
35
|
-
if (typeof out[key] === 'string')
|
|
36
|
-
out[key] = canonicalSessionFile(out[key]);
|
|
37
|
-
}
|
|
38
|
-
return out;
|
|
39
|
-
}
|
|
40
|
-
function readMeta(nodeId) {
|
|
41
|
-
const p = nodeMetaPath(nodeId);
|
|
42
|
-
if (!existsSync(p))
|
|
43
|
-
return null;
|
|
44
|
-
// Legacy metas may still carry runtime fields on disk; toIdentity-on-read is
|
|
45
|
-
// unnecessary (callers go through getNode, which overlays the row), but the
|
|
46
|
-
// raw parse is typed as identity — extra props are ignored.
|
|
47
|
-
//
|
|
48
|
-
// Tolerate an EMPTY or unparseable meta.json by treating it as MISSING (null)
|
|
49
|
-
// rather than throwing. A bare write-temp-then-rename without fsync can leave a
|
|
50
|
-
// 0-byte meta.json on an NFS-backed home after the holding process/sandbox dies
|
|
51
|
-
// (see writeMeta + the guest cold-boot bug it guards against); a single corrupt
|
|
52
|
-
// node must not crash rebuildIndex/revive for the whole canvas. Recovery
|
|
53
|
-
// (re-seed from a durable snapshot) is the caller's job.
|
|
54
|
-
const raw = readFileSync(p, 'utf8');
|
|
55
|
-
if (raw.trim() === '')
|
|
56
|
-
return null;
|
|
57
|
-
try {
|
|
58
|
-
return JSON.parse(raw);
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
7
|
+
import { activateArmedExternalActionDelivery, armExternalActionDelivery } from './deliveries.js';
|
|
8
|
+
import { findNodeBySessionFileSql } from './sessions.js';
|
|
9
|
+
import { readFault, readLiveTurn } from './recovery.js';
|
|
10
|
+
import { hasUnreconciledWorktree, openWorktree, readWorktree } from './worktrees.js';
|
|
11
|
+
import { isPidAlive, recordedPidLiveness } from './pid.js';
|
|
12
|
+
import { ensureHome, ensureNodeDirs, jobDir, nodeDir } from './paths.js';
|
|
13
|
+
function jsonValue(value, field) {
|
|
14
|
+
if (value === undefined || value === null)
|
|
61
15
|
return null;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
* Durable write: fsync the temp file before the rename, then fsync the parent
|
|
67
|
-
* directory so the rename itself is on disk. A bare writeFileSync+rename is not
|
|
68
|
-
* crash-durable — a host crash or abrupt sandbox teardown shortly after the
|
|
69
|
-
* rename can surface a 0-byte or missing meta.json (observed on a guest home as
|
|
70
|
-
* a cold-boot 0-byte meta.json → rebuild-index/revive JSON.parse('')). meta.json
|
|
71
|
-
* is written rarely (birth, polymorph, session-id capture, naming) so the
|
|
72
|
-
* per-write fsync cost is negligible. */
|
|
73
|
-
function writeMeta(meta) {
|
|
74
|
-
writeJsonFileDurably(nodeMetaPath(meta.node_id), toIdentity(meta));
|
|
75
|
-
}
|
|
76
|
-
// row index — identity columns are a derived projection of meta; runtime
|
|
77
|
-
// columns are authoritative. The two have DIFFERENT writers: upsertRow only
|
|
78
|
-
// ever touches identity (so a re-index never clobbers live runtime), while
|
|
79
|
-
// createNode seeds runtime once and the atomic setters own it thereafter.
|
|
80
|
-
/** Upsert the IDENTITY columns of a node's row. ON CONFLICT updates identity
|
|
81
|
-
* ONLY — runtime columns (status/intent/pi_pid/window/tmux_session/pane) are left
|
|
82
|
-
* exactly as they are, so re-indexing or an identity edit never disturbs live
|
|
83
|
-
* state. A fresh insert takes the schema defaults for runtime. */
|
|
84
|
-
function upsertRow(meta) {
|
|
85
|
-
openDb()
|
|
86
|
-
.prepare(`INSERT INTO nodes (node_id, name, description, cycles, kind, mode, lifecycle, cwd, host_kind, profile_id, parent, created, pi_session_file)
|
|
87
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
88
|
-
ON CONFLICT(node_id) DO UPDATE SET
|
|
89
|
-
name=excluded.name, description=excluded.description, cycles=excluded.cycles,
|
|
90
|
-
kind=excluded.kind, mode=excluded.mode,
|
|
91
|
-
lifecycle=excluded.lifecycle, cwd=excluded.cwd, host_kind=excluded.host_kind,
|
|
92
|
-
profile_id=excluded.profile_id, parent=excluded.parent,
|
|
93
|
-
pi_session_file=excluded.pi_session_file`)
|
|
94
|
-
.run(meta.node_id, meta.name, meta.description ?? null, meta.cycles ?? null, meta.kind, meta.mode, meta.lifecycle, meta.cwd, meta.host_kind ?? null, meta.profile_id ?? null, meta.parent ?? null, meta.created, typeof meta.pi_session_file === 'string' ? canonicalSessionFile(meta.pi_session_file) : null);
|
|
16
|
+
const encoded = JSON.stringify(value);
|
|
17
|
+
if (encoded === undefined)
|
|
18
|
+
throw new TypeError(`${field} must be JSON-serializable`);
|
|
19
|
+
return encoded;
|
|
95
20
|
}
|
|
96
21
|
function insertArmedOutcomeDelivery(db, nodeId, delivery) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
status, intent, waiting_for, final_report, finalized_at, deadline_at, pi_pid, "window", tmux_session, pane)
|
|
110
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
111
|
-
ON CONFLICT(node_id) DO UPDATE SET
|
|
112
|
-
name=excluded.name, description=excluded.description, cycles=excluded.cycles,
|
|
113
|
-
kind=excluded.kind, mode=excluded.mode,
|
|
114
|
-
lifecycle=excluded.lifecycle, cwd=excluded.cwd, host_kind=excluded.host_kind,
|
|
115
|
-
profile_id=excluded.profile_id, parent=excluded.parent,
|
|
116
|
-
pi_session_file=excluded.pi_session_file,
|
|
117
|
-
status=excluded.status, intent=excluded.intent, waiting_for=excluded.waiting_for,
|
|
118
|
-
final_report=excluded.final_report, finalized_at=excluded.finalized_at,
|
|
119
|
-
pi_pid=excluded.pi_pid, "window"=excluded."window", tmux_session=excluded.tmux_session,
|
|
120
|
-
pane=excluded.pane`)
|
|
121
|
-
.run(meta.node_id, meta.name, meta.description ?? null, meta.cycles ?? null, meta.kind, meta.mode, meta.lifecycle, meta.cwd, meta.host_kind ?? null, meta.profile_id ?? null, meta.parent ?? null, meta.created, typeof meta.pi_session_file === 'string' ? canonicalSessionFile(meta.pi_session_file) : null, meta.status ?? 'active', meta.intent ?? null, meta.waiting_for ?? null, meta.final_report ?? null, meta.finalized_at ?? null, meta.deadline_at ?? null, meta.pi_pid ?? null, meta.window ?? null, meta.tmux_session ?? null, meta.pane ?? null);
|
|
22
|
+
armExternalActionDelivery(db, {
|
|
23
|
+
destinationId: delivery.actionName,
|
|
24
|
+
adapter: {
|
|
25
|
+
adapter: 'subprocess', argv: delivery.argv, cwd: delivery.cwd,
|
|
26
|
+
config_origin: delivery.configOrigin, payload: delivery.payload ?? null,
|
|
27
|
+
},
|
|
28
|
+
idempotencyKey: `node-outcome:${nodeId}`,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function seedRow(meta, db) {
|
|
32
|
+
db.prepare(`INSERT INTO nodes (node_id, name, description, title, icon, cycles, kind, mode, lifecycle, cwd, host_kind, profile_id, parent, spawned_by, creator, fork_from, fork_source_file, created, passive_default, cycle_pending, model_override, persona_ack_json, review_binding_json, launch_config_json, status, intent, waiting_for, final_report, finalized_at, deadline_at, "window", tmux_session, pane) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
33
|
+
.run(meta.node_id, meta.name, meta.description ?? null, meta.title ?? null, meta.icon ?? null, meta.cycles ?? null, meta.kind, meta.mode, meta.lifecycle, meta.cwd, meta.host_kind ?? null, meta.profile_id ?? null, meta.parent ?? null, meta.spawned_by ?? null, meta.creator ?? null, meta.fork_from ?? null, meta.fork_source_file ?? null, meta.created, meta.passive_default === true ? 1 : 0, meta.cycle_pending === true ? 1 : 0, meta.model_override ?? null, jsonValue(meta.persona_ack, 'persona_ack'), jsonValue(meta.review_binding, 'review_binding'), jsonValue(meta.launch, 'launch'), meta.status ?? 'active', meta.intent ?? null, meta.waiting_for ?? null, meta.final_report ?? null, meta.finalized_at ?? null, meta.deadline_at ?? null, meta.window ?? null, meta.tmux_session ?? null, meta.pane ?? null);
|
|
122
34
|
}
|
|
123
35
|
function rowFrom(r) {
|
|
124
|
-
|
|
36
|
+
const node = {
|
|
125
37
|
node_id: r['node_id'],
|
|
126
38
|
name: r['name'],
|
|
127
39
|
description: r['description'] ?? undefined,
|
|
@@ -156,75 +68,54 @@ function rowFrom(r) {
|
|
|
156
68
|
window: r['window'] ?? null,
|
|
157
69
|
tmux_session: r['tmux_session'] ?? null,
|
|
158
70
|
pane: r['pane'] ?? null,
|
|
71
|
+
fault: null,
|
|
72
|
+
streaming: false,
|
|
159
73
|
};
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
.get(nodeId);
|
|
167
|
-
if (r === undefined)
|
|
168
|
-
return null;
|
|
169
|
-
return {
|
|
170
|
-
status: r['status'] ?? 'active',
|
|
171
|
-
intent: r['intent'] ?? null,
|
|
172
|
-
waiting_for: r['waiting_for'] ?? null,
|
|
173
|
-
frozen_at: r['frozen_at'] ?? null,
|
|
174
|
-
respawn_failures: r['respawn_failures'] ?? 0,
|
|
175
|
-
respawn_not_before: r['respawn_not_before'] ?? null,
|
|
176
|
-
launched_at: r['launched_at'] ?? null,
|
|
177
|
-
last_settled_at: r['last_settled_at'] ?? null,
|
|
178
|
-
terminal_reason: r['terminal_reason'] ?? null,
|
|
179
|
-
final_report: r['final_report'] ?? null,
|
|
180
|
-
finalized_at: r['finalized_at'] ?? null,
|
|
181
|
-
outcome_kind: r['outcome_kind'] ?? null,
|
|
182
|
-
outcome_reason: r['outcome_reason'] ?? null,
|
|
183
|
-
outcome_at: r['outcome_at'] ?? null,
|
|
184
|
-
outcome_revision: r['outcome_revision'] ?? 0,
|
|
185
|
-
outcome_detail_json: r['outcome_detail_json'] ?? null,
|
|
186
|
-
deadline_at: r['deadline_at'] ?? null,
|
|
187
|
-
pi_pid: r['pi_pid'] ?? null,
|
|
188
|
-
pi_pid_identity: r['pi_pid_identity'] ?? null,
|
|
189
|
-
window: r['window'] ?? null,
|
|
190
|
-
tmux_session: r['tmux_session'] ?? null,
|
|
191
|
-
pane: r['pane'] ?? null,
|
|
192
|
-
};
|
|
74
|
+
const fault = readFault(node.node_id);
|
|
75
|
+
node.fault = fault?.link === 'pi→provider' && fault.retry.by === 'daemon' && fault.retry.disposition === 'auto' && !isPidAlive(node.pi_pid)
|
|
76
|
+
? null
|
|
77
|
+
: fault;
|
|
78
|
+
node.streaming = readLiveTurn(node.node_id)?.busy === true && isPidAlive(node.pi_pid);
|
|
79
|
+
return node;
|
|
193
80
|
}
|
|
194
81
|
// Nodes
|
|
195
|
-
/** Create a node
|
|
196
|
-
* row (identity + runtime from the incoming meta). Returns the hydrated view. */
|
|
82
|
+
/** Create a SQL-authoritative node, then ensure its artifact directories exist. */
|
|
197
83
|
export function createNode(meta, opts = {}) {
|
|
198
84
|
ensureHome();
|
|
199
|
-
const scaffolded = !existsSync(nodeDir(meta.node_id));
|
|
200
|
-
ensureNodeDirs(meta.node_id);
|
|
201
|
-
try {
|
|
202
|
-
writeMeta(meta);
|
|
203
|
-
}
|
|
204
|
-
catch (error) {
|
|
205
|
-
// No row exists yet, so a dir left here is invisible to every surface and to
|
|
206
|
-
// ghost-row reaping. Only unwind a dir this call created.
|
|
207
|
-
if (scaffolded)
|
|
208
|
-
rmSync(nodeDir(meta.node_id), { recursive: true, force: true });
|
|
209
|
-
throw error;
|
|
210
|
-
}
|
|
211
85
|
withCanvasWrite((db) => {
|
|
212
86
|
seedRow(meta, db);
|
|
87
|
+
if (meta.managed_worktree !== undefined && meta.managed_worktree !== null)
|
|
88
|
+
openWorktree(meta.node_id, meta.managed_worktree, db);
|
|
213
89
|
if (opts.outcomeDelivery !== undefined)
|
|
214
90
|
insertArmedOutcomeDelivery(db, meta.node_id, opts.outcomeDelivery);
|
|
215
91
|
});
|
|
92
|
+
ensureNodeDirs(meta.node_id);
|
|
216
93
|
return getNode(meta.node_id);
|
|
217
94
|
}
|
|
218
|
-
/** The canonical node record
|
|
219
|
-
* runtime (the row). Null if unknown. */
|
|
95
|
+
/** The canonical node record, hydrated from SQL. Null if unknown. */
|
|
220
96
|
export function getNode(nodeId) {
|
|
221
|
-
const
|
|
222
|
-
if (
|
|
97
|
+
const row = openDb().prepare('SELECT * FROM node_rows WHERE node_id = ?').get(nodeId);
|
|
98
|
+
if (row === undefined)
|
|
223
99
|
return null;
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
100
|
+
const worktree = readWorktree(nodeId);
|
|
101
|
+
return {
|
|
102
|
+
...rowFrom(row),
|
|
103
|
+
title: row['title'] ?? undefined,
|
|
104
|
+
icon: row['icon'] ?? undefined,
|
|
105
|
+
spawned_by: row['spawned_by'] ?? null,
|
|
106
|
+
creator: row['creator'] ?? null,
|
|
107
|
+
fork_from: row['fork_from'] ?? null,
|
|
108
|
+
fork_source_file: row['fork_source_file'] ?? null,
|
|
109
|
+
passive_default: row['passive_default'] === 1,
|
|
110
|
+
cycle_pending: row['cycle_pending'] === 1,
|
|
111
|
+
model_override: row['model_override'] ?? null,
|
|
112
|
+
persona_ack: row['persona_ack_json'] == null ? undefined : JSON.parse(row['persona_ack_json']),
|
|
113
|
+
review_binding: row['review_binding_json'] == null ? undefined : JSON.parse(row['review_binding_json']),
|
|
114
|
+
launch: row['launch_config_json'] == null ? undefined : JSON.parse(row['launch_config_json']),
|
|
115
|
+
pi_session_id: row['pi_session_id'] ?? null,
|
|
116
|
+
pi_session_file: row['pi_session_file'] ?? null,
|
|
117
|
+
...(worktree === null ? {} : { managed_worktree: worktree }),
|
|
118
|
+
};
|
|
228
119
|
}
|
|
229
120
|
export function canonicalSessionFile(sessionFile) {
|
|
230
121
|
const absolute = resolve(sessionFile);
|
|
@@ -232,20 +123,12 @@ export function canonicalSessionFile(sessionFile) {
|
|
|
232
123
|
}
|
|
233
124
|
/** Resolve the crouter node that owns one exact Pi session file. */
|
|
234
125
|
export function findNodeBySessionFile(sessionFile) {
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
// opened during repair of an older duplicate projection: the deterministic
|
|
238
|
-
// oldest row remains the owner instead of turning a lookup into a crash.
|
|
239
|
-
const match = openDb()
|
|
240
|
-
.prepare('SELECT node_id FROM nodes WHERE pi_session_file = ? ORDER BY created, node_id LIMIT 1')
|
|
241
|
-
.get(canonical);
|
|
242
|
-
return match === undefined ? null : getNode(match.node_id);
|
|
126
|
+
const nodeId = findNodeBySessionFileSql(sessionFile);
|
|
127
|
+
return nodeId === null ? null : getNode(nodeId);
|
|
243
128
|
}
|
|
244
129
|
/** The indexed row (from the db) — cheap for queries that don't need full meta. */
|
|
245
130
|
export function getRow(nodeId) {
|
|
246
|
-
const r = openDb()
|
|
247
|
-
.prepare('SELECT * FROM nodes WHERE node_id = ?')
|
|
248
|
-
.get(nodeId);
|
|
131
|
+
const r = openDb().prepare('SELECT * FROM node_rows WHERE node_id = ?').get(nodeId);
|
|
249
132
|
return r ? rowFrom(r) : null;
|
|
250
133
|
}
|
|
251
134
|
/** The node row whose durable LOCATION pane is `pane`, or null. Lets placement
|
|
@@ -253,14 +136,12 @@ export function getRow(nodeId) {
|
|
|
253
136
|
* to adopt a caller's pane as a focus). pane is not UNIQUE in the schema, but a
|
|
254
137
|
* live pane backs at most one node, so this returns the single match. */
|
|
255
138
|
export function getRowByPane(pane) {
|
|
256
|
-
const r = openDb()
|
|
257
|
-
.prepare('SELECT * FROM nodes WHERE pane = ?')
|
|
258
|
-
.get(pane);
|
|
139
|
+
const r = openDb().prepare('SELECT * FROM node_rows WHERE pane = ?').get(pane);
|
|
259
140
|
return r ? rowFrom(r) : null;
|
|
260
141
|
}
|
|
261
142
|
/** Nodes whose persisted spawn deadline has elapsed and that remain live work. */
|
|
262
143
|
export function dueNodeDeadlines(nowIso) {
|
|
263
|
-
return openDb().prepare(`SELECT * FROM
|
|
144
|
+
return openDb().prepare(`SELECT * FROM node_rows
|
|
264
145
|
WHERE deadline_at IS NOT NULL AND outcome_at IS NULL
|
|
265
146
|
AND deadline_at <= ? AND status IN ('active','idle') AND kind != 'human'
|
|
266
147
|
ORDER BY deadline_at, created, node_id`).all(nowIso).map(rowFrom);
|
|
@@ -287,24 +168,56 @@ export function shiftProfileNodeDeadlines(profileId, pauseDurationMs) {
|
|
|
287
168
|
return shifted;
|
|
288
169
|
});
|
|
289
170
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
171
|
+
function sqlInput(value, field) {
|
|
172
|
+
if (value === null || value === undefined)
|
|
173
|
+
return null;
|
|
174
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'bigint' || value instanceof Uint8Array)
|
|
175
|
+
return value;
|
|
176
|
+
throw new TypeError(`${field} must be a SQLite scalar`);
|
|
177
|
+
}
|
|
178
|
+
/** Update node identity/configuration columns. Sessions and worktrees have named stores. */
|
|
294
179
|
export function updateNode(nodeId, patch) {
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
180
|
+
const fields = [];
|
|
181
|
+
const add = (key, column = key) => {
|
|
182
|
+
if (Object.prototype.hasOwnProperty.call(patch, key))
|
|
183
|
+
fields.push([column, sqlInput(patch[key], key)]);
|
|
184
|
+
};
|
|
185
|
+
add('name');
|
|
186
|
+
add('description');
|
|
187
|
+
add('title');
|
|
188
|
+
add('icon');
|
|
189
|
+
add('cycles');
|
|
190
|
+
add('kind');
|
|
191
|
+
add('mode');
|
|
192
|
+
add('lifecycle');
|
|
193
|
+
add('cwd');
|
|
194
|
+
add('host_kind');
|
|
195
|
+
add('profile_id');
|
|
196
|
+
add('parent');
|
|
197
|
+
add('spawned_by');
|
|
198
|
+
add('creator');
|
|
199
|
+
add('fork_from');
|
|
200
|
+
add('fork_source_file');
|
|
201
|
+
add('model_override');
|
|
202
|
+
if (Object.prototype.hasOwnProperty.call(patch, 'passive_default'))
|
|
203
|
+
fields.push(['passive_default', patch.passive_default === true ? 1 : 0]);
|
|
204
|
+
if (Object.prototype.hasOwnProperty.call(patch, 'cycle_pending'))
|
|
205
|
+
fields.push(['cycle_pending', patch.cycle_pending === true ? 1 : 0]);
|
|
206
|
+
if (Object.prototype.hasOwnProperty.call(patch, 'persona_ack'))
|
|
207
|
+
fields.push(['persona_ack_json', jsonValue(patch.persona_ack, 'persona_ack')]);
|
|
208
|
+
if (Object.prototype.hasOwnProperty.call(patch, 'review_binding'))
|
|
209
|
+
fields.push(['review_binding_json', jsonValue(patch.review_binding, 'review_binding')]);
|
|
210
|
+
if (Object.prototype.hasOwnProperty.call(patch, 'launch'))
|
|
211
|
+
fields.push(['launch_config_json', jsonValue(patch.launch, 'launch')]);
|
|
212
|
+
if (fields.length === 0) {
|
|
213
|
+
const existing = getNode(nodeId);
|
|
214
|
+
if (existing === null)
|
|
302
215
|
throw new Error(`unknown node: ${nodeId}`);
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
216
|
+
return existing;
|
|
217
|
+
}
|
|
218
|
+
const result = openDb().prepare(`UPDATE nodes SET ${fields.map(([column]) => `${column} = ?`).join(', ')} WHERE node_id = ?`).run(...fields.map(([, value]) => value), nodeId);
|
|
219
|
+
if (result.changes !== 1)
|
|
220
|
+
throw new Error(`unknown node: ${nodeId}`);
|
|
308
221
|
return getNode(nodeId);
|
|
309
222
|
}
|
|
310
223
|
// Atomic runtime setters — each one a single-statement UPDATE on the WAL'd row,
|
|
@@ -390,20 +303,13 @@ export function transitionStatusIntent(nodeId, fields, opts = {}) {
|
|
|
390
303
|
if (Number(result.changes) === 0)
|
|
391
304
|
return 0;
|
|
392
305
|
const latched = db.prepare('SELECT outcome_at FROM nodes WHERE node_id = ?').get(nodeId);
|
|
393
|
-
if (outcome.supersede === true || latched.outcome_at === outcome.at) {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
WHERE node_id = ?`).run(outcome.documentJson ?? null, now, now, nodeId);
|
|
401
|
-
}
|
|
402
|
-
else {
|
|
403
|
-
db.prepare(`UPDATE node_outcome_deliveries
|
|
404
|
-
SET state = 'pending', next_attempt_at = ?, document_json = ?, updated_at = ?
|
|
405
|
-
WHERE node_id = ? AND state = 'armed' AND ? IS NOT NULL`).run(now, outcome.documentJson ?? null, now, nodeId, outcome.documentJson ?? null);
|
|
406
|
-
}
|
|
306
|
+
if ((outcome.supersede === true || latched.outcome_at === outcome.at) && outcome.documentJson !== null && outcome.documentJson !== undefined) {
|
|
307
|
+
activateArmedExternalActionDelivery(db, {
|
|
308
|
+
idempotencyKey: `node-outcome:${nodeId}`,
|
|
309
|
+
label: nodeId,
|
|
310
|
+
bodyJson: outcome.documentJson,
|
|
311
|
+
supersede: outcome.supersede === true,
|
|
312
|
+
});
|
|
407
313
|
}
|
|
408
314
|
return Number(result.changes);
|
|
409
315
|
});
|
|
@@ -434,32 +340,6 @@ export function setPresence(nodeId, presence) {
|
|
|
434
340
|
.prepare('UPDATE nodes SET tmux_session = ?, "window" = ?, pane = ? WHERE node_id = ?')
|
|
435
341
|
.run(presence.tmux_session ?? null, presence.window ?? null, presence.pane ?? null, nodeId);
|
|
436
342
|
}
|
|
437
|
-
/** Record the live pi pid (daemon liveness signal) PLUS its launch-time
|
|
438
|
-
* identity fingerprint, in one atomic write:
|
|
439
|
-
* `capturePidIdentities([pid])` is a signal-0-adjacent `ps -p <pid>` probe
|
|
440
|
-
* taken RIGHT NOW, while `pid` is the process we just spawned/bound and
|
|
441
|
-
* therefore definitely still exists — this is the LAUNCH-time baseline
|
|
442
|
-
* `headlessBrokerHost.teardown()` later compares against before signaling
|
|
443
|
-
* anything. A failed probe (rare — `ps` itself misbehaving) or a probe that
|
|
444
|
-
* finds no row for `pid` (raced away in the instant between spawn and this
|
|
445
|
-
* call) both write `NULL`: no baseline recorded, so the teardown guard fails
|
|
446
|
-
* open exactly like a node booted before this column existed — never a false
|
|
447
|
-
* identity that could itself cause a false mismatch later. */
|
|
448
|
-
export function recordPid(nodeId, pid) {
|
|
449
|
-
const identity = capturePidIdentities([pid])?.get(pid) ?? null;
|
|
450
|
-
openDb()
|
|
451
|
-
.prepare('UPDATE nodes SET pi_pid = ?, pi_pid_identity = ? WHERE node_id = ?')
|
|
452
|
-
.run(pid, identity, nodeId);
|
|
453
|
-
}
|
|
454
|
-
/** Clear the pi pid (window-backed relaunch, before the fresh pi re-records it)
|
|
455
|
-
* AND its recorded launch-time identity — the two are written together by
|
|
456
|
-
* `recordPid` and must be cleared together, or a subsequent relaunch that
|
|
457
|
-
* reuses the pid namespace could compare its own fresh pid against a STALE
|
|
458
|
-
* identity left over from the pid this row held before. */
|
|
459
|
-
export function clearRecordedPid(nodeId) {
|
|
460
|
-
openDb().prepare('UPDATE nodes SET pi_pid = NULL, pi_pid_identity = NULL WHERE node_id = ?').run(nodeId);
|
|
461
|
-
}
|
|
462
|
-
export const clearPid = clearRecordedPid;
|
|
463
343
|
export function recordLaunch(nodeId, at) {
|
|
464
344
|
openDb()
|
|
465
345
|
.prepare('UPDATE nodes SET launched_at = ?, frozen_at = NULL, respawn_not_before = NULL WHERE node_id = ?')
|
|
@@ -479,70 +359,6 @@ export function recordRespawnAttempt(nodeId, attempt) {
|
|
|
479
359
|
.prepare('UPDATE nodes SET respawn_failures = ?, respawn_not_before = ? WHERE node_id = ?')
|
|
480
360
|
.run(attempt.failures, attempt.notBefore, nodeId);
|
|
481
361
|
}
|
|
482
|
-
/** ONE-TIME completion of the boot-scoped-identity format migration, run once
|
|
483
|
-
* at daemon startup (the natural boot-reconciliation chokepoint, right after
|
|
484
|
-
* `reconcileBootLiveness`). It closes the gap the `identitiesMatch` legacy
|
|
485
|
-
* lane only TOLERATES: a broker that survives an in-place crouter upgrade
|
|
486
|
-
* keeps its old `lstart#ticks` row indefinitely (`recordPid` only fires on
|
|
487
|
-
* launch/revive/session_start), so the cross-boot ticks-collision exposure the
|
|
488
|
-
* boot-scoped base was added to close never provably shuts for those rows.
|
|
489
|
-
*
|
|
490
|
-
* For every row whose RECORDED identity is legacy-format while this platform
|
|
491
|
-
* now composes new-format (a readable kernel boot_id):
|
|
492
|
-
* - if the recorded pid's CURRENT live identity matches the recorded one via
|
|
493
|
-
* the migration lane (same live process, `identitiesMatch` true) AND that
|
|
494
|
-
* current identity is itself boot-scoped — re-record it with the new
|
|
495
|
-
* composer, completing the migration in place;
|
|
496
|
-
* - otherwise (pid gone, probe failed, reused — no match, or no readable
|
|
497
|
-
* boot_id for it) — leave the row untouched for the normal liveness
|
|
498
|
-
* handling that already runs each tick.
|
|
499
|
-
*
|
|
500
|
-
* After this runs once, the legacy compare lane only ever engages for
|
|
501
|
-
* genuinely no-boot_id platforms. NOT a per-caller reconciliation layer — a
|
|
502
|
-
* single startup sweep. `captureIdentity` is injectable for unit tests.
|
|
503
|
-
*
|
|
504
|
-
* GATED on proven same-boot provenance by its caller (daemon startup runs it
|
|
505
|
-
* only when `reconcileBootLiveness` returned `sameBootProven` — see
|
|
506
|
-
* `bootProvenSame`). This is what makes a bare ticks match safe to bless as a
|
|
507
|
-
* current-boot identity: without that proof an equal-ticks collision with an
|
|
508
|
-
* unrelated process from a DIFFERENT boot could be rewritten as
|
|
509
|
-
* `<currentBootId>#<ticks>`, permanently baptizing a stranger.
|
|
510
|
-
*
|
|
511
|
-
* Not transactional by design: it never HOLDS the write lock across the (slow,
|
|
512
|
-
* per-row) `ps` probe. Each re-record is a compare-and-swap single-row UPDATE
|
|
513
|
-
* constrained by ALL three snapshotted values (`node_id`, `pi_pid`,
|
|
514
|
-
* `pi_pid_identity`), counted only when a row actually changed. So if the old
|
|
515
|
-
* broker exits and a concurrent revive atomically records a fresh
|
|
516
|
-
* `(pi_pid, pi_pid_identity)` between this sweep's probe and its update, the
|
|
517
|
-
* CAS matches zero rows and the migration is skipped rather than clobbering
|
|
518
|
-
* the newer identity with the stale one. */
|
|
519
|
-
export function migrateLegacyPidIdentities(captureIdentity = (pid) => capturePidIdentities([pid])?.get(pid)) {
|
|
520
|
-
const db = openDb();
|
|
521
|
-
const rows = db
|
|
522
|
-
.prepare('SELECT node_id, pi_pid, pi_pid_identity FROM nodes WHERE pi_pid IS NOT NULL AND pi_pid_identity IS NOT NULL')
|
|
523
|
-
.all();
|
|
524
|
-
// Compare-and-swap: rewrite only if the row STILL carries the exact pid +
|
|
525
|
-
// legacy identity we probed. A concurrent revive between probe and update
|
|
526
|
-
// records a new (pi_pid, pi_pid_identity) pair, so this WHERE fails to match
|
|
527
|
-
// and we skip rather than clobber the fresh baseline.
|
|
528
|
-
const upd = db.prepare('UPDATE nodes SET pi_pid_identity = ? WHERE node_id = ? AND pi_pid = ? AND pi_pid_identity = ?');
|
|
529
|
-
const migrated = [];
|
|
530
|
-
for (const row of rows) {
|
|
531
|
-
if (!isLegacyTicksIdentity(row.pi_pid_identity))
|
|
532
|
-
continue;
|
|
533
|
-
const current = captureIdentity(row.pi_pid);
|
|
534
|
-
if (current === undefined)
|
|
535
|
-
continue; // pid gone / probe failed → normal liveness handling
|
|
536
|
-
if (!isBootScopedIdentity(current))
|
|
537
|
-
continue; // no readable boot_id for it → nothing to upgrade
|
|
538
|
-
if (!identitiesMatch(current, row.pi_pid_identity))
|
|
539
|
-
continue; // reused → leave to liveness
|
|
540
|
-
const res = upd.run(current, row.node_id, row.pi_pid, row.pi_pid_identity);
|
|
541
|
-
if (res.changes > 0)
|
|
542
|
-
migrated.push(row.node_id); // skipped if a concurrent revive re-recorded
|
|
543
|
-
}
|
|
544
|
-
return { migrated };
|
|
545
|
-
}
|
|
546
362
|
/** The one clause that hides unclaimed warm-pool spares from every listing.
|
|
547
363
|
* A spare is a real node with a booted broker, but nobody has been handed it
|
|
548
364
|
* yet — surfacing it would put a phantom empty conversation in the user's
|
|
@@ -564,6 +380,16 @@ export function listNodesByProfile(profileId) {
|
|
|
564
380
|
});
|
|
565
381
|
}
|
|
566
382
|
/** All rows, optionally filtered by status and terminal execution traces. Unclaimed warm spares are excluded. */
|
|
383
|
+
/** Every node's durable memory-resolution coordinates, including unclaimed warm-pool spares. */
|
|
384
|
+
export function listNodeMemoryTargets() {
|
|
385
|
+
return openDb()
|
|
386
|
+
.prepare('SELECT node_id, cwd, profile_id FROM nodes ORDER BY created')
|
|
387
|
+
.all()
|
|
388
|
+
.map((row) => {
|
|
389
|
+
const node = row;
|
|
390
|
+
return { nodeId: node.node_id, cwd: node.cwd, profileId: node.profile_id };
|
|
391
|
+
});
|
|
392
|
+
}
|
|
567
393
|
export function listNodes(filter) {
|
|
568
394
|
const clauses = [NOT_A_SPARE];
|
|
569
395
|
const params = [];
|
|
@@ -577,7 +403,7 @@ export function listNodes(filter) {
|
|
|
577
403
|
if (filter?.withRecordedPidOrFrozen === true)
|
|
578
404
|
clauses.push('(pi_pid IS NOT NULL OR frozen_at IS NOT NULL)');
|
|
579
405
|
const rows = openDb()
|
|
580
|
-
.prepare(`SELECT * FROM
|
|
406
|
+
.prepare(`SELECT * FROM node_rows WHERE ${clauses.join(' AND ')} ORDER BY created`)
|
|
581
407
|
.all(...params);
|
|
582
408
|
return rows.map(rowFrom);
|
|
583
409
|
}
|
|
@@ -612,7 +438,7 @@ export function unregisterWarmSpare(nodeId) {
|
|
|
612
438
|
function warmSpareRowsFor(db, recipeKey) {
|
|
613
439
|
const sql = `SELECT w.node_id AS node_id, w.recipe_key AS recipe_key, w.created AS created,
|
|
614
440
|
n.status AS status, n.pi_pid AS pi_pid, n.pi_pid_identity AS pi_pid_identity
|
|
615
|
-
FROM warm_pool w JOIN
|
|
441
|
+
FROM warm_pool w JOIN node_rows n ON n.node_id = w.node_id` +
|
|
616
442
|
(recipeKey === undefined ? '' : ' WHERE w.recipe_key = ?') +
|
|
617
443
|
' ORDER BY w.created';
|
|
618
444
|
const rows = (recipeKey === undefined ? db.prepare(sql).all() : db.prepare(sql).all(recipeKey));
|
|
@@ -662,8 +488,7 @@ function spareIsClaimable(row) {
|
|
|
662
488
|
* The claim also restamps the row's `created` to `created` — birth time is a
|
|
663
489
|
* spare's MINT time, and consumers sort conversations on it, so an hour-old
|
|
664
490
|
* spare must not sort as an hour-old conversation. This is the one sanctioned
|
|
665
|
-
* write of `created` after birth
|
|
666
|
-
* the caller mirrors the same stamp into meta.json. */
|
|
491
|
+
* write of `created` after birth. */
|
|
667
492
|
export function claimWarmSpare(recipeKey, created, opts = {}) {
|
|
668
493
|
return withCanvasWrite((db) => {
|
|
669
494
|
const row = warmSpareRowsFor(db, recipeKey).find(spareIsClaimable);
|
|
@@ -694,7 +519,7 @@ export function childrenOf(id) {
|
|
|
694
519
|
.all(id).map((r) => r.node_id);
|
|
695
520
|
}
|
|
696
521
|
/** Every node's lean roster row, oldest first — a single scan indexed by
|
|
697
|
-
* `idx_nodes_created
|
|
522
|
+
* `idx_nodes_created`, with no per-row hydration or N+1 query. */
|
|
698
523
|
export function rosterNodes() {
|
|
699
524
|
return openDb()
|
|
700
525
|
.prepare(`SELECT node_id, name, kind, mode, lifecycle, status, cwd, host_kind, parent, created
|
|
@@ -735,7 +560,7 @@ export function rosterEdges() {
|
|
|
735
560
|
* last session HERE"), so it never teleports across projects. */
|
|
736
561
|
export function listResumableRoots(cwd) {
|
|
737
562
|
const rows = openDb()
|
|
738
|
-
.prepare(`SELECT * FROM
|
|
563
|
+
.prepare(`SELECT * FROM node_rows
|
|
739
564
|
WHERE parent IS NULL AND kind != 'human'
|
|
740
565
|
AND status IN ('active','idle') AND cwd = ?
|
|
741
566
|
AND ${NOT_A_SPARE}
|
|
@@ -914,46 +739,6 @@ export function withFreshTerminalGuard(nodeId, isTerminal, body) {
|
|
|
914
739
|
return { kind: 'ran', result: body() };
|
|
915
740
|
});
|
|
916
741
|
}
|
|
917
|
-
// Index rebuild
|
|
918
|
-
/** Rebuild node rows from on-disk metas (the db node table is a derived index).
|
|
919
|
-
* Only the IDENTITY columns are rebuilt — they are a projection of meta. The
|
|
920
|
-
* runtime columns (status/intent/pi_pid/window/tmux_session/pane) are NOT in meta
|
|
921
|
-
* and NOT re-derivable from it: they describe live process/presence state, so
|
|
922
|
-
* an existing row keeps them and a freshly re-created row takes the schema's
|
|
923
|
-
* quiescent defaults (status='active', the rest null). The daemon reconciles
|
|
924
|
-
* liveness from tmux reality, not from a stale file.
|
|
925
|
-
* Edges are left intact — subscribes_to is db-authoritative; spawned_by is
|
|
926
|
-
* re-derived from each meta's `spawned_by` (fallback: `parent` for legacy metas). */
|
|
927
|
-
export function rebuildIndex() {
|
|
928
|
-
if (!existsSync(nodesRoot()))
|
|
929
|
-
return;
|
|
930
|
-
// Collect every on-disk meta first, then index in TWO passes. Under the
|
|
931
|
-
// edges→nodes FK (migration v4), a `spawned_by` edge insert whose endpoint
|
|
932
|
-
// row isn't present yet violates the constraint — so ALL node rows must exist
|
|
933
|
-
// before ANY edge is added.
|
|
934
|
-
const metas = [];
|
|
935
|
-
for (const id of readdirSync(nodesRoot())) {
|
|
936
|
-
if (!existsSync(join(nodeDir(id), 'meta.json')))
|
|
937
|
-
continue;
|
|
938
|
-
const meta = readMeta(id);
|
|
939
|
-
if (!meta)
|
|
940
|
-
continue;
|
|
941
|
-
metas.push(meta);
|
|
942
|
-
}
|
|
943
|
-
// Pass 1 — upsert every node row (the edge endpoints).
|
|
944
|
-
for (const meta of metas)
|
|
945
|
-
upsertRow(meta);
|
|
946
|
-
// Pass 2 — add the audit-only `spawned_by` provenance edges. Skip any whose
|
|
947
|
-
// provenance node has no on-disk meta (a deleted/pruned ancestor): the FK
|
|
948
|
-
// would reject it, and an orphan provenance edge is exactly what v4 makes
|
|
949
|
-
// unrepresentable.
|
|
950
|
-
const known = new Set(metas.map((m) => m.node_id));
|
|
951
|
-
for (const meta of metas) {
|
|
952
|
-
const prov = meta.spawned_by ?? meta.parent;
|
|
953
|
-
if (prov && known.has(prov))
|
|
954
|
-
recordSpawn(meta.node_id, prov);
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
742
|
/** True when this node's identity is still required to own a managed checkout.
|
|
958
743
|
*
|
|
959
744
|
* Ownership outlives the OPEN state. A closed record whose cleanup is still
|
|
@@ -969,10 +754,7 @@ export function rebuildIndex() {
|
|
|
969
754
|
* the checkout and retains its branch for a person on purpose, so there is no
|
|
970
755
|
* residual for the sweep to own. */
|
|
971
756
|
function hasUnreconciledManagedWorktree(nodeId) {
|
|
972
|
-
|
|
973
|
-
if (wt == null || wt.state === 'abandoned')
|
|
974
|
-
return false;
|
|
975
|
-
return wt.state === 'open' || wt.cleanup !== 'complete';
|
|
757
|
+
return hasUnreconciledWorktree(nodeId);
|
|
976
758
|
}
|
|
977
759
|
/** Whether a row may be removed by an automatic collection sweep. */
|
|
978
760
|
export function isCollectible(row, opts = {}) {
|
|
@@ -1050,7 +832,7 @@ export function pruneNodes(opts) {
|
|
|
1050
832
|
const selfId = envNodeId() ?? '';
|
|
1051
833
|
const db = openDb();
|
|
1052
834
|
const terminal = db
|
|
1053
|
-
.prepare(`SELECT * FROM
|
|
835
|
+
.prepare(`SELECT * FROM node_rows
|
|
1054
836
|
WHERE status IN ('dead', 'done', 'canceled') AND created < ?
|
|
1055
837
|
ORDER BY created`)
|
|
1056
838
|
.all(cutoff)
|
|
@@ -1060,7 +842,7 @@ export function pruneNodes(opts) {
|
|
|
1060
842
|
// Stale non-terminal sweep (opt-in): active | idle past the TTL whose process
|
|
1061
843
|
// is provably gone (pi_pid NULL or not alive). Never the caller itself.
|
|
1062
844
|
const stale = !includeStale ? [] : db
|
|
1063
|
-
.prepare(`SELECT node_id, status, created, pi_pid FROM
|
|
845
|
+
.prepare(`SELECT node_id, status, created, pi_pid FROM node_rows
|
|
1064
846
|
WHERE status IN ('active', 'idle') AND created < ?
|
|
1065
847
|
ORDER BY created`)
|
|
1066
848
|
.all(cutoff)
|
|
@@ -1082,49 +864,6 @@ export function pruneNodes(opts) {
|
|
|
1082
864
|
deletePrunedNodes(candidates);
|
|
1083
865
|
return { pruned: candidates, dryRun };
|
|
1084
866
|
}
|
|
1085
|
-
/** Drop rows whose on-disk `nodes/<id>/` dir is gone — a node that no longer
|
|
1086
|
-
* exists in the only place it is durably stored.
|
|
1087
|
-
*
|
|
1088
|
-
* `createNode` scaffolds the dir and writes meta.json BEFORE seeding the row, and
|
|
1089
|
-
* both delete paths ({@link deleteNode}, `deletePrunedNodes`) commit the row
|
|
1090
|
-
* removal BEFORE removing the dir, so a row with no dir is never a transient of
|
|
1091
|
-
* either — it is a node whose durable state was lost. Nothing about it is
|
|
1092
|
-
* recoverable: `getNode` reads meta.json and returns null, so every surface that
|
|
1093
|
-
* resolves a node (revive, focus, close, rename, message) rejects it as unknown
|
|
1094
|
-
* while the roster keeps listing it. The row IS the residue, so deleting it is
|
|
1095
|
-
* the whole repair; there is no dir left to remove.
|
|
1096
|
-
*
|
|
1097
|
-
* Because `active`/`idle` ghosts are the ones that jam surfaces, status is not a
|
|
1098
|
-
* filter here — count-based pruning protects those rows precisely so daemon
|
|
1099
|
-
* crash-recovery can resume them, and that reason does not survive the dir being
|
|
1100
|
-
* gone.
|
|
1101
|
-
*
|
|
1102
|
-
* Refuses to act on "can't tell": an unreadable nodes root, or a readable one
|
|
1103
|
-
* holding no node dirs at all while rows exist, reads as a detached or wiped
|
|
1104
|
-
* store rather than per-node loss, and reaps nothing. Returns the reaped ids. */
|
|
1105
|
-
export function reapGhostRows() {
|
|
1106
|
-
const rows = listNodes();
|
|
1107
|
-
if (rows.length === 0)
|
|
1108
|
-
return [];
|
|
1109
|
-
let onDisk;
|
|
1110
|
-
try {
|
|
1111
|
-
onDisk = new Set(readdirSync(nodesRoot()));
|
|
1112
|
-
}
|
|
1113
|
-
catch {
|
|
1114
|
-
return []; // nodes root absent or unreadable — can't tell, so touch nothing
|
|
1115
|
-
}
|
|
1116
|
-
if (onDisk.size === 0)
|
|
1117
|
-
return [];
|
|
1118
|
-
const ghosts = rows.map((r) => r.node_id).filter((id) => !onDisk.has(id));
|
|
1119
|
-
if (ghosts.length === 0)
|
|
1120
|
-
return [];
|
|
1121
|
-
withCanvasWrite((db) => {
|
|
1122
|
-
const del = db.prepare('DELETE FROM nodes WHERE node_id = ?');
|
|
1123
|
-
for (const id of ghosts)
|
|
1124
|
-
del.run(id);
|
|
1125
|
-
});
|
|
1126
|
-
return ghosts;
|
|
1127
|
-
}
|
|
1128
867
|
/** Hard-delete a set of pruned nodes in one transaction (the edges→nodes and
|
|
1129
868
|
* anchored-crons FKs cascade their coupled state), followed by best-effort
|
|
1130
869
|
* on-disk `nodes/<id>/` removal. Shared by TTL and count pruning so their
|