@vincemakes/kiso-tui-cells 0.39.2 → 0.40.1
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/approval-panel.d.ts +35 -3
- package/dist/approval-panel.js +66 -10
- package/dist/components.d.ts +16 -1
- package/dist/components.js +32 -6
- package/dist/diff.d.ts +5 -2
- package/dist/diff.js +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/render.d.ts +1 -6
- package/dist/render.js +19 -0
- package/dist/strings.d.ts +15 -2
- package/dist/strings.js +36 -5
- package/package.json +1 -1
package/dist/approval-panel.d.ts
CHANGED
|
@@ -458,10 +458,42 @@ export declare function panelAffordance(view: PanelView, phase: PanelPhase, curs
|
|
|
458
458
|
* height is its row count (the W20 rule the #checked throw demands).
|
|
459
459
|
*/
|
|
460
460
|
export declare function pickBlockRows(view: PanelView, state: PickRuntime, W: number, maxRows: number): string[];
|
|
461
|
-
/** The digits are the keys, so
|
|
462
|
-
* the digits there are
|
|
463
|
-
*
|
|
461
|
+
/** The digits are the keys, so what one SCREEN offers is still bounded by
|
|
462
|
+
* the digits there are — but the list itself is no longer bounded by it.
|
|
463
|
+
*
|
|
464
|
+
* Finding DC-58 (the owner, 2026-09-21): this constant used to cap what the
|
|
465
|
+
* KEYBOARD could reach (`/model` on a 60-profile config offered nine and
|
|
466
|
+
* said `/model <name>` for the rest). The window now follows the cursor —
|
|
467
|
+
* `pickWindow` — so PICK_MAX is only how many rows one screen shows, and
|
|
468
|
+
* ↑↓ walks every option.
|
|
469
|
+
*/
|
|
464
470
|
export declare const PICK_MAX = 9;
|
|
471
|
+
/** The option window the pick panel draws and the digits name: `size` rows
|
|
472
|
+
* starting at `first`, with `first` derived from the cursor so it cannot
|
|
473
|
+
* drift from it. Edge-following (the window starts moving only when the
|
|
474
|
+
* cursor leaves it) and clamped at both ends, so the last page is full
|
|
475
|
+
* rather than short.
|
|
476
|
+
*
|
|
477
|
+
* Pure, and the ONE copy: the renderer, the digit keys and the click
|
|
478
|
+
* hit-test all ask this function rather than each deriving an offset.
|
|
479
|
+
*/
|
|
480
|
+
/** The pick panel's window for THIS frame's budget — the one derivation the
|
|
481
|
+
* renderer and the input layer share (review of this round).
|
|
482
|
+
*
|
|
483
|
+
* The renderer draws it; `Dock.visiblePickWindow` hands the SAME value to the
|
|
484
|
+
* digit keys, because a size derived twice is a size that can disagree — and
|
|
485
|
+
* on a short terminal it did (two rows drawn, nine assumed, so `1` could mean
|
|
486
|
+
* a row nobody could see). `phase` is compared as a string so this module
|
|
487
|
+
* keeps its zero-dependency contract.
|
|
488
|
+
*/
|
|
489
|
+
export declare function pickWindowOf(view: PanelView, cursor: number, phase: string, maxRows: number): {
|
|
490
|
+
first: number;
|
|
491
|
+
size: number;
|
|
492
|
+
};
|
|
493
|
+
export declare function pickWindow(cursor: number, count: number, size: number): {
|
|
494
|
+
first: number;
|
|
495
|
+
size: number;
|
|
496
|
+
};
|
|
465
497
|
/** The input row's lead: the digit range while picking, the named
|
|
466
498
|
* prompt while typing one out. */
|
|
467
499
|
export declare function pickLeadPlain(view: PanelView, state: PickRuntime): string;
|
package/dist/approval-panel.js
CHANGED
|
@@ -575,20 +575,37 @@ export function pickBlockRows(view, state, W, maxRows) {
|
|
|
575
575
|
// which it already spends on `profile: <name>`. A second axis that
|
|
576
576
|
// truncates on a normal terminal is not a second axis.
|
|
577
577
|
const strip = spec.options[state.cursor]?.levels !== undefined && state.phase === "options" ? 1 : 0;
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
578
|
+
// 2026-09-21 (finding DC-58, the owner's report): the window FOLLOWS the
|
|
579
|
+
// cursor instead of being pinned to the top. It used to be
|
|
580
|
+
// `slice(0, min(budget, PICK_MAX))`, so with a 60-profile config the
|
|
581
|
+
// keyboard reached the first nine and the rest were reachable only by
|
|
582
|
+
// typing the name.
|
|
583
|
+
//
|
|
584
|
+
// REVIEW of this round: the SIZE depends on the frame's budget, so the
|
|
585
|
+
// renderer and the digit keys used to disagree on a short terminal
|
|
586
|
+
// (drawn rows 1-2, keys computing against nine). `pickWindowOf` is the
|
|
587
|
+
// one derivation both sides use — the renderer draws it, the input layer
|
|
588
|
+
// is HANDED it (`visiblePickWindow`).
|
|
589
|
+
const win = pickWindowOf(view, state.cursor, state.phase, maxRows);
|
|
590
|
+
const shown = spec.options.slice(win.first, win.first + win.size);
|
|
581
591
|
// R2: the note takes a COLUMN, not three spaces after a label of
|
|
582
592
|
// whatever length this row happened to have, and the cursor row
|
|
583
593
|
// wears the bar and the arrow like every other list in the
|
|
584
594
|
// product. This panel was the last one still saying "selected"
|
|
585
595
|
// with bold alone.
|
|
586
|
-
|
|
596
|
+
// 0.40.1 (the owner's dogfood, 2026-09-21): the row NUMBER is gone. The
|
|
597
|
+
// digits still pick by visible position (the keys sheet documents them),
|
|
598
|
+
// but a number the person reads as decoration is noise the label pays
|
|
599
|
+
// for at every width — and the owner picks with ↑↓ and the mouse.
|
|
600
|
+
const lead = (o, _i, cursor) => `${cursor ? "\u2192" : " "} ${escapeTerminal(o.label)}`;
|
|
587
601
|
const widest = Math.max(...shown.map((o, i) => visibleWidth(lead(o, i, false))));
|
|
588
602
|
const stop = shown.some((o) => o.note !== undefined) && widest + 2 <= Math.floor(room / 2) && room - widest - 2 >= 18 ? widest + 2 : 0;
|
|
589
603
|
for (let i = 0; i < shown.length; i += 1) {
|
|
590
604
|
const o = shown[i];
|
|
591
|
-
|
|
605
|
+
// DC-58: `i` is the row's position in the WINDOW, the cursor is an
|
|
606
|
+
// option index — the mark lands on the cursor's own row after any
|
|
607
|
+
// scroll only when the window's origin is added back.
|
|
608
|
+
const mark = win.first + i === state.cursor && state.phase === "options";
|
|
592
609
|
const plain = lead(o, i, mark);
|
|
593
610
|
const head = `${mark ? p.bold : ""}${plain}${mark ? p.reset : ""}`;
|
|
594
611
|
const note = o.note === undefined
|
|
@@ -605,9 +622,10 @@ export function pickBlockRows(view, state, W, maxRows) {
|
|
|
605
622
|
rows.push(mark ? selectionBar(text, visibleWidth(text), W) : ` ${text}`);
|
|
606
623
|
}
|
|
607
624
|
if (spec.options.length > shown.length) {
|
|
608
|
-
|
|
625
|
+
const to = win.first + shown.length;
|
|
626
|
+
rows.push(` ${cutLine(`${p.dim} \u2195 ${win.first + 1}-${to} / ${spec.options.length} \u2014 \u2191\u2193 scrolls${p.reset}`, room)}`);
|
|
609
627
|
}
|
|
610
|
-
const axis = shown[state.cursor];
|
|
628
|
+
const axis = shown[state.cursor - win.first];
|
|
611
629
|
if (strip === 1 && axis?.levels !== undefined) {
|
|
612
630
|
const off = new Set(axis.disabled ?? []);
|
|
613
631
|
// the RUNTIME cursor, not the option's starting index: the option
|
|
@@ -639,10 +657,48 @@ export function pickBlockRows(view, state, W, maxRows) {
|
|
|
639
657
|
rows.push(`${p.dim}${"\u2500".repeat(Math.max(0, W))}${p.reset}`);
|
|
640
658
|
return rows;
|
|
641
659
|
}
|
|
642
|
-
/** The digits are the keys, so
|
|
643
|
-
* the digits there are
|
|
644
|
-
*
|
|
660
|
+
/** The digits are the keys, so what one SCREEN offers is still bounded by
|
|
661
|
+
* the digits there are — but the list itself is no longer bounded by it.
|
|
662
|
+
*
|
|
663
|
+
* Finding DC-58 (the owner, 2026-09-21): this constant used to cap what the
|
|
664
|
+
* KEYBOARD could reach (`/model` on a 60-profile config offered nine and
|
|
665
|
+
* said `/model <name>` for the rest). The window now follows the cursor —
|
|
666
|
+
* `pickWindow` — so PICK_MAX is only how many rows one screen shows, and
|
|
667
|
+
* ↑↓ walks every option.
|
|
668
|
+
*/
|
|
645
669
|
export const PICK_MAX = 9;
|
|
670
|
+
/** The option window the pick panel draws and the digits name: `size` rows
|
|
671
|
+
* starting at `first`, with `first` derived from the cursor so it cannot
|
|
672
|
+
* drift from it. Edge-following (the window starts moving only when the
|
|
673
|
+
* cursor leaves it) and clamped at both ends, so the last page is full
|
|
674
|
+
* rather than short.
|
|
675
|
+
*
|
|
676
|
+
* Pure, and the ONE copy: the renderer, the digit keys and the click
|
|
677
|
+
* hit-test all ask this function rather than each deriving an offset.
|
|
678
|
+
*/
|
|
679
|
+
/** The pick panel's window for THIS frame's budget — the one derivation the
|
|
680
|
+
* renderer and the input layer share (review of this round).
|
|
681
|
+
*
|
|
682
|
+
* The renderer draws it; `Dock.visiblePickWindow` hands the SAME value to the
|
|
683
|
+
* digit keys, because a size derived twice is a size that can disagree — and
|
|
684
|
+
* on a short terminal it did (two rows drawn, nine assumed, so `1` could mean
|
|
685
|
+
* a row nobody could see). `phase` is compared as a string so this module
|
|
686
|
+
* keeps its zero-dependency contract.
|
|
687
|
+
*/
|
|
688
|
+
export function pickWindowOf(view, cursor, phase, maxRows) {
|
|
689
|
+
const count = view.pick?.options.length ?? 0;
|
|
690
|
+
const strip = view.pick?.options[cursor]?.levels !== undefined && phase === "options" ? 1 : 0;
|
|
691
|
+
const chrome = 5 + strip + (count > Math.min(Math.max(1, maxRows - 5 - strip), PICK_MAX) ? 1 : 0);
|
|
692
|
+
const budget = Math.max(1, maxRows - chrome);
|
|
693
|
+
return pickWindow(cursor, count, Math.min(budget, PICK_MAX));
|
|
694
|
+
}
|
|
695
|
+
export function pickWindow(cursor, count, size) {
|
|
696
|
+
const win = Math.max(1, Math.min(size, count));
|
|
697
|
+
if (count <= win)
|
|
698
|
+
return { first: 0, size: win };
|
|
699
|
+
const first = Math.max(0, Math.min(cursor - win + 1, count - win));
|
|
700
|
+
return { first, size: win };
|
|
701
|
+
}
|
|
646
702
|
/** The input row's lead: the digit range while picking, the named
|
|
647
703
|
* prompt while typing one out. */
|
|
648
704
|
export function pickLeadPlain(view, state) {
|
package/dist/components.d.ts
CHANGED
|
@@ -162,6 +162,19 @@ export type BodyCell = {
|
|
|
162
162
|
kind: "notice";
|
|
163
163
|
text: string;
|
|
164
164
|
done: true;
|
|
165
|
+
}
|
|
166
|
+
/** 4c — the resumed session's earlier history, replayed into cells and
|
|
167
|
+
* FOLDED: the row is ONE line on screen and never expands there (a
|
|
168
|
+
* resize reprint of a six-thousand-event session redraws one row);
|
|
169
|
+
* the children are the replayed cells, read in the ctrl+r viewer.
|
|
170
|
+
* `summary` is a compaction checkpoint's text — what the model sees in
|
|
171
|
+
* place of the turns it covers — null for a plain fold. */
|
|
172
|
+
| {
|
|
173
|
+
kind: "fold";
|
|
174
|
+
label: string;
|
|
175
|
+
children: BodyCell[];
|
|
176
|
+
summary: string | null;
|
|
177
|
+
done: true;
|
|
165
178
|
} | {
|
|
166
179
|
kind: "banner";
|
|
167
180
|
version: string;
|
|
@@ -272,7 +285,9 @@ export declare function foldTerms(reads: number, edits: number, others: readonly
|
|
|
272
285
|
* reads a payload it did not write, and a guess about what it means
|
|
273
286
|
* would be a row the product cannot stand behind.
|
|
274
287
|
*/
|
|
275
|
-
|
|
288
|
+
/** `seconds` null: 4c's replayed card, settled from a log with no clock —
|
|
289
|
+
* the head names the outcome and says nothing about time. */
|
|
290
|
+
export declare function askedBlock(resultText: string, seconds: number | null, W: number): string[];
|
|
276
291
|
/** The caps — screen rows counted AFTER the fold, at the current width
|
|
277
292
|
* (the W7 table). The renderer-cut row is inside the cap. */
|
|
278
293
|
/** R13 — ONE preview cap, every tool. It was the shell's alone while
|
package/dist/components.js
CHANGED
|
@@ -30,7 +30,7 @@ import { displayWidth, visibleWidth, widthCut } from "./width.js";
|
|
|
30
30
|
// KEY_BINDINGS). strings.js imports only render/width here, so this edge
|
|
31
31
|
// adds no cycle.
|
|
32
32
|
import { displayVerb } from "./strings.js";
|
|
33
|
-
import { bannerLines, breathFrame, cutLine, escapeTerminal, foldThinking, foldThinkingRow, foldResult, renderTerminalGap, renderToolSummary, toolTarget, kUnit, palette, currentGround, } from "./render.js";
|
|
33
|
+
import { bannerLines, breathFrame, cutLine, escapeTerminal, stripAnsi, foldThinking, foldThinkingRow, foldResult, renderTerminalGap, renderToolSummary, toolTarget, kUnit, palette, currentGround, } from "./render.js";
|
|
34
34
|
// TUI2-MD: the markdown renderer's surface reaches the tui through this
|
|
35
35
|
// module (the tui's components shim re-exports it) — one import edge,
|
|
36
36
|
// and it points one way: md.ts measures with the width authority, never
|
|
@@ -179,6 +179,8 @@ export function cellComponent(cell) {
|
|
|
179
179
|
return new MarkdownBlock(cell);
|
|
180
180
|
case "notice":
|
|
181
181
|
return new ErrorLine(cell);
|
|
182
|
+
case "fold":
|
|
183
|
+
return new FoldRow(cell);
|
|
182
184
|
case "banner":
|
|
183
185
|
return new Banner(cell);
|
|
184
186
|
case "raw":
|
|
@@ -627,7 +629,7 @@ class ToolExecution {
|
|
|
627
629
|
// that is not the ask's own JSON, so a payload this renderer
|
|
628
630
|
// did not write can never be guessed at.
|
|
629
631
|
if (c.name === "ask_user" && c.reason === null && !c.isError) {
|
|
630
|
-
const asked = askedBlock(c.resultText, c.startedAt !== null && c.doneAt !== null ? (c.doneAt - c.startedAt) / 1000 : 0, W);
|
|
632
|
+
const asked = askedBlock(c.resultText, c.startedAt !== null && c.doneAt !== null ? (c.doneAt - c.startedAt) / 1000 : c.startedAt === null && c.doneAt === null ? null : 0, W);
|
|
631
633
|
if (asked.length > 0)
|
|
632
634
|
return asked;
|
|
633
635
|
}
|
|
@@ -644,7 +646,10 @@ class ToolExecution {
|
|
|
644
646
|
out.push(...toolBlockBody(c, W, ctx));
|
|
645
647
|
return out;
|
|
646
648
|
}
|
|
647
|
-
|
|
649
|
+
// 4c: a card settled from the durable log carries no clock at all
|
|
650
|
+
// (the log's events have no timestamps) — it says nothing about
|
|
651
|
+
// time rather than `?s`, which reads as a measurement that failed.
|
|
652
|
+
const elapsed = c.startedAt !== null && c.doneAt !== null ? settledLabel((c.doneAt - c.startedAt) / 1000) : c.startedAt === null && c.doneAt === null ? "" : "?s";
|
|
648
653
|
// TUI2-R1.5 ⑤ (VD-6): the line count is stated EXACTLY ONCE. Every
|
|
649
654
|
// read card carried it twice — `(2 lines, 0.0s) · 2 lines · ctrl+o
|
|
650
655
|
// expands` — because the parens and the suffix were written by
|
|
@@ -1099,6 +1104,8 @@ export function foldTerms(reads, edits, others) {
|
|
|
1099
1104
|
* reads a payload it did not write, and a guess about what it means
|
|
1100
1105
|
* would be a row the product cannot stand behind.
|
|
1101
1106
|
*/
|
|
1107
|
+
/** `seconds` null: 4c's replayed card, settled from a log with no clock —
|
|
1108
|
+
* the head names the outcome and says nothing about time. */
|
|
1102
1109
|
export function askedBlock(resultText, seconds, W) {
|
|
1103
1110
|
let parsed;
|
|
1104
1111
|
try {
|
|
@@ -1111,7 +1118,7 @@ export function askedBlock(resultText, seconds, W) {
|
|
|
1111
1118
|
return [];
|
|
1112
1119
|
const asked = parsed;
|
|
1113
1120
|
const p = palette();
|
|
1114
|
-
const head = (n, outcome) => cutLine(` ${p.bold}asked${p.reset} ${n} ${n === 1 ? "question" : "questions"} ${p.dim}(${outcome}, ${seconds.toFixed(1)}s)${p.reset}`, W);
|
|
1121
|
+
const head = (n, outcome) => cutLine(` ${p.bold}asked${p.reset} ${n} ${n === 1 ? "question" : "questions"} ${p.dim}(${seconds === null ? outcome : `${outcome}, ${seconds.toFixed(1)}s`})${p.reset}`, W);
|
|
1115
1122
|
const row = (body) => cutLine(` ${p.dim}│${p.reset} ${body}`, W);
|
|
1116
1123
|
if (Array.isArray(asked.declined) && asked.declined.length > 0) {
|
|
1117
1124
|
// the honest decline record: WHAT went unanswered, and what the
|
|
@@ -1439,7 +1446,8 @@ function blockRows(text, W, tone = "dim") {
|
|
|
1439
1446
|
// rows around them take `washDim`, which was chosen for that ground.
|
|
1440
1447
|
const open = tone === "dim" ? p.dim : "";
|
|
1441
1448
|
const close = tone === "dim" ? p.reset : "";
|
|
1442
|
-
|
|
1449
|
+
// 0.40.0: the output's own styling is dropped whole before the escape
|
|
1450
|
+
for (const raw of escapeTerminal(stripAnsi(text)).split("\n")) {
|
|
1443
1451
|
for (const row of foldLine(raw, textW))
|
|
1444
1452
|
rows.push(`${open}${bodyRow()}${row}${close}`);
|
|
1445
1453
|
}
|
|
@@ -1643,7 +1651,12 @@ export function diffBody(diff, W, expanded = false) {
|
|
|
1643
1651
|
? `${p.red}- ${escapeTerminal(d.text)}${p.reset}`
|
|
1644
1652
|
: d.kind === "+"
|
|
1645
1653
|
? `${p.green}+ ${escapeTerminal(d.text)}${p.reset}`
|
|
1646
|
-
:
|
|
1654
|
+
: d.kind === "note"
|
|
1655
|
+
? // 0.40.0: a note starts in the MARKER column, where no line
|
|
1656
|
+
// of the file ever does — it reads as kiso's sentence about
|
|
1657
|
+
// the diff, not as an unchanged line inside it
|
|
1658
|
+
`${p.dim}${escapeTerminal(d.text)}${p.reset}`
|
|
1659
|
+
: `${p.dim} ${escapeTerminal(d.text)}${p.reset}`;
|
|
1647
1660
|
// W2: the diff body is a bounded block's body — the │ gutter
|
|
1648
1661
|
// (dim), never the old bold ▎ rail (the table lists no ▎); the
|
|
1649
1662
|
// +/- marks and their colors ride the content
|
|
@@ -1728,6 +1741,19 @@ class ErrorLine {
|
|
|
1728
1741
|
return foldWords(escapeTerminal(this.cell.text), W);
|
|
1729
1742
|
}
|
|
1730
1743
|
}
|
|
1744
|
+
/** 4c — the folded history's ONE row. Dim, like every row that is about
|
|
1745
|
+
* the transcript rather than in it; cut, never wrapped, so it is one row
|
|
1746
|
+
* at every width. */
|
|
1747
|
+
class FoldRow {
|
|
1748
|
+
cell;
|
|
1749
|
+
constructor(cell) {
|
|
1750
|
+
this.cell = cell;
|
|
1751
|
+
}
|
|
1752
|
+
render(W, _ctx) {
|
|
1753
|
+
const p = palette();
|
|
1754
|
+
return [cutLine(` ${p.dim}${escapeTerminal(this.cell.label)}${p.reset}`, W)];
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1731
1757
|
/** The CLI's pre-rendered blocks (the banner, the recap, slash-command
|
|
1732
1758
|
* output) — the SGR applied at composition (render.ts), folded here
|
|
1733
1759
|
* verbatim: the #16b contract (no re-escaping) holds, and the fold is
|
package/dist/diff.d.ts
CHANGED
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
* RENDERER truncates (18 head + 18 tail + "… N lines"); the stats come
|
|
11
11
|
* from the full diff.
|
|
12
12
|
*/
|
|
13
|
-
/** The diff block's per-row kind.
|
|
13
|
+
/** The diff block's per-row kind. `note` (0.40.0) is kiso's sentence
|
|
14
|
+
* ABOUT the diff — the renderer's cut, a search that is not there or is
|
|
15
|
+
* there more than once — never a line of the file, so never drawn where
|
|
16
|
+
* the file's lines are. */
|
|
14
17
|
export type DiffLine = {
|
|
15
|
-
kind: "-" | "+" | " ";
|
|
18
|
+
kind: "-" | "+" | " " | "note";
|
|
16
19
|
text: string;
|
|
17
20
|
};
|
|
18
21
|
export interface DiffResult {
|
package/dist/diff.js
CHANGED
|
@@ -76,7 +76,7 @@ export function truncateDiff(diff) {
|
|
|
76
76
|
const omitted = diff.length - 2 * TRUNCATE_KEEP;
|
|
77
77
|
return [
|
|
78
78
|
...diff.slice(0, TRUNCATE_KEEP),
|
|
79
|
-
{ kind: "
|
|
79
|
+
{ kind: "note", text: `… ${omitted} lines (/last for full)` },
|
|
80
80
|
...diff.slice(diff.length - TRUNCATE_KEEP),
|
|
81
81
|
];
|
|
82
82
|
}
|
|
@@ -117,7 +117,7 @@ export function editFileDiff(oldContent, search, replace, path) {
|
|
|
117
117
|
const at = oldContent.indexOf(search);
|
|
118
118
|
if (at < 0) {
|
|
119
119
|
return {
|
|
120
|
-
lines: [{ kind: "
|
|
120
|
+
lines: [{ kind: "note", text: `pattern not found in ${path ?? "the file"}` }],
|
|
121
121
|
added: 0,
|
|
122
122
|
removed: 0,
|
|
123
123
|
outcome: "not-found",
|
|
@@ -131,7 +131,7 @@ export function editFileDiff(oldContent, search, replace, path) {
|
|
|
131
131
|
// same rule as the miss above, for the same reason.
|
|
132
132
|
if (search.length > 0 && oldContent.indexOf(search, at + 1) > at) {
|
|
133
133
|
return {
|
|
134
|
-
lines: [{ kind: "
|
|
134
|
+
lines: [{ kind: "note", text: `pattern matches more than one place in ${path ?? "the file"}` }],
|
|
135
135
|
added: 0,
|
|
136
136
|
removed: 0,
|
|
137
137
|
outcome: "ambiguous",
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export { pendingQueueRows } from "./components.js";
|
|
|
12
12
|
export { charWidth, displayWidth, leadWidth, widthOf } from "./width.js";
|
|
13
13
|
export { panelAffordance, panelBlockRows, panelLead, panelLeadPlain, panelLeadWidth, panelStatus, type PanelArgs, type PanelFlavor, type PanelPhase, deletionRiskHint, SAFER_BACK, SAFER_DEGRADED, type SaferOption, type SaferRuntime, panelOptions, type PanelOption, type PanelOptionKind, type PanelState, type PanelVerdict, type PanelView, type AskAnswer, type AskOption, type AskQuestion, type AskResult, type AskRuntime, type AskSpec, } from "./approval-panel.js";
|
|
14
14
|
export { interactivePrompt, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView, type TrustArtifact, } from "./strings.js";
|
|
15
|
-
export { extensionsBannerText, helpRows, unansweredAskView, type BannerExtension } from "./strings.js";
|
|
15
|
+
export { coldResumeLine, coldResumeView, extensionsBannerText, helpRows, unansweredAskView, type BannerExtension } from "./strings.js";
|
|
16
16
|
export { displayVerb } from "./strings.js";
|
|
17
17
|
export { bannerLines, COLOR_OFF, COLOR_DARK, COLOR_LIGHT, COLOR_NEUTRAL, COLOR_ON, currentGround, setGround, escapeTerminal, foldResult, foldThinking, kUnit, palette, relativeTime, renderResumeList, MOTION_FRAMES, TWINKLE, breathFrame, twinkleFrame, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, type Palette, type ResumeMeta, } from "./render.js";
|
|
18
18
|
/** DC-3 — the ground: is the terminal light or dark. Pure; see the
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,7 @@ export { panelAffordance, panelBlockRows, panelLead, panelLeadPlain, panelLeadWi
|
|
|
24
24
|
// the cli; what the human reads is presentation.
|
|
25
25
|
export { interactivePrompt, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView, } from "./strings.js";
|
|
26
26
|
// KC3.5: the interrupted-ask copy and the extracted /help table.
|
|
27
|
-
export { extensionsBannerText, helpRows, unansweredAskView } from "./strings.js";
|
|
27
|
+
export { coldResumeLine, coldResumeView, extensionsBannerText, helpRows, unansweredAskView } from "./strings.js";
|
|
28
28
|
// TUI2-R2pre ④: the ONE display-verb table — the screen names the act,
|
|
29
29
|
// the tool table names the call.
|
|
30
30
|
export { displayVerb } from "./strings.js";
|
package/dist/render.d.ts
CHANGED
|
@@ -146,12 +146,7 @@ export declare const COLOR_OFF: Palette;
|
|
|
146
146
|
export declare function setGround(g: Ground): void;
|
|
147
147
|
export declare function currentGround(): Ground;
|
|
148
148
|
export declare function palette(): Palette;
|
|
149
|
-
|
|
150
|
-
* E group/round 8: strip terminal-injection vectors from MODEL/TOOL text before it
|
|
151
|
-
* reaches the terminal — ESC, C0 (except \t \n), C1, CR, backspace, and
|
|
152
|
-
* bidi overrides. The kiso colors are applied by render, not by the data.
|
|
153
|
-
* EVERY externally-sourced string must pass through this before any output.
|
|
154
|
-
*/
|
|
149
|
+
export declare function stripAnsi(text: string): string;
|
|
155
150
|
export declare function escapeTerminal(text: string): string;
|
|
156
151
|
/**
|
|
157
152
|
* v2b — one thinking BLOCK folds to ONE dim line: the first 100 chars, a
|
package/dist/render.js
CHANGED
|
@@ -97,6 +97,25 @@ export function palette() {
|
|
|
97
97
|
* bidi overrides. The kiso colors are applied by render, not by the data.
|
|
98
98
|
* EVERY externally-sourced string must pass through this before any output.
|
|
99
99
|
*/
|
|
100
|
+
/**
|
|
101
|
+
* 0.40.0 (the owner's dogfood) — a tool's OUTPUT, shown without its
|
|
102
|
+
* terminal styling. escapeTerminal drops the ESC byte and nothing else, so
|
|
103
|
+
* a coloured test run reached the card as `[31m─── [1m[41m Failed Tests`.
|
|
104
|
+
* Here the whole sequence goes: CSI (colours, cursor moves), OSC (titles,
|
|
105
|
+
* links), DCS/SOS/PM/APC strings, charset selections, the two-byte escapes,
|
|
106
|
+
* and the 8-bit CSI. Every branch is linear: a string body stops at the
|
|
107
|
+
* first ESC (only its terminator may hold one), so hostile output — an
|
|
108
|
+
* unterminated `ESC P` repeated — never scans to the end of the text once
|
|
109
|
+
* per sequence (the lead's review: that was n²/2 on the render path).
|
|
110
|
+
* Only output bodies pass through here — in a NAME the
|
|
111
|
+
* `[31m` remnant is the visible sign of an injected sequence, and stripping
|
|
112
|
+
* it would let `sh<ESC>[31mell` read as `shell`.
|
|
113
|
+
*/
|
|
114
|
+
// eslint-disable-next-line no-control-regex
|
|
115
|
+
const ANSI_SEQUENCE = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[PX^_][^\x1b]*\x1b\\|\x1b[ -/]+[0-~]|\x1b[@-Z\\-_]|\x9b[0-?]*[ -/]*[@-~]/g;
|
|
116
|
+
export function stripAnsi(text) {
|
|
117
|
+
return text.replace(ANSI_SEQUENCE, "");
|
|
118
|
+
}
|
|
100
119
|
export function escapeTerminal(text) {
|
|
101
120
|
// eslint-disable-next-line no-control-regex
|
|
102
121
|
return text
|
package/dist/strings.d.ts
CHANGED
|
@@ -96,6 +96,16 @@ export declare function uncertainView(name: string, executionId: string): PanelV
|
|
|
96
96
|
* back. Re-asking is safe — that is what the first option says out loud.)
|
|
97
97
|
*/
|
|
98
98
|
export declare function unansweredAskView(executionId: string): PanelView;
|
|
99
|
+
/**
|
|
100
|
+
* 0.40.0 (the owner's session) — the cold resume. A session resumed 27
|
|
101
|
+
* minutes after its last request re-sent a 727k prefix the provider had
|
|
102
|
+
* evicted. The first request after a long pause pays for the whole prefix
|
|
103
|
+
* either way; compacting first turns that one expensive request into a
|
|
104
|
+
* summary call, and every turn after it is small. The line names the size
|
|
105
|
+
* and the age, so the person can judge.
|
|
106
|
+
*/
|
|
107
|
+
export declare function coldResumeLine(tokens: number, minutesAgo: number): string;
|
|
108
|
+
export declare function coldResumeView(tokens: number, minutesAgo: number): PanelView;
|
|
99
109
|
/** An extension as the banner names it — the live `connecting` flag is
|
|
100
110
|
* the MCP bridge's in-flight state ("mcp (connecting…)"). Structural on
|
|
101
111
|
* purpose: the runtime's KisoExtension satisfies it without this
|
|
@@ -103,6 +113,9 @@ export declare function unansweredAskView(executionId: string): PanelView;
|
|
|
103
113
|
export interface BannerExtension {
|
|
104
114
|
readonly name: string;
|
|
105
115
|
readonly connecting?: boolean;
|
|
116
|
+
/** A fact about the extension in this session, printed in parentheses
|
|
117
|
+
* after its name — "ask (off in dontAsk)". `connecting` wins. */
|
|
118
|
+
readonly note?: string;
|
|
106
119
|
}
|
|
107
120
|
/**
|
|
108
121
|
* KC3.5 slice ⓪ (the extraction) — the `[N extensions: …]` banner text:
|
|
@@ -188,7 +201,7 @@ export declare function keysSheetRows(W: number): string[];
|
|
|
188
201
|
* sheet renders, joined — so the two can disagree only by deleting a
|
|
189
202
|
* test. The sheet is the readable form; this is the greppable one. */
|
|
190
203
|
export declare function keysHelpRow(): string;
|
|
191
|
-
/** The slash commands `/help` lists. The keys
|
|
192
|
-
* table are not commands and are not here. */
|
|
204
|
+
/** The slash commands `/help` lists. The keys, the `!` gestures and the
|
|
205
|
+
* `/<name>` skill rule in the table are not commands and are not here. */
|
|
193
206
|
export declare function slashCommandNames(): readonly string[];
|
|
194
207
|
export declare function helpRows(): string[];
|
package/dist/strings.js
CHANGED
|
@@ -143,6 +143,31 @@ export function unansweredAskView(executionId) {
|
|
|
143
143
|
fallbackQuestion: `an unanswered question was interrupted (${executionId}) — ask it again? (y)es / (n)o `,
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* 0.40.0 (the owner's session) — the cold resume. A session resumed 27
|
|
148
|
+
* minutes after its last request re-sent a 727k prefix the provider had
|
|
149
|
+
* evicted. The first request after a long pause pays for the whole prefix
|
|
150
|
+
* either way; compacting first turns that one expensive request into a
|
|
151
|
+
* summary call, and every turn after it is small. The line names the size
|
|
152
|
+
* and the age, so the person can judge.
|
|
153
|
+
*/
|
|
154
|
+
export function coldResumeLine(tokens, minutesAgo) {
|
|
155
|
+
return `this session is ${Math.round(tokens / 1000)}k tokens, last used ${minutesAgo} min ago, and its cache is cold`;
|
|
156
|
+
}
|
|
157
|
+
export function coldResumeView(tokens, minutesAgo) {
|
|
158
|
+
const line = coldResumeLine(tokens, minutesAgo);
|
|
159
|
+
return {
|
|
160
|
+
flavor: "simple",
|
|
161
|
+
name: "cold cache",
|
|
162
|
+
title: `compact first? (${Math.round(tokens / 1000)}k tokens, ${minutesAgo} min idle)`,
|
|
163
|
+
speaker: "kiso",
|
|
164
|
+
statusText: "❯ resumed session",
|
|
165
|
+
args: { kind: "text", lines: [line, "compacting first is one summary call, then every turn is cheap"] },
|
|
166
|
+
ruleOverride: `${line} — compact first? (one summary call, then every turn is cheap)`,
|
|
167
|
+
simpleOptions: ["compact first", "keep the full history"],
|
|
168
|
+
fallbackQuestion: `${line} — compact first? (y)es / (n)o `,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
146
171
|
/**
|
|
147
172
|
* KC3.5 slice ⓪ (the extraction) — the `[N extensions: …]` banner text:
|
|
148
173
|
* the built-in column, then the user-level names, then the project-level
|
|
@@ -158,7 +183,7 @@ export function extensionsBannerText(builtIn, user, project) {
|
|
|
158
183
|
const total = builtIn.length + user.length + project.length;
|
|
159
184
|
if (total === 0)
|
|
160
185
|
return "";
|
|
161
|
-
const label = (e) =>
|
|
186
|
+
const label = (e) => e.connecting === true ? `${e.name} (connecting…)` : e.note !== undefined ? `${e.name} (${e.note})` : e.name;
|
|
162
187
|
const parts = [];
|
|
163
188
|
if (builtIn.length > 0)
|
|
164
189
|
parts.push(`built-in: ${builtIn.map(label).join(", ")}`);
|
|
@@ -447,7 +472,7 @@ const HELP_TABLE = [
|
|
|
447
472
|
// been dispatchable since TUI2-R1 slice 6 and was never listed here,
|
|
448
473
|
// so the only way to learn it existed was to read the source.
|
|
449
474
|
["/context", "show where the context went — the per-request rent ledger"],
|
|
450
|
-
["/mode", "show the approval tier; /mode <name> switches (
|
|
475
|
+
["/mode", "show the approval tier; /mode <name> switches (default/accept-edits/plan/dontAsk/bypass)"],
|
|
451
476
|
["/model", "list model profiles; /model <name|provider/model> switches"],
|
|
452
477
|
["/compact", "summarize the older conversation to free context"],
|
|
453
478
|
["/clear", "start a fresh conversation (the old session stays resumable)"],
|
|
@@ -455,6 +480,12 @@ const HELP_TABLE = [
|
|
|
455
480
|
// §2.5: the conversation is untouched — this rereads what kiso was
|
|
456
481
|
// built with, not what it has said.
|
|
457
482
|
["/reload", "reread extensions, skills and config into this session"],
|
|
483
|
+
// 0.40.0: a skill is a user turn — its SKILL.md, then your args. The
|
|
484
|
+
// `/<name>` row says the rule a person needs before they install a
|
|
485
|
+
// skill named like a command: the command wins.
|
|
486
|
+
["/skills", "list the installed skills, and any that cannot load"],
|
|
487
|
+
["/skill", "run a skill as your turn: /skill <name> [args]"],
|
|
488
|
+
["/<name>", "runs the skill <name> when no command above has that name"],
|
|
458
489
|
// §2.2: the two shell gestures and their one escape. They sit
|
|
459
490
|
// beside the slash commands because that is what a reader is
|
|
460
491
|
// looking for when they look here, even though `!` is not one.
|
|
@@ -477,10 +508,10 @@ const HELP_TABLE = [
|
|
|
477
508
|
// table is mentioned here. DC-1 changes the PADDING, not the words.
|
|
478
509
|
["keys", "enter sends \u00b7 ctrl+J newline (shift+enter where encoded) \u00b7 esc stops the run \u00b7 alt+\u23ce stops it and sends this instead \u00b7 @ files \u00b7 1-4 answers an ask"],
|
|
479
510
|
];
|
|
480
|
-
/** The slash commands `/help` lists. The keys
|
|
481
|
-
* table are not commands and are not here. */
|
|
511
|
+
/** The slash commands `/help` lists. The keys, the `!` gestures and the
|
|
512
|
+
* `/<name>` skill rule in the table are not commands and are not here. */
|
|
482
513
|
export function slashCommandNames() {
|
|
483
|
-
return HELP_TABLE.map(([name]) => name).filter((n) => n
|
|
514
|
+
return HELP_TABLE.map(([name]) => name).filter((n) => /^\/[a-z]+$/.test(n));
|
|
484
515
|
}
|
|
485
516
|
export function helpRows() {
|
|
486
517
|
const p = palette();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui-cells",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.1",
|
|
4
4
|
"description": "kiso tui-cells \u2014 the components cell renderer (components, diff, width, the render slice). Zero runtime dependencies: input is data, output is bytes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|