@crouton-kit/crouter 0.3.70 → 0.3.79
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/README.md +1 -1
- package/dist/build-root.d.ts +12 -4
- package/dist/build-root.js +25 -6
- package/dist/builtin-memory/00-runtime-base.md +3 -10
- package/dist/builtin-memory/04-base-worker.md +18 -0
- package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
- package/dist/builtin-memory/crouter-development/plugins.md +82 -5
- package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +13 -34
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +1115 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +234 -71
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +2 -9
- package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +2 -516
- package/dist/builtin-pi-packages/pi-crtr-extensions/package-lock.json +2 -2
- package/dist/builtin-pi-packages/pi-mode-switch/README.md +11 -39
- package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +20 -15
- package/dist/builtin-pi-packages/pi-mode-switch/package.json +1 -1
- package/dist/builtin-views/canvas/tui.mjs +8 -9
- package/dist/builtin-views/chat/tui.mjs +12 -12
- package/dist/builtin-views/git-pr/tui.mjs +7 -8
- package/dist/builtin-views/inbox/tui.mjs +27 -41
- package/dist/builtin-views/linkedin/tui.mjs +23 -37
- package/dist/builtin-views/prompt-review/tui.mjs +11 -11
- package/dist/builtin-views/settings/tui.mjs +11 -11
- package/dist/builtin-views/workspace-sidebar/tui.mjs +8 -9
- package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +11 -6
- package/dist/clients/attach/__tests__/attach-keybindings.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/attach-keybindings.test.js +113 -0
- package/dist/clients/attach/__tests__/crtr-output-render.test.js +1 -1
- package/dist/clients/attach/__tests__/mermaid-render.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/mermaid-render.test.js +28 -0
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +110 -0
- package/dist/clients/attach/attach-cmd.d.ts +9 -1
- package/dist/clients/attach/attach-cmd.js +841 -792
- package/dist/clients/attach/auth-pickers.d.ts +0 -12
- package/dist/clients/attach/auth-pickers.js +64 -15
- package/dist/clients/attach/canvas-panels.js +2 -3
- package/dist/clients/attach/chat-view.d.ts +7 -8
- package/dist/clients/attach/chat-view.js +119 -79
- package/dist/clients/attach/edit-diff-render.d.ts +6 -0
- package/dist/clients/attach/edit-diff-render.js +263 -0
- package/dist/clients/attach/graph-overlay.d.ts +12 -2
- package/dist/clients/attach/graph-overlay.js +83 -33
- package/dist/clients/attach/input-controller.d.ts +28 -2
- package/dist/clients/attach/input-controller.js +38 -5
- package/dist/clients/attach/mermaid-render.js +5 -2
- package/dist/clients/attach/pickers.d.ts +8 -7
- package/dist/clients/attach/pickers.js +11 -17
- package/dist/clients/attach/slash-commands.d.ts +9 -0
- package/dist/clients/attach/slash-commands.js +127 -7
- package/dist/clients/attach/titled-editor.d.ts +12 -1
- package/dist/clients/attach/titled-editor.js +103 -8
- package/dist/commands/canvas-browse.js +2 -2
- package/dist/commands/human/queue.js +3 -4
- package/dist/commands/memory/lint.js +39 -5
- package/dist/commands/memory/write.js +1 -0
- package/dist/commands/node.js +9 -2
- package/dist/commands/pkg/plugin-inspect.js +22 -1
- package/dist/commands/pkg/plugin-manage.js +31 -9
- package/dist/commands/surface-tmux-spread.js +1 -3
- package/dist/commands/sys/__tests__/config-keybindings.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/config-keybindings.test.js +55 -0
- package/dist/commands/sys/__tests__/config-model-ladders.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/config-model-ladders.test.js +121 -0
- package/dist/commands/sys/__tests__/setup-core.test.js +158 -1
- package/dist/commands/sys/config.js +18 -21
- package/dist/commands/sys/doctor.js +42 -4
- package/dist/commands/sys/setup-core.d.ts +49 -1
- package/dist/commands/sys/setup-core.js +161 -4
- package/dist/commands/sys/setup.d.ts +88 -0
- package/dist/commands/sys/setup.js +943 -169
- package/dist/commands/view-pick.d.ts +4 -0
- package/dist/commands/view-pick.js +17 -7
- package/dist/core/__tests__/base-worker-prompt.test.d.ts +1 -0
- package/dist/core/__tests__/base-worker-prompt.test.js +24 -0
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +232 -1
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +34 -9
- package/dist/core/__tests__/command-plugins-surfaces.test.d.ts +1 -0
- package/dist/core/__tests__/command-plugins-surfaces.test.js +298 -0
- package/dist/core/__tests__/command-plugins.test.d.ts +1 -0
- package/dist/core/__tests__/command-plugins.test.js +444 -0
- package/dist/core/__tests__/fault-classifier.test.js +15 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +6 -0
- package/dist/core/__tests__/fixtures/fake-engine.js +9 -1
- package/dist/core/__tests__/full/broker-dialogs.test.js +7 -2
- package/dist/core/__tests__/host-teardown-process-group.test.js +15 -4
- package/dist/core/__tests__/preview-registry-sync.test.js +30 -1
- package/dist/core/__tests__/scope-crouter-home-fence.test.d.ts +1 -0
- package/dist/core/__tests__/scope-crouter-home-fence.test.js +55 -0
- package/dist/core/__tests__/stream-watchdog.test.d.ts +1 -0
- package/dist/core/__tests__/stream-watchdog.test.js +70 -0
- package/dist/core/__tests__/tmux-surface.test.js +72 -0
- package/dist/core/canvas/browse/__tests__/model.test.js +23 -5
- package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
- package/dist/core/canvas/browse/app.d.ts +6 -0
- package/dist/core/canvas/browse/app.js +230 -41
- package/dist/core/canvas/browse/model.d.ts +10 -6
- package/dist/core/canvas/browse/model.js +20 -12
- package/dist/core/canvas/browse/pins.d.ts +4 -0
- package/dist/core/canvas/browse/pins.js +29 -0
- package/dist/core/canvas/browse/render.d.ts +13 -1
- package/dist/core/canvas/browse/render.js +72 -15
- package/dist/core/canvas/nav-model.js +5 -11
- package/dist/core/canvas/paths.d.ts +5 -0
- package/dist/core/canvas/paths.js +7 -0
- package/dist/core/command-plugins/adapter.d.ts +15 -0
- package/dist/core/command-plugins/adapter.js +145 -0
- package/dist/core/command-plugins/compose.d.ts +5 -0
- package/dist/core/command-plugins/compose.js +56 -0
- package/dist/core/command-plugins/discovery.d.ts +104 -0
- package/dist/core/command-plugins/discovery.js +565 -0
- package/dist/core/config.d.ts +48 -0
- package/dist/core/config.js +228 -110
- package/dist/core/fault-classifier.js +1 -1
- package/dist/core/fs-utils.d.ts +1 -0
- package/dist/core/fs-utils.js +15 -1
- package/dist/core/keybindings/__tests__/bespoke-consumers.test.d.ts +1 -0
- package/dist/core/keybindings/__tests__/bespoke-consumers.test.js +40 -0
- package/dist/core/keybindings/__tests__/resolve.test.d.ts +1 -0
- package/dist/core/keybindings/__tests__/resolve.test.js +224 -0
- package/dist/core/keybindings/catalog.d.ts +14 -0
- package/dist/core/keybindings/catalog.js +257 -0
- package/dist/core/keybindings/index.d.ts +5 -0
- package/dist/core/keybindings/index.js +4 -0
- package/dist/core/keybindings/match.d.ts +29 -0
- package/dist/core/keybindings/match.js +99 -0
- package/dist/core/keybindings/persistence.d.ts +14 -0
- package/dist/core/keybindings/persistence.js +31 -0
- package/dist/core/keybindings/resolve.d.ts +14 -0
- package/dist/core/keybindings/resolve.js +305 -0
- package/dist/core/keybindings/types.d.ts +37 -0
- package/dist/core/keybindings/types.js +1 -0
- package/dist/core/memory-resolver.d.ts +1 -1
- package/dist/core/memory-resolver.js +10 -3
- package/dist/core/predicate.d.ts +5 -3
- package/dist/core/predicate.js +5 -3
- package/dist/core/profiles/select.d.ts +6 -0
- package/dist/core/profiles/select.js +86 -52
- package/dist/core/provider-management.d.ts +12 -0
- package/dist/core/provider-management.js +24 -0
- package/dist/core/runtime/broker-protocol.d.ts +33 -5
- package/dist/core/runtime/broker.js +164 -14
- package/dist/core/runtime/launch.d.ts +39 -6
- package/dist/core/runtime/launch.js +78 -20
- package/dist/core/runtime/naming.js +3 -3
- package/dist/core/runtime/pi-cli.d.ts +6 -0
- package/dist/core/runtime/pi-cli.js +16 -2
- package/dist/core/runtime/pi-vendored.d.ts +8 -0
- package/dist/core/runtime/pi-vendored.js +14 -0
- package/dist/core/runtime/placement.d.ts +2 -2
- package/dist/core/runtime/placement.js +4 -1
- package/dist/core/runtime/promote.js +4 -0
- package/dist/core/runtime/recap.d.ts +3 -3
- package/dist/core/runtime/recap.js +75 -47
- package/dist/core/runtime/recycle.js +6 -1
- package/dist/core/runtime/reset.js +5 -0
- package/dist/core/runtime/session-list-cache.d.ts +33 -0
- package/dist/core/runtime/session-list-cache.js +338 -0
- package/dist/core/runtime/spawn.js +52 -45
- package/dist/core/runtime/stream-watchdog.d.ts +26 -0
- package/dist/core/runtime/stream-watchdog.js +75 -0
- package/dist/core/runtime/tmux-chrome.d.ts +1 -1
- package/dist/core/runtime/tmux-chrome.js +2 -2
- package/dist/core/runtime/tmux.d.ts +18 -15
- package/dist/core/runtime/tmux.js +217 -110
- package/dist/core/scope.js +27 -4
- package/dist/core/subscription-state.d.ts +90 -0
- package/dist/core/subscription-state.js +762 -0
- package/dist/core/tui/__tests__/host-keybindings.test.d.ts +1 -0
- package/dist/core/tui/__tests__/host-keybindings.test.js +112 -0
- package/dist/core/tui/host.d.ts +30 -1
- package/dist/core/tui/host.js +148 -34
- package/dist/core/view/contract.d.ts +17 -3
- package/dist/daemon/crtrd.js +253 -12
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +41 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +121 -0
- package/dist/pi-extensions/canvas-inbox-watcher.d.ts +1 -1
- package/dist/pi-extensions/canvas-inbox-watcher.js +203 -25
- package/dist/pi-extensions/canvas-recap.d.ts +4 -0
- package/dist/pi-extensions/canvas-recap.js +73 -46
- package/dist/pi-extensions/canvas-stophook.d.ts +11 -0
- package/dist/pi-extensions/canvas-stophook.js +33 -4
- package/dist/types.d.ts +9 -26
- package/dist/types.js +1 -28
- package/dist/web-client/assets/{index-DiFuLcp6.js → index--md2ylfi.js} +1 -1
- package/dist/web-client/index.html +1 -1
- package/dist/web-client/sw.js +1 -1
- package/package.json +8 -4
- package/dist/pi-extensions/canvas-nav.d.ts +0 -43
- package/dist/pi-extensions/canvas-nav.js +0 -640
- package/dist/pi-extensions/widget-order-bus.d.ts +0 -6
- package/dist/pi-extensions/widget-order-bus.js +0 -34
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// edit-diff-render.ts — Claude-Code-style diff rendering for the attach viewer.
|
|
2
|
+
//
|
|
3
|
+
// pi's builtin edit renderer (core/tools/edit.js → renderDiff) paints diff
|
|
4
|
+
// lines with colored FOREGROUND text plus inverse-video on changed tokens.
|
|
5
|
+
// This module re-skins that body the way Claude Code renders diffs: every
|
|
6
|
+
// added line gets a full-width tinted green BACKGROUND, every removed line a
|
|
7
|
+
// full-width tinted red background, with the changed span inside a 1↔1 line
|
|
8
|
+
// modification emphasized in a brighter tint of the same color.
|
|
9
|
+
//
|
|
10
|
+
// We deliberately keep pi's builtin edit definition as the engine — it owns
|
|
11
|
+
// the async preview computation (computeEditsDiff keyed off ctx.state) and
|
|
12
|
+
// the header/status-bg lifecycle — and only swap the rendered diff body.
|
|
13
|
+
// The raw diff string lives on the call component (`preview.diff`), so no
|
|
14
|
+
// ANSI re-parsing of pi's output is ever needed.
|
|
15
|
+
import { createEditToolDefinition } from '@earendil-works/pi-coding-agent';
|
|
16
|
+
import { Container, Spacer, Text } from '@earendil-works/pi-tui';
|
|
17
|
+
const FALLBACK_ADDED = { r: 181, g: 189, b: 104 }; // pi dark green
|
|
18
|
+
const FALLBACK_REMOVED = { r: 204, g: 102, b: 102 }; // pi dark red
|
|
19
|
+
const FALLBACK_CARD_BG = { r: 40, g: 50, b: 40 }; // pi dark toolSuccessBg
|
|
20
|
+
/** Standard xterm-256 palette → rgb (cube + grayscale ramps; 16 base approximated). */
|
|
21
|
+
function xterm256ToRgb(n) {
|
|
22
|
+
if (n < 16) {
|
|
23
|
+
const base = [
|
|
24
|
+
[0, 0, 0], [205, 0, 0], [0, 205, 0], [205, 205, 0],
|
|
25
|
+
[0, 0, 238], [205, 0, 205], [0, 205, 205], [229, 229, 229],
|
|
26
|
+
[127, 127, 127], [255, 0, 0], [0, 255, 0], [255, 255, 0],
|
|
27
|
+
[92, 92, 255], [255, 0, 255], [0, 255, 255], [255, 255, 255],
|
|
28
|
+
];
|
|
29
|
+
const [r, g, b] = base[n] ?? [128, 128, 128];
|
|
30
|
+
return { r, g, b };
|
|
31
|
+
}
|
|
32
|
+
if (n < 232) {
|
|
33
|
+
const i = n - 16;
|
|
34
|
+
const steps = [0, 95, 135, 175, 215, 255];
|
|
35
|
+
return { r: steps[Math.floor(i / 36) % 6], g: steps[Math.floor(i / 6) % 6], b: steps[i % 6] };
|
|
36
|
+
}
|
|
37
|
+
const v = 8 + (n - 232) * 10;
|
|
38
|
+
return { r: v, g: v, b: v };
|
|
39
|
+
}
|
|
40
|
+
/** rgb → nearest xterm-256 cube/gray index (for non-truecolor terminals). */
|
|
41
|
+
function rgbTo256(c) {
|
|
42
|
+
const toCube = (v) => (v < 48 ? 0 : v < 115 ? 1 : Math.min(5, Math.floor((v - 35) / 40)));
|
|
43
|
+
const steps = [0, 95, 135, 175, 215, 255];
|
|
44
|
+
const cr = toCube(c.r);
|
|
45
|
+
const cg = toCube(c.g);
|
|
46
|
+
const cb = toCube(c.b);
|
|
47
|
+
const cubeIdx = 16 + 36 * cr + 6 * cg + cb;
|
|
48
|
+
const cube = { r: steps[cr], g: steps[cg], b: steps[cb] };
|
|
49
|
+
const gray = Math.max(0, Math.min(23, Math.round((((c.r + c.g + c.b) / 3) - 8) / 10)));
|
|
50
|
+
const grayIdx = 232 + gray;
|
|
51
|
+
const grayRgb = xterm256ToRgb(grayIdx);
|
|
52
|
+
const dist = (a, b) => (a.r - b.r) ** 2 + (a.g - b.g) ** 2 + (a.b - b.b) ** 2;
|
|
53
|
+
return dist(c, cube) <= dist(c, grayRgb) ? cubeIdx : grayIdx;
|
|
54
|
+
}
|
|
55
|
+
/** Parse the rgb out of a theme SGR open code (`38/48;2;r;g;b` or `38/48;5;n`). */
|
|
56
|
+
function parseAnsiRgb(ansi) {
|
|
57
|
+
const truecolor = ansi.match(/\x1b\[[34]8;2;(\d+);(\d+);(\d+)m/);
|
|
58
|
+
if (truecolor)
|
|
59
|
+
return { r: Number(truecolor[1]), g: Number(truecolor[2]), b: Number(truecolor[3]) };
|
|
60
|
+
const indexed = ansi.match(/\x1b\[[34]8;5;(\d+)m/);
|
|
61
|
+
if (indexed)
|
|
62
|
+
return xterm256ToRgb(Number(indexed[1]));
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
function mix(a, b, t) {
|
|
66
|
+
return {
|
|
67
|
+
r: Math.round(a.r * t + b.r * (1 - t)),
|
|
68
|
+
g: Math.round(a.g * t + b.g * (1 - t)),
|
|
69
|
+
b: Math.round(a.b * t + b.b * (1 - t)),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function bgOpen(c, mode) {
|
|
73
|
+
if (mode === 'truecolor')
|
|
74
|
+
return `\x1b[48;2;${c.r};${c.g};${c.b}m`;
|
|
75
|
+
return `\x1b[48;5;${rgbTo256(c)}m`;
|
|
76
|
+
}
|
|
77
|
+
function themeFgRgb(theme, name, fallback) {
|
|
78
|
+
try {
|
|
79
|
+
return parseAnsiRgb(theme.getFgAnsi(name)) ?? fallback;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return fallback;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function themeBgRgb(theme, name, fallback) {
|
|
86
|
+
try {
|
|
87
|
+
return parseAnsiRgb(theme.getBgAnsi(name)) ?? fallback;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return fallback;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Diff body rendering
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
/** pi's display-diff line format: "+123 content" / "-123 content" / " 123 content". */
|
|
97
|
+
function parseDiffLine(line) {
|
|
98
|
+
const match = line.match(/^([+-\s])(\s*\d*)\s(.*)$/);
|
|
99
|
+
if (!match)
|
|
100
|
+
return null;
|
|
101
|
+
return { prefix: match[1], lineNum: match[2], content: match[3] };
|
|
102
|
+
}
|
|
103
|
+
function replaceTabs(text) {
|
|
104
|
+
return text.replace(/\t/g, ' ');
|
|
105
|
+
}
|
|
106
|
+
/** Common prefix/suffix char-span diff for a single-line 1↔1 modification. */
|
|
107
|
+
function changedSpans(oldS, newS) {
|
|
108
|
+
const maxP = Math.min(oldS.length, newS.length);
|
|
109
|
+
let p = 0;
|
|
110
|
+
while (p < maxP && oldS[p] === newS[p])
|
|
111
|
+
p++;
|
|
112
|
+
let s = 0;
|
|
113
|
+
while (s < maxP - p && oldS[oldS.length - 1 - s] === newS[newS.length - 1 - s])
|
|
114
|
+
s++;
|
|
115
|
+
return { old: [p, oldS.length - s], new: [p, newS.length - s] };
|
|
116
|
+
}
|
|
117
|
+
/** Wrap the changed span in a brighter tint, restoring the line tint after. */
|
|
118
|
+
function emphasize(content, span, emphOpen, lineOpen) {
|
|
119
|
+
const [start, end] = span;
|
|
120
|
+
if (end <= start)
|
|
121
|
+
return content;
|
|
122
|
+
return content.slice(0, start) + emphOpen + content.slice(start, end) + lineOpen + content.slice(end);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Build the Claude-Code-style diff body: one full-width background-tinted
|
|
126
|
+
* Text per added/removed line (continuation lines of a wrapped long line keep
|
|
127
|
+
* the tint, exactly like Claude Code), plain context lines in between.
|
|
128
|
+
*
|
|
129
|
+
* `restoreBg` is the SGR that must follow each tinted line so the host Box's
|
|
130
|
+
* own background resumes on its right padding column (a bare `\x1b[49m` would
|
|
131
|
+
* punch a default-bg notch into the card edge).
|
|
132
|
+
*/
|
|
133
|
+
function buildDiffBody(diffText, theme, restoreBg) {
|
|
134
|
+
const mode = theme.getColorMode();
|
|
135
|
+
const cardBg = themeBgRgb(theme, 'toolSuccessBg', FALLBACK_CARD_BG);
|
|
136
|
+
const addedFg = themeFgRgb(theme, 'toolDiffAdded', FALLBACK_ADDED);
|
|
137
|
+
const removedFg = themeFgRgb(theme, 'toolDiffRemoved', FALLBACK_REMOVED);
|
|
138
|
+
const addedLineBg = bgOpen(mix(addedFg, cardBg, 0.24), mode);
|
|
139
|
+
const addedEmphBg = bgOpen(mix(addedFg, cardBg, 0.48), mode);
|
|
140
|
+
const removedLineBg = bgOpen(mix(removedFg, cardBg, 0.24), mode);
|
|
141
|
+
const removedEmphBg = bgOpen(mix(removedFg, cardBg, 0.48), mode);
|
|
142
|
+
const container = new Container();
|
|
143
|
+
const pushLine = (styled, lineBg) => {
|
|
144
|
+
const bgFn = lineBg === undefined ? undefined : (padded) => lineBg + padded + restoreBg;
|
|
145
|
+
container.addChild(new Text(styled, 0, 0, bgFn));
|
|
146
|
+
};
|
|
147
|
+
const pushRemoved = (lineNum, content) => {
|
|
148
|
+
pushLine(`${theme.fg('toolDiffRemoved', `-${lineNum}`)} ${content}`, removedLineBg);
|
|
149
|
+
};
|
|
150
|
+
const pushAdded = (lineNum, content) => {
|
|
151
|
+
pushLine(`${theme.fg('toolDiffAdded', `+${lineNum}`)} ${content}`, addedLineBg);
|
|
152
|
+
};
|
|
153
|
+
const lines = diffText.split('\n');
|
|
154
|
+
let i = 0;
|
|
155
|
+
while (i < lines.length) {
|
|
156
|
+
const parsed = parseDiffLine(lines[i]);
|
|
157
|
+
if (!parsed) {
|
|
158
|
+
pushLine(theme.fg('toolDiffContext', lines[i]));
|
|
159
|
+
i++;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (parsed.prefix === '-') {
|
|
163
|
+
const removed = [];
|
|
164
|
+
while (i < lines.length) {
|
|
165
|
+
const p = parseDiffLine(lines[i]);
|
|
166
|
+
if (!p || p.prefix !== '-')
|
|
167
|
+
break;
|
|
168
|
+
removed.push({ lineNum: p.lineNum, content: replaceTabs(p.content) });
|
|
169
|
+
i++;
|
|
170
|
+
}
|
|
171
|
+
const added = [];
|
|
172
|
+
while (i < lines.length) {
|
|
173
|
+
const p = parseDiffLine(lines[i]);
|
|
174
|
+
if (!p || p.prefix !== '+')
|
|
175
|
+
break;
|
|
176
|
+
added.push({ lineNum: p.lineNum, content: replaceTabs(p.content) });
|
|
177
|
+
i++;
|
|
178
|
+
}
|
|
179
|
+
// Single-line modification → emphasize the changed span in a brighter tint.
|
|
180
|
+
if (removed.length === 1 && added.length === 1) {
|
|
181
|
+
const spans = changedSpans(removed[0].content, added[0].content);
|
|
182
|
+
pushRemoved(removed[0].lineNum, emphasize(removed[0].content, spans.old, removedEmphBg, removedLineBg));
|
|
183
|
+
pushAdded(added[0].lineNum, emphasize(added[0].content, spans.new, addedEmphBg, addedLineBg));
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
for (const line of removed)
|
|
187
|
+
pushRemoved(line.lineNum, line.content);
|
|
188
|
+
for (const line of added)
|
|
189
|
+
pushAdded(line.lineNum, line.content);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else if (parsed.prefix === '+') {
|
|
193
|
+
pushAdded(parsed.lineNum, replaceTabs(parsed.content));
|
|
194
|
+
i++;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
pushLine(theme.fg('toolDiffContext', ` ${parsed.lineNum} ${replaceTabs(parsed.content)}`));
|
|
198
|
+
i++;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return container;
|
|
202
|
+
}
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
// Tool definition wrapper
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
/** Replace the pi-rendered diff body inside the edit call Box (children:
|
|
207
|
+
* [header, Spacer, body]) with the full-background rendering. No-op when
|
|
208
|
+
* there is no successful preview (header-only or error states keep pi's
|
|
209
|
+
* rendering untouched). */
|
|
210
|
+
function restyleEditCallBox(box, theme) {
|
|
211
|
+
const preview = box.preview;
|
|
212
|
+
if (!preview || 'error' in preview || box.children.length < 2)
|
|
213
|
+
return;
|
|
214
|
+
const header = box.children[0];
|
|
215
|
+
box.clear();
|
|
216
|
+
box.addChild(header);
|
|
217
|
+
box.addChild(new Spacer(1));
|
|
218
|
+
// With a successful preview the edit card bg is always toolSuccessBg
|
|
219
|
+
// (mirrors pi's getEditHeaderBg), so tinted lines restore to that color.
|
|
220
|
+
box.addChild(buildDiffBody(preview.diff, theme, theme.getBgAnsi('toolSuccessBg')));
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Attach-viewer tool definition for `edit`: pi's builtin definition drives
|
|
224
|
+
* all state (preview computation, header, success/error bg), then the diff
|
|
225
|
+
* body is re-skinned Claude-Code-style after every render pass.
|
|
226
|
+
*/
|
|
227
|
+
export function createCrtrEditToolDefinition(cwd) {
|
|
228
|
+
const builtin = createEditToolDefinition(cwd);
|
|
229
|
+
return {
|
|
230
|
+
name: 'edit',
|
|
231
|
+
label: 'edit',
|
|
232
|
+
description: 'Render edit diffs with full-line backgrounds in the attach viewer.',
|
|
233
|
+
parameters: {},
|
|
234
|
+
renderShell: 'self',
|
|
235
|
+
renderCall(args, theme, context) {
|
|
236
|
+
const component = builtin.renderCall(args, theme, context);
|
|
237
|
+
const callBox = context.state.callComponent;
|
|
238
|
+
if (callBox)
|
|
239
|
+
restyleEditCallBox(callBox, theme);
|
|
240
|
+
return component;
|
|
241
|
+
},
|
|
242
|
+
renderResult(result, options, theme, context) {
|
|
243
|
+
const component = builtin.renderResult(result, options, theme, context);
|
|
244
|
+
const callBox = context.state.callComponent;
|
|
245
|
+
if (callBox) {
|
|
246
|
+
// builtin renderResult re-ran buildEditCallComponent (pi styling) —
|
|
247
|
+
// restyle the refreshed body.
|
|
248
|
+
restyleEditCallBox(callBox, theme);
|
|
249
|
+
return component;
|
|
250
|
+
}
|
|
251
|
+
// Fallback path (no call box state, e.g. a bare result replay): render
|
|
252
|
+
// the result diff standalone. No host Box here, so restore to default bg.
|
|
253
|
+
const resultDiff = result?.details?.diff;
|
|
254
|
+
if (!context.isError && typeof resultDiff === 'string') {
|
|
255
|
+
const container = new Container();
|
|
256
|
+
container.addChild(new Spacer(1));
|
|
257
|
+
container.addChild(buildDiffBody(resultDiff, theme, '\x1b[49m'));
|
|
258
|
+
return container;
|
|
259
|
+
}
|
|
260
|
+
return component;
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Component, type TUI } from '@earendil-works/pi-tui';
|
|
2
|
+
import { type BindingResolution, type BindingId } from '../../core/keybindings/index.js';
|
|
2
3
|
import { type CanvasSource } from '../../core/canvas/source.js';
|
|
3
4
|
import type { AttachPalette } from './config-load.js';
|
|
4
5
|
export declare class GraphOverlay implements Component {
|
|
@@ -6,6 +7,7 @@ export declare class GraphOverlay implements Component {
|
|
|
6
7
|
private readonly self;
|
|
7
8
|
private readonly getAsks;
|
|
8
9
|
private readonly palette;
|
|
10
|
+
private bindings;
|
|
9
11
|
private readonly source;
|
|
10
12
|
private handle;
|
|
11
13
|
/** Manual fold OVERRIDES (h collapses → userCollapsed, l expands → userExpanded);
|
|
@@ -17,7 +19,7 @@ export declare class GraphOverlay implements Component {
|
|
|
17
19
|
private cursorId;
|
|
18
20
|
/** Viewport scroll offset (row index of the top visible tree row). */
|
|
19
21
|
private scrollTop;
|
|
20
|
-
/** Transient
|
|
22
|
+
/** Transient close confirmation gate. */
|
|
21
23
|
private pendingConfirm;
|
|
22
24
|
private snapshot;
|
|
23
25
|
private snapshotSeq;
|
|
@@ -26,7 +28,15 @@ export declare class GraphOverlay implements Component {
|
|
|
26
28
|
* default and makes Enter/m/x no-op instead of shelling local focus/kill
|
|
27
29
|
* commands against a remote graph row. See the file header. */
|
|
28
30
|
private readonly remote;
|
|
29
|
-
constructor(tui: TUI, self: string, getAsks: () => Record<string, number>, palette: AttachPalette, source?: CanvasSource);
|
|
31
|
+
constructor(tui: TUI, self: string, getAsks: () => Record<string, number>, palette: AttachPalette, bindings: BindingResolution<BindingId>, source?: CanvasSource);
|
|
32
|
+
setBindings(bindings: BindingResolution<BindingId>): void;
|
|
33
|
+
private matches;
|
|
34
|
+
private hint;
|
|
35
|
+
/** Compose footer hints in priority order, dropping WHOLE entries that would
|
|
36
|
+
* overflow `budget` visible columns rather than letting the border mid-clip
|
|
37
|
+
* the tail — so the prioritized head (dismiss + remapped local actions) stays
|
|
38
|
+
* discoverable even in a narrow overlay. */
|
|
39
|
+
private fitHints;
|
|
30
40
|
isOpen(): boolean;
|
|
31
41
|
open(): void;
|
|
32
42
|
close(): void;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// graph-overlay.ts — the
|
|
1
|
+
// graph-overlay.ts — the configurable GRAPH navigator overlay for `crtr surface attach`.
|
|
2
2
|
//
|
|
3
3
|
// Native reimplementation of canvas-nav.ts's GRAPH modal as a pi-tui OVERLAY
|
|
4
4
|
// (the viewer has no pi extension host). `tui.showOverlay(this, …)` mounts this
|
|
@@ -6,12 +6,9 @@
|
|
|
6
6
|
// CAPTURES keyboard focus (mirrors extension-dialogs.ts);
|
|
7
7
|
// `OverlayHandle.hide()` tears it down and restores focus to the editor. While
|
|
8
8
|
// shown, every key routes to handleInput() — a fold-aware NERDTree of the
|
|
9
|
-
// active CanvasSource subscription graph
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// `e` is a tmux-menu prefixBind, not a GRAPH key, so it is swallowed (per
|
|
13
|
-
// canvas-nav). Model/render uses the CanvasSource snapshot built for this
|
|
14
|
-
// overlay; keys are decoded with pi-tui's canonical matchesKey (kitty/CSI-u aware).
|
|
9
|
+
// active CanvasSource subscription graph. Actions and footer hints resolve from
|
|
10
|
+
// the crouter binding catalog; input is decoded with pi-tui's canonical matcher
|
|
11
|
+
// (kitty/CSI-u aware).
|
|
15
12
|
//
|
|
16
13
|
// Enter/m/x SHELL `crtr` out-of-process (node focus / node lifecycle close) — the viewer
|
|
17
14
|
// itself never spawns pi or opens a session (the §0 one-writer invariant). It
|
|
@@ -30,6 +27,7 @@
|
|
|
30
27
|
// graph structure + node metadata ONLY.
|
|
31
28
|
import { execFile } from 'node:child_process';
|
|
32
29
|
import { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';
|
|
30
|
+
import { formatBinding, matchesPiTuiInput, } from '../../core/keybindings/index.js';
|
|
33
31
|
import { fullName } from '../../core/canvas/index.js';
|
|
34
32
|
import { activeFaultForDisplay } from '../../core/canvas/render.js';
|
|
35
33
|
import { readFault } from '../../core/runtime/fault.js';
|
|
@@ -43,18 +41,34 @@ import { beginFrame, navLabel, nodeGlyph, tokensCell, cycleBadge, askBadge, acti
|
|
|
43
41
|
// clipped. margin keeps it off the screen edges.
|
|
44
42
|
const HEIGHT_PCT = 0.72;
|
|
45
43
|
const OVERLAY_OPTIONS = { anchor: 'center', width: '72%', minWidth: 48, maxHeight: '72%', margin: 1 };
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
44
|
+
// The footer is width-budgeted (fitHints drops WHOLE trailing entries that
|
|
45
|
+
// won't fit rather than mid-clipping the tail). List the keys a user most needs
|
|
46
|
+
// to discover first — dismiss (the only way out) then the remapped local
|
|
47
|
+
// actions — so they survive at the narrowest overlay width; routine navigation
|
|
48
|
+
// trails and is the first to be dropped.
|
|
49
|
+
const GRAPH_PRIMARY_LOCAL_HINTS = [
|
|
50
|
+
['crtr.graph.dismiss', 'dismiss'],
|
|
51
|
+
['crtr.graph.focus', 'focus'],
|
|
52
|
+
['crtr.graph.close-subtree', 'close'],
|
|
53
|
+
['crtr.graph.focus-manager', 'manager'],
|
|
54
|
+
];
|
|
55
|
+
const GRAPH_PRIMARY_REMOTE_HINTS = [
|
|
56
|
+
['crtr.graph.dismiss', 'dismiss'],
|
|
57
|
+
];
|
|
58
|
+
const GRAPH_NAV_HINTS = [
|
|
59
|
+
['crtr.graph.down', 'down'],
|
|
60
|
+
['crtr.graph.up', 'up'],
|
|
61
|
+
['crtr.graph.first', 'first'],
|
|
62
|
+
['crtr.graph.last', 'last'],
|
|
63
|
+
['crtr.graph.collapse-or-manager', 'fold/manager'],
|
|
64
|
+
['crtr.graph.expand-or-child', 'expand/child'],
|
|
65
|
+
];
|
|
53
66
|
export class GraphOverlay {
|
|
54
67
|
tui;
|
|
55
68
|
self;
|
|
56
69
|
getAsks;
|
|
57
70
|
palette;
|
|
71
|
+
bindings;
|
|
58
72
|
source;
|
|
59
73
|
handle;
|
|
60
74
|
/** Manual fold OVERRIDES (h collapses → userCollapsed, l expands → userExpanded);
|
|
@@ -66,7 +80,7 @@ export class GraphOverlay {
|
|
|
66
80
|
cursorId;
|
|
67
81
|
/** Viewport scroll offset (row index of the top visible tree row). */
|
|
68
82
|
scrollTop = 0;
|
|
69
|
-
/** Transient
|
|
83
|
+
/** Transient close confirmation gate. */
|
|
70
84
|
pendingConfirm;
|
|
71
85
|
snapshot;
|
|
72
86
|
snapshotSeq = 0;
|
|
@@ -75,14 +89,43 @@ export class GraphOverlay {
|
|
|
75
89
|
* default and makes Enter/m/x no-op instead of shelling local focus/kill
|
|
76
90
|
* commands against a remote graph row. See the file header. */
|
|
77
91
|
remote;
|
|
78
|
-
constructor(tui, self, getAsks, palette, source = localCanvasSource) {
|
|
92
|
+
constructor(tui, self, getAsks, palette, bindings, source = localCanvasSource) {
|
|
79
93
|
this.tui = tui;
|
|
80
94
|
this.self = self;
|
|
81
95
|
this.getAsks = getAsks;
|
|
82
96
|
this.palette = palette;
|
|
97
|
+
this.bindings = bindings;
|
|
83
98
|
this.source = source;
|
|
84
99
|
this.remote = source instanceof RemoteCanvasSource;
|
|
85
100
|
}
|
|
101
|
+
setBindings(bindings) {
|
|
102
|
+
this.bindings = bindings;
|
|
103
|
+
if (this.handle !== undefined)
|
|
104
|
+
this.tui.requestRender();
|
|
105
|
+
}
|
|
106
|
+
matches(id, data) {
|
|
107
|
+
return matchesPiTuiInput(this.bindings, id, data, (input, gesture) => matchesKey(input, gesture));
|
|
108
|
+
}
|
|
109
|
+
hint(entries) {
|
|
110
|
+
return entries.flatMap(([id, label]) => this.bindings.gestures(id).length === 0 ? [] : [`${formatBinding(this.bindings, id)} ${label}`]);
|
|
111
|
+
}
|
|
112
|
+
/** Compose footer hints in priority order, dropping WHOLE entries that would
|
|
113
|
+
* overflow `budget` visible columns rather than letting the border mid-clip
|
|
114
|
+
* the tail — so the prioritized head (dismiss + remapped local actions) stays
|
|
115
|
+
* discoverable even in a narrow overlay. */
|
|
116
|
+
fitHints(entries, budget) {
|
|
117
|
+
const SEP = 3; // visible width of ' · '
|
|
118
|
+
const out = [];
|
|
119
|
+
let used = 0;
|
|
120
|
+
for (const piece of this.hint(entries)) {
|
|
121
|
+
const add = (out.length === 0 ? 0 : SEP) + visibleWidth(piece);
|
|
122
|
+
if (used + add > budget)
|
|
123
|
+
break;
|
|
124
|
+
out.push(piece);
|
|
125
|
+
used += add;
|
|
126
|
+
}
|
|
127
|
+
return out.join(' · ');
|
|
128
|
+
}
|
|
86
129
|
isOpen() {
|
|
87
130
|
return this.handle !== undefined;
|
|
88
131
|
}
|
|
@@ -341,8 +384,14 @@ export class GraphOverlay {
|
|
|
341
384
|
? `${BOLD}⌗ canvas graph${RESET} ${DIM}(${cursorIdx + 1}/${rows.length})${RESET}`
|
|
342
385
|
: `${BOLD}⌗ canvas graph${RESET} ${DIM}(loading)${RESET}`;
|
|
343
386
|
const hint = this.pendingConfirm !== undefined
|
|
344
|
-
? `${YELLOW}${this.pendingConfirm.label} ${BOLD}
|
|
345
|
-
|
|
387
|
+
? `${YELLOW}${this.pendingConfirm.label} ${BOLD}${this.hint([
|
|
388
|
+
['crtr.graph.confirm.accept', 'accept'],
|
|
389
|
+
['crtr.graph.confirm.cancel', 'cancel'],
|
|
390
|
+
]).join(' · ')}${RESET}`
|
|
391
|
+
: `${DIM}${this.fitHints([
|
|
392
|
+
...(this.remote ? GRAPH_PRIMARY_REMOTE_HINTS : GRAPH_PRIMARY_LOCAL_HINTS),
|
|
393
|
+
...GRAPH_NAV_HINTS,
|
|
394
|
+
], Math.max(1, width - 5))}${RESET}`; // budget matches borderRow's span
|
|
346
395
|
// Frame the panel: title in the top border, hint in the bottom border, tree
|
|
347
396
|
// rows boxed with themed side bars (truncate/pad to the inner width so the
|
|
348
397
|
// right edge stays flush and opaque).
|
|
@@ -385,18 +434,19 @@ export class GraphOverlay {
|
|
|
385
434
|
dispatch(data) {
|
|
386
435
|
// y/n confirm gate takes precedence over everything.
|
|
387
436
|
if (this.pendingConfirm !== undefined) {
|
|
388
|
-
if (
|
|
437
|
+
if (this.matches('crtr.graph.confirm.accept', data)) {
|
|
389
438
|
const act = this.pendingConfirm.action;
|
|
390
439
|
this.pendingConfirm = undefined;
|
|
391
440
|
act();
|
|
392
441
|
}
|
|
393
442
|
else {
|
|
394
|
-
|
|
443
|
+
// Preserve GRAPH's modal fallback: any non-accept key cancels close.
|
|
444
|
+
this.pendingConfirm = undefined;
|
|
395
445
|
}
|
|
396
446
|
this.tui.requestRender();
|
|
397
447
|
return;
|
|
398
448
|
}
|
|
399
|
-
if (
|
|
449
|
+
if (this.matches('crtr.graph.dismiss', data)) {
|
|
400
450
|
this.close();
|
|
401
451
|
return;
|
|
402
452
|
}
|
|
@@ -427,29 +477,29 @@ export class GraphOverlay {
|
|
|
427
477
|
}
|
|
428
478
|
return idx;
|
|
429
479
|
};
|
|
430
|
-
if (
|
|
480
|
+
if (this.matches('crtr.graph.down', data)) {
|
|
431
481
|
idx = step(idx, 1);
|
|
432
482
|
this.cursorId = rows[idx]?.id ?? this.cursorId;
|
|
433
483
|
this.tui.requestRender();
|
|
434
484
|
return;
|
|
435
485
|
}
|
|
436
|
-
if (
|
|
486
|
+
if (this.matches('crtr.graph.up', data)) {
|
|
437
487
|
idx = step(idx, -1);
|
|
438
488
|
this.cursorId = rows[idx]?.id ?? this.cursorId;
|
|
439
489
|
this.tui.requestRender();
|
|
440
490
|
return;
|
|
441
491
|
}
|
|
442
|
-
if (
|
|
492
|
+
if (this.matches('crtr.graph.first', data)) {
|
|
443
493
|
this.cursorId = rows[edge(1)]?.id ?? this.cursorId;
|
|
444
494
|
this.tui.requestRender();
|
|
445
495
|
return;
|
|
446
496
|
}
|
|
447
|
-
if (
|
|
497
|
+
if (this.matches('crtr.graph.last', data)) {
|
|
448
498
|
this.cursorId = rows[edge(-1)]?.id ?? this.cursorId;
|
|
449
499
|
this.tui.requestRender();
|
|
450
500
|
return;
|
|
451
501
|
}
|
|
452
|
-
if (
|
|
502
|
+
if (this.matches('crtr.graph.collapse-or-manager', data)) {
|
|
453
503
|
if (cur !== undefined && cur.hasKids && !cur.collapsed) {
|
|
454
504
|
this.userCollapsed.add(cur.id);
|
|
455
505
|
this.userExpanded.delete(cur.id);
|
|
@@ -463,7 +513,7 @@ export class GraphOverlay {
|
|
|
463
513
|
}
|
|
464
514
|
return;
|
|
465
515
|
}
|
|
466
|
-
if (
|
|
516
|
+
if (this.matches('crtr.graph.expand-or-child', data)) {
|
|
467
517
|
if (cur !== undefined && cur.collapsed && cur.hasKids) {
|
|
468
518
|
this.userExpanded.add(cur.id);
|
|
469
519
|
this.userCollapsed.delete(cur.id);
|
|
@@ -477,12 +527,12 @@ export class GraphOverlay {
|
|
|
477
527
|
}
|
|
478
528
|
return;
|
|
479
529
|
}
|
|
480
|
-
//
|
|
530
|
+
// Focus/manager/close actions drive local lifecycle/focus (`crtr node focus` / `crtr node
|
|
481
531
|
// lifecycle close`) against `this.cursorId` — a REMOTE graph row's id has no
|
|
482
532
|
// relationship to this machine's canvas.db (or, worse, collides with one
|
|
483
533
|
// that does), so all three no-op in remote mode instead of shelling a local
|
|
484
|
-
// mutation command against a remote id.
|
|
485
|
-
if (
|
|
534
|
+
// mutation command against a remote id. Their hints are omitted remotely.
|
|
535
|
+
if (this.matches('crtr.graph.focus', data)) {
|
|
486
536
|
if (this.remote)
|
|
487
537
|
return;
|
|
488
538
|
if (this.cursorId !== undefined)
|
|
@@ -490,7 +540,7 @@ export class GraphOverlay {
|
|
|
490
540
|
this.close();
|
|
491
541
|
return;
|
|
492
542
|
}
|
|
493
|
-
if (
|
|
543
|
+
if (this.matches('crtr.graph.focus-manager', data)) {
|
|
494
544
|
if (this.remote)
|
|
495
545
|
return;
|
|
496
546
|
const mgr = snapshot?.managerOf.get(this.self);
|
|
@@ -500,7 +550,7 @@ export class GraphOverlay {
|
|
|
500
550
|
}
|
|
501
551
|
return;
|
|
502
552
|
}
|
|
503
|
-
if (
|
|
553
|
+
if (this.matches('crtr.graph.close-subtree', data)) {
|
|
504
554
|
if (this.remote)
|
|
505
555
|
return;
|
|
506
556
|
const target = this.cursorId ?? this.self;
|
|
@@ -510,7 +560,7 @@ export class GraphOverlay {
|
|
|
510
560
|
this.tui.requestRender();
|
|
511
561
|
return;
|
|
512
562
|
}
|
|
513
|
-
// Modal: swallow everything else
|
|
563
|
+
// Modal: swallow everything else so a stray
|
|
514
564
|
// key never reaches the editor underneath.
|
|
515
565
|
}
|
|
516
566
|
// -------------------------------------------------------------------------
|
|
@@ -30,6 +30,10 @@ export interface InputControllerHooks {
|
|
|
30
30
|
/** OPTIONAL: copy the last assistant message to the clipboard — forwarded to
|
|
31
31
|
* the slash context so `/copy` works. Wired by attach-cmd (it owns ChatView). */
|
|
32
32
|
onCopy?: () => void;
|
|
33
|
+
/** OPTIONAL: recolor the editor's name chip — forwarded to the slash context
|
|
34
|
+
* so `/color` paints it (`null` clears). Wired by attach-cmd (it owns the
|
|
35
|
+
* TitledEditor). */
|
|
36
|
+
onColor?: (color: string | null) => void;
|
|
33
37
|
/** OPTIONAL: Ctrl+O / app.tools.expand — flip global tool-output expansion in
|
|
34
38
|
* the render layer (ChatView owns the components). Absent → the key no-ops. */
|
|
35
39
|
onToggleToolsExpand?: () => void;
|
|
@@ -51,6 +55,13 @@ export interface InputControllerHooks {
|
|
|
51
55
|
openLoginPicker?: () => void;
|
|
52
56
|
/** OPTIONAL: open the native /logout provider picker (viewer-local auth flow). */
|
|
53
57
|
openLogoutPicker?: () => void;
|
|
58
|
+
/** OPTIONAL: an EXPLICIT model choice was just issued (native /model picker
|
|
59
|
+
* select, or `/model <arg>`) — fired alongside the `set_model` frame, with
|
|
60
|
+
* the exact string requested. attach-cmd arms its persist-as-default write
|
|
61
|
+
* on this and completes it when the broker's `model_changed` confirms the
|
|
62
|
+
* switch (so a rejected spec never gets persisted). Absent → picks stay
|
|
63
|
+
* session-only (e.g. a remote attach, which must not write local config). */
|
|
64
|
+
onExplicitModelSet?: (model: string) => void;
|
|
54
65
|
}
|
|
55
66
|
export declare class InputController {
|
|
56
67
|
private readonly tui;
|
|
@@ -59,6 +70,10 @@ export declare class InputController {
|
|
|
59
70
|
private readonly hooks;
|
|
60
71
|
/** The currently-rendered blocking dialog, if any (for supersede/dismiss). */
|
|
61
72
|
private dialog;
|
|
73
|
+
/** The request id of {@link dialog} — so a broker-driven `extension_ui_dismiss`
|
|
74
|
+
* tears down ONLY the matching overlay (correlated dismissal), never an
|
|
75
|
+
* unrelated one. */
|
|
76
|
+
private dialogId;
|
|
62
77
|
/** The currently-open native picker overlay, if any (for supersede/dismiss). */
|
|
63
78
|
private picker;
|
|
64
79
|
/** Monotonic open counter — a slow read-op reply whose `seq` is no longer the
|
|
@@ -75,9 +90,17 @@ export declare class InputController {
|
|
|
75
90
|
private emitCommand;
|
|
76
91
|
/** Render extension UI requests from the broker. Blocking dialogs route their
|
|
77
92
|
* answer back to the broker; non-blocking notify requests become the viewer's
|
|
78
|
-
* normal notice line
|
|
79
|
-
*
|
|
93
|
+
* normal notice line and NOTHING else — a notify must never tear down an
|
|
94
|
+
* unrelated blocking dialog whose broker request is still pending. A dialog is
|
|
95
|
+
* torn down only by (a) a NEW blocking dialog superseding it (control handoff
|
|
96
|
+
* re-route), (b) its own answer, or (c) a correlated {@link dismissDialog}
|
|
97
|
+
* from the broker (the request was aborted/timed out out-of-band). */
|
|
80
98
|
attachDialog(req: RpcExtensionUIRequest): void;
|
|
99
|
+
/** Broker-driven correlated dismissal: the broker resolved this request itself
|
|
100
|
+
* (its request was aborted out-of-band — e.g. a local OAuth loopback callback
|
|
101
|
+
* won the race against this manual-paste dialog — or its timeout fired). Tear
|
|
102
|
+
* down ONLY the overlay whose id matches; leave any other dialog untouched. */
|
|
103
|
+
dismissDialog(id: string): void;
|
|
81
104
|
/** Feed the latest engine state so read-only commands (e.g. `/session`) report
|
|
82
105
|
* current values. Optional; the controller works without it. */
|
|
83
106
|
setState(state: BrokerSnapshot['state']): void;
|
|
@@ -100,6 +123,9 @@ export declare class InputController {
|
|
|
100
123
|
* once we focus an inner child. A throwing builder is caught into a notice (m5). */
|
|
101
124
|
private showPicker;
|
|
102
125
|
private openModelPicker;
|
|
126
|
+
/** Unlike the other pickers, the session picker opens IMMEDIATELY — its
|
|
127
|
+
* component owns async loaders (with a Loading header), so the multi-second
|
|
128
|
+
* session-dir scan runs off the open path instead of blocking it. */
|
|
103
129
|
private openSessionPicker;
|
|
104
130
|
private openTreePicker;
|
|
105
131
|
private openForkPicker;
|