@crouton-kit/crouter 0.3.28 → 0.3.30
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.d.ts +15 -7
- package/dist/build-root.js +48 -34
- 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 +5 -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 +22 -17
- 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__/slash-quit-copy.test.js +42 -0
- package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
- package/dist/clients/attach/attach-cmd.js +282 -46
- 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 +4 -2
- package/dist/clients/attach/canvas-panels.js +45 -17
- 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 +13 -4
- package/dist/clients/attach/graph-overlay.js +59 -19
- package/dist/clients/attach/input-controller.d.ts +75 -13
- package/dist/clients/attach/input-controller.js +305 -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 +22 -0
- package/dist/clients/attach/slash-commands.js +91 -25
- 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 +18 -1
- package/dist/clients/attach/view-socket.js +70 -1
- 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 +4 -25
- package/dist/commands/dashboard.js +5 -1
- package/dist/commands/memory/__tests__/lint-schema.test.js +6 -6
- package/dist/commands/memory/lint.js +14 -2
- 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 +8 -4
- 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/sync.d.ts +1 -0
- package/dist/commands/sys/sync.js +187 -0
- package/dist/commands/sys.js +3 -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__/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 +17 -1
- package/dist/core/__tests__/fixtures/fake-engine.js +22 -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__/lifecycle.test.js +1 -19
- package/dist/core/__tests__/listing-completeness.test.js +2 -2
- 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.js +2 -2
- 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.js +8 -7
- 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.d.ts +4 -1
- package/dist/core/runtime/broker.js +494 -58
- package/dist/core/runtime/close.d.ts +12 -2
- package/dist/core/runtime/close.js +37 -16
- package/dist/core/runtime/front-door.js +6 -14
- 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 -335
- package/dist/core/runtime/placement.js +291 -858
- 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 +40 -139
- package/dist/core/runtime/spawn.d.ts +15 -16
- package/dist/core/runtime/spawn.js +165 -194
- 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 -52
- package/dist/core/runtime/tmux.js +35 -99
- 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/substrate/index.d.ts +1 -1
- package/dist/core/substrate/index.js +1 -1
- package/dist/core/substrate/injected-store.js +3 -3
- 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 +10 -3
- package/dist/core/substrate/schema.js +6 -3
- 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 +16 -16
- package/dist/pi-extensions/canvas-nav.js +5 -0
- 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 +14 -12
- package/dist/pi-extensions/canvas-stophook.js +87 -101
- 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 -297
- 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__/slash-quit-copy.test.d.ts} +0 -0
- /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/titled-editor.test.d.ts} +0 -0
|
@@ -16,21 +16,25 @@
|
|
|
16
16
|
// is therefore a BRANCH wrapping a single leaf: `crtr attach to <node>`.
|
|
17
17
|
import { randomUUID } from 'node:crypto';
|
|
18
18
|
import { execFile } from 'node:child_process';
|
|
19
|
-
import { CombinedAutocompleteProvider, Container, ProcessTerminal, Text, TUI, matchesKey, } from '@earendil-works/pi-tui';
|
|
20
|
-
import {
|
|
19
|
+
import { CombinedAutocompleteProvider, Container, ProcessTerminal, Text, TUI, matchesKey, truncateToWidth, } from '@earendil-works/pi-tui';
|
|
20
|
+
import { DynamicBorder, getSelectListTheme, } from '@earendil-works/pi-coding-agent';
|
|
21
21
|
import { defineBranch, defineLeaf } from '../../core/command.js';
|
|
22
22
|
import { InputError } from '../../core/io.js';
|
|
23
23
|
import { getNode, getRow } from '../../core/canvas/index.js';
|
|
24
24
|
// tmux driver verbs only through placement (the §5.1 model-over-driver seam) —
|
|
25
25
|
// never `core/runtime/tmux.js` directly.
|
|
26
|
-
import { setPaneOption, currentTmux } from '../../core/runtime/placement.js';
|
|
27
|
-
import { climbRoot } from '../../core/canvas/nav-model.js';
|
|
26
|
+
import { setPaneOption, currentTmux, inTmux, installMenuBinding } from '../../core/runtime/placement.js';
|
|
27
|
+
import { climbRoot, visibleWidth } from '../../core/canvas/nav-model.js';
|
|
28
28
|
import { ChatView } from './chat-view.js';
|
|
29
29
|
import { InputController } from './input-controller.js';
|
|
30
|
+
import { writeClipboardText } from './clipboard-text.js';
|
|
30
31
|
import { buildCanvasPanelLines } from './canvas-panels.js';
|
|
31
32
|
import { GraphOverlay } from './graph-overlay.js';
|
|
32
33
|
import { slashCommandList } from './slash-commands.js';
|
|
33
|
-
import { applyTheme, createKeybindingsManager } from './config-load.js';
|
|
34
|
+
import { applyTheme, attachPalette, createKeybindingsManager, defaultAgentDir, mirrorKeybindingsToEditor, mirrorKittyProtocolToEditor, } from './config-load.js';
|
|
35
|
+
import { buildLoginPicker, buildLogoutPicker } from './auth-pickers.js';
|
|
36
|
+
import { TitledEditor, thinkingBorderColor, thinkingTitleStyle, defaultTitleStyle } from './titled-editor.js';
|
|
37
|
+
import { fetchGitInfo } from './git-info.js';
|
|
34
38
|
import { BrokerUnavailableError, ViewSocketClient, reconnectShouldGiveUp } from './view-socket.js';
|
|
35
39
|
/** Async per-node ask-map fetch (NON-blocking — the viewer must never block its
|
|
36
40
|
* input pump on a shell-out, unlike canvas-nav's execFileSync). Buckets a whole
|
|
@@ -122,22 +126,47 @@ async function runAttach(nodeId, observer) {
|
|
|
122
126
|
// 1. Theme + keybindings FIRST — pi's components throw "Theme not initialized"
|
|
123
127
|
// otherwise (T5 contract). One KeybindingsManager feeds BOTH editor + input.
|
|
124
128
|
applyTheme({ cwd: meta.cwd });
|
|
129
|
+
const pal = attachPalette();
|
|
125
130
|
const km = createKeybindingsManager();
|
|
131
|
+
// pi-coding-agent's CustomEditor may resolve a SEPARATE pi-tui instance whose
|
|
132
|
+
// global keybindings we'd otherwise never set — mirror the manager onto it so
|
|
133
|
+
// the editor honors the user's bindings (e.g. alt+enter → newline). No-op on a
|
|
134
|
+
// deduped install. Awaited before tui.start so the editor never handles a key
|
|
135
|
+
// against stale defaults.
|
|
136
|
+
await mirrorKeybindingsToEditor(km);
|
|
137
|
+
// The front door installs the Alt+C menu binding before it ever loads a theme,
|
|
138
|
+
// so it lands unstyled. Now that this viewer process HAS themed (applyTheme
|
|
139
|
+
// above populated the live-theme global), re-install it so the menu picks up
|
|
140
|
+
// the distinct-surface background. Idempotent re-bind; tmux-only, best-effort.
|
|
141
|
+
if (inTmux()) {
|
|
142
|
+
try {
|
|
143
|
+
installMenuBinding();
|
|
144
|
+
}
|
|
145
|
+
catch { /* best-effort */ }
|
|
146
|
+
}
|
|
126
147
|
// 2. TUI + layout containers + editor. Top-to-bottom render order (set at
|
|
127
|
-
// step 8): chat · badge · managers (↑ subscriber) · editor
|
|
128
|
-
// (↓ subscriptions) ·
|
|
148
|
+
// step 8): chat · rule · badge · managers (↑ subscriber) · editor (themed
|
|
149
|
+
// border) · reports (↓ subscriptions) · status bar. A themed DynamicBorder
|
|
150
|
+
// rule divides the scrolling chat from the fixed chrome below it. The badge
|
|
151
|
+
// sits in the BOTTOM-anchored chrome
|
|
129
152
|
// stack (directly above managers), NOT as the topmost child: pi-tui anchors
|
|
130
153
|
// the viewport to the cursor at the bottom, so a topmost badge scrolls off
|
|
131
154
|
// into scrollback the instant the chat exceeds one screen. The badge + the
|
|
132
155
|
// two canvas panels reproduce the pi-extension chrome natively (Unit Q).
|
|
133
|
-
const
|
|
134
|
-
const
|
|
156
|
+
const procTerminal = new ProcessTerminal();
|
|
157
|
+
const tui = new TUI(procTerminal);
|
|
135
158
|
const chatContainer = new Container();
|
|
159
|
+
const chrome = new Container(); // a themed rule dividing scrolling chat from the fixed chrome below
|
|
136
160
|
const managers = new Container();
|
|
137
161
|
const reports = new Container();
|
|
162
|
+
const queued = new Container(); // pending steering/follow-up messages, above the editor
|
|
163
|
+
const pickerPanel = new Container(); // native pickers (/model etc.), inline directly under the editor
|
|
138
164
|
const footer = new Container();
|
|
139
|
-
|
|
140
|
-
const
|
|
165
|
+
chrome.addChild(new DynamicBorder(pal.border));
|
|
166
|
+
const editorTheme = { borderColor: pal.border, selectList: getSelectListTheme() };
|
|
167
|
+
// TitledEditor paints the session name into its top border (solid-background
|
|
168
|
+
// chip) and tracks a thinking-level border color, both set from live state below.
|
|
169
|
+
const editor = new TitledEditor(tui, editorTheme, km, { paddingX: 1 });
|
|
141
170
|
// Slash-command autocomplete: builtins + native canvas commands now; enriched
|
|
142
171
|
// with the broker's engine/extension/skill commands on the get_commands ack.
|
|
143
172
|
const setCommands = (commands) => {
|
|
@@ -147,47 +176,80 @@ async function runAttach(nodeId, observer) {
|
|
|
147
176
|
// 3. Render layer (T5). ChatView owns the chat scroll + activity spinner; the
|
|
148
177
|
// footer/status line below is ours, so onFooterEvent is left unset (footer
|
|
149
178
|
// updates flow through handleBrokerFrame's single update path instead).
|
|
150
|
-
const chatView = new ChatView(tui, chatContainer, { cwd: meta.cwd });
|
|
179
|
+
const chatView = new ChatView(tui, chatContainer, { cwd: meta.cwd, palette: pal });
|
|
151
180
|
// 4. Footer/status line — surfaces role + live state + transient notices.
|
|
152
181
|
const clientId = randomUUID();
|
|
153
182
|
let role = observer ? 'observer' : 'controller';
|
|
183
|
+
let brokerAgentDir;
|
|
154
184
|
let liveState;
|
|
155
185
|
let notice = '';
|
|
186
|
+
// Live context occupancy for the footer: `contextTokens` is the prompt-token
|
|
187
|
+
// count of the most recent assistant turn (input + cacheRead + cacheWrite =
|
|
188
|
+
// everything sent to the model, since with prompt caching bare `input` is just
|
|
189
|
+
// the uncached slice and badly understates the context), read off the relayed
|
|
190
|
+
// message_end stream. It rides data the viewer already receives, so the
|
|
191
|
+
// indicator needs no broker/protocol change.
|
|
192
|
+
let contextTokens;
|
|
193
|
+
const formatTokens = (n) => n >= 1000 ? `${(n / 1000).toFixed(n >= 10000 ? 0 : 1)}k` : `${n}`;
|
|
194
|
+
// A themed, aligned status bar: live state on the LEFT (role · model · streaming
|
|
195
|
+
// · context · queued), the transient notice pushed to the RIGHT edge — not jammed inline.
|
|
156
196
|
const renderFooter = () => {
|
|
157
|
-
const
|
|
197
|
+
const segs = [
|
|
198
|
+
role === 'controller' ? pal.active('● drive') : pal.muted('○ read-only'),
|
|
199
|
+
];
|
|
158
200
|
if (liveState?.model)
|
|
159
|
-
|
|
201
|
+
segs.push(pal.info(liveState.model));
|
|
160
202
|
if (liveState?.isStreaming)
|
|
161
|
-
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (
|
|
166
|
-
|
|
203
|
+
segs.push(pal.active('streaming'));
|
|
204
|
+
if (contextTokens !== undefined) {
|
|
205
|
+
segs.push(pal.muted(`${formatTokens(contextTokens)} ctx`));
|
|
206
|
+
}
|
|
207
|
+
if (liveState && liveState.pendingMessageCount > 0) {
|
|
208
|
+
segs.push(pal.muted(`queued ${liveState.pendingMessageCount}`));
|
|
209
|
+
}
|
|
210
|
+
const left = segs.join(pal.muted(' · '));
|
|
211
|
+
const right = notice ? pal.warning(`⚠ ${notice}`) : '';
|
|
212
|
+
// Text(_, 1, 0) pads 1 col each side, so the usable span is columns - 2.
|
|
213
|
+
const span = Math.max(1, (process.stdout.columns ?? 80) - 2);
|
|
214
|
+
const rightW = visibleWidth(right);
|
|
215
|
+
// Truncate the left segment (ANSI-aware) so a long model/notice never wraps;
|
|
216
|
+
// the right-aligned notice keeps its slot.
|
|
217
|
+
const maxLeft = right ? Math.max(0, span - rightW - 1) : span;
|
|
218
|
+
const leftFit = visibleWidth(left) > maxLeft ? truncateToWidth(left, maxLeft, '…') : left;
|
|
219
|
+
const gap = Math.max(right ? 1 : 0, span - visibleWidth(leftFit) - rightW);
|
|
220
|
+
const line = right ? leftFit + ' '.repeat(gap) + right : leftFit;
|
|
167
221
|
footer.clear();
|
|
168
222
|
footer.addChild(new Text(line, 1, 0));
|
|
169
223
|
tui.requestRender();
|
|
170
224
|
};
|
|
171
|
-
|
|
225
|
+
// Transient notices (errors, command feedback) auto-clear after a few seconds so
|
|
226
|
+
// a stale "error: …" doesn't linger in the footer forever. `sticky` keeps a notice
|
|
227
|
+
// up until explicitly replaced (used by the reconnect indicator, which is cleared
|
|
228
|
+
// by hand once the socket redials).
|
|
229
|
+
const NOTICE_CLEAR_MS = 3_000;
|
|
230
|
+
let noticeTimer;
|
|
231
|
+
const setNotice = (msg, opts) => {
|
|
172
232
|
notice = msg;
|
|
233
|
+
if (noticeTimer !== undefined) {
|
|
234
|
+
clearTimeout(noticeTimer);
|
|
235
|
+
noticeTimer = undefined;
|
|
236
|
+
}
|
|
237
|
+
if (msg && !opts?.sticky) {
|
|
238
|
+
noticeTimer = setTimeout(() => {
|
|
239
|
+
noticeTimer = undefined;
|
|
240
|
+
notice = '';
|
|
241
|
+
renderFooter();
|
|
242
|
+
}, NOTICE_CLEAR_MS);
|
|
243
|
+
}
|
|
173
244
|
renderFooter();
|
|
174
245
|
};
|
|
175
|
-
// Name badge — the session name (= the canvas editor label `kind (mode)
|
|
176
|
-
// name`), relayed over the socket and patched live (Feature 1). Rendered just
|
|
177
|
-
// above the managers line in the bottom chrome so it stays in-viewport.
|
|
178
|
-
const renderBadge = () => {
|
|
179
|
-
badge.clear();
|
|
180
|
-
if (liveState?.sessionName)
|
|
181
|
-
badge.addChild(new Text(`\x1b[1m${liveState.sessionName}\x1b[22m`, 1, 0));
|
|
182
|
-
tui.requestRender();
|
|
183
|
-
};
|
|
184
246
|
// Subscribed-node panel — manager line above the editor, live reports below,
|
|
185
247
|
// read straight from canvas.db via nav-model (Feature 2). asksMap is refreshed
|
|
186
248
|
// by the low-rate poll below; beginFrame() inside buildCanvasPanelLines memoizes
|
|
187
249
|
// the db reads for each rebuild.
|
|
188
250
|
let asksMap = {};
|
|
189
251
|
const renderPanels = () => {
|
|
190
|
-
const { managers: mLines, reports: rLines } = buildCanvasPanelLines(nodeId, asksMap);
|
|
252
|
+
const { managers: mLines, reports: rLines } = buildCanvasPanelLines(nodeId, asksMap, pal);
|
|
191
253
|
managers.clear();
|
|
192
254
|
for (const l of mLines)
|
|
193
255
|
managers.addChild(new Text(l, 1, 0));
|
|
@@ -196,24 +258,109 @@ async function runAttach(nodeId, observer) {
|
|
|
196
258
|
reports.addChild(new Text(l, 1, 0));
|
|
197
259
|
tui.requestRender();
|
|
198
260
|
};
|
|
199
|
-
// The alt+g GRAPH overlay (Feature 3) — a
|
|
200
|
-
// nav-model graph, reading the live asksMap.
|
|
201
|
-
const graphOverlay = new GraphOverlay(tui, nodeId, () => asksMap);
|
|
261
|
+
// The alt+g GRAPH overlay (Feature 3) — a bounded, centered modal navigator
|
|
262
|
+
// over the same nav-model graph, reading the live asksMap.
|
|
263
|
+
const graphOverlay = new GraphOverlay(tui, nodeId, () => asksMap, pal);
|
|
202
264
|
// Refresh all canvas chrome (panels + overlay if open) from canvas.db.
|
|
203
265
|
const refreshChrome = () => {
|
|
204
266
|
renderPanels();
|
|
205
267
|
graphOverlay.refresh();
|
|
206
268
|
};
|
|
269
|
+
// Git context painted into the RIGHT of the editor's top border: cwd leaf ·
|
|
270
|
+
// branch · status symbols (● dirty, ⇡ ahead, ⇣ behind). Refreshed by the poll.
|
|
271
|
+
const renderGitInfo = (info) => {
|
|
272
|
+
const parts = [pal.muted(info.dir)];
|
|
273
|
+
if (info.branch)
|
|
274
|
+
parts.push(pal.info(`⎇ ${info.branch}`));
|
|
275
|
+
const sym = [];
|
|
276
|
+
if (info.dirty)
|
|
277
|
+
sym.push(pal.warning('●'));
|
|
278
|
+
if (info.ahead > 0)
|
|
279
|
+
sym.push(pal.active(`⇡${info.ahead}`));
|
|
280
|
+
if (info.behind > 0)
|
|
281
|
+
sym.push(pal.active(`⇣${info.behind}`));
|
|
282
|
+
if (sym.length > 0)
|
|
283
|
+
parts.push(sym.join(''));
|
|
284
|
+
// A trailing space keeps the symbols off the very last border cell.
|
|
285
|
+
editor.info = ` ${parts.join(pal.muted(' · '))} `;
|
|
286
|
+
tui.requestRender();
|
|
287
|
+
};
|
|
288
|
+
const pollGitInfo = () => fetchGitInfo(meta.cwd, renderGitInfo);
|
|
289
|
+
// Queued steering/follow-up messages, shown above the editor so you can SEE
|
|
290
|
+
// what is waiting (not just the footer count). Texts arrive on `queue_update`;
|
|
291
|
+
// the welcome snapshot carries only a count, so the panel fills from the first
|
|
292
|
+
// queue change after attach.
|
|
293
|
+
let queuedMessages = [];
|
|
294
|
+
const renderQueued = () => {
|
|
295
|
+
queued.clear();
|
|
296
|
+
if (queuedMessages.length > 0) {
|
|
297
|
+
const span = Math.max(1, (process.stdout.columns ?? 80) - 4);
|
|
298
|
+
queued.addChild(new Text(pal.muted(`queued (${queuedMessages.length}):`), 1, 0));
|
|
299
|
+
for (const msg of queuedMessages) {
|
|
300
|
+
const oneLine = msg.replace(/\s+/g, ' ').trim();
|
|
301
|
+
queued.addChild(new Text(pal.faint(` ⋯ ${truncateToWidth(oneLine, span, '…')}`), 1, 0));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
tui.requestRender();
|
|
305
|
+
};
|
|
207
306
|
// 5. Input layer (T6). onCommand/onDialogResponse → socket; onNotice → footer;
|
|
208
307
|
// nodeId/onGraph feed the slash context (/promote targets this node, /graph
|
|
209
|
-
// toggles the overlay)
|
|
308
|
+
// toggles the overlay); onRequest is the correlated read-op channel the
|
|
309
|
+
// native pickers ride on.
|
|
210
310
|
const input = new InputController(tui, editor, km, {
|
|
211
311
|
onCommand: (frame) => socket.send(frame),
|
|
212
312
|
onDialogResponse: (resp) => socket.send(resp),
|
|
213
313
|
onNotice: (msg) => setNotice(msg),
|
|
214
314
|
nodeId,
|
|
215
315
|
onGraph: () => graphOverlay.toggle(),
|
|
316
|
+
// `/quit` — detach this viewer (the engine keeps running). Must tear down
|
|
317
|
+
// locally; a bare `bye` would just trigger the reconnect loop.
|
|
318
|
+
onQuit: () => teardown('detach'),
|
|
319
|
+
// `/copy` — last assistant message → system clipboard.
|
|
320
|
+
onCopy: () => copyLastAssistant(),
|
|
321
|
+
// Global display toggles (Ctrl+O tools / Ctrl+T thinking) — pure render state
|
|
322
|
+
// owned by ChatView, surfaced as a footer notice mirroring pi's status line.
|
|
323
|
+
onToggleToolsExpand: () => setNotice(chatView.toggleToolsExpanded() ? 'Tool output: expanded' : 'Tool output: collapsed'),
|
|
324
|
+
onToggleThinking: () => setNotice(chatView.toggleThinking() ? 'Thinking blocks: hidden' : 'Thinking blocks: visible'),
|
|
325
|
+
// Wire the correlated read-op channel so the native pickers (/model,
|
|
326
|
+
// /resume, /fork, /tree, /settings, /scoped-models) can fetch their payloads.
|
|
327
|
+
// Without this every picker degrades to the "isn't available in this viewer"
|
|
328
|
+
// notice (see slash-commands.ts + InputController.openPicker).
|
|
329
|
+
onRequest: (frame) => socket.request(frame),
|
|
330
|
+
// Mount the native pickers inline in the chrome stack, directly under the
|
|
331
|
+
// editor (queued-panel pattern) — not as a floating centered modal — so a
|
|
332
|
+
// picker reads as part of the editor, like pi's own selectors.
|
|
333
|
+
onMountPicker: (component) => {
|
|
334
|
+
pickerPanel.addChild(component);
|
|
335
|
+
tui.requestRender();
|
|
336
|
+
},
|
|
337
|
+
onUnmountPicker: () => {
|
|
338
|
+
pickerPanel.clear();
|
|
339
|
+
tui.requestRender();
|
|
340
|
+
},
|
|
341
|
+
// Auth pickers — viewer-local OAuth/API-key flows. Auth operations target the
|
|
342
|
+
// SAME auth.json the broker uses (broker + viewer share a filesystem in the
|
|
343
|
+
// tmux-local attach model), so these run entirely in the viewer process and
|
|
344
|
+
// then notify the broker to reload via a reload_auth frame.
|
|
345
|
+
openLoginPicker: () => {
|
|
346
|
+
const dir = brokerAgentDir ?? defaultAgentDir();
|
|
347
|
+
input.openAuthPicker((controls) => buildLoginPicker(tui, dir, () => socket.send({ type: 'reload_auth' }), (m) => setNotice(m), controls));
|
|
348
|
+
},
|
|
349
|
+
openLogoutPicker: () => {
|
|
350
|
+
const dir = brokerAgentDir ?? defaultAgentDir();
|
|
351
|
+
input.openAuthPicker((controls) => buildLogoutPicker(dir, () => socket.send({ type: 'reload_auth' }), (m) => setNotice(m), controls.close));
|
|
352
|
+
},
|
|
216
353
|
});
|
|
354
|
+
// `/copy` — copy the last assistant message (held by ChatView, kept current
|
|
355
|
+
// from both the welcome snapshot and the live stream) to the system clipboard.
|
|
356
|
+
const copyLastAssistant = () => {
|
|
357
|
+
const text = chatView.getLastAssistantText();
|
|
358
|
+
if (!text) {
|
|
359
|
+
setNotice('Nothing to copy — no assistant message yet');
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
setNotice(writeClipboardText(text) ? 'Copied last message to clipboard' : 'Copy failed — no clipboard tool found');
|
|
363
|
+
};
|
|
217
364
|
// Feed fresh state into the input controller AND the footer. Steer-vs-prompt
|
|
218
365
|
// routing (T6) reads `state.isStreaming`, so this must stay current — the
|
|
219
366
|
// broker only ships full state in `welcome`, so we patch isStreaming/name/etc
|
|
@@ -221,8 +368,14 @@ async function runAttach(nodeId, observer) {
|
|
|
221
368
|
const setLiveState = (state) => {
|
|
222
369
|
liveState = state;
|
|
223
370
|
input.setState(state);
|
|
371
|
+
// The session name lives in the editor's top border (solid chip); the border
|
|
372
|
+
// color tracks the agent's thinking level. Both follow live state.
|
|
373
|
+
editor.title = state.sessionName ? `⬢ ${state.sessionName}` : '';
|
|
374
|
+
editor.borderColor = thinkingBorderColor(state.thinkingLevel, pal.border);
|
|
375
|
+
// Title chip background = the same thinking-level color (bold white text), so
|
|
376
|
+
// the name chip and the border rule read as one hue.
|
|
377
|
+
editor.titleStyle = thinkingTitleStyle(state.thinkingLevel, defaultTitleStyle);
|
|
224
378
|
renderFooter();
|
|
225
|
-
renderBadge();
|
|
226
379
|
// Panel liveness rides the relayed event stream (agent_start/agent_end/
|
|
227
380
|
// queue_update/session_info_changed all patch state through here) plus the
|
|
228
381
|
// low-rate poll below for changes that emit no event (child status flips).
|
|
@@ -238,7 +391,12 @@ async function runAttach(nodeId, observer) {
|
|
|
238
391
|
switch (frame.type) {
|
|
239
392
|
case 'welcome': {
|
|
240
393
|
role = frame.role;
|
|
394
|
+
brokerAgentDir = frame.agentDir;
|
|
241
395
|
chatView.applySnapshot(frame.snapshot);
|
|
396
|
+
// Seed the footer's context indicator from the catch-up snapshot.
|
|
397
|
+
const cu = frame.snapshot.stats.contextUsage;
|
|
398
|
+
if (cu && cu.tokens != null)
|
|
399
|
+
contextTokens = cu.tokens;
|
|
242
400
|
setLiveState(frame.snapshot.state);
|
|
243
401
|
// Ask the broker for its merged command list (engine/extension/skill
|
|
244
402
|
// commands) to enrich slash autocomplete beyond the builtins+canvas set.
|
|
@@ -254,6 +412,12 @@ async function runAttach(nodeId, observer) {
|
|
|
254
412
|
renderFooter();
|
|
255
413
|
break;
|
|
256
414
|
}
|
|
415
|
+
case 'model_changed': {
|
|
416
|
+
// The broker's own post-set_model/cycle_model announcement (pi emits no
|
|
417
|
+
// engine event for a model switch) — keeps the footer's model current.
|
|
418
|
+
patchState({ model: frame.model });
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
257
421
|
case 'error': {
|
|
258
422
|
if (frame.code === 'not_controller') {
|
|
259
423
|
setNotice('read-only — another viewer is the controller');
|
|
@@ -277,9 +441,38 @@ async function runAttach(nodeId, observer) {
|
|
|
277
441
|
}
|
|
278
442
|
break;
|
|
279
443
|
}
|
|
444
|
+
// navigate_tree's detail is the navigated-to user message's text (pi
|
|
445
|
+
// parity: the tree navigator restores it to the editor for re-editing);
|
|
446
|
+
// the rewound transcript itself arrives via the broker's re-welcome.
|
|
447
|
+
if (frame.for === 'navigate_tree' && frame.ok) {
|
|
448
|
+
if (frame.detail) {
|
|
449
|
+
editor.setText(frame.detail);
|
|
450
|
+
tui.requestRender();
|
|
451
|
+
}
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
if (frame.for === 'share') {
|
|
455
|
+
if (frame.ok && frame.detail) {
|
|
456
|
+
setNotice(`Shared: ${frame.detail}`);
|
|
457
|
+
writeClipboardText(frame.detail);
|
|
458
|
+
}
|
|
459
|
+
else if (!frame.ok) {
|
|
460
|
+
setNotice(`share failed: ${frame.detail ?? 'unknown error'}`);
|
|
461
|
+
}
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
if (frame.for === 'clone' && !frame.ok) {
|
|
465
|
+
setNotice(`clone failed: ${frame.detail ?? 'unknown error'}`);
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
280
468
|
if (!frame.ok) {
|
|
281
469
|
setNotice(`command failed: ${frame.for}${frame.detail ? ` — ${frame.detail}` : ''}`);
|
|
470
|
+
break;
|
|
282
471
|
}
|
|
472
|
+
// Confirm commands whose effect is otherwise invisible — without this a
|
|
473
|
+
// `/reload` looks like it did nothing (the engine reloads silently).
|
|
474
|
+
if (frame.for === 'reload')
|
|
475
|
+
setNotice('Reloaded keybindings, extensions, skills, prompts, and themes');
|
|
283
476
|
break;
|
|
284
477
|
}
|
|
285
478
|
case 'extension_ui_request': {
|
|
@@ -290,16 +483,27 @@ async function runAttach(nodeId, observer) {
|
|
|
290
483
|
// A relayed AgentSessionEvent — render it, and keep local state fresh.
|
|
291
484
|
const event = frame;
|
|
292
485
|
chatView.handleEvent(event);
|
|
486
|
+
// The most recent assistant turn's prompt tokens ARE the live context
|
|
487
|
+
// size — read straight off the relayed message, no broker round-trip.
|
|
488
|
+
if (event.type === 'message_end' && event.message.role === 'assistant') {
|
|
489
|
+
const u = event.message.usage;
|
|
490
|
+
contextTokens = u.input + u.cacheRead + u.cacheWrite;
|
|
491
|
+
renderFooter();
|
|
492
|
+
}
|
|
293
493
|
if (event.type === 'agent_start')
|
|
294
494
|
patchState({ isStreaming: true });
|
|
295
495
|
else if (event.type === 'agent_end')
|
|
296
496
|
patchState({ isStreaming: false });
|
|
297
497
|
else if (event.type === 'session_info_changed')
|
|
298
498
|
patchState({ sessionName: event.name });
|
|
299
|
-
else if (event.type === 'thinking_level_changed')
|
|
499
|
+
else if (event.type === 'thinking_level_changed') {
|
|
300
500
|
patchState({ thinkingLevel: event.level });
|
|
501
|
+
setNotice(`Thinking level: ${event.level}`);
|
|
502
|
+
}
|
|
301
503
|
else if (event.type === 'queue_update') {
|
|
302
|
-
|
|
504
|
+
queuedMessages = [...event.steering, ...event.followUp];
|
|
505
|
+
renderQueued();
|
|
506
|
+
patchState({ pendingMessageCount: queuedMessages.length });
|
|
303
507
|
}
|
|
304
508
|
break;
|
|
305
509
|
}
|
|
@@ -334,6 +538,8 @@ async function runAttach(nodeId, observer) {
|
|
|
334
538
|
tornDown = true;
|
|
335
539
|
if (asksTimer !== undefined)
|
|
336
540
|
clearInterval(asksTimer);
|
|
541
|
+
if (noticeTimer !== undefined)
|
|
542
|
+
clearTimeout(noticeTimer);
|
|
337
543
|
graphOverlay.close();
|
|
338
544
|
clearPaneTag();
|
|
339
545
|
removeKeyListener();
|
|
@@ -357,10 +563,17 @@ async function runAttach(nodeId, observer) {
|
|
|
357
563
|
resolveDone();
|
|
358
564
|
};
|
|
359
565
|
// The handshake the viewer sends on connect AND on every successful redial.
|
|
566
|
+
// After a broker refresh / crash-revive the fresh process has no controller and
|
|
567
|
+
// no memory of us, so re-`hello` reclaims our role and the `welcome` snapshot
|
|
568
|
+
// repaints full scrollback/state. Sends the CURRENT `role`, not the constructor
|
|
569
|
+
// `observer` flag: if we were preempted to observer at runtime (a
|
|
570
|
+
// `control_changed` demoted us), re-hello'ing as 'controller' would let us
|
|
571
|
+
// reclaim control from the new controller after a refresh — so the demotion
|
|
572
|
+
// must survive the reconnect.
|
|
360
573
|
const sendHello = () => {
|
|
361
574
|
socket.send({
|
|
362
575
|
type: 'hello',
|
|
363
|
-
role
|
|
576
|
+
role,
|
|
364
577
|
client_id: clientId,
|
|
365
578
|
term: { cols: process.stdout.columns ?? 80, rows: process.stdout.rows ?? 24 },
|
|
366
579
|
});
|
|
@@ -379,7 +592,7 @@ async function runAttach(nodeId, observer) {
|
|
|
379
592
|
let reconnecting = false;
|
|
380
593
|
const attemptReconnect = async () => {
|
|
381
594
|
reconnecting = true;
|
|
382
|
-
setNotice('↻ node refreshing — reconnecting…');
|
|
595
|
+
setNotice('↻ node refreshing — reconnecting…', { sticky: true });
|
|
383
596
|
const deadline = Date.now() + RECONNECT_GIVEUP_MS;
|
|
384
597
|
let delay = 200;
|
|
385
598
|
while (!tornDown && Date.now() < deadline) {
|
|
@@ -417,14 +630,30 @@ async function runAttach(nodeId, observer) {
|
|
|
417
630
|
return; // the dead socket's own close during the redial loop
|
|
418
631
|
void attemptReconnect();
|
|
419
632
|
});
|
|
420
|
-
// ctrl+
|
|
421
|
-
//
|
|
422
|
-
//
|
|
633
|
+
// ctrl+d → detach immediately (lifecycle is ours). ctrl+c is gentler: a single
|
|
634
|
+
// tap CLEARS the editor (and arms a window); a second tap within that window
|
|
635
|
+
// detaches. A global TUI input listener fires BEFORE the focused component (and
|
|
636
|
+
// before any dialog overlay), so this is consistent regardless of focus.
|
|
637
|
+
const CTRL_C_DETACH_MS = 1000;
|
|
638
|
+
let lastCtrlCAt = 0;
|
|
423
639
|
const removeKeyListener = tui.addInputListener((data) => {
|
|
424
|
-
if (matchesKey(data, 'ctrl+
|
|
640
|
+
if (matchesKey(data, 'ctrl+d')) {
|
|
425
641
|
teardown('detach');
|
|
426
642
|
return { consume: true };
|
|
427
643
|
}
|
|
644
|
+
if (matchesKey(data, 'ctrl+c')) {
|
|
645
|
+
const now = Date.now();
|
|
646
|
+
if (now - lastCtrlCAt < CTRL_C_DETACH_MS) {
|
|
647
|
+
lastCtrlCAt = 0;
|
|
648
|
+
teardown('detach');
|
|
649
|
+
return { consume: true };
|
|
650
|
+
}
|
|
651
|
+
lastCtrlCAt = now;
|
|
652
|
+
editor.setText('');
|
|
653
|
+
setNotice('Editor cleared — press Ctrl+C again to detach');
|
|
654
|
+
tui.requestRender();
|
|
655
|
+
return { consume: true };
|
|
656
|
+
}
|
|
428
657
|
// alt+g toggles the GRAPH overlay. The global listener fires BEFORE the
|
|
429
658
|
// focused component, so this works whether the editor or the overlay holds
|
|
430
659
|
// focus (closing it from inside the overlay too).
|
|
@@ -443,22 +672,29 @@ async function runAttach(nodeId, observer) {
|
|
|
443
672
|
// 8. Lay out, focus the editor, start, then handshake. Starting before the
|
|
444
673
|
// handshake means the welcome's applySnapshot renders into a live TUI.
|
|
445
674
|
tui.addChild(chatContainer);
|
|
446
|
-
tui.addChild(
|
|
675
|
+
tui.addChild(chrome);
|
|
447
676
|
tui.addChild(managers);
|
|
677
|
+
tui.addChild(queued);
|
|
448
678
|
tui.addChild(editor);
|
|
679
|
+
tui.addChild(pickerPanel);
|
|
449
680
|
tui.addChild(reports);
|
|
450
681
|
tui.addChild(footer);
|
|
451
682
|
tui.setFocus(editor);
|
|
452
683
|
renderFooter();
|
|
453
|
-
renderBadge();
|
|
454
684
|
renderPanels();
|
|
685
|
+
renderQueued();
|
|
686
|
+
pollGitInfo();
|
|
455
687
|
tui.start();
|
|
688
|
+
// ProcessTerminal negotiated the keyboard protocol on OUR pi-tui instance; the
|
|
689
|
+
// editor reads the flag from its own (possibly separate) instance, so mirror it.
|
|
690
|
+
void mirrorKittyProtocolToEditor(procTerminal.kittyProtocolActive);
|
|
456
691
|
sendHello();
|
|
457
692
|
// Low-rate canvas-chrome refresh: repaint panels/overlay from canvas.db (child
|
|
458
693
|
// status flips emit no broker event) and re-poll the ask map asynchronously.
|
|
459
694
|
const ASKS_POLL_MS = 5_000;
|
|
460
695
|
const pollChrome = () => {
|
|
461
696
|
refreshChrome();
|
|
697
|
+
pollGitInfo();
|
|
462
698
|
fetchAsksAsync(climbRoot(nodeId), (counts) => {
|
|
463
699
|
if (JSON.stringify(counts) !== JSON.stringify(asksMap)) {
|
|
464
700
|
asksMap = counts;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TUI } from '@earendil-works/pi-tui';
|
|
2
|
+
import type { Picker, PickerControls } from './pickers.js';
|
|
3
|
+
/** `/login` — show the provider selector, then run pi's native login flow in
|
|
4
|
+
* the viewer. On success, notifies the broker to reload auth via reload_auth.
|
|
5
|
+
* Returns a Picker (OAuthSelectorComponent as the overlay). */
|
|
6
|
+
export declare function buildLoginPicker(tui: TUI, agentDir: string, onAuthReloaded: () => void, notify: (msg: string) => void, controls: PickerControls): Picker;
|
|
7
|
+
/** `/logout` — show the provider selector in logout mode. On select, calls
|
|
8
|
+
* authStorage.logout(providerId) directly, then notifies the broker to reload. */
|
|
9
|
+
export declare function buildLogoutPicker(agentDir: string, onAuthReloaded: () => void, notify: (msg: string) => void, close: () => void): Picker;
|