@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
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// auth-pickers.ts — native /login and /logout overlays for `crtr attach`.
|
|
2
|
+
//
|
|
3
|
+
// Runs pi's REAL OAuth/API-key login flow locally in the viewer process.
|
|
4
|
+
// Since crtr attach is tmux-local only (viewer + broker = same machine, same
|
|
5
|
+
// filesystem), we can construct a local AuthStorage pointing at the SAME
|
|
6
|
+
// auth.json the broker uses — no socket round-trip needed for auth mutations.
|
|
7
|
+
//
|
|
8
|
+
// The OAuthSelectorComponent (provider picker) and LoginDialogComponent (the
|
|
9
|
+
// actual login flow UI) are pi's own exported components, used verbatim.
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { execFile } from 'node:child_process';
|
|
12
|
+
import { AuthStorage, ModelRegistry, OAuthSelectorComponent, LoginDialogComponent, } from '@earendil-works/pi-coding-agent';
|
|
13
|
+
/** Build an AuthStorage + ModelRegistry from the broker's agentDir (or pi's
|
|
14
|
+
* default). Both point at the SAME auth.json the broker uses — the viewer and
|
|
15
|
+
* broker share the filesystem (tmux-local invariant). */
|
|
16
|
+
function buildLocalAuth(agentDir) {
|
|
17
|
+
const authPath = join(agentDir, 'auth.json');
|
|
18
|
+
const modelsPath = join(agentDir, 'models.json');
|
|
19
|
+
const authStorage = AuthStorage.create(authPath);
|
|
20
|
+
const registry = ModelRegistry.create(authStorage, modelsPath);
|
|
21
|
+
return { authStorage, registry };
|
|
22
|
+
}
|
|
23
|
+
/** Assemble the AuthSelectorProvider list for the OAuth selector: OAuth providers
|
|
24
|
+
* from authStorage.getOAuthProviders() (authType 'oauth') + API-key providers
|
|
25
|
+
* derived from the registry's known providers that aren't OAuth (authType 'api_key').
|
|
26
|
+
* Mirrors how pi's interactive mode composes its login list. */
|
|
27
|
+
function buildProviderList(authStorage, registry) {
|
|
28
|
+
const oauthProviders = authStorage.getOAuthProviders();
|
|
29
|
+
const oauthIds = new Set(oauthProviders.map((p) => p.id));
|
|
30
|
+
const providers = oauthProviders.map((p) => ({
|
|
31
|
+
id: p.id,
|
|
32
|
+
name: p.name,
|
|
33
|
+
authType: 'oauth',
|
|
34
|
+
}));
|
|
35
|
+
// Add API-key providers from the registry that aren't OAuth providers.
|
|
36
|
+
const allModels = registry.getAll();
|
|
37
|
+
const seenProviders = new Set(oauthIds);
|
|
38
|
+
for (const m of allModels) {
|
|
39
|
+
if (!seenProviders.has(m.provider)) {
|
|
40
|
+
seenProviders.add(m.provider);
|
|
41
|
+
providers.push({
|
|
42
|
+
id: m.provider,
|
|
43
|
+
name: registry.getProviderDisplayName(m.provider),
|
|
44
|
+
authType: 'api_key',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return providers;
|
|
49
|
+
}
|
|
50
|
+
/** Open the browser with a URL (best-effort; platform-specific). */
|
|
51
|
+
function openBrowser(url) {
|
|
52
|
+
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
53
|
+
execFile(cmd, [url], { timeout: 5_000 }, () => {
|
|
54
|
+
/* best-effort: browser open is fire-and-forget */
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/** `/login` — show the provider selector, then run pi's native login flow in
|
|
58
|
+
* the viewer. On success, notifies the broker to reload auth via reload_auth.
|
|
59
|
+
* Returns a Picker (OAuthSelectorComponent as the overlay). */
|
|
60
|
+
export function buildLoginPicker(tui, agentDir, onAuthReloaded, notify, controls) {
|
|
61
|
+
const { close, replace } = controls;
|
|
62
|
+
const { authStorage, registry } = buildLocalAuth(agentDir);
|
|
63
|
+
const providers = buildProviderList(authStorage, registry);
|
|
64
|
+
const getAuthStatus = (id) => authStorage.getAuthStatus(id);
|
|
65
|
+
const onSelect = (providerId) => {
|
|
66
|
+
const provider = providers.find((p) => p.id === providerId);
|
|
67
|
+
if (provider === undefined) {
|
|
68
|
+
notify(`Unknown provider: ${providerId}`);
|
|
69
|
+
close();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// `finish()` is the single idempotent exit for the login dialog: tear the
|
|
73
|
+
// whole picker down (which restores editor focus), exactly once, no matter
|
|
74
|
+
// which path (success, failure, or an Esc-cancel that aborts mid-flow) gets
|
|
75
|
+
// there first. The OAuth/cancel paths resolve through DIFFERENT channels (the
|
|
76
|
+
// login() promise vs the dialog's onComplete), so the guard prevents a double
|
|
77
|
+
// close + double notify.
|
|
78
|
+
let settled = false;
|
|
79
|
+
const finish = () => {
|
|
80
|
+
if (settled)
|
|
81
|
+
return;
|
|
82
|
+
settled = true;
|
|
83
|
+
close();
|
|
84
|
+
};
|
|
85
|
+
const dialog = new LoginDialogComponent(tui, providerId, (success, message) => {
|
|
86
|
+
// The dialog's own completion (chiefly an Esc-cancel). On a real OAuth
|
|
87
|
+
// success the login() promise below already finished + notified, so the
|
|
88
|
+
// settled guard makes this a no-op then.
|
|
89
|
+
if (settled)
|
|
90
|
+
return;
|
|
91
|
+
if (success) {
|
|
92
|
+
notify(`Logged in to ${provider.name}`);
|
|
93
|
+
onAuthReloaded();
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
notify(message ? `Login failed: ${message}` : `Login to ${provider.name} was cancelled`);
|
|
97
|
+
}
|
|
98
|
+
finish();
|
|
99
|
+
}, provider.name);
|
|
100
|
+
// Swap the provider selector for the login dialog IN PLACE — it renders
|
|
101
|
+
// inline under the editor (same slot as the model picker), not as a centered
|
|
102
|
+
// overlay. `replace` focuses the dialog; `close()` (via finish) unmounts it.
|
|
103
|
+
replace(dialog, dialog);
|
|
104
|
+
if (provider.authType === 'oauth') {
|
|
105
|
+
const isKnownOAuth = authStorage.getOAuthProviders().some((p) => p.id === providerId);
|
|
106
|
+
if (!isKnownOAuth) {
|
|
107
|
+
// An extension-registered provider: its OAuth impl lives in the broker's
|
|
108
|
+
// registry, not the viewer's local one, so we cannot drive the flow here.
|
|
109
|
+
dialog.showInfo([
|
|
110
|
+
`Provider "${provider.name}" is extension-registered and cannot be logged in`,
|
|
111
|
+
`from the viewer (the extension runs in the broker, not here).`,
|
|
112
|
+
`Run /login directly in the node's terminal, or log in via the pi CLI.`,
|
|
113
|
+
]);
|
|
114
|
+
// The dialog stays up; an Esc routes through onComplete → finish().
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
authStorage
|
|
118
|
+
.login(providerId, {
|
|
119
|
+
onAuth: (info) => {
|
|
120
|
+
dialog.showAuth(info.url, info.instructions);
|
|
121
|
+
openBrowser(info.url);
|
|
122
|
+
},
|
|
123
|
+
onDeviceCode: (info) => {
|
|
124
|
+
dialog.showDeviceCode(info);
|
|
125
|
+
openBrowser(info.verificationUri);
|
|
126
|
+
},
|
|
127
|
+
onPrompt: (prompt) => dialog.showPrompt(prompt.message, prompt.placeholder),
|
|
128
|
+
onProgress: (message) => {
|
|
129
|
+
dialog.showProgress(message);
|
|
130
|
+
},
|
|
131
|
+
onManualCodeInput: () => dialog.showManualInput('Paste the code or URL:'),
|
|
132
|
+
onSelect: async (prompt) => {
|
|
133
|
+
const labels = prompt.options.map((o, i) => `${i + 1}. ${o.label}`).join('\n');
|
|
134
|
+
const answer = await dialog.showPrompt(`${prompt.message}\n${labels}`, '1');
|
|
135
|
+
const idx = parseInt(answer, 10) - 1;
|
|
136
|
+
return prompt.options[idx]?.id;
|
|
137
|
+
},
|
|
138
|
+
signal: dialog.signal,
|
|
139
|
+
})
|
|
140
|
+
.then(() => {
|
|
141
|
+
if (settled)
|
|
142
|
+
return; // a cancel/abort already closed it
|
|
143
|
+
notify(`Logged in to ${provider.name}`);
|
|
144
|
+
onAuthReloaded();
|
|
145
|
+
finish();
|
|
146
|
+
})
|
|
147
|
+
.catch((err) => {
|
|
148
|
+
// An Esc-cancel aborts the flow; the dialog's onComplete already
|
|
149
|
+
// notified + finished, so only report a genuine error here.
|
|
150
|
+
if (err?.name !== 'AbortError') {
|
|
151
|
+
notify(`Login error: ${String(err?.message ?? err)}`);
|
|
152
|
+
}
|
|
153
|
+
finish();
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
// API-key provider: prompt for the key and persist it locally.
|
|
158
|
+
dialog
|
|
159
|
+
.showPrompt(`Enter API key for ${provider.name}:`, 'sk-...')
|
|
160
|
+
.then((key) => {
|
|
161
|
+
if (settled)
|
|
162
|
+
return;
|
|
163
|
+
if (key.trim()) {
|
|
164
|
+
authStorage.set(providerId, { type: 'api_key', key: key.trim() });
|
|
165
|
+
notify(`API key saved for ${provider.name}`);
|
|
166
|
+
onAuthReloaded();
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
notify('No key entered — login cancelled');
|
|
170
|
+
}
|
|
171
|
+
finish();
|
|
172
|
+
})
|
|
173
|
+
.catch(() => finish());
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
const component = new OAuthSelectorComponent('login', authStorage, providers, onSelect, () => close(), getAuthStatus);
|
|
177
|
+
return { component, focus: component };
|
|
178
|
+
}
|
|
179
|
+
/** `/logout` — show the provider selector in logout mode. On select, calls
|
|
180
|
+
* authStorage.logout(providerId) directly, then notifies the broker to reload. */
|
|
181
|
+
export function buildLogoutPicker(agentDir, onAuthReloaded, notify, close) {
|
|
182
|
+
const { authStorage, registry } = buildLocalAuth(agentDir);
|
|
183
|
+
const providers = buildProviderList(authStorage, registry);
|
|
184
|
+
const getAuthStatus = (id) => authStorage.getAuthStatus(id);
|
|
185
|
+
const onSelect = (providerId) => {
|
|
186
|
+
const provider = providers.find((p) => p.id === providerId);
|
|
187
|
+
authStorage.logout(providerId);
|
|
188
|
+
close();
|
|
189
|
+
notify(`Logged out of ${provider?.name ?? providerId}`);
|
|
190
|
+
onAuthReloaded();
|
|
191
|
+
};
|
|
192
|
+
const component = new OAuthSelectorComponent('logout', authStorage, providers, onSelect, () => close(), getAuthStatus);
|
|
193
|
+
return { component, focus: component };
|
|
194
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AttachPalette } from './config-load.js';
|
|
2
|
+
export interface CanvasPanelLines {
|
|
3
|
+
/** Above-editor manager line(s) — empty for a root node (no manager). */
|
|
4
|
+
managers: string[];
|
|
5
|
+
/** Below-editor report row(s) + self's trailing ⚑ asks line — empty when none. */
|
|
6
|
+
reports: string[];
|
|
7
|
+
}
|
|
8
|
+
/** Build the BASE panel line stacks for `nodeId` from canvas.db (ported verbatim
|
|
9
|
+
* from canvas-nav.ts renderBase). `asks` is the per-node pending-ask map the
|
|
10
|
+
* caller polls; pass `{}` to omit the ⚑ badges. `palette` (the live theme)
|
|
11
|
+
* drives the headers + dim cells; omit it and the panels render uncolored. */
|
|
12
|
+
export declare function buildCanvasPanelLines(nodeId: string, asks: Record<string, number>, palette?: AttachPalette): CanvasPanelLines;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// canvas-panels.ts — the subscribed-node panel for `crtr attach` (Unit Q).
|
|
2
|
+
//
|
|
3
|
+
// Native reimplementation of canvas-nav.ts's BASE chrome (renderBase), reading
|
|
4
|
+
// the subscription graph DIRECTLY from canvas.db via the shared nav-model layer
|
|
5
|
+
// (no pi extension host, no broker frame). Produces the two line stacks the
|
|
6
|
+
// viewer paints into Containers around the editor:
|
|
7
|
+
// managers → ABOVE the editor: this node's manager (its first subscriber).
|
|
8
|
+
// reports → BELOW the editor: this node's live reports (active|idle
|
|
9
|
+
// subscriptions), plus a trailing ⚑ line for self's own asks.
|
|
10
|
+
//
|
|
11
|
+
// Pure: given a node id + the per-node ask map it returns ANSI strings; the
|
|
12
|
+
// caller owns the Containers and the refresh trigger. beginFrame() memoizes the
|
|
13
|
+
// canvas.db reads for this one build pass (mirrors canvas-nav's per-frame cache),
|
|
14
|
+
// so a whole panel rebuild is a single fan-out of disk/db reads.
|
|
15
|
+
import { beginFrame, cNode, managerOf, liveReports, climbRoot, computeSubtreeActivity, navLabel, coloredGlyph, truncate, tokensCell, cycleBadge, childBadge, liveBelowBadge, askBadge, activityCell, YELLOW, RESET, } from '../../core/canvas/nav-model.js';
|
|
16
|
+
/** Column widths for the aligned panel rows (visible columns). name is sized to
|
|
17
|
+
* the widest label across BOTH panels (capped); kind is wide enough for the
|
|
18
|
+
* longest persona kind (`orchestrator` = 12) so real kinds never truncate;
|
|
19
|
+
* tokens is fixed. Both panels share one grid. */
|
|
20
|
+
const KIND_W = 12;
|
|
21
|
+
const TOK_W = 6;
|
|
22
|
+
const NAME_CAP = 22;
|
|
23
|
+
/** Pad to `w`, or ellipsize with `…` when longer (plain text — callers pass
|
|
24
|
+
* un-styled labels). */
|
|
25
|
+
function nameCell(s, w) {
|
|
26
|
+
return s.length > w ? `${s.slice(0, w - 1)}…` : s + ' '.repeat(w - s.length);
|
|
27
|
+
}
|
|
28
|
+
function padStartVisible(s, w) {
|
|
29
|
+
return s.length >= w ? s.slice(0, w) : ' '.repeat(w - s.length) + s;
|
|
30
|
+
}
|
|
31
|
+
/** Build the BASE panel line stacks for `nodeId` from canvas.db (ported verbatim
|
|
32
|
+
* from canvas-nav.ts renderBase). `asks` is the per-node pending-ask map the
|
|
33
|
+
* caller polls; pass `{}` to omit the ⚑ badges. `palette` (the live theme)
|
|
34
|
+
* drives the headers + dim cells; omit it and the panels render uncolored. */
|
|
35
|
+
export function buildCanvasPanelLines(nodeId, asks, palette) {
|
|
36
|
+
// Fresh snapshot: drop the previous build's memoized reads so this pass sees
|
|
37
|
+
// current disk+db state, then read-once within it.
|
|
38
|
+
beginFrame();
|
|
39
|
+
// Styling: theme-driven when a palette is passed, plain otherwise (so the
|
|
40
|
+
// panels still build for a non-attach caller / a test).
|
|
41
|
+
const muted = palette?.muted ?? ((s) => s);
|
|
42
|
+
const faint = palette?.faint ?? ((s) => s);
|
|
43
|
+
// One subtree-activity pass (rooted at the ancestry root) feeds the ⇣N
|
|
44
|
+
// live-work-below badge on both the manager line and every report row.
|
|
45
|
+
const activity = computeSubtreeActivity(climbRoot(nodeId), nodeId);
|
|
46
|
+
// One shared name-column width across BOTH panels so the grids line up.
|
|
47
|
+
const mgr = managerOf(nodeId);
|
|
48
|
+
const live = liveReports(nodeId);
|
|
49
|
+
const gridIds = [...(mgr !== undefined ? [mgr] : []), ...live];
|
|
50
|
+
const nameW = gridIds.length > 0
|
|
51
|
+
? Math.min(NAME_CAP, Math.max(...gridIds.map((id) => navLabel(cNode(id), id).length)))
|
|
52
|
+
: NAME_CAP;
|
|
53
|
+
// A node row laid out on the shared grid: glyph · name · kind · tokens · badges.
|
|
54
|
+
const row = (id) => {
|
|
55
|
+
const n = cNode(id);
|
|
56
|
+
const name = nameCell(navLabel(n, id), nameW);
|
|
57
|
+
const kind = faint(nameCell(n?.kind ?? '', KIND_W));
|
|
58
|
+
const tokens = muted(padStartVisible(tokensCell(id), TOK_W));
|
|
59
|
+
const badges = `${cycleBadge(n)}${childBadge(n)}${liveBelowBadge(n, activity.activeBelow)}${askBadge(id, asks)}${activityCell(id, n)}`;
|
|
60
|
+
return truncate(` ${coloredGlyph(n)} ${name} ${kind} ${tokens}${badges}`);
|
|
61
|
+
};
|
|
62
|
+
// --- managers panel (↑) ---
|
|
63
|
+
const managers = [];
|
|
64
|
+
if (mgr !== undefined) {
|
|
65
|
+
managers.push(row(mgr));
|
|
66
|
+
}
|
|
67
|
+
// --- reports panel (↓) ---
|
|
68
|
+
const reports = [];
|
|
69
|
+
if (live.length > 0) {
|
|
70
|
+
for (const id of live)
|
|
71
|
+
reports.push(row(id));
|
|
72
|
+
}
|
|
73
|
+
// Self's own pending asks (no self row in BASE) → a trailing inline line.
|
|
74
|
+
const selfAsks = asks[nodeId] ?? 0;
|
|
75
|
+
if (selfAsks > 0)
|
|
76
|
+
reports.push(` ${YELLOW}⚑ ${selfAsks} pending${RESET}`);
|
|
77
|
+
return { managers, reports };
|
|
78
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Container, type TUI } from '@earendil-works/pi-tui';
|
|
2
2
|
import { type AgentSessionEvent } from '@earendil-works/pi-coding-agent';
|
|
3
3
|
import type { BrokerSnapshot } from '../../core/runtime/broker-protocol.js';
|
|
4
|
+
import type { AttachPalette } from './config-load.js';
|
|
4
5
|
export interface ChatViewOptions {
|
|
5
6
|
/** Working dir used by `ToolExecutionComponent` for path/diff display. Defaults
|
|
6
7
|
* to the viewer's cwd; T7 may pass the node's working dir for nicer paths. */
|
|
@@ -19,6 +20,9 @@ export interface ChatViewOptions {
|
|
|
19
20
|
* (queue_update, session_info_changed, thinking_level_changed). The attach
|
|
20
21
|
* command (T7) wires a footer here; absent → these events are no-ops. */
|
|
21
22
|
onFooterEvent?: (event: AgentSessionEvent) => void;
|
|
23
|
+
/** Theme palette for the activity spinner / dim status / error lines. Absent →
|
|
24
|
+
* the module defaults (faint + ANSI red) are used. */
|
|
25
|
+
palette?: AttachPalette;
|
|
22
26
|
}
|
|
23
27
|
export declare class ChatView {
|
|
24
28
|
private readonly tui;
|
|
@@ -31,10 +35,16 @@ export declare class ChatView {
|
|
|
31
35
|
private readonly cwd;
|
|
32
36
|
private readonly showImages;
|
|
33
37
|
private readonly imageWidthCells;
|
|
34
|
-
private
|
|
38
|
+
private hideThinking;
|
|
35
39
|
private readonly hiddenThinkingLabel;
|
|
36
|
-
private
|
|
40
|
+
private toolOutputExpanded;
|
|
37
41
|
private readonly onFooterEvent;
|
|
42
|
+
/** Theme-derived colorizers for the activity/status area. */
|
|
43
|
+
private readonly spinnerStyle;
|
|
44
|
+
private readonly dimStyle;
|
|
45
|
+
private readonly errorStyle;
|
|
46
|
+
/** Bold accent for the expanded crtr-context label. */
|
|
47
|
+
private readonly labelStyle;
|
|
38
48
|
/** The assistant message component currently being streamed (between
|
|
39
49
|
* message_start and message_end for an assistant turn). */
|
|
40
50
|
private streamingComponent;
|
|
@@ -44,6 +54,10 @@ export declare class ChatView {
|
|
|
44
54
|
private activityLoader;
|
|
45
55
|
/** While true, `append` skips re-pinning the status container (bulk rebuild). */
|
|
46
56
|
private bulkMode;
|
|
57
|
+
/** Text of the most recent assistant message, kept current from the welcome
|
|
58
|
+
* snapshot AND the live event stream so `/copy` (getLastAssistantText) works
|
|
59
|
+
* whether the message arrived before or after attach. */
|
|
60
|
+
private lastAssistantText;
|
|
47
61
|
constructor(tui: TUI, container: Container, opts?: ChatViewOptions);
|
|
48
62
|
/** Render the full message history from a `welcome` snapshot. Ports pi's
|
|
49
63
|
* `renderSessionContext` (interactive-mode.js:2553): assistant messages spawn
|
|
@@ -55,6 +69,14 @@ export declare class ChatView {
|
|
|
55
69
|
* timers die with the process, but if T7 reuses a ChatView across reconnects in
|
|
56
70
|
* one process it should call this on detach to avoid a leaked interval. */
|
|
57
71
|
dispose(): void;
|
|
72
|
+
/** Ctrl+O / app.tools.expand — flip global tool-output expansion and apply it to
|
|
73
|
+
* every rendered component that supports it. Returns the new expanded state so
|
|
74
|
+
* the caller can surface a notice. */
|
|
75
|
+
toggleToolsExpanded(): boolean;
|
|
76
|
+
/** Ctrl+T / app.thinking.toggle — flip thinking-block visibility and apply it to
|
|
77
|
+
* every assistant message (the in-flight streaming bubble is itself a child of
|
|
78
|
+
* the container, so the loop covers it too). Returns the new HIDDEN state. */
|
|
79
|
+
toggleThinking(): boolean;
|
|
58
80
|
handleEvent(event: AgentSessionEvent): void;
|
|
59
81
|
private addMessageToChat;
|
|
60
82
|
/** Default-path tool component (toolDef undefined → builtin/default rendering). */
|
|
@@ -72,6 +94,9 @@ export declare class ChatView {
|
|
|
72
94
|
private showError;
|
|
73
95
|
/** Extract the tool-call content items from an assistant message. */
|
|
74
96
|
private assistantToolCalls;
|
|
97
|
+
/** The text of the most recent assistant message, for `/copy`. Undefined when
|
|
98
|
+
* no assistant message has been seen (or it had no text content). */
|
|
99
|
+
getLastAssistantText(): string | undefined;
|
|
75
100
|
/** Concatenate the text blocks of a user message (string or content array). */
|
|
76
101
|
private userMessageText;
|
|
77
102
|
}
|
|
@@ -22,12 +22,31 @@
|
|
|
22
22
|
// extensions degrade to the default, which is the decided Phase-4 behavior.
|
|
23
23
|
import { Container, Loader, Spacer, Text } from '@earendil-works/pi-tui';
|
|
24
24
|
import { AssistantMessageComponent, BashExecutionComponent, BranchSummaryMessageComponent, CompactionSummaryMessageComponent, CustomMessageComponent, getMarkdownTheme, parseSkillBlock, SkillInvocationMessageComponent, ToolExecutionComponent, UserMessageComponent, } from '@earendil-works/pi-coding-agent';
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
const
|
|
30
|
-
|
|
25
|
+
import { ContextMessageComponent } from './context-message.js';
|
|
26
|
+
/** customType stamped on the injected `<crtr-context>` bearings block (mirrors
|
|
27
|
+
* CONTEXT_INTRO_CUSTOM_TYPE in canvas-context-intro.ts — kept as a local literal
|
|
28
|
+
* so the viewer never imports the in-process extension). */
|
|
29
|
+
const CONTEXT_INTRO_CUSTOM_TYPE = 'crtr-context';
|
|
30
|
+
/** Theme-derived chrome colors (spinner / dim / error) are supplied by the
|
|
31
|
+
* attach command via `ChatViewOptions.palette` (built from the live theme in
|
|
32
|
+
* config-load.ts). pi does not re-export the raw `theme` instance, so when no
|
|
33
|
+
* palette is passed (ChatView used standalone) these defaults stand in: a faint
|
|
34
|
+
* dim and ANSI red, matching the palette's own fallbacks. The message components
|
|
35
|
+
* themselves still use `getMarkdownTheme()` for full-fidelity rendering. */
|
|
36
|
+
const defaultSpinnerStyle = (s) => `\x1b[2m${s}\x1b[22m`;
|
|
37
|
+
const defaultDimStyle = (s) => `\x1b[2m${s}\x1b[22m`;
|
|
38
|
+
/** A component supports the Ctrl+O global tool-output toggle. Mirrors pi's
|
|
39
|
+
* `isExpandable` duck-type (interactive-mode.js): any rendered child carrying a
|
|
40
|
+
* `setExpanded(boolean)` method — tool executions, custom/compaction/branch/skill
|
|
41
|
+
* message components — participates; everything else (the status container, plain
|
|
42
|
+
* text, assistant/user bubbles) is skipped. */
|
|
43
|
+
function isExpandable(obj) {
|
|
44
|
+
return (typeof obj === 'object' &&
|
|
45
|
+
obj !== null &&
|
|
46
|
+
'setExpanded' in obj &&
|
|
47
|
+
typeof obj.setExpanded === 'function');
|
|
48
|
+
}
|
|
49
|
+
const defaultErrorStyle = (s) => `\x1b[31m${s}\x1b[39m`;
|
|
31
50
|
export class ChatView {
|
|
32
51
|
tui;
|
|
33
52
|
/** The chat scroll area handed in by the attach command. */
|
|
@@ -43,6 +62,12 @@ export class ChatView {
|
|
|
43
62
|
hiddenThinkingLabel;
|
|
44
63
|
toolOutputExpanded;
|
|
45
64
|
onFooterEvent;
|
|
65
|
+
/** Theme-derived colorizers for the activity/status area. */
|
|
66
|
+
spinnerStyle;
|
|
67
|
+
dimStyle;
|
|
68
|
+
errorStyle;
|
|
69
|
+
/** Bold accent for the expanded crtr-context label. */
|
|
70
|
+
labelStyle = (s) => `\x1b[1m${s}\x1b[22m`;
|
|
46
71
|
/** The assistant message component currently being streamed (between
|
|
47
72
|
* message_start and message_end for an assistant turn). */
|
|
48
73
|
streamingComponent;
|
|
@@ -52,6 +77,10 @@ export class ChatView {
|
|
|
52
77
|
activityLoader;
|
|
53
78
|
/** While true, `append` skips re-pinning the status container (bulk rebuild). */
|
|
54
79
|
bulkMode = false;
|
|
80
|
+
/** Text of the most recent assistant message, kept current from the welcome
|
|
81
|
+
* snapshot AND the live event stream so `/copy` (getLastAssistantText) works
|
|
82
|
+
* whether the message arrived before or after attach. */
|
|
83
|
+
lastAssistantText;
|
|
55
84
|
constructor(tui, container, opts = {}) {
|
|
56
85
|
this.tui = tui;
|
|
57
86
|
this.container = container;
|
|
@@ -62,6 +91,9 @@ export class ChatView {
|
|
|
62
91
|
this.hiddenThinkingLabel = opts.hiddenThinkingLabel ?? 'Thinking...';
|
|
63
92
|
this.toolOutputExpanded = opts.toolOutputExpanded ?? false;
|
|
64
93
|
this.onFooterEvent = opts.onFooterEvent;
|
|
94
|
+
this.spinnerStyle = opts.palette?.active ?? defaultSpinnerStyle;
|
|
95
|
+
this.dimStyle = opts.palette?.muted ?? defaultDimStyle;
|
|
96
|
+
this.errorStyle = opts.palette?.error ?? defaultErrorStyle;
|
|
65
97
|
this.container.addChild(this.statusContainer);
|
|
66
98
|
}
|
|
67
99
|
// -------------------------------------------------------------------------
|
|
@@ -86,6 +118,11 @@ export class ChatView {
|
|
|
86
118
|
for (let i = 0; i < messages.length; i++) {
|
|
87
119
|
const message = messages[i];
|
|
88
120
|
if (message.role === 'assistant') {
|
|
121
|
+
// Keep the `/copy` source current from the catch-up transcript too, not
|
|
122
|
+
// only the live stream (you usually attach AFTER the last reply landed).
|
|
123
|
+
const text = this.userMessageText(message);
|
|
124
|
+
if (text.trim())
|
|
125
|
+
this.lastAssistantText = text;
|
|
89
126
|
// If the snapshot caught an IN-FLIGHT assistant turn (state.isStreaming
|
|
90
127
|
// and this is the trailing, non-terminal assistant message), bind it as
|
|
91
128
|
// the live streaming component so the relayed message_update/message_end
|
|
@@ -148,6 +185,35 @@ export class ChatView {
|
|
|
148
185
|
this.setActivity(undefined);
|
|
149
186
|
}
|
|
150
187
|
// -------------------------------------------------------------------------
|
|
188
|
+
// Global display toggles (Ctrl+O / Ctrl+T), driven by the input controller.
|
|
189
|
+
// Pure client-side render state — no broker round-trip — ported from pi's
|
|
190
|
+
// `setToolsExpanded` / `toggleThinkingBlockVisibility`.
|
|
191
|
+
// -------------------------------------------------------------------------
|
|
192
|
+
/** Ctrl+O / app.tools.expand — flip global tool-output expansion and apply it to
|
|
193
|
+
* every rendered component that supports it. Returns the new expanded state so
|
|
194
|
+
* the caller can surface a notice. */
|
|
195
|
+
toggleToolsExpanded() {
|
|
196
|
+
this.toolOutputExpanded = !this.toolOutputExpanded;
|
|
197
|
+
for (const child of this.container.children) {
|
|
198
|
+
if (isExpandable(child))
|
|
199
|
+
child.setExpanded(this.toolOutputExpanded);
|
|
200
|
+
}
|
|
201
|
+
this.tui.requestRender();
|
|
202
|
+
return this.toolOutputExpanded;
|
|
203
|
+
}
|
|
204
|
+
/** Ctrl+T / app.thinking.toggle — flip thinking-block visibility and apply it to
|
|
205
|
+
* every assistant message (the in-flight streaming bubble is itself a child of
|
|
206
|
+
* the container, so the loop covers it too). Returns the new HIDDEN state. */
|
|
207
|
+
toggleThinking() {
|
|
208
|
+
this.hideThinking = !this.hideThinking;
|
|
209
|
+
for (const child of this.container.children) {
|
|
210
|
+
if (child instanceof AssistantMessageComponent)
|
|
211
|
+
child.setHideThinkingBlock(this.hideThinking);
|
|
212
|
+
}
|
|
213
|
+
this.tui.requestRender();
|
|
214
|
+
return this.hideThinking;
|
|
215
|
+
}
|
|
216
|
+
// -------------------------------------------------------------------------
|
|
151
217
|
// Live stream: the 15-of-17-case handleEvent port (turn_start/turn_end no-op,
|
|
152
218
|
// exactly as interactive pi falls through default for those two).
|
|
153
219
|
// -------------------------------------------------------------------------
|
|
@@ -199,6 +265,13 @@ export class ChatView {
|
|
|
199
265
|
case 'message_end': {
|
|
200
266
|
if (event.message.role !== 'assistant')
|
|
201
267
|
break;
|
|
268
|
+
// Stash the completed reply's text for `/copy` (skip aborted/error turns,
|
|
269
|
+
// whose text is partial or absent).
|
|
270
|
+
if (event.message.stopReason !== 'aborted' && event.message.stopReason !== 'error') {
|
|
271
|
+
const text = this.userMessageText(event.message);
|
|
272
|
+
if (text.trim())
|
|
273
|
+
this.lastAssistantText = text;
|
|
274
|
+
}
|
|
202
275
|
if (this.streamingComponent) {
|
|
203
276
|
const stopReason = event.message.stopReason;
|
|
204
277
|
let errorMessage = event.message.errorMessage;
|
|
@@ -328,6 +401,15 @@ export class ChatView {
|
|
|
328
401
|
}
|
|
329
402
|
case 'custom': {
|
|
330
403
|
if (message.display) {
|
|
404
|
+
// The <crtr-context> bearings block folds to a one-liner under Ctrl+O
|
|
405
|
+
// (the broker runs its renderer extension, so reimplement it natively).
|
|
406
|
+
if (message.customType === CONTEXT_INTRO_CUSTOM_TYPE) {
|
|
407
|
+
this.append(new ContextMessageComponent(this.userMessageText(message), this.toolOutputExpanded, this.dimStyle,
|
|
408
|
+
// Bold + active accent, so the [crtr context] label reads as a
|
|
409
|
+
// distinct color against the dim rest of the line.
|
|
410
|
+
(s) => this.spinnerStyle(this.labelStyle(s))));
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
331
413
|
// No extension renderer in the viewer → default custom-message render.
|
|
332
414
|
const component = new CustomMessageComponent(message, undefined, getMarkdownTheme());
|
|
333
415
|
component.setExpanded(this.toolOutputExpanded);
|
|
@@ -406,11 +488,12 @@ export class ChatView {
|
|
|
406
488
|
this.setActivity(undefined);
|
|
407
489
|
this.streamingComponent = undefined;
|
|
408
490
|
this.pendingTools.clear();
|
|
491
|
+
this.lastAssistantText = undefined;
|
|
409
492
|
this.container.clear();
|
|
410
493
|
this.container.addChild(this.statusContainer);
|
|
411
494
|
}
|
|
412
495
|
makeLoader(message) {
|
|
413
|
-
return new Loader(this.tui, spinnerStyle, dimStyle, message);
|
|
496
|
+
return new Loader(this.tui, this.spinnerStyle, this.dimStyle, message);
|
|
414
497
|
}
|
|
415
498
|
/** Swap the single active activity indicator (working/compaction/retry). */
|
|
416
499
|
setActivity(loader) {
|
|
@@ -422,11 +505,11 @@ export class ChatView {
|
|
|
422
505
|
}
|
|
423
506
|
showStatus(message) {
|
|
424
507
|
this.append(new Spacer(1));
|
|
425
|
-
this.append(new Text(dimStyle(message), 1, 0));
|
|
508
|
+
this.append(new Text(this.dimStyle(message), 1, 0));
|
|
426
509
|
}
|
|
427
510
|
showError(message) {
|
|
428
511
|
this.append(new Spacer(1));
|
|
429
|
-
this.append(new Text(
|
|
512
|
+
this.append(new Text(this.errorStyle(message), 1, 0));
|
|
430
513
|
}
|
|
431
514
|
/** Extract the tool-call content items from an assistant message. */
|
|
432
515
|
assistantToolCalls(message) {
|
|
@@ -435,6 +518,12 @@ export class ChatView {
|
|
|
435
518
|
return [];
|
|
436
519
|
return content.filter((c) => typeof c === 'object' && c !== null && c.type === 'toolCall');
|
|
437
520
|
}
|
|
521
|
+
/** The text of the most recent assistant message, for `/copy`. Undefined when
|
|
522
|
+
* no assistant message has been seen (or it had no text content). */
|
|
523
|
+
getLastAssistantText() {
|
|
524
|
+
const t = this.lastAssistantText?.trim();
|
|
525
|
+
return t ? t : undefined;
|
|
526
|
+
}
|
|
438
527
|
/** Concatenate the text blocks of a user message (string or content array). */
|
|
439
528
|
userMessageText(message) {
|
|
440
529
|
const content = message.content;
|
|
@@ -1,16 +1,55 @@
|
|
|
1
1
|
import type { ImageContent } from '@earendil-works/pi-ai';
|
|
2
|
-
/** A clipboard image ready to attach, plus an optional
|
|
3
|
-
*
|
|
2
|
+
/** A clipboard image ready to attach, plus an optional note for the controller
|
|
3
|
+
* to surface — either a resize note (from pi's `formatDimensionNote`) or, when
|
|
4
|
+
* no image was attached, the reason (dropped over the cap, or no clipboard
|
|
5
|
+
* tool / read failure). */
|
|
4
6
|
export interface ClipboardImageResult {
|
|
5
7
|
/** Ready to push into a `prompt`/`steer`/`follow_up` frame's `images?`. ABSENT
|
|
6
|
-
* when the image was read but DROPPED (a fallback exceeded MAX_BYTES base64)
|
|
7
|
-
*
|
|
8
|
+
* when the image was read but DROPPED (a fallback exceeded MAX_BYTES base64),
|
|
9
|
+
* or when there was no image to read but a precise reason is worth surfacing;
|
|
10
|
+
* `note` then carries that reason and the caller attaches nothing. */
|
|
8
11
|
image?: ImageContent;
|
|
9
12
|
/** Either a resize note ("Resized from 4032×3024 to 1568×1176", present only
|
|
10
|
-
* when resized) or, when `image` is absent, the reason
|
|
13
|
+
* when resized) or, when `image` is absent, the reason no image was attached. */
|
|
11
14
|
note?: string;
|
|
12
15
|
}
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
+
/** Persist a (already-resized) clipboard image to a stable temp file and return
|
|
17
|
+
* its absolute path, so the attach editor can drop the PATH inline into the
|
|
18
|
+
* prompt instead of inlining base64 the agent can't see. The broker runs on the
|
|
19
|
+
* same host, so a tmpdir path is readable by the engine. */
|
|
20
|
+
export declare function writeClipboardImageToFile(image: ImageContent): string;
|
|
21
|
+
/** Read + resize the current clipboard image. Returns `null` ONLY when the
|
|
22
|
+
* clipboard genuinely holds no image; a `note`-only result means there is a
|
|
23
|
+
* precise reason to surface (no clipboard tool, read failure, or an over-cap
|
|
24
|
+
* drop). All best-effort — the caller shows a brief notice and carries on. */
|
|
16
25
|
export declare function readClipboardImage(): Promise<ClipboardImageResult | null>;
|
|
26
|
+
interface RawImage {
|
|
27
|
+
bytes: Buffer;
|
|
28
|
+
mimeType: string;
|
|
29
|
+
}
|
|
30
|
+
/** Outcome of the raw clipboard read, distinguishing the three cases the caller
|
|
31
|
+
* must tell apart: `image` present → got bytes; `note` present → no image AND a
|
|
32
|
+
* precise reason to surface (no clipboard tool / read failure); both absent →
|
|
33
|
+
* the clipboard genuinely holds no image. */
|
|
34
|
+
export interface RawReadOutcome {
|
|
35
|
+
image?: RawImage;
|
|
36
|
+
note?: string;
|
|
37
|
+
}
|
|
38
|
+
/** Result of probing one clipboard tool, distinguishing a MISSING binary (so the
|
|
39
|
+
* caller can say "no clipboard tool") from a tool that ran but produced no image
|
|
40
|
+
* (`bytes` absent, `missing` false → genuinely empty). */
|
|
41
|
+
export interface RunResult {
|
|
42
|
+
/** Tool stdout when it produced non-empty output. */
|
|
43
|
+
bytes?: Buffer;
|
|
44
|
+
/** The binary was not found on PATH (ENOENT). */
|
|
45
|
+
missing?: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** Pure macOS dispatch (the regression-test seam): try `pngpaste` if present,
|
|
48
|
+
* else fall through to the native osascript read — a missing `pngpaste` must
|
|
49
|
+
* NOT collapse to "no image", which was the stock-Mac bug. */
|
|
50
|
+
export declare function selectMacOutcome(runTool: (command: string, args: string[]) => RunResult, readNative: () => RawReadOutcome): RawReadOutcome;
|
|
51
|
+
/** Pure Linux dispatch (the regression-test seam): Wayland (`wl-paste`) first,
|
|
52
|
+
* then X11 (`xclip`), PNG then JPEG. If NO tool binary was found, surface a
|
|
53
|
+
* precise "install …" note; if a tool ran but found no image, report empty. */
|
|
54
|
+
export declare function selectLinuxOutcome(runTool: (command: string, args: string[]) => RunResult, wayland: boolean): RawReadOutcome;
|
|
55
|
+
export {};
|