@vincemakes/kiso-tui 0.39.2 → 0.40.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/dist/compositor.d.ts +29 -3
- package/dist/compositor.js +114 -7
- package/dist/context-ledger.d.ts +5 -0
- package/dist/context-ledger.js +11 -2
- package/dist/editor.d.ts +5 -2
- package/dist/editor.js +69 -9
- package/dist/index.d.ts +3 -3
- package/dist/index.js +6 -3
- package/dist/lines.d.ts +5 -0
- package/dist/lines.js +19 -4
- package/dist/pick-input.d.ts +5 -1
- package/dist/pick-input.js +33 -5
- package/dist/session-picker.d.ts +54 -26
- package/dist/session-picker.js +109 -54
- package/dist/status.d.ts +62 -2
- package/dist/status.js +94 -42
- package/package.json +2 -2
package/dist/compositor.d.ts
CHANGED
|
@@ -132,10 +132,13 @@ export declare class Body {
|
|
|
132
132
|
toolProgress(callId: string, text: string): void;
|
|
133
133
|
toolSucceeded(callId: string): void;
|
|
134
134
|
toolFailed(callId: string, error: string): void;
|
|
135
|
+
/** `untimed`: 4c's replay settles a card from the durable log, whose
|
|
136
|
+
* events carry no clock — the card then says nothing about time. */
|
|
135
137
|
toolResult(callId: string, result: {
|
|
136
138
|
content: string;
|
|
137
139
|
isError: boolean;
|
|
138
140
|
reason?: string | null;
|
|
141
|
+
untimed?: boolean;
|
|
139
142
|
}): void;
|
|
140
143
|
textAppend(text: string): void;
|
|
141
144
|
textEnd(): void;
|
|
@@ -144,6 +147,18 @@ export declare class Body {
|
|
|
144
147
|
* CLI's wall-clocked thinking window. */
|
|
145
148
|
endTurn(thoughtSeconds: number): void;
|
|
146
149
|
terminal(label: string, statusLineText: string): void;
|
|
150
|
+
/**
|
|
151
|
+
* 4c — fold what `replay` puts on the body into ONE row. The replayed
|
|
152
|
+
* cells are the same cells a live run makes (the caller drives the
|
|
153
|
+
* ordinary mutations), moved out of the transcript into the fold cell:
|
|
154
|
+
* the row commits as one line and never expands on screen — so a resize
|
|
155
|
+
* reprint redraws one row however long the history — and the ctrl+r
|
|
156
|
+
* viewer reads them. Mutations only schedule frames, so nothing the
|
|
157
|
+
* replay adds can have committed before it is moved. Active bodies
|
|
158
|
+
* only: a pipe has no viewer to read a fold with (the caller prints the
|
|
159
|
+
* plain tail there).
|
|
160
|
+
*/
|
|
161
|
+
fold(label: string, replay: () => void, summary?: string | null): void;
|
|
147
162
|
notice(text: string): void;
|
|
148
163
|
/** W20 — the task checklist as STATE, not events: the FIRST call of a
|
|
149
164
|
* turn creates the ONE live block (done:false — the commit loop only
|
|
@@ -339,8 +354,19 @@ export declare class Body {
|
|
|
339
354
|
* reads it — the marker math never desyncs by construction). */
|
|
340
355
|
editCol(): number;
|
|
341
356
|
/** The old dock's redraw — the editor's onRender target: mark + the
|
|
342
|
-
* scheduler (16ms coalescing — the old sync draw coalesces the same).
|
|
343
|
-
|
|
357
|
+
* scheduler (16ms coalescing — the old sync draw coalesces the same).
|
|
358
|
+
*
|
|
359
|
+
* Item 6: a KEY-originated redraw paints on the next tick and takes
|
|
360
|
+
* the pending trailing frame with it. Keyboard input is the one
|
|
361
|
+
* latency-sensitive source: the trailing window made every key wait
|
|
362
|
+
* 16 ms, and 40 ms on Terminal.app — where an IME commit erases the
|
|
363
|
+
* terminal's marked text at once and the committed characters came
|
|
364
|
+
* back a frame window later, a visible blink. Stream, tool and
|
|
365
|
+
* spinner marks keep the trailing window exactly (the conservative
|
|
366
|
+
* mode's throughput protection is for them). setImmediate, not a
|
|
367
|
+
* synchronous paint: every mutation one chunk of keys makes still
|
|
368
|
+
* lands in one frame. */
|
|
369
|
+
redraw(fromKey?: boolean): void;
|
|
344
370
|
/** Teardown — flush a pending frame, stop the timers. */
|
|
345
371
|
close(): void;
|
|
346
372
|
/** The live region's scalar — the unit tests assert the cap directly
|
|
@@ -403,5 +429,5 @@ export declare class Dock {
|
|
|
403
429
|
* the LineInput's own bindQueue). */
|
|
404
430
|
bindQueue(state: () => readonly string[]): void;
|
|
405
431
|
editCol(): number;
|
|
406
|
-
redraw(): void;
|
|
432
|
+
redraw(fromKey?: boolean): void;
|
|
407
433
|
}
|
package/dist/compositor.js
CHANGED
|
@@ -197,6 +197,10 @@ export class Body {
|
|
|
197
197
|
#lastInputRows = 1;
|
|
198
198
|
#lastAnchorRow = 0;
|
|
199
199
|
#frameTimer = null;
|
|
200
|
+
#inputFrame = null;
|
|
201
|
+
/** Body mutations and non-key redraws so far — the input frame's
|
|
202
|
+
* "did anything but typing change the screen?" */
|
|
203
|
+
#runMarks = 0;
|
|
200
204
|
#spinnerTimer = null;
|
|
201
205
|
#spinnerI = 0;
|
|
202
206
|
#lastThinking = null;
|
|
@@ -520,6 +524,8 @@ export class Body {
|
|
|
520
524
|
this.#write(`${p.red} failed: ${escapeTerminal(error.slice(0, 160))}${p.reset}\n`);
|
|
521
525
|
}
|
|
522
526
|
}
|
|
527
|
+
/** `untimed`: 4c's replay settles a card from the durable log, whose
|
|
528
|
+
* events carry no clock — the card then says nothing about time. */
|
|
523
529
|
toolResult(callId, result) {
|
|
524
530
|
const call = this.#pendingCalls.get(callId);
|
|
525
531
|
if (call !== undefined) {
|
|
@@ -543,7 +549,7 @@ export class Body {
|
|
|
543
549
|
cell.isError = result.isError;
|
|
544
550
|
cell.resultText = result.content;
|
|
545
551
|
cell.reason = result.reason ?? null;
|
|
546
|
-
cell.doneAt = Date.now();
|
|
552
|
+
cell.doneAt = result.untimed === true ? null : Date.now();
|
|
547
553
|
cell.done = true;
|
|
548
554
|
}
|
|
549
555
|
this.#mark();
|
|
@@ -692,6 +698,31 @@ export class Body {
|
|
|
692
698
|
this.#cells.push({ kind: "terminal", label: label.trim(), line: statusLineText, done: true });
|
|
693
699
|
this.#mark();
|
|
694
700
|
}
|
|
701
|
+
/**
|
|
702
|
+
* 4c — fold what `replay` puts on the body into ONE row. The replayed
|
|
703
|
+
* cells are the same cells a live run makes (the caller drives the
|
|
704
|
+
* ordinary mutations), moved out of the transcript into the fold cell:
|
|
705
|
+
* the row commits as one line and never expands on screen — so a resize
|
|
706
|
+
* reprint redraws one row however long the history — and the ctrl+r
|
|
707
|
+
* viewer reads them. Mutations only schedule frames, so nothing the
|
|
708
|
+
* replay adds can have committed before it is moved. Active bodies
|
|
709
|
+
* only: a pipe has no viewer to read a fold with (the caller prints the
|
|
710
|
+
* plain tail there).
|
|
711
|
+
*/
|
|
712
|
+
fold(label, replay, summary = null) {
|
|
713
|
+
if (!this.#isActive())
|
|
714
|
+
return;
|
|
715
|
+
const start = this.#cells.length;
|
|
716
|
+
replay();
|
|
717
|
+
this.#closeOpenThinking();
|
|
718
|
+
this.#closeOpenText();
|
|
719
|
+
const children = this.#cells.splice(start);
|
|
720
|
+
for (const cell of children)
|
|
721
|
+
if (!cell.done)
|
|
722
|
+
cell.done = true; // a replay settles; nothing in a fold is live
|
|
723
|
+
this.#cells.push({ kind: "fold", label, children, summary, done: true });
|
|
724
|
+
this.#mark();
|
|
725
|
+
}
|
|
695
726
|
notice(text) {
|
|
696
727
|
if (!this.#isActive()) {
|
|
697
728
|
this.#closeOpenThinking();
|
|
@@ -935,8 +966,15 @@ export class Body {
|
|
|
935
966
|
const cell = this.#cells[idx];
|
|
936
967
|
if (cell === undefined)
|
|
937
968
|
continue;
|
|
938
|
-
//
|
|
939
|
-
//
|
|
969
|
+
// 4c — the resumed history's fold: the head is its one row, the
|
|
970
|
+
// body is the replayed turns rendered at the viewer's width —
|
|
971
|
+
// the checkpoint's summary first when it is one.
|
|
972
|
+
if (cell.kind === "fold") {
|
|
973
|
+
out.push({ head: cellComponent(cell).render(inner, ctx)[0] ?? "", body: this.#foldBody(cell, inner, ctx) });
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
// R13 — every other entry is a card, and a card's entry is its
|
|
977
|
+
// own full body.
|
|
940
978
|
if (cell.kind !== "tool")
|
|
941
979
|
continue;
|
|
942
980
|
// the tool card's FULL body — the same rows its own ctrl+o
|
|
@@ -959,6 +997,24 @@ export class Body {
|
|
|
959
997
|
}
|
|
960
998
|
return out;
|
|
961
999
|
}
|
|
1000
|
+
/** 4c — a fold's children as the transcript would have shown them: each
|
|
1001
|
+
* cell's own render, spaced by the body's own formula (no blank between
|
|
1002
|
+
* two blocks of one message). */
|
|
1003
|
+
#foldBody(cell, W, ctx) {
|
|
1004
|
+
const out = [];
|
|
1005
|
+
let prev = null;
|
|
1006
|
+
let prevKind = null;
|
|
1007
|
+
const cells = cell.summary === null ? cell.children : [{ kind: "raw", lines: cell.summary.split("\n"), done: true, wrap: "words" }, ...cell.children];
|
|
1008
|
+
for (const child of cells) {
|
|
1009
|
+
const rows = cellComponent(child).render(W, ctx);
|
|
1010
|
+
if (rows.length === 0)
|
|
1011
|
+
continue;
|
|
1012
|
+
out.push(...(prevKind === "md" && child.kind === "md" ? rows : bodySpacing(prev, rows)));
|
|
1013
|
+
prev = rows;
|
|
1014
|
+
prevKind = child.kind;
|
|
1015
|
+
}
|
|
1016
|
+
return out;
|
|
1017
|
+
}
|
|
962
1018
|
/** The viewer's band: its title, its list, its keys. */
|
|
963
1019
|
#viewerBand(W) {
|
|
964
1020
|
if (this.#viewer === null)
|
|
@@ -1513,20 +1569,64 @@ export class Body {
|
|
|
1513
1569
|
return 1 + leadWidth(lead) + st.cursor;
|
|
1514
1570
|
}
|
|
1515
1571
|
/** The old dock's redraw — the editor's onRender target: mark + the
|
|
1516
|
-
* scheduler (16ms coalescing — the old sync draw coalesces the same).
|
|
1517
|
-
|
|
1572
|
+
* scheduler (16ms coalescing — the old sync draw coalesces the same).
|
|
1573
|
+
*
|
|
1574
|
+
* Item 6: a KEY-originated redraw paints on the next tick and takes
|
|
1575
|
+
* the pending trailing frame with it. Keyboard input is the one
|
|
1576
|
+
* latency-sensitive source: the trailing window made every key wait
|
|
1577
|
+
* 16 ms, and 40 ms on Terminal.app — where an IME commit erases the
|
|
1578
|
+
* terminal's marked text at once and the committed characters came
|
|
1579
|
+
* back a frame window later, a visible blink. Stream, tool and
|
|
1580
|
+
* spinner marks keep the trailing window exactly (the conservative
|
|
1581
|
+
* mode's throughput protection is for them). setImmediate, not a
|
|
1582
|
+
* synchronous paint: every mutation one chunk of keys makes still
|
|
1583
|
+
* lands in one frame. */
|
|
1584
|
+
redraw(fromKey = false) {
|
|
1518
1585
|
if (!this.#isActive())
|
|
1519
1586
|
return;
|
|
1520
1587
|
this.#dirty = true;
|
|
1588
|
+
if (fromKey) {
|
|
1589
|
+
this.#scheduleInputFrame();
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
this.#runMarks += 1; // a non-key redraw is not typing: it keeps the window
|
|
1521
1593
|
this.#scheduleFrame();
|
|
1522
1594
|
}
|
|
1523
1595
|
// ---- the scheduler (event-driven; zero heartbeat timers) ----
|
|
1524
1596
|
#mark() {
|
|
1525
1597
|
if (!this.#isActive())
|
|
1526
1598
|
return;
|
|
1599
|
+
this.#runMarks += 1;
|
|
1527
1600
|
this.#dirty = true;
|
|
1528
1601
|
this.#scheduleFrame();
|
|
1529
1602
|
}
|
|
1603
|
+
#scheduleInputFrame() {
|
|
1604
|
+
if (this.#inputFrame !== null)
|
|
1605
|
+
return;
|
|
1606
|
+
const marksAtKey = this.#runMarks;
|
|
1607
|
+
this.#inputFrame = setImmediate(() => {
|
|
1608
|
+
this.#inputFrame = null;
|
|
1609
|
+
// A key whose consequence is run state (an approval verdict, a
|
|
1610
|
+
// submit) mutates the body before this tick. That frame carries
|
|
1611
|
+
// the run, so it keeps the run's trailing window, exactly as
|
|
1612
|
+
// before: painting it now showed an approved write's transient
|
|
1613
|
+
// live card for one frame before its result settled.
|
|
1614
|
+
if (this.#runMarks !== marksAtKey) {
|
|
1615
|
+
if (this.#dirty)
|
|
1616
|
+
this.#scheduleFrame();
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
if (this.#frameTimer !== null) {
|
|
1620
|
+
clearTimeout(this.#frameTimer);
|
|
1621
|
+
this.#frameTimer = null;
|
|
1622
|
+
}
|
|
1623
|
+
if (this.#dirty) {
|
|
1624
|
+
this.#dirty = false;
|
|
1625
|
+
this.render();
|
|
1626
|
+
}
|
|
1627
|
+
});
|
|
1628
|
+
this.#inputFrame.unref();
|
|
1629
|
+
}
|
|
1530
1630
|
#scheduleFrame() {
|
|
1531
1631
|
if (this.#frameTimer !== null)
|
|
1532
1632
|
return;
|
|
@@ -1574,6 +1674,10 @@ export class Body {
|
|
|
1574
1674
|
}
|
|
1575
1675
|
/** Teardown — flush a pending frame, stop the timers. */
|
|
1576
1676
|
close() {
|
|
1677
|
+
if (this.#inputFrame !== null) {
|
|
1678
|
+
clearImmediate(this.#inputFrame);
|
|
1679
|
+
this.#inputFrame = null;
|
|
1680
|
+
}
|
|
1577
1681
|
if (this.#frameTimer !== null) {
|
|
1578
1682
|
clearTimeout(this.#frameTimer);
|
|
1579
1683
|
this.#frameTimer = null;
|
|
@@ -2024,6 +2128,9 @@ export class Body {
|
|
|
2024
2128
|
// oldest-first, so the newest cut is at the front.
|
|
2025
2129
|
if (cell.kind === "tool" && lines.some((l) => l.includes("ctrl+o")))
|
|
2026
2130
|
this.#collapsed.unshift(i);
|
|
2131
|
+
// 4c: a fold is read in the viewer and nowhere else.
|
|
2132
|
+
if (cell.kind === "fold")
|
|
2133
|
+
this.#collapsed.unshift(i);
|
|
2027
2134
|
this.#lineCache[i] = lines;
|
|
2028
2135
|
const placed = this.#space(i, i > 0 ? this.#lineCache[i - 1] : null, lines);
|
|
2029
2136
|
this.#committed += 1;
|
|
@@ -2848,8 +2955,8 @@ export class Dock {
|
|
|
2848
2955
|
editCol() {
|
|
2849
2956
|
return compositorRef?.editCol() ?? 1;
|
|
2850
2957
|
}
|
|
2851
|
-
redraw() {
|
|
2852
|
-
compositorRef?.redraw();
|
|
2958
|
+
redraw(fromKey = false) {
|
|
2959
|
+
compositorRef?.redraw(fromKey);
|
|
2853
2960
|
}
|
|
2854
2961
|
}
|
|
2855
2962
|
/** The one-compositor registry — the Dock façade routes to it. */
|
package/dist/context-ledger.d.ts
CHANGED
|
@@ -58,6 +58,11 @@ export interface ContextLedger {
|
|
|
58
58
|
* the detail text rides after them, dim, and is cut by the caller's
|
|
59
59
|
* width if it must be.
|
|
60
60
|
*/
|
|
61
|
+
/** 0.40.0 — the ONE fill rule for a ▰▱ meter: `ratio` of `cells`, rounded,
|
|
62
|
+
* clamped to the bar. The `/context` bar and the compacting row's bar both
|
|
63
|
+
* draw through it, so the two cannot fill differently. Plain glyphs; the
|
|
64
|
+
* caller styles them. */
|
|
65
|
+
export declare function meterGlyphs(ratio: number, cells: number): string;
|
|
61
66
|
export declare function contextRows(ledger: ContextLedger): string[];
|
|
62
67
|
/** TUI2-R1 (E) — the honest fallback. The ledger is written PER REQUEST:
|
|
63
68
|
* a session that has not called the model yet has no sidecar, and the
|
package/dist/context-ledger.js
CHANGED
|
@@ -45,15 +45,24 @@ function k(n) {
|
|
|
45
45
|
* the detail text rides after them, dim, and is cut by the caller's
|
|
46
46
|
* width if it must be.
|
|
47
47
|
*/
|
|
48
|
+
/** 0.40.0 — the ONE fill rule for a ▰▱ meter: `ratio` of `cells`, rounded,
|
|
49
|
+
* clamped to the bar. The `/context` bar and the compacting row's bar both
|
|
50
|
+
* draw through it, so the two cannot fill differently. Plain glyphs; the
|
|
51
|
+
* caller styles them. */
|
|
52
|
+
export function meterGlyphs(ratio, cells) {
|
|
53
|
+
const filled = Math.max(0, Math.min(cells, Math.round((Number.isFinite(ratio) ? ratio : 0) * cells)));
|
|
54
|
+
return `${"\u25b0".repeat(filled)}${"\u25b1".repeat(cells - filled)}`;
|
|
55
|
+
}
|
|
48
56
|
export function contextRows(ledger) {
|
|
49
57
|
const p = palette();
|
|
50
58
|
const used = ledger.systemPrompt + ledger.toolTable + ledger.skillsIndex + ledger.envelope + ledger.messages;
|
|
51
59
|
const free = Math.max(0, ledger.window - used);
|
|
52
60
|
const ratio = ledger.window > 0 ? Math.min(1, used / ledger.window) : 1;
|
|
53
|
-
const
|
|
61
|
+
const bar = meterGlyphs(ratio, BAR_CELLS);
|
|
62
|
+
const filled = bar.indexOf("\u25b1") < 0 ? BAR_CELLS : bar.indexOf("\u25b1");
|
|
54
63
|
const rows = [
|
|
55
64
|
`${p.bold}context — ${k(used)} / ${k(ledger.window)} tokens (${Math.round(ratio * 100)}%)${p.reset}`,
|
|
56
|
-
`${p.bold}${
|
|
65
|
+
`${p.bold}${bar.slice(0, filled)}${p.reset}${p.dim}${bar.slice(filled)}${p.reset}`,
|
|
57
66
|
];
|
|
58
67
|
/** One surface row: the label at 14 columns, the count right-aligned
|
|
59
68
|
* at 5, then the dim detail. */
|
package/dist/editor.d.ts
CHANGED
|
@@ -65,7 +65,7 @@ export declare const MENU_ITEMS: readonly MenuItem[];
|
|
|
65
65
|
export declare class Editor {
|
|
66
66
|
#private;
|
|
67
67
|
readonly closed: Promise<void>;
|
|
68
|
-
constructor(onRender: () => void);
|
|
68
|
+
constructor(onRender: (fromKey: boolean) => void);
|
|
69
69
|
/**
|
|
70
70
|
* DC-7 — the terminal answering a question kiso asked it.
|
|
71
71
|
*
|
|
@@ -153,6 +153,9 @@ export declare class Editor {
|
|
|
153
153
|
items: readonly MenuItem[];
|
|
154
154
|
selected: number;
|
|
155
155
|
} | null;
|
|
156
|
+
/** 0.40.1 — bind the menu's extra entries (the CLI binds the installed
|
|
157
|
+
* skills). A function, read per keystroke, never a snapshot. */
|
|
158
|
+
bindMenuExtras(extras: () => readonly MenuItem[]): void;
|
|
156
159
|
/** KC3 §3 — bind the file source. The tui owns no file list and
|
|
157
160
|
* never touches a disk (input is data, output is bytes): the CLI
|
|
158
161
|
* feeds the paths, and until it does, the picker cannot open at
|
|
@@ -168,7 +171,7 @@ export declare class Editor {
|
|
|
168
171
|
capped: boolean;
|
|
169
172
|
} | null;
|
|
170
173
|
/** Open the picker on a bound card source (PickInput, S5). */
|
|
171
|
-
beginPick(cards: () => readonly SessionCardView[], onPick: (id: string | null) => void): void;
|
|
174
|
+
beginPick(cards: () => readonly SessionCardView[], onPick: (id: string | null) => void, here?: string): void;
|
|
172
175
|
pickState(): SessionPickState | null;
|
|
173
176
|
/** One-shot question mode: the NEXT submit answers, not a turn. */
|
|
174
177
|
question(_query: string, cb: (answer: string) => void): void;
|
package/dist/editor.js
CHANGED
|
@@ -110,7 +110,7 @@ export const MOUSE_OFF = "\x1b[?1000l\x1b[?1006l";
|
|
|
110
110
|
export const PROMPT = "▌ ";
|
|
111
111
|
export const PROMPT_WIDTH = displayWidth(PROMPT);
|
|
112
112
|
export const MENU_ITEMS = [
|
|
113
|
-
{ name: "/mode", desc: "switch the approval tier (
|
|
113
|
+
{ name: "/mode", desc: "switch the approval tier (default/accept-edits/plan/dontAsk/bypass)" },
|
|
114
114
|
{ name: "/model", desc: "list model profiles; switch with /model <name|provider/model>" },
|
|
115
115
|
{ name: "/compact", desc: "summarize the older conversation to free context" },
|
|
116
116
|
// the /resume+/clear mini-spec: the session-navigation pair
|
|
@@ -130,6 +130,10 @@ export const MENU_ITEMS = [
|
|
|
130
130
|
{ name: "/status", desc: "show session id, event count, and context estimate" },
|
|
131
131
|
// TUI2-R1 (E): the rent-ledger attribution — where the context went
|
|
132
132
|
{ name: "/context", desc: "show where the context went — the last request's rent ledger" },
|
|
133
|
+
// 0.40.0: the person's door to the installed skills — `/<name>` works
|
|
134
|
+
// too when no command here has that name.
|
|
135
|
+
{ name: "/skills", desc: "list the installed skills — /<name> [args] runs one" },
|
|
136
|
+
{ name: "/skill", desc: "run a skill as your turn: /skill <name> [args]" },
|
|
133
137
|
{ name: "/help", desc: "print this list of commands" },
|
|
134
138
|
];
|
|
135
139
|
/** KC1 §3 — the newline code point. Every source (paste, Ctrl+J, the
|
|
@@ -341,6 +345,14 @@ export class Editor {
|
|
|
341
345
|
* KEY, the CLI owns what it means. */
|
|
342
346
|
#copyCbs = [];
|
|
343
347
|
#onRender;
|
|
348
|
+
/** Item 6: true while a chunk of keys is being fed — the render hook
|
|
349
|
+
* is told a frame is key-originated, and the compositor paints it on
|
|
350
|
+
* the next tick instead of waiting out the trailing frame window. */
|
|
351
|
+
#feeding = false;
|
|
352
|
+
/** Item 6: set when a key in this chunk handed control to the run —
|
|
353
|
+
* a submitted line or a panel verdict. What follows is run state, not
|
|
354
|
+
* typing, so its frames keep the run's trailing window. */
|
|
355
|
+
#handedOff = false;
|
|
344
356
|
/** TUI2-R1 (D): the keys sheet — a static one-screen overlay opened by
|
|
345
357
|
* `?` on an empty composer and closed by the next key, whatever it
|
|
346
358
|
* is. Deliberately a BOOLEAN and not a panel: the panel machinery
|
|
@@ -348,6 +360,9 @@ export class Editor {
|
|
|
348
360
|
* buffer), and the sheet has no interaction to speak of. */
|
|
349
361
|
#sheetOpen = false;
|
|
350
362
|
#menuOpen = false; // v3 §04: the slash-command menu
|
|
363
|
+
/** 0.40.1: the menu's extra entries (the installed skills), read LIVE on
|
|
364
|
+
* every keystroke — a skill added by /reload appears without rebinding. */
|
|
365
|
+
#menuExtras = () => [];
|
|
351
366
|
#menuSel = 0;
|
|
352
367
|
// KC3 §3 — the @ file picker. THREE fields and no more: the armed
|
|
353
368
|
// bit, the selection, and the per-open SNAPSHOT of the file list.
|
|
@@ -420,7 +435,7 @@ export class Editor {
|
|
|
420
435
|
#closedResolve;
|
|
421
436
|
closed;
|
|
422
437
|
constructor(onRender) {
|
|
423
|
-
this.#onRender = onRender;
|
|
438
|
+
this.#onRender = () => onRender(this.#feeding && !this.#handedOff);
|
|
424
439
|
this.#onData = (raw) => this.feed(raw);
|
|
425
440
|
this.closed = new Promise((resolve) => {
|
|
426
441
|
this.#closedResolve = resolve;
|
|
@@ -794,11 +809,28 @@ export class Editor {
|
|
|
794
809
|
const line = this.line();
|
|
795
810
|
if (!line.startsWith("/"))
|
|
796
811
|
return [];
|
|
797
|
-
|
|
812
|
+
// 0.40.1: the built-ins first, then the extras (skills) — and a
|
|
813
|
+
// built-in WINS a shared name, so a skill named like a command is
|
|
814
|
+
// never listed twice and never shadows it (the dispatcher's rule)
|
|
815
|
+
const builtins = new Set(MENU_ITEMS.map((m) => m.name));
|
|
816
|
+
const all = [...MENU_ITEMS, ...this.#menuExtras().filter((m) => !builtins.has(m.name))];
|
|
817
|
+
return all.filter((m) => m.name.startsWith(line));
|
|
818
|
+
}
|
|
819
|
+
/** 0.40.1 — bind the menu's extra entries (the CLI binds the installed
|
|
820
|
+
* skills). A function, read per keystroke, never a snapshot. */
|
|
821
|
+
bindMenuExtras(extras) {
|
|
822
|
+
this.#menuExtras = extras;
|
|
798
823
|
}
|
|
799
824
|
#refreshMenu() {
|
|
800
|
-
if (this.#panelInput.up())
|
|
801
|
-
|
|
825
|
+
if (this.#panelInput.up()) {
|
|
826
|
+
// W21: the menu never opens while the panel owns the keys — but
|
|
827
|
+
// the key that got here still changed the line, and insert and
|
|
828
|
+
// delete render only through this method (item 6: returning
|
|
829
|
+
// before the render left a panel's typed text to ride the next
|
|
830
|
+
// spinner tick, 0–150 ms late, or no tick at all).
|
|
831
|
+
this.#onRender();
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
802
834
|
const f = this.#menuFiltered();
|
|
803
835
|
this.#menuOpen = f.length > 0;
|
|
804
836
|
if (this.#menuSel >= f.length)
|
|
@@ -938,8 +970,8 @@ export class Editor {
|
|
|
938
970
|
}
|
|
939
971
|
// ── TUI2-R2 ② — the session picker ───────────────────────────────
|
|
940
972
|
/** Open the picker on a bound card source (PickInput, S5). */
|
|
941
|
-
beginPick(cards, onPick) {
|
|
942
|
-
this.#pickInput.begin(cards, onPick);
|
|
973
|
+
beginPick(cards, onPick, here) {
|
|
974
|
+
this.#pickInput.begin(cards, onPick, here);
|
|
943
975
|
}
|
|
944
976
|
pickState() {
|
|
945
977
|
return this.#pickInput.state();
|
|
@@ -957,7 +989,15 @@ export class Editor {
|
|
|
957
989
|
* stashed and restored at close, the panel takes the keys and the
|
|
958
990
|
* input row's lead, the composer's own bands close. */
|
|
959
991
|
beginPanel(view, onCommit, opts) {
|
|
960
|
-
|
|
992
|
+
const handOff = (v) => {
|
|
993
|
+
// item 6: a verdict hands the frame to the run. The panel already
|
|
994
|
+
// asked for a key frame on this key; the non-key render after the
|
|
995
|
+
// verdict demotes it to the run's window.
|
|
996
|
+
this.#handedOff = true;
|
|
997
|
+
onCommit(v);
|
|
998
|
+
this.#onRender();
|
|
999
|
+
};
|
|
1000
|
+
this.#panelInput.begin(view, handOff, opts);
|
|
961
1001
|
}
|
|
962
1002
|
/** W21: cancel the panel — the SIGINT path's pair to beginPanel. */
|
|
963
1003
|
cancelPanel() {
|
|
@@ -1074,6 +1114,21 @@ export class Editor {
|
|
|
1074
1114
|
// ---- input ----
|
|
1075
1115
|
/** Feed raw stdin bytes — the parser. Public for unit tests. */
|
|
1076
1116
|
feed(raw) {
|
|
1117
|
+
const outer = this.#feeding;
|
|
1118
|
+
const outerHandedOff = this.#handedOff;
|
|
1119
|
+
this.#feeding = true;
|
|
1120
|
+
if (!outer)
|
|
1121
|
+
this.#handedOff = false;
|
|
1122
|
+
try {
|
|
1123
|
+
this.#feedKeys(raw);
|
|
1124
|
+
}
|
|
1125
|
+
finally {
|
|
1126
|
+
this.#feeding = outer;
|
|
1127
|
+
if (!outer)
|
|
1128
|
+
this.#handedOff = outerHandedOff;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
#feedKeys(raw) {
|
|
1077
1132
|
const text = this.#pending + this.#decoder.decode(raw, { stream: true });
|
|
1078
1133
|
this.#pending = "";
|
|
1079
1134
|
// TUI2-R1 (D): the sheet is up — ANY key closes it, and the key
|
|
@@ -1512,6 +1567,10 @@ export class Editor {
|
|
|
1512
1567
|
}
|
|
1513
1568
|
i += 1;
|
|
1514
1569
|
}
|
|
1570
|
+
else if (c === "\t" && this.#pickInput.toggleScope()) {
|
|
1571
|
+
// 0.40.0: the session picker's scope — this workspace ↔ all
|
|
1572
|
+
i += 1;
|
|
1573
|
+
}
|
|
1515
1574
|
else if (c === "\t" && this.#atUp()) {
|
|
1516
1575
|
// KC3 §3: Tab accepts the selected path — the token becomes
|
|
1517
1576
|
// `@<path> `. Never the file's content.
|
|
@@ -2436,6 +2495,7 @@ export class Editor {
|
|
|
2436
2495
|
// recalls a readable line that still expands when it is sent
|
|
2437
2496
|
// again (the map outlives the buffer, by design).
|
|
2438
2497
|
const sent = this.#expandPastes(line);
|
|
2498
|
+
this.#handedOff = true;
|
|
2439
2499
|
const cb = this.#questionCb;
|
|
2440
2500
|
this.#questionCb = null;
|
|
2441
2501
|
if (cb !== null) {
|
|
@@ -2449,7 +2509,7 @@ export class Editor {
|
|
|
2449
2509
|
}
|
|
2450
2510
|
if (cb === null && line !== "")
|
|
2451
2511
|
this.#remember(line);
|
|
2452
|
-
this.#onRender();
|
|
2512
|
+
this.#onRender(); // item 6: #handedOff is set, so this render is non-key — the submit's frame keeps the run's window
|
|
2453
2513
|
}
|
|
2454
2514
|
/** A2: step the history browse; a delta past the newest exits back to
|
|
2455
2515
|
* the pre-browse input. */
|
package/dist/index.d.ts
CHANGED
|
@@ -12,12 +12,12 @@ export { Container, foldLine, foldWords, visibleWidth, SPINNER, type Component,
|
|
|
12
12
|
export { Editor, MENU_ITEMS, PROMPT, PROMPT_WIDTH, displayWidth, charWidth, widthOf, type MenuItem, } from "./editor.js";
|
|
13
13
|
export { bannerLines, COLOR_OFF, COLOR_ON, currentGround, setGround, escapeTerminal, foldResult, foldThinking, kUnit, palette, renderEvent, renderRecap, renderResumeList, renderSessionLine, renderStatusLine, relativeTime, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, type Palette, type PathResolver, type RecapStats, type ResumeMeta, type RenderInput, type RenderResult, type RunUsage, } from "./lines.js";
|
|
14
14
|
export { editFileDiff, truncateDiff, writeFileDiff, type DiffLine, type DiffResult } from "./diff.js";
|
|
15
|
-
export { STATUS_GLYPHS, cacheHitPct, decodeRate, idleStatus, runningStatus, type StatusMeter } from "./status.js";
|
|
15
|
+
export { STATUS_GLYPHS, cacheHitPct, compactingStatus, composeRow, decodeRate, idleStatus, retrySegment, runningStatus, type CompactingProgress, type RetryOnRow, type RowSegment, type StatusMeter } from "./status.js";
|
|
16
16
|
export { contextRows, contextUnavailableRows, type ContextLedger } from "./context-ledger.js";
|
|
17
17
|
export { interactivePrompt, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView, verifyOfferView, type TrustArtifact } from "./strings.js";
|
|
18
18
|
export { AT_CAP, AT_SKIP, AT_VISIBLE, atEmbed, atFilter, atPanelRows, atWindow, bandHeader, longestRun, type AtItem, type AtMatch } from "./at-picker.js";
|
|
19
|
-
export {
|
|
19
|
+
export { idColumn, sessionAge, sessionCounterRow, sessionFilter, sessionListFooter, sessionListHeader, sessionListUnknownLine, sessionListRow, sessionNote, sessionPickerRows, sessionRow, scopeSessions, scopeTitle, type PickScopeState, type SessionCardView, type SessionPickState, } from "./session-picker.js";
|
|
20
20
|
export { ASK_HEADER_CAP, ASK_MAX_OPTIONS, ASK_MAX_QUESTIONS, ASK_MIN_OPTIONS, askAffordance, askAnswers, askBlockRows, askCommitCustom, askDeclineAll, askDeclineList, askKey, askLeadPlain, askStart, askStatus, askView, type AskAnswer, type AskOption, type AskQuestion, type AskResult, type AskRuntime, type AskSpec, type AskStep, } from "./ask-panel.js";
|
|
21
21
|
export { resolveGround, type Ground } from "@vincemakes/kiso-tui-cells";
|
|
22
22
|
export { settledLabel } from "@vincemakes/kiso-tui-cells";
|
|
23
|
-
export { KEY_BINDINGS, PANEL_KEYS_ROW, displayVerb, extensionsBannerText, helpRows, keysHelpRow, keysSheetRows, slashCommandNames, unansweredAskView, type BannerExtension, type KeyBinding } from "./strings.js";
|
|
23
|
+
export { KEY_BINDINGS, PANEL_KEYS_ROW, coldResumeLine, coldResumeView, displayVerb, extensionsBannerText, helpRows, keysHelpRow, keysSheetRows, slashCommandNames, unansweredAskView, type BannerExtension, type KeyBinding } from "./strings.js";
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ export { bannerLines, COLOR_OFF, COLOR_ON, currentGround, setGround, escapeTermi
|
|
|
19
19
|
export { editFileDiff, truncateDiff, writeFileDiff } from "./diff.js";
|
|
20
20
|
// KC2 §5: the status rows' formatters — the CLI keeps the state and the
|
|
21
21
|
// repaint, the terminal layer owns what the row says.
|
|
22
|
-
export { STATUS_GLYPHS, cacheHitPct, decodeRate, idleStatus, runningStatus } from "./status.js";
|
|
22
|
+
export { STATUS_GLYPHS, cacheHitPct, compactingStatus, composeRow, decodeRate, idleStatus, retrySegment, runningStatus } from "./status.js";
|
|
23
23
|
// TUI2-R1 (E): /context's attribution rows — a pure function of the
|
|
24
24
|
// counts the trace sidecar already records (the CLI reads, this renders).
|
|
25
25
|
export { contextRows, contextUnavailableRows } from "./context-ledger.js";
|
|
@@ -33,7 +33,10 @@ export { AT_CAP, AT_SKIP, AT_VISIBLE, atEmbed, atFilter, atPanelRows, atWindow,
|
|
|
33
33
|
// TUI2-R2 ①–③: the session picker's pure half — the durability badge,
|
|
34
34
|
// the row (picked or printed), the band, and the filter. The CARDS are
|
|
35
35
|
// the cli's projection (session-cards.ts); this turns them into bytes.
|
|
36
|
-
export {
|
|
36
|
+
export { idColumn, sessionAge, sessionCounterRow, sessionFilter, sessionListFooter, sessionListHeader, sessionListUnknownLine, sessionListRow, sessionNote, sessionPickerRows, sessionRow,
|
|
37
|
+
// 0.40.0: the workspace scope — pure, so `kiso sessions` and the picker
|
|
38
|
+
// scope by one rule
|
|
39
|
+
scopeSessions, scopeTitle, } from "./session-picker.js";
|
|
37
40
|
// KC3.5 (the ask round): the ask view — the panel machinery generalized.
|
|
38
41
|
// The cli composes the view and hands the answers to the tool; the keys,
|
|
39
42
|
// the rows and the walk are the terminal layer's.
|
|
@@ -46,4 +49,4 @@ export { resolveGround } from "@vincemakes/kiso-tui-cells";
|
|
|
46
49
|
// one duration form: the CLI's own surfaces label a settled duration the
|
|
47
50
|
// way a settled card does, rather than writing a second one.
|
|
48
51
|
export { settledLabel } from "@vincemakes/kiso-tui-cells";
|
|
49
|
-
export { KEY_BINDINGS, PANEL_KEYS_ROW, displayVerb, extensionsBannerText, helpRows, keysHelpRow, keysSheetRows, slashCommandNames, unansweredAskView } from "./strings.js";
|
|
52
|
+
export { KEY_BINDINGS, PANEL_KEYS_ROW, coldResumeLine, coldResumeView, displayVerb, extensionsBannerText, helpRows, keysHelpRow, keysSheetRows, slashCommandNames, unansweredAskView } from "./strings.js";
|
package/dist/lines.d.ts
CHANGED
|
@@ -169,6 +169,11 @@ export interface RecapStats {
|
|
|
169
169
|
* passed only when above the noise floor — the re-sent-uncached
|
|
170
170
|
* prefix. Absent → the recap bytes stay the historical form. */
|
|
171
171
|
readonly missed?: number;
|
|
172
|
+
/** 0.40.0 (the owner): the turn's first request came this many minutes
|
|
173
|
+
* after the session's last bill, and the prompt cache had gone cold —
|
|
174
|
+
* the recap says so instead of a bare `fresh 730k · cache 0%`, which
|
|
175
|
+
* is true and reads like a fault. Absent → the historical form. */
|
|
176
|
+
readonly coldAfterMinutes?: number;
|
|
172
177
|
readonly ctxLeftPct: number | null;
|
|
173
178
|
/** R3g — the terminal's width. The recap is the ONE row on the screen
|
|
174
179
|
* that was never measured: it is written raw, so a line longer than
|
package/dist/lines.js
CHANGED
|
@@ -96,8 +96,8 @@ export function renderEvent(ev, prevThinking = false, resolvePath = (p) => p) {
|
|
|
96
96
|
case "compacted":
|
|
97
97
|
return { text: `${p.dim} [compacted ${ev.cleared.length} results]${p.reset}\n`, newline: true, prompt: false };
|
|
98
98
|
case "summarized":
|
|
99
|
-
// ADR-0044:
|
|
100
|
-
//
|
|
99
|
+
// ADR-0044: /compact's event is off-loop; since ADR-0055 Amendment 1
|
|
100
|
+
// the in-run tiers also append one mid-run, and this is its line.
|
|
101
101
|
return { text: `${p.dim} [summarized up to seq ${ev.coversToSeq}]${p.reset}\n`, newline: true, prompt: false };
|
|
102
102
|
case "uncertain_pending":
|
|
103
103
|
return {
|
|
@@ -211,7 +211,16 @@ export function renderRecap(s) {
|
|
|
211
211
|
? [`${s.tools} tool${s.tools === 1 ? "" : "s"}${edits > 0 ? ` (${edits} edit${edits === 1 ? "" : "s"})` : ""}`]
|
|
212
212
|
: [];
|
|
213
213
|
const parts = [`took ${elapsedLabel(s.seconds)}`, ...work];
|
|
214
|
-
|
|
214
|
+
// The cold turn: the gap and the re-read are the FACTS and come first,
|
|
215
|
+
// so a cut from the end never reaches them; "cache cold after" is the
|
|
216
|
+
// label, and it is the first thing shortened when the row is narrow.
|
|
217
|
+
const cold = s.coldAfterMinutes !== undefined && s.usage.known && s.usage.in !== null;
|
|
218
|
+
if (cold) {
|
|
219
|
+
parts.push(`cache cold after ${s.coldAfterMinutes} min`, `re-read ${kUnit(s.missed ?? s.usage.in ?? 0)}`);
|
|
220
|
+
if (s.usage.out !== null)
|
|
221
|
+
parts.push(`out ${kUnit(s.usage.out)}`);
|
|
222
|
+
}
|
|
223
|
+
else if (s.usage.known) {
|
|
215
224
|
const seg = `${s.usage.in !== null ? `fresh ${kUnit(s.usage.in)}` : ""}${s.usage.in !== null && s.usage.out !== null ? " " : ""}${s.usage.out !== null ? `out ${kUnit(s.usage.out)}` : ""}`;
|
|
216
225
|
if (seg !== "")
|
|
217
226
|
parts.push(seg);
|
|
@@ -230,7 +239,13 @@ export function renderRecap(s) {
|
|
|
230
239
|
// R3g: ONE physical row, at any width — the same rule every other row
|
|
231
240
|
// in the product obeys. The cut is the honest "…": the recap said
|
|
232
241
|
// more than fits, and says so.
|
|
233
|
-
|
|
242
|
+
let line = parts.join(" · ");
|
|
243
|
+
// A cold turn that does not fit sheds its LABEL first, then the cut
|
|
244
|
+
// below takes the tail — the gap and the re-read stand in front of it.
|
|
245
|
+
if (cold && s.width !== undefined && s.width >= 20 && visibleWidth(`✦ ${line}`) > s.width) {
|
|
246
|
+
parts[parts.indexOf(`cache cold after ${s.coldAfterMinutes} min`)] = `cold ${s.coldAfterMinutes} min`;
|
|
247
|
+
line = parts.join(" · ");
|
|
248
|
+
}
|
|
234
249
|
// R3g: the floor is the renderer's own guard against a caller that
|
|
235
250
|
// hands it a degenerate width (a PTY with no winsize reports 0). A
|
|
236
251
|
// recap cut to one character is worse than one that wraps.
|
package/dist/pick-input.d.ts
CHANGED
|
@@ -9,13 +9,17 @@ export declare class PickInput {
|
|
|
9
9
|
* (the buffer becomes the filter query) and `onPick` receives the
|
|
10
10
|
* chosen id — or null when the human leaves without picking, which
|
|
11
11
|
* is a first-class outcome and not an error. */
|
|
12
|
-
begin(cards: () => readonly SessionCardView[], onPick: (id: string | null) => void): void;
|
|
12
|
+
begin(cards: () => readonly SessionCardView[], onPick: (id: string | null) => void, here?: string): void;
|
|
13
13
|
/** The picker's state, derived: the full card list (the id column
|
|
14
14
|
* measures over ALL of them, so the columns never jump), the
|
|
15
15
|
* filtered matches, and the selection CLAMPED at read time — the
|
|
16
16
|
* same correction discipline the @ picker uses, for the same
|
|
17
17
|
* reason: narrowing can only ever shrink the list. */
|
|
18
18
|
state(): SessionPickState | null;
|
|
19
|
+
/** 0.40.0 — tab flips CURRENT ↔ ALL. The filter owns every printable
|
|
20
|
+
* key (the buffer IS the query), so the toggle cannot be one. True when
|
|
21
|
+
* the picker owned the key. */
|
|
22
|
+
toggleScope(): boolean;
|
|
19
23
|
/** The band's height estimate: the header + the windowed rows (or
|
|
20
24
|
* the one "no match" row) + the counter. */
|
|
21
25
|
rows(): number;
|