koneck 2.128.55 → 2.128.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/diff-layout.d.ts +120 -0
- package/dist/diff-layout.d.ts.map +1 -0
- package/dist/diff-layout.js +173 -0
- package/dist/diff-layout.js.map +1 -0
- package/dist/ink-chat.d.ts +36 -13
- package/dist/ink-chat.d.ts.map +1 -1
- package/dist/ink-chat.js +133 -122
- package/dist/ink-chat.js.map +1 -1
- package/dist/session-changes.d.ts +1 -1
- package/dist/session-changes.d.ts.map +1 -1
- package/dist/session-changes.js +5 -2
- package/dist/session-changes.js.map +1 -1
- package/dist/web/api.d.ts.map +1 -1
- package/dist/web/api.js +10 -0
- package/dist/web/api.js.map +1 -1
- package/dist/web/sessions.d.ts +6 -0
- package/dist/web/sessions.d.ts.map +1 -1
- package/dist/web/sessions.js +8 -0
- package/dist/web/sessions.js.map +1 -1
- package/dist/web/ui-client.d.ts.map +1 -1
- package/dist/web/ui-client.js +79 -6
- package/dist/web/ui-client.js.map +1 -1
- package/dist/web/ui-css.d.ts.map +1 -1
- package/dist/web/ui-css.js +16 -0
- package/dist/web/ui-css.js.map +1 -1
- package/package.json +1 -1
package/dist/ink-chat.js
CHANGED
|
@@ -46,6 +46,7 @@ import { SessionChanges, readForDiff as readForDiffAt, totalsOf } from './sessio
|
|
|
46
46
|
import os from 'node:os';
|
|
47
47
|
import { modeLine, nextMode, statusFacts, barCells, PLACEHOLDER, contextSplit, contextSplitLines } from './status-line.js';
|
|
48
48
|
import { KONECK_VERSION } from './version.js';
|
|
49
|
+
import { fileListView, listLength, diffRows, PANEL_CONTEXT, PANEL_DIFF_LINES } from './diff-layout.js';
|
|
49
50
|
import { savePlan, plansDir, PLAN_CHOICES, PROCEED_PROMPT } from './plan-file.js';
|
|
50
51
|
import { logoFor } from './logo.js';
|
|
51
52
|
import { describeActivity } from './activity.js';
|
|
@@ -793,7 +794,7 @@ export function expandPastes(draft, pastes) {
|
|
|
793
794
|
* capped: a 4,000-line rewrite scrolled past is no more readable in a panel than in a transcript,
|
|
794
795
|
* and the count of what was left out is shown instead.
|
|
795
796
|
*/
|
|
796
|
-
|
|
797
|
+
// PANEL_DIFF_LINES lives in diff-layout.ts, so the browser's Changes view caps at the same point.
|
|
797
798
|
/** The tabs of the usage pane, in the order they are shown. */
|
|
798
799
|
const USAGE_TABS = ['settings', 'session', 'context', 'models', 'limits'];
|
|
799
800
|
const MIN_REPLY_WIDTH = 24; // below this a table cannot render legibly anyway
|
|
@@ -1263,6 +1264,34 @@ export function inkApprovalDecision(requireApproval, mode, tool, argsJson, rules
|
|
|
1263
1264
|
return rules.some(rule => ruleMatches(rule, tool, argsJson)) ? 'allow' : 'ask';
|
|
1264
1265
|
}
|
|
1265
1266
|
/** A context report names the evidence for its denominator instead of pretending every model fits. */
|
|
1267
|
+
/**
|
|
1268
|
+
* What the turn is waiting on, for the bracket after the elapsed time.
|
|
1269
|
+
*
|
|
1270
|
+
* Reasoning comes first once any has arrived. A reasoning chunk carries no reply text, so it
|
|
1271
|
+
* counted as a frame "before content" and the line said "ollama is holding the line, no output
|
|
1272
|
+
* yet" — with the thinking count tacked on after it — for the whole time the model was visibly
|
|
1273
|
+
* thinking. Reported as: "koneck write 'ollama is holding the line, no output' yet the model is
|
|
1274
|
+
* already thinking". Holding the line is for frames that carry nothing at all: a gateway's
|
|
1275
|
+
* keep-alives. The browser already said "thinking" here.
|
|
1276
|
+
*/
|
|
1277
|
+
export function waitDetail(w) {
|
|
1278
|
+
const thinking = `thinking, ${fmtTokens(Math.round(w.thinkingChars / 4))} reasoning tokens`;
|
|
1279
|
+
if (w.liveTokens > 0) {
|
|
1280
|
+
// Reply text has come and the model has gone back to reasoning: both are true, both are said.
|
|
1281
|
+
return `${fmtTokens(w.liveTokens)} tokens` + (w.thinkingChars > 0 && w.thinkingRecent ? ` | ${thinking}` : '');
|
|
1282
|
+
}
|
|
1283
|
+
if (w.tool)
|
|
1284
|
+
return 'running ' + w.tool;
|
|
1285
|
+
if (w.thinkingChars > 0)
|
|
1286
|
+
return thinking;
|
|
1287
|
+
// A gateway routing to several backends sends content-free frames while it finds one. Saying
|
|
1288
|
+
// "waiting for the first token" through that reads as a hang, when the request was accepted.
|
|
1289
|
+
if (w.frames > 0)
|
|
1290
|
+
return `${w.provider} is holding the line, no output yet`;
|
|
1291
|
+
if (w.elapsedMs > 8_000)
|
|
1292
|
+
return `no response from ${w.provider} yet`;
|
|
1293
|
+
return 'starting';
|
|
1294
|
+
}
|
|
1266
1295
|
export function contextStatusText(model, sessionPromptTokens, configuredLimit, detected) {
|
|
1267
1296
|
const known = detected?.limit ?? configuredLimit ?? contextWindowFor(model);
|
|
1268
1297
|
const source = detected?.source === 'provider' ? 'reported by the connected provider'
|
|
@@ -1534,96 +1563,71 @@ function renderCodeLine(text, kind, width, lang) {
|
|
|
1534
1563
|
/**
|
|
1535
1564
|
* The live diff panel — the work as it happens, beside the conversation.
|
|
1536
1565
|
*
|
|
1537
|
-
*
|
|
1538
|
-
*
|
|
1539
|
-
*
|
|
1540
|
-
*
|
|
1541
|
-
*
|
|
1566
|
+
* Laid out the way Claude Code's is, which was the picture it was asked for with: the changed files
|
|
1567
|
+
* listed at the top with their own counts, tests and generated files folded into one line, and
|
|
1568
|
+
* below a rule the one file being read. Its changed lines sit on a band the whole width of the
|
|
1569
|
+
* panel, and a line too long for it wraps, carrying its + or - onto each piece.
|
|
1570
|
+
*
|
|
1571
|
+
* It used to stack every file's diff in one column, so the fifteenth file was a scroll through
|
|
1572
|
+
* fourteen others, and a long line was cut at the panel's edge. What is listed, in what order,
|
|
1573
|
+
* what is folded and how a line wraps are decided in diff-layout.ts, which the browser's Changes
|
|
1574
|
+
* view uses too.
|
|
1575
|
+
*
|
|
1576
|
+
* Alt+↑/↓ (or Alt+P/N, for Windows Terminal, which keeps Alt+arrows for its own panes) moves
|
|
1577
|
+
* through the list, Alt+PgUp/PgDn and the wheel scroll the file, Alt+T opens the fold.
|
|
1542
1578
|
*/
|
|
1543
|
-
export function DiffPanel({ files, width, height, activity, activityRows = 0, scroll = 0 }) {
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
const
|
|
1549
|
-
const
|
|
1550
|
-
const
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
*/
|
|
1561
|
-
if (shownFiles > 0)
|
|
1562
|
-
rows.push({ t: 'rule', key: `r-${d.path}` });
|
|
1563
|
-
const numbers = d.hunks.flatMap(h => h.lines.map(l => l.after ?? l.before ?? 0));
|
|
1564
|
-
const gutter = Math.max(3, String(Math.max(0, ...numbers)).length);
|
|
1565
|
-
const codeWidth = Math.max(12, inner - gutter - 2);
|
|
1566
|
-
const lang = spanLangFor(d.path);
|
|
1567
|
-
const tag = d.created ? 'new' : d.deleted ? 'deleted' : `+${d.added}${d.removed ? ` -${d.removed}` : ''}`;
|
|
1568
|
-
rows.push({ t: 'head', key: `h-${d.path}`, path: d.path, tag, deleted: d.deleted });
|
|
1569
|
-
if (d.binary) {
|
|
1570
|
-
rows.push({ t: 'binary', key: `b-${d.path}` });
|
|
1571
|
-
shownFiles++;
|
|
1572
|
-
continue;
|
|
1573
|
-
}
|
|
1574
|
-
for (const [hi, hunk] of d.hunks.entries()) {
|
|
1575
|
-
if (hi > 0)
|
|
1576
|
-
rows.push({ t: 'gap', key: `g-${d.path}-${hi}`, gutter });
|
|
1577
|
-
for (const [li, l] of hunk.lines.entries()) {
|
|
1578
|
-
rows.push({
|
|
1579
|
-
t: 'line', key: `l-${d.path}-${hi}-${li}`, gutter, codeWidth, lang,
|
|
1580
|
-
shown: (l.kind === 'del' ? l.before : l.after) ?? undefined,
|
|
1581
|
-
sign: l.kind === 'add' ? '+' : l.kind === 'del' ? '-' : ' ',
|
|
1582
|
-
kind: l.kind, text: l.text,
|
|
1583
|
-
});
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
if (d.hiddenLines > 0) {
|
|
1587
|
-
rows.push({ t: 'more', key: `m-${d.path}`, gutter, count: d.hiddenLines });
|
|
1588
|
-
}
|
|
1589
|
-
shownFiles++;
|
|
1590
|
-
rows.push({ t: 'spacer', key: `s-${d.path}` });
|
|
1579
|
+
export function DiffPanel({ files, width, height, activity, activityRows = 0, scroll = 0, selected = 0, showGenerated = false }) {
|
|
1580
|
+
// A rule down the left instead of a box round it: two rows more for the diff, and the panel
|
|
1581
|
+
// reads as a pane beside the conversation, as in the panel it was modelled on.
|
|
1582
|
+
const inner = Math.max(20, width - 3);
|
|
1583
|
+
const room = Math.max(4, height - activityRows);
|
|
1584
|
+
const listLen = listLength(files, showGenerated);
|
|
1585
|
+
const listRows = Math.max(1, Math.min(listLen, 8, Math.max(2, Math.floor(room * 0.3))));
|
|
1586
|
+
const view = fileListView(files, { selected, rows: listRows, showGenerated });
|
|
1587
|
+
const d = view.selected >= 0 ? files[view.selected] : undefined;
|
|
1588
|
+
const counts = (added, removed, tag) => (_jsxs(_Fragment, { children: [tag !== '' && added === 0 && removed === 0 && _jsx(Text, { color: DIM, children: tag }), added > 0 && _jsxs(Text, { color: DIFF_ADD, children: ["+", added] }), removed > 0 && _jsxs(Text, { color: DIFF_DEL, children: [added > 0 ? ' ' : '', "-", removed] })] }));
|
|
1589
|
+
const list = view.rows.map(f => (_jsxs(Box, { justifyContent: "space-between", width: inner, children: [_jsxs(Text, { color: f.selected ? INK : MUTED, bold: f.selected, wrap: "truncate-start", children: [f.selected ? '› ' : ' ', f.path] }), _jsxs(Text, { children: [" ", counts(f.added, f.removed, f.tag)] })] }, `f-${f.path}`)));
|
|
1590
|
+
const hints = [];
|
|
1591
|
+
if (view.above > 0 || view.below > 0) {
|
|
1592
|
+
hints.push(_jsxs(Text, { color: DIM, wrap: "truncate", children: [view.above > 0 ? ` ↑ ${view.above} more above` : '', view.above > 0 && view.below > 0 ? ' ·' : '', view.below > 0 ? ` ↓ ${view.below} more below` : '', " (alt+\u2193 to move)"] }, "more"));
|
|
1593
|
+
}
|
|
1594
|
+
if (view.folded.count > 0) {
|
|
1595
|
+
hints.push(_jsx(Text, { color: DIM, wrap: "truncate", children: ` ${view.folded.count} tests/generated (alt+t to ${view.folded.open ? 'hide' : 'show'})` }, "fold"));
|
|
1591
1596
|
}
|
|
1597
|
+
/*
|
|
1598
|
+
* Only the rows on screen are built into elements.
|
|
1599
|
+
*
|
|
1600
|
+
* Measured on a Raspberry Pi 5 with two 200-line diffs: building every row's elements on every
|
|
1601
|
+
* frame cost 281ms a repaint, against 1.09ms with the panel shut. A row object is cheap; a React
|
|
1602
|
+
* element per row is not. So the rows are described in full — the scroll needs the count — and
|
|
1603
|
+
* drawn only for the window.
|
|
1604
|
+
*/
|
|
1605
|
+
const fixedRows = 2 + list.length + hints.length + (d ? 4 : 0) + (activity ? activityRows : 0);
|
|
1606
|
+
const bodyHeight = Math.max(1, height - fixedRows);
|
|
1607
|
+
const numbers = d ? d.hunks.flatMap(h => h.lines.map(l => l.after ?? l.before ?? 0)) : [];
|
|
1608
|
+
const gutter = Math.max(3, String(Math.max(0, ...numbers)).length);
|
|
1609
|
+
const codeWidth = Math.max(8, inner - gutter - 3);
|
|
1610
|
+
const rows = d ? diffRows(d, codeWidth) : [];
|
|
1592
1611
|
const maxScroll = Math.max(0, rows.length - bodyHeight);
|
|
1593
1612
|
const from = Math.max(0, Math.min(scroll, maxScroll));
|
|
1594
|
-
const
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
return _jsx(Text, { children: " " }, r.key);
|
|
1608
|
-
default:
|
|
1609
|
-
/*
|
|
1610
|
-
* One Text per line, with the pieces nested inside it, rather than a Box of siblings.
|
|
1611
|
-
*
|
|
1612
|
-
* A Box is a layout node: the engine measures and places it and every child. A Text with
|
|
1613
|
-
* Texts inside it is one node holding spans. With forty lines on screen and half a dozen
|
|
1614
|
-
* pieces each that is the difference between laying out two hundred and fifty nodes a
|
|
1615
|
-
* frame and laying out forty — and it is the same mistake that stranded the composer's
|
|
1616
|
-
* cursor on the first row, in a place where it costs time rather than correctness.
|
|
1617
|
-
*/
|
|
1618
|
-
return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: DIM, children: String(r.shown ?? '').padStart(r.gutter) }), _jsx(Text, { color: r.kind === 'add' ? DIFF_ADD : r.kind === 'del' ? DIFF_DEL : DIM, ...(r.kind !== 'ctx'
|
|
1619
|
-
? { backgroundColor: r.kind === 'add' ? DIFF_ADD_BG : DIFF_DEL_BG } : {}), children: r.sign }), renderCodeLine(r.text, r.kind, r.codeWidth, r.lang)] }, r.key));
|
|
1620
|
-
}
|
|
1613
|
+
const lang = d ? spanLangFor(d.path) : undefined;
|
|
1614
|
+
const body = rows.slice(from, from + bodyHeight).map((r, i) => {
|
|
1615
|
+
const key = `d-${from + i}`;
|
|
1616
|
+
if (r.t === 'binary')
|
|
1617
|
+
return _jsx(Text, { color: DIM, children: " binary \u2014 not shown" }, key);
|
|
1618
|
+
if (r.t === 'more')
|
|
1619
|
+
return _jsx(Text, { color: DIM, children: `${' '.repeat(gutter)} ⋯ ${r.count} more lines not shown` }, key);
|
|
1620
|
+
const bg = r.kind === 'add' ? DIFF_ADD_BG : r.kind === 'del' ? DIFF_DEL_BG : undefined;
|
|
1621
|
+
const fg = r.kind === 'add' ? DIFF_ADD : r.kind === 'del' ? DIFF_DEL : DIM;
|
|
1622
|
+
const tint = bg ? { backgroundColor: bg } : {};
|
|
1623
|
+
// One Text with the pieces nested: a Box of siblings is a layout node per piece, forty lines
|
|
1624
|
+
// times half a dozen pieces a frame.
|
|
1625
|
+
return (_jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: r.kind === 'ctx' ? DIM : fg, ...tint, children: [(r.first && r.number !== null ? String(r.number) : '').padStart(gutter), ' ', r.sign, ' '] }), renderCodeLine(r.text, r.kind, codeWidth, lang)] }, key));
|
|
1621
1626
|
});
|
|
1622
|
-
const
|
|
1623
|
-
? '
|
|
1624
|
-
:
|
|
1625
|
-
|
|
1626
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, height: height, borderStyle: "round", borderColor: CYAN, paddingX: 1, flexShrink: 0, overflowY: "hidden", children: [_jsxs(Box, { justifyContent: "space-between", width: inner, children: [_jsxs(Box, { children: [_jsx(Text, { color: INK, bold: true, children: summary }), added > 0 && _jsxs(Text, { color: DIFF_ADD, children: [" +", added] }), removed > 0 && _jsxs(Text, { color: DIFF_DEL, children: [" -", removed] })] }), _jsx(Text, { color: DIM, children: "/diff" })] }), activity && _jsxs(_Fragment, { children: [_jsx(Text, { color: DIM, children: '─'.repeat(inner) }), _jsx(Box, { flexDirection: "column", height: Math.max(1, activityRows - 1), overflowY: "hidden", children: activity })] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflowY: "hidden", children: body }), shownFiles < files.length && (_jsx(Text, { color: DIM, children: `… ${files.length - shownFiles} more file(s) changed` }))] }));
|
|
1627
|
+
const scrolled = maxScroll > 0
|
|
1628
|
+
? `${from > 0 ? `↑${from}` : ''}${from < maxScroll ? ` ↓${maxScroll - from}` : ''}`.trim()
|
|
1629
|
+
: '';
|
|
1630
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, height: height, borderStyle: "single", borderColor: DIM, borderTop: false, borderRight: false, borderBottom: false, paddingLeft: 1, flexShrink: 0, overflowY: "hidden", children: [_jsxs(Box, { justifyContent: "space-between", width: inner, children: [_jsxs(Box, { children: [_jsx(Text, { color: INK, bold: true, children: files.length === 0 ? 'no changes yet' : `${files.length} file${files.length === 1 ? '' : 's'} changed` }), view.added > 0 && _jsxs(Text, { color: DIFF_ADD, children: [" +", view.added] }), view.removed > 0 && _jsxs(Text, { color: DIFF_DEL, children: [" -", view.removed] })] }), _jsx(Text, { color: DIM, children: "/diff off" })] }), activity && _jsxs(_Fragment, { children: [_jsx(Text, { color: DIM, children: '─'.repeat(inner) }), _jsx(Box, { flexDirection: "column", height: Math.max(1, activityRows - 1), overflowY: "hidden", children: activity })] }), _jsx(Text, { children: " " }), list, hints, d && _jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: DIM, children: '─'.repeat(inner) }), _jsxs(Box, { justifyContent: "space-between", width: inner, children: [_jsx(Text, { color: INK, bold: true, wrap: "truncate-start", children: d.path }), _jsxs(Text, { color: DIM, children: [d.created && d.removed === 0 ? ' new' : d.deleted ? ' deleted' : '', scrolled ? ` ${scrolled}` : ''] })] }), _jsx(Text, { color: DIM, children: '─'.repeat(inner) })] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, flexShrink: 1, overflowY: "hidden", children: body })] }));
|
|
1627
1631
|
}
|
|
1628
1632
|
/**
|
|
1629
1633
|
* Exported for one reason: so a test can mount it.
|
|
@@ -1950,7 +1954,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
1950
1954
|
* the truth about what the change currently is.
|
|
1951
1955
|
*/
|
|
1952
1956
|
if (changesRef.current.size) {
|
|
1953
|
-
const all = changesRef.current.diffs(PANEL_DIFF_LINES);
|
|
1957
|
+
const all = changesRef.current.diffs(PANEL_DIFF_LINES, PANEL_CONTEXT);
|
|
1954
1958
|
setLiveDiffs(all);
|
|
1955
1959
|
/*
|
|
1956
1960
|
* Open itself on the session's first change, once, unless the person has already decided.
|
|
@@ -2372,6 +2376,9 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
2372
2376
|
* studying a diff should not cost your place in the conversation.
|
|
2373
2377
|
*/
|
|
2374
2378
|
const [panelScroll, setPanelScroll] = useState(0);
|
|
2379
|
+
/** Which file of the panel's list is being read, and whether tests and generated files are listed. */
|
|
2380
|
+
const [panelFile, setPanelFile] = useState(0);
|
|
2381
|
+
const [panelGenerated, setPanelGenerated] = useState(false);
|
|
2375
2382
|
/**
|
|
2376
2383
|
* The layout the wheel listener needs, in refs.
|
|
2377
2384
|
*
|
|
@@ -5859,6 +5866,24 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
5859
5866
|
* needs `/mouse on`. This is the keyboard equivalent, and it is deliberately a modifier
|
|
5860
5867
|
* nothing else in the composer uses, so it cannot be reached by accident mid-sentence.
|
|
5861
5868
|
*/
|
|
5869
|
+
/*
|
|
5870
|
+
* Alt+↑/↓ moves through the panel's list of files, as in the panel it was modelled on; the
|
|
5871
|
+
* file itself scrolls with Alt+PgUp/PgDn and the wheel. Alt+P/N do the same for Windows
|
|
5872
|
+
* Terminal, which keeps Alt+arrows for moving between its own panes. Alt+T opens the fold
|
|
5873
|
+
* of tests and generated files.
|
|
5874
|
+
*/
|
|
5875
|
+
const prevFile = key.meta && (key.upArrow || input === 'p');
|
|
5876
|
+
const nextFile = key.meta && (key.downArrow || input === 'n');
|
|
5877
|
+
if (diffPanelOpenRef.current && (prevFile || nextFile)) {
|
|
5878
|
+
const last = Math.max(0, listLength(liveDiffs, panelGenerated) - 1);
|
|
5879
|
+
setPanelFile(v => Math.max(0, Math.min(last, v + (prevFile ? -1 : 1))));
|
|
5880
|
+
setPanelScroll(0);
|
|
5881
|
+
return;
|
|
5882
|
+
}
|
|
5883
|
+
if (diffPanelOpenRef.current && key.meta && input === 't') {
|
|
5884
|
+
setPanelGenerated(v => !v);
|
|
5885
|
+
return;
|
|
5886
|
+
}
|
|
5862
5887
|
if (key.meta && (key.upArrow || key.downArrow)) {
|
|
5863
5888
|
setPanelScroll(v => Math.max(0, Math.min(2_000, v + (key.upArrow ? -3 : 3))));
|
|
5864
5889
|
return;
|
|
@@ -6856,34 +6881,25 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
6856
6881
|
const gutter = Math.max(2, String(Math.max(0, ...numbers)).length);
|
|
6857
6882
|
const width = Math.max(24, barWidth - gutter - 8);
|
|
6858
6883
|
const header = d.created ? 'new file' : d.deleted ? 'deleted' : diffSummary(d);
|
|
6859
|
-
const
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
sign: l.kind === 'add' ? '+' : l.kind === 'del' ? '-' : ' ',
|
|
6867
|
-
text: l.text });
|
|
6868
|
-
}
|
|
6869
|
-
}
|
|
6884
|
+
const lang = spanLangFor(d.path);
|
|
6885
|
+
/*
|
|
6886
|
+
* Rows from diff-layout.ts, the same the panel draws: a long line wraps with its sign on each
|
|
6887
|
+
* piece rather than being cut at the edge, and the band behind a changed line covers its
|
|
6888
|
+
* number too. The ceiling counts rows as drawn, so a wrapped line costs what it takes.
|
|
6889
|
+
*/
|
|
6890
|
+
const all = diffRows({ ...d, hiddenLines: 0 }, width);
|
|
6870
6891
|
// A budget of zero is meaningful: show the file and what happened to it, and none of the
|
|
6871
6892
|
// body. On a short terminal those lines are the difference between a calm screen and one
|
|
6872
6893
|
// that wipes itself on every keystroke.
|
|
6873
6894
|
const shownLines = all.slice(0, Math.max(0, maxLines));
|
|
6874
6895
|
const clipped = all.length - shownLines.length;
|
|
6875
|
-
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: DIM, children: "\u2514" }), _jsx(Text, { color: CYAN, children: d.path }), _jsx(Text, { color: d.created ? DIFF_ADD : d.deleted ? DIFF_DEL : MUTED, children: header })] }),
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
const bg = l.kind === 'add' ? DIFF_ADD_BG : l.kind === 'del' ? DIFF_DEL_BG : undefined;
|
|
6883
|
-
// Tabs would make the tinted block a ragged width, so they are expanded.
|
|
6884
|
-
const body = `${l.sign} ${l.text}`.replace(/\t/g, ' ');
|
|
6885
|
-
return (_jsxs(Box, { children: [_jsxs(Text, { color: DIM, children: [String(l.shown ?? '').padStart(gutter), " "] }), _jsx(Text, { color: fg, ...(bg ? { backgroundColor: bg } : {}), children: fitCells(body, width, true) })] }, l.key));
|
|
6886
|
-
})()), clipped > 0 && (_jsxs(Text, { color: DIM, children: [' '.repeat(gutter), " ", shownLines.length === 0 ? '' : '... ', clipped, " ", shownLines.length === 0 ? 'changed lines' : 'more lines', ", shown in full when this finishes"] })), d.hiddenLines > 0 && (_jsxs(Text, { color: DIM, children: [' '.repeat(gutter), " ... ", d.hiddenLines, " more changed lines"] }))] }, key));
|
|
6896
|
+
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: DIM, children: "\u2514" }), _jsx(Text, { color: CYAN, children: d.path }), _jsx(Text, { color: d.created ? DIFF_ADD : d.deleted ? DIFF_DEL : MUTED, children: header })] }), shownLines.map((r, i) => {
|
|
6897
|
+
if (r.t !== 'line')
|
|
6898
|
+
return _jsx(Text, { color: DIM, children: ' binary file - not shown' }, i);
|
|
6899
|
+
const fg = r.kind === 'add' ? DIFF_ADD : r.kind === 'del' ? DIFF_DEL : DIM;
|
|
6900
|
+
const bg = r.kind === 'add' ? DIFF_ADD_BG : r.kind === 'del' ? DIFF_DEL_BG : undefined;
|
|
6901
|
+
return (_jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: fg, ...(bg ? { backgroundColor: bg } : {}), children: [(r.first && r.number !== null ? String(r.number) : '').padStart(gutter), ' ', r.sign, ' '] }), renderCodeLine(r.text, r.kind, width, lang)] }, i));
|
|
6902
|
+
}), clipped > 0 && (_jsxs(Text, { color: DIM, children: [' '.repeat(gutter), " ", shownLines.length === 0 ? '' : '... ', clipped, " ", shownLines.length === 0 ? 'changed lines' : 'more lines', ", shown in full when this finishes"] })), d.hiddenLines > 0 && (_jsxs(Text, { color: DIM, children: [' '.repeat(gutter), " ... ", d.hiddenLines, " more changed lines"] }))] }, key));
|
|
6887
6903
|
};
|
|
6888
6904
|
const renderStep = (step, key) => {
|
|
6889
6905
|
if (step.kind === 'say') {
|
|
@@ -7076,17 +7092,12 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
7076
7092
|
})(), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsxs(Text, { color: GREEN, children: [animate ? SPINNER[spinFrame] : '●', " "] }), _jsx(Shimmer, { text: workWord({
|
|
7077
7093
|
elapsedMs, tools: turnToolsRef.current,
|
|
7078
7094
|
tokens: liveTokens, recovered: turnRecoveredRef.current,
|
|
7079
|
-
}, spinWord)[0], frame: shimmerFrame, base: MUTED }), _jsx(Text, { color: MUTED, children: "\u2026 " }), _jsxs(Text, { color: DIM, children: ["(", fmtElapsed(elapsedMs), " | ",
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
: providerFramesRef.current > 0 ? `${cfg.provider} is holding the line, no output yet`
|
|
7086
|
-
: elapsedMs > 8_000 ? `no response from ${cfg.provider} yet`
|
|
7087
|
-
: 'starting', thinkingRef.current.chars > 0 && Date.now() - thinkingRef.current.at < 2_000
|
|
7088
|
-
? ` | thinking, ${fmtTokens(Math.round(thinkingRef.current.chars / 4))} reasoning tokens`
|
|
7089
|
-
: '', ")"] })] }) }), showThinking && !ask && thinkingTextRef.current.trim() !== '' && (_jsx(Box, { flexDirection: "column", paddingLeft: 2, children: thinkingTail(thinkingTextRef.current, THINKING_LINES, barWidth - 6).map((line, i) => (_jsx(Text, { color: DIM, italic: true, children: line }, i))) })), _jsx(Box, { paddingLeft: 2, children: _jsx(Text, { color: DIM, children: "esc to cancel and keep your input \u00B7 ctrl+c to exit" }) })] })) }), usagePane && (() => {
|
|
7095
|
+
}, spinWord)[0], frame: shimmerFrame, base: MUTED }), _jsx(Text, { color: MUTED, children: "\u2026 " }), _jsxs(Text, { color: DIM, children: ["(", fmtElapsed(elapsedMs), " | ", waitDetail({
|
|
7096
|
+
liveTokens, tool: liveToolRef.current?.name ?? null, provider: cfg.provider,
|
|
7097
|
+
frames: providerFramesRef.current, elapsedMs,
|
|
7098
|
+
thinkingChars: thinkingRef.current.chars,
|
|
7099
|
+
thinkingRecent: Date.now() - thinkingRef.current.at < 2_000,
|
|
7100
|
+
}), ")"] })] }) }), showThinking && !ask && thinkingTextRef.current.trim() !== '' && (_jsx(Box, { flexDirection: "column", paddingLeft: 2, children: thinkingTail(thinkingTextRef.current, THINKING_LINES, barWidth - 6).map((line, i) => (_jsx(Text, { color: DIM, italic: true, children: line }, i))) })), _jsx(Box, { paddingLeft: 2, children: _jsx(Text, { color: DIM, children: "esc to cancel and keep your input \u00B7 ctrl+c to exit" }) })] })) }), usagePane && (() => {
|
|
7090
7101
|
const width = Math.min(barWidth, usagePane === 'settings' ? 96 : 66);
|
|
7091
7102
|
const inner = width - 4;
|
|
7092
7103
|
const BAR_W = Math.max(12, inner - 14);
|
|
@@ -7510,7 +7521,7 @@ export function App({ config: initialConfig, clearFrame, synchronizedFrames = fa
|
|
|
7510
7521
|
: '*type the number, or arrows and enter · esc refuses*',
|
|
7511
7522
|
];
|
|
7512
7523
|
return (_jsx(Box, { marginTop: 1, children: _jsx(Panel, { width: panelWidth(lines, barWidth, 46), color: AMBER, title: `Permission — ${modeSpec(mode).label} mode`, children: lines }) }));
|
|
7513
|
-
})()] }), sidePanelOpen && (_jsx(Box, { flexDirection: "column", width: diffPanelWidth, flexShrink: 0, children: diffPanelOpen && _jsx(DiffPanel, { files: liveDiffs, width: diffPanelWidth, scroll: panelScroll, height: Math.max(6, frameRows - 6), activityRows: activityPanelOpen ? 4 : 0, activity: activityPanelOpen ? _jsxs(_Fragment, { children: [_jsxs(Text, { color: CYAN, bold: true, children: ["Activity \u00B7 ", fmtElapsed(busyStart.current ? Date.now() - busyStart.current : 0)] }), _jsx(Text, { color: AMBER, wrap: "truncate", children: ask ? 'Waiting for your approval' : liveToolRef.current
|
|
7524
|
+
})()] }), sidePanelOpen && (_jsx(Box, { flexDirection: "column", width: diffPanelWidth, flexShrink: 0, children: diffPanelOpen && _jsx(DiffPanel, { files: liveDiffs, width: diffPanelWidth, scroll: panelScroll, selected: panelFile, showGenerated: panelGenerated, height: Math.max(6, frameRows - 6), activityRows: activityPanelOpen ? 4 : 0, activity: activityPanelOpen ? _jsxs(_Fragment, { children: [_jsxs(Text, { color: CYAN, bold: true, children: ["Activity \u00B7 ", fmtElapsed(busyStart.current ? Date.now() - busyStart.current : 0)] }), _jsx(Text, { color: AMBER, wrap: "truncate", children: ask ? 'Waiting for your approval' : liveToolRef.current
|
|
7514
7525
|
? 'Running ' + liveToolRef.current.name : thinkingRef.current.chars > 0
|
|
7515
7526
|
? 'Waiting for model · reasoning received' : 'Waiting for model' }), _jsx(Text, { color: MUTED, wrap: "truncate", children: liveToolRef.current?.detail || plan?.steps.find(s => s.state !== 'done')?.text || 'esc to interrupt' })] }) : undefined }) }))] }), picker && (() => {
|
|
7516
7527
|
const list = visiblePickerItems;
|