@north-light/crouter 0.3.172 → 0.3.174
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/runtime-selector.mjs +8 -1
- package/dist/api/__tests__/{client.test.js → serial/client.test.js} +2 -135
- package/dist/api/client.d.ts +12 -21
- package/dist/api/client.js +22 -31
- package/dist/api/dto/broker-ops.d.ts +158 -0
- package/dist/api/dto/broker-ops.js +6 -0
- package/dist/api/dto/crons.d.ts +6 -0
- package/dist/api/dto/human.d.ts +7 -1
- package/dist/api/dto/inbox.d.ts +1 -71
- package/dist/api/dto/inbox.js +1 -9
- package/dist/api/dto/nodes.d.ts +6 -7
- package/dist/api/dto/reviews.d.ts +1 -11
- package/dist/api/routes.d.ts +7 -4
- package/dist/api/routes.js +8 -5
- package/dist/build-root.js +3 -3
- package/dist/builtin-memory/04-base-worker.md +1 -4
- package/dist/builtin-memory/05-kinds/advisor/00-base.md +1 -5
- package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +2 -7
- package/dist/builtin-memory/05-kinds/design/00-base.md +1 -6
- package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -6
- package/dist/builtin-memory/05-kinds/explore/00-base.md +1 -4
- package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +1 -2
- package/dist/builtin-memory/05-kinds/general/00-base.md +1 -3
- package/dist/builtin-memory/05-kinds/plan/00-base.md +1 -6
- package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +1 -2
- package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +1 -3
- package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +1 -3
- package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +1 -3
- package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -3
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +1 -4
- package/dist/builtin-memory/05-kinds/review/companion/00-base.md +2 -3
- package/dist/builtin-memory/05-kinds/spec/00-base.md +1 -2
- package/dist/builtin-memory/advisor/council.md +1 -6
- package/dist/builtin-memory/design.md +2 -7
- package/dist/builtin-memory/development.md +9 -14
- package/dist/builtin-memory/internal/marketplaces.md +2 -6
- package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
- package/dist/builtin-memory/internal/plugins.md +2 -8
- package/dist/builtin-memory/internal/storage-tiers.md +1 -2
- package/dist/builtin-memory/testing.md +41 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/{provider-rotation.test.ts → serial/provider-rotation.test.ts} +106 -177
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +5 -15
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +1 -28
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +43 -88
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +55 -0
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +60 -0
- package/dist/cli.js +2 -2
- package/dist/clients/attach/__tests__/attach-remote-readonly.test.js +37 -131
- package/dist/clients/attach/__tests__/chat-view-snapshot-ordering.test.js +71 -152
- package/dist/clients/attach/__tests__/context-message.test.js +16 -174
- package/dist/clients/attach/__tests__/crtr-output.test.js +36 -2069
- package/dist/clients/attach/__tests__/diagram.test.js +2 -2
- package/dist/clients/attach/__tests__/edit-diff.test.js +0 -4
- package/dist/clients/attach/__tests__/file-review-focus.test.js +2 -1
- package/dist/clients/attach/__tests__/group-activity.test.js +40 -229
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +26 -76
- package/dist/clients/attach/__tests__/prompt-preview.test.js +4 -70
- package/dist/clients/attach/__tests__/ref-autocomplete.test.js +11 -125
- package/dist/clients/attach/__tests__/titled-editor-preview.test.js +14 -205
- package/dist/clients/attach/command.d.ts +2 -1
- package/dist/clients/attach/command.js +81 -76
- package/dist/clients/attach/input/capabilities.d.ts +27 -0
- package/dist/clients/attach/input/capabilities.js +96 -0
- package/dist/clients/attach/input/controller.d.ts +20 -182
- package/dist/clients/attach/input/controller.js +128 -537
- package/dist/clients/attach/input/overlay-owner.d.ts +41 -0
- package/dist/clients/attach/input/overlay-owner.js +199 -0
- package/dist/clients/attach/overlays/file-review.d.ts +0 -4
- package/dist/clients/attach/overlays/file-review.js +13 -5
- package/dist/clients/attach/overlays/graph.js +2 -10
- package/dist/clients/attach/render/chat-view.d.ts +4 -3
- package/dist/clients/attach/render/chat-view.js +39 -33
- package/dist/clients/attach/render/group-activity.d.ts +1 -0
- package/dist/clients/attach/render/group-activity.js +20 -13
- package/dist/clients/attach/render/group-recap.js +12 -3
- package/dist/clients/attach/session/connection.js +9 -15
- package/dist/clients/attach/session/editor-inventory.d.ts +6 -5
- package/dist/clients/attach/session/editor-inventory.js +7 -8
- package/dist/clients/attach/session/file-links.d.ts +13 -0
- package/dist/clients/attach/session/file-links.js +107 -0
- package/dist/clients/attach/session/frame.d.ts +3 -0
- package/dist/clients/attach/session/frame.js +10 -1
- package/dist/clients/attach/session/frames.js +2 -3
- package/dist/clients/attach/session/input-wiring.d.ts +4 -1
- package/dist/clients/attach/session/input-wiring.js +15 -14
- package/dist/clients/attach/session/mouse.d.ts +6 -1
- package/dist/clients/attach/session/mouse.js +8 -5
- package/dist/clients/attach/session/profile-files.js +18 -12
- package/dist/clients/attach/session/reconnect.js +1 -1
- package/dist/clients/attach/session/state-sync.js +1 -2
- package/dist/clients/attach/session/surface-host.d.ts +3 -2
- package/dist/clients/attach/session/surface-host.js +4 -4
- package/dist/clients/attach/slash/dispatch.d.ts +8 -31
- package/dist/clients/attach/slash/dispatch.js +34 -81
- package/dist/clients/attach/viewer.js +820 -860
- package/dist/clients/conversation/projection.d.ts +79 -0
- package/dist/clients/conversation/projection.js +341 -0
- package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +159 -0
- package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +151 -0
- package/dist/clients/inbox/controller.d.ts +0 -4
- package/dist/clients/inbox/controller.js +0 -2
- package/dist/clients/inbox/review/__tests__/review-core.test.js +4 -293
- package/dist/clients/inbox/review/__tests__/teardown-notice.test.js +100 -0
- package/dist/clients/inbox/review/comments-client.d.ts +3 -2
- package/dist/clients/inbox/review/comments-client.js +12 -12
- package/dist/clients/inbox/review/companion-pane.d.ts +30 -0
- package/dist/clients/inbox/review/companion-pane.js +86 -0
- package/dist/clients/inbox/review/document-surface.d.ts +182 -0
- package/dist/clients/inbox/review/{document-region.js → document-surface.js} +252 -48
- package/dist/clients/inbox/review/frame.js +5 -4
- package/dist/clients/inbox/review/launch.d.ts +8 -8
- package/dist/clients/inbox/review/launch.js +52 -92
- package/dist/clients/inbox/review/nvim.d.ts +2 -0
- package/dist/clients/inbox/review/nvim.js +14 -12
- package/dist/clients/inbox/review/review-client.d.ts +12 -8
- package/dist/clients/inbox/review/review-client.js +46 -35
- package/dist/clients/inbox/review-adapter.d.ts +3 -10
- package/dist/clients/inbox/review-adapter.js +27 -19
- package/dist/clients/inbox/surface.d.ts +0 -4
- package/dist/clients/inbox/surface.js +0 -2
- package/dist/clients/surfaces/host.d.ts +11 -3
- package/dist/commands/__tests__/api-client-cold-start-diagnostic.test.js +7 -34
- package/dist/commands/__tests__/human.test.js +11 -322
- package/dist/commands/api-client.d.ts +5 -5
- package/dist/commands/api-client.js +6 -85
- package/dist/commands/canvas-browse.js +3 -29
- package/dist/commands/canvas-config.js +1 -1
- package/dist/commands/canvas-use.js +3 -3
- package/dist/commands/canvas.js +1 -1
- package/dist/commands/cron.js +5 -1
- package/dist/commands/human/queue.d.ts +0 -4
- package/dist/commands/human/queue.js +7 -40
- package/dist/commands/memory/list.d.ts +14 -0
- package/dist/commands/memory/list.js +35 -31
- package/dist/commands/memory/read.d.ts +7 -0
- package/dist/commands/memory/read.js +14 -8
- package/dist/commands/node/bash.d.ts +1 -0
- package/dist/commands/node/bash.js +198 -0
- package/dist/commands/node/create.d.ts +23 -0
- package/dist/commands/node/create.js +207 -0
- package/dist/commands/node/inspect.d.ts +2 -0
- package/dist/commands/node/inspect.js +302 -0
- package/dist/commands/node/lifecycle.d.ts +4 -0
- package/dist/commands/node/lifecycle.js +415 -0
- package/dist/commands/node/message.d.ts +1 -0
- package/dist/commands/node/message.js +164 -0
- package/dist/commands/node/pyramid.d.ts +2 -0
- package/dist/commands/node/pyramid.js +79 -0
- package/dist/commands/node/subscription.d.ts +1 -0
- package/dist/commands/node/subscription.js +82 -0
- package/dist/commands/node/wait.d.ts +1 -0
- package/dist/commands/node/wait.js +159 -0
- package/dist/commands/node-lifecycle-revive.js +10 -57
- package/dist/commands/node.d.ts +0 -42
- package/dist/commands/node.js +11 -2065
- package/dist/commands/search.js +1 -1
- package/dist/commands/surface/node/cycle.d.ts +1 -0
- package/dist/commands/surface/node/cycle.js +83 -0
- package/dist/commands/surface/node/focus.d.ts +1 -0
- package/dist/commands/surface/node/focus.js +67 -0
- package/dist/commands/surface/node/id.d.ts +1 -0
- package/dist/commands/surface/node/id.js +38 -0
- package/dist/commands/surface/node/pin.d.ts +1 -0
- package/dist/commands/surface/node/pin.js +42 -0
- package/dist/commands/surface/node/placement.d.ts +46 -0
- package/dist/commands/surface/node/placement.js +124 -0
- package/dist/commands/surface-node.js +4 -1
- package/dist/commands/surface-reload.js +1 -2
- package/dist/commands/surface-tmux-spread.js +1 -1
- package/dist/commands/surface.js +5 -20
- package/dist/commands/sys/__tests__/setup-core.test.js +18 -271
- package/dist/commands/sys/config.js +6 -6
- package/dist/commands/sys/logs.js +1 -1
- package/dist/commands/sys/panels/broker-limits-panel.d.ts +17 -0
- package/dist/commands/sys/panels/broker-limits-panel.js +104 -0
- package/dist/commands/sys/panels/dependencies-panel.d.ts +14 -0
- package/dist/commands/sys/panels/dependencies-panel.js +80 -0
- package/dist/commands/sys/panels/keybindings-panel.d.ts +56 -0
- package/dist/commands/sys/panels/keybindings-panel.js +703 -0
- package/dist/commands/sys/panels/keys-panel.d.ts +16 -0
- package/dist/commands/sys/panels/keys-panel.js +60 -0
- package/dist/commands/sys/panels/models-panel.d.ts +77 -0
- package/dist/commands/sys/panels/models-panel.js +647 -0
- package/dist/commands/sys/panels/panel.d.ts +45 -0
- package/dist/commands/sys/panels/panel.js +137 -0
- package/dist/commands/sys/panels/plugins-panel.d.ts +16 -0
- package/dist/commands/sys/panels/plugins-panel.js +80 -0
- package/dist/commands/sys/panels/prefs-panel.d.ts +21 -0
- package/dist/commands/sys/panels/prefs-panel.js +85 -0
- package/dist/commands/sys/panels/provider-panel.d.ts +62 -0
- package/dist/commands/sys/panels/provider-panel.js +538 -0
- package/dist/commands/sys/settings-shell.d.ts +55 -0
- package/dist/commands/sys/settings-shell.js +400 -0
- package/dist/commands/sys/settings.d.ts +2 -0
- package/dist/commands/sys/settings.js +31 -0
- package/dist/commands/sys/setup-core.d.ts +3 -33
- package/dist/commands/sys/setup-core.js +27 -78
- package/dist/commands/sys/setup-settings-runner.d.ts +24 -0
- package/dist/commands/sys/setup-settings-runner.js +63 -0
- package/dist/commands/sys/setup-wizard.d.ts +26 -125
- package/dist/commands/sys/setup-wizard.js +209 -2473
- package/dist/commands/sys/setup.js +5 -7
- package/dist/commands/sys.js +3 -2
- package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +50 -0
- package/dist/core/__tests__/broker-launch-candidates.test.js +20 -9
- package/dist/core/__tests__/broker-preflight.test.js +1 -1
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +108 -27
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +14 -0
- package/dist/core/__tests__/canvas.test.js +2 -36
- package/dist/core/__tests__/close.test.js +14 -3
- package/dist/core/__tests__/context-intro.test.js +32 -143
- package/dist/core/__tests__/cron-broker-capacity.test.js +22 -3
- package/dist/core/__tests__/daemon-boot.test.js +2 -20
- package/dist/core/__tests__/daemon-wedge.test.js +1 -1
- package/dist/core/__tests__/dead-node-policy-table.test.js +1 -18
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +1 -1
- package/dist/core/__tests__/fixtures/fake-engine.js +2 -2
- package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
- package/dist/core/__tests__/focuses.test.js +4 -98
- package/dist/core/__tests__/helpers/broker-clients.d.ts +0 -3
- package/dist/core/__tests__/helpers/broker-clients.js +0 -14
- package/dist/core/__tests__/helpers/harness.d.ts +0 -8
- package/dist/core/__tests__/helpers/harness.js +7 -87
- package/dist/core/__tests__/helpers/viewer-panes.js +1 -1
- package/dist/core/__tests__/inline-memory-refs.test.js +5 -134
- package/dist/core/__tests__/kickoff.test.js +1 -1
- package/dist/core/__tests__/memory-resolver-precedence.test.js +29 -1
- package/dist/core/__tests__/migration.test.js +35 -48
- package/dist/core/__tests__/on-read-dedup-resume.test.js +22 -12
- package/dist/core/__tests__/passive-subscription.test.js +26 -49
- package/dist/core/__tests__/preview-registry-sync.test.js +1 -2
- package/dist/core/__tests__/prune.test.js +94 -1
- package/dist/core/__tests__/review-model-floor.test.js +0 -32
- package/dist/core/__tests__/seam/broker-attach-multiclient.test.js +255 -0
- package/dist/core/__tests__/{full → seam}/broker-attach-stream.test.js +39 -7
- package/dist/core/__tests__/{full → seam}/broker-crash-teardown.test.js +30 -29
- package/dist/core/__tests__/{full → seam}/broker-pane-resolution.test.js +54 -5
- package/dist/core/__tests__/{full → seam}/broker-provider-retry.test.js +134 -82
- package/dist/core/__tests__/{full → seam}/broker-whip-interrupt.test.js +15 -4
- package/dist/core/__tests__/{full/idle-release-settlement.test.js → seam/dormancy-release.test.js} +69 -17
- package/dist/core/__tests__/{full/yield-message-transaction.test.js → seam/yield-refresh-transaction.test.js} +31 -9
- package/dist/core/__tests__/secrets.test.js +1 -7
- package/dist/core/__tests__/{broker-fork-seam.test.js → serial/broker-fork-seam.test.js} +58 -7
- package/dist/core/__tests__/{broker-sdk-wiring.test.js → serial/broker-sdk-wiring.test.js} +18 -162
- package/dist/core/__tests__/{broker-snapshot-history.test.js → serial/broker-snapshot-history.test.js} +9 -9
- package/dist/core/__tests__/{command-plugins.test.js → serial/command-plugins.test.js} +105 -15
- package/dist/core/__tests__/{deferred-no-wake.test.js → serial/deferred-no-wake.test.js} +23 -20
- package/dist/core/__tests__/{flagship-lifecycle.test.js → serial/flagship-lifecycle.test.js} +7 -7
- package/dist/core/__tests__/{host-teardown-process-group.test.js → serial/host-teardown-process-group.test.js} +6 -6
- package/dist/core/__tests__/{human-deliver-e2e.test.js → serial/human-deliver-e2e.test.js} +12 -12
- package/dist/core/__tests__/{live-mutation.test.js → serial/live-mutation.test.js} +5 -5
- package/dist/core/__tests__/{refresh-stall-recycle.test.js → serial/refresh-stall-recycle.test.js} +5 -14
- package/dist/core/__tests__/{revive.test.js → serial/revive.test.js} +15 -100
- package/dist/core/__tests__/{spawn-root.test.js → serial/spawn-root.test.js} +32 -15
- package/dist/core/__tests__/serial/subscription-delivery.test.js +126 -0
- package/dist/core/__tests__/serial/tmux-surface.test.js +256 -0
- package/dist/core/__tests__/{worktree.test.js → serial/worktree.test.js} +15 -15
- package/dist/core/__tests__/session-model.test.js +0 -18
- package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +9 -0
- package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +50 -0
- package/dist/core/broker-client/__tests__/transport-relay.test.js +15 -16
- package/dist/core/broker-client/__tests__/transport-socket.test.js +28 -0
- package/dist/core/broker-client/client.d.ts +2 -2
- package/dist/core/broker-client/client.js +4 -7
- package/dist/core/broker-client/transport-relay.d.ts +13 -19
- package/dist/core/broker-client/transport-relay.js +19 -34
- package/dist/core/broker-client/transport-socket.d.ts +2 -2
- package/dist/core/broker-client/transport-socket.js +9 -2
- package/dist/core/broker-client/transport.d.ts +7 -3
- package/dist/core/broker-client/transport.js +4 -4
- package/dist/core/canvas/__tests__/attention.test.js +1 -18
- package/dist/core/canvas/__tests__/remote-canvas-source.test.js +7 -64
- package/dist/core/canvas/__tests__/remote-transport.test.js +2 -3
- package/dist/core/canvas/__tests__/render-remote.test.js +7 -52
- package/dist/core/canvas/browse/app.d.ts +2 -17
- package/dist/core/canvas/browse/app.js +16 -88
- package/dist/core/canvas/browse/model.d.ts +0 -4
- package/dist/core/canvas/browse/model.js +1 -12
- package/dist/core/canvas/browse/render.js +6 -3
- package/dist/core/canvas/crons.d.ts +8 -5
- package/dist/core/canvas/crons.js +7 -6
- package/dist/core/canvas/extensions.d.ts +7 -3
- package/dist/core/canvas/extensions.js +16 -4
- package/dist/core/canvas/nav-model.d.ts +0 -4
- package/dist/core/canvas/nav-model.js +2 -14
- package/dist/core/canvas/node-order.d.ts +3 -0
- package/dist/core/canvas/node-order.js +12 -0
- package/dist/core/canvas/remote-canvas-source.d.ts +3 -6
- package/dist/core/canvas/remote-canvas-source.js +8 -15
- package/dist/core/command.js +11 -4
- package/dist/core/config.js +3 -1
- package/dist/core/events/envelope.js +1 -1
- package/dist/core/events/read.js +7 -21
- package/dist/core/events/source.d.ts +0 -2
- package/dist/core/events/source.js +0 -16
- package/dist/core/events/types.d.ts +1 -1
- package/dist/core/fault-classifier.d.ts +1 -1
- package/dist/core/feed/passive.d.ts +0 -11
- package/dist/core/feed/passive.js +1 -62
- package/dist/core/human/__tests__/{inbox-core.test.js → serial/inbox-core.test.js} +6 -6
- package/dist/core/keybindings/__tests__/resolve.test.js +1 -96
- package/dist/core/keybindings/background-bash.js +3 -4
- package/dist/core/keybindings/catalog.js +1 -1
- package/dist/core/memory/inline-ref-grammar.js +3 -3
- package/dist/core/memory-resolver.d.ts +11 -25
- package/dist/core/memory-resolver.js +51 -33
- package/dist/core/model-routes.d.ts +15 -0
- package/dist/core/model-routes.js +12 -0
- package/dist/core/pool-credential-store.js +1 -1
- package/dist/core/preview-registry.js +8 -9
- package/dist/core/profiles/__tests__/root-profile.test.js +4 -14
- package/dist/core/review/realize.d.ts +0 -13
- package/dist/core/review/realize.js +0 -40
- package/dist/core/review/types.d.ts +1 -1
- package/dist/core/runtime/__tests__/engine-route.test.js +2 -2
- package/dist/core/runtime/bearings.js +1 -11
- package/dist/core/runtime/broker/client-registry.d.ts +33 -0
- package/dist/core/runtime/broker/client-registry.js +164 -0
- package/dist/core/runtime/broker/daemon-ops.d.ts +19 -0
- package/dist/core/runtime/broker/daemon-ops.js +49 -0
- package/dist/core/runtime/broker/event-projection.d.ts +42 -0
- package/dist/core/runtime/broker/event-projection.js +178 -0
- package/dist/core/runtime/broker/fault-retry.d.ts +39 -0
- package/dist/core/runtime/broker/fault-retry.js +277 -0
- package/dist/core/runtime/broker/frame-dispatch.d.ts +238 -0
- package/dist/core/runtime/broker/frame-dispatch.js +1537 -0
- package/dist/core/runtime/broker/inbox.d.ts +22 -0
- package/dist/core/runtime/broker/inbox.js +167 -0
- package/dist/core/runtime/broker/passive.d.ts +16 -0
- package/dist/core/runtime/broker/passive.js +44 -0
- package/dist/core/runtime/broker/read-ops.d.ts +23 -0
- package/dist/core/runtime/broker/read-ops.js +150 -0
- package/dist/core/runtime/broker/rebind.d.ts +74 -0
- package/dist/core/runtime/broker/rebind.js +211 -0
- package/dist/core/runtime/broker/tool-groups.d.ts +39 -0
- package/dist/core/runtime/broker/tool-groups.js +244 -0
- package/dist/core/runtime/broker-extension-render.d.ts +11 -0
- package/dist/core/runtime/broker-extension-render.js +181 -0
- package/dist/core/runtime/broker-persona-guidance.d.ts +13 -0
- package/dist/core/runtime/broker-persona-guidance.js +58 -0
- package/dist/core/runtime/broker-protocol.d.ts +1 -1
- package/dist/core/runtime/broker-protocol.js +1 -1
- package/dist/core/runtime/broker.d.ts +5 -196
- package/dist/core/runtime/broker.js +276 -3059
- package/dist/core/runtime/canvas-extensions.js +11 -2
- package/dist/core/runtime/fault.js +1 -1
- package/dist/core/runtime/model-selection.d.ts +9 -0
- package/dist/core/runtime/model-selection.js +12 -0
- package/dist/core/runtime/model-swap.d.ts +3 -3
- package/dist/core/runtime/model-swap.js +33 -25
- package/dist/core/runtime/placement-tmux.d.ts +3 -14
- package/dist/core/runtime/placement-tmux.js +11 -80
- package/dist/core/runtime/placement.d.ts +4 -2
- package/dist/core/runtime/placement.js +7 -12
- package/dist/core/runtime/promote.d.ts +14 -1
- package/dist/core/runtime/promote.js +1 -1
- package/dist/core/runtime/reset.d.ts +1 -1
- package/dist/core/runtime/roadmap.js +1 -1
- package/dist/core/runtime/spawn.d.ts +8 -0
- package/dist/core/runtime/spawn.js +36 -6
- package/dist/core/runtime/stop-guard.d.ts +4 -3
- package/dist/core/runtime/stop-guard.js +36 -18
- package/dist/core/runtime/stop-signals.d.ts +4 -1
- package/dist/core/runtime/stream-watchdog.js +4 -5
- package/dist/core/runtime/structured-output.d.ts +15 -2
- package/dist/core/runtime/structured-output.js +35 -12
- package/dist/core/runtime/tmux-bindings.d.ts +25 -0
- package/dist/core/runtime/tmux-bindings.js +682 -0
- package/dist/core/runtime/tmux-chrome.d.ts +2 -1
- package/dist/core/runtime/tmux-chrome.js +3 -2
- package/dist/core/runtime/{tmux.d.ts → tmux-driver.d.ts} +18 -26
- package/dist/core/runtime/tmux-driver.js +598 -0
- package/dist/core/runtime/view-socket.d.ts +14 -0
- package/dist/core/runtime/view-socket.js +83 -0
- package/dist/core/session-model/session-state.d.ts +1 -2
- package/dist/core/session-model/session-state.js +5 -8
- package/dist/core/spawn.d.ts +1 -1
- package/dist/core/spawn.js +2 -2
- package/dist/core/substrate/gate.d.ts +1 -1
- package/dist/core/substrate/on-read-pure.d.ts +7 -0
- package/dist/core/substrate/on-read-pure.js +272 -0
- package/dist/core/termrender/termrender.js +32 -2
- package/dist/core/user-settings.d.ts +23 -1
- package/dist/core/user-settings.js +18 -1
- package/dist/daemon/api/__tests__/node-model-validation.test.js +134 -0
- package/dist/daemon/api/__tests__/review-terminal-conflict.test.js +98 -0
- package/dist/daemon/api/__tests__/{full → seam}/api-server.test.js +6 -48
- package/dist/daemon/api/__tests__/{full → seam}/b10-attach-modelauth.test.js +17 -44
- package/dist/daemon/api/__tests__/{full/push-parity.test.js → seam/leaf-api-parity.test.js} +61 -42
- package/dist/daemon/api/bridge.js +40 -81
- package/dist/daemon/api/handlers/broker-ops.d.ts +2 -0
- package/dist/daemon/api/handlers/broker-ops.js +383 -0
- package/dist/daemon/api/handlers/crons.js +9 -4
- package/dist/daemon/api/handlers/files.js +6 -7
- package/dist/daemon/api/handlers/inbox.js +2 -340
- package/dist/daemon/api/handlers/nodes.d.ts +10 -1
- package/dist/daemon/api/handlers/nodes.js +74 -39
- package/dist/daemon/api/handlers/review-comments.js +5 -1
- package/dist/daemon/api/handlers/reviews.js +0 -19
- package/dist/daemon/api/map.js +25 -4
- package/dist/daemon/api/router.js +2 -3
- package/dist/daemon/api/server.js +5 -7
- package/dist/daemon/cron-run.d.ts +4 -0
- package/dist/daemon/cron-run.js +30 -13
- package/dist/daemon/crtrd.d.ts +3 -58
- package/dist/daemon/crtrd.js +28 -1244
- package/dist/daemon/manage.js +0 -1
- package/dist/daemon/reconcilers/broker-supervision.d.ts +50 -0
- package/dist/daemon/reconcilers/broker-supervision.js +549 -0
- package/dist/daemon/reconcilers/cron-lane.d.ts +10 -0
- package/dist/daemon/reconcilers/cron-lane.js +32 -0
- package/dist/daemon/reconcilers/dormant-inbox.d.ts +18 -0
- package/dist/daemon/reconcilers/dormant-inbox.js +143 -0
- package/dist/daemon/reconcilers/storage-maintenance.d.ts +22 -0
- package/dist/daemon/reconcilers/storage-maintenance.js +213 -0
- package/dist/daemon/review/finish.js +2 -5
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +34 -53
- package/dist/pi-extensions/__tests__/canvas-recap.test.js +16 -113
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +72 -99
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +29 -12
- package/dist/pi-extensions/broker-local.d.ts +20 -0
- package/dist/pi-extensions/broker-local.js +89 -0
- package/dist/pi-extensions/canvas-context-intro.d.ts +3 -3
- package/dist/pi-extensions/canvas-context-intro.js +22 -11
- package/dist/pi-extensions/canvas-doc-substrate.js +17 -34
- package/dist/pi-extensions/canvas-goal-capture.js +7 -12
- package/dist/pi-extensions/canvas-inbox-watcher.js +124 -139
- package/dist/pi-extensions/canvas-passive-context.d.ts +3 -3
- package/dist/pi-extensions/canvas-passive-context.js +22 -18
- package/dist/pi-extensions/canvas-recap.d.ts +2 -2
- package/dist/pi-extensions/canvas-recap.js +48 -21
- package/dist/pi-extensions/canvas-review-boundary.js +10 -4
- package/dist/pi-extensions/canvas-stophook.d.ts +0 -5
- package/dist/pi-extensions/canvas-stophook.js +149 -431
- package/dist/pi-extensions/canvas-structured-output.js +52 -30
- package/dist/pi-extensions/envelope-merge.d.ts +13 -0
- package/dist/pi-extensions/envelope-merge.js +29 -0
- package/dist/shared/working-activity.d.ts +3 -2
- package/dist/shared/working-activity.js +3 -2
- package/dist/types.d.ts +6 -5
- package/dist/types.js +2 -2
- package/docs/compat/hearth-crtr-v1.md +4 -12
- package/docs/compat/hearth-crtr-v2.md +3 -11
- package/docs/compat/hearth-crtr-v3.md +4 -12
- package/docs/compat/hearth-crtr-v4.md +3 -11
- package/docs/compat/hearth-crtr-v5.md +3 -11
- package/docs/public-api.md +3 -7
- package/package.json +7 -49
- package/runtime.lock.json +2766 -14533
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/claude-plugin-commands.test.ts +0 -49
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/memory-slash-commands.test.ts +0 -194
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/statusline.test.ts +0 -68
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/test.ts +0 -119
- package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +0 -439
- package/dist/clients/attach/__tests__/attach-keybindings.test.js +0 -170
- package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +0 -57
- package/dist/clients/attach/__tests__/editor-frame-title.test.js +0 -19
- package/dist/clients/attach/__tests__/input-controller-extension-command.test.js +0 -32
- package/dist/clients/attach/__tests__/markdown-headings.test.js +0 -35
- package/dist/clients/attach/__tests__/session-identity-refresh.test.js +0 -32
- package/dist/clients/conversation/conversation.d.ts +0 -65
- package/dist/clients/conversation/conversation.js +0 -318
- package/dist/clients/conversation/frames.d.ts +0 -30
- package/dist/clients/conversation/frames.js +0 -52
- package/dist/clients/conversation/region.d.ts +0 -51
- package/dist/clients/conversation/region.js +0 -265
- package/dist/clients/inbox/__tests__/inbox-controller.test.js +0 -247
- package/dist/clients/inbox/__tests__/mount-panel.test.js +0 -776
- package/dist/clients/inbox/review/__tests__/remap.test.js +0 -196
- package/dist/clients/inbox/review/columns.d.ts +0 -22
- package/dist/clients/inbox/review/columns.js +0 -40
- package/dist/clients/inbox/review/composition.d.ts +0 -60
- package/dist/clients/inbox/review/composition.js +0 -278
- package/dist/clients/inbox/review/document-region.d.ts +0 -120
- package/dist/clients/surfaces/region.d.ts +0 -16
- package/dist/clients/web/__tests__/push-engine.test.js +0 -158
- package/dist/clients/web/__tests__/push-registry.test.d.ts +0 -1
- package/dist/clients/web/__tests__/push-registry.test.js +0 -177
- package/dist/clients/web/dev-server.d.ts +0 -7
- package/dist/clients/web/dev-server.js +0 -46
- package/dist/clients/web/events.d.ts +0 -19
- package/dist/clients/web/events.js +0 -163
- package/dist/clients/web/push-engine.d.ts +0 -44
- package/dist/clients/web/push-engine.js +0 -125
- package/dist/clients/web/push-registry.d.ts +0 -85
- package/dist/clients/web/push-registry.js +0 -373
- package/dist/clients/web/server.d.ts +0 -19
- package/dist/clients/web/server.js +0 -661
- package/dist/clients/web/web-client/shared/protocol.d.ts +0 -214
- package/dist/clients/web/web-client/shared/protocol.js +0 -8
- package/dist/clients/web/web-cmd.d.ts +0 -2
- package/dist/clients/web/web-cmd.js +0 -141
- package/dist/commands/__tests__/api-canvas-source.test.d.ts +0 -1
- package/dist/commands/__tests__/api-canvas-source.test.js +0 -37
- package/dist/commands/__tests__/canvas-config.test.d.ts +0 -1
- package/dist/commands/__tests__/canvas-config.test.js +0 -107
- package/dist/commands/__tests__/node-no-kickoff-flag.test.d.ts +0 -1
- package/dist/commands/__tests__/node-no-kickoff-flag.test.js +0 -73
- package/dist/commands/__tests__/node-worktree-flag.test.d.ts +0 -1
- package/dist/commands/__tests__/node-worktree-flag.test.js +0 -49
- package/dist/commands/__tests__/search-contents.test.d.ts +0 -1
- package/dist/commands/__tests__/search-contents.test.js +0 -52
- package/dist/commands/memory/__tests__/lint.test.d.ts +0 -1
- package/dist/commands/memory/__tests__/lint.test.js +0 -15
- package/dist/commands/sys/__tests__/config-broker-thresholds.test.d.ts +0 -1
- package/dist/commands/sys/__tests__/config-broker-thresholds.test.js +0 -95
- package/dist/commands/sys/__tests__/config-keybindings.test.d.ts +0 -1
- package/dist/commands/sys/__tests__/config-keybindings.test.js +0 -55
- package/dist/commands/sys/__tests__/helpers/setup-runtime.d.ts +0 -5
- package/dist/commands/sys/__tests__/helpers/setup-runtime.js +0 -19
- package/dist/commands/sys/__tests__/setup-front-door.test.d.ts +0 -1
- package/dist/commands/sys/__tests__/setup-front-door.test.js +0 -102
- package/dist/commands/sys/__tests__/setup-models.test.d.ts +0 -1
- package/dist/commands/sys/__tests__/setup-models.test.js +0 -205
- package/dist/commands/sys/__tests__/setup-providers-viewport.test.d.ts +0 -1
- package/dist/commands/sys/__tests__/setup-providers-viewport.test.js +0 -155
- package/dist/commands/sys/__tests__/sync-deps.test.d.ts +0 -1
- package/dist/commands/sys/__tests__/sync-deps.test.js +0 -179
- package/dist/commands/sys/__tests__/sync-import.test.d.ts +0 -1
- package/dist/commands/sys/__tests__/sync-import.test.js +0 -315
- package/dist/core/__tests__/base-worker-prompt.test.d.ts +0 -1
- package/dist/core/__tests__/base-worker-prompt.test.js +0 -57
- package/dist/core/__tests__/command-plugins-surfaces.test.d.ts +0 -1
- package/dist/core/__tests__/command-plugins-surfaces.test.js +0 -359
- package/dist/core/__tests__/connection-reconnect-resume.test.d.ts +0 -1
- package/dist/core/__tests__/connection-reconnect-resume.test.js +0 -36
- package/dist/core/__tests__/finalization-migration.test.d.ts +0 -1
- package/dist/core/__tests__/finalization-migration.test.js +0 -38
- package/dist/core/__tests__/fixtures/extra-extension.d.ts +0 -1
- package/dist/core/__tests__/fixtures/extra-extension.js +0 -3
- package/dist/core/__tests__/fixtures/spawn-proof-target.test.d.ts +0 -1
- package/dist/core/__tests__/fixtures/spawn-proof-target.test.js +0 -15
- package/dist/core/__tests__/fork.test.d.ts +0 -1
- package/dist/core/__tests__/fork.test.js +0 -108
- package/dist/core/__tests__/full/broker-attach-limits.test.d.ts +0 -1
- package/dist/core/__tests__/full/broker-attach-limits.test.js +0 -147
- package/dist/core/__tests__/full/broker-deliver-ack.test.d.ts +0 -1
- package/dist/core/__tests__/full/broker-deliver-ack.test.js +0 -73
- package/dist/core/__tests__/full/broker-dialogs.test.d.ts +0 -1
- package/dist/core/__tests__/full/broker-dialogs.test.js +0 -180
- package/dist/core/__tests__/full/broker-display-snapshot.test.d.ts +0 -1
- package/dist/core/__tests__/full/broker-display-snapshot.test.js +0 -75
- package/dist/core/__tests__/full/broker-dormant-wake.test.d.ts +0 -1
- package/dist/core/__tests__/full/broker-dormant-wake.test.js +0 -130
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +0 -1
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +0 -57
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +0 -1
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +0 -80
- package/dist/core/__tests__/full/cascade-close.test.d.ts +0 -1
- package/dist/core/__tests__/full/cascade-close.test.js +0 -240
- package/dist/core/__tests__/full/detach-focus.test.d.ts +0 -1
- package/dist/core/__tests__/full/detach-focus.test.js +0 -126
- package/dist/core/__tests__/full/idle-release-settlement.test.d.ts +0 -1
- package/dist/core/__tests__/full/issue-146-yield-spawn-focus.test.d.ts +0 -1
- package/dist/core/__tests__/full/issue-146-yield-spawn-focus.test.js +0 -107
- package/dist/core/__tests__/full/park-clock-release.test.d.ts +0 -1
- package/dist/core/__tests__/full/park-clock-release.test.js +0 -56
- package/dist/core/__tests__/full/yield-message-transaction.test.d.ts +0 -1
- package/dist/core/__tests__/memory-index-render.test.d.ts +0 -1
- package/dist/core/__tests__/memory-index-render.test.js +0 -60
- package/dist/core/__tests__/model-routes.test.d.ts +0 -1
- package/dist/core/__tests__/model-routes.test.js +0 -41
- package/dist/core/__tests__/on-read-identity.test.d.ts +0 -1
- package/dist/core/__tests__/on-read-identity.test.js +0 -61
- package/dist/core/__tests__/phase4-review-migration.test.d.ts +0 -1
- package/dist/core/__tests__/phase4-review-migration.test.js +0 -100
- package/dist/core/__tests__/prune-to-limit.test.d.ts +0 -1
- package/dist/core/__tests__/prune-to-limit.test.js +0 -126
- package/dist/core/__tests__/reset.test.d.ts +0 -1
- package/dist/core/__tests__/reset.test.js +0 -69
- package/dist/core/__tests__/spawn-no-kickoff.test.d.ts +0 -1
- package/dist/core/__tests__/spawn-no-kickoff.test.js +0 -115
- package/dist/core/__tests__/steer-note.test.d.ts +0 -1
- package/dist/core/__tests__/steer-note.test.js +0 -41
- package/dist/core/__tests__/subcommand-tier.test.d.ts +0 -1
- package/dist/core/__tests__/subcommand-tier.test.js +0 -99
- package/dist/core/__tests__/subscription-delivery.test.js +0 -211
- package/dist/core/__tests__/test-runner-cheap-sharding.test.d.ts +0 -1
- package/dist/core/__tests__/test-runner-cheap-sharding.test.js +0 -97
- package/dist/core/__tests__/tmux-surface.test.js +0 -484
- package/dist/core/__tests__/wake-bearings.test.d.ts +0 -1
- package/dist/core/__tests__/wake-bearings.test.js +0 -102
- package/dist/core/canvas/__tests__/remote-event-stream.test.d.ts +0 -1
- package/dist/core/canvas/__tests__/remote-event-stream.test.js +0 -146
- package/dist/core/canvas/__tests__/source-resolve.test.d.ts +0 -1
- package/dist/core/canvas/__tests__/source-resolve.test.js +0 -77
- package/dist/core/canvas/browse/__tests__/model.test.d.ts +0 -1
- package/dist/core/canvas/browse/__tests__/model.test.js +0 -290
- package/dist/core/canvas/browse/__tests__/rebuild-coalescer.test.d.ts +0 -1
- package/dist/core/canvas/browse/__tests__/rebuild-coalescer.test.js +0 -134
- package/dist/core/canvas/browse/__tests__/render.test.d.ts +0 -1
- package/dist/core/canvas/browse/__tests__/render.test.js +0 -111
- package/dist/core/canvas/browse/rebuild-coalescer.d.ts +0 -7
- package/dist/core/canvas/browse/rebuild-coalescer.js +0 -61
- package/dist/core/canvas/remote-event-stream.d.ts +0 -24
- package/dist/core/canvas/remote-event-stream.js +0 -94
- package/dist/core/feed/__tests__/coalesce-human.test.d.ts +0 -1
- package/dist/core/feed/__tests__/coalesce-human.test.js +0 -36
- package/dist/core/feed/__tests__/empty-node-guard.test.d.ts +0 -1
- package/dist/core/feed/__tests__/empty-node-guard.test.js +0 -45
- package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.d.ts +0 -1
- package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.js +0 -84
- package/dist/core/human/__tests__/visible-paths.test.d.ts +0 -1
- package/dist/core/human/__tests__/visible-paths.test.js +0 -28
- package/dist/core/keybindings/__tests__/inbox-affordance.test.d.ts +0 -1
- package/dist/core/keybindings/__tests__/inbox-affordance.test.js +0 -38
- package/dist/core/review/__tests__/project.test.d.ts +0 -1
- package/dist/core/review/__tests__/project.test.js +0 -71
- package/dist/core/runtime/__tests__/command-expansion.test.d.ts +0 -1
- package/dist/core/runtime/__tests__/command-expansion.test.js +0 -25
- package/dist/core/runtime/__tests__/session-visibility.test.d.ts +0 -1
- package/dist/core/runtime/__tests__/session-visibility.test.js +0 -119
- package/dist/core/runtime/__tests__/spawn-at-given-id.test.d.ts +0 -1
- package/dist/core/runtime/__tests__/spawn-at-given-id.test.js +0 -34
- package/dist/core/runtime/tmux.js +0 -1314
- package/dist/daemon/api/__tests__/full/push-parity.test.d.ts +0 -1
- package/dist/daemon/api/__tests__/full/worktree-parity.test.d.ts +0 -1
- package/dist/daemon/api/__tests__/full/worktree-parity.test.js +0 -160
- package/dist/daemon/api/__tests__/inbox.test.d.ts +0 -1
- package/dist/daemon/api/__tests__/inbox.test.js +0 -489
- package/dist/web-client/apple-touch-icon.png +0 -0
- package/dist/web-client/assets/fragment-mono-latin-400-normal-BYwT3kSJ.woff +0 -0
- package/dist/web-client/assets/fragment-mono-latin-400-normal-yxdJ5AmL.woff2 +0 -0
- package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-BbKYyvR9.woff2 +0 -0
- package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-CT4YFKeK.woff +0 -0
- package/dist/web-client/assets/index-CCPaLeW7.css +0 -2
- package/dist/web-client/assets/index-kyOWsnZK.js +0 -86
- package/dist/web-client/assets/martian-mono-cyrillic-wght-normal-B84CD5C_.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-latin-ext-wght-normal-DlL6xMw5.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-latin-wght-normal-5W32yIyr.woff2 +0 -0
- package/dist/web-client/assets/workbox-window.prod.es5-Bd17z0YL.js +0 -2
- package/dist/web-client/icon-192.png +0 -0
- package/dist/web-client/icon-512.png +0 -0
- package/dist/web-client/icon-maskable-512.png +0 -0
- package/dist/web-client/index.html +0 -17
- package/dist/web-client/manifest.webmanifest +0 -1
- package/dist/web-client/sw.js +0 -1
- /package/dist/api/__tests__/{client.test.d.ts → serial/client.test.d.ts} +0 -0
- /package/dist/clients/inbox/__tests__/{inbox-controller.test.d.ts → serial/inbox-controller.test.d.ts} +0 -0
- /package/dist/clients/inbox/__tests__/{mount-panel.test.d.ts → serial/mount-panel.test.d.ts} +0 -0
- /package/dist/clients/{attach/__tests__/attach-chrome-remote.test.d.ts → inbox/review/__tests__/teardown-notice.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/attach-keybindings.test.d.ts → core/__tests__/broker-extension-canvas-db-boundary.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/crtr-output-coverage.test.d.ts → core/__tests__/seam/broker-attach-multiclient.test.d.ts} +0 -0
- /package/dist/core/__tests__/{full → seam}/broker-attach-stream.test.d.ts +0 -0
- /package/dist/core/__tests__/{full → seam}/broker-crash-teardown.test.d.ts +0 -0
- /package/dist/core/__tests__/{full → seam}/broker-pane-resolution.test.d.ts +0 -0
- /package/dist/core/__tests__/{full → seam}/broker-provider-retry.test.d.ts +0 -0
- /package/dist/core/__tests__/{full → seam}/broker-whip-interrupt.test.d.ts +0 -0
- /package/dist/{clients/attach/__tests__/editor-frame-title.test.d.ts → core/__tests__/seam/dormancy-release.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/input-controller-extension-command.test.d.ts → core/__tests__/seam/yield-refresh-transaction.test.d.ts} +0 -0
- /package/dist/core/__tests__/{broker-fork-seam.test.d.ts → serial/broker-fork-seam.test.d.ts} +0 -0
- /package/dist/core/__tests__/{broker-sdk-wiring.test.d.ts → serial/broker-sdk-wiring.test.d.ts} +0 -0
- /package/dist/core/__tests__/{broker-snapshot-history.test.d.ts → serial/broker-snapshot-history.test.d.ts} +0 -0
- /package/dist/core/__tests__/{command-plugins.test.d.ts → serial/command-plugins.test.d.ts} +0 -0
- /package/dist/core/__tests__/{deferred-no-wake.test.d.ts → serial/deferred-no-wake.test.d.ts} +0 -0
- /package/dist/core/__tests__/{flagship-lifecycle.test.d.ts → serial/flagship-lifecycle.test.d.ts} +0 -0
- /package/dist/core/__tests__/{host-teardown-process-group.test.d.ts → serial/host-teardown-process-group.test.d.ts} +0 -0
- /package/dist/core/__tests__/{human-deliver-e2e.test.d.ts → serial/human-deliver-e2e.test.d.ts} +0 -0
- /package/dist/core/__tests__/{live-mutation.test.d.ts → serial/live-mutation.test.d.ts} +0 -0
- /package/dist/core/__tests__/{refresh-stall-recycle.test.d.ts → serial/refresh-stall-recycle.test.d.ts} +0 -0
- /package/dist/core/__tests__/{revive.test.d.ts → serial/revive.test.d.ts} +0 -0
- /package/dist/core/__tests__/{spawn-root.test.d.ts → serial/spawn-root.test.d.ts} +0 -0
- /package/dist/core/__tests__/{subscription-delivery.test.d.ts → serial/subscription-delivery.test.d.ts} +0 -0
- /package/dist/core/__tests__/{tmux-surface.test.d.ts → serial/tmux-surface.test.d.ts} +0 -0
- /package/dist/core/__tests__/{worktree.test.d.ts → serial/worktree.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/markdown-headings.test.d.ts → core/__tests__/watchdog-abort-arms-retry.test.d.ts} +0 -0
- /package/dist/{clients/attach/__tests__/session-identity-refresh.test.d.ts → core/broker-client/__tests__/transport-socket.test.d.ts} +0 -0
- /package/dist/core/human/__tests__/{inbox-core.test.d.ts → serial/inbox-core.test.d.ts} +0 -0
- /package/dist/{clients/inbox/review/__tests__/remap.test.d.ts → daemon/api/__tests__/node-model-validation.test.d.ts} +0 -0
- /package/dist/{clients/surfaces/region.js → daemon/api/__tests__/review-terminal-conflict.test.d.ts} +0 -0
- /package/dist/daemon/api/__tests__/{full → seam}/api-server.test.d.ts +0 -0
- /package/dist/daemon/api/__tests__/{full → seam}/b10-attach-modelauth.test.d.ts +0 -0
- /package/dist/{clients/web/__tests__/push-engine.test.d.ts → daemon/api/__tests__/seam/leaf-api-parity.test.d.ts} +0 -0
|
@@ -1,192 +1,85 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// command frames (sent via `hooks.onCommand`), reads pasted clipboard images to
|
|
5
|
-
// attach to the next message (review M1), and renders blocking extension dialogs
|
|
6
|
-
// (delegating to T5's `renderDialog`, routing the answer through
|
|
7
|
-
// `hooks.onDialogResponse`). It NEVER touches the session or spawns pi — it only
|
|
8
|
-
// produces frames; T7's `viewer.ts` constructs it and wires the socket.
|
|
9
|
-
//
|
|
10
|
-
// Keybinding matching is delegated to CustomEditor + its KeybindingsManager
|
|
11
|
-
// (built by config.ts with the vendored `app.*` defs + user overrides) — that
|
|
12
|
-
// is what calls `matchesKey` internally, so the controller wires intent via the
|
|
13
|
-
// editor's `onSubmit`/`onEscape`/`onPasteImage` hooks + `onAction(app.*)`
|
|
14
|
-
// registrations rather than matching raw keys itself.
|
|
1
|
+
// InputController owns editor intent and submission. Transient picker and dialog
|
|
2
|
+
// lifetime belongs to InputOverlayOwner; viewer permission belongs to the single
|
|
3
|
+
// ViewerCapabilities value supplied by session/input-wiring.
|
|
15
4
|
import { spawn } from 'node:child_process';
|
|
16
5
|
import { readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
17
6
|
import { tmpdir } from 'node:os';
|
|
18
7
|
import { join } from 'node:path';
|
|
19
8
|
import { BROKER_READ_CAPS, encodeFrame, } from '../../../core/runtime/broker-protocol.js';
|
|
20
|
-
import { isBlockingDialog, renderDialog } from '../overlays/dialogs.js';
|
|
21
|
-
import { readClipboardImage, writeClipboardImageToFile } from './clipboard-image.js';
|
|
22
|
-
import { dispatchSlashCommand, isSlashCommand, readOnlySlashRefusal } from '../slash/dispatch.js';
|
|
23
9
|
import { classifySubmit } from '../../conversation/submit.js';
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
* extension-dialog default since the model/session/tree lists are denser. */
|
|
29
|
-
const PICKER_OVERLAY_OPTIONS = { anchor: 'center', width: '80%', maxHeight: '85%' };
|
|
30
|
-
/** Max gap between two Esc presses to count as a double-tap (pi uses 500ms). */
|
|
10
|
+
import { dispatchSlashCommand, isSlashCommand } from '../slash/dispatch.js';
|
|
11
|
+
import { viewerOperationForFrame } from './capabilities.js';
|
|
12
|
+
import { readClipboardImage, writeClipboardImageToFile } from './clipboard-image.js';
|
|
13
|
+
import { InputOverlayOwner } from './overlay-owner.js';
|
|
31
14
|
const DOUBLE_ESCAPE_MS = 500;
|
|
32
|
-
/** Largest drive frame the controller will emit. The broker DESTROYS the viewer
|
|
33
|
-
* socket on any client line over BROKER_READ_CAPS.maxLineBytes (24 MiB), so we
|
|
34
|
-
* refuse to emit within a 4 MiB margin of that. `emitDrive` measures the ACTUAL
|
|
35
|
-
* encoded frame (text + JSON envelope), so the cap holds however the bytes
|
|
36
|
-
* split. Pasted images go to a temp file and enter the prompt as a path, never
|
|
37
|
-
* as inline base64, so a paste can no longer push a frame toward this ceiling. */
|
|
38
15
|
const MAX_FRAME_BYTES = BROKER_READ_CAPS.maxLineBytes - 4 * 1024 * 1024;
|
|
39
|
-
/** ClientToBroker frame types that mutate the engine/session — everything the
|
|
40
|
-
* broker gates behind a writable role (`read_only` in broker.ts), plus
|
|
41
|
-
* `reload_auth`
|
|
42
|
-
* (open to any role server-side, but viewer-local login/logout are unwired for
|
|
43
|
-
* a read-only viewer — this entry is belt-and-suspenders in case a
|
|
44
|
-
* future path routes it through onCommand). A read-only viewer — an observer
|
|
45
|
-
* (`--observer`) or a REMOTE attach (`--canvas`, always an observer) — is
|
|
46
|
-
* a strictly VIEW-ONLY surface: even though the broker would itself reject
|
|
47
|
-
* these from an observer, the input layer must never even emit one —
|
|
48
|
-
* server-side rejection alone is not the contract.
|
|
49
|
-
* `dequeue` is listed for documentation completeness even though it never
|
|
50
|
-
* reaches `emitCommand` (it rides the separate `onRequest` read-op channel —
|
|
51
|
-
* see `handleDequeue`'s own capability guard). Read-only frames (get_commands,
|
|
52
|
-
* list_models, list_sessions, get_tree, get_settings, list_scoped_models)
|
|
53
|
-
* and hello/bye/shutdown/extension_ui_response are deliberately absent —
|
|
54
|
-
* reads stay live for remote (pickers just never get the chance to select,
|
|
55
|
-
* see slash/dispatch.ts's READ_ONLY_SAFE_BUILTIN_NAMES gate). */
|
|
56
|
-
const DRIVE_FRAME_TYPES = new Set([
|
|
57
|
-
'prompt', 'steer', 'follow_up', 'abort', 'bash',
|
|
58
|
-
'set_model', 'cycle_model', 'cycle_ladder', 'cycle_thinking', 'set_thinking_level', 'dequeue',
|
|
59
|
-
'set_auto_retry', 'set_auto_compaction', 'compact', 'new_session',
|
|
60
|
-
'switch_session', 'fork', 'set_session_name', 'navigate_tree', 'reload',
|
|
61
|
-
'export', 'clone', 'share', 'reload_auth',
|
|
62
|
-
]);
|
|
63
16
|
export class InputController {
|
|
64
17
|
tui;
|
|
65
18
|
editor;
|
|
66
19
|
keybindings;
|
|
67
20
|
hooks;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
/** The request id of {@link dialog} — so a broker-driven `extension_ui_dismiss`
|
|
71
|
-
* tears down ONLY the matching overlay (correlated dismissal), never an
|
|
72
|
-
* unrelated one. */
|
|
73
|
-
dialogId;
|
|
74
|
-
/** The currently-open native picker overlay, if any (for supersede/dismiss). */
|
|
75
|
-
picker;
|
|
76
|
-
/** Monotonic open counter — a slow read-op reply whose `seq` is no longer the
|
|
77
|
-
* latest is dropped, so an earlier picker never pops over a later request. */
|
|
78
|
-
openSeq = 0;
|
|
79
|
-
/** Latest engine state from `welcome`/`session_info_changed` (for `/session`). */
|
|
21
|
+
capabilities;
|
|
22
|
+
overlays;
|
|
80
23
|
state;
|
|
81
|
-
/** Timestamp of the last lone Esc, for double-tap → tree detection (0 = none). */
|
|
82
24
|
lastEscapeAt = 0;
|
|
83
|
-
/** Pasted-image registry: placeholder ordinal → temp-file path. The editor
|
|
84
|
-
* shows a clean `[Image #N]` token instead of the ugly temp path; on submit
|
|
85
|
-
* each token is expanded back to its path so the agent reads it off disk.
|
|
86
|
-
* Cleared whenever the editor is emptied (send / clear). */
|
|
87
25
|
pastedImages = new Map();
|
|
88
|
-
/** Monotonic paste counter — the `N` in `[Image #N]`. */
|
|
89
26
|
pasteSeq = 0;
|
|
90
|
-
constructor(tui, editor, keybindings, hooks) {
|
|
27
|
+
constructor(tui, editor, keybindings, hooks, capabilities) {
|
|
91
28
|
this.tui = tui;
|
|
92
29
|
this.editor = editor;
|
|
93
30
|
this.keybindings = keybindings;
|
|
94
31
|
this.hooks = hooks;
|
|
32
|
+
this.capabilities = capabilities;
|
|
33
|
+
this.overlays = new InputOverlayOwner(tui, editor, keybindings, {
|
|
34
|
+
send: (frame) => this.emitCommand(frame),
|
|
35
|
+
notify: (message) => this.notify(message),
|
|
36
|
+
onDialogResponse: hooks.onDialogResponse,
|
|
37
|
+
onRequest: hooks.onRequest,
|
|
38
|
+
onMountPicker: hooks.onMountPicker,
|
|
39
|
+
onUnmountPicker: hooks.onUnmountPicker,
|
|
40
|
+
state: () => this.state,
|
|
41
|
+
});
|
|
95
42
|
this.wire();
|
|
96
43
|
}
|
|
97
|
-
/** Why this viewer may not mutate the engine, or `undefined` when it may. The
|
|
98
|
-
* ONE capability gate: a remote attach and an `--observer` attach are both
|
|
99
|
-
* read-only, and the role is fixed at hello, so this answer cannot change
|
|
100
|
-
* mid-session. */
|
|
101
|
-
readOnlyReason() {
|
|
102
|
-
if (this.hooks.remote === true)
|
|
103
|
-
return 'Read-only remote attach';
|
|
104
|
-
if ((this.hooks.role ?? 'controller') !== 'controller')
|
|
105
|
-
return 'Read-only observer attach';
|
|
106
|
-
return undefined;
|
|
107
|
-
}
|
|
108
|
-
/** Central choke point for every command frame the input layer emits (direct
|
|
109
|
-
* keybindings, emitDrive, and the slash-command/picker `send` sink). Returns
|
|
110
|
-
* false when the gate refused the frame, so callers stop without emitting
|
|
111
|
-
* it. A writable local attach is byte-for-byte unchanged. */
|
|
112
|
-
emitCommand(frame) {
|
|
113
|
-
const reason = this.readOnlyReason();
|
|
114
|
-
if (reason !== undefined && DRIVE_FRAME_TYPES.has(frame.type)) {
|
|
115
|
-
this.notify(`${reason} — cannot drive the engine`);
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
this.hooks.onCommand(frame);
|
|
119
|
-
return true;
|
|
120
|
-
}
|
|
121
|
-
/** The model-ladder keybinding (alt+m / alt+shift+m), which viewer.ts's global
|
|
122
|
-
* key listener owns rather than the editor's action table. Public so that
|
|
123
|
-
* binding rides the gate above instead of going straight to the socket. */
|
|
124
44
|
cycleLadder(direction) {
|
|
125
45
|
this.emitCommand({ type: 'cycle_ladder', direction });
|
|
126
46
|
}
|
|
127
|
-
/** Send viewer-owned text without touching the editor or its history. The
|
|
128
|
-
* broker aborts an active turn before starting this prompt, so a whip never
|
|
129
|
-
* waits in pi's steering queue behind the work it is meant to replace. */
|
|
130
47
|
sendInterrupting(text) {
|
|
131
48
|
return this.emitDrive({ type: 'prompt', text, interrupt: true });
|
|
132
49
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
* normal notice line and NOTHING else — a notify must never tear down an
|
|
136
|
-
* unrelated blocking dialog whose broker request is still pending. A dialog is
|
|
137
|
-
* torn down only by (a) a NEW blocking dialog superseding it, (b) its own
|
|
138
|
-
* answer, or (c) a correlated {@link dismissDialog}
|
|
139
|
-
* from the broker (the request was aborted/timed out out-of-band). */
|
|
140
|
-
attachDialog(req) {
|
|
141
|
-
if (req.method === 'notify') {
|
|
142
|
-
const prefix = req.notifyType === 'error' ? 'Error: ' : req.notifyType === 'warning' ? 'Warning: ' : '';
|
|
143
|
-
this.notify(`${prefix}${req.message}`);
|
|
144
|
-
this.tui.requestRender();
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
// Everything past here TEARS DOWN the showing dialog, so a non-blocking
|
|
148
|
-
// method must never reach it: pi's envelope carries display/editor methods
|
|
149
|
-
// this viewer handles elsewhere (or not at all), and letting one through
|
|
150
|
-
// would orphan an in-flight blocking request whose overlay it dismissed
|
|
151
|
-
// without answering. `renderDialog` is inert for them anyway — the guard is
|
|
152
|
-
// about the teardown above it, not the overlay below it.
|
|
153
|
-
if (!isBlockingDialog(req))
|
|
154
|
-
return;
|
|
155
|
-
this.dialog?.dismiss();
|
|
156
|
-
this.dialogId = req.id;
|
|
157
|
-
this.dialog = renderDialog(this.tui, req, (resp) => {
|
|
158
|
-
this.dialog = undefined;
|
|
159
|
-
this.dialogId = undefined;
|
|
160
|
-
this.hooks.onDialogResponse(resp);
|
|
161
|
-
}, this.keybindings);
|
|
162
|
-
this.tui.requestRender();
|
|
50
|
+
attachDialog(request) {
|
|
51
|
+
this.overlays.attachDialog(request);
|
|
163
52
|
}
|
|
164
|
-
/** Broker-driven correlated dismissal: the broker resolved this request itself
|
|
165
|
-
* (its request was aborted out-of-band — e.g. a local OAuth loopback callback
|
|
166
|
-
* won the race against this manual-paste dialog — or its timeout fired). Tear
|
|
167
|
-
* down ONLY the overlay whose id matches; leave any other dialog untouched. */
|
|
168
53
|
dismissDialog(id) {
|
|
169
|
-
|
|
170
|
-
return;
|
|
171
|
-
this.dialog?.dismiss();
|
|
172
|
-
this.dialog = undefined;
|
|
173
|
-
this.dialogId = undefined;
|
|
174
|
-
this.tui.requestRender();
|
|
54
|
+
this.overlays.dismissDialog(id);
|
|
175
55
|
}
|
|
176
|
-
/** Feed the latest engine state so read-only commands (e.g. `/session`) report
|
|
177
|
-
* current values. Optional; the controller works without it. */
|
|
178
56
|
setState(state) {
|
|
179
57
|
this.state = state;
|
|
180
58
|
}
|
|
181
|
-
|
|
59
|
+
openFileReviewPicker(candidates, cwd, onChoose) {
|
|
60
|
+
this.overlays.openFileReviewPicker(candidates, cwd, onChoose);
|
|
61
|
+
}
|
|
62
|
+
/** Retained as a narrow delegate for auth affordances wired by the session. */
|
|
63
|
+
openAuthPicker(prepare) {
|
|
64
|
+
return this.overlays.openAuthPicker(prepare);
|
|
65
|
+
}
|
|
66
|
+
send = (frame) => {
|
|
67
|
+
this.emitCommand(frame);
|
|
68
|
+
};
|
|
69
|
+
emitCommand(frame) {
|
|
70
|
+
const operation = viewerOperationForFrame(frame);
|
|
71
|
+
if (operation === undefined || this.capabilities.permits(operation)) {
|
|
72
|
+
this.hooks.onCommand(frame);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
this.notify(`${this.readOnlyLabel()} — cannot drive the engine`);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
readOnlyLabel() {
|
|
79
|
+
return this.capabilities.remote ? 'Read-only remote attach' : 'Read-only observer attach';
|
|
80
|
+
}
|
|
182
81
|
wire() {
|
|
183
82
|
this.editor.onSubmit = (text) => this.handleSubmit(text);
|
|
184
|
-
// Esc — mirror pi's native interactive escape (interactive-mode
|
|
185
|
-
// setupKeyHandlers): while streaming, abort the turn; with an EMPTY editor
|
|
186
|
-
// and idle, a double-tap (≤500ms) opens the session-tree navigator to rewind
|
|
187
|
-
// to an earlier point (a lone tap just arms it); a non-empty editor swallows
|
|
188
|
-
// Esc (it never clears text). openTreePicker is the same get_tree →
|
|
189
|
-
// navigate_tree path that `/tree` and app.session.tree use.
|
|
190
83
|
this.editor.onEscape = () => {
|
|
191
84
|
if (this.state?.isStreaming) {
|
|
192
85
|
this.emitCommand({ type: 'abort' });
|
|
@@ -197,55 +90,36 @@ export class InputController {
|
|
|
197
90
|
const now = Date.now();
|
|
198
91
|
if (now - this.lastEscapeAt < DOUBLE_ESCAPE_MS) {
|
|
199
92
|
this.lastEscapeAt = 0;
|
|
200
|
-
void this.openTreePicker();
|
|
93
|
+
void this.overlays.openTreePicker();
|
|
201
94
|
}
|
|
202
95
|
else {
|
|
203
96
|
this.lastEscapeAt = now;
|
|
204
97
|
}
|
|
205
98
|
};
|
|
206
|
-
|
|
207
|
-
// the file PATH into the editor at the caret (so the agent can read it).
|
|
208
|
-
this.editor.onPasteImage = () => {
|
|
209
|
-
void this.handlePaste();
|
|
210
|
-
};
|
|
211
|
-
// A file DRAGGED into the pane arrives as bracketed-paste text (its path), not
|
|
212
|
-
// an image event — TitledEditor recognizes an image path and routes it here so
|
|
213
|
-
// it becomes an `[Image #N]` token just like a Ctrl+V paste.
|
|
99
|
+
this.editor.onPasteImage = () => { void this.handlePaste(); };
|
|
214
100
|
const dropEditor = this.editor;
|
|
215
|
-
dropEditor.onPasteImagePath = (path) => {
|
|
216
|
-
this.attachImage(path);
|
|
217
|
-
return true;
|
|
218
|
-
};
|
|
219
|
-
// Keyboard shortcuts that map 1:1 to a frame needing no engine-side data.
|
|
101
|
+
dropEditor.onPasteImagePath = (path) => { this.attachImage(path); return true; };
|
|
220
102
|
this.editor.onAction('app.session.new', () => this.emitCommand({ type: 'new_session' }));
|
|
221
103
|
this.editor.onAction('app.model.cycleForward', () => this.emitCommand({ type: 'cycle_model', direction: 'forward' }));
|
|
222
104
|
this.editor.onAction('app.model.cycleBackward', () => this.emitCommand({ type: 'cycle_model', direction: 'backward' }));
|
|
223
105
|
this.editor.onAction('app.thinking.cycle', () => this.emitCommand({ type: 'cycle_thinking' }));
|
|
224
106
|
this.editor.onAction('app.message.followUp', () => this.handleFollowUp());
|
|
225
107
|
this.editor.onAction('app.message.dequeue', () => void this.handleDequeue());
|
|
226
|
-
// Pure client-side render toggles (ChatView owns the components).
|
|
227
108
|
this.editor.onAction('app.tools.expand', () => this.hooks.onToggleToolsExpand?.());
|
|
228
109
|
this.editor.onAction('app.thinking.toggle', () => this.hooks.onToggleThinking?.());
|
|
229
|
-
// Ctrl+G — compose the prompt in $VISUAL/$EDITOR, then load it back.
|
|
230
110
|
this.editor.onAction('app.editor.external', () => void this.openExternalEditor());
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
this.editor.onAction('app.
|
|
235
|
-
this.editor.onAction('app.session.tree', () => void this.openTreePicker());
|
|
236
|
-
this.editor.onAction('app.session.fork', () => void this.openForkPicker());
|
|
237
|
-
this.editor.onAction('app.session.resume', () => void this.openSessionPicker());
|
|
111
|
+
this.editor.onAction('app.model.select', () => void this.overlays.openModelPicker());
|
|
112
|
+
this.editor.onAction('app.session.tree', () => void this.overlays.openTreePicker());
|
|
113
|
+
this.editor.onAction('app.session.fork', () => void this.overlays.openForkPicker());
|
|
114
|
+
this.editor.onAction('app.session.resume', () => void this.overlays.openSessionPicker());
|
|
238
115
|
}
|
|
239
116
|
slashContext() {
|
|
240
117
|
return {
|
|
241
|
-
send:
|
|
118
|
+
send: this.send,
|
|
242
119
|
notify: (message) => this.notify(message),
|
|
120
|
+
capabilities: this.capabilities,
|
|
243
121
|
state: this.state,
|
|
244
122
|
cwd: process.cwd(),
|
|
245
|
-
nodeId: this.hooks.nodeId,
|
|
246
|
-
profileId: this.hooks.profileId,
|
|
247
|
-
remote: this.hooks.remote,
|
|
248
|
-
role: this.hooks.role,
|
|
249
123
|
onGraph: this.hooks.onGraph,
|
|
250
124
|
onInbox: this.hooks.onInbox,
|
|
251
125
|
onHelp: this.hooks.onHelp,
|
|
@@ -256,257 +130,38 @@ export class InputController {
|
|
|
256
130
|
onColor: this.hooks.onColor,
|
|
257
131
|
onNodeMetadata: this.hooks.onNodeMetadata,
|
|
258
132
|
onFoldTools: this.hooks.onFoldTools,
|
|
259
|
-
openModelPicker: () => void this.openModelPicker(),
|
|
260
|
-
openSessionPicker: () => void this.openSessionPicker(),
|
|
261
|
-
openForkPicker: () => void this.openForkPicker(),
|
|
262
|
-
openTreePicker: () => void this.openTreePicker(),
|
|
263
|
-
openSettingsPicker: () => void this.openSettingsPicker(),
|
|
264
|
-
openScopedModelsPicker: () => void this.openScopedModelsPicker(),
|
|
265
|
-
openMcpPicker: (serverName) => this.openMcpPicker(serverName),
|
|
266
|
-
openLoginPicker: this.hooks.openLoginPicker
|
|
267
|
-
openLogoutPicker: this.hooks.openLogoutPicker
|
|
133
|
+
openModelPicker: () => void this.overlays.openModelPicker(),
|
|
134
|
+
openSessionPicker: () => void this.overlays.openSessionPicker(),
|
|
135
|
+
openForkPicker: () => void this.overlays.openForkPicker(),
|
|
136
|
+
openTreePicker: () => void this.overlays.openTreePicker(),
|
|
137
|
+
openSettingsPicker: () => void this.overlays.openSettingsPicker(),
|
|
138
|
+
openScopedModelsPicker: () => void this.overlays.openScopedModelsPicker(),
|
|
139
|
+
openMcpPicker: (serverName) => this.overlays.openMcpPicker(serverName),
|
|
140
|
+
openLoginPicker: this.hooks.openLoginPicker,
|
|
141
|
+
openLogoutPicker: this.hooks.openLogoutPicker,
|
|
268
142
|
};
|
|
269
143
|
}
|
|
270
|
-
// -------------------------------------------------------------------------
|
|
271
|
-
// Native interactive pickers (overlays). Each fetches its payload via the
|
|
272
|
-
// correlated read-op, builds the REAL pi selector, and routes selection to the
|
|
273
|
-
// same command frame the text-arg slash path sends. Opened by slash command
|
|
274
|
-
// (via slashContext) AND by keybinding (onAction, below).
|
|
275
|
-
// -------------------------------------------------------------------------
|
|
276
|
-
/** Send a command frame to the broker (picker selection sink). */
|
|
277
|
-
send = (frame) => {
|
|
278
|
-
this.emitCommand(frame);
|
|
279
|
-
};
|
|
280
|
-
/** Fetch a picker payload (narrowed on `kind`), then build + show its overlay
|
|
281
|
-
* iff this open is still the latest (m4: a stale slow reply is dropped). Any
|
|
282
|
-
* failure — unwired channel, wrong kind, request error, or a throwing builder
|
|
283
|
-
* — surfaces a notice instead of an unhandled rejection (m5). */
|
|
284
|
-
async openPicker(frame, kind, label, build) {
|
|
285
|
-
if (this.hooks.onRequest === undefined) {
|
|
286
|
-
this.notify(`The ${label} picker isn't available in this viewer`);
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
const seq = ++this.openSeq;
|
|
290
|
-
let reply;
|
|
291
|
-
try {
|
|
292
|
-
reply = await this.hooks.onRequest(frame);
|
|
293
|
-
}
|
|
294
|
-
catch (err) {
|
|
295
|
-
this.notify(`Could not open the ${label} picker: ${err instanceof Error ? err.message : String(err)}`);
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
if (seq !== this.openSeq)
|
|
299
|
-
return; // a newer picker open superseded this one
|
|
300
|
-
if (reply.kind !== kind) {
|
|
301
|
-
this.notify(`Unexpected reply building the ${label} picker`);
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
this.showPicker((controls) => build(reply, controls.close));
|
|
305
|
-
}
|
|
306
|
-
/** Show a picker built by `build` (given a `close` that tears it down).
|
|
307
|
-
* Mounted INLINE under the editor when viewer.ts wires onMountPicker/
|
|
308
|
-
* onUnmountPicker (the integrated chrome path); otherwise falls back to a
|
|
309
|
-
* centered overlay. Supersedes any picker already on screen. Routes focus to
|
|
310
|
-
* the builder's declared focus target (an inner list for fork/settings) and
|
|
311
|
-
* ALWAYS restores editor focus on close — `OverlayHandle.hide()` only
|
|
312
|
-
* auto-restores when the OUTER component held focus, which is not the case
|
|
313
|
-
* once we focus an inner child. A throwing builder is caught into a notice (m5). */
|
|
314
|
-
showPicker(build) {
|
|
315
|
-
this.picker?.dismiss();
|
|
316
|
-
const inline = this.hooks.onMountPicker !== undefined && this.hooks.onUnmountPicker !== undefined;
|
|
317
|
-
let handle;
|
|
318
|
-
let current;
|
|
319
|
-
let done = false;
|
|
320
|
-
const disposeCurrent = () => {
|
|
321
|
-
const disposable = current?.component;
|
|
322
|
-
try {
|
|
323
|
-
disposable?.dispose?.();
|
|
324
|
-
}
|
|
325
|
-
catch {
|
|
326
|
-
/* ignore dispose errors during teardown */
|
|
327
|
-
}
|
|
328
|
-
};
|
|
329
|
-
const mount = (p) => {
|
|
330
|
-
if (inline)
|
|
331
|
-
this.hooks.onMountPicker(p.component);
|
|
332
|
-
else
|
|
333
|
-
handle = this.tui.showOverlay(p.component, PICKER_OVERLAY_OPTIONS);
|
|
334
|
-
this.tui.setFocus(p.focus);
|
|
335
|
-
};
|
|
336
|
-
const unmount = () => {
|
|
337
|
-
if (inline)
|
|
338
|
-
this.hooks.onUnmountPicker?.();
|
|
339
|
-
else
|
|
340
|
-
handle?.hide();
|
|
341
|
-
};
|
|
342
|
-
// Swap the mounted component IN PLACE (same inline slot) — multi-step flows
|
|
343
|
-
// like /login replace the provider selector with the login dialog without
|
|
344
|
-
// ever popping a centered overlay. Does NOT restore editor focus (only a
|
|
345
|
-
// genuine close does); focus moves to the new component.
|
|
346
|
-
const replace = (component, focus) => {
|
|
347
|
-
if (done)
|
|
348
|
-
return;
|
|
349
|
-
disposeCurrent();
|
|
350
|
-
unmount();
|
|
351
|
-
current = { component, focus };
|
|
352
|
-
mount(current);
|
|
353
|
-
this.tui.requestRender();
|
|
354
|
-
};
|
|
355
|
-
const close = () => {
|
|
356
|
-
if (done)
|
|
357
|
-
return;
|
|
358
|
-
done = true;
|
|
359
|
-
disposeCurrent();
|
|
360
|
-
unmount();
|
|
361
|
-
this.picker = undefined;
|
|
362
|
-
this.tui.setFocus(this.editor);
|
|
363
|
-
this.tui.requestRender();
|
|
364
|
-
};
|
|
365
|
-
try {
|
|
366
|
-
current = build({ close, replace });
|
|
367
|
-
}
|
|
368
|
-
catch (err) {
|
|
369
|
-
this.notify(`Could not open picker: ${err instanceof Error ? err.message : String(err)}`);
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
mount(current);
|
|
373
|
-
this.picker = {
|
|
374
|
-
// Supersede: tear down without restoring editor focus — the new picker
|
|
375
|
-
// grabs focus next. Genuine close restores the editor (above).
|
|
376
|
-
dismiss: () => {
|
|
377
|
-
if (done)
|
|
378
|
-
return;
|
|
379
|
-
done = true;
|
|
380
|
-
disposeCurrent();
|
|
381
|
-
unmount();
|
|
382
|
-
},
|
|
383
|
-
};
|
|
384
|
-
this.tui.requestRender();
|
|
385
|
-
}
|
|
386
|
-
openModelPicker() {
|
|
387
|
-
return this.openPicker({ type: 'list_models' }, 'list_models', 'model', (data, close) => buildModelPicker(this.tui, data, this.send, close));
|
|
388
|
-
}
|
|
389
|
-
/** Unlike the other pickers, the session picker opens IMMEDIATELY — its
|
|
390
|
-
* component owns async loaders (with a Loading header), so the multi-second
|
|
391
|
-
* session-dir scan runs off the open path instead of blocking it. */
|
|
392
|
-
openSessionPicker() {
|
|
393
|
-
const request = this.hooks.onRequest;
|
|
394
|
-
if (request === undefined) {
|
|
395
|
-
this.notify("The session picker isn't available in this viewer");
|
|
396
|
-
return Promise.resolve();
|
|
397
|
-
}
|
|
398
|
-
this.openSeq++; // supersede any in-flight fetch-first picker open
|
|
399
|
-
this.showPicker((controls) => buildSessionPicker(this.tui, this.state?.sessionFile, request, this.keybindings, this.send, controls.close));
|
|
400
|
-
return Promise.resolve();
|
|
401
|
-
}
|
|
402
|
-
openTreePicker() {
|
|
403
|
-
return this.openPicker({ type: 'get_tree' }, 'get_tree', 'tree', (data, close) => buildTreePicker(this.tui, data, this.send, close));
|
|
404
|
-
}
|
|
405
|
-
openForkPicker() {
|
|
406
|
-
return this.openPicker({ type: 'get_tree' }, 'get_tree', 'fork', (data, close) => buildForkPicker(data, this.send, close));
|
|
407
|
-
}
|
|
408
|
-
openSettingsPicker() {
|
|
409
|
-
return this.openPicker({ type: 'get_settings' }, 'get_settings', 'settings', (data, close) => buildSettingsPicker(data, this.send, close, (m) => this.notify(m)));
|
|
410
|
-
}
|
|
411
|
-
openScopedModelsPicker() {
|
|
412
|
-
return this.openPicker({ type: 'list_scoped_models' }, 'list_scoped_models', 'scoped-models', (data, close) => buildScopedModelsPicker(data, close, (m) => this.notify(m)));
|
|
413
|
-
}
|
|
414
|
-
/** `/mcp` — the MCP server status panel. Viewer-local (no read-op): the rows
|
|
415
|
-
* come from the adapter's on-disk config sources + tool cache
|
|
416
|
-
* (overlays/mcp.ts), and the actions (connect / auth-start) ride a `prompt`
|
|
417
|
-
* frame to the engine, where the adapter runtime actually lives. With a
|
|
418
|
-
* server name (`/mcp <server>`), act on that server directly. */
|
|
419
|
-
openMcpPicker(serverName) {
|
|
420
|
-
const rows = readMcpServers();
|
|
421
|
-
if (rows.length === 0) {
|
|
422
|
-
this.notify(MCP_EMPTY_STATE_HINT);
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
if (serverName !== undefined) {
|
|
426
|
-
const row = rows.find((r) => r.name === serverName);
|
|
427
|
-
if (row === undefined) {
|
|
428
|
-
this.notify(`No MCP server named ${JSON.stringify(serverName)} — usage: /mcp [server]`);
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
startMcpServerAction(row, this.send, (m) => this.notify(m));
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
this.showPicker((controls) => buildMcpPicker(rows, this.send, controls.close, (m) => this.notify(m)));
|
|
435
|
-
}
|
|
436
|
-
/** Open the transcript-backed file-review picker. The caller owns the popup
|
|
437
|
-
* process and cursor insertion; this controller only owns picker lifetime. */
|
|
438
|
-
openFileReviewPicker(candidates, cwd, onChoose) {
|
|
439
|
-
this.showPicker((controls) => buildFileReviewPicker(candidates, cwd, (file) => {
|
|
440
|
-
// The review mounts a full-pane surface and takes focus. Close this
|
|
441
|
-
// picker first so its editor-focus restoration cannot steal focus
|
|
442
|
-
// back from the newly mounted review.
|
|
443
|
-
controls.close();
|
|
444
|
-
onChoose(file);
|
|
445
|
-
}, controls.close, (message) => this.notify(message)));
|
|
446
|
-
}
|
|
447
|
-
/** Open a viewer-local auth picker (login/logout) — uses the same showPicker
|
|
448
|
-
* machinery as read-op pickers, but with a locally-built component (no read-op
|
|
449
|
-
* request; auth operations are viewer-local in tmux-local attach). `prepare`
|
|
450
|
-
* is async because provider status is async; superseded opens drop on openSeq. */
|
|
451
|
-
async openAuthPicker(prepare) {
|
|
452
|
-
const seq = ++this.openSeq;
|
|
453
|
-
let build;
|
|
454
|
-
try {
|
|
455
|
-
build = await prepare();
|
|
456
|
-
}
|
|
457
|
-
catch (err) {
|
|
458
|
-
this.notify(`Could not open the auth picker: ${err instanceof Error ? err.message : String(err)}`);
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
if (seq !== this.openSeq)
|
|
462
|
-
return; // a newer picker open superseded this one
|
|
463
|
-
this.showPicker(build);
|
|
464
|
-
}
|
|
465
144
|
handleSubmit(text) {
|
|
466
145
|
const trimmed = text.trim();
|
|
467
146
|
if (!trimmed)
|
|
468
147
|
return;
|
|
469
|
-
if (isSlashCommand(trimmed)) {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
// or engine frame is emitted.
|
|
473
|
-
const refusal = readOnlySlashRefusal(trimmed, slashCtx);
|
|
474
|
-
if (refusal !== undefined) {
|
|
475
|
-
this.notify(refusal);
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
// Recognized builtin/scoped-out → handled here; unrecognized falls through
|
|
479
|
-
// and is sent to the engine as a prompt (extension command).
|
|
480
|
-
if (dispatchSlashCommand(trimmed, slashCtx)) {
|
|
481
|
-
this.editor.setText('');
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
148
|
+
if (isSlashCommand(trimmed) && dispatchSlashCommand(trimmed, this.slashContext())) {
|
|
149
|
+
this.editor.setText('');
|
|
150
|
+
return;
|
|
484
151
|
}
|
|
485
|
-
// Classify the remaining cases: dormant, bash, prompt/steer.
|
|
486
152
|
const isDormant = this.hooks.isDormant?.() === true;
|
|
487
|
-
const onDormantSubmit = this.hooks.onDormantSubmit;
|
|
488
153
|
const classification = classifySubmit(text, {
|
|
489
154
|
isDormant,
|
|
490
|
-
canWakeDormant: isDormant && onDormantSubmit !== undefined,
|
|
155
|
+
canWakeDormant: isDormant && this.capabilities.permits('wake-dormant') && this.hooks.onDormantSubmit !== undefined,
|
|
491
156
|
isStreaming: this.state?.isStreaming === true,
|
|
492
157
|
});
|
|
493
158
|
if (classification.kind === 'empty')
|
|
494
159
|
return;
|
|
495
160
|
if (classification.kind === 'dormant') {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
// deliver the text as a wake message: it revives the node and lands as the
|
|
499
|
-
// first thing it reads, and the reconnect supervisor repaints the live pane.
|
|
500
|
-
// Local-only — remote attach leaves onDormantSubmit unwired, so prompts fall
|
|
501
|
-
// through unchanged there. Slash commands are dispatched above and don't reach
|
|
502
|
-
// here, so `/quit`-style local commands keep working while dormant.
|
|
503
|
-
// Raw text (whitespace and all) — not `trimmed` — per Major 4: emptiness is
|
|
504
|
-
// judged on the trimmed view above, but what's actually delivered is the
|
|
505
|
-
// human's exact typed content, same as the prompt/steer frame below.
|
|
506
|
-
if (onDormantSubmit === undefined)
|
|
161
|
+
const submit = this.hooks.onDormantSubmit;
|
|
162
|
+
if (submit === undefined)
|
|
507
163
|
return;
|
|
508
|
-
|
|
509
|
-
onDormantSubmit(expanded);
|
|
164
|
+
submit(this.expandImagePlaceholders(classification.text));
|
|
510
165
|
this.editor.addToHistory(classification.trimmed);
|
|
511
166
|
this.editor.setText('');
|
|
512
167
|
this.clearPastedImages();
|
|
@@ -514,37 +169,20 @@ export class InputController {
|
|
|
514
169
|
return;
|
|
515
170
|
}
|
|
516
171
|
if (classification.kind === 'bash') {
|
|
517
|
-
// `!cmd` / `!!cmd` → run bash in the engine and inject output into context,
|
|
518
|
-
// WITHOUT starting an agent turn (pi-native `!` semantics). `!!` withholds
|
|
519
|
-
// the output from the LLM. The broker relays the run as bash_* frames.
|
|
520
172
|
if (!this.emitDrive({ type: 'bash', command: classification.command, excludeFromContext: classification.excludeFromContext }))
|
|
521
173
|
return;
|
|
522
174
|
this.editor.addToHistory(classification.trimmed);
|
|
523
175
|
this.editor.setText('');
|
|
524
176
|
return;
|
|
525
177
|
}
|
|
526
|
-
// An unrecognized slash command belongs to the engine's extension runner.
|
|
527
|
-
// `dispatchSlashCommand` returned false above, so route it exactly like
|
|
528
|
-
// ordinary text instead of dropping the classifier's intermediate `slash`
|
|
529
|
-
// result. Submit-while-streaming = steer (pi-native parity): interject into
|
|
530
|
-
// the running turn instead of queueing a fresh prompt. `isStreaming` is the
|
|
531
|
-
// broker snapshot's busy signal; the broker routes `steer` → session.steer()
|
|
532
|
-
// and `prompt` → session.prompt(). Idle / no state yet → prompt (safe default).
|
|
533
|
-
// Classification above (slash/dormant/bash/emptiness) reads the TRIMMED
|
|
534
|
-
// view, but the frame itself carries the exact editor text (post image-
|
|
535
|
-
// placeholder substitution) — leading/trailing whitespace is the human's,
|
|
536
|
-
// not ours to discard.
|
|
537
178
|
const delivery = classification.kind === 'slash'
|
|
538
179
|
? (this.state?.isStreaming === true ? 'steer' : 'prompt')
|
|
539
180
|
: classification.kind;
|
|
540
181
|
if (delivery !== 'steer' && delivery !== 'prompt')
|
|
541
182
|
return;
|
|
542
183
|
const expanded = this.expandImagePlaceholders(classification.kind === 'slash' ? text : classification.text);
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
: { type: 'prompt', text: expanded };
|
|
546
|
-
if (!this.emitDrive(frame))
|
|
547
|
-
return; // refused or too large — no frame emitted
|
|
184
|
+
if (!this.emitDrive(delivery === 'steer' ? { type: 'steer', text: expanded } : { type: 'prompt', text: expanded }))
|
|
185
|
+
return;
|
|
548
186
|
this.editor.addToHistory(classification.trimmed);
|
|
549
187
|
this.editor.setText('');
|
|
550
188
|
this.clearPastedImages();
|
|
@@ -555,99 +193,28 @@ export class InputController {
|
|
|
555
193
|
const trimmed = text.trim();
|
|
556
194
|
if (!trimmed)
|
|
557
195
|
return;
|
|
558
|
-
// Same trimmed-classification/original-content split as handleSubmit's
|
|
559
|
-
// prompt/steer frame: emptiness is judged on the trimmed view, but the
|
|
560
|
-
// sent text is the exact editor content (post image-placeholder
|
|
561
|
-
// substitution), whitespace and all.
|
|
562
196
|
if (!this.emitDrive({ type: 'follow_up', text: this.expandImagePlaceholders(text) }))
|
|
563
197
|
return;
|
|
564
198
|
this.editor.addToHistory(trimmed);
|
|
565
199
|
this.editor.setText('');
|
|
566
200
|
this.clearPastedImages();
|
|
567
201
|
}
|
|
568
|
-
/** Replace every `[Image #N]` token with the temp-file path of the image the
|
|
569
|
-
* user pasted, so the agent receives a readable path even though the editor
|
|
570
|
-
* only ever showed the clean placeholder. Unknown ordinals are left as-is. */
|
|
571
|
-
expandImagePlaceholders(text) {
|
|
572
|
-
if (this.pastedImages.size === 0)
|
|
573
|
-
return text;
|
|
574
|
-
return text.replace(/\[Image #(\d+)\]/g, (match, digits) => {
|
|
575
|
-
const path = this.pastedImages.get(Number(digits));
|
|
576
|
-
return path ?? match;
|
|
577
|
-
});
|
|
578
|
-
}
|
|
579
|
-
/** Drop the pasted-image registry and reset the ordinal counter — called once
|
|
580
|
-
* a submit has consumed the editor text. */
|
|
581
|
-
clearPastedImages() {
|
|
582
|
-
this.pastedImages.clear();
|
|
583
|
-
this.pasteSeq = 0;
|
|
584
|
-
}
|
|
585
|
-
/** Ctrl+G / app.editor.external — open the current editor text in $VISUAL/$EDITOR
|
|
586
|
-
* and load the saved result back. Ported from pi's `openExternalEditor`: stop
|
|
587
|
-
* the TUI to release the terminal, run the editor with inherited stdio, and on
|
|
588
|
-
* a clean exit (status 0) replace the editor text; the socket keeps running in
|
|
589
|
-
* the background throughout. */
|
|
590
|
-
async openExternalEditor() {
|
|
591
|
-
const editorCmd = process.env.VISUAL || process.env.EDITOR;
|
|
592
|
-
if (!editorCmd) {
|
|
593
|
-
this.notify('No editor configured — set $VISUAL or $EDITOR');
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
const editorWithExpanded = this.editor;
|
|
597
|
-
const currentText = editorWithExpanded.getExpandedText?.() ?? this.editor.getText();
|
|
598
|
-
const tmpFile = join(tmpdir(), `crtr-attach-editor-${Date.now()}.pi.md`);
|
|
599
|
-
try {
|
|
600
|
-
writeFileSync(tmpFile, currentText, 'utf-8');
|
|
601
|
-
this.tui.stop();
|
|
602
|
-
// Split on spaces so `code --wait`-style commands keep their args.
|
|
603
|
-
const [cmd, ...args] = editorCmd.split(' ');
|
|
604
|
-
process.stdout.write(`Launching external editor: ${editorCmd}\nThe viewer resumes when it exits.\n`);
|
|
605
|
-
const status = await new Promise((resolve) => {
|
|
606
|
-
const child = spawn(cmd, [...args, tmpFile], {
|
|
607
|
-
stdio: 'inherit',
|
|
608
|
-
shell: process.platform === 'win32',
|
|
609
|
-
});
|
|
610
|
-
child.on('error', () => resolve(null));
|
|
611
|
-
child.on('close', (code) => resolve(code));
|
|
612
|
-
});
|
|
613
|
-
if (status === 0) {
|
|
614
|
-
this.editor.setText(readFileSync(tmpFile, 'utf-8').replace(/\n$/, ''));
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
catch (err) {
|
|
618
|
-
this.notify(`External editor failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
619
|
-
}
|
|
620
|
-
finally {
|
|
621
|
-
try {
|
|
622
|
-
unlinkSync(tmpFile);
|
|
623
|
-
}
|
|
624
|
-
catch {
|
|
625
|
-
/* ignore temp-file cleanup errors */
|
|
626
|
-
}
|
|
627
|
-
this.tui.start();
|
|
628
|
-
this.tui.requestRender(true);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
/** Alt+Up / app.message.dequeue — clear the broker's queued steering + follow-up
|
|
632
|
-
* messages and restore them to the editor, prepended to any in-progress text
|
|
633
|
-
* (pi's `restoreQueuedMessagesToEditor`: queued text first, joined by blank
|
|
634
|
-
* lines). A read-AND-mutate op over the correlated request channel. */
|
|
635
202
|
async handleDequeue() {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
this.notify(`${reason} — cannot restore queued messages`);
|
|
203
|
+
if (!this.capabilities.permits('dequeue')) {
|
|
204
|
+
this.notify(`${this.readOnlyLabel()} — cannot restore queued messages`);
|
|
639
205
|
return;
|
|
640
206
|
}
|
|
641
|
-
|
|
207
|
+
const request = this.hooks.onRequest;
|
|
208
|
+
if (request === undefined) {
|
|
642
209
|
this.notify("Restoring queued messages isn't available in this viewer");
|
|
643
210
|
return;
|
|
644
211
|
}
|
|
645
212
|
let reply;
|
|
646
213
|
try {
|
|
647
|
-
reply = await
|
|
214
|
+
reply = await request({ type: 'dequeue' });
|
|
648
215
|
}
|
|
649
|
-
catch (
|
|
650
|
-
this.notify(`Could not restore queued messages: ${
|
|
216
|
+
catch (error) {
|
|
217
|
+
this.notify(`Could not restore queued messages: ${error instanceof Error ? error.message : String(error)}`);
|
|
651
218
|
return;
|
|
652
219
|
}
|
|
653
220
|
if (reply.kind !== 'dequeue') {
|
|
@@ -659,28 +226,22 @@ export class InputController {
|
|
|
659
226
|
this.notify('No queued messages to restore');
|
|
660
227
|
return;
|
|
661
228
|
}
|
|
662
|
-
|
|
663
|
-
this.editor.setText(combined);
|
|
229
|
+
this.editor.setText([queued.join('\n\n'), this.editor.getText()].filter((value) => value.trim()).join('\n\n'));
|
|
664
230
|
this.tui.requestRender();
|
|
665
231
|
this.notify(`Restored ${queued.length} queued message${queued.length > 1 ? 's' : ''} to editor`);
|
|
666
232
|
}
|
|
667
|
-
/** Send a drive frame iff the WHOLE encoded frame fits under MAX_FRAME_BYTES
|
|
668
|
-
* (the broker destroys the socket on any line over its 24 MiB read cap). Over
|
|
669
|
-
* the ceiling → notify + refuse without sending a socket-destroying overflow. */
|
|
670
233
|
emitDrive(frame) {
|
|
671
|
-
let bytes;
|
|
672
234
|
try {
|
|
673
|
-
bytes = Buffer.byteLength(encodeFrame(frame));
|
|
235
|
+
const bytes = Buffer.byteLength(encodeFrame(frame));
|
|
236
|
+
if (bytes > MAX_FRAME_BYTES) {
|
|
237
|
+
this.notify(`Message too large to send (${Math.round(bytes / (1024 * 1024))} MiB) — shorten the text or remove an attached image`);
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
674
240
|
}
|
|
675
241
|
catch {
|
|
676
242
|
this.notify('Message could not be encoded');
|
|
677
243
|
return false;
|
|
678
244
|
}
|
|
679
|
-
if (bytes > MAX_FRAME_BYTES) {
|
|
680
|
-
const mib = Math.round(bytes / (1024 * 1024));
|
|
681
|
-
this.notify(`Message too large to send (${mib} MiB) — shorten the text or remove an attached image`);
|
|
682
|
-
return false;
|
|
683
|
-
}
|
|
684
245
|
return this.emitCommand(frame);
|
|
685
246
|
}
|
|
686
247
|
async handlePaste() {
|
|
@@ -690,34 +251,64 @@ export class InputController {
|
|
|
690
251
|
this.notify('No image in the clipboard');
|
|
691
252
|
return;
|
|
692
253
|
}
|
|
693
|
-
// The clipboard layer read an image but DROPPED it — surface the reason.
|
|
694
254
|
if (!result.image) {
|
|
695
255
|
this.notify(result.note ?? 'Image not pasted');
|
|
696
256
|
return;
|
|
697
257
|
}
|
|
698
|
-
|
|
699
|
-
// relying on inline base64 the viewer can't render), then attach it as an
|
|
700
|
-
// `[Image #N]` token (see attachImage).
|
|
701
|
-
const path = writeClipboardImageToFile(result.image);
|
|
702
|
-
this.attachImage(path, result.note);
|
|
258
|
+
this.attachImage(writeClipboardImageToFile(result.image), result.note);
|
|
703
259
|
}
|
|
704
260
|
catch {
|
|
705
261
|
this.notify('Could not read the clipboard image');
|
|
706
262
|
}
|
|
707
263
|
}
|
|
708
|
-
/** Register an image path in the paste registry and splice a clean `[Image #N]`
|
|
709
|
-
* token into the editor at the caret instead of the raw path. On submit the
|
|
710
|
-
* token is expanded back to the path (see expandImagePlaceholders), so the
|
|
711
|
-
* agent reads the image off disk. Shared by clipboard paste and drag-drop. */
|
|
712
264
|
attachImage(path, note) {
|
|
713
265
|
const n = ++this.pasteSeq;
|
|
714
266
|
this.pastedImages.set(n, path);
|
|
715
|
-
const
|
|
716
|
-
|
|
717
|
-
this.editor.insertTextAtCursor(`${sep}[Image #${n}] `);
|
|
267
|
+
const current = this.editor.getText();
|
|
268
|
+
this.editor.insertTextAtCursor(`${current && !/\s$/.test(current) ? ' ' : ''}[Image #${n}] `);
|
|
718
269
|
this.notify(note ? `Image #${n} attached (${note})` : `Image #${n} attached`);
|
|
719
270
|
this.tui.requestRender();
|
|
720
271
|
}
|
|
272
|
+
expandImagePlaceholders(text) {
|
|
273
|
+
return text.replace(/\[Image #(\d+)\]/g, (match, digits) => this.pastedImages.get(Number(digits)) ?? match);
|
|
274
|
+
}
|
|
275
|
+
clearPastedImages() {
|
|
276
|
+
this.pastedImages.clear();
|
|
277
|
+
this.pasteSeq = 0;
|
|
278
|
+
}
|
|
279
|
+
async openExternalEditor() {
|
|
280
|
+
const editorCommand = process.env.VISUAL || process.env.EDITOR;
|
|
281
|
+
if (!editorCommand) {
|
|
282
|
+
this.notify('No editor configured — set $VISUAL or $EDITOR');
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const expandedEditor = this.editor;
|
|
286
|
+
const temporaryFile = join(tmpdir(), `crtr-attach-editor-${Date.now()}.pi.md`);
|
|
287
|
+
try {
|
|
288
|
+
writeFileSync(temporaryFile, expandedEditor.getExpandedText?.() ?? this.editor.getText(), 'utf-8');
|
|
289
|
+
this.tui.stop();
|
|
290
|
+
const [command, ...args] = editorCommand.split(' ');
|
|
291
|
+
process.stdout.write(`Launching external editor: ${editorCommand}\nThe viewer resumes when it exits.\n`);
|
|
292
|
+
const status = await new Promise((resolve) => {
|
|
293
|
+
const child = spawn(command, [...args, temporaryFile], { stdio: 'inherit', shell: process.platform === 'win32' });
|
|
294
|
+
child.on('error', () => resolve(null));
|
|
295
|
+
child.on('close', (code) => resolve(code));
|
|
296
|
+
});
|
|
297
|
+
if (status === 0)
|
|
298
|
+
this.editor.setText(readFileSync(temporaryFile, 'utf-8').replace(/\n$/, ''));
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
this.notify(`External editor failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
302
|
+
}
|
|
303
|
+
finally {
|
|
304
|
+
try {
|
|
305
|
+
unlinkSync(temporaryFile);
|
|
306
|
+
}
|
|
307
|
+
catch { /* ignore temp cleanup */ }
|
|
308
|
+
this.tui.start();
|
|
309
|
+
this.tui.requestRender(true);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
721
312
|
notify(message) {
|
|
722
313
|
this.hooks.onNotice?.(message);
|
|
723
314
|
}
|