@vincemakes/kiso-code 0.16.3 → 0.16.4

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/chat.js CHANGED
@@ -378,7 +378,7 @@ function taskVerdictWords(kind) {
378
378
  }
379
379
  /** W21 — the panel view for a permission_requested: the rule line (the
380
380
  * why-asked speaker + the §3.5 fix hint), the toolTarget title, the
381
- * " run paused" status, and the ALWAYS-verbose args. */
381
+ * " run paused" status, and the ALWAYS-verbose args. */
382
382
  function approvalView(name, ev, amended = false) {
383
383
  const speaker = ev.speaker ?? "kiso";
384
384
  const input = ev.input ?? {};
@@ -396,7 +396,7 @@ function approvalView(name, ev, amended = false) {
396
396
  speaker,
397
397
  ...(hint !== undefined ? { hint } : {}),
398
398
  ...(risk !== null ? { riskHint: risk } : {}),
399
- statusText: " run paused",
399
+ statusText: " run paused",
400
400
  args: approvalArgs(name, input),
401
401
  fallbackQuestion: `approve ${escapeTerminal(name)}? (y/n) `,
402
402
  // TUI2-R3v2 ③: the v4 frame's "(amended)" marker. It says WHY this
@@ -549,7 +549,9 @@ submitTurn) {
549
549
  // user's words. Provenance is honest on screen, not only in
550
550
  // the log.
551
551
  if (ev.source === "system") {
552
- body.notice("\u25c6 verification pass");
552
+ // R2 (law 1.3): the ◆ said nothing the words did not. What
553
+ // makes this row honest is that it NAMES itself machinery.
554
+ body.notice("verification pass");
553
555
  body.notice(` ${typeof ev.content === "string" ? ev.content : ""}`);
554
556
  break;
555
557
  }
@@ -761,7 +763,9 @@ submitTurn) {
761
763
  // recap (the truncation-guard philosophy: the cut is visible
762
764
  // in the scrollback, the model's own text intact).
763
765
  if (ev.outcome.kind === "max_tokens") {
764
- body.notice('┌ answer truncated at max_tokens say "continue" to finish');
766
+ // R2 (law 1.1): the notice was wearing a box corner. A notice
767
+ // is a sentence addressed to a human; it needs no edge.
768
+ body.notice('answer truncated at max_tokens — say "continue" to finish');
765
769
  }
766
770
  bodyLog(renderRecap({
767
771
  seconds: Math.round((Date.now() - turnStart) / 1000),
package/dist/dispatch.js CHANGED
@@ -203,7 +203,11 @@ export function dispatch(line, ctx) {
203
203
  // user who sees it is exactly the user who needs
204
204
  // the path spelled out
205
205
  ...(names.length === 0 ? { emptyNote: "no profiles — define models in ~/.kiso/config.json" } : {}),
206
- }, ctx.isRunning() ? "▸ run paused" : `▸ ${getMode()}`), (v) => resolve(v.action === "picked" ? v.result : null));
206
+ },
207
+ // DC-12 (design §4): a panel WAITING ON A HUMAN says ⏸. The
208
+ // else-branch keeps ▸ — it names the current tier, which is
209
+ // what ▸ means everywhere else.
210
+ ctx.isRunning() ? "⏸ run paused" : `▸ ${getMode()}`), (v) => resolve(v.action === "picked" ? v.result : null));
207
211
  });
208
212
  ctx.paintIdle();
209
213
  if (picked === null) {
@@ -365,7 +369,7 @@ export function dispatch(line, ctx) {
365
369
  const elapsed = compactStart > 0 ? ((Date.now() - compactStart) / 1000).toFixed(1) : "?";
366
370
  // a non-null result implies onStart ran — the "?" is
367
371
  // reachable only at the type level
368
- body.notice(`[/compact] compacted · ${compactInfo?.rounds ?? "?"} rounds → 1 summary · saved ~${kUnit(result.savedTokens)} · ctx ${ctxBefore ?? "?"}% → ${ctxAfter}% · ${elapsed}s`);
372
+ body.notice(`[/compact] compacted · ${compactInfo?.rounds ?? "?"} rounds → 1 summary · saved ~${kUnit(result.savedTokens)} · ctx ${ctxBefore ?? "?"}% → ${ctxAfter}% · ${elapsed}s`);
369
373
  }
370
374
  }
371
375
  catch (err) {
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ import { newSessionId } from "./session-id.js";
28
28
  import { createInterface } from "node:readline";
29
29
  import { fileURLToPath } from "node:url";
30
30
  import { join } from "node:path";
31
- import { BADGE_GLYPH, Body, Editor, bannerLines, currentGround, resolveGround, setGround, escapeTerminal, extensionsBannerText, idColumn, idleStatus, interactivePrompt, palette, renderSessionLine, sessionListFooter, sessionListRow } from "@vincemakes/kiso-tui";
31
+ import { Body, Editor, bannerLines, currentGround, resolveGround, setGround, escapeTerminal, extensionsBannerText, idColumn, idleStatus, interactivePrompt, palette, renderSessionLine, sessionListFooter, sessionListRow } from "@vincemakes/kiso-tui";
32
32
  import { createAgent, disposeExtensions, loadExtensions, loadProjectExtensions, SessionStore, } from "@vincemakes/kiso-runtime";
33
33
  import { createFauxProvider } from "@vincemakes/kiso-evals";
34
34
  import { createCodingTools } from "@vincemakes/kiso-tools-node";
@@ -245,6 +245,18 @@ function makeLineInput() {
245
245
  // reader, nothing blocking: an answer that never arrives leaves the
246
246
  // ground `unknown`, and `unknown` is a supported palette, not a
247
247
  // failure — see packages/tui-cells/src/ground.ts.
248
+ // DC-14 (P1): the ladder is walked ONCE AT STARTUP, before the query
249
+ // and independently of it. It used to be walked only inside the
250
+ // reply callback below — so on a terminal that does not answer OSC
251
+ // 11 (tmux does not forward the reply by default), rung 1 never
252
+ // ran: `KISO_THEME=dark` is SETTLED as "an explicit answer always
253
+ // wins" and won nothing. Rung 3 was worse than dead — `COLORFGBG`
254
+ // exists FOR the terminals without OSC 11, and could only ever be
255
+ // consulted when an OSC reply had arrived and been malformed. With
256
+ // §3.2 recording that rung 2 is unmeasured, that was the mainline.
257
+ setGround(resolveGround({ theme: process.env.KISO_THEME, colorfgbg: process.env.COLORFGBG }));
258
+ // the reply, when there is one, re-walks the ladder WITH it — and
259
+ // `theme` goes in first again, so an explicit answer still wins.
248
260
  editor.onOsc((reply) => {
249
261
  const next = resolveGround({ theme: process.env.KISO_THEME, osc: reply, colorfgbg: process.env.COLORFGBG });
250
262
  if (next === currentGround())
@@ -261,7 +273,12 @@ function makeLineInput() {
261
273
  // W6: the box's prompt goes light — "› " (the box already says
262
274
  // "input lives here"; the line-mode path keeps the brick ▌, so
263
275
  // pipe bytes do not change)
264
- dock.bindInput(() => editor.dockState(), "› ");
276
+ // R2 (owner, 2026-08-27): no prompt glyph. The cursor sits at column
277
+ // one, between the two rules — the rules already say "input lives
278
+ // here", which is the argument W6 made for the box and the only part
279
+ // of it that survives. A prompt character is a third thing saying
280
+ // the same thing, and it cost the row a column.
281
+ dock.bindInput(() => editor.dockState(), "");
265
282
  // TUI2-R3v2 ②: the click hit-test's wiring — the compositor places
266
283
  // the panel's option rows, so the compositor is what the editor asks
267
284
  // where they are. Neither side computes the other's geometry.
@@ -313,37 +330,6 @@ function extensionsBanner(resume = []) {
313
330
  cwd: cwd.startsWith(home) ? `~${cwd.slice(home.length)}` : cwd,
314
331
  });
315
332
  }
316
- /** W5: the opening-screen resume list — up to 3 recent sessions, newest
317
- * first, the CURRENT session excluded (it is not something to pick back
318
- * up). Every field already exists behind the store's SessionMeta (the
319
- * `kiso sessions` line shows the same data) — only the projection and
320
- * the sort live here.
321
- *
322
- * TT-1B (W5 unification): each row carries the picker's badge glyph,
323
- * derived through the SAME projection the picker uses (session-cards —
324
- * one source of truth about durability; a second derivation would be
325
- * none). Only the 3 chosen sessions are opened — the read-only listing
326
- * rule holds (projectSessionCard's own guarantee), and the cost is
327
- * bounded by the list, never the home. */
328
- async function recentSessions(id, agent) {
329
- const picked = agent
330
- .sessions()
331
- .filter((m) => m.id !== id)
332
- .sort((a, b) => b.updatedAt - a.updatedAt)
333
- .slice(0, 3);
334
- const store = sessionStoreRef;
335
- const out = [];
336
- for (const m of picked) {
337
- let badge;
338
- if (store !== null) {
339
- const session = await agent.session({ id: m.id, ...(acceptDrift() ? { acceptDrift: true } : {}) });
340
- const card = projectSessionCard({ id: m.id, updatedAt: m.updatedAt, records: store.load(m.id), asks: session.pendingApprovals().length });
341
- badge = BADGE_GLYPH[card.badge];
342
- }
343
- out.push({ title: m.title, events: m.events, runs: m.runs, updatedAt: m.updatedAt, ...(badge === undefined ? {} : { badge }) });
344
- }
345
- return out;
346
- }
347
333
  /**
348
334
  * A area: the coding-agent system prompt — ONE constant, byte-stable for the
349
335
  * session's lifetime (D area). Kept under ~80 lines; no template engine.
@@ -643,13 +629,21 @@ async function chatLoop(agent, firstId, input, autoCompact) {
643
629
  // prompt inside a conversation with thousands of events — the
644
630
  // durable log was right there and none of it was shown. Empty for
645
631
  // a fresh session, so `kiso chat` is byte-identical.
646
- for (const line of resumeTail(session.log.all))
632
+ for (const line of resumeTail(session.log.all, process.stdout.columns ?? 80))
647
633
  bodyLog(line);
648
- extensionsBanner(await recentSessions(id, agent));
634
+ // R2 (owner, 2026-08-27): the resume list is NOT on the opening
635
+ // screen. `/resume` is where you go looking for a session; the
636
+ // opening's job is to say what THIS one is.
637
+ //
638
+ // It takes DC-8 with it: building those rows opened the three most
639
+ // recent sessions just to draw a badge, and `agent.session()`
640
+ // throws on profile drift — so one drifted session anywhere in the
641
+ // history stopped kiso from starting at all.
642
+ extensionsBanner();
649
643
  }
650
644
  else {
651
645
  bodyLog(`session ${id} (switched — previous: ${prev}, /resume ${prev} returns)\n`);
652
- for (const line of resumeTail(session.log.all))
646
+ for (const line of resumeTail(session.log.all, process.stdout.columns ?? 80))
653
647
  bodyLog(line);
654
648
  if (currentFaux)
655
649
  session.setAdapter(createFauxProvider(readFauxScript().slice(fauxSkip(id))));
@@ -917,7 +911,7 @@ async function main() {
917
911
  session.setAdapter(createFauxProvider(readFauxScript().slice(fauxSkip(id))));
918
912
  // REL-0152-D5 — the same tail on the explicit-id form. NOT on
919
913
  // the -p path above: that one's stdout is a machine's input.
920
- for (const line of resumeTail(session.log.all))
914
+ for (const line of resumeTail(session.log.all, process.stdout.columns ?? 80))
921
915
  bodyLog(line);
922
916
  await resume(session, prompt, faux, input);
923
917
  break;
@@ -23,4 +23,4 @@
23
23
  */
24
24
  export declare function resumeTail(events: readonly {
25
25
  readonly type: string;
26
- }[]): string[];
26
+ }[], W?: number): string[];
@@ -28,7 +28,7 @@ const clip = (s, n) => (s.length <= n ? s : `${s.slice(0, n - 1)}…`);
28
28
  * prompt. Empty when there is nothing to show, and the caller prints
29
29
  * nothing rather than an empty frame.
30
30
  */
31
- export function resumeTail(events) {
31
+ export function resumeTail(events, W = 80) {
32
32
  const turns = [];
33
33
  let ask = null;
34
34
  let reply = "";
@@ -51,9 +51,22 @@ export function resumeTail(events) {
51
51
  return [];
52
52
  const lines = [];
53
53
  const skipped = turns.length - shown.length;
54
- lines.push(skipped > 0 ? `─ resuming · ${turns.length} turns, showing the last ${shown.length} ─` : `─ resuming · ${turns.length} turn${turns.length === 1 ? "" : "s"} ─`);
54
+ // R2 (law 1.1): the head is the ONE rule, labelled the way every
55
+ // other band on screen labels itself. It used to be `─ … ─`, a second
56
+ // divider weight that existed nowhere else in the product.
57
+ const label = skipped > 0 ? `resuming · ${turns.length} turns, showing the last ${shown.length}` : `resuming · ${turns.length} turn${turns.length === 1 ? "" : "s"}`;
58
+ const head = `\u254c\u254c\u254c ${label} `;
59
+ // the cut floor is the WIDTH, never a constant: `Math.max(4, W)` let a
60
+ // four-cell rule out at W=1..3, i.e. a chrome row wider than the
61
+ // terminal. Every glyph here is one cell (the label is ascii plus
62
+ // `·`), so a code-unit slice is a cell slice.
63
+ lines.push(`${head}${"\u254c".repeat(Math.max(1, W - head.length))}`.slice(0, Math.max(1, W)));
55
64
  for (const t of shown) {
56
- lines.push(` › ${clip(oneLine(t.ask), 100)}`);
65
+ // the human's turn keeps the quote gutter — the one glyph in the
66
+ // product that means "these are somebody's exact words". The `\u203a`
67
+ // it used to carry is the composer's prompt, and the composer no
68
+ // longer has one.
69
+ lines.push(` \u2502 ${clip(oneLine(t.ask), 100)}`);
57
70
  const body = oneLine(t.reply);
58
71
  // A turn with no reply is a turn that was INTERRUPTED — the case
59
72
  // resume exists for. Saying so beats printing a blank line.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-code",
3
- "version": "0.16.3",
3
+ "version": "0.16.4",
4
4
  "description": "kiso CLI — the durable coding agent that survives kill -9: kiso chat / kiso resume / kiso sessions.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -18,19 +18,19 @@
18
18
  "test": "vitest run"
19
19
  },
20
20
  "dependencies": {
21
- "@vincemakes/kiso-ask-ext": "0.16.3",
22
- "@vincemakes/kiso-core": "0.16.3",
23
- "@vincemakes/kiso-evals": "0.16.3",
24
- "@vincemakes/kiso-mcp-ext": "0.16.3",
25
- "@vincemakes/kiso-provider-anthropic": "0.16.3",
26
- "@vincemakes/kiso-provider-openai": "0.16.3",
27
- "@vincemakes/kiso-runtime": "0.16.3",
28
- "@vincemakes/kiso-skills-ext": "0.16.3",
29
- "@vincemakes/kiso-subagent-ext": "0.16.3",
30
- "@vincemakes/kiso-task-ext": "0.16.3",
31
- "@vincemakes/kiso-tools-node": "0.16.3",
32
- "@vincemakes/kiso-tui": "0.16.3",
33
- "@vincemakes/kiso-tui-cells": "0.16.3"
21
+ "@vincemakes/kiso-ask-ext": "0.16.4",
22
+ "@vincemakes/kiso-core": "0.16.4",
23
+ "@vincemakes/kiso-evals": "0.16.4",
24
+ "@vincemakes/kiso-mcp-ext": "0.16.4",
25
+ "@vincemakes/kiso-provider-anthropic": "0.16.4",
26
+ "@vincemakes/kiso-provider-openai": "0.16.4",
27
+ "@vincemakes/kiso-runtime": "0.16.4",
28
+ "@vincemakes/kiso-skills-ext": "0.16.4",
29
+ "@vincemakes/kiso-subagent-ext": "0.16.4",
30
+ "@vincemakes/kiso-task-ext": "0.16.4",
31
+ "@vincemakes/kiso-tools-node": "0.16.4",
32
+ "@vincemakes/kiso-tui": "0.16.4",
33
+ "@vincemakes/kiso-tui-cells": "0.16.4"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^26.1.2",