@north-light/crouter 0.3.173 → 0.3.175
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/memory/write.js +13 -12
- 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,2101 +1,68 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import test from 'node:test';
|
|
3
3
|
import { visibleWidth } from '@earendil-works/pi-tui';
|
|
4
|
-
import { createCrtrBashToolDefinition, isCrtrBashCommand, isCrtrHelpInvocation,
|
|
5
|
-
import { HELP_ICON, iconForPath } from '../../../core/preview-registry.js';
|
|
4
|
+
import { createCrtrBashToolDefinition, isCrtrBashCommand, isCrtrHelpInvocation, } from '../render/crtr-output.js';
|
|
6
5
|
const theme = {
|
|
7
6
|
// `text` and `dim` are the preview's base hierarchy roles; leave them
|
|
8
7
|
// transparent here so assertions can still match the underlying receipt.
|
|
9
|
-
// Semantic colors remain tagged and are asserted where state matters.
|
|
10
8
|
fg(name, text) { return name === 'text' || name === 'dim' ? text : `<${name}>${text}</${name}>`; },
|
|
11
9
|
bg(_name, text) { return text; },
|
|
12
10
|
bold(text) { return `<b>${text}</b>`; },
|
|
13
11
|
};
|
|
14
|
-
const plainTheme = {
|
|
15
|
-
fg(_name, text) { return text; },
|
|
16
|
-
bg(_name, text) { return text; },
|
|
17
|
-
bold(text) { return text; },
|
|
18
|
-
};
|
|
19
12
|
/** pi calls tool renderers with a shared state bag; a normal (unfolded) call has an empty one. */
|
|
20
|
-
function
|
|
21
|
-
const
|
|
22
|
-
return
|
|
13
|
+
function renderCall(command) {
|
|
14
|
+
const definition = createCrtrBashToolDefinition({ command });
|
|
15
|
+
return definition.renderCall({ command }, theme, { state: {} }).render(80).join('\n');
|
|
23
16
|
}
|
|
24
|
-
function
|
|
25
|
-
|
|
17
|
+
function renderResult(command, text, expanded) {
|
|
18
|
+
const definition = createCrtrBashToolDefinition({ command });
|
|
19
|
+
return definition.renderResult({ content: [{ type: 'text', text }] }, { expanded }, theme, {}).render(70);
|
|
26
20
|
}
|
|
27
21
|
test('detects crtr in bash command position, not heredoc body prose', () => {
|
|
28
22
|
assert.equal(isCrtrBashCommand('crtr node new -h'), true);
|
|
29
23
|
assert.equal(isCrtrBashCommand('crtr node inspect snapshot --node abc'), true);
|
|
30
|
-
assert.equal(isCrtrBashCommand('
|
|
31
|
-
assert.equal(isCrtrBashCommand('npm test && /usr/local/bin/crouter surface web serve'), true);
|
|
24
|
+
assert.equal(isCrtrBashCommand('npm test && /usr/local/bin/crouter surface attach to node-1'), true);
|
|
32
25
|
assert.equal(isCrtrBashCommand("cat <<'EOF'\nask about crtr node new\nEOF\necho done"), false);
|
|
33
26
|
assert.equal(isCrtrBashCommand('echo crtr node new'), false);
|
|
34
27
|
});
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
assert.match(rendered, new RegExp(iconForPath(['canvas', 'dashboard'])));
|
|
42
|
-
assert.match(rendered, /crtr/);
|
|
43
|
-
assert.match(rendered, /canvas dashboard/);
|
|
44
|
-
assert.doesNotMatch(rendered, /◆/);
|
|
45
|
-
assert.match(def.renderResult({ content: [{ type: 'text', text: '<command name="canvas">' }], details: undefined }, { expanded: true }, theme, {}).render(80).join('\n'), /<accent><b><command/);
|
|
46
|
-
});
|
|
47
|
-
test('styles crtr structural output and status glyphs', () => {
|
|
48
|
-
const rendered = renderCrtrOutputText('<command name="node">\nInput\n● active\nCommand exited with code 1', theme);
|
|
49
|
-
assert.match(rendered, /<accent><b><command/);
|
|
50
|
-
assert.match(rendered, /<toolTitle><b>Input/);
|
|
51
|
-
assert.match(rendered, /<success>● active/);
|
|
52
|
-
assert.match(rendered, /<error>Command exited/);
|
|
53
|
-
});
|
|
54
|
-
test('crtr results do not invalidate their owning tool component', () => {
|
|
55
|
-
let ownerInvalidations = 0;
|
|
56
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
|
|
57
|
-
const component = def.renderResult({ content: [{ type: 'text', text: 'dashboard' }] }, { expanded: false }, theme, { invalidate: () => { ownerInvalidations++; } });
|
|
58
|
-
component.invalidate?.();
|
|
59
|
-
assert.equal(ownerInvalidations, 0);
|
|
60
|
-
});
|
|
61
|
-
// Help output is structural schema text, never a command result, so the
|
|
62
|
-
// viewer detects it from the invocation itself and gives it help-specific
|
|
63
|
-
// collapsed chrome rather than treating it as a normal result preview.
|
|
64
|
-
test('detects a bare -h/--help token in a crtr invocation', () => {
|
|
28
|
+
// Help output is structural schema text, never a command result, so the viewer
|
|
29
|
+
// detects it from the invocation itself. Detection is scoped to the extracted
|
|
30
|
+
// crtr segment's own tokens, with quote boundaries respected and outer shell
|
|
31
|
+
// redirects treated as plumbing — so unrelated siblings, quoted prompt text,
|
|
32
|
+
// and redirect targets neither trip help chrome nor leak into the shown path.
|
|
33
|
+
test('scopes -h/--help detection and the displayed path to the crtr invocation\'s own argv', () => {
|
|
65
34
|
assert.equal(isCrtrHelpInvocation('crtr node new -h'), true);
|
|
66
35
|
assert.equal(isCrtrHelpInvocation('crtr node inspect snapshot --node abc --help'), true);
|
|
67
36
|
assert.equal(isCrtrHelpInvocation('crtr canvas dashboard'), false);
|
|
68
37
|
assert.equal(isCrtrHelpInvocation('echo crtr node new -h'), false);
|
|
69
|
-
});
|
|
70
|
-
// Help-token detection is scoped to the extracted crtr segment's own tokens,
|
|
71
|
-
// with quote boundaries respected, so unrelated shell siblings and quoted
|
|
72
|
-
// prompt text do not affect the crtr preview.
|
|
73
|
-
test('does not misclassify -h/--help outside the crtr invocation\'s own argv as help', () => {
|
|
74
38
|
assert.equal(isCrtrHelpInvocation('echo -h && crtr canvas snapshot'), false);
|
|
75
39
|
assert.equal(isCrtrHelpInvocation('crtr canvas snapshot && echo --help'), false);
|
|
76
40
|
assert.equal(isCrtrHelpInvocation('crtr node new "write about -h flags"'), false);
|
|
77
|
-
// Sanity: the real thing one token over still trips it.
|
|
78
|
-
assert.equal(isCrtrHelpInvocation('crtr node new -h'), true);
|
|
79
|
-
assert.equal(isCrtrHelpInvocation('crtr node new --help'), true);
|
|
80
|
-
});
|
|
81
|
-
// Realistic leaf schema (renderLeafArgv shape: `name: summary.` then an
|
|
82
|
-
// Input-headed param block) — a stand-in for e.g. `crtr canvas dashboard -h`.
|
|
83
|
-
const LEAF_SCHEMA = 'canvas dashboard: render the canvas as an ASCII subscription tree.\n\n' +
|
|
84
|
-
'Input\n' +
|
|
85
|
-
' --root ROOT string, optional. Node id to use as the tree root.\n\n' +
|
|
86
|
-
'Output (fields carried in the rendered result)\n' +
|
|
87
|
-
' tree string. Multi-line ASCII tree.\n\n' +
|
|
88
|
-
'Effects\n' +
|
|
89
|
-
' Read-only: queries canvas.db.';
|
|
90
|
-
// Realistic branch schema (renderBranch shape: one <command description=...>
|
|
91
|
-
// card with self-closing <subcommand .../> rows) — a stand-in for e.g.
|
|
92
|
-
// `crtr node inspect -h`.
|
|
93
|
-
const BRANCH_SCHEMA = '<command name="node inspect" description="read the canvas graph">\n' +
|
|
94
|
-
'<subcommand name="list" description="list nodes on the canvas" whenToUse="a very long rubric that is not part of the preview at all"/>\n\n' +
|
|
95
|
-
'<subcommand name="show" description="show a node + its spine neighbors" whenToUse="another long rubric"/>\n' +
|
|
96
|
-
'</command>';
|
|
97
|
-
test('collapsed help preview never parrots the call header or dumps the raw schema, and stays within the line cap', () => {
|
|
98
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard -h' });
|
|
99
|
-
const collapsed = def
|
|
100
|
-
.renderResult({ content: [{ type: 'text', text: LEAF_SCHEMA }] }, { expanded: false }, theme, {})
|
|
101
|
-
.render(80)
|
|
102
|
-
.join('\n');
|
|
103
|
-
// The call header already carries the help glyph header; the collapsed body is
|
|
104
|
-
// JUST the synthesized preview content, no repeated status/help label.
|
|
105
|
-
assert.doesNotMatch(collapsed, new RegExp(`${HELP_ICON} crtr help`));
|
|
106
|
-
assert.match(collapsed, /ctrl\+o to expand/);
|
|
107
|
-
assert.doesNotMatch(collapsed, /<success>✓ crtr done/);
|
|
108
|
-
// No raw schema dump leaking into the body.
|
|
109
|
-
assert.doesNotMatch(collapsed, /<command/);
|
|
110
|
-
// A useful synthesized slice IS present: the leaf's own summary line.
|
|
111
|
-
assert.match(collapsed, /render the canvas as an ASCII subscription/);
|
|
112
|
-
assert.match(collapsed, /Params: --root/);
|
|
113
|
-
assert.ok(collapsed.split('\n').length <= 20, `expected <=20 lines, got ${collapsed.split('\n').length}`);
|
|
114
|
-
// Full schema remains readable on demand.
|
|
115
|
-
const expanded = def
|
|
116
|
-
.renderResult({ content: [{ type: 'text', text: LEAF_SCHEMA }] }, { expanded: true }, theme, {})
|
|
117
|
-
.render(80)
|
|
118
|
-
.join('\n');
|
|
119
|
-
assert.match(expanded, /Node id to use as the tree root/);
|
|
120
|
-
assert.match(expanded, /Read-only: queries canvas\.db/);
|
|
121
|
-
});
|
|
122
|
-
test('collapsed branch/root help synthesizes a subcommand/description preview, not the raw <subcommand> tags', () => {
|
|
123
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr node inspect -h' });
|
|
124
|
-
const collapsed = def
|
|
125
|
-
.renderResult({ content: [{ type: 'text', text: BRANCH_SCHEMA }] }, { expanded: false }, theme, {})
|
|
126
|
-
.render(80)
|
|
127
|
-
.join('\n');
|
|
128
|
-
assert.doesNotMatch(collapsed, /<command/);
|
|
129
|
-
assert.doesNotMatch(collapsed, /<subcommand/);
|
|
130
|
-
assert.doesNotMatch(collapsed, /whenToUse/);
|
|
131
|
-
// Useful preview: branch description plus name — description subcommand rows.
|
|
132
|
-
assert.match(collapsed, /read the canvas graph/);
|
|
133
|
-
assert.match(collapsed, /list.*—.*list nodes on the canvas/s);
|
|
134
|
-
assert.match(collapsed, /show.*—.*show a node \+ its spine neighbors/s);
|
|
135
|
-
assert.ok(collapsed.split('\n').length <= 20, `expected <=20 lines, got ${collapsed.split('\n').length}`);
|
|
136
|
-
});
|
|
137
|
-
// Realistic root schema (renderRoot shape: a tagline, then one
|
|
138
|
-
// `<command name="X">\n<concept>\nuse when ...` block per subtree, NO
|
|
139
|
-
// description attribute on the tag) — a stand-in for `crtr -h`.
|
|
140
|
-
const ROOT_SCHEMA = 'crtr: agentic runtime.\n\n' +
|
|
141
|
-
'<command name="memory">\n' +
|
|
142
|
-
'a memory document you read on demand\n' +
|
|
143
|
-
'use when a task matches stored knowledge or a preference\n' +
|
|
144
|
-
'</command>\n\n' +
|
|
145
|
-
'<command name="node">\n' +
|
|
146
|
-
'a unit of the agent runtime\n' +
|
|
147
|
-
'use when delegating work\n' +
|
|
148
|
-
'</command>\n\n' +
|
|
149
|
-
'Globals\n' +
|
|
150
|
-
' -h print help\n\n' +
|
|
151
|
-
'I/O contract: ...';
|
|
152
|
-
test('collapsed root help synthesizes a tagline + name/concept preview, not the raw <command> blocks', () => {
|
|
153
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr -h' });
|
|
154
|
-
const collapsed = def
|
|
155
|
-
.renderResult({ content: [{ type: 'text', text: ROOT_SCHEMA }] }, { expanded: false }, theme, {})
|
|
156
|
-
.render(80)
|
|
157
|
-
.join('\n');
|
|
158
|
-
assert.doesNotMatch(collapsed, /<command/);
|
|
159
|
-
assert.match(collapsed, /crtr: agentic runtime/);
|
|
160
|
-
assert.match(collapsed, /memory.*—.*a memory document you read on demand/s);
|
|
161
|
-
assert.match(collapsed, /node.*—.*a unit of the agent runtime/s);
|
|
162
|
-
assert.ok(collapsed.split('\n').length <= 20, `expected <=20 lines, got ${collapsed.split('\n').length}`);
|
|
163
|
-
});
|
|
164
|
-
test('a branch help preview with more subcommands than the line cap truncates with an expand hint, never mid-tag', () => {
|
|
165
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr node -h' });
|
|
166
|
-
const manySubs = Array.from({ length: 30 }, (_, i) => `<subcommand name="sub${i}" description="description for subcommand ${i}" whenToUse="long rubric ${i}"/>`).join('\n\n');
|
|
167
|
-
const bigBranchSchema = `<command name="node" description="spawn and navigate canvas nodes">\n${manySubs}\n</command>`;
|
|
168
|
-
const collapsed = def
|
|
169
|
-
.renderResult({ content: [{ type: 'text', text: bigBranchSchema }] }, { expanded: false }, theme, {})
|
|
170
|
-
.render(80)
|
|
171
|
-
.join('\n');
|
|
172
|
-
assert.doesNotMatch(collapsed, /<subcommand/);
|
|
173
|
-
assert.match(collapsed, /ctrl\+o to expand/);
|
|
174
|
-
assert.ok(collapsed.split('\n').length <= 20, `expected <=20 lines, got ${collapsed.split('\n').length}`);
|
|
175
|
-
});
|
|
176
|
-
// Contract: outer bash redirects are shell plumbing, not crtr argv, so the
|
|
177
|
-
// displayed crtr path and help detection include only the command segment.
|
|
178
|
-
test('a shell redirect on the outer bash call never leaks into the displayed crtr path or help detection', () => {
|
|
179
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr push -h 2>&1' });
|
|
180
|
-
const rendered = renderCrtrCall(def, { command: 'crtr push -h 2>&1' }).render(80).join('\n');
|
|
181
|
-
assert.match(rendered, new RegExp(HELP_ICON));
|
|
182
|
-
assert.match(rendered, /crtr help/);
|
|
183
|
-
assert.match(rendered, /<b>push<\/b>/);
|
|
184
|
-
assert.doesNotMatch(rendered, /2>/);
|
|
185
|
-
assert.doesNotMatch(rendered, />&1/);
|
|
186
41
|
assert.equal(isCrtrHelpInvocation('crtr push -h 2>&1'), true);
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const noSpace = createCrtrBashToolDefinition({ command: 'crtr push -h 2>/tmp/out' });
|
|
196
|
-
const noSpaceRendered = renderCrtrCall(noSpace, { command: 'crtr push -h 2>/tmp/out' }).render(80).join('\n');
|
|
197
|
-
assert.match(noSpaceRendered, /<b>push<\/b>/);
|
|
198
|
-
assert.doesNotMatch(noSpaceRendered, /2>/);
|
|
199
|
-
assert.doesNotMatch(noSpaceRendered, /tmp\/out/);
|
|
200
|
-
const noSpaceMid = createCrtrBashToolDefinition({ command: 'crtr node new --name foo >/tmp/log' });
|
|
201
|
-
const noSpaceMidRendered = renderCrtrCall(noSpaceMid, { command: 'crtr node new --name foo >/tmp/log' }).render(80).join('\n');
|
|
202
|
-
assert.match(noSpaceMidRendered, /node new --name foo/);
|
|
203
|
-
assert.doesNotMatch(noSpaceMidRendered, /tmp\/log/);
|
|
204
|
-
});
|
|
205
|
-
// Contract: the call header owns the help glyph label; the collapsed result
|
|
206
|
-
// body contains only synthesized preview content, never a repeated header.
|
|
207
|
-
test('a collapsed help card never shows the help label twice (header + body)', () => {
|
|
208
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard -h' });
|
|
209
|
-
const header = renderCrtrCall(def, { command: 'crtr canvas dashboard -h' }).render(80).join('\n');
|
|
210
|
-
const body = def
|
|
211
|
-
.renderResult({ content: [{ type: 'text', text: LEAF_SCHEMA }] }, { expanded: false }, theme, {})
|
|
212
|
-
.render(80)
|
|
213
|
-
.join('\n');
|
|
214
|
-
const helpLabel = new RegExp(`${HELP_ICON} crtr help`, 'g');
|
|
215
|
-
const headerLabelCount = (header.match(helpLabel) ?? []).length;
|
|
216
|
-
const bodyLabelCount = (body.match(helpLabel) ?? []).length;
|
|
217
|
-
assert.equal(headerLabelCount, 1);
|
|
218
|
-
assert.equal(bodyLabelCount, 0);
|
|
219
|
-
});
|
|
220
|
-
// REQUIREMENT: expanded (ctrl+o) is raw truth — the exact invoked command
|
|
221
|
-
// (redirects and all) plus the exact, unsummarized output, for both help and
|
|
222
|
-
// normal commands.
|
|
223
|
-
test('expanded view shows the original raw command (redirects included) plus the raw output', () => {
|
|
224
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr push -h 2>&1' });
|
|
225
|
-
const expanded = def
|
|
226
|
-
.renderResult({ content: [{ type: 'text', text: LEAF_SCHEMA }] }, { expanded: true }, theme, {})
|
|
227
|
-
.render(80)
|
|
228
|
-
.join('\n');
|
|
229
|
-
assert.match(expanded, /\$ crtr push -h 2>&1/);
|
|
230
|
-
assert.match(expanded, /render the canvas as an ASCII subscription/);
|
|
231
|
-
assert.match(expanded, /Read-only: queries canvas\.db/);
|
|
232
|
-
// Normal (non-help) command: expanded still carries the raw invocation.
|
|
233
|
-
const normalDef = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
|
|
234
|
-
const normalExpanded = normalDef
|
|
235
|
-
.renderResult({ content: [{ type: 'text', text: 'a normal-sized dashboard body' }] }, { expanded: true }, theme, {})
|
|
236
|
-
.render(80)
|
|
237
|
-
.join('\n');
|
|
238
|
-
assert.match(normalExpanded, /\$ crtr canvas dashboard/);
|
|
239
|
-
assert.match(normalExpanded, /a normal-sized dashboard body/);
|
|
240
|
-
});
|
|
241
|
-
test('a running or failed crtr -h invocation never gets the muted help status', () => {
|
|
242
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr node new -h' });
|
|
243
|
-
const running = def
|
|
244
|
-
.renderResult({ content: [{ type: 'text', text: '' }] }, { expanded: false, isPartial: true }, theme, {})
|
|
245
|
-
.render(80)
|
|
246
|
-
.join('\n');
|
|
247
|
-
assert.match(running, /<warning>⟳ crtr running<\/warning>/);
|
|
248
|
-
});
|
|
249
|
-
// BUG regression (Silas, 2026-07-01): a SUCCESSFUL normal crtr call showed a
|
|
250
|
-
// "✓ crtr done" body status line — pure noise, since the header already names
|
|
251
|
-
// the command and the output is right there. Success (help or normal) carries
|
|
252
|
-
// no body status; only running/failed do.
|
|
253
|
-
test('a successful crtr call shows no "crtr done" body status line', () => {
|
|
254
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr push update' });
|
|
255
|
-
const collapsed = def
|
|
256
|
-
.renderResult({ content: [{ type: 'text', text: 'some push output' }] }, { expanded: false }, theme, {})
|
|
257
|
-
.render(80)
|
|
258
|
-
.join('\n');
|
|
259
|
-
assert.doesNotMatch(collapsed, /crtr done/);
|
|
260
|
-
assert.match(collapsed, /some push output/);
|
|
261
|
-
const expanded = def
|
|
262
|
-
.renderResult({ content: [{ type: 'text', text: 'some push output' }] }, { expanded: true }, theme, {})
|
|
263
|
-
.render(80)
|
|
264
|
-
.join('\n');
|
|
265
|
-
assert.doesNotMatch(expanded, /crtr done/);
|
|
266
|
-
});
|
|
267
|
-
// Command-owned preview: a leaf declares `help.preview.suppressOutput` next
|
|
268
|
-
// to its own definition (e.g. canvas snapshot, node inspect snapshot —
|
|
269
|
-
// machine-readable dumps, not agent glances). The viewer reads this
|
|
270
|
-
// synchronously from the bundle-safe static registry (preview-registry.ts,
|
|
271
|
-
// kept honest by preview-registry-sync.test.ts) keyed by the real leaf path.
|
|
272
|
-
test('command-owned suppressOutput collapses a successful result, never on failure', () => {
|
|
273
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr canvas snapshot' });
|
|
274
|
-
const component = def.renderResult({ content: [{ type: 'text', text: '{"nodes":[/* huge */]}' }] }, { expanded: false }, theme, {});
|
|
275
|
-
const collapsed = component.render(80).join('\n');
|
|
276
|
-
// A successful call shows no body status line at all (✓ crtr done is noise).
|
|
277
|
-
assert.doesNotMatch(collapsed, /crtr done/);
|
|
278
|
-
assert.doesNotMatch(collapsed, /huge/);
|
|
279
|
-
// A failed run is never collapsed, even when the leaf opts into suppressOutput.
|
|
280
|
-
const failedComponent = def.renderResult({ content: [{ type: 'text', text: 'huge dump on failure too' }] }, { expanded: false }, theme, { isError: true });
|
|
281
|
-
assert.match(failedComponent.render(80).join('\n'), /huge dump on failure too/);
|
|
282
|
-
});
|
|
283
|
-
test('command-owned suppressOutput matches the leaf name even with positional args after it', () => {
|
|
284
|
-
// node inspect snapshot takes a required positional id; the lookup must
|
|
285
|
-
// match the leaf-name prefix and ignore the argument that follows it.
|
|
286
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr node inspect snapshot abc' });
|
|
287
|
-
const component = def.renderResult({ content: [{ type: 'text', text: '{"snapshot":{/* huge */}}' }] }, { expanded: false }, theme, {});
|
|
288
|
-
const collapsed = component.render(80).join('\n');
|
|
289
|
-
assert.doesNotMatch(collapsed, /crtr done/);
|
|
290
|
-
assert.doesNotMatch(collapsed, /huge/);
|
|
291
|
-
});
|
|
292
|
-
// Command-owned preview (Silas, 2026-07-01, option C): a `crtr memory read`
|
|
293
|
-
// collapsed card is a tidy frontmatter glance — `name · kind · scope` identity
|
|
294
|
-
// line (path dropped as noise, follow_up hint dropped) + a short content
|
|
295
|
-
// teaser — not the full doc body, which ctrl+o reveals.
|
|
296
|
-
const MEMORY_READ_OUTPUT = [
|
|
297
|
-
'- name: no-flag-and-defer',
|
|
298
|
-
'- kind: preference',
|
|
299
|
-
'- scope: user',
|
|
300
|
-
'- path: /Users/silasrhyneer/.crouter/memory/no-flag-and-defer.md',
|
|
301
|
-
'',
|
|
302
|
-
'**content:** Fix issues in the same pass you find them — never "flagged for later".',
|
|
303
|
-
'',
|
|
304
|
-
'',
|
|
305
|
-
'**follow_up:** Use --frontmatter on this same command to inspect the YAML frontmatter, or edit `path` directly for a body-only tweak. Browse the inventory with `crtr memory list`.',
|
|
306
|
-
].join('\n');
|
|
307
|
-
test('a crtr memory read card previews a tidy frontmatter glance, not the raw block or body dump', () => {
|
|
308
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr memory read no-flag-and-defer' });
|
|
309
|
-
const collapsed = def
|
|
310
|
-
.renderResult({ content: [{ type: 'text', text: MEMORY_READ_OUTPUT }] }, { expanded: false }, theme, {})
|
|
311
|
-
.render(120)
|
|
312
|
-
.join('\n');
|
|
313
|
-
// Curated frontmatter identity line: name · kind · scope, path/follow_up gone.
|
|
314
|
-
assert.match(collapsed, /no-flag-and-defer · preference · user/);
|
|
315
|
-
assert.doesNotMatch(collapsed, /\/Users\//);
|
|
316
|
-
assert.doesNotMatch(collapsed, /follow_up/);
|
|
317
|
-
assert.doesNotMatch(collapsed, /Use --frontmatter/);
|
|
318
|
-
// A slice of the content is teased, and the raw `**content:**` marker is gone.
|
|
319
|
-
assert.match(collapsed, /Fix issues in the same pass/);
|
|
320
|
-
assert.doesNotMatch(collapsed, /\*\*content:\*\*/);
|
|
321
|
-
// No spurious success status line.
|
|
322
|
-
assert.doesNotMatch(collapsed, /crtr done/);
|
|
323
|
-
assert.match(collapsed, /ctrl\+o to expand/);
|
|
324
|
-
// A long doc body is teased, then clipped with an ellipsis — never dumped whole.
|
|
325
|
-
const longOutput = [
|
|
326
|
-
'- name: pi-frontmatter-rules',
|
|
327
|
-
'- kind: knowledge',
|
|
328
|
-
'- scope: user',
|
|
329
|
-
'- path: /x/pi-frontmatter-rules.md',
|
|
330
|
-
'',
|
|
331
|
-
'**content:** # Title',
|
|
332
|
-
'line one',
|
|
333
|
-
'line two',
|
|
334
|
-
'line three',
|
|
335
|
-
'line four',
|
|
336
|
-
'line five',
|
|
337
|
-
].join('\n');
|
|
338
|
-
const longCollapsed = def
|
|
339
|
-
.renderResult({ content: [{ type: 'text', text: longOutput }] }, { expanded: false }, theme, {})
|
|
340
|
-
.render(120)
|
|
341
|
-
.join('\n');
|
|
342
|
-
assert.match(longCollapsed, /pi-frontmatter-rules · knowledge · user/);
|
|
343
|
-
assert.match(longCollapsed, /…/);
|
|
344
|
-
assert.doesNotMatch(longCollapsed, /line five/);
|
|
345
|
-
});
|
|
346
|
-
// REQUIREMENT: the memory read summarizer must return null (not a blank
|
|
347
|
-
// summary) when its expected metadata/content markers are absent — an error
|
|
348
|
-
// result (missing/ambiguous doc name, etc.) has none of the `- name:`/
|
|
349
|
-
// `**content:**` shape, so it must fall back to the raw output.
|
|
350
|
-
test('a crtr memory read error output falls back to raw, never a blank summary card', () => {
|
|
351
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr memory read nonexistent-doc' });
|
|
352
|
-
const collapsed = def
|
|
353
|
-
.renderResult({ content: [{ type: 'text', text: 'crtr: memory read: no document found matching "nonexistent-doc"' }] }, { expanded: false }, theme, { isError: true })
|
|
354
|
-
.render(120)
|
|
355
|
-
.join('\n');
|
|
356
|
-
assert.match(collapsed, /no document found matching/);
|
|
357
|
-
});
|
|
358
|
-
test('an expanded crtr memory read shows the full raw output, not the frontmatter glance', () => {
|
|
359
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr memory read no-flag-and-defer' });
|
|
360
|
-
const expanded = def
|
|
361
|
-
.renderResult({ content: [{ type: 'text', text: MEMORY_READ_OUTPUT }] }, { expanded: true }, theme, {})
|
|
362
|
-
.render(120)
|
|
363
|
-
.join('\n');
|
|
364
|
-
// Raw truth: the path, the content marker, and the follow_up hint are all present.
|
|
365
|
-
assert.match(expanded, /\$ crtr memory read no-flag-and-defer/);
|
|
366
|
-
assert.match(expanded, /\.crouter\/memory\/no-flag-and-defer\.md/);
|
|
367
|
-
assert.match(expanded, /\*\*follow_up:\*\*/);
|
|
368
|
-
});
|
|
369
|
-
const MEMORY_LINT_CLEAN_OUTPUT = [
|
|
370
|
-
'- checked: 261',
|
|
371
|
-
'- corpora: {"memory_stores":261}',
|
|
372
|
-
'',
|
|
373
|
-
'Corpus green — zero invalid frontmatter docs.',
|
|
374
|
-
'',
|
|
375
|
-
'0 findings.',
|
|
376
|
-
].join('\n');
|
|
377
|
-
const MEMORY_LINT_FINDINGS_OUTPUT = [
|
|
378
|
-
'crtr: memory lint: /tmp/lint-test/.crouter/memory/bad-doc.md: invalid kind: "bogus" (expected knowledge|preference)',
|
|
379
|
-
'crtr: memory lint: /tmp/lint-test/.crouter/memory/bad-doc2.md: missing `when-and-why-to-read`: one read-routing line',
|
|
380
|
-
'<error code="error">',
|
|
381
|
-
'memory lint: 2 finding(s) across 229 files',
|
|
382
|
-
'Next: Fix each doc (quote YAML values containing `: `; use a valid kind/rung/gate), then re-run `crtr memory lint`.',
|
|
383
|
-
'</error>',
|
|
384
|
-
].join('\n');
|
|
385
|
-
test('a clean crtr memory lint collapses to PASS and docs checked, with semantic tones', () => {
|
|
386
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr memory lint' });
|
|
387
|
-
const collapsed = def
|
|
388
|
-
.renderResult({ content: [{ type: 'text', text: MEMORY_LINT_CLEAN_OUTPUT }] }, { expanded: false }, theme, {})
|
|
389
|
-
.render(120)
|
|
390
|
-
.join('\n');
|
|
391
|
-
assert.match(collapsed, /<success>PASS<\/success>/);
|
|
392
|
-
assert.match(collapsed, /<muted>261 docs checked<\/muted>/);
|
|
393
|
-
assert.doesNotMatch(collapsed, /- checked:/);
|
|
394
|
-
assert.doesNotMatch(collapsed, /- corpora:/);
|
|
395
|
-
assert.doesNotMatch(collapsed, /0 findings\./);
|
|
396
|
-
assert.match(collapsed, /ctrl\+o to expand/);
|
|
397
|
-
const expanded = def
|
|
398
|
-
.renderResult({ content: [{ type: 'text', text: MEMORY_LINT_CLEAN_OUTPUT }] }, { expanded: true }, theme, {})
|
|
399
|
-
.render(120)
|
|
400
|
-
.join('\n');
|
|
401
|
-
assert.match(expanded, /checked: 261/);
|
|
402
|
-
assert.match(expanded, /corpora: \{"memory_stores":261\}/);
|
|
403
|
-
assert.match(expanded, /0 findings\./);
|
|
404
|
-
});
|
|
405
|
-
// REQUIREMENT: a real failed `crtr memory lint` (delivered to the renderer as
|
|
406
|
-
// `{ isError: true }` — see render/chat-view.ts) must still collapse to the
|
|
407
|
-
// command-owned findings summary, not fall through to the raw failed output;
|
|
408
|
-
// only a command with no summarizer (or a shape neither summarizer branch
|
|
409
|
-
// recognizes) stays raw on failure.
|
|
410
|
-
test('a crtr memory lint with findings collapses to a findings count and warnings even on the real failed-command path', () => {
|
|
411
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr memory lint' });
|
|
412
|
-
const collapsed = def
|
|
413
|
-
.renderResult({ content: [{ type: 'text', text: MEMORY_LINT_FINDINGS_OUTPUT }] }, { expanded: false }, theme, { isError: true })
|
|
414
|
-
.render(120)
|
|
415
|
-
.join('\n');
|
|
416
|
-
assert.match(collapsed, /<error>2 findings<\/error>/);
|
|
417
|
-
assert.match(collapsed, /<warning>[^<]*invalid kind: "bogus"[^<]*<\/warning>/);
|
|
418
|
-
assert.match(collapsed, /<warning>[^<]*missing `when-and-why-to-read`[^<]*<\/warning>/);
|
|
419
|
-
assert.match(collapsed, /<muted>229 docs checked<\/muted>/);
|
|
420
|
-
assert.doesNotMatch(collapsed, /PASS/);
|
|
421
|
-
// No raw error block/XML leaks into the collapsed summary.
|
|
422
|
-
assert.doesNotMatch(collapsed, /code="error"/);
|
|
423
|
-
assert.doesNotMatch(collapsed, /Next: Fix each doc/);
|
|
424
|
-
});
|
|
425
|
-
// Help must look like help at the call header itself, not only once the
|
|
426
|
-
// (possibly collapsed) result renders.
|
|
427
|
-
test('renderCall shows a help-specific header for a -h/--help invocation, no bash chrome', () => {
|
|
428
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr node config -h' });
|
|
429
|
-
const rendered = renderCrtrCall(def, { command: 'crtr node config -h' }).render(80).join('\n');
|
|
430
|
-
assert.equal(rendered.trimEnd().split('\n').length, 1);
|
|
431
|
-
assert.match(rendered, new RegExp(HELP_ICON));
|
|
432
|
-
assert.match(rendered, /crtr help/);
|
|
433
|
-
assert.doesNotMatch(rendered, /via bash/);
|
|
434
|
-
assert.doesNotMatch(rendered, /\$ crtr/);
|
|
435
|
-
assert.doesNotMatch(rendered, /-h/);
|
|
436
|
-
assert.match(rendered, /node config/);
|
|
437
|
-
});
|
|
438
|
-
// Table-driven header coverage across every mapped root family (rule 12: real
|
|
439
|
-
// Nerd Font glyphs, never the generic ◆) — includes `memory lint` (the
|
|
440
|
-
// motivating case for the memory-lint summarizer above), not just `canvas`/
|
|
441
|
-
// `node`.
|
|
442
|
-
test('renderCall uses a mapped Nerd Font glyph and one line for every mapped root family, never the generic ◆', () => {
|
|
443
|
-
const cases = [
|
|
444
|
-
{ command: 'crtr memory lint', iconTokens: ['memory', 'lint'], path: 'memory lint' },
|
|
445
|
-
{ command: 'crtr canvas dashboard', iconTokens: ['canvas', 'dashboard'], path: 'canvas dashboard' },
|
|
446
|
-
{ command: 'crtr node yield', iconTokens: ['node', 'yield'], path: 'node yield' },
|
|
447
|
-
{ command: 'crtr human ask', iconTokens: ['human', 'ask'], path: 'human ask' },
|
|
448
|
-
{ command: 'crtr node config set foo bar', iconTokens: ['node', 'config'], path: 'node config set foo bar' },
|
|
449
|
-
{ command: 'crtr pkg plugin list', iconTokens: ['pkg', 'plugin', 'list'], path: 'pkg plugin list' },
|
|
450
|
-
{ command: 'crtr push update', iconTokens: ['push', 'update'], path: 'push update' },
|
|
451
|
-
{ command: 'crtr search web', iconTokens: ['search', 'web'], path: 'search web' },
|
|
452
|
-
{ command: 'crtr surface attach to demo', iconTokens: ['surface', 'attach', 'to'], path: 'surface attach to demo' },
|
|
453
|
-
{ command: 'crtr sys doctor', iconTokens: ['sys', 'doctor'], path: 'sys doctor' },
|
|
454
|
-
];
|
|
455
|
-
for (const { command, iconTokens, path } of cases) {
|
|
456
|
-
const def = createCrtrBashToolDefinition({ command });
|
|
457
|
-
// Width 200 (not 80): this test's fake theme wraps each styled span in
|
|
458
|
-
// literal `<tag>...</tag>` text (not real ANSI escapes the wrapper would
|
|
459
|
-
// strip for width purposes), so the visible-width accounting used by the
|
|
460
|
-
// real Text component's word-wrap counts that markup as content width —
|
|
461
|
-
// a generous width keeps every case here well clear of an unrelated wrap.
|
|
462
|
-
const rendered = renderCrtrCall(def, { command }).render(200).join('\n');
|
|
463
|
-
assert.equal(rendered.trimEnd().split('\n').length, 1, `expected one line for ${command}`);
|
|
464
|
-
assert.match(rendered, new RegExp(iconForPath(iconTokens)), `expected the ${iconTokens.join(' ')} glyph in ${command}`);
|
|
465
|
-
assert.match(rendered, /crtr/, `expected "crtr" in ${command}`);
|
|
466
|
-
assert.match(rendered, new RegExp(path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), `expected path "${path}" on the same line`);
|
|
467
|
-
assert.doesNotMatch(rendered, /◆/, `unexpected generic diamond for ${command}`);
|
|
468
|
-
}
|
|
469
|
-
});
|
|
470
|
-
test('renderCall for a normal invocation previews the crtr path, not a raw bash echo', () => {
|
|
471
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr node config set foo bar' });
|
|
472
|
-
const rendered = renderCrtrCall(def, { command: 'crtr node config set foo bar' }).render(200).join('\n');
|
|
473
|
-
assert.equal(rendered.trimEnd().split('\n').length, 1);
|
|
474
|
-
assert.match(rendered, new RegExp(iconForPath(['node', 'config'])));
|
|
475
|
-
assert.match(rendered, /crtr/);
|
|
476
|
-
assert.match(rendered, /node config set foo bar/);
|
|
477
|
-
assert.doesNotMatch(rendered, /◆/);
|
|
478
|
-
assert.doesNotMatch(rendered, /via bash/);
|
|
479
|
-
assert.doesNotMatch(rendered, /\$ crtr/);
|
|
480
|
-
});
|
|
481
|
-
test('a leaf that does not declare suppressOutput is never collapsed', () => {
|
|
482
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
|
|
483
|
-
const component = def.renderResult({ content: [{ type: 'text', text: 'a normal-sized dashboard body' }] }, { expanded: false }, theme, {});
|
|
484
|
-
assert.match(component.render(80).join('\n'), /a normal-sized dashboard body/);
|
|
485
|
-
});
|
|
486
|
-
test('expanded crtr output clamps long single lines to the terminal width', () => {
|
|
487
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
|
|
488
|
-
const longLine = 'x'.repeat(442);
|
|
489
|
-
const lines = def
|
|
490
|
-
.renderResult({ content: [{ type: 'text', text: longLine }] }, { expanded: true }, theme, {})
|
|
491
|
-
.render(70);
|
|
492
|
-
assert.ok(lines.length > 0);
|
|
493
|
-
for (const line of lines) {
|
|
494
|
-
assert.ok(visibleWidth(line) <= 70, `line exceeded width: ${visibleWidth(line)} > 70`);
|
|
495
|
-
}
|
|
496
|
-
});
|
|
497
|
-
test('collapsed crtr output clamps all rendered chrome to the terminal width', () => {
|
|
498
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
|
|
499
|
-
const longLine = 'y'.repeat(442);
|
|
500
|
-
const lines = def
|
|
501
|
-
.renderResult({ content: [{ type: 'text', text: longLine }] }, { expanded: false }, theme, {})
|
|
502
|
-
.render(70);
|
|
503
|
-
assert.ok(lines.length > 0);
|
|
504
|
-
for (const line of lines) {
|
|
505
|
-
assert.ok(visibleWidth(line) <= 70, `line exceeded width: ${visibleWidth(line)} > 70`);
|
|
506
|
-
}
|
|
507
|
-
});
|
|
508
|
-
const WIDE_GENERIC_TABLE_OUTPUT = [
|
|
509
|
-
'24 plugins:',
|
|
510
|
-
'',
|
|
511
|
-
'| name | version | description | status |',
|
|
512
|
-
'| --- | --- | --- | --- |',
|
|
513
|
-
...Array.from({ length: 24 }, (_, i) => `| plugin-${String(i).padStart(2, '0')} | 1.0.${i} | This is a deliberately over-wide description cell that used to wrap the collapsed card mid-cell for row ${i} | ${i % 2 === 0 ? 'enabled' : 'disabled'} |`),
|
|
514
|
-
].join('\n');
|
|
515
|
-
test('generic collapsed fallback renders wide GFM tables aligned, clipped, and row-capped instead of raw pipe rows', () => {
|
|
516
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr surface web serve' });
|
|
517
|
-
const rendered = def
|
|
518
|
-
.renderResult({ content: [{ type: 'text', text: WIDE_GENERIC_TABLE_OUTPUT }] }, { expanded: false }, plainTheme, {})
|
|
519
|
-
.render(58)
|
|
520
|
-
.join('\n');
|
|
521
|
-
assert.doesNotMatch(rendered, /\|/);
|
|
522
|
-
assert.match(rendered, /plugin-00\s+1\.0\.0\s+.*…/);
|
|
523
|
-
assert.match(rendered, /more lines/);
|
|
524
|
-
assert.doesNotMatch(rendered, /plugin-23/);
|
|
525
|
-
assert.ok(rendered.split('\n').length <= 20, `expected <=20 lines, got ${rendered.split('\n').length}`);
|
|
526
|
-
const row0 = rendered.split('\n').find((line) => line.includes('plugin-00')) ?? '';
|
|
527
|
-
const row1 = rendered.split('\n').find((line) => line.includes('plugin-01')) ?? '';
|
|
528
|
-
assert.notEqual(row0, '');
|
|
529
|
-
assert.notEqual(row1, '');
|
|
530
|
-
assert.equal(row0.indexOf('1.0.0'), row1.indexOf('1.0.1'));
|
|
531
|
-
for (const line of rendered.split('\n')) {
|
|
532
|
-
assert.ok(visibleWidth(line) <= 58, `line exceeded width: ${visibleWidth(line)} > 58: ${line}`);
|
|
533
|
-
}
|
|
534
|
-
});
|
|
535
|
-
const HEADERLESS_TABLE_TAIL_OUTPUT = [
|
|
536
|
-
'| mr2iy5tw-a1f5e1f0 | batch-a-implementation | /Users/silasrhyneer/Code/cli/crouter | active |',
|
|
537
|
-
'| mr147q9d-c7681b04 | initial-session-setup | /Users/silasrhyneer/Code/cli/crouter | idle |',
|
|
538
|
-
'| mr2ejzaw-6f70e858 | preview-roadmap | /Users/silasrhyneer/Code/cli/crouter | done |',
|
|
539
|
-
].join('\n');
|
|
540
|
-
test('generic collapsed fallback aligns a header-less truncated table tail by column count', () => {
|
|
541
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr surface web serve' });
|
|
542
|
-
const rendered = def
|
|
543
|
-
.renderResult({ content: [{ type: 'text', text: HEADERLESS_TABLE_TAIL_OUTPUT }] }, { expanded: false }, plainTheme, {})
|
|
544
|
-
.render(64)
|
|
545
|
-
.join('\n');
|
|
546
|
-
assert.doesNotMatch(rendered, /\|/);
|
|
547
|
-
assert.match(rendered, /batch-a-implemen/);
|
|
548
|
-
const rows = rendered.split('\n').filter((line) => line.includes('mr')).map(stripAnsi);
|
|
549
|
-
assert.equal(rows.length, 3);
|
|
550
|
-
assert.equal(rows[0].indexOf('/Users'), rows[1].indexOf('/Users'));
|
|
551
|
-
assert.equal(rows[1].indexOf('/Users'), rows[2].indexOf('/Users'));
|
|
552
|
-
});
|
|
553
|
-
test('generic collapsed fallback renders the pi truncation marker as one muted line', () => {
|
|
554
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr surface web serve' });
|
|
555
|
-
const marker = '[Showing lines 951-1000 of 1000 (50.0KB limit). Full output: /tmp/pi-bash-output.log]';
|
|
556
|
-
const rendered = def
|
|
557
|
-
.renderResult({ content: [{ type: 'text', text: `last retained line\n${marker}` }] }, { expanded: false }, theme, {})
|
|
558
|
-
.render(240)
|
|
559
|
-
.join('\n');
|
|
560
|
-
assert.match(rendered, /<muted>\[Showing lines 951-1000 of 1000 \(50\.0KB limit\)\. Full output: \/tmp\/pi-bash-output\.log\]<\/muted>/);
|
|
561
|
-
assert.equal((rendered.match(/Showing lines/g) ?? []).length, 1);
|
|
562
|
-
});
|
|
563
|
-
const NO_EDGE_PIPE_TABLE_OUTPUT = [
|
|
564
|
-
'name | version | status',
|
|
565
|
-
'--- | --- | ---',
|
|
566
|
-
'alpha | 1.0.0 | enabled',
|
|
567
|
-
'beta | 2.1.0 | disabled',
|
|
568
|
-
].join('\n');
|
|
569
|
-
test('generic collapsed fallback aligns a GFM table whose rows omit both outer pipes', () => {
|
|
570
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr surface web serve' });
|
|
571
|
-
const rendered = def
|
|
572
|
-
.renderResult({ content: [{ type: 'text', text: NO_EDGE_PIPE_TABLE_OUTPUT }] }, { expanded: false }, plainTheme, {})
|
|
573
|
-
.render(60)
|
|
574
|
-
.join('\n');
|
|
575
|
-
assert.doesNotMatch(rendered, /\|/);
|
|
576
|
-
assert.doesNotMatch(rendered, /---/);
|
|
577
|
-
const rows = rendered.split('\n').filter((line) => line.trim() !== '');
|
|
578
|
-
const alphaRow = rows.find((line) => line.includes('alpha')) ?? '';
|
|
579
|
-
const betaRow = rows.find((line) => line.includes('beta')) ?? '';
|
|
580
|
-
assert.notEqual(alphaRow, '');
|
|
581
|
-
assert.notEqual(betaRow, '');
|
|
582
|
-
assert.equal(alphaRow.indexOf('1.0.0'), betaRow.indexOf('2.1.0'));
|
|
42
|
+
const redirected = renderCall('crtr push -h 2>&1');
|
|
43
|
+
assert.match(redirected, /<b>push<\/b>/);
|
|
44
|
+
assert.doesNotMatch(redirected, /2>|>&1/);
|
|
45
|
+
// Redirect operators glued to their target with no space are plumbing too,
|
|
46
|
+
// mid-argv as well as trailing.
|
|
47
|
+
const midArgv = renderCall('crtr node new --name foo >/tmp/log 2>&1');
|
|
48
|
+
assert.match(midArgv, /node new --name foo/);
|
|
49
|
+
assert.doesNotMatch(midArgv, /tmp\/log|2>/);
|
|
583
50
|
});
|
|
584
|
-
test('
|
|
585
|
-
|
|
586
|
-
const rendered = def
|
|
587
|
-
.renderResult({ content: [{ type: 'text', text: 'warning: use x | y instead' }] }, { expanded: false }, plainTheme, {})
|
|
588
|
-
.render(60)
|
|
589
|
-
.join('\n');
|
|
590
|
-
assert.match(rendered, /warning: use x \| y instead/);
|
|
591
|
-
});
|
|
592
|
-
test('icon lookup uses per-leaf glyphs before family glyphs, with help unchanged', () => {
|
|
593
|
-
assert.equal(iconForPath(['canvas', 'dashboard']), String.fromCodePoint(0xf0e4));
|
|
594
|
-
assert.notEqual(iconForPath(['canvas', 'dashboard']), iconForPath(['canvas']));
|
|
595
|
-
assert.equal(iconForPath(['node', 'inspect', 'list']), String.fromCodePoint(0xf03a));
|
|
596
|
-
assert.notEqual(iconForPath(['node', 'inspect', 'list']), iconForPath(['node']));
|
|
597
|
-
assert.equal(iconForPath(['sys', 'doctor']), String.fromCodePoint(0xf0f1));
|
|
598
|
-
assert.equal(HELP_ICON, String.fromCodePoint(0xf059));
|
|
599
|
-
});
|
|
600
|
-
// ---------------------------------------------------------------------------
|
|
601
|
-
// canvas/node runtime cockpit summaries (first non-memory batch, Silas's
|
|
602
|
-
// 2026-07-01 reframe: collapsed cards are computed status digests, not
|
|
603
|
-
// decorated stdout). Fixtures below are literal renderResult() output shapes
|
|
604
|
-
// (core/render.ts: a `- field: N` scalar bullet, an `N <label>:` count
|
|
605
|
-
// lead-in + GFM table for an array of objects) — not the live command tree,
|
|
606
|
-
// so these stay independent of what actually runs on a real canvas.
|
|
607
|
-
// ---------------------------------------------------------------------------
|
|
608
|
-
const DASHBOARD_OUTPUT = [
|
|
609
|
-
'- nodes: 4',
|
|
610
|
-
'',
|
|
611
|
-
'● root-node [developer/orchestrator] ctx 5k',
|
|
612
|
-
'├─ ✓ child-a ...',
|
|
613
|
-
'└─ ○ child-b ...',
|
|
614
|
-
'',
|
|
615
|
-
'4 rows:',
|
|
616
|
-
'',
|
|
617
|
-
'| node_id | name | status | kind | mode | ctx_tokens | asks | cwd | created |',
|
|
618
|
-
'| --- | --- | --- | --- | --- | --- | --- | --- | --- |',
|
|
619
|
-
'| n1 | root-node | active | developer | orchestrator | 5000 | 0 | /Users/x/proj | 2026-01-01T00:00:00.000Z |',
|
|
620
|
-
'| n2 | child-a | active | developer | base | 12000 | 3 | /Users/x/proj | 2026-01-01T00:00:00.000Z |',
|
|
621
|
-
'| n3 | child-b | done | advisor | base | 8000 | 0 | /Users/x/proj | 2026-01-01T00:00:00.000Z |',
|
|
622
|
-
'| n4 | child-c | idle | reviewer | base | 3000 | 0 | /Users/x/proj | 2026-01-01T00:00:00.000Z |',
|
|
623
|
-
].join('\n');
|
|
624
|
-
test('a crtr canvas dashboard card collapses to a status-count digest, not the raw tree/table', () => {
|
|
51
|
+
test('crtr results do not invalidate their owning tool component', () => {
|
|
52
|
+
let ownerInvalidations = 0;
|
|
625
53
|
const def = createCrtrBashToolDefinition({ command: 'crtr canvas dashboard' });
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
.join('\n');
|
|
630
|
-
assert.match(collapsed, /2 active/);
|
|
631
|
-
assert.match(collapsed, /1 idle/);
|
|
632
|
-
assert.match(collapsed, /1 done/);
|
|
633
|
-
assert.match(collapsed, /<muted>largest ctx: 12k tokens<\/muted>/);
|
|
634
|
-
assert.match(collapsed, /<warning>3 pending asks<\/warning>/);
|
|
635
|
-
assert.doesNotMatch(collapsed, /\| ---/);
|
|
636
|
-
assert.doesNotMatch(collapsed, /node_id/);
|
|
637
|
-
assert.doesNotMatch(collapsed, /crtr done/);
|
|
638
|
-
assert.match(collapsed, /ctrl\+o to expand/);
|
|
639
|
-
const expanded = def
|
|
640
|
-
.renderResult({ content: [{ type: 'text', text: DASHBOARD_OUTPUT }] }, { expanded: true }, theme, {})
|
|
641
|
-
.render(120)
|
|
642
|
-
.join('\n');
|
|
643
|
-
assert.match(expanded, /\$ crtr canvas dashboard/);
|
|
644
|
-
assert.match(expanded, /\| node_id \| name \|/);
|
|
645
|
-
assert.match(expanded, /root-node \[developer\/orchestrator\]/);
|
|
646
|
-
});
|
|
647
|
-
const NODE_LIST_OUTPUT = [
|
|
648
|
-
'4 nodes:',
|
|
649
|
-
'',
|
|
650
|
-
'| node_id | name | status | kind | mode | lifecycle | cwd | created | hanging |',
|
|
651
|
-
'| --- | --- | --- | --- | --- | --- | --- | --- | --- |',
|
|
652
|
-
'| n1 | alpha | active | developer | orchestrator | terminal | /a | 2026-01-01T00:00:00.000Z | none |',
|
|
653
|
-
'| n2 | beta | active | developer | base | terminal | /a | 2026-01-01T00:00:00.000Z | none |',
|
|
654
|
-
'| n3 | gamma | done | advisor | base | terminal | /a | 2026-01-01T00:00:00.000Z | none |',
|
|
655
|
-
'| n4 | delta | idle | reviewer | base | resident | /a | 2026-01-01T00:00:00.000Z | none |',
|
|
656
|
-
].join('\n');
|
|
657
|
-
test('a crtr node inspect list card collapses to status + kind digests, not the raw table', () => {
|
|
658
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr node inspect list' });
|
|
659
|
-
const collapsed = def
|
|
660
|
-
.renderResult({ content: [{ type: 'text', text: NODE_LIST_OUTPUT }] }, { expanded: false }, theme, {})
|
|
661
|
-
.render(120)
|
|
662
|
-
.join('\n');
|
|
663
|
-
assert.match(collapsed, /2 active/);
|
|
664
|
-
assert.match(collapsed, /1 idle/);
|
|
665
|
-
assert.match(collapsed, /1 done/);
|
|
666
|
-
assert.match(collapsed, /<muted>kinds: developer×2, advisor×1, reviewer×1<\/muted>/);
|
|
667
|
-
assert.doesNotMatch(collapsed, /\| ---/);
|
|
668
|
-
assert.doesNotMatch(collapsed, /node_id/);
|
|
669
|
-
assert.doesNotMatch(collapsed, /crtr done/);
|
|
670
|
-
assert.match(collapsed, /ctrl\+o to expand/);
|
|
671
|
-
});
|
|
672
|
-
test('a crtr canvas attention count card renders a computed health tone, error when pending, success when clear', () => {
|
|
673
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr canvas attention count' });
|
|
674
|
-
const pending = def
|
|
675
|
-
.renderResult({ content: [{ type: 'text', text: '- count: 14' }] }, { expanded: false }, theme, {})
|
|
676
|
-
.render(120)
|
|
677
|
-
.join('\n');
|
|
678
|
-
assert.match(pending, /<error>14 pending asks<\/error>/);
|
|
679
|
-
assert.match(pending, /ctrl\+o to expand/);
|
|
680
|
-
const clear = def
|
|
681
|
-
.renderResult({ content: [{ type: 'text', text: '- count: 0' }] }, { expanded: false }, theme, {})
|
|
682
|
-
.render(120)
|
|
683
|
-
.join('\n');
|
|
684
|
-
assert.match(clear, /<success>PASS · no pending asks<\/success>/);
|
|
685
|
-
assert.doesNotMatch(clear, /0 pending asks/);
|
|
686
|
-
});
|
|
687
|
-
const ATTENTION_LIST_OUTPUT = [
|
|
688
|
-
'- total: 14',
|
|
689
|
-
'',
|
|
690
|
-
'5 items:',
|
|
691
|
-
'',
|
|
692
|
-
'| node_id | name | cwd | count |',
|
|
693
|
-
'| --- | --- | --- | --- |',
|
|
694
|
-
'| mq3geac4-9b947921 | general | /Users/silasrhyneer/Code/alaria-map-site | 7 |',
|
|
695
|
-
'| mqa7c632-24f0e384 | general | /Users/silasrhyneer/Code/cli/crouter | 3 |',
|
|
696
|
-
'| mqa9w9qy-e1cafda1 | general | /Users/silasrhyneer/Code/godot/StarTheory2 | 2 |',
|
|
697
|
-
'| mqt6lz47-09daf181 | developer | /Users/silasrhyneer/Code/godot/StarTheory2-worktrees/escape-pod-selection | 1 |',
|
|
698
|
-
'| mr0v8vxi-580c7572 | general | /Users/silasrhyneer/Code/northlight | 1 |',
|
|
699
|
-
].join('\n');
|
|
700
|
-
test('a crtr canvas attention list card collapses to total/hottest/digest, not the raw table', () => {
|
|
701
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr canvas attention list' });
|
|
702
|
-
const collapsed = def
|
|
703
|
-
.renderResult({ content: [{ type: 'text', text: ATTENTION_LIST_OUTPUT }] }, { expanded: false }, theme, {})
|
|
704
|
-
.render(120)
|
|
705
|
-
.join('\n');
|
|
706
|
-
assert.match(collapsed, /14 total · 5 cwds/);
|
|
707
|
-
assert.match(collapsed, /<warning>hottest: alaria-map-site \(7\)<\/warning>/);
|
|
708
|
-
assert.match(collapsed, /<muted>other cwds: [^<]*crouter[^<]*<\/muted>/);
|
|
709
|
-
assert.doesNotMatch(collapsed, /\| ---/);
|
|
710
|
-
assert.doesNotMatch(collapsed, /node_id/);
|
|
711
|
-
assert.match(collapsed, /ctrl\+o to expand/);
|
|
712
|
-
const empty = def
|
|
713
|
-
.renderResult({ content: [{ type: 'text', text: ['- total: 0', '', '0 items.'].join('\n') }] }, { expanded: false }, theme, {})
|
|
714
|
-
.render(120)
|
|
715
|
-
.join('\n');
|
|
716
|
-
assert.match(empty, /<success>PASS · no pending asks<\/success>/);
|
|
717
|
-
});
|
|
718
|
-
// Command-owned preview: `node inspect show` inlines a full node meta object
|
|
719
|
-
// (system prompt + every telemetry field) with no useful human-glance slice —
|
|
720
|
-
// same rationale as canvas snapshot / node inspect snapshot, mirrored in the
|
|
721
|
-
// bundle-safe SUPPRESS_OUTPUT_LEAVES registry.
|
|
722
|
-
test('node inspect show is command-owned suppressOutput, never on failure', () => {
|
|
723
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr node inspect show abc' });
|
|
724
|
-
const collapsed = def.renderResult({ content: [{ type: 'text', text: '{"node":{/* huge */}}' }] }, { expanded: false }, theme, {});
|
|
725
|
-
const rendered = collapsed.render(80).join('\n');
|
|
726
|
-
assert.doesNotMatch(rendered, /crtr done/);
|
|
727
|
-
assert.doesNotMatch(rendered, /huge/);
|
|
728
|
-
assert.match(rendered, /suppressed — ctrl\+o to expand/);
|
|
729
|
-
const failed = def.renderResult({ content: [{ type: 'text', text: 'huge dump on failure too' }] }, { expanded: false }, theme, { isError: true });
|
|
730
|
-
assert.match(failed.render(80).join('\n'), /huge dump on failure too/);
|
|
731
|
-
});
|
|
732
|
-
// ---------------------------------------------------------------------------
|
|
733
|
-
// Batch B: canvas/node/push runtime cockpit summaries (Silas's
|
|
734
|
-
// 2026-07-01 reframe continued). Fixtures below are literal render() output
|
|
735
|
-
// shapes copied from the real command source (see preview-registry.ts's
|
|
736
|
-
// per-summarizer doc comments for the exact file:line), not the live command
|
|
737
|
-
// tree — these stay independent of what actually runs on a real canvas.
|
|
738
|
-
// ---------------------------------------------------------------------------
|
|
739
|
-
test('a crtr canvas attention map card collapses to a hottest-id digest, PASS when all zero', () => {
|
|
740
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr canvas attention map --view mq1' });
|
|
741
|
-
const collapsed = def
|
|
742
|
-
.renderResult({ content: [{ type: 'text', text: '- counts: {"n1":0,"n2":3,"n3":5}' }] }, { expanded: false }, theme, {})
|
|
743
|
-
.render(120)
|
|
744
|
-
.join('\n');
|
|
745
|
-
assert.match(collapsed, /8 asks across 2 nodes/);
|
|
746
|
-
assert.match(collapsed, /<warning>hottest: n3 \(5\)<\/warning>/);
|
|
747
|
-
const zero = def
|
|
748
|
-
.renderResult({ content: [{ type: 'text', text: '- counts: {"n1":0,"n2":0}' }] }, { expanded: false }, theme, {})
|
|
749
|
-
.render(120)
|
|
750
|
-
.join('\n');
|
|
751
|
-
assert.match(zero, /<success>PASS · no pending asks<\/success>/);
|
|
752
|
-
const zeroExpanded = def
|
|
753
|
-
.renderResult({ content: [{ type: 'text', text: '- counts: {"n1":0,"n2":0}' }] }, { expanded: true }, theme, {})
|
|
754
|
-
.render(120)
|
|
755
|
-
.join('\n');
|
|
756
|
-
assert.match(zeroExpanded, /counts: \{"n1":0,"n2":0\}/);
|
|
757
|
-
const expanded = def
|
|
758
|
-
.renderResult({ content: [{ type: 'text', text: '- counts: {"n1":0,"n2":3,"n3":5}' }] }, { expanded: true }, theme, {})
|
|
759
|
-
.render(120)
|
|
760
|
-
.join('\n');
|
|
761
|
-
assert.match(expanded, /counts: \{"n1":0,"n2":3,"n3":5\}/);
|
|
762
|
-
});
|
|
763
|
-
test('a crtr node lifecycle revive card collapses to a kicked/revived status line, argv-recovered id', () => {
|
|
764
|
-
const kickedDef = createCrtrBashToolDefinition({ command: 'crtr node lifecycle revive mq2jpqz5-87fa626e --now' });
|
|
765
|
-
const kicked = kickedDef
|
|
766
|
-
.renderResult({ content: [{ type: 'text', text: ['- session: mysession', '- kicked: true', '', 'Sent SIGTERM to worker-a (mq2jpqz5-87fa626e, pid 456).'].join('\n') }] }, { expanded: false }, theme, {})
|
|
767
|
-
.render(120)
|
|
768
|
-
.join('\n');
|
|
769
|
-
assert.match(kicked, /<warning>kicked — daemon resumes in ~20s<\/warning>/);
|
|
770
|
-
const kickedExpanded = kickedDef
|
|
771
|
-
.renderResult({ content: [{ type: 'text', text: ['- session: mysession', '- kicked: true', '', 'Sent SIGTERM to worker-a (mq2jpqz5-87fa626e, pid 456).'].join('\n') }] }, { expanded: true }, theme, {})
|
|
772
|
-
.render(120)
|
|
773
|
-
.join('\n');
|
|
774
|
-
assert.match(kickedExpanded, /Sent SIGTERM to worker-a \(mq2jpqz5-87fa626e, pid 456\)\./);
|
|
775
|
-
const revivedDef = createCrtrBashToolDefinition({ command: 'crtr node lifecycle revive mq2jpqz5-87fa626e' });
|
|
776
|
-
const revived = revivedDef
|
|
777
|
-
.renderResult({ content: [{ type: 'text', text: ['- session: mysession', '- resumed: true', '- ready: true'].join('\n') }] }, { expanded: false }, theme, {})
|
|
778
|
-
.render(120)
|
|
779
|
-
.join('\n');
|
|
780
|
-
assert.match(revived, /<success>revived mq2jpqz5-87fa626e<\/success>/);
|
|
781
|
-
assert.doesNotMatch(revived, /not yet attachable/);
|
|
782
|
-
const revivedExpanded = revivedDef
|
|
783
|
-
.renderResult({ content: [{ type: 'text', text: ['- session: mysession', '- resumed: true', '- ready: true'].join('\n') }] }, { expanded: true }, theme, {})
|
|
784
|
-
.render(120)
|
|
785
|
-
.join('\n');
|
|
786
|
-
assert.match(revivedExpanded, /session: mysession/);
|
|
787
|
-
const notReady = revivedDef
|
|
788
|
-
.renderResult({ content: [{ type: 'text', text: ['- resumed: false', '- ready: false'].join('\n') }] }, { expanded: false }, theme, {})
|
|
789
|
-
.render(120)
|
|
790
|
-
.join('\n');
|
|
791
|
-
assert.match(notReady, /<warning>not yet attachable \(socket not ready\)<\/warning>/);
|
|
792
|
-
const notReadyExpanded = revivedDef
|
|
793
|
-
.renderResult({ content: [{ type: 'text', text: ['- resumed: false', '- ready: false'].join('\n') }] }, { expanded: true }, theme, {})
|
|
794
|
-
.render(120)
|
|
795
|
-
.join('\n');
|
|
796
|
-
assert.match(notReadyExpanded, /resumed: false/);
|
|
797
|
-
assert.match(notReadyExpanded, /ready: false/);
|
|
798
|
-
// `canvas revive --all` carries a `- mode:` bullet; no summarizer is
|
|
799
|
-
// registered for it (rare admin path) — falls through to generic
|
|
800
|
-
// truncation, raw `mode` text survives.
|
|
801
|
-
const allDef = createCrtrBashToolDefinition({ command: 'crtr canvas revive --all' });
|
|
802
|
-
const allPreview = allDef
|
|
803
|
-
.renderResult({ content: [{ type: 'text', text: '- mode: preview\n- candidates: mq1' }] }, { expanded: false }, theme, {})
|
|
804
|
-
.render(120)
|
|
805
|
-
.join('\n');
|
|
806
|
-
assert.match(allPreview, /mode: preview/);
|
|
807
|
-
const allPreviewExpanded = revivedDef
|
|
808
|
-
.renderResult({ content: [{ type: 'text', text: '- mode: preview\n- candidates: mq1' }] }, { expanded: true }, theme, {})
|
|
809
|
-
.render(120)
|
|
810
|
-
.join('\n');
|
|
811
|
-
assert.match(allPreviewExpanded, /candidates: mq1/);
|
|
812
|
-
});
|
|
813
|
-
const HISTORY_SEARCH_HITS_OUTPUT = [
|
|
814
|
-
'3 of 10 hits:',
|
|
815
|
-
'',
|
|
816
|
-
'| ref | node | kind | source | ts | snippet |',
|
|
817
|
-
'| --- | --- | --- | --- | --- | --- |',
|
|
818
|
-
'| n1:reports/a.md | alpha (n1) | developer | report:final | 2026-01-01T00:00:00.000Z | did a thing |',
|
|
819
|
-
'| n2:reports/b.md | beta (n2) | developer | doc | 2026-01-02T00:00:00.000Z | did another |',
|
|
820
|
-
'| n3:reports/c.md | gamma (n3) | advisor | report:update | 2026-01-03T00:00:00.000Z | did a third |',
|
|
821
|
-
'',
|
|
822
|
-
'- next_cursor: abc123',
|
|
823
|
-
'- total: 10',
|
|
824
|
-
'',
|
|
825
|
-
'Read a hit with `canvas history read <ref>`; narrow with `canvas history search -h`.',
|
|
826
|
-
].join('\n');
|
|
827
|
-
test('a crtr canvas history search card collapses to a hit count + top rows, not the raw table', () => {
|
|
828
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr canvas history search foo' });
|
|
829
|
-
const collapsed = def
|
|
830
|
-
.renderResult({ content: [{ type: 'text', text: HISTORY_SEARCH_HITS_OUTPUT }] }, { expanded: false }, theme, {})
|
|
831
|
-
.render(140)
|
|
832
|
-
.join('\n');
|
|
833
|
-
assert.match(collapsed, /10 hits/);
|
|
834
|
-
assert.match(collapsed, /<muted>alpha \(n1\) · developer · 2026-01-01T00:00:00\.000Z<\/muted>/);
|
|
835
|
-
assert.doesNotMatch(collapsed, /\| ref \|/);
|
|
836
|
-
const expanded = def
|
|
837
|
-
.renderResult({ content: [{ type: 'text', text: HISTORY_SEARCH_HITS_OUTPUT }] }, { expanded: true }, theme, {})
|
|
838
|
-
.render(140)
|
|
839
|
-
.join('\n');
|
|
840
|
-
assert.match(expanded, /\| ref \| node \| kind \| source \| ts \| snippet \|/);
|
|
841
|
-
assert.match(expanded, /did a thing/);
|
|
842
|
-
const zero = def
|
|
843
|
-
.renderResult({ content: [{ type: 'text', text: ['0 hits.', '', '- next_cursor: null\n- total: 0', 'Read a hit…'].join('\n\n') }] }, { expanded: false }, theme, {})
|
|
844
|
-
.render(120)
|
|
845
|
-
.join('\n');
|
|
846
|
-
assert.match(zero, /<muted>0 hits<\/muted>/);
|
|
847
|
-
const zeroExpanded = def
|
|
848
|
-
.renderResult({ content: [{ type: 'text', text: ['0 hits.', '', '- next_cursor: null\n- total: 0', 'Read a hit…'].join('\n\n') }] }, { expanded: true }, theme, {})
|
|
849
|
-
.render(120)
|
|
850
|
-
.join('\n');
|
|
851
|
-
assert.match(zeroExpanded, /total: 0/);
|
|
852
|
-
// --full mode shares the same `N of M hits:` lead-in but has no table —
|
|
853
|
-
// deliberately skipped (null), raw survives.
|
|
854
|
-
const full = def
|
|
855
|
-
.renderResult({ content: [{ type: 'text', text: '1 of 1 hits:\n\n### n1:reports/a.md\n- node: alpha (n1) | kind: developer | source: doc | ts: 2026-01-01\n\nfull body here' }] }, { expanded: false }, theme, {})
|
|
856
|
-
.render(120)
|
|
857
|
-
.join('\n');
|
|
858
|
-
assert.match(full, /full body here/);
|
|
859
|
-
const fullExpanded = def
|
|
860
|
-
.renderResult({ content: [{ type: 'text', text: '1 of 1 hits:\n\n### n1:reports/a.md\n- node: alpha (n1) | kind: developer | source: doc | ts: 2026-01-01\n\nfull body here' }] }, { expanded: true }, theme, {})
|
|
861
|
-
.render(120)
|
|
862
|
-
.join('\n');
|
|
863
|
-
assert.match(fullExpanded, /full body here/);
|
|
864
|
-
// Lead-in says 3 displayed hits, but the table only has 2 rows — a
|
|
865
|
-
// truncated/mismatched render is a shape mismatch, not a partial summary:
|
|
866
|
-
// skipped (null), raw survives even collapsed.
|
|
867
|
-
const truncated = def
|
|
868
|
-
.renderResult({
|
|
869
|
-
content: [
|
|
870
|
-
{
|
|
871
|
-
type: 'text',
|
|
872
|
-
text: [
|
|
873
|
-
'3 of 10 hits:',
|
|
874
|
-
'',
|
|
875
|
-
'| ref | node | kind | source | ts | snippet |',
|
|
876
|
-
'| --- | --- | --- | --- | --- | --- |',
|
|
877
|
-
'| n1:reports/a.md | alpha (n1) | developer | report:final | 2026-01-01T00:00:00.000Z | did a thing |',
|
|
878
|
-
'| n2:reports/b.md | beta (n2) | developer | doc | 2026-01-02T00:00:00.000Z | did another |',
|
|
879
|
-
'',
|
|
880
|
-
'- next_cursor: abc123',
|
|
881
|
-
'- total: 10',
|
|
882
|
-
].join('\n'),
|
|
883
|
-
},
|
|
884
|
-
],
|
|
885
|
-
}, { expanded: false }, theme, {})
|
|
886
|
-
.render(140)
|
|
887
|
-
.join('\n');
|
|
888
|
-
assert.match(truncated, /3 of 10 hits:/);
|
|
889
|
-
assert.doesNotMatch(truncated, /<muted>alpha \(n1\) · developer/);
|
|
890
|
-
});
|
|
891
|
-
const HISTORY_READ_OUTPUT = [
|
|
892
|
-
'alpha (n1) | source: report:final | ts: 2026-01-01T00:00:00.000Z | ref: n1:reports/a.md',
|
|
893
|
-
'',
|
|
894
|
-
'Line one of body.',
|
|
895
|
-
'Line two of body.',
|
|
896
|
-
'Line three of body.',
|
|
897
|
-
'Line four of body.',
|
|
898
|
-
].join('\n');
|
|
899
|
-
test('a crtr canvas history read card previews an identity line + content teaser', () => {
|
|
900
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr canvas history read n1:reports/a.md' });
|
|
901
|
-
const collapsed = def
|
|
902
|
-
.renderResult({ content: [{ type: 'text', text: HISTORY_READ_OUTPUT }] }, { expanded: false }, theme, {})
|
|
903
|
-
.render(120)
|
|
904
|
-
.join('\n');
|
|
905
|
-
assert.match(collapsed, /alpha \(n1\) · report:final · 2026-01-01T00:00:00\.000Z/);
|
|
906
|
-
assert.match(collapsed, /Line one of body\./);
|
|
907
|
-
assert.match(collapsed, /…/);
|
|
908
|
-
assert.doesNotMatch(collapsed, /Line four of body\./);
|
|
909
|
-
const expanded = def
|
|
910
|
-
.renderResult({ content: [{ type: 'text', text: HISTORY_READ_OUTPUT }] }, { expanded: true }, theme, {})
|
|
911
|
-
.render(120)
|
|
912
|
-
.join('\n');
|
|
913
|
-
assert.match(expanded, /Line four of body\./);
|
|
914
|
-
});
|
|
915
|
-
const HISTORY_SHOW_OUTPUT = [
|
|
916
|
-
'node: alpha (n1)',
|
|
917
|
-
'',
|
|
918
|
-
'4 artifacts:',
|
|
919
|
-
'',
|
|
920
|
-
'| ref | source | ts | detail |',
|
|
921
|
-
'| --- | --- | --- | --- |',
|
|
922
|
-
'| n1:reports/a.md | report:final | 2026-01-01T00:00:00.000Z | Final report (120B) |',
|
|
923
|
-
'| n1:reports/b.md | report:update | 2026-01-02T00:00:00.000Z | Update (80B) |',
|
|
924
|
-
'| n1:docs/design.md | doc | 2026-01-03T00:00:00.000Z | Design doc |',
|
|
925
|
-
'| n1:roadmap.md | roadmap | 2026-01-04T00:00:00.000Z | Roadmap |',
|
|
926
|
-
'',
|
|
927
|
-
'Read one with `canvas history read <ref>`; reopen the node with `node lifecycle revive n1`.',
|
|
928
|
-
].join('\n');
|
|
929
|
-
test('a crtr node inspect artifacts card collapses to a node line + type-count digest, not the raw table', () => {
|
|
930
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr node inspect artifacts n1' });
|
|
931
|
-
const collapsed = def
|
|
932
|
-
.renderResult({ content: [{ type: 'text', text: HISTORY_SHOW_OUTPUT }] }, { expanded: false }, theme, {})
|
|
933
|
-
.render(120)
|
|
934
|
-
.join('\n');
|
|
935
|
-
assert.match(collapsed, /4 artifacts/);
|
|
936
|
-
assert.match(collapsed, /<muted>reports×2, docs×1, roadmaps×1<\/muted>/);
|
|
937
|
-
assert.doesNotMatch(collapsed, /\| ref \|/);
|
|
938
|
-
const expanded = def
|
|
939
|
-
.renderResult({ content: [{ type: 'text', text: HISTORY_SHOW_OUTPUT }] }, { expanded: true }, theme, {})
|
|
940
|
-
.render(120)
|
|
941
|
-
.join('\n');
|
|
942
|
-
assert.match(expanded, /\| ref \| source \| ts \| detail \|/);
|
|
943
|
-
assert.match(expanded, /Final report {2}\(120B\)/);
|
|
944
|
-
const empty = def
|
|
945
|
-
.renderResult({ content: [{ type: 'text', text: ['node: alpha (n1)', '', '0 artifacts.', '', 'Read one…'].join('\n\n') }] }, { expanded: false }, theme, {})
|
|
946
|
-
.render(120)
|
|
947
|
-
.join('\n');
|
|
948
|
-
assert.match(empty, /<muted>alpha \(n1\) · 0 artifacts<\/muted>/);
|
|
949
|
-
// Lead-in says 4 artifacts, but the table only has 3 rows — a
|
|
950
|
-
// truncated/mismatched render is a shape mismatch, not a partial summary:
|
|
951
|
-
// skipped (null), raw survives even collapsed.
|
|
952
|
-
const truncated = def
|
|
953
|
-
.renderResult({
|
|
954
|
-
content: [
|
|
955
|
-
{
|
|
956
|
-
type: 'text',
|
|
957
|
-
text: [
|
|
958
|
-
'node: alpha (n1)',
|
|
959
|
-
'',
|
|
960
|
-
'4 artifacts:',
|
|
961
|
-
'',
|
|
962
|
-
'| ref | source | ts | detail |',
|
|
963
|
-
'| --- | --- | --- | --- |',
|
|
964
|
-
'| n1:reports/a.md | report:final | 2026-01-01T00:00:00.000Z | Final report (120B) |',
|
|
965
|
-
'| n1:reports/b.md | report:update | 2026-01-02T00:00:00.000Z | Update (80B) |',
|
|
966
|
-
'| n1:docs/design.md | doc | 2026-01-03T00:00:00.000Z | Design doc |',
|
|
967
|
-
].join('\n'),
|
|
968
|
-
},
|
|
969
|
-
],
|
|
970
|
-
}, { expanded: false }, theme, {})
|
|
971
|
-
.render(120)
|
|
972
|
-
.join('\n');
|
|
973
|
-
assert.match(truncated, /4 artifacts:/);
|
|
974
|
-
assert.doesNotMatch(truncated, /<muted>reports×/);
|
|
975
|
-
const emptyExpanded = def
|
|
976
|
-
.renderResult({ content: [{ type: 'text', text: ['node: alpha (n1)', '', '0 artifacts.', '', 'Read one…'].join('\n\n') }] }, { expanded: true }, theme, {})
|
|
977
|
-
.render(120)
|
|
978
|
-
.join('\n');
|
|
979
|
-
assert.match(emptyExpanded, /0 artifacts\./);
|
|
980
|
-
});
|
|
981
|
-
test('crtr sys daemon start/status/stop cards collapse to a one-line pid status', () => {
|
|
982
|
-
const start = createCrtrBashToolDefinition({ command: 'crtr sys daemon start' });
|
|
983
|
-
assert.match(start.renderResult({ content: [{ type: 'text', text: '- started: true\n- pid: 4242' }] }, { expanded: false }, theme, {}).render(120).join('\n'), /<success>started · pid 4242<\/success>/);
|
|
984
|
-
assert.match(start.renderResult({ content: [{ type: 'text', text: '- started: false\n- existing_pid: 99' }] }, { expanded: false }, theme, {}).render(120).join('\n'), /<muted>already running · pid 99<\/muted>/);
|
|
985
|
-
assert.match(start.renderResult({ content: [{ type: 'text', text: '- started: false\n- existing_pid: 99' }] }, { expanded: true }, theme, {}).render(120).join('\n'), /existing_pid: 99/);
|
|
986
|
-
const startExpanded = start
|
|
987
|
-
.renderResult({ content: [{ type: 'text', text: '- started: true\n- pid: 4242' }] }, { expanded: true }, theme, {})
|
|
988
|
-
.render(120)
|
|
989
|
-
.join('\n');
|
|
990
|
-
assert.match(startExpanded, /started: true/);
|
|
991
|
-
assert.match(startExpanded, /pid: 4242/);
|
|
992
|
-
const status = createCrtrBashToolDefinition({ command: 'crtr sys daemon status' });
|
|
993
|
-
assert.match(status.renderResult({ content: [{ type: 'text', text: '- running: true\n- pid: 4242' }] }, { expanded: false }, theme, {}).render(120).join('\n'), /<success>running · pid 4242<\/success>/);
|
|
994
|
-
assert.match(status.renderResult({ content: [{ type: 'text', text: '- running: false' }] }, { expanded: false }, theme, {}).render(120).join('\n'), /<error>not running<\/error>/);
|
|
995
|
-
assert.match(status.renderResult({ content: [{ type: 'text', text: '- running: false' }] }, { expanded: true }, theme, {}).render(120).join('\n'), /running: false/);
|
|
996
|
-
const statusExpanded = status
|
|
997
|
-
.renderResult({ content: [{ type: 'text', text: '- running: true\n- pid: 4242' }] }, { expanded: true }, theme, {})
|
|
998
|
-
.render(120)
|
|
999
|
-
.join('\n');
|
|
1000
|
-
assert.match(statusExpanded, /running: true/);
|
|
1001
|
-
assert.match(statusExpanded, /pid: 4242/);
|
|
1002
|
-
const stop = createCrtrBashToolDefinition({ command: 'crtr sys daemon stop' });
|
|
1003
|
-
assert.match(stop.renderResult({ content: [{ type: 'text', text: '- stopped: true\n- pid: 4242' }] }, { expanded: false }, theme, {}).render(120).join('\n'), /stopped · pid 4242/);
|
|
1004
|
-
assert.match(stop.renderResult({ content: [{ type: 'text', text: '- stopped: false' }] }, { expanded: false }, theme, {}).render(120).join('\n'), /<muted>was not running<\/muted>/);
|
|
1005
|
-
assert.match(stop.renderResult({ content: [{ type: 'text', text: '- stopped: false' }] }, { expanded: true }, theme, {}).render(120).join('\n'), /stopped: false/);
|
|
1006
|
-
const stopExpanded = stop
|
|
1007
|
-
.renderResult({ content: [{ type: 'text', text: '- stopped: true\n- pid: 4242' }] }, { expanded: true }, theme, {})
|
|
1008
|
-
.render(120)
|
|
1009
|
-
.join('\n');
|
|
1010
|
-
assert.match(stopExpanded, /stopped: true/);
|
|
1011
|
-
assert.match(stopExpanded, /pid: 4242/);
|
|
1012
|
-
});
|
|
1013
|
-
test('a crtr canvas issue new card collapses to an issue/note confirmation, not the raw bullets', () => {
|
|
1014
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr canvas issue new --type bug' });
|
|
1015
|
-
const github = def
|
|
1016
|
-
.renderResult({
|
|
1017
|
-
content: [
|
|
1018
|
-
{
|
|
1019
|
-
type: 'text',
|
|
1020
|
-
text: '- created: true\n- target: github\n- type: bug\n- title: Fix the thing\n- url: https://github.com/acme/repo/issues/42',
|
|
1021
|
-
},
|
|
1022
|
-
],
|
|
1023
|
-
}, { expanded: false }, theme, {})
|
|
1024
|
-
.render(120)
|
|
1025
|
-
.join('\n');
|
|
1026
|
-
assert.match(github, /<success>issue #42<\/success>/);
|
|
1027
|
-
assert.match(github, /<muted>https:\/\/github\.com\/acme\/repo\/issues\/42<\/muted>/);
|
|
1028
|
-
const githubExpanded = def
|
|
1029
|
-
.renderResult({
|
|
1030
|
-
content: [
|
|
1031
|
-
{
|
|
1032
|
-
type: 'text',
|
|
1033
|
-
text: '- created: true\n- target: github\n- type: bug\n- title: Fix the thing\n- url: https://github.com/acme/repo/issues/42',
|
|
1034
|
-
},
|
|
1035
|
-
],
|
|
1036
|
-
}, { expanded: true }, theme, {})
|
|
1037
|
-
.render(120)
|
|
1038
|
-
.join('\n');
|
|
1039
|
-
assert.match(githubExpanded, /url: https:\/\/github\.com\/acme\/repo\/issues\/42/);
|
|
1040
|
-
const file = def
|
|
1041
|
-
.renderResult({
|
|
1042
|
-
content: [
|
|
1043
|
-
{
|
|
1044
|
-
type: 'text',
|
|
1045
|
-
text: '- created: true\n- target: file\n- type: idea\n- title: Add the thing\n- path: /Users/x/proj/.todos/20260101-add-the-thing.md',
|
|
1046
|
-
},
|
|
1047
|
-
],
|
|
1048
|
-
}, { expanded: false }, theme, {})
|
|
1049
|
-
.render(120)
|
|
1050
|
-
.join('\n');
|
|
1051
|
-
assert.match(file, /<success>note saved<\/success>/);
|
|
1052
|
-
assert.match(file, /<muted>20260101-add-the-thing\.md<\/muted>/);
|
|
1053
|
-
const fileExpanded = def
|
|
1054
|
-
.renderResult({
|
|
1055
|
-
content: [
|
|
1056
|
-
{
|
|
1057
|
-
type: 'text',
|
|
1058
|
-
text: '- created: true\n- target: file\n- type: idea\n- title: Add the thing\n- path: /Users/x/proj/.todos/20260101-add-the-thing.md',
|
|
1059
|
-
},
|
|
1060
|
-
],
|
|
1061
|
-
}, { expanded: true }, theme, {})
|
|
1062
|
-
.render(120)
|
|
1063
|
-
.join('\n');
|
|
1064
|
-
assert.match(fileExpanded, /path: \/Users\/x\/proj\/\.todos\/20260101-add-the-thing\.md/);
|
|
1065
|
-
const empty = def
|
|
1066
|
-
.renderResult({ content: [{ type: 'text', text: '- created: false' }] }, { expanded: false }, theme, {})
|
|
1067
|
-
.render(120)
|
|
1068
|
-
.join('\n');
|
|
1069
|
-
assert.match(empty, /<muted>nothing captured<\/muted>/);
|
|
1070
|
-
const emptyExpanded = def
|
|
1071
|
-
.renderResult({ content: [{ type: 'text', text: '- created: false' }] }, { expanded: true }, theme, {})
|
|
1072
|
-
.render(120)
|
|
1073
|
-
.join('\n');
|
|
1074
|
-
assert.match(emptyExpanded, /created: false/);
|
|
1075
|
-
});
|
|
1076
|
-
test('a crtr node new card collapses to a spawned status line, not the raw follow_up prose', () => {
|
|
1077
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr node new --kind developer' });
|
|
1078
|
-
const spawned = def
|
|
1079
|
-
.renderResult({ content: [{ type: 'text', text: ['Spawned "worker-a" (mq2jpqz5-87fa626e) — active.', '', 'Independent root spawned — hand it off and move on.'].join('\n') }] }, { expanded: false }, theme, {})
|
|
1080
|
-
.render(120)
|
|
1081
|
-
.join('\n');
|
|
1082
|
-
assert.match(spawned, /<success>spawned worker-a \(mq2jpqz5-87fa626e\) · active<\/success>/);
|
|
1083
|
-
const spawnedExpanded = def
|
|
1084
|
-
.renderResult({ content: [{ type: 'text', text: ['Spawned "worker-a" (mq2jpqz5-87fa626e) — active.', '', 'Independent root spawned — hand it off and move on.'].join('\n') }] }, { expanded: true }, theme, {})
|
|
1085
|
-
.render(120)
|
|
1086
|
-
.join('\n');
|
|
1087
|
-
assert.match(spawnedExpanded, /Independent root spawned — hand it off and move on\./);
|
|
1088
|
-
});
|
|
1089
|
-
test('a crtr node message send card collapses to a delivered status line, not the raw guidance prose', () => {
|
|
1090
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr node message send --to mq123' });
|
|
1091
|
-
const delivered = def
|
|
1092
|
-
.renderResult({ content: [{ type: 'text', text: 'Delivered.' }] }, { expanded: false }, theme, {})
|
|
1093
|
-
.render(120)
|
|
1094
|
-
.join('\n');
|
|
1095
|
-
assert.match(delivered, /<success>delivered<\/success>/);
|
|
1096
|
-
const deliveredExpanded = def
|
|
1097
|
-
.renderResult({ content: [{ type: 'text', text: 'Delivered.' }] }, { expanded: true }, theme, {})
|
|
1098
|
-
.render(120)
|
|
1099
|
-
.join('\n');
|
|
1100
|
-
assert.match(deliveredExpanded, /Delivered\./);
|
|
1101
|
-
const revived = def
|
|
1102
|
-
.renderResult({ content: [{ type: 'text', text: 'Delivered — the dormant target was revived to receive it.' }] }, { expanded: false }, theme, {})
|
|
1103
|
-
.render(120)
|
|
1104
|
-
.join('\n');
|
|
1105
|
-
assert.match(revived, /<success>delivered · revived<\/success>/);
|
|
1106
|
-
const revivedExpanded = def
|
|
1107
|
-
.renderResult({ content: [{ type: 'text', text: 'Delivered — the dormant target was revived to receive it.' }] }, { expanded: true }, theme, {})
|
|
1108
|
-
.render(120)
|
|
1109
|
-
.join('\n');
|
|
1110
|
-
assert.match(revivedExpanded, /Delivered — the dormant target was revived to receive it\./);
|
|
1111
|
-
const deferredHeld = def
|
|
1112
|
-
.renderResult({ content: [{ type: 'text', text: "Delivered — held for the target's next natural cycle; not revived (deferred)." }] }, { expanded: false }, theme, {})
|
|
1113
|
-
.render(120)
|
|
1114
|
-
.join('\n');
|
|
1115
|
-
assert.match(deferredHeld, /<accent>delivered · held \(deferred\)<\/accent>/);
|
|
1116
|
-
const deferredHeldExpanded = def
|
|
1117
|
-
.renderResult({ content: [{ type: 'text', text: "Delivered — held for the target's next natural cycle; not revived (deferred)." }] }, { expanded: true }, theme, {})
|
|
1118
|
-
.render(120)
|
|
1119
|
-
.join('\n');
|
|
1120
|
-
assert.match(deferredHeldExpanded, /Delivered — held for the target's next natural cycle; not revived \(deferred\)\./);
|
|
1121
|
-
});
|
|
1122
|
-
test('a crtr node yield card collapses to a refresh-armed status line, not the raw bullets', () => {
|
|
1123
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr node yield' });
|
|
1124
|
-
const withExtras = def
|
|
1125
|
-
.renderResult({ content: [{ type: 'text', text: '- node_id: mq123\n- promoted: true\n- model: strong\n- will_refresh: true' }] }, { expanded: false }, theme, {})
|
|
1126
|
-
.render(120)
|
|
1127
|
-
.join('\n');
|
|
1128
|
-
assert.match(withExtras, /<accent>yield armed — refreshes on next wake<\/accent>/);
|
|
1129
|
-
assert.match(withExtras, /<muted>promoted → orchestrator · model: strong<\/muted>/);
|
|
1130
|
-
const withExtrasExpanded = def
|
|
1131
|
-
.renderResult({ content: [{ type: 'text', text: '- node_id: mq123\n- promoted: true\n- model: strong\n- will_refresh: true' }] }, { expanded: true }, theme, {})
|
|
1132
|
-
.render(120)
|
|
1133
|
-
.join('\n');
|
|
1134
|
-
assert.match(withExtrasExpanded, /promoted: true/);
|
|
1135
|
-
assert.match(withExtrasExpanded, /model: strong/);
|
|
1136
|
-
const plain = def
|
|
1137
|
-
.renderResult({ content: [{ type: 'text', text: '- node_id: mq123\n- will_refresh: true' }] }, { expanded: false }, theme, {})
|
|
1138
|
-
.render(120)
|
|
1139
|
-
.join('\n');
|
|
1140
|
-
assert.match(plain, /<accent>yield armed — refreshes on next wake<\/accent>/);
|
|
1141
|
-
assert.doesNotMatch(plain, /promoted/);
|
|
1142
|
-
const plainExpanded = def
|
|
1143
|
-
.renderResult({ content: [{ type: 'text', text: '- node_id: mq123\n- will_refresh: true' }] }, { expanded: true }, theme, {})
|
|
1144
|
-
.render(120)
|
|
1145
|
-
.join('\n');
|
|
1146
|
-
assert.match(plainExpanded, /node_id: mq123/);
|
|
1147
|
-
});
|
|
1148
|
-
test('a crtr node lifecycle close card collapses to a cascade digest, not the raw prose sentence', () => {
|
|
1149
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr node lifecycle close --node mq123' });
|
|
1150
|
-
const cascade = def
|
|
1151
|
-
.renderResult({ content: [{ type: 'text', text: 'Closed mq123 and its exclusive subtree — 3 node(s) closed, 1 spared (still managed from outside the subtree).' }] }, { expanded: false }, theme, {})
|
|
1152
|
-
.render(120)
|
|
1153
|
-
.join('\n');
|
|
1154
|
-
assert.match(cascade, /<success>closed mq123<\/success>/);
|
|
1155
|
-
assert.match(cascade, /<warning>2 descendants canceled<\/warning>/);
|
|
1156
|
-
assert.match(cascade, /<muted>1 spared \(managed elsewhere\)<\/muted>/);
|
|
1157
|
-
const cascadeExpanded = def
|
|
1158
|
-
.renderResult({ content: [{ type: 'text', text: 'Closed mq123 and its exclusive subtree — 3 node(s) closed, 1 spared (still managed from outside the subtree).' }] }, { expanded: true }, theme, {})
|
|
1159
|
-
.render(120)
|
|
1160
|
-
// Expanded lines WRAP rather than clip, so rejoin the wrap to assert the
|
|
1161
|
-
// full sentence survived intact.
|
|
1162
|
-
.join(' ')
|
|
1163
|
-
.replace(/\s+/g, ' ');
|
|
1164
|
-
assert.match(cascadeExpanded, /Closed mq123 and its exclusive subtree — 3 node\(s\) closed, 1 spared \(still managed from outside the subtree\)/);
|
|
1165
|
-
const solo = def
|
|
1166
|
-
.renderResult({ content: [{ type: 'text', text: 'Closed mq123 and its exclusive subtree — 1 node(s) closed.' }] }, { expanded: false }, theme, {})
|
|
1167
|
-
.render(120)
|
|
1168
|
-
.join('\n');
|
|
1169
|
-
assert.match(solo, /<success>closed mq123<\/success>/);
|
|
1170
|
-
assert.doesNotMatch(solo, /descendants? canceled/);
|
|
1171
|
-
const soloExpanded = def
|
|
1172
|
-
.renderResult({ content: [{ type: 'text', text: 'Closed mq123 and its exclusive subtree — 1 node(s) closed.' }] }, { expanded: true }, theme, {})
|
|
1173
|
-
.render(120)
|
|
1174
|
-
.join('\n');
|
|
1175
|
-
assert.match(soloExpanded, /Closed mq123 and its exclusive subtree — 1 node\(s\) closed\./);
|
|
1176
|
-
});
|
|
1177
|
-
test('crtr push urgent/final cards collapse to a one-line outcome + report basename', () => {
|
|
1178
|
-
const urgentDef = createCrtrBashToolDefinition({ command: 'crtr push urgent' });
|
|
1179
|
-
const urgent = urgentDef
|
|
1180
|
-
.renderResult({ content: [{ type: 'text', text: 'Urgent report fanned to 3 subscriber(s) — they are force-woken.\n\n- report: /Users/x/nodes/mq1/reports/20260101-urgent.md' }] }, { expanded: false }, theme, {})
|
|
1181
|
-
.render(120)
|
|
1182
|
-
.join('\n');
|
|
1183
|
-
assert.match(urgent, /<warning>urgent → 3 subscribers woken<\/warning>/);
|
|
1184
|
-
const urgentExpanded = urgentDef
|
|
1185
|
-
.renderResult({ content: [{ type: 'text', text: 'Urgent report fanned to 3 subscriber(s) — they are force-woken.\n\n- report: /Users/x/nodes/mq1/reports/20260101-urgent.md' }] }, { expanded: true }, theme, {})
|
|
1186
|
-
.render(120)
|
|
1187
|
-
.join('\n');
|
|
1188
|
-
assert.match(urgentExpanded, /report: \/Users\/x\/nodes\/mq1\/reports\/20260101-urgent\.md/);
|
|
1189
|
-
const finalDef = createCrtrBashToolDefinition({ command: 'crtr push final' });
|
|
1190
|
-
const final = finalDef
|
|
1191
|
-
.renderResult({
|
|
1192
|
-
content: [
|
|
1193
|
-
{
|
|
1194
|
-
type: 'text',
|
|
1195
|
-
text: 'Result recorded — node finished; its engine shuts down on next stop. Nothing more to do here: STOP your turn immediately. Reply with just "Done." and nothing else.\n\n- report: /Users/x/nodes/mq1/reports/20260101-final.md',
|
|
1196
|
-
},
|
|
1197
|
-
],
|
|
1198
|
-
}, { expanded: false }, theme, {})
|
|
1199
|
-
.render(120)
|
|
1200
|
-
.join('\n');
|
|
1201
|
-
assert.match(final, /<success>final pushed · node done<\/success>/);
|
|
1202
|
-
assert.match(final, /<muted>20260101-final\.md<\/muted>/);
|
|
1203
|
-
const finalExpanded = finalDef
|
|
1204
|
-
.renderResult({
|
|
1205
|
-
content: [
|
|
1206
|
-
{
|
|
1207
|
-
type: 'text',
|
|
1208
|
-
text: 'Result recorded — node finished; its engine shuts down on next stop. Nothing more to do here: STOP your turn immediately. Reply with just "Done." and nothing else.\n\n- report: /Users/x/nodes/mq1/reports/20260101-final.md',
|
|
1209
|
-
},
|
|
1210
|
-
],
|
|
1211
|
-
}, { expanded: true }, theme, {})
|
|
1212
|
-
.render(120)
|
|
1213
|
-
.join('\n');
|
|
1214
|
-
assert.match(finalExpanded, /report: \/Users\/x\/nodes\/mq1\/reports\/20260101-final\.md/);
|
|
1215
|
-
});
|
|
1216
|
-
// ---------------------------------------------------------------------------
|
|
1217
|
-
// Batch C — human/memory/pkg summarizers (preview-registry.ts "Batch C")
|
|
1218
|
-
// ---------------------------------------------------------------------------
|
|
1219
|
-
// Ask and review share the queuedFollowUp road-sign (prompts.ts): arrival is
|
|
1220
|
-
// always just durable queue state, so the card collapses to one posted line.
|
|
1221
|
-
// The summarizer keys on the stable `Queued for the human` prefix; the inbox-
|
|
1222
|
-
// affordance tail resolves per call, so this fixture pins one resolved form.
|
|
1223
|
-
const QUEUED_FOLLOW_UP = 'Queued for the human. Their answer is pushed to your inbox and wakes you when they respond — never poll, verify it opened, or block. To see the queue, press ⌥I in a node viewer (or run `crtr human list` for the queue).';
|
|
1224
|
-
const HUMAN_ASK_QUEUED_OUTPUT = [
|
|
1225
|
-
'- job_id: mrASK0001',
|
|
1226
|
-
'',
|
|
1227
|
-
'**dir:** /Users/silas/Code/cli/crouter',
|
|
1228
|
-
'',
|
|
1229
|
-
`**follow_up:** ${QUEUED_FOLLOW_UP}`,
|
|
1230
|
-
].join('\n');
|
|
1231
|
-
test('a crtr human ask card collapses to a posted status + question teaser, not the raw fields', () => {
|
|
1232
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr human ask "Should we ship the preview rollout now?"' });
|
|
1233
|
-
const collapsed = def
|
|
1234
|
-
.renderResult({ content: [{ type: 'text', text: HUMAN_ASK_QUEUED_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1235
|
-
.render(140)
|
|
1236
|
-
.join('\n');
|
|
1237
|
-
assert.match(collapsed, /<accent>ask posted → waiting on human<\/accent>/);
|
|
1238
|
-
assert.match(collapsed, /<muted>Should we ship the preview rollout now\?<\/muted>/);
|
|
1239
|
-
assert.doesNotMatch(collapsed, /- job_id:/);
|
|
1240
|
-
const expanded = def
|
|
1241
|
-
.renderResult({ content: [{ type: 'text', text: HUMAN_ASK_QUEUED_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1242
|
-
.render(140)
|
|
1243
|
-
.join('\n');
|
|
1244
|
-
assert.match(expanded, /job_id: mrASK0001/);
|
|
1245
|
-
assert.match(expanded, /wakes you when they respond/);
|
|
1246
|
-
});
|
|
1247
|
-
const HUMAN_REVIEW_NEW_OUTPUT = [
|
|
1248
|
-
'- job_id: mrREV0001',
|
|
1249
|
-
'',
|
|
1250
|
-
'**dir:** /Users/silas/Code/cli/crouter',
|
|
1251
|
-
'',
|
|
1252
|
-
'**output:** /Users/silas/Code/cli/crouter/feedback.json',
|
|
1253
|
-
'',
|
|
1254
|
-
'- review_id: review-0001',
|
|
1255
|
-
'',
|
|
1256
|
-
'**follow_up:** The review is live for the person in the attach viewer with its companion agent. Approval is pushed to the origin inbox when submitted.',
|
|
1257
|
-
].join('\n');
|
|
1258
|
-
test('a crtr human review new card collapses to its live companion status + file basename, not the raw fields', () => {
|
|
1259
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr human review new /Users/silas/Code/cli/crouter/spec.md --subtitle "Review the implementation."' });
|
|
1260
|
-
const collapsed = def
|
|
1261
|
-
.renderResult({ content: [{ type: 'text', text: HUMAN_REVIEW_NEW_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1262
|
-
.render(140)
|
|
1263
|
-
.join('\n');
|
|
1264
|
-
assert.match(collapsed, /<accent>review posted: spec\.md → live with companion<\/accent>/);
|
|
1265
|
-
assert.doesNotMatch(collapsed, /- job_id:/);
|
|
1266
|
-
const expanded = def
|
|
1267
|
-
.renderResult({ content: [{ type: 'text', text: HUMAN_REVIEW_NEW_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1268
|
-
.render(140)
|
|
1269
|
-
.join('\n');
|
|
1270
|
-
assert.match(expanded, /job_id: mrREV0001/);
|
|
1271
|
-
assert.match(expanded, /review_id: review-0001/);
|
|
1272
|
-
assert.match(expanded, /Approval is pushed to the origin inbox when\s+submitted/);
|
|
1273
|
-
});
|
|
1274
|
-
const HUMAN_LIST_OUTPUT = [
|
|
1275
|
-
'- total: 2',
|
|
1276
|
-
'',
|
|
1277
|
-
'2 items:',
|
|
1278
|
-
'',
|
|
1279
|
-
'| id | job_id | dir | title | kind | blocked_since | asking_node_id | asking_node_name | conversation_id | conversation_title | interaction_count |',
|
|
1280
|
-
'| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |',
|
|
1281
|
-
'| h1 | mrJOB1 | /Users/silas/Code/cli/crouter | Ship the rollout? | decision | 2026-06-30T12:00:00.000Z | mrNODE1 | orchestrator | c1 | Rollout plan | 3 |',
|
|
1282
|
-
'| h2 | mrJOB2 | /Users/silas/Code/cli/crouter | Quick check | | 2026-06-30T12:05:00.000Z | mrNODE2 | reviewer | c2 | Spec review | 1 |',
|
|
1283
|
-
].join('\n');
|
|
1284
|
-
test('a crtr human list card collapses to a pending count + kind digest, not the raw table', () => {
|
|
1285
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr human list' });
|
|
1286
|
-
const collapsed = def
|
|
1287
|
-
.renderResult({ content: [{ type: 'text', text: HUMAN_LIST_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1288
|
-
.render(140)
|
|
1289
|
-
.join('\n');
|
|
1290
|
-
assert.match(collapsed, /2 pending/);
|
|
1291
|
-
assert.match(collapsed, /<muted>decision×1<\/muted>/);
|
|
1292
|
-
assert.doesNotMatch(collapsed, /\| id \|/);
|
|
1293
|
-
const expanded = def
|
|
1294
|
-
.renderResult({ content: [{ type: 'text', text: HUMAN_LIST_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1295
|
-
.render(140)
|
|
1296
|
-
.join('\n');
|
|
1297
|
-
assert.match(expanded, /\| id \| job_id \| dir \|/);
|
|
1298
|
-
const none = def
|
|
1299
|
-
.renderResult({ content: [{ type: 'text', text: '- total: 0\n\n0 items.' }] }, { expanded: false }, theme, {})
|
|
1300
|
-
.render(140)
|
|
1301
|
-
.join('\n');
|
|
1302
|
-
assert.match(none, /<success>none pending<\/success>/);
|
|
1303
|
-
// REQUIREMENT: total is nonzero but the page is empty (exhausted
|
|
1304
|
-
// pagination) — must NOT collapse to "none pending" (that's only correct
|
|
1305
|
-
// for a true zero total); fall back to the generic renderer instead.
|
|
1306
|
-
const exhausted = def
|
|
1307
|
-
.renderResult({ content: [{ type: 'text', text: '- total: 3\n\n0 items.' }] }, { expanded: false }, theme, {})
|
|
1308
|
-
.render(140)
|
|
1309
|
-
.join('\n');
|
|
1310
|
-
assert.doesNotMatch(exhausted, /none pending/);
|
|
1311
|
-
});
|
|
1312
|
-
const HUMAN_DECK_OUTPUT = [
|
|
1313
|
-
'- id: h1',
|
|
1314
|
-
'- title: Ship the rollout?',
|
|
1315
|
-
'- kind: decision',
|
|
1316
|
-
'- interaction_count: 1',
|
|
1317
|
-
'',
|
|
1318
|
-
'1 interactions:',
|
|
1319
|
-
'',
|
|
1320
|
-
'| id | kind | prompt | options | allow_freetext |',
|
|
1321
|
-
'| --- | --- | --- | --- | --- |',
|
|
1322
|
-
'| i1 | decision | Ship it? | {"id":"yes","label":"Yes"}, {"id":"no","label":"No"} | false |',
|
|
1323
|
-
].join('\n');
|
|
1324
|
-
const HUMAN_DECK_MULTI_OUTPUT = [
|
|
1325
|
-
'- id: h1',
|
|
1326
|
-
'- title: Ship the rollout?',
|
|
1327
|
-
'- kind: decision',
|
|
1328
|
-
'- interaction_count: 2',
|
|
1329
|
-
'',
|
|
1330
|
-
'2 interactions:',
|
|
1331
|
-
'',
|
|
1332
|
-
'| id | kind | prompt | options | allow_freetext |',
|
|
1333
|
-
'| --- | --- | --- | --- | --- |',
|
|
1334
|
-
'| i1 | decision | Ship it? | {"id":"yes","label":"Yes"}, {"id":"no","label":"No"} | true |',
|
|
1335
|
-
'| i2 | question | Any concerns? | | true |',
|
|
1336
|
-
].join('\n');
|
|
1337
|
-
test('a crtr human deck card collapses to a prompt teaser + option/freetext digest, not the raw table', () => {
|
|
1338
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr human deck h1' });
|
|
1339
|
-
const collapsed = def
|
|
1340
|
-
.renderResult({ content: [{ type: 'text', text: HUMAN_DECK_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1341
|
-
.render(140)
|
|
1342
|
-
.join('\n');
|
|
1343
|
-
assert.match(collapsed, /Ship it\?/);
|
|
1344
|
-
assert.match(collapsed, /<muted>2 options<\/muted>/);
|
|
1345
|
-
assert.doesNotMatch(collapsed, /\| id \| kind \|/);
|
|
1346
|
-
const expanded = def
|
|
1347
|
-
.renderResult({ content: [{ type: 'text', text: HUMAN_DECK_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1348
|
-
.render(140)
|
|
1349
|
-
.join('\n');
|
|
1350
|
-
assert.match(expanded, /\| id \| kind \| prompt \| options \| allow_freetext \|/);
|
|
1351
|
-
const multi = def
|
|
1352
|
-
.renderResult({ content: [{ type: 'text', text: HUMAN_DECK_MULTI_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1353
|
-
.render(140)
|
|
1354
|
-
.join('\n');
|
|
1355
|
-
assert.match(multi, /Ship it\?/);
|
|
1356
|
-
assert.match(multi, /<muted>2 options · freetext · 2 interactions<\/muted>/);
|
|
1357
|
-
// REQUIREMENT: a malformed/truncated `allow_freetext` cell (neither
|
|
1358
|
-
// `true` nor `false`) must not silently default to "no freetext" — it
|
|
1359
|
-
// must fall back to the generic renderer instead.
|
|
1360
|
-
const malformedFreetext = [
|
|
1361
|
-
'- id: h1',
|
|
1362
|
-
'- title: Ship the rollout?',
|
|
1363
|
-
'- kind: decision',
|
|
1364
|
-
'- interaction_count: 1',
|
|
1365
|
-
'',
|
|
1366
|
-
'1 interactions:',
|
|
1367
|
-
'',
|
|
1368
|
-
'| id | kind | prompt | options | allow_freetext |',
|
|
1369
|
-
'| --- | --- | --- | --- | --- |',
|
|
1370
|
-
'| i1 | decision | Ship it? | {"id":"yes","label":"Yes"}, {"id":"no","label":"No"} | |',
|
|
1371
|
-
].join('\n');
|
|
1372
|
-
const malformed = def
|
|
1373
|
-
.renderResult({ content: [{ type: 'text', text: malformedFreetext }] }, { expanded: false }, theme, {})
|
|
1374
|
-
.render(140)
|
|
1375
|
-
.join('\n');
|
|
1376
|
-
assert.doesNotMatch(malformed, /<muted>2 options<\/muted>/);
|
|
1377
|
-
});
|
|
1378
|
-
const MEMORY_LIST_OUTPUT = [
|
|
1379
|
-
'- total: 3',
|
|
1380
|
-
'',
|
|
1381
|
-
'Read one in full with `crtr memory read <name>`, or pass --paths for its file path to edit directly. Narrow with --kind / --scope, page with --cursor, or search a topic with `crtr memory find <query>`.',
|
|
1382
|
-
'',
|
|
1383
|
-
'3 items:',
|
|
1384
|
-
'',
|
|
1385
|
-
'| name | short_form | kind | scope | is_dir |',
|
|
1386
|
-
'| --- | --- | --- | --- | --- |',
|
|
1387
|
-
'| ai/x | AI helpers reference | knowledge | user | false |',
|
|
1388
|
-
'| taste/y | Taste doc | preference | project | false |',
|
|
1389
|
-
'| todos/z | Todo doc | knowledge | node | false |',
|
|
1390
|
-
].join('\n');
|
|
1391
|
-
test('a crtr memory list card collapses to a doc count + kind/scope digest, not the raw table', () => {
|
|
1392
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr memory list' });
|
|
1393
|
-
const collapsed = def
|
|
1394
|
-
.renderResult({ content: [{ type: 'text', text: MEMORY_LIST_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1395
|
-
.render(140)
|
|
1396
|
-
.join('\n');
|
|
1397
|
-
assert.match(collapsed, /3 docs/);
|
|
1398
|
-
assert.match(collapsed, /<muted>knowledge×2, preference×1 \/ node×1, project×1, user×1<\/muted>/);
|
|
1399
|
-
assert.doesNotMatch(collapsed, /\| name \|/);
|
|
1400
|
-
const expanded = def
|
|
1401
|
-
.renderResult({ content: [{ type: 'text', text: MEMORY_LIST_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1402
|
-
.render(140)
|
|
1403
|
-
.join('\n');
|
|
1404
|
-
assert.match(expanded, /\| name \| short_form \| kind \|/);
|
|
1405
|
-
});
|
|
1406
|
-
const MEMORY_FIND_OUTPUT = [
|
|
1407
|
-
'- query: batch c fixtures',
|
|
1408
|
-
'- matches: 12',
|
|
1409
|
-
'',
|
|
1410
|
-
'Matches ranked by relevance.',
|
|
1411
|
-
'',
|
|
1412
|
-
'2 hits:',
|
|
1413
|
-
'',
|
|
1414
|
-
'| name | kind | scope | score | short_form |',
|
|
1415
|
-
'| --- | --- | --- | --- | --- |',
|
|
1416
|
-
'| ai/x | knowledge | user | 18 | AI helpers reference |',
|
|
1417
|
-
'| taste/y | preference | project | 11 | Taste doc |',
|
|
1418
|
-
].join('\n');
|
|
1419
|
-
const MEMORY_FIND_EMPTY_OUTPUT = ['- query: nonexistent thing', '', 'Matches ranked by relevance.', '', '0 hits.'].join('\n');
|
|
1420
|
-
const MEMORY_FIND_GREP_OUTPUT = [
|
|
1421
|
-
'- query: TODO',
|
|
1422
|
-
'- matches: 1',
|
|
1423
|
-
'',
|
|
1424
|
-
'Matches ranked by relevance.',
|
|
1425
|
-
'',
|
|
1426
|
-
'1 hits:',
|
|
1427
|
-
'',
|
|
1428
|
-
'| name | scope | line | text |',
|
|
1429
|
-
'| --- | --- | --- | --- |',
|
|
1430
|
-
'| notes/a | project | 12 | TODO: fix this |',
|
|
1431
|
-
].join('\n');
|
|
1432
|
-
test('a crtr memory find card collapses to a hit count + name digest, not the raw table', () => {
|
|
1433
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr memory find "batch c fixtures"' });
|
|
1434
|
-
const collapsed = def
|
|
1435
|
-
.renderResult({ content: [{ type: 'text', text: MEMORY_FIND_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1436
|
-
.render(140)
|
|
1437
|
-
.join('\n');
|
|
1438
|
-
assert.match(collapsed, /2 hits for "batch c fixtures"/);
|
|
1439
|
-
assert.match(collapsed, /<muted>ai\/x, taste\/y<\/muted>/);
|
|
1440
|
-
assert.doesNotMatch(collapsed, /\| name \|/);
|
|
1441
|
-
const expanded = def
|
|
1442
|
-
.renderResult({ content: [{ type: 'text', text: MEMORY_FIND_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1443
|
-
.render(140)
|
|
1444
|
-
.join('\n');
|
|
1445
|
-
assert.match(expanded, /\| name \| kind \| scope \|/);
|
|
1446
|
-
assert.doesNotMatch(expanded, /\| path \|/);
|
|
1447
|
-
const empty = def
|
|
1448
|
-
.renderResult({ content: [{ type: 'text', text: MEMORY_FIND_EMPTY_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1449
|
-
.render(140)
|
|
1450
|
-
.join('\n');
|
|
1451
|
-
assert.match(empty, /<muted>0 hits for "nonexistent thing"<\/muted>/);
|
|
1452
|
-
const grepDef = createCrtrBashToolDefinition({ command: 'crtr memory find TODO --grep' });
|
|
1453
|
-
const grepCollapsed = grepDef
|
|
1454
|
-
.renderResult({ content: [{ type: 'text', text: MEMORY_FIND_GREP_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1455
|
-
.render(140)
|
|
1456
|
-
.join('\n');
|
|
1457
|
-
assert.doesNotMatch(grepCollapsed, /hits for "TODO"/);
|
|
1458
|
-
assert.match(grepCollapsed, /TODO: fix this/);
|
|
1459
|
-
});
|
|
1460
|
-
const MEMORY_WRITE_CREATED_OUTPUT = [
|
|
1461
|
-
'- name: taste/batch-c-fixtures',
|
|
1462
|
-
'- kind: knowledge',
|
|
1463
|
-
'- scope: node',
|
|
1464
|
-
'- created: true',
|
|
1465
|
-
'',
|
|
1466
|
-
'**path:** /Users/silas/Code/cli/crouter/.crouter/memory/taste/batch-c-fixtures.md',
|
|
1467
|
-
'',
|
|
1468
|
-
'**follow_up:** Written. Read it back with `crtr memory read taste/batch-c-fixtures`.',
|
|
1469
|
-
'',
|
|
1470
|
-
'4 frontmatter:',
|
|
1471
|
-
'',
|
|
1472
|
-
'| key | value |',
|
|
1473
|
-
'| --- | --- |',
|
|
1474
|
-
'| kind | knowledge |',
|
|
1475
|
-
'| when-and-why-to-read | When changing batch C fixtures, read this because the stable contract prevents rework. |',
|
|
1476
|
-
'| system-prompt-visibility | preview |',
|
|
1477
|
-
'| file-read-visibility | none |',
|
|
1478
|
-
].join('\n');
|
|
1479
|
-
const MEMORY_WRITE_UPDATED_OUTPUT = [
|
|
1480
|
-
'- name: taste/batch-c-fixtures',
|
|
1481
|
-
'- kind: knowledge',
|
|
1482
|
-
'- scope: node',
|
|
1483
|
-
'- created: false',
|
|
1484
|
-
'',
|
|
1485
|
-
'**path:** /Users/silas/Code/cli/crouter/.crouter/memory/taste/batch-c-fixtures.md',
|
|
1486
|
-
'',
|
|
1487
|
-
'**follow_up:** Updated. Read it back with `crtr memory read taste/batch-c-fixtures`.',
|
|
1488
|
-
'',
|
|
1489
|
-
'1 frontmatter:',
|
|
1490
|
-
'',
|
|
1491
|
-
'| key | value |',
|
|
1492
|
-
'| --- | --- |',
|
|
1493
|
-
'| kind | knowledge |',
|
|
1494
|
-
].join('\n');
|
|
1495
|
-
test('a crtr memory write card shows selected frontmatter as aligned key/value pairs', () => {
|
|
1496
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr memory write taste/batch-c-fixtures \\\n --kind knowledge \\\n --system-prompt-visibility preview' });
|
|
1497
|
-
const collapsed = def
|
|
1498
|
-
.renderResult({ content: [{ type: 'text', text: MEMORY_WRITE_CREATED_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1499
|
-
.render(140)
|
|
1500
|
-
.join('\n');
|
|
1501
|
-
assert.match(collapsed, /<success>wrote taste\/batch-c-fixtures<\/success>/);
|
|
1502
|
-
assert.match(collapsed, /kind\s*: knowledge/);
|
|
1503
|
-
assert.match(collapsed, /when-and-why-to-read\s*: When changing batch C fixtures/);
|
|
1504
|
-
assert.match(collapsed, /system-prompt-visibility\s*: preview/);
|
|
1505
|
-
assert.match(collapsed, /file-read-visibility\s*: none/);
|
|
1506
|
-
assert.doesNotMatch(collapsed, /\/Users\/silas/);
|
|
1507
|
-
assert.doesNotMatch(collapsed, /- created:/);
|
|
1508
|
-
const expanded = def
|
|
1509
|
-
.renderResult({ content: [{ type: 'text', text: MEMORY_WRITE_CREATED_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1510
|
-
.render(140)
|
|
1511
|
-
.join('\n');
|
|
1512
|
-
assert.match(expanded, /created: true/);
|
|
1513
|
-
assert.match(expanded, /4 frontmatter:/);
|
|
1514
|
-
const updated = def
|
|
1515
|
-
.renderResult({ content: [{ type: 'text', text: MEMORY_WRITE_UPDATED_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1516
|
-
.render(140)
|
|
1517
|
-
.join('\n');
|
|
1518
|
-
assert.match(updated, /<success>updated taste\/batch-c-fixtures<\/success>/);
|
|
1519
|
-
assert.match(updated, /kind\s*: knowledge/);
|
|
1520
|
-
});
|
|
1521
|
-
test('configuration mutations share an outcome-first aligned key/value preview', () => {
|
|
1522
|
-
const cases = [
|
|
1523
|
-
{
|
|
1524
|
-
command: 'crtr node config --node n1 --model strong --mode orchestrator',
|
|
1525
|
-
output: 'Configured n1 — model=anthropic/claude-opus-4-6, mode=orchestrator',
|
|
1526
|
-
expected: [/configured n1/, /model\s*: anthropic\/claude-opus-4-6/, /mode\s*: orchestrator/],
|
|
1527
|
-
},
|
|
1528
|
-
{
|
|
1529
|
-
command: 'crtr sys config set auto_update.crtr --value apply',
|
|
1530
|
-
output: '- key: auto_update.crtr\n- value: apply\n- scope: user',
|
|
1531
|
-
expected: [/updated config/, /auto_update\.crtr\s*: apply/, /scope\s*: user/],
|
|
1532
|
-
},
|
|
1533
|
-
{
|
|
1534
|
-
command: 'crtr canvas config add prod --preview https://prod.example --relay-token -',
|
|
1535
|
-
output: "Registered remote canvas target 'prod' → https://prod.example (token stored).",
|
|
1536
|
-
expected: [/registered prod/, /preview\s*: https:\/\/prod\.example/, /relay-token\s*: stored/],
|
|
1537
|
-
},
|
|
1538
|
-
{
|
|
1539
|
-
command: 'crtr canvas config remove old',
|
|
1540
|
-
output: "Removed remote canvas target 'old'.",
|
|
1541
|
-
expected: [/removed old/],
|
|
1542
|
-
},
|
|
1543
|
-
{
|
|
1544
|
-
command: 'crtr canvas use prod',
|
|
1545
|
-
output: 'Active canvas: prod.',
|
|
1546
|
-
expected: [/active canvas: prod/],
|
|
1547
|
-
},
|
|
1548
|
-
];
|
|
1549
|
-
for (const item of cases) {
|
|
1550
|
-
const def = createCrtrBashToolDefinition({ command: item.command });
|
|
1551
|
-
const collapsed = def
|
|
1552
|
-
.renderResult({ content: [{ type: 'text', text: item.output }] }, { expanded: false }, theme, {})
|
|
1553
|
-
.render(140)
|
|
1554
|
-
.join('\n');
|
|
1555
|
-
for (const expected of item.expected)
|
|
1556
|
-
assert.match(collapsed, expected);
|
|
1557
|
-
}
|
|
54
|
+
const component = def.renderResult({ content: [{ type: 'text', text: 'dashboard' }] }, { expanded: false }, theme, { invalidate: () => { ownerInvalidations++; } });
|
|
55
|
+
component.invalidate?.();
|
|
56
|
+
assert.equal(ownerInvalidations, 0);
|
|
1558
57
|
});
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
['crtr pkg plugin remove ai', '- removed: true\n\n1 scopes:\n- user', [/removed ai/, /scopes\s*: user/]],
|
|
1569
|
-
['crtr pkg market add https://example.test/m.git', '- name: official\n- scope: user\n\n/Users/silas/.crouter/marketplaces/official', [/added marketplace official/, /scope\s*: user/]],
|
|
1570
|
-
['crtr pkg market remove official', '- name: official\n- removed: true', [/removed marketplace official/]],
|
|
1571
|
-
['crtr pkg market update --name official', '1 updated:\n\n| name | updated | sha |\n| --- | --- | --- |\n| official | true | abcdef123 |', [/updated official/, /abcdef1/]],
|
|
1572
|
-
];
|
|
1573
|
-
for (const [command, output, expected] of cases) {
|
|
1574
|
-
const def = createCrtrBashToolDefinition({ command });
|
|
1575
|
-
const collapsed = def.renderResult({ content: [{ type: 'text', text: output }] }, { expanded: false }, theme, {}).render(140).join('\n');
|
|
1576
|
-
for (const pattern of expected)
|
|
1577
|
-
assert.match(collapsed, pattern, command);
|
|
1578
|
-
assert.match(collapsed, /ctrl\+o to expand/, command);
|
|
58
|
+
// Pane-overflow invariant: a rendered line wider than the pane breaks the
|
|
59
|
+
// viewer's frame-length guarantee, expanded or collapsed.
|
|
60
|
+
test('crtr output clamps long lines to the terminal width, expanded and collapsed', () => {
|
|
61
|
+
for (const expanded of [true, false]) {
|
|
62
|
+
const lines = renderResult('crtr canvas dashboard', 'x'.repeat(442), expanded);
|
|
63
|
+
assert.ok(lines.length > 0);
|
|
64
|
+
for (const line of lines) {
|
|
65
|
+
assert.ok(visibleWidth(line) <= 70, `line exceeded width (expanded=${expanded}): ${visibleWidth(line)} > 70`);
|
|
66
|
+
}
|
|
1579
67
|
}
|
|
1580
68
|
});
|
|
1581
|
-
const PKG_PLUGIN_INSTALL_OUTPUT = [
|
|
1582
|
-
'- name: ai',
|
|
1583
|
-
'- scope: user',
|
|
1584
|
-
'- transport: exec',
|
|
1585
|
-
'- commands: {"mounts":["app"],"issues":[]}',
|
|
1586
|
-
'',
|
|
1587
|
-
'/Users/silas/.crouter/plugins/ai',
|
|
1588
|
-
].join('\n');
|
|
1589
|
-
const PKG_PLUGIN_INSTALL_SHORT_PATH_OUTPUT = ['- name: ai', '- scope: user', '- transport: http', '- path: /a'].join('\n');
|
|
1590
|
-
test('a crtr pkg plugin install card collapses to an installed status + scope/path digest, not the raw fields', () => {
|
|
1591
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr pkg plugin install ai' });
|
|
1592
|
-
const collapsed = def
|
|
1593
|
-
.renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_INSTALL_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1594
|
-
.render(140)
|
|
1595
|
-
.join('\n');
|
|
1596
|
-
assert.match(collapsed, /<success>installed ai · exec<\/success>/);
|
|
1597
|
-
assert.match(collapsed, /<muted>user · \/Users\/silas\/\.crouter\/plugins\/ai<\/muted>/);
|
|
1598
|
-
assert.doesNotMatch(collapsed, /- name:/);
|
|
1599
|
-
const expanded = def
|
|
1600
|
-
.renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_INSTALL_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1601
|
-
.render(140)
|
|
1602
|
-
.join('\n');
|
|
1603
|
-
assert.match(expanded, /\/Users\/silas\/\.crouter\/plugins\/ai/);
|
|
1604
|
-
const shortPath = def
|
|
1605
|
-
.renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_INSTALL_SHORT_PATH_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1606
|
-
.render(140)
|
|
1607
|
-
.join('\n');
|
|
1608
|
-
assert.match(shortPath, /<success>installed ai · http<\/success>/);
|
|
1609
|
-
assert.match(shortPath, /<muted>user · \/a<\/muted>/);
|
|
1610
|
-
});
|
|
1611
|
-
const PKG_PLUGIN_UPDATE_UPDATED_OUTPUT = ['1 updated:', '', '| name | transport | updated | sha |', '| --- | --- | --- | --- |', '| ai | http | true | |'].join('\n');
|
|
1612
|
-
const PKG_PLUGIN_UPDATE_CURRENT_OUTPUT = ['1 updated:', '', '| name | transport | updated | sha |', '| --- | --- | --- | --- |', '| ai | exec | false | abc1234 |'].join('\n');
|
|
1613
|
-
const PKG_PLUGIN_UPDATE_MULTI_OUTPUT = [
|
|
1614
|
-
'3 updated:',
|
|
1615
|
-
'',
|
|
1616
|
-
'| name | transport | updated | sha |',
|
|
1617
|
-
'| --- | --- | --- | --- |',
|
|
1618
|
-
'| ai | exec | true | aaa1111 |',
|
|
1619
|
-
'| beta | exec | false | bbb2222 |',
|
|
1620
|
-
'| gamma | http | true | |',
|
|
1621
|
-
].join('\n');
|
|
1622
|
-
test('a crtr pkg plugin update card collapses to an updated/current transport-aware status + sha, not the raw table', () => {
|
|
1623
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr pkg plugin update --name ai' });
|
|
1624
|
-
const collapsed = def
|
|
1625
|
-
.renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_UPDATE_UPDATED_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1626
|
-
.render(140)
|
|
1627
|
-
.join('\n');
|
|
1628
|
-
assert.match(collapsed, /<success>refreshed ai<\/success>/);
|
|
1629
|
-
assert.match(collapsed, /<muted>http<\/muted>/);
|
|
1630
|
-
assert.doesNotMatch(collapsed, /\| name \| transport \| updated \|/);
|
|
1631
|
-
const expanded = def
|
|
1632
|
-
.renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_UPDATE_UPDATED_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1633
|
-
.render(140)
|
|
1634
|
-
.join('\n');
|
|
1635
|
-
assert.match(expanded, /\| name \| transport \| updated \| sha \|/);
|
|
1636
|
-
const current = def
|
|
1637
|
-
.renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_UPDATE_CURRENT_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1638
|
-
.render(140)
|
|
1639
|
-
.join('\n');
|
|
1640
|
-
assert.match(current, /<muted>ai already current<\/muted>/);
|
|
1641
|
-
assert.match(current, /<muted>abc1234<\/muted>/);
|
|
1642
|
-
const multiDef = createCrtrBashToolDefinition({ command: 'crtr pkg plugin update' });
|
|
1643
|
-
const multi = multiDef
|
|
1644
|
-
.renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_UPDATE_MULTI_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1645
|
-
.render(140)
|
|
1646
|
-
.join('\n');
|
|
1647
|
-
assert.match(multi, /2 updated · 1 already current · 1 HTTP/);
|
|
1648
|
-
const empty = multiDef
|
|
1649
|
-
.renderResult({ content: [{ type: 'text', text: '0 updated.' }] }, { expanded: false }, theme, {})
|
|
1650
|
-
.render(140)
|
|
1651
|
-
.join('\n');
|
|
1652
|
-
assert.match(empty, /<muted>nothing to update<\/muted>/);
|
|
1653
|
-
// REQUIREMENT: a malformed `updated` cell (neither `true` nor `false`) in
|
|
1654
|
-
// a multi-row table must not be silently counted as "already current" —
|
|
1655
|
-
// the multi-row loop must reject exactly like the single-row path does.
|
|
1656
|
-
const malformedUpdate = [
|
|
1657
|
-
'3 updated:',
|
|
1658
|
-
'',
|
|
1659
|
-
'| name | transport | updated | sha |',
|
|
1660
|
-
'| --- | --- | --- | --- |',
|
|
1661
|
-
'| ai | exec | true | aaa1111 |',
|
|
1662
|
-
'| beta | exec | maybe | bbb2222 |',
|
|
1663
|
-
'| gamma | http | false | |',
|
|
1664
|
-
].join('\n');
|
|
1665
|
-
const malformed = multiDef
|
|
1666
|
-
.renderResult({ content: [{ type: 'text', text: malformedUpdate }] }, { expanded: false }, theme, {})
|
|
1667
|
-
.render(140)
|
|
1668
|
-
.join('\n');
|
|
1669
|
-
assert.doesNotMatch(malformed, /already current/);
|
|
1670
|
-
});
|
|
1671
|
-
const PKG_PLUGIN_LIST_OUTPUT = [
|
|
1672
|
-
'- total: 3',
|
|
1673
|
-
'',
|
|
1674
|
-
'3 items:',
|
|
1675
|
-
'',
|
|
1676
|
-
'| name | scope | version | enabled | transport | source_marketplace | path |',
|
|
1677
|
-
'| --- | --- | --- | --- | --- | --- | --- |',
|
|
1678
|
-
'| ai | user | 0.7.1 | true | exec | crouter-official-marketplace | /Users/silas/.crouter/plugins/ai |',
|
|
1679
|
-
'| beta | user | 1.0.0 | false | | crouter-official-marketplace | /Users/silas/.crouter/plugins/beta |',
|
|
1680
|
-
'| gamma | project | 2.3.0 | true | http | custom-market | /Users/silas/Code/cli/crouter/.crouter/plugins/gamma |',
|
|
1681
|
-
].join('\n');
|
|
1682
|
-
test('a crtr pkg plugin list card collapses to a plugin/enabled count + name digest, not the raw table', () => {
|
|
1683
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr pkg plugin list' });
|
|
1684
|
-
const collapsed = def
|
|
1685
|
-
.renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_LIST_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1686
|
-
.render(140)
|
|
1687
|
-
.join('\n');
|
|
1688
|
-
assert.match(collapsed, /3 plugins · 2 enabled/);
|
|
1689
|
-
assert.match(collapsed, /<muted>ai \(exec\), beta, gamma \(http\)<\/muted>/);
|
|
1690
|
-
assert.doesNotMatch(collapsed, /\| name \| scope \|/);
|
|
1691
|
-
const expanded = def
|
|
1692
|
-
.renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_LIST_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1693
|
-
.render(140)
|
|
1694
|
-
.join('\n');
|
|
1695
|
-
assert.match(expanded, /\| name \| scope \| version \| enabled \| transport \|/);
|
|
1696
|
-
const empty = def
|
|
1697
|
-
.renderResult({ content: [{ type: 'text', text: '- total: 0\n\n0 items.' }] }, { expanded: false }, theme, {})
|
|
1698
|
-
.render(140)
|
|
1699
|
-
.join('\n');
|
|
1700
|
-
assert.match(empty, /<muted>0 plugins<\/muted>/);
|
|
1701
|
-
// REQUIREMENT: nonzero total with an empty page (exhausted pagination)
|
|
1702
|
-
// must NOT collapse to "0 plugins" — fall back to the generic renderer.
|
|
1703
|
-
const exhausted = def
|
|
1704
|
-
.renderResult({ content: [{ type: 'text', text: '- total: 3\n\n0 items.' }] }, { expanded: false }, theme, {})
|
|
1705
|
-
.render(140)
|
|
1706
|
-
.join('\n');
|
|
1707
|
-
assert.doesNotMatch(exhausted, /0 plugins/);
|
|
1708
|
-
});
|
|
1709
|
-
const PKG_PLUGIN_SHOW_OUTPUT = [
|
|
1710
|
-
'- name: ai',
|
|
1711
|
-
'- scope: user',
|
|
1712
|
-
'- path: /Users/silas/.crouter/plugins/ai',
|
|
1713
|
-
'- enabled: true',
|
|
1714
|
-
'- manifest: {"name":"ai","version":"0.7.1","description":"AI helpers","commands":"commands.json","transport":{"kind":"exec","executable":"bin/ai.js"}}',
|
|
1715
|
-
'- commands: {"manifestPath":"/Users/silas/.crouter/plugins/ai/commands.json","mounts":["ai"],"issues":[]}',
|
|
1716
|
-
'',
|
|
1717
|
-
'2 docs:',
|
|
1718
|
-
'',
|
|
1719
|
-
'| name | path |',
|
|
1720
|
-
'| --- | --- |',
|
|
1721
|
-
'| ai/x | /Users/silas/.crouter/plugins/ai/memory/ai/x.md |',
|
|
1722
|
-
'| ai/y | /Users/silas/.crouter/plugins/ai/memory/ai/y.md |',
|
|
1723
|
-
].join('\n');
|
|
1724
|
-
const PKG_PLUGIN_SHOW_DISABLED_OUTPUT = [
|
|
1725
|
-
'- name: beta',
|
|
1726
|
-
'- scope: user',
|
|
1727
|
-
'- path: /Users/silas/.crouter/plugins/beta',
|
|
1728
|
-
'- enabled: false',
|
|
1729
|
-
'- manifest: {"name":"beta","version":"1.0.0","description":"Beta helpers"}',
|
|
1730
|
-
'',
|
|
1731
|
-
'0 docs.',
|
|
1732
|
-
].join('\n');
|
|
1733
|
-
test('a crtr pkg plugin show card collapses to a name@version + enabled status + doc count, not the raw fields', () => {
|
|
1734
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr pkg plugin show ai' });
|
|
1735
|
-
const collapsed = def
|
|
1736
|
-
.renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_SHOW_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1737
|
-
.render(140)
|
|
1738
|
-
.join('\n');
|
|
1739
|
-
assert.match(collapsed, /<success>ai@0\.7\.1 · exec · enabled<\/success>/);
|
|
1740
|
-
assert.match(collapsed, /<muted>2 substrate docs<\/muted>/);
|
|
1741
|
-
assert.doesNotMatch(collapsed, /\| name \| path \|/);
|
|
1742
|
-
const expanded = def
|
|
1743
|
-
.renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_SHOW_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1744
|
-
.render(140)
|
|
1745
|
-
.join('\n');
|
|
1746
|
-
assert.match(expanded, /\| name \| path \|/);
|
|
1747
|
-
const disabledDef = createCrtrBashToolDefinition({ command: 'crtr pkg plugin show beta' });
|
|
1748
|
-
const disabled = disabledDef
|
|
1749
|
-
.renderResult({ content: [{ type: 'text', text: PKG_PLUGIN_SHOW_DISABLED_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1750
|
-
.render(140)
|
|
1751
|
-
.join('\n');
|
|
1752
|
-
assert.match(disabled, /<muted>beta@1\.0\.0 · disabled<\/muted>/);
|
|
1753
|
-
assert.match(disabled, /<muted>0 substrate docs<\/muted>/);
|
|
1754
|
-
});
|
|
1755
|
-
const PKG_MARKET_LIST_OUTPUT = [
|
|
1756
|
-
'- total: 2',
|
|
1757
|
-
'',
|
|
1758
|
-
'2 items:',
|
|
1759
|
-
'',
|
|
1760
|
-
'| name | scope | url | ref | path |',
|
|
1761
|
-
'| --- | --- | --- | --- | --- |',
|
|
1762
|
-
'| crouter-official-marketplace | user | https://github.com/crouton-labs/crouter-official-marketplace | main | /Users/silas/.crouter/marketplaces/crouter-official-marketplace |',
|
|
1763
|
-
'| custom-market | project | https://github.com/example/custom-market | main | /Users/silas/Code/cli/crouter/.crouter/marketplaces/custom-market |',
|
|
1764
|
-
].join('\n');
|
|
1765
|
-
test('a crtr pkg market list card collapses to a marketplace count + name digest, not the raw table', () => {
|
|
1766
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr pkg market list' });
|
|
1767
|
-
const collapsed = def
|
|
1768
|
-
.renderResult({ content: [{ type: 'text', text: PKG_MARKET_LIST_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1769
|
-
.render(140)
|
|
1770
|
-
.join('\n');
|
|
1771
|
-
assert.match(collapsed, /2 marketplaces/);
|
|
1772
|
-
assert.match(collapsed, /<muted>crouter-official-marketplace, custom-market<\/muted>/);
|
|
1773
|
-
assert.doesNotMatch(collapsed, /\| name \| scope \|/);
|
|
1774
|
-
const expanded = def
|
|
1775
|
-
.renderResult({ content: [{ type: 'text', text: PKG_MARKET_LIST_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1776
|
-
.render(140)
|
|
1777
|
-
.join('\n');
|
|
1778
|
-
assert.match(expanded, /\| name \| scope \| url \|/);
|
|
1779
|
-
const empty = def
|
|
1780
|
-
.renderResult({ content: [{ type: 'text', text: '- total: 0\n\n0 items.' }] }, { expanded: false }, theme, {})
|
|
1781
|
-
.render(140)
|
|
1782
|
-
.join('\n');
|
|
1783
|
-
assert.match(empty, /<muted>0 marketplaces<\/muted>/);
|
|
1784
|
-
// REQUIREMENT: nonzero total with an empty page (exhausted pagination)
|
|
1785
|
-
// must NOT collapse to "0 marketplaces" — fall back to the generic renderer.
|
|
1786
|
-
const exhausted = def
|
|
1787
|
-
.renderResult({ content: [{ type: 'text', text: '- total: 3\n\n0 items.' }] }, { expanded: false }, theme, {})
|
|
1788
|
-
.render(140)
|
|
1789
|
-
.join('\n');
|
|
1790
|
-
assert.doesNotMatch(exhausted, /0 marketplaces/);
|
|
1791
|
-
});
|
|
1792
|
-
const PKG_MARKET_BROWSE_ROWS = Array.from({ length: 11 }, (_, i) => {
|
|
1793
|
-
const n = i + 1;
|
|
1794
|
-
return `| p${n} | main | github | 0.${n}.0 | Plugin ${n} | tag${n} | false | |`;
|
|
1795
|
-
});
|
|
1796
|
-
const PKG_MARKET_BROWSE_OUTPUT = [
|
|
1797
|
-
'- marketplace: crouter-official-marketplace',
|
|
1798
|
-
'- total: 11',
|
|
1799
|
-
'',
|
|
1800
|
-
'11 items:',
|
|
1801
|
-
'',
|
|
1802
|
-
'| name | ref | source | version | description | keywords | installed | installed_scope |',
|
|
1803
|
-
'| --- | --- | --- | --- | --- | --- | --- | --- |',
|
|
1804
|
-
...PKG_MARKET_BROWSE_ROWS,
|
|
1805
|
-
].join('\n');
|
|
1806
|
-
test('a crtr pkg market browse card collapses to a plugin count in <market> + capped name digest, not the raw table', () => {
|
|
1807
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr pkg market browse crouter-official-marketplace' });
|
|
1808
|
-
const collapsed = def
|
|
1809
|
-
.renderResult({ content: [{ type: 'text', text: PKG_MARKET_BROWSE_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1810
|
-
.render(140)
|
|
1811
|
-
.join('\n');
|
|
1812
|
-
assert.match(collapsed, /11 plugins in crouter-official-marketplace/);
|
|
1813
|
-
assert.match(collapsed, /<muted>p1, p2, p3, p4, p5, p6, p7, p8, … 3 more<\/muted>/);
|
|
1814
|
-
assert.doesNotMatch(collapsed, /\| name \| ref \|/);
|
|
1815
|
-
const expanded = def
|
|
1816
|
-
.renderResult({ content: [{ type: 'text', text: PKG_MARKET_BROWSE_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1817
|
-
.render(140)
|
|
1818
|
-
.join('\n');
|
|
1819
|
-
assert.match(expanded, /\| name \| ref \| source \|/);
|
|
1820
|
-
const empty = def
|
|
1821
|
-
.renderResult({ content: [{ type: 'text', text: '- marketplace: crouter-official-marketplace\n- total: 0\n\n0 items.' }] }, { expanded: false }, theme, {})
|
|
1822
|
-
.render(140)
|
|
1823
|
-
.join('\n');
|
|
1824
|
-
assert.match(empty, /<muted>0 plugins in crouter-official-marketplace<\/muted>/);
|
|
1825
|
-
// REQUIREMENT: nonzero total with an empty page (exhausted pagination)
|
|
1826
|
-
// must NOT collapse to "0 plugins in <market>" — fall back to generic.
|
|
1827
|
-
const exhausted = def
|
|
1828
|
-
.renderResult({ content: [{ type: 'text', text: '- marketplace: crouter-official-marketplace\n- total: 3\n\n0 items.' }] }, { expanded: false }, theme, {})
|
|
1829
|
-
.render(140)
|
|
1830
|
-
.join('\n');
|
|
1831
|
-
assert.doesNotMatch(exhausted, /0 plugins in crouter-official-marketplace/);
|
|
1832
|
-
});
|
|
1833
|
-
// ---------------------------------------------------------------------------
|
|
1834
|
-
// Batch D — search/sys/surface summarizers + sys sysprompt suppression.
|
|
1835
|
-
// ---------------------------------------------------------------------------
|
|
1836
|
-
test('sys sysprompt is command-owned suppressOutput, never on failure', () => {
|
|
1837
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr sys sysprompt abc' });
|
|
1838
|
-
const component = def.renderResult({ content: [{ type: 'text', text: '- node_id: abc\n- captured_at: 2026-07-01T00:00:00.000Z\n- chars: 40000\n\n# System prompt\n...(huge prompt dump)...' }] }, { expanded: false }, theme, {});
|
|
1839
|
-
const collapsed = component.render(80).join('\n');
|
|
1840
|
-
assert.doesNotMatch(collapsed, /huge prompt dump/);
|
|
1841
|
-
assert.match(collapsed, /suppressed — ctrl\+o to expand/);
|
|
1842
|
-
const failedComponent = def.renderResult({ content: [{ type: 'text', text: 'huge dump on failure too' }] }, { expanded: false }, theme, { isError: true });
|
|
1843
|
-
assert.match(failedComponent.render(80).join('\n'), /huge dump on failure too/);
|
|
1844
|
-
});
|
|
1845
|
-
const SEARCH_WEB_OUTPUT = [
|
|
1846
|
-
'2 results for "test query":',
|
|
1847
|
-
'',
|
|
1848
|
-
'### 1. First Result Title',
|
|
1849
|
-
'https://example.com/page1 · 2024-01-15 · Jane Doe',
|
|
1850
|
-
'- highlight one text here',
|
|
1851
|
-
'- highlight two text here',
|
|
1852
|
-
'',
|
|
1853
|
-
'### 2. Second Result Title',
|
|
1854
|
-
'https://other.org/page2',
|
|
1855
|
-
'- another highlight',
|
|
1856
|
-
'',
|
|
1857
|
-
'Fetch full content for selected result URLs with the contents leaf after checking its schema. No good hits? Broaden the query, drop domain filters, or try --type deep.',
|
|
1858
|
-
].join('\n');
|
|
1859
|
-
test('a crtr search web card collapses to a result count + title — domain digest, not the raw sections', () => {
|
|
1860
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr search web "test query"' });
|
|
1861
|
-
const collapsed = def
|
|
1862
|
-
.renderResult({ content: [{ type: 'text', text: SEARCH_WEB_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1863
|
-
.render(140)
|
|
1864
|
-
.join('\n');
|
|
1865
|
-
assert.match(collapsed, /2 results/);
|
|
1866
|
-
assert.match(collapsed, /<muted>First Result Title — example\.com<\/muted>/);
|
|
1867
|
-
assert.match(collapsed, /<muted>Second Result Title — other\.org<\/muted>/);
|
|
1868
|
-
assert.doesNotMatch(collapsed, /### 1\./);
|
|
1869
|
-
const expanded = def
|
|
1870
|
-
.renderResult({ content: [{ type: 'text', text: SEARCH_WEB_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1871
|
-
.render(140)
|
|
1872
|
-
.join('\n');
|
|
1873
|
-
assert.match(expanded, /### 1\. First Result Title/);
|
|
1874
|
-
assert.match(expanded, /### 2\. Second Result Title/);
|
|
1875
|
-
const empty = def
|
|
1876
|
-
.renderResult({ content: [{ type: 'text', text: 'No results for "empty query".\n\nFetch full content for selected result URLs with the contents leaf after checking its schema. No good hits? Broaden the query, drop domain filters, or try --type deep.' }] }, { expanded: false }, theme, {})
|
|
1877
|
-
.render(140)
|
|
1878
|
-
.join('\n');
|
|
1879
|
-
assert.match(empty, /<muted>0 results for "empty query"<\/muted>/);
|
|
1880
|
-
});
|
|
1881
|
-
const SEARCH_ANSWER_OUTPUT = [
|
|
1882
|
-
'The answer is 42, according to the sources below.',
|
|
1883
|
-
'',
|
|
1884
|
-
'Sources (2):',
|
|
1885
|
-
'1. Deep Thought Paper — https://example.com/paper',
|
|
1886
|
-
'2. Hitchhiker Wiki — https://wiki.example.org/guide',
|
|
1887
|
-
'',
|
|
1888
|
-
'Read any cited source in full with the contents leaf after checking its schema. Want raw ranked results instead of a synthesized answer? Use the web leaf.',
|
|
1889
|
-
].join('\n');
|
|
1890
|
-
test('a crtr search answer card collapses to the answer teaser + citation count, not the raw sources list', () => {
|
|
1891
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr search answer "what is the answer"' });
|
|
1892
|
-
const collapsed = def
|
|
1893
|
-
.renderResult({ content: [{ type: 'text', text: SEARCH_ANSWER_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1894
|
-
.render(140)
|
|
1895
|
-
.join('\n');
|
|
1896
|
-
assert.match(collapsed, /The answer is 42, according to the sources below\./);
|
|
1897
|
-
assert.match(collapsed, /<muted>2 citations<\/muted>/);
|
|
1898
|
-
assert.doesNotMatch(collapsed, /Sources \(2\):/);
|
|
1899
|
-
const expanded = def
|
|
1900
|
-
.renderResult({ content: [{ type: 'text', text: SEARCH_ANSWER_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1901
|
-
.render(140)
|
|
1902
|
-
.join('\n');
|
|
1903
|
-
assert.match(expanded, /Sources \(2\):/);
|
|
1904
|
-
assert.match(expanded, /Deep Thought Paper/);
|
|
1905
|
-
const noAnswer = def
|
|
1906
|
-
.renderResult({ content: [{ type: 'text', text: 'No answer was returned.\n\nRead any cited source in full with the contents leaf after checking its schema. Want raw ranked results instead of a synthesized answer? Use the web leaf.' }] }, { expanded: false }, theme, {})
|
|
1907
|
-
.render(140)
|
|
1908
|
-
.join('\n');
|
|
1909
|
-
assert.match(noAnswer, /<muted>no answer returned<\/muted>/);
|
|
1910
|
-
});
|
|
1911
|
-
const SEARCH_CONTENTS_OUTPUT = [
|
|
1912
|
-
'Extracted 2 URLs:',
|
|
1913
|
-
'',
|
|
1914
|
-
'### 1. Example Page',
|
|
1915
|
-
'https://example.com/a',
|
|
1916
|
-
'Some highlight text about the page contents that is reasonably long for testing chars count.',
|
|
1917
|
-
'',
|
|
1918
|
-
'### 2. Another Page',
|
|
1919
|
-
'https://example.org/b',
|
|
1920
|
-
'- bullet one',
|
|
1921
|
-
'- bullet two',
|
|
1922
|
-
'',
|
|
1923
|
-
'Stale or empty content? Re-run with --max-age-hours 0 to force a fresh crawl. Need to find more pages? Use the web leaf after checking its schema.',
|
|
1924
|
-
].join('\n');
|
|
1925
|
-
test('a crtr search contents card collapses to a per-URL title + char-count digest, not the raw sections', () => {
|
|
1926
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr search contents https://example.com/a,https://example.org/b' });
|
|
1927
|
-
const collapsed = def
|
|
1928
|
-
.renderResult({ content: [{ type: 'text', text: SEARCH_CONTENTS_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1929
|
-
.render(140)
|
|
1930
|
-
.join('\n');
|
|
1931
|
-
assert.match(collapsed, /extracted 2 URLs/);
|
|
1932
|
-
assert.match(collapsed, /<muted>Example Page · \d+ chars<\/muted>/);
|
|
1933
|
-
assert.match(collapsed, /<muted>Another Page · \d+ chars<\/muted>/);
|
|
1934
|
-
assert.doesNotMatch(collapsed, /### 1\./);
|
|
1935
|
-
const expanded = def
|
|
1936
|
-
.renderResult({ content: [{ type: 'text', text: SEARCH_CONTENTS_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1937
|
-
.render(140)
|
|
1938
|
-
.join('\n');
|
|
1939
|
-
assert.match(expanded, /### 1\. Example Page/);
|
|
1940
|
-
const failed = def
|
|
1941
|
-
.renderResult({
|
|
1942
|
-
content: [{
|
|
1943
|
-
type: 'text',
|
|
1944
|
-
text: [
|
|
1945
|
-
'No content extracted.',
|
|
1946
|
-
'Failed (2):',
|
|
1947
|
-
'- https://dead.example.com — 404 Not Found',
|
|
1948
|
-
'- https://slow.example.com — timeout',
|
|
1949
|
-
'',
|
|
1950
|
-
'Stale or empty content? Re-run with --max-age-hours 0 to force a fresh crawl. Need to find more pages? Use the web leaf after checking its schema.',
|
|
1951
|
-
].join('\n'),
|
|
1952
|
-
}],
|
|
1953
|
-
}, { expanded: false }, theme, {})
|
|
1954
|
-
.render(140)
|
|
1955
|
-
.join('\n');
|
|
1956
|
-
assert.match(failed, /<error>0 extracted · 2 failed<\/error>/);
|
|
1957
|
-
const nothingAtAll = def
|
|
1958
|
-
.renderResult({ content: [{ type: 'text', text: 'No content extracted.\n\nStale or empty content? Re-run with --max-age-hours 0 to force a fresh crawl. Need to find more pages? Use the web leaf after checking its schema.' }] }, { expanded: false }, theme, {})
|
|
1959
|
-
.render(140)
|
|
1960
|
-
.join('\n');
|
|
1961
|
-
assert.match(nothingAtAll, /<muted>no content extracted<\/muted>/);
|
|
1962
|
-
});
|
|
1963
|
-
const SYS_SETUP_OUTPUT = [
|
|
1964
|
-
'crtr sys setup (non-interactive)',
|
|
1965
|
-
'',
|
|
1966
|
-
'Packages:',
|
|
1967
|
-
'- pi-crtr-extensions — crtr companion extensions. (installed)',
|
|
1968
|
-
'- pi-claude-oauth-adapter — OAuth adapter. (not installed)',
|
|
1969
|
-
'',
|
|
1970
|
-
'System deps:',
|
|
1971
|
-
'- tmux: ✓ installed',
|
|
1972
|
-
'- nvim: brew install neovim',
|
|
1973
|
-
'',
|
|
1974
|
-
'- Exa key: write to /Users/example/.crouter/exa.key or set EXA_API_KEY',
|
|
1975
|
-
'Run `crtr sys setup` in a TTY to open the wizard.',
|
|
1976
|
-
].join('\n');
|
|
1977
|
-
test('a crtr sys setup card collapses to an ok/pending step digest, not the raw sections', () => {
|
|
1978
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr sys setup' });
|
|
1979
|
-
const collapsed = def
|
|
1980
|
-
.renderResult({ content: [{ type: 'text', text: SYS_SETUP_OUTPUT }] }, { expanded: false }, theme, {})
|
|
1981
|
-
.render(140)
|
|
1982
|
-
.join('\n');
|
|
1983
|
-
assert.match(collapsed, /<warning>2 ok · 2 pending<\/warning>/);
|
|
1984
|
-
assert.match(collapsed, /<muted>pi-claude-oauth-adapter, nvim<\/muted>/);
|
|
1985
|
-
assert.doesNotMatch(collapsed, /Packages:/);
|
|
1986
|
-
const expanded = def
|
|
1987
|
-
.renderResult({ content: [{ type: 'text', text: SYS_SETUP_OUTPUT }] }, { expanded: true }, theme, {})
|
|
1988
|
-
.render(140)
|
|
1989
|
-
.join('\n');
|
|
1990
|
-
assert.match(expanded, /Packages:/);
|
|
1991
|
-
assert.match(expanded, /System deps:/);
|
|
1992
|
-
const allOk = def
|
|
1993
|
-
.renderResult({ content: [{ type: 'text', text: SYS_SETUP_OUTPUT.replace('(not installed)', '(installed)').replace('brew install neovim', '✓ installed') }] }, { expanded: false }, theme, {})
|
|
1994
|
-
.render(140)
|
|
1995
|
-
.join('\n');
|
|
1996
|
-
assert.match(allOk, /<success>4 steps ok<\/success>/);
|
|
1997
|
-
});
|
|
1998
|
-
const SYS_DOCTOR_PASS_OUTPUT = [
|
|
1999
|
-
'- ok: true',
|
|
2000
|
-
'- failed: 0',
|
|
2001
|
-
'',
|
|
2002
|
-
'2 checks:',
|
|
2003
|
-
'',
|
|
2004
|
-
'| scope | name | status | message | remediation |',
|
|
2005
|
-
'| --- | --- | --- | --- | --- |',
|
|
2006
|
-
'| user | plugin:ai:dir | pass | directory exists | |',
|
|
2007
|
-
'| user | plugin:ai:manifest | pass | manifest valid | |',
|
|
2008
|
-
].join('\n');
|
|
2009
|
-
const SYS_DOCTOR_FAIL_OUTPUT = [
|
|
2010
|
-
'- ok: false',
|
|
2011
|
-
'- failed: 1',
|
|
2012
|
-
'',
|
|
2013
|
-
'2 checks:',
|
|
2014
|
-
'',
|
|
2015
|
-
'| scope | name | status | message | remediation |',
|
|
2016
|
-
'| --- | --- | --- | --- | --- |',
|
|
2017
|
-
'| user | plugin:ai:dir | pass | directory exists | |',
|
|
2018
|
-
'| user | plugin:beta:dir | fail | directory missing: /path/to/beta | |',
|
|
2019
|
-
].join('\n');
|
|
2020
|
-
test('a crtr sys doctor card collapses to PASS or an issue digest, not the raw checks table', () => {
|
|
2021
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr sys doctor' });
|
|
2022
|
-
const pass = def
|
|
2023
|
-
.renderResult({ content: [{ type: 'text', text: SYS_DOCTOR_PASS_OUTPUT }] }, { expanded: false }, theme, {})
|
|
2024
|
-
.render(140)
|
|
2025
|
-
.join('\n');
|
|
2026
|
-
assert.match(pass, /<success>PASS · 2 checks<\/success>/);
|
|
2027
|
-
assert.doesNotMatch(pass, /\| scope \| name \|/);
|
|
2028
|
-
const fail = def
|
|
2029
|
-
.renderResult({ content: [{ type: 'text', text: SYS_DOCTOR_FAIL_OUTPUT }] }, { expanded: false }, theme, {})
|
|
2030
|
-
.render(140)
|
|
2031
|
-
.join('\n');
|
|
2032
|
-
assert.match(fail, /<error>1 issue<\/error>/);
|
|
2033
|
-
assert.match(fail, /<warning>plugin:beta:dir: directory missing: \/path\/to\/beta<\/warning>/);
|
|
2034
|
-
const expanded = def
|
|
2035
|
-
.renderResult({ content: [{ type: 'text', text: SYS_DOCTOR_FAIL_OUTPUT }] }, { expanded: true }, theme, {})
|
|
2036
|
-
.render(140)
|
|
2037
|
-
.join('\n');
|
|
2038
|
-
assert.match(expanded, /\| scope \| name \| status \|/);
|
|
2039
|
-
});
|
|
2040
|
-
const SYS_UPDATE_CHECK_OUTPUT = [
|
|
2041
|
-
'- up_to_date: false',
|
|
2042
|
-
'',
|
|
2043
|
-
'2 updates:',
|
|
2044
|
-
'',
|
|
2045
|
-
'| name | kind | current | latest | up_to_date | unreachable |',
|
|
2046
|
-
'| --- | --- | --- | --- | --- | --- |',
|
|
2047
|
-
'| @north-light/crouter | self | 0.3.34 | 0.3.35 | false | false |',
|
|
2048
|
-
'| some-marketplace | marketplace | abc123 | abc123 | true | false |',
|
|
2049
|
-
].join('\n');
|
|
2050
|
-
test('a crtr sys update --check card collapses to an outdated/checked digest, not the raw table', () => {
|
|
2051
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr sys update --check' });
|
|
2052
|
-
const collapsed = def
|
|
2053
|
-
.renderResult({ content: [{ type: 'text', text: SYS_UPDATE_CHECK_OUTPUT }] }, { expanded: false }, theme, {})
|
|
2054
|
-
.render(140)
|
|
2055
|
-
.join('\n');
|
|
2056
|
-
assert.match(collapsed, /<warning>1 outdated · 2 checked<\/warning>/);
|
|
2057
|
-
assert.match(collapsed, /<muted>@north-light\/crouter: 0\.3\.34 → 0\.3\.35<\/muted>/);
|
|
2058
|
-
assert.doesNotMatch(collapsed, /\| name \| kind \|/);
|
|
2059
|
-
const expanded = def
|
|
2060
|
-
.renderResult({ content: [{ type: 'text', text: SYS_UPDATE_CHECK_OUTPUT }] }, { expanded: true }, theme, {})
|
|
2061
|
-
.render(140)
|
|
2062
|
-
.join('\n');
|
|
2063
|
-
assert.match(expanded, /\| name \| kind \| current \|/);
|
|
2064
|
-
const upToDate = def
|
|
2065
|
-
.renderResult({ content: [{ type: 'text', text: '- up_to_date: true\n\n1 updates:\n\n| name | kind | current | latest | up_to_date | unreachable |\n| --- | --- | --- | --- | --- | --- |\n| a | self | 1.0.0 | 1.0.0 | true | false |' }] }, { expanded: false }, theme, {})
|
|
2066
|
-
.render(140)
|
|
2067
|
-
.join('\n');
|
|
2068
|
-
assert.match(upToDate, /<success>PASS · 1 up to date<\/success>/);
|
|
2069
|
-
const applyDone = def
|
|
2070
|
-
.renderResult({ content: [{ type: 'text', text: '- target: all\n- status: done' }] }, { expanded: false }, theme, {})
|
|
2071
|
-
.render(140)
|
|
2072
|
-
.join('\n');
|
|
2073
|
-
assert.match(applyDone, /<success>updated · all<\/success>/);
|
|
2074
|
-
const applyFailed = def
|
|
2075
|
-
.renderResult({ content: [{ type: 'text', text: '- target: self\n- status: failed\n- error: network unreachable' }] }, { expanded: false }, theme, {})
|
|
2076
|
-
.render(140)
|
|
2077
|
-
.join('\n');
|
|
2078
|
-
assert.match(applyFailed, /<error>update failed · self<\/error>/);
|
|
2079
|
-
assert.match(applyFailed, /<muted>network unreachable<\/muted>/);
|
|
2080
|
-
});
|
|
2081
|
-
test('a crtr sys feedback card collapses to a filed/saved-locally pointer, not the raw fields', () => {
|
|
2082
|
-
const def = createCrtrBashToolDefinition({ command: 'crtr sys feedback "crtr crashed"' });
|
|
2083
|
-
const filed = def
|
|
2084
|
-
.renderResult({ content: [{ type: 'text', text: '- status: filed\n- url: https://github.com/crouton-labs/crouter/issues/123' }] }, { expanded: false }, theme, {})
|
|
2085
|
-
.render(140)
|
|
2086
|
-
.join('\n');
|
|
2087
|
-
assert.match(filed, /<success>issue filed<\/success>/);
|
|
2088
|
-
assert.match(filed, /<muted>https:\/\/github\.com\/crouton-labs\/crouter\/issues\/123<\/muted>/);
|
|
2089
|
-
const savedText = '- status: saved-locally\n- path: /Users/example/.crouter/feedback/2026-07-01T00-00-00-000Z.md\n- error: gh not available: not found';
|
|
2090
|
-
const saved = def
|
|
2091
|
-
.renderResult({ content: [{ type: 'text', text: savedText }] }, { expanded: false }, theme, {})
|
|
2092
|
-
.render(140)
|
|
2093
|
-
.join('\n');
|
|
2094
|
-
assert.match(saved, /<warning>saved locally — not filed<\/warning>/);
|
|
2095
|
-
assert.match(saved, /<muted>2026-07-01T00-00-00-000Z\.md<\/muted>/);
|
|
2096
|
-
const expanded = def
|
|
2097
|
-
.renderResult({ content: [{ type: 'text', text: savedText }] }, { expanded: true }, theme, {})
|
|
2098
|
-
.render(140)
|
|
2099
|
-
.join('\n');
|
|
2100
|
-
assert.match(expanded, /gh not available: not found/);
|
|
2101
|
-
});
|