@vincemakes/kiso-tui 0.46.1 → 0.47.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/ask-panel.d.ts +10 -5
- package/dist/ask-panel.js +109 -54
- package/dist/at-picker.d.ts +38 -68
- package/dist/at-picker.js +50 -141
- package/dist/compositor.d.ts +79 -10
- package/dist/compositor.js +339 -57
- package/dist/context-ledger.d.ts +28 -0
- package/dist/context-ledger.js +62 -0
- package/dist/editor.d.ts +17 -0
- package/dist/editor.js +76 -15
- package/dist/index.d.ts +10 -10
- package/dist/index.js +10 -10
- package/dist/lines.d.ts +11 -0
- package/dist/lines.js +38 -0
- package/dist/notice-meta.d.ts +25 -0
- package/dist/notice-meta.js +83 -0
- package/dist/panel-input.d.ts +2 -2
- package/dist/panel-input.js +70 -60
- package/dist/pick-input.d.ts +23 -3
- package/dist/pick-input.js +9 -29
- package/dist/session-picker.d.ts +37 -22
- package/dist/session-picker.js +270 -57
- package/dist/status.d.ts +78 -75
- package/dist/status.js +182 -108
- package/package.json +2 -2
package/dist/status.js
CHANGED
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
* widens to name the new gesture.
|
|
23
23
|
*/
|
|
24
24
|
import { kUnit } from "./lines.js";
|
|
25
|
-
import { meterGlyphs } from "./context-ledger.js";
|
|
25
|
+
import { meterCells, meterGlyphs } from "./context-ledger.js";
|
|
26
26
|
/** The compacting row's bar width — short: it shares a row. */
|
|
27
27
|
const BAR_ON_ROW = 6;
|
|
28
28
|
import { elapsedLabel } from "@vincemakes/kiso-tui-cells";
|
|
29
|
-
import { TWINKLE } from "@vincemakes/kiso-tui-cells/render";
|
|
30
|
-
import { displayWidth } from "@vincemakes/kiso-tui-cells/width";
|
|
29
|
+
import { TWINKLE, cutLine, palette } from "@vincemakes/kiso-tui-cells/render";
|
|
30
|
+
import { displayWidth, visibleWidth } from "@vincemakes/kiso-tui-cells/width";
|
|
31
31
|
/**
|
|
32
32
|
* R3 (design §5.2) — the working glyph family is the TWINKLE, and the
|
|
33
33
|
* CLI's 200ms spinner walks it exactly as it walked the four quadrant
|
|
@@ -43,25 +43,6 @@ import { displayWidth } from "@vincemakes/kiso-tui-cells/width";
|
|
|
43
43
|
* Glyphs only, no colour: intact under NO_COLOR and on any ground.
|
|
44
44
|
*/
|
|
45
45
|
export const STATUS_GLYPHS = TWINKLE;
|
|
46
|
-
/**
|
|
47
|
-
* The ~ctx estimate as the whole-percent LEFT, or `ctx ?` when there is no
|
|
48
|
-
* window to divide by.
|
|
49
|
-
*
|
|
50
|
-
* A percentage needs a denominator. When the model's context window is not
|
|
51
|
-
* KNOWN — the registry records null for it, nobody set one in the profile,
|
|
52
|
-
* no env — there is no denominator, and this row's rule is the same as
|
|
53
|
-
* every other number on it: a measurement, or nothing. It used to print
|
|
54
|
-
* `ctx left ~null%`, which at least did not invent a figure; `ctx ?` says
|
|
55
|
-
* the same thing to a reader.
|
|
56
|
-
*
|
|
57
|
-
* The failure this replaces is worse than either: the window fell back to
|
|
58
|
-
* a hardcoded 200,000 and the row printed a confident `ctx left ~82%`
|
|
59
|
-
* against a number nobody had measured. A reader had no way to tell that
|
|
60
|
-
* percentage from one computed against a real window.
|
|
61
|
-
*/
|
|
62
|
-
function ctxSegment(ratio) {
|
|
63
|
-
return Number.isFinite(ratio) ? `ctx left ~${Math.round((1 - ratio) * 100)}%` : "ctx ?";
|
|
64
|
-
}
|
|
65
46
|
/**
|
|
66
47
|
* TPS-1 — the settled DECODE rate of one model call: its output tokens
|
|
67
48
|
* over the seconds from the call's first streamed event to its usage
|
|
@@ -91,19 +72,22 @@ export function decodeRate(outputTokens, elapsedMs) {
|
|
|
91
72
|
export function composeRow(head, segments, W) {
|
|
92
73
|
const present = segments.filter((x) => x != null && x.text !== "");
|
|
93
74
|
const join = (xs) => [head, ...xs.map((x) => x.text)].join(" · ");
|
|
75
|
+
// the compaction round: a fact may carry colour (the progress cells), and
|
|
76
|
+
// its codes take no cells — measured without them, a plain row as before
|
|
77
|
+
const width = (t) => displayWidth(t.replace(/\x1b\[[0-9;]*m/g, ""));
|
|
94
78
|
const full = join(present);
|
|
95
|
-
if (W === undefined ||
|
|
79
|
+
if (W === undefined || width(full) <= W)
|
|
96
80
|
return full;
|
|
97
81
|
// 1. elide every label in its middle
|
|
98
82
|
let row = present.map((x) => (x.kind === "label" ? { ...x, text: elideMiddle(x.text, LABEL_ON_ROW) } : x));
|
|
99
|
-
if (
|
|
83
|
+
if (width(join(row)) <= W)
|
|
100
84
|
return join(row);
|
|
101
85
|
// 2. drop hints from the end, one at a time
|
|
102
86
|
for (let i = row.length - 1; i >= 0; i -= 1) {
|
|
103
87
|
if (row[i].kind !== "hint")
|
|
104
88
|
continue;
|
|
105
89
|
row = [...row.slice(0, i), ...row.slice(i + 1)];
|
|
106
|
-
if (
|
|
90
|
+
if (width(join(row)) <= W)
|
|
107
91
|
return join(row);
|
|
108
92
|
}
|
|
109
93
|
// 3. facts are never dropped: past this point the row is over budget,
|
|
@@ -118,44 +102,13 @@ export function retrySegment(r) {
|
|
|
118
102
|
const head = `retrying ${r.attempt}/${r.maxRetries} · ${r.code}`;
|
|
119
103
|
return r.remainingMs > 0 ? `${head} · ${Math.ceil(r.remainingMs / 1000)}s` : head;
|
|
120
104
|
}
|
|
121
|
-
/** `● 2 tasks running
|
|
122
|
-
*
|
|
123
|
-
*
|
|
105
|
+
/** `● 2 tasks running` — or nothing. A task kiso lost track of is not a
|
|
106
|
+
* second count here: it is an event, said once in the transcript
|
|
107
|
+
* (Amendment 8). */
|
|
124
108
|
export function tasksSegment(c) {
|
|
125
|
-
if (c === undefined)
|
|
109
|
+
if (c === undefined || c.running <= 0)
|
|
126
110
|
return "";
|
|
127
|
-
|
|
128
|
-
if (c.running > 0 && c.unknown > 0)
|
|
129
|
-
return `● ${n(c.running, "task")} running · ◌ ${c.unknown} unknown`;
|
|
130
|
-
if (c.running > 0)
|
|
131
|
-
return `● ${n(c.running, "task")} running`;
|
|
132
|
-
if (c.unknown > 0)
|
|
133
|
-
return `◌ ${n(c.unknown, "task")} unknown`;
|
|
134
|
-
return "";
|
|
135
|
-
}
|
|
136
|
-
export function runningStatus(glyph, since, outTokens, ctxRatio, tokPerSec = null, W, retry,
|
|
137
|
-
// 3e: the session's tasks, and whether a running command can be moved
|
|
138
|
-
// to the background now (the row teaches ctrl+b exactly then)
|
|
139
|
-
tasks, detachable = false) {
|
|
140
|
-
const out = outTokens !== null ? ` ↓ ${kUnit(outTokens)} tokens` : "";
|
|
141
|
-
const seconds = Math.max(1, Math.round((Date.now() - since) / 1000));
|
|
142
|
-
return composeRow(`${glyph} working ${elapsedLabel(seconds)}${out}`, [
|
|
143
|
-
// ADR-0005 Amendment 2: a pending retry is a FACT and sits first — it
|
|
144
|
-
// is the one thing on the row that explains why nothing is arriving,
|
|
145
|
-
// and a retry budget of minutes with nothing on screen reads as a
|
|
146
|
-
// hung session.
|
|
147
|
-
retry != null ? { kind: "fact", text: retrySegment(retry) } : null,
|
|
148
|
-
// TPS-1: after each call SETTLES within the turn, between the tokens
|
|
149
|
-
// segment and the stop hint. The default is null and that is the
|
|
150
|
-
// honest rule spelled as a default — the recovery flow has no
|
|
151
|
-
// per-call timing state, so its row says nothing rather than guessing.
|
|
152
|
-
tokPerSec !== null ? { kind: "fact", text: `${tokPerSec} tok/s` } : null,
|
|
153
|
-
{ kind: "fact", text: tasksSegment(tasks) },
|
|
154
|
-
{ kind: "hint", text: "esc stop" },
|
|
155
|
-
detachable ? { kind: "hint", text: "ctrl+b background" } : null,
|
|
156
|
-
{ kind: "hint", text: "alt+⏎ redirect" },
|
|
157
|
-
{ kind: "fact", text: ctxSegment(ctxRatio) },
|
|
158
|
-
], W);
|
|
111
|
+
return `● ${c.running} task${c.running === 1 ? "" : "s"} running`;
|
|
159
112
|
}
|
|
160
113
|
/**
|
|
161
114
|
* The COMPACTING row (W18): the covered rounds, the pre-call token
|
|
@@ -164,16 +117,32 @@ tasks, detachable = false) {
|
|
|
164
117
|
* inline template in dispatch (0.40.0) so it composes like every other
|
|
165
118
|
* row and has a place for what the launch build adds to it.
|
|
166
119
|
*/
|
|
167
|
-
|
|
120
|
+
/** The compaction round (owner, 2026-10-06) — the summary's progress in
|
|
121
|
+
* the bar's own cells (§8.9's ctx meter, `meterCells`): `▆` filled in
|
|
122
|
+
* `ink2`, the rest in the track colour. `▰▱` drew tiny in Menlo and
|
|
123
|
+
* retired from the bar; off a known ground there are no colours to tell
|
|
124
|
+
* filled from empty, so the glyphs carry it there. */
|
|
125
|
+
function progressCells(ratio, cells) {
|
|
126
|
+
const p = palette();
|
|
127
|
+
if (p.track === "")
|
|
128
|
+
return meterGlyphs(ratio, cells);
|
|
129
|
+
const filled = Math.max(0, Math.min(cells, Math.round((Number.isFinite(ratio) ? ratio : 0) * cells)));
|
|
130
|
+
return `${p.ink2}${"\u2586".repeat(filled)}${p.track}${"\u2586".repeat(cells - filled)}${p.fgEnd}`;
|
|
131
|
+
}
|
|
132
|
+
export function compactingStatus(glyph, rounds, tokens, elapsedSeconds, W, retry, progress, why) {
|
|
168
133
|
// 0.40.0: with a budget to measure against, the covered size and the bar
|
|
169
134
|
// are ONE fact — what went in, and how much of the output budget has
|
|
170
135
|
// come out. Without a budget the row keeps the covered size alone: the
|
|
171
136
|
// bar never invents a denominator.
|
|
172
137
|
const covered = progress != null && progress.budget !== null && progress.budget > 0
|
|
173
|
-
? `~${kUnit(tokens)} \u2192 ${
|
|
138
|
+
? `~${kUnit(tokens)} \u2192 ${progressCells(progress.produced / progress.budget, BAR_ON_ROW)} ${kUnit(progress.produced)}/${kUnit(progress.budget)}`
|
|
174
139
|
: `~${kUnit(tokens)} tokens`;
|
|
175
140
|
return composeRow(`${glyph} compacting`, [
|
|
176
|
-
|
|
141
|
+
// Graphite §8.7 (R3b, G7): why it is compacting — `manual` for
|
|
142
|
+
// /compact, `auto` past the configured threshold — first, a fact
|
|
143
|
+
why !== undefined && why !== "" ? { kind: "fact", text: why } : null,
|
|
144
|
+
// the compaction round: one round is a round
|
|
145
|
+
{ kind: "fact", text: `${rounds} round${rounds === 1 ? "" : "s"}` },
|
|
177
146
|
{ kind: "fact", text: covered },
|
|
178
147
|
// why the figure jumped when the usage landed — a HINT, so a narrow
|
|
179
148
|
// row gives it up before the bar, the seconds or the retry
|
|
@@ -221,50 +190,6 @@ function elideMiddle(text, max) {
|
|
|
221
190
|
}
|
|
222
191
|
return `${head}…${tail}`;
|
|
223
192
|
}
|
|
224
|
-
/**
|
|
225
|
-
* The IDLE row: the approval tier as the CALLER names it, the /mode hint,
|
|
226
|
-
* the model driving the session, the TUI2-R1 meter when there is one, and
|
|
227
|
-
* the ctx estimate. Called without a meter — or with one that knows
|
|
228
|
-
* nothing — the row is byte-identical to the pre-round row.
|
|
229
|
-
*
|
|
230
|
-
* DF-0330-F1 — THE DROP ORDER. `W` is the row's budget; given one, the row
|
|
231
|
-
* gives ground in a fixed order rather than letting invariant ① cut its
|
|
232
|
-
* end off. Found the hard way: at 100 columns the ` · N tok/s` segment
|
|
233
|
-
* never appeared and at 140 it did, because the row was 102 columns wide
|
|
234
|
-
* and the segment TPS-1 added sat last.
|
|
235
|
-
*
|
|
236
|
-
* 1. the MODEL ID is elided in its middle. It is the only segment that
|
|
237
|
-
* varies, it is the one that grew (the owner's is 35 columns of a
|
|
238
|
-
* 90-column row), and eliding it gives the row back a budget instead
|
|
239
|
-
* of re-allocating a deficit;
|
|
240
|
-
* 2. `/mode to switch` is dropped. It teaches a gesture; `/mode` and `?`
|
|
241
|
-
* still exist and the row is not the only place they are taught;
|
|
242
|
-
* 3. the FACTS are never dropped and never cut — the tier, CH, the ctx
|
|
243
|
-
* estimate and the rate. A row that silently drops a measurement is
|
|
244
|
-
* the defect this rule exists to prevent.
|
|
245
|
-
*
|
|
246
|
-
* The elision is ON THE ROW only. `/model`, the session log and the trace
|
|
247
|
-
* ledger all keep the id whole — the row is a view, never the record.
|
|
248
|
-
*
|
|
249
|
-
* No `W` means no dropping, which is what the callers that do not know
|
|
250
|
-
* their width should get: today's row, unchanged.
|
|
251
|
-
*/
|
|
252
|
-
export function idleStatus(tier, model, ctxRatio, meter, W, floorOff = false, tasks) {
|
|
253
|
-
return composeRow(`▸ ${tier}`, [
|
|
254
|
-
// 0.40.0: the catastrophe floor is on by default and says nothing;
|
|
255
|
-
// OFF is the state worth seeing, and a fact beside the tier it
|
|
256
|
-
// changes the meaning of.
|
|
257
|
-
floorOff ? { kind: "fact", text: "floor off" } : null,
|
|
258
|
-
// 3e: work still going on (or gone unknown) after the turn ended
|
|
259
|
-
{ kind: "fact", text: tasksSegment(tasks) },
|
|
260
|
-
{ kind: "hint", text: "/mode to switch" },
|
|
261
|
-
{ kind: "label", text: model },
|
|
262
|
-
meter?.cacheHitPct != null ? { kind: "fact", text: `CH ${Math.round(meter.cacheHitPct)}%` } : null,
|
|
263
|
-
// costUsd deliberately NOT rendered — see StatusMeter.costUsd.
|
|
264
|
-
{ kind: "fact", text: ctxSegment(ctxRatio) },
|
|
265
|
-
meter?.tokPerSec != null ? { kind: "fact", text: `${meter.tokPerSec} tok/s` } : null, // TPS-1: last, after the ctx estimate
|
|
266
|
-
], W);
|
|
267
|
-
}
|
|
268
193
|
/** TUI2-R1 (E) — the cache hit rate the status row shows, from the usage
|
|
269
194
|
* the CLI already tracks. The denominator is the TOTAL the model was
|
|
270
195
|
* given (fresh + cacheRead), which is the E2 ruling's own: cacheRead
|
|
@@ -278,3 +203,152 @@ export function cacheHitPct(usage) {
|
|
|
278
203
|
const total = fresh + cached;
|
|
279
204
|
return total > 0 ? (cached / total) * 100 : null;
|
|
280
205
|
}
|
|
206
|
+
/** The ctx meter's width in cells (§8.9). */
|
|
207
|
+
/**
|
|
208
|
+
* §8.9 — the ctx meter: ten `▆` cells with the used share filled, then the
|
|
209
|
+
* percentage used. Filled cells take `ink2` below the soft compaction tier,
|
|
210
|
+
* gold from the soft tier to the hard one, the failure colour past the hard
|
|
211
|
+
* tier; the empty cells are `track`. There is no marker inside the bar —
|
|
212
|
+
* the tier shows as colour only (owner, 2026-09-28). Off a known ground
|
|
213
|
+
* the colours carry nothing, so the meter is the percentage alone (§1.2:
|
|
214
|
+
* the fact survives without the colour). `ctx ?` when the window is not
|
|
215
|
+
* known.
|
|
216
|
+
*/
|
|
217
|
+
export function ctxMeter(ctx) {
|
|
218
|
+
if (ctx === null || !Number.isFinite(ctx.used))
|
|
219
|
+
return "ctx ?";
|
|
220
|
+
const used = Math.max(0, ctx.used);
|
|
221
|
+
const pct = `${Math.round(used * 100)}%`;
|
|
222
|
+
const p = palette();
|
|
223
|
+
if (p.track === "")
|
|
224
|
+
return `ctx ${pct}`;
|
|
225
|
+
// the cells follow the percentage SHOWN: `ctx 0%` is an empty meter,
|
|
226
|
+
// and from 1% at least one cell is filled (owner, 2026-09-29 — a lit
|
|
227
|
+
// cell beside `0%` read as a contradiction); one rule with /context's
|
|
228
|
+
return `${p.dim}ctx${p.reset} ${meterCells(used, ctx.soft, ctx.hard)} ${p.dim}${pct}${p.reset}`;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* §8.9 — the status bar, composed for `W` cells. Left: the mode chip,
|
|
232
|
+
* `floor off`, `/mode to switch`, the model and its effort, the ctx meter,
|
|
233
|
+
* the decode rate. The cache share is the seal's (owner, 2026-09-29: every
|
|
234
|
+
* run's closing row already says it), not the bar's. Right: the branch, the folder, and the
|
|
235
|
+
* `ctrl+o` switch while a card has rows behind the key.
|
|
236
|
+
*
|
|
237
|
+
* §8.5 — what gives way, in order: the `ctrl+o` hint, the folder (the
|
|
238
|
+
* terminal title names it too), the branch, the model's middle, and last
|
|
239
|
+
* `/mode to switch` — the one place a newcomer meets modes. The facts
|
|
240
|
+
* never drop; past that the row is invariant ①'s to cut.
|
|
241
|
+
*
|
|
242
|
+
* On a known ground the chip is a surface and the segments are spaced; off
|
|
243
|
+
* one the head is today's `▸ <mode>` and the segments are joined with ` · `
|
|
244
|
+
* — the same facts, in words that survive without colour.
|
|
245
|
+
*/
|
|
246
|
+
export function statusBar(b, W, expand) {
|
|
247
|
+
const p = palette();
|
|
248
|
+
const painted = p.washDone !== "";
|
|
249
|
+
const sep = painted ? " " : " · ";
|
|
250
|
+
const tierChip = painted ? `${b.modeAlert === true ? p.fail : ""}${p.washDone} ${b.mode} ${p.washEnd}${b.modeAlert === true ? p.fgEnd : ""}` : `▸ ${b.mode}`;
|
|
251
|
+
// the switch is not a tier, so it is not in the tier's chip: a chip of its
|
|
252
|
+
// own, never dropped — it changes what kiso does as much as the tier does
|
|
253
|
+
const chip = b.dontAsk !== true ? tierChip : painted ? `${tierChip} ${p.gold}${p.washDone} don't ask ${p.washEnd}${p.fgEnd}` : `${tierChip} · don't ask`;
|
|
254
|
+
const fail = (s) => (painted ? `${p.fail}${s}${p.fgEnd}` : s);
|
|
255
|
+
const dim = (s) => `${p.dim}${s}${p.reset}`;
|
|
256
|
+
// the bar's words are quiet (§1.2: grey chrome); off a known ground each
|
|
257
|
+
// side is ONE dim span — the shape the status row always had
|
|
258
|
+
const quiet = (s) => (painted ? dim(s) : s);
|
|
259
|
+
// drop: 0 = never; otherwise the order it gives way in (1 first)
|
|
260
|
+
const left = [
|
|
261
|
+
b.floorOff ? { text: fail("floor off"), drop: 0 } : null,
|
|
262
|
+
// the main-sync round (ADR-0058 3e, §8.9): the session's tasks, right
|
|
263
|
+
// after the mode as 0.46.0's status row has them (owner, 2026-10-06): a
|
|
264
|
+
// FACT, which never gives way — `●` in the machine's blue (running), `◌`
|
|
265
|
+
// in gold (an outcome nobody can know is the one that needs the
|
|
266
|
+
// person, §4); the words quiet like the rest of the bar
|
|
267
|
+
tasksSegment(b.tasks) === "" ? null : { text: painted ? tasksOnBar(b.tasks) : tasksSegment(b.tasks), drop: 0 },
|
|
268
|
+
{ text: quiet("/mode to switch"), drop: 4 },
|
|
269
|
+
{ text: b.model, drop: 0 },
|
|
270
|
+
{ text: ctxMeter(b.ctx), drop: 0 },
|
|
271
|
+
b.tokPerSec !== null ? { text: quiet(`${b.tokPerSec} tok/s`), drop: 0 } : null,
|
|
272
|
+
];
|
|
273
|
+
const right = [
|
|
274
|
+
b.branch !== null ? { text: painted ? `${p.blue}${b.branch}${p.fgEnd}` : b.branch, drop: 3 } : null,
|
|
275
|
+
b.folder !== null ? { text: quiet(b.folder), drop: 2 } : null,
|
|
276
|
+
expand !== null ? { text: quiet(`ctrl+o ${expand}`), drop: 1 } : null,
|
|
277
|
+
];
|
|
278
|
+
let model = b.model;
|
|
279
|
+
const compose = (dropped) => {
|
|
280
|
+
const l = left.filter((x) => x !== null && (x.drop === 0 || x.drop > dropped)).map((x) => (x.text === b.model ? quiet(model) : x.text));
|
|
281
|
+
const r = right.filter((x) => x !== null && (x.drop === 0 || x.drop > dropped)).map((x) => x.text);
|
|
282
|
+
const lt = painted ? [chip, ...l].join(sep) : dim([chip, ...l].join(sep));
|
|
283
|
+
if (r.length === 0)
|
|
284
|
+
return lt;
|
|
285
|
+
const rt = painted ? r.join(" ") : dim(r.join(" "));
|
|
286
|
+
const gap = W - visibleWidth(lt) - visibleWidth(rt);
|
|
287
|
+
return gap >= 2 ? `${lt}${" ".repeat(gap)}${rt}` : `${lt} ${rt}`;
|
|
288
|
+
};
|
|
289
|
+
const fits = (row) => visibleWidth(row) <= W;
|
|
290
|
+
let row = "";
|
|
291
|
+
for (const level of [0, 1, 2, 3]) {
|
|
292
|
+
row = compose(level);
|
|
293
|
+
if (fits(row))
|
|
294
|
+
return row;
|
|
295
|
+
}
|
|
296
|
+
// the model's middle gives way before `/mode to switch` does (DF-0330-F1)
|
|
297
|
+
model = elideMiddle(b.model, LABEL_ON_ROW);
|
|
298
|
+
for (const level of [3, 4]) {
|
|
299
|
+
row = compose(level);
|
|
300
|
+
if (fits(row))
|
|
301
|
+
return row;
|
|
302
|
+
}
|
|
303
|
+
return cutLine(row, W);
|
|
304
|
+
}
|
|
305
|
+
/** The bar's task segment, painted: the mark coloured, the words dim.
|
|
306
|
+
* The main sync (0.46.2, Amendment 8): the gold `◌ N unknown` beside it
|
|
307
|
+
* retired — a task kiso lost track of is said once in the transcript, a
|
|
308
|
+
* TASK row, not counted on the bar ("running" beside "may be running"
|
|
309
|
+
* read as a contradiction to the owner). */
|
|
310
|
+
function tasksOnBar(c) {
|
|
311
|
+
const p = palette();
|
|
312
|
+
return c.running > 0 ? `${p.blue}\u25cf${p.fgEnd} ${p.dim}${c.running} task${c.running === 1 ? "" : "s"} running${p.reset}` : "";
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* §8.7 — the LIVE ROW's words for a running turn: `working` for the whole
|
|
316
|
+
* turn, whatever the model is doing (it never switches to "thinking" —
|
|
317
|
+
* what the model thinks is in the stream), the elapsed, the output tokens,
|
|
318
|
+
* the decode rate. A pending retry replaces it. The keys ride the row's
|
|
319
|
+
* right end.
|
|
320
|
+
*/
|
|
321
|
+
export function workingRow(glyph, since, outTokens, tokPerSec, W, retry, detachable = false) {
|
|
322
|
+
// a pending retry REPLACES `working` while it lasts (§8.7): it is the one
|
|
323
|
+
// thing that explains why nothing is arriving (ADR-0005 Amendment 2),
|
|
324
|
+
// and its countdown is the row's pulse — whole seconds, rounded up, so
|
|
325
|
+
// it never says 0s while still waiting
|
|
326
|
+
if (retry != null) {
|
|
327
|
+
const wait = retry.remainingMs > 0 ? ` · next try in ${Math.ceil(retry.remainingMs / 1000)}s` : "";
|
|
328
|
+
return liveRow(`↻ retrying ${retry.attempt}/${retry.maxRetries} · ${retry.code}${wait}`, ["esc gives up"], W);
|
|
329
|
+
}
|
|
330
|
+
const out = outTokens !== null ? ` · ↓ ${kUnit(outTokens)}` : "";
|
|
331
|
+
const seconds = Math.max(1, Math.round((Date.now() - since) / 1000));
|
|
332
|
+
const facts = [`${glyph} working ${elapsedLabel(seconds)}${out}`, ...(tokPerSec !== null ? [`${tokPerSec} tok/s`] : [])].join(" · ");
|
|
333
|
+
// the main-sync round (ADR-0058 3e): ctrl+b exactly while a running
|
|
334
|
+
// command can be moved to the background — it gives way after esc
|
|
335
|
+
if (detachable)
|
|
336
|
+
return liveRow(facts, ["esc stop \u00b7 ctrl+b background \u00b7 \u23ce steer \u00b7 alt+\u23ce redirect", "esc stop \u00b7 ctrl+b background \u00b7 \u23ce steer", "esc stop \u00b7 ctrl+b background", "esc stop"], W);
|
|
337
|
+
return liveRow(facts, ["esc stop · ⏎ steer · alt+⏎ redirect", "esc stop · ⏎ steer", "esc stop"], W);
|
|
338
|
+
}
|
|
339
|
+
/** §8.7 — a live row: its mark in the mark column (column 0), its facts
|
|
340
|
+
* from the content edge, its keys right-aligned; the keys give way from
|
|
341
|
+
* the right when the row is short, the facts never (past them the row is
|
|
342
|
+
* cut). */
|
|
343
|
+
export function liveRow(facts, keys, W) {
|
|
344
|
+
const p = palette();
|
|
345
|
+
const lead = facts;
|
|
346
|
+
for (const k of [...keys, ""]) {
|
|
347
|
+
if (k === "")
|
|
348
|
+
return visibleWidth(lead) <= W ? lead : cutLine(lead, W);
|
|
349
|
+
const gap = W - visibleWidth(lead) - visibleWidth(k);
|
|
350
|
+
if (gap >= 2)
|
|
351
|
+
return `${lead}${" ".repeat(gap)}${p.dim}${k}${p.reset}`;
|
|
352
|
+
}
|
|
353
|
+
return cutLine(lead, W);
|
|
354
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.0",
|
|
4
4
|
"description": "kiso tui \u2014 the pure terminal layer (cell renderer, dock, raw editor, diff, palette). Zero runtime dependencies: input is data, output is bytes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,6 +35,6 @@
|
|
|
35
35
|
},
|
|
36
36
|
"homepage": "https://github.com/vincemakes/kiso/tree/main/packages/tui#readme",
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@vincemakes/kiso-tui-cells": "0.
|
|
38
|
+
"@vincemakes/kiso-tui-cells": "0.47.0"
|
|
39
39
|
}
|
|
40
40
|
}
|