@vincemakes/kiso-tui 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/compositor.d.ts +39 -3
- package/dist/compositor.js +133 -8
- package/dist/context-ledger.d.ts +5 -0
- package/dist/context-ledger.js +11 -2
- package/dist/editor.d.ts +22 -7
- package/dist/editor.js +111 -40
- 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/panel-input.d.ts +8 -0
- package/dist/panel-input.js +17 -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/editor.js
CHANGED
|
@@ -46,13 +46,20 @@ import { PickInput } from "./pick-input.js";
|
|
|
46
46
|
// "you>" text is gone (the brick IS the prompt; the pipe path's readline
|
|
47
47
|
// prompt keeps its own "you> " — v2a line mode, byte-for-byte).
|
|
48
48
|
/**
|
|
49
|
-
* TUI2-R3v2
|
|
49
|
+
* TUI2-R3v2 ②, corrected by the owner's ruling of 2026-09-21 (finding
|
|
50
|
+
* DC-56) — the mouse-mode bytes, stated once.
|
|
50
51
|
*
|
|
51
52
|
* ?1000 is the button-event report and ?1006 is the SGR encoding that
|
|
52
53
|
* makes it parseable past column 95 (the legacy X10 encoding packs the
|
|
53
|
-
* coordinate into one byte and simply breaks on a wide terminal).
|
|
54
|
-
*
|
|
55
|
-
*
|
|
54
|
+
* coordinate into one byte and simply breaks on a wide terminal). kiso
|
|
55
|
+
* still RESETS them — on entry (a previous process may have died with
|
|
56
|
+
* reporting left on) and on exit — but it no longer ENABLES them, because
|
|
57
|
+
* a terminal that is reporting stops scrolling its own scrollback: a
|
|
58
|
+
* surface that takes the mouse takes the history with it. kiso's
|
|
59
|
+
* transcript lives in that scrollback by design, and the first version of
|
|
60
|
+
* this feature locked it exactly where a person most wants to read back —
|
|
61
|
+
* under a blocking question. The history wins: no surface claims the
|
|
62
|
+
* mouse, and every surface keeps its keys.
|
|
56
63
|
*/
|
|
57
64
|
/**
|
|
58
65
|
* R5 — the viewer's key table, as a pure function of the input chunk.
|
|
@@ -105,12 +112,11 @@ const ALT_WORD = new Map([
|
|
|
105
112
|
["\x7f", "killBack"],
|
|
106
113
|
["\x08", "killBack"],
|
|
107
114
|
]);
|
|
108
|
-
export const MOUSE_ON = "\x1b[?1000h\x1b[?1006h";
|
|
109
115
|
export const MOUSE_OFF = "\x1b[?1000l\x1b[?1006l";
|
|
110
116
|
export const PROMPT = "▌ ";
|
|
111
117
|
export const PROMPT_WIDTH = displayWidth(PROMPT);
|
|
112
118
|
export const MENU_ITEMS = [
|
|
113
|
-
{ name: "/mode", desc: "switch the approval tier (
|
|
119
|
+
{ name: "/mode", desc: "switch the approval tier (default/accept-edits/plan/dontAsk/bypass)" },
|
|
114
120
|
{ name: "/model", desc: "list model profiles; switch with /model <name|provider/model>" },
|
|
115
121
|
{ name: "/compact", desc: "summarize the older conversation to free context" },
|
|
116
122
|
// the /resume+/clear mini-spec: the session-navigation pair
|
|
@@ -130,6 +136,10 @@ export const MENU_ITEMS = [
|
|
|
130
136
|
{ name: "/status", desc: "show session id, event count, and context estimate" },
|
|
131
137
|
// TUI2-R1 (E): the rent-ledger attribution — where the context went
|
|
132
138
|
{ name: "/context", desc: "show where the context went — the last request's rent ledger" },
|
|
139
|
+
// 0.40.0: the person's door to the installed skills — `/<name>` works
|
|
140
|
+
// too when no command here has that name.
|
|
141
|
+
{ name: "/skills", desc: "list the installed skills — /<name> [args] runs one" },
|
|
142
|
+
{ name: "/skill", desc: "run a skill as your turn: /skill <name> [args]" },
|
|
133
143
|
{ name: "/help", desc: "print this list of commands" },
|
|
134
144
|
];
|
|
135
145
|
/** KC1 §3 — the newline code point. Every source (paste, Ctrl+J, the
|
|
@@ -310,12 +320,16 @@ export class Editor {
|
|
|
310
320
|
/** TUI2-R3v2 ①: one-shot — a panel that just closed swallows the
|
|
311
321
|
* habitual trailing enter rather than submitting the restored draft. */
|
|
312
322
|
#swallowEnter = false;
|
|
313
|
-
/** TUI2-R3v2 ②: whether SGR 1006 reporting is currently enabled. */
|
|
314
|
-
#mouseOn = false;
|
|
315
323
|
/** TUI2-R3v2 ②: where the compositor put the panel's option rows this
|
|
316
324
|
* frame (absolute 1-based screen rows). The editor owns no geometry —
|
|
317
325
|
* it asks the surface that placed them. */
|
|
318
326
|
#panelRows = null;
|
|
327
|
+
/** B (review of this round): the pick panel's window, published by the
|
|
328
|
+
* surface that DREW it — the frame's budget decides the size, so the
|
|
329
|
+
* editor PULLS it here instead of letting the input layer re-derive one
|
|
330
|
+
* (two derivations are two sizes, and on a short terminal the digits named
|
|
331
|
+
* rows nobody could see). */
|
|
332
|
+
#pickWin = null;
|
|
319
333
|
#lineCb = null;
|
|
320
334
|
#pendingLines = []; // submits before onLine is wired (startup) — never dropped
|
|
321
335
|
#sigintCb = null;
|
|
@@ -341,6 +355,14 @@ export class Editor {
|
|
|
341
355
|
* KEY, the CLI owns what it means. */
|
|
342
356
|
#copyCbs = [];
|
|
343
357
|
#onRender;
|
|
358
|
+
/** Item 6: true while a chunk of keys is being fed — the render hook
|
|
359
|
+
* is told a frame is key-originated, and the compositor paints it on
|
|
360
|
+
* the next tick instead of waiting out the trailing frame window. */
|
|
361
|
+
#feeding = false;
|
|
362
|
+
/** Item 6: set when a key in this chunk handed control to the run —
|
|
363
|
+
* a submitted line or a panel verdict. What follows is run state, not
|
|
364
|
+
* typing, so its frames keep the run's trailing window. */
|
|
365
|
+
#handedOff = false;
|
|
344
366
|
/** TUI2-R1 (D): the keys sheet — a static one-screen overlay opened by
|
|
345
367
|
* `?` on an empty composer and closed by the next key, whatever it
|
|
346
368
|
* is. Deliberately a BOOLEAN and not a panel: the panel machinery
|
|
@@ -348,6 +370,9 @@ export class Editor {
|
|
|
348
370
|
* buffer), and the sheet has no interaction to speak of. */
|
|
349
371
|
#sheetOpen = false;
|
|
350
372
|
#menuOpen = false; // v3 §04: the slash-command menu
|
|
373
|
+
/** 0.40.1: the menu's extra entries (the installed skills), read LIVE on
|
|
374
|
+
* every keystroke — a skill added by /reload appears without rebinding. */
|
|
375
|
+
#menuExtras = () => [];
|
|
351
376
|
#menuSel = 0;
|
|
352
377
|
// KC3 §3 — the @ file picker. THREE fields and no more: the armed
|
|
353
378
|
// bit, the selection, and the per-open SNAPSHOT of the file list.
|
|
@@ -420,7 +445,7 @@ export class Editor {
|
|
|
420
445
|
#closedResolve;
|
|
421
446
|
closed;
|
|
422
447
|
constructor(onRender) {
|
|
423
|
-
this.#onRender = onRender;
|
|
448
|
+
this.#onRender = () => onRender(this.#feeding && !this.#handedOff);
|
|
424
449
|
this.#onData = (raw) => this.feed(raw);
|
|
425
450
|
this.closed = new Promise((resolve) => {
|
|
426
451
|
this.#closedResolve = resolve;
|
|
@@ -510,7 +535,6 @@ export class Editor {
|
|
|
510
535
|
// the human has no way to ask for it back.
|
|
511
536
|
process.stdin.setRawMode(true);
|
|
512
537
|
process.stdout.write(MOUSE_OFF);
|
|
513
|
-
this.#mouseOn = false;
|
|
514
538
|
process.stdout.write("\x1b[?2004h");
|
|
515
539
|
process.stdin.on("data", this.#onData);
|
|
516
540
|
}
|
|
@@ -794,11 +818,28 @@ export class Editor {
|
|
|
794
818
|
const line = this.line();
|
|
795
819
|
if (!line.startsWith("/"))
|
|
796
820
|
return [];
|
|
797
|
-
|
|
821
|
+
// 0.40.1: the built-ins first, then the extras (skills) — and a
|
|
822
|
+
// built-in WINS a shared name, so a skill named like a command is
|
|
823
|
+
// never listed twice and never shadows it (the dispatcher's rule)
|
|
824
|
+
const builtins = new Set(MENU_ITEMS.map((m) => m.name));
|
|
825
|
+
const all = [...MENU_ITEMS, ...this.#menuExtras().filter((m) => !builtins.has(m.name))];
|
|
826
|
+
return all.filter((m) => m.name.startsWith(line));
|
|
827
|
+
}
|
|
828
|
+
/** 0.40.1 — bind the menu's extra entries (the CLI binds the installed
|
|
829
|
+
* skills). A function, read per keystroke, never a snapshot. */
|
|
830
|
+
bindMenuExtras(extras) {
|
|
831
|
+
this.#menuExtras = extras;
|
|
798
832
|
}
|
|
799
833
|
#refreshMenu() {
|
|
800
|
-
if (this.#panelInput.up())
|
|
801
|
-
|
|
834
|
+
if (this.#panelInput.up()) {
|
|
835
|
+
// W21: the menu never opens while the panel owns the keys — but
|
|
836
|
+
// the key that got here still changed the line, and insert and
|
|
837
|
+
// delete render only through this method (item 6: returning
|
|
838
|
+
// before the render left a panel's typed text to ride the next
|
|
839
|
+
// spinner tick, 0–150 ms late, or no tick at all).
|
|
840
|
+
this.#onRender();
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
802
843
|
const f = this.#menuFiltered();
|
|
803
844
|
this.#menuOpen = f.length > 0;
|
|
804
845
|
if (this.#menuSel >= f.length)
|
|
@@ -938,8 +979,8 @@ export class Editor {
|
|
|
938
979
|
}
|
|
939
980
|
// ── TUI2-R2 ② — the session picker ───────────────────────────────
|
|
940
981
|
/** Open the picker on a bound card source (PickInput, S5). */
|
|
941
|
-
beginPick(cards, onPick) {
|
|
942
|
-
this.#pickInput.begin(cards, onPick);
|
|
982
|
+
beginPick(cards, onPick, here) {
|
|
983
|
+
this.#pickInput.begin(cards, onPick, here);
|
|
943
984
|
}
|
|
944
985
|
pickState() {
|
|
945
986
|
return this.#pickInput.state();
|
|
@@ -957,7 +998,15 @@ export class Editor {
|
|
|
957
998
|
* stashed and restored at close, the panel takes the keys and the
|
|
958
999
|
* input row's lead, the composer's own bands close. */
|
|
959
1000
|
beginPanel(view, onCommit, opts) {
|
|
960
|
-
|
|
1001
|
+
const handOff = (v) => {
|
|
1002
|
+
// item 6: a verdict hands the frame to the run. The panel already
|
|
1003
|
+
// asked for a key frame on this key; the non-key render after the
|
|
1004
|
+
// verdict demotes it to the run's window.
|
|
1005
|
+
this.#handedOff = true;
|
|
1006
|
+
onCommit(v);
|
|
1007
|
+
this.#onRender();
|
|
1008
|
+
};
|
|
1009
|
+
this.#panelInput.begin(view, handOff, opts);
|
|
961
1010
|
}
|
|
962
1011
|
/** W21: cancel the panel — the SIGINT path's pair to beginPanel. */
|
|
963
1012
|
cancelPanel() {
|
|
@@ -998,7 +1047,6 @@ export class Editor {
|
|
|
998
1047
|
// chance this process gets, and emitting six harmless bytes twice
|
|
999
1048
|
// is not a cost worth reasoning about.
|
|
1000
1049
|
process.stdout.write(MOUSE_OFF);
|
|
1001
|
-
this.#mouseOn = false;
|
|
1002
1050
|
process.stdout.write("\x1b[?2004l"); // bracketed paste OFF
|
|
1003
1051
|
// REL-0161: the hardware cursor was hidden for the session's whole
|
|
1004
1052
|
// life (the compositor's entry reset); this is the one place kiso
|
|
@@ -1010,30 +1058,20 @@ export class Editor {
|
|
|
1010
1058
|
this.#closedResolve();
|
|
1011
1059
|
}
|
|
1012
1060
|
/**
|
|
1013
|
-
* TUI2-R3v2
|
|
1014
|
-
*
|
|
1061
|
+
* TUI2-R3v2 ②, corrected by the owner's ruling of 2026-09-21 (finding
|
|
1062
|
+
* DC-56) — the bands still tell their host when a selection surface
|
|
1063
|
+
* opens or closes, and the host's answer is now "leave the terminal
|
|
1064
|
+
* alone".
|
|
1015
1065
|
*
|
|
1016
|
-
*
|
|
1017
|
-
*
|
|
1018
|
-
*
|
|
1019
|
-
*
|
|
1020
|
-
*
|
|
1021
|
-
*
|
|
1022
|
-
*
|
|
1023
|
-
* not depend on the order they unwind in.
|
|
1066
|
+
* The bands' contract is unchanged — a band reports that it has taken
|
|
1067
|
+
* the composer — but the mouse is not a thing kiso hands out: reporting
|
|
1068
|
+
* on means the terminal stops scrolling its own scrollback, and every
|
|
1069
|
+
* band kiso has (the approval/ask/pick panel, the session picker, the @
|
|
1070
|
+
* picker) is a keyboard surface — digits, arrows, enter, esc, a filter.
|
|
1071
|
+
* The call sites stay so a future surface that DOES want the mouse has
|
|
1072
|
+
* one place to ask, and the ruling is written at that place.
|
|
1024
1073
|
*/
|
|
1025
|
-
#
|
|
1026
|
-
if (this.#mouseOn === on)
|
|
1027
|
-
return;
|
|
1028
|
-
this.#mouseOn = on;
|
|
1029
|
-
if (this.#entered)
|
|
1030
|
-
process.stdout.write(on ? MOUSE_ON : MOUSE_OFF);
|
|
1031
|
-
}
|
|
1032
|
-
/** The surfaces that own a selection — the approval/ask/pick panel, the
|
|
1033
|
-
* session picker and the @ picker. Any one of them up = reporting on. */
|
|
1034
|
-
#syncMouse() {
|
|
1035
|
-
this.#setMouse(this.#panelInput.up() || this.#pickInput.up() || this.#atUp());
|
|
1036
|
-
}
|
|
1074
|
+
#syncMouse() { }
|
|
1037
1075
|
/** OR-11 (a) — the lead the composer's row is DRAWN with, which is not
|
|
1038
1076
|
* always the brick. The CLI binds the compositor's input lead as `""`
|
|
1039
1077
|
* (a prompt character is a third thing saying "input lives here", and
|
|
@@ -1074,6 +1112,21 @@ export class Editor {
|
|
|
1074
1112
|
// ---- input ----
|
|
1075
1113
|
/** Feed raw stdin bytes — the parser. Public for unit tests. */
|
|
1076
1114
|
feed(raw) {
|
|
1115
|
+
const outer = this.#feeding;
|
|
1116
|
+
const outerHandedOff = this.#handedOff;
|
|
1117
|
+
this.#feeding = true;
|
|
1118
|
+
if (!outer)
|
|
1119
|
+
this.#handedOff = false;
|
|
1120
|
+
try {
|
|
1121
|
+
this.#feedKeys(raw);
|
|
1122
|
+
}
|
|
1123
|
+
finally {
|
|
1124
|
+
this.#feeding = outer;
|
|
1125
|
+
if (!outer)
|
|
1126
|
+
this.#handedOff = outerHandedOff;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
#feedKeys(raw) {
|
|
1077
1130
|
const text = this.#pending + this.#decoder.decode(raw, { stream: true });
|
|
1078
1131
|
this.#pending = "";
|
|
1079
1132
|
// TUI2-R1 (D): the sheet is up — ANY key closes it, and the key
|
|
@@ -1512,6 +1565,10 @@ export class Editor {
|
|
|
1512
1565
|
}
|
|
1513
1566
|
i += 1;
|
|
1514
1567
|
}
|
|
1568
|
+
else if (c === "\t" && this.#pickInput.toggleScope()) {
|
|
1569
|
+
// 0.40.0: the session picker's scope — this workspace ↔ all
|
|
1570
|
+
i += 1;
|
|
1571
|
+
}
|
|
1515
1572
|
else if (c === "\t" && this.#atUp()) {
|
|
1516
1573
|
// KC3 §3: Tab accepts the selected path — the token becomes
|
|
1517
1574
|
// `@<path> `. Never the file's content.
|
|
@@ -1692,6 +1749,13 @@ export class Editor {
|
|
|
1692
1749
|
* and context-menu everywhere else and would mean "approve" here.
|
|
1693
1750
|
* The stakes are a side effect the human did not ask for, and an
|
|
1694
1751
|
* ambiguous mouse event is not consent.
|
|
1752
|
+
*
|
|
1753
|
+
* 2026-09-21 (finding DC-56): kiso no longer ENABLES reporting, so on a
|
|
1754
|
+
* terminal this path is reached only when something else left the mouse
|
|
1755
|
+
* on — and then a click still answers the panel. The wheel stays
|
|
1756
|
+
* dropped either way (a scroll past a panel is not a choice), which is
|
|
1757
|
+
* the other half of why the settled rule is "no surface takes the
|
|
1758
|
+
* mouse": a terminal that is reporting cannot scroll its own history.
|
|
1695
1759
|
*/
|
|
1696
1760
|
#mouseEvent(params, press) {
|
|
1697
1761
|
if (!press)
|
|
@@ -1712,6 +1776,11 @@ export class Editor {
|
|
|
1712
1776
|
bindPanelRows(fn) {
|
|
1713
1777
|
this.#panelRows = fn;
|
|
1714
1778
|
}
|
|
1779
|
+
/** B: the pick window the last frame drew — the Dock's value, not the
|
|
1780
|
+
* editor's guess. Same pull-on-demand shape as `bindPanelRows`. */
|
|
1781
|
+
bindPickWindow(fn) {
|
|
1782
|
+
this.#pickWin = fn;
|
|
1783
|
+
}
|
|
1715
1784
|
/** TMUX-F1 ②: `run` identical arrow sequences arrived in ONE read. Every
|
|
1716
1785
|
* surface but one gets every press — a wheel over the transcript viewer,
|
|
1717
1786
|
* a panel, a multi-row draft scrolls, as a wheel should. The HISTORY
|
|
@@ -1941,6 +2010,7 @@ export class Editor {
|
|
|
1941
2010
|
reflow: () => this.#reflow(),
|
|
1942
2011
|
render: () => this.#onRender(),
|
|
1943
2012
|
syncMouse: () => this.#syncMouse(),
|
|
2013
|
+
pickWindow: () => this.#pickWin?.() ?? null,
|
|
1944
2014
|
closeBands: () => {
|
|
1945
2015
|
this.#menuOpen = false;
|
|
1946
2016
|
this.#menuSel = 0;
|
|
@@ -2436,6 +2506,7 @@ export class Editor {
|
|
|
2436
2506
|
// recalls a readable line that still expands when it is sent
|
|
2437
2507
|
// again (the map outlives the buffer, by design).
|
|
2438
2508
|
const sent = this.#expandPastes(line);
|
|
2509
|
+
this.#handedOff = true;
|
|
2439
2510
|
const cb = this.#questionCb;
|
|
2440
2511
|
this.#questionCb = null;
|
|
2441
2512
|
if (cb !== null) {
|
|
@@ -2449,7 +2520,7 @@ export class Editor {
|
|
|
2449
2520
|
}
|
|
2450
2521
|
if (cb === null && line !== "")
|
|
2451
2522
|
this.#remember(line);
|
|
2452
|
-
this.#onRender();
|
|
2523
|
+
this.#onRender(); // item 6: #handedOff is set, so this render is non-key — the submit's frame keeps the run's window
|
|
2453
2524
|
}
|
|
2454
2525
|
/** A2: step the history browse; a delta past the newest exits back to
|
|
2455
2526
|
* 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/panel-input.d.ts
CHANGED
|
@@ -32,6 +32,14 @@ export interface BandHost {
|
|
|
32
32
|
line(): string;
|
|
33
33
|
/** The buffer with its paste capsules expanded — the text that would leave the editor. */
|
|
34
34
|
expandPastes(line: string): string;
|
|
35
|
+
/** The pick panel's window as the RENDERER drew it (review of this round):
|
|
36
|
+
* the size depends on the frame's budget, so the digit keys must be handed
|
|
37
|
+
* the value instead of deriving it again from PICK_MAX. Absent → the input
|
|
38
|
+
* layer falls back to its own derivation. */
|
|
39
|
+
pickWindow?(): {
|
|
40
|
+
first: number;
|
|
41
|
+
size: number;
|
|
42
|
+
} | null;
|
|
35
43
|
/** Empty the buffer: chars, cursor and the ↑↓ goal column. */
|
|
36
44
|
clear(): void;
|
|
37
45
|
/** Type one code point at the cursor. */
|
package/dist/panel-input.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* and the mouse hit. The bodies are the same; only the buffer access
|
|
17
17
|
* goes through the host.
|
|
18
18
|
*/
|
|
19
|
-
import { PICK_MAX, panelOptions, saferDegradedNote, startLevel, stepLevel, } from "./approval-panel.js";
|
|
19
|
+
import { PICK_MAX, panelOptions, pickWindow, saferDegradedNote, startLevel, stepLevel, } from "./approval-panel.js";
|
|
20
20
|
import { askCommitCustom, askKey, askOnCustomRow, askStart } from "./ask-panel.js";
|
|
21
21
|
export class PanelInput {
|
|
22
22
|
host;
|
|
@@ -206,7 +206,11 @@ export class PanelInput {
|
|
|
206
206
|
if (panel === null)
|
|
207
207
|
return false;
|
|
208
208
|
if (panel.pick !== null && panel.pick.phase === "options") {
|
|
209
|
-
|
|
209
|
+
// DC-58: EVERY option is reachable by ↑↓ — the window follows the
|
|
210
|
+
// cursor (`pickWindow`) and PICK_MAX is only how many rows one
|
|
211
|
+
// screen holds. This bound used to be `min(count, PICK_MAX)`, which
|
|
212
|
+
// is what left a 60-profile config with nine reachable rows.
|
|
213
|
+
const n = panel.view.pick.options.length;
|
|
210
214
|
const cur = panel.pick.cursor;
|
|
211
215
|
const next = dir === "up" ? Math.max(0, cur - 1) : Math.min(Math.max(0, n - 1), cur + 1);
|
|
212
216
|
// OR-7: the second axis belongs to the highlighted option, so it
|
|
@@ -448,9 +452,18 @@ export class PanelInput {
|
|
|
448
452
|
const panel = this.#panel;
|
|
449
453
|
if (panel === null || panel.pick === null)
|
|
450
454
|
return;
|
|
451
|
-
|
|
455
|
+
const count = panel.view.pick.options.length;
|
|
456
|
+
// DC-58: a digit names the row ON SCREEN. The window's origin is what
|
|
457
|
+
// turns a visible position into an option index — the same
|
|
458
|
+
// `pickWindow` the renderer drew the rows with, so what the row says
|
|
459
|
+
// and what the key does cannot drift apart.
|
|
460
|
+
const { first, size } = this.host.pickWindow?.() ?? pickWindow(panel.pick.cursor, count, PICK_MAX);
|
|
461
|
+
if (index < 0 || index >= size)
|
|
452
462
|
return;
|
|
453
|
-
|
|
463
|
+
const target = first + index;
|
|
464
|
+
if (target >= count)
|
|
465
|
+
return;
|
|
466
|
+
panel.pick = { cursor: target, phase: "options", level: startLevel(panel.view.pick.options[target]) };
|
|
454
467
|
this.host.render();
|
|
455
468
|
}
|
|
456
469
|
#pickPanelEnter() {
|
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;
|
package/dist/pick-input.js
CHANGED
|
@@ -18,12 +18,16 @@
|
|
|
18
18
|
* through the host.
|
|
19
19
|
*/
|
|
20
20
|
import { AT_VISIBLE } from "./at-picker.js";
|
|
21
|
-
import { sessionFilter } from "./session-picker.js";
|
|
21
|
+
import { scopeSessions, sessionFilter } from "./session-picker.js";
|
|
22
22
|
export class PickInput {
|
|
23
23
|
host;
|
|
24
24
|
#cards = null;
|
|
25
25
|
#commit = null;
|
|
26
26
|
#sel = 0;
|
|
27
|
+
/** 0.40.0: the running workspace (undefined = an unscoped picker), and
|
|
28
|
+
* whether the person flipped to ALL. */
|
|
29
|
+
#here = undefined;
|
|
30
|
+
#all = false;
|
|
27
31
|
constructor(host) {
|
|
28
32
|
this.host = host;
|
|
29
33
|
}
|
|
@@ -34,10 +38,12 @@ export class PickInput {
|
|
|
34
38
|
* (the buffer becomes the filter query) and `onPick` receives the
|
|
35
39
|
* chosen id — or null when the human leaves without picking, which
|
|
36
40
|
* is a first-class outcome and not an error. */
|
|
37
|
-
begin(cards, onPick) {
|
|
41
|
+
begin(cards, onPick, here) {
|
|
38
42
|
this.#cards = cards;
|
|
39
43
|
this.#commit = onPick;
|
|
40
44
|
this.#sel = 0;
|
|
45
|
+
this.#here = here;
|
|
46
|
+
this.#all = false;
|
|
41
47
|
this.host.syncMouse();
|
|
42
48
|
this.host.clear();
|
|
43
49
|
this.host.reflow();
|
|
@@ -51,15 +57,35 @@ export class PickInput {
|
|
|
51
57
|
state() {
|
|
52
58
|
if (this.#cards === null)
|
|
53
59
|
return null;
|
|
54
|
-
|
|
60
|
+
// 0.40.0: the filter runs INSIDE the scope — typing narrows what the
|
|
61
|
+
// title says is showing, never reaches past it
|
|
62
|
+
const scoped = this.#here === undefined ? null : scopeSessions(this.#cards(), this.#here, this.#all);
|
|
63
|
+
const cards = scoped === null ? this.#cards() : scoped.cards;
|
|
55
64
|
const matches = sessionFilter(cards, this.host.line());
|
|
56
|
-
return { cards, matches, selected: Math.max(0, Math.min(this.#sel, matches.length - 1)) };
|
|
65
|
+
return { cards, matches, selected: Math.max(0, Math.min(this.#sel, matches.length - 1)), scope: scoped?.scope ?? null };
|
|
66
|
+
}
|
|
67
|
+
/** 0.40.0 — tab flips CURRENT ↔ ALL. The filter owns every printable
|
|
68
|
+
* key (the buffer IS the query), so the toggle cannot be one. True when
|
|
69
|
+
* the picker owned the key. */
|
|
70
|
+
toggleScope() {
|
|
71
|
+
const view = this.state();
|
|
72
|
+
if (view === null || view.scope === null || view.scope === undefined)
|
|
73
|
+
return false;
|
|
74
|
+
this.#all = !this.#all;
|
|
75
|
+
this.#sel = 0;
|
|
76
|
+
this.host.reflow();
|
|
77
|
+
this.host.render();
|
|
78
|
+
return true;
|
|
57
79
|
}
|
|
58
80
|
/** The band's height estimate: the header + the windowed rows (or
|
|
59
81
|
* the one "no match" row) + the counter. */
|
|
60
82
|
rows() {
|
|
61
83
|
const view = this.state();
|
|
62
|
-
|
|
84
|
+
if (view === null)
|
|
85
|
+
return 0;
|
|
86
|
+
// 0.40.1: + the unknown-workspace row, when CURRENT shows it
|
|
87
|
+
const unknownRow = view.scope != null && !view.scope.all && view.scope.unknown > 0 ? 1 : 0;
|
|
88
|
+
return Math.min(Math.max(view.matches.length, 1), AT_VISIBLE) + 2 + unknownRow;
|
|
63
89
|
}
|
|
64
90
|
/** ↑↓: the selection walks the matches and stops at both ends. True
|
|
65
91
|
* when the picker owned the key (the caller renders). */
|
|
@@ -78,6 +104,8 @@ export class PickInput {
|
|
|
78
104
|
this.#cards = null;
|
|
79
105
|
this.#commit = null;
|
|
80
106
|
this.#sel = 0;
|
|
107
|
+
this.#here = undefined;
|
|
108
|
+
this.#all = false;
|
|
81
109
|
this.host.syncMouse();
|
|
82
110
|
this.host.clear();
|
|
83
111
|
this.host.reflow();
|