@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
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { type PreparedPage } from './page-authoring.js';
|
|
3
|
+
import { type PageResponses } from './page-schema.js';
|
|
4
|
+
export type HumanRequestKind = 'page' | 'review';
|
|
5
|
+
export type HumanRequestState = 'binding' | 'open' | 'settled';
|
|
6
|
+
export type HumanRequestOutcome = 'approved' | 'answered' | 'canceled' | 'dismissed';
|
|
7
|
+
export interface HumanRequestRecord {
|
|
8
|
+
request_id: string;
|
|
9
|
+
kind: HumanRequestKind;
|
|
10
|
+
inbox_ticket_id: string;
|
|
11
|
+
request_dir: string;
|
|
12
|
+
/** The immutable complete page revision selected by SQL. Null for reviews. */
|
|
13
|
+
presentation_dir: string | null;
|
|
14
|
+
requester_node_id: string | null;
|
|
15
|
+
companion_node_id: string | null;
|
|
16
|
+
idempotency_key: string | null;
|
|
17
|
+
state: HumanRequestState;
|
|
18
|
+
outcome: HumanRequestOutcome | null;
|
|
19
|
+
inbox_listed: boolean;
|
|
20
|
+
settleable: boolean;
|
|
21
|
+
title: string;
|
|
22
|
+
subtitle: string | null;
|
|
23
|
+
file: string | null;
|
|
24
|
+
specification_json: string;
|
|
25
|
+
action_json: string | null;
|
|
26
|
+
progress_json: string | null;
|
|
27
|
+
feedback_json: string | null;
|
|
28
|
+
result_json: string | null;
|
|
29
|
+
anchor_base_sha256: string | null;
|
|
30
|
+
anchor_base_bytes: Buffer | null;
|
|
31
|
+
created_at: string;
|
|
32
|
+
opened_at: string | null;
|
|
33
|
+
submit_requested_at: string | null;
|
|
34
|
+
settled_at: string | null;
|
|
35
|
+
settlement_delivery_id: string | null;
|
|
36
|
+
action_delivery_id: string | null;
|
|
37
|
+
submit_note_delivery_id: string | null;
|
|
38
|
+
projected_at: string | null;
|
|
39
|
+
projection_error: string | null;
|
|
40
|
+
companion_retired_at: string | null;
|
|
41
|
+
updated_at: string;
|
|
42
|
+
}
|
|
43
|
+
export interface NewReviewRequest {
|
|
44
|
+
request_id: string;
|
|
45
|
+
idempotency_key: string;
|
|
46
|
+
requester_node_id: string;
|
|
47
|
+
companion_node_id: string;
|
|
48
|
+
request_dir: string;
|
|
49
|
+
file: string;
|
|
50
|
+
specification: Record<string, unknown>;
|
|
51
|
+
anchor_base_sha256: string;
|
|
52
|
+
anchor_base_bytes: Buffer;
|
|
53
|
+
created_at: string;
|
|
54
|
+
subtitle?: string | null;
|
|
55
|
+
inbox_listed: boolean;
|
|
56
|
+
}
|
|
57
|
+
export interface CreatePageRequestInput {
|
|
58
|
+
/** Frozen by a cron obligation when creation must be idempotent across a daemon death. */
|
|
59
|
+
request_id?: string;
|
|
60
|
+
inbox_ticket_id?: string;
|
|
61
|
+
prepared: PreparedPage;
|
|
62
|
+
requester_node_id?: string | null;
|
|
63
|
+
action_json?: string | null;
|
|
64
|
+
}
|
|
65
|
+
export interface SettleRequestArgs {
|
|
66
|
+
request_id: string;
|
|
67
|
+
outcome: HumanRequestOutcome;
|
|
68
|
+
result_json: string;
|
|
69
|
+
settled_at: string;
|
|
70
|
+
}
|
|
71
|
+
export interface RequestSettlementMarks {
|
|
72
|
+
settlement_delivery_id?: string | null;
|
|
73
|
+
action_delivery_id?: string | null;
|
|
74
|
+
}
|
|
75
|
+
export declare function humanRequestsRoot(): string;
|
|
76
|
+
export declare function requestDir(requestId: string): string;
|
|
77
|
+
export interface PageRevisionStorage {
|
|
78
|
+
writeFile(path: string, content: string): void;
|
|
79
|
+
syncFile(path: string): void;
|
|
80
|
+
syncDirectory(path: string): void;
|
|
81
|
+
}
|
|
82
|
+
/** Publish a complete, immutable revision before a request can select it. */
|
|
83
|
+
export declare function publishPageRevision(dir: string, prepared: PreparedPage, storage?: PageRevisionStorage): string;
|
|
84
|
+
/** The SQL-selected page revision. There is no path-based fallback for page reads. */
|
|
85
|
+
export declare function presentationDir(request: HumanRequestRecord): string;
|
|
86
|
+
/** Creates one SQL-authoritative page request. `request_id` is optional for ordinary callers and frozen by cron creation. */
|
|
87
|
+
export declare function createPageRequest(input: CreatePageRequestInput): HumanRequestRecord;
|
|
88
|
+
/** Claims a review idempotency key before companion realization. */
|
|
89
|
+
export declare function commitBindingRequest(input: NewReviewRequest): {
|
|
90
|
+
request_id: string;
|
|
91
|
+
won: boolean;
|
|
92
|
+
};
|
|
93
|
+
export declare function getRequest(requestId: string): HumanRequestRecord | null;
|
|
94
|
+
export declare function getRequestByInboxTicket(ticketId: string): HumanRequestRecord | null;
|
|
95
|
+
export declare function getRequestByDirectory(dir: string): HumanRequestRecord | null;
|
|
96
|
+
export declare function getRequestByCompanion(nodeId: string): HumanRequestRecord | null;
|
|
97
|
+
export declare function getRequestByIdempotencyKey(key: string): HumanRequestRecord | null;
|
|
98
|
+
export declare function listOpenInboxRequests(): HumanRequestRecord[];
|
|
99
|
+
export declare function openRequestsByRequester(nodeId: string): HumanRequestRecord[];
|
|
100
|
+
/** Indexed attention projection: only open inbox-listed requests with a reply target keep a node wakeable. */
|
|
101
|
+
export declare function listOpenReplyBearingRequests(): HumanRequestRecord[];
|
|
102
|
+
export declare function listBindingRequests(): HumanRequestRecord[];
|
|
103
|
+
export declare function listSubmitRequestedRequests(): HumanRequestRecord[];
|
|
104
|
+
export declare function listUnprojectedReviews(): HumanRequestRecord[];
|
|
105
|
+
export declare function listRequests(filter?: {
|
|
106
|
+
kind?: HumanRequestKind;
|
|
107
|
+
state?: HumanRequestState;
|
|
108
|
+
requester_node_id?: string;
|
|
109
|
+
file?: string;
|
|
110
|
+
}): HumanRequestRecord[];
|
|
111
|
+
export declare function openRequest(requestId: string, openedAt: string): boolean;
|
|
112
|
+
/** A binding whose immutable branch disappeared never recaptures a new origin. */
|
|
113
|
+
export declare function cancelBindingRequest(requestId: string, at: string, reason: string): boolean;
|
|
114
|
+
export declare function requestSubmit(requestId: string, at: string): boolean;
|
|
115
|
+
/** The first-writer-wins terminal transition. Callers add obligations in this same `withCanvasWrite` boundary. */
|
|
116
|
+
export declare function settleRequestLocked(db: DatabaseSync, args: SettleRequestArgs): HumanRequestRecord | null;
|
|
117
|
+
export declare function setSettlementDeliveryMarksLocked(db: DatabaseSync, requestId: string, marks: RequestSettlementMarks): void;
|
|
118
|
+
export declare function setSubmitNoteDeliveryLocked(db: DatabaseSync, requestId: string, deliveryId: string): boolean;
|
|
119
|
+
export declare function revisePageRequest(requestId: string, values: {
|
|
120
|
+
presentation_dir: string;
|
|
121
|
+
specification_json: string;
|
|
122
|
+
title: string;
|
|
123
|
+
subtitle: string | null;
|
|
124
|
+
inbox_listed: boolean;
|
|
125
|
+
settleable: boolean;
|
|
126
|
+
}): boolean;
|
|
127
|
+
export declare function writeProgress(ticketId: string, responses: PageResponses): boolean;
|
|
128
|
+
export declare function markProjected(requestId: string, at: string): void;
|
|
129
|
+
export declare function markProjectionError(requestId: string, error: string): void;
|
|
130
|
+
export declare function markCompanionRetired(requestId: string, at: string): void;
|
|
131
|
+
export declare function readAnchorBase(requestId: string): {
|
|
132
|
+
bytes: Buffer;
|
|
133
|
+
sha256: string;
|
|
134
|
+
} | null;
|
|
135
|
+
export declare function replaceAnchorBaseLocked(db: DatabaseSync, requestId: string, bytes: Buffer, sha256: string): void;
|
|
136
|
+
export declare function migrateHumanRequestsState(db: DatabaseSync): void;
|
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { basename, join } from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { crtrHome, isSafeNodeId, nodesRoot } from '../canvas/paths.js';
|
|
6
|
+
import { openDb, withCanvasWrite } from '../canvas/db.js';
|
|
7
|
+
import { syncDirectory, syncFile } from '../fs-utils.js';
|
|
8
|
+
import { newNodeId } from '../runtime/nodes.js';
|
|
9
|
+
import { pageManifestPath, pageModulePath, pagePath } from './convention.js';
|
|
10
|
+
import { preparePageSource } from './page-authoring.js';
|
|
11
|
+
import { pageDeliverySchema, validatePageManifest } from './page-schema.js';
|
|
12
|
+
function bool(value) { return value === 1 || value === true; }
|
|
13
|
+
function recordFromRow(raw) {
|
|
14
|
+
return {
|
|
15
|
+
request_id: raw['request_id'],
|
|
16
|
+
kind: raw['kind'],
|
|
17
|
+
inbox_ticket_id: raw['inbox_ticket_id'],
|
|
18
|
+
request_dir: raw['request_dir'],
|
|
19
|
+
presentation_dir: raw['presentation_dir'],
|
|
20
|
+
requester_node_id: raw['requester_node_id'],
|
|
21
|
+
companion_node_id: raw['companion_node_id'],
|
|
22
|
+
idempotency_key: raw['idempotency_key'],
|
|
23
|
+
state: raw['state'],
|
|
24
|
+
outcome: raw['outcome'],
|
|
25
|
+
inbox_listed: bool(raw['inbox_listed']),
|
|
26
|
+
settleable: bool(raw['settleable']),
|
|
27
|
+
title: raw['title'],
|
|
28
|
+
subtitle: raw['subtitle'],
|
|
29
|
+
file: raw['file'],
|
|
30
|
+
specification_json: raw['specification_json'],
|
|
31
|
+
action_json: raw['action_json'],
|
|
32
|
+
progress_json: raw['progress_json'],
|
|
33
|
+
feedback_json: raw['feedback_json'],
|
|
34
|
+
result_json: raw['result_json'],
|
|
35
|
+
anchor_base_sha256: raw['anchor_base_sha256'],
|
|
36
|
+
anchor_base_bytes: raw['anchor_base_bytes'] === null ? null : Buffer.from(raw['anchor_base_bytes']),
|
|
37
|
+
created_at: raw['created_at'],
|
|
38
|
+
opened_at: raw['opened_at'],
|
|
39
|
+
submit_requested_at: raw['submit_requested_at'],
|
|
40
|
+
settled_at: raw['settled_at'],
|
|
41
|
+
settlement_delivery_id: raw['settlement_delivery_id'],
|
|
42
|
+
action_delivery_id: raw['action_delivery_id'],
|
|
43
|
+
submit_note_delivery_id: raw['submit_note_delivery_id'],
|
|
44
|
+
projected_at: raw['projected_at'],
|
|
45
|
+
projection_error: raw['projection_error'],
|
|
46
|
+
companion_retired_at: raw['companion_retired_at'],
|
|
47
|
+
updated_at: raw['updated_at'],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function one(db, where, ...params) {
|
|
51
|
+
const raw = db.prepare(`SELECT * FROM human_requests WHERE ${where}`).get(...params);
|
|
52
|
+
return raw === undefined ? null : recordFromRow(raw);
|
|
53
|
+
}
|
|
54
|
+
function many(db, where, params = [], order = 'created_at ASC, request_id ASC') {
|
|
55
|
+
return db.prepare(`SELECT * FROM human_requests WHERE ${where} ORDER BY ${order}`).all(...params).map(recordFromRow);
|
|
56
|
+
}
|
|
57
|
+
export function humanRequestsRoot() { return join(crtrHome(), 'requests'); }
|
|
58
|
+
export function requestDir(requestId) {
|
|
59
|
+
if (!isSafeNodeId(requestId))
|
|
60
|
+
throw new TypeError(`request_id must be a safe path segment: ${requestId}`);
|
|
61
|
+
const root = humanRequestsRoot();
|
|
62
|
+
mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
63
|
+
return join(realpathSync(root), requestId);
|
|
64
|
+
}
|
|
65
|
+
function randomTicketId() { return randomBytes(32).toString('hex'); }
|
|
66
|
+
function legacyTicketId(root, name) {
|
|
67
|
+
return createHash('sha256').update(`${realpathSync(root)}\0${name}`, 'utf8').digest('hex');
|
|
68
|
+
}
|
|
69
|
+
const durablePageRevisionStorage = {
|
|
70
|
+
writeFile(path, content) { writeFileSync(path, content, { flag: 'wx', mode: 0o600 }); },
|
|
71
|
+
syncFile,
|
|
72
|
+
syncDirectory,
|
|
73
|
+
};
|
|
74
|
+
function revisionRoot(dir) { return join(dir, 'revisions'); }
|
|
75
|
+
/** Publish a complete, immutable revision before a request can select it. */
|
|
76
|
+
export function publishPageRevision(dir, prepared, storage = durablePageRevisionStorage) {
|
|
77
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
78
|
+
const revisions = revisionRoot(dir);
|
|
79
|
+
mkdirSync(revisions, { recursive: true, mode: 0o700 });
|
|
80
|
+
const revision = join(revisions, randomBytes(16).toString('hex'));
|
|
81
|
+
mkdirSync(revision, { mode: 0o700 });
|
|
82
|
+
const document = pagePath(revision, prepared.manifest.dialect);
|
|
83
|
+
storage.writeFile(document, prepared.document);
|
|
84
|
+
storage.syncFile(document);
|
|
85
|
+
if (prepared.manifest.dialect === 'jsx') {
|
|
86
|
+
const module = pageModulePath(revision);
|
|
87
|
+
storage.writeFile(module, prepared.module);
|
|
88
|
+
storage.syncFile(module);
|
|
89
|
+
}
|
|
90
|
+
const manifest = pageManifestPath(revision);
|
|
91
|
+
storage.writeFile(manifest, `${JSON.stringify(prepared.manifest, null, 2)}\n`);
|
|
92
|
+
storage.syncFile(manifest);
|
|
93
|
+
storage.syncDirectory(revision);
|
|
94
|
+
storage.syncDirectory(revisions);
|
|
95
|
+
storage.syncDirectory(dir);
|
|
96
|
+
storage.syncDirectory(humanRequestsRoot());
|
|
97
|
+
return revision;
|
|
98
|
+
}
|
|
99
|
+
/** The SQL-selected page revision. There is no path-based fallback for page reads. */
|
|
100
|
+
export function presentationDir(request) {
|
|
101
|
+
if (request.kind !== 'page' || request.presentation_dir === null)
|
|
102
|
+
throw new Error(`page request has no selected presentation: ${request.request_id}`);
|
|
103
|
+
return request.presentation_dir;
|
|
104
|
+
}
|
|
105
|
+
/** Creates one SQL-authoritative page request. `request_id` is optional for ordinary callers and frozen by cron creation. */
|
|
106
|
+
export function createPageRequest(input) {
|
|
107
|
+
const requestId = input.request_id ?? newNodeId();
|
|
108
|
+
const dir = requestDir(requestId);
|
|
109
|
+
const selectedPresentation = publishPageRevision(dir, input.prepared);
|
|
110
|
+
const at = new Date().toISOString();
|
|
111
|
+
const manifest = input.prepared.manifest;
|
|
112
|
+
return withCanvasWrite((db) => {
|
|
113
|
+
db.prepare(`
|
|
114
|
+
INSERT INTO human_requests (
|
|
115
|
+
request_id, kind, inbox_ticket_id, request_dir, presentation_dir, requester_node_id, state, inbox_listed,
|
|
116
|
+
settleable, title, subtitle, specification_json, action_json, created_at, updated_at
|
|
117
|
+
) VALUES (?, 'page', ?, ?, ?, ?, 'open', ?, ?, ?, ?, ?, ?, ?, ?)
|
|
118
|
+
`).run(requestId, input.inbox_ticket_id ?? randomTicketId(), dir, selectedPresentation, input.requester_node_id ?? null, manifest.delivery.inbox ? 1 : 0, manifest.delivery.inbox || manifest.delivery.reply ? 1 : 0, manifest.title, manifest.subtitle ?? null, JSON.stringify(manifest), input.action_json ?? null, at, at);
|
|
119
|
+
const request = one(db, 'request_id = ?', requestId);
|
|
120
|
+
if (request === null)
|
|
121
|
+
throw new Error(`page request insert did not produce a row: ${requestId}`);
|
|
122
|
+
return request;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/** Claims a review idempotency key before companion realization. */
|
|
126
|
+
export function commitBindingRequest(input) {
|
|
127
|
+
return withCanvasWrite((db) => {
|
|
128
|
+
const inserted = db.prepare(`
|
|
129
|
+
INSERT INTO human_requests (
|
|
130
|
+
request_id, kind, inbox_ticket_id, request_dir, requester_node_id, companion_node_id,
|
|
131
|
+
idempotency_key, state, inbox_listed, settleable, title, subtitle, file,
|
|
132
|
+
specification_json, anchor_base_sha256, anchor_base_bytes, created_at, updated_at
|
|
133
|
+
) VALUES (?, 'review', ?, ?, ?, ?, ?, 'binding', ?, 1, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
134
|
+
ON CONFLICT(idempotency_key) DO NOTHING
|
|
135
|
+
`).run(input.request_id, randomTicketId(), input.request_dir, input.requester_node_id, input.companion_node_id, input.idempotency_key, input.inbox_listed ? 1 : 0, basename(input.file), input.subtitle ?? null, input.file, JSON.stringify(input.specification), input.anchor_base_sha256, input.anchor_base_bytes, input.created_at, input.created_at);
|
|
136
|
+
const winner = one(db, 'idempotency_key = ?', input.idempotency_key);
|
|
137
|
+
if (winner === null)
|
|
138
|
+
throw new Error(`review binding insert did not produce a row: ${input.idempotency_key}`);
|
|
139
|
+
return { request_id: winner.request_id, won: inserted.changes > 0 };
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
export function getRequest(requestId) { return one(openDb(), 'request_id = ?', requestId); }
|
|
143
|
+
export function getRequestByInboxTicket(ticketId) { return one(openDb(), 'inbox_ticket_id = ?', ticketId); }
|
|
144
|
+
export function getRequestByDirectory(dir) {
|
|
145
|
+
let canonical = dir;
|
|
146
|
+
try {
|
|
147
|
+
canonical = realpathSync(dir);
|
|
148
|
+
}
|
|
149
|
+
catch { /* A caller may name a request directory before it exists. */ }
|
|
150
|
+
return one(openDb(), 'request_dir = ?', canonical);
|
|
151
|
+
}
|
|
152
|
+
export function getRequestByCompanion(nodeId) { return one(openDb(), "companion_node_id = ? AND state <> 'binding'", nodeId); }
|
|
153
|
+
export function getRequestByIdempotencyKey(key) { return one(openDb(), 'idempotency_key = ?', key); }
|
|
154
|
+
export function listOpenInboxRequests() {
|
|
155
|
+
return many(openDb(), "state = 'open' AND inbox_listed = 1", [], 'created_at DESC, request_id DESC');
|
|
156
|
+
}
|
|
157
|
+
export function openRequestsByRequester(nodeId) {
|
|
158
|
+
return many(openDb(), "state = 'open' AND requester_node_id = ?", [nodeId], 'created_at DESC, request_id DESC');
|
|
159
|
+
}
|
|
160
|
+
/** Indexed attention projection: only open inbox-listed requests with a reply target keep a node wakeable. */
|
|
161
|
+
export function listOpenReplyBearingRequests() {
|
|
162
|
+
return many(openDb(), "state = 'open' AND inbox_listed = 1 AND requester_node_id IS NOT NULL", [], 'created_at DESC, request_id DESC');
|
|
163
|
+
}
|
|
164
|
+
export function listBindingRequests() { return many(openDb(), "kind = 'review' AND state = 'binding'"); }
|
|
165
|
+
export function listSubmitRequestedRequests() { return many(openDb(), "kind = 'review' AND state = 'open' AND submit_requested_at IS NOT NULL"); }
|
|
166
|
+
export function listUnprojectedReviews() { return many(openDb(), "kind = 'review' AND state = 'settled' AND projected_at IS NULL"); }
|
|
167
|
+
export function listRequests(filter = {}) {
|
|
168
|
+
const clauses = ['1'];
|
|
169
|
+
const params = [];
|
|
170
|
+
for (const [column, value] of Object.entries(filter)) {
|
|
171
|
+
if (value !== undefined) {
|
|
172
|
+
clauses.push(`${column} = ?`);
|
|
173
|
+
params.push(value);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return many(openDb(), clauses.join(' AND '), params);
|
|
177
|
+
}
|
|
178
|
+
export function openRequest(requestId, openedAt) {
|
|
179
|
+
return withCanvasWrite((db) => db.prepare("UPDATE human_requests SET state = 'open', opened_at = ?, updated_at = ? WHERE request_id = ? AND state = 'binding'").run(openedAt, openedAt, requestId).changes > 0);
|
|
180
|
+
}
|
|
181
|
+
/** A binding whose immutable branch disappeared never recaptures a new origin. */
|
|
182
|
+
export function cancelBindingRequest(requestId, at, reason) {
|
|
183
|
+
return withCanvasWrite((db) => db.prepare(`
|
|
184
|
+
UPDATE human_requests SET state = 'settled', outcome = 'canceled', result_json = ?, settled_at = ?, updated_at = ?
|
|
185
|
+
WHERE request_id = ? AND kind = 'review' AND state = 'binding'
|
|
186
|
+
`).run(JSON.stringify({ schema: 'humanloop.cancel/v1', kind: 'canceled', canceledAt: at, reason }), at, at, requestId).changes > 0);
|
|
187
|
+
}
|
|
188
|
+
export function requestSubmit(requestId, at) {
|
|
189
|
+
return withCanvasWrite((db) => db.prepare("UPDATE human_requests SET submit_requested_at = ?, updated_at = ? WHERE request_id = ? AND kind = 'review' AND state = 'open' AND submit_requested_at IS NULL").run(at, at, requestId).changes > 0);
|
|
190
|
+
}
|
|
191
|
+
/** The first-writer-wins terminal transition. Callers add obligations in this same `withCanvasWrite` boundary. */
|
|
192
|
+
export function settleRequestLocked(db, args) {
|
|
193
|
+
const changed = db.prepare(`
|
|
194
|
+
UPDATE human_requests SET state = 'settled', outcome = ?, result_json = ?, settled_at = ?,
|
|
195
|
+
progress_json = NULL, updated_at = ?
|
|
196
|
+
WHERE request_id = ? AND state = 'open'
|
|
197
|
+
`).run(args.outcome, args.result_json, args.settled_at, args.settled_at, args.request_id).changes;
|
|
198
|
+
return changed === 0 ? null : one(db, 'request_id = ?', args.request_id);
|
|
199
|
+
}
|
|
200
|
+
export function setSettlementDeliveryMarksLocked(db, requestId, marks) {
|
|
201
|
+
db.prepare(`UPDATE human_requests SET settlement_delivery_id = ?, action_delivery_id = ?, updated_at = ? WHERE request_id = ?`).run(marks.settlement_delivery_id ?? null, marks.action_delivery_id ?? null, new Date().toISOString(), requestId);
|
|
202
|
+
}
|
|
203
|
+
export function setSubmitNoteDeliveryLocked(db, requestId, deliveryId) {
|
|
204
|
+
return db.prepare(`UPDATE human_requests SET submit_note_delivery_id = ?, updated_at = ? WHERE request_id = ? AND state = 'open' AND submit_note_delivery_id IS NULL`).run(deliveryId, new Date().toISOString(), requestId).changes > 0;
|
|
205
|
+
}
|
|
206
|
+
export function revisePageRequest(requestId, values) {
|
|
207
|
+
return withCanvasWrite((db) => db.prepare(`
|
|
208
|
+
UPDATE human_requests SET presentation_dir = ?, specification_json = ?, title = ?, subtitle = ?, inbox_listed = ?, settleable = ?, updated_at = ?
|
|
209
|
+
WHERE request_id = ? AND kind = 'page' AND state = 'open'
|
|
210
|
+
`).run(values.presentation_dir, values.specification_json, values.title, values.subtitle, values.inbox_listed ? 1 : 0, values.settleable ? 1 : 0, new Date().toISOString(), requestId).changes > 0);
|
|
211
|
+
}
|
|
212
|
+
export function writeProgress(ticketId, responses) {
|
|
213
|
+
return withCanvasWrite((db) => db.prepare(`
|
|
214
|
+
UPDATE human_requests SET progress_json = ?, updated_at = ?
|
|
215
|
+
WHERE inbox_ticket_id = ? AND kind = 'page' AND state = 'open' AND settleable = 1
|
|
216
|
+
`).run(JSON.stringify({ kind: 'page', responses, savedAt: new Date().toISOString() }), new Date().toISOString(), ticketId).changes > 0);
|
|
217
|
+
}
|
|
218
|
+
export function markProjected(requestId, at) {
|
|
219
|
+
withCanvasWrite((db) => db.prepare(`UPDATE human_requests SET projected_at = ?, projection_error = NULL, updated_at = ? WHERE request_id = ?`).run(at, at, requestId));
|
|
220
|
+
}
|
|
221
|
+
export function markProjectionError(requestId, error) {
|
|
222
|
+
withCanvasWrite((db) => db.prepare(`UPDATE human_requests SET projection_error = ?, updated_at = ? WHERE request_id = ? AND projected_at IS NULL`).run(error, new Date().toISOString(), requestId));
|
|
223
|
+
}
|
|
224
|
+
export function markCompanionRetired(requestId, at) {
|
|
225
|
+
withCanvasWrite((db) => db.prepare(`UPDATE human_requests SET companion_retired_at = ?, updated_at = ? WHERE request_id = ? AND companion_retired_at IS NULL`).run(at, at, requestId));
|
|
226
|
+
}
|
|
227
|
+
export function readAnchorBase(requestId) {
|
|
228
|
+
const row = openDb().prepare('SELECT anchor_base_bytes, anchor_base_sha256 FROM human_requests WHERE request_id = ?').get(requestId);
|
|
229
|
+
return row === undefined || row.anchor_base_bytes === null || row.anchor_base_sha256 === null ? null : { bytes: Buffer.from(row.anchor_base_bytes), sha256: row.anchor_base_sha256 };
|
|
230
|
+
}
|
|
231
|
+
export function replaceAnchorBaseLocked(db, requestId, bytes, sha256) {
|
|
232
|
+
db.prepare('UPDATE human_requests SET anchor_base_bytes = ?, anchor_base_sha256 = ?, updated_at = ? WHERE request_id = ?').run(bytes, sha256, new Date().toISOString(), requestId);
|
|
233
|
+
}
|
|
234
|
+
function legacyPageResult(result) {
|
|
235
|
+
if (typeof result !== 'object' || result === null)
|
|
236
|
+
throw new Error('result is not an object');
|
|
237
|
+
const row = result;
|
|
238
|
+
if (row['kind'] === 'canceled')
|
|
239
|
+
return { outcome: row['disposition'] === 'dismissed' ? 'dismissed' : 'canceled', settledAt: String(row['canceledAt']) };
|
|
240
|
+
return { outcome: 'answered', settledAt: String(row['completedAt']) };
|
|
241
|
+
}
|
|
242
|
+
function readJson(path) {
|
|
243
|
+
try {
|
|
244
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
throw new Error(`corrupt required input: ${path}`, { cause: error });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function migrationId() { return randomBytes(16).toString('hex'); }
|
|
251
|
+
function nextMessageSeq(db) {
|
|
252
|
+
return Number(db.prepare('SELECT COALESCE(MAX(seq), 0) AS seq FROM messages').get().seq) + 1;
|
|
253
|
+
}
|
|
254
|
+
function importedTerminalDelivery(db, requestId, requesterNodeId, kind) {
|
|
255
|
+
const row = kind === 'page'
|
|
256
|
+
? db.prepare(`SELECT d.delivery_id FROM deliveries d JOIN messages m ON m.message_id = d.message_id
|
|
257
|
+
WHERE d.destination_kind = 'node_inbox' AND d.destination_id = ? AND m.sender_node_id = ?
|
|
258
|
+
AND m.disposition IN ('human-answer', 'human-canceled') LIMIT 1`).get(requesterNodeId, requestId)
|
|
259
|
+
: db.prepare(`SELECT d.delivery_id FROM deliveries d JOIN messages m ON m.message_id = d.message_id
|
|
260
|
+
WHERE d.destination_kind = 'node_inbox' AND d.destination_id = ? AND m.body_json LIKE ? LIMIT 1`).get(requesterNodeId, `%\"review_id\":\"${requestId}\"%`);
|
|
261
|
+
return row?.delivery_id ?? null;
|
|
262
|
+
}
|
|
263
|
+
function companionRetirementProven(db, companionNodeId, outcome) {
|
|
264
|
+
if (companionNodeId === null || outcome === null)
|
|
265
|
+
return false;
|
|
266
|
+
const row = db.prepare('SELECT status, final_report FROM nodes WHERE node_id = ?').get(companionNodeId);
|
|
267
|
+
return row?.status === 'canceled' || (outcome === 'approved' && row?.status === 'done' && row.final_report !== null);
|
|
268
|
+
}
|
|
269
|
+
function createMigratedTerminalDelivery(db, input) {
|
|
270
|
+
const messageId = migrationId();
|
|
271
|
+
const deliveryId = migrationId();
|
|
272
|
+
const now = Date.parse(input.settledAt);
|
|
273
|
+
const createdAt = Number.isFinite(now) ? now : 0;
|
|
274
|
+
const disposition = input.outcome === 'answered' || input.outcome === 'approved' ? 'human-answer' : 'human-canceled';
|
|
275
|
+
db.prepare(`INSERT INTO messages
|
|
276
|
+
(message_id, seq, operation_id, created_at, sender_node_id, sender_name, kind, label, body_ref, body_json, disposition)
|
|
277
|
+
VALUES (?, ?, ?, ?, NULL, 'crtrd', 'completed', ?, NULL, ?, ?)`)
|
|
278
|
+
.run(messageId, nextMessageSeq(db), migrationId(), input.settledAt, input.kind === 'review' ? (input.outcome === 'approved' ? 'review approved by the user' : 'review canceled') : (input.outcome === 'answered' ? 'human request answered' : 'human request canceled'), JSON.stringify(input.kind === 'review'
|
|
279
|
+
? { review_id: input.requestId, result_path: `${input.requestDir}/result.json`, result: JSON.parse(input.resultJson) }
|
|
280
|
+
: { request_id: input.requestId, result: JSON.parse(input.resultJson) }), disposition);
|
|
281
|
+
db.prepare(`INSERT INTO deliveries
|
|
282
|
+
(delivery_id, message_id, destination_kind, destination_id, urgency, wake, state, attempts, created_at, updated_at)
|
|
283
|
+
VALUES (?, ?, 'node_inbox', ?, 'normal', 1, 'pending', 0, ?, ?)`)
|
|
284
|
+
.run(deliveryId, messageId, input.requesterNodeId, createdAt, createdAt);
|
|
285
|
+
return deliveryId;
|
|
286
|
+
}
|
|
287
|
+
function terminalDeliveryMark(db, input) {
|
|
288
|
+
if (input.requesterNodeId === null || input.outcome === null || input.resultJson === null || input.settledAt === null)
|
|
289
|
+
return null;
|
|
290
|
+
const imported = importedTerminalDelivery(db, input.requestId, input.requesterNodeId, input.kind);
|
|
291
|
+
if (imported !== null)
|
|
292
|
+
return imported;
|
|
293
|
+
if (input.deliveredAt !== null && input.deliveredAt !== undefined)
|
|
294
|
+
return null;
|
|
295
|
+
return createMigratedTerminalDelivery(db, {
|
|
296
|
+
requestId: input.requestId, requesterNodeId: input.requesterNodeId, kind: input.kind,
|
|
297
|
+
outcome: input.outcome, resultJson: input.resultJson, settledAt: input.settledAt, requestDir: input.requestDir,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
/** Imports every legacy page and review inside the caller's already-open migration transaction. */
|
|
301
|
+
const legacyPageSourceSchema = z.object({
|
|
302
|
+
sessionName: z.string().optional(),
|
|
303
|
+
askedBy: z.string().optional(),
|
|
304
|
+
blockedSince: z.string().optional(),
|
|
305
|
+
profileName: z.string().optional(),
|
|
306
|
+
nodeId: z.string().optional(),
|
|
307
|
+
}).strict();
|
|
308
|
+
const legacyPageSlotSchema = z.object({
|
|
309
|
+
id: z.string().regex(/^[A-Za-z0-9_-]{1,64}$/).optional(),
|
|
310
|
+
kind: z.string().min(1),
|
|
311
|
+
step: z.number().int().nonnegative(),
|
|
312
|
+
config: z.record(z.string(), z.unknown()),
|
|
313
|
+
unvalidated: z.literal(true).optional(),
|
|
314
|
+
display: z.literal(true).optional(),
|
|
315
|
+
}).strict();
|
|
316
|
+
const legacyPageManifestSchema = z.object({
|
|
317
|
+
schema: z.literal('crtr.page/v1'),
|
|
318
|
+
dialect: z.enum(['md', 'html', 'jsx']),
|
|
319
|
+
title: z.string().trim().min(1),
|
|
320
|
+
subtitle: z.string().optional(),
|
|
321
|
+
placement: z.enum(['inline', 'panel']),
|
|
322
|
+
document: z.enum(['page.md', 'page.html', 'page.tsx']),
|
|
323
|
+
steps: z.number().int().positive(),
|
|
324
|
+
slots: z.array(legacyPageSlotSchema),
|
|
325
|
+
source: legacyPageSourceSchema.optional(),
|
|
326
|
+
}).strict().superRefine((manifest, ctx) => {
|
|
327
|
+
const document = manifest.dialect === 'md' ? 'page.md' : manifest.dialect === 'html' ? 'page.html' : 'page.tsx';
|
|
328
|
+
if (manifest.document !== document)
|
|
329
|
+
ctx.addIssue({ code: 'custom', path: ['document'], message: 'document must agree with dialect' });
|
|
330
|
+
if (manifest.dialect === 'md' && manifest.steps !== 1)
|
|
331
|
+
ctx.addIssue({ code: 'custom', path: ['steps'], message: 'md pages have exactly one step' });
|
|
332
|
+
});
|
|
333
|
+
function legacyPresentationSource(manifest) {
|
|
334
|
+
if (manifest.source === undefined)
|
|
335
|
+
return undefined;
|
|
336
|
+
const { blockedSince, ...source } = manifest.source;
|
|
337
|
+
const normalized = { ...source, ...(blockedSince !== undefined && z.string().datetime({ offset: true }).safeParse(blockedSince).success ? { emittedAt: blockedSince } : {}) };
|
|
338
|
+
return Object.keys(normalized).length === 0 ? undefined : normalized;
|
|
339
|
+
}
|
|
340
|
+
/** A discontinued v1 page is replayed as a static JSX page so current readers retain its authored bytes without reviving the retired dialect. */
|
|
341
|
+
function legacyReplayDocument(title, subtitle, document) {
|
|
342
|
+
return `export default function LegacyPage() { return <Page title={${JSON.stringify(title)}}${subtitle === undefined ? '' : ` subtitle={${JSON.stringify(subtitle)}}`}><pre>{${JSON.stringify(document)}}</pre></Page>; }\n`;
|
|
343
|
+
}
|
|
344
|
+
function migrateLegacyPage(dir, raw) {
|
|
345
|
+
const manifestPath = join(dir, 'page.json');
|
|
346
|
+
const parsed = legacyPageManifestSchema.safeParse(raw);
|
|
347
|
+
if (!parsed.success)
|
|
348
|
+
throw new Error(`corrupt required input: ${manifestPath}`);
|
|
349
|
+
const manifest = parsed.data;
|
|
350
|
+
const source = legacyPresentationSource(manifest);
|
|
351
|
+
const delivery = {
|
|
352
|
+
placement: manifest.placement,
|
|
353
|
+
inbox: true,
|
|
354
|
+
reply: manifest.source?.blockedSince !== undefined,
|
|
355
|
+
};
|
|
356
|
+
try {
|
|
357
|
+
validatePageManifest({
|
|
358
|
+
schema: 'crtr.page/v2', dialect: 'jsx', title: manifest.title,
|
|
359
|
+
...(manifest.subtitle === undefined ? {} : { subtitle: manifest.subtitle }),
|
|
360
|
+
delivery, document: 'page.tsx', steps: manifest.steps, slots: manifest.slots,
|
|
361
|
+
...(source === undefined ? {} : { source }),
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
throw new Error(`corrupt required input: ${manifestPath}`);
|
|
366
|
+
}
|
|
367
|
+
const sourcePath = join(dir, manifest.document);
|
|
368
|
+
if (!existsSync(sourcePath))
|
|
369
|
+
throw new Error(`corrupt required input: ${sourcePath}`);
|
|
370
|
+
const sourceDocument = readFileSync(sourcePath, 'utf8');
|
|
371
|
+
const prepared = preparePageSource({ dialect: 'jsx', document: legacyReplayDocument(manifest.title, manifest.subtitle, sourceDocument), source, delivery });
|
|
372
|
+
if (source !== undefined)
|
|
373
|
+
return prepared;
|
|
374
|
+
const { source: _source, ...manifestWithoutSource } = prepared.manifest;
|
|
375
|
+
return { ...prepared, manifest: manifestWithoutSource };
|
|
376
|
+
}
|
|
377
|
+
export function migrateHumanRequestsState(db) {
|
|
378
|
+
db.exec(`
|
|
379
|
+
CREATE TABLE human_requests (
|
|
380
|
+
request_id TEXT PRIMARY KEY, kind TEXT NOT NULL CHECK (kind IN ('page','review')),
|
|
381
|
+
inbox_ticket_id TEXT NOT NULL UNIQUE, request_dir TEXT NOT NULL, presentation_dir TEXT,
|
|
382
|
+
requester_node_id TEXT,
|
|
383
|
+
companion_node_id TEXT UNIQUE, idempotency_key TEXT UNIQUE,
|
|
384
|
+
state TEXT NOT NULL CHECK (state IN ('binding','open','settled')),
|
|
385
|
+
outcome TEXT CHECK (outcome IN ('approved','answered','canceled','dismissed')),
|
|
386
|
+
inbox_listed INTEGER NOT NULL, settleable INTEGER NOT NULL, title TEXT NOT NULL, subtitle TEXT,
|
|
387
|
+
file TEXT, specification_json TEXT NOT NULL, action_json TEXT, progress_json TEXT, feedback_json TEXT,
|
|
388
|
+
result_json TEXT, anchor_base_sha256 TEXT, anchor_base_bytes BLOB, created_at TEXT NOT NULL,
|
|
389
|
+
opened_at TEXT, submit_requested_at TEXT, settled_at TEXT, settlement_delivery_id TEXT,
|
|
390
|
+
action_delivery_id TEXT, submit_note_delivery_id TEXT, projected_at TEXT, projection_error TEXT,
|
|
391
|
+
companion_retired_at TEXT, updated_at TEXT NOT NULL,
|
|
392
|
+
CHECK ((state = 'settled') = (outcome IS NOT NULL)),
|
|
393
|
+
CHECK ((state = 'settled') = (result_json IS NOT NULL)),
|
|
394
|
+
CHECK (kind <> 'page' OR presentation_dir IS NOT NULL)
|
|
395
|
+
);
|
|
396
|
+
CREATE INDEX idx_human_requests_inbox ON human_requests(state, inbox_listed);
|
|
397
|
+
CREATE INDEX idx_human_requests_kind_state ON human_requests(kind, state);
|
|
398
|
+
CREATE INDEX idx_human_requests_submit ON human_requests(state, submit_requested_at);
|
|
399
|
+
CREATE INDEX idx_human_requests_file ON human_requests(file);
|
|
400
|
+
CREATE INDEX idx_human_requests_requester ON human_requests(requester_node_id);
|
|
401
|
+
`);
|
|
402
|
+
const insert = db.prepare(`INSERT INTO human_requests (
|
|
403
|
+
request_id, kind, inbox_ticket_id, request_dir, presentation_dir, requester_node_id, companion_node_id, idempotency_key,
|
|
404
|
+
state, outcome, inbox_listed, settleable, title, subtitle, file, specification_json, action_json,
|
|
405
|
+
progress_json, feedback_json, result_json, anchor_base_sha256, anchor_base_bytes, created_at, opened_at,
|
|
406
|
+
submit_requested_at, settled_at, settlement_delivery_id, action_delivery_id, projected_at, projection_error,
|
|
407
|
+
companion_retired_at, updated_at
|
|
408
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
409
|
+
if (db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'reviews'").get() !== undefined) {
|
|
410
|
+
const rows = db.prepare('SELECT * FROM reviews').all();
|
|
411
|
+
for (const row of rows) {
|
|
412
|
+
const legacyState = row['state'];
|
|
413
|
+
const settled = legacyState === 'approved' || legacyState === 'canceled';
|
|
414
|
+
const result = legacyState === 'canceled' && row['result_json'] === null
|
|
415
|
+
? JSON.stringify({ schema: 'humanloop.cancel/v1', kind: 'canceled', canceledAt: row['canceled_at'], ...(row['cancel_reason'] === '' || row['cancel_reason'] === null ? {} : { reason: row['cancel_reason'] }), ...(row['cancel_actor'] === null ? {} : { actor: row['cancel_actor'] }) })
|
|
416
|
+
: row['result_json'];
|
|
417
|
+
if (settled && result === null)
|
|
418
|
+
throw new Error(`corrupt required input: reviews/${row['review_id']} has no terminal result`);
|
|
419
|
+
const requestDirectory = row['bridge_node_id'] === null ? join(crtrHome(), 'reviews', row['review_id']) : join(nodesRoot(), row['bridge_node_id']);
|
|
420
|
+
const spec = {
|
|
421
|
+
origin_kind: row['origin_kind'], bridge_node_id: row['bridge_node_id'],
|
|
422
|
+
origin_session_file: row['origin_session_file'], origin_session_id: row['origin_session_id'], origin_leaf_id: row['origin_leaf_id'],
|
|
423
|
+
model_spec: row['model_spec'], branch_file: row['branch_file'], baseline_sha256: row['baseline_sha256'], baseline_bytes: row['baseline_bytes'],
|
|
424
|
+
captured_at: row['captured_at'], output_path: row['output_path'],
|
|
425
|
+
};
|
|
426
|
+
const outcome = settled ? (legacyState === 'approved' ? 'approved' : 'canceled') : null;
|
|
427
|
+
const settledAt = settled ? (legacyState === 'approved' ? row['approved_at'] : row['canceled_at']) : null;
|
|
428
|
+
const settlementDeliveryId = terminalDeliveryMark(db, {
|
|
429
|
+
requestId: row['review_id'], requesterNodeId: row['origin_node_id'], kind: 'review', outcome, resultJson: result,
|
|
430
|
+
settledAt, requestDir: requestDirectory, deliveredAt: row['delivered_at'],
|
|
431
|
+
});
|
|
432
|
+
const companionRetiredAt = companionRetirementProven(db, row['companion_node_id'], outcome) ? settledAt : null;
|
|
433
|
+
insert.run(row['review_id'], 'review', row['origin_kind'] === 'ticket' ? legacyTicketId(nodesRoot(), basename(requestDirectory)) : randomTicketId(), requestDirectory, null, row['origin_node_id'], row['companion_node_id'], row['idempotency_key'], settled ? 'settled' : legacyState, outcome, row['origin_kind'] === 'ticket' ? 1 : 0, 1, basename(row['file']), row['subtitle'], row['file'], JSON.stringify(spec), null, null, null, result, row['anchor_base_sha256'], row['anchor_base_bytes'], row['created'], row['opened_at'], row['submit_requested_at'], settledAt, settlementDeliveryId, null, row['projected_at'], row['projection_error'], companionRetiredAt, row['created']);
|
|
434
|
+
}
|
|
435
|
+
db.exec('DROP TABLE reviews');
|
|
436
|
+
}
|
|
437
|
+
for (const entry of existsSync(nodesRoot()) ? readdirSync(nodesRoot(), { withFileTypes: true }) : []) {
|
|
438
|
+
if (!entry.isDirectory())
|
|
439
|
+
continue;
|
|
440
|
+
const dir = join(nodesRoot(), entry.name);
|
|
441
|
+
const manifestPath = join(dir, 'page.json');
|
|
442
|
+
const reviewPath = join(dir, 'review.json');
|
|
443
|
+
if (!existsSync(manifestPath)) {
|
|
444
|
+
if (existsSync(reviewPath))
|
|
445
|
+
continue;
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
if (db.prepare('SELECT 1 FROM human_requests WHERE request_id = ?').get(entry.name) !== undefined)
|
|
449
|
+
continue;
|
|
450
|
+
const rawManifest = readJson(manifestPath);
|
|
451
|
+
if (typeof rawManifest !== 'object' || rawManifest === null || Array.isArray(rawManifest))
|
|
452
|
+
throw new Error(`corrupt required input: ${manifestPath}`);
|
|
453
|
+
let manifest = rawManifest;
|
|
454
|
+
let retainedPresentationDirectory = realpathSync(dir);
|
|
455
|
+
if (manifest['schema'] === 'crtr.page/v1') {
|
|
456
|
+
const prepared = migrateLegacyPage(dir, rawManifest);
|
|
457
|
+
const stableRequestDirectory = requestDir(entry.name);
|
|
458
|
+
retainedPresentationDirectory = publishPageRevision(stableRequestDirectory, prepared);
|
|
459
|
+
manifest = prepared.manifest;
|
|
460
|
+
}
|
|
461
|
+
const delivery = pageDeliverySchema.safeParse(manifest['delivery']);
|
|
462
|
+
if (typeof manifest['title'] !== 'string' || !delivery.success)
|
|
463
|
+
throw new Error(`corrupt required input: ${manifestPath}`);
|
|
464
|
+
let canonicalManifest;
|
|
465
|
+
try {
|
|
466
|
+
canonicalManifest = validatePageManifest(manifest);
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
throw new Error(`corrupt required input: ${manifestPath}`);
|
|
470
|
+
}
|
|
471
|
+
const source = canonicalManifest.source;
|
|
472
|
+
const resultPath = join(dir, 'response.json');
|
|
473
|
+
const result = existsSync(resultPath) ? readJson(resultPath) : null;
|
|
474
|
+
const terminal = result === null ? null : legacyPageResult(result);
|
|
475
|
+
const actionDelivery = db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'deliveries'").get() === undefined
|
|
476
|
+
? null
|
|
477
|
+
: db.prepare("SELECT delivery_id FROM deliveries WHERE idempotency_key = ?").get(`human-action:${legacyTicketId(nodesRoot(), entry.name)}`);
|
|
478
|
+
const actionPath = join(dir, 'action.json');
|
|
479
|
+
const progressPath = join(dir, 'progress.json');
|
|
480
|
+
const feedbackPath = join(dir, 'feedback-comments.json');
|
|
481
|
+
const replyRoutePath = join(dir, 'reply-route.json');
|
|
482
|
+
const feedback = existsSync(feedbackPath) ? readJson(feedbackPath) : null;
|
|
483
|
+
if (feedback !== null && (typeof feedback !== 'object' || typeof feedback['companionNodeId'] !== 'undefined' && typeof feedback['companionNodeId'] !== 'string')) {
|
|
484
|
+
throw new Error(`corrupt required input: ${feedbackPath}`);
|
|
485
|
+
}
|
|
486
|
+
const replyRoute = existsSync(replyRoutePath) ? readJson(replyRoutePath) : null;
|
|
487
|
+
if (replyRoute !== null && (typeof replyRoute !== 'object' || typeof replyRoute['bridge_node_id'] !== 'string')) {
|
|
488
|
+
throw new Error(`corrupt required input: ${replyRoutePath}`);
|
|
489
|
+
}
|
|
490
|
+
const bridgeNodeId = replyRoute?.['bridge_node_id'];
|
|
491
|
+
const bridgeRequester = typeof bridgeNodeId !== 'string' || db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'nodes'").get() === undefined
|
|
492
|
+
? null
|
|
493
|
+
: db.prepare('SELECT parent FROM nodes WHERE node_id = ?').get(bridgeNodeId);
|
|
494
|
+
const requesterNodeId = bridgeRequester?.parent ?? (typeof source?.['nodeId'] === 'string' ? source['nodeId'] : null);
|
|
495
|
+
const companionNodeId = feedback?.['companionNodeId'];
|
|
496
|
+
const createdAt = typeof source?.['emittedAt'] === 'string' ? source['emittedAt'] : statSync(manifestPath).mtime.toISOString();
|
|
497
|
+
const resultJson = result === null ? null : JSON.stringify(result);
|
|
498
|
+
const settlementDeliveryId = terminalDeliveryMark(db, {
|
|
499
|
+
requestId: entry.name, requesterNodeId, kind: 'page', outcome: terminal?.outcome ?? null, resultJson,
|
|
500
|
+
settledAt: terminal?.settledAt ?? null, requestDir: dir,
|
|
501
|
+
});
|
|
502
|
+
const companionRetiredAt = companionRetirementProven(db, companionNodeId ?? null, terminal?.outcome ?? null) ? terminal?.settledAt ?? null : null;
|
|
503
|
+
const stableRequestDirectory = requestDir(entry.name);
|
|
504
|
+
mkdirSync(stableRequestDirectory, { recursive: true, mode: 0o700 });
|
|
505
|
+
insert.run(entry.name, 'page', legacyTicketId(nodesRoot(), entry.name), stableRequestDirectory, retainedPresentationDirectory, requesterNodeId, companionNodeId ?? null, null, terminal === null ? 'open' : 'settled', terminal?.outcome ?? null, delivery.data.inbox ? 1 : 0, delivery.data.inbox || delivery.data.reply ? 1 : 0, canonicalManifest.title, canonicalManifest.subtitle ?? null, null, JSON.stringify(canonicalManifest), existsSync(actionPath) ? JSON.stringify(readJson(actionPath)) : null, existsSync(progressPath) && terminal === null ? JSON.stringify(readJson(progressPath)) : null, feedback === null ? null : JSON.stringify(feedback), resultJson, null, null, createdAt, null, null, terminal?.settledAt ?? null, settlementDeliveryId, actionDelivery?.delivery_id ?? null, null, null, companionRetiredAt, createdAt);
|
|
506
|
+
}
|
|
507
|
+
const invalid = db.prepare("SELECT request_id FROM human_requests WHERE state = 'settled' AND result_json IS NULL").get();
|
|
508
|
+
if (invalid !== undefined)
|
|
509
|
+
throw new Error(`human request migration produced terminal row without result: ${invalid.request_id}`);
|
|
510
|
+
if (db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'review_comments'").get() !== undefined) {
|
|
511
|
+
const orphan = db.prepare("SELECT review_id FROM review_comments WHERE review_id NOT IN (SELECT request_id FROM human_requests WHERE kind = 'review') LIMIT 1").get();
|
|
512
|
+
if (orphan !== undefined)
|
|
513
|
+
throw new Error(`review comment has no migrated review request: ${orphan.review_id}`);
|
|
514
|
+
}
|
|
515
|
+
}
|