@vincemakes/kiso-code 0.51.1 → 0.53.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.
@@ -21,7 +21,7 @@
21
21
  */
22
22
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
23
23
  import { dirname, join } from "node:path";
24
- import { kisoHome } from "../state.js";
24
+ import { kisoHome } from "@vincemakes/kiso-runtime";
25
25
  export class AuthError extends Error {
26
26
  }
27
27
  /** The providers a credential can be stored for (the runtime's built-in
package/dist/chat.d.ts CHANGED
@@ -411,5 +411,7 @@ export interface ChatNav {
411
411
  /** 0.40.0: an id outside this project's folder — refused, or opened where it is */
412
412
  readonly route?: (id: string) => SessionRoute | null;
413
413
  readonly pick?: () => Promise<string | null>;
414
+ /** the session's first message was sent: the caller may title the session from it (Session.title) */
415
+ readonly titled?: (firstMessage: string) => void;
414
416
  }
415
417
  export declare function chat(session: AgentSession, faux: boolean, input: LineInput, autoCompact?: AutoCompact, nav?: ChatNav, seed?: readonly string[]): Promise<ChatEnd>;
package/dist/chat.js CHANGED
@@ -1386,7 +1386,11 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
1386
1386
  // 0.40.0: a skill turn is not scanned either — its text is a
1387
1387
  // SKILL.md body, and a body that mentions `diagram.png` must not
1388
1388
  // attach a file from the workspace the person never pointed at.
1389
+ input.setGhost?.(null); // a new turn: the last run's follow-up no longer applies
1389
1390
  const content = via !== undefined ? text : attachImages(text, input.attachments?.(), protectedFiles());
1391
+ // the person's first message of the session: a title is made from it beside the run, never in its way
1392
+ if (via === undefined && !session.log.all.some((e) => e.type === "user_input"))
1393
+ nav?.titled?.(text);
1390
1394
  // ADR-0058: a task notice is the runtime's input — source "system"
1391
1395
  const run = via !== undefined ? session.run(content, { via, ...(via.kind === "tasks" ? { source: "system" } : {}) }) : session.run(content);
1392
1396
  currentRun = run;
@@ -1435,6 +1439,15 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
1435
1439
  // round 8: after EVERY turn the prompt is re-armed — the human
1436
1440
  // never types blind after the first turn.
1437
1441
  input.prompt();
1442
+ // the follow-up (owner 2026-11-03): after a run that ended well, the line the person may send next,
1443
+ // asked of the session's own model with the run's own prefix (Session.followUp); shown dim in the
1444
+ // empty box only while no other turn has started or waits; off with the faux model and KISO_FOLLOWUP=off
1445
+ if (last?.type === "terminal" && last.outcome.kind === "completed" && !faux && input.setGhost !== undefined && process.env["KISO_FOLLOWUP"] !== "off" && queued === 0) {
1446
+ void session.followUp({ maxTokens: 200, signal: AbortSignal.timeout(15_000) }).then((line) => {
1447
+ if (turnNo === myTurn && currentRun === null && queued === 0)
1448
+ input.setGhost?.(line === "" ? null : line);
1449
+ }, () => undefined);
1450
+ }
1438
1451
  // the ergonomics batch C8: the opt-in auto-compact — checked AFTER the
1439
1452
  // turn ended (the run's terminal is in the log, the ratio
1440
1453
  // is post-run).
@@ -3,7 +3,6 @@
3
3
  * project-instruction injection. Moved verbatim from index.ts (R1,
4
4
  * 2026-09-23): the entry module keeps no prompt text of its own.
5
5
  */
6
- import { protectedFiles } from "./state.js";
7
6
  import { isProtectedPath, protectedIdentity } from "@vincemakes/kiso-tools-node";
8
7
  import { join } from "node:path";
9
8
  import { readFileSync } from "node:fs";
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The coding product's configuration and sign-in, as a library door: the
3
+ * profiles in `~/.kiso/config.json`, the credential store, the adapter
4
+ * options a profile resolves to, the efforts the person last picked, and the
5
+ * per-project session folders. A host that is not the terminal (the desktop
6
+ * app) reads the same login and the same Works as the terminal does.
7
+ *
8
+ * Almost read-only: the one write is `setCredential`, the terminal's own single
9
+ * credential write path (an API key; no OAuth flow), and `scaffoldFirstRun`, the
10
+ * terminal's first-run files. No project config (it needs the terminal's trust prompt). Nothing here
11
+ * reaches the terminal's state or UI; a test holds that line.
12
+ */
13
+ import { type KisoConfig, type ModelProfile } from "./config.js";
14
+ export { ConfigError, applyEffortDeclarations, authForProfile, loadUserConfig, mergeConfigs, profileAvailable, resolveAutoCompact, resolveContextWindow, resolveModel, unavailableReason, } from "./config.js";
15
+ export type { KisoConfig, ModelProfile, ProfileAuth, ProfileKind, ResolvedModel } from "./config.js";
16
+ export { adapterOptionsFor } from "./auth/adapter-options.js";
17
+ export type { AdapterAuth, AdapterOptions } from "./auth/adapter-options.js";
18
+ export { AuthError, KNOWN_PROVIDERS, endpointCredentialId, maskSecret, providerIdOf, setCredential } from "./auth/credentials.js";
19
+ export type { Credential } from "./auth/credentials.js";
20
+ export { preferences, rememberEffort, startingEffort } from "./preferences.js";
21
+ export { isFirstRun, scaffoldFirstRun } from "./first-run.js";
22
+ export { fauxScript as demoScript } from "./demo-script.js";
23
+ export { canonicalPath, claimProjectDir, projectDirFor, projectLayoutActive, sessionFolders } from "./projects.js";
24
+ export type { SessionFolder } from "./projects.js";
25
+ /**
26
+ * The context window someone stated for a profile, or null when nobody has: the window the person set (the environment, the profile,
27
+ * the configuration, in that order), else the metadata registry's for the profile's endpoint, else for the model's own identity.
28
+ * The terminal's own chain, less what only a running terminal knows (a window an endpoint's refusal taught it).
29
+ */
30
+ export declare function contextWindowOf(profile: Pick<ModelProfile, "model" | "baseUrl" | "contextWindow">, config?: KisoConfig): number | null;
31
+ /** The reasoning levels a profile's route accepts, and the one it uses when none is sent; null when nobody states them. */
32
+ export declare function effortOptions(profile: Pick<ModelProfile, "model" | "baseUrl">): {
33
+ readonly levels: readonly string[];
34
+ readonly default: string | null;
35
+ } | null;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The coding product's configuration and sign-in, as a library door: the
3
+ * profiles in `~/.kiso/config.json`, the credential store, the adapter
4
+ * options a profile resolves to, the efforts the person last picked, and the
5
+ * per-project session folders. A host that is not the terminal (the desktop
6
+ * app) reads the same login and the same Works as the terminal does.
7
+ *
8
+ * Almost read-only: the one write is `setCredential`, the terminal's own single
9
+ * credential write path (an API key; no OAuth flow), and `scaffoldFirstRun`, the
10
+ * terminal's first-run files. No project config (it needs the terminal's trust prompt). Nothing here
11
+ * reaches the terminal's state or UI; a test holds that line.
12
+ */
13
+ import { lookupContextWindow, lookupModelMetadata } from "@vincemakes/kiso-runtime/internal";
14
+ import { resolveContextWindow } from "./config.js";
15
+ export { ConfigError, applyEffortDeclarations, authForProfile, loadUserConfig, mergeConfigs, profileAvailable, resolveAutoCompact, resolveContextWindow, resolveModel, unavailableReason, } from "./config.js";
16
+ export { adapterOptionsFor } from "./auth/adapter-options.js";
17
+ export { AuthError, KNOWN_PROVIDERS, endpointCredentialId, maskSecret, providerIdOf, setCredential } from "./auth/credentials.js";
18
+ export { preferences, rememberEffort, startingEffort } from "./preferences.js";
19
+ export { isFirstRun, scaffoldFirstRun } from "./first-run.js";
20
+ export { fauxScript as demoScript } from "./demo-script.js";
21
+ export { canonicalPath, claimProjectDir, projectDirFor, projectLayoutActive, sessionFolders } from "./projects.js";
22
+ /**
23
+ * The context window someone stated for a profile, or null when nobody has: the window the person set (the environment, the profile,
24
+ * the configuration, in that order), else the metadata registry's for the profile's endpoint, else for the model's own identity.
25
+ * The terminal's own chain, less what only a running terminal knows (a window an endpoint's refusal taught it).
26
+ */
27
+ export function contextWindowOf(profile, config) {
28
+ const set = resolveContextWindow(config ?? {}, profile);
29
+ return set ?? lookupContextWindow(profile.model, profile.baseUrl)?.tokens ?? null;
30
+ }
31
+ /** The reasoning levels a profile's route accepts, and the one it uses when none is sent; null when nobody states them. */
32
+ export function effortOptions(profile) {
33
+ const effort = lookupModelMetadata(profile.model, profile.baseUrl)?.capabilities.reasoning?.effort ?? null;
34
+ return effort === null ? null : { levels: effort.levels, default: effort.default };
35
+ }
package/dist/config.js CHANGED
@@ -34,7 +34,7 @@
34
34
  import { readFileSync, statSync } from "node:fs";
35
35
  import { homedir } from "node:os";
36
36
  import { join, win32 } from "node:path";
37
- import { kisoHome } from "./state.js";
37
+ import { kisoHome } from "@vincemakes/kiso-runtime";
38
38
  import { MODE_VALUES, parseMode } from "./mode.js";
39
39
  import { AuthError, effectiveBaseUrl, endpointCredentialId, getCredential, providerIdOf } from "./auth/credentials.js";
40
40
  import { BUILTIN_MANIFESTS, declareEffort } from "@vincemakes/kiso-runtime/internal";
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The keyless demo script, as plain data with no imports of its own: the terminal's first-run demo and any other host that shows the
3
+ * same tour (the desktop app's first-run guide) read it from here.
4
+ */
5
+ import type { FauxScript } from "@vincemakes/kiso-evals";
6
+ /**
7
+ * The keyless demo script: tours the tools so `kiso chat` exercises them.
8
+ * FOUR turns: each user turn consumes two model rounds (call → result →
9
+ * summary), so at least two consecutive user turns work in one process
10
+ * (F group).
11
+ */
12
+ export declare function fauxScript(): FauxScript;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The keyless demo script: tours the tools so `kiso chat` exercises them.
3
+ * FOUR turns: each user turn consumes two model rounds (call → result →
4
+ * summary), so at least two consecutive user turns work in one process
5
+ * (F group).
6
+ */
7
+ export function fauxScript() {
8
+ return [
9
+ {
10
+ events: [
11
+ // bootstrap P1: a multi-delta thinking block — renders as ONE
12
+ // streaming segment, not one line per token.
13
+ { type: "thinking", text: "Let me think about" },
14
+ { type: "thinking", text: " the workspace" },
15
+ { type: "thinking", text: " before acting." },
16
+ { type: "text_start" },
17
+ { type: "text_delta", text: "I'm the faux model. Let me look at the working directory." },
18
+ { type: "tool_call_end", callId: "c1", name: "list_dir", input: {} },
19
+ { type: "stop", reason: "tool_use" },
20
+ ],
21
+ },
22
+ {
23
+ events: [
24
+ { type: "text_delta", text: "I see the workspace. What would you like me to inspect or change?" },
25
+ { type: "stop", reason: "end_turn" },
26
+ ],
27
+ },
28
+ {
29
+ events: [
30
+ { type: "text_delta", text: "The faux model is still here, with full context." },
31
+ { type: "stop", reason: "end_turn" },
32
+ ],
33
+ },
34
+ {
35
+ events: [
36
+ { type: "text_delta", text: "And this is the end of the scripted tour." },
37
+ { type: "stop", reason: "end_turn" },
38
+ ],
39
+ },
40
+ ];
41
+ }
@@ -4,7 +4,9 @@
4
4
  * the context (the chain, the run state, the prompt arming).
5
5
  */
6
6
  import type { SessionRoute } from "./projects.js";
7
- import type { AgentSession } from "@vincemakes/kiso-runtime";
7
+ import { type StoreRecord } from "@vincemakes/kiso-runtime/internal";
8
+ import { type AgentSession } from "@vincemakes/kiso-runtime";
9
+ import type { SessionCardView } from "@vincemakes/kiso-tui";
8
10
  import { type LineInput } from "./state.js";
9
11
  import type { UserInputVia } from "@vincemakes/kiso-core";
10
12
  /** Everything dispatch touches that chat() owns. */
@@ -72,3 +74,6 @@ export declare function parseBang(trimmed: string): {
72
74
  export declare function markCompactWhy(why: string): void;
73
75
  export declare function markAutoCompact(): void;
74
76
  export declare function dispatch(line: string, ctx: DispatchCtx): void;
77
+ /** /fork's rows: each finished run's reply, newest first, by its first line and when it ended; the opened row says the
78
+ * line it answered. The card's id is the seq of the run's terminal, where SessionStore.fork cuts. */
79
+ export declare function replyCards(records: readonly StoreRecord[]): SessionCardView[];
package/dist/dispatch.js CHANGED
@@ -5,11 +5,12 @@
5
5
  */
6
6
  import { STATUS_GLYPHS, contextRows, contextSheetRows, contextUnavailableRows, contextUnavailableSheetRows, displayVerb, escapeTerminal, helpRows, toolTarget, infoSheetRows, kUnit, modePickView, modelPickView, namedPickView, settingsPickView, compactingStatus, liveRow, palette, renderEvent, settledLabel, slashCommandNames } from "@vincemakes/kiso-tui";
7
7
  import { newSessionId } from "./session-id.js";
8
- import { buildAdapter, lookupModelMetadata, readSessionName, resolveContinuationScope, resolveReasoning, sessionTitle, tiersFor, writeSessionName } from "@vincemakes/kiso-runtime/internal";
8
+ import { buildAdapter, isForkNote, lookupModelMetadata, readSessionName, resolveContinuationScope, resolveReasoning, sessionTitle, tiersFor, writeSessionName } from "@vincemakes/kiso-runtime/internal";
9
+ import { ForkRefusedError, SessionStore } from "@vincemakes/kiso-runtime";
9
10
  import { MODE_LABEL, MODE_NOTE, OFFERED_MODES, applyModeSetting, getDontAsk, getMode, modeDisplay, parseMode, setDontAsk } from "./mode.js";
10
11
  import { clipboardWrite, lastAnswer } from "./clipboard.js";
11
12
  import { protectedBangReason, protectedShellVerdict } from "./protected-shell.js";
12
- import { activeStoreDir, agentBaseUrl, currentProfileName, setCurrentProfileName, currentModelName, agentModel, body, bodyLog, codingToolOptions, protectedFiles, kisoHome, configModels, dock, lastBinding, loadedSkillsCatalog, mergedConfig, readContextLedger, retryOnRow, sessionsDir, setAgentModel, setConfiguredWindow, setCurrentModelName, setModelChoice, setRetryShown, upstreamOf, VERSION, setLastBinding } from "./state.js";
13
+ import { activeStoreDir, sessionStoreRef, agentBaseUrl, currentProfileName, setCurrentProfileName, currentModelName, agentModel, body, bodyLog, codingToolOptions, protectedFiles, kisoHome, configModels, dock, lastBinding, loadedSkillsCatalog, mergedConfig, readContextLedger, retryOnRow, sessionsDir, setAgentModel, setConfiguredWindow, setCurrentModelName, setModelChoice, setRetryShown, upstreamOf, VERSION, setLastBinding } from "./state.js";
13
14
  import { adapterOptionsFor } from "./auth/adapter-options.js";
14
15
  import { profileProviderLabel, providerLabel } from "./provider-label.js";
15
16
  import { installedVersion, versionStatusLine } from "./stale-version.js";
@@ -1384,6 +1385,44 @@ export function dispatch(line, ctx) {
1384
1385
  ctx.requestReload();
1385
1386
  return;
1386
1387
  }
1388
+ if (trimmed === "/fork") {
1389
+ // owner 2026-11-03: the web app's fork, one rule — a new session from after one of this session's replies
1390
+ // (SessionStore.fork), then the terminal switches to it as /resume does; the replies are picked in /resume's band
1391
+ if (ctx.isRunning()) {
1392
+ body.notice("[/fork] a run is in flight — let it finish (esc stops it), then fork");
1393
+ ctx.input.prompt();
1394
+ return;
1395
+ }
1396
+ ctx.chainRef.current = ctx.chainRef.current.then(async () => {
1397
+ const cards = replyCards(sessionStoreRef?.load(ctx.session.id) ?? []);
1398
+ if (cards.length === 0) {
1399
+ body.notice("no finished reply to fork after yet");
1400
+ ctx.input.prompt();
1401
+ return;
1402
+ }
1403
+ const fork = (afterSeq) => {
1404
+ try {
1405
+ const made = new SessionStore(activeStoreDir).fork(ctx.session.id, { afterSeq });
1406
+ body.notice(`forked "${escapeTerminal(readSessionName(activeStoreDir, made.id) ?? made.id)}" — switching to it`);
1407
+ ctx.requestSwitch(made.id);
1408
+ }
1409
+ catch (error) {
1410
+ body.notice(error instanceof ForkRefusedError ? `[/fork] ${error.message}` : `[/fork] ${error instanceof Error ? error.message : String(error)}`);
1411
+ ctx.input.prompt();
1412
+ }
1413
+ };
1414
+ // no band (a pipe, a dock-less terminal): after the last reply, the sidebar's Fork
1415
+ if (ctx.input.pick === undefined || !dock.active)
1416
+ return fork(Number(cards[0].id));
1417
+ ctx.input.pick(() => cards, (picked) => {
1418
+ if (picked === null)
1419
+ ctx.input.prompt(); // esc: nothing forked, nothing said
1420
+ else
1421
+ fork(Number(picked));
1422
+ }, undefined, "fork");
1423
+ });
1424
+ return;
1425
+ }
1387
1426
  if (trimmed === "/resume" || trimmed.startsWith("/resume ")) {
1388
1427
  // the mini-spec: the in-session door to the durable sessions —
1389
1428
  // /resume <id> switches directly; bare /resume opens the SAME
@@ -1589,3 +1628,27 @@ async function tasksCommand(arg, ctx) {
1589
1628
  else if (action === 1)
1590
1629
  stop(task);
1591
1630
  }
1631
+ /** /fork's rows: each finished run's reply, newest first, by its first line and when it ended; the opened row says the
1632
+ * line it answered. The card's id is the seq of the run's terminal, where SessionStore.fork cuts. */
1633
+ export function replyCards(records) {
1634
+ const cards = [];
1635
+ let asked = "";
1636
+ let reply = "";
1637
+ for (const { event: e, ts } of records) {
1638
+ if (e.type === "user_input" && e.source !== "system") {
1639
+ const content = Array.isArray(e.content) ? e.content.filter((b) => !(b.type === "text" && isForkNote(b.text ?? ""))) : e.content;
1640
+ asked = (typeof content === "string" ? content : content.map((b) => (b.type === "text" ? (b.text ?? "") : "")).join(" ")).trim();
1641
+ reply = "";
1642
+ }
1643
+ else if (e.type === "tool_result")
1644
+ reply = "";
1645
+ else if (e.type === "text_delta")
1646
+ reply += e.text;
1647
+ else if (e.type === "terminal" && e.outcome.kind === "completed") {
1648
+ // the row is how the person recognizes the reply: its first line, inline code read as plain text
1649
+ const first = (reply.split("\n").map((l) => l.trim()).find((l) => l !== "") ?? "(no reply text)").replace(/`([^`]*)`/g, "$1");
1650
+ cards.unshift({ id: String(e.seq), title: first, badge: "completed", turns: null, updatedAt: ts, uncertain: 0, asks: 0, outcome: null, detail: `answered: ${asked.split("\n")[0] ?? ""}` });
1651
+ }
1652
+ }
1653
+ return cards;
1654
+ }
@@ -5,6 +5,8 @@
5
5
  */
6
6
  import type { FauxScript } from "@vincemakes/kiso-evals";
7
7
  import { type LineInput } from "./state.js";
8
+ import { fauxScript } from "./demo-script.js";
9
+ export { fauxScript };
8
10
  /**
9
11
  * E area: how many faux-script turns a session has already consumed. The faux
10
12
  * provider's script counter is per-process, so a FRESH process that resumes
@@ -23,13 +25,6 @@ export declare function fauxSkip(id: string): number;
23
25
  * multi-tool trajectory. Absent → the built-in demo script.
24
26
  */
25
27
  export declare function readFauxScript(): FauxScript;
26
- /**
27
- * The keyless demo script: tours the tools so `kiso chat` exercises them.
28
- * FOUR turns: each user turn consumes two model rounds (call → result →
29
- * summary), so at least two consecutive user turns work in one process
30
- * (F group).
31
- */
32
- export declare function fauxScript(): FauxScript;
33
28
  /** round 10: a faux-mode run whose scripted turns are exhausted must NOT print a
34
29
  * provider error and exit 0 — the honest outcome is a loud message and a
35
30
  * non-zero exit. Thrown as a CONTROLLED exception (never process.exit):
package/dist/faux-glue.js CHANGED
@@ -7,6 +7,8 @@ import { readFileSync } from "node:fs";
7
7
  import { escapeTerminal } from "@vincemakes/kiso-tui";
8
8
  import { SessionStore } from "@vincemakes/kiso-runtime";
9
9
  import { sessionsDir } from "./state.js";
10
+ import { fauxScript } from "./demo-script.js";
11
+ export { fauxScript };
10
12
  /**
11
13
  * E area: how many faux-script turns a session has already consumed. The faux
12
14
  * provider's script counter is per-process, so a FRESH process that resumes
@@ -47,47 +49,6 @@ export function readFauxScript() {
47
49
  process.exit(1);
48
50
  }
49
51
  }
50
- /**
51
- * The keyless demo script: tours the tools so `kiso chat` exercises them.
52
- * FOUR turns: each user turn consumes two model rounds (call → result →
53
- * summary), so at least two consecutive user turns work in one process
54
- * (F group).
55
- */
56
- export function fauxScript() {
57
- return [
58
- {
59
- events: [
60
- // bootstrap P1: a multi-delta thinking block — renders as ONE
61
- // streaming segment, not one line per token.
62
- { type: "thinking", text: "Let me think about" },
63
- { type: "thinking", text: " the workspace" },
64
- { type: "thinking", text: " before acting." },
65
- { type: "text_start" },
66
- { type: "text_delta", text: "I'm the faux model. Let me look at the working directory." },
67
- { type: "tool_call_end", callId: "c1", name: "list_dir", input: {} },
68
- { type: "stop", reason: "tool_use" },
69
- ],
70
- },
71
- {
72
- events: [
73
- { type: "text_delta", text: "I see the workspace. What would you like me to inspect or change?" },
74
- { type: "stop", reason: "end_turn" },
75
- ],
76
- },
77
- {
78
- events: [
79
- { type: "text_delta", text: "The faux model is still here, with full context." },
80
- { type: "stop", reason: "end_turn" },
81
- ],
82
- },
83
- {
84
- events: [
85
- { type: "text_delta", text: "And this is the end of the scripted tour." },
86
- { type: "stop", reason: "end_turn" },
87
- ],
88
- },
89
- ];
90
- }
91
52
  /** round 10: a faux-mode run whose scripted turns are exhausted must NOT print a
92
53
  * provider error and exit 0 — the honest outcome is a loud message and a
93
54
  * non-zero exit. Thrown as a CONTROLLED exception (never process.exit):
package/dist/first-run.js CHANGED
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
12
12
  import { join } from "node:path";
13
- import { kisoHome } from "./state.js";
13
+ import { kisoHome } from "@vincemakes/kiso-runtime";
14
14
  /** The sentinel: present = the home has seen the 0.1.45 first-run
15
15
  * sequence (its config surface exists — no re-scaffold, no re-note). */
16
16
  const SENTINEL = "first-run";
package/dist/index.js CHANGED
@@ -29,10 +29,11 @@ import { newSessionId } from "./session-id.js";
29
29
  import { createInterface } from "node:readline";
30
30
  import { Readable } from "node:stream";
31
31
  import { fileURLToPath } from "node:url";
32
- import { basename, join } from "node:path";
32
+ import { createRequire } from "node:module";
33
+ import { basename, dirname, join } from "node:path";
33
34
  import { Body, Editor, PROMPT, bannerLines, currentGround, currentGroundRgb, parseOscColor, resolveGround, setGround, escapeTerminal, extensionsBannerText, extensionsFact, idColumn, interactivePrompt, palette, renderSessionLine, sessionListFooter, sessionListHeader, sessionListRow, sessionListUnknownLine, slashCommandNames } from "@vincemakes/kiso-tui";
34
35
  import { disposeExtensions, SessionStore } from "@vincemakes/kiso-runtime";
35
- import { listSessionSidecars, migrateSummaries, readProfile, readSessionName, summaryMigrationPending } from "@vincemakes/kiso-runtime/internal";
36
+ import { listSessionSidecars, migrateSummaries, readProfile, readSessionName, summaryMigrationPending, writeSessionName } from "@vincemakes/kiso-runtime/internal";
36
37
  import { skillMenuItems } from "./skill-invoke.js";
37
38
  import { canonicalPath, hasSession, locateSession, projectLayoutActive, sessionFolders } from "./projects.js";
38
39
  import { reverseMigration } from "./session-migration.js";
@@ -331,8 +332,8 @@ function editorInput(editor) {
331
332
  // TUI2-R2 ②: the session picker — the editor owns the keys (the
332
333
  // selection walk, the filter, enter/esc), the compositor draws the
333
334
  // band, and the id comes back here.
334
- pick(cards, onPick, here) {
335
- editor.beginPick(cards, onPick, here);
335
+ pick(cards, onPick, here, kind) {
336
+ editor.beginPick(cards, onPick, here, kind);
336
337
  },
337
338
  // W22: the pending-turn queue — the ↑ pop walk (the keys); the
338
339
  // chips are the compositor's bindQueue (the dock side).
@@ -340,6 +341,9 @@ function editorInput(editor) {
340
341
  editor.bindQueue(state, pop);
341
342
  },
342
343
  // ADR-0057: the steers a stop handed back return to the buffer.
344
+ setGhost(text) {
345
+ editor.setGhost(text);
346
+ },
343
347
  restore(text) {
344
348
  editor.restore(text);
345
349
  },
@@ -1157,6 +1161,30 @@ async function chatLoop(first, firstId, input, autoCompact) {
1157
1161
  sessions: () => agent.sessionIds(),
1158
1162
  route: (sessionId) => routeSession(sessionId),
1159
1163
  ...(process.stdin.isTTY ? { pick: () => pickSession(agent, input) } : {}),
1164
+ // owner 2026-11-03: a session is titled by a summary of its first message, in the background; the person's own
1165
+ // name wins, before or after; off with KISO_AUTO_TITLE=off and with the faux model, and never without a TTY:
1166
+ // a piped chat is a script or a bench leg, where one more request per session is not the person's
1167
+ ...(currentFaux || process.env["KISO_AUTO_TITLE"] === "off" || process.stdin.isTTY !== true
1168
+ ? {}
1169
+ : {
1170
+ titled: (firstMessage) => {
1171
+ // the session and its folder as they are NOW: a /resume or a switch before the title lands must not
1172
+ // send it to another session, nor repaint another session's window title
1173
+ const [titling, mine, dir] = [session, session.id, activeStoreDir];
1174
+ if (readSessionName(dir, mine) !== null)
1175
+ return;
1176
+ void titling.title(firstMessage, { signal: AbortSignal.timeout(15_000) }).then((title) => {
1177
+ if (title === "" || readSessionName(dir, mine) !== null)
1178
+ return;
1179
+ writeSessionName(dir, mine, title);
1180
+ // `id` is the loop's: the session open now (a switch moves it on)
1181
+ if (id !== mine)
1182
+ return;
1183
+ setTitleName(title);
1184
+ paintWindowTitle();
1185
+ }, () => undefined);
1186
+ },
1187
+ }),
1160
1188
  };
1161
1189
  const end = await chat(session, currentFaux, input, autoCompact, nav, seed);
1162
1190
  if (end.next === "exit") {
@@ -1266,6 +1294,32 @@ async function main() {
1266
1294
  process.exitCode = await applyPatch(args.slice(1));
1267
1295
  return;
1268
1296
  }
1297
+ // W2 (2026-11-01): `kiso web` runs the web app, its own package (@vincemakes/kiso-web), when it is
1298
+ // installed beside kiso, and names the install command when it is not. The FIRST argument alone
1299
+ // triggers it, before any flag is read: every other argument is the web app's (`kiso web --no-open`),
1300
+ // and a session named "web" still opens with `kiso chat web`. No terminal UI is drawn on this path,
1301
+ // so the host's own ctrl+C handling decides, and its exit is this command's exit.
1302
+ if (args[0] === "web") {
1303
+ let bin;
1304
+ try {
1305
+ const manifest = createRequire(import.meta.url).resolve("@vincemakes/kiso-web/package.json");
1306
+ bin = join(dirname(manifest), JSON.parse(readFileSync(manifest, "utf8")).bin["kiso-web"] ?? "");
1307
+ }
1308
+ catch {
1309
+ process.stderr.write("kiso web: the web app is not installed — npm i -g @vincemakes/kiso-web\n");
1310
+ process.exitCode = 1;
1311
+ return;
1312
+ }
1313
+ const child = spawn(process.execPath, [bin, ...args.slice(1)], { stdio: "inherit" });
1314
+ process.on("SIGINT", () => { }); // the terminal sends it to the host too
1315
+ for (const sig of ["SIGTERM", "SIGHUP"])
1316
+ process.on(sig, () => child.kill(sig));
1317
+ process.exitCode = await new Promise((done) => {
1318
+ child.on("error", () => done(1));
1319
+ child.on("exit", (code) => done(code ?? 1));
1320
+ });
1321
+ return;
1322
+ }
1269
1323
  // XP-1: --accept-drift (a flag, never an env var) authorizes opening a
1270
1324
  // session whose recorded profile materially drifted — the
1271
1325
  // acknowledgement is recorded as a new revision by the runtime.
@@ -1864,6 +1918,7 @@ async function main() {
1864
1918
  " kiso logout <provider> remove the stored credential (or --endpoint <url>)\n" +
1865
1919
  " kiso auth list stored credentials (keys masked)\n" +
1866
1920
  " kiso update install the latest release (npm i -g @vincemakes/kiso-code@latest)\n" +
1921
+ " kiso web [--port <n>] [--no-open] the web app in the browser (its own package: npm i -g @vincemakes/kiso-web)\n" +
1867
1922
  " kiso help this help\n\n" +
1868
1923
  "flags (any position):\n" +
1869
1924
  " --model <profile|provider/model> pick the model (also /model in-session)\n" +
package/dist/mode.d.ts CHANGED
@@ -122,8 +122,8 @@ export declare function applyModeState(s: ModeState): void;
122
122
  * deny>allow>ask over the SPEAKING verdicts (abstain = no opinion), so a
123
123
  * user extension's deny wins over any mode tier, full-access included
124
124
  * (the monotonicity e2e pins it). */
125
- export declare function modeExtensions(workspaceRoot?: () => string): readonly KisoExtension[];
125
+ export declare function modeExtensions(workspaceRoot?: () => string, currentTier?: () => Mode): readonly KisoExtension[];
126
126
  /** The plan tier's system prompt addition — injected at startup when the
127
127
  * initial mode is plan (the session prompt is fixed at creation; runtime
128
128
  * switches are guided by the deny reason). */
129
- export declare function modeSystemPrompt(): string | undefined;
129
+ export declare function modeSystemPrompt(tier?: Mode): string | undefined;
package/dist/mode.js CHANGED
@@ -165,9 +165,9 @@ export function applyModeState(s) {
165
165
  current = s.mode;
166
166
  dontAsk = s.dontAsk;
167
167
  }
168
- /** The per-tier verdict for a tool call — only when this tier is current. */
169
- function tierVerdict(tier, call, workspaceRoot) {
170
- if (tier !== current)
168
+ /** The per-tier verdict for a tool call — only when this tier is current (`now`: the tier in force, the process's own by default). */
169
+ function tierVerdict(tier, call, workspaceRoot, now) {
170
+ if (tier !== now)
171
171
  return { action: "abstain" }; // not our tier — no opinion
172
172
  switch (tier) {
173
173
  case "manual":
@@ -215,12 +215,13 @@ function tierVerdict(tier, call, workspaceRoot) {
215
215
  * deny>allow>ask over the SPEAKING verdicts (abstain = no opinion), so a
216
216
  * user extension's deny wins over any mode tier, full-access included
217
217
  * (the monotonicity e2e pins it). */
218
- export function modeExtensions(workspaceRoot = () => process.cwd()) {
219
- return [...MODES.filter((m) => m === current), ...MODES.filter((m) => m !== current)].map((m) => ({
218
+ export function modeExtensions(workspaceRoot = () => process.cwd(), currentTier = getMode) {
219
+ // the tier in force is read on every decision, so a host that holds several sessions can give each its own (the terminal passes none)
220
+ return [...MODES.filter((m) => m === currentTier()), ...MODES.filter((m) => m !== currentTier())].map((m) => ({
220
221
  name: `mode:${m}`,
221
222
  approvals: [
222
223
  {
223
- decide: async (payload) => tierVerdict(m, { name: payload.name, input: payload.input ?? {} }, workspaceRoot),
224
+ decide: async (payload) => tierVerdict(m, { name: payload.name, input: payload.input ?? {} }, workspaceRoot, currentTier()),
224
225
  },
225
226
  ],
226
227
  }));
@@ -228,8 +229,8 @@ export function modeExtensions(workspaceRoot = () => process.cwd()) {
228
229
  /** The plan tier's system prompt addition — injected at startup when the
229
230
  * initial mode is plan (the session prompt is fixed at creation; runtime
230
231
  * switches are guided by the deny reason). */
231
- export function modeSystemPrompt() {
232
- if (current !== "plan")
232
+ export function modeSystemPrompt(tier = current) {
233
+ if (tier !== "plan")
233
234
  return undefined;
234
235
  return ("plan mode: read-only. You may inspect the workspace (read_file, list_dir, search_text, " +
235
236
  "read_skill) but every write/edit/shell call is DENIED with 'plan mode: read-only'. " +
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The coding product's policy, as a library door: the prompt and tool rules,
3
+ * and the extensions that bound what a coding agent may do (the catastrophe
4
+ * floor, the protected-file shell guard, the read-only shell allow, the loop
5
+ * breaker, the mode tiers). Everything here is state-free except the mode
6
+ * tier, which is process-local (`getMode` / `setMode`). A host that is not
7
+ * the terminal (the desktop app) composes the same agent from these and the
8
+ * public runtime and tool packages.
9
+ */
10
+ export { CODING_TOOL_RULES, SYSTEM_PROMPT, composeSystemPrompt } from "./coding-prompt.js";
11
+ export { breakerExtension } from "./breaker.js";
12
+ export { floorExtension } from "./floor.js";
13
+ export { MODE_LABEL, OFFERED_MODES, getMode, modeExtensions, modeSystemPrompt, setMode } from "./mode.js";
14
+ export { protectedShellExtension } from "./protected-shell.js";
15
+ export { guardSavedAllow, neverInheritedCall } from "./protected-writes.js";
16
+ export { readOnlyShellExtension } from "./readonly-shell.js";
package/dist/policy.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The coding product's policy, as a library door: the prompt and tool rules,
3
+ * and the extensions that bound what a coding agent may do (the catastrophe
4
+ * floor, the protected-file shell guard, the read-only shell allow, the loop
5
+ * breaker, the mode tiers). Everything here is state-free except the mode
6
+ * tier, which is process-local (`getMode` / `setMode`). A host that is not
7
+ * the terminal (the desktop app) composes the same agent from these and the
8
+ * public runtime and tool packages.
9
+ */
10
+ export { CODING_TOOL_RULES, SYSTEM_PROMPT, composeSystemPrompt } from "./coding-prompt.js";
11
+ export { breakerExtension } from "./breaker.js";
12
+ export { floorExtension } from "./floor.js";
13
+ export { MODE_LABEL, OFFERED_MODES, getMode, modeExtensions, modeSystemPrompt, setMode } from "./mode.js";
14
+ export { protectedShellExtension } from "./protected-shell.js";
15
+ export { guardSavedAllow, neverInheritedCall } from "./protected-writes.js";
16
+ export { readOnlyShellExtension } from "./readonly-shell.js";
@@ -15,7 +15,7 @@
15
15
  import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
16
16
  import { dirname, join } from "node:path";
17
17
  import { lookupModelMetadata, resolveReasoning } from "@vincemakes/kiso-runtime/internal";
18
- import { kisoHome } from "./state.js";
18
+ import { kisoHome } from "@vincemakes/kiso-runtime";
19
19
  let prefs = null;
20
20
  let prefsPath = null;
21
21
  export function preferencesPath(home = kisoHome()) {
package/dist/replay.d.ts CHANGED
@@ -19,7 +19,7 @@
19
19
  * summary (what the model sees next) and the turns it covers, read in
20
20
  * the viewer — then any uncovered earlier turns folded as above.
21
21
  */
22
- import type { TaskInfo } from "@vincemakes/kiso-runtime/internal";
22
+ import { type TaskInfo } from "@vincemakes/kiso-runtime/internal";
23
23
  import type { NoticeMark } from "@vincemakes/kiso-tui";
24
24
  /** The Body surface the replay drives — the live run's own mutations. */
25
25
  export interface ReplayBody {
package/dist/replay.js CHANGED
@@ -22,6 +22,7 @@
22
22
  import { echoText } from "@vincemakes/kiso-tui-cells/render";
23
23
  import { bandHeader } from "@vincemakes/kiso-tui-cells/strings";
24
24
  import { cappedWaitLine, taskNoticeLines, taskNoticeRows, taskWhat } from "./task-notice.js";
25
+ import { isForkNote } from "@vincemakes/kiso-runtime/internal";
25
26
  /** How many turns stay on screen in full. */
26
27
  const FULL_TURNS = 2;
27
28
  /** A user turn's opening line, or null when this input does not open one
@@ -32,7 +33,9 @@ function askOf(e) {
32
33
  if (e.type !== "user_input" || e.source === "system")
33
34
  return null;
34
35
  const via = e.via;
35
- const text = typeof via?.line === "string" ? via.line : echoText(e.content);
36
+ // a fork's first input carries the fork note before the person's words: the model's, not the line they typed
37
+ const content = Array.isArray(e.content) ? e.content.filter((b) => !(b.type === "text" && isForkNote(b.text ?? ""))) : e.content;
38
+ const text = typeof via?.line === "string" ? via.line : echoText(content);
36
39
  return text.trim() === "" ? null : text;
37
40
  }
38
41
  /** Split events into turns: each starts at a user turn's input; anything
package/dist/state.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { Dock, type AtItem, type Body, type PanelVerdict, type PanelView, type SaferAnswer, type SessionCardView } from "@vincemakes/kiso-tui";
9
9
  import type { TaskDeliveryItem } from "@vincemakes/kiso-core";
10
- import type { KisoExtension, StoreRecord } from "@vincemakes/kiso-runtime";
10
+ import { kisoHome, type KisoExtension, type StoreRecord } from "@vincemakes/kiso-runtime";
11
11
  import type { ReasoningSetting } from "@vincemakes/kiso-runtime/internal";
12
12
  import { TaskManager } from "@vincemakes/kiso-runtime/internal";
13
13
  import { type ShellTasks } from "@vincemakes/kiso-tools-node";
@@ -15,7 +15,7 @@ import { type ShellTasks } from "@vincemakes/kiso-tools-node";
15
15
  * it (sessions, trust, extensions, mcp config, skills). The dedicated
16
16
  * env vars (KISO_EXTENSIONS_DIR / KISO_MCP_CONFIG / KISO_SKILLS_DIR)
17
17
  * still override their own path; nothing hard-codes ~/.kiso anymore. */
18
- export declare function kisoHome(): string;
18
+ export { kisoHome };
19
19
  /** DC-49 — the workspace the coding tools may touch, and the one place it
20
20
  * is decided. The session's own tool set and the `!` command's runner both
21
21
  * need it, and two copies of a ruling-bearing literal is how a later DC-49
@@ -241,7 +241,7 @@ export interface LineInput {
241
241
  * picked id, or null when the human leaves without picking.
242
242
  * OPTIONAL: only the raw-mode editor has it, and the picker only
243
243
  * ever opens on a TTY (a pipe has nobody to pick). */
244
- pick?(cards: () => readonly SessionCardView[], onPick: (id: string | null) => void, here?: string): void;
244
+ pick?(cards: () => readonly SessionCardView[], onPick: (id: string | null) => void, here?: string, kind?: "resume" | "fork"): void;
245
245
  /** W22: bind the pending-turn queue — the ↑ pop walks the CLI's
246
246
  * live slots (each pop cancels the turn), esc ends the walk after
247
247
  * one more pop. The chips are the compositor's own bindQueue. */
@@ -249,6 +249,9 @@ export interface LineInput {
249
249
  /** ADR-0057: put text back into the editor — the steers a stop handed
250
250
  * back before they landed. The pipe path has no editor; optional. */
251
251
  restore?(text: string): void;
252
+ /** The follow-up: a line the person may send next, dim in the empty box, Tab takes it; null clears it. Optional: the
253
+ * readline input has no box to draw it in. */
254
+ setGhost?(text: string | null): void;
252
255
  /** R3a: cross-session history — seed the recall buffer, register the
253
256
  * append sink. The pipe path has no recall keys; optional. */
254
257
  bindHistory?(seed: readonly string[], persist: (line: string) => void): void;
@@ -445,4 +448,3 @@ export declare function lastBinding(): LastBinding | null;
445
448
  export declare function setAcceptDrift(v: boolean): void;
446
449
  export declare function acceptDrift(): boolean;
447
450
  export declare function observedRevisionFromLog(sessionId: string | undefined, full: string, kind?: "edit" | "write"): string | undefined;
448
- export {};
package/dist/state.js CHANGED
@@ -12,8 +12,9 @@ import { dirname, join, resolve } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { AT_CAP, AT_SKIP, Dock } from "@vincemakes/kiso-tui";
14
14
  import { projectMessages } from "@vincemakes/kiso-core";
15
+ import { kisoHome } from "@vincemakes/kiso-runtime";
15
16
  import { TaskManager } from "@vincemakes/kiso-runtime/internal";
16
- import { processTaskBackend } from "@vincemakes/kiso-tools-node";
17
+ import { processTaskBackend, readShowsWholeFile } from "@vincemakes/kiso-tools-node";
17
18
  import { collectWriter } from "@vincemakes/kiso-subagent-ext";
18
19
  import { canonicalPath, LEGACY_SESSIONS_DIR, projectDirFor, projectLayoutActive } from "./projects.js";
19
20
  import { taskNoticeLines, taskWhat } from "./task-notice.js";
@@ -21,9 +22,7 @@ import { taskNoticeLines, taskWhat } from "./task-notice.js";
21
22
  * it (sessions, trust, extensions, mcp config, skills). The dedicated
22
23
  * env vars (KISO_EXTENSIONS_DIR / KISO_MCP_CONFIG / KISO_SKILLS_DIR)
23
24
  * still override their own path; nothing hard-codes ~/.kiso anymore. */
24
- export function kisoHome() {
25
- return process.env.KISO_HOME ?? join(homedir(), ".kiso");
26
- }
25
+ export { kisoHome };
27
26
  /** DC-49 — the workspace the coding tools may touch, and the one place it
28
27
  * is decided. The session's own tool set and the `!` command's runner both
29
28
  * need it, and two copies of a ruling-bearing literal is how a later DC-49
@@ -709,7 +708,7 @@ export function observedRevisionFromLog(sessionId, full, kind = "edit") {
709
708
  if (m.role === "assistant") {
710
709
  for (const b of m.blocks) {
711
710
  if (b.type === "tool_use" && typeof b.input.path === "string") {
712
- calls.set(b.callId, { name: b.name, full: realOrResolved(resolve(process.cwd(), b.input.path)), ranged: b.input.offset !== undefined || b.input.limit !== undefined });
711
+ calls.set(b.callId, { name: b.name, full: realOrResolved(resolve(process.cwd(), b.input.path)), input: b.input });
713
712
  }
714
713
  }
715
714
  }
@@ -722,9 +721,11 @@ export function observedRevisionFromLog(sessionId, full, kind = "edit") {
722
721
  if (hit === undefined)
723
722
  continue;
724
723
  if (!m.isError && (c.name === "read_file" || c.name === "edit_file" || c.name === "write_file")) {
724
+ const sameRevision = hit[1] === rev;
725
725
  rev = hit[1];
726
+ // a later partial read of the SAME revision keeps what the model already saw whole; another revision starts over
726
727
  if (c.name === "read_file")
727
- wholeKnown = !c.ranged && !body.includes("(call again with offset=") && !body.includes("[output capped at");
728
+ wholeKnown = readShowsWholeFile(c.input, body) || (wholeKnown && sameRevision);
728
729
  else if (c.name === "write_file")
729
730
  wholeKnown = true;
730
731
  }
@@ -90,8 +90,12 @@ export function lostReason(task) {
90
90
  // an unreadable journal: the general reason below
91
91
  }
92
92
  const unconfirmed = records.find((r) => r.type === "stop_unconfirmed");
93
- if (unconfirmed !== undefined)
94
- return `its stop could not be confirmed (pids ${unconfirmed.pids.join(", ")})`;
93
+ if (unconfirmed !== undefined) {
94
+ return unconfirmed.pids.length > 0 ? `its stop could not be confirmed (pids ${unconfirmed.pids.join(", ")})` : "its stop could not be confirmed (what still held it could not be named)";
95
+ }
96
+ const failed = records.find((r) => r.type === "handover_failed");
97
+ if (failed !== undefined)
98
+ return `its hand-over to a task failed (${failed.reason}); the command was running and its end is not known`;
95
99
  if (task.backend === "foreground")
96
100
  return "the kiso that held it ended without recording its end";
97
101
  return "its runner is gone without recording its end";
package/package.json CHANGED
@@ -1,12 +1,23 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-code",
3
- "version": "0.51.1",
3
+ "version": "0.53.0",
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",
7
7
  "bin": {
8
8
  "kiso": "dist/index.js"
9
9
  },
10
+ "exports": {
11
+ "./config": {
12
+ "types": "./dist/config-door.d.ts",
13
+ "default": "./dist/config-door.js"
14
+ },
15
+ "./policy": {
16
+ "types": "./dist/policy.d.ts",
17
+ "default": "./dist/policy.js"
18
+ },
19
+ "./package.json": "./package.json"
20
+ },
10
21
  "files": [
11
22
  "dist",
12
23
  "README.md",
@@ -18,19 +29,19 @@
18
29
  "test": "vitest run"
19
30
  },
20
31
  "dependencies": {
21
- "@vincemakes/kiso-ask-ext": "0.51.1",
22
- "@vincemakes/kiso-core": "0.51.1",
23
- "@vincemakes/kiso-evals": "0.51.1",
24
- "@vincemakes/kiso-mcp-ext": "0.51.1",
25
- "@vincemakes/kiso-provider-anthropic": "0.51.1",
26
- "@vincemakes/kiso-provider-openai": "0.51.1",
27
- "@vincemakes/kiso-provider-openai-responses": "0.51.1",
28
- "@vincemakes/kiso-runtime": "0.51.1",
29
- "@vincemakes/kiso-skills-ext": "0.51.1",
30
- "@vincemakes/kiso-subagent-ext": "0.51.1",
31
- "@vincemakes/kiso-tools-node": "0.51.1",
32
- "@vincemakes/kiso-tui": "0.51.1",
33
- "@vincemakes/kiso-tui-cells": "0.51.1"
32
+ "@vincemakes/kiso-ask-ext": "0.53.0",
33
+ "@vincemakes/kiso-core": "0.53.0",
34
+ "@vincemakes/kiso-evals": "0.53.0",
35
+ "@vincemakes/kiso-mcp-ext": "0.53.0",
36
+ "@vincemakes/kiso-provider-anthropic": "0.53.0",
37
+ "@vincemakes/kiso-provider-openai": "0.53.0",
38
+ "@vincemakes/kiso-provider-openai-responses": "0.53.0",
39
+ "@vincemakes/kiso-runtime": "0.53.0",
40
+ "@vincemakes/kiso-skills-ext": "0.53.0",
41
+ "@vincemakes/kiso-subagent-ext": "0.53.0",
42
+ "@vincemakes/kiso-tools-node": "0.53.0",
43
+ "@vincemakes/kiso-tui": "0.53.0",
44
+ "@vincemakes/kiso-tui-cells": "0.53.0"
34
45
  },
35
46
  "devDependencies": {
36
47
  "@types/node": "^26.1.2",