@north-light/crouter 0.3.172 → 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} +106 -177
- 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 +55 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +60 -0
- 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
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
// 1. A passive subscriber's pushes land in passive.jsonl, NOT inbox.jsonl
|
|
3
3
|
// (so the inbox-watcher never wakes it); an active subscriber's land in
|
|
4
4
|
// inbox.jsonl as before.
|
|
5
|
-
// 2.
|
|
5
|
+
// 2. the broker drain reads + clears the accumulator (surfaces exactly once).
|
|
6
6
|
// 3. canvas-passive-context formats drained entries as timestamped XML and
|
|
7
7
|
// transforms an `input` event into pre-text + the original message.
|
|
8
8
|
//
|
|
9
9
|
// Run: node --import tsx/esm --test src/core/__tests__/passive-subscription.test.ts
|
|
10
10
|
import { test, before, beforeEach, afterEach, after } from 'node:test';
|
|
11
11
|
import assert from 'node:assert/strict';
|
|
12
|
-
import { mkdtempSync, mkdirSync, rmSync, existsSync, appendFileSync } from 'node:fs';
|
|
12
|
+
import { mkdtempSync, mkdirSync, rmSync, existsSync, readFileSync, appendFileSync } from 'node:fs';
|
|
13
13
|
import { tmpdir } from 'node:os';
|
|
14
14
|
import { basename, join } from 'node:path';
|
|
15
15
|
import { createNode, subscribe } from '../canvas/canvas.js';
|
|
@@ -17,11 +17,18 @@ import { closeDb } from '../canvas/db.js';
|
|
|
17
17
|
import { inboxPath, passivePath, apiSocketPath } from '../canvas/paths.js';
|
|
18
18
|
import { createApiServer } from '../../daemon/api/server.js';
|
|
19
19
|
import { push } from '../feed/feed.js';
|
|
20
|
-
import { appendPassive
|
|
20
|
+
import { appendPassive } from '../feed/passive.js';
|
|
21
21
|
import { readInboxSince } from '../feed/inbox.js';
|
|
22
|
+
import { drainBrokerPassive } from '../runtime/broker/passive.js';
|
|
22
23
|
import { readLeaf } from '../../commands/canvas-history/read.js';
|
|
23
|
-
import registerCanvasPassiveContext
|
|
24
|
+
import registerCanvasPassiveContext from '../../pi-extensions/canvas-passive-context.js';
|
|
24
25
|
let home;
|
|
26
|
+
function readPassiveEntries(nodeId) {
|
|
27
|
+
const path = passivePath(nodeId);
|
|
28
|
+
if (!existsSync(path))
|
|
29
|
+
return [];
|
|
30
|
+
return readFileSync(path, 'utf8').split('\n').filter(Boolean).map((line) => JSON.parse(line));
|
|
31
|
+
}
|
|
25
32
|
// The one test that resolves an emitted feed ref drives `canvas history read`'s
|
|
26
33
|
// leaf (`readLeaf`), a thin CLI forwarder to crtrd since the API re-plumb. Stand
|
|
27
34
|
// up a real in-process `createApiServer()` on the isolated CRTR_HOME's socket so
|
|
@@ -76,7 +83,7 @@ test('passive push accumulates in passive.jsonl, not inbox.jsonl', async () => {
|
|
|
76
83
|
const result = await push('pub', { kind: 'update', body: 'first observation\nmore detail' });
|
|
77
84
|
// The emitted feed ref is the same canonical handle accepted by `canvas
|
|
78
85
|
// history read`, while PushResult.reportPath remains an absolute path.
|
|
79
|
-
const acc =
|
|
86
|
+
const acc = readPassiveEntries('observer');
|
|
80
87
|
const ref = acc[0].ref;
|
|
81
88
|
assert.equal(ref, `pub:reports/${basename(result.reportPath)}`);
|
|
82
89
|
const resolved = await readLeaf.run({ ref });
|
|
@@ -113,21 +120,21 @@ test('mixed active + passive subscribers route to their own stores', async () =>
|
|
|
113
120
|
assert.deepEqual(new Set(res.deliveredTo), new Set(['active-sub', 'passive-sub']));
|
|
114
121
|
assert.equal(readInboxSince('active-sub').length, 1);
|
|
115
122
|
assert.equal(existsSync(passivePath('active-sub')), false);
|
|
116
|
-
assert.equal(
|
|
123
|
+
assert.equal(readPassiveEntries('passive-sub').length, 1);
|
|
117
124
|
assert.equal(existsSync(inboxPath('passive-sub')), false);
|
|
118
125
|
});
|
|
119
|
-
test('
|
|
126
|
+
test('broker passive drain reads then clears (surfaces exactly once)', () => {
|
|
120
127
|
createNode(node('observer'));
|
|
121
128
|
appendPassive('observer', { from: 'a', tier: 'normal', kind: 'update', label: 'one' });
|
|
122
129
|
appendPassive('observer', { from: 'b', tier: 'normal', kind: 'update', label: 'two' });
|
|
123
|
-
const drained =
|
|
130
|
+
const drained = drainBrokerPassive('observer');
|
|
124
131
|
assert.equal(drained.length, 2);
|
|
125
132
|
assert.deepEqual(drained.map((e) => e.label), ['one', 'two']); // oldest first
|
|
126
133
|
// Cleared — a second drain is empty.
|
|
127
|
-
assert.equal(
|
|
128
|
-
assert.equal(
|
|
134
|
+
assert.equal(drainBrokerPassive('observer').length, 0);
|
|
135
|
+
assert.equal(readPassiveEntries('observer').length, 0);
|
|
129
136
|
});
|
|
130
|
-
test('
|
|
137
|
+
test('broker passive drain tolerates a corrupt line — keeps every good entry, loses only the bad one', () => {
|
|
131
138
|
createNode(node('observer'));
|
|
132
139
|
appendPassive('observer', { from: 'a', tier: 'normal', kind: 'update', label: 'one' });
|
|
133
140
|
appendPassive('observer', { from: 'b', tier: 'normal', kind: 'update', label: 'two' });
|
|
@@ -135,43 +142,13 @@ test('drainPassive tolerates a corrupt line — keeps every good entry, loses on
|
|
|
135
142
|
// mid-append). The whole feed must NOT be discarded over it.
|
|
136
143
|
appendFileSync(passivePath('observer'), '{not valid json\n', 'utf8');
|
|
137
144
|
appendPassive('observer', { from: 'c', tier: 'normal', kind: 'update', label: 'three' });
|
|
138
|
-
const drained =
|
|
145
|
+
const drained = drainBrokerPassive('observer');
|
|
139
146
|
assert.equal(drained.length, 3, 'all three good entries survive the corrupt line');
|
|
140
147
|
assert.deepEqual(drained.map((e) => e.label), ['one', 'two', 'three']);
|
|
141
148
|
// And it still cleared: a second drain is empty (snapshot removed).
|
|
142
|
-
assert.equal(
|
|
149
|
+
assert.equal(drainBrokerPassive('observer').length, 0);
|
|
143
150
|
assert.equal(existsSync(passivePath('observer')), false);
|
|
144
151
|
});
|
|
145
|
-
test('readPassive tolerates a corrupt line without clearing', () => {
|
|
146
|
-
createNode(node('observer'));
|
|
147
|
-
appendPassive('observer', { from: 'a', tier: 'normal', kind: 'update', label: 'one' });
|
|
148
|
-
appendFileSync(passivePath('observer'), 'garbage}\n', 'utf8');
|
|
149
|
-
appendPassive('observer', { from: 'b', tier: 'normal', kind: 'update', label: 'two' });
|
|
150
|
-
const read = readPassive('observer');
|
|
151
|
-
assert.deepEqual(read.map((e) => e.label), ['one', 'two']);
|
|
152
|
-
});
|
|
153
|
-
test('formatPassive renders timestamped XML update blocks', () => {
|
|
154
|
-
const entries = [
|
|
155
|
-
{
|
|
156
|
-
entry_id: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
157
|
-
operation_id: '11111111111111111111111111111111',
|
|
158
|
-
ts: '2026-06-03T12:00:00.000Z',
|
|
159
|
-
from: 'pub-a', tier: 'normal', kind: 'update', label: 'alpha happened',
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
entry_id: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
|
163
|
-
operation_id: '22222222222222222222222222222222',
|
|
164
|
-
ts: '2026-06-03T12:05:00.000Z',
|
|
165
|
-
from: 'pub-b', tier: 'urgent', kind: 'final', label: 'beta done',
|
|
166
|
-
},
|
|
167
|
-
];
|
|
168
|
-
const xml = formatPassive(entries);
|
|
169
|
-
assert.match(xml, /<passive-subscription-backlog count="2"/);
|
|
170
|
-
assert.match(xml, /<update from="pub-a" kind="update" at="2026-06-03T12:00:00.000Z">/);
|
|
171
|
-
assert.match(xml, /alpha happened/);
|
|
172
|
-
assert.match(xml, /<update from="pub-b" kind="final" at="2026-06-03T12:05:00.000Z">/);
|
|
173
|
-
assert.match(xml, /<\/passive-subscription-backlog>/);
|
|
174
|
-
});
|
|
175
152
|
function makeFakePi() {
|
|
176
153
|
return { on(e, h) { if (e === 'input')
|
|
177
154
|
this.handler = h; } };
|
|
@@ -186,27 +163,27 @@ test('input handler injects drained backlog as pre-text, then clears it', async
|
|
|
186
163
|
registerCanvasPassiveContext(pi);
|
|
187
164
|
assert.ok(pi.handler, 'input handler registered');
|
|
188
165
|
// First message → backlog drains in as pre-text before the user's text.
|
|
189
|
-
const out = pi.handler({ type: 'input', text: 'hey what happened', source: 'interactive' });
|
|
166
|
+
const out = await pi.handler({ type: 'input', text: 'hey what happened', source: 'interactive' });
|
|
190
167
|
assert.equal(out.action, 'transform');
|
|
191
168
|
assert.match(out.text, /<passive-subscription-backlog/);
|
|
192
169
|
assert.match(out.text, /the body of the report/); // dereferenced report body
|
|
193
170
|
assert.match(out.text, /hey what happened$/); // original message preserved at the end
|
|
194
171
|
// Second message → nothing accumulated → left untouched.
|
|
195
|
-
const out2 = pi.handler({ type: 'input', text: 'still there?', source: 'interactive' });
|
|
172
|
+
const out2 = await pi.handler({ type: 'input', text: 'still there?', source: 'interactive' });
|
|
196
173
|
assert.ok(out2 === undefined || out2.action === 'continue');
|
|
197
174
|
// Oversized canonical reports must leave an actionable reader command in the
|
|
198
175
|
// truncation tail, using the exact ref emitted into the passive feed.
|
|
199
176
|
await push('pub', { kind: 'update', body: 'oversized report line '.repeat(250) });
|
|
200
|
-
const oversizedRef =
|
|
201
|
-
const out3 = pi.handler({ type: 'input', text: 'show me the rest', source: 'interactive' });
|
|
177
|
+
const oversizedRef = readPassiveEntries('observer')[0].ref;
|
|
178
|
+
const out3 = await pi.handler({ type: 'input', text: 'show me the rest', source: 'interactive' });
|
|
202
179
|
assert.equal(out3.action, 'transform');
|
|
203
180
|
assert.ok(out3.text.includes(`crtr canvas history read ${oversizedRef}`), 'truncation tail names the canonical reader and emitted ref');
|
|
204
181
|
});
|
|
205
|
-
test('input handler is inert when nothing is accumulated', () => {
|
|
182
|
+
test('input handler is inert when nothing is accumulated', async () => {
|
|
206
183
|
createNode(node('observer'));
|
|
207
184
|
process.env['CRTR_NODE_ID'] = 'observer';
|
|
208
185
|
const pi = makeFakePi();
|
|
209
186
|
registerCanvasPassiveContext(pi);
|
|
210
|
-
const out = pi.handler({ type: 'input', text: 'plain message', source: 'interactive' });
|
|
187
|
+
const out = await pi.handler({ type: 'input', text: 'plain message', source: 'interactive' });
|
|
211
188
|
assert.ok(out === undefined || out.action === 'continue');
|
|
212
189
|
});
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
// src/core/preview-registry.ts is a hand-maintained, zero-import mirror of
|
|
5
5
|
// every leaf's `help.preview.suppressOutput` — it has to be hand-maintained
|
|
6
6
|
// (not computed) because the attach viewer can never import the live command
|
|
7
|
-
// tree (build-root.ts pulls in every subtree
|
|
8
|
-
// Tailwind native binary — see command-preview-implementation.md). This test
|
|
7
|
+
// tree (build-root.ts pulls in every command subtree). This test
|
|
9
8
|
// is the only thing that keeps the mirror honest: it walks the real,
|
|
10
9
|
// buildRoot()-assembled tree (safe here — this is a test, not the attach
|
|
11
10
|
// bundle) and asserts the set of leaves declaring `suppressOutput: true`
|
|
@@ -3,13 +3,17 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import { mkdtempSync, rmSync, existsSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
-
import { createNode, getRow, subscribe, recordSpawn, subscribersOf, subscriptionsOf, pruneNodes, } from '../canvas/canvas.js';
|
|
6
|
+
import { createNode, getRow, subscribe, recordPid, recordSpawn, subscribersOf, subscriptionsOf, pruneNodes, pruneToLimit, } from '../canvas/canvas.js';
|
|
7
7
|
import { closeDb, openDb } from '../canvas/db.js';
|
|
8
8
|
import { nodeDir } from '../canvas/paths.js';
|
|
9
9
|
let home;
|
|
10
10
|
const DAY = 86_400_000;
|
|
11
11
|
const old = () => new Date(Date.now() - 30 * DAY).toISOString();
|
|
12
12
|
const now = () => new Date().toISOString();
|
|
13
|
+
// A timestamp `secondsAgo` before now — for the count-based pruner, created
|
|
14
|
+
// order doubles as recency order (no telemetry.json is written, so recencyMs
|
|
15
|
+
// falls back to the created epoch).
|
|
16
|
+
const at = (secondsAgo) => new Date(Date.now() - secondsAgo * 1000).toISOString();
|
|
13
17
|
function node(id, over = {}) {
|
|
14
18
|
return {
|
|
15
19
|
node_id: id,
|
|
@@ -133,3 +137,92 @@ test('prune retains an old terminal node with an open managed worktree', () => {
|
|
|
133
137
|
assert.ok(getRow('old-worktree') !== null, 'node remains to own its managed worktree');
|
|
134
138
|
assert.ok(existsSync(nodeDir('old-worktree')), 'node dir remains');
|
|
135
139
|
});
|
|
140
|
+
test('count pruning keeps all live nodes even when liveCount exceeds the cap', () => {
|
|
141
|
+
// 5 live (active/idle with a running pi) + 2 old terminal; cap of 3.
|
|
142
|
+
// target = max(3, 5) = 5, so NO live node is ever deleted; the 2 terminal
|
|
143
|
+
// fall beyond rank 5 and are pruned.
|
|
144
|
+
for (let i = 0; i < 5; i++) {
|
|
145
|
+
createNode(node(`live-${i}`, { status: 'active', created: at(i) }));
|
|
146
|
+
recordPid(`live-${i}`, process.pid); // process.pid is alive → isPidAlive true
|
|
147
|
+
}
|
|
148
|
+
createNode(node('old-a', { status: 'dead', created: at(100) }));
|
|
149
|
+
createNode(node('old-b', { status: 'done', created: at(101) }));
|
|
150
|
+
const result = pruneToLimit({ maxNodes: 3 });
|
|
151
|
+
assert.equal(result.dryRun, false);
|
|
152
|
+
assert.deepEqual(result.pruned.map((p) => p.node_id).sort(), ['old-a', 'old-b']);
|
|
153
|
+
for (let i = 0; i < 5; i++) {
|
|
154
|
+
assert.ok(getRow(`live-${i}`) !== null, `live-${i} must survive past the cap`);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
test('count pruning deletes the oldest terminal nodes beyond max(cap, liveCount)', () => {
|
|
158
|
+
// 1 live + 5 terminal of descending recency; cap of 3 → target = max(3,1) = 3.
|
|
159
|
+
// Ranked by recency DESC: live-0, term-0, term-1 (top 3, kept), then
|
|
160
|
+
// term-2, term-3, term-4 (rank >= 3, pruned).
|
|
161
|
+
createNode(node('live-0', { status: 'idle', created: at(0) }));
|
|
162
|
+
recordPid('live-0', process.pid);
|
|
163
|
+
for (let i = 0; i < 5; i++) {
|
|
164
|
+
createNode(node(`term-${i}`, { status: 'dead', created: at(10 + i) }));
|
|
165
|
+
}
|
|
166
|
+
const result = pruneToLimit({ maxNodes: 3 });
|
|
167
|
+
assert.deepEqual(result.pruned.map((p) => p.node_id).sort(), ['term-2', 'term-3', 'term-4']);
|
|
168
|
+
// The kept set: the live node + the two newest terminal.
|
|
169
|
+
for (const id of ['live-0', 'term-0', 'term-1']) {
|
|
170
|
+
assert.ok(getRow(id) !== null, `${id} must survive`);
|
|
171
|
+
assert.ok(existsSync(nodeDir(id)), `${id} dir must survive`);
|
|
172
|
+
}
|
|
173
|
+
// The pruned set: rows AND dirs gone.
|
|
174
|
+
for (const id of ['term-2', 'term-3', 'term-4']) {
|
|
175
|
+
assert.equal(getRow(id), null, `${id} row should be pruned`);
|
|
176
|
+
assert.ok(!existsSync(nodeDir(id)), `${id} dir should be removed`);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
test('count pruning dry-run reports candidates without deleting rows or dirs', () => {
|
|
180
|
+
createNode(node('live-0', { status: 'idle', created: at(0) }));
|
|
181
|
+
recordPid('live-0', process.pid);
|
|
182
|
+
for (let i = 0; i < 5; i++) {
|
|
183
|
+
createNode(node(`term-${i}`, { status: 'dead', created: at(10 + i) }));
|
|
184
|
+
}
|
|
185
|
+
const result = pruneToLimit({ maxNodes: 3, dryRun: true });
|
|
186
|
+
assert.equal(result.dryRun, true);
|
|
187
|
+
assert.deepEqual(result.pruned.map((p) => p.node_id).sort(), ['term-2', 'term-3', 'term-4']);
|
|
188
|
+
for (const id of ['term-2', 'term-3', 'term-4']) {
|
|
189
|
+
assert.ok(getRow(id) !== null, `${id} row must survive a dry run`);
|
|
190
|
+
assert.ok(existsSync(nodeDir(id)), `${id} dir must survive a dry run`);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
test('a non-terminal node with a dead pid is never a count-prune candidate (issue #114)', () => {
|
|
194
|
+
// An active row whose broker crashed: status stays 'active' but pi_pid is
|
|
195
|
+
// NULL (never recorded), so isPidAlive would be false. It must still be
|
|
196
|
+
// protected — crash-recovery, not the count-based pruner, owns this row —
|
|
197
|
+
// even though it's the oldest node and cap is exceeded by newer terminal rows.
|
|
198
|
+
createNode(node('crashed-active', { status: 'active', created: at(100) }));
|
|
199
|
+
for (let i = 0; i < 5; i++) {
|
|
200
|
+
createNode(node(`term-${i}`, { status: 'dead', created: at(i) }));
|
|
201
|
+
}
|
|
202
|
+
const result = pruneToLimit({ maxNodes: 3 });
|
|
203
|
+
assert.ok(!result.pruned.some((p) => p.node_id === 'crashed-active'), 'a crashed-but-active node must not be selected for pruning');
|
|
204
|
+
assert.ok(getRow('crashed-active') !== null, 'crashed-active row must survive');
|
|
205
|
+
assert.ok(existsSync(nodeDir('crashed-active')), 'crashed-active dir must survive');
|
|
206
|
+
});
|
|
207
|
+
test('count pruning retains an open managed worktree node beyond the cap', () => {
|
|
208
|
+
createNode(node('open-worktree', {
|
|
209
|
+
status: 'dead',
|
|
210
|
+
created: at(100),
|
|
211
|
+
managed_worktree: {
|
|
212
|
+
state: 'open',
|
|
213
|
+
path: join(home, 'worktrees', 'open-worktree'),
|
|
214
|
+
branch: 'crtr/open-worktree',
|
|
215
|
+
repo_root: '/tmp/repo',
|
|
216
|
+
base_ref: 'main',
|
|
217
|
+
base_sha: 'base',
|
|
218
|
+
created: at(100),
|
|
219
|
+
},
|
|
220
|
+
}));
|
|
221
|
+
for (let i = 0; i < 3; i++) {
|
|
222
|
+
createNode(node(`term-${i}`, { status: 'dead', created: at(i) }));
|
|
223
|
+
}
|
|
224
|
+
const result = pruneToLimit({ maxNodes: 1 });
|
|
225
|
+
assert.ok(!result.pruned.some((p) => p.node_id === 'open-worktree'));
|
|
226
|
+
assert.ok(getRow('open-worktree') !== null, 'node remains to own its managed worktree');
|
|
227
|
+
assert.ok(existsSync(nodeDir('open-worktree')), 'node dir remains');
|
|
228
|
+
});
|
|
@@ -43,20 +43,6 @@ test('base review ignores a light override and keeps its medium floor', () => {
|
|
|
43
43
|
assert.equal(spec('review', 'light'), expected(REVIEW_FLOOR));
|
|
44
44
|
assert.equal(spec('review', 'haiku'), expected(REVIEW_FLOOR));
|
|
45
45
|
});
|
|
46
|
-
test('no base kind defaults below the medium rung', () => {
|
|
47
|
-
const kinds = defaultKindsConfig();
|
|
48
|
-
for (const kind of [
|
|
49
|
-
'general', 'developer', 'design', 'plan', 'spec', 'review',
|
|
50
|
-
'plan/reviewers/requirements-coverage', 'plan/reviewers/pattern-consistency',
|
|
51
|
-
'plan/reviewers/code-smells', 'plan/reviewers/security', 'plan/reviewers/architecture-fit',
|
|
52
|
-
'spec/requirements',
|
|
53
|
-
]) {
|
|
54
|
-
// Kind defaults are provider-qualified and differ per kind; what must hold
|
|
55
|
-
// for every one of these is the rung, not the provider or the concrete id.
|
|
56
|
-
const rung = kinds[kind].model.split('/').pop();
|
|
57
|
-
assert.ok(rung === 'medium' || rung === 'strong' || rung === 'ultra', `${kind} defaults to ${kinds[kind].model}`);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
46
|
test('plan reviewer sub-kinds also keep their medium worker floor', () => {
|
|
61
47
|
assert.equal(spec('plan/reviewers/security', 'light'), expected('medium'));
|
|
62
48
|
assert.equal(spec('plan/reviewers/requirements-coverage', 'light'), expected('medium'));
|
|
@@ -68,24 +54,6 @@ test('review still permits equal-or-stronger explicit models', () => {
|
|
|
68
54
|
assert.equal(spec('review', 'strong'), expected('strong'));
|
|
69
55
|
assert.equal(spec('review', 'ultra'), expected('ultra'));
|
|
70
56
|
});
|
|
71
|
-
test('concrete overrides stay pinned while logical provider/strength overrides remain unpinned', () => {
|
|
72
|
-
const qualified = buildLaunchSpec('developer', 'base', {
|
|
73
|
-
lifecycle: 'terminal', hasManager: true, cwd: home, profileId: null, model: 'anthropic/ultra',
|
|
74
|
-
}).launch;
|
|
75
|
-
const exact = buildLaunchSpec('developer', 'base', {
|
|
76
|
-
lifecycle: 'terminal', hasManager: true, cwd: home, profileId: null, model: 'openai/gpt-5',
|
|
77
|
-
}).launch;
|
|
78
|
-
const bare = buildLaunchSpec('developer', 'base', {
|
|
79
|
-
lifecycle: 'terminal', hasManager: true, cwd: home, profileId: null, model: 'ultra',
|
|
80
|
-
}).launch;
|
|
81
|
-
assert.equal(qualified.modelProviderPinned, false);
|
|
82
|
-
assert.equal(exact.modelProviderPinned, true);
|
|
83
|
-
assert.equal(bare.modelProviderPinned, false);
|
|
84
|
-
});
|
|
85
|
-
test('explore remains cheap by default and may explicitly run light', () => {
|
|
86
|
-
assert.equal(spec('explore'), expected(defaultKindsConfig()['explore'].model));
|
|
87
|
-
assert.equal(spec('explore', 'light'), expected('light'));
|
|
88
|
-
});
|
|
89
57
|
// `node new --model` widened to the full spec grammar (provider/tier, exact
|
|
90
58
|
// provider/id, family alias) alongside the bare tier — the review floor must
|
|
91
59
|
// still hold no matter which shape the requested model arrives in.
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// Run with: node --import tsx/esm --test src/core/__tests__/seam/broker-attach-multiclient.test.ts
|
|
2
|
+
//
|
|
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
|
+
//
|
|
6
|
+
// The MULTI-CLIENT half of the `crtr surface attach` acceptance gate: everything
|
|
7
|
+
// whose contract is "more than one viewer is attached at once". One shared broker
|
|
8
|
+
// serves all four journeys.
|
|
9
|
+
//
|
|
10
|
+
// 3-PART HEADER (headless):
|
|
11
|
+
// (1) CONTRACT — every client that hellos as `controller` is welcomed writable,
|
|
12
|
+
// simultaneously and independently; each writable client may submit, every
|
|
13
|
+
// connected client (both writers plus a read-only observer) receives the
|
|
14
|
+
// merged stream of both submissions and the `model_changed` broadcast, and a
|
|
15
|
+
// client that hellos as `observer` is rejected with error{read_only} when it
|
|
16
|
+
// tries to drive. ONE blocking dialog fans to EVERY writable client, is
|
|
17
|
+
// handed to a writable client attaching MID-dialog via
|
|
18
|
+
// `welcome.pending_dialog`, is settled by the FIRST response with ITS value,
|
|
19
|
+
// dismisses every peer by id, and ignores a late peer answer. An in-place
|
|
20
|
+
// session mutation (`navigate_tree`) re-welcomes every viewer onto the
|
|
21
|
+
// rewound transcript. A late joiner's welcome is COMPLETE: retained display
|
|
22
|
+
// levels ride in `welcome.snapshot.display` and no display edge follows it.
|
|
23
|
+
// (2) WHY BROKER/SOCKET-LEVEL, NOT PANE/WINDOW — role admission, dialog fan-out,
|
|
24
|
+
// re-welcome fan-out and catch-up shape are broker-process and view.sock
|
|
25
|
+
// contracts, exercised over a pure Unix socket using the production
|
|
26
|
+
// ViewSocketClient with a real broker process and no tmux pane or window.
|
|
27
|
+
// (3) HOW THE HEADLESS DRIVE ASSERTS THE CONTRACT — the real socket responses
|
|
28
|
+
// provide the welcome roles, per-writer relay frames, the read-only
|
|
29
|
+
// rejection, the fanned dialog frames plus the value and latency recorded in
|
|
30
|
+
// fake-pi.dialog.jsonl, the second welcome after navigate_tree, and the
|
|
31
|
+
// welcome payload of a late joiner.
|
|
32
|
+
//
|
|
33
|
+
// One shared broker is booted in before() and reused across every test. There is
|
|
34
|
+
// no writer slot to hand off, so each test simply attaches at the role it wants;
|
|
35
|
+
// afterEach closes the prior sockets, which is also what leaves the broker
|
|
36
|
+
// VIEWERLESS for the display catch-up test.
|
|
37
|
+
import { test, before, after, afterEach } from 'node:test';
|
|
38
|
+
import assert from 'node:assert/strict';
|
|
39
|
+
import { createHeadlessHarness } from '../helpers/harness.js';
|
|
40
|
+
import { isPidAlive } from '../../canvas/pid.js';
|
|
41
|
+
import { createAttachKit, delay, tok, frameHas } from '../helpers/broker-clients.js';
|
|
42
|
+
let h;
|
|
43
|
+
let id; // ONE shared broker, reused across every test (1 real boot)
|
|
44
|
+
const kit = createAttachKit(() => h);
|
|
45
|
+
const { attach, attachUntil } = kit;
|
|
46
|
+
before(async () => {
|
|
47
|
+
h = await createHeadlessHarness({ sessionPrefix: 'crtr-brkmulti' });
|
|
48
|
+
const root = h.spawnRoot('broker-attach-multiclient suite root');
|
|
49
|
+
id = await h.spawnHeadlessChild(root, 'headless worker — multi-client gates');
|
|
50
|
+
});
|
|
51
|
+
after(async () => {
|
|
52
|
+
if (h !== undefined)
|
|
53
|
+
await h.dispose();
|
|
54
|
+
});
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
kit.closeAll();
|
|
57
|
+
});
|
|
58
|
+
const brokerPid = () => h.node(id).pi_pid;
|
|
59
|
+
// Attach a writable client. Every `hello` claiming `controller` is admitted
|
|
60
|
+
// writable — attaching a peer never demotes an already-attached one.
|
|
61
|
+
const writer = async (cid) => {
|
|
62
|
+
const a = await attach(id, 'controller', cid);
|
|
63
|
+
assert.equal(a.welcome.role, 'controller', `${cid}: admitted writable, not demoted by an existing peer`);
|
|
64
|
+
return a;
|
|
65
|
+
};
|
|
66
|
+
const observer = async (cid) => {
|
|
67
|
+
const a = await attach(id, 'observer', cid);
|
|
68
|
+
assert.equal(a.welcome.role, 'observer', `${cid}: an observer hello stays read-only`);
|
|
69
|
+
return a;
|
|
70
|
+
};
|
|
71
|
+
// The broker handles one client's frames in order, so an `ack` for a get_commands
|
|
72
|
+
// sent AFTER some other frame proves that earlier frame was already processed.
|
|
73
|
+
// Used to make "a late peer answer changed nothing" a deterministic assertion
|
|
74
|
+
// instead of a sleep.
|
|
75
|
+
const barrier = async (c, label) => {
|
|
76
|
+
c.send({ type: 'get_commands' });
|
|
77
|
+
await c.waitFrame((f) => f.type === 'ack' && f.for === 'get_commands', label);
|
|
78
|
+
};
|
|
79
|
+
const isRequest = (f) => f.type === 'extension_ui_request';
|
|
80
|
+
const dismissFor = (reqId) => (f) => f.type === 'extension_ui_dismiss' && f.id === reqId;
|
|
81
|
+
const welcomes = (frames) => frames.filter((f) => f.type === 'welcome');
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// G4 — multi-writer admission + read-only gating + broadcast fan-out. Guards: two
|
|
84
|
+
// simultaneous `controller` hellos are BOTH welcomed writable, each one's own
|
|
85
|
+
// submission reaches the engine, every connected client sees BOTH turns, an
|
|
86
|
+
// explicit observer that tries to drive is rejected with error{read_only}, and a
|
|
87
|
+
// model switch — for which pi emits no AgentSessionEvent at all — reaches EVERY
|
|
88
|
+
// viewer as a `model_changed` frame rather than only acking the requester
|
|
89
|
+
// (observed 2026-06-11: the status bar kept showing the old model).
|
|
90
|
+
// Failure mode: a singleton writer slot demoting the second client, a writer
|
|
91
|
+
// whose prompt is silently dropped, fan-out that misses a viewer, a read-only
|
|
92
|
+
// client that can drive the engine, or a model switch only its requester hears.
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
test('G4 — two simultaneous clients are both welcomed controller, each submits, all clients see both turns and the model switch; an observer drive → error{read_only}', async () => {
|
|
95
|
+
const [w1, w2] = await Promise.all([
|
|
96
|
+
attach(id, 'controller', 'g4-writer-1'),
|
|
97
|
+
attach(id, 'controller', 'g4-writer-2'),
|
|
98
|
+
]);
|
|
99
|
+
assert.equal(w1.welcome.role, 'controller', 'first client is welcomed writable');
|
|
100
|
+
assert.equal(w2.welcome.role, 'controller', 'second SIMULTANEOUS client is ALSO welcomed writable (no singleton slot)');
|
|
101
|
+
const obs = await attach(id, 'observer', 'g4-observer');
|
|
102
|
+
assert.equal(obs.welcome.role, 'observer', 'an explicit observer hello stays read-only');
|
|
103
|
+
// A read-only client may not drive the engine — the role is per-client and
|
|
104
|
+
// fixed by its own hello, so this rejection is independent of who else is
|
|
105
|
+
// attached.
|
|
106
|
+
obs.send({ type: 'prompt', text: 'a read-only client must not drive' });
|
|
107
|
+
const err = await obs.waitFrame((f) => f.type === 'error', 'G4 observer prompt rejected');
|
|
108
|
+
assert.equal(err.code, 'read_only', 'G4: observer prompt → error{read_only}');
|
|
109
|
+
// Each writer submits its OWN token. Submissions are sequenced (a prompt sent
|
|
110
|
+
// mid-stream routes as a steer and produces no second turn), so each turn is
|
|
111
|
+
// driven to its terminal agent_end before the next writer submits.
|
|
112
|
+
const tokenA = tok('G4-WRITER-1');
|
|
113
|
+
w1.send({ type: 'prompt', text: tokenA });
|
|
114
|
+
await w1.waitFrame((f) => f.type === 'agent_end' && frameHas(f, tokenA), 'G4 writer 1 saw its own turn');
|
|
115
|
+
const tokenB = tok('G4-WRITER-2');
|
|
116
|
+
w2.send({ type: 'prompt', text: tokenB });
|
|
117
|
+
await w2.waitFrame((f) => f.type === 'agent_end' && frameHas(f, tokenB), 'G4 writer 2 saw its own turn');
|
|
118
|
+
// The merged stream: EVERY connected client — both writers and the read-only
|
|
119
|
+
// observer — receives BOTH writers' turns.
|
|
120
|
+
await w2.waitFrame((f) => f.type === 'agent_end' && frameHas(f, tokenA), 'G4 writer 2 ALSO received writer 1 turn');
|
|
121
|
+
await w1.waitFrame((f) => f.type === 'agent_end' && frameHas(f, tokenB), 'G4 writer 1 ALSO received writer 2 turn');
|
|
122
|
+
await obs.waitFrame((f) => f.type === 'agent_end' && frameHas(f, tokenA), 'G4 observer received writer 1 turn');
|
|
123
|
+
await obs.waitFrame((f) => f.type === 'agent_end' && frameHas(f, tokenB), 'G4 observer received writer 2 turn');
|
|
124
|
+
// A model switch has no AgentSessionEvent to ride: the broker mirrors
|
|
125
|
+
// snapshot.state.model to every client itself. Pre-fix only the requester's
|
|
126
|
+
// bare ack existed, so both model_changed waits time out.
|
|
127
|
+
w1.send({ type: 'set_model', model: 'anthropic/fake-fable' });
|
|
128
|
+
const ack = await w1.waitFrame((f) => f.type === 'ack' && f.for === 'set_model', 'G4 set_model ack');
|
|
129
|
+
assert.ok(ack.type === 'ack' && ack.ok, 'G4: set_model acked ok to the requester');
|
|
130
|
+
const cm = await w1.waitFrame((f) => f.type === 'model_changed', 'G4 requester receives model_changed');
|
|
131
|
+
assert.ok(cm.type === 'model_changed' && cm.model?.id === 'fake-fable', 'G4: the requester sees the new model id');
|
|
132
|
+
const om = await obs.waitFrame((f) => f.type === 'model_changed', 'G4 observer receives model_changed');
|
|
133
|
+
assert.ok(om.type === 'model_changed' && om.model?.id === 'fake-fable', 'G4: a read-only observer ALSO hears the switch');
|
|
134
|
+
});
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// G5 — dialog fan-out, mid-dialog handoff, and first-response settlement.
|
|
137
|
+
// Guards: ONE blocking dialog reaches EVERY writable client (never a single
|
|
138
|
+
// owner), a writable client attaching MID-dialog is handed it via
|
|
139
|
+
// welcome.pending_dialog and becomes a full peer in the race to answer, the first
|
|
140
|
+
// answer unblocks the engine with ITS value (not the default), every fanned
|
|
141
|
+
// client gets the correlated extension_ui_dismiss, and a peer answering
|
|
142
|
+
// afterwards is a no-op. An observer is never fanned an answerable dialog.
|
|
143
|
+
// Failure modes: a dialog only one viewer can see (the others sit blind), a
|
|
144
|
+
// late-joining writable viewer blind to an in-flight dialog, peers left holding
|
|
145
|
+
// an overlay nothing can answer, or a late peer answer re-settling a dead dialog.
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
test('G5 — one dialog fans out to every writable client and to a mid-dialog joiner, the first response settles it, all are dismissed, and a late peer response is a no-op', async () => {
|
|
148
|
+
const base = h.dialogResults(id).length;
|
|
149
|
+
const a = await writer('g5-A');
|
|
150
|
+
const b = await writer('g5-B');
|
|
151
|
+
const obs = await observer('g5-obs'); // must never be fanned an answerable dialog
|
|
152
|
+
h.fakeCmd(id, { cmd: 'dialog', timeout: 30_000 }); // generous: a writable client answers first
|
|
153
|
+
const reqA = await a.waitFrame(isRequest, 'G5 dialog fanned out to writable A');
|
|
154
|
+
const reqB = await b.waitFrame(isRequest, 'G5 dialog fanned out to writable B');
|
|
155
|
+
const reqId = reqA.id;
|
|
156
|
+
assert.equal(reqB.id, reqId, 'G5: both writable clients were fanned the SAME dialog id, not two dialogs');
|
|
157
|
+
assert.equal(reqA.method, 'confirm', 'G5: the fanned dialog is the confirm() the engine raised');
|
|
158
|
+
// A writable client attaching MID-dialog is handed the pending dialog in its
|
|
159
|
+
// welcome (retry covers the beat between the raise and this attach); an
|
|
160
|
+
// observer attaching mid-dialog is not.
|
|
161
|
+
assert.equal(obs.welcome.pending_dialog, null, 'G5: an observer welcome never carries a pending dialog');
|
|
162
|
+
const late = await attachUntil(id, 'controller', 'g5-late', (x) => x.welcome.role === 'controller' && x.welcome.pending_dialog != null, 'G5 writable client attaches mid-dialog and is handed the pending dialog');
|
|
163
|
+
assert.equal(late.welcome.pending_dialog.id, reqId, 'G5: welcome.pending_dialog is the SAME dialog the peers hold');
|
|
164
|
+
assert.equal(late.welcome.pending_dialog.method, 'confirm', 'G5: the pending dialog is the confirm()');
|
|
165
|
+
// The mid-dialog joiner answers FIRST — the engine must proceed with its true,
|
|
166
|
+
// not the false default.
|
|
167
|
+
late.send({ type: 'extension_ui_response', id: reqId, confirmed: true });
|
|
168
|
+
await late.waitFrame(dismissFor(reqId), 'G5 answerer receives the correlated dismiss');
|
|
169
|
+
await a.waitFrame(dismissFor(reqId), 'G5 peer A receives the correlated dismiss for the dialog it never answered');
|
|
170
|
+
await b.waitFrame(dismissFor(reqId), 'G5 peer B receives the correlated dismiss for the dialog it never answered');
|
|
171
|
+
const results = await h.waitFor(() => (h.dialogResults(id).length > base ? h.dialogResults(id) : null), { label: 'G5 dialog settled by the first response', timeoutMs: 15_000 });
|
|
172
|
+
assert.equal(results[base].resolved, true, 'G5: the engine proceeded with the first answer (true), not the default (false)');
|
|
173
|
+
// The observer was attached the whole time: it saw the broadcast dismiss but was
|
|
174
|
+
// never handed the answerable request.
|
|
175
|
+
await obs.waitFrame(dismissFor(reqId), 'G5 observer sees the broadcast dismiss (ordering barrier)');
|
|
176
|
+
assert.equal(obs.frames.filter(isRequest).length, 0, 'G5: an observer is never fanned an answerable dialog');
|
|
177
|
+
// B answers the already-settled dialog. The broker deleted the entry on the first
|
|
178
|
+
// response, so this finds nothing and is a silent no-op: no second engine
|
|
179
|
+
// resolution, and the recorded answer stays the first answerer's.
|
|
180
|
+
b.send({ type: 'extension_ui_response', id: reqId, confirmed: false });
|
|
181
|
+
await barrier(b, 'G5 broker processed B late response (ordered ack)');
|
|
182
|
+
const settled = h.dialogResults(id);
|
|
183
|
+
assert.equal(settled.length, base + 1, 'G5: the late peer response resolved nothing — no second dialog entry');
|
|
184
|
+
assert.equal(settled[base].resolved, true, "G5: the late peer response did not overwrite the first answerer's value");
|
|
185
|
+
assert.equal(isPidAlive(brokerPid()), true, 'G5: the late no-op response did not fault the broker');
|
|
186
|
+
});
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
// G9 — re-welcome after an IN-PLACE session mutation (real bug, observed
|
|
189
|
+
// 2026-06-11 in `crtr surface attach`: double-Esc/`/tree` opened the session-tree
|
|
190
|
+
// picker, but SELECTING an entry visibly did nothing). session.navigateTree()
|
|
191
|
+
// rewinds in place — same session file, new leaf, NO relayed AgentSessionEvent —
|
|
192
|
+
// so unlike the session-REPLACING ops (new_session/switch_session/fork →
|
|
193
|
+
// runReplacement → reWelcomeAll) the handler used to only ack, leaving every
|
|
194
|
+
// attached client rendering the abandoned branch and dropping the navigated-to
|
|
195
|
+
// user message's editorText. Failure mode: no second welcome arrives at all, or
|
|
196
|
+
// the re-welcome serves a stale snapshot.
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
test('G9 — navigate_tree re-welcomes every viewer onto the rewound transcript and acks the editor text', async () => {
|
|
199
|
+
const c = await writer('nav-ctrl');
|
|
200
|
+
// Accrue one observable turn so the rewind has something to drop.
|
|
201
|
+
const token = tok('NAV-TURN');
|
|
202
|
+
c.send({ type: 'prompt', text: token });
|
|
203
|
+
await c.waitFrame((f) => f.type === 'agent_end' && frameHas(f, token), 'G9 turn relayed before navigate');
|
|
204
|
+
// The observer attaches AFTER the turn so its first welcome deterministically
|
|
205
|
+
// holds the token (the pre-navigate baseline).
|
|
206
|
+
const o = await attachUntil(id, 'observer', 'nav-obs', (a) => frameHas(a.welcome, token), 'G9 observer welcome holds the pre-navigate turn');
|
|
207
|
+
c.send({ type: 'navigate_tree', targetId: 'entry-x' });
|
|
208
|
+
// The ack carries the navigated-to user message's text in detail (the viewer
|
|
209
|
+
// restores it to the editor). Pre-fix: ack had no detail.
|
|
210
|
+
const ack = await c.waitFrame((f) => f.type === 'ack' && f.for === 'navigate_tree', 'G9 navigate_tree ack');
|
|
211
|
+
assert.ok(ack.type === 'ack' && ack.ok, 'G9: navigate_tree acked ok');
|
|
212
|
+
assert.equal(ack.type === 'ack' ? ack.detail : undefined, 'rewound-to:entry-x', 'G9: ack.detail carries the rewound-to editor text');
|
|
213
|
+
// BOTH viewers get re-welcomed onto the rewound history. Pre-fix: no second
|
|
214
|
+
// welcome ever arrives (navigate_tree only acked).
|
|
215
|
+
await h.waitFor(() => (welcomes(c.frames).length >= 2 ? true : null), { label: 'G9 controller re-welcomed' });
|
|
216
|
+
await h.waitFor(() => (welcomes(o.frames).length >= 2 ? true : null), { label: 'G9 observer re-welcomed' });
|
|
217
|
+
// The re-welcome serves the REWOUND transcript: the fake's navigateTree popped
|
|
218
|
+
// the last accrued turn, so the token present in the observer's first welcome
|
|
219
|
+
// must be gone from its second.
|
|
220
|
+
const [first, second] = welcomes(o.frames);
|
|
221
|
+
assert.ok(frameHas(first, token), 'G9: pre-navigate welcome holds the turn');
|
|
222
|
+
assert.ok(!frameHas(second, token), 'G9: re-welcome serves the rewound transcript (turn dropped)');
|
|
223
|
+
assert.equal(second.snapshot.messages.length, first.snapshot.messages.length - 2, 'G9: re-welcome history is exactly one turn shorter (a turn = user + assistant = 2 tree messages)');
|
|
224
|
+
});
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// G10 — catch-up is ONE atomic frame. `ctx.ui.setStatus/setWidget/setTitle` are
|
|
227
|
+
// LEVELS with no pi getter, so the broker is their only holder; it ships that
|
|
228
|
+
// held state as `welcome.snapshot.display` and sends NO display edge after the
|
|
229
|
+
// welcome, so a late joiner renders complete chrome on its FIRST paint. Painting
|
|
230
|
+
// here happens with ZERO viewers attached (afterEach closed the previous test's
|
|
231
|
+
// clients), so only retention can carry it. Pre-2026-07-24 `sendWelcome`
|
|
232
|
+
// re-emitted N synthetic display frames after the snapshot: catch-up was
|
|
233
|
+
// non-atomic and the viewer had to clear-and-rebuild, painting blank chrome in
|
|
234
|
+
// the gap. Both halves of that cut fail this test.
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
test('G10 — retained extension display state rides IN welcome.snapshot.display, and no display edge follows the welcome', async () => {
|
|
237
|
+
const DISPLAY_METHODS = new Set(['setStatus', 'setWidget', 'setTitle']);
|
|
238
|
+
const isDisplayFrame = (f) => f.type === 'extension_ui_request' && f.method !== undefined && DISPLAY_METHODS.has(f.method);
|
|
239
|
+
h.fakeCmd(id, { cmd: 'display', text: 'crtr-brief', reason: 'painted while detached' });
|
|
240
|
+
const a = await attachUntil(id, 'observer', 'g10-late-joiner', (at) => at.welcome.snapshot.display?.statuses['crtr-brief'] !== undefined, 'G10 welcome.snapshot.display carries the retained status');
|
|
241
|
+
const display = a.welcome.snapshot.display;
|
|
242
|
+
assert.equal(display.statuses['crtr-brief'], 'painted while detached', 'G10: setStatus level survived in the snapshot');
|
|
243
|
+
assert.deepEqual(display.widgets['crtr-brief'], { lines: ['painted while detached'], placement: 'aboveEditor' }, 'G10: setWidget level survived in the snapshot, placement included');
|
|
244
|
+
assert.equal(display.title, 'painted while detached', 'G10: setTitle level survived in the snapshot');
|
|
245
|
+
// Atomicity: the welcome is COMPLETE. Give the broker a generous beat to emit a
|
|
246
|
+
// replay it must not emit, then assert nothing display-shaped arrived at all.
|
|
247
|
+
await delay(500);
|
|
248
|
+
assert.deepEqual(a.frames.filter((f) => isDisplayFrame(f)).map((f) => f.method), [], 'G10: catch-up is the welcome alone — no synthetic display edges replayed after it');
|
|
249
|
+
// A LIVE paint still arrives as an edge frame: the snapshot replaces the replay,
|
|
250
|
+
// not the ongoing relay.
|
|
251
|
+
h.fakeCmd(id, { cmd: 'display', text: 'crtr-live', reason: 'painted while attached' });
|
|
252
|
+
const live = (await a.waitFrame((f) => f.type === 'extension_ui_request' && f.statusKey === 'crtr-live', 'G10 a live setStatus still relays as an extension_ui_request edge'));
|
|
253
|
+
assert.equal(live.method, 'setStatus');
|
|
254
|
+
assert.equal(live.statusText, 'painted while attached');
|
|
255
|
+
});
|