@x-code-cli/core 0.2.1 → 0.2.2
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/agent/api-errors.js +1 -1
- package/dist/agent/api-errors.js.map +1 -1
- package/dist/agent/compression.d.ts +25 -0
- package/dist/agent/compression.d.ts.map +1 -0
- package/dist/agent/compression.js +105 -0
- package/dist/agent/compression.js.map +1 -0
- package/dist/agent/diff.js.map +1 -1
- package/dist/agent/file-ingest.d.ts +14 -0
- package/dist/agent/file-ingest.d.ts.map +1 -1
- package/dist/agent/file-ingest.js +125 -34
- package/dist/agent/file-ingest.js.map +1 -1
- package/dist/agent/light-compact.d.ts.map +1 -1
- package/dist/agent/light-compact.js +0 -19
- package/dist/agent/light-compact.js.map +1 -1
- package/dist/agent/loop-guard.d.ts.map +1 -1
- package/dist/agent/loop-guard.js.map +1 -1
- package/dist/agent/loop-state.d.ts.map +1 -1
- package/dist/agent/loop-state.js +8 -1
- package/dist/agent/loop-state.js.map +1 -1
- package/dist/agent/loop.d.ts +2 -3
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +16 -92
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/messages.d.ts +9 -0
- package/dist/agent/messages.d.ts.map +1 -1
- package/dist/agent/messages.js +15 -0
- package/dist/agent/messages.js.map +1 -1
- package/dist/agent/plan-storage.d.ts.map +1 -1
- package/dist/agent/plan-storage.js +1 -1
- package/dist/agent/plan-storage.js.map +1 -1
- package/dist/agent/plan-tools.d.ts +8 -0
- package/dist/agent/plan-tools.d.ts.map +1 -0
- package/dist/agent/plan-tools.js +150 -0
- package/dist/agent/plan-tools.js.map +1 -0
- package/dist/agent/provider-compat.d.ts.map +1 -1
- package/dist/agent/provider-compat.js.map +1 -1
- package/dist/agent/sub-agents/built-in.d.ts.map +1 -1
- package/dist/agent/sub-agents/built-in.js +41 -15
- package/dist/agent/sub-agents/built-in.js.map +1 -1
- package/dist/agent/sub-agents/loader.d.ts.map +1 -1
- package/dist/agent/sub-agents/loader.js.map +1 -1
- package/dist/agent/sub-agents/runner.d.ts.map +1 -1
- package/dist/agent/sub-agents/runner.js +12 -8
- package/dist/agent/sub-agents/runner.js.map +1 -1
- package/dist/agent/tool-execution.d.ts +34 -2
- package/dist/agent/tool-execution.d.ts.map +1 -1
- package/dist/agent/tool-execution.js +363 -360
- package/dist/agent/tool-execution.js.map +1 -1
- package/dist/agent/tool-result-sanitize.d.ts +21 -7
- package/dist/agent/tool-result-sanitize.d.ts.map +1 -1
- package/dist/agent/tool-result-sanitize.js +56 -30
- package/dist/agent/tool-result-sanitize.js.map +1 -1
- package/dist/agent/vision-fallback.d.ts.map +1 -1
- package/dist/agent/vision-fallback.js +3 -14
- package/dist/agent/vision-fallback.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/permissions/index.d.ts +9 -4
- package/dist/permissions/index.d.ts.map +1 -1
- package/dist/permissions/index.js +41 -6
- package/dist/permissions/index.js.map +1 -1
- package/dist/permissions/session-store.d.ts +34 -20
- package/dist/permissions/session-store.d.ts.map +1 -1
- package/dist/permissions/session-store.js +94 -34
- package/dist/permissions/session-store.js.map +1 -1
- package/dist/providers/cache-control.d.ts.map +1 -1
- package/dist/providers/cache-control.js +0 -29
- package/dist/providers/cache-control.js.map +1 -1
- package/dist/providers/thinking.d.ts.map +1 -1
- package/dist/providers/thinking.js +2 -6
- package/dist/providers/thinking.js.map +1 -1
- package/dist/tools/ask-user.d.ts.map +1 -1
- package/dist/tools/ask-user.js +17 -7
- package/dist/tools/ask-user.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +8 -1
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/glob.d.ts.map +1 -1
- package/dist/tools/glob.js +9 -2
- package/dist/tools/glob.js.map +1 -1
- package/dist/tools/grep.d.ts +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +29 -6
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/list-dir.d.ts.map +1 -1
- package/dist/tools/list-dir.js.map +1 -1
- package/dist/tools/read-file.d.ts.map +1 -1
- package/dist/tools/read-file.js +78 -36
- package/dist/tools/read-file.js.map +1 -1
- package/dist/tools/shell-provider.d.ts +1 -0
- package/dist/tools/shell-provider.d.ts.map +1 -1
- package/dist/tools/shell-provider.js +7 -0
- package/dist/tools/shell-provider.js.map +1 -1
- package/dist/tools/shell-utils.d.ts.map +1 -1
- package/dist/tools/shell-utils.js +45 -2
- package/dist/tools/shell-utils.js.map +1 -1
- package/dist/tools/shell.d.ts.map +1 -1
- package/dist/tools/shell.js +15 -1
- package/dist/tools/shell.js.map +1 -1
- package/dist/tools/task.d.ts.map +1 -1
- package/dist/tools/task.js +4 -4
- package/dist/tools/task.js.map +1 -1
- package/dist/tools/todo-write.d.ts.map +1 -1
- package/dist/tools/todo-write.js.map +1 -1
- package/dist/tools/web-fetch.d.ts +2 -0
- package/dist/tools/web-fetch.d.ts.map +1 -1
- package/dist/tools/web-fetch.js +92 -27
- package/dist/tools/web-fetch.js.map +1 -1
- package/dist/tools/write-file.d.ts.map +1 -1
- package/dist/tools/write-file.js +7 -1
- package/dist/tools/write-file.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/lru-cache.d.ts +17 -0
- package/dist/utils/lru-cache.d.ts.map +1 -0
- package/dist/utils/lru-cache.js +40 -0
- package/dist/utils/lru-cache.js.map +1 -0
- package/dist/utils/media-type.d.ts +5 -0
- package/dist/utils/media-type.d.ts.map +1 -0
- package/dist/utils/media-type.js +19 -0
- package/dist/utils/media-type.js.map +1 -0
- package/dist/utils/message-helpers.d.ts +6 -0
- package/dist/utils/message-helpers.d.ts.map +1 -0
- package/dist/utils/message-helpers.js +14 -0
- package/dist/utils/message-helpers.js.map +1 -0
- package/package.json +1 -1
package/dist/tools/read-file.js
CHANGED
|
@@ -16,53 +16,95 @@ import path from 'node:path';
|
|
|
16
16
|
import { tool } from 'ai';
|
|
17
17
|
import { z } from 'zod';
|
|
18
18
|
import { classifyFile } from '../agent/file-ingest.js';
|
|
19
|
+
import { mediaTypeFor } from '../utils/media-type.js';
|
|
19
20
|
import { formatToolError } from '../utils/tool-errors.js';
|
|
20
21
|
import { reportProgress } from './progress.js';
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
* a realistic ceiling for "skim the whole thing", and anything bigger is
|
|
38
|
-
* almost always used with grep first. */
|
|
39
|
-
const LARGE_FILE_LINE_THRESHOLD = 500;
|
|
22
|
+
/** Default cap on lines returned by a no-args readFile call. Aligned with
|
|
23
|
+
* Claude Code's MAX_LINES_TO_READ — picked empirically: 2000 lines is a
|
|
24
|
+
* realistic ceiling for "skim the whole thing", anything bigger is almost
|
|
25
|
+
* always used with grep first. Was 500 originally, bumped after observing
|
|
26
|
+
* that 500 forced too many round-trips for legitimate "read this whole
|
|
27
|
+
* module" cases (4× more calls than CC for the same coverage). */
|
|
28
|
+
const LARGE_FILE_LINE_THRESHOLD = 2000;
|
|
29
|
+
/** Byte cap on a single tool-result payload. Mirrors the @-attach ingest cap
|
|
30
|
+
* in file-ingest.ts and Claude Code's Read-tool 25K-token default (~100 KB
|
|
31
|
+
* English / ~75 KB CJK; 256 KB gives headroom). Applies to BOTH the
|
|
32
|
+
* default head case AND the explicit offset/limit case — without this,
|
|
33
|
+
* a model that asks for `limit: 90000` on a multi-MB file gets the entire
|
|
34
|
+
* thing dumped into context and the next turn fails with
|
|
35
|
+
* context_length_exceeded. CC enforces the same invariant via
|
|
36
|
+
* `validateContentTokens`. */
|
|
37
|
+
const MAX_READ_BYTES = 256 * 1024;
|
|
40
38
|
async function readTextResult(filePath, offset, limit) {
|
|
41
39
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
42
40
|
const lines = content.split('\n');
|
|
43
41
|
const totalLines = lines.length;
|
|
44
|
-
// When the caller passes neither offset nor limit and the file is large,
|
|
45
|
-
// return only the head and tell the model how to request the rest. Without
|
|
46
|
-
// this guard, models happily read 2000-line files "just to see what's in
|
|
47
|
-
// there" and the full content rides along on every subsequent turn. The
|
|
48
|
-
// downstream truncator (tool-result-sanitize) would eventually clip this,
|
|
49
|
-
// but doing it at the tool level preserves intent — the model sees
|
|
50
|
-
// explicitly that the file was large and that it should narrow the range.
|
|
51
42
|
const userSpecifiedRange = offset != null || limit != null;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
43
|
+
// Decide which slice the caller asked for; head-truncation is its own
|
|
44
|
+
// mode so the trailing hint can say "showing first N" vs "byte cap hit".
|
|
45
|
+
let start;
|
|
46
|
+
let end;
|
|
47
|
+
let isHeadTruncation = false;
|
|
48
|
+
if (userSpecifiedRange) {
|
|
49
|
+
start = (offset ?? 1) - 1;
|
|
50
|
+
end = limit ? start + limit : lines.length;
|
|
51
|
+
}
|
|
52
|
+
else if (totalLines > LARGE_FILE_LINE_THRESHOLD) {
|
|
53
|
+
start = 0;
|
|
54
|
+
end = LARGE_FILE_LINE_THRESHOLD;
|
|
55
|
+
isHeadTruncation = true;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
start = 0;
|
|
59
|
+
end = lines.length;
|
|
58
60
|
}
|
|
59
|
-
const start = (offset ?? 1) - 1;
|
|
60
|
-
const end = limit ? start + limit : lines.length;
|
|
61
61
|
const sliced = lines.slice(start, end);
|
|
62
|
-
|
|
62
|
+
// Build the numbered-line output line-by-line, stopping as soon as adding
|
|
63
|
+
// the next line would push past MAX_READ_BYTES. Per-line byte counting
|
|
64
|
+
// is necessary for CJK / wide-char content where line.length lies about
|
|
65
|
+
// the on-the-wire size.
|
|
66
|
+
const formatted = [];
|
|
67
|
+
let bytes = 0;
|
|
68
|
+
for (let i = 0; i < sliced.length; i++) {
|
|
69
|
+
const numbered = `${start + i + 1}\t${sliced[i]}`;
|
|
70
|
+
const addedBytes = Buffer.byteLength(numbered, 'utf-8') + (formatted.length > 0 ? 1 : 0);
|
|
71
|
+
if (bytes + addedBytes > MAX_READ_BYTES && formatted.length > 0)
|
|
72
|
+
break;
|
|
73
|
+
formatted.push(numbered);
|
|
74
|
+
bytes += addedBytes;
|
|
75
|
+
}
|
|
76
|
+
const includedLines = formatted.length;
|
|
77
|
+
const body = formatted.join('\n');
|
|
78
|
+
// Trailing hint — same shape as Claude Code's MaxFileReadTokenExceededError
|
|
79
|
+
// message: tells the model exactly which next call will work, so it can
|
|
80
|
+
// self-recover instead of giving up or repeating the same call.
|
|
81
|
+
if (isHeadTruncation) {
|
|
82
|
+
const note = includedLines < sliced.length ? ` (further capped at ${MAX_READ_BYTES / 1024} KB)` : '';
|
|
83
|
+
return (body +
|
|
84
|
+
`\n\n[readFile: showing first ${includedLines}/${totalLines} lines${note}. ` +
|
|
85
|
+
`Call readFile again with offset/limit to view other ranges, or use grep to find specific symbols. ` +
|
|
86
|
+
`For whole-file analysis of very large files, consider delegating to a sub-agent via the task tool — ` +
|
|
87
|
+
`each sub-agent reads in isolated context and returns only a summary.]`);
|
|
88
|
+
}
|
|
89
|
+
if (includedLines < sliced.length) {
|
|
90
|
+
const nextOffset = start + includedLines + 1;
|
|
91
|
+
return (body +
|
|
92
|
+
`\n\n[readFile: output capped at ${MAX_READ_BYTES / 1024} KB; ` +
|
|
93
|
+
`returned ${includedLines}/${sliced.length} requested lines (lines ${start + 1}-${start + includedLines}). ` +
|
|
94
|
+
`Call readFile again with offset=${nextOffset} for the next chunk, or narrow the range.]`);
|
|
95
|
+
}
|
|
96
|
+
return body;
|
|
63
97
|
}
|
|
64
98
|
export const readFile = tool({
|
|
65
|
-
description:
|
|
99
|
+
description: `Read a file from the local filesystem. Assume this tool can read all files on the machine.
|
|
100
|
+
|
|
101
|
+
Usage:
|
|
102
|
+
- The filePath parameter must be an absolute path, not a relative path.
|
|
103
|
+
- You can optionally specify offset and limit (especially handy for long files), but it's recommended to read the whole file first.
|
|
104
|
+
- Results are returned with line numbers starting at 1.
|
|
105
|
+
- This tool can read images (PNG, JPG, etc.) and PDFs — their content is presented inline.
|
|
106
|
+
- This tool can only read files, not directories. To list a directory, use listDir or shell with ls.
|
|
107
|
+
- If a file path is provided by the user, assume it is valid.`,
|
|
66
108
|
inputSchema: z.object({
|
|
67
109
|
filePath: z.string().describe('Absolute path to the file'),
|
|
68
110
|
offset: z.number().optional().describe('Start line (1-based, text files only)'),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-file.js","sourceRoot":"","sources":["../../src/tools/read-file.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,EAAE;AACF,2EAA2E;AAC3E,wEAAwE;AACxE,qEAAqE;AACrE,2EAA2E;AAC3E,gCAAgC;AAChC,EAAE;AACF,sEAAsE;AACtE,sEAAsE;AACtE,wEAAwE;AACxE,wEAAwE;AACxE,0BAA0B;AAC1B,OAAO,EAAE,MAAM,kBAAkB,CAAA;AACjC,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAEzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAE9C,
|
|
1
|
+
{"version":3,"file":"read-file.js","sourceRoot":"","sources":["../../src/tools/read-file.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell-provider.d.ts","sourceRoot":"","sources":["../../src/tools/shell-provider.ts"],"names":[],"mappings":"AAMA,OAAO,
|
|
1
|
+
{"version":3,"file":"shell-provider.d.ts","sourceRoot":"","sources":["../../src/tools/shell-provider.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,aAAa,EAAS,MAAM,OAAO,CAAA;AAIjD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,KAAK,GAAG,YAAY,CAAA;AAMrD,eAAO,MAAM,gBAAgB,QAAmB,CAAA;AAEhD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IACvB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;uBAEmB;IACnB,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,SAAS,CAAA;IACf,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,aAAa,CAAA;CAC/D;AA6DD,wBAAgB,gBAAgB,IAAI,aAAa,CAYhD"}
|
|
@@ -6,12 +6,18 @@
|
|
|
6
6
|
// hand-roll quote escapes for PowerShell.
|
|
7
7
|
import { execa } from 'execa';
|
|
8
8
|
import os from 'node:os';
|
|
9
|
+
// 20 MB — matches Claude Code's ripgrep buffer; generous enough for real
|
|
10
|
+
// workloads, small enough to prevent an accidental `yes` or `find /` from
|
|
11
|
+
// eating all memory. When exceeded, execa terminates the child with SIGTERM
|
|
12
|
+
// and surfaces a "maxBuffer exceeded" error.
|
|
13
|
+
export const MAX_SHELL_BUFFER = 20 * 1024 * 1024;
|
|
9
14
|
function createPosixProvider(executable, type) {
|
|
10
15
|
return {
|
|
11
16
|
type,
|
|
12
17
|
spawn(command, opts) {
|
|
13
18
|
return execa(executable, ['-c', command], {
|
|
14
19
|
timeout: opts.timeout,
|
|
20
|
+
maxBuffer: MAX_SHELL_BUFFER,
|
|
15
21
|
cwd: opts.cwd,
|
|
16
22
|
reject: false,
|
|
17
23
|
cancelSignal: opts.signal,
|
|
@@ -52,6 +58,7 @@ function createPowerShellProvider(executable) {
|
|
|
52
58
|
].join('\n');
|
|
53
59
|
return execa(executable, ['-NoProfile', '-NonInteractive', '-EncodedCommand', encodePowerShellCommand(wrapped)], {
|
|
54
60
|
timeout: opts.timeout,
|
|
61
|
+
maxBuffer: MAX_SHELL_BUFFER,
|
|
55
62
|
cwd: opts.cwd,
|
|
56
63
|
reject: false,
|
|
57
64
|
cancelSignal: opts.signal,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell-provider.js","sourceRoot":"","sources":["../../src/tools/shell-provider.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,EAAE;AACF,8EAA8E;AAC9E,4EAA4E;AAC5E,8EAA8E;AAC9E,0CAA0C;AAC1C,OAAO,
|
|
1
|
+
{"version":3,"file":"shell-provider.js","sourceRoot":"","sources":["../../src/tools/shell-provider.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,EAAE;AACF,8EAA8E;AAC9E,4EAA4E;AAC5E,8EAA8E;AAC9E,0CAA0C;AAC1C,OAAO,EAAsB,KAAK,EAAE,MAAM,OAAO,CAAA;AAEjD,OAAO,EAAE,MAAM,SAAS,CAAA;AAIxB,yEAAyE;AACzE,0EAA0E;AAC1E,4EAA4E;AAC5E,6CAA6C;AAC7C,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;AAiBhD,SAAS,mBAAmB,CAAC,UAAkB,EAAE,IAAoB;IACnE,OAAO;QACL,IAAI;QACJ,KAAK,CAAC,OAAO,EAAE,IAAI;YACjB,OAAO,KAAK,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE;gBACxC,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,SAAS,EAAE,gBAAgB;gBAC3B,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,MAAM,EAAE,KAAK;gBACb,YAAY,EAAE,IAAI,CAAC,MAAM;gBACzB,GAAG,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,gBAAgB,EAAE,OAAO,EAAE;aACjE,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC;AAED,+EAA+E;AAC/E,4EAA4E;AAC5E,8EAA8E;AAC9E,yBAAyB;AACzB,SAAS,uBAAuB,CAAC,SAAiB;IAChD,OAAO,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;AAC7D,CAAC;AAED,SAAS,wBAAwB,CAAC,UAAkB;IAClD,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,KAAK,CAAC,OAAO,EAAE,IAAI;YACjB,6DAA6D;YAC7D,wEAAwE;YACxE,mEAAmE;YACnE,gDAAgD;YAChD,qEAAqE;YACrE,qEAAqE;YACrE,4BAA4B;YAC5B,oEAAoE;YACpE,qEAAqE;YACrE,uEAAuE;YACvE,qEAAqE;YACrE,iEAAiE;YACjE,MAAM,OAAO,GAAG;gBACd,0DAA0D;gBAC1D,0CAA0C;gBAC1C,OAAO;gBACP,qFAAqF;gBACrF,YAAY;aACb,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACZ,OAAO,KAAK,CAAC,UAAU,EAAE,CAAC,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,uBAAuB,CAAC,OAAO,CAAC,CAAC,EAAE;gBAC/G,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,SAAS,EAAE,gBAAgB;gBAC3B,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,MAAM,EAAE,KAAK;gBACb,YAAY,EAAE,IAAI,CAAC,MAAM;gBACzB,GAAG,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,gBAAgB,EAAE,OAAO,EAAE;aACjE,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,IAAI,EAAE,CAAC,QAAQ,EAAE,KAAK,OAAO,EAAE,CAAC;QAC9B,wEAAwE;QACxE,6DAA6D;QAC7D,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAA;QAC/B,IAAI,KAAK,IAAI,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1C,OAAO,mBAAmB,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;QAC3E,CAAC;QACD,OAAO,wBAAwB,CAAC,gBAAgB,CAAC,CAAA;IACnD,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,WAAW,CAAA;IAClD,OAAO,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;AACnF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell-utils.d.ts","sourceRoot":"","sources":["../../src/tools/shell-utils.ts"],"names":[],"mappings":"AAKA,YAAY,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAEpD,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CA0CxD;
|
|
1
|
+
{"version":3,"file":"shell-utils.d.ts","sourceRoot":"","sources":["../../src/tools/shell-utils.ts"],"names":[],"mappings":"AAKA,YAAY,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAEpD,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CA0CxD;AAoGD,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED,+DAA+D;AAC/D,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGlD"}
|
|
@@ -76,18 +76,61 @@ const READ_ONLY_COMMANDS = [
|
|
|
76
76
|
'Test-Path',
|
|
77
77
|
];
|
|
78
78
|
/** Git sub-commands that are read-only */
|
|
79
|
-
const READ_ONLY_GIT_SUBCOMMANDS = ['status', 'log', 'diff', 'branch', 'show', 'remote', 'tag'];
|
|
79
|
+
const READ_ONLY_GIT_SUBCOMMANDS = ['status', 'log', 'diff', 'branch', 'show', 'remote', 'tag', 'stash list', 'reflog'];
|
|
80
80
|
// Pre-compiled regexes for performance
|
|
81
81
|
const READ_ONLY_REGEX = new RegExp(`^\\s*(${READ_ONLY_COMMANDS.join('|')}|git\\s+(${READ_ONLY_GIT_SUBCOMMANDS.join('|')}))\\b`);
|
|
82
82
|
const DESTRUCTIVE_PATTERNS = [
|
|
83
|
+
// ── Filesystem destruction ──
|
|
83
84
|
/\brm\s+(-[a-z]*f|-[a-z]*r|--force|--recursive)/,
|
|
85
|
+
/\brm\s+-rf\b/,
|
|
84
86
|
/\bsudo\b/,
|
|
85
87
|
/\bmkfs\b/,
|
|
86
88
|
/\bdd\s+if=/,
|
|
87
89
|
/\b(chmod|chown)\s+.*\//,
|
|
88
90
|
/>\s*\/dev\/sd/,
|
|
89
91
|
/\bformat\b/,
|
|
90
|
-
/\bRemove-Item\s+.*-Recurse
|
|
92
|
+
/\bRemove-Item\s+.*-Recurse/i,
|
|
93
|
+
/\bRemove-Item\s+.*-Force/i,
|
|
94
|
+
/\bdel\s+\/[sS]/,
|
|
95
|
+
/\brmdir\s+\/[sS]/,
|
|
96
|
+
// ── Git destructive operations ──
|
|
97
|
+
/\bgit\s+push\s+.*--force\b/,
|
|
98
|
+
/\bgit\s+push\s+-f\b/,
|
|
99
|
+
/\bgit\s+reset\s+--hard\b/,
|
|
100
|
+
/\bgit\s+clean\s+-[a-z]*f/,
|
|
101
|
+
/\bgit\s+checkout\s+--\s*\./,
|
|
102
|
+
/\bgit\s+rebase\b/,
|
|
103
|
+
/\bgit\s+filter-branch\b/,
|
|
104
|
+
/\bgit\s+reflog\s+expire\b/,
|
|
105
|
+
/\bgit\s+gc\s+--prune\b/,
|
|
106
|
+
// ── Remote code execution / download-and-exec ──
|
|
107
|
+
/\bcurl\s.*\|\s*(ba)?sh\b/,
|
|
108
|
+
/\bwget\s.*\|\s*(ba)?sh\b/,
|
|
109
|
+
/\bcurl\s.*\|\s*python/,
|
|
110
|
+
/\bwget\s.*\|\s*python/,
|
|
111
|
+
// ── System control ──
|
|
112
|
+
/\bshutdown\b/,
|
|
113
|
+
/\breboot\b/,
|
|
114
|
+
/\binit\s+[06]\b/,
|
|
115
|
+
/\bsystemctl\s+(stop|disable|mask|halt|poweroff)\b/,
|
|
116
|
+
/\bkillall\b/,
|
|
117
|
+
/\bpkill\s+-9\b/,
|
|
118
|
+
/\bStop-Computer\b/i,
|
|
119
|
+
/\bRestart-Computer\b/i,
|
|
120
|
+
// ── Database destruction ──
|
|
121
|
+
/\bDROP\s+(DATABASE|TABLE|SCHEMA)\b/i,
|
|
122
|
+
/\bTRUNCATE\s+TABLE\b/i,
|
|
123
|
+
/\bDELETE\s+FROM\s+\S+\s*;?\s*$/im,
|
|
124
|
+
// ── Container / infra destruction ──
|
|
125
|
+
/\bdocker\s+(rm|rmi|system\s+prune|volume\s+rm)\b/,
|
|
126
|
+
/\bkubectl\s+delete\b/,
|
|
127
|
+
// ── Environment pollution ──
|
|
128
|
+
/\bnpm\s+publish\b/,
|
|
129
|
+
/\bpnpm\s+publish\b/,
|
|
130
|
+
/\byarn\s+publish\b/,
|
|
131
|
+
// ── Disk / partition ──
|
|
132
|
+
/\bfdisk\b/,
|
|
133
|
+
/\bparted\b/,
|
|
91
134
|
];
|
|
92
135
|
/** Check if a sub-command is read-only (safe to auto-allow) */
|
|
93
136
|
export function isReadOnly(cmd) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell-utils.js","sourceRoot":"","sources":["../../src/tools/shell-utils.ts"],"names":[],"mappings":"AAOA,oFAAoF;AACpF,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,gDAAgD;IAChD,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,IAAI,OAAO,GAAG,EAAE,CAAA;IAChB,IAAI,aAAa,GAAG,KAAK,CAAA;IACzB,IAAI,aAAa,GAAG,KAAK,CAAA;IAEzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;QACjB,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;QAEvB,IAAI,EAAE,KAAK,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;YACjC,aAAa,GAAG,CAAC,aAAa,CAAA;YAC9B,OAAO,IAAI,EAAE,CAAA;QACf,CAAC;aAAM,IAAI,EAAE,KAAK,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;YACxC,aAAa,GAAG,CAAC,aAAa,CAAA;YAC9B,OAAO,IAAI,EAAE,CAAA;QACf,CAAC;aAAM,IAAI,CAAC,aAAa,IAAI,CAAC,aAAa,EAAE,CAAC;YAC5C,IAAI,EAAE,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBAC/B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;gBACnB,OAAO,GAAG,EAAE,CAAA;gBACZ,CAAC,EAAE,CAAA,CAAC,cAAc;YACpB,CAAC;iBAAM,IAAI,EAAE,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACtC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;gBACnB,OAAO,GAAG,EAAE,CAAA;gBACZ,CAAC,EAAE,CAAA,CAAC,cAAc;YACpB,CAAC;iBAAM,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACtB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;gBACnB,OAAO,GAAG,EAAE,CAAA;YACd,CAAC;iBAAM,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;gBACtB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;gBACnB,OAAO,GAAG,EAAE,CAAA;YACd,CAAC;iBAAM,CAAC;gBACN,OAAO,IAAI,EAAE,CAAA;YACf,CAAC;QACH,CAAC;aAAM,CAAC;YACN,OAAO,IAAI,EAAE,CAAA;QACf,CAAC;IACH,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAEvC,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;AACnD,CAAC;AAED,2DAA2D;AAC3D,MAAM,kBAAkB,GAAG;IACzB,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,KAAK;IACL,KAAK;IACL,MAAM;IACN,MAAM;IACN,IAAI;IACJ,MAAM;IACN,OAAO;IACP,MAAM;IACN,MAAM;IACN,MAAM;IACN,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,UAAU;IACV,MAAM;IACN,MAAM;IACN,aAAa;IACb,eAAe;IACf,cAAc;IACd,aAAa;IACb,eAAe;IACf,WAAW;CACZ,CAAA;AAED,0CAA0C;AAC1C,MAAM,yBAAyB,GAAG,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"shell-utils.js","sourceRoot":"","sources":["../../src/tools/shell-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/tools/shell.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,KAAK;;;
|
|
1
|
+
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/tools/shell.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,KAAK;;;SAqBhB,CAAA"}
|
package/dist/tools/shell.js
CHANGED
|
@@ -2,7 +2,21 @@
|
|
|
2
2
|
import { tool } from 'ai';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
export const shell = tool({
|
|
5
|
-
description:
|
|
5
|
+
description: `Execute a shell command and return stdout/stderr. The working directory persists between commands.
|
|
6
|
+
|
|
7
|
+
IMPORTANT: Avoid using this tool to run grep, rg, cat, head, tail, sed, or awk commands. Instead, use the appropriate dedicated tool — they provide a better user experience:
|
|
8
|
+
- File search: Use glob (NOT find or ls)
|
|
9
|
+
- Content search: Use grep tool (NOT grep/rg command)
|
|
10
|
+
- Read files: Use readFile (NOT cat/head/tail)
|
|
11
|
+
- Edit files: Use edit (NOT sed/awk)
|
|
12
|
+
- Write files: Use writeFile (NOT echo >/cat <<EOF)
|
|
13
|
+
|
|
14
|
+
Instructions:
|
|
15
|
+
- If your command will create new directories or files, first run ls to verify the parent directory exists and is the correct location.
|
|
16
|
+
- Always quote file paths that contain spaces with double quotes.
|
|
17
|
+
- When issuing multiple commands: if they are independent, make multiple shell tool calls in a single message for parallelism. If they depend on each other, use '&&' to chain them. Use ';' only when you need sequential execution but don't care if earlier commands fail. Do NOT use newlines to separate commands.
|
|
18
|
+
- For git commands: prefer creating a new commit rather than amending. Never skip hooks (--no-verify) unless the user explicitly asks. Before running destructive operations (git reset --hard, git push --force), consider safer alternatives.
|
|
19
|
+
- Do not sleep between commands that can run immediately.`,
|
|
6
20
|
inputSchema: z.object({
|
|
7
21
|
command: z.string().describe('The command to execute'),
|
|
8
22
|
timeout: z.number().optional().describe('Timeout in milliseconds (default: 30000)'),
|
package/dist/tools/shell.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/tools/shell.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAEzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC;IACxB,WAAW,
|
|
1
|
+
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/tools/shell.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAEzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE;;;;;;;;;;;;;;0DAc2C;IACxD,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,wBAAwB,CAAC;QACtD,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0CAA0C,CAAC;KACpF,CAAC;IACF,sGAAsG;CACvG,CAAC,CAAA"}
|
package/dist/tools/task.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/tools/task.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/tools/task.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AAEvE;;;2DAG2D;AAC3D,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,CAkE3E;AAED;4DAC4D;AAC5D,wBAAgB,cAAc,CAAC,QAAQ,EAAE,gBAAgB;;;;UAcxD"}
|
package/dist/tools/task.js
CHANGED
|
@@ -13,9 +13,7 @@ import { z } from 'zod';
|
|
|
13
13
|
* the model knows what subagent_type values are valid. */
|
|
14
14
|
export function buildTaskToolDescription(registry) {
|
|
15
15
|
const agents = registry.list();
|
|
16
|
-
const agentList = agents
|
|
17
|
-
.map((a) => ` - ${a.name}: ${a.description}`)
|
|
18
|
-
.join('\n');
|
|
16
|
+
const agentList = agents.map((a) => ` - ${a.name}: ${a.description}`).join('\n');
|
|
19
17
|
return `Launch a sub-agent to handle a task in an isolated context. The sub-agent runs with its own message history and returns only its final conclusion — its intermediate tool calls never enter your context window, keeping the main conversation lean.
|
|
20
18
|
|
|
21
19
|
Available sub-agents:
|
|
@@ -87,7 +85,9 @@ export function createTaskTool(registry) {
|
|
|
87
85
|
inputSchema: z.object({
|
|
88
86
|
description: z.string().describe('A short (3-5 words) description of the task'),
|
|
89
87
|
subagent_type: z.string().describe(`Which sub-agent to use. Available: ${registry.names().join(', ')}`),
|
|
90
|
-
prompt: z
|
|
88
|
+
prompt: z
|
|
89
|
+
.string()
|
|
90
|
+
.describe('The complete task instruction sent to the sub-agent. Be specific — the sub-agent has no prior context.'),
|
|
91
91
|
}),
|
|
92
92
|
// No execute — handled manually in tool-execution.ts
|
|
93
93
|
});
|
package/dist/tools/task.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task.js","sourceRoot":"","sources":["../../src/tools/task.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,EAAE;AACF,sEAAsE;AACtE,kEAAkE;AAClE,gEAAgE;AAChE,kEAAkE;AAClE,mBAAmB;AACnB,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"task.js","sourceRoot":"","sources":["../../src/tools/task.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,EAAE;AACF,sEAAsE;AACtE,kEAAkE;AAClE,gEAAgE;AAChE,kEAAkE;AAClE,mBAAmB;AACnB,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAEzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB;;;2DAG2D;AAC3D,MAAM,UAAU,wBAAwB,CAAC,QAA0B;IACjE,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAA;IAC9B,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAEjF,OAAO;;;EAGP,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WA0DA,CAAA;AACX,CAAC;AAED;4DAC4D;AAC5D,MAAM,UAAU,cAAc,CAAC,QAA0B;IACvD,OAAO,IAAI,CAAC;QACV,WAAW,EAAE,wBAAwB,CAAC,QAAQ,CAAC;QAC/C,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACpB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;YAC/E,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sCAAsC,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACvG,MAAM,EAAE,CAAC;iBACN,MAAM,EAAE;iBACR,QAAQ,CACP,wGAAwG,CACzG;SACJ,CAAC;QACF,qDAAqD;KACtD,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"todo-write.d.ts","sourceRoot":"","sources":["../../src/tools/todo-write.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;0CAY0C;AAC1C,eAAO,MAAM,SAAS;;;;;;
|
|
1
|
+
{"version":3,"file":"todo-write.d.ts","sourceRoot":"","sources":["../../src/tools/todo-write.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;0CAY0C;AAC1C,eAAO,MAAM,SAAS;;;;;;SA2GpB,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"todo-write.js","sourceRoot":"","sources":["../../src/tools/todo-write.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAEzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;;;;;;0CAY0C;AAC1C,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0DAqE2C;IACxD,8DAA8D;IAC9D,mEAAmE;IACnE,qEAAqE;IACrE,kEAAkE;IAClE,iEAAiE;IACjE,gEAAgE;IAChE,4DAA4D;IAC5D,iEAAiE;IACjE,oEAAoE;IACpE,oEAAoE;IACpE,6DAA6D;IAC7D,kEAAkE;IAClE,+BAA+B;IAC/B,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,KAAK,EAAE,CAAC;aACL,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gDAAgD,CAAC;YACzF,UAAU,EAAE,CAAC;iBACV,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,
|
|
1
|
+
{"version":3,"file":"todo-write.js","sourceRoot":"","sources":["../../src/tools/todo-write.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAEzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;;;;;;0CAY0C;AAC1C,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0DAqE2C;IACxD,8DAA8D;IAC9D,mEAAmE;IACnE,qEAAqE;IACrE,kEAAkE;IAClE,iEAAiE;IACjE,gEAAgE;IAChE,4DAA4D;IAC5D,iEAAiE;IACjE,oEAAoE;IACpE,oEAAoE;IACpE,6DAA6D;IAC7D,kEAAkE;IAClE,+BAA+B;IAC/B,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,KAAK,EAAE,CAAC;aACL,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gDAAgD,CAAC;YACzF,UAAU,EAAE,CAAC;iBACV,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CACP,oGAAoG,CACrG;YACH,MAAM,EAAE,CAAC;iBACN,IAAI,CAAC,CAAC,SAAS,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC;iBAC7C,QAAQ,EAAE;iBACV,QAAQ,CACP,wGAAwG,CACzG;SACJ,CAAC,CACH;aACA,QAAQ,CACP,8GAA8G,CAC/G;KACJ,CAAC;CACH,CAAC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-fetch.d.ts","sourceRoot":"","sources":["../../src/tools/web-fetch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"web-fetch.d.ts","sourceRoot":"","sources":["../../src/tools/web-fetch.ts"],"names":[],"mappings":"AA6DA,2CAA2C;AAC3C,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkB3D;AAyDD,eAAO,MAAM,QAAQ;;;UAyEnB,CAAA"}
|
package/dist/tools/web-fetch.js
CHANGED
|
@@ -4,6 +4,7 @@ import * as cheerio from 'cheerio';
|
|
|
4
4
|
import TurndownService from 'turndown';
|
|
5
5
|
import { tool } from 'ai';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
+
import { LruCache } from '../utils/lru-cache.js';
|
|
7
8
|
import { formatToolError } from '../utils/tool-errors.js';
|
|
8
9
|
import { reportProgress } from './progress.js';
|
|
9
10
|
const FETCH_TIMEOUT_MS = 15_000;
|
|
@@ -13,9 +14,10 @@ const FETCH_TIMEOUT_MS = 15_000;
|
|
|
13
14
|
// This is a per-call cap; the model can always fetch again with a narrower prompt.
|
|
14
15
|
const MAX_CONTENT_CHARS = 100_000;
|
|
15
16
|
// Raw HTML ceiling before turndown. 10 MB is comfortable for any real doc page;
|
|
16
|
-
// enforced
|
|
17
|
-
//
|
|
17
|
+
// enforced both by content-length header AND by streaming body read (see
|
|
18
|
+
// readResponseBody) so chunked responses are also bounded.
|
|
18
19
|
const MAX_HTTP_BYTES = 10 * 1024 * 1024;
|
|
20
|
+
const MAX_URL_LENGTH = 2000;
|
|
19
21
|
const CACHE_TTL_MS = 15 * 60 * 1000;
|
|
20
22
|
const CACHE_MAX_ENTRIES = 50;
|
|
21
23
|
const BROWSER_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36';
|
|
@@ -23,28 +25,55 @@ const BROWSER_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
|
|
|
23
25
|
// while blocking browser impersonators that fail TLS-fingerprint checks.
|
|
24
26
|
const FALLBACK_UA = 'x-code-cli/0.1 (+https://github.com/woai3c/x-code-cli)';
|
|
25
27
|
const YEAR = new Date().getFullYear();
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
// ── SSRF protection ──
|
|
29
|
+
// Reject URLs targeting internal/private networks. Mirrors Claude Code's
|
|
30
|
+
// validateURL: hostname must have ≥2 dot-separated segments (rejects
|
|
31
|
+
// `localhost`, bare hostnames), no embedded credentials, no non-HTTP schemes,
|
|
32
|
+
// and no IPs in private/link-local/loopback ranges.
|
|
33
|
+
const PRIVATE_IP_PATTERNS = [
|
|
34
|
+
/^127\./, // loopback
|
|
35
|
+
/^10\./, // 10.0.0.0/8
|
|
36
|
+
/^172\.(1[6-9]|2\d|3[01])\./, // 172.16.0.0/12
|
|
37
|
+
/^192\.168\./, // 192.168.0.0/16
|
|
38
|
+
/^169\.254\./, // link-local (AWS/GCP metadata)
|
|
39
|
+
/^0\./, // 0.0.0.0/8
|
|
40
|
+
/^::1$/, // IPv6 loopback
|
|
41
|
+
/^fd[0-9a-f]{2}:/i, // IPv6 ULA
|
|
42
|
+
/^fe80:/i, // IPv6 link-local
|
|
43
|
+
];
|
|
44
|
+
function isPrivateHost(hostname) {
|
|
45
|
+
const lower = hostname.toLowerCase();
|
|
46
|
+
if (lower === 'localhost' || lower.endsWith('.local') || lower.endsWith('.internal'))
|
|
47
|
+
return true;
|
|
48
|
+
// IP-literal in URL — strip surrounding brackets for IPv6
|
|
49
|
+
const bare = lower.startsWith('[') ? lower.slice(1, -1) : lower;
|
|
50
|
+
return PRIVATE_IP_PATTERNS.some((re) => re.test(bare));
|
|
39
51
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
52
|
+
/** @internal Exported for testing only. */
|
|
53
|
+
export function validateFetchUrl(url) {
|
|
54
|
+
if (url.length > MAX_URL_LENGTH)
|
|
55
|
+
return `URL exceeds ${MAX_URL_LENGTH} character limit`;
|
|
56
|
+
let parsed;
|
|
57
|
+
try {
|
|
58
|
+
parsed = new URL(url);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return 'Invalid URL';
|
|
62
|
+
}
|
|
63
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
64
|
+
return `Unsupported protocol: ${parsed.protocol} (only http/https allowed)`;
|
|
65
|
+
}
|
|
66
|
+
if (parsed.username || parsed.password)
|
|
67
|
+
return 'URLs with embedded credentials are not allowed';
|
|
68
|
+
const parts = parsed.hostname.split('.');
|
|
69
|
+
if (parts.length < 2)
|
|
70
|
+
return `Hostname "${parsed.hostname}" is not a public domain (must have at least two segments)`;
|
|
71
|
+
if (isPrivateHost(parsed.hostname)) {
|
|
72
|
+
return `Fetching private/internal address "${parsed.hostname}" is blocked for security`;
|
|
45
73
|
}
|
|
46
|
-
|
|
74
|
+
return null;
|
|
47
75
|
}
|
|
76
|
+
const fetchCache = new LruCache({ maxEntries: CACHE_MAX_ENTRIES, ttlMs: CACHE_TTL_MS });
|
|
48
77
|
const turndown = new TurndownService({
|
|
49
78
|
headingStyle: 'atx',
|
|
50
79
|
codeBlockStyle: 'fenced',
|
|
@@ -56,9 +85,41 @@ async function doFetch(url, userAgent) {
|
|
|
56
85
|
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,text/plain;q=0.8,*/*;q=0.5',
|
|
57
86
|
'Accept-Language': 'en-US,en;q=0.9',
|
|
58
87
|
},
|
|
88
|
+
redirect: 'follow',
|
|
59
89
|
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
60
90
|
});
|
|
61
91
|
}
|
|
92
|
+
/** Stream-read response body with a hard byte cap. Prevents OOM on chunked
|
|
93
|
+
* responses where content-length is absent or lying. */
|
|
94
|
+
async function readResponseBody(response, maxBytes) {
|
|
95
|
+
const reader = response.body?.getReader();
|
|
96
|
+
if (!reader)
|
|
97
|
+
return response.text();
|
|
98
|
+
const chunks = [];
|
|
99
|
+
let totalBytes = 0;
|
|
100
|
+
while (true) {
|
|
101
|
+
const { done, value } = await reader.read();
|
|
102
|
+
if (done)
|
|
103
|
+
break;
|
|
104
|
+
totalBytes += value.byteLength;
|
|
105
|
+
if (totalBytes > maxBytes) {
|
|
106
|
+
await reader.cancel();
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
chunks.push(value);
|
|
110
|
+
}
|
|
111
|
+
const merged = new Uint8Array(Math.min(totalBytes, maxBytes));
|
|
112
|
+
let offset = 0;
|
|
113
|
+
for (const chunk of chunks) {
|
|
114
|
+
const remaining = merged.byteLength - offset;
|
|
115
|
+
if (remaining <= 0)
|
|
116
|
+
break;
|
|
117
|
+
const slice = chunk.byteLength > remaining ? chunk.subarray(0, remaining) : chunk;
|
|
118
|
+
merged.set(slice, offset);
|
|
119
|
+
offset += slice.byteLength;
|
|
120
|
+
}
|
|
121
|
+
return new TextDecoder().decode(merged);
|
|
122
|
+
}
|
|
62
123
|
function formatOutput(url, markdown, prompt) {
|
|
63
124
|
if (prompt) {
|
|
64
125
|
return `# Content from ${url}\n\n${markdown}\n\n---\nExtract instruction: ${prompt}`;
|
|
@@ -77,7 +138,10 @@ export const webFetch = tool({
|
|
|
77
138
|
}),
|
|
78
139
|
execute: async ({ url, prompt }, { toolCallId }) => {
|
|
79
140
|
try {
|
|
80
|
-
const
|
|
141
|
+
const urlError = validateFetchUrl(url);
|
|
142
|
+
if (urlError)
|
|
143
|
+
return `Error: ${urlError}`;
|
|
144
|
+
const cached = fetchCache.get(url);
|
|
81
145
|
if (cached) {
|
|
82
146
|
reportProgress(toolCallId, 'Using cached copy');
|
|
83
147
|
return formatOutput(url, cached, prompt);
|
|
@@ -93,18 +157,19 @@ export const webFetch = tool({
|
|
|
93
157
|
if (!response.ok) {
|
|
94
158
|
return `Error: HTTP ${response.status} ${response.statusText}`;
|
|
95
159
|
}
|
|
96
|
-
//
|
|
97
|
-
// so we also rely on fetch's 15s timeout to bound pathological pages.
|
|
160
|
+
// Reject upfront when content-length exceeds the cap.
|
|
98
161
|
const contentLength = Number(response.headers.get('content-length') ?? '0');
|
|
99
162
|
if (contentLength > MAX_HTTP_BYTES) {
|
|
100
163
|
const mb = Math.round(contentLength / 1024 / 1024);
|
|
101
164
|
return `Error: Content too large (${mb} MB, limit ${MAX_HTTP_BYTES / 1024 / 1024} MB)`;
|
|
102
165
|
}
|
|
103
166
|
const contentType = response.headers.get('content-type') ?? '';
|
|
104
|
-
|
|
167
|
+
// Stream-read with hard byte cap — prevents OOM on chunked responses
|
|
168
|
+
// where content-length is absent or lies.
|
|
169
|
+
const body = await readResponseBody(response, MAX_HTTP_BYTES);
|
|
105
170
|
if (contentType.includes('application/json')) {
|
|
106
171
|
const json = body.slice(0, MAX_CONTENT_CHARS);
|
|
107
|
-
|
|
172
|
+
fetchCache.set(url, json);
|
|
108
173
|
return formatOutput(url, json, prompt);
|
|
109
174
|
}
|
|
110
175
|
const $ = cheerio.load(body);
|
|
@@ -117,7 +182,7 @@ export const webFetch = tool({
|
|
|
117
182
|
if (markdown.length > MAX_CONTENT_CHARS) {
|
|
118
183
|
markdown = markdown.slice(0, MAX_CONTENT_CHARS) + '\n\n... [content truncated]';
|
|
119
184
|
}
|
|
120
|
-
|
|
185
|
+
fetchCache.set(url, markdown);
|
|
121
186
|
return formatOutput(url, markdown, prompt);
|
|
122
187
|
}
|
|
123
188
|
catch (err) {
|