@vincemakes/kiso-code 0.1.37 → 0.1.39

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
@@ -100,8 +100,8 @@ function approvalDiff(name, input) {
100
100
  * and the consumer skips them — the pipe bytes stay identical.
101
101
  */
102
102
  /**
103
- * round 6 (the todo round): translate a do-not-compact-tagged tool result whose
104
- * content follows the todo echo contract (a [todo] header line + one
103
+ * round 6 (the task round): translate a do-not-compact-tagged tool result whose
104
+ * content follows the task echo contract (a [task] header line + one
105
105
  * `[pending|active|done] text` line per item) into the checklist cell's
106
106
  * structured items. Null = not a checklist — the ordinary result cell
107
107
  * renders. Keyed on the TAG (what the extension declared), never on a
@@ -114,7 +114,7 @@ function parseChecklist(tags, content) {
114
114
  const items = [];
115
115
  let header = "";
116
116
  for (const line of content.split("\n")) {
117
- const head = /^\[todo\] (.*)$/.exec(line);
117
+ const head = /^\[task\] (.*)$/.exec(line);
118
118
  if (head !== null) {
119
119
  header = head[1];
120
120
  continue;
@@ -177,9 +177,23 @@ export async function consumeRun(session, run, input, turnNo, faux, liveInput, s
177
177
  const turnStart = Date.now();
178
178
  let toolCount = 0;
179
179
  let editCount = 0;
180
+ // W14: the thinking event carries NO timestamp — the CLI wall-clocks
181
+ // the thinking window: it opens at the first thinking event and closes
182
+ // at the first non-thinking event (the fold needs the seconds).
183
+ let thoughtSeconds = 0;
184
+ let thinkingSince = null;
180
185
  try {
181
186
  for await (const ev of run) {
182
187
  last = ev;
188
+ if (ev.type !== "thinking") {
189
+ if (thinkingSince !== null) {
190
+ thoughtSeconds += (Date.now() - thinkingSince) / 1000;
191
+ thinkingSince = null;
192
+ }
193
+ }
194
+ else if (thinkingSince === null) {
195
+ thinkingSince = Date.now();
196
+ }
183
197
  // v2a (the double echo): the interactive echo was already rendered by the
184
198
  // input source — rendering the event again is the double echo.
185
199
  // v2b: DOCKED — the echo lives in the input row (H), NOT the body;
@@ -218,8 +232,19 @@ export async function consumeRun(session, run, input, turnNo, faux, liveInput, s
218
232
  break;
219
233
  case "tool_result": {
220
234
  const text = typeof ev.content === "string" ? ev.content : "";
221
- body.toolResult(ev.callId, { content: text, isError: ev.isError });
222
- // round 6 (the todo round): a result tagged do-not-compact whose content
235
+ // W19: a DENIED call carries its reason — extracted from the
236
+ // result's "[Permission denied] " prefix, keyed on the
237
+ // "denied" tag (the parseChecklist discipline: the tag
238
+ // declares, the prefix confirms). The ToolCell renders the
239
+ // pinned row (full name, target, reason, no timing).
240
+ let reason = null;
241
+ if ((ev.tags ?? []).includes("denied")) {
242
+ const m = /^\[Permission denied\] (.*)$/.exec(text);
243
+ if (m !== null)
244
+ reason = m[1];
245
+ }
246
+ body.toolResult(ev.callId, { content: text, isError: ev.isError, reason });
247
+ // round 6 (the task round): a result tagged do-not-compact whose content
223
248
  // follows the checklist shape also renders as the durable
224
249
  // checklist cell (the CLI translates Event → the tui's own
225
250
  // shape; a non-matching parse falls back to the ordinary
@@ -271,12 +296,19 @@ export async function consumeRun(session, run, input, turnNo, faux, liveInput, s
271
296
  // events (zero tokens). The dock's status bar still paints.
272
297
  statusCb?.(usage, estimateCtxRatio(session));
273
298
  const ratio = estimateCtxRatio(session);
299
+ // W14: the turn record closes HERE — before the recap logs, so
300
+ // the commit loop folds the quiet turn's held cells first (the
301
+ // fold line lands above the recap, natural cell order).
302
+ body.endTurn(Math.round(thoughtSeconds));
274
303
  bodyLog(renderRecap({
275
304
  seconds: Math.round((Date.now() - turnStart) / 1000),
276
305
  tools: toolCount,
277
306
  edits: editCount,
278
307
  usage,
279
308
  ctxLeftPct: Number.isFinite(ratio) ? (1 - ratio) * 100 : null,
309
+ // W19: under plan the recap becomes the way-forward row
310
+ // (the /mode hints are the mode's exits).
311
+ mode: getMode(),
280
312
  }));
281
313
  break;
282
314
  }
@@ -436,7 +468,10 @@ export async function chat(session, faux, input, autoCompact) {
436
468
  return;
437
469
  const ratio = estimateCtxRatio(session);
438
470
  const pct = Number.isFinite(ratio) ? Math.round((1 - ratio) * 100) : null;
439
- dock.setStatus(`▸ ${getMode()} · /mode to switch · ${agentModel} · ctx left ~${pct}%`);
471
+ // W19: under plan the idle row makes the posture unmistakable
472
+ // the W4 parentheses idiom names the read-only constraint.
473
+ const tier = getMode() === "plan" ? "plan (read-only)" : getMode();
474
+ dock.setStatus(`▸ ${tier} · /mode to switch · ${agentModel} · ctx left ~${pct}%`);
440
475
  };
441
476
  const statusCb = (u, ctx) => {
442
477
  runUsage = u;
@@ -479,6 +514,10 @@ export async function chat(session, faux, input, autoCompact) {
479
514
  }
480
515
  dispatch(line, dispatchCtx);
481
516
  });
517
+ // W15: the expand key — the editor forwards ctrl+r; dispatch runs the
518
+ // chain action (the sentinel's control char marks the key, so a typed
519
+ // "expand" turn is never intercepted).
520
+ input.onExpand(() => dispatch("\x12expand", dispatchCtx));
482
521
  // Recovery first: a session with a dangling pause or uncertain
483
522
  // executions must resolve them BEFORE the REPL accepts new turns —
484
523
  // otherwise the interrupted run dangles while a new one starts.
package/dist/dispatch.js CHANGED
@@ -66,6 +66,35 @@ export function dispatch(line, ctx) {
66
66
  });
67
67
  return;
68
68
  }
69
+ if (trimmed === "\x12expand") {
70
+ // W15: the expand key (ctrl+r) — /last aimed at a chosen cell.
71
+ // The TARGET is picked at press time: a LIVE tool cell toggles
72
+ // IMMEDIATELY in place (the compositor owns those rows and
73
+ // redraws them — the approval pause is exactly when the user
74
+ // reads a cut diff, and the key must answer then, never after
75
+ // the run). A COMMITTED cell can never toggle — history is never
76
+ // rewritten (ADR-0046) — so its expanded block and the empty
77
+ // answer queue on the chain like /last: the block lands as new
78
+ // content after any in-flight turn. The sentinel carries the
79
+ // control char so a typed "expand" turn is never intercepted.
80
+ const r = body.expandNext();
81
+ if (r.kind === "toggled") {
82
+ ctx.input.prompt(); // in place — the frame already repainted
83
+ return;
84
+ }
85
+ const land = async () => {
86
+ if (r.kind === "appended") {
87
+ for (const line of r.lines)
88
+ bodyLog(line);
89
+ }
90
+ else if (r.kind === "none") {
91
+ bodyLog("[nothing to expand]");
92
+ }
93
+ ctx.input.prompt();
94
+ };
95
+ ctx.chainRef.current = ctx.chainRef.current.then(land);
96
+ return;
97
+ }
69
98
  if (trimmed === "/status") {
70
99
  // B area: session id, durable event count, and the ~ context
71
100
  // estimate — all read straight from the live session, nothing
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@
25
25
  import { readFileSync, rmSync } from "node:fs";
26
26
  import { createInterface } from "node:readline";
27
27
  import { join } from "node:path";
28
- import { Body, Editor, PROMPT as EDITOR_PROMPT, bannerLines, palette, renderSessionLine } from "@vincemakes/kiso-tui";
28
+ import { Body, Editor, bannerLines, palette, renderSessionLine } from "@vincemakes/kiso-tui";
29
29
  import { escapeTerminal } from "@vincemakes/kiso-tui";
30
30
  import { createAgent, disposeExtensions, loadExtensions, loadProjectExtensions, SessionStore, } from "@vincemakes/kiso-runtime";
31
31
  import { createFauxProvider } from "@vincemakes/kiso-evals";
@@ -70,6 +70,10 @@ function readlineInput(rl) {
70
70
  onEscape() {
71
71
  /* readline has no bare-Esc semantics — ignored. */
72
72
  },
73
+ onExpand() {
74
+ /* readline has no ctrl+r binding — ignored (W15 rides the
75
+ * editor path only). */
76
+ },
73
77
  question(query, cb) {
74
78
  rl.question(query, cb);
75
79
  },
@@ -112,6 +116,9 @@ function editorInput(editor) {
112
116
  onEscape(cb) {
113
117
  editor.onEscape(cb);
114
118
  },
119
+ onExpand(cb) {
120
+ editor.onExpand(cb);
121
+ },
115
122
  question(query, cb) {
116
123
  editor.question(query, cb);
117
124
  },
@@ -145,8 +152,10 @@ function makeLineInput() {
145
152
  if (process.stdin.isTTY) {
146
153
  const editor = new Editor(() => (dock.active ? dock.redraw() : editor.selfRender()));
147
154
  editor.enter();
148
- const p = palette();
149
- dock.bindInput(() => editor.dockState(), `${p.bold}${EDITOR_PROMPT}${p.reset}`);
155
+ // W6: the box's prompt goes light — "› " (the box already says
156
+ // "input lives here"; the line-mode path keeps the brick ▌, so
157
+ // pipe bytes do not change)
158
+ dock.bindInput(() => editor.dockState(), "› ");
150
159
  dock.bindMenu(() => editor.menuState()); // v3 §04: the slash-command menu
151
160
  return editorInput(editor);
152
161
  }
package/dist/state.d.ts CHANGED
@@ -27,6 +27,9 @@ export interface LineInput {
27
27
  onSigint(cb: () => void): void;
28
28
  onEot(cb: () => void): void;
29
29
  onEscape(cb: () => void): void;
30
+ /** W15: the expand key (ctrl+r) — the chain-level action, never the
31
+ * editor's own interpretation. */
32
+ onExpand(cb: () => void): void;
30
33
  question(query: string, cb: (answer: string) => void): void;
31
34
  cancelQuestion(): void;
32
35
  emitLine(line: string): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-code",
3
- "version": "0.1.37",
3
+ "version": "0.1.39",
4
4
  "description": "kiso CLI — the coding-agent reference product: kiso chat / kiso resume / kiso sessions.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -24,7 +24,7 @@
24
24
  "@vincemakes/kiso-provider-openai": "0.1.31",
25
25
  "@vincemakes/kiso-runtime": "0.1.31",
26
26
  "@vincemakes/kiso-tools-node": "0.1.31",
27
- "@vincemakes/kiso-tui": "0.1.37"
27
+ "@vincemakes/kiso-tui": "0.1.39"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "^26.1.2",