@vincemakes/kiso-code 0.44.0 → 0.45.1

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
@@ -369,7 +369,9 @@ export declare function consumeRun(session: AgentSession, run: Run, input: LineI
369
369
  /** W21: the amend words ("Yes + feedback") ride the NEXT user turn —
370
370
  * threaded from chat's submitTurn; absent in the recovery flow
371
371
  * (resume) where a dropped amend is noticed instead. */
372
- submitTurn?: (line: string) => void): Promise<import("@vincemakes/kiso-core").Event | undefined>;
372
+ submitTurn?: (line: string) => void,
373
+ /** ADR-0057: a steer landed — an input after the run's own. */
374
+ onInputLanded?: () => void): Promise<import("@vincemakes/kiso-core").Event | undefined>;
373
375
  /** Interactive REPL: stream events, pause for approvals, Ctrl+C aborts. */
374
376
  /** The chat loop's ENDING: exit closes the process's REPL for good; a
375
377
  * switch hands main another session id to re-enter chat with — the
package/dist/chat.js CHANGED
@@ -10,7 +10,7 @@ import { askView, coldResumeLine, coldResumeView, deletionRiskHint, editFileDiff
10
10
  import { canonicalTargetPath, isProtectedPath, protectedIdentity, shellProgressPath } from "@vincemakes/kiso-tools-node";
11
11
  import { queuedSwitchLines } from "./state.js";
12
12
  import { echoText } from "@vincemakes/kiso-tui-cells/render";
13
- import { canonicalizeUsage } from "@vincemakes/kiso-runtime";
13
+ import { canonicalizeUsage, RunClosedError } from "@vincemakes/kiso-runtime";
14
14
  import { canonicalizeUsageForModel, requestBudget } from "@vincemakes/kiso-runtime/internal";
15
15
  import { dispatch, abortBangCommand } from "./dispatch.js";
16
16
  import { paintWindowTitle } from "./window-title.js";
@@ -854,8 +854,11 @@ export async function consumeRun(session, run, input, turnNo, faux, statusCb,
854
854
  /** W21: the amend words ("Yes + feedback") ride the NEXT user turn —
855
855
  * threaded from chat's submitTurn; absent in the recovery flow
856
856
  * (resume) where a dropped amend is noticed instead. */
857
- submitTurn) {
857
+ submitTurn,
858
+ /** ADR-0057: a steer landed — an input after the run's own. */
859
+ onInputLanded) {
858
860
  let last;
861
+ let inputsSeen = 0;
859
862
  // W22: the TURN's usage — null until a call settles, then the sum of
860
863
  // every call in this turn (accumulateUsage). Null rather than an empty
861
864
  // accumulator, so the first call is the sum rather than an addition to
@@ -931,6 +934,9 @@ submitTurn) {
931
934
  // construction (ADR-0040).
932
935
  switch (ev.type) {
933
936
  case "user_input":
937
+ inputsSeen += 1;
938
+ if (inputsSeen > 1)
939
+ onInputLanded?.();
934
940
  // The window title is re-derived here because THIS is when a
935
941
  // session stops being nameless: the tab opened as `kiso —
936
942
  // <workspace>` and the first substantive prompt is what gives
@@ -1294,10 +1300,11 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
1294
1300
  (async () => {
1295
1301
  let last;
1296
1302
  try {
1297
- last = await consumeRun(session, run, input, myTurn, faux, statusCb, submitTurn);
1303
+ last = await consumeRun(session, run, input, myTurn, faux, statusCb, submitTurn, landed);
1298
1304
  stopSpinner();
1299
1305
  paintIdle();
1300
1306
  currentRun = null;
1307
+ handBack(run);
1301
1308
  // 0.40.5: the kiso on disk may no longer be the one this
1302
1309
  // session runs (an upgrade since it started) — said once per
1303
1310
  // installed version (stale-version.ts).
@@ -1335,6 +1342,7 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
1335
1342
  }
1336
1343
  console.error(`\n[run failed] ${err instanceof Error ? err.message : String(err)}\n`);
1337
1344
  currentRun = null;
1345
+ handBack(run);
1338
1346
  input.prompt();
1339
1347
  resolve();
1340
1348
  }
@@ -1347,6 +1355,7 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
1347
1355
  // aborted terminal, which the consumer keeps consuming.
1348
1356
  console.log("\n[aborting run]");
1349
1357
  pendingAsk?.();
1358
+ giveBack();
1350
1359
  currentRun.abort();
1351
1360
  }
1352
1361
  else if (pendingAsk !== null) {
@@ -1379,6 +1388,7 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
1379
1388
  if (currentRun) {
1380
1389
  console.log("\n[aborting run]");
1381
1390
  pendingAsk?.();
1391
+ giveBack();
1382
1392
  currentRun.abort();
1383
1393
  }
1384
1394
  });
@@ -1392,6 +1402,9 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
1392
1402
  return dispatch(line, dispatchCtx);
1393
1403
  console.log("\n[redirecting run]");
1394
1404
  pendingAsk?.();
1405
+ // ADR-0057: steers that had not landed ride the correction — one next
1406
+ // message, not a queue; the correction first (§3: it corrects them).
1407
+ const unsent = handed(currentRun.retract().length);
1395
1408
  currentRun.abort();
1396
1409
  // §3: a correction must run BEFORE the follow-ups queued earlier —
1397
1410
  // it is a correction OF them. The existing slot mechanics compose
@@ -1406,9 +1419,9 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
1406
1419
  const jumped = pendingTurns.map((s) => ({ content: s.content, via: s.via }));
1407
1420
  for (let i = jumped.length; i > 0; i -= 1)
1408
1421
  popQueue();
1409
- submitTurn(line);
1422
+ queueTurn(unsent.length > 0 ? [line, ...unsent.map((u) => u.content)].join("\n\n") : line);
1410
1423
  for (const j of jumped)
1411
- submitTurn(j.content, j.via);
1424
+ queueTurn(j.content, j.via);
1412
1425
  });
1413
1426
  // round 5 (P1-11): the PERSISTENT line listener is installed BEFORE the
1414
1427
  // startup recovery — a cancelled question's re-emitted "line" needs a
@@ -1541,7 +1554,7 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
1541
1554
  return;
1542
1555
  spentUsd = (spentUsd ?? 0) + usd;
1543
1556
  };
1544
- const submitTurn = (line, via) => {
1557
+ const queueTurn = (line, via) => {
1545
1558
  const slot = { line: via?.line ?? line, content: line, ...(via !== undefined ? { via } : {}), cancelled: false };
1546
1559
  pendingTurns.push(slot);
1547
1560
  queued += 1;
@@ -1588,10 +1601,78 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
1588
1601
  queued = Math.max(0, queued - 1);
1589
1602
  return slot.line;
1590
1603
  };
1604
+ // ADR-0057 (the owner's ruling, 2026-09-28): Enter while a run is live
1605
+ // is a STEER — there is no queue. `steering` mirrors what the run holds
1606
+ // and has not admitted; the run takes ALL of it at its next quiescent
1607
+ // boundary, and the landed user_input clears the rows. Piped and
1608
+ // task-file input keep one turn per line: a script has nobody steering.
1609
+ const steerable = process.stdin.isTTY === true && input.literal !== true;
1610
+ let steering = [];
1611
+ const steer = (line, via) => {
1612
+ if (!steerable || currentRun === null)
1613
+ return false;
1614
+ try {
1615
+ currentRun.steer(via !== undefined ? line : attachImages(line, input.attachments?.(), protectedFiles()));
1616
+ }
1617
+ catch (err) {
1618
+ if (err instanceof RunClosedError)
1619
+ return false; // the run is ending: this is the next turn
1620
+ throw err;
1621
+ }
1622
+ steering.push({ line: via?.line ?? line, content: line, ...(via !== undefined ? { via } : {}) });
1623
+ return true;
1624
+ };
1625
+ const submitTurn = (line, via) => {
1626
+ if (!steer(line, via))
1627
+ queueTurn(line, via);
1628
+ };
1629
+ /** The mirror of the last `n` steers the run handed back; clears it. */
1630
+ const handed = (n) => {
1631
+ const out = n > 0 ? steering.slice(-n) : [];
1632
+ steering = [];
1633
+ return out;
1634
+ };
1635
+ const landed = () => {
1636
+ steering = [];
1637
+ };
1638
+ /** A run sealed with steers still pending (max_turns, an END_TURN
1639
+ * result, an error): they are the person's next message, sent now. */
1640
+ const handBack = (run) => {
1641
+ const left = handed(run.unadmitted().length);
1642
+ if (left.length === 1)
1643
+ queueTurn(left[0].content, left[0].via);
1644
+ else if (left.length > 1)
1645
+ queueTurn(left.map((s) => s.content).join("\n\n"));
1646
+ };
1647
+ /** Esc / ctrl+c: a stop is not a send — steers that have not landed
1648
+ * go back to the editor. */
1649
+ const giveBack = () => {
1650
+ if (currentRun === null)
1651
+ return;
1652
+ const back = handed(currentRun.retract().length);
1653
+ if (back.length > 0)
1654
+ input.restore?.(back.map((s) => s.line).join("\n"));
1655
+ };
1656
+ /** ↑ takes back the LAST steer that has not landed (the others go back
1657
+ * in, in order); with none, the pipe-era queue pop. */
1658
+ const popPending = () => {
1659
+ if (currentRun !== null && steering.length > 0) {
1660
+ const back = currentRun.retract();
1661
+ const mine = handed(back.length);
1662
+ const last = mine.pop();
1663
+ for (const c of back.slice(0, -1))
1664
+ currentRun.steer(c);
1665
+ steering = mine;
1666
+ if (last !== undefined)
1667
+ return last.line;
1668
+ }
1669
+ return popQueue();
1670
+ };
1591
1671
  // W22: the visibility invariant's binds — the dock renders the
1592
- // pending chips (+N queued), the editor routes the pop keys.
1593
- dock.bindQueue(() => pendingTurns.map((s) => s.line));
1594
- input.bindQueue(() => pendingTurns.map((s) => s.line), popQueue);
1672
+ // pending chips, the editor routes the pop keys.
1673
+ const pendingLines = () => [...steering.map((s) => s.line), ...pendingTurns.map((s) => s.line)];
1674
+ dock.bindQueue(pendingLines);
1675
+ input.bindQueue(pendingLines, popPending);
1595
1676
  const dispatchCtx = {
1596
1677
  session,
1597
1678
  input,
@@ -1717,8 +1798,9 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
1717
1798
  runStart = Date.now();
1718
1799
  runUsage = { in: null, out: null, cache: null, known: false };
1719
1800
  lastTokPerSec = null; // TPS-1: nothing has settled in THIS turn yet
1720
- const last = await consumeRun(session, recoveryRun, input, turnNo, faux, statusCb, submitTurn);
1801
+ const last = await consumeRun(session, recoveryRun, input, turnNo, faux, statusCb, submitTurn, landed);
1721
1802
  currentRun = null;
1803
+ handBack(recoveryRun);
1722
1804
  failOnFauxExhaustion(last, faux, input);
1723
1805
  // E group (the graceful-exit gate ③): the same deferred re-check
1724
1806
  // as the turn path — the recovery run's end is also a safe point.
package/dist/index.js CHANGED
@@ -319,6 +319,10 @@ function editorInput(editor) {
319
319
  bindQueue(state, pop) {
320
320
  editor.bindQueue(state, pop);
321
321
  },
322
+ // ADR-0057: the steers a stop handed back return to the buffer.
323
+ restore(text) {
324
+ editor.restore(text);
325
+ },
322
326
  // R3a: cross-session history — the CLI owns the file I/O.
323
327
  bindHistory(seed, persist) {
324
328
  editor.bindHistory(seed, persist);
package/dist/state.d.ts CHANGED
@@ -179,6 +179,9 @@ export interface LineInput {
179
179
  * live slots (each pop cancels the turn), esc ends the walk after
180
180
  * one more pop. The chips are the compositor's own bindQueue. */
181
181
  bindQueue(state: () => readonly string[], pop: () => string | null): void;
182
+ /** ADR-0057: put text back into the editor — the steers a stop handed
183
+ * back before they landed. The pipe path has no editor; optional. */
184
+ restore?(text: string): void;
182
185
  /** R3a: cross-session history — seed the recall buffer, register the
183
186
  * append sink. The pipe path has no recall keys; optional. */
184
187
  bindHistory?(seed: readonly string[], persist: (line: string) => void): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-code",
3
- "version": "0.44.0",
3
+ "version": "0.45.1",
4
4
  "description": "kiso-code \u2014 the coding agent on kiso, the durable runtime for AI agents: 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.44.0",
22
- "@vincemakes/kiso-core": "0.44.0",
23
- "@vincemakes/kiso-evals": "0.44.0",
24
- "@vincemakes/kiso-mcp-ext": "0.44.0",
25
- "@vincemakes/kiso-provider-anthropic": "0.44.0",
26
- "@vincemakes/kiso-provider-openai": "0.44.0",
27
- "@vincemakes/kiso-provider-openai-responses": "0.44.0",
28
- "@vincemakes/kiso-runtime": "0.44.0",
29
- "@vincemakes/kiso-skills-ext": "0.44.0",
30
- "@vincemakes/kiso-subagent-ext": "0.44.0",
31
- "@vincemakes/kiso-tools-node": "0.44.0",
32
- "@vincemakes/kiso-tui": "0.44.0",
33
- "@vincemakes/kiso-tui-cells": "0.44.0"
21
+ "@vincemakes/kiso-ask-ext": "0.45.1",
22
+ "@vincemakes/kiso-core": "0.45.1",
23
+ "@vincemakes/kiso-evals": "0.45.1",
24
+ "@vincemakes/kiso-mcp-ext": "0.45.1",
25
+ "@vincemakes/kiso-provider-anthropic": "0.45.1",
26
+ "@vincemakes/kiso-provider-openai": "0.45.1",
27
+ "@vincemakes/kiso-provider-openai-responses": "0.45.1",
28
+ "@vincemakes/kiso-runtime": "0.45.1",
29
+ "@vincemakes/kiso-skills-ext": "0.45.1",
30
+ "@vincemakes/kiso-subagent-ext": "0.45.1",
31
+ "@vincemakes/kiso-tools-node": "0.45.1",
32
+ "@vincemakes/kiso-tui": "0.45.1",
33
+ "@vincemakes/kiso-tui-cells": "0.45.1"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^26.1.2",