@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
|
@@ -7,16 +7,19 @@
|
|
|
7
7
|
// its helpers in isolation.
|
|
8
8
|
import { test, before, after, afterEach } from 'node:test';
|
|
9
9
|
import assert from 'node:assert/strict';
|
|
10
|
-
import { existsSync,
|
|
10
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { createHeadlessHarness, readLines } from '../helpers/harness.js';
|
|
13
13
|
import { createAttachKit } from '../helpers/broker-clients.js';
|
|
14
|
+
import { openDb } from '../../canvas/db.js';
|
|
14
15
|
import { defaultModelLaddersConfig } from '../../../types.js';
|
|
15
|
-
import {
|
|
16
|
+
import { admitEpisode, readEpisode as readProviderRetryEpisode, readFault, recordPendingEpisode } from '../../canvas/recovery.js';
|
|
16
17
|
import { buildPiArgv, normalizeModel } from '../../runtime/launch.js';
|
|
18
|
+
import { writeRawConfig } from '../../config.js';
|
|
17
19
|
import { encodeFrame } from '../../runtime/broker-protocol.js';
|
|
18
20
|
import { CONNECTION_FAULT_RECOVERY_BODY, PROVIDER_FAULT_RECOVERY_BODY, generatedContextText, parseCard } from '../../../shared/generated-context.js';
|
|
19
21
|
import { RUNTIME_RESTART_CONTINUATION } from '../../runtime/kickoff.js';
|
|
22
|
+
import { writeSubscriptionPool } from '../../subscription-state.js';
|
|
20
23
|
const MODEL_LADDERS = defaultModelLaddersConfig();
|
|
21
24
|
function resolvedModel(spec) {
|
|
22
25
|
const normalized = normalizeModel(spec, MODEL_LADDERS);
|
|
@@ -31,26 +34,46 @@ const OPENAI_STRONG = resolvedModel('openai/strong');
|
|
|
31
34
|
let h;
|
|
32
35
|
const kit = createAttachKit(() => h);
|
|
33
36
|
const { attachUntil } = kit;
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
return
|
|
37
|
+
function episodeSessionId(episode) {
|
|
38
|
+
assert.ok(episode.sessionId !== null, 'a provider retry episode is bound to a session');
|
|
39
|
+
return episode.sessionId;
|
|
37
40
|
}
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
function admitProviderRetryEpisode(nodeId, episode, fault) {
|
|
42
|
+
return admitEpisode(nodeId, { sessionId: episodeSessionId(episode), faultId: fault.faultId, retry: fault.retry });
|
|
43
|
+
}
|
|
44
|
+
function setActiveFaultAnchor(nodeId, anchorEntryId) {
|
|
45
|
+
const result = openDb().prepare('UPDATE node_faults SET anchor_entry_id = ? WHERE node_id = ? AND active = 1').run(anchorEntryId, nodeId);
|
|
46
|
+
assert.equal(result.changes, 1, 'the fixture changes exactly one active fault');
|
|
47
|
+
}
|
|
48
|
+
function restoreAdmittedProviderRetryEpisode(nodeId, episode, fault) {
|
|
49
|
+
const stored = recordPendingEpisode(nodeId, {
|
|
50
|
+
sessionId: episodeSessionId(episode),
|
|
51
|
+
input: {
|
|
52
|
+
link: fault.link,
|
|
53
|
+
op: fault.op,
|
|
54
|
+
kind: fault.kind,
|
|
55
|
+
retry: fault.retry,
|
|
56
|
+
message: fault.message,
|
|
57
|
+
since: fault.since,
|
|
58
|
+
operation_id: fault.operation_id,
|
|
59
|
+
...(fault.anchorEntryId === undefined ? {} : { anchorEntryId: fault.anchorEntryId }),
|
|
60
|
+
...(fault.providerError === undefined ? {} : { providerError: fault.providerError }),
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
assert.ok(stored !== null, 'the bound session accepts a pending recovery episode');
|
|
64
|
+
assert.equal(admitProviderRetryEpisode(nodeId, episode, stored), true, 'the pending recovery episode becomes admitted');
|
|
65
|
+
}
|
|
66
|
+
async function seedPool(provider, cooling) {
|
|
67
|
+
await writeSubscriptionPool(provider, [{
|
|
42
68
|
label: 'test-account', refresh: 'refresh', access: 'access', expires: Date.now() + 60_000,
|
|
43
69
|
rateLimitedUntil: cooling ? Date.now() + 60_000 : 0, lastAttemptAt: 0, lastRateLimitedAt: 0,
|
|
44
|
-
}])
|
|
70
|
+
}]);
|
|
45
71
|
}
|
|
46
72
|
function modelRoute(spec) {
|
|
47
73
|
return { ultra: spec, strong: spec, medium: spec, light: spec };
|
|
48
74
|
}
|
|
49
75
|
function seedCapacityFallbackRoutes() {
|
|
50
|
-
|
|
51
|
-
const userConfigPath = join(userConfigDir, 'config.json');
|
|
52
|
-
mkdirSync(userConfigDir, { recursive: true });
|
|
53
|
-
writeFileSync(userConfigPath, JSON.stringify({
|
|
76
|
+
writeRawConfig('user', {
|
|
54
77
|
kinds: { 'test/capacity-fallback': { whenToUse: 'Capacity fallback regression.', model: 'anthropic/medium' } },
|
|
55
78
|
modelRoutes: {
|
|
56
79
|
primary: { family: 'anthropic', credentialSource: 'user', providerId: 'fake-primary', models: modelRoute('fake-primary/primary') },
|
|
@@ -58,8 +81,7 @@ function seedCapacityFallbackRoutes() {
|
|
|
58
81
|
largerFallback: { family: 'anthropic', credentialSource: 'third', providerId: 'fake-large', models: modelRoute('fake-large/large') },
|
|
59
82
|
},
|
|
60
83
|
modelRouting: { credentialSourceOrder: ['user', 'northlight', 'third'], familyOrder: ['anthropic'], strengthFallback: ['medium'] },
|
|
61
|
-
})
|
|
62
|
-
return userConfigPath;
|
|
84
|
+
});
|
|
63
85
|
}
|
|
64
86
|
function seedCapacityFixture(id, largeContextWindow = 999) {
|
|
65
87
|
const dir = join(h.home, 'nodes', id);
|
|
@@ -119,10 +141,12 @@ afterEach(() => {
|
|
|
119
141
|
});
|
|
120
142
|
test('a normalized 503 survives into a durable provider fault and re-drives the turn', { timeout: 30_000 }, async () => {
|
|
121
143
|
const parent = h.spawnRoot('structured provider failure parent');
|
|
122
|
-
seedPool('anthropic', false);
|
|
123
|
-
seedPool('openai-codex', false);
|
|
144
|
+
await seedPool('anthropic', false);
|
|
145
|
+
await seedPool('openai-codex', false);
|
|
124
146
|
const child = await h.spawnHeadlessChild(parent, 'structured provider failure', { model: OPENAI_STRONG.spec });
|
|
125
147
|
await h.waitFor(() => agentStartCount(child) >= 1, { label: 'initial structured-failure fixture turn started' });
|
|
148
|
+
const kickoffObserver = await attachUntil(child, 'observer', 'structured-503-kickoff-settled', (a) => a.welcome.snapshot.state.isStreaming === false, 'structured 503 kickoff settled');
|
|
149
|
+
kickoffObserver.close();
|
|
126
150
|
const baselineStarts = agentStartCount(child);
|
|
127
151
|
h.fakeCmd(child, {
|
|
128
152
|
cmd: 'stop',
|
|
@@ -146,8 +170,8 @@ test('a managed-cooling terminal authors a rate-limit auto fault whose deadline
|
|
|
146
170
|
const parent = h.spawnRoot('cooling terminal parent');
|
|
147
171
|
// The terminal fault path is pool-independent — the stophook classifies purely
|
|
148
172
|
// off the errorMessage text + the cooling diagnostic, so pool state is inert.
|
|
149
|
-
seedPool('anthropic', false);
|
|
150
|
-
seedPool('openai-codex', false);
|
|
173
|
+
await seedPool('anthropic', false);
|
|
174
|
+
await seedPool('openai-codex', false);
|
|
151
175
|
const child = await h.spawnHeadlessChild(parent, 'cooling terminal', { model: OPENAI_STRONG.spec });
|
|
152
176
|
await h.waitFor(() => agentStartCount(child) >= 1, { label: 'initial cooling fixture turn started' });
|
|
153
177
|
const kickoffObserver = await attachUntil(child, 'observer', 'cooling-terminal-kickoff-settled', (a) => a.welcome.snapshot.state.isStreaming === false, 'cooling terminal kickoff settled');
|
|
@@ -187,8 +211,8 @@ test('a managed-cooling terminal authors a rate-limit auto fault whose deadline
|
|
|
187
211
|
});
|
|
188
212
|
test('a crashed broker re-drives a pending episode and a no-progress admitted one, never a progressed or invalidated episode', { timeout: 90_000 }, async () => {
|
|
189
213
|
const parent = h.spawnRoot('durable provider episode parent');
|
|
190
|
-
seedPool('anthropic', false);
|
|
191
|
-
seedPool('openai-codex', false);
|
|
214
|
+
await seedPool('anthropic', false);
|
|
215
|
+
await seedPool('openai-codex', false);
|
|
192
216
|
const pendingNode = await h.spawnHeadlessChild(parent, 'pending provider episode', { model: OPENAI_STRONG.spec });
|
|
193
217
|
await h.waitFor(() => agentStartCount(pendingNode) >= 1, { label: 'pending episode fixture started' });
|
|
194
218
|
const pendingKickoffObserver = await attachUntil(pendingNode, 'observer', 'pending-provider-kickoff-settled', (a) => a.welcome.snapshot.state.isStreaming === false, 'pending provider episode kickoff settled');
|
|
@@ -198,7 +222,7 @@ test('a crashed broker re-drives a pending episode and a no-progress admitted on
|
|
|
198
222
|
cmd: 'stop', reason: 'error', errorMessage: 'Managed provider pools are temporarily unavailable.', deadline: Date.now() + 2_000,
|
|
199
223
|
});
|
|
200
224
|
const pending = await h.waitFor(() => readProviderRetryEpisode(pendingNode)?.state === 'pending' ? readProviderRetryEpisode(pendingNode) : null, { label: 'pending provider episode persisted before crash' });
|
|
201
|
-
assert.equal(pending.
|
|
225
|
+
assert.equal(pending.sessionId, h.node(pendingNode).pi_session_id, 'the pending episode is tied to its bound session');
|
|
202
226
|
await crashAndRespawn(pendingNode, h.bootCount(pendingNode));
|
|
203
227
|
await h.waitFor(() => agentStartCount(pendingNode) >= pendingStarts + 1, { label: 'replacement re-drove the pending episode' });
|
|
204
228
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -227,7 +251,7 @@ test('a crashed broker re-drives a pending episode and a no-progress admitted on
|
|
|
227
251
|
// already elapsed (see the design's "effectively zero delay" note), so this
|
|
228
252
|
// proves the re-drive without a real ~60s wait.
|
|
229
253
|
const admittedFaultForEpisode = { ...admittedFault, retry: { ...admittedFault.retry, nextAt: new Date(Date.now() - 1_000).toISOString() } };
|
|
230
|
-
assert.equal(admitProviderRetryEpisode(admittedNode, admittedPending
|
|
254
|
+
assert.equal(admitProviderRetryEpisode(admittedNode, admittedPending, admittedFaultForEpisode), true, 'the dispatch boundary durably admits the matching episode');
|
|
231
255
|
assert.equal(readProviderRetryEpisode(admittedNode)?.state, 'admitted');
|
|
232
256
|
await crashAndRespawn(admittedNode, h.bootCount(admittedNode));
|
|
233
257
|
await h.waitFor(() => agentStartCount(admittedNode) >= admittedStarts + 1, { label: 'replacement re-drove the no-progress admitted episode' });
|
|
@@ -269,7 +293,7 @@ test('a crashed broker re-drives a pending episode and a no-progress admitted on
|
|
|
269
293
|
const progressPending = await h.waitFor(() => readProviderRetryEpisode(progressNode)?.state === 'pending' ? readProviderRetryEpisode(progressNode) : null, { label: 'progress episode pending record persisted' });
|
|
270
294
|
const progressFault = readFault(progressNode);
|
|
271
295
|
assert.ok(progressFault !== null, 'the pending episode retains its retry fault before admission');
|
|
272
|
-
assert.equal(admitProviderRetryEpisode(progressNode, progressPending
|
|
296
|
+
assert.equal(admitProviderRetryEpisode(progressNode, progressPending, progressFault), true, 'the dispatch boundary durably admits the matching episode');
|
|
273
297
|
assert.equal(readProviderRetryEpisode(progressNode)?.state, 'admitted');
|
|
274
298
|
// A real successful turn past the anchor: proves the dead attempt's slot is
|
|
275
299
|
// no longer the branch tail, so recoverOnStartup's no-progress proof must
|
|
@@ -278,16 +302,12 @@ test('a crashed broker re-drives a pending episode and a no-progress admitted on
|
|
|
278
302
|
progressController.send({ type: 'prompt', text: `progress past anchor ${Date.now()}` });
|
|
279
303
|
await progressController.waitFrame((f) => f.type === 'agent_end', 'the progress turn settles');
|
|
280
304
|
progressController.close();
|
|
281
|
-
// The natural settle above
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
// the job-dir file directly, bypassing the guarded API (mirrors the
|
|
285
|
-
// `reload_auth leaves a parked auth fault` precedent above in this file), so
|
|
286
|
-
// the crash below tests the no-progress proof against a genuine post-anchor
|
|
287
|
-
// assistant turn instead of an unreliable real-crash race.
|
|
305
|
+
// The natural settle above clears the episode. Restore the same admitted SQL
|
|
306
|
+
// record so the crash below tests the no-progress proof against a genuine
|
|
307
|
+
// post-anchor assistant turn rather than an unreliable real-crash race.
|
|
288
308
|
await h.waitFor(() => readProviderRetryEpisode(progressNode) === null, { label: 'the settled turn cleared the episode' });
|
|
289
|
-
|
|
290
|
-
assert.equal(readProviderRetryEpisode(progressNode)?.state, 'admitted', 'the
|
|
309
|
+
restoreAdmittedProviderRetryEpisode(progressNode, progressPending, progressFault);
|
|
310
|
+
assert.equal(readProviderRetryEpisode(progressNode)?.state, 'admitted', 'the SQL fixture restores the admitted episode for the crash below');
|
|
291
311
|
const progressStartsBeforeCrash = agentStartCount(progressNode);
|
|
292
312
|
await crashAndRespawn(progressNode, h.bootCount(progressNode));
|
|
293
313
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -343,8 +363,8 @@ test('a crashed broker re-drives a pending episode and a no-progress admitted on
|
|
|
343
363
|
});
|
|
344
364
|
test('reload_auth re-drives a parked fatal provider-auth turn exactly once', { timeout: 30_000 }, async () => {
|
|
345
365
|
const parent = h.spawnRoot('auth reload parent');
|
|
346
|
-
seedPool('anthropic', false);
|
|
347
|
-
seedPool('openai-codex', false);
|
|
366
|
+
await seedPool('anthropic', false);
|
|
367
|
+
await seedPool('openai-codex', false);
|
|
348
368
|
const child = await h.spawnHeadlessChild(parent, 'auth reload child', { model: OPENAI_STRONG.spec });
|
|
349
369
|
await h.waitFor(() => agentStartCount(child) >= 1, { label: 'initial auth fixture turn started' });
|
|
350
370
|
const baselineStarts = agentStartCount(child);
|
|
@@ -373,8 +393,8 @@ test('reload_auth re-drives a parked fatal provider-auth turn exactly once', { t
|
|
|
373
393
|
});
|
|
374
394
|
test('reload_auth leaves a parked auth fault with its original session when navigation is overtaken by replacement', { timeout: 30_000 }, async () => {
|
|
375
395
|
const parent = h.spawnRoot('auth replacement parent');
|
|
376
|
-
seedPool('anthropic', false);
|
|
377
|
-
seedPool('openai-codex', false);
|
|
396
|
+
await seedPool('anthropic', false);
|
|
397
|
+
await seedPool('openai-codex', false);
|
|
378
398
|
const child = await h.spawnHeadlessChild(parent, 'auth replacement child', { model: OPENAI_STRONG.spec });
|
|
379
399
|
await h.waitFor(() => agentStartCount(child) >= 1, { label: 'initial replacement fixture turn started' });
|
|
380
400
|
h.fakeCmd(child, {
|
|
@@ -391,10 +411,7 @@ test('reload_auth leaves a parked auth fault with its original session when navi
|
|
|
391
411
|
// Force the redrive's leaf/anchor comparison to diverge so it actually enters
|
|
392
412
|
// navigateTree (a matching leaf/anchor legitimately skips the rewind) — the
|
|
393
413
|
// fixture's fault otherwise anchors to the leaf already current.
|
|
394
|
-
|
|
395
|
-
const rawFault = JSON.parse(readFileSync(faultFilePath, 'utf8'));
|
|
396
|
-
rawFault['anchorEntryId'] = 'auth-replacement-anchor-mismatch';
|
|
397
|
-
writeFileSync(faultFilePath, JSON.stringify(rawFault));
|
|
414
|
+
setActiveFaultAnchor(child, 'auth-replacement-anchor-mismatch');
|
|
398
415
|
writeFileSync(join(nodeDir, 'fake-pi.navigate-hold'), '');
|
|
399
416
|
const controller = await kit.attach(child, 'controller', 'auth-replacement-test');
|
|
400
417
|
controller.send({ type: 'reload_auth' });
|
|
@@ -412,8 +429,8 @@ test('reload_auth leaves a parked auth fault with its original session when navi
|
|
|
412
429
|
// definitive failure make the episode terminal.
|
|
413
430
|
test('a recovered compact-and-retry commits nothing, while a definitive failure publishes one structured overflow fault and terminal notice', { timeout: 30_000 }, async () => {
|
|
414
431
|
const parent = h.spawnRoot('overflow parent');
|
|
415
|
-
seedPool('anthropic', false);
|
|
416
|
-
seedPool('openai-codex', false);
|
|
432
|
+
await seedPool('anthropic', false);
|
|
433
|
+
await seedPool('openai-codex', false);
|
|
417
434
|
const child = await h.spawnHeadlessChild(parent, 'overflow child', { model: OPENAI_STRONG.spec });
|
|
418
435
|
h.fakeCmd(child, { cmd: 'overflow', outcome: 'recover' });
|
|
419
436
|
await h.waitFor(() => overflowOutcome(child) === 'recover' ? true : null, { label: 'recovered overflow reached agent_settled' });
|
|
@@ -437,18 +454,21 @@ test('a recovered compact-and-retry commits nothing, while a definitive failure
|
|
|
437
454
|
assert.equal(autoFault.kind, 'rate-limit', 'ordinary settled provider failures retain broker-owned retry behavior');
|
|
438
455
|
h.fakeCmd(child, { cmd: 'overflow', outcome: 'fail' });
|
|
439
456
|
await h.waitFor(() => overflowOutcome(child) === 'fail' ? true : null, { label: 'failed overflow reached agent_settled' });
|
|
440
|
-
const fault =
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
assert.
|
|
457
|
+
const fault = await h.waitFor(() => {
|
|
458
|
+
const current = readFault(child);
|
|
459
|
+
return current?.kind === 'context-overflow' ? current : null;
|
|
460
|
+
}, { label: 'failed overflow publishes its definitive context-overflow fault' });
|
|
461
|
+
assert.equal(fault.kind, 'context-overflow');
|
|
462
|
+
assert.equal(fault.op, 'context overflow recovery');
|
|
463
|
+
assert.equal(fault.retry.disposition, 'fatal');
|
|
464
|
+
assert.match(fault.message, /failed after one compact-and-retry attempt/i);
|
|
445
465
|
await h.tick();
|
|
446
466
|
assert.equal(h.status(child), 'dead', 'the daemon applies one terminal child transition');
|
|
447
467
|
const notices = h.inbox(parent).filter((entry) => /exceeded its model context window/i.test(entry.label));
|
|
448
468
|
assert.equal(notices.length, 1, 'the parent receives exactly one structured overflow notice');
|
|
449
469
|
});
|
|
450
470
|
test('provider-retry and model-not-found fallback both terminate when every available target has a smaller known context window', { timeout: 30_000 }, async () => {
|
|
451
|
-
|
|
471
|
+
seedCapacityFallbackRoutes();
|
|
452
472
|
try {
|
|
453
473
|
const parent = h.spawnRoot('capacity fallback parent');
|
|
454
474
|
const providerRetry = await h.spawnHeadlessChild(parent, 'provider retry capacity fallback', { kind: 'test/capacity-fallback' });
|
|
@@ -474,7 +494,7 @@ test('provider-retry and model-not-found fallback both terminate when every avai
|
|
|
474
494
|
assert.match(modelNotFoundFault.message, /1000 tokens/, 'the terminal fault survives ordinary settlement staging');
|
|
475
495
|
}
|
|
476
496
|
finally {
|
|
477
|
-
|
|
497
|
+
writeRawConfig('user', {});
|
|
478
498
|
}
|
|
479
499
|
});
|
|
480
500
|
// Two real logical launches keep both user paths non-vacuous: the picker first
|
|
@@ -482,14 +502,11 @@ test('provider-retry and model-not-found fallback both terminate when every avai
|
|
|
482
502
|
// directly against still-present logical intent before Shift+Tab and replay.
|
|
483
503
|
test("picker and Alt+M choices each clear logical routing, and Alt+M / Shift+Tab become the durable restart recipe", { timeout: 30_000 }, async () => {
|
|
484
504
|
const parent = h.spawnRoot('model durability parent');
|
|
485
|
-
seedPool('anthropic', false);
|
|
486
|
-
seedPool('openai-codex', false);
|
|
487
|
-
|
|
488
|
-
const userConfigPath = join(userConfigDir, 'config.json');
|
|
489
|
-
mkdirSync(userConfigDir, { recursive: true });
|
|
490
|
-
writeFileSync(userConfigPath, JSON.stringify({
|
|
505
|
+
await seedPool('anthropic', false);
|
|
506
|
+
await seedPool('openai-codex', false);
|
|
507
|
+
writeRawConfig('user', {
|
|
491
508
|
kinds: { 'test/logical-openai': { whenToUse: 'Logical OpenAI routing test.', model: 'openai/strong' } },
|
|
492
|
-
})
|
|
509
|
+
});
|
|
493
510
|
const pickerNode = await h.spawnHeadlessChild(parent, 'picker durability child', { kind: 'test/logical-openai' });
|
|
494
511
|
const pickerBefore = h.node(pickerNode);
|
|
495
512
|
assert.deepEqual(pickerBefore.launch?.modelIntent, { family: 'openai', strength: 'strong' }, 'picker fixture starts from a logical launch preference');
|
|
@@ -541,17 +558,14 @@ test("picker and Alt+M choices each clear logical routing, and Alt+M / Shift+Tab
|
|
|
541
558
|
assert.equal(replay.argv[modelArg + 1], expected, 'the replayed model argument carries both choices');
|
|
542
559
|
assert.equal(replay.env.CRTR_MODEL_INTENT, undefined, 'the stale logical intent is absent from the restarted broker environment');
|
|
543
560
|
ladderCtrl.close();
|
|
544
|
-
|
|
561
|
+
writeRawConfig('user', {});
|
|
545
562
|
});
|
|
546
563
|
test('reload_auth restores a newly authenticated personal route before the next idle prompt', { timeout: 30_000 }, async () => {
|
|
547
564
|
const personal = { provider: 'personal-anthropic', id: 'claude-personal' };
|
|
548
565
|
const funded = { provider: 'northlight-openai', id: 'gpt-funded' };
|
|
549
566
|
const personalSpec = `${personal.provider}/${personal.id}:xhigh`;
|
|
550
|
-
const userConfigDir = join(h.agentHome, '.crouter');
|
|
551
|
-
const userConfigPath = join(userConfigDir, 'config.json');
|
|
552
567
|
const unauthenticatedProvider = join(h.home, 'fake-pi.unauthenticated-provider');
|
|
553
|
-
|
|
554
|
-
writeFileSync(userConfigPath, JSON.stringify({
|
|
568
|
+
writeRawConfig('user', {
|
|
555
569
|
// A fixture-private kind, never `general`: the child launches with the
|
|
556
570
|
// package root as cwd, so an ambient project-scope config above the repo
|
|
557
571
|
// (`~/.crouter/config.json`) overrides this user-scope `general` and the
|
|
@@ -562,7 +576,7 @@ test('reload_auth restores a newly authenticated personal route before the next
|
|
|
562
576
|
funded: { family: 'anthropic', credentialSource: 'northlight', providerId: funded.provider, models: { ultra: `${funded.provider}/${funded.id}:medium`, strong: `${funded.provider}/${funded.id}:medium`, medium: `${funded.provider}/${funded.id}:medium`, light: `${funded.provider}/${funded.id}:medium` } },
|
|
563
577
|
},
|
|
564
578
|
modelRouting: { credentialSourceOrder: ['user', 'northlight'], familyOrder: ['anthropic'], strengthFallback: ['medium'] },
|
|
565
|
-
})
|
|
579
|
+
});
|
|
566
580
|
writeFileSync(unauthenticatedProvider, personal.provider);
|
|
567
581
|
try {
|
|
568
582
|
const parent = h.spawnRoot('auth route reconciliation parent');
|
|
@@ -571,6 +585,9 @@ test('reload_auth restores a newly authenticated personal route before the next
|
|
|
571
585
|
const controller = await kit.connectRaw(child, { read: true });
|
|
572
586
|
controller.send({ type: 'hello', role: 'controller', client_id: 'auth-route-reconciliation' });
|
|
573
587
|
await h.waitFor(() => controller.frames.some((frame) => frame.type === 'welcome') ? true : null, { label: 'route reconciliation controller welcomed' });
|
|
588
|
+
await h.waitFor(() => agentStartCount(child) >= 1, { label: 'route reconciliation kickoff started' });
|
|
589
|
+
const kickoffObserver = await attachUntil(child, 'observer', 'route-reconciliation-kickoff-settled', (a) => a.welcome.snapshot.state.isStreaming === false, 'route reconciliation kickoff settled');
|
|
590
|
+
kickoffObserver.close();
|
|
574
591
|
rmSync(unauthenticatedProvider, { force: true });
|
|
575
592
|
const frameStart = controller.frames.length;
|
|
576
593
|
controller.writeRaw(encodeFrame({ type: 'reload_auth' })
|
|
@@ -595,7 +612,7 @@ test('reload_auth restores a newly authenticated personal route before the next
|
|
|
595
612
|
}
|
|
596
613
|
finally {
|
|
597
614
|
rmSync(unauthenticatedProvider, { force: true });
|
|
598
|
-
|
|
615
|
+
writeRawConfig('user', {});
|
|
599
616
|
}
|
|
600
617
|
});
|
|
601
618
|
// MODEL-LESS LAUNCH RE-DRIVE — MUST REMAIN THE LAST TEST IN THIS FILE. Its
|
|
@@ -629,12 +646,9 @@ test('reload_auth restores a newly authenticated personal route before the next
|
|
|
629
646
|
// subscription-pool occupancy (broker-sdk-wiring's scope).
|
|
630
647
|
test('login re-drive → list_models self-heals (0a) and reload_auth resolves a model + fires the deferred kickoff', { timeout: 30_000 }, async () => {
|
|
631
648
|
const noAuthSentinel = join(h.home, 'fake-pi.no-auth');
|
|
632
|
-
|
|
633
|
-
const userConfigPath = join(userConfigDir, 'config.json');
|
|
634
|
-
mkdirSync(userConfigDir, { recursive: true });
|
|
635
|
-
writeFileSync(userConfigPath, JSON.stringify({
|
|
649
|
+
writeRawConfig('user', {
|
|
636
650
|
kinds: { general: { whenToUse: 'General work.', model: 'anthropic/medium' } },
|
|
637
|
-
})
|
|
651
|
+
});
|
|
638
652
|
writeFileSync(noAuthSentinel, '1');
|
|
639
653
|
try {
|
|
640
654
|
const parent = h.spawnRoot('login re-drive parent');
|
|
@@ -663,6 +677,6 @@ test('login re-drive → list_models self-heals (0a) and reload_auth resolves a
|
|
|
663
677
|
}
|
|
664
678
|
finally {
|
|
665
679
|
rmSync(noAuthSentinel, { force: true });
|
|
666
|
-
|
|
680
|
+
writeRawConfig('user', {});
|
|
667
681
|
}
|
|
668
682
|
});
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
// acting root:
|
|
5
5
|
//
|
|
6
6
|
// (1) Issue #222/#353 — a real idle-release cycle must settle the inbox watcher
|
|
7
|
-
// before the broker exits, and the daemon may only revive for a
|
|
8
|
-
//
|
|
7
|
+
// before the broker exits, and the daemon may only revive for a later
|
|
8
|
+
// pending SQL delivery.
|
|
9
9
|
// (2) The observed 57-broker / ~5.4GB leak — DAEMON SUPERVISION, not an agent
|
|
10
10
|
// event, owns the unattended clock. A node with no live obligation is
|
|
11
11
|
// terminalized into prunable history (a resident completed its
|
|
@@ -21,7 +21,8 @@ import { after, before, test } from 'node:test';
|
|
|
21
21
|
import assert from 'node:assert/strict';
|
|
22
22
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
23
23
|
import { join } from 'node:path';
|
|
24
|
-
import {
|
|
24
|
+
import { enqueueNodeMail } from '../../feed/messages.js';
|
|
25
|
+
import { openDb } from '../../canvas/db.js';
|
|
25
26
|
import { acquireCronRunLease, armCron, setCronState } from '../../canvas/crons.js';
|
|
26
27
|
import { registerWarmSpare, subscribe, subscriptionsOf, updateNode } from '../../canvas/index.js';
|
|
27
28
|
import { isPidAlive } from '../../canvas/pid.js';
|
|
@@ -31,6 +32,17 @@ import { createAttachKit } from '../helpers/broker-clients.js';
|
|
|
31
32
|
let h;
|
|
32
33
|
let root;
|
|
33
34
|
const attach = createAttachKit(() => h);
|
|
35
|
+
function deliveryState(deliveryId) {
|
|
36
|
+
return openDb().prepare('SELECT state FROM deliveries WHERE delivery_id = ?').get(deliveryId)?.state;
|
|
37
|
+
}
|
|
38
|
+
function mailTo(nodeId) {
|
|
39
|
+
return openDb().prepare(`SELECT m.sender_node_id, m.body_json, m.label FROM deliveries d JOIN messages m ON m.message_id = d.message_id
|
|
40
|
+
WHERE d.destination_kind = 'node_inbox' AND d.destination_id = ? ORDER BY m.seq`).all(nodeId).map((row) => ({
|
|
41
|
+
sender: row.sender_node_id,
|
|
42
|
+
body: row.body_json === null ? null : JSON.parse(row.body_json),
|
|
43
|
+
label: row.label,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
34
46
|
before(async () => {
|
|
35
47
|
h = await createHarness({ headless: true, sessionPrefix: 'crtr-dormancy' });
|
|
36
48
|
root = h.spawnRoot('dormancy-release suite root');
|
|
@@ -105,8 +117,8 @@ test('idle-release settles its real watcher before exit; daemon revives exactly
|
|
|
105
117
|
// run in-process. Keeping this compiled seam at production cadence would add
|
|
106
118
|
// multiple seconds of polling around two real broker boots, so it uses the
|
|
107
119
|
// same fast cadence without weakening what it proves: the settlement
|
|
108
|
-
// lifecycle,
|
|
109
|
-
// independent of how fast the watcher polls.
|
|
120
|
+
// lifecycle, delivery acknowledgement semantics, and hasPendingMessages veto
|
|
121
|
+
// are all independent of how fast the watcher polls.
|
|
110
122
|
const manager = h.fabricateBrokerNode({ id: 'settle-veto-manager', status: 'idle', parent: root });
|
|
111
123
|
updateNode(manager, {
|
|
112
124
|
launch: {
|
|
@@ -127,12 +139,13 @@ test('idle-release settles its real watcher before exit; daemon revives exactly
|
|
|
127
139
|
// awaiting, so its real stophook takes the idle-release branch.
|
|
128
140
|
const worker = h.fabricateBrokerNode({ parent: manager, status: 'idle', intent: 'idle-release' });
|
|
129
141
|
subscribe(manager, worker, true);
|
|
130
|
-
const first =
|
|
131
|
-
|
|
132
|
-
tier: 'normal',
|
|
142
|
+
const first = enqueueNodeMail(manager, {
|
|
143
|
+
senderNodeId: 'worker-a',
|
|
133
144
|
kind: 'final',
|
|
134
145
|
label: 'first completed',
|
|
135
|
-
|
|
146
|
+
bodyJson: { body: 'first completed' },
|
|
147
|
+
urgency: 'normal',
|
|
148
|
+
wake: 1,
|
|
136
149
|
});
|
|
137
150
|
await h.awaitWake(manager, { match: /first completed/ });
|
|
138
151
|
// The real watcher injects an idle message, whose fake session now starts and
|
|
@@ -140,27 +153,28 @@ test('idle-release settles its real watcher before exit; daemon revives exactly
|
|
|
140
153
|
// broker exit. No test-driven turn or settlement dispatch is involved.
|
|
141
154
|
await awaitReleased(manager);
|
|
142
155
|
await h.awaitFleetExit(manager);
|
|
143
|
-
assert.equal(
|
|
156
|
+
assert.equal(deliveryState(first.deliveryId), 'acknowledged', 'the exact handed-off SQL delivery is acknowledged before clean shutdown');
|
|
144
157
|
const cyclesAfterRelease = h.node(manager).cycles ?? 0;
|
|
145
158
|
await h.tick();
|
|
146
159
|
assert.equal(h.node(manager).cycles ?? 0, cyclesAfterRelease, 'a second supervision tick does not revive a settled row');
|
|
147
160
|
const injectedBeforeSecond = h.injected(manager).length;
|
|
148
|
-
const second =
|
|
149
|
-
|
|
150
|
-
tier: 'normal',
|
|
161
|
+
const second = enqueueNodeMail(manager, {
|
|
162
|
+
senderNodeId: 'worker-b',
|
|
151
163
|
kind: 'final',
|
|
152
164
|
label: 'second completed',
|
|
153
|
-
|
|
165
|
+
bodyJson: { body: 'second completed' },
|
|
166
|
+
urgency: 'normal',
|
|
167
|
+
wake: 1,
|
|
154
168
|
});
|
|
155
|
-
assert.
|
|
156
|
-
assert.equal(
|
|
169
|
+
assert.equal(deliveryState(first.deliveryId), 'acknowledged', 'the first receipt remains terminal');
|
|
170
|
+
assert.equal(deliveryState(second.deliveryId), 'pending', 'the later delivery is independently pending');
|
|
157
171
|
await h.tick();
|
|
158
172
|
assert.equal(h.node(manager).cycles ?? 0, cyclesAfterRelease + 1, 'one later row revives once');
|
|
159
173
|
await h.awaitBoot(manager, { minCount: 2 });
|
|
160
174
|
await h.awaitWake(manager, { sinceCount: injectedBeforeSecond, match: /second completed/ });
|
|
161
175
|
await awaitReleased(manager);
|
|
162
176
|
await h.awaitFleetExit(manager);
|
|
163
|
-
assert.equal(
|
|
177
|
+
assert.equal(deliveryState(second.deliveryId), 'acknowledged', 'the later delivery is acknowledged at its own natural idle release');
|
|
164
178
|
await h.tick();
|
|
165
179
|
assert.equal(h.node(manager).cycles ?? 0, cyclesAfterRelease + 1, 'the settled later row does not cause a duplicate revival');
|
|
166
180
|
});
|
|
@@ -177,7 +191,7 @@ test('an already idle resident with no live obligation reconciles into completed
|
|
|
177
191
|
await h.tick();
|
|
178
192
|
assert.equal(h.node(nodeId)?.status, 'done', 'an older idle row becomes pruning-eligible without needing a broker revive');
|
|
179
193
|
assert.equal(h.node(nodeId)?.intent, 'parked', 'the reconciled row carries the durable park marker (isParked)');
|
|
180
|
-
const wake =
|
|
194
|
+
const wake = mailTo(root).find((entry) => entry.sender === nodeId && entry.body?.['reason'] === 'child-auto-done');
|
|
181
195
|
assert.ok(wake, 'the subscribing root learns that the parked resident completed without a final report');
|
|
182
196
|
});
|
|
183
197
|
test('an unattended resident with no live obligation completes on the daemon clock', { timeout: 30_000 }, async () => {
|
|
@@ -235,7 +249,7 @@ test('an unattended resident with no live obligation completes on the daemon clo
|
|
|
235
249
|
await h.awaitFleetExit(nodeId);
|
|
236
250
|
assert.equal(h.fleet.has(nodeId), false, 'exit policy forgets the parked broker');
|
|
237
251
|
assert.equal(h.node(nodeId)?.status, 'done', 'the row is terminal and eligible for history pruning');
|
|
238
|
-
const wake =
|
|
252
|
+
const wake = mailTo(root).find((entry) => entry.sender === nodeId && entry.body?.['reason'] === 'child-auto-done');
|
|
239
253
|
assert.ok(wake, 'the subscribing root is woken when the unattended clock completes the resident');
|
|
240
254
|
assert.match(wake.label, /submitted no final report/, 'the wake distinguishes automatic completion from a final report');
|
|
241
255
|
await h.tick();
|
|
@@ -7,7 +7,8 @@ import { after, before, test } from 'node:test';
|
|
|
7
7
|
import assert from 'node:assert/strict';
|
|
8
8
|
import { existsSync, readFileSync } from 'node:fs';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
|
-
import {
|
|
10
|
+
import { enqueueNodeMail } from '../../feed/messages.js';
|
|
11
|
+
import { acknowledgedNodeMailCount } from '../../canvas/deliveries.js';
|
|
11
12
|
import { updateNode } from '../../canvas/index.js';
|
|
12
13
|
import { reviveNode } from '../../runtime/revive.js';
|
|
13
14
|
import { createHeadlessHarness } from '../helpers/harness.js';
|
|
@@ -44,11 +45,12 @@ test('a Jiti-loaded watcher prepends its held deferred batch to a native broker
|
|
|
44
45
|
});
|
|
45
46
|
await reviveNode(nodeId, { resume: true, capacity: 'freeze' });
|
|
46
47
|
await h.awaitBoot(nodeId);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
tier: 'deferred',
|
|
48
|
+
enqueueNodeMail(nodeId, {
|
|
49
|
+
senderNodeId: 'child',
|
|
50
50
|
kind: 'update',
|
|
51
51
|
label: 'held Jiti deferred update',
|
|
52
|
+
urgency: 'deferred',
|
|
53
|
+
wake: 1,
|
|
52
54
|
});
|
|
53
55
|
await delay(150);
|
|
54
56
|
const customPath = join(h.home, 'nodes', nodeId, 'fake-pi.custom-messages.jsonl');
|
|
@@ -59,5 +61,5 @@ test('a Jiti-loaded watcher prepends its held deferred batch to a native broker
|
|
|
59
61
|
await controller.waitFrame((frame) => frame.type === 'agent_end' && frameHas(frame, prompt), 'the human prompt turn');
|
|
60
62
|
const customMessages = readFileSync(customPath, 'utf8');
|
|
61
63
|
assert.match(customMessages, /held Jiti deferred update/, 'the Jiti extension instance joined its held batch before the native broker prompt');
|
|
62
|
-
assert.equal(
|
|
64
|
+
assert.equal(acknowledgedNodeMailCount(nodeId), 1, 'agent_settled acknowledges the joined SQL delivery');
|
|
63
65
|
});
|
|
@@ -7,8 +7,11 @@ import { after, before, test } from 'node:test';
|
|
|
7
7
|
import assert from 'node:assert/strict';
|
|
8
8
|
import { readFileSync } from 'node:fs';
|
|
9
9
|
import { subscribe } from '../../canvas/index.js';
|
|
10
|
+
import { claimNodeMail } from '../../canvas/deliveries.js';
|
|
11
|
+
import { beginExecution } from '../../canvas/executions.js';
|
|
10
12
|
import { resolveRef } from '../../canvas/history.js';
|
|
11
|
-
import {
|
|
13
|
+
import { coalesce, nodeMailEntry } from '../../feed/inbox.js';
|
|
14
|
+
import { enqueueNodeMail } from '../../feed/messages.js';
|
|
12
15
|
import { pushUpdate } from '../../feed/feed.js';
|
|
13
16
|
import { contextDirNote } from '../../runtime/bearings-render.js';
|
|
14
17
|
import { coalesceBrokerInbox } from '../../runtime/broker/inbox.js';
|
|
@@ -16,15 +19,24 @@ import { createHeadlessHarness } from '../helpers/harness.js';
|
|
|
16
19
|
let h;
|
|
17
20
|
let root;
|
|
18
21
|
let recipient;
|
|
22
|
+
let recipientExecution;
|
|
19
23
|
before(async () => {
|
|
20
24
|
h = await createHeadlessHarness({ sessionPrefix: 'crtr-inbox-reference-guidance' });
|
|
21
25
|
root = h.spawnRoot('inbox reference guidance seam root');
|
|
22
26
|
recipient = h.fabricateBrokerNode({ id: 'inbox-reference-recipient', parent: root, status: 'idle' });
|
|
27
|
+
recipientExecution = beginExecution(recipient, { daemonEpoch: h.fleet.epoch() });
|
|
23
28
|
});
|
|
24
29
|
after(async () => {
|
|
25
30
|
if (h !== undefined)
|
|
26
31
|
await h.dispose();
|
|
27
32
|
});
|
|
33
|
+
function latestEntry() {
|
|
34
|
+
const claim = claimNodeMail(recipient, recipientExecution, 'wake');
|
|
35
|
+
assert.equal(claim.action, 'claimed');
|
|
36
|
+
const delivery = claim.deliveries.at(-1);
|
|
37
|
+
assert.ok(delivery, 'the newly enqueued delivery is handed to the recipient execution');
|
|
38
|
+
return nodeMailEntry(delivery);
|
|
39
|
+
}
|
|
28
40
|
function onlyCard(entries) {
|
|
29
41
|
const deliveries = coalesce(entries);
|
|
30
42
|
assert.equal(deliveries.length, 1, 'one non-human inbox entry produces one card');
|
|
@@ -36,7 +48,7 @@ test('delivered report and message refs name the exact way to read their bodies'
|
|
|
36
48
|
subscribe(recipient, publisher, true);
|
|
37
49
|
const reportBody = 'published report body';
|
|
38
50
|
await pushUpdate(publisher, reportBody);
|
|
39
|
-
const reportEntry =
|
|
51
|
+
const reportEntry = latestEntry();
|
|
40
52
|
assert.equal(reportEntry.ref?.startsWith(`${publisher}:reports/`), true, 'delivery carries the canonical report handle');
|
|
41
53
|
const reportRef = reportEntry.ref;
|
|
42
54
|
const reportCard = onlyCard([reportEntry]);
|
|
@@ -49,13 +61,15 @@ test('delivered report and message refs name the exact way to read their bodies'
|
|
|
49
61
|
'a'.repeat(1001),
|
|
50
62
|
Array.from({ length: 13 }, (_, i) => `line ${i + 1}`).join('\n'),
|
|
51
63
|
]) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
64
|
+
enqueueNodeMail(recipient, {
|
|
65
|
+
senderNodeId: publisher,
|
|
66
|
+
urgency: 'normal',
|
|
67
|
+
wake: 1,
|
|
55
68
|
kind: 'message',
|
|
56
69
|
label: 'long message',
|
|
57
|
-
|
|
70
|
+
bodyJson: { body },
|
|
58
71
|
});
|
|
72
|
+
const message = latestEntry();
|
|
59
73
|
assert.ok(message.ref?.startsWith('/'), 'a clipped message stores its full body at an absolute recipient path');
|
|
60
74
|
const messageCard = onlyCard([message]);
|
|
61
75
|
const brokerMessageCard = coalesceBrokerInbox([message], new Map())[0].text;
|
|
@@ -8,11 +8,35 @@ import assert from 'node:assert/strict';
|
|
|
8
8
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
import { closeDb } from '../../canvas/db.js';
|
|
11
|
-
import {
|
|
11
|
+
import { messageBodyForDelivery, readExternalActionDelivery } from '../../canvas/deliveries.js';
|
|
12
12
|
import { createHeadlessHarness } from '../helpers/harness.js';
|
|
13
13
|
let h;
|
|
14
14
|
let root;
|
|
15
15
|
let project;
|
|
16
|
+
function outcomeDelivery(nodeId) {
|
|
17
|
+
const row = readExternalActionDelivery(`node-outcome:${nodeId}`);
|
|
18
|
+
if (row === null)
|
|
19
|
+
return null;
|
|
20
|
+
const adapter = row.adapter;
|
|
21
|
+
if (adapter?.adapter !== 'subprocess' || !Array.isArray(adapter.argv) || !adapter.argv.every((entry) => typeof entry === 'string') || typeof adapter.cwd !== 'string')
|
|
22
|
+
throw new Error(`outcome delivery ${row.deliveryId} has an invalid subprocess adapter`);
|
|
23
|
+
if (row.state !== 'armed' && row.state !== 'pending' && row.state !== 'handed_off' && row.state !== 'acknowledged' && row.state !== 'permanent_failed')
|
|
24
|
+
throw new Error(`outcome delivery ${row.deliveryId} has an invalid state`);
|
|
25
|
+
return {
|
|
26
|
+
state: row.state === 'handed_off' ? 'running' : row.state === 'acknowledged' ? 'accepted' : row.state,
|
|
27
|
+
actionName: row.destinationId,
|
|
28
|
+
argv: adapter.argv,
|
|
29
|
+
cwd: adapter.cwd,
|
|
30
|
+
configOrigin: typeof adapter.config_origin === 'string' ? adapter.config_origin : '',
|
|
31
|
+
payload: adapter.payload ?? null,
|
|
32
|
+
document: row.messageId === null ? null : messageBodyForDelivery(row.deliveryId),
|
|
33
|
+
attempt: row.attempts,
|
|
34
|
+
nextAttemptAt: row.nextAttemptAt ?? 0,
|
|
35
|
+
acceptedAt: row.acknowledgedAt,
|
|
36
|
+
permanentFailedAt: row.failedAt,
|
|
37
|
+
lastFailure: row.lastFailure,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
16
40
|
function assertKeys(value, expected, label) {
|
|
17
41
|
assert.deepEqual(Object.keys(value).sort(), [...expected].sort(), `${label} has the pinned document fields`);
|
|
18
42
|
}
|
|
@@ -54,7 +78,7 @@ function assertResultDocument(document, nodeId, action, payloadJson, attempt) {
|
|
|
54
78
|
function deliveryWhen(nodeId, predicate, label) {
|
|
55
79
|
return h.waitFor(() => {
|
|
56
80
|
closeDb();
|
|
57
|
-
const delivery =
|
|
81
|
+
const delivery = outcomeDelivery(nodeId);
|
|
58
82
|
return delivery !== null && predicate(delivery) ? delivery : null;
|
|
59
83
|
}, { label, timeoutMs: 20_000 });
|
|
60
84
|
}
|
|
@@ -71,7 +95,7 @@ async function createAndFinalize(action, payload) {
|
|
|
71
95
|
const nodeId = createdBody.node_id;
|
|
72
96
|
await h.awaitBoot(nodeId);
|
|
73
97
|
closeDb();
|
|
74
|
-
const armed =
|
|
98
|
+
const armed = outcomeDelivery(nodeId);
|
|
75
99
|
assert.notEqual(armed, null, `node birth arms ${action} delivery`);
|
|
76
100
|
assert.equal(armed.state, 'armed');
|
|
77
101
|
assert.equal(armed.actionName, action);
|