@vincemakes/kiso-code 0.16.2 → 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, 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";
@@ -43,7 +43,7 @@ import { loadProjectConfig, loadUserConfig, mergeConfigs, resolveAutoCompact, re
43
43
  import { resume } from "./resume.js";
44
44
  import { resumeTail } from "./resume-tail.js";
45
45
  import { armByteTrace } from "./byte-trace.js";
46
- import { tmpdir } from "node:os";
46
+ import { tmpdir, homedir } from "node:os";
47
47
  import { clipboardImage } from "./clipboard.js";
48
48
  import { collectSessionCards, projectSessionCard } from "./session-cards.js";
49
49
  // The moved exports stay reachable from this entry — the test imports
@@ -233,10 +233,52 @@ function makeLineInput() {
233
233
  if (process.stdin.isTTY) {
234
234
  const editor = new Editor(() => (dock.active ? dock.redraw() : editor.selfRender()));
235
235
  editor.enter();
236
+ // DC-3: ask the terminal what its background is, and paint the
237
+ // first frame without waiting for the answer.
238
+ //
239
+ // Every grey in the palette is a claim about the background, and
240
+ // kiso had never established one — inline code was chosen on a dark
241
+ // terminal and measured 1.54:1 on a white one. The question is one
242
+ // write; the answer comes back through the editor, which is already
243
+ // the process's single reader of stdin (DC-7 taught it to swallow
244
+ // OSC rather than type it into the draft). No timer, no second
245
+ // reader, nothing blocking: an answer that never arrives leaves the
246
+ // ground `unknown`, and `unknown` is a supported palette, not a
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.
260
+ editor.onOsc((reply) => {
261
+ const next = resolveGround({ theme: process.env.KISO_THEME, osc: reply, colorfgbg: process.env.COLORFGBG });
262
+ if (next === currentGround())
263
+ return;
264
+ setGround(next);
265
+ body.onGroundChange();
266
+ });
267
+ // The query is COLOUR machinery, so it obeys the colour gate: a piped
268
+ // stdout and NO_COLOR both carry zero ANSI, and an OSC written into a
269
+ // pipe would be the first byte to break that. `palette().bold` is the
270
+ // same test the dock activates on — one gate, not a second opinion.
271
+ if (palette().bold !== "" && process.stdout.isTTY)
272
+ process.stdout.write("\x1b]11;?\x07");
236
273
  // W6: the box's prompt goes light — "› " (the box already says
237
274
  // "input lives here"; the line-mode path keeps the brick ▌, so
238
275
  // pipe bytes do not change)
239
- 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(), "");
240
282
  // TUI2-R3v2 ②: the click hit-test's wiring — the compositor places
241
283
  // the panel's option rows, so the compositor is what the editor asks
242
284
  // where they are. Neither side computes the other's geometry.
@@ -274,38 +316,19 @@ function extensionsBanner(resume = []) {
274
316
  bodyLog(`${text}\n`);
275
317
  return;
276
318
  }
277
- body.banner(VERSION, text.replace(/^ · /, ""), resume);
278
- }
279
- /** W5: the opening-screen resume list up to 3 recent sessions, newest
280
- * first, the CURRENT session excluded (it is not something to pick back
281
- * up). Every field already exists behind the store's SessionMeta (the
282
- * `kiso sessions` line shows the same data) — only the projection and
283
- * the sort live here.
284
- *
285
- * TT-1B (W5 unification): each row carries the picker's badge glyph,
286
- * derived through the SAME projection the picker uses (session-cards —
287
- * one source of truth about durability; a second derivation would be
288
- * none). Only the 3 chosen sessions are opened — the read-only listing
289
- * rule holds (projectSessionCard's own guarantee), and the cost is
290
- * bounded by the list, never the home. */
291
- async function recentSessions(id, agent) {
292
- const picked = agent
293
- .sessions()
294
- .filter((m) => m.id !== id)
295
- .sort((a, b) => b.updatedAt - a.updatedAt)
296
- .slice(0, 3);
297
- const store = sessionStoreRef;
298
- const out = [];
299
- for (const m of picked) {
300
- let badge;
301
- if (store !== null) {
302
- const session = await agent.session({ id: m.id, ...(acceptDrift() ? { acceptDrift: true } : {}) });
303
- const card = projectSessionCard({ id: m.id, updatedAt: m.updatedAt, records: store.load(m.id), asks: session.pendingApprovals().length });
304
- badge = BADGE_GLYPH[card.badge];
305
- }
306
- out.push({ title: m.title, events: m.events, runs: m.runs, updatedAt: m.updatedAt, ...(badge === undefined ? {} : { badge }) });
307
- }
308
- return out;
319
+ // R2: the opening answers the three questions a first screen is asked.
320
+ // The model and the tier come from the same state the status line reads
321
+ // (one source, so the two can never disagree); the workspace is the
322
+ // home-relative cwd, because `~/Desktop/devv/kiso` is what a human
323
+ // calls the place and `/Users/vinve/Desktop/devv/kiso` is what a
324
+ // filesystem calls it.
325
+ const home = homedir();
326
+ const cwd = process.cwd();
327
+ body.banner(VERSION, text.replace(/^ · /, ""), resume, {
328
+ model: agentModel,
329
+ mode: getMode() === "plan" ? "plan (read-only)" : getMode(),
330
+ cwd: cwd.startsWith(home) ? `~${cwd.slice(home.length)}` : cwd,
331
+ });
309
332
  }
310
333
  /**
311
334
  * A area: the coding-agent system prompt — ONE constant, byte-stable for the
@@ -606,13 +629,21 @@ async function chatLoop(agent, firstId, input, autoCompact) {
606
629
  // prompt inside a conversation with thousands of events — the
607
630
  // durable log was right there and none of it was shown. Empty for
608
631
  // a fresh session, so `kiso chat` is byte-identical.
609
- for (const line of resumeTail(session.log.all))
632
+ for (const line of resumeTail(session.log.all, process.stdout.columns ?? 80))
610
633
  bodyLog(line);
611
- 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();
612
643
  }
613
644
  else {
614
645
  bodyLog(`session ${id} (switched — previous: ${prev}, /resume ${prev} returns)\n`);
615
- for (const line of resumeTail(session.log.all))
646
+ for (const line of resumeTail(session.log.all, process.stdout.columns ?? 80))
616
647
  bodyLog(line);
617
648
  if (currentFaux)
618
649
  session.setAdapter(createFauxProvider(readFauxScript().slice(fauxSkip(id))));
@@ -880,7 +911,7 @@ async function main() {
880
911
  session.setAdapter(createFauxProvider(readFauxScript().slice(fauxSkip(id))));
881
912
  // REL-0152-D5 — the same tail on the explicit-id form. NOT on
882
913
  // the -p path above: that one's stdout is a machine's input.
883
- for (const line of resumeTail(session.log.all))
914
+ for (const line of resumeTail(session.log.all, process.stdout.columns ?? 80))
884
915
  bodyLog(line);
885
916
  await resume(session, prompt, faux, input);
886
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,7 +1,7 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-code",
3
- "version": "0.16.2",
4
- "description": "kiso CLI \u2014 the durable coding agent that survives kill -9: kiso chat / kiso resume / kiso sessions.",
3
+ "version": "0.16.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",
7
7
  "bin": {
@@ -18,19 +18,19 @@
18
18
  "test": "vitest run"
19
19
  },
20
20
  "dependencies": {
21
- "@vincemakes/kiso-ask-ext": "0.16.2",
22
- "@vincemakes/kiso-core": "0.16.2",
23
- "@vincemakes/kiso-evals": "0.16.2",
24
- "@vincemakes/kiso-mcp-ext": "0.16.2",
25
- "@vincemakes/kiso-provider-anthropic": "0.16.2",
26
- "@vincemakes/kiso-provider-openai": "0.16.2",
27
- "@vincemakes/kiso-runtime": "0.16.2",
28
- "@vincemakes/kiso-skills-ext": "0.16.2",
29
- "@vincemakes/kiso-subagent-ext": "0.16.2",
30
- "@vincemakes/kiso-task-ext": "0.16.2",
31
- "@vincemakes/kiso-tools-node": "0.16.2",
32
- "@vincemakes/kiso-tui": "0.16.2",
33
- "@vincemakes/kiso-tui-cells": "0.16.2"
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",