@vincemakes/kiso-code 0.53.0 → 0.54.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/chat.js CHANGED
@@ -1444,7 +1444,7 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
1444
1444
  // empty box only while no other turn has started or waits; off with the faux model and KISO_FOLLOWUP=off
1445
1445
  if (last?.type === "terminal" && last.outcome.kind === "completed" && !faux && input.setGhost !== undefined && process.env["KISO_FOLLOWUP"] !== "off" && queued === 0) {
1446
1446
  void session.followUp({ maxTokens: 200, signal: AbortSignal.timeout(15_000) }).then((line) => {
1447
- if (turnNo === myTurn && currentRun === null && queued === 0)
1447
+ if (turnNo === myTurn && currentRun === null && queued === 0 && !leaving)
1448
1448
  input.setGhost?.(line === "" ? null : line);
1449
1449
  }, () => undefined);
1450
1450
  }
@@ -1902,11 +1902,13 @@ export async function chat(session, faux, input, autoCompact, nav, seed) {
1902
1902
  requestSwitch: (id) => {
1903
1903
  switchTo = id;
1904
1904
  leaving = true;
1905
+ input.setGhost?.(null); // REL-0530-F1: the follow-up belongs to the session being left
1905
1906
  resolveEnd();
1906
1907
  },
1907
1908
  requestReload: () => {
1908
1909
  reloadReq = true;
1909
1910
  leaving = true;
1911
+ input.setGhost?.(null);
1910
1912
  resolveEnd();
1911
1913
  },
1912
1914
  sessions: () => nav?.sessions() ?? [],
@@ -34,7 +34,7 @@ import { adapterOptionsFor } from "./auth/adapter-options.js";
34
34
  import { createFauxProvider } from "@vincemakes/kiso-evals";
35
35
  import { sweepStaleMergeDirs } from "./temp-sweep.js";
36
36
  import { CODING_TOOL_RULES, composeSystemPrompt } from "./coding-prompt.js";
37
- import { createAgent, loadExtensions, loadProjectExtensions, SessionStore } from "@vincemakes/kiso-runtime";
37
+ import { createAgent, loadExtensions, loadProjectExtensions, readEnvironment, SessionStore } from "@vincemakes/kiso-runtime";
38
38
  import { preferences, startingEffort } from "./preferences.js";
39
39
  /** LT-1: the stream watchdog's bound from the environment — a non-negative
40
40
  * number of milliseconds (0 disables it); anything else is ignored. The
@@ -269,7 +269,7 @@ export async function createCodingAgent(sessionId, input, modelFlag) {
269
269
  setUserProtectedPaths(userConfig?.protectedPaths);
270
270
  const extensions = [
271
271
  protectedShellExtension({ files: protectedFiles, workspaceRoot, env: () => ({ home: homedir(), kisoHome: kisoHome() }) }),
272
- floorExtension(() => floorOn, workspaceRoot),
272
+ floorExtension(() => floorOn, workspaceRoot, undefined, () => (sessionId === undefined ? [] : readEnvironment(activeStoreDir, sessionId).added)),
273
273
  breakerExtension(),
274
274
  ...modeExtensions(workspaceRoot),
275
275
  readOnlyShellExtension(codingToolOptions),
@@ -74,6 +74,8 @@ export declare function parseBang(trimmed: string): {
74
74
  export declare function markCompactWhy(why: string): void;
75
75
  export declare function markAutoCompact(): void;
76
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. */
77
+ /** /fork's rows: each finished run, newest first, by the person's message it answered and when it ended; the opened
78
+ * row shows the reply's first line under it (owner 2026-11-04: the person knows a turn by what they asked, and an
79
+ * "answered:" label over their own line read backwards). The card's id is the seq of the run's terminal, where
80
+ * SessionStore.fork cuts. */
79
81
  export declare function replyCards(records: readonly StoreRecord[]): SessionCardView[];
package/dist/dispatch.js CHANGED
@@ -6,6 +6,7 @@
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
8
  import { buildAdapter, isForkNote, lookupModelMetadata, readSessionName, resolveContinuationScope, resolveReasoning, sessionTitle, tiersFor, writeSessionName } from "@vincemakes/kiso-runtime/internal";
9
+ import { isEnvironmentNote } from "@vincemakes/kiso-runtime";
9
10
  import { ForkRefusedError, SessionStore } from "@vincemakes/kiso-runtime";
10
11
  import { MODE_LABEL, MODE_NOTE, OFFERED_MODES, applyModeSetting, getDontAsk, getMode, modeDisplay, parseMode, setDontAsk } from "./mode.js";
11
12
  import { clipboardWrite, lastAnswer } from "./clipboard.js";
@@ -1628,15 +1629,17 @@ async function tasksCommand(arg, ctx) {
1628
1629
  else if (action === 1)
1629
1630
  stop(task);
1630
1631
  }
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. */
1632
+ /** /fork's rows: each finished run, newest first, by the person's message it answered and when it ended; the opened
1633
+ * row shows the reply's first line under it (owner 2026-11-04: the person knows a turn by what they asked, and an
1634
+ * "answered:" label over their own line read backwards). The card's id is the seq of the run's terminal, where
1635
+ * SessionStore.fork cuts. */
1633
1636
  export function replyCards(records) {
1634
1637
  const cards = [];
1635
1638
  let asked = "";
1636
1639
  let reply = "";
1637
1640
  for (const { event: e, ts } of records) {
1638
1641
  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;
1642
+ const content = Array.isArray(e.content) ? e.content.filter((b) => !(b.type === "text" && (isForkNote(b.text ?? "") || isEnvironmentNote(b.text ?? "")))) : e.content;
1640
1643
  asked = (typeof content === "string" ? content : content.map((b) => (b.type === "text" ? (b.text ?? "") : "")).join(" ")).trim();
1641
1644
  reply = "";
1642
1645
  }
@@ -1645,9 +1648,10 @@ export function replyCards(records) {
1645
1648
  else if (e.type === "text_delta")
1646
1649
  reply += e.text;
1647
1650
  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
1651
+ // the row is the person's own line; the reply's first line, inline code read as plain text, opens under it
1649
1652
  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] ?? ""}` });
1653
+ const line = asked.split("\n").map((l) => l.trim()).find((l) => l !== "") ?? "(no message text)";
1654
+ cards.unshift({ id: String(e.seq), title: line, badge: "completed", turns: null, updatedAt: ts, uncertain: 0, asks: 0, outcome: null, detail: `└ ${first}` });
1651
1655
  }
1652
1656
  }
1653
1657
  return cards;
@@ -65,6 +65,8 @@ export function findEmptySessions(dirs, alive = pidAlive) {
65
65
  const files = [name];
66
66
  if (names.includes(lock))
67
67
  files.push(lock);
68
+ if (names.includes(`${id}.environment.json`))
69
+ files.push(`${id}.environment.json`);
68
70
  if (existsSync(join(dir, "traces", `${id}.jsonl`)))
69
71
  files.push(join("traces", `${id}.jsonl`));
70
72
  empty.push({ dir, id, files });
package/dist/floor.d.ts CHANGED
@@ -88,12 +88,12 @@ export declare function gitInvocation(args: readonly LooseWord[]): GitInvocation
88
88
  * Would the floor refuse this command line? `workspaceRoot` is where the
89
89
  * shell runs it.
90
90
  */
91
- export declare function floorCheck(commandLine: string, workspaceRoot: string, home?: string): FloorVerdict;
91
+ export declare function floorCheck(commandLine: string, workspaceRoot: string, home?: string, added?: readonly string[]): FloorVerdict;
92
92
  /** Is there a destructive command anywhere in the line, whatever its
93
93
  * target? A saved allow is never inherited by one (plan §4). */
94
94
  export declare function isDestructive(commandLine: string): boolean;
95
95
  /** The chain member, at the chain's head: DENY or ABSTAIN. */
96
- export declare function floorExtension(on: () => boolean, workspaceRoot: () => string, check?: typeof floorCheck): KisoExtension;
96
+ export declare function floorExtension(on: () => boolean, workspaceRoot: () => string, check?: typeof floorCheck, added?: () => readonly string[]): KisoExtension;
97
97
  /** Plan §4: "destructive commands never inherit a saved allow in the
98
98
  * asking modes" — the predicate the saved-allow wrap
99
99
  * (protected-writes.ts) is given, keyed on destructiveness, not on the
package/dist/floor.js CHANGED
@@ -77,9 +77,9 @@ function windowsRoots(home) {
77
77
  /** Memoized per (workspace, home): the roots do not move within a session,
78
78
  * and resolving forty of them cost every shell call ~4 ms. */
79
79
  const WHERE = new Map();
80
- function where(root, home) {
80
+ function where(root, home, added = []) {
81
81
  const dialect = hostDialect();
82
- const key = `${dialect.msys ? "msys" : "posix"}\0${root}\0${home}`;
82
+ const key = `${dialect.msys ? "msys" : "posix"}\0${root}\0${home}\0${added.join("\0")}`;
83
83
  const hit = WHERE.get(key);
84
84
  if (hit !== undefined)
85
85
  return hit;
@@ -94,6 +94,7 @@ function where(root, home) {
94
94
  rootReal,
95
95
  homeReal: real(home),
96
96
  gitReal: join(rootReal, ".git"),
97
+ added: added.map(real),
97
98
  sys: named(roots.sys),
98
99
  mounts: named(roots.mounts),
99
100
  temps: [...new Set(roots.temps.map(real))],
@@ -137,6 +138,12 @@ function unrecoverable(w, p, over) {
137
138
  return what("the workspace root");
138
139
  if (within(p, w.rootReal))
139
140
  return what(`a directory above the workspace (${p})`);
141
+ for (const a of w.added) {
142
+ if (same(p, a) || within(p, a))
143
+ return what(`an added folder, or a directory above one (${p})`);
144
+ if (within(hostDialect().path.join(a, ".git"), p))
145
+ return what("an added folder's .git");
146
+ }
140
147
  // R7: the workspace's history — without a remote, gone
141
148
  if (within(w.gitReal, p))
142
149
  return what("the workspace's .git");
@@ -519,8 +526,8 @@ const child = (s) => ({ cwds: [...s.cwds], lastCd: null, prev: s.prev });
519
526
  * Would the floor refuse this command line? `workspaceRoot` is where the
520
527
  * shell runs it.
521
528
  */
522
- export function floorCheck(commandLine, workspaceRoot, home = homedir()) {
523
- const w = where(workspaceRoot, home);
529
+ export function floorCheck(commandLine, workspaceRoot, home = homedir(), added = []) {
530
+ const w = where(workspaceRoot, home, added);
524
531
  const seen = new Set();
525
532
  const tilde = (word) => (word.tilde && (word.text === "~" || word.text.startsWith("~/")) ? home + word.text.slice(1) : word.text);
526
533
  const walk = (nodes, state, depth) => {
@@ -652,7 +659,7 @@ export function isDestructive(commandLine) {
652
659
  }
653
660
  const ABSTAIN = { action: "abstain" };
654
661
  /** The chain member, at the chain's head: DENY or ABSTAIN. */
655
- export function floorExtension(on, workspaceRoot, check = floorCheck) {
662
+ export function floorExtension(on, workspaceRoot, check = floorCheck, added = () => []) {
656
663
  return {
657
664
  name: "floor",
658
665
  approvals: [
@@ -669,7 +676,7 @@ export function floorExtension(on, workspaceRoot, check = floorCheck) {
669
676
  // allow. The floor denies what it could not read.
670
677
  let v;
671
678
  try {
672
- v = check(command, workspaceRoot());
679
+ v = check(command, workspaceRoot(), undefined, added());
673
680
  }
674
681
  catch (err) {
675
682
  return { action: "deny", reason: `the floor could not read this line (${err instanceof Error ? err.name : "error"}) — kiso does not run it.` };
package/dist/index.js CHANGED
@@ -33,6 +33,7 @@ import { createRequire } from "node:module";
33
33
  import { basename, dirname, join } from "node:path";
34
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";
35
35
  import { disposeExtensions, SessionStore } from "@vincemakes/kiso-runtime";
36
+ import { readEnvironment } from "@vincemakes/kiso-runtime";
36
37
  import { listSessionSidecars, migrateSummaries, readProfile, readSessionName, summaryMigrationPending, writeSessionName } from "@vincemakes/kiso-runtime/internal";
37
38
  import { skillMenuItems } from "./skill-invoke.js";
38
39
  import { canonicalPath, hasSession, locateSession, projectLayoutActive, sessionFolders } from "./projects.js";
@@ -1073,6 +1074,10 @@ async function chatLoop(first, firstId, input, autoCompact) {
1073
1074
  refused = true;
1074
1075
  continue;
1075
1076
  }
1077
+ // ADR-0058 Amendment 19: the folders a web Work added; the tools here honour them too
1078
+ const addedFolders = readEnvironment(activeStoreDir, id).added;
1079
+ if (addedFolders.length > 0)
1080
+ bodyLog(`also working in: ${addedFolders.map(tildeOf).join(", ")}`);
1076
1081
  opened = id;
1077
1082
  if (rebuilt) {
1078
1083
  rebuilt = false;
package/dist/policy.d.ts CHANGED
@@ -10,7 +10,9 @@
10
10
  export { CODING_TOOL_RULES, SYSTEM_PROMPT, composeSystemPrompt } from "./coding-prompt.js";
11
11
  export { breakerExtension } from "./breaker.js";
12
12
  export { floorExtension } from "./floor.js";
13
+ export { fileStopRoots } from "./shell-words.js";
13
14
  export { MODE_LABEL, OFFERED_MODES, getMode, modeExtensions, modeSystemPrompt, setMode } from "./mode.js";
14
15
  export { protectedShellExtension } from "./protected-shell.js";
15
16
  export { guardSavedAllow, neverInheritedCall } from "./protected-writes.js";
16
17
  export { readOnlyShellExtension } from "./readonly-shell.js";
18
+ export { cdTargets, isGhPrCreate } from "./shell-facts.js";
package/dist/policy.js CHANGED
@@ -10,7 +10,9 @@
10
10
  export { CODING_TOOL_RULES, SYSTEM_PROMPT, composeSystemPrompt } from "./coding-prompt.js";
11
11
  export { breakerExtension } from "./breaker.js";
12
12
  export { floorExtension } from "./floor.js";
13
+ export { fileStopRoots } from "./shell-words.js";
13
14
  export { MODE_LABEL, OFFERED_MODES, getMode, modeExtensions, modeSystemPrompt, setMode } from "./mode.js";
14
15
  export { protectedShellExtension } from "./protected-shell.js";
15
16
  export { guardSavedAllow, neverInheritedCall } from "./protected-writes.js";
16
17
  export { readOnlyShellExtension } from "./readonly-shell.js";
18
+ export { cdTargets, isGhPrCreate } from "./shell-facts.js";
package/dist/projects.js CHANGED
@@ -153,7 +153,7 @@ export function locateSession(home, id, except) {
153
153
  * repeated move is a no-op.
154
154
  */
155
155
  export function sessionFiles(id) {
156
- return [join("traces", `${id}.jsonl`), `${id}.meta.json`, `${id}.lock`, `${id}.jsonl`];
156
+ return [join("traces", `${id}.jsonl`), `${id}.meta.json`, `${id}.environment.json`, `${id}.environment.json.lock`, `${id}.lock`, `${id}.jsonl`];
157
157
  }
158
158
  export function moveSessionFiles(fromDir, toDir, id, files = sessionFiles(id)) {
159
159
  let moved = 0;
package/dist/replay.js CHANGED
@@ -23,6 +23,7 @@ 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
25
  import { isForkNote } from "@vincemakes/kiso-runtime/internal";
26
+ import { isEnvironmentNote } from "@vincemakes/kiso-runtime";
26
27
  /** How many turns stay on screen in full. */
27
28
  const FULL_TURNS = 2;
28
29
  /** A user turn's opening line, or null when this input does not open one
@@ -34,7 +35,7 @@ function askOf(e) {
34
35
  return null;
35
36
  const via = e.via;
36
37
  // 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 content = Array.isArray(e.content) ? e.content.filter((b) => !(b.type === "text" && (isForkNote(b.text ?? "") || isEnvironmentNote(b.text ?? "")))) : e.content;
38
39
  const text = typeof via?.line === "string" ? via.line : echoText(content);
39
40
  return text.trim() === "" ? null : text;
40
41
  }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The directories a line moves the shell to, in order, when the call that ran it did not fail: the literal `cd <path>` commands of a line
3
+ * that is one `&&` chain, so every one of them ran (`cd /nonexistent; echo hello` exits 0 without having moved, and so does
4
+ * `false && cd /x && ls || true`). Relative paths resolve against `base`, then against the previous `cd`; `~` is `home`. An option, a
5
+ * variable or a word the host's shell cannot spell stops the reading there.
6
+ */
7
+ export declare function cdTargets(line: string, base: string, home: string): readonly string[];
8
+ /** Whether the line is one `&&` chain that ENDS in `gh pr create`: what its output ends with is the pull request it made (a mention in an echo, a `||` branch, or a command after it that prints another URL, is not one). */
9
+ export declare function isGhPrCreate(line: string): boolean;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Two facts a UI may read from a shell line, conservatively: literal words only, and what the reader cannot read yields nothing.
3
+ * They are DISPLAY facts (the web page's Recent folders and Repository rows): nothing here grants or refuses anything.
4
+ */
5
+ import { hostDialect, msysPath, parseShellLooseChecked } from "./shell-words.js";
6
+ const literal = (w) => (w === undefined || w.unknownAt !== -1 || w.variableOnly ? null : w.text);
7
+ /** The commands of a line that is ONE `&&` chain (no `||`, `;`, pipe or group anywhere), or null: only then did every command run when the call did not fail. */
8
+ function chainOf(line) {
9
+ const { nodes, truncated } = parseShellLooseChecked(line);
10
+ if (truncated || nodes.some((n, i) => n.kind !== "cmd" || (i > 0 && n.joinedBy !== "&&")))
11
+ return null;
12
+ return nodes;
13
+ }
14
+ /**
15
+ * The directories a line moves the shell to, in order, when the call that ran it did not fail: the literal `cd <path>` commands of a line
16
+ * that is one `&&` chain, so every one of them ran (`cd /nonexistent; echo hello` exits 0 without having moved, and so does
17
+ * `false && cd /x && ls || true`). Relative paths resolve against `base`, then against the previous `cd`; `~` is `home`. An option, a
18
+ * variable or a word the host's shell cannot spell stops the reading there.
19
+ */
20
+ export function cdTargets(line, base, home) {
21
+ const chain = chainOf(line);
22
+ if (chain === null)
23
+ return [];
24
+ const out = [];
25
+ const d = hostDialect(); // the shell's own spelling: Git Bash on Windows (`/c/x`, `C:/x`), POSIX elsewhere
26
+ let at = base;
27
+ for (const cmd of chain) {
28
+ if (literal(cmd.argv[0]) !== "cd")
29
+ continue;
30
+ const raw = cmd.argv.length === 2 ? literal(cmd.argv[1]) : null;
31
+ if (raw === null || raw === "-" || raw.startsWith("-"))
32
+ break;
33
+ const word = raw === "~" || raw.startsWith("~/") ? d.path.join(home, raw.slice(2)) : raw;
34
+ const read = d.msys ? msysPath(word) : { native: word };
35
+ if ("opaque" in read)
36
+ break;
37
+ at = d.path.resolve(at, read.native);
38
+ out.push(at);
39
+ }
40
+ return out;
41
+ }
42
+ /** Whether the line is one `&&` chain that ENDS in `gh pr create`: what its output ends with is the pull request it made (a mention in an echo, a `||` branch, or a command after it that prints another URL, is not one). */
43
+ export function isGhPrCreate(line) {
44
+ const c = chainOf(line)?.at(-1);
45
+ return c !== undefined && literal(c.argv[0]) === "gh" && literal(c.argv[1]) === "pr" && literal(c.argv[2]) === "create";
46
+ }
@@ -93,6 +93,8 @@ export declare const HOME_SUBTREES: readonly string[];
93
93
  /** The home subtrees for this host: on Windows, AppData too — credentials
94
94
  * and configuration that runs live there (Windows P2). */
95
95
  export declare function homeSubtrees(): readonly string[];
96
+ /** Folders the file tools' new reach never enters (ADR-0058 Amendment 19): kiso's own home and the credential homes. */
97
+ export declare function fileStopRoots(home: string, kisoHome: string): readonly string[];
96
98
  /** A real path in the case the DISK holds it. The JS realpath keeps the
97
99
  * case it was given, so on a case-insensitive disk `.ENV` stayed `.ENV`
98
100
  * and walked past every name-based predicate (the lead's review, B2). */
@@ -334,6 +334,10 @@ export const HOME_SUBTREES = [".ssh", ".config", ".kiso", ".gnupg", ".aws"];
334
334
  export function homeSubtrees() {
335
335
  return hostDialect().msys ? [...HOME_SUBTREES, "AppData"] : HOME_SUBTREES;
336
336
  }
337
+ /** Folders the file tools' new reach never enters (ADR-0058 Amendment 19): kiso's own home and the credential homes. */
338
+ export function fileStopRoots(home, kisoHome) {
339
+ return [kisoHome, ...homeSubtrees().map((s) => hostDialect().path.join(home, s))];
340
+ }
337
341
  /** A real path in the case the DISK holds it. The JS realpath keeps the
338
342
  * case it was given, so on a case-insensitive disk `.ENV` stayed `.ENV`
339
343
  * and walked past every name-based predicate (the lead's review, B2). */
package/dist/state.d.ts CHANGED
@@ -10,7 +10,7 @@ import type { TaskDeliveryItem } from "@vincemakes/kiso-core";
10
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
- import { type ShellTasks } from "@vincemakes/kiso-tools-node";
13
+ import { type FileAccess, type ShellTasks } from "@vincemakes/kiso-tools-node";
14
14
  /** finding #11: KISO_HOME is the ONE root — every default path derives from
15
15
  * it (sessions, trust, extensions, mcp config, skills). The dedicated
16
16
  * env vars (KISO_EXTENSIONS_DIR / KISO_MCP_CONFIG / KISO_SKILLS_DIR)
@@ -35,6 +35,8 @@ export declare function codingToolOptions(): {
35
35
  readonly excludeRoots: readonly string[];
36
36
  readonly secretEnvNames: readonly string[];
37
37
  readonly protectedFiles: readonly string[];
38
+ readonly access: (sessionId: string | undefined) => FileAccess;
39
+ readonly stopRoots: readonly string[];
38
40
  readonly tasks: (sessionId: string | undefined) => ShellTasks | undefined;
39
41
  readonly observedRevision: (sessionId: string | undefined, full: string, kind?: "edit" | "write") => string | undefined;
40
42
  readonly limits?: {
package/dist/state.js CHANGED
@@ -12,12 +12,14 @@ 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
+ import { kisoHome, readEnvironment } from "@vincemakes/kiso-runtime";
16
16
  import { TaskManager } from "@vincemakes/kiso-runtime/internal";
17
17
  import { processTaskBackend, readShowsWholeFile } from "@vincemakes/kiso-tools-node";
18
18
  import { collectWriter } from "@vincemakes/kiso-subagent-ext";
19
19
  import { canonicalPath, LEGACY_SESSIONS_DIR, projectDirFor, projectLayoutActive } from "./projects.js";
20
20
  import { taskNoticeLines, taskWhat } from "./task-notice.js";
21
+ import { getMode } from "./mode.js";
22
+ import { fileStopRoots } from "./shell-words.js";
21
23
  /** finding #11: KISO_HOME is the ONE root — every default path derives from
22
24
  * it (sessions, trust, extensions, mcp config, skills). The dedicated
23
25
  * env vars (KISO_EXTENSIONS_DIR / KISO_MCP_CONFIG / KISO_SKILLS_DIR)
@@ -49,6 +51,12 @@ let printRun = false;
49
51
  export function setPrintRun() {
50
52
  printRun = true;
51
53
  }
54
+ /** ADR-0058 Amendment 19: the same list the web reads, per call; a delegate child gets none of it. */
55
+ function fileAccess(sessionId) {
56
+ if ((Number.parseInt(process.env.KISO_SUBAGENT_DEPTH ?? "0", 10) || 0) >= 1)
57
+ return { added: [], outside: "deny" };
58
+ return { added: sessionId === undefined || activeStoreDir === "" ? [] : readEnvironment(activeStoreDir, sessionId).added, outside: getMode() === "full-access" ? "allow" : "deny" };
59
+ }
52
60
  export function codingToolOptions() {
53
61
  // KISO_MIN_FOREGROUND_MS — the test rigs' knob (like KISO_AUTO_DETACH_MS):
54
62
  // the least foreground wait (ADR-0058 Amendment 10, default 60 s). The
@@ -66,6 +74,8 @@ export function codingToolOptions() {
66
74
  excludeRoots: [kisoHome()],
67
75
  secretEnvNames: secretEnvNamesOf(configModels),
68
76
  protectedFiles: protectedFiles(),
77
+ access: fileAccess,
78
+ stopRoots: fileStopRoots(homedir(), kisoHome()),
69
79
  tasks: tasksFor,
70
80
  observedRevision: observedRevisionFromLog,
71
81
  ...(Object.keys(limits).length > 0 ? { limits } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-code",
3
- "version": "0.53.0",
3
+ "version": "0.54.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",
@@ -29,19 +29,19 @@
29
29
  "test": "vitest run"
30
30
  },
31
31
  "dependencies": {
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"
32
+ "@vincemakes/kiso-ask-ext": "0.54.0",
33
+ "@vincemakes/kiso-core": "0.54.0",
34
+ "@vincemakes/kiso-evals": "0.54.0",
35
+ "@vincemakes/kiso-mcp-ext": "0.54.0",
36
+ "@vincemakes/kiso-provider-anthropic": "0.54.0",
37
+ "@vincemakes/kiso-provider-openai": "0.54.0",
38
+ "@vincemakes/kiso-provider-openai-responses": "0.54.0",
39
+ "@vincemakes/kiso-runtime": "0.54.0",
40
+ "@vincemakes/kiso-skills-ext": "0.54.0",
41
+ "@vincemakes/kiso-subagent-ext": "0.54.0",
42
+ "@vincemakes/kiso-tools-node": "0.54.0",
43
+ "@vincemakes/kiso-tui": "0.54.0",
44
+ "@vincemakes/kiso-tui-cells": "0.54.0"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^26.1.2",