@vincemakes/kiso-tools-node 0.1.31 → 0.1.33
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 +4 -3
- package/dist/index.js +54 -11
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
* context.
|
|
13
13
|
*
|
|
14
14
|
* the token round: reads are RANGEABLE (read_file offset/limit, default head 200
|
|
15
|
-
* lines)
|
|
16
|
-
* an actionable continuation note
|
|
17
|
-
*
|
|
15
|
+
* lines), search/list are capped (50 / 200), shell output is capped — every
|
|
16
|
+
* truncation carries an actionable continuation note in the N of M form and
|
|
17
|
+
* states what was dropped (deterministic per file state), so the model
|
|
18
|
+
* always has a path to the full content.
|
|
18
19
|
*/
|
|
19
20
|
import { type Tool, type ToolResult } from "@vincemakes/kiso-core";
|
|
20
21
|
/**
|
package/dist/index.js
CHANGED
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
* context.
|
|
13
13
|
*
|
|
14
14
|
* the token round: reads are RANGEABLE (read_file offset/limit, default head 200
|
|
15
|
-
* lines)
|
|
16
|
-
* an actionable continuation note
|
|
17
|
-
*
|
|
15
|
+
* lines), search/list are capped (50 / 200), shell output is capped — every
|
|
16
|
+
* truncation carries an actionable continuation note in the N of M form and
|
|
17
|
+
* states what was dropped (deterministic per file state), so the model
|
|
18
|
+
* always has a path to the full content.
|
|
18
19
|
*/
|
|
19
20
|
import { execFileSync, spawn } from "node:child_process";
|
|
20
21
|
import { chmodSync, existsSync, readdirSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
@@ -33,6 +34,17 @@ const MAX_DIR_ENTRIES = 200;
|
|
|
33
34
|
function cap(text) {
|
|
34
35
|
return text.length > OUTPUT_CAP ? `${text.slice(0, OUTPUT_CAP)}\n…[truncated]` : text;
|
|
35
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* R-C item 2: chunked accumulation under the cap that COUNTS what it drops —
|
|
39
|
+
* the shell overflow note names the dropped bytes and the recovery path
|
|
40
|
+
* (the W10 continuation, made model-facing: what was dropped, how many).
|
|
41
|
+
*/
|
|
42
|
+
function capAccumulate(current, chunk) {
|
|
43
|
+
const room = OUTPUT_CAP - current.length;
|
|
44
|
+
if (room >= chunk.length)
|
|
45
|
+
return { text: current + chunk, dropped: 0 };
|
|
46
|
+
return { text: room <= 0 ? current : current + chunk.slice(0, room), dropped: chunk.length - Math.max(room, 0) };
|
|
47
|
+
}
|
|
36
48
|
/**
|
|
37
49
|
* A path that the workspace boundary refuses — never attempted, reported as
|
|
38
50
|
* a precondition (the tool COULD run it, the gate refused it).
|
|
@@ -178,7 +190,7 @@ function moreLinesNote(nextOffset, remaining) {
|
|
|
178
190
|
export function readFileTool(opts) {
|
|
179
191
|
return defineTool({
|
|
180
192
|
name: "read_file",
|
|
181
|
-
description: "Read a file's content from disk. Relative to the workspace root. Returns the first 200 lines by default; a file with more lines appends a note with the exact count and the offset to continue from. Pass offset (1-based first line) and/or limit (line count) to read a range.",
|
|
193
|
+
description: "Read a file's content from disk (the workspace reader — prefer it over shell cat/head/tail). Relative to the workspace root. Returns the first 200 lines by default; a file with more lines appends a note with the exact count and the offset to continue from. Pass offset (1-based first line) and/or limit (line count) to read a range.",
|
|
182
194
|
parameters: {
|
|
183
195
|
type: "object",
|
|
184
196
|
properties: {
|
|
@@ -189,6 +201,8 @@ export function readFileTool(opts) {
|
|
|
189
201
|
required: ["path"],
|
|
190
202
|
},
|
|
191
203
|
idempotent: true,
|
|
204
|
+
promptSnippet: "read_file — whole files or offset/limit ranges, workspace-relative paths",
|
|
205
|
+
promptGuidelines: ["read only the range you need — offset/limit beat whole-file reads"],
|
|
192
206
|
execute: async ({ path, offset, limit }) => {
|
|
193
207
|
try {
|
|
194
208
|
const full = resolveWithinRoot(opts.workspaceRoot, path);
|
|
@@ -273,6 +287,8 @@ export function listDirTool(opts) {
|
|
|
273
287
|
properties: { path: { type: "string", description: "Workspace-relative directory to list" } },
|
|
274
288
|
},
|
|
275
289
|
idempotent: true,
|
|
290
|
+
promptSnippet: "list_dir — directory entries (the workspace ls)",
|
|
291
|
+
promptGuidelines: ["narrow to a subdirectory when the listing caps at 200 entries"],
|
|
276
292
|
execute: async ({ path }) => {
|
|
277
293
|
try {
|
|
278
294
|
const dir = resolveWithinRoot(opts.workspaceRoot, path ?? ".");
|
|
@@ -282,7 +298,9 @@ export function listDirTool(opts) {
|
|
|
282
298
|
});
|
|
283
299
|
let content = entries.length ? cap(entries.slice(0, MAX_DIR_ENTRIES).join("\n")) : "(empty directory)";
|
|
284
300
|
if (entries.length > MAX_DIR_ENTRIES) {
|
|
285
|
-
|
|
301
|
+
// R-C item 2: the N of M form — the cap names its
|
|
302
|
+
// continuation (narrow to a subdirectory for more).
|
|
303
|
+
content += `\n… ${MAX_DIR_ENTRIES} of ${entries.length} entries shown (narrow to a subdirectory for more)`;
|
|
286
304
|
}
|
|
287
305
|
return { content, isError: false };
|
|
288
306
|
}
|
|
@@ -297,7 +315,7 @@ export function listDirTool(opts) {
|
|
|
297
315
|
export function searchTextTool(opts) {
|
|
298
316
|
return defineTool({
|
|
299
317
|
name: "search_text",
|
|
300
|
-
description: "Search files under a workspace directory (recursive) for a regular expression. Returns matching file:line excerpts, capped at 50 — an overflow note states the count of further matches (narrow the pattern to see them).",
|
|
318
|
+
description: "Search files under a workspace directory (recursive) for a regular expression (the workspace grep — prefer it over shell grep/rg). Returns matching file:line excerpts, capped at 50 — an overflow note states the count of further matches (narrow the pattern to see them).",
|
|
301
319
|
parameters: {
|
|
302
320
|
type: "object",
|
|
303
321
|
properties: {
|
|
@@ -307,6 +325,8 @@ export function searchTextTool(opts) {
|
|
|
307
325
|
required: ["pattern"],
|
|
308
326
|
},
|
|
309
327
|
idempotent: true,
|
|
328
|
+
promptSnippet: "search_text — regex search over workspace files",
|
|
329
|
+
promptGuidelines: ["narrow the pattern when the result caps — never re-run a broad search"],
|
|
310
330
|
execute: async ({ pattern, path }) => {
|
|
311
331
|
let root;
|
|
312
332
|
try {
|
|
@@ -366,8 +386,11 @@ export function searchTextTool(opts) {
|
|
|
366
386
|
return { content: `search_text failed: ${err.message}`, isError: true, errorKind: "fatal" };
|
|
367
387
|
}
|
|
368
388
|
let content = matches.length ? cap(matches.join("\n")) : "(no matches)";
|
|
369
|
-
if (totalMatches > matches.length)
|
|
370
|
-
|
|
389
|
+
if (totalMatches > matches.length) {
|
|
390
|
+
// R-C item 2: the N of M form — the cap names its
|
|
391
|
+
// continuation (narrow the pattern for more).
|
|
392
|
+
content += `\n… ${matches.length} of ${totalMatches} matches shown (narrow the pattern for more)`;
|
|
393
|
+
}
|
|
371
394
|
return { content, isError: false };
|
|
372
395
|
},
|
|
373
396
|
});
|
|
@@ -384,6 +407,7 @@ export function writeFileTool(opts) {
|
|
|
384
407
|
},
|
|
385
408
|
required: ["path", "content"],
|
|
386
409
|
},
|
|
410
|
+
promptSnippet: "write_file — write a whole file (creates parent directories)",
|
|
387
411
|
execute: async ({ path, content }) => {
|
|
388
412
|
let full;
|
|
389
413
|
try {
|
|
@@ -450,6 +474,8 @@ export function editFileTool(opts) {
|
|
|
450
474
|
},
|
|
451
475
|
required: ["path", "search", "replace"],
|
|
452
476
|
},
|
|
477
|
+
promptSnippet: "edit_file — replace an exact old_string block (never rewrite whole files)",
|
|
478
|
+
promptGuidelines: ["the search text must match the file EXACTLY — read the target first"],
|
|
453
479
|
execute: async ({ path, search, replace }) => {
|
|
454
480
|
let full;
|
|
455
481
|
try {
|
|
@@ -532,6 +558,8 @@ export function shellTool(opts) {
|
|
|
532
558
|
},
|
|
533
559
|
required: ["command"],
|
|
534
560
|
},
|
|
561
|
+
promptSnippet: "shell — real system commands only (builds, tests, git)",
|
|
562
|
+
promptGuidelines: ["commands run in the workspace root; on failure read the error and adjust — never repeat blindly"],
|
|
535
563
|
execute: async ({ command, timeoutMs }, ctx) => {
|
|
536
564
|
const timeout = timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
|
|
537
565
|
// E group: a PRE-aborted signal never spawns the command.
|
|
@@ -554,6 +582,8 @@ export function shellTool(opts) {
|
|
|
554
582
|
});
|
|
555
583
|
let stdout = "";
|
|
556
584
|
let stderr = "";
|
|
585
|
+
let stdoutDropped = 0;
|
|
586
|
+
let stderrDropped = 0;
|
|
557
587
|
let exited = false;
|
|
558
588
|
let settled = false;
|
|
559
589
|
let killing = false;
|
|
@@ -665,10 +695,14 @@ export function shellTool(opts) {
|
|
|
665
695
|
});
|
|
666
696
|
});
|
|
667
697
|
child.stdout?.on("data", (d) => {
|
|
668
|
-
|
|
698
|
+
const r = capAccumulate(stdout, d.toString());
|
|
699
|
+
stdout = r.text;
|
|
700
|
+
stdoutDropped += r.dropped;
|
|
669
701
|
});
|
|
670
702
|
child.stderr?.on("data", (d) => {
|
|
671
|
-
|
|
703
|
+
const r = capAccumulate(stderr, d.toString());
|
|
704
|
+
stderr = r.text;
|
|
705
|
+
stderrDropped += r.dropped;
|
|
672
706
|
});
|
|
673
707
|
child.on("error", (err) => {
|
|
674
708
|
settle({ content: `shell failed: ${err.message}`, isError: true, errorKind: "fatal" });
|
|
@@ -677,7 +711,16 @@ export function shellTool(opts) {
|
|
|
677
711
|
exited = true;
|
|
678
712
|
if (killing)
|
|
679
713
|
return; // the timeout/abort verdict owns the result
|
|
680
|
-
|
|
714
|
+
// R-C item 2: the overflow note names WHAT was dropped and
|
|
715
|
+
// the recovery path — a silent tail-cut would be the exact
|
|
716
|
+
// destructive class this round kills (W10 made model-facing).
|
|
717
|
+
const overflowNote = (dropped, stream) => dropped === 0
|
|
718
|
+
? ""
|
|
719
|
+
: `\n… [${stream} capped at ${OUTPUT_CAP} chars — ${dropped} more chars dropped; capture to a file and read it with read_file, or narrow the command]`;
|
|
720
|
+
const combined = (stdout +
|
|
721
|
+
overflowNote(stdoutDropped, "stdout") +
|
|
722
|
+
(stderr ? `\n[stderr] ${stderr}` : "") +
|
|
723
|
+
overflowNote(stderrDropped, "stderr")).trim();
|
|
681
724
|
settle(code === 0
|
|
682
725
|
? { content: combined || "(no output)", isError: false }
|
|
683
726
|
: { content: `exit ${code}: ${combined}`, isError: true, errorKind: "fatal" });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tools-node",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.33",
|
|
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.1.
|
|
24
|
+
"@vincemakes/kiso-core": "0.1.32"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^26.1.2",
|