@vincemakes/kiso-tui 0.1.35 → 0.1.36
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/components.js +155 -16
- package/dist/compositor.d.ts +7 -4
- package/dist/compositor.js +7 -4
- package/package.json +1 -1
package/dist/components.js
CHANGED
|
@@ -188,8 +188,14 @@ class ThinkingFold {
|
|
|
188
188
|
return [`${palette().dim}…${widthCut(trimmed, slice)}${suffix}${palette().reset}`];
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
|
-
/** The tool execution line + the
|
|
192
|
-
*
|
|
191
|
+
/** The tool execution line + the bounded block — every state is its
|
|
192
|
+
* own render; the lines fold (the summary gives way first). W7 (the
|
|
193
|
+
* flow contract): the block's BODY (the rows below the header) is
|
|
194
|
+
* capped in SCREEN rows AFTER the fold, at the current width — the
|
|
195
|
+
* renderer-cut row (`└ +N … · ctrl+r`) sits INSIDE the cap (a
|
|
196
|
+
* truncated block is cap−1 output rows + the cut row); the TOOL-cut
|
|
197
|
+
* row (`└ capped by …` — the tool's OWN truncation note, W10) is a
|
|
198
|
+
* DIFFERENT fact, never counted in the output cap. */
|
|
193
199
|
class ToolExecution {
|
|
194
200
|
cell;
|
|
195
201
|
constructor(cell) {
|
|
@@ -205,29 +211,162 @@ class ToolExecution {
|
|
|
205
211
|
const line = c.isError
|
|
206
212
|
? `${p.red}✗ ${name} (${escapeTerminal(c.resultText.split("\n")[0].slice(0, 60))}, ${elapsed}s)${p.reset}`
|
|
207
213
|
: `${p.bold}✓ ${name}${p.reset} (${summary}${c.added + c.removed > 0 ? `, +${c.added} -${c.removed}` : ""}, ${elapsed}s)`;
|
|
208
|
-
|
|
214
|
+
const out = foldLine(line, W);
|
|
215
|
+
out.push(...toolBlockBody(c, W));
|
|
216
|
+
return out;
|
|
209
217
|
}
|
|
210
218
|
if (c.state === "approval") {
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const body = d.kind === "-"
|
|
215
|
-
? `${p.red}- ${escapeTerminal(d.text)}${p.reset}`
|
|
216
|
-
: d.kind === "+"
|
|
217
|
-
? `${p.green}+ ${escapeTerminal(d.text)}${p.reset}`
|
|
218
|
-
: `${p.dim} ${escapeTerminal(d.text)}${p.reset}`;
|
|
219
|
-
lines.push(...foldLine(`${p.bold}▎${p.reset}${body}`, W));
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return lines;
|
|
219
|
+
const out = foldLine(`→ ${name} ${summary} ${p.bold}⏸${p.reset}`, W);
|
|
220
|
+
out.push(...toolBlockBody(c, W));
|
|
221
|
+
return out;
|
|
223
222
|
}
|
|
224
223
|
if (c.state === "running") {
|
|
225
224
|
const elapsed = c.startedAt !== null ? Math.max(1, Math.round((ctx.now - c.startedAt) / 1000)) : 1;
|
|
226
|
-
|
|
225
|
+
const out = foldLine(`→ ${name} ${summary} ${p.bold}${SPINNER[ctx.spinnerI % SPINNER.length]}${p.reset} ${elapsed}s`, W);
|
|
226
|
+
out.push(...toolBlockBody(c, W));
|
|
227
|
+
return out;
|
|
227
228
|
}
|
|
228
229
|
return foldLine(`→ ${name} ${summary}`, W);
|
|
229
230
|
}
|
|
230
231
|
}
|
|
232
|
+
// ---- the bounded-block flow contract (W7, W8, W10) ----
|
|
233
|
+
/** The caps — screen rows counted AFTER the fold, at the current width
|
|
234
|
+
* (the W7 table). The renderer-cut row is inside the cap. */
|
|
235
|
+
const CAP_SHELL_SETTLED = 5; // the shell output tail, settled
|
|
236
|
+
const CAP_LIVE_WINDOW = 3; // the running tool's FIXED window (W8)
|
|
237
|
+
const CAP_DIFF = 12; // the approval diff: head + the named middle + tail
|
|
238
|
+
const CAP_ERROR = 3; // the error text head
|
|
239
|
+
/** The block body rows' prefixes (W2's gutter table): │ a bounded
|
|
240
|
+
* block's body, └ the block's last row — what was cut, where the rest
|
|
241
|
+
* is. Structural (constraint 1) — they survive a pipe. */
|
|
242
|
+
const BODY_ROW = " │ ";
|
|
243
|
+
const CUT_ROW = " └ ";
|
|
244
|
+
const blockMemo = new WeakMap();
|
|
245
|
+
/** The block's body rows below the header (memoized, W9). */
|
|
246
|
+
function toolBlockBody(c, W) {
|
|
247
|
+
const memo = blockMemo.get(c);
|
|
248
|
+
const state = `${c.state}:${c.isError}:${c.name}`;
|
|
249
|
+
const content = c.state === "approval" ? (c.diff ?? null) : c.resultText;
|
|
250
|
+
if (memo !== undefined && memo.width === W && memo.state === state && memo.content === content)
|
|
251
|
+
return memo.rows;
|
|
252
|
+
const p = palette();
|
|
253
|
+
const rows = c.state === "done"
|
|
254
|
+
? c.isError
|
|
255
|
+
? errorBody(c.resultText, W)
|
|
256
|
+
: c.name.startsWith("shell")
|
|
257
|
+
? shellTail(c.resultText, W)
|
|
258
|
+
: []
|
|
259
|
+
: c.state === "running"
|
|
260
|
+
? liveWindow(c.resultText, W)
|
|
261
|
+
: c.state === "approval"
|
|
262
|
+
? diffBody(c.diff, W)
|
|
263
|
+
: [];
|
|
264
|
+
const note = toolCutNote(c.name, c.resultText);
|
|
265
|
+
if (note !== null)
|
|
266
|
+
rows.push(...foldLine(`${p.dim}${CUT_ROW}${note}${p.reset}`, W));
|
|
267
|
+
blockMemo.set(c, { width: W, state, content, rows });
|
|
268
|
+
return rows;
|
|
269
|
+
}
|
|
270
|
+
/** Fold result text into dim body rows (the BODY_ROW prefix): escape,
|
|
271
|
+
* split, fold each line at W−prefix; trailing empty rows (the result's
|
|
272
|
+
* final newline) drop. */
|
|
273
|
+
function blockRows(text, W) {
|
|
274
|
+
const p = palette();
|
|
275
|
+
const textW = Math.max(1, W - visibleWidth(BODY_ROW));
|
|
276
|
+
const rows = [];
|
|
277
|
+
for (const raw of escapeTerminal(text).split("\n")) {
|
|
278
|
+
for (const row of foldLine(raw, textW))
|
|
279
|
+
rows.push(`${p.dim}${BODY_ROW}${row}${p.reset}`);
|
|
280
|
+
}
|
|
281
|
+
while (rows.length > 0 && visibleWidth(rows[rows.length - 1]) === visibleWidth(BODY_ROW))
|
|
282
|
+
rows.pop();
|
|
283
|
+
return rows;
|
|
284
|
+
}
|
|
285
|
+
/** The shell output tail, settled: the LAST rows, capped at 5 — the
|
|
286
|
+
* renderer cut at the block's bottom ("earlier rows" — the conclusion
|
|
287
|
+
* is at the end, pi's truncateToVisualLines direction). */
|
|
288
|
+
function shellTail(text, W) {
|
|
289
|
+
const p = palette();
|
|
290
|
+
const rows = blockRows(text, W);
|
|
291
|
+
if (rows.length <= CAP_SHELL_SETTLED)
|
|
292
|
+
return rows;
|
|
293
|
+
const kept = CAP_SHELL_SETTLED - 1;
|
|
294
|
+
const cut = foldLine(`${p.dim}${CUT_ROW}+${rows.length - kept} earlier rows · ctrl+r${p.reset}`, W);
|
|
295
|
+
return [...rows.slice(rows.length - kept), ...cut];
|
|
296
|
+
}
|
|
297
|
+
/** The error text head: the FIRST rows, capped at 3 — the answer is at
|
|
298
|
+
* the start (opencode's collapseToolOutput direction). The header row
|
|
299
|
+
* already summarizes the first line, so the body starts at line 2. */
|
|
300
|
+
function errorBody(text, W) {
|
|
301
|
+
const p = palette();
|
|
302
|
+
const rows = blockRows(text.split("\n").slice(1).join("\n"), W);
|
|
303
|
+
if (rows.length <= CAP_ERROR)
|
|
304
|
+
return rows;
|
|
305
|
+
const cut = foldLine(`${p.dim}${CUT_ROW}+${rows.length - (CAP_ERROR - 1)} more · ctrl+r${p.reset}`, W);
|
|
306
|
+
return [...rows.slice(0, CAP_ERROR - 1), ...cut];
|
|
307
|
+
}
|
|
308
|
+
/** The running tool's FIXED-height window (W8): exactly 3 rows from
|
|
309
|
+
* the FIRST frame — blank-padded before output arrives, the renderer
|
|
310
|
+
* cut inside the window. The height changes exactly once, at settle —
|
|
311
|
+
* a cell that grows mid-list would shift every row after it on every
|
|
312
|
+
* delta (the parallel-tools jitter). */
|
|
313
|
+
function liveWindow(text, W) {
|
|
314
|
+
const p = palette();
|
|
315
|
+
if (text === "") {
|
|
316
|
+
return [`${p.dim}${BODY_ROW}${p.reset}`, `${p.dim}${BODY_ROW}${p.reset}`, `${p.dim}${CUT_ROW}waiting for output${p.reset}`];
|
|
317
|
+
}
|
|
318
|
+
const rows = blockRows(text, W);
|
|
319
|
+
if (rows.length <= CAP_LIVE_WINDOW) {
|
|
320
|
+
while (rows.length < CAP_LIVE_WINDOW)
|
|
321
|
+
rows.push(`${p.dim}${BODY_ROW}${p.reset}`);
|
|
322
|
+
return rows;
|
|
323
|
+
}
|
|
324
|
+
const cut = foldLine(`${p.dim}${CUT_ROW}+${rows.length - (CAP_LIVE_WINDOW - 1)} earlier rows · ctrl+r${p.reset}`, W);
|
|
325
|
+
return [...rows.slice(rows.length - (CAP_LIVE_WINDOW - 1)), ...cut];
|
|
326
|
+
}
|
|
327
|
+
/** The approval mini-diff (W7): capped at 12 folded rows — the head +
|
|
328
|
+
* the named middle (the renderer cut — what was cut, how to expand) +
|
|
329
|
+
* the tail. The rows are folded at the current width BEFORE the cap —
|
|
330
|
+
* the R1 measured bug: truncateDiff capped at 40 ENTRIES while the
|
|
331
|
+
* fold turned them into 73 SCREEN rows at W≤80 (a 44-row terminal's
|
|
332
|
+
* content cap is H−4 = 40 — the approval force-committed a third of
|
|
333
|
+
* the screen into scrollback inside one frame). */
|
|
334
|
+
function diffBody(diff, W) {
|
|
335
|
+
const p = palette();
|
|
336
|
+
if (diff === null)
|
|
337
|
+
return [];
|
|
338
|
+
const rows = [];
|
|
339
|
+
for (const d of diff) {
|
|
340
|
+
const body = d.kind === "-"
|
|
341
|
+
? `${p.red}- ${escapeTerminal(d.text)}${p.reset}`
|
|
342
|
+
: d.kind === "+"
|
|
343
|
+
? `${p.green}+ ${escapeTerminal(d.text)}${p.reset}`
|
|
344
|
+
: `${p.dim} ${escapeTerminal(d.text)}${p.reset}`;
|
|
345
|
+
rows.push(...foldLine(`${p.bold}▎${p.reset}${body}`, W));
|
|
346
|
+
}
|
|
347
|
+
if (rows.length <= CAP_DIFF)
|
|
348
|
+
return rows;
|
|
349
|
+
const head = Math.floor((CAP_DIFF - 1) / 2);
|
|
350
|
+
const tail = CAP_DIFF - 1 - head;
|
|
351
|
+
const cut = foldLine(`${p.dim}${CUT_ROW}+${rows.length - head - tail} rows · ctrl+r to expand · /last for the full diff${p.reset}`, W);
|
|
352
|
+
return [...rows.slice(0, head), ...cut, ...rows.slice(rows.length - tail)];
|
|
353
|
+
}
|
|
354
|
+
/** The TOOL's OWN truncation note (W10) — a different fact from the
|
|
355
|
+
* renderer's cut: the tools truncate and append a continuation note
|
|
356
|
+
* (packages/tools-node/src/index.ts — read_file's "call again with
|
|
357
|
+
* offset=N", the output cap, list_dir's entry cap). The note reaches
|
|
358
|
+
* the MODEL and never the human — this row surfaces it. Detected in
|
|
359
|
+
* the result's TAIL (the note is appended at the end); returns null
|
|
360
|
+
* when the tool did not truncate. */
|
|
361
|
+
function toolCutNote(name, resultText) {
|
|
362
|
+
const tail = resultText.slice(-300);
|
|
363
|
+
const m = /offset=(\d+)/.exec(tail);
|
|
364
|
+
if (m !== null)
|
|
365
|
+
return `capped by ${escapeTerminal(name)} · offset=${m[1]} for the rest`;
|
|
366
|
+
if (/…\[truncated\]/.test(tail) || /… \+?\d+ more (?:lines|entries)/.test(tail))
|
|
367
|
+
return `capped by ${escapeTerminal(name)} · /last for the rest`;
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
231
370
|
/** The assistant body text — wrapped at W, the inline-code tint per
|
|
232
371
|
* row (the #16e rule: a span never matches across rows). */
|
|
233
372
|
class AssistantMessage {
|
package/dist/compositor.d.ts
CHANGED
|
@@ -20,10 +20,13 @@
|
|
|
20
20
|
* sharp edge (asserted by the VT-emulator gate);
|
|
21
21
|
* - two crash invariants: ① every emitted line's visible width ≤ W
|
|
22
22
|
* (components fold; a violation THROWS with diagnostics — pi
|
|
23
|
-
* tui-main-screen.ts:447-473, no silent truncate); ②
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* tui-main-screen.ts:447-473, no silent truncate); ② every steady-
|
|
24
|
+
* frame CUP lands in the CONTENT area (rows ≤ H−4−menu — the
|
|
25
|
+
* committed band, the stale/gap ELs, and the LIVE lines at their
|
|
26
|
+
* model rows; fix C's sanctioned reinterpretation, ADR-0046) — the
|
|
27
|
+
* CHROME rows (H−3..H) are RELATIVE-only (vertical A/B, horizontal
|
|
28
|
+
* G/D); CUP over the whole screen exists only in the full-redraw
|
|
29
|
+
* path (the first frame, the resize repaint);
|
|
27
30
|
* - the cursor DERIVES from the frame: the focus component embeds the
|
|
28
31
|
* APC marker in its rendered line; the compositor locates, strips,
|
|
29
32
|
* and relatively positions from the frame — no side-channel cursor
|
package/dist/compositor.js
CHANGED
|
@@ -20,10 +20,13 @@
|
|
|
20
20
|
* sharp edge (asserted by the VT-emulator gate);
|
|
21
21
|
* - two crash invariants: ① every emitted line's visible width ≤ W
|
|
22
22
|
* (components fold; a violation THROWS with diagnostics — pi
|
|
23
|
-
* tui-main-screen.ts:447-473, no silent truncate); ②
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* tui-main-screen.ts:447-473, no silent truncate); ② every steady-
|
|
24
|
+
* frame CUP lands in the CONTENT area (rows ≤ H−4−menu — the
|
|
25
|
+
* committed band, the stale/gap ELs, and the LIVE lines at their
|
|
26
|
+
* model rows; fix C's sanctioned reinterpretation, ADR-0046) — the
|
|
27
|
+
* CHROME rows (H−3..H) are RELATIVE-only (vertical A/B, horizontal
|
|
28
|
+
* G/D); CUP over the whole screen exists only in the full-redraw
|
|
29
|
+
* path (the first frame, the resize repaint);
|
|
27
30
|
* - the cursor DERIVES from the frame: the focus component embeds the
|
|
28
31
|
* APC marker in its rendered line; the compositor locates, strips,
|
|
29
32
|
* and relatively positions from the frame — no side-channel cursor
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.36",
|
|
4
4
|
"description": "kiso tui — 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",
|