@north-light/crouter 0.3.173 → 0.3.174
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/bin/runtime-selector.mjs +8 -1
- package/dist/api/__tests__/{client.test.js → serial/client.test.js} +2 -135
- package/dist/api/client.d.ts +12 -21
- package/dist/api/client.js +22 -31
- package/dist/api/dto/broker-ops.d.ts +158 -0
- package/dist/api/dto/broker-ops.js +6 -0
- package/dist/api/dto/crons.d.ts +6 -0
- package/dist/api/dto/human.d.ts +7 -1
- package/dist/api/dto/inbox.d.ts +1 -71
- package/dist/api/dto/inbox.js +1 -9
- package/dist/api/dto/nodes.d.ts +6 -7
- package/dist/api/dto/reviews.d.ts +1 -11
- package/dist/api/routes.d.ts +7 -4
- package/dist/api/routes.js +8 -5
- package/dist/build-root.js +3 -3
- package/dist/builtin-memory/04-base-worker.md +1 -4
- package/dist/builtin-memory/05-kinds/advisor/00-base.md +1 -5
- package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +2 -7
- package/dist/builtin-memory/05-kinds/design/00-base.md +1 -6
- package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -6
- package/dist/builtin-memory/05-kinds/explore/00-base.md +1 -4
- package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +1 -2
- package/dist/builtin-memory/05-kinds/general/00-base.md +1 -3
- package/dist/builtin-memory/05-kinds/plan/00-base.md +1 -6
- package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +1 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +1 -3
- package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +1 -3
- package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +1 -3
- package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -3
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +1 -4
- package/dist/builtin-memory/05-kinds/review/companion/00-base.md +2 -3
- package/dist/builtin-memory/05-kinds/spec/00-base.md +1 -2
- package/dist/builtin-memory/advisor/council.md +1 -6
- package/dist/builtin-memory/design.md +2 -7
- package/dist/builtin-memory/development.md +9 -14
- package/dist/builtin-memory/internal/marketplaces.md +2 -6
- package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
- package/dist/builtin-memory/internal/plugins.md +2 -8
- package/dist/builtin-memory/internal/storage-tiers.md +1 -2
- package/dist/builtin-memory/testing.md +41 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/{provider-rotation.test.ts → serial/provider-rotation.test.ts} +27 -179
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +5 -15
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +1 -28
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +43 -88
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +7 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +9 -1
- package/dist/cli.js +2 -2
- package/dist/clients/attach/__tests__/attach-remote-readonly.test.js +37 -131
- package/dist/clients/attach/__tests__/chat-view-snapshot-ordering.test.js +71 -152
- package/dist/clients/attach/__tests__/context-message.test.js +16 -174
- package/dist/clients/attach/__tests__/crtr-output.test.js +36 -2069
- package/dist/clients/attach/__tests__/diagram.test.js +2 -2
- package/dist/clients/attach/__tests__/edit-diff.test.js +0 -4
- package/dist/clients/attach/__tests__/file-review-focus.test.js +2 -1
- package/dist/clients/attach/__tests__/group-activity.test.js +40 -229
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +26 -76
- package/dist/clients/attach/__tests__/prompt-preview.test.js +4 -70
- package/dist/clients/attach/__tests__/ref-autocomplete.test.js +11 -125
- package/dist/clients/attach/__tests__/titled-editor-preview.test.js +14 -205
- package/dist/clients/attach/command.d.ts +2 -1
- package/dist/clients/attach/command.js +81 -76
- package/dist/clients/attach/input/capabilities.d.ts +27 -0
- package/dist/clients/attach/input/capabilities.js +96 -0
- package/dist/clients/attach/input/controller.d.ts +20 -182
- package/dist/clients/attach/input/controller.js +128 -537
- package/dist/clients/attach/input/overlay-owner.d.ts +41 -0
- package/dist/clients/attach/input/overlay-owner.js +199 -0
- package/dist/clients/attach/overlays/file-review.d.ts +0 -4
- package/dist/clients/attach/overlays/file-review.js +13 -5
- package/dist/clients/attach/overlays/graph.js +2 -10
- package/dist/clients/attach/render/chat-view.d.ts +4 -3
- package/dist/clients/attach/render/chat-view.js +39 -33
- package/dist/clients/attach/render/group-activity.d.ts +1 -0
- package/dist/clients/attach/render/group-activity.js +20 -13
- package/dist/clients/attach/render/group-recap.js +12 -3
- package/dist/clients/attach/session/connection.js +9 -15
- package/dist/clients/attach/session/editor-inventory.d.ts +6 -5
- package/dist/clients/attach/session/editor-inventory.js +7 -8
- package/dist/clients/attach/session/file-links.d.ts +13 -0
- package/dist/clients/attach/session/file-links.js +107 -0
- package/dist/clients/attach/session/frame.d.ts +3 -0
- package/dist/clients/attach/session/frame.js +10 -1
- package/dist/clients/attach/session/frames.js +2 -3
- package/dist/clients/attach/session/input-wiring.d.ts +4 -1
- package/dist/clients/attach/session/input-wiring.js +15 -14
- package/dist/clients/attach/session/mouse.d.ts +6 -1
- package/dist/clients/attach/session/mouse.js +8 -5
- package/dist/clients/attach/session/profile-files.js +18 -12
- package/dist/clients/attach/session/reconnect.js +1 -1
- package/dist/clients/attach/session/state-sync.js +1 -2
- package/dist/clients/attach/session/surface-host.d.ts +3 -2
- package/dist/clients/attach/session/surface-host.js +4 -4
- package/dist/clients/attach/slash/dispatch.d.ts +8 -31
- package/dist/clients/attach/slash/dispatch.js +34 -81
- package/dist/clients/attach/viewer.js +820 -860
- package/dist/clients/conversation/projection.d.ts +79 -0
- package/dist/clients/conversation/projection.js +341 -0
- package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +159 -0
- package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +151 -0
- package/dist/clients/inbox/controller.d.ts +0 -4
- package/dist/clients/inbox/controller.js +0 -2
- package/dist/clients/inbox/review/__tests__/review-core.test.js +4 -293
- package/dist/clients/inbox/review/__tests__/teardown-notice.test.js +100 -0
- package/dist/clients/inbox/review/comments-client.d.ts +3 -2
- package/dist/clients/inbox/review/comments-client.js +12 -12
- package/dist/clients/inbox/review/companion-pane.d.ts +30 -0
- package/dist/clients/inbox/review/companion-pane.js +86 -0
- package/dist/clients/inbox/review/document-surface.d.ts +182 -0
- package/dist/clients/inbox/review/{document-region.js → document-surface.js} +252 -48
- package/dist/clients/inbox/review/frame.js +5 -4
- package/dist/clients/inbox/review/launch.d.ts +8 -8
- package/dist/clients/inbox/review/launch.js +52 -92
- package/dist/clients/inbox/review/nvim.d.ts +2 -0
- package/dist/clients/inbox/review/nvim.js +14 -12
- package/dist/clients/inbox/review/review-client.d.ts +12 -8
- package/dist/clients/inbox/review/review-client.js +46 -35
- package/dist/clients/inbox/review-adapter.d.ts +3 -10
- package/dist/clients/inbox/review-adapter.js +27 -19
- package/dist/clients/inbox/surface.d.ts +0 -4
- package/dist/clients/inbox/surface.js +0 -2
- package/dist/clients/surfaces/host.d.ts +11 -3
- package/dist/commands/__tests__/api-client-cold-start-diagnostic.test.js +7 -34
- package/dist/commands/__tests__/human.test.js +11 -322
- package/dist/commands/api-client.d.ts +5 -5
- package/dist/commands/api-client.js +6 -85
- package/dist/commands/canvas-browse.js +3 -29
- package/dist/commands/canvas-config.js +1 -1
- package/dist/commands/canvas-use.js +3 -3
- package/dist/commands/canvas.js +1 -1
- package/dist/commands/cron.js +5 -1
- package/dist/commands/human/queue.d.ts +0 -4
- package/dist/commands/human/queue.js +7 -40
- package/dist/commands/memory/list.d.ts +14 -0
- package/dist/commands/memory/list.js +35 -31
- package/dist/commands/memory/read.d.ts +7 -0
- package/dist/commands/memory/read.js +14 -8
- package/dist/commands/node/bash.d.ts +1 -0
- package/dist/commands/node/bash.js +198 -0
- package/dist/commands/node/create.d.ts +23 -0
- package/dist/commands/node/create.js +207 -0
- package/dist/commands/node/inspect.d.ts +2 -0
- package/dist/commands/node/inspect.js +302 -0
- package/dist/commands/node/lifecycle.d.ts +4 -0
- package/dist/commands/node/lifecycle.js +415 -0
- package/dist/commands/node/message.d.ts +1 -0
- package/dist/commands/node/message.js +164 -0
- package/dist/commands/node/pyramid.d.ts +2 -0
- package/dist/commands/node/pyramid.js +79 -0
- package/dist/commands/node/subscription.d.ts +1 -0
- package/dist/commands/node/subscription.js +82 -0
- package/dist/commands/node/wait.d.ts +1 -0
- package/dist/commands/node/wait.js +159 -0
- package/dist/commands/node-lifecycle-revive.js +10 -57
- package/dist/commands/node.d.ts +0 -42
- package/dist/commands/node.js +11 -2065
- package/dist/commands/search.js +1 -1
- package/dist/commands/surface/node/cycle.d.ts +1 -0
- package/dist/commands/surface/node/cycle.js +83 -0
- package/dist/commands/surface/node/focus.d.ts +1 -0
- package/dist/commands/surface/node/focus.js +67 -0
- package/dist/commands/surface/node/id.d.ts +1 -0
- package/dist/commands/surface/node/id.js +38 -0
- package/dist/commands/surface/node/pin.d.ts +1 -0
- package/dist/commands/surface/node/pin.js +42 -0
- package/dist/commands/surface/node/placement.d.ts +46 -0
- package/dist/commands/surface/node/placement.js +124 -0
- package/dist/commands/surface-node.js +4 -1
- package/dist/commands/surface-reload.js +1 -2
- package/dist/commands/surface-tmux-spread.js +1 -1
- package/dist/commands/surface.js +5 -20
- package/dist/commands/sys/__tests__/setup-core.test.js +18 -271
- package/dist/commands/sys/config.js +6 -6
- package/dist/commands/sys/logs.js +1 -1
- package/dist/commands/sys/panels/broker-limits-panel.d.ts +17 -0
- package/dist/commands/sys/panels/broker-limits-panel.js +104 -0
- package/dist/commands/sys/panels/dependencies-panel.d.ts +14 -0
- package/dist/commands/sys/panels/dependencies-panel.js +80 -0
- package/dist/commands/sys/panels/keybindings-panel.d.ts +56 -0
- package/dist/commands/sys/panels/keybindings-panel.js +703 -0
- package/dist/commands/sys/panels/keys-panel.d.ts +16 -0
- package/dist/commands/sys/panels/keys-panel.js +60 -0
- package/dist/commands/sys/panels/models-panel.d.ts +77 -0
- package/dist/commands/sys/panels/models-panel.js +647 -0
- package/dist/commands/sys/panels/panel.d.ts +45 -0
- package/dist/commands/sys/panels/panel.js +137 -0
- package/dist/commands/sys/panels/plugins-panel.d.ts +16 -0
- package/dist/commands/sys/panels/plugins-panel.js +80 -0
- package/dist/commands/sys/panels/prefs-panel.d.ts +21 -0
- package/dist/commands/sys/panels/prefs-panel.js +85 -0
- package/dist/commands/sys/panels/provider-panel.d.ts +62 -0
- package/dist/commands/sys/panels/provider-panel.js +538 -0
- package/dist/commands/sys/settings-shell.d.ts +55 -0
- package/dist/commands/sys/settings-shell.js +400 -0
- package/dist/commands/sys/settings.d.ts +2 -0
- package/dist/commands/sys/settings.js +31 -0
- package/dist/commands/sys/setup-core.d.ts +3 -33
- package/dist/commands/sys/setup-core.js +27 -78
- package/dist/commands/sys/setup-settings-runner.d.ts +24 -0
- package/dist/commands/sys/setup-settings-runner.js +63 -0
- package/dist/commands/sys/setup-wizard.d.ts +26 -125
- package/dist/commands/sys/setup-wizard.js +209 -2473
- package/dist/commands/sys/setup.js +5 -7
- package/dist/commands/sys.js +3 -2
- package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +50 -0
- package/dist/core/__tests__/broker-launch-candidates.test.js +20 -9
- package/dist/core/__tests__/broker-preflight.test.js +1 -1
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +108 -27
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +14 -0
- package/dist/core/__tests__/canvas.test.js +2 -36
- package/dist/core/__tests__/close.test.js +14 -3
- package/dist/core/__tests__/context-intro.test.js +32 -143
- package/dist/core/__tests__/cron-broker-capacity.test.js +22 -3
- package/dist/core/__tests__/daemon-boot.test.js +2 -20
- package/dist/core/__tests__/daemon-wedge.test.js +1 -1
- package/dist/core/__tests__/dead-node-policy-table.test.js +1 -18
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +1 -1
- package/dist/core/__tests__/fixtures/fake-engine.js +2 -2
- package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
- package/dist/core/__tests__/focuses.test.js +4 -98
- package/dist/core/__tests__/helpers/broker-clients.d.ts +0 -3
- package/dist/core/__tests__/helpers/broker-clients.js +0 -14
- package/dist/core/__tests__/helpers/harness.d.ts +0 -8
- package/dist/core/__tests__/helpers/harness.js +7 -87
- package/dist/core/__tests__/helpers/viewer-panes.js +1 -1
- package/dist/core/__tests__/inline-memory-refs.test.js +5 -134
- package/dist/core/__tests__/kickoff.test.js +1 -1
- package/dist/core/__tests__/memory-resolver-precedence.test.js +29 -1
- package/dist/core/__tests__/migration.test.js +35 -48
- package/dist/core/__tests__/on-read-dedup-resume.test.js +22 -12
- package/dist/core/__tests__/passive-subscription.test.js +26 -49
- package/dist/core/__tests__/preview-registry-sync.test.js +1 -2
- package/dist/core/__tests__/prune.test.js +94 -1
- package/dist/core/__tests__/review-model-floor.test.js +0 -32
- package/dist/core/__tests__/seam/broker-attach-multiclient.test.js +255 -0
- package/dist/core/__tests__/{full → seam}/broker-attach-stream.test.js +39 -7
- package/dist/core/__tests__/{full → seam}/broker-crash-teardown.test.js +30 -29
- package/dist/core/__tests__/{full → seam}/broker-pane-resolution.test.js +54 -5
- package/dist/core/__tests__/{full → seam}/broker-provider-retry.test.js +134 -82
- package/dist/core/__tests__/{full → seam}/broker-whip-interrupt.test.js +15 -4
- package/dist/core/__tests__/{full/idle-release-settlement.test.js → seam/dormancy-release.test.js} +69 -17
- package/dist/core/__tests__/{full/yield-message-transaction.test.js → seam/yield-refresh-transaction.test.js} +31 -9
- package/dist/core/__tests__/secrets.test.js +1 -7
- package/dist/core/__tests__/{broker-fork-seam.test.js → serial/broker-fork-seam.test.js} +58 -7
- package/dist/core/__tests__/{broker-sdk-wiring.test.js → serial/broker-sdk-wiring.test.js} +18 -162
- package/dist/core/__tests__/{broker-snapshot-history.test.js → serial/broker-snapshot-history.test.js} +9 -9
- package/dist/core/__tests__/{command-plugins.test.js → serial/command-plugins.test.js} +105 -15
- package/dist/core/__tests__/{deferred-no-wake.test.js → serial/deferred-no-wake.test.js} +23 -20
- package/dist/core/__tests__/{flagship-lifecycle.test.js → serial/flagship-lifecycle.test.js} +7 -7
- package/dist/core/__tests__/{host-teardown-process-group.test.js → serial/host-teardown-process-group.test.js} +6 -6
- package/dist/core/__tests__/{human-deliver-e2e.test.js → serial/human-deliver-e2e.test.js} +12 -12
- package/dist/core/__tests__/{live-mutation.test.js → serial/live-mutation.test.js} +5 -5
- package/dist/core/__tests__/{refresh-stall-recycle.test.js → serial/refresh-stall-recycle.test.js} +5 -14
- package/dist/core/__tests__/{revive.test.js → serial/revive.test.js} +15 -100
- package/dist/core/__tests__/{spawn-root.test.js → serial/spawn-root.test.js} +32 -15
- package/dist/core/__tests__/serial/subscription-delivery.test.js +126 -0
- package/dist/core/__tests__/serial/tmux-surface.test.js +256 -0
- package/dist/core/__tests__/{worktree.test.js → serial/worktree.test.js} +15 -15
- package/dist/core/__tests__/session-model.test.js +0 -18
- package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +9 -0
- package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +50 -0
- package/dist/core/broker-client/__tests__/transport-relay.test.js +15 -16
- package/dist/core/broker-client/__tests__/transport-socket.test.js +28 -0
- package/dist/core/broker-client/client.d.ts +2 -2
- package/dist/core/broker-client/client.js +4 -7
- package/dist/core/broker-client/transport-relay.d.ts +13 -19
- package/dist/core/broker-client/transport-relay.js +19 -34
- package/dist/core/broker-client/transport-socket.d.ts +2 -2
- package/dist/core/broker-client/transport-socket.js +9 -2
- package/dist/core/broker-client/transport.d.ts +7 -3
- package/dist/core/broker-client/transport.js +4 -4
- package/dist/core/canvas/__tests__/attention.test.js +1 -18
- package/dist/core/canvas/__tests__/remote-canvas-source.test.js +7 -64
- package/dist/core/canvas/__tests__/remote-transport.test.js +2 -3
- package/dist/core/canvas/__tests__/render-remote.test.js +7 -52
- package/dist/core/canvas/browse/app.d.ts +2 -17
- package/dist/core/canvas/browse/app.js +16 -88
- package/dist/core/canvas/browse/model.d.ts +0 -4
- package/dist/core/canvas/browse/model.js +1 -12
- package/dist/core/canvas/browse/render.js +6 -3
- package/dist/core/canvas/crons.d.ts +8 -5
- package/dist/core/canvas/crons.js +7 -6
- package/dist/core/canvas/extensions.d.ts +7 -3
- package/dist/core/canvas/extensions.js +16 -4
- package/dist/core/canvas/nav-model.d.ts +0 -4
- package/dist/core/canvas/nav-model.js +2 -14
- package/dist/core/canvas/node-order.d.ts +3 -0
- package/dist/core/canvas/node-order.js +12 -0
- package/dist/core/canvas/remote-canvas-source.d.ts +3 -6
- package/dist/core/canvas/remote-canvas-source.js +8 -15
- package/dist/core/command.js +11 -4
- package/dist/core/config.js +3 -1
- package/dist/core/events/envelope.js +1 -1
- package/dist/core/events/read.js +7 -21
- package/dist/core/events/source.d.ts +0 -2
- package/dist/core/events/source.js +0 -16
- package/dist/core/events/types.d.ts +1 -1
- package/dist/core/fault-classifier.d.ts +1 -1
- package/dist/core/feed/passive.d.ts +0 -11
- package/dist/core/feed/passive.js +1 -62
- package/dist/core/human/__tests__/{inbox-core.test.js → serial/inbox-core.test.js} +6 -6
- package/dist/core/keybindings/__tests__/resolve.test.js +1 -96
- package/dist/core/keybindings/background-bash.js +3 -4
- package/dist/core/keybindings/catalog.js +1 -1
- package/dist/core/memory/inline-ref-grammar.js +3 -3
- package/dist/core/memory-resolver.d.ts +11 -25
- package/dist/core/memory-resolver.js +51 -33
- package/dist/core/model-routes.d.ts +15 -0
- package/dist/core/model-routes.js +12 -0
- package/dist/core/pool-credential-store.js +1 -1
- package/dist/core/preview-registry.js +8 -9
- package/dist/core/profiles/__tests__/root-profile.test.js +4 -14
- package/dist/core/review/realize.d.ts +0 -13
- package/dist/core/review/realize.js +0 -40
- package/dist/core/review/types.d.ts +1 -1
- package/dist/core/runtime/__tests__/engine-route.test.js +2 -2
- package/dist/core/runtime/bearings.js +1 -11
- package/dist/core/runtime/broker/client-registry.d.ts +33 -0
- package/dist/core/runtime/broker/client-registry.js +164 -0
- package/dist/core/runtime/broker/daemon-ops.d.ts +19 -0
- package/dist/core/runtime/broker/daemon-ops.js +49 -0
- package/dist/core/runtime/broker/event-projection.d.ts +42 -0
- package/dist/core/runtime/broker/event-projection.js +178 -0
- package/dist/core/runtime/broker/fault-retry.d.ts +39 -0
- package/dist/core/runtime/broker/fault-retry.js +277 -0
- package/dist/core/runtime/broker/frame-dispatch.d.ts +238 -0
- package/dist/core/runtime/broker/frame-dispatch.js +1537 -0
- package/dist/core/runtime/broker/inbox.d.ts +22 -0
- package/dist/core/runtime/broker/inbox.js +167 -0
- package/dist/core/runtime/broker/passive.d.ts +16 -0
- package/dist/core/runtime/broker/passive.js +44 -0
- package/dist/core/runtime/broker/read-ops.d.ts +23 -0
- package/dist/core/runtime/broker/read-ops.js +150 -0
- package/dist/core/runtime/broker/rebind.d.ts +74 -0
- package/dist/core/runtime/broker/rebind.js +211 -0
- package/dist/core/runtime/broker/tool-groups.d.ts +39 -0
- package/dist/core/runtime/broker/tool-groups.js +244 -0
- package/dist/core/runtime/broker-extension-render.d.ts +11 -0
- package/dist/core/runtime/broker-extension-render.js +181 -0
- package/dist/core/runtime/broker-persona-guidance.d.ts +13 -0
- package/dist/core/runtime/broker-persona-guidance.js +58 -0
- package/dist/core/runtime/broker-protocol.d.ts +1 -1
- package/dist/core/runtime/broker-protocol.js +1 -1
- package/dist/core/runtime/broker.d.ts +5 -196
- package/dist/core/runtime/broker.js +276 -3059
- package/dist/core/runtime/canvas-extensions.js +11 -2
- package/dist/core/runtime/fault.js +1 -1
- package/dist/core/runtime/model-selection.d.ts +9 -0
- package/dist/core/runtime/model-selection.js +12 -0
- package/dist/core/runtime/model-swap.d.ts +3 -3
- package/dist/core/runtime/model-swap.js +33 -25
- package/dist/core/runtime/placement-tmux.d.ts +3 -14
- package/dist/core/runtime/placement-tmux.js +11 -80
- package/dist/core/runtime/placement.d.ts +4 -2
- package/dist/core/runtime/placement.js +7 -12
- package/dist/core/runtime/promote.d.ts +14 -1
- package/dist/core/runtime/promote.js +1 -1
- package/dist/core/runtime/reset.d.ts +1 -1
- package/dist/core/runtime/roadmap.js +1 -1
- package/dist/core/runtime/spawn.d.ts +8 -0
- package/dist/core/runtime/spawn.js +36 -6
- package/dist/core/runtime/stop-guard.d.ts +4 -3
- package/dist/core/runtime/stop-guard.js +36 -18
- package/dist/core/runtime/stop-signals.d.ts +4 -1
- package/dist/core/runtime/stream-watchdog.js +4 -5
- package/dist/core/runtime/structured-output.d.ts +15 -2
- package/dist/core/runtime/structured-output.js +35 -12
- package/dist/core/runtime/tmux-bindings.d.ts +25 -0
- package/dist/core/runtime/tmux-bindings.js +682 -0
- package/dist/core/runtime/tmux-chrome.d.ts +2 -1
- package/dist/core/runtime/tmux-chrome.js +3 -2
- package/dist/core/runtime/{tmux.d.ts → tmux-driver.d.ts} +18 -26
- package/dist/core/runtime/tmux-driver.js +598 -0
- package/dist/core/runtime/view-socket.d.ts +14 -0
- package/dist/core/runtime/view-socket.js +83 -0
- package/dist/core/session-model/session-state.d.ts +1 -2
- package/dist/core/session-model/session-state.js +5 -8
- package/dist/core/spawn.d.ts +1 -1
- package/dist/core/spawn.js +2 -2
- package/dist/core/substrate/gate.d.ts +1 -1
- package/dist/core/substrate/on-read-pure.d.ts +7 -0
- package/dist/core/substrate/on-read-pure.js +272 -0
- package/dist/core/termrender/termrender.js +32 -2
- package/dist/core/user-settings.d.ts +23 -1
- package/dist/core/user-settings.js +18 -1
- package/dist/daemon/api/__tests__/node-model-validation.test.js +134 -0
- package/dist/daemon/api/__tests__/review-terminal-conflict.test.js +98 -0
- package/dist/daemon/api/__tests__/{full → seam}/api-server.test.js +6 -48
- package/dist/daemon/api/__tests__/{full → seam}/b10-attach-modelauth.test.js +17 -44
- package/dist/daemon/api/__tests__/{full/push-parity.test.js → seam/leaf-api-parity.test.js} +61 -42
- package/dist/daemon/api/bridge.js +40 -81
- package/dist/daemon/api/handlers/broker-ops.d.ts +2 -0
- package/dist/daemon/api/handlers/broker-ops.js +383 -0
- package/dist/daemon/api/handlers/crons.js +9 -4
- package/dist/daemon/api/handlers/files.js +6 -7
- package/dist/daemon/api/handlers/inbox.js +2 -340
- package/dist/daemon/api/handlers/nodes.d.ts +10 -1
- package/dist/daemon/api/handlers/nodes.js +74 -39
- package/dist/daemon/api/handlers/review-comments.js +5 -1
- package/dist/daemon/api/handlers/reviews.js +0 -19
- package/dist/daemon/api/map.js +25 -4
- package/dist/daemon/api/router.js +2 -3
- package/dist/daemon/api/server.js +5 -7
- package/dist/daemon/cron-run.d.ts +4 -0
- package/dist/daemon/cron-run.js +30 -13
- package/dist/daemon/crtrd.d.ts +3 -58
- package/dist/daemon/crtrd.js +28 -1244
- package/dist/daemon/manage.js +0 -1
- package/dist/daemon/reconcilers/broker-supervision.d.ts +50 -0
- package/dist/daemon/reconcilers/broker-supervision.js +549 -0
- package/dist/daemon/reconcilers/cron-lane.d.ts +10 -0
- package/dist/daemon/reconcilers/cron-lane.js +32 -0
- package/dist/daemon/reconcilers/dormant-inbox.d.ts +18 -0
- package/dist/daemon/reconcilers/dormant-inbox.js +143 -0
- package/dist/daemon/reconcilers/storage-maintenance.d.ts +22 -0
- package/dist/daemon/reconcilers/storage-maintenance.js +213 -0
- package/dist/daemon/review/finish.js +2 -5
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +34 -53
- package/dist/pi-extensions/__tests__/canvas-recap.test.js +16 -113
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +72 -99
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +29 -12
- package/dist/pi-extensions/broker-local.d.ts +20 -0
- package/dist/pi-extensions/broker-local.js +89 -0
- package/dist/pi-extensions/canvas-context-intro.d.ts +3 -3
- package/dist/pi-extensions/canvas-context-intro.js +22 -11
- package/dist/pi-extensions/canvas-doc-substrate.js +17 -34
- package/dist/pi-extensions/canvas-goal-capture.js +7 -12
- package/dist/pi-extensions/canvas-inbox-watcher.js +124 -139
- package/dist/pi-extensions/canvas-passive-context.d.ts +3 -3
- package/dist/pi-extensions/canvas-passive-context.js +22 -18
- package/dist/pi-extensions/canvas-recap.d.ts +2 -2
- package/dist/pi-extensions/canvas-recap.js +48 -21
- package/dist/pi-extensions/canvas-review-boundary.js +10 -4
- package/dist/pi-extensions/canvas-stophook.d.ts +0 -5
- package/dist/pi-extensions/canvas-stophook.js +149 -431
- package/dist/pi-extensions/canvas-structured-output.js +52 -30
- package/dist/pi-extensions/envelope-merge.d.ts +13 -0
- package/dist/pi-extensions/envelope-merge.js +29 -0
- package/dist/shared/working-activity.d.ts +3 -2
- package/dist/shared/working-activity.js +3 -2
- package/dist/types.d.ts +6 -5
- package/dist/types.js +2 -2
- package/docs/compat/hearth-crtr-v1.md +4 -12
- package/docs/compat/hearth-crtr-v2.md +3 -11
- package/docs/compat/hearth-crtr-v3.md +4 -12
- package/docs/compat/hearth-crtr-v4.md +3 -11
- package/docs/compat/hearth-crtr-v5.md +3 -11
- package/docs/public-api.md +3 -7
- package/package.json +7 -49
- package/runtime.lock.json +2766 -14533
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/claude-plugin-commands.test.ts +0 -49
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/memory-slash-commands.test.ts +0 -194
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/statusline.test.ts +0 -68
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/test.ts +0 -119
- package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +0 -439
- package/dist/clients/attach/__tests__/attach-keybindings.test.js +0 -170
- package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +0 -57
- package/dist/clients/attach/__tests__/editor-frame-title.test.js +0 -19
- package/dist/clients/attach/__tests__/input-controller-extension-command.test.js +0 -32
- package/dist/clients/attach/__tests__/markdown-headings.test.js +0 -35
- package/dist/clients/attach/__tests__/session-identity-refresh.test.js +0 -32
- package/dist/clients/conversation/conversation.d.ts +0 -65
- package/dist/clients/conversation/conversation.js +0 -318
- package/dist/clients/conversation/frames.d.ts +0 -30
- package/dist/clients/conversation/frames.js +0 -52
- package/dist/clients/conversation/region.d.ts +0 -51
- package/dist/clients/conversation/region.js +0 -265
- package/dist/clients/inbox/__tests__/inbox-controller.test.js +0 -247
- package/dist/clients/inbox/__tests__/mount-panel.test.js +0 -776
- package/dist/clients/inbox/review/__tests__/remap.test.js +0 -196
- package/dist/clients/inbox/review/columns.d.ts +0 -22
- package/dist/clients/inbox/review/columns.js +0 -40
- package/dist/clients/inbox/review/composition.d.ts +0 -60
- package/dist/clients/inbox/review/composition.js +0 -278
- package/dist/clients/inbox/review/document-region.d.ts +0 -120
- package/dist/clients/surfaces/region.d.ts +0 -16
- package/dist/clients/web/__tests__/push-engine.test.js +0 -158
- package/dist/clients/web/__tests__/push-registry.test.d.ts +0 -1
- package/dist/clients/web/__tests__/push-registry.test.js +0 -177
- package/dist/clients/web/dev-server.d.ts +0 -7
- package/dist/clients/web/dev-server.js +0 -46
- package/dist/clients/web/events.d.ts +0 -19
- package/dist/clients/web/events.js +0 -163
- package/dist/clients/web/push-engine.d.ts +0 -44
- package/dist/clients/web/push-engine.js +0 -125
- package/dist/clients/web/push-registry.d.ts +0 -85
- package/dist/clients/web/push-registry.js +0 -373
- package/dist/clients/web/server.d.ts +0 -19
- package/dist/clients/web/server.js +0 -661
- package/dist/clients/web/web-client/shared/protocol.d.ts +0 -214
- package/dist/clients/web/web-client/shared/protocol.js +0 -8
- package/dist/clients/web/web-cmd.d.ts +0 -2
- package/dist/clients/web/web-cmd.js +0 -141
- package/dist/commands/__tests__/api-canvas-source.test.d.ts +0 -1
- package/dist/commands/__tests__/api-canvas-source.test.js +0 -37
- package/dist/commands/__tests__/canvas-config.test.d.ts +0 -1
- package/dist/commands/__tests__/canvas-config.test.js +0 -107
- package/dist/commands/__tests__/node-no-kickoff-flag.test.d.ts +0 -1
- package/dist/commands/__tests__/node-no-kickoff-flag.test.js +0 -73
- package/dist/commands/__tests__/node-worktree-flag.test.d.ts +0 -1
- package/dist/commands/__tests__/node-worktree-flag.test.js +0 -49
- package/dist/commands/__tests__/search-contents.test.d.ts +0 -1
- package/dist/commands/__tests__/search-contents.test.js +0 -52
- package/dist/commands/memory/__tests__/lint.test.d.ts +0 -1
- package/dist/commands/memory/__tests__/lint.test.js +0 -15
- package/dist/commands/sys/__tests__/config-broker-thresholds.test.d.ts +0 -1
- package/dist/commands/sys/__tests__/config-broker-thresholds.test.js +0 -95
- package/dist/commands/sys/__tests__/config-keybindings.test.d.ts +0 -1
- package/dist/commands/sys/__tests__/config-keybindings.test.js +0 -55
- package/dist/commands/sys/__tests__/helpers/setup-runtime.d.ts +0 -5
- package/dist/commands/sys/__tests__/helpers/setup-runtime.js +0 -19
- package/dist/commands/sys/__tests__/setup-front-door.test.d.ts +0 -1
- package/dist/commands/sys/__tests__/setup-front-door.test.js +0 -102
- package/dist/commands/sys/__tests__/setup-models.test.d.ts +0 -1
- package/dist/commands/sys/__tests__/setup-models.test.js +0 -205
- package/dist/commands/sys/__tests__/setup-providers-viewport.test.d.ts +0 -1
- package/dist/commands/sys/__tests__/setup-providers-viewport.test.js +0 -155
- package/dist/commands/sys/__tests__/sync-deps.test.d.ts +0 -1
- package/dist/commands/sys/__tests__/sync-deps.test.js +0 -179
- package/dist/commands/sys/__tests__/sync-import.test.d.ts +0 -1
- package/dist/commands/sys/__tests__/sync-import.test.js +0 -315
- package/dist/core/__tests__/base-worker-prompt.test.d.ts +0 -1
- package/dist/core/__tests__/base-worker-prompt.test.js +0 -57
- package/dist/core/__tests__/command-plugins-surfaces.test.d.ts +0 -1
- package/dist/core/__tests__/command-plugins-surfaces.test.js +0 -359
- package/dist/core/__tests__/connection-reconnect-resume.test.d.ts +0 -1
- package/dist/core/__tests__/connection-reconnect-resume.test.js +0 -36
- package/dist/core/__tests__/finalization-migration.test.d.ts +0 -1
- package/dist/core/__tests__/finalization-migration.test.js +0 -38
- package/dist/core/__tests__/fixtures/extra-extension.d.ts +0 -1
- package/dist/core/__tests__/fixtures/extra-extension.js +0 -3
- package/dist/core/__tests__/fixtures/spawn-proof-target.test.d.ts +0 -1
- package/dist/core/__tests__/fixtures/spawn-proof-target.test.js +0 -15
- package/dist/core/__tests__/fork.test.d.ts +0 -1
- package/dist/core/__tests__/fork.test.js +0 -108
- package/dist/core/__tests__/full/broker-attach-limits.test.d.ts +0 -1
- package/dist/core/__tests__/full/broker-attach-limits.test.js +0 -147
- package/dist/core/__tests__/full/broker-deliver-ack.test.d.ts +0 -1
- package/dist/core/__tests__/full/broker-deliver-ack.test.js +0 -73
- package/dist/core/__tests__/full/broker-dialogs.test.d.ts +0 -1
- package/dist/core/__tests__/full/broker-dialogs.test.js +0 -180
- package/dist/core/__tests__/full/broker-display-snapshot.test.d.ts +0 -1
- package/dist/core/__tests__/full/broker-display-snapshot.test.js +0 -75
- package/dist/core/__tests__/full/broker-dormant-wake.test.d.ts +0 -1
- package/dist/core/__tests__/full/broker-dormant-wake.test.js +0 -130
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +0 -1
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +0 -57
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +0 -1
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +0 -80
- package/dist/core/__tests__/full/cascade-close.test.d.ts +0 -1
- package/dist/core/__tests__/full/cascade-close.test.js +0 -240
- package/dist/core/__tests__/full/detach-focus.test.d.ts +0 -1
- package/dist/core/__tests__/full/detach-focus.test.js +0 -126
- package/dist/core/__tests__/full/idle-release-settlement.test.d.ts +0 -1
- package/dist/core/__tests__/full/issue-146-yield-spawn-focus.test.d.ts +0 -1
- package/dist/core/__tests__/full/issue-146-yield-spawn-focus.test.js +0 -107
- package/dist/core/__tests__/full/park-clock-release.test.d.ts +0 -1
- package/dist/core/__tests__/full/park-clock-release.test.js +0 -56
- package/dist/core/__tests__/full/yield-message-transaction.test.d.ts +0 -1
- package/dist/core/__tests__/memory-index-render.test.d.ts +0 -1
- package/dist/core/__tests__/memory-index-render.test.js +0 -60
- package/dist/core/__tests__/model-routes.test.d.ts +0 -1
- package/dist/core/__tests__/model-routes.test.js +0 -41
- package/dist/core/__tests__/on-read-identity.test.d.ts +0 -1
- package/dist/core/__tests__/on-read-identity.test.js +0 -61
- package/dist/core/__tests__/phase4-review-migration.test.d.ts +0 -1
- package/dist/core/__tests__/phase4-review-migration.test.js +0 -100
- package/dist/core/__tests__/prune-to-limit.test.d.ts +0 -1
- package/dist/core/__tests__/prune-to-limit.test.js +0 -126
- package/dist/core/__tests__/reset.test.d.ts +0 -1
- package/dist/core/__tests__/reset.test.js +0 -69
- package/dist/core/__tests__/spawn-no-kickoff.test.d.ts +0 -1
- package/dist/core/__tests__/spawn-no-kickoff.test.js +0 -115
- package/dist/core/__tests__/steer-note.test.d.ts +0 -1
- package/dist/core/__tests__/steer-note.test.js +0 -41
- package/dist/core/__tests__/subcommand-tier.test.d.ts +0 -1
- package/dist/core/__tests__/subcommand-tier.test.js +0 -99
- package/dist/core/__tests__/subscription-delivery.test.js +0 -211
- package/dist/core/__tests__/test-runner-cheap-sharding.test.d.ts +0 -1
- package/dist/core/__tests__/test-runner-cheap-sharding.test.js +0 -97
- package/dist/core/__tests__/tmux-surface.test.js +0 -484
- package/dist/core/__tests__/wake-bearings.test.d.ts +0 -1
- package/dist/core/__tests__/wake-bearings.test.js +0 -102
- package/dist/core/canvas/__tests__/remote-event-stream.test.d.ts +0 -1
- package/dist/core/canvas/__tests__/remote-event-stream.test.js +0 -146
- package/dist/core/canvas/__tests__/source-resolve.test.d.ts +0 -1
- package/dist/core/canvas/__tests__/source-resolve.test.js +0 -77
- package/dist/core/canvas/browse/__tests__/model.test.d.ts +0 -1
- package/dist/core/canvas/browse/__tests__/model.test.js +0 -290
- package/dist/core/canvas/browse/__tests__/rebuild-coalescer.test.d.ts +0 -1
- package/dist/core/canvas/browse/__tests__/rebuild-coalescer.test.js +0 -134
- package/dist/core/canvas/browse/__tests__/render.test.d.ts +0 -1
- package/dist/core/canvas/browse/__tests__/render.test.js +0 -111
- package/dist/core/canvas/browse/rebuild-coalescer.d.ts +0 -7
- package/dist/core/canvas/browse/rebuild-coalescer.js +0 -61
- package/dist/core/canvas/remote-event-stream.d.ts +0 -24
- package/dist/core/canvas/remote-event-stream.js +0 -94
- package/dist/core/feed/__tests__/coalesce-human.test.d.ts +0 -1
- package/dist/core/feed/__tests__/coalesce-human.test.js +0 -36
- package/dist/core/feed/__tests__/empty-node-guard.test.d.ts +0 -1
- package/dist/core/feed/__tests__/empty-node-guard.test.js +0 -45
- package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.d.ts +0 -1
- package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.js +0 -84
- package/dist/core/human/__tests__/visible-paths.test.d.ts +0 -1
- package/dist/core/human/__tests__/visible-paths.test.js +0 -28
- package/dist/core/keybindings/__tests__/inbox-affordance.test.d.ts +0 -1
- package/dist/core/keybindings/__tests__/inbox-affordance.test.js +0 -38
- package/dist/core/review/__tests__/project.test.d.ts +0 -1
- package/dist/core/review/__tests__/project.test.js +0 -71
- package/dist/core/runtime/__tests__/command-expansion.test.d.ts +0 -1
- package/dist/core/runtime/__tests__/command-expansion.test.js +0 -25
- package/dist/core/runtime/__tests__/session-visibility.test.d.ts +0 -1
- package/dist/core/runtime/__tests__/session-visibility.test.js +0 -119
- package/dist/core/runtime/__tests__/spawn-at-given-id.test.d.ts +0 -1
- package/dist/core/runtime/__tests__/spawn-at-given-id.test.js +0 -34
- package/dist/core/runtime/tmux.js +0 -1314
- package/dist/daemon/api/__tests__/full/push-parity.test.d.ts +0 -1
- package/dist/daemon/api/__tests__/full/worktree-parity.test.d.ts +0 -1
- package/dist/daemon/api/__tests__/full/worktree-parity.test.js +0 -160
- package/dist/daemon/api/__tests__/inbox.test.d.ts +0 -1
- package/dist/daemon/api/__tests__/inbox.test.js +0 -489
- package/dist/web-client/apple-touch-icon.png +0 -0
- package/dist/web-client/assets/fragment-mono-latin-400-normal-BYwT3kSJ.woff +0 -0
- package/dist/web-client/assets/fragment-mono-latin-400-normal-yxdJ5AmL.woff2 +0 -0
- package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-BbKYyvR9.woff2 +0 -0
- package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-CT4YFKeK.woff +0 -0
- package/dist/web-client/assets/index-CCPaLeW7.css +0 -2
- package/dist/web-client/assets/index-kyOWsnZK.js +0 -86
- package/dist/web-client/assets/martian-mono-cyrillic-wght-normal-B84CD5C_.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-latin-ext-wght-normal-DlL6xMw5.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-latin-wght-normal-5W32yIyr.woff2 +0 -0
- package/dist/web-client/assets/workbox-window.prod.es5-Bd17z0YL.js +0 -2
- package/dist/web-client/icon-192.png +0 -0
- package/dist/web-client/icon-512.png +0 -0
- package/dist/web-client/icon-maskable-512.png +0 -0
- package/dist/web-client/index.html +0 -17
- package/dist/web-client/manifest.webmanifest +0 -1
- package/dist/web-client/sw.js +0 -1
- /package/dist/api/__tests__/{client.test.d.ts → serial/client.test.d.ts} +0 -0
- /package/dist/clients/inbox/__tests__/{inbox-controller.test.d.ts → serial/inbox-controller.test.d.ts} +0 -0
- /package/dist/clients/inbox/__tests__/{mount-panel.test.d.ts → serial/mount-panel.test.d.ts} +0 -0
- /package/dist/clients/{attach/__tests__/attach-chrome-remote.test.d.ts → inbox/review/__tests__/teardown-notice.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/attach-keybindings.test.d.ts → core/__tests__/broker-extension-canvas-db-boundary.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/crtr-output-coverage.test.d.ts → core/__tests__/seam/broker-attach-multiclient.test.d.ts} +0 -0
- /package/dist/core/__tests__/{full → seam}/broker-attach-stream.test.d.ts +0 -0
- /package/dist/core/__tests__/{full → seam}/broker-crash-teardown.test.d.ts +0 -0
- /package/dist/core/__tests__/{full → seam}/broker-pane-resolution.test.d.ts +0 -0
- /package/dist/core/__tests__/{full → seam}/broker-provider-retry.test.d.ts +0 -0
- /package/dist/core/__tests__/{full → seam}/broker-whip-interrupt.test.d.ts +0 -0
- /package/dist/{clients/attach/__tests__/editor-frame-title.test.d.ts → core/__tests__/seam/dormancy-release.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/input-controller-extension-command.test.d.ts → core/__tests__/seam/yield-refresh-transaction.test.d.ts} +0 -0
- /package/dist/core/__tests__/{broker-fork-seam.test.d.ts → serial/broker-fork-seam.test.d.ts} +0 -0
- /package/dist/core/__tests__/{broker-sdk-wiring.test.d.ts → serial/broker-sdk-wiring.test.d.ts} +0 -0
- /package/dist/core/__tests__/{broker-snapshot-history.test.d.ts → serial/broker-snapshot-history.test.d.ts} +0 -0
- /package/dist/core/__tests__/{command-plugins.test.d.ts → serial/command-plugins.test.d.ts} +0 -0
- /package/dist/core/__tests__/{deferred-no-wake.test.d.ts → serial/deferred-no-wake.test.d.ts} +0 -0
- /package/dist/core/__tests__/{flagship-lifecycle.test.d.ts → serial/flagship-lifecycle.test.d.ts} +0 -0
- /package/dist/core/__tests__/{host-teardown-process-group.test.d.ts → serial/host-teardown-process-group.test.d.ts} +0 -0
- /package/dist/core/__tests__/{human-deliver-e2e.test.d.ts → serial/human-deliver-e2e.test.d.ts} +0 -0
- /package/dist/core/__tests__/{live-mutation.test.d.ts → serial/live-mutation.test.d.ts} +0 -0
- /package/dist/core/__tests__/{refresh-stall-recycle.test.d.ts → serial/refresh-stall-recycle.test.d.ts} +0 -0
- /package/dist/core/__tests__/{revive.test.d.ts → serial/revive.test.d.ts} +0 -0
- /package/dist/core/__tests__/{spawn-root.test.d.ts → serial/spawn-root.test.d.ts} +0 -0
- /package/dist/core/__tests__/{subscription-delivery.test.d.ts → serial/subscription-delivery.test.d.ts} +0 -0
- /package/dist/core/__tests__/{tmux-surface.test.d.ts → serial/tmux-surface.test.d.ts} +0 -0
- /package/dist/core/__tests__/{worktree.test.d.ts → serial/worktree.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/markdown-headings.test.d.ts → core/__tests__/watchdog-abort-arms-retry.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/session-identity-refresh.test.d.ts → core/broker-client/__tests__/transport-socket.test.d.ts} +0 -0
- /package/dist/core/human/__tests__/{inbox-core.test.d.ts → serial/inbox-core.test.d.ts} +0 -0
- /package/dist/{clients/inbox/review/__tests__/remap.test.d.ts → daemon/api/__tests__/node-model-validation.test.d.ts} +0 -0
- /package/dist/{clients/surfaces/region.js → daemon/api/__tests__/review-terminal-conflict.test.d.ts} +0 -0
- /package/dist/daemon/api/__tests__/{full → seam}/api-server.test.d.ts +0 -0
- /package/dist/daemon/api/__tests__/{full → seam}/b10-attach-modelauth.test.d.ts +0 -0
- /package/dist/{clients/web/__tests__/push-engine.test.d.ts → daemon/api/__tests__/seam/leaf-api-parity.test.d.ts} +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
// Run with: node --import tsx/esm --test src/core/__tests__/
|
|
1
|
+
// Run with: node --import tsx/esm --test src/core/__tests__/seam/broker-attach-stream.test.ts
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
// (~5s pi-SDK load), so it lives in
|
|
3
|
+
// COMPILED SEAM (real-boot-bound): tmux-free, but it boots a REAL broker process
|
|
4
|
+
// (~5s pi-SDK load), so it lives in seam/ (CI), not the fast local loop.
|
|
5
5
|
//
|
|
6
6
|
// T8 — the `crtr surface attach` acceptance gate, gates G1/G1b/G2 (controller drive,
|
|
7
|
-
// relay/coalescing, detach survival)
|
|
8
|
-
// broker-lifecycle.test.ts; the gates are the original acceptance
|
|
7
|
+
// relay/coalescing, detach survival) plus the deliver ack's truthfulness (G3).
|
|
8
|
+
// Split out of broker-lifecycle.test.ts; the gates are the original acceptance
|
|
9
|
+
// gate, unchanged.
|
|
9
10
|
//
|
|
10
11
|
// 3-PART HEADER (headless-retarget):
|
|
11
12
|
// (1) BUG IT LOCKS — the broker's client fan-out: a controller `prompt` must run
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
// the lock holds headlessly (createHeadlessHarness, no tmux). It needs a REAL
|
|
23
24
|
// broker PROCESS (not just model state) because it proves the cross-process
|
|
24
25
|
// frame codec + fan-out + coalescer, which is why this file keeps ONE real
|
|
25
|
-
// boot
|
|
26
|
+
// boot shared across all three gates.
|
|
26
27
|
// (3) HOW THE HEADLESS DRIVE STILL FAILS ON REGRESSION — each gate attaches the
|
|
27
28
|
// PRODUCTION ViewSocketClient over the real socket and asserts on the frames
|
|
28
29
|
// the real broker relays (order, types, snapshot, pid stability). A broken
|
|
@@ -39,7 +40,8 @@ import { test, before, after, afterEach } from 'node:test';
|
|
|
39
40
|
import assert from 'node:assert/strict';
|
|
40
41
|
import { createHeadlessHarness } from '../helpers/harness.js';
|
|
41
42
|
import { isPidAlive } from '../../canvas/pid.js';
|
|
42
|
-
import { createAttachKit, delay, tok, frameHas } from '../helpers/broker-clients.js';
|
|
43
|
+
import { createAttachKit, brokerLogText, delay, tok, frameHas } from '../helpers/broker-clients.js';
|
|
44
|
+
import { REJECT_PROMPT_CALL } from '../fixtures/fake-engine.js';
|
|
43
45
|
let h;
|
|
44
46
|
let id; // ONE shared broker, reused across G1/G1b/G2 (1 real boot, not 3)
|
|
45
47
|
const kit = createAttachKit(() => h);
|
|
@@ -131,3 +133,33 @@ test('G2 — detach (bye+close) leaves the broker alive + the engine still emitt
|
|
|
131
133
|
assert.ok(o.welcome.snapshot.messages.length > 0, 'G2: the engine produced a message after the detach');
|
|
132
134
|
assert.equal(isPidAlive(pid), true, 'G2: broker still alive after the post-detach turn');
|
|
133
135
|
});
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// G3 — the interactive `deliver` ack reports the ENGINE CALL's real outcome, not
|
|
138
|
+
// the fact that a call was routed. A `session.prompt()` that REJECTS outright —
|
|
139
|
+
// the shape a broker whose model resolved but whose provider has no installed
|
|
140
|
+
// credential produces ("No API key found for anthropic") — used to still ack
|
|
141
|
+
// ok:true, so crtrd returned `delivered:true, delivered_via:engine` for a message
|
|
142
|
+
// the engine had refused and the one-shot client closed before the error frame
|
|
143
|
+
// arrived: the message vanished with no trace. The ack now races acceptance (the
|
|
144
|
+
// engine's own agent_start) against rejection and records
|
|
145
|
+
// `broker.prompt.deliver_failed` in the node's job/log.jsonl. Shares this file's
|
|
146
|
+
// one broker: the contract is two frames on an attached controller.
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
test('G3 — a deliver whose prompt rejects acks ok:false and records deliver_failed; a succeeding deliver acks ok:true via prompt', async () => {
|
|
149
|
+
// Both delivers must route as `prompt` (idle), never `steer` — the steer route
|
|
150
|
+
// legitimately acks at routing time — so attach only once the engine is idle.
|
|
151
|
+
const c = await attachUntil(id, 'controller', 'g3-ctrl', (a) => a.welcome.role === 'controller' && a.welcome.snapshot.state.isStreaming === false, 'G3 controller admitted while the engine is idle');
|
|
152
|
+
// FAILURE: the engine refuses the call itself. Before the fix this acked
|
|
153
|
+
// ok:true and the rejection left no trace in the node's log.
|
|
154
|
+
c.send({ type: 'deliver', id: 'd-fail', text: `do a thing ${REJECT_PROMPT_CALL}` });
|
|
155
|
+
const failAck = await c.waitFrame((f) => f.type === 'ack' && f.for === 'deliver' && f.id === 'd-fail', 'G3 rejected deliver ack');
|
|
156
|
+
assert.equal(failAck.type === 'ack' && failAck.ok, false, 'G3: a rejected prompt must not ack delivered:true');
|
|
157
|
+
assert.match((failAck.type === 'ack' ? failAck.detail : undefined) ?? '', /No API key found/, 'G3: the ack detail carries the engine rejection');
|
|
158
|
+
await h.waitFor(() => (brokerLogText(h, id).includes('broker.prompt.deliver_failed') ? true : null), { label: 'G3 broker.prompt.deliver_failed recorded in job/log.jsonl' });
|
|
159
|
+
// POSITIVE CONTROL: an ordinary deliver still acks ok:true from acceptance
|
|
160
|
+
// (the engine's agent_start), detail naming the prompt route.
|
|
161
|
+
c.send({ type: 'deliver', id: 'd-ok', text: 'an ordinary interactive message' });
|
|
162
|
+
const okAck = await c.waitFrame((f) => f.type === 'ack' && f.for === 'deliver' && f.id === 'd-ok', 'G3 accepted deliver ack');
|
|
163
|
+
assert.equal(okAck.type === 'ack' && okAck.ok, true, 'G3: an accepted prompt acks delivered:true');
|
|
164
|
+
assert.equal(okAck.type === 'ack' ? okAck.detail : undefined, 'prompt');
|
|
165
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Run with: node --import tsx/esm --test src/core/__tests__/
|
|
1
|
+
// Run with: node --import tsx/esm --test src/core/__tests__/seam/broker-crash-teardown.test.ts
|
|
2
2
|
//
|
|
3
3
|
// Daemon-epoch broker ownership, real-process crash and teardown. The broker's
|
|
4
4
|
// exit event is the sole death authority: a SIGTERM of a streaming broker
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
// broker and reboots the node through THIS process's reviveNode, so every
|
|
15
15
|
// broker below is harness-fleet-owned and every SIGKILL's REAL ChildProcess
|
|
16
16
|
// exit drives policy automatically — no synthetic delivery, no tick, no grace.
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
17
|
+
// Each crash has its own real broker setup, so either top-level test remains a
|
|
18
|
+
// complete journey under name filtering and pays only its own first respawn
|
|
19
|
+
// backoff.
|
|
20
20
|
import { test, before, after } from 'node:test';
|
|
21
21
|
import assert from 'node:assert/strict';
|
|
22
22
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
@@ -28,10 +28,6 @@ import { RUNTIME_RESTART_CONTINUATION } from '../../runtime/kickoff.js';
|
|
|
28
28
|
import { jobDir } from '../../canvas/paths.js';
|
|
29
29
|
let h;
|
|
30
30
|
let root;
|
|
31
|
-
// One broker lifecycle threaded through the two serial tests below.
|
|
32
|
-
let id;
|
|
33
|
-
let boots;
|
|
34
|
-
let idleRevivedPid;
|
|
35
31
|
let priorHoldTurn;
|
|
36
32
|
before(async () => {
|
|
37
33
|
priorHoldTurn = process.env['PI_FAKE_ENGINE_HOLD_TURN'];
|
|
@@ -48,12 +44,12 @@ after(async () => {
|
|
|
48
44
|
process.env['PI_FAKE_ENGINE_HOLD_TURN'] = priorHoldTurn;
|
|
49
45
|
});
|
|
50
46
|
// ===========================================================================
|
|
51
|
-
// Items 4 + 5 + 6 —
|
|
52
|
-
// graceful busy teardown → strict resume + continuation
|
|
53
|
-
// crash → recovery cycle → close.
|
|
47
|
+
// Items 4 + 5 + 6 — two independent real-process journeys:
|
|
48
|
+
// graceful busy teardown → strict resume + continuation;
|
|
49
|
+
// dirty interrupted-turn crash → recovery cycle → close.
|
|
54
50
|
// ===========================================================================
|
|
55
51
|
test('SIGTERM of a busy broker aborts cleanly and resumes with a continuation', { timeout: 30_000 }, async () => {
|
|
56
|
-
id = await h.spawnHeadlessChild(root, 'headless worker — crash
|
|
52
|
+
const id = await h.spawnHeadlessChild(root, 'headless worker — graceful crash recovery');
|
|
57
53
|
const boot1 = await h.awaitBoot(id);
|
|
58
54
|
const oldPid = h.node(id).pi_pid;
|
|
59
55
|
assert.equal(oldPid, boot1.pid, 'canvas pid identifies the broker that booted the session');
|
|
@@ -61,7 +57,7 @@ test('SIGTERM of a busy broker aborts cleanly and resumes with a continuation',
|
|
|
61
57
|
await h.waitFor(() => existsSync(h.brokerSock(id)), { label: 'broker socket bound' });
|
|
62
58
|
await h.waitFor(() => existsSync(join(jobDir(id), 'busy')), { label: 'fixture turn is active before SIGTERM' });
|
|
63
59
|
assert.equal(h.node(id).intent ?? null, null, 'fresh broker has a null intent (not refresh/idle-release)');
|
|
64
|
-
boots = h.bootCount(id);
|
|
60
|
+
const boots = h.bootCount(id);
|
|
65
61
|
// SIGTERM exercises broker.ts's bounded session.abort() path. Its REAL child
|
|
66
62
|
// exit sees the broker-written clean-abort certificate and resumes only after
|
|
67
63
|
// the first-failure backoff; no synthetic fleet delivery is involved.
|
|
@@ -70,37 +66,42 @@ test('SIGTERM of a busy broker aborts cleanly and resumes with a continuation',
|
|
|
70
66
|
assert.equal(existsSync(join(jobDir(id), 'clean-abort')), true, 'settled abort leaves durable clean-abort evidence before exit policy respawns');
|
|
71
67
|
// This is the file's SECOND real broker boot (exit-event recovery), behind a
|
|
72
68
|
// 5s short-lived-exit backoff, on an oversubscribed CI runner sharing the
|
|
73
|
-
// host with the other
|
|
69
|
+
// host with the other compiled-seam files' own broker spawns. Give it a wide,
|
|
74
70
|
// still-bounded ceiling.
|
|
75
71
|
const boot2 = await h.awaitBoot(id, { minCount: boots + 1, timeoutMs: 25_000 });
|
|
76
72
|
assert.equal(boot2.resuming, true, 'clean-abort exit-event recovery RESUMES the saved .jsonl');
|
|
77
73
|
assert.equal(boot2.prompt, RUNTIME_RESTART_CONTINUATION, 'the strict resume injects the runtime-restart continuation');
|
|
78
74
|
assert.ok(!boot2.rawArgv.includes('--crtr-new-cycle'), 'clean abort never starts a recovery cycle');
|
|
79
|
-
|
|
80
|
-
assert.ok(
|
|
81
|
-
assert.notEqual(
|
|
75
|
+
const revivedPid = h.node(id).pi_pid;
|
|
76
|
+
assert.ok(revivedPid != null && isPidAlive(revivedPid), 'the revived broker pid is alive');
|
|
77
|
+
assert.notEqual(revivedPid, oldPid, 'the revived pid is distinct from the crashed one');
|
|
82
78
|
assert.equal(isPidAlive(oldPid), false, 'one-writer: the crashed pid never resurrected');
|
|
83
79
|
});
|
|
84
80
|
test('SIGKILL of a busy broker stays a dirty recovery cycle; close tears down the replacement', { timeout: 30_000 }, async () => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
const id = await h.spawnHeadlessChild(root, 'headless worker — dirty crash+teardown');
|
|
82
|
+
const boot1 = await h.awaitBoot(id);
|
|
83
|
+
const boots = h.bootCount(id);
|
|
84
|
+
const busyPid = h.node(id).pi_pid;
|
|
85
|
+
assert.equal(busyPid, boot1.pid, 'canvas pid identifies the broker that owns the dirty-crash session');
|
|
86
|
+
assert.equal(isPidAlive(busyPid), true, 'busy broker is alive before SIGKILL');
|
|
87
|
+
// This broker has only the durable busy marker — SIGKILL cannot run the abort
|
|
88
|
+
// handler or write clean-abort evidence. Strict resume would only reopen the
|
|
89
|
+
// transcript at an idle prompt; policy must select resume:false and inject the
|
|
90
|
+
// normal fresh-cycle kickoff after this journey's first 5s backoff.
|
|
90
91
|
markBusy(id);
|
|
91
|
-
process.kill(
|
|
92
|
-
await h.waitFor(() => !isPidAlive(
|
|
93
|
-
const
|
|
92
|
+
process.kill(busyPid, 'SIGKILL');
|
|
93
|
+
await h.waitFor(() => !isPidAlive(busyPid), { label: 'mid-turn crashed broker pid is dead' });
|
|
94
|
+
const boot2 = await h.awaitBoot(id, { minCount: boots + 1, timeoutMs: 25_000 });
|
|
94
95
|
// The fake SessionManager reports `resuming=true` for ANY existing session
|
|
95
96
|
// path, including a cycle; production distinguishes cycling with the argv
|
|
96
97
|
// token and non-null kickoff. Assert those load-bearing inputs directly.
|
|
97
|
-
const recoveryArgv =
|
|
98
|
+
const recoveryArgv = boot2.rawArgv;
|
|
98
99
|
assert.ok(recoveryArgv.includes('--crtr-new-cycle'), 'a surviving busy marker selects --crtr-new-cycle');
|
|
99
|
-
assert.match(
|
|
100
|
+
assert.match(boot2.prompt ?? '', /You have been revived fresh after a context refresh/, 'the recovery cycle receives a real roadmap\/feed\/context kickoff');
|
|
100
101
|
const recoveryPid = h.node(id).pi_pid;
|
|
101
102
|
assert.ok(recoveryPid != null && isPidAlive(recoveryPid), 'the recovery-cycle broker pid is alive');
|
|
102
|
-
assert.notEqual(recoveryPid,
|
|
103
|
-
assert.equal(isPidAlive(
|
|
103
|
+
assert.notEqual(recoveryPid, busyPid, 'the recovery cycle uses a distinct broker pid');
|
|
104
|
+
assert.equal(isPidAlive(busyPid), false, 'one-writer: the interrupted pid never resurrected');
|
|
104
105
|
// --- Item 5: clean teardown of the (revived, live) broker. node lifecycle close marks
|
|
105
106
|
// canceled BEFORE teardown (crash-safe order), then hostFor(broker).teardown
|
|
106
107
|
// sends the `shutdown` frame → the broker disposes + exits, socket unlinked. ---
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// Run with: node --import tsx/esm --test src/core/__tests__/
|
|
1
|
+
// Run with: node --import tsx/esm --test src/core/__tests__/seam/broker-pane-resolution.test.ts
|
|
2
2
|
//
|
|
3
3
|
// REGRESSION — broker-hosted nodes were second-class in the tmux command
|
|
4
4
|
// surface (audit: findings-headless-default.md, Gaps 1 + 3). Both regressions
|
|
5
|
-
// are observable only with a REAL tmux pane, so this is a
|
|
5
|
+
// are observable only with a REAL tmux pane, so this is a compiled-seam test.
|
|
6
6
|
//
|
|
7
|
-
// (1) Gap 1 — `nodeInPane()` (src/commands/node.ts) resolved a pane via
|
|
7
|
+
// (1) Gap 1 — `nodeInPane()` (src/commands/surface/node/placement.ts) resolved a pane via
|
|
8
8
|
// window→node ONLY. A broker engine runs DETACHED with window=null, and its
|
|
9
9
|
// on-screen presence is a `crtr surface attach` VIEWER pane, so the window lookup
|
|
10
10
|
// never found it: viewer navigation and node lifecycle operations all
|
|
@@ -22,13 +22,23 @@
|
|
|
22
22
|
// node, the second reused IN PLACE (in_place=true). A focus() that always
|
|
23
23
|
// opened a fresh pane would yield two tagged panes (or crash on the UNIQUE
|
|
24
24
|
// constraint) — either way this goes RED.
|
|
25
|
+
//
|
|
26
|
+
// (4) `node lifecycle demote --detach` (the Alt+C → D menu path) CLOSES the
|
|
27
|
+
// node's viewer pane and its `@crtr_node` tag while the broker ENGINE keeps
|
|
28
|
+
// running untouched, reconnectable by a later focus. Only the pane-closing
|
|
29
|
+
// half needs tmux — "detach leaves the broker running" is already proved
|
|
30
|
+
// headlessly by broker-attach-stream G2 — so it rides this file's pane, tag
|
|
31
|
+
// helpers and budget instead of its own harness.
|
|
25
32
|
import { test, before, after } from 'node:test';
|
|
26
33
|
import assert from 'node:assert/strict';
|
|
27
34
|
import { spawnSync } from 'node:child_process';
|
|
35
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
36
|
+
import { join } from 'node:path';
|
|
28
37
|
import { createHarness, hasTmux, TMUX_TIMEOUT_MS } from '../helpers/harness.js';
|
|
29
|
-
import { taggedViewerPanes, waitForOneTaggedViewer } from '../helpers/viewer-panes.js';
|
|
38
|
+
import { paneTag, taggedViewerPanes, waitForOneTaggedViewer } from '../helpers/viewer-panes.js';
|
|
30
39
|
import { closeDb } from '../../canvas/db.js';
|
|
31
|
-
import {
|
|
40
|
+
import { isPidAlive } from '../../canvas/pid.js';
|
|
41
|
+
import { nodeInPane } from '../../../commands/surface/node/placement.js';
|
|
32
42
|
let h;
|
|
33
43
|
let root;
|
|
34
44
|
before(async () => {
|
|
@@ -153,3 +163,42 @@ test('surface node focus reuses a viewer in another session instead of reopening
|
|
|
153
163
|
spawnSync('tmux', ['kill-session', '-t', otherSession], { stdio: 'ignore', timeout: TMUX_TIMEOUT_MS });
|
|
154
164
|
}
|
|
155
165
|
});
|
|
166
|
+
// 30s for the same reason as the two tests above: the viewer self-tags
|
|
167
|
+
// asynchronously on connect, and this test waits for that tag before detaching.
|
|
168
|
+
test('node lifecycle demote --detach closes the viewer pane + tag while the broker engine keeps running', { timeout: 30_000 }, async (t) => {
|
|
169
|
+
if (!hasTmux())
|
|
170
|
+
return t.skip('tmux unavailable');
|
|
171
|
+
const broker = await h.spawnHeadlessChild(root, 'detach while the broker keeps running');
|
|
172
|
+
closeDb();
|
|
173
|
+
// Make the node NON-EMPTY (it produced an assistant message) so `--detach`
|
|
174
|
+
// exercises the "close the viewer, broker survives" contract and NOT the
|
|
175
|
+
// reap-empty path, which tears an output-less shell down.
|
|
176
|
+
const sessionFile = h.node(broker).pi_session_file;
|
|
177
|
+
assert.ok(sessionFile != null, 'the node has a session file');
|
|
178
|
+
mkdirSync(join(sessionFile, '..'), { recursive: true });
|
|
179
|
+
writeFileSync(sessionFile, JSON.stringify({ type: 'message', message: { role: 'assistant', content: [{ type: 'text', text: 'hi' }] } }) + '\n');
|
|
180
|
+
// Put the node genuinely on screen: one viewer pane beside a caller pane.
|
|
181
|
+
const caller = makePane();
|
|
182
|
+
const focused = h.cli(broker, ['surface', 'node', 'focus', broker, '--pane', caller]);
|
|
183
|
+
assert.equal(focused.code, 0, `surface node focus exit 0\n${focused.stderr}`);
|
|
184
|
+
const viewerPane = await waitForOneTaggedViewer(h.session, broker);
|
|
185
|
+
// The engine the detach must NOT disturb is the one serving the viewer that is
|
|
186
|
+
// now on screen, so read its pid here — a headless child that idle-released
|
|
187
|
+
// after its kickoff turn is revived BY the focus, under a new pid.
|
|
188
|
+
closeDb();
|
|
189
|
+
const brokerPid = h.node(broker).pi_pid;
|
|
190
|
+
assert.ok(isPidAlive(brokerPid), 'broker engine alive before the detach');
|
|
191
|
+
// Drive the REAL verb on the focused node (the Alt+C → D menu path).
|
|
192
|
+
const res = h.cli(broker, ['node', 'lifecycle', 'demote', '--node', broker, '--pane', viewerPane, '--detach']);
|
|
193
|
+
assert.equal(res.code, 0, `node lifecycle demote --detach exit 0\n${res.stderr}`);
|
|
194
|
+
assert.match(res.stdout, /viewer closed \(broker still running off-screen\)/, `the verb reports the viewer closed with the broker alive\n${res.stdout}`);
|
|
195
|
+
closeDb();
|
|
196
|
+
const after = h.node(broker);
|
|
197
|
+
assert.equal(after.status, 'active', 'detach does NOT end the node');
|
|
198
|
+
assert.equal(after.lifecycle, 'terminal', 'lifecycle flips terminal IN PLACE');
|
|
199
|
+
assert.equal(after.pi_pid, brokerPid, 'the broker pid is UNCHANGED (engine not killed or respawned)');
|
|
200
|
+
assert.equal(isPidAlive(brokerPid), true, 'the broker engine keeps running off-screen');
|
|
201
|
+
// The viewer pane is CLOSED and untagged — no orphaned/phantom viewport.
|
|
202
|
+
await h.waitFor(() => (taggedViewerPanes(h.session, broker).length === 0 ? true : null), { label: 'the detached viewer pane is gone from the tag registry' });
|
|
203
|
+
assert.equal(paneTag(viewerPane), '', 'NO phantom @crtr_node tag resolves on the closed viewer %id');
|
|
204
|
+
});
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
// Run with: node --import tsx/esm --test src/core/__tests__/
|
|
1
|
+
// Run with: node --import tsx/esm --test src/core/__tests__/seam/broker-provider-retry.test.ts
|
|
2
2
|
//
|
|
3
|
-
// Broker-level regression coverage for turn-time provider rotation
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// Broker-level regression coverage for turn-time provider rotation, context
|
|
4
|
+
// overflow settlement, model-choice durability, and the model-less launch
|
|
5
|
+
// re-drive. The fake engine emits pi's actual lifecycle events over
|
|
6
|
+
// session.subscribe, so this exercises runBroker's live handlers rather than
|
|
7
|
+
// its helpers in isolation.
|
|
6
8
|
import { test, before, after, afterEach } from 'node:test';
|
|
7
9
|
import assert from 'node:assert/strict';
|
|
8
10
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
@@ -26,6 +28,7 @@ function resolvedModel(spec) {
|
|
|
26
28
|
const OPENAI_STRONG = resolvedModel('openai/strong');
|
|
27
29
|
let h;
|
|
28
30
|
const kit = createAttachKit(() => h);
|
|
31
|
+
const { attachUntil } = kit;
|
|
29
32
|
function poolPath(provider) {
|
|
30
33
|
const name = provider === 'anthropic' ? 'anthropic-subscriptions.json' : 'openai-codex-subscriptions.json';
|
|
31
34
|
return join(h.agentHome, '.pi', 'agent', name);
|
|
@@ -38,9 +41,24 @@ function seedPool(provider, cooling) {
|
|
|
38
41
|
rateLimitedUntil: cooling ? Date.now() + 60_000 : 0, lastAttemptAt: 0, lastRateLimitedAt: 0,
|
|
39
42
|
}]));
|
|
40
43
|
}
|
|
44
|
+
// The node's recorded model (fake-pi.model.json, written by the fake's
|
|
45
|
+
// recordModel) — `null` for a model-less boot. A disk read, so it survives the
|
|
46
|
+
// broker exiting after its kickoff turn idle-releases.
|
|
41
47
|
function model(id) {
|
|
42
48
|
return JSON.parse(readFileSync(join(h.home, 'nodes', id, 'fake-pi.model.json'), 'utf8'));
|
|
43
49
|
}
|
|
50
|
+
// The fake engine records each settled overflow episode's outcome in
|
|
51
|
+
// fake-pi.overflow.json, OVERWRITING it per episode. Two episodes on one broker
|
|
52
|
+
// must therefore gate on the parsed `outcome`, never on file existence.
|
|
53
|
+
function overflowOutcome(id) {
|
|
54
|
+
try {
|
|
55
|
+
const raw = readFileSync(join(h.home, 'nodes', id, 'fake-pi.overflow.json'), 'utf8');
|
|
56
|
+
return JSON.parse(raw).outcome ?? null;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
44
62
|
// Every fired lifecycle event is appended to fake-pi.events.jsonl. A broker
|
|
45
63
|
// re-drive is a fresh session.prompt → emitTurn, which fires one 'agent_start';
|
|
46
64
|
// counting them is the observable proof of how many times the turn was re-driven.
|
|
@@ -178,35 +196,20 @@ test('reload_auth leaves a parked auth fault with its original session when navi
|
|
|
178
196
|
assert.equal(agentStartCount(child), baselineStarts, 'the replacement session receives no stale recovery prompt');
|
|
179
197
|
controller.close();
|
|
180
198
|
});
|
|
181
|
-
|
|
182
|
-
|
|
199
|
+
// Both overflow outcomes on ONE broker, in order: Pi's compact-and-retry
|
|
200
|
+
// recovers (nothing is committed, the child lives), and only then does a
|
|
201
|
+
// definitive failure make the episode terminal.
|
|
202
|
+
test('a recovered compact-and-retry commits nothing, while a definitive failure publishes one structured overflow fault and terminal notice', { timeout: 30_000 }, async () => {
|
|
203
|
+
const parent = h.spawnRoot('overflow parent');
|
|
183
204
|
seedPool('anthropic', false);
|
|
184
205
|
seedPool('openai-codex', false);
|
|
185
|
-
const child = await h.spawnHeadlessChild(parent, '
|
|
206
|
+
const child = await h.spawnHeadlessChild(parent, 'overflow child', { model: OPENAI_STRONG.spec });
|
|
186
207
|
h.fakeCmd(child, { cmd: 'overflow', outcome: 'recover' });
|
|
187
|
-
await h.waitFor(() =>
|
|
208
|
+
await h.waitFor(() => overflowOutcome(child) === 'recover' ? true : null, { label: 'recovered overflow reached agent_settled' });
|
|
188
209
|
await h.tick();
|
|
189
210
|
assert.equal(readFault(child), null, 'raw overflow is never committed before Pi recovery settles');
|
|
190
211
|
assert.notEqual(h.status(child), 'dead', 'a recovered child remains live');
|
|
191
212
|
assert.doesNotMatch(h.inbox(parent).map((entry) => entry.label).join('\n'), /context window|will not recover/i, 'recovery publishes no fatal parent notice');
|
|
192
|
-
});
|
|
193
|
-
test('queued continuation success supersedes an earlier definitive compaction failure', { timeout: 30_000 }, async () => {
|
|
194
|
-
const parent = h.spawnRoot('continued overflow parent');
|
|
195
|
-
seedPool('anthropic', false);
|
|
196
|
-
seedPool('openai-codex', false);
|
|
197
|
-
const child = await h.spawnHeadlessChild(parent, 'continued overflow child', { model: OPENAI_STRONG.spec });
|
|
198
|
-
h.fakeCmd(child, { cmd: 'overflow', outcome: 'failure-continued' });
|
|
199
|
-
await h.waitFor(() => existsSync(join(h.home, 'nodes', child, 'fake-pi.overflow.json')), { label: 'post-failure continuation reached agent_settled' });
|
|
200
|
-
await h.tick();
|
|
201
|
-
assert.equal(readFault(child), null, 'the final successful continuation owns the settled provider outcome');
|
|
202
|
-
assert.notEqual(h.status(child), 'dead', 'an earlier compaction failure cannot reap a recovered child');
|
|
203
|
-
assert.doesNotMatch(h.inbox(parent).map((entry) => entry.label).join('\n'), /context window|will not recover/i, 'the superseded compaction failure publishes no parent notice');
|
|
204
|
-
});
|
|
205
|
-
test('failed compact-and-retry publishes one structured overflow fault and terminal notice', { timeout: 30_000 }, async () => {
|
|
206
|
-
const parent = h.spawnRoot('failed overflow parent');
|
|
207
|
-
seedPool('anthropic', false);
|
|
208
|
-
seedPool('openai-codex', false);
|
|
209
|
-
const child = await h.spawnHeadlessChild(parent, 'failed overflow child', { model: OPENAI_STRONG.spec });
|
|
210
213
|
// A settled ordinary provider failure leaves an active daemon-owned retry
|
|
211
214
|
// marker in place while the next turn runs. The later terminal overflow must
|
|
212
215
|
// supersede that marker rather than lose to its priority in fault storage.
|
|
@@ -222,7 +225,7 @@ test('failed compact-and-retry publishes one structured overflow fault and termi
|
|
|
222
225
|
}, { label: 'ordinary settled provider fault published' });
|
|
223
226
|
assert.equal(autoFault.kind, 'rate-limit', 'ordinary settled provider failures retain broker-owned retry behavior');
|
|
224
227
|
h.fakeCmd(child, { cmd: 'overflow', outcome: 'fail' });
|
|
225
|
-
await h.waitFor(() =>
|
|
228
|
+
await h.waitFor(() => overflowOutcome(child) === 'fail' ? true : null, { label: 'failed overflow reached agent_settled' });
|
|
226
229
|
const fault = readFault(child);
|
|
227
230
|
assert.equal(fault?.kind, 'context-overflow');
|
|
228
231
|
assert.equal(fault?.op, 'context overflow recovery');
|
|
@@ -233,65 +236,53 @@ test('failed compact-and-retry publishes one structured overflow fault and termi
|
|
|
233
236
|
const notices = h.inbox(parent).filter((entry) => /exceeded its model context window/i.test(entry.label));
|
|
234
237
|
assert.equal(notices.length, 1, 'the parent receives exactly one structured overflow notice');
|
|
235
238
|
});
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
h.fakeCmd(child, { cmd: 'overflow', outcome: 'uncompacted' });
|
|
242
|
-
await h.waitFor(() => existsSync(join(h.home, 'nodes', child, 'fake-pi.overflow.json')), { label: 'uncompacted overflow reached agent_settled' });
|
|
243
|
-
const fault = readFault(child);
|
|
244
|
-
assert.equal(fault?.kind, 'context-overflow', 'settlement makes a raw overflow terminal when recovery never began');
|
|
245
|
-
assert.equal(fault?.retry.disposition, 'fatal');
|
|
246
|
-
await h.tick();
|
|
247
|
-
assert.equal(h.status(child), 'dead', 'settled raw overflow cannot leave the child hanging');
|
|
248
|
-
});
|
|
249
|
-
test('an explicit picker choice pins and clears prior logical routing; an internal no-raw-token path preserves that decision', { timeout: 30_000 }, async () => {
|
|
250
|
-
const parent = h.spawnRoot('picker frame parent');
|
|
251
|
-
seedPool('anthropic', false);
|
|
252
|
-
seedPool('openai-codex', false);
|
|
253
|
-
const child = await h.spawnHeadlessChild(parent, 'picker frame child', { model: 'openai/strong' }); // logical route boot
|
|
254
|
-
const ctrl = await kit.attach(child, 'controller', 'picker-test');
|
|
255
|
-
ctrl.send({ type: 'set_model', model: OPENAI_STRONG.spec }); // exactly what buildModelPicker sends — no `pinned`
|
|
256
|
-
await ctrl.waitFrame((f) => f.type === 'ack' && f.for === 'set_model', 'picker set_model acked');
|
|
257
|
-
let after = h.node(child);
|
|
258
|
-
assert.equal(after.launch?.modelProviderPinned, true, 'the omitted-pinned frame derives pinned from the real openai-codex registry namespace');
|
|
259
|
-
assert.equal(after.launch?.modelIntent, undefined, 'the explicit concrete choice clears the logical launch intent');
|
|
260
|
-
assert.equal(after.launch?.env.CRTR_MODEL_INTENT, undefined, 'a revive cannot restore the stale logical route intent');
|
|
261
|
-
// `cycle_ladder` — an internal no-raw-token path (no `model`/`pinned` on the
|
|
262
|
-
// frame at all) — reuses the SAME resolve+setModel machinery `set_model` does
|
|
263
|
-
// (real `nextLadderModel`/`resolveModelQuery`, the fake engine's real
|
|
264
|
-
// `session.setModel`), unlike `cycle_model` which the fake engine's session
|
|
265
|
-
// does not implement. `broadcastModelChanged()` is called here with no
|
|
266
|
-
// override, so the existing pin must survive verbatim.
|
|
267
|
-
ctrl.send({ type: 'cycle_ladder', direction: 'forward' });
|
|
268
|
-
await ctrl.waitFrame((f) => f.type === 'ack' && f.for === 'cycle_ladder', 'cycle_ladder acked');
|
|
269
|
-
after = h.node(child);
|
|
270
|
-
assert.equal(after.launch?.modelProviderPinned, true, 'an internal no-raw-token change (cycle_ladder) preserves the existing pin verbatim');
|
|
271
|
-
ctrl.close();
|
|
272
|
-
});
|
|
273
|
-
test("a user's Alt+M and Shift+Tab choices replace stale launch intent for restart replay", { timeout: 30_000 }, async () => {
|
|
274
|
-
const parent = h.spawnRoot('session choice durability parent');
|
|
239
|
+
// Two real logical launches keep both user paths non-vacuous: the picker first
|
|
240
|
+
// proves an explicit concrete choice pins and clears routing, while Alt+M runs
|
|
241
|
+
// directly against still-present logical intent before Shift+Tab and replay.
|
|
242
|
+
test("picker and Alt+M choices each clear logical routing, and Alt+M / Shift+Tab become the durable restart recipe", { timeout: 30_000 }, async () => {
|
|
243
|
+
const parent = h.spawnRoot('model durability parent');
|
|
275
244
|
seedPool('anthropic', false);
|
|
276
245
|
seedPool('openai-codex', false);
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
assert.deepEqual(
|
|
280
|
-
assert.equal(
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
await
|
|
284
|
-
let
|
|
285
|
-
assert.equal(
|
|
286
|
-
assert.equal(
|
|
246
|
+
const pickerNode = await h.spawnHeadlessChild(parent, 'picker durability child', { model: 'openai/strong' });
|
|
247
|
+
const pickerBefore = h.node(pickerNode);
|
|
248
|
+
assert.deepEqual(pickerBefore.launch?.modelIntent, { family: 'openai', strength: 'strong' }, 'picker fixture starts from a logical launch preference');
|
|
249
|
+
assert.equal(pickerBefore.launch?.env.CRTR_MODEL_INTENT, 'openai/strong');
|
|
250
|
+
const pickerCtrl = await kit.attach(pickerNode, 'controller', 'picker-durability-test');
|
|
251
|
+
pickerCtrl.send({ type: 'set_model', model: OPENAI_STRONG.spec }); // exactly what buildModelPicker sends — no `pinned`
|
|
252
|
+
await pickerCtrl.waitFrame((f) => f.type === 'ack' && f.for === 'set_model', 'picker set_model acked');
|
|
253
|
+
let pickerState = h.node(pickerNode);
|
|
254
|
+
assert.equal(pickerState.launch?.modelProviderPinned, true, 'the omitted-pinned frame derives pinned from the real openai-codex registry namespace');
|
|
255
|
+
assert.equal(pickerState.launch?.modelIntent, undefined, 'the explicit concrete choice clears the logical launch intent');
|
|
256
|
+
assert.equal(pickerState.launch?.env.CRTR_MODEL_INTENT, undefined, 'a revive cannot restore the stale logical route intent');
|
|
257
|
+
// `cycle_ladder` carries no raw model/pin token. Running it after the picker
|
|
258
|
+
// proves that this internal change preserves the concrete provider pin.
|
|
259
|
+
pickerCtrl.send({ type: 'cycle_ladder', direction: 'forward' });
|
|
260
|
+
await pickerCtrl.waitFrame((f) => f.type === 'ack' && f.for === 'cycle_ladder', 'post-picker Alt+M cycle_ladder acked');
|
|
261
|
+
pickerState = h.node(pickerNode);
|
|
262
|
+
assert.equal(pickerState.launch?.modelProviderPinned, true, 'an internal no-raw-token ladder change preserves the explicit picker pin');
|
|
263
|
+
pickerCtrl.close();
|
|
264
|
+
// A separate logical launch reaches Alt+M with BOTH intent representations
|
|
265
|
+
// still populated. This is the regression path: a user-selected ladder rung
|
|
266
|
+
// must clear them before a restart can resolve the stale route over it.
|
|
267
|
+
const ladderNode = await h.spawnHeadlessChild(parent, 'ladder durability child', { model: 'openai/strong' });
|
|
268
|
+
const ladderBefore = h.node(ladderNode);
|
|
269
|
+
assert.deepEqual(ladderBefore.launch?.modelIntent, { family: 'openai', strength: 'strong' }, 'Alt+M starts with durable logical modelIntent present');
|
|
270
|
+
assert.equal(ladderBefore.launch?.env.CRTR_MODEL_INTENT, 'openai/strong', 'Alt+M starts with the launch environment intent present');
|
|
271
|
+
const ladderCtrl = await kit.attach(ladderNode, 'controller', 'ladder-durability-test');
|
|
272
|
+
ladderCtrl.send({ type: 'cycle_ladder', direction: 'forward' });
|
|
273
|
+
await ladderCtrl.waitFrame((f) => f.type === 'ack' && f.for === 'cycle_ladder', 'logical-launch Alt+M cycle_ladder acked');
|
|
274
|
+
let after = h.node(ladderNode);
|
|
275
|
+
assert.equal(after.launch?.modelIntent, undefined, 'Alt+M clears the durable logical launch intent');
|
|
276
|
+
assert.equal(after.launch?.env.CRTR_MODEL_INTENT, undefined, 'Alt+M clears the logical intent from the restart environment');
|
|
277
|
+
assert.equal(after.launch?.modelProviderPinned, ladderBefore.launch?.modelProviderPinned, 'Alt+M preserves the existing pin state verbatim');
|
|
287
278
|
assert.equal(after.launch?.model, after.model_override, 'the selected model and thinking rung is the durable revive recipe');
|
|
288
279
|
const rung = after.launch?.model;
|
|
289
280
|
assert.ok(rung, 'Alt+M persisted a concrete model rung');
|
|
290
|
-
const thinkingFrameOffset =
|
|
291
|
-
|
|
292
|
-
const thinkingChanged = await h.waitFor(() =>
|
|
293
|
-
await
|
|
294
|
-
after = h.node(
|
|
281
|
+
const thinkingFrameOffset = ladderCtrl.frames.length;
|
|
282
|
+
ladderCtrl.send({ type: 'cycle_thinking' });
|
|
283
|
+
const thinkingChanged = await h.waitFor(() => ladderCtrl.frames.slice(thinkingFrameOffset).find((f) => f.type === 'thinking_level_changed') ?? null, { label: 'Shift+Tab thinking change relayed' });
|
|
284
|
+
await ladderCtrl.waitFrame((f) => f.type === 'ack' && f.for === 'cycle_thinking', 'Shift+Tab cycle acked');
|
|
285
|
+
after = h.node(ladderNode);
|
|
295
286
|
const level = thinkingChanged.type === 'thinking_level_changed' ? thinkingChanged.level : 'off';
|
|
296
287
|
const bareRung = rung.replace(/:(?:off|minimal|low|medium|high|xhigh|max)$/, '');
|
|
297
288
|
const expected = `${bareRung}${level === 'off' ? '' : `:${level}`}`;
|
|
@@ -302,7 +293,7 @@ test("a user's Alt+M and Shift+Tab choices replace stale launch intent for resta
|
|
|
302
293
|
assert.notEqual(modelArg, -1, 'a daemon revive replays a model argument');
|
|
303
294
|
assert.equal(replay.argv[modelArg + 1], expected, 'the replayed model argument carries both choices');
|
|
304
295
|
assert.equal(replay.env.CRTR_MODEL_INTENT, undefined, 'the stale logical intent is absent from the restarted broker environment');
|
|
305
|
-
|
|
296
|
+
ladderCtrl.close();
|
|
306
297
|
});
|
|
307
298
|
test('reload_auth restores a newly authenticated personal route before the next idle prompt', { timeout: 30_000 }, async () => {
|
|
308
299
|
const personal = { provider: 'personal-anthropic', id: 'claude-personal' };
|
|
@@ -354,3 +345,64 @@ test('reload_auth restores a newly authenticated personal route before the next
|
|
|
354
345
|
rmSync(userConfigPath, { force: true });
|
|
355
346
|
}
|
|
356
347
|
});
|
|
348
|
+
// MODEL-LESS LAUNCH RE-DRIVE — MUST REMAIN THE LAST TEST IN THIS FILE. Its
|
|
349
|
+
// zero-auth seam, <CRTR_HOME>/fake-pi.no-auth, is HOME-GLOBAL: while it exists
|
|
350
|
+
// the fake engine's modelRegistry reports a genuine zero-auth boot, so no other
|
|
351
|
+
// boot may overlap it. Arming it before the spawn is race-free against the
|
|
352
|
+
// broker's boot-time registry read, and deleting it mid-test is a live auth-on
|
|
353
|
+
// flip that reload_auth's modelRegistry.refresh() re-reads (fake-engine.ts,
|
|
354
|
+
// createAgentSessionServices).
|
|
355
|
+
//
|
|
356
|
+
// Locks two broker.ts launch-model fixes:
|
|
357
|
+
// 0b unknown-stub detection — the SDK hands a model-less session an
|
|
358
|
+
// unknown-stub model (never `undefined`), so sessionHasNoModel() must defer
|
|
359
|
+
// the kickoff instead of prompting a keyless engine.
|
|
360
|
+
// 0a self-healing model-less registry — the construction-time mask that
|
|
361
|
+
// suppresses the SDK's findInitialModel must reveal() the instant
|
|
362
|
+
// construction returns, or list_models stays broken forever for a
|
|
363
|
+
// model-less-booted node even after a /login. The auth-on list_models
|
|
364
|
+
// assertion below is the ONLY one that discriminates that bug: with the
|
|
365
|
+
// mask never revealed, getAvailable() returns [] forever.
|
|
366
|
+
//
|
|
367
|
+
// The request is LOGICAL (`anthropic/medium`), deliberately. A CONCRETE pin can
|
|
368
|
+
// never reach a model-less boot: both `node new`'s assertLaunchModelRegistered
|
|
369
|
+
// and resolveLaunchModel's own unregisteredConcreteModel gate treat a concrete
|
|
370
|
+
// spec absent from the serving registry as a FATAL model-not-found, and under
|
|
371
|
+
// the zero-auth sentinel every spec is absent. A logical request skips both
|
|
372
|
+
// gates by design, so the zero-auth window produces the tier-3 model-less boot
|
|
373
|
+
// this test exists to cover. After the flip its candidate tail includes the
|
|
374
|
+
// fake registry's non-managed `fake/fake-model`, whose hasConfiguredAuth() the
|
|
375
|
+
// sentinel controls — so the re-drive resolves without needing managed
|
|
376
|
+
// subscription-pool occupancy (broker-sdk-wiring's scope).
|
|
377
|
+
test('login re-drive → list_models self-heals (0a) and reload_auth resolves a model + fires the deferred kickoff', { timeout: 30_000 }, async () => {
|
|
378
|
+
const noAuthSentinel = join(h.home, 'fake-pi.no-auth');
|
|
379
|
+
writeFileSync(noAuthSentinel, '1');
|
|
380
|
+
try {
|
|
381
|
+
const parent = h.spawnRoot('login re-drive parent');
|
|
382
|
+
const child = await h.spawnHeadlessChild(parent, 'login re-drive', { model: 'anthropic/medium' });
|
|
383
|
+
assert.equal(model(child), null, 'boots model-less under zero-auth');
|
|
384
|
+
assert.equal(agentStartCount(child), 0, 'kickoff deferred at boot');
|
|
385
|
+
const c = await attachUntil(child, 'controller', 'redrive-ctrl', (a) => a.welcome.role === 'controller', 'redrive controller admitted');
|
|
386
|
+
// A read-op reply is a correlated `data` frame the client swallows into
|
|
387
|
+
// request()'s by-id resolver (NEVER a generic 'frame'), so we MUST use
|
|
388
|
+
// client.request(), not send()+waitFrame.
|
|
389
|
+
// Baseline: under zero-auth the registry reports nothing available.
|
|
390
|
+
const lm0 = await c.client.request({ type: 'list_models' });
|
|
391
|
+
assert.ok(lm0.kind === 'list_models' && lm0.availableIds.length === 0, 'zero-auth: no models available');
|
|
392
|
+
// FLIP AUTH ON, then re-query while the broker is still alive (no turn has run
|
|
393
|
+
// yet). getAvailable() flows through the revealed construction proxy →
|
|
394
|
+
// NON-empty. With the mask never revealed it would still be [].
|
|
395
|
+
rmSync(noAuthSentinel, { force: true });
|
|
396
|
+
const lm1 = await c.client.request({ type: 'list_models' });
|
|
397
|
+
assert.ok(lm1.kind === 'list_models' && lm1.availableIds.length > 0, 'auth on: list_models reports an available model (0a self-heal — the construction mask revealed)');
|
|
398
|
+
// reload_auth re-drives the launch policy → resolves a model and fires the
|
|
399
|
+
// deferred kickoff. Disk reads, so they survive the broker idle-releasing
|
|
400
|
+
// once that kickoff turn ends.
|
|
401
|
+
c.send({ type: 'reload_auth' });
|
|
402
|
+
await h.waitFor(() => model(child) !== null, { label: 're-drive selected a model' });
|
|
403
|
+
await h.waitFor(() => agentStartCount(child) >= 1, { label: 'deferred kickoff fired after re-drive' });
|
|
404
|
+
}
|
|
405
|
+
finally {
|
|
406
|
+
rmSync(noAuthSentinel, { force: true });
|
|
407
|
+
}
|
|
408
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Run with: node --import tsx/esm --test src/core/__tests__/
|
|
1
|
+
// Run with: node --import tsx/esm --test src/core/__tests__/seam/broker-whip-interrupt.test.ts
|
|
2
2
|
//
|
|
3
3
|
// A whip is a transport-level abort-then-prompt, not an ordinary steering queue
|
|
4
4
|
// entry. This needs a real broker because the guarantee crosses the socket frame,
|
|
@@ -31,9 +31,20 @@ test('a whip aborts the active turn before starting its prompt', { timeout: 20_0
|
|
|
31
31
|
const id = await h.spawnHeadlessChild(root, 'headless worker — held turn');
|
|
32
32
|
const controller = await kit.attach(id, 'controller', 'whip-controller');
|
|
33
33
|
assert.equal(controller.welcome.snapshot.state.isStreaming, true, 'the original turn is held active');
|
|
34
|
-
const
|
|
34
|
+
const frameOffset = controller.frames.length;
|
|
35
35
|
const message = 'Whip command must start after the active turn is interrupted';
|
|
36
36
|
controller.send({ type: 'prompt', text: message, interrupt: true });
|
|
37
|
-
await h.waitFor(() =>
|
|
38
|
-
|
|
37
|
+
const whipFrames = await h.waitFor(() => {
|
|
38
|
+
const frames = controller.frames.slice(frameOffset);
|
|
39
|
+
return frames.some((frame) => frame.type === 'agent_start') &&
|
|
40
|
+
frames.some((frame) => frame.type === 'message_start' && JSON.stringify(frame).includes(message))
|
|
41
|
+
? frames
|
|
42
|
+
: null;
|
|
43
|
+
}, { label: 'whip prompt started a new turn after the abort' });
|
|
44
|
+
const settledIndex = whipFrames.findIndex((frame) => frame.type === 'agent_settled');
|
|
45
|
+
const whipStartIndex = whipFrames.findIndex((frame) => frame.type === 'agent_start');
|
|
46
|
+
const whipMessageIndex = whipFrames.findIndex((frame) => frame.type === 'message_start' && JSON.stringify(frame).includes(message));
|
|
47
|
+
assert.ok(settledIndex >= 0, 'abort releases the held original turn through agent_settled');
|
|
48
|
+
assert.ok(whipStartIndex > settledIndex, 'the original held turn settles before the whip turn starts');
|
|
49
|
+
assert.ok(whipMessageIndex > settledIndex, 'the whip message starts only after the original turn settles');
|
|
39
50
|
});
|