@deepseek-harness-tui/dsh-tui 0.7.3 → 0.8.0
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 +2 -2
- package/bin/dsh-tui.js +19 -3
- package/cordis.patch.yml +24 -11
- package/cordis.yml +9 -1
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +2 -1
- package/lib/types/components/MessageList.d.ts +3 -1
- package/lib/types/components/MessageList.d.ts.map +1 -1
- package/lib/types/components/MessageList.js +4 -4
- package/lib/types/components/PluginSceneBoundary.d.ts +31 -0
- package/lib/types/components/PluginSceneBoundary.d.ts.map +1 -0
- package/lib/types/components/PluginSceneBoundary.js +29 -0
- package/lib/types/components/SkillsPicker.d.ts +18 -0
- package/lib/types/components/SkillsPicker.d.ts.map +1 -0
- package/lib/types/components/SkillsPicker.js +50 -0
- package/lib/types/components/SplitDiffView.d.ts +28 -0
- package/lib/types/components/SplitDiffView.d.ts.map +1 -0
- package/lib/types/components/SplitDiffView.js +417 -0
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts +3 -1
- package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
- package/lib/types/components/messages/AssistantToolUseMessage.js +17 -7
- package/lib/types/dsh-adapter/channel.d.ts +68 -0
- package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
- package/lib/types/dsh-adapter/channel.js +120 -5
- package/lib/types/dsh-adapter/contract.d.ts +1 -1
- package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
- package/lib/types/dsh-adapter/contract.js +1 -0
- package/lib/types/dsh-adapter/index.d.ts +4 -0
- package/lib/types/dsh-adapter/index.d.ts.map +1 -1
- package/lib/types/dsh-adapter/index.js +1 -0
- package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
- package/lib/types/dsh-adapter/plugin.js +59 -0
- package/lib/types/dsh-adapter/promptDebug.d.ts +10 -0
- package/lib/types/dsh-adapter/promptDebug.d.ts.map +1 -0
- package/lib/types/dsh-adapter/promptDebug.js +148 -0
- package/lib/types/dsh-adapter/scenes.d.ts +75 -0
- package/lib/types/dsh-adapter/scenes.d.ts.map +1 -0
- package/lib/types/dsh-adapter/scenes.js +78 -0
- package/lib/types/dsh-adapter/settings-sections.d.ts +116 -0
- package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -0
- package/lib/types/dsh-adapter/settings-sections.js +63 -0
- package/lib/types/dsh-adapter/settingsEditor.d.ts +128 -0
- package/lib/types/dsh-adapter/settingsEditor.d.ts.map +1 -0
- package/lib/types/dsh-adapter/settingsEditor.js +244 -0
- package/lib/types/i18n.d.ts +137 -10
- package/lib/types/i18n.d.ts.map +1 -1
- package/lib/types/i18n.js +36 -3
- package/lib/types/jsx-runtime.d.ts +2 -0
- package/lib/types/jsx-runtime.d.ts.map +1 -0
- package/lib/types/jsx-runtime.js +13 -0
- package/lib/types/modelRoute.d.ts +10 -2
- package/lib/types/modelRoute.d.ts.map +1 -1
- package/lib/types/modelRoute.js +33 -4
- package/lib/types/scenes.d.ts +3 -0
- package/lib/types/scenes.d.ts.map +1 -0
- package/lib/types/scenes.js +4 -0
- package/lib/types/screens/Chat.d.ts.map +1 -1
- package/lib/types/screens/Chat.js +107 -11
- package/lib/types/screens/Settings.d.ts +20 -0
- package/lib/types/screens/Settings.d.ts.map +1 -0
- package/lib/types/screens/Settings.js +333 -0
- package/lib/types/screens/StatusLine.js +1 -1
- package/lib/types/settings-sections.d.ts +3 -0
- package/lib/types/settings-sections.d.ts.map +1 -0
- package/lib/types/settings-sections.js +4 -0
- package/lib/types/theme.d.ts +12 -0
- package/lib/types/theme.d.ts.map +1 -1
- package/lib/types/theme.js +36 -0
- package/package.json +17 -1
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text } from '../ui.js';
|
|
4
|
+
import * as JsDiff from 'diff';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { extname } from 'node:path';
|
|
7
|
+
import { getCliHighlightPromise } from '../cc/cliHighlight.js';
|
|
8
|
+
import { getTheme } from '../theme.js';
|
|
9
|
+
import { useTheme } from './design-system/ThemeProvider.js';
|
|
10
|
+
/** Replace tabs so width math and alignment hold (pi convention). */
|
|
11
|
+
const expandTabs = (text) => text.replaceAll('\t', ' ');
|
|
12
|
+
// --- syntax highlighting ----------------------------------------------------
|
|
13
|
+
/** highlight.js token classes → theme syntax tokens. `default` catches
|
|
14
|
+
* every unmapped class so cli-highlight's own yellow never leaks through
|
|
15
|
+
* (issue #250, P2-6). */
|
|
16
|
+
const SYNTAX_CLASS_TO_TOKEN = {
|
|
17
|
+
keyword: 'syntaxKeyword',
|
|
18
|
+
built_in: 'syntaxKeyword',
|
|
19
|
+
literal: 'syntaxKeyword',
|
|
20
|
+
string: 'syntaxString',
|
|
21
|
+
subst: 'syntaxString',
|
|
22
|
+
quote: 'syntaxString',
|
|
23
|
+
comment: 'syntaxComment',
|
|
24
|
+
number: 'syntaxNumber',
|
|
25
|
+
title: 'syntaxFunction',
|
|
26
|
+
'title.function_': 'syntaxFunction',
|
|
27
|
+
function: 'syntaxFunction',
|
|
28
|
+
'title.class_': 'syntaxType',
|
|
29
|
+
type: 'syntaxType',
|
|
30
|
+
class: 'syntaxType',
|
|
31
|
+
tag: 'syntaxType',
|
|
32
|
+
name: 'syntaxType',
|
|
33
|
+
attr: 'syntaxVariable',
|
|
34
|
+
attribute: 'syntaxVariable',
|
|
35
|
+
variable: 'syntaxVariable',
|
|
36
|
+
'template-variable': 'syntaxVariable',
|
|
37
|
+
params: 'syntaxVariable',
|
|
38
|
+
operator: 'syntaxOperator',
|
|
39
|
+
punctuation: 'syntaxPunctuation',
|
|
40
|
+
meta: 'syntaxPunctuation',
|
|
41
|
+
symbol: 'syntaxConstant',
|
|
42
|
+
regexp: 'syntaxConstant',
|
|
43
|
+
default: 'syntaxVariable',
|
|
44
|
+
};
|
|
45
|
+
/** chalk style for one raw theme value. Accepts every form the theme
|
|
46
|
+
* loader documents: #rgb, #rrggbb, #rrggbbaa (alpha stripped), rgb() with
|
|
47
|
+
* or without spaces, ansi256(n), ansi:name (issue #250, P2-5). */
|
|
48
|
+
export function chalkFromToken(token) {
|
|
49
|
+
let match = /^#([0-9a-fA-F]{3})$/.exec(token);
|
|
50
|
+
if (match !== null) {
|
|
51
|
+
const [r, g, b] = match[1].split('').map(c => parseInt(c + c, 16));
|
|
52
|
+
return chalk.rgb(r, g, b);
|
|
53
|
+
}
|
|
54
|
+
match = /^#([0-9a-fA-F]{6})(?:[0-9a-fA-F]{2})?$/.exec(token);
|
|
55
|
+
if (match !== null)
|
|
56
|
+
return chalk.hex(`#${match[1]}`);
|
|
57
|
+
match = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/.exec(token);
|
|
58
|
+
if (match !== null)
|
|
59
|
+
return chalk.rgb(Number(match[1]), Number(match[2]), Number(match[3]));
|
|
60
|
+
match = /^ansi256\((\d+)\)$/.exec(token);
|
|
61
|
+
if (match !== null)
|
|
62
|
+
return chalk.ansi256(Number(match[1]));
|
|
63
|
+
match = /^ansi:(\w+)$/.exec(token);
|
|
64
|
+
if (match !== null) {
|
|
65
|
+
const name = match[1] === 'blackBright' ? 'gray' : match[1];
|
|
66
|
+
const style = chalk[name];
|
|
67
|
+
if (style !== undefined)
|
|
68
|
+
return style;
|
|
69
|
+
}
|
|
70
|
+
return (text) => text;
|
|
71
|
+
}
|
|
72
|
+
/** ANSI 16-color SGR codes → rgb() strings (the dark-ansi palette path). */
|
|
73
|
+
const ANSI16 = {
|
|
74
|
+
30: 'rgb(0,0,0)', 31: 'rgb(128,0,0)', 32: 'rgb(0,128,0)', 33: 'rgb(128,128,0)',
|
|
75
|
+
34: 'rgb(0,0,128)', 35: 'rgb(128,0,128)', 36: 'rgb(0,128,128)', 37: 'rgb(192,192,192)',
|
|
76
|
+
90: 'rgb(128,128,128)', 91: 'rgb(255,0,0)', 92: 'rgb(0,255,0)', 93: 'rgb(255,255,0)',
|
|
77
|
+
94: 'rgb(0,0,255)', 95: 'rgb(255,0,255)', 96: 'rgb(0,255,255)', 97: 'rgb(255,255,255)',
|
|
78
|
+
};
|
|
79
|
+
/** Parse a cli-highlight ANSI string into colored runs, preserving open
|
|
80
|
+
* spans across embedded newlines (multi-line strings/comments). Handles
|
|
81
|
+
* truecolor 38;2, 256-color 38;5 (tmux / FORCE_COLOR=2, issue #250 P1-1),
|
|
82
|
+
* and single-code 16-color SGR. */
|
|
83
|
+
export function parseAnsiRuns(text) {
|
|
84
|
+
const runs = [];
|
|
85
|
+
let color;
|
|
86
|
+
let rest = text;
|
|
87
|
+
// eslint-disable-next-line no-control-regex -- parsing SGR is the point
|
|
88
|
+
const sgr = /\x1b\[([0-9;]+)m/;
|
|
89
|
+
while (rest.length > 0) {
|
|
90
|
+
const match = sgr.exec(rest);
|
|
91
|
+
const chunk = match === null ? rest : rest.slice(0, match.index);
|
|
92
|
+
if (chunk !== '')
|
|
93
|
+
runs.push(color === undefined ? { text: chunk } : { text: chunk, color });
|
|
94
|
+
if (match === null)
|
|
95
|
+
break;
|
|
96
|
+
const codes = match[1].split(';').map(Number);
|
|
97
|
+
if (codes.includes(0) || codes.includes(39))
|
|
98
|
+
color = undefined;
|
|
99
|
+
else if (codes[0] === 38 && codes[1] === 2 && codes.length >= 5) {
|
|
100
|
+
color = `rgb(${codes[2]},${codes[3]},${codes[4]})`;
|
|
101
|
+
}
|
|
102
|
+
else if (codes[0] === 38 && codes[1] === 5 && codes.length >= 3) {
|
|
103
|
+
color = `ansi256(${codes[2]})`;
|
|
104
|
+
}
|
|
105
|
+
else if (codes.length === 1 && ANSI16[codes[0]] !== undefined) {
|
|
106
|
+
color = ANSI16[codes[0]];
|
|
107
|
+
}
|
|
108
|
+
rest = rest.slice(match.index + match[0].length);
|
|
109
|
+
}
|
|
110
|
+
return runs;
|
|
111
|
+
}
|
|
112
|
+
/** Split flat runs into per-line run arrays at embedded newlines. */
|
|
113
|
+
function splitRunsToLines(runs) {
|
|
114
|
+
const lines = [[]];
|
|
115
|
+
for (const run of runs) {
|
|
116
|
+
const parts = run.text.split('\n');
|
|
117
|
+
for (let i = 0; i < parts.length; i++) {
|
|
118
|
+
if (i > 0)
|
|
119
|
+
lines.push([]);
|
|
120
|
+
if (parts[i] !== '') {
|
|
121
|
+
lines[lines.length - 1].push(run.color === undefined ? { text: parts[i] } : { text: parts[i], color: run.color });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return lines;
|
|
126
|
+
}
|
|
127
|
+
/** Whole-hunk syntax runs per line, cached per (theme signature + language
|
|
128
|
+
* + text): one entry survives any amount of card re-rendering, and the
|
|
129
|
+
* palette change invalidates it (issue #250, P1-2 + P2-8). */
|
|
130
|
+
const syntaxCache = new Map();
|
|
131
|
+
const SYNTAX_CACHE_MAX = 40;
|
|
132
|
+
export function highlightLines(text, language, hl, chTheme, themeSig) {
|
|
133
|
+
if (hl === null || chTheme === undefined || language === undefined)
|
|
134
|
+
return undefined;
|
|
135
|
+
if (text === '' || !hl.supportsLanguage(language))
|
|
136
|
+
return undefined;
|
|
137
|
+
const key = `${themeSig}${language}${text}`;
|
|
138
|
+
const cached = syntaxCache.get(key);
|
|
139
|
+
if (cached !== undefined)
|
|
140
|
+
return cached;
|
|
141
|
+
let lines;
|
|
142
|
+
try {
|
|
143
|
+
lines = splitRunsToLines(parseAnsiRuns(hl.highlight(text, { language, theme: chTheme })));
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
if (syntaxCache.size >= SYNTAX_CACHE_MAX)
|
|
149
|
+
syntaxCache.clear();
|
|
150
|
+
syntaxCache.set(key, lines);
|
|
151
|
+
return lines;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Merge syntax runs with word-diff change flags over the same string: both
|
|
155
|
+
* partition it, so an offset sweep yields runs carrying a syntax color AND
|
|
156
|
+
* a changed flag. The render layer lets `changed` override the color.
|
|
157
|
+
*/
|
|
158
|
+
function mergeRuns(syntax, words) {
|
|
159
|
+
const out = [];
|
|
160
|
+
let si = 0;
|
|
161
|
+
let wi = 0;
|
|
162
|
+
let sOff = 0;
|
|
163
|
+
let wOff = 0;
|
|
164
|
+
while (si < syntax.length && wi < words.length) {
|
|
165
|
+
const s = syntax[si];
|
|
166
|
+
const w = words[wi];
|
|
167
|
+
const sLen = s.text.length - sOff;
|
|
168
|
+
const wLen = w.text.length - wOff;
|
|
169
|
+
const take = Math.min(sLen, wLen);
|
|
170
|
+
if (take > 0) {
|
|
171
|
+
out.push({
|
|
172
|
+
text: w.text.slice(wOff, wOff + take),
|
|
173
|
+
changed: w.changed,
|
|
174
|
+
color: w.changed ? undefined : s.color,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
sOff += take;
|
|
178
|
+
wOff += take;
|
|
179
|
+
if (sOff >= s.text.length) {
|
|
180
|
+
si++;
|
|
181
|
+
sOff = 0;
|
|
182
|
+
}
|
|
183
|
+
if (wOff >= w.text.length) {
|
|
184
|
+
wi++;
|
|
185
|
+
wOff = 0;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
// --- word-level diff --------------------------------------------------------
|
|
191
|
+
/** Strip the shared leading whitespace before word-diffing: otherwise the
|
|
192
|
+
* whole indent reads as one changed blob (pi's renderIntraLineDiff trick). */
|
|
193
|
+
function wordSegments(oldLine, newLine) {
|
|
194
|
+
const oldIndent = /^\s*/.exec(oldLine)?.[0] ?? '';
|
|
195
|
+
const newIndent = /^\s*/.exec(newLine)?.[0] ?? '';
|
|
196
|
+
const sharedIndent = oldIndent === newIndent ? oldIndent : '';
|
|
197
|
+
const parts = JsDiff.diffWords(oldLine.slice(sharedIndent.length), newLine.slice(sharedIndent.length));
|
|
198
|
+
const oldSegments = sharedIndent === '' ? [] : [{ text: sharedIndent, changed: false }];
|
|
199
|
+
const newSegments = sharedIndent === '' ? [] : [{ text: sharedIndent, changed: false }];
|
|
200
|
+
for (const part of parts) {
|
|
201
|
+
if (part.added)
|
|
202
|
+
newSegments.push({ text: part.value, changed: true });
|
|
203
|
+
else if (part.removed)
|
|
204
|
+
oldSegments.push({ text: part.value, changed: true });
|
|
205
|
+
else {
|
|
206
|
+
oldSegments.push({ text: part.value, changed: false });
|
|
207
|
+
newSegments.push({ text: part.value, changed: false });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return { old: oldSegments, new: newSegments };
|
|
211
|
+
}
|
|
212
|
+
const plainSegments = (line) => [{ text: line, changed: false }];
|
|
213
|
+
// --- hunk alignment ---------------------------------------------------------
|
|
214
|
+
/**
|
|
215
|
+
* Pair the lines of an unequal removed+added block via a case-insensitive
|
|
216
|
+
* LCS: lines equal modulo case align as change pairs, everything before the
|
|
217
|
+
* first match stays block order. An inserted line above an edited one
|
|
218
|
+
* (`foo,bar` → `insert,FOO,bar`) therefore yields `insert` add-only, then
|
|
219
|
+
* `foo ↔ FOO`, instead of the index-zip misalignment (issue #250, P2-4).
|
|
220
|
+
*/
|
|
221
|
+
function lcsPairs(oldLines, newLines) {
|
|
222
|
+
const m = oldLines.length;
|
|
223
|
+
const n = newLines.length;
|
|
224
|
+
// dp[i][j] = LCS length of oldLines[i:] and newLines[j:].
|
|
225
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
226
|
+
const eq = (a, b) => a.toLowerCase() === b.toLowerCase();
|
|
227
|
+
for (let i = m - 1; i >= 0; i--) {
|
|
228
|
+
for (let j = n - 1; j >= 0; j--) {
|
|
229
|
+
dp[i][j] = eq(oldLines[i], newLines[j])
|
|
230
|
+
? dp[i + 1][j + 1] + 1
|
|
231
|
+
: Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const pairs = [];
|
|
235
|
+
let i = 0;
|
|
236
|
+
let j = 0;
|
|
237
|
+
while (i < m && j < n) {
|
|
238
|
+
if (eq(oldLines[i], newLines[j])) {
|
|
239
|
+
pairs.push([i, j]);
|
|
240
|
+
i++;
|
|
241
|
+
j++;
|
|
242
|
+
}
|
|
243
|
+
else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
244
|
+
i++;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
j++;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return pairs;
|
|
251
|
+
}
|
|
252
|
+
/** Align one file's hunks into rows (plain text + word flags; styling is a
|
|
253
|
+
* render-time concern over the visible slice). */
|
|
254
|
+
function alignFileDiff(fileIndex, oldText, newText) {
|
|
255
|
+
const rows = [];
|
|
256
|
+
const parts = JsDiff.diffLines(expandTabs(oldText ?? ''), expandTabs(newText));
|
|
257
|
+
let oldIndex = 0;
|
|
258
|
+
let newIndex = 0;
|
|
259
|
+
for (let i = 0; i < parts.length; i++) {
|
|
260
|
+
const part = parts[i];
|
|
261
|
+
const lines = part.value.replace(/\n$/, '').split('\n');
|
|
262
|
+
if (!part.added && !part.removed) {
|
|
263
|
+
for (const line of lines) {
|
|
264
|
+
rows.push({
|
|
265
|
+
kind: 'context',
|
|
266
|
+
fileIndex,
|
|
267
|
+
oldIndex: oldIndex++,
|
|
268
|
+
newIndex: newIndex++,
|
|
269
|
+
oldWords: plainSegments(line),
|
|
270
|
+
newWords: plainSegments(line),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (part.removed) {
|
|
276
|
+
const addedPart = parts[i + 1]?.added === true ? parts[i + 1] : undefined;
|
|
277
|
+
const addedLines = addedPart === undefined ? [] : addedPart.value.replace(/\n$/, '').split('\n');
|
|
278
|
+
// Same-length replacement blocks zip index-for-index (the common
|
|
279
|
+
// edit case); unequal blocks pair through the ci-LCS so an inserted
|
|
280
|
+
// line cannot shift the alignment (issue #250, P2-4).
|
|
281
|
+
const pairs = lines.length === addedLines.length
|
|
282
|
+
? lines.map((_, index) => [index, index])
|
|
283
|
+
: lcsPairs(lines, addedLines);
|
|
284
|
+
// Block order: walk both lists; unpaired entries emit in place, pairs
|
|
285
|
+
// emit at the later of the two positions.
|
|
286
|
+
const events = [];
|
|
287
|
+
let oi = 0;
|
|
288
|
+
let ai = 0;
|
|
289
|
+
for (const [o, a] of pairs) {
|
|
290
|
+
while (oi < o)
|
|
291
|
+
events.push({ kind: 'del', o: oi++ });
|
|
292
|
+
while (ai < a)
|
|
293
|
+
events.push({ kind: 'add', a: ai++ });
|
|
294
|
+
events.push({ kind: 'change', o: oi++, a: ai++ });
|
|
295
|
+
}
|
|
296
|
+
while (oi < lines.length)
|
|
297
|
+
events.push({ kind: 'del', o: oi++ });
|
|
298
|
+
while (ai < addedLines.length)
|
|
299
|
+
events.push({ kind: 'add', a: ai++ });
|
|
300
|
+
for (const event of events) {
|
|
301
|
+
if (event.kind === 'change') {
|
|
302
|
+
const segments = wordSegments(lines[event.o], addedLines[event.a]);
|
|
303
|
+
rows.push({
|
|
304
|
+
kind: 'change',
|
|
305
|
+
fileIndex,
|
|
306
|
+
oldIndex: oldIndex++,
|
|
307
|
+
newIndex: newIndex++,
|
|
308
|
+
oldWords: segments.old,
|
|
309
|
+
newWords: segments.new,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
else if (event.kind === 'del') {
|
|
313
|
+
rows.push({ kind: 'del', fileIndex, oldIndex: oldIndex++, oldWords: plainSegments(lines[event.o]) });
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
rows.push({ kind: 'add', fileIndex, newIndex: newIndex++, newWords: plainSegments(addedLines[event.a]) });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (addedPart !== undefined)
|
|
320
|
+
i++;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
for (const line of lines) {
|
|
324
|
+
rows.push({ kind: 'add', fileIndex, newIndex: newIndex++, newWords: plainSegments(line) });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return rows;
|
|
328
|
+
}
|
|
329
|
+
// --- rendering --------------------------------------------------------------
|
|
330
|
+
function PaneLine({ side, kind, width, tone, padLeft = false, }) {
|
|
331
|
+
const backgroundColor = kind === 'context'
|
|
332
|
+
? 'toolCardBackground'
|
|
333
|
+
: tone === 'old'
|
|
334
|
+
? 'diffRemovedDimmed'
|
|
335
|
+
: 'diffAddedDimmed';
|
|
336
|
+
const wordColor = tone === 'old' ? 'diffRemovedWord' : 'diffAddedWord';
|
|
337
|
+
// Status gutter instead of line numbers: ToolFileDiff has no file
|
|
338
|
+
// offsets, and an invented number misleads (issue #250, P2-3).
|
|
339
|
+
const marker = kind === 'context' ? ' ' : tone === 'old' ? '−' : '+';
|
|
340
|
+
const prefix = padLeft ? ` ${marker}` : marker;
|
|
341
|
+
return (_jsxs(Box, { width: width, flexShrink: 0, backgroundColor: backgroundColor, children: [_jsx(Text, { dimColor: true, backgroundColor: backgroundColor, children: `${prefix} ` }), side === undefined ? (_jsx(Text, { backgroundColor: backgroundColor, children: " " })) : (_jsx(Text, { wrap: "truncate", backgroundColor: backgroundColor, children: side.segments.map((segment, index) => segment.changed ? (_jsx(Text, { color: wordColor, bold: true, backgroundColor: backgroundColor, children: segment.text }, index)) : (_jsx(Text, { color: segment.color, backgroundColor: backgroundColor, children: segment.text }, index))) }))] }));
|
|
342
|
+
}
|
|
343
|
+
export function SplitDiffView({ diffs, width, maxRows, verbose, }) {
|
|
344
|
+
const [hl, setHl] = React.useState(null);
|
|
345
|
+
React.useEffect(() => {
|
|
346
|
+
let mounted = true;
|
|
347
|
+
void getCliHighlightPromise().then(loaded => {
|
|
348
|
+
if (mounted)
|
|
349
|
+
setHl(loaded);
|
|
350
|
+
});
|
|
351
|
+
return () => { mounted = false; };
|
|
352
|
+
}, []);
|
|
353
|
+
const [themeName] = useTheme();
|
|
354
|
+
// Resolved-palette signature: covers dark→light, light→dark, AND the
|
|
355
|
+
// `auto` base flip where the name never changes (issue #250, P1-2).
|
|
356
|
+
const themeSig = React.useMemo(() => {
|
|
357
|
+
const theme = getTheme(themeName);
|
|
358
|
+
return Object.values(SYNTAX_CLASS_TO_TOKEN)
|
|
359
|
+
.map(key => theme[key] ?? '')
|
|
360
|
+
.join('|');
|
|
361
|
+
}, [themeName]);
|
|
362
|
+
const chTheme = React.useMemo(() => {
|
|
363
|
+
const theme = getTheme(themeName);
|
|
364
|
+
const out = {};
|
|
365
|
+
for (const [tokenClass, tokenKey] of Object.entries(SYNTAX_CLASS_TO_TOKEN)) {
|
|
366
|
+
const value = theme[tokenKey];
|
|
367
|
+
if (value !== undefined)
|
|
368
|
+
out[tokenClass] = chalkFromToken(value);
|
|
369
|
+
}
|
|
370
|
+
return out;
|
|
371
|
+
// themeSig changes exactly when any syntax token's resolved value does.
|
|
372
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
373
|
+
}, [themeSig]);
|
|
374
|
+
// Alignment is text-only and capped BEFORE styling: the highlighter and
|
|
375
|
+
// word-diff work lands only on the visible slice (issue #250, P2-8).
|
|
376
|
+
const rows = [];
|
|
377
|
+
let prevPath;
|
|
378
|
+
diffs.forEach((diff, fileIndex) => {
|
|
379
|
+
if (diffs.length > 1) {
|
|
380
|
+
if (diff.path !== prevPath)
|
|
381
|
+
rows.push({ separator: diff.path });
|
|
382
|
+
else
|
|
383
|
+
rows.push({ separator: '⋯' });
|
|
384
|
+
}
|
|
385
|
+
prevPath = diff.path;
|
|
386
|
+
rows.push(...alignFileDiff(fileIndex, diff.oldText, diff.newText));
|
|
387
|
+
});
|
|
388
|
+
const totalRows = rows.length;
|
|
389
|
+
const capped = verbose || totalRows <= maxRows || totalRows - maxRows === 1;
|
|
390
|
+
const visible = capped ? rows : rows.slice(0, maxRows);
|
|
391
|
+
const hidden = totalRows - visible.length;
|
|
392
|
+
const paneWidth = Math.max(20, Math.floor((width - 1) / 2));
|
|
393
|
+
// Whole-hunk highlight per file (multi-line lexer state preserved);
|
|
394
|
+
// only the visible rows merge syntax runs with word flags below.
|
|
395
|
+
const fileSyntax = diffs.map(diff => {
|
|
396
|
+
const language = extname(diff.path).replace(/^\./, '') || undefined;
|
|
397
|
+
return {
|
|
398
|
+
old: highlightLines(expandTabs(diff.oldText ?? ''), language, hl, chTheme, themeSig),
|
|
399
|
+
next: highlightLines(expandTabs(diff.newText), language, hl, chTheme, themeSig),
|
|
400
|
+
};
|
|
401
|
+
});
|
|
402
|
+
return (_jsxs(Box, { flexDirection: "column", width: paneWidth * 2 + 1, backgroundColor: "toolCardBackgroundDim", children: [visible.map((row, index) => {
|
|
403
|
+
if ('separator' in row) {
|
|
404
|
+
return (_jsx(Box, { width: paneWidth * 2 + 1, children: _jsx(Text, { dimColor: true, wrap: "truncate", children: row.separator === '⋯' ? '⋯' : ` ${row.separator}` }) }, index));
|
|
405
|
+
}
|
|
406
|
+
const syntax = fileSyntax[row.fileIndex];
|
|
407
|
+
const oldRuns = row.oldIndex !== undefined ? syntax?.old?.[row.oldIndex] : undefined;
|
|
408
|
+
const newRuns = row.newIndex !== undefined ? syntax?.next?.[row.newIndex] : undefined;
|
|
409
|
+
const oldSide = row.oldWords === undefined
|
|
410
|
+
? undefined
|
|
411
|
+
: { segments: oldRuns !== undefined ? mergeRuns(oldRuns, row.oldWords) : row.oldWords };
|
|
412
|
+
const newSide = row.newWords === undefined
|
|
413
|
+
? undefined
|
|
414
|
+
: { segments: newRuns !== undefined ? mergeRuns(newRuns, row.newWords) : row.newWords };
|
|
415
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(PaneLine, { side: oldSide, kind: row.kind === 'add' ? 'context' : row.kind, tone: "old", width: paneWidth }), _jsx(Box, { width: 1, flexShrink: 0, backgroundColor: "toolCardBackgroundDim", children: _jsx(Text, { dimColor: true, children: "\u2502" }) }), _jsx(PaneLine, { side: newSide, kind: row.kind === 'del' ? 'context' : row.kind, tone: "new", width: paneWidth, padLeft: true })] }, index));
|
|
416
|
+
}), hidden > 0 && (_jsx(Text, { dimColor: true, children: `… +${hidden} lines (ctrl+o to expand)` }))] }));
|
|
417
|
+
}
|
|
@@ -19,6 +19,8 @@ type Props = {
|
|
|
19
19
|
* is worth less than mentioning it once.
|
|
20
20
|
*/
|
|
21
21
|
footnote?: string;
|
|
22
|
+
/** Diff presentation preference; `auto` picks by terminal width. */
|
|
23
|
+
diffLayout?: 'auto' | 'split' | 'unified';
|
|
22
24
|
};
|
|
23
25
|
/**
|
|
24
26
|
* Tool-call card: `● Edit /path` header with a blinking status dot, then the
|
|
@@ -26,6 +28,6 @@ type Props = {
|
|
|
26
28
|
* output, read content — instead of the raw result dump (mirroring Claude Code's `AssistantToolUseMessage.tsx` + the dsh-tools presentation views the
|
|
27
29
|
* channel captures per call).
|
|
28
30
|
*/
|
|
29
|
-
export declare function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected, isExpanded, footnote, }: Props): React.ReactNode;
|
|
31
|
+
export declare function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected, isExpanded, footnote, diffLayout, }: Props): React.ReactNode;
|
|
30
32
|
export {};
|
|
31
33
|
//# sourceMappingURL=AssistantToolUseMessage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssistantToolUseMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/AssistantToolUseMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAA8C,OAAO,EAAE,MAAM,8BAA8B,CAAA;
|
|
1
|
+
{"version":3,"file":"AssistantToolUseMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/AssistantToolUseMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAA8C,OAAO,EAAE,MAAM,8BAA8B,CAAA;AAKvG,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,OAAO,CAAA;IACb,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAA;IAClB,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAA;IAChB,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,sEAAsE;IACtE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;CAC1C,CAAA;AAyMD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,IAAI,EACJ,SAAS,EACT,OAAO,EACP,UAAkB,EAClB,UAAkB,EAClB,QAAQ,EACR,UAAmB,GACpB,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CAoIzB"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from '../../ui.js';
|
|
2
|
+
import { Box, Text, useTerminalSize } from '../../ui.js';
|
|
3
3
|
import { stringWidth } from '../../ink/stringWidth.js';
|
|
4
4
|
import { useAnimationFrame } from '../../ink/hooks/use-animation-frame.js';
|
|
5
5
|
import { ToolUseLoader } from '../ToolUseLoader.js';
|
|
6
|
+
import { SplitDiffView } from '../SplitDiffView.js';
|
|
6
7
|
import { formatDuration } from '../../cc/format.js';
|
|
7
8
|
/** Tool display names: DSH emits lowercase tool ids (`bash`); Claude Code
|
|
8
9
|
* shows capitalized names (`Bash`). Map the common ones, fall back to the
|
|
@@ -32,8 +33,11 @@ const TEXT_BODY_MAX_LINES = 3;
|
|
|
32
33
|
/** Diff bodies cap at the upstream chat row's 8 (dsh-client-ui-tool's
|
|
33
34
|
* CHAT_DIFF_MAX_LINES) — denser information than log output. */
|
|
34
35
|
const DIFF_BODY_MAX_LINES = 8;
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
/** Minimum terminal width for the two-pane diff: below this the panes
|
|
37
|
+
* would squeeze under ~50 columns each and the unified view reads better. */
|
|
38
|
+
const SPLIT_DIFF_MIN_COLS = 110;
|
|
39
|
+
const GUTTER_FIRST = ' ⎿ ';
|
|
40
|
+
const GUTTER_REST = ' ';
|
|
37
41
|
const add = (text) => ({ text, tone: 'add' });
|
|
38
42
|
const del = (text) => ({ text, tone: 'del' });
|
|
39
43
|
const dim = (text) => ({ text, tone: 'dim' });
|
|
@@ -163,7 +167,7 @@ function HeaderTitle({ name, title, isTerminal, displayArgs }) {
|
|
|
163
167
|
* output, read content — instead of the raw result dump (mirroring Claude Code's `AssistantToolUseMessage.tsx` + the dsh-tools presentation views the
|
|
164
168
|
* channel captures per call).
|
|
165
169
|
*/
|
|
166
|
-
export function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected = false, isExpanded = false, footnote, }) {
|
|
170
|
+
export function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected = false, isExpanded = false, footnote, diffLayout = 'auto', }) {
|
|
167
171
|
const isRunning = tool.status === 'running';
|
|
168
172
|
const isError = tool.status === 'error';
|
|
169
173
|
const displayArgs = verbose ? tool.argsFull ?? tool.argsText : tool.argsText;
|
|
@@ -188,12 +192,18 @@ export function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected =
|
|
|
188
192
|
const elapsedText = elapsedMs !== undefined ? ` · ${formatDuration(elapsedMs)}` : '';
|
|
189
193
|
// Body lines: the structured view first, raw result text as the fallback
|
|
190
194
|
// (tools without a presenter, or a folded row awaiting loadOlder).
|
|
195
|
+
// Wide terminals render diffs as a two-pane side-by-side instead: one
|
|
196
|
+
// source line per terminal row (truncate) keeps the panes row-aligned,
|
|
197
|
+
// which the flat add/del line model cannot express.
|
|
198
|
+
const { columns } = useTerminalSize();
|
|
199
|
+
const useSplitDiff = !isError && view?.card === 'diff' &&
|
|
200
|
+
(diffLayout === 'split' || (diffLayout !== 'unified' && columns >= SPLIT_DIFF_MIN_COLS));
|
|
191
201
|
let body = [];
|
|
192
202
|
if (isError) {
|
|
193
203
|
if (tool.errorText)
|
|
194
204
|
body = [{ text: tool.errorText, tone: 'error' }];
|
|
195
205
|
}
|
|
196
|
-
else {
|
|
206
|
+
else if (!useSplitDiff) {
|
|
197
207
|
if (view !== undefined)
|
|
198
208
|
body = viewLines(view);
|
|
199
209
|
if (body.length === 0 && result) {
|
|
@@ -212,7 +222,7 @@ export function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected =
|
|
|
212
222
|
? 'messageActionsBackground'
|
|
213
223
|
: isExpanded
|
|
214
224
|
? 'userMessageBackgroundHover'
|
|
215
|
-
:
|
|
225
|
+
: 'toolCardBackgroundDim', children: _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsxs(Box, { flexDirection: "row", flexWrap: "nowrap", minWidth: minWidth, children: [_jsx(ToolUseLoader, { shouldAnimate: isRunning, isUnresolved: isRunning, isError: isError }), _jsx(HeaderTitle, { name: name, title: headerTitle, isTerminal: headerIsTerminal, displayArgs: displayArgs }), !isRunning && (_jsx(Box, { flexWrap: "nowrap", children: _jsx(Text, { dimColor: true, children: elapsedText }) }))] }), useSplitDiff && view?.card === 'diff' ? (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 3, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: GUTTER_FIRST }) }), _jsx(SplitDiffView, { diffs: view.diffs, width: columns - 4, maxRows: DIFF_BODY_MAX_LINES, verbose: verbose })] })) : (rendered.map((line, index) => (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 3, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: index === 0 ? GUTTER_FIRST : GUTTER_REST }) }), _jsx(Box, { flexGrow: 1, children: _jsx(Text, { color: line.tone === 'add'
|
|
216
226
|
? 'diffAddedWord'
|
|
217
227
|
: line.tone === 'del'
|
|
218
228
|
? 'diffRemovedWord'
|
|
@@ -220,5 +230,5 @@ export function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected =
|
|
|
220
230
|
? 'error'
|
|
221
231
|
: line.tone === 'hint'
|
|
222
232
|
? 'subtle'
|
|
223
|
-
: undefined, dimColor: line.tone === 'dim', wrap: "wrap", children: line.text === '' ? ' ' : line.text }) })] }, index)))] }) }));
|
|
233
|
+
: undefined, dimColor: line.tone === 'dim', wrap: "wrap", children: line.text === '' ? ' ' : line.text }) })] }, index)))), useSplitDiff && footnote !== undefined && (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 3, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: GUTTER_REST }) }), _jsx(Text, { color: "subtle", children: footnote })] }))] }) }));
|
|
224
234
|
}
|
|
@@ -10,6 +10,9 @@ import { type SessionModeSpec } from '../sessionModes.js';
|
|
|
10
10
|
import type { SpinnerMode } from '../components/Spinner/spinnerMode.js';
|
|
11
11
|
import { type ActivityState } from 'dsh-working-activity/status';
|
|
12
12
|
import { type TuiWorkspaceCommand, type TuiWorkspaceCommandResult, type TuiWorkspaceTarget } from './workspaces.js';
|
|
13
|
+
import type { TuiSettingsSection } from './settings-sections.js';
|
|
14
|
+
import type { SettingsHost } from './settingsEditor.js';
|
|
15
|
+
import type { TuiSceneDescriptor } from './scenes.js';
|
|
13
16
|
type ChannelImageBlock = Extract<ContentBlock, {
|
|
14
17
|
type: 'image';
|
|
15
18
|
}>;
|
|
@@ -217,6 +220,15 @@ export interface LoadedContextSkill {
|
|
|
217
220
|
readonly name: string;
|
|
218
221
|
readonly description: string;
|
|
219
222
|
}
|
|
223
|
+
/** One skill in the live agent's catalog, for the `/skills` picker (issue #204). */
|
|
224
|
+
export interface SkillInfo {
|
|
225
|
+
readonly name: string;
|
|
226
|
+
readonly description: string;
|
|
227
|
+
/** True when `/name` invokes it (it appears in the `/` menu, issue #86). */
|
|
228
|
+
readonly userInvocable: boolean;
|
|
229
|
+
/** Discovery source bucket (bundled / user-* / project-* / runtime / …). */
|
|
230
|
+
readonly source: string;
|
|
231
|
+
}
|
|
220
232
|
/** One model-visible tool from the prompt assembly. */
|
|
221
233
|
export interface LoadedContextTool {
|
|
222
234
|
readonly name: string;
|
|
@@ -304,6 +316,8 @@ export interface Channel {
|
|
|
304
316
|
readonly workingActivity: ActivityStatus | undefined;
|
|
305
317
|
/** Working-activity indicator preset name (`claude`/`moon`/…/`random`). */
|
|
306
318
|
readonly activityFrames: string | undefined;
|
|
319
|
+
/** Edit/Write diff presentation preference (`auto`/`split`/`unified`). */
|
|
320
|
+
readonly diffLayout: 'auto' | 'split' | 'unified';
|
|
307
321
|
/** Whether the in-process working-activity line is shown (config.activity). */
|
|
308
322
|
readonly activityEnabled: boolean;
|
|
309
323
|
/** Whether the segmented context bar row shows in the status footer
|
|
@@ -353,6 +367,22 @@ export interface Channel {
|
|
|
353
367
|
* back to sending the line to the model).
|
|
354
368
|
*/
|
|
355
369
|
runExternalCommand(name: string, rawInput: string): Promise<string | undefined>;
|
|
370
|
+
/**
|
|
371
|
+
* Plugin-registered full-screen scene currently replacing the conversation
|
|
372
|
+
* (the `dsh-tui-scenes` runtime), if any. The chat screen renders its
|
|
373
|
+
* component INSTEAD of the transcript — the same whole-terminal treatment
|
|
374
|
+
* the trajectory scene gets — and hands it the keyboard; `undefined`
|
|
375
|
+
* renders the conversation normally.
|
|
376
|
+
*/
|
|
377
|
+
readonly pluginScene: TuiSceneDescriptor | undefined;
|
|
378
|
+
/**
|
|
379
|
+
* Open a registered plugin scene by id. Plugin command handlers usually
|
|
380
|
+
* call the runtime directly (`ctx.tuiScenes.open`); this passthrough lets
|
|
381
|
+
* host-side UI code do the same without touching cordis services.
|
|
382
|
+
*/
|
|
383
|
+
openPluginScene(id: string): boolean;
|
|
384
|
+
/** Close the open plugin scene, if any (a no-op otherwise). */
|
|
385
|
+
closePluginScene(): void;
|
|
356
386
|
/** 侧问(CC /btw):无工具单轮 LLM 调用,复用当前会话上下文;结果不落 session log。 */
|
|
357
387
|
sideQuestion(question: string, options?: {
|
|
358
388
|
signal?: AbortSignal;
|
|
@@ -459,12 +489,29 @@ export interface Channel {
|
|
|
459
489
|
setActivityFrames(name: string): boolean;
|
|
460
490
|
/** Advertised models across every registered provider route (empty when the LLM service is absent). */
|
|
461
491
|
listModels(): Promise<readonly LlmModelInfo[]>;
|
|
492
|
+
/** The live agent's full skill catalog for `/skills` (issue #204) — name,
|
|
493
|
+
* description, invocation flags and source bucket. Undefined on a failed
|
|
494
|
+
* or incomplete registry read (the picker shows an error); empty only
|
|
495
|
+
* when no registry is mounted or it genuinely holds nothing. */
|
|
496
|
+
listSkills(): Promise<readonly SkillInfo[] | undefined>;
|
|
462
497
|
/** Safe credential metadata for `/login`; undefined without the service. */
|
|
463
498
|
describeCredential(ref: string): Promise<CredentialStatus | undefined>;
|
|
464
499
|
/** Runtime capabilities for the `/provider` wizard, over the settings /
|
|
465
500
|
* credentials / llm seams; undefined when the composition lacks them
|
|
466
501
|
* (bare cordis.yml start without the dsh-base services). */
|
|
467
502
|
providerSetup(): ProviderSetupHost | undefined;
|
|
503
|
+
/**
|
|
504
|
+
* Runtime capabilities for the `/settings` screen, over the settings /
|
|
505
|
+
* credentials seams; undefined when the composition lacks the settings
|
|
506
|
+
* service (the screen then renders plugin sections as unavailable and
|
|
507
|
+
* namespaces read-only).
|
|
508
|
+
*/
|
|
509
|
+
settingsHost(): SettingsHost | undefined;
|
|
510
|
+
/** Plugin-declared settings sections from the `tuiSettingsSections` seam
|
|
511
|
+
* (empty when the seam or every provider is absent). */
|
|
512
|
+
settingsSections(): readonly TuiSettingsSection[];
|
|
513
|
+
/** Subscribe to settings-section register/unregister events. */
|
|
514
|
+
subscribeSettingsSections(listener: () => void): () => void;
|
|
468
515
|
/** Top-level entries of the session cwd for `@` file completion. */
|
|
469
516
|
listFiles(): Promise<readonly string[]>;
|
|
470
517
|
/** Every session the persistence backend stores, classified and unfiltered
|
|
@@ -596,6 +643,10 @@ export interface ChannelState {
|
|
|
596
643
|
workingActivity: ActivityStatus | undefined;
|
|
597
644
|
/** Working-activity indicator preset (see the public Channel type). */
|
|
598
645
|
activityFrames: string | undefined;
|
|
646
|
+
/** Diff presentation preference (see the public Channel type). */
|
|
647
|
+
diffLayout: 'auto' | 'split' | 'unified';
|
|
648
|
+
/** Apply a diff-layout change (see the public Channel type). */
|
|
649
|
+
setDiffLayout(layout: 'auto' | 'split' | 'unified'): void;
|
|
599
650
|
/** Working-activity display switch (see the public Channel type). */
|
|
600
651
|
activityEnabled: boolean;
|
|
601
652
|
/** Context bar row switch (see the public Channel type). */
|
|
@@ -623,6 +674,12 @@ export interface ChannelState {
|
|
|
623
674
|
commandCompletions(input: string): readonly CommandCompletion[];
|
|
624
675
|
/** Run a plugin-registered command (see the public Channel type). */
|
|
625
676
|
runExternalCommand(name: string, rawInput: string): Promise<string | undefined>;
|
|
677
|
+
/** Open plugin scene mirrored from the scenes runtime (see the public Channel type). */
|
|
678
|
+
pluginScene: TuiSceneDescriptor | undefined;
|
|
679
|
+
/** Open a plugin scene by id (see the public Channel type). */
|
|
680
|
+
openPluginScene(id: string): boolean;
|
|
681
|
+
/** Close the open plugin scene (see the public Channel type). */
|
|
682
|
+
closePluginScene(): void;
|
|
626
683
|
/** Estimated context segments by content type (pi-nano-context style bar). */
|
|
627
684
|
contextSegments: {
|
|
628
685
|
system: number;
|
|
@@ -687,10 +744,18 @@ export interface ChannelState {
|
|
|
687
744
|
/** Switch the working-activity indicator preset (see the public Channel). */
|
|
688
745
|
setActivityFrames(name: string): boolean;
|
|
689
746
|
listModels(): Promise<readonly LlmModelInfo[]>;
|
|
747
|
+
/** The live agent's skill catalog for `/skills` (see the public Channel type). */
|
|
748
|
+
listSkills(): Promise<readonly SkillInfo[] | undefined>;
|
|
690
749
|
/** Safe credential metadata for `/login` (see the public Channel type). */
|
|
691
750
|
describeCredential(ref: string): Promise<CredentialStatus | undefined>;
|
|
692
751
|
/** `/provider` wizard capabilities (see the public Channel type). */
|
|
693
752
|
providerSetup(): ProviderSetupHost | undefined;
|
|
753
|
+
/** `/settings` screen capabilities (see the public Channel type). */
|
|
754
|
+
settingsHost(): SettingsHost | undefined;
|
|
755
|
+
/** Plugin-declared settings sections (see the public Channel type). */
|
|
756
|
+
settingsSections(): readonly TuiSettingsSection[];
|
|
757
|
+
/** Subscribe to settings-section register/unregister events. */
|
|
758
|
+
subscribeSettingsSections(listener: () => void): () => void;
|
|
694
759
|
listFiles(): Promise<readonly string[]>;
|
|
695
760
|
listSessions(): Promise<readonly SessionSummary[]>;
|
|
696
761
|
/** Trailing exchanges of a persisted session (see the public Channel type). */
|
|
@@ -746,6 +811,9 @@ export declare function createChannel(ctx: Context, initialAgent: Agent, options
|
|
|
746
811
|
/** Indicator preset for the working-activity line (`claude`/`moon`/
|
|
747
812
|
* `comet`/`dots`/… or `random`); default `claude`. */
|
|
748
813
|
activityFrames?: string;
|
|
814
|
+
/** Edit/Write diff presentation; default `auto` (side-by-side ≥110
|
|
815
|
+
* columns, unified below). */
|
|
816
|
+
diffLayout?: 'auto' | 'split' | 'unified';
|
|
749
817
|
/** Show the segmented context bar row in the status footer; default on
|
|
750
818
|
* (cordis.yml `contextBar: false` hides it, issue #29). */
|
|
751
819
|
contextBar?: boolean;
|