@vincemakes/kiso-code 0.1.39 → 0.1.41

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.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * estimates. All bodies moved verbatim from index.ts.
6
6
  */
7
7
  import { type RunUsage } from "@vincemakes/kiso-tui";
8
- import type { AgentSession } from "@vincemakes/kiso-runtime";
8
+ import type { AgentSession, Run } from "@vincemakes/kiso-runtime";
9
9
  import { type LineInput } from "./state.js";
10
10
  /**
11
11
  * The ergonomics batch C8 — the /compact auto-trigger, OPT-IN (default off: only an
@@ -38,12 +38,16 @@ export declare function estimateCtxRatio(session: AgentSession): number;
38
38
  export declare function startStatusSpinner(onTick: (glyph: string) => void): () => void;
39
39
  /**
40
40
  * Consume a run, answering approval pauses as they arrive. `resumeMode`
41
- * marks a session.resume() continuation. v2a: `faux` picks the status
42
- * line's form; `liveInput` (non-null only in interactive chat) carries the
43
- * last line THIS process's readline consumed the double-echo filter.
41
+ * marks a session.resume() continuation. `faux` picks the status line's
42
+ * form. W22: EVERY user_input event renders its UserMessage chip in the
43
+ * body the v2a double-echo filter is retired (the transient input-row
44
+ * echo is UI, the chip is the record; the momentary double-render is
45
+ * the design's explicit point).
44
46
  */
45
- export declare function consumeRun(session: AgentSession, run: AsyncIterable<import("@vincemakes/kiso-core").Event>, input: LineInput, turnNo: number, faux: boolean, liveInput: {
46
- current: string | null;
47
- } | null, statusCb: ((usage: RunUsage, ctxRatio: number) => void) | null): Promise<import("@vincemakes/kiso-core").Event | undefined>;
47
+ export declare function consumeRun(session: AgentSession, run: Run, input: LineInput, turnNo: number, faux: boolean, statusCb: ((usage: RunUsage, ctxRatio: number) => void) | null,
48
+ /** W21: the amend words ("Yes + feedback") ride the NEXT user turn —
49
+ * threaded from chat's submitTurn; absent in the recovery flow
50
+ * (resume) where a dropped amend is noticed instead. */
51
+ submitTurn?: (line: string) => void): Promise<import("@vincemakes/kiso-core").Event | undefined>;
48
52
  /** Interactive REPL: stream events, pause for approvals, Ctrl+C aborts. */
49
53
  export declare function chat(session: AgentSession, faux: boolean, input: LineInput, autoCompact?: AutoCompact): Promise<void>;
package/dist/chat.js CHANGED
@@ -5,12 +5,12 @@
5
5
  * estimates. All bodies moved verbatim from index.ts.
6
6
  */
7
7
  import { readFileSync } from "node:fs";
8
- import { escapeTerminal, kUnit, palette, renderEvent, renderRecap } from "@vincemakes/kiso-tui";
8
+ import { escapeTerminal, kUnit, palette, renderEvent, renderRecap, toolTarget, } from "@vincemakes/kiso-tui";
9
9
  import { editFileDiff, writeFileDiff } from "@vincemakes/kiso-tui";
10
10
  import { canonicalTargetPath } from "@vincemakes/kiso-tools-node";
11
11
  import { dispatch } from "./dispatch.js";
12
- import { CANCELLED, agentModel, body, bodyLog, configuredWindow, dock } from "./state.js";
13
- import { ask, pendingAsk, resolveUncertains } from "./trust-ui.js";
12
+ import { agentModel, body, bodyLog, configuredWindow, dock } from "./state.js";
13
+ import { addDontAskAgainRule, askPanel, fixHintFor, pendingAsk, resolveUncertains } from "./trust-ui.js";
14
14
  import { FauxExhaustionError, failOnFauxExhaustion } from "./faux-glue.js";
15
15
  import { MODES, getMode, setMode } from "./mode.js";
16
16
  /** B area: default context window for the ~ctx estimate (config overridable). */
@@ -93,6 +93,40 @@ function approvalDiff(name, input) {
93
93
  return null; // never let the diff break the approval
94
94
  }
95
95
  }
96
+ /** W21 — the panel view for a permission_requested: the rule line (the
97
+ * why-asked speaker + the §3.5 fix hint), the toolTarget title, the
98
+ * "▸ run paused" status, and the ALWAYS-verbose args. */
99
+ function approvalView(name, ev) {
100
+ const speaker = ev.speaker ?? "kiso";
101
+ const input = ev.input ?? {};
102
+ // exactOptionalPropertyTypes: the hint is OMITTED when the speaker has
103
+ // no fix (mode:accept-edits, shell in default) — never `hint: undefined`.
104
+ const hint = fixHintFor(speaker, name);
105
+ return {
106
+ flavor: "approval",
107
+ name,
108
+ title: toolTarget(name, input),
109
+ speaker,
110
+ ...(hint !== undefined ? { hint } : {}),
111
+ statusText: "▸ run paused",
112
+ args: approvalArgs(name, input),
113
+ fallbackQuestion: `approve ${escapeTerminal(name)}? (y/n) `,
114
+ };
115
+ }
116
+ /** The panel's ALWAYS-verbose args: edit_file/write_file → the full ±
117
+ * diff (the tool cell's capped copy never reaches the panel — the
118
+ * human approves the WHOLE change), shell → the full command line,
119
+ * anything else → the pretty-printed JSON. Nothing that is asked for
120
+ * approval is ever truncated. */
121
+ function approvalArgs(name, input) {
122
+ if (name === "edit_file" || name === "write_file") {
123
+ return { kind: "diff", diff: approvalDiff(name, input)?.lines ?? null };
124
+ }
125
+ if (name === "shell") {
126
+ return { kind: "text", lines: [String(input.command ?? "")] };
127
+ }
128
+ return { kind: "text", lines: JSON.stringify(input, null, 2).split("\n") };
129
+ }
96
130
  /**
97
131
  * The ergonomics batch C5 — the translation layer: the tui renders its OWN data shape
98
132
  * (RenderInput, zero kiso-core imports); the CLI translates its Event
@@ -165,13 +199,26 @@ function toRenderInput(ev) {
165
199
  }
166
200
  /**
167
201
  * Consume a run, answering approval pauses as they arrive. `resumeMode`
168
- * marks a session.resume() continuation. v2a: `faux` picks the status
169
- * line's form; `liveInput` (non-null only in interactive chat) carries the
170
- * last line THIS process's readline consumed the double-echo filter.
202
+ * marks a session.resume() continuation. `faux` picks the status line's
203
+ * form. W22: EVERY user_input event renders its UserMessage chip in the
204
+ * body the v2a double-echo filter is retired (the transient input-row
205
+ * echo is UI, the chip is the record; the momentary double-render is
206
+ * the design's explicit point).
171
207
  */
172
- export async function consumeRun(session, run, input, turnNo, faux, liveInput, statusCb) {
208
+ export async function consumeRun(session, run, input, turnNo, faux, statusCb,
209
+ /** W21: the amend words ("Yes + feedback") ride the NEXT user turn —
210
+ * threaded from chat's submitTurn; absent in the recovery flow
211
+ * (resume) where a dropped amend is noticed instead. */
212
+ submitTurn) {
173
213
  let last;
174
214
  let usage = { in: null, out: null, cache: null, known: false };
215
+ // R-C item 4: the per-turn cache miss — the overlap with the previous
216
+ // prompt that SHOULD have been cached but was re-sent uncached:
217
+ // missed = min(prevIn, in) − cacheRead. Below the 1024-token floor
218
+ // (Anthropic's minimum cacheable block) it is noise — not surfaced.
219
+ const CACHE_MISS_FLOOR = 1024;
220
+ let prevIn = null;
221
+ let missed = null;
175
222
  // v3 §02: the recap line derives ENTIRELY from the local event stream
176
223
  // (zero tokens) — wall seconds, tool/edit counts, usage, ctx left.
177
224
  const turnStart = Date.now();
@@ -194,17 +241,6 @@ export async function consumeRun(session, run, input, turnNo, faux, liveInput, s
194
241
  else if (thinkingSince === null) {
195
242
  thinkingSince = Date.now();
196
243
  }
197
- // v2a (the double echo): the interactive echo was already rendered by the
198
- // input source — rendering the event again is the double echo.
199
- // v2b: DOCKED — the echo lives in the input row (H), NOT the body;
200
- // the body render is the ONLY visible copy of the sent line.
201
- if (ev.type === "user_input" &&
202
- liveInput !== null &&
203
- liveInput.current === (typeof ev.content === "string" ? ev.content : "") &&
204
- process.stdin.isTTY &&
205
- !dock.active) {
206
- continue;
207
- }
208
244
  // v2d: EVERY event only mutates a cell — the Body is the single
209
245
  // writer of the scroll region, so interleaving is impossible by
210
246
  // construction (ADR-0040).
@@ -260,10 +296,18 @@ export async function consumeRun(session, run, input, turnNo, faux, liveInput, s
260
296
  case "text_end":
261
297
  body.textEnd();
262
298
  break;
263
- case "usage":
299
+ case "usage": {
264
300
  usage = { in: ev.inputTokens, out: ev.outputTokens, cache: ev.cacheRead, known: ev.known };
301
+ // R-C item 4: min(prevIn, in) is the part that could have been
302
+ // cached; what cacheRead did NOT cover is the miss.
303
+ if (usage.in !== null && usage.cache !== null && prevIn !== null) {
304
+ const m = Math.min(prevIn, usage.in) - usage.cache;
305
+ missed = m > CACHE_MISS_FLOOR ? m : null;
306
+ }
307
+ prevIn = usage.in;
265
308
  statusCb?.(usage, estimateCtxRatio(session));
266
309
  break;
310
+ }
267
311
  case "uncertain_pending":
268
312
  // ruling #12 (ADR-0038): the ⚠ line is pure INFORMATION now — the
269
313
  // approval chain guards retries, and the human question belongs
@@ -276,18 +320,71 @@ export async function consumeRun(session, run, input, turnNo, faux, liveInput, s
276
320
  // v2e: the mini-diff for edit/write at the approval moment —
277
321
  // the human sees the change BEFORE deciding (auto-allowed tools
278
322
  // skip the diff: nobody is looking).
323
+ // W21: the PANEL replaces the line question — the bounded block
324
+ // with the ALWAYS-verbose args (the full diff / command / JSON,
325
+ // nothing the human approves is ever cut) and the numbered
326
+ // options. The verdict maps to the session approvals:
327
+ // - bare No → approve(false) FIRST (the denial settles the
328
+ // request), THEN run.abort() — the run's aborted terminal
329
+ // closes the cell;
330
+ // - No+words → approve(false, words) — the words become the
331
+ // tool_result, the run continues;
332
+ // - Yes+amend → approve(true), the words ride the NEXT turn;
333
+ // - esc → cancel, the conservative denial.
279
334
  const name = ev.name;
280
335
  body.toolApproval(ev.callId, approvalDiff(name, ev.input ?? {}));
281
336
  const decisionId = ev.decisionId;
282
- const answer = await ask(input, `approve ${escapeTerminal(name)}? (y/n) `);
283
- if (answer === CANCELLED) {
284
- // round 10: a cancellation is a CONSERVATIVE denial, explicitly
285
- // distinguished from the user typing "n".
286
- body.notice("[approval cancelled treated as a denial]");
287
- await session.approve(decisionId, false);
288
- continue;
337
+ const verdict = await askPanel(input, approvalView(name, ev));
338
+ switch (verdict.action) {
339
+ case "cancel": {
340
+ // round 10: a cancellation is a CONSERVATIVE denial,
341
+ // explicitly distinguished from the user typing "n".
342
+ body.notice("[approval cancelled — treated as a denial]");
343
+ await session.approve(decisionId, false);
344
+ break;
345
+ }
346
+ case "allow": {
347
+ await session.approve(decisionId, true);
348
+ if (verdict.reason.trim() !== "") {
349
+ if (submitTurn !== undefined)
350
+ submitTurn(verdict.reason);
351
+ else
352
+ body.notice("[amend words dropped — the recovery flow has no live prompt]");
353
+ }
354
+ break;
355
+ }
356
+ case "allow-rule": {
357
+ // R3: the don't-ask-again extension is ALLOW-ONLY (never
358
+ // emits deny or ask — the mode and safe-defaults moats
359
+ // keep their teeth); the generated file is human-editable
360
+ // and human-deletable — that IS the revocation path.
361
+ await session.approve(decisionId, true);
362
+ await addDontAskAgainRule(verdict.rule);
363
+ break;
364
+ }
365
+ case "deny": {
366
+ if (verdict.reason.trim() !== "") {
367
+ // No+words — the words become the tool_result; the
368
+ // run continues with the model seeing the denial.
369
+ await session.approve(decisionId, false, verdict.reason);
370
+ }
371
+ else {
372
+ // bare No — the denial settles the pause FIRST, then
373
+ // the run aborts.
374
+ await session.approve(decisionId, false);
375
+ run.abort();
376
+ }
377
+ break;
378
+ }
289
379
  }
290
- await session.approve(decisionId, answer.trim().toLowerCase().startsWith("y"));
380
+ break;
381
+ }
382
+ case "permission_decided": {
383
+ // A5: the verdict binds INTO the tool cell — the aggregated
384
+ // head row (name + status + decidedBy in ONE row), never a
385
+ // free-standing ` approved` orphan. The render.ts case stays
386
+ // for the PIPE path (the transcript is the raw event stream).
387
+ body.toolVerdict(ev.callId ?? "", ev.decision, ev.decidedBy, ev.reason);
291
388
  break;
292
389
  }
293
390
  case "terminal": {
@@ -300,11 +397,21 @@ export async function consumeRun(session, run, input, turnNo, faux, liveInput, s
300
397
  // the commit loop folds the quiet turn's held cells first (the
301
398
  // fold line lands above the recap, natural cell order).
302
399
  body.endTurn(Math.round(thoughtSeconds));
400
+ // D4: the max_tokens truncation is named, never silent — the
401
+ // honest notice rides after the partial answer, before the
402
+ // recap (the truncation-guard philosophy: the cut is visible
403
+ // in the scrollback, the model's own text intact).
404
+ if (ev.outcome.kind === "max_tokens") {
405
+ body.notice('┌ answer truncated at max_tokens — say "continue" to finish');
406
+ }
303
407
  bodyLog(renderRecap({
304
408
  seconds: Math.round((Date.now() - turnStart) / 1000),
305
409
  tools: toolCount,
306
410
  edits: editCount,
307
411
  usage,
412
+ // R-C item 4: only an above-floor miss is surfaced —
413
+ // the recap gains "· miss N" on the cache segment.
414
+ ...(missed !== null ? { missed } : {}),
308
415
  ctxLeftPct: Number.isFinite(ratio) ? (1 - ratio) * 100 : null,
309
416
  // W19: under plan the recap becomes the way-forward row
310
417
  // (the /mode hints are the mode's exits).
@@ -338,10 +445,6 @@ export async function chat(session, faux, input, autoCompact) {
338
445
  let cancelled = false;
339
446
  const turn = (text) => new Promise((resolve, reject) => {
340
447
  queued = Math.max(0, queued - 1); // a queued turn starts
341
- // v2a: the echo filter compares the user_input event against THIS
342
- // turn's own input — lines that arrive ahead of their turn (piped
343
- // bursts, queued replays) must not overwrite the reference.
344
- liveInput.current = text;
345
448
  const run = session.run(text);
346
449
  currentRun = run;
347
450
  turnNo += 1;
@@ -357,7 +460,7 @@ export async function chat(session, faux, input, autoCompact) {
357
460
  (async () => {
358
461
  let last;
359
462
  try {
360
- last = await consumeRun(session, run, input, myTurn, faux, liveInput, statusCb);
463
+ last = await consumeRun(session, run, input, myTurn, faux, statusCb, submitTurn);
361
464
  stopSpinner();
362
465
  paintIdle();
363
466
  currentRun = null;
@@ -440,12 +543,14 @@ export async function chat(session, faux, input, autoCompact) {
440
543
  // B area: user-turn counter for the status line. /last and /think read
441
544
  // the body (the ToolCell / ThinkingCell final states).
442
545
  let turnNo = 0;
443
- // v2a: the last line THIS process's readline consumed the double-echo
444
- // filter (see consumeRun). Only interactive chat sets it.
445
- const liveInput = { current: null };
446
- // v2c: turns submitted while another runs are QUEUED on the chain — the
447
- // live count rides the status bar (+N queued).
546
+ // v2c: turns submitted while another runs are QUEUED on the chain —
547
+ // the live count rides the status bar (+N queued).
448
548
  let queued = 0;
549
+ // W22: the pending turns — the LIVE slots the chips + the ↑/esc pop
550
+ // read (the dock renders the lines, the editor pops the last one).
551
+ // A slot leaves the queue when its turn STARTS or when the user
552
+ // pops it (cancelled — the chain segment skips it).
553
+ const pendingTurns = [];
449
554
  // v2b: the live status bar (docked only). Modes: /mode switches repaint
450
555
  // it immediately through paintStatus (the last turn stats are kept).
451
556
  // v3 §03: the status bar has TWO states. Idle: the mode is ALWAYS
@@ -478,9 +583,35 @@ export async function chat(session, faux, input, autoCompact) {
478
583
  paintRunning();
479
584
  };
480
585
  const submitTurn = (line) => {
586
+ const slot = { line, cancelled: false };
587
+ pendingTurns.push(slot);
481
588
  queued += 1;
482
- chainRef.current = chainRef.current.then(() => turn(line));
589
+ chainRef.current = chainRef.current.then(() => {
590
+ if (slot.cancelled)
591
+ return; // the pop already dropped it — no double count
592
+ const idx = pendingTurns.indexOf(slot);
593
+ if (idx >= 0)
594
+ pendingTurns.splice(idx, 1); // the chip leaves when the turn STARTS
595
+ return turn(line);
596
+ });
597
+ };
598
+ // W22: the ↑/esc pop — the LAST queued slot leaves the queue
599
+ // (cancelled + spliced + counted down); null when the queue is
600
+ // empty. The chain segment skips the cancelled slot, so the popped
601
+ // message NEVER runs — it returns to the editor instead.
602
+ const popQueue = () => {
603
+ const slot = pendingTurns[pendingTurns.length - 1];
604
+ if (slot === undefined)
605
+ return null;
606
+ slot.cancelled = true;
607
+ pendingTurns.pop();
608
+ queued = Math.max(0, queued - 1);
609
+ return slot.line;
483
610
  };
611
+ // W22: the visibility invariant's binds — the dock renders the
612
+ // pending chips (+N queued), the editor routes the pop keys.
613
+ dock.bindQueue(() => pendingTurns.map((s) => s.line));
614
+ input.bindQueue(() => pendingTurns.map((s) => s.line), popQueue);
484
615
  const dispatchCtx = {
485
616
  session,
486
617
  input,
@@ -528,7 +659,7 @@ export async function chat(session, faux, input, autoCompact) {
528
659
  const recoveryRun = session.resume();
529
660
  currentRun = recoveryRun;
530
661
  turnNo += 1;
531
- const last = await consumeRun(session, recoveryRun, input, turnNo, faux, liveInput, statusCb);
662
+ const last = await consumeRun(session, recoveryRun, input, turnNo, faux, statusCb, submitTurn);
532
663
  currentRun = null;
533
664
  failOnFauxExhaustion(last, faux, input);
534
665
  maybeAutoCompact(); // the ergonomics batch C8: the recovery run ended too — same check (awaited by the exit re-await)
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ import { createAgent, disposeExtensions, loadExtensions, loadProjectExtensions,
31
31
  import { createFauxProvider } from "@vincemakes/kiso-evals";
32
32
  import { createCodingTools } from "@vincemakes/kiso-tools-node";
33
33
  import { MODES, modeExtensions, modeFromEnv, modeSystemPrompt, setMode } from "./mode.js";
34
- import { body, bodyLog, currentFaux, dock, extensionsDir, loadedExtensions, mergedConfig, mergedTempPaths, projectExtensions, sessionsDir, setAgentModel, setBody, setConfigModels, setConfiguredWindow, setCurrentFaux, setCurrentModelName, setExtensionLists, setMergedConfig, userExtensions, VERSION } from "./state.js";
34
+ import { body, bodyLog, currentFaux, dock, extensionsDir, loadedExtensions, mergedConfig, mergedTempPaths, projectExtensions, sessionsDir, setAgentModel, setBody, setConfigModels, setConfiguredWindow, setCurrentAgentExtensions, setCurrentFaux, setCurrentModelName, setExtensionLists, setMergedConfig, userExtensions, VERSION } from "./state.js";
35
35
  import { interactivePrompt, resolveProjectTrust } from "./trust-ui.js";
36
36
  import { fauxSkip, readFauxScript } from "./faux-glue.js";
37
37
  import { autoCompactFromEnv, chat, contextWindowTokens } from "./chat.js";
@@ -81,6 +81,19 @@ function readlineInput(rl) {
81
81
  /* the rl.question stays pending; the settled branch re-emits
82
82
  * the answer as a new line. */
83
83
  },
84
+ // W21: readline is never asked — askPanel's non-TTY branch
85
+ // auto-denies before any panel opens (the pipe path).
86
+ panelAsk() {
87
+ /* unreachable — non-TTY asks auto-deny in askPanel */
88
+ },
89
+ panelCancel() {
90
+ /* unreachable */
91
+ },
92
+ // W22: readline has no ↑/esc pop — the pipe path shows no chips
93
+ // and pops nothing (the queue drains on its own).
94
+ bindQueue() {
95
+ /* unreachable — no raw keys in the pipe path */
96
+ },
84
97
  emitLine(line) {
85
98
  rl.emit("line", line);
86
99
  },
@@ -125,6 +138,19 @@ function editorInput(editor) {
125
138
  cancelQuestion() {
126
139
  editor.cancelQuestion();
127
140
  },
141
+ // W21: the panel — the editor's own state machine takes the
142
+ // keys; the compositor renders it via the bound state.
143
+ panelAsk(view, onCommit) {
144
+ editor.beginPanel(view, onCommit);
145
+ },
146
+ panelCancel() {
147
+ editor.cancelPanel();
148
+ },
149
+ // W22: the pending-turn queue — the ↑ pop walk (the keys); the
150
+ // chips are the compositor's bindQueue (the dock side).
151
+ bindQueue(state, pop) {
152
+ editor.bindQueue(state, pop);
153
+ },
128
154
  emitLine() {
129
155
  /* the editor's buffer survives a cancelled question — its text
130
156
  * becomes the next turn on Enter (the readline re-emit
@@ -157,6 +183,7 @@ function makeLineInput() {
157
183
  // pipe bytes do not change)
158
184
  dock.bindInput(() => editor.dockState(), "› ");
159
185
  dock.bindMenu(() => editor.menuState()); // v3 §04: the slash-command menu
186
+ dock.bindApproval(() => editor.panelState()); // W21: the panel's bound state
160
187
  return editorInput(editor);
161
188
  }
162
189
  return readlineInput(createInterface({ input: process.stdin, output: process.stdout }));
@@ -302,6 +329,13 @@ async function makeAgent(fauxSkipTurns = 0, input, modelFlag) {
302
329
  setCurrentModelName(resolved.name);
303
330
  }
304
331
  setAgentModel(model); // v2b: the status bar shows it
332
+ // W21: the extensions array is built ONCE per agent and shared with
333
+ // the runtime by reference — the don't-ask-again writer pushes the
334
+ // generated extension into it so a first-time rule joins the chain
335
+ // at the NEXT run (the run's policies are fixed at its start; run.ts
336
+ // re-reads the config's extensions array per run).
337
+ const extensions = [...modeExtensions(), ...loadedExtensions];
338
+ setCurrentAgentExtensions(extensions);
305
339
  const definition = {
306
340
  model,
307
341
  store,
@@ -327,7 +361,7 @@ async function makeAgent(fauxSkipTurns = 0, input, modelFlag) {
327
361
  // Modes: the five tiers join at the CHAIN HEAD, before the user/
328
362
  // project extensions (the deny>ask>allow composition keeps a user
329
363
  // deny winning over any mode tier — bypass included).
330
- extensions: [...modeExtensions(), ...loadedExtensions],
364
+ extensions,
331
365
  ...(resolved !== null
332
366
  ? {
333
367
  provider: resolved.profile.kind,
package/dist/resume.js CHANGED
@@ -48,7 +48,7 @@ export async function resume(session, prompt, faux, input) {
48
48
  });
49
49
  try {
50
50
  turnNo += 1;
51
- const last = await consumeRun(session, run, input, turnNo, faux, null, statusCb);
51
+ const last = await consumeRun(session, run, input, turnNo, faux, statusCb);
52
52
  failOnFauxExhaustion(last, faux, input);
53
53
  }
54
54
  finally {
package/dist/state.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * creates the mutable ones (setBody / setAgentModel / setExtensionLists);
6
6
  * the moved modules read and mutate at call time.
7
7
  */
8
- import { Dock, type Body } from "@vincemakes/kiso-tui";
8
+ import { Dock, type Body, type PanelVerdict, type PanelView } from "@vincemakes/kiso-tui";
9
9
  import type { KisoExtension } from "@vincemakes/kiso-runtime";
10
10
  /** finding #11: KISO_HOME is the ONE root — every default path derives from
11
11
  * it (sessions, trust, extensions, mcp config, skills). The dedicated
@@ -32,6 +32,16 @@ export interface LineInput {
32
32
  onExpand(cb: () => void): void;
33
33
  question(query: string, cb: (answer: string) => void): void;
34
34
  cancelQuestion(): void;
35
+ /** W21: open the approval panel — the editor's state machine takes
36
+ * the keys (the digits/y/n/tab/esc/enter routing, the rule input,
37
+ * the tab-amend), the compositor renders the block + the leads. */
38
+ panelAsk(view: PanelView, onCommit: (v: PanelVerdict) => void): void;
39
+ /** W21: cancel the panel — the SIGINT pair to panelAsk. */
40
+ panelCancel(): void;
41
+ /** W22: bind the pending-turn queue — the ↑ pop walks the CLI's
42
+ * live slots (each pop cancels the turn), esc ends the walk after
43
+ * one more pop. The chips are the compositor's own bindQueue. */
44
+ bindQueue(state: () => readonly string[], pop: () => string | null): void;
35
45
  emitLine(line: string): void;
36
46
  line(): string;
37
47
  clearLine(): void;
@@ -84,6 +94,13 @@ export declare let userExtensions: readonly KisoExtension[];
84
94
  * banner distinguishes them from the user-level ones. */
85
95
  export declare let projectExtensions: readonly KisoExtension[];
86
96
  export declare function setExtensionLists(user: readonly KisoExtension[], project: readonly KisoExtension[], loaded: readonly KisoExtension[]): void;
97
+ /** W21: the CURRENT agent's extensions array — set by makeAgent, the
98
+ * don't-ask-again writer pushes the generated extension into it so a
99
+ * first-time rule joins the chain at the NEXT run (the run's policies
100
+ * are fixed at its start; the array is shared by reference with the
101
+ * runtime's session config — run.ts re-reads it per run). */
102
+ export declare let currentAgentExtensions: KisoExtension[];
103
+ export declare function setCurrentAgentExtensions(value: KisoExtension[]): void;
87
104
  /** E3: temp artifacts of the mcp/skills merge — removed on exit. */
88
105
  export declare const mergedTempPaths: string[];
89
106
  /** The CLI's own version — read from the package.json next to the build. */
package/dist/state.js CHANGED
@@ -89,6 +89,15 @@ export function setExtensionLists(user, project, loaded) {
89
89
  projectExtensions = project;
90
90
  loadedExtensions = loaded;
91
91
  }
92
+ /** W21: the CURRENT agent's extensions array — set by makeAgent, the
93
+ * don't-ask-again writer pushes the generated extension into it so a
94
+ * first-time rule joins the chain at the NEXT run (the run's policies
95
+ * are fixed at its start; the array is shared by reference with the
96
+ * runtime's session config — run.ts re-reads it per run). */
97
+ export let currentAgentExtensions = [];
98
+ export function setCurrentAgentExtensions(value) {
99
+ currentAgentExtensions = value;
100
+ }
92
101
  /** E3: temp artifacts of the mcp/skills merge — removed on exit. */
93
102
  export const mergedTempPaths = [];
94
103
  /** The CLI's own version — read from the package.json next to the build. */
@@ -1,31 +1,62 @@
1
1
  /**
2
2
  * The ergonomics batch B4 (pure move) — the human-facing question UI: the E3 project
3
- * trust gate (ADR-0037), the mcp/skills env merges, the generic ask()
4
- * (approvals, trust, uncertain resolutions), and the uncertain-execution
3
+ * trust gate (ADR-0037), the mcp/skills env merges, the W21 approval
4
+ * panel (askPanel the bounded block, the verdict mapping lives in
5
+ * chat.ts), the don't-ask-again rule writer, and the uncertain-execution
5
6
  * decisions. All bodies moved verbatim from index.ts.
6
7
  */
8
+ import { type PanelVerdict, type PanelView } from "@vincemakes/kiso-tui";
7
9
  import { type ProjectArtifacts } from "@vincemakes/kiso-runtime";
8
10
  import type { AgentSession } from "@vincemakes/kiso-runtime";
9
- import { CANCELLED, type LineInput } from "./state.js";
11
+ import { type LineInput } from "./state.js";
10
12
  /** v2a: the interactive prompt — blue, the identity accent. readline owns
11
13
  * the echo of what the user types; we own the prompt's color. (v2c: the
12
14
  * readline prompt keeps "you> " — the brick ▌ is the dock's row only;
13
15
  * pipe bytes must not change.) */
14
16
  export declare function interactivePrompt(): string;
15
17
  /**
16
- * Ask the human a question. Non-interactive stdin (piped, CI) cannot wait
17
- * forever: approvals auto-deny and uncertain executions auto-abandon, both
18
- * printed loudly never silently ignored, never hung (Area 7).
18
+ * W21 — ask the human with the approval panel: the bounded block that
19
+ * replaces the running tool's live window while the approval is pending.
20
+ * The editor owns the keys (the digits/tab/esc/enter routing, the rule
21
+ * input, the tab-amend); the compositor renders the block + the leads;
22
+ * the CLI maps the verdict (the bare-No abort, the No+words tool_result,
23
+ * the allow-amend words riding the next turn — all live in chat.ts,
24
+ * never here).
19
25
  *
20
- * rounds 8/10: the question is ABORTABLE a pending rl.question is registered in
21
- * `pendingAsk` and the SIGINT handler resolves it with the CANCELLED
22
- * sentinel. The rl.question callback is NOT left dangling: an input that
23
- * arrives after the cancellation is re-emitted as a fresh "line" — it
24
- * becomes the next user turn instead of being swallowed by the dead
25
- * question.
26
+ * Non-interactive stdin (piped, CI) cannot wait forever: the approval
27
+ * AUTO-DENIES, printed loudly never silently ignored, never hung
28
+ * (Area 7). A TTY without a dock (rows < 4) falls back to the v2a line
29
+ * question (y/n allow/deny) the panel cannot render without the
30
+ * dock's live region.
31
+ *
32
+ * rounds 8/10: the ask is ABORTABLE — registered in `pendingAsk`, the
33
+ * SIGINT handler resolves it with { action: "cancel" } (the panel closes
34
+ * through the editor's panelCancel, the dock-less question through
35
+ * cancelQuestion). The dock-less question callback is NOT left dangling:
36
+ * an input that arrives after the cancellation is re-emitted as a fresh
37
+ * "line" — it becomes the next user turn instead of being swallowed by
38
+ * the dead question.
26
39
  */
27
40
  export declare let pendingAsk: (() => void) | null;
28
- export declare function ask(input: LineInput, question: string): Promise<string | typeof CANCELLED>;
41
+ export declare function askPanel(input: LineInput, view: PanelView): Promise<PanelVerdict>;
42
+ /**
43
+ * W21/R3 — the "2 Yes, don't ask again for <tool>" rule: a GENERATED
44
+ * extension, ALLOW-ONLY by construction (it never emits deny or ask —
45
+ * the mode moat and the safe-defaults moat keep their teeth). The file
46
+ * is HUMAN-EDITABLE and HUMAN-DELETABLE — that IS the revocation path.
47
+ * The loader sorts *.mjs alphabetically, so dont-ask-again.mjs binds
48
+ * BEFORE safe-defaults.mjs: the allow is the chain's first word, the
49
+ * safe-defaults deny moat still wins (deny > allow — the R3 order).
50
+ */
51
+ export declare function addDontAskAgainRule(rule: string): Promise<void>;
52
+ /**
53
+ * W21 — the §3.5 fix-hint table: per speaker, the ONE-line path to stop
54
+ * being asked. The hint names the FIX, never a bypass ("switch to
55
+ * bypass" would be an approval-hole nudge — the R3 ruling). The
56
+ * extension hint collapses the home prefix to ~ (the hint must stay
57
+ * short enough for the one-row rule line at narrow widths).
58
+ */
59
+ export declare function fixHintFor(speaker: string, tool: string): string | undefined;
29
60
  /**
30
61
  * E3 — the project-level trust gate (ADR-0037): capability is trusted by
31
62
  * content digest, not by directory. Runs BEFORE any extension loads — the
@@ -46,5 +77,9 @@ export declare function resolveProjectTrust(input: LineInput): Promise<ProjectAr
46
77
  * with project-wins and a stderr note. Exported for tests.
47
78
  */
48
79
  export declare function applyProjectMerges(artifacts: ProjectArtifacts): void;
49
- /** Decide every uncertain execution with the human (r)erun/(a)bandon. */
80
+ /** Decide every uncertain execution with the human the panel's simple
81
+ * flavor: 1 Yes = rerun, 3 No = abandon; a cancel records NOTHING (the
82
+ * execution stays uncertain and durable — no rerun/abandoned is
83
+ * fabricated, round 10). The resolution feedback lands in the body —
84
+ * the body is the single stdout writer, never a stray console.log. */
50
85
  export declare function resolveUncertains(session: AgentSession, input: LineInput, isCancelled: () => boolean): Promise<void>;
package/dist/trust-ui.js CHANGED
@@ -1,16 +1,19 @@
1
1
  /**
2
2
  * The ergonomics batch B4 (pure move) — the human-facing question UI: the E3 project
3
- * trust gate (ADR-0037), the mcp/skills env merges, the generic ask()
4
- * (approvals, trust, uncertain resolutions), and the uncertain-execution
3
+ * trust gate (ADR-0037), the mcp/skills env merges, the W21 approval
4
+ * panel (askPanel the bounded block, the verdict mapping lives in
5
+ * chat.ts), the don't-ask-again rule writer, and the uncertain-execution
5
6
  * decisions. All bodies moved verbatim from index.ts.
6
7
  */
7
- import { existsSync, mkdtempSync, readFileSync, readdirSync, symlinkSync, writeFileSync } from "node:fs";
8
- import { tmpdir } from "node:os";
8
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, symlinkSync, writeFileSync } from "node:fs";
9
+ import { homedir, tmpdir } from "node:os";
9
10
  import { join } from "node:path";
11
+ import { pathToFileURL } from "node:url";
10
12
  import { escapeTerminal, palette } from "@vincemakes/kiso-tui";
11
13
  import { projectArtifacts, recordTrust, trustFor } from "@vincemakes/kiso-runtime";
12
- import { CANCELLED, bodyLog, dock, kisoHome, mergedTempPaths } from "./state.js";
14
+ import { bodyLog, currentAgentExtensions, dock, extensionsDir, kisoHome, mergedTempPaths } from "./state.js";
13
15
  import { loadUserConfig, resolveProjectTrustPolicy } from "./config.js";
16
+ import { getMode } from "./mode.js";
14
17
  /** v2a: the interactive prompt — blue, the identity accent. readline owns
15
18
  * the echo of what the user types; we own the prompt's color. (v2c: the
16
19
  * readline prompt keeps "you> " — the brick ▌ is the dock's row only;
@@ -20,31 +23,33 @@ export function interactivePrompt() {
20
23
  return `${p.bold}you> ${p.reset}`;
21
24
  }
22
25
  /**
23
- * Ask the human a question. Non-interactive stdin (piped, CI) cannot wait
24
- * forever: approvals auto-deny and uncertain executions auto-abandon, both
25
- * printed loudly never silently ignored, never hung (Area 7).
26
+ * W21 — ask the human with the approval panel: the bounded block that
27
+ * replaces the running tool's live window while the approval is pending.
28
+ * The editor owns the keys (the digits/tab/esc/enter routing, the rule
29
+ * input, the tab-amend); the compositor renders the block + the leads;
30
+ * the CLI maps the verdict (the bare-No abort, the No+words tool_result,
31
+ * the allow-amend words riding the next turn — all live in chat.ts,
32
+ * never here).
26
33
  *
27
- * rounds 8/10: the question is ABORTABLE a pending rl.question is registered in
28
- * `pendingAsk` and the SIGINT handler resolves it with the CANCELLED
29
- * sentinel. The rl.question callback is NOT left dangling: an input that
30
- * arrives after the cancellation is re-emitted as a fresh "line" — it
31
- * becomes the next user turn instead of being swallowed by the dead
32
- * question.
34
+ * Non-interactive stdin (piped, CI) cannot wait forever: the approval
35
+ * AUTO-DENIES, printed loudly never silently ignored, never hung
36
+ * (Area 7). A TTY without a dock (rows < 4) falls back to the v2a line
37
+ * question (y/n allow/deny) the panel cannot render without the
38
+ * dock's live region.
39
+ *
40
+ * rounds 8/10: the ask is ABORTABLE — registered in `pendingAsk`, the
41
+ * SIGINT handler resolves it with { action: "cancel" } (the panel closes
42
+ * through the editor's panelCancel, the dock-less question through
43
+ * cancelQuestion). The dock-less question callback is NOT left dangling:
44
+ * an input that arrives after the cancellation is re-emitted as a fresh
45
+ * "line" — it becomes the next user turn instead of being swallowed by
46
+ * the dead question.
33
47
  */
34
48
  export let pendingAsk = null;
35
- export function ask(input, question) {
49
+ export function askPanel(input, view) {
36
50
  if (!process.stdin.isTTY) {
37
- console.log(`[non-interactive — no human to ask: ${question}]`);
38
- return Promise.resolve("");
39
- }
40
- // v2b: docked — the question takes over the status position, the
41
- // answer lands at the input line. v2c: a TTY without a dock (rows < 4)
42
- // prints the question into the body — the editor cannot show it.
43
- if (dock.active) {
44
- dock.showQuestion(question);
45
- }
46
- else {
47
- bodyLog(question);
51
+ console.log(`[non-interactive — no human to ask: ${view.fallbackQuestion}]`);
52
+ return Promise.resolve({ action: "deny", reason: "no human to ask" });
48
53
  }
49
54
  return new Promise((resolve) => {
50
55
  let settled = false;
@@ -53,28 +58,108 @@ export function ask(input, question) {
53
58
  return;
54
59
  settled = true;
55
60
  pendingAsk = null;
56
- input.cancelQuestion();
57
- resolve(CANCELLED); // the run is aborting — the question is dead
58
- };
59
- // v2b: docked — the question reads at the input line, whose prompt
60
- // is the same blue you> (the editor's brick row; the readline path
61
- // passes the plain question). An empty prompt would start readline
62
- // at column 1 while the dock renders "you> " — the typed answer
63
- // would land on the prompt and drift (probe-confirmed).
64
- input.question(dock.active ? interactivePrompt() : question, (answer) => {
65
- if (settled) {
66
- // The question was cancelled; this line is a NEW user turn.
67
- input.emitLine(answer);
68
- return;
69
- }
70
- settled = true;
71
- pendingAsk = null;
72
61
  if (dock.active)
73
- dock.clearQuestion();
74
- resolve(answer);
75
- });
62
+ input.panelCancel();
63
+ else
64
+ input.cancelQuestion();
65
+ resolve({ action: "cancel" });
66
+ };
67
+ if (dock.active) {
68
+ input.panelAsk(view, (verdict) => {
69
+ if (settled)
70
+ return; // a cancelled panel's late commit is dead
71
+ settled = true;
72
+ pendingAsk = null;
73
+ resolve(verdict);
74
+ });
75
+ }
76
+ else {
77
+ // v2c: a TTY without a dock (rows < 4) — the fallback question
78
+ // in the body; the y/n line answer maps to the verdicts.
79
+ bodyLog(view.fallbackQuestion);
80
+ input.question(view.fallbackQuestion, (answer) => {
81
+ if (settled) {
82
+ // The question was cancelled; this line is a NEW user turn.
83
+ input.emitLine(answer);
84
+ return;
85
+ }
86
+ settled = true;
87
+ pendingAsk = null;
88
+ const yes = answer.trim().toLowerCase().startsWith("y");
89
+ resolve(yes ? { action: "allow", reason: "" } : { action: "deny", reason: "" });
90
+ });
91
+ }
76
92
  });
77
93
  }
94
+ /**
95
+ * W21/R3 — the "2 Yes, don't ask again for <tool>" rule: a GENERATED
96
+ * extension, ALLOW-ONLY by construction (it never emits deny or ask —
97
+ * the mode moat and the safe-defaults moat keep their teeth). The file
98
+ * is HUMAN-EDITABLE and HUMAN-DELETABLE — that IS the revocation path.
99
+ * The loader sorts *.mjs alphabetically, so dont-ask-again.mjs binds
100
+ * BEFORE safe-defaults.mjs: the allow is the chain's first word, the
101
+ * safe-defaults deny moat still wins (deny > allow — the R3 order).
102
+ */
103
+ export async function addDontAskAgainRule(rule) {
104
+ const dir = extensionsDir();
105
+ const file = join(dir, "dont-ask-again.mjs");
106
+ const existed = existsSync(file);
107
+ const module = [
108
+ "// generated by kiso — the \"2 Yes, don't ask again for <tool>\" rule.",
109
+ "// HUMAN-EDITABLE and HUMAN-DELETABLE — this file IS the revocation path:",
110
+ "// delete a rule from the set (or the whole file) to be asked again.",
111
+ "// ALLOW-ONLY by design (R3): never emits deny or ask — the mode moat",
112
+ "// and the safe-defaults moat keep their teeth.",
113
+ `export const RULES = new Set(${JSON.stringify([rule])});`,
114
+ "",
115
+ "export default {",
116
+ ' name: "dont-ask-again",',
117
+ " approvals: [",
118
+ " {",
119
+ " decide(call) {",
120
+ ' return RULES.has(call.name) ? { action: "allow" } : { action: "abstain" };',
121
+ " },",
122
+ " },",
123
+ " ],",
124
+ "};",
125
+ "",
126
+ ].join("\n");
127
+ mkdirSync(dir, { recursive: true });
128
+ writeFileSync(file, module, "utf8");
129
+ // The live in-session effect: a file that existed at startup was
130
+ // imported by the loader — ONE namespace — so mutating the exported
131
+ // RULES Set is live NOW (the current run's chain reads the same Set).
132
+ const mod = (await import(pathToFileURL(file).href));
133
+ if (mod.RULES !== undefined && !mod.RULES.has(rule))
134
+ mod.RULES.add(rule);
135
+ // A FIRST-time rule (a file that did not exist at startup) is not yet
136
+ // in the current run's chain — the chain is fixed at a run's start —
137
+ // so the generated extension joins the NEXT run's policies (run.ts
138
+ // re-reads the session config's extensions array, shared by
139
+ // reference). The honest story: a new rule applies from the next run.
140
+ if (!existed && mod.default !== undefined) {
141
+ currentAgentExtensions.push(mod.default);
142
+ }
143
+ }
144
+ /**
145
+ * W21 — the §3.5 fix-hint table: per speaker, the ONE-line path to stop
146
+ * being asked. The hint names the FIX, never a bypass ("switch to
147
+ * bypass" would be an approval-hole nudge — the R3 ruling). The
148
+ * extension hint collapses the home prefix to ~ (the hint must stay
149
+ * short enough for the one-row rule line at narrow widths).
150
+ */
151
+ export function fixHintFor(speaker, tool) {
152
+ if (speaker === "mode:default") {
153
+ if (tool === "shell")
154
+ return undefined;
155
+ return "/mode accept-edits auto-approves edits";
156
+ }
157
+ if (speaker === "mode:manual" || speaker === "mode:plan")
158
+ return "/mode default";
159
+ if (speaker === "mode:accept-edits" || speaker === "mode:bypass")
160
+ return undefined;
161
+ return `edit ${join(extensionsDir(), `${speaker}.mjs`).replace(homedir(), "~")}`;
162
+ }
78
163
  /**
79
164
  * E3 — the project-level trust gate (ADR-0037): capability is trusted by
80
165
  * content digest, not by directory. Runs BEFORE any extension loads — the
@@ -110,14 +195,30 @@ export async function resolveProjectTrust(input) {
110
195
  console.error(`[project .kiso] found ${artifacts.files.length} artifact(s) in ${artifacts.root} — not trusted, not loaded (run kiso interactively once to decide)`);
111
196
  return null;
112
197
  }
113
- // v2c: the shared input (the editor on a TTY) reads the answer; the
114
- // dock shows the question at the status position.
198
+ // v2c: the shared input (the editor on a TTY) reads the answer.
199
+ // W21: the trust gate is the panel's SIMPLE flavor — the ruleOverride
200
+ // carries the question, the args the artifact listing (the same rows
201
+ // the bodyLog below records, verbatim — the listing still lands in
202
+ // the scrollback; the panel is a bounded block, the record is not).
115
203
  bodyLog(`[project .kiso] ${artifacts.root}`);
116
204
  for (const f of artifacts.files) {
117
205
  bodyLog(` ${f.path} (${f.digest.slice(0, 6)})`);
118
206
  }
119
- const answer = await ask(input, `trust this project's .kiso? (y/n) `);
120
- const granted = answer !== CANCELLED && answer.trim().toLowerCase().startsWith("y");
207
+ const verdict = await askPanel(input, {
208
+ flavor: "simple",
209
+ name: "project trust",
210
+ title: artifacts.root,
211
+ speaker: "kiso",
212
+ statusText: "▸ project trust",
213
+ args: { kind: "text", lines: artifacts.files.map((f) => `${f.path} (${f.digest.slice(0, 6)})`) },
214
+ ruleOverride: "trust this project's .kiso?",
215
+ fallbackQuestion: `trust this project's .kiso? (y/n) `,
216
+ });
217
+ // A cancel is a "no" HERE — refused is sticky, the project does not
218
+ // load (re-evaluate by deleting the trust line or changing a file).
219
+ // The non-TTY branch above returned WITHOUT a record so an interactive
220
+ // run can still decide later.
221
+ const granted = verdict.action === "allow";
121
222
  recordTrust({ root: artifacts.root, digest: artifacts.digest, decision: granted ? "granted" : "refused" });
122
223
  if (!granted)
123
224
  return null;
@@ -209,17 +310,30 @@ function readdirSyncSafe(dir) {
209
310
  return []; // no skills dir on either level = nothing to merge
210
311
  }
211
312
  }
212
- /** Decide every uncertain execution with the human (r)erun/(a)bandon. */
313
+ /** Decide every uncertain execution with the human the panel's simple
314
+ * flavor: 1 Yes = rerun, 3 No = abandon; a cancel records NOTHING (the
315
+ * execution stays uncertain and durable — no rerun/abandoned is
316
+ * fabricated, round 10). The resolution feedback lands in the body —
317
+ * the body is the single stdout writer, never a stray console.log. */
213
318
  export async function resolveUncertains(session, input, isCancelled) {
214
319
  for (const uncertain of session.uncertainExecutions()) {
215
- const answer = await ask(input, `⚠ interrupted execution: ${escapeTerminal(uncertain.name)} (${uncertain.executionId}) — did it apply? (r)erun / (a)bandon: `);
216
- if (isCancelled() || answer === CANCELLED) {
320
+ const verdict = await askPanel(input, {
321
+ flavor: "simple",
322
+ name: "uncertain execution",
323
+ title: `${uncertain.name} (${uncertain.executionId})`,
324
+ speaker: "kiso",
325
+ statusText: "▸ uncertain execution",
326
+ args: { kind: "text", lines: [uncertain.executionId] },
327
+ ruleOverride: "did the interrupted execution apply? — 1 rerun · 3 abandon",
328
+ fallbackQuestion: `⚠ interrupted execution: ${escapeTerminal(uncertain.name)} (${uncertain.executionId}) — did it apply? (y)es / (n)o `,
329
+ });
330
+ if (isCancelled() || verdict.action === "cancel") {
217
331
  // round 10: a cancellation NEVER records a verdict — the execution
218
332
  // stays uncertain and durable; no rerun/abandoned is fabricated.
219
333
  return;
220
334
  }
221
- const resolution = answer.trim().toLowerCase().startsWith("r") ? "rerun" : "abandoned";
335
+ const resolution = verdict.action === "allow" ? "rerun" : "abandoned";
222
336
  await session.resolveUncertain(uncertain.executionId, resolution);
223
- console.log(` ${resolution}\n`);
337
+ bodyLog(` ${resolution}`);
224
338
  }
225
339
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-code",
3
- "version": "0.1.39",
3
+ "version": "0.1.41",
4
4
  "description": "kiso CLI — the coding-agent reference product: kiso chat / kiso resume / kiso sessions.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -18,13 +18,14 @@
18
18
  "test": "vitest run"
19
19
  },
20
20
  "dependencies": {
21
- "@vincemakes/kiso-core": "0.1.30",
22
- "@vincemakes/kiso-evals": "0.1.31",
23
- "@vincemakes/kiso-provider-anthropic": "0.1.31",
24
- "@vincemakes/kiso-provider-openai": "0.1.31",
25
- "@vincemakes/kiso-runtime": "0.1.31",
26
- "@vincemakes/kiso-tools-node": "0.1.31",
27
- "@vincemakes/kiso-tui": "0.1.39"
21
+ "@vincemakes/kiso-core": "0.1.32",
22
+ "@vincemakes/kiso-evals": "0.1.33",
23
+ "@vincemakes/kiso-provider-anthropic": "0.1.33",
24
+ "@vincemakes/kiso-provider-openai": "0.1.33",
25
+ "@vincemakes/kiso-runtime": "0.1.33",
26
+ "@vincemakes/kiso-tools-node": "0.1.33",
27
+ "@vincemakes/kiso-tui": "0.1.41",
28
+ "@vincemakes/kiso-tui-cells": "0.1.41"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@types/node": "^26.1.2",