@crouton-kit/crouter 0.3.68 → 0.3.78
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/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-pi-packages/pi-crtr-extensions/README.md +13 -34
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +639 -741
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +241 -58
- 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 +365 -94
- 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/cli.d.ts +1 -1
- package/dist/cli.js +1 -0
- package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +11 -7
- 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 +20 -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 +1 -1
- package/dist/clients/attach/attach-cmd.js +733 -730
- package/dist/clients/attach/auth-pickers.d.ts +12 -0
- package/dist/clients/attach/auth-pickers.js +11 -0
- 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/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 +13 -17
- package/dist/clients/attach/slash-commands.d.ts +9 -0
- package/dist/clients/attach/slash-commands.js +128 -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/memory/lint.js +39 -5
- package/dist/commands/memory/write.js +1 -0
- package/dist/commands/node.js +12 -5
- package/dist/commands/profile/new.js +30 -5
- 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/config.js +18 -21
- package/dist/commands/sys/setup-core.d.ts +12 -1
- package/dist/commands/sys/setup-core.js +25 -3
- package/dist/commands/sys/setup.js +166 -20
- 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__/fault-classifier.test.js +15 -0
- package/dist/core/__tests__/full/broker-dialogs.test.js +7 -2
- package/dist/core/__tests__/full/broker-pane-resolution.test.js +9 -6
- package/dist/core/__tests__/full/cascade-close.test.js +5 -2
- package/dist/core/__tests__/full/dead-pane-regression.test.js +6 -3
- package/dist/core/__tests__/full/detach-focus.test.js +10 -5
- package/dist/core/__tests__/full/human-new-window-regression.test.js +6 -3
- package/dist/core/__tests__/full/review-render-pane-regression.test.js +2 -2
- package/dist/core/__tests__/helpers/harness.d.ts +1 -0
- package/dist/core/__tests__/helpers/harness.js +28 -4
- package/dist/core/__tests__/host-teardown-process-group.test.js +15 -4
- package/dist/core/__tests__/live-mutation-verbs.test.js +5 -4
- package/dist/core/__tests__/review-model-floor.test.js +1 -1
- 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/__tests__/remote-event-stream.test.js +11 -9
- package/dist/core/canvas/__tests__/render-remote.test.js +7 -4
- package/dist/core/canvas/browse/__tests__/model.test.js +23 -5
- package/dist/core/canvas/browse/__tests__/rebuild-coalescer.test.js +30 -13
- package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
- package/dist/core/canvas/browse/app.js +164 -5
- 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/canvas/pid.d.ts +14 -10
- package/dist/core/canvas/pid.js +14 -10
- package/dist/core/config.d.ts +53 -2
- 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__/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 +256 -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/runtime/broker-protocol.d.ts +33 -5
- package/dist/core/runtime/broker.js +165 -14
- package/dist/core/runtime/launch.d.ts +39 -6
- package/dist/core/runtime/launch.js +79 -21
- 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/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 +47 -44
- package/dist/core/runtime/recycle.js +6 -1
- package/dist/core/runtime/reset.js +5 -0
- package/dist/core/runtime/revive.js +28 -18
- package/dist/core/runtime/session-list-cache.d.ts +23 -0
- package/dist/core/runtime/session-list-cache.js +270 -0
- package/dist/core/runtime/spawn.js +51 -32
- 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 +216 -110
- 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/__tests__/transport-remote.test.js +9 -6
- package/dist/core/view/contract.d.ts +17 -3
- package/dist/daemon/crtrd-cli.d.ts +1 -1
- package/dist/daemon/crtrd-cli.js +1 -0
- 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 +38 -37
- package/dist/pi-extensions/canvas-stophook.d.ts +11 -0
- package/dist/pi-extensions/canvas-stophook.js +33 -4
- package/dist/suppress-experimental-warnings.d.ts +1 -0
- package/dist/suppress-experimental-warnings.js +15 -0
- package/dist/types.d.ts +3 -13
- package/dist/types.js +12 -36
- package/dist/web-client/assets/{index-CnF5r8ky.js → index--md2ylfi.js} +18 -17
- package/dist/web-client/assets/index-BUdm9s9s.css +2 -0
- package/dist/web-client/index.html +3 -3
- package/dist/web-client/sw.js +1 -1
- package/package.json +11 -8
- package/scripts/postinstall.mjs +1 -1
- 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
- package/dist/web-client/assets/index-BnmSLNLa.css +0 -2
|
@@ -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
|
+
}
|
|
@@ -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;
|
|
@@ -52,7 +52,7 @@ const MAX_FRAME_BYTES = BROKER_READ_CAPS.maxLineBytes - 4 * 1024 * 1024;
|
|
|
52
52
|
* see slash-commands.ts's REMOTE_SAFE_BUILTIN_NAMES gate). */
|
|
53
53
|
const DRIVE_FRAME_TYPES = new Set([
|
|
54
54
|
'prompt', 'steer', 'follow_up', 'abort', 'bash',
|
|
55
|
-
'set_model', 'cycle_model', 'cycle_thinking', 'set_thinking_level', 'dequeue',
|
|
55
|
+
'set_model', 'cycle_model', 'cycle_ladder', 'cycle_thinking', 'set_thinking_level', 'dequeue',
|
|
56
56
|
'set_auto_retry', 'set_auto_compaction', 'compact', 'new_session',
|
|
57
57
|
'switch_session', 'fork', 'set_session_name', 'navigate_tree', 'reload',
|
|
58
58
|
'export', 'clone', 'share', 'request_control', 'release_control', 'reload_auth',
|
|
@@ -64,6 +64,10 @@ export class InputController {
|
|
|
64
64
|
hooks;
|
|
65
65
|
/** The currently-rendered blocking dialog, if any (for supersede/dismiss). */
|
|
66
66
|
dialog;
|
|
67
|
+
/** The request id of {@link dialog} — so a broker-driven `extension_ui_dismiss`
|
|
68
|
+
* tears down ONLY the matching overlay (correlated dismissal), never an
|
|
69
|
+
* unrelated one. */
|
|
70
|
+
dialogId;
|
|
67
71
|
/** The currently-open native picker overlay, if any (for supersede/dismiss). */
|
|
68
72
|
picker;
|
|
69
73
|
/** Monotonic open counter — a slow read-op reply whose `seq` is no longer the
|
|
@@ -92,8 +96,11 @@ export class InputController {
|
|
|
92
96
|
}
|
|
93
97
|
/** Render extension UI requests from the broker. Blocking dialogs route their
|
|
94
98
|
* answer back to the broker; non-blocking notify requests become the viewer's
|
|
95
|
-
* normal notice line
|
|
96
|
-
*
|
|
99
|
+
* normal notice line and NOTHING else — a notify must never tear down an
|
|
100
|
+
* unrelated blocking dialog whose broker request is still pending. A dialog is
|
|
101
|
+
* torn down only by (a) a NEW blocking dialog superseding it (control handoff
|
|
102
|
+
* re-route), (b) its own answer, or (c) a correlated {@link dismissDialog}
|
|
103
|
+
* from the broker (the request was aborted/timed out out-of-band). */
|
|
97
104
|
attachDialog(req) {
|
|
98
105
|
if (req.method === 'notify') {
|
|
99
106
|
const prefix = req.notifyType === 'error' ? 'Error: ' : req.notifyType === 'warning' ? 'Warning: ' : '';
|
|
@@ -102,12 +109,26 @@ export class InputController {
|
|
|
102
109
|
return;
|
|
103
110
|
}
|
|
104
111
|
this.dialog?.dismiss();
|
|
112
|
+
this.dialogId = req.id;
|
|
105
113
|
this.dialog = renderDialog(this.tui, req, (resp) => {
|
|
106
114
|
this.dialog = undefined;
|
|
115
|
+
this.dialogId = undefined;
|
|
107
116
|
this.hooks.onDialogResponse(resp);
|
|
108
117
|
}, this.keybindings);
|
|
109
118
|
this.tui.requestRender();
|
|
110
119
|
}
|
|
120
|
+
/** Broker-driven correlated dismissal: the broker resolved this request itself
|
|
121
|
+
* (its request was aborted out-of-band — e.g. a local OAuth loopback callback
|
|
122
|
+
* won the race against this manual-paste dialog — or its timeout fired). Tear
|
|
123
|
+
* down ONLY the overlay whose id matches; leave any other dialog untouched. */
|
|
124
|
+
dismissDialog(id) {
|
|
125
|
+
if (this.dialogId !== id)
|
|
126
|
+
return;
|
|
127
|
+
this.dialog?.dismiss();
|
|
128
|
+
this.dialog = undefined;
|
|
129
|
+
this.dialogId = undefined;
|
|
130
|
+
this.tui.requestRender();
|
|
131
|
+
}
|
|
111
132
|
/** Feed the latest engine state so read-only commands (e.g. `/session`) report
|
|
112
133
|
* current values. Optional; the controller works without it. */
|
|
113
134
|
setState(state) {
|
|
@@ -174,6 +195,7 @@ export class InputController {
|
|
|
174
195
|
onGraph: this.hooks.onGraph,
|
|
175
196
|
onQuit: this.hooks.onQuit,
|
|
176
197
|
onCopy: this.hooks.onCopy,
|
|
198
|
+
onColor: this.hooks.onColor,
|
|
177
199
|
openModelPicker: () => void this.openModelPicker(),
|
|
178
200
|
openSessionPicker: () => void this.openSessionPicker(),
|
|
179
201
|
openForkPicker: () => void this.openForkPicker(),
|
|
@@ -182,6 +204,7 @@ export class InputController {
|
|
|
182
204
|
openScopedModelsPicker: () => void this.openScopedModelsPicker(),
|
|
183
205
|
openLoginPicker: this.hooks.openLoginPicker ? () => this.hooks.openLoginPicker() : undefined,
|
|
184
206
|
openLogoutPicker: this.hooks.openLogoutPicker ? () => this.hooks.openLogoutPicker() : undefined,
|
|
207
|
+
onExplicitModelSet: this.hooks.onExplicitModelSet,
|
|
185
208
|
};
|
|
186
209
|
}
|
|
187
210
|
// -------------------------------------------------------------------------
|
|
@@ -299,10 +322,20 @@ export class InputController {
|
|
|
299
322
|
this.tui.requestRender();
|
|
300
323
|
}
|
|
301
324
|
openModelPicker() {
|
|
302
|
-
return this.openPicker({ type: 'list_models' }, 'list_models', 'model', (data, close) => buildModelPicker(this.tui, data, this.send, close));
|
|
325
|
+
return this.openPicker({ type: 'list_models' }, 'list_models', 'model', (data, close) => buildModelPicker(this.tui, data, this.send, close, this.hooks.onExplicitModelSet));
|
|
303
326
|
}
|
|
327
|
+
/** Unlike the other pickers, the session picker opens IMMEDIATELY — its
|
|
328
|
+
* component owns async loaders (with a Loading header), so the multi-second
|
|
329
|
+
* session-dir scan runs off the open path instead of blocking it. */
|
|
304
330
|
openSessionPicker() {
|
|
305
|
-
|
|
331
|
+
const request = this.hooks.onRequest;
|
|
332
|
+
if (request === undefined) {
|
|
333
|
+
this.notify("The session picker isn't available in this viewer");
|
|
334
|
+
return Promise.resolve();
|
|
335
|
+
}
|
|
336
|
+
this.openSeq++; // supersede any in-flight fetch-first picker open
|
|
337
|
+
this.showPicker((controls) => buildSessionPicker(this.tui, this.state?.sessionFile, request, this.keybindings, this.send, controls.close));
|
|
338
|
+
return Promise.resolve();
|
|
306
339
|
}
|
|
307
340
|
openTreePicker() {
|
|
308
341
|
return this.openPicker({ type: 'get_tree' }, 'get_tree', 'tree', (data, close) => buildTreePicker(this.tui, data, this.send, close));
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
//
|
|
19
19
|
// Scope (v1): only ```mermaid fences. The wider termrender directive vocabulary
|
|
20
20
|
// is deliberately out of scope — see the base prompt's Diagrams note.
|
|
21
|
-
import { isRendererReady, renderMarkdown } from '@crouton-kit/humanloop';
|
|
21
|
+
import { ensureRenderer, isRendererReady, renderMarkdown } from '@crouton-kit/humanloop';
|
|
22
22
|
/** A complete mermaid fence: ```mermaid (opening info string) ... closing ```.
|
|
23
23
|
* Line-anchored (m flag), non-greedy body (captured). An UNCLOSED fence (still
|
|
24
24
|
* streaming) does not match, so mid-stream text is left raw and only settles to
|
|
@@ -71,7 +71,10 @@ function stripBlankEdges(lines) {
|
|
|
71
71
|
* diagrams have no width control and overflow rather than truncate), which is
|
|
72
72
|
* stripped here, so an approximate pane width is fine. */
|
|
73
73
|
export function transformMermaidFences(text, width) {
|
|
74
|
-
if (!textHasMermaid(text)
|
|
74
|
+
if (!textHasMermaid(text))
|
|
75
|
+
return text;
|
|
76
|
+
ensureRenderer();
|
|
77
|
+
if (!isRendererReady())
|
|
75
78
|
return text;
|
|
76
79
|
return text.replace(MERMAID_FENCE, (whole, body) => {
|
|
77
80
|
try {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Component, type KeybindingsManager, type TUI } from '@earendil-works/pi-tui';
|
|
2
|
-
import type { ClientToBroker, GetSettingsData, GetTreeData, ListModelsData, ListScopedModelsData
|
|
2
|
+
import type { ClientToBroker, GetSettingsData, GetTreeData, ListModelsData, ListScopedModelsData } from '../../core/runtime/broker-protocol.js';
|
|
3
3
|
import type { ReadOpRequest } from './view-socket.js';
|
|
4
4
|
/** A built picker overlay: the `component` to show, and the `focus` target the
|
|
5
5
|
* caller must `setFocus` (the component itself, or the inner list that owns
|
|
@@ -28,12 +28,13 @@ type Notify = (message: string) => void;
|
|
|
28
28
|
/** `/model` (and ctrl+l) — the real model selector with search + scope toggle.
|
|
29
29
|
* Select → `set_model` with the resolved `provider/id` (the broker's
|
|
30
30
|
* `findModelSpec` requires that form). */
|
|
31
|
-
export declare function buildModelPicker(tui: TUI, data: ListModelsData, send: Send, close: Close): Picker;
|
|
32
|
-
/** `/resume` — the real session selector.
|
|
33
|
-
* fetch each scope on demand
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
|
|
31
|
+
export declare function buildModelPicker(tui: TUI, data: ListModelsData, send: Send, close: Close, onSelect?: (model: string) => void): Picker;
|
|
32
|
+
/** `/resume` — the real session selector. Opens INSTANTLY and reconstructs from
|
|
33
|
+
* async loaders that fetch each scope on demand — the component's own Loading
|
|
34
|
+
* header covers the scan (listing a large session dir takes seconds, so the
|
|
35
|
+
* fetch must stay off the open path). Select → `switch_session`. Rename/delete
|
|
36
|
+
* are out of scope (no command frame). */
|
|
37
|
+
export declare function buildSessionPicker(tui: TUI, currentSessionFile: string | undefined, request: Request, keybindings: KeybindingsManager, send: Send, close: Close): Picker;
|
|
37
38
|
/** `/tree` — the real tree navigator (pure data). Select → `navigate_tree`.
|
|
38
39
|
* Label-edit is out of scope (no command frame) → no `onLabelChange`. */
|
|
39
40
|
export declare function buildTreePicker(tui: TUI, data: GetTreeData, send: Send, close: Close): Picker;
|
|
@@ -51,7 +51,7 @@ function modelRegistryAdapter(all, available) {
|
|
|
51
51
|
/** `/model` (and ctrl+l) — the real model selector with search + scope toggle.
|
|
52
52
|
* Select → `set_model` with the resolved `provider/id` (the broker's
|
|
53
53
|
* `findModelSpec` requires that form). */
|
|
54
|
-
export function buildModelPicker(tui, data, send, close) {
|
|
54
|
+
export function buildModelPicker(tui, data, send, close, onSelect) {
|
|
55
55
|
const all = data.models;
|
|
56
56
|
const availableSet = new Set(data.availableIds);
|
|
57
57
|
const available = all.filter((m) => availableSet.has(`${m.provider}/${m.id}`));
|
|
@@ -60,29 +60,23 @@ export function buildModelPicker(tui, data, send, close) {
|
|
|
60
60
|
: undefined;
|
|
61
61
|
const component = new ModelSelectorComponent(tui, current, SettingsManager.inMemory(), modelRegistryAdapter(all, available), data.scopedModels, (model) => {
|
|
62
62
|
send({ type: 'set_model', model: `${model.provider}/${model.id}` });
|
|
63
|
+
onSelect?.(`${model.provider}/${model.id}`);
|
|
63
64
|
close();
|
|
64
65
|
}, () => close());
|
|
65
66
|
return { component, focus: component };
|
|
66
67
|
}
|
|
67
|
-
/** `/resume` — the real session selector.
|
|
68
|
-
* fetch each scope on demand
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
|
|
68
|
+
/** `/resume` — the real session selector. Opens INSTANTLY and reconstructs from
|
|
69
|
+
* async loaders that fetch each scope on demand — the component's own Loading
|
|
70
|
+
* header covers the scan (listing a large session dir takes seconds, so the
|
|
71
|
+
* fetch must stay off the open path). Select → `switch_session`. Rename/delete
|
|
72
|
+
* are out of scope (no command frame). */
|
|
73
|
+
export function buildSessionPicker(tui, currentSessionFile, request, keybindings, send, close) {
|
|
72
74
|
const revive = (d) => d.sessions.map((s) => ({ ...s, created: new Date(s.created), modified: new Date(s.modified) }));
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
if (firstCwd) {
|
|
76
|
-
firstCwd = false;
|
|
77
|
-
return revive(prefetchedCwd);
|
|
78
|
-
}
|
|
79
|
-
return revive((await request({ type: 'list_sessions', scope: 'cwd' })));
|
|
80
|
-
};
|
|
81
|
-
const allLoader = async () => revive((await request({ type: 'list_sessions', scope: 'all' })));
|
|
82
|
-
const component = new SessionSelectorComponent(currentLoader, allLoader, (path) => {
|
|
75
|
+
const loader = (scope) => async () => revive((await request({ type: 'list_sessions', scope })));
|
|
76
|
+
const component = new SessionSelectorComponent(loader('cwd'), loader('all'), (path) => {
|
|
83
77
|
send({ type: 'switch_session', path });
|
|
84
78
|
close();
|
|
85
|
-
}, () => close(), () => close(), () => tui.requestRender(), { showRenameHint: false, keybindings: keybindings },
|
|
79
|
+
}, () => close(), () => close(), () => tui.requestRender(), { showRenameHint: false, keybindings: keybindings }, currentSessionFile);
|
|
86
80
|
return { component, focus: component };
|
|
87
81
|
}
|
|
88
82
|
/** `/tree` — the real tree navigator (pure data). Select → `navigate_tree`.
|
|
@@ -141,6 +135,8 @@ export function buildSettingsPicker(data, send, close, notify) {
|
|
|
141
135
|
onClearOnShrinkChange: out,
|
|
142
136
|
onShowTerminalProgressChange: out,
|
|
143
137
|
onWarningsChange: out,
|
|
138
|
+
onShowCacheMissNoticesChange: out,
|
|
139
|
+
onOutputPadChange: out,
|
|
144
140
|
};
|
|
145
141
|
const component = new SettingsSelectorComponent(data.settings, callbacks);
|
|
146
142
|
return { component, focus: component.getSettingsList() };
|
|
@@ -46,10 +46,19 @@ export interface SlashContext {
|
|
|
46
46
|
openTreePicker?: () => void;
|
|
47
47
|
openSettingsPicker?: () => void;
|
|
48
48
|
openScopedModelsPicker?: () => void;
|
|
49
|
+
/** An EXPLICIT model choice was just issued via `/model <arg>` — mirrors
|
|
50
|
+
* `InputControllerHooks.onExplicitModelSet` (the picker path fires it from
|
|
51
|
+
* `buildModelPicker`). attach-cmd persists the choice as the node kind's
|
|
52
|
+
* default once the broker confirms with `model_changed`. */
|
|
53
|
+
onExplicitModelSet?: (model: string) => void;
|
|
49
54
|
/** Open the native /login provider selector. */
|
|
50
55
|
openLoginPicker?: () => void;
|
|
51
56
|
/** Open the native /logout provider selector. */
|
|
52
57
|
openLogoutPicker?: () => void;
|
|
58
|
+
/** `/color` — recolor the editor's name chip (the "bubble" /rename names).
|
|
59
|
+
* `null` clears back to the default chip. Wired by attach-cmd (it owns the
|
|
60
|
+
* TitledEditor); absent → only the tmux pane outline is painted. */
|
|
61
|
+
onColor?: (color: string | null) => void;
|
|
53
62
|
}
|
|
54
63
|
/** Canvas slash-commands — implemented NATIVE in the viewer, the ONLY live host:
|
|
55
64
|
* the broker binds mode:'print', so broker pi-extensions never run their
|