@vincemakes/kiso-tools-node 0.24.5 → 0.25.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/index.d.ts CHANGED
@@ -91,8 +91,15 @@ export interface WorkspaceToolsOptions {
91
91
  * STRIPPED — a shell command must not inherit the agent's API keys (a
92
92
  * nested kiso would hit the REAL provider and blow up faux e2e runs;
93
93
  * the keys are an exposure surface for any command).
94
+ *
95
+ * ADR-0031 Amendment 1: a RECORD is the third shape — the STRIPPED
96
+ * environment plus these explicit entries, the entries winning (the
97
+ * MCP surface's decision 2, applied here). An embedding host injects
98
+ * what its tools need without a temp file and without opening the
99
+ * whole environment; an entry may deliberately re-add a stripped
100
+ * name — explicit beats the heuristic, as it does for MCP servers.
94
101
  */
95
- readonly shellEnv?: "inherit";
102
+ readonly shellEnv?: "inherit" | Readonly<Record<string, string>>;
96
103
  /**
97
104
  * DC-54 — the bounds that keep a tool call finite. Every field is
98
105
  * optional and defaults to the constant beside it; a host embedding
package/dist/index.js CHANGED
@@ -803,6 +803,7 @@ export function writeFileTool(opts) {
803
803
  promptSnippet: "write_file — create or replace a whole file",
804
804
  promptGuidelines: ["write/edit: cite the file's latest revision as expectedRevision; each successful mutation returns the next one; use \"absent\" only to create"],
805
805
  execute: async ({ path, content, expectedRevision: citedRevision }) => {
806
+ const maxReadBytes = opts.limits?.readMaxFileBytes ?? READ_MAX_FILE_BYTES;
806
807
  // WR-1-F2: normalize every plausible copy of the token FIRST —
807
808
  // tolerance in the reader, strictness in the comparison.
808
809
  const expectedRevision = citedRevision === undefined ? undefined : normalizeRevision(citedRevision);
@@ -838,6 +839,19 @@ export function writeFileTool(opts) {
838
839
  if (!exists) {
839
840
  return precondition(`write_file: ${path} no longer exists — pass expectedRevision:"absent" to create it`);
840
841
  }
842
+ // DC-54 owed (R14) — the ceiling, before the read.
843
+ //
844
+ // `read_file` got this bound in 0.24.5; these two did not,
845
+ // and they read the WHOLE file to compute a revision BEFORE
846
+ // the comparison that would reject the call — so the freeze
847
+ // happened on the way to the refusal rather than instead of
848
+ // it. Same ceiling, same precondition shape, same reason: a
849
+ // revision over a prefix could never match, so there is
850
+ // nothing to compute and nothing to compare.
851
+ const wSize = statSync(full).size;
852
+ if (wSize > maxReadBytes) {
853
+ return precondition(`write_file: ${path} is ${mib(wSize)} — too large to read (ceiling ${mib(maxReadBytes)}); use shell with sed/head to take a range`);
854
+ }
841
855
  const current = contentRevision(readFileSync(full));
842
856
  if (current !== expectedRevision) {
843
857
  return precondition(`write_file: ${path} changed since ${expectedRevision} — read it again and cite its [rev:…] line, then re-apply the change`);
@@ -937,6 +951,7 @@ export function editFileTool(opts) {
937
951
  promptSnippet: "edit_file — replace an exact old_string block (never rewrite whole files)",
938
952
  promptGuidelines: ["write/edit: cite the file's latest revision as expectedRevision; each successful mutation returns the next one; use \"absent\" only to create"],
939
953
  execute: async ({ path, search, replace, edits, expectedRevision: citedRevision }) => {
954
+ const maxReadBytes = opts.limits?.readMaxFileBytes ?? READ_MAX_FILE_BYTES;
940
955
  // WR-1-F2: normalize the citation (see write_file).
941
956
  const expectedRevision = citedRevision === undefined ? undefined : normalizeRevision(citedRevision);
942
957
  // WR-1E2 — the form XOR (the draft-07 subset has no oneOf; the
@@ -983,6 +998,19 @@ export function editFileTool(opts) {
983
998
  // what was edited (the external validation→replacement window
984
999
  // remains and is the narrowed claim). Staleness reports BEFORE
985
1000
  // the pattern search: the truer cause first.
1001
+ // DC-54 owed (R14) — the ceiling, before the read.
1002
+ //
1003
+ // `read_file` got this bound in 0.24.5; these two did not,
1004
+ // and they read the WHOLE file to compute a revision BEFORE
1005
+ // the comparison that would reject the call — so the freeze
1006
+ // happened on the way to the refusal rather than instead of
1007
+ // it. Same ceiling, same precondition shape, same reason: a
1008
+ // revision over a prefix could never match, so there is
1009
+ // nothing to compute and nothing to compare.
1010
+ const eSize = statSync(full).size;
1011
+ if (eSize > maxReadBytes) {
1012
+ return precondition(`edit_file: ${path} is ${mib(eSize)} — too large to read (ceiling ${mib(maxReadBytes)}); use shell with sed/head to take a range`);
1013
+ }
986
1014
  const bytes = readFileSync(full);
987
1015
  const current = contentRevision(bytes);
988
1016
  if (current !== expectedRevision) {
@@ -1105,13 +1133,16 @@ export function shellTool(opts) {
1105
1133
  // not just the outer shell (Area 4). cwd is the workspace.
1106
1134
  // bootstrap #3 (finding #7): the shell child NEVER inherits kiso's own
1107
1135
  // provider credentials by default — only the explicit
1108
- // shellEnv: "inherit" opt-in keeps them.
1136
+ // shellEnv: "inherit" opt-in keeps them. A record (ADR-0031
1137
+ // Amendment 1) rides on the STRIPPED base and wins per key.
1109
1138
  const child = spawn(command, {
1110
1139
  shell: true,
1111
1140
  detached: true,
1112
1141
  cwd: opts.workspaceRoot,
1113
1142
  stdio: ["ignore", "pipe", "pipe"],
1114
- env: opts.shellEnv === "inherit" ? process.env : strippedShellEnv(process.env),
1143
+ env: opts.shellEnv === "inherit"
1144
+ ? process.env
1145
+ : { ...strippedShellEnv(process.env), ...(opts.shellEnv ?? {}) },
1115
1146
  });
1116
1147
  let stdout = "";
1117
1148
  let stderr = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-tools-node",
3
- "version": "0.24.5",
3
+ "version": "0.25.0",
4
4
  "description": "kiso coding tools for Node hosts — read file, list directory, search text, write/edit file, shell command.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -21,7 +21,7 @@
21
21
  "test": "vitest run"
22
22
  },
23
23
  "dependencies": {
24
- "@vincemakes/kiso-core": "0.24.5"
24
+ "@vincemakes/kiso-core": "0.25.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^26.1.2",