@vincemakes/kiso-code 0.1.36 → 0.1.37

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/dispatch.js CHANGED
@@ -3,10 +3,10 @@
3
3
  * turns. The bodies moved verbatim from chat()'s closure; chat provides
4
4
  * the context (the chain, the run state, the prompt arming).
5
5
  */
6
- import { escapeTerminal, palette } from "@vincemakes/kiso-tui";
6
+ import { escapeTerminal, kUnit, palette } from "@vincemakes/kiso-tui";
7
7
  import { buildAdapter } from "@vincemakes/kiso-runtime";
8
8
  import { MODES, getMode, setMode } from "./mode.js";
9
- import { agentModel, body, bodyLog, configModels, setAgentModel, setCurrentModelName } from "./state.js";
9
+ import { agentModel, body, bodyLog, configModels, dock, setAgentModel, setCurrentModelName } from "./state.js";
10
10
  import { directWriteProfile, profileAvailable } from "./config.js";
11
11
  /** The ONE dispatcher — slash commands, exit, and turns. The recovery
12
12
  * replay routes through it too — a queued "/last" must never become a
@@ -164,19 +164,76 @@ export function dispatch(line, ctx) {
164
164
  return;
165
165
  }
166
166
  ctx.chainRef.current = ctx.chainRef.current.then(async () => {
167
+ // W18: the compaction indicator — the whole call runs under
168
+ // a live status row (the INDETERMINATE form: the summary is
169
+ // ONE adapter call, no fraction exists — kiso never invents
170
+ // a percentage), with a REAL cancel: esc aborts the signal,
171
+ // which the runtime observes at every phase boundary.
172
+ const abort = new AbortController();
173
+ let compactCancelled = false;
174
+ const onEscape = () => {
175
+ if (abort.signal.aborted)
176
+ return;
177
+ compactCancelled = true;
178
+ abort.abort();
179
+ };
180
+ ctx.input.onEscape(onEscape);
181
+ let compactStart = 0;
182
+ let compactTimer = null;
183
+ // the `as` on the initializer keeps the flow type the full union —
184
+ // onStart fills this during the call, but a closure assignment
185
+ // never re-narrows the outer scope (it would read `never`)
186
+ let compactInfo = null;
187
+ // the ctx estimate BEFORE the summarized event lands (the used
188
+ // fraction — the recap's "ctx 91% → 34%" drops after compacting)
189
+ let ctxBefore = null;
190
+ const compacting = (info) => {
191
+ const text = (elapsed) => `▘ compacting · ${info.rounds} rounds · ~${kUnit(info.tokens)} tokens · ${Math.max(0, elapsed)}s`;
192
+ compactStart = Date.now();
193
+ ctxBefore = Math.round(ctx.estimateCtx() * 100);
194
+ dock.setStatus(text(0), "esc to cancel");
195
+ compactTimer = setInterval(() => {
196
+ dock.setStatus(text(Math.round((Date.now() - compactStart) / 1000)), "esc to cancel");
197
+ }, 1000);
198
+ };
167
199
  try {
168
- const result = await ctx.session.summarize();
200
+ const result = await ctx.session.summarize({
201
+ signal: abort.signal,
202
+ onStart: (info) => {
203
+ compactInfo = info;
204
+ compacting(info);
205
+ },
206
+ });
169
207
  if (result === null) {
170
208
  body.notice("[/compact] nothing to compact — fewer than 5 rounds yet");
171
209
  }
172
210
  else {
173
- body.notice(`[/compact] saved ~${result.savedTokens.toLocaleString("en-US")} tokens`);
211
+ // W18: the settled RECAP replaces the bare saved-token
212
+ // notice — it names what actually happened: the covered
213
+ // rounds, the one summary, the savings, the ctx drop
214
+ // (the estimate BEFORE vs AFTER — the same chars/4
215
+ // proxy the status bar shows, marked ~), and the time.
216
+ const ctxAfter = Math.round(ctx.estimateCtx() * 100);
217
+ const elapsed = compactStart > 0 ? ((Date.now() - compactStart) / 1000).toFixed(1) : "?";
218
+ // a non-null result implies onStart ran — the "?" is
219
+ // reachable only at the type level
220
+ body.notice(`[/compact] ▞ compacted · ${compactInfo?.rounds ?? "?"} rounds → 1 summary · saved ~${kUnit(result.savedTokens)} · ctx ${ctxBefore ?? "?"}% → ${ctxAfter}% · ${elapsed}s`);
174
221
  }
175
222
  }
176
223
  catch (err) {
177
224
  // Honest failure: nothing was persisted, the session
178
225
  // is unchanged (ADR-0044 crash semantics).
179
- body.notice(`[/compact] failed: ${err instanceof Error ? err.message : String(err)}`);
226
+ if (compactCancelled) {
227
+ body.notice("[/compact] cancelled — nothing was persisted");
228
+ }
229
+ else {
230
+ body.notice(`[/compact] failed: ${err instanceof Error ? err.message : String(err)}`);
231
+ }
232
+ }
233
+ finally {
234
+ if (compactTimer !== null)
235
+ clearInterval(compactTimer);
236
+ ctx.paintIdle();
180
237
  }
181
238
  ctx.input.prompt();
182
239
  });
package/dist/index.js CHANGED
@@ -40,24 +40,6 @@ import { resume } from "./resume.js";
40
40
  // The moved exports stay reachable from this entry — the test imports
41
41
  // (project-trust, coding-agent) never change (B4: zero assertion changes).
42
42
  export { applyProjectMerges } from "./trust-ui.js";
43
- /** The banner: the block-letter logo (design fixed). TTY only — pipes, e2e
44
- * drivers, and CI see byte-for-byte the old output; the extensions line
45
- * merges into the third row on TTY and stays a standalone line off-TTY.
46
- * v2a: the logo rows stay dim; the TAGLINE (row 2) is the blue identity
47
- * accent. */
48
- function startupBanner() {
49
- // v3 §01: the banner is block-split — three independent logo rows
50
- // (TOP / tagline / BOTTOM), then TWO info rows (version,
51
- // extensions), each truncated at the window width; < 40 columns
52
- // skips the logo. The historical `[N extensions: names]` text rides
53
- // the extensions row verbatim (the e2e assertions keep matching).
54
- const p = palette();
55
- // A pty without a winsize reports columns = 0 (not undefined) — treat
56
- // it as the default width, never as a 0-column truncation.
57
- const W = process.stdout.columns ?? 0;
58
- const rows = bannerLines(W > 0 ? W : 80, VERSION, bannerExtensionText().replace(/^ · /, ""));
59
- return `${rows.map((r) => `${p.dim}${r}${p.reset}`).join("\n")}\n`;
60
- }
61
43
  /** The v2b behavior, unchanged: readline owns the line, SIGINT, and the
62
44
  * prompt. Only ever constructed when stdin is NOT a TTY. The rl starts
63
45
  * consuming stdin at construction (main), so 'line' events are buffered
@@ -187,11 +169,13 @@ function bannerExtensionText() {
187
169
  parts.push(`project: ${projectExtensions.map(label).join(", ")}`);
188
170
  return ` · [${total} extension${total === 1 ? "" : "s"}: ${parts.join(" · ")}]`;
189
171
  }
190
- /** E1: the startup banner line(s) — TTY: logo + merged extensions; off-TTY:
191
- * the historical `[N extensions: ...]` standalone line (zero change). */
192
- function extensionsBanner() {
172
+ /** E1: the startup banner line(s) — TTY: logo + merged extensions + the
173
+ * W5 resume list as a LIVE banner cell (W1: the tier re-derives on
174
+ * resize; the resume list re-gates with the tier); off-TTY: the
175
+ * historical `[N extensions: ...]` standalone line (zero change). */
176
+ function extensionsBanner(resume = []) {
193
177
  if (process.stdout.isTTY) {
194
- bodyLog(startupBanner());
178
+ body.banner(VERSION, bannerExtensionText().replace(/^ · /, ""), resume);
195
179
  return;
196
180
  }
197
181
  const text = bannerExtensionText();
@@ -199,6 +183,19 @@ function extensionsBanner() {
199
183
  return;
200
184
  bodyLog(`${text}\n`);
201
185
  }
186
+ /** W5: the opening-screen resume list — up to 3 recent sessions, newest
187
+ * first, the CURRENT session excluded (it is not something to pick back
188
+ * up). Every field already exists behind the store's SessionMeta (the
189
+ * `kiso sessions` line shows the same data) — only the projection and
190
+ * the sort live here. */
191
+ function recentSessions(id, agent) {
192
+ return agent
193
+ .sessions()
194
+ .filter((m) => m.id !== id)
195
+ .sort((a, b) => b.updatedAt - a.updatedAt)
196
+ .slice(0, 3)
197
+ .map(({ title, events, runs, updatedAt }) => ({ title, events, runs, updatedAt }));
198
+ }
202
199
  /**
203
200
  * A area: the coding-agent system prompt — ONE constant, byte-stable for the
204
201
  * session's lifetime (D area). Kept under ~80 lines; no template engine.
@@ -409,7 +406,7 @@ async function main() {
409
406
  applyConfigMode();
410
407
  const session = await agent.session({ id });
411
408
  bodyLog(`session ${id}\n`);
412
- extensionsBanner();
409
+ extensionsBanner(recentSessions(id, agent));
413
410
  faux = currentFaux;
414
411
  await chat(session, faux, input, resolveAutoCompact(mergedConfig));
415
412
  break;
@@ -439,7 +436,7 @@ async function main() {
439
436
  }
440
437
  case "help": {
441
438
  const p = palette();
442
- console.log(`${p.dim}${bannerLines(80, VERSION, "").join("\n")}${p.reset}\n\n` +
439
+ console.log(`${p.dim}${bannerLines(80, process.stdout.rows ?? 0, VERSION, "").join("\n")}${p.reset}\n\n` +
443
440
  "kiso — the coding agent that survives kill -9\n\n" +
444
441
  " kiso [sessionId] interactive session (default command)\n" +
445
442
  " kiso chat [sessionId] same as above\n" +
@@ -457,7 +454,7 @@ async function main() {
457
454
  const agent = await makeAgent(fauxSkip(id));
458
455
  const session = await agent.session({ id });
459
456
  bodyLog(`session ${id}\n`);
460
- extensionsBanner();
457
+ extensionsBanner(recentSessions(id, agent));
461
458
  await chat(session, faux, input, autoCompactFromEnv());
462
459
  break;
463
460
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-code",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
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.36"
27
+ "@vincemakes/kiso-tui": "0.1.37"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "^26.1.2",