@vincemakes/kiso-code 0.6.0 → 0.8.0

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/builtin.d.ts CHANGED
@@ -1,2 +1,8 @@
1
+ import { type AskUI } from "@vincemakes/kiso-ask-ext";
1
2
  import type { KisoExtension } from "@vincemakes/kiso-runtime";
2
- export declare function builtInLayer(user: readonly KisoExtension[], project: readonly KisoExtension[]): Promise<readonly KisoExtension[]>;
3
+ export declare function builtInLayer(user: readonly KisoExtension[], project: readonly KisoExtension[],
4
+ /** KC3.5: built-in #4 — the ask extension, loaded ONLY when a panel
5
+ * bridge exists (an interactive TTY). No bridge, no fourth built-in:
6
+ * a headless session never pays the rent for a question nobody could
7
+ * answer, and its tool table cannot mention ask_user. */
8
+ ask?: AskUI): Promise<readonly KisoExtension[]>;
package/dist/builtin.js CHANGED
@@ -24,8 +24,14 @@
24
24
  import createMcp from "@vincemakes/kiso-mcp-ext";
25
25
  import createSkills from "@vincemakes/kiso-skills-ext";
26
26
  import createSubagent from "@vincemakes/kiso-subagent-ext";
27
- export async function builtInLayer(user, project) {
28
- const all = await Promise.all([createMcp(), createSkills(), createSubagent()]);
27
+ import createAsk, {} from "@vincemakes/kiso-ask-ext";
28
+ export async function builtInLayer(user, project,
29
+ /** KC3.5: built-in #4 — the ask extension, loaded ONLY when a panel
30
+ * bridge exists (an interactive TTY). No bridge, no fourth built-in:
31
+ * a headless session never pays the rent for a question nobody could
32
+ * answer, and its tool table cannot mention ask_user. */
33
+ ask) {
34
+ const all = await Promise.all([createMcp(), createSkills(), createSubagent(), ...(ask === undefined ? [] : [createAsk(ask)])]);
29
35
  const shadowed = all.filter((b) => user.some((u) => u.name === b.name));
30
36
  for (const s of shadowed) {
31
37
  console.error(`[extensions] user extension "${s.name}" shadows the built-in — the built-in is not loaded`);
package/dist/dispatch.js CHANGED
@@ -3,7 +3,7 @@
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, kUnit, palette } from "@vincemakes/kiso-tui";
6
+ import { escapeTerminal, helpRows, kUnit, palette } from "@vincemakes/kiso-tui";
7
7
  import { buildAdapter } from "@vincemakes/kiso-runtime/internal";
8
8
  import { MODES, getMode, setMode } from "./mode.js";
9
9
  import { agentModel, body, bodyLog, configModels, dock, setAgentModel, setCurrentModelName } from "./state.js";
@@ -15,22 +15,16 @@ import { directWriteProfile, profileAvailable } from "./config.js";
15
15
  export function dispatch(line, ctx) {
16
16
  const trimmed = line.trim();
17
17
  if (trimmed === "/help") {
18
- // Prints the available commands with one-line descriptions.
19
- // v2a: the command names are the blue identity accent.
20
- const p = palette();
21
- const cmd = (name, desc) => `${p.bold}${name}${p.reset} ${desc}`;
18
+ // KC3.5 slice ⓪ (the extraction): the ROWS moved to the terminal
19
+ // layer's strings module (helpRows the KC3 §1 pattern: what the
20
+ // human reads is presentation). The FLOW is what stays here, and
21
+ // it is unchanged: print on the chain, then re-prompt. The rows
22
+ // are byte-identical to the eight bodyLog calls they replace —
23
+ // the last one still carries its own \n, so `exit` and `keys`
24
+ // land as two rows from one call (bodyLog splits on \n).
22
25
  ctx.chainRef.current = ctx.chainRef.current.then(async () => {
23
- bodyLog(cmd("/help", "print this list of commands"));
24
- bodyLog(cmd("/think", "show the last full thinking block"));
25
- bodyLog(cmd("/last", "show the most recent tool call's input and output"));
26
- bodyLog(cmd("/status", "show session id, event count, and context estimate"));
27
- bodyLog(cmd("/mode", "show the approval tier; /mode <name> switches (manual/default/accept-edits/plan/bypass)"));
28
- bodyLog(cmd("/model", "list model profiles; /model <name|provider/model> switches"));
29
- bodyLog(cmd("/compact", "summarize the older conversation to free context"));
30
- // KC1: the composer's keys ride the SAME bodyLog call (it splits
31
- // on \n) — the help gains a row, the cli source does not.
32
- // KC2: the redirect joins the same row for the same reason.
33
- bodyLog(`${cmd("exit", "leave the session")}\n${cmd("keys", "enter sends · ctrl+J newline (shift+enter where encoded) · esc stops the run · alt+⏎ stops it and sends this instead")}`);
26
+ for (const row of helpRows())
27
+ bodyLog(row);
34
28
  ctx.input.prompt();
35
29
  });
36
30
  return;
package/dist/index.js CHANGED
@@ -26,14 +26,14 @@ import { readFileSync, realpathSync, rmSync } from "node:fs";
26
26
  import { createInterface } from "node:readline";
27
27
  import { fileURLToPath } from "node:url";
28
28
  import { join } from "node:path";
29
- import { Body, Editor, bannerLines, escapeTerminal, palette, renderSessionLine } from "@vincemakes/kiso-tui";
29
+ import { Body, Editor, bannerLines, escapeTerminal, extensionsBannerText, interactivePrompt, palette, renderSessionLine } from "@vincemakes/kiso-tui";
30
30
  import { createAgent, disposeExtensions, loadExtensions, loadProjectExtensions, SessionStore, } from "@vincemakes/kiso-runtime";
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
34
  import { builtInLayer } from "./builtin.js";
35
- import { body, bodyLog, builtInExtensions, currentFaux, dock, extensionsDir, loadedExtensions, mergedConfig, mergedTempPaths, projectExtensions, sessionsDir, setAgentModel, setBody, setConfigModels, setConfiguredWindow, setCurrentAgentExtensions, setCurrentFaux, setCurrentModelName, setExtensionLists, setMergedConfig, userExtensions, VERSION } from "./state.js";
36
- import { interactivePrompt, resolveProjectTrust } from "./trust-ui.js";
35
+ import { atFiles, body, bodyLog, builtInExtensions, currentFaux, dock, extensionsDir, loadedExtensions, mergedConfig, mergedTempPaths, projectExtensions, sessionsDir, setAgentModel, setBody, setConfigModels, setConfiguredWindow, setCurrentAgentExtensions, setCurrentFaux, setCurrentModelName, setExtensionLists, setMergedConfig, userExtensions, VERSION } from "./state.js";
36
+ import { askUi, resolveProjectTrust } from "./trust-ui.js";
37
37
  import { isFirstRun, scaffoldFirstRun } from "./first-run.js";
38
38
  import { fauxSkip, readFauxScript } from "./faux-glue.js";
39
39
  import { autoCompactFromEnv, chat, contextWindowTokens } from "./chat.js";
@@ -196,6 +196,8 @@ function makeLineInput() {
196
196
  // pipe bytes do not change)
197
197
  dock.bindInput(() => editor.dockState(), "› ");
198
198
  dock.bindMenu(() => editor.menuState()); // v3 §04: the slash-command menu
199
+ editor.bindAtItems(atFiles); // KC3 §5: the file source — listed per OPEN
200
+ dock.bindAt(() => editor.atState()); // KC3 §4: the picker's band
199
201
  dock.bindApproval(() => editor.panelState()); // W21: the panel's bound state
200
202
  return editorInput(editor);
201
203
  }
@@ -207,20 +209,11 @@ function makeLineInput() {
207
209
  * a fresh install reads `[3 extensions: built-in: mcp, skills, subagent]`
208
210
  * with zero disk setup (E5: the task extension is opt-in). */
209
211
  function bannerExtensionText() {
210
- const total = builtInExtensions.length + userExtensions.length + projectExtensions.length;
211
- if (total === 0)
212
- return "";
213
- const parts = [];
214
- // 0.1.26 (MCP lazy connection): an extension with a live `connecting` flag shows
215
- // its in-flight state in the banner — "mcp (connecting…)".
216
- const label = (e) => e.connecting === true ? `${e.name} (connecting…)` : e.name;
217
- if (builtInExtensions.length > 0)
218
- parts.push(`built-in: ${builtInExtensions.map(label).join(", ")}`);
219
- if (userExtensions.length > 0)
220
- parts.push(userExtensions.map(label).join(", "));
221
- if (projectExtensions.length > 0)
222
- parts.push(`project: ${projectExtensions.map(label).join(", ")}`);
223
- return ` · [${total} extension${total === 1 ? "" : "s"}: ${parts.join(" · ")}]`;
212
+ // KC3.5 slice (the extraction): the composition moved to the
213
+ // terminal layer (extensionsBannerText a pure function of the three
214
+ // name lists, including the "(connecting…)" in-flight label). Which
215
+ // lists exist is the CLI's fact and stays here.
216
+ return extensionsBannerText(builtInExtensions, userExtensions, projectExtensions);
224
217
  }
225
218
  /** E1: the startup banner line(s) — TTY: logo + merged extensions + the
226
219
  * W5 resume list as a LIVE banner cell (W1: the tier re-derives on
@@ -372,7 +365,11 @@ async function makeAgent(sessionId, input, modelFlag) {
372
365
  const proj = project !== null ? await loadProjectExtensions(process.cwd(), user) : [];
373
366
  // R-D 0.1.45: the built-in layer registers by module import (builtin.ts)
374
367
  // — a user extension may shadow a built-in, a project one may not.
375
- const builtIn = await builtInLayer(user, proj);
368
+ // KC3.5: built-in #4 (ask) registers ONLY where a human can answer —
369
+ // the panel bridge is the argument, and a non-TTY session has none to
370
+ // give. A piped run's composed tool table therefore cannot contain
371
+ // ask_user (T-Q3: the bench's structural byte-identity proof).
372
+ const builtIn = await builtInLayer(user, proj, input !== undefined && process.stdin.isTTY ? askUi(input) : undefined);
376
373
  setExtensionLists(builtIn, user, proj, [...builtIn, ...user, ...proj]);
377
374
  // merge round B — the config surface: user config + (trusted) project config,
378
375
  // resolved with flags > env > project > user > default. The CLI never
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, type PanelVerdict, type PanelView } from "@vincemakes/kiso-tui";
8
+ import { Dock, type AtItem, 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
@@ -15,6 +15,30 @@ export declare function kisoHome(): string;
15
15
  export declare function sessionsDir(): string;
16
16
  /** E1: the extension scan directory — KISO_EXTENSIONS_DIR overrides. */
17
17
  export declare function extensionsDir(): string;
18
+ /**
19
+ * KC3 §5 — the @ picker's file source. Computed PER OPEN: no index, no
20
+ * daemon, no watcher, nothing to invalidate and nothing to go stale.
21
+ * (The editor snapshots the result for the life of one open, so this
22
+ * runs once per `@`, not once per keystroke.)
23
+ *
24
+ * In a git repo, git IS the answer. `ls-files -c -o --exclude-standard`
25
+ * is the tracked files AND the untracked ones that are not ignored, in
26
+ * ONE process: the same set the user's own tooling calls "the project",
27
+ * with every .gitignore in the tree already honoured — and honoured by
28
+ * git rather than by a reimplementation of git's rules. stderr is
29
+ * discarded because "not a git repository" must never land in a live
30
+ * TUI frame.
31
+ *
32
+ * Outside a repo — or with no git on PATH — the bounded walk stands
33
+ * in: it prunes AT_SKIP before descending and stops at AT_CAP + 1
34
+ * entries, the extra entry being what lets the panel's counter SAY it
35
+ * truncated instead of showing the first two thousand files as though
36
+ * they were all of them.
37
+ *
38
+ * Paths are forward-slashed on both branches (git's own format), so
39
+ * the fuzzy filter sees one alphabet whichever branch ran.
40
+ */
41
+ export declare function atFiles(): readonly AtItem[];
18
42
  /**
19
43
  * v2c — the interactive input source. TTYs use the raw-mode Editor (the
20
44
  * self-drawn input row — width-aware, the CJK-drift root cause retired,
package/dist/state.js CHANGED
@@ -5,11 +5,12 @@
5
5
  * creates the mutable ones (setBody / setAgentModel / setExtensionLists);
6
6
  * the moved modules read and mutate at call time.
7
7
  */
8
- import { readFileSync } from "node:fs";
8
+ import { execFileSync } from "node:child_process";
9
+ import { readdirSync, readFileSync } from "node:fs";
9
10
  import { homedir } from "node:os";
10
11
  import { dirname, join } from "node:path";
11
12
  import { fileURLToPath } from "node:url";
12
- import { Dock } from "@vincemakes/kiso-tui";
13
+ import { AT_CAP, AT_SKIP, Dock } from "@vincemakes/kiso-tui";
13
14
  /** finding #11: KISO_HOME is the ONE root — every default path derives from
14
15
  * it (sessions, trust, extensions, mcp config, skills). The dedicated
15
16
  * env vars (KISO_EXTENSIONS_DIR / KISO_MCP_CONFIG / KISO_SKILLS_DIR)
@@ -24,6 +25,62 @@ export function sessionsDir() {
24
25
  export function extensionsDir() {
25
26
  return process.env.KISO_EXTENSIONS_DIR ?? join(kisoHome(), "extensions");
26
27
  }
28
+ /**
29
+ * KC3 §5 — the @ picker's file source. Computed PER OPEN: no index, no
30
+ * daemon, no watcher, nothing to invalidate and nothing to go stale.
31
+ * (The editor snapshots the result for the life of one open, so this
32
+ * runs once per `@`, not once per keystroke.)
33
+ *
34
+ * In a git repo, git IS the answer. `ls-files -c -o --exclude-standard`
35
+ * is the tracked files AND the untracked ones that are not ignored, in
36
+ * ONE process: the same set the user's own tooling calls "the project",
37
+ * with every .gitignore in the tree already honoured — and honoured by
38
+ * git rather than by a reimplementation of git's rules. stderr is
39
+ * discarded because "not a git repository" must never land in a live
40
+ * TUI frame.
41
+ *
42
+ * Outside a repo — or with no git on PATH — the bounded walk stands
43
+ * in: it prunes AT_SKIP before descending and stops at AT_CAP + 1
44
+ * entries, the extra entry being what lets the panel's counter SAY it
45
+ * truncated instead of showing the first two thousand files as though
46
+ * they were all of them.
47
+ *
48
+ * Paths are forward-slashed on both branches (git's own format), so
49
+ * the fuzzy filter sees one alphabet whichever branch ran.
50
+ */
51
+ export function atFiles() {
52
+ let paths;
53
+ try {
54
+ paths = execFileSync("git", ["ls-files", "-c", "-o", "--exclude-standard"], { cwd: process.cwd(), encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], maxBuffer: 1 << 24 }).split("\n").filter((p) => p !== "");
55
+ }
56
+ catch {
57
+ paths = atWalk(process.cwd(), "", []);
58
+ }
59
+ return paths.slice(0, AT_CAP + 1).map((path) => ({ path }));
60
+ }
61
+ /** The fallback walk. `prefix` carries the repo-relative directory so
62
+ * the result needs no path arithmetic afterwards. An unreadable
63
+ * directory contributes nothing and ends nothing — the recursion's own
64
+ * try catches it at that level, so one locked subtree cannot stop a
65
+ * file picker from opening. */
66
+ function atWalk(dir, prefix, out) {
67
+ try {
68
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
69
+ if (out.length > AT_CAP)
70
+ break;
71
+ if (AT_SKIP.has(e.name))
72
+ continue;
73
+ if (e.isDirectory())
74
+ atWalk(`${dir}/${e.name}`, `${prefix}${e.name}/`, out);
75
+ else if (e.isFile())
76
+ out.push(`${prefix}${e.name}`);
77
+ }
78
+ }
79
+ catch {
80
+ // unreadable — skipped
81
+ }
82
+ return out;
83
+ }
27
84
  /** v2b: the bottom-anchored UI — docked only on a color TTY; pipes and
28
85
  * NO_COLOR stay the v2a line mode byte-for-byte. Created at load, like
29
86
  * the pre-split module-scope const. */
@@ -6,14 +6,10 @@
6
6
  * decisions. All bodies moved verbatim from index.ts.
7
7
  */
8
8
  import { type PanelVerdict, type PanelView } from "@vincemakes/kiso-tui";
9
+ import type { AskUI } from "@vincemakes/kiso-ask-ext";
9
10
  import { type ProjectArtifacts } from "@vincemakes/kiso-runtime";
10
11
  import type { AgentSession } from "@vincemakes/kiso-runtime";
11
12
  import { type LineInput } from "./state.js";
12
- /** v2a: the interactive prompt — blue, the identity accent. readline owns
13
- * the echo of what the user types; we own the prompt's color. (v2c: the
14
- * readline prompt keeps "you> " — the brick ▌ is the dock's row only;
15
- * pipe bytes must not change.) */
16
- export declare function interactivePrompt(): string;
17
13
  /**
18
14
  * W21 — ask the human with the approval panel: the bounded block that
19
15
  * replaces the running tool's live window while the approval is pending.
@@ -39,6 +35,24 @@ export declare function interactivePrompt(): string;
39
35
  */
40
36
  export declare let pendingAsk: (() => void) | null;
41
37
  export declare function askPanel(input: LineInput, view: PanelView): Promise<PanelVerdict>;
38
+ /**
39
+ * KC3.5 — the AskUI bridge: the panel the ask extension asks through.
40
+ *
41
+ * It is deliberately three lines of GLUE. Everything an ask needs was
42
+ * already built for W21 and is reused whole: askPanel owns the
43
+ * abortable ask (the SIGINT/esc path resolves it), the non-interactive
44
+ * refusal (printed loudly, never hung) and the dock-less fallback; the
45
+ * editor owns the keys and the buffer stash; the tui owns the rows and
46
+ * the walk. What is left here is the mapping — the panel's `answers`
47
+ * verdict IS the tool's result, and EVERY other verdict is the decline,
48
+ * which is an honest recorded outcome naming what went unanswered.
49
+ *
50
+ * (A cancel, a non-interactive deny and a dock-less y/n all land in the
51
+ * same place on purpose: none of them is an answer to a multiple-choice
52
+ * question, and inventing one would be the dishonesty the whole round
53
+ * is built to avoid.)
54
+ */
55
+ export declare function askUi(input: LineInput): AskUI;
42
56
  /**
43
57
  * W21/R3 — the "2 Yes, don't ask again for <tool>" rule: a GENERATED
44
58
  * extension, ALLOW-ONLY by construction (it never emits deny or ask —
package/dist/trust-ui.js CHANGED
@@ -9,19 +9,11 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, symlinkS
9
9
  import { homedir, tmpdir } from "node:os";
10
10
  import { join } from "node:path";
11
11
  import { pathToFileURL } from "node:url";
12
- import { escapeTerminal, palette } from "@vincemakes/kiso-tui";
12
+ import { askDeclineAll, askView, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView, unansweredAskView } from "@vincemakes/kiso-tui";
13
13
  import { projectArtifacts, recordTrust, trustFor } from "@vincemakes/kiso-runtime";
14
14
  import { bodyLog, currentAgentExtensions, dock, extensionsDir, kisoHome, mergedTempPaths } from "./state.js";
15
15
  import { loadUserConfig, resolveProjectTrustPolicy } from "./config.js";
16
16
  import { getMode } from "./mode.js";
17
- /** v2a: the interactive prompt — blue, the identity accent. readline owns
18
- * the echo of what the user types; we own the prompt's color. (v2c: the
19
- * readline prompt keeps "you> " — the brick ▌ is the dock's row only;
20
- * pipe bytes must not change.) */
21
- export function interactivePrompt() {
22
- const p = palette();
23
- return `${p.bold}you> ${p.reset}`;
24
- }
25
17
  /**
26
18
  * W21 — ask the human with the approval panel: the bounded block that
27
19
  * replaces the running tool's live window while the approval is pending.
@@ -91,6 +83,31 @@ export function askPanel(input, view) {
91
83
  }
92
84
  });
93
85
  }
86
+ /**
87
+ * KC3.5 — the AskUI bridge: the panel the ask extension asks through.
88
+ *
89
+ * It is deliberately three lines of GLUE. Everything an ask needs was
90
+ * already built for W21 and is reused whole: askPanel owns the
91
+ * abortable ask (the SIGINT/esc path resolves it), the non-interactive
92
+ * refusal (printed loudly, never hung) and the dock-less fallback; the
93
+ * editor owns the keys and the buffer stash; the tui owns the rows and
94
+ * the walk. What is left here is the mapping — the panel's `answers`
95
+ * verdict IS the tool's result, and EVERY other verdict is the decline,
96
+ * which is an honest recorded outcome naming what went unanswered.
97
+ *
98
+ * (A cancel, a non-interactive deny and a dock-less y/n all land in the
99
+ * same place on purpose: none of them is an answer to a multiple-choice
100
+ * question, and inventing one would be the dishonesty the whole round
101
+ * is built to avoid.)
102
+ */
103
+ export function askUi(input) {
104
+ return {
105
+ ask: async (spec) => {
106
+ const verdict = await askPanel(input, askView(spec));
107
+ return verdict.action === "answers" ? verdict.result : askDeclineAll(spec);
108
+ },
109
+ };
110
+ }
94
111
  /**
95
112
  * W21/R3 — the "2 Yes, don't ask again for <tool>" rule: a GENERATED
96
113
  * extension, ALLOW-ONLY by construction (it never emits deny or ask —
@@ -192,7 +209,7 @@ export async function resolveProjectTrust(input) {
192
209
  // First discovery — list every artifact (file name + digest short
193
210
  // prefix) and ask the human ONCE.
194
211
  if (!process.stdin.isTTY) {
195
- console.error(`[project .kiso] found ${artifacts.files.length} artifact(s) in ${artifacts.root} — not trusted, not loaded (run kiso interactively once to decide)`);
212
+ console.error(projectUntrustedNote(artifacts.files.length, artifacts.root));
196
213
  return null;
197
214
  }
198
215
  // v2c: the shared input (the editor on a TTY) reads the answer.
@@ -201,19 +218,9 @@ export async function resolveProjectTrust(input) {
201
218
  // the bodyLog below records, verbatim — the listing still lands in
202
219
  // the scrollback; the panel is a bounded block, the record is not).
203
220
  bodyLog(`[project .kiso] ${artifacts.root}`);
204
- for (const f of artifacts.files) {
205
- bodyLog(` ${f.path} (${f.digest.slice(0, 6)})`);
206
- }
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
- });
221
+ for (const row of projectTrustRows(artifacts.files, " "))
222
+ bodyLog(row);
223
+ const verdict = await askPanel(input, projectTrustView(artifacts.root, artifacts.files));
217
224
  // A cancel is a "no" HERE — refused is sticky, the project does not
218
225
  // load (re-evaluate by deleting the trust line or changing a file).
219
226
  // The non-TTY branch above returned WITHOUT a record so an interactive
@@ -317,16 +324,12 @@ function readdirSyncSafe(dir) {
317
324
  * the body is the single stdout writer, never a stray console.log. */
318
325
  export async function resolveUncertains(session, input, isCancelled) {
319
326
  for (const uncertain of session.uncertainExecutions()) {
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
- });
327
+ // KC3.5 §4: an interrupted ask_user is not a side effect that may
328
+ // have applied — it is a question nobody answered. The COPY says
329
+ // so; the mechanism is untouched (allow → the runtime's own rerun
330
+ // resolution, whose error-fill text this round never edits).
331
+ const view = uncertain.name === "ask_user" ? unansweredAskView(uncertain.executionId) : uncertainView(uncertain.name, uncertain.executionId);
332
+ const verdict = await askPanel(input, view);
330
333
  if (isCancelled() || verdict.action === "cancel") {
331
334
  // round 10: a cancellation NEVER records a verdict — the execution
332
335
  // stays uncertain and durable; no rerun/abandoned is fabricated.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-code",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
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",
@@ -18,18 +18,19 @@
18
18
  "test": "vitest run"
19
19
  },
20
20
  "dependencies": {
21
- "@vincemakes/kiso-core": "0.6.0",
22
- "@vincemakes/kiso-evals": "0.6.0",
23
- "@vincemakes/kiso-mcp-ext": "0.6.0",
24
- "@vincemakes/kiso-provider-anthropic": "0.6.0",
25
- "@vincemakes/kiso-provider-openai": "0.6.0",
26
- "@vincemakes/kiso-runtime": "0.6.0",
27
- "@vincemakes/kiso-skills-ext": "0.6.0",
28
- "@vincemakes/kiso-subagent-ext": "0.6.0",
29
- "@vincemakes/kiso-task-ext": "0.6.0",
30
- "@vincemakes/kiso-tools-node": "0.6.0",
31
- "@vincemakes/kiso-tui": "0.6.0",
32
- "@vincemakes/kiso-tui-cells": "0.6.0"
21
+ "@vincemakes/kiso-ask-ext": "0.8.0",
22
+ "@vincemakes/kiso-core": "0.8.0",
23
+ "@vincemakes/kiso-evals": "0.8.0",
24
+ "@vincemakes/kiso-mcp-ext": "0.8.0",
25
+ "@vincemakes/kiso-provider-anthropic": "0.8.0",
26
+ "@vincemakes/kiso-provider-openai": "0.8.0",
27
+ "@vincemakes/kiso-runtime": "0.8.0",
28
+ "@vincemakes/kiso-skills-ext": "0.8.0",
29
+ "@vincemakes/kiso-subagent-ext": "0.8.0",
30
+ "@vincemakes/kiso-task-ext": "0.8.0",
31
+ "@vincemakes/kiso-tools-node": "0.8.0",
32
+ "@vincemakes/kiso-tui": "0.8.0",
33
+ "@vincemakes/kiso-tui-cells": "0.8.0"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@types/node": "^26.1.2",