@crouton-kit/crouter 0.3.27 → 0.3.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-root.js +4 -2
- package/dist/builtin-memory/crouter-development/marketplaces.md +7 -6
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +1 -1
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +1 -1
- package/dist/builtin-memory/crouter-development/personas.md +1 -1
- package/dist/builtin-memory/crouter-development/plugins.md +20 -18
- package/dist/builtin-memory/design.md +1 -1
- package/dist/builtin-memory/development.md +1 -1
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
- package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/planning.md +1 -1
- package/dist/builtin-memory/spec.md +6 -4
- package/dist/builtin-personas/orchestration-kernel.md +4 -5
- package/dist/builtin-personas/runtime-base.md +8 -4
- package/dist/builtin-personas/spec/PERSONA.md +8 -4
- package/dist/builtin-personas/spec/orchestrator.md +5 -3
- package/dist/builtin-personas/spec/requirements/PERSONA.md +10 -0
- package/dist/builtin-views/canvas/core.mjs +586 -0
- package/dist/builtin-views/canvas/text.mjs +58 -0
- package/dist/builtin-views/canvas/tui.mjs +165 -0
- package/dist/builtin-views/canvas/web.jsx +120 -0
- package/dist/builtin-views/git-pr/core.mjs +673 -0
- package/dist/builtin-views/git-pr/text.mjs +84 -0
- package/dist/builtin-views/git-pr/tui.mjs +302 -0
- package/dist/builtin-views/git-pr/web.jsx +216 -0
- package/dist/builtin-views/inbox/_lib/render.mjs +1 -3
- package/dist/builtin-views/inbox/core.mjs +1273 -0
- package/dist/builtin-views/inbox/text.mjs +73 -0
- package/dist/builtin-views/inbox/tui.mjs +312 -0
- package/dist/builtin-views/inbox/web.jsx +188 -0
- package/dist/builtin-views/linkedin/core.mjs +906 -0
- package/dist/builtin-views/linkedin/text.mjs +69 -0
- package/dist/builtin-views/linkedin/tui.mjs +427 -0
- package/dist/builtin-views/linkedin/web.jsx +206 -0
- package/dist/builtin-views/workspace-sidebar/core.mjs +621 -0
- package/dist/builtin-views/workspace-sidebar/text.mjs +53 -0
- package/dist/builtin-views/workspace-sidebar/tui.mjs +142 -0
- package/dist/builtin-views/workspace-sidebar/web.jsx +109 -0
- package/dist/cli.js +3 -3
- package/dist/clients/attach/__tests__/action-parity.test.js +48 -0
- package/dist/clients/attach/__tests__/clipboard-image.test.js +76 -0
- package/dist/clients/attach/__tests__/editor-newline.test.js +57 -0
- package/dist/clients/attach/__tests__/git-info.test.js +31 -0
- package/dist/clients/attach/__tests__/onrequest-wired.test.js +20 -0
- package/dist/clients/attach/__tests__/reconnect-giveup.test.js +30 -0
- package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
- package/dist/clients/attach/__tests__/titled-editor.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
- package/dist/clients/attach/attach-cmd.js +445 -41
- package/dist/clients/attach/auth-pickers.d.ts +9 -0
- package/dist/clients/attach/auth-pickers.js +194 -0
- package/dist/clients/attach/canvas-panels.d.ts +12 -0
- package/dist/clients/attach/canvas-panels.js +78 -0
- package/dist/clients/attach/chat-view.d.ts +27 -2
- package/dist/clients/attach/chat-view.js +98 -9
- package/dist/clients/attach/clipboard-image.d.ts +47 -8
- package/dist/clients/attach/clipboard-image.js +150 -26
- package/dist/clients/attach/clipboard-text.d.ts +3 -0
- package/dist/clients/attach/clipboard-text.js +39 -0
- package/dist/clients/attach/config-load.d.ts +54 -7
- package/dist/clients/attach/config-load.js +99 -3
- package/dist/clients/attach/context-message.d.ts +12 -0
- package/dist/clients/attach/context-message.js +72 -0
- package/dist/clients/attach/git-info.d.ts +20 -0
- package/dist/clients/attach/git-info.js +44 -0
- package/dist/clients/attach/graph-overlay.d.ts +43 -0
- package/dist/clients/attach/graph-overlay.js +306 -0
- package/dist/clients/attach/input-controller.d.ts +81 -13
- package/dist/clients/attach/input-controller.js +307 -67
- package/dist/clients/attach/pickers.d.ts +58 -0
- package/dist/clients/attach/pickers.js +171 -0
- package/dist/clients/attach/slash-commands.d.ts +44 -1
- package/dist/clients/attach/slash-commands.js +250 -27
- package/dist/clients/attach/titled-editor.d.ts +33 -0
- package/dist/clients/attach/titled-editor.js +84 -0
- package/dist/clients/attach/view-socket.d.ts +37 -2
- package/dist/clients/attach/view-socket.js +131 -7
- package/dist/clients/web/dev-server.d.ts +7 -0
- package/dist/clients/web/dev-server.js +59 -0
- package/dist/clients/web/events.d.ts +14 -0
- package/dist/clients/web/events.js +151 -0
- package/dist/clients/web/server.d.ts +18 -0
- package/dist/clients/web/server.js +450 -0
- package/dist/clients/web/web-cmd.d.ts +2 -0
- package/dist/clients/web/web-cmd.js +120 -0
- package/dist/commands/canvas.js +1 -2
- package/dist/commands/chord.js +1 -1
- package/dist/commands/dashboard.js +5 -1
- package/dist/commands/human/prompts.js +3 -3
- package/dist/commands/human/queue.d.ts +17 -0
- package/dist/commands/human/queue.js +111 -4
- package/dist/commands/memory/__tests__/lint-schema.test.js +27 -4
- package/dist/commands/memory/lint.d.ts +5 -4
- package/dist/commands/memory/lint.js +23 -7
- package/dist/commands/memory/read.js +2 -2
- package/dist/commands/memory/shared.d.ts +7 -1
- package/dist/commands/memory/shared.js +18 -3
- package/dist/commands/memory/write.js +20 -7
- package/dist/commands/memory.js +4 -4
- package/dist/commands/node.d.ts +1 -1
- package/dist/commands/node.js +64 -65
- package/dist/commands/pkg.js +1 -2
- package/dist/commands/revive.js +11 -3
- package/dist/commands/sys/feedback.d.ts +1 -0
- package/dist/commands/sys/feedback.js +163 -0
- package/dist/commands/sys/sync.d.ts +1 -0
- package/dist/commands/sys/sync.js +187 -0
- package/dist/commands/sys.js +4 -2
- package/dist/commands/view-cycle.js +2 -2
- package/dist/commands/view-list.js +8 -8
- package/dist/commands/view-new.js +21 -17
- package/dist/commands/view-pick.js +1 -1
- package/dist/commands/view-run.js +35 -14
- package/dist/commands/view.js +8 -6
- package/dist/commands/workspace.d.ts +2 -0
- package/dist/commands/workspace.js +161 -0
- package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
- package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
- package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
- package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
- package/dist/core/__tests__/broker-snapshot-history.test.d.ts +1 -0
- package/dist/core/__tests__/broker-snapshot-history.test.js +105 -0
- package/dist/core/__tests__/child-death-wake.test.js +56 -111
- package/dist/core/__tests__/close.test.js +14 -20
- package/dist/core/__tests__/context-intro.test.js +19 -19
- package/dist/core/__tests__/daemon-boot.test.js +30 -23
- package/dist/core/__tests__/draw-style.test.js +1 -1
- package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +24 -1
- package/dist/core/__tests__/fixtures/fake-engine.js +32 -2
- package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
- package/dist/core/__tests__/focuses.test.js +25 -13
- package/dist/core/__tests__/fork.test.js +22 -5
- package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
- package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
- package/dist/core/__tests__/full/cascade-close.test.js +22 -8
- package/dist/core/__tests__/full/detach-focus.test.js +66 -169
- package/dist/core/__tests__/helpers/harness.d.ts +6 -9
- package/dist/core/__tests__/helpers/harness.js +19 -36
- package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
- package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
- package/dist/core/__tests__/human-stranded-deliver.test.d.ts +1 -0
- package/dist/core/__tests__/human-stranded-deliver.test.js +108 -0
- package/dist/core/__tests__/lifecycle.test.js +1 -19
- package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
- package/dist/core/__tests__/memory-resolver.test.js +1 -1
- package/dist/core/__tests__/on-read-dedup-resume.test.d.ts +1 -0
- package/dist/core/__tests__/on-read-dedup-resume.test.js +81 -0
- package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
- package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
- package/dist/core/__tests__/relaunch-root.test.js +114 -0
- package/dist/core/__tests__/reset.test.js +19 -79
- package/dist/core/__tests__/steer-note.test.js +9 -7
- package/dist/core/__tests__/tmux-surface.test.js +13 -7
- package/dist/core/bootstrap.d.ts +0 -3
- package/dist/core/bootstrap.js +1 -143
- package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
- package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
- package/dist/core/canvas/browse/app.js +131 -8
- package/dist/core/canvas/browse/model.d.ts +23 -5
- package/dist/core/canvas/browse/model.js +68 -13
- package/dist/core/canvas/browse/render.d.ts +7 -1
- package/dist/core/canvas/browse/render.js +178 -47
- package/dist/core/canvas/canvas.js +1 -1
- package/dist/core/canvas/focuses.d.ts +4 -4
- package/dist/core/canvas/focuses.js +7 -7
- package/dist/core/canvas/nav-model.d.ts +162 -0
- package/dist/core/canvas/nav-model.js +487 -0
- package/dist/core/canvas/paths.d.ts +7 -0
- package/dist/core/canvas/paths.js +9 -0
- package/dist/core/canvas/render.d.ts +68 -9
- package/dist/core/canvas/render.js +195 -61
- package/dist/core/canvas/types.d.ts +16 -23
- package/dist/core/config.js +1 -4
- package/dist/core/memory-resolver.js +43 -5
- package/dist/core/runtime/bearings.d.ts +1 -1
- package/dist/core/runtime/bearings.js +8 -8
- package/dist/core/runtime/branded-host.d.ts +16 -0
- package/dist/core/runtime/branded-host.js +127 -0
- package/dist/core/runtime/broker-protocol.d.ts +212 -6
- package/dist/core/runtime/broker-sdk.d.ts +0 -12
- package/dist/core/runtime/broker-sdk.js +77 -6
- package/dist/core/runtime/broker.d.ts +5 -1
- package/dist/core/runtime/broker.js +520 -59
- package/dist/core/runtime/close.d.ts +12 -2
- package/dist/core/runtime/close.js +37 -16
- package/dist/core/runtime/front-door.js +14 -7
- package/dist/core/runtime/host.d.ts +14 -34
- package/dist/core/runtime/host.js +14 -50
- package/dist/core/runtime/launch.d.ts +12 -9
- package/dist/core/runtime/launch.js +27 -15
- package/dist/core/runtime/lifecycle.d.ts +1 -1
- package/dist/core/runtime/lifecycle.js +15 -19
- package/dist/core/runtime/nodes.d.ts +0 -55
- package/dist/core/runtime/nodes.js +6 -74
- package/dist/core/runtime/placement.d.ts +91 -334
- package/dist/core/runtime/placement.js +262 -828
- package/dist/core/runtime/promote.d.ts +2 -0
- package/dist/core/runtime/promote.js +24 -6
- package/dist/core/runtime/recap.d.ts +8 -0
- package/dist/core/runtime/recap.js +107 -0
- package/dist/core/runtime/recycle.js +25 -61
- package/dist/core/runtime/reset.d.ts +43 -37
- package/dist/core/runtime/reset.js +131 -218
- package/dist/core/runtime/revive.d.ts +9 -29
- package/dist/core/runtime/revive.js +45 -135
- package/dist/core/runtime/spawn.d.ts +15 -11
- package/dist/core/runtime/spawn.js +168 -136
- package/dist/core/runtime/surface-bg.d.ts +11 -0
- package/dist/core/runtime/surface-bg.js +68 -0
- package/dist/core/runtime/tmux.d.ts +15 -43
- package/dist/core/runtime/tmux.js +38 -80
- package/dist/core/scope.d.ts +1 -2
- package/dist/core/scope.js +1 -5
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
- package/dist/core/skill-sync/builtins.d.ts +42 -0
- package/dist/core/skill-sync/builtins.js +112 -0
- package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
- package/dist/core/skill-sync/claude-plugins.js +71 -0
- package/dist/core/skill-sync/engine.d.ts +42 -0
- package/dist/core/skill-sync/engine.js +633 -0
- package/dist/core/skill-sync/export.d.ts +23 -0
- package/dist/core/skill-sync/export.js +86 -0
- package/dist/core/skill-sync/manifest.d.ts +64 -0
- package/dist/core/skill-sync/manifest.js +181 -0
- package/dist/core/skill-sync/profile.d.ts +76 -0
- package/dist/core/skill-sync/profile.js +173 -0
- package/dist/core/skill-sync/snapshot.d.ts +57 -0
- package/dist/core/skill-sync/snapshot.js +120 -0
- package/dist/core/spawn.d.ts +14 -0
- package/dist/core/spawn.js +29 -9
- package/dist/core/substrate/index.d.ts +2 -2
- package/dist/core/substrate/index.js +7 -7
- package/dist/core/substrate/injected-store.d.ts +10 -0
- package/dist/core/substrate/injected-store.js +55 -0
- package/dist/core/substrate/on-read.js +68 -6
- package/dist/core/substrate/render.d.ts +8 -11
- package/dist/core/substrate/render.js +29 -43
- package/dist/core/substrate/schema.d.ts +16 -11
- package/dist/core/substrate/schema.js +32 -31
- package/dist/core/tui/host.d.ts +12 -4
- package/dist/core/tui/host.js +280 -149
- package/dist/core/view/bridge.d.ts +10 -0
- package/dist/core/view/bridge.js +31 -0
- package/dist/core/view/chrome.d.ts +9 -0
- package/dist/core/view/chrome.js +22 -0
- package/dist/core/view/contract.d.ts +171 -0
- package/dist/core/view/contract.js +23 -0
- package/dist/core/view/loader.d.ts +31 -0
- package/dist/core/view/loader.js +188 -0
- package/dist/core/view/transport-local.d.ts +7 -0
- package/dist/core/view/transport-local.js +70 -0
- package/dist/core/view/transport.d.ts +4 -0
- package/dist/core/view/transport.js +15 -0
- package/dist/daemon/crtrd.d.ts +38 -27
- package/dist/daemon/crtrd.js +303 -376
- package/dist/daemon/manage.js +6 -1
- package/dist/index.js +1 -1
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
- package/dist/pi-extensions/canvas-context-intro.js +4 -4
- package/dist/pi-extensions/canvas-doc-substrate.js +29 -20
- package/dist/pi-extensions/canvas-nav.js +35 -385
- package/dist/pi-extensions/canvas-recap.d.ts +37 -0
- package/dist/pi-extensions/canvas-recap.js +236 -0
- package/dist/pi-extensions/canvas-resume.js +2 -1
- package/dist/pi-extensions/canvas-stophook.d.ts +15 -13
- package/dist/pi-extensions/canvas-stophook.js +119 -103
- package/dist/pi-extensions/canvas-view.js +2 -1
- package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
- package/dist/pi-extensions/widget-order-bus.js +34 -0
- package/dist/prompts/view.d.ts +2 -2
- package/dist/prompts/view.js +145 -73
- package/dist/types.d.ts +1 -6
- package/dist/types.js +1 -3
- package/dist/web/ViewChrome.d.ts +7 -0
- package/dist/web/ViewChrome.js +28 -0
- package/dist/web/ViewPane.d.ts +39 -0
- package/dist/web/ViewPane.js +48 -0
- package/dist/web/index.d.ts +6 -0
- package/dist/web/index.js +16 -0
- package/dist/web/runtime.d.ts +39 -0
- package/dist/web/runtime.js +133 -0
- package/dist/web/states.d.ts +24 -0
- package/dist/web/states.js +24 -0
- package/dist/web/transport-http.d.ts +5 -0
- package/dist/web/transport-http.js +28 -0
- package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
- package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
- package/dist/web-client/index.html +13 -0
- package/package.json +20 -6
- package/dist/builtin-views/canvas/client.mjs +0 -303
- package/dist/builtin-views/canvas/view.mjs +0 -576
- package/dist/builtin-views/git-pr/client.mjs +0 -440
- package/dist/builtin-views/git-pr/view.mjs +0 -675
- package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
- package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
- package/dist/builtin-views/inbox/view.mjs +0 -889
- package/dist/builtin-views/linkedin/client.mjs +0 -610
- package/dist/builtin-views/linkedin/view.mjs +0 -1171
- package/dist/commands/pkg/bridge.d.ts +0 -1
- package/dist/commands/pkg/bridge.js +0 -137
- package/dist/commands/skill/author.d.ts +0 -3
- package/dist/commands/skill/author.js +0 -140
- package/dist/commands/skill/shared.d.ts +0 -3
- package/dist/commands/skill/shared.js +0 -19
- package/dist/commands/skill.d.ts +0 -2
- package/dist/commands/skill.js +0 -21
- package/dist/commands/tmux-spread.d.ts +0 -2
- package/dist/commands/tmux-spread.js +0 -144
- package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
- package/dist/core/__tests__/full/placement-focus.test.js +0 -309
- package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
- package/dist/core/__tests__/full/placement-revive.test.js +0 -238
- package/dist/core/__tests__/full/placement-teardown.test.js +0 -221
- package/dist/core/__tests__/home-session.test.js +0 -186
- package/dist/core/__tests__/relaunch.test.js +0 -335
- package/dist/core/bridge-map.d.ts +0 -19
- package/dist/core/bridge-map.js +0 -73
- package/dist/core/tui/contract.d.ts +0 -83
- package/dist/core/tui/contract.js +0 -8
- package/dist/core/tui/loader.d.ts +0 -16
- package/dist/core/tui/loader.js +0 -94
- package/dist/prompts/skill.d.ts +0 -2
- package/dist/prompts/skill.js +0 -650
- /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
- /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/reconnect-giveup.test.d.ts} +0 -0
- /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
|
@@ -12,41 +12,43 @@
|
|
|
12
12
|
// is what calls `matchesKey` internally, so the controller wires intent via the
|
|
13
13
|
// editor's `onSubmit`/`onEscape`/`onPasteImage` hooks + `onAction(app.*)`
|
|
14
14
|
// registrations rather than matching raw keys itself.
|
|
15
|
+
import { spawn } from 'node:child_process';
|
|
16
|
+
import { readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { tmpdir } from 'node:os';
|
|
18
|
+
import { join } from 'node:path';
|
|
15
19
|
import { BROKER_READ_CAPS, encodeFrame, } from '../../core/runtime/broker-protocol.js';
|
|
16
20
|
import { renderDialog } from './extension-dialogs.js';
|
|
17
|
-
import { readClipboardImage } from './clipboard-image.js';
|
|
21
|
+
import { readClipboardImage, writeClipboardImageToFile } from './clipboard-image.js';
|
|
18
22
|
import { dispatchSlashCommand, isSlashCommand } from './slash-commands.js';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
* (images + unbounded text + JSON envelope) is then bounded airtight by the
|
|
26
|
-
* MAX_FRAME_BYTES guard in `emitDrive` — this budget alone is not enough, since
|
|
27
|
-
* `text` is otherwise unbounded. */
|
|
28
|
-
const MAX_PENDING_IMAGE_BYTES = 16 * 1024 * 1024;
|
|
29
|
-
const MAX_PENDING_IMAGES = 4;
|
|
23
|
+
import { buildForkPicker, buildModelPicker, buildScopedModelsPicker, buildSessionPicker, buildSettingsPicker, buildTreePicker, } from './pickers.js';
|
|
24
|
+
/** Overlay geometry for the native pickers — a touch wider/taller than the
|
|
25
|
+
* extension-dialog default since the model/session/tree lists are denser. */
|
|
26
|
+
const PICKER_OVERLAY_OPTIONS = { anchor: 'center', width: '80%', maxHeight: '85%' };
|
|
27
|
+
/** Max gap between two Esc presses to count as a double-tap (pi uses 500ms). */
|
|
28
|
+
const DOUBLE_ESCAPE_MS = 500;
|
|
30
29
|
/** Largest drive frame the controller will emit. The broker DESTROYS the viewer
|
|
31
30
|
* socket on any client line over BROKER_READ_CAPS.maxLineBytes (24 MiB), so we
|
|
32
31
|
* refuse to emit within a 4 MiB margin of that. `emitDrive` measures the ACTUAL
|
|
33
|
-
* encoded frame (text +
|
|
34
|
-
*
|
|
35
|
-
*
|
|
32
|
+
* encoded frame (text + JSON envelope), so the cap holds however the bytes
|
|
33
|
+
* split. Pasted images go to a temp file and enter the prompt as a path, never
|
|
34
|
+
* as inline base64, so a paste can no longer push a frame toward this ceiling. */
|
|
36
35
|
const MAX_FRAME_BYTES = BROKER_READ_CAPS.maxLineBytes - 4 * 1024 * 1024;
|
|
37
36
|
export class InputController {
|
|
38
37
|
tui;
|
|
39
38
|
editor;
|
|
40
39
|
keybindings;
|
|
41
40
|
hooks;
|
|
42
|
-
/** Images pasted since the last send, attached to the next prompt/follow-up. */
|
|
43
|
-
pendingImages = [];
|
|
44
|
-
/** Running base64-byte total of `pendingImages` (review M2 aggregate budget). */
|
|
45
|
-
pendingImageBytes = 0;
|
|
46
41
|
/** The currently-rendered blocking dialog, if any (for supersede/dismiss). */
|
|
47
42
|
dialog;
|
|
43
|
+
/** The currently-open native picker overlay, if any (for supersede/dismiss). */
|
|
44
|
+
picker;
|
|
45
|
+
/** Monotonic open counter — a slow read-op reply whose `seq` is no longer the
|
|
46
|
+
* latest is dropped, so an earlier picker never pops over a later request. */
|
|
47
|
+
openSeq = 0;
|
|
48
48
|
/** Latest engine state from `welcome`/`session_info_changed` (for `/session`). */
|
|
49
49
|
state;
|
|
50
|
+
/** Timestamp of the last lone Esc, for double-tap → tree detection (0 = none). */
|
|
51
|
+
lastEscapeAt = 0;
|
|
50
52
|
constructor(tui, editor, keybindings, hooks) {
|
|
51
53
|
this.tui = tui;
|
|
52
54
|
this.editor = editor;
|
|
@@ -73,16 +75,52 @@ export class InputController {
|
|
|
73
75
|
// -------------------------------------------------------------------------
|
|
74
76
|
wire() {
|
|
75
77
|
this.editor.onSubmit = (text) => this.handleSubmit(text);
|
|
76
|
-
// Esc
|
|
77
|
-
|
|
78
|
-
//
|
|
78
|
+
// Esc — mirror pi's native interactive escape (interactive-mode
|
|
79
|
+
// setupKeyHandlers): while streaming, abort the turn; with an EMPTY editor
|
|
80
|
+
// and idle, a double-tap (≤500ms) opens the session-tree navigator to rewind
|
|
81
|
+
// to an earlier point (a lone tap just arms it); a non-empty editor swallows
|
|
82
|
+
// Esc (it never clears text). openTreePicker is the same get_tree →
|
|
83
|
+
// navigate_tree path that `/tree` and app.session.tree use.
|
|
84
|
+
this.editor.onEscape = () => {
|
|
85
|
+
if (this.state?.isStreaming) {
|
|
86
|
+
this.hooks.onCommand({ type: 'abort' });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (this.editor.getText().trim())
|
|
90
|
+
return;
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
if (now - this.lastEscapeAt < DOUBLE_ESCAPE_MS) {
|
|
93
|
+
this.lastEscapeAt = 0;
|
|
94
|
+
void this.openTreePicker();
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
this.lastEscapeAt = now;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
// Ctrl+V / Alt+V → read clipboard image, save it to a temp file, and splice
|
|
101
|
+
// the file PATH into the editor at the caret (so the agent can read it).
|
|
79
102
|
this.editor.onPasteImage = () => {
|
|
80
103
|
void this.handlePaste();
|
|
81
104
|
};
|
|
82
105
|
// Keyboard shortcuts that map 1:1 to a frame needing no engine-side data.
|
|
83
106
|
this.editor.onAction('app.session.new', () => this.hooks.onCommand({ type: 'new_session' }));
|
|
84
|
-
this.editor.onAction('app.model.cycleForward', () => this.hooks.onCommand({ type: 'cycle_model' }));
|
|
107
|
+
this.editor.onAction('app.model.cycleForward', () => this.hooks.onCommand({ type: 'cycle_model', direction: 'forward' }));
|
|
108
|
+
this.editor.onAction('app.model.cycleBackward', () => this.hooks.onCommand({ type: 'cycle_model', direction: 'backward' }));
|
|
109
|
+
this.editor.onAction('app.thinking.cycle', () => this.hooks.onCommand({ type: 'cycle_thinking' }));
|
|
85
110
|
this.editor.onAction('app.message.followUp', () => this.handleFollowUp());
|
|
111
|
+
this.editor.onAction('app.message.dequeue', () => void this.handleDequeue());
|
|
112
|
+
// Pure client-side render toggles (ChatView owns the components).
|
|
113
|
+
this.editor.onAction('app.tools.expand', () => this.hooks.onToggleToolsExpand?.());
|
|
114
|
+
this.editor.onAction('app.thinking.toggle', () => this.hooks.onToggleThinking?.());
|
|
115
|
+
// Ctrl+G — compose the prompt in $VISUAL/$EDITOR, then load it back.
|
|
116
|
+
this.editor.onAction('app.editor.external', () => void this.openExternalEditor());
|
|
117
|
+
// Native pickers via keybinding. `app.model.select` is bound to ctrl+l by
|
|
118
|
+
// default (vendored in config-load); the session.* actions ship with no
|
|
119
|
+
// default key but fire if the user binds one — wiring them keeps parity.
|
|
120
|
+
this.editor.onAction('app.model.select', () => void this.openModelPicker());
|
|
121
|
+
this.editor.onAction('app.session.tree', () => void this.openTreePicker());
|
|
122
|
+
this.editor.onAction('app.session.fork', () => void this.openForkPicker());
|
|
123
|
+
this.editor.onAction('app.session.resume', () => void this.openSessionPicker());
|
|
86
124
|
}
|
|
87
125
|
slashContext() {
|
|
88
126
|
return {
|
|
@@ -90,13 +128,163 @@ export class InputController {
|
|
|
90
128
|
notify: (message) => this.notify(message),
|
|
91
129
|
state: this.state,
|
|
92
130
|
cwd: process.cwd(),
|
|
131
|
+
nodeId: this.hooks.nodeId,
|
|
132
|
+
onGraph: this.hooks.onGraph,
|
|
133
|
+
onQuit: this.hooks.onQuit,
|
|
134
|
+
onCopy: this.hooks.onCopy,
|
|
135
|
+
openModelPicker: () => void this.openModelPicker(),
|
|
136
|
+
openSessionPicker: () => void this.openSessionPicker(),
|
|
137
|
+
openForkPicker: () => void this.openForkPicker(),
|
|
138
|
+
openTreePicker: () => void this.openTreePicker(),
|
|
139
|
+
openSettingsPicker: () => void this.openSettingsPicker(),
|
|
140
|
+
openScopedModelsPicker: () => void this.openScopedModelsPicker(),
|
|
141
|
+
openLoginPicker: this.hooks.openLoginPicker ? () => this.hooks.openLoginPicker() : undefined,
|
|
142
|
+
openLogoutPicker: this.hooks.openLogoutPicker ? () => this.hooks.openLogoutPicker() : undefined,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
// -------------------------------------------------------------------------
|
|
146
|
+
// Native interactive pickers (overlays). Each fetches its payload via the
|
|
147
|
+
// correlated read-op, builds the REAL pi selector, and routes selection to the
|
|
148
|
+
// same command frame the text-arg slash path sends. Opened by slash command
|
|
149
|
+
// (via slashContext) AND by keybinding (onAction, below).
|
|
150
|
+
// -------------------------------------------------------------------------
|
|
151
|
+
/** Send a command frame to the broker (picker selection sink). */
|
|
152
|
+
send = (frame) => this.hooks.onCommand(frame);
|
|
153
|
+
/** Fetch a picker payload (narrowed on `kind`), then build + show its overlay
|
|
154
|
+
* iff this open is still the latest (m4: a stale slow reply is dropped). Any
|
|
155
|
+
* failure — unwired channel, wrong kind, request error, or a throwing builder
|
|
156
|
+
* — surfaces a notice instead of an unhandled rejection (m5). */
|
|
157
|
+
async openPicker(frame, kind, label, build) {
|
|
158
|
+
if (this.hooks.onRequest === undefined) {
|
|
159
|
+
this.notify(`The ${label} picker isn't available in this viewer`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const seq = ++this.openSeq;
|
|
163
|
+
let reply;
|
|
164
|
+
try {
|
|
165
|
+
reply = await this.hooks.onRequest(frame);
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
this.notify(`Could not open the ${label} picker: ${err instanceof Error ? err.message : String(err)}`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (seq !== this.openSeq)
|
|
172
|
+
return; // a newer picker open superseded this one
|
|
173
|
+
if (reply.kind !== kind) {
|
|
174
|
+
this.notify(`Unexpected reply building the ${label} picker`);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
this.showPicker((controls) => build(reply, controls.close));
|
|
178
|
+
}
|
|
179
|
+
/** Show a picker built by `build` (given a `close` that tears it down).
|
|
180
|
+
* Mounted INLINE under the editor when attach-cmd wires onMountPicker/
|
|
181
|
+
* onUnmountPicker (the integrated chrome path); otherwise falls back to a
|
|
182
|
+
* centered overlay. Supersedes any picker already on screen. Routes focus to
|
|
183
|
+
* the builder's declared focus target (an inner list for fork/settings) and
|
|
184
|
+
* ALWAYS restores editor focus on close — `OverlayHandle.hide()` only
|
|
185
|
+
* auto-restores when the OUTER component held focus, which is not the case
|
|
186
|
+
* once we focus an inner child. A throwing builder is caught into a notice (m5). */
|
|
187
|
+
showPicker(build) {
|
|
188
|
+
this.picker?.dismiss();
|
|
189
|
+
const inline = this.hooks.onMountPicker !== undefined && this.hooks.onUnmountPicker !== undefined;
|
|
190
|
+
let handle;
|
|
191
|
+
let current;
|
|
192
|
+
let done = false;
|
|
193
|
+
const disposeCurrent = () => {
|
|
194
|
+
const disposable = current?.component;
|
|
195
|
+
try {
|
|
196
|
+
disposable?.dispose?.();
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
/* ignore dispose errors during teardown */
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const mount = (p) => {
|
|
203
|
+
if (inline)
|
|
204
|
+
this.hooks.onMountPicker(p.component);
|
|
205
|
+
else
|
|
206
|
+
handle = this.tui.showOverlay(p.component, PICKER_OVERLAY_OPTIONS);
|
|
207
|
+
this.tui.setFocus(p.focus);
|
|
208
|
+
};
|
|
209
|
+
const unmount = () => {
|
|
210
|
+
if (inline)
|
|
211
|
+
this.hooks.onUnmountPicker?.();
|
|
212
|
+
else
|
|
213
|
+
handle?.hide();
|
|
214
|
+
};
|
|
215
|
+
// Swap the mounted component IN PLACE (same inline slot) — multi-step flows
|
|
216
|
+
// like /login replace the provider selector with the login dialog without
|
|
217
|
+
// ever popping a centered overlay. Does NOT restore editor focus (only a
|
|
218
|
+
// genuine close does); focus moves to the new component.
|
|
219
|
+
const replace = (component, focus) => {
|
|
220
|
+
if (done)
|
|
221
|
+
return;
|
|
222
|
+
disposeCurrent();
|
|
223
|
+
unmount();
|
|
224
|
+
current = { component, focus };
|
|
225
|
+
mount(current);
|
|
226
|
+
this.tui.requestRender();
|
|
227
|
+
};
|
|
228
|
+
const close = () => {
|
|
229
|
+
if (done)
|
|
230
|
+
return;
|
|
231
|
+
done = true;
|
|
232
|
+
disposeCurrent();
|
|
233
|
+
unmount();
|
|
234
|
+
this.picker = undefined;
|
|
235
|
+
this.tui.setFocus(this.editor);
|
|
236
|
+
this.tui.requestRender();
|
|
237
|
+
};
|
|
238
|
+
try {
|
|
239
|
+
current = build({ close, replace });
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
this.notify(`Could not open picker: ${err instanceof Error ? err.message : String(err)}`);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
mount(current);
|
|
246
|
+
this.picker = {
|
|
247
|
+
// Supersede: tear down without restoring editor focus — the new picker
|
|
248
|
+
// grabs focus next. Genuine close restores the editor (above).
|
|
249
|
+
dismiss: () => {
|
|
250
|
+
if (done)
|
|
251
|
+
return;
|
|
252
|
+
done = true;
|
|
253
|
+
disposeCurrent();
|
|
254
|
+
unmount();
|
|
255
|
+
},
|
|
93
256
|
};
|
|
257
|
+
this.tui.requestRender();
|
|
258
|
+
}
|
|
259
|
+
openModelPicker() {
|
|
260
|
+
return this.openPicker({ type: 'list_models' }, 'list_models', 'model', (data, close) => buildModelPicker(this.tui, data, this.send, close));
|
|
261
|
+
}
|
|
262
|
+
openSessionPicker() {
|
|
263
|
+
return this.openPicker({ type: 'list_sessions', scope: 'cwd' }, 'list_sessions', 'session', (data, close) => buildSessionPicker(this.tui, data, this.hooks.onRequest, this.keybindings, this.send, close));
|
|
264
|
+
}
|
|
265
|
+
openTreePicker() {
|
|
266
|
+
return this.openPicker({ type: 'get_tree' }, 'get_tree', 'tree', (data, close) => buildTreePicker(this.tui, data, this.send, close));
|
|
267
|
+
}
|
|
268
|
+
openForkPicker() {
|
|
269
|
+
return this.openPicker({ type: 'get_tree' }, 'get_tree', 'fork', (data, close) => buildForkPicker(data, this.send, close));
|
|
270
|
+
}
|
|
271
|
+
openSettingsPicker() {
|
|
272
|
+
return this.openPicker({ type: 'get_settings' }, 'get_settings', 'settings', (data, close) => buildSettingsPicker(data, this.send, close, (m) => this.notify(m)));
|
|
273
|
+
}
|
|
274
|
+
openScopedModelsPicker() {
|
|
275
|
+
return this.openPicker({ type: 'list_scoped_models' }, 'list_scoped_models', 'scoped-models', (data, close) => buildScopedModelsPicker(data, close, (m) => this.notify(m)));
|
|
276
|
+
}
|
|
277
|
+
/** Open a viewer-local auth picker (login/logout) — uses the same showPicker
|
|
278
|
+
* machinery as read-op pickers, but with a locally-built component (no read-op
|
|
279
|
+
* request; auth operations are viewer-local in tmux-local attach). */
|
|
280
|
+
openAuthPicker(build) {
|
|
281
|
+
this.showPicker(build);
|
|
94
282
|
}
|
|
95
283
|
handleSubmit(text) {
|
|
96
284
|
const trimmed = text.trim();
|
|
97
|
-
if (!trimmed
|
|
285
|
+
if (!trimmed)
|
|
98
286
|
return;
|
|
99
|
-
if (
|
|
287
|
+
if (isSlashCommand(trimmed)) {
|
|
100
288
|
// Recognized builtin/scoped-out → handled here; unrecognized falls through
|
|
101
289
|
// and is sent to the engine as a prompt (extension command).
|
|
102
290
|
if (dispatchSlashCommand(trimmed, this.slashContext())) {
|
|
@@ -104,34 +292,105 @@ export class InputController {
|
|
|
104
292
|
return;
|
|
105
293
|
}
|
|
106
294
|
}
|
|
107
|
-
const images = this.pendingImagesPayload();
|
|
108
295
|
// Submit-while-streaming = steer (pi-native parity): interject into the
|
|
109
296
|
// running turn instead of queueing a fresh prompt. `isStreaming` is the
|
|
110
297
|
// broker snapshot's busy signal; the broker routes `steer` → session.steer()
|
|
111
298
|
// and `prompt` → session.prompt(). Idle / no state yet → prompt (safe default).
|
|
112
299
|
const busy = this.state?.isStreaming === true;
|
|
113
300
|
const frame = busy
|
|
114
|
-
? { type: 'steer', text: trimmed
|
|
115
|
-
: { type: 'prompt', text: trimmed
|
|
301
|
+
? { type: 'steer', text: trimmed }
|
|
302
|
+
: { type: 'prompt', text: trimmed };
|
|
116
303
|
if (!this.emitDrive(frame))
|
|
117
|
-
return; // too large — keep editor
|
|
118
|
-
this.
|
|
119
|
-
if (trimmed)
|
|
120
|
-
this.editor.addToHistory(trimmed);
|
|
304
|
+
return; // too large — keep editor to trim
|
|
305
|
+
this.editor.addToHistory(trimmed);
|
|
121
306
|
this.editor.setText('');
|
|
122
307
|
}
|
|
123
308
|
handleFollowUp() {
|
|
124
309
|
const text = this.editor.getText().trim();
|
|
125
|
-
if (!text
|
|
310
|
+
if (!text)
|
|
126
311
|
return;
|
|
127
|
-
|
|
128
|
-
if (!this.emitDrive({ type: 'follow_up', text, images }))
|
|
312
|
+
if (!this.emitDrive({ type: 'follow_up', text }))
|
|
129
313
|
return;
|
|
130
|
-
this.
|
|
131
|
-
if (text)
|
|
132
|
-
this.editor.addToHistory(text);
|
|
314
|
+
this.editor.addToHistory(text);
|
|
133
315
|
this.editor.setText('');
|
|
134
316
|
}
|
|
317
|
+
/** Ctrl+G / app.editor.external — open the current editor text in $VISUAL/$EDITOR
|
|
318
|
+
* and load the saved result back. Ported from pi's `openExternalEditor`: stop
|
|
319
|
+
* the TUI to release the terminal, run the editor with inherited stdio, and on
|
|
320
|
+
* a clean exit (status 0) replace the editor text; the socket keeps running in
|
|
321
|
+
* the background throughout. */
|
|
322
|
+
async openExternalEditor() {
|
|
323
|
+
const editorCmd = process.env.VISUAL || process.env.EDITOR;
|
|
324
|
+
if (!editorCmd) {
|
|
325
|
+
this.notify('No editor configured — set $VISUAL or $EDITOR');
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const editorWithExpanded = this.editor;
|
|
329
|
+
const currentText = editorWithExpanded.getExpandedText?.() ?? this.editor.getText();
|
|
330
|
+
const tmpFile = join(tmpdir(), `crtr-attach-editor-${Date.now()}.pi.md`);
|
|
331
|
+
try {
|
|
332
|
+
writeFileSync(tmpFile, currentText, 'utf-8');
|
|
333
|
+
this.tui.stop();
|
|
334
|
+
// Split on spaces so `code --wait`-style commands keep their args.
|
|
335
|
+
const [cmd, ...args] = editorCmd.split(' ');
|
|
336
|
+
process.stdout.write(`Launching external editor: ${editorCmd}\nThe viewer resumes when it exits.\n`);
|
|
337
|
+
const status = await new Promise((resolve) => {
|
|
338
|
+
const child = spawn(cmd, [...args, tmpFile], {
|
|
339
|
+
stdio: 'inherit',
|
|
340
|
+
shell: process.platform === 'win32',
|
|
341
|
+
});
|
|
342
|
+
child.on('error', () => resolve(null));
|
|
343
|
+
child.on('close', (code) => resolve(code));
|
|
344
|
+
});
|
|
345
|
+
if (status === 0) {
|
|
346
|
+
this.editor.setText(readFileSync(tmpFile, 'utf-8').replace(/\n$/, ''));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
catch (err) {
|
|
350
|
+
this.notify(`External editor failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
351
|
+
}
|
|
352
|
+
finally {
|
|
353
|
+
try {
|
|
354
|
+
unlinkSync(tmpFile);
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
/* ignore temp-file cleanup errors */
|
|
358
|
+
}
|
|
359
|
+
this.tui.start();
|
|
360
|
+
this.tui.requestRender(true);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/** Alt+Up / app.message.dequeue — clear the broker's queued steering + follow-up
|
|
364
|
+
* messages and restore them to the editor, prepended to any in-progress text
|
|
365
|
+
* (pi's `restoreQueuedMessagesToEditor`: queued text first, joined by blank
|
|
366
|
+
* lines). A read-AND-mutate op over the correlated request channel. */
|
|
367
|
+
async handleDequeue() {
|
|
368
|
+
if (this.hooks.onRequest === undefined) {
|
|
369
|
+
this.notify("Restoring queued messages isn't available in this viewer");
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
let reply;
|
|
373
|
+
try {
|
|
374
|
+
reply = await this.hooks.onRequest({ type: 'dequeue' });
|
|
375
|
+
}
|
|
376
|
+
catch (err) {
|
|
377
|
+
this.notify(`Could not restore queued messages: ${err instanceof Error ? err.message : String(err)}`);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (reply.kind !== 'dequeue') {
|
|
381
|
+
this.notify('Unexpected reply restoring queued messages');
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const queued = [...reply.steering, ...reply.followUp];
|
|
385
|
+
if (queued.length === 0) {
|
|
386
|
+
this.notify('No queued messages to restore');
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const combined = [queued.join('\n\n'), this.editor.getText()].filter((t) => t.trim()).join('\n\n');
|
|
390
|
+
this.editor.setText(combined);
|
|
391
|
+
this.tui.requestRender();
|
|
392
|
+
this.notify(`Restored ${queued.length} queued message${queued.length > 1 ? 's' : ''} to editor`);
|
|
393
|
+
}
|
|
135
394
|
/** Send a drive frame iff the WHOLE encoded frame fits under MAX_FRAME_BYTES
|
|
136
395
|
* (the broker destroys the socket on any line over its 24 MiB read cap). Over
|
|
137
396
|
* the ceiling → notify + refuse so the caller leaves the editor + pending
|
|
@@ -160,44 +419,25 @@ export class InputController {
|
|
|
160
419
|
this.notify('No image in the clipboard');
|
|
161
420
|
return;
|
|
162
421
|
}
|
|
163
|
-
// The clipboard layer read an image but DROPPED it
|
|
164
|
-
// ceiling) — surface the reason and attach nothing.
|
|
422
|
+
// The clipboard layer read an image but DROPPED it — surface the reason.
|
|
165
423
|
if (!result.image) {
|
|
166
|
-
this.notify(result.note ?? 'Image not
|
|
424
|
+
this.notify(result.note ?? 'Image not pasted');
|
|
167
425
|
return;
|
|
168
426
|
}
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
this.pendingImages.push(result.image);
|
|
179
|
-
this.pendingImageBytes += bytes;
|
|
180
|
-
this.notify(result.note
|
|
181
|
-
? `Image attached (${result.note}) — sends with your next message`
|
|
182
|
-
: 'Image attached — sends with your next message');
|
|
427
|
+
// Persist to a temp file and splice the PATH into the editor at the caret,
|
|
428
|
+
// so the agent reads the image off disk instead of relying on inline base64
|
|
429
|
+
// the viewer can't render.
|
|
430
|
+
const path = writeClipboardImageToFile(result.image);
|
|
431
|
+
const cur = this.editor.getText();
|
|
432
|
+
const sep = cur && !/\s$/.test(cur) ? ' ' : '';
|
|
433
|
+
this.editor.insertTextAtCursor(`${sep}${path} `);
|
|
434
|
+
this.notify(result.note ? `Image saved (${result.note}) — path inserted` : 'Image saved — path inserted');
|
|
183
435
|
this.tui.requestRender();
|
|
184
436
|
}
|
|
185
437
|
catch {
|
|
186
438
|
this.notify('Could not read the clipboard image');
|
|
187
439
|
}
|
|
188
440
|
}
|
|
189
|
-
/** Snapshot the pending images as an `images?` payload (undefined when none)
|
|
190
|
-
* WITHOUT clearing — so a frame refused by `emitDrive` (too large) keeps them
|
|
191
|
-
* for the user to trim. The caller clears via `clearPendingImages` only after a
|
|
192
|
-
* successful send. */
|
|
193
|
-
pendingImagesPayload() {
|
|
194
|
-
return this.pendingImages.length === 0 ? undefined : this.pendingImages.slice();
|
|
195
|
-
}
|
|
196
|
-
/** Drop all pending images + reset the aggregate byte counter (after a send). */
|
|
197
|
-
clearPendingImages() {
|
|
198
|
-
this.pendingImages = [];
|
|
199
|
-
this.pendingImageBytes = 0;
|
|
200
|
-
}
|
|
201
441
|
notify(message) {
|
|
202
442
|
this.hooks.onNotice?.(message);
|
|
203
443
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type Component, type KeybindingsManager, type TUI } from '@earendil-works/pi-tui';
|
|
2
|
+
import type { ClientToBroker, GetSettingsData, GetTreeData, ListModelsData, ListScopedModelsData, ListSessionsData } from '../../core/runtime/broker-protocol.js';
|
|
3
|
+
import type { ReadOpRequest } from './view-socket.js';
|
|
4
|
+
/** A built picker overlay: the `component` to show, and the `focus` target the
|
|
5
|
+
* caller must `setFocus` (the component itself, or the inner list that owns
|
|
6
|
+
* `handleInput`). */
|
|
7
|
+
export interface Picker {
|
|
8
|
+
component: Component;
|
|
9
|
+
focus: Component;
|
|
10
|
+
}
|
|
11
|
+
/** Controls handed to a picker builder by InputController.showPicker. `close`
|
|
12
|
+
* tears the whole picker down + restores editor focus; `replace` swaps the
|
|
13
|
+
* CURRENTLY-mounted component for a new one IN THE SAME inline slot (no centered
|
|
14
|
+
* overlay), focusing it — used by multi-step flows like /login, where selecting
|
|
15
|
+
* a provider replaces the selector with the login dialog right under the editor. */
|
|
16
|
+
export interface PickerControls {
|
|
17
|
+
close: () => void;
|
|
18
|
+
replace: (component: Component, focus: Component) => void;
|
|
19
|
+
}
|
|
20
|
+
/** Send a command frame to the broker (= InputController hooks.onCommand). */
|
|
21
|
+
type Send = (frame: ClientToBroker) => void;
|
|
22
|
+
/** Issue a correlated read-op (lazy loaders need it after construction). */
|
|
23
|
+
type Request = (frame: ReadOpRequest) => Promise<unknown>;
|
|
24
|
+
/** Tear the picker overlay down (owned by InputController). */
|
|
25
|
+
type Close = () => void;
|
|
26
|
+
/** Surface a one-line viewer notice. */
|
|
27
|
+
type Notify = (message: string) => void;
|
|
28
|
+
/** `/model` (and ctrl+l) — the real model selector with search + scope toggle.
|
|
29
|
+
* Select → `set_model` with the resolved `provider/id` (the broker's
|
|
30
|
+
* `findModelSpec` requires that form). */
|
|
31
|
+
export declare function buildModelPicker(tui: TUI, data: ListModelsData, send: Send, close: Close): Picker;
|
|
32
|
+
/** `/resume` — the real session selector. Reconstructs from async loaders that
|
|
33
|
+
* fetch each scope on demand (cwd is pre-fetched so the current-session marker
|
|
34
|
+
* shows immediately; the `all` scope loads only on toggle). Select →
|
|
35
|
+
* `switch_session`. Rename/delete are out of scope (no command frame). */
|
|
36
|
+
export declare function buildSessionPicker(tui: TUI, prefetchedCwd: ListSessionsData, request: Request, keybindings: KeybindingsManager, send: Send, close: Close): Picker;
|
|
37
|
+
/** `/tree` — the real tree navigator (pure data). Select → `navigate_tree`.
|
|
38
|
+
* Label-edit is out of scope (no command frame) → no `onLabelChange`. */
|
|
39
|
+
export declare function buildTreePicker(tui: TUI, data: GetTreeData, send: Send, close: Close): Picker;
|
|
40
|
+
/** `/fork` — the real prior-user-message selector (pure data, from
|
|
41
|
+
* `get_tree.forkPoints`). Select → `fork`. The outer component has no
|
|
42
|
+
* `handleInput`; the inner `getMessageList()` does (focus target). */
|
|
43
|
+
export declare function buildForkPicker(data: GetTreeData, send: Send, close: Close): Picker;
|
|
44
|
+
/** `/settings` — the real settings menu (the wire `settings` IS a SettingsConfig
|
|
45
|
+
* superset). Only the contract-enumerated mutations are wired: thinking level →
|
|
46
|
+
* `set_thinking_level`, auto-compact → `set_auto_compaction`. Every other toggle
|
|
47
|
+
* has no command frame (out of scope), so its callback notifies rather than
|
|
48
|
+
* silently lying that the change took. The theme submenu is viewer-local (CTO).
|
|
49
|
+
* The outer component has no `handleInput`; `getSettingsList()` is the focus
|
|
50
|
+
* target. */
|
|
51
|
+
export declare function buildSettingsPicker(data: GetSettingsData, send: Send, close: Close, notify: Notify): Picker;
|
|
52
|
+
/** `/scoped-models` — READ-ONLY via a pi-tui SelectList. CONTRACT GAP: pi (through
|
|
53
|
+
* 0.79.1) does not re-export `ScopedModelsSelectorComponent`, and the contract
|
|
54
|
+
* exposes no command frame for its enable/disable/persist mutations, so a
|
|
55
|
+
* faithful editable picker is not buildable in scope. This shows the registry
|
|
56
|
+
* with the enabled set marked; select notifies that toggling is unsupported. */
|
|
57
|
+
export declare function buildScopedModelsPicker(data: ListScopedModelsData, close: Close, notify: Notify): Picker;
|
|
58
|
+
export {};
|