@molecule/api-ai-tools 1.4.3 → 1.5.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/README.md +27 -1
- package/dist/input-normalizer.d.ts +7 -0
- package/dist/input-normalizer.d.ts.map +1 -1
- package/dist/input-normalizer.js +9 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +5 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +64 -13
- package/dist/utilities.d.ts +13 -0
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +21 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
|
|
|
3
3
|
Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
|
|
4
4
|
Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
|
|
5
5
|
To change this document, edit the module-level JSDoc in src/index.ts.
|
|
6
|
-
Generated: 2026-
|
|
6
|
+
Generated: 2026-10-15T00:28:10.570Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/api-ai-tools
|
|
@@ -323,6 +323,14 @@ Re-export SkillEntry as DiscoveredSkill for backwards compatibility.
|
|
|
323
323
|
type DiscoveredSkill = SkillEntry
|
|
324
324
|
```
|
|
325
325
|
|
|
326
|
+
### Classes
|
|
327
|
+
|
|
328
|
+
#### `ToolInputError`
|
|
329
|
+
|
|
330
|
+
A tool call the handler refuses with a message meant for the model as is —
|
|
331
|
+
thrown from deep inside a handler (a path check), returned by the guard as the
|
|
332
|
+
call's `error` without the generic "check your parameters" wrapping.
|
|
333
|
+
|
|
326
334
|
### Functions
|
|
327
335
|
|
|
328
336
|
#### `buildAgentPrompt(ctx)`
|
|
@@ -503,6 +511,24 @@ function isEnvFilePath(path: string): boolean
|
|
|
503
511
|
|
|
504
512
|
**Returns:** `true` when the file is an env file.
|
|
505
513
|
|
|
514
|
+
#### `isOutsideRoot(path, projectRoot)`
|
|
515
|
+
|
|
516
|
+
Whether a tool path points outside the project root once normalized (an
|
|
517
|
+
absolute path elsewhere, or a `..` that climbs out). {@link resolvePath}
|
|
518
|
+
clamps such a path to the root; a file tool must refuse it instead of
|
|
519
|
+
answering about the root — a `list_files /tmp/x` used to return the
|
|
520
|
+
workspace listing as if it were `/tmp/x` (X1 runs sonnet-m17 and sol-m05,
|
|
521
|
+
2026-10-14).
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
function isOutsideRoot(path: string, projectRoot: string): boolean
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
- `path` — The path the tool was given.
|
|
528
|
+
- `projectRoot` — The project root.
|
|
529
|
+
|
|
530
|
+
**Returns:** True when the path leaves the root.
|
|
531
|
+
|
|
506
532
|
#### `isValidGlob(pattern)`
|
|
507
533
|
|
|
508
534
|
Validate that a glob/include pattern is safe (no shell metacharacters that could
|
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* A tool call the handler refuses with a message meant for the model as is —
|
|
9
|
+
* thrown from deep inside a handler (a path check), returned by the guard as the
|
|
10
|
+
* call's `error` without the generic "check your parameters" wrapping.
|
|
11
|
+
*/
|
|
12
|
+
export declare class ToolInputError extends Error {
|
|
13
|
+
}
|
|
7
14
|
/** The JSON-Schema-ish shape every tool in this package declares. */
|
|
8
15
|
export interface NormalizableSchema {
|
|
9
16
|
name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input-normalizer.d.ts","sourceRoot":"","sources":["../src/input-normalizer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,qEAAqE;AACrE,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,CAAC,EAAE;QACX,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;QACpE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KACpB,CAAA;CACF;AAED,uDAAuD;AACvD,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,gEAAgE;IAChE,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,2DAA2D;IAC3D,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAiDD,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;CAC1C;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,EAAE,GAAG,EAAE,OAAO,GAAG,eAAe,CAiD5F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,EAAE,GAChB,MAAM,CAaR;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,kBAAkB,EAC1B,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,EAC5E,OAAO,GAAE,YAAiB,GACzB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"input-normalizer.d.ts","sourceRoot":"","sources":["../src/input-normalizer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,KAAK;CAAG;AAE5C,qEAAqE;AACrE,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,CAAC,EAAE;QACX,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;QACpE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KACpB,CAAA;CACF;AAED,uDAAuD;AACvD,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,gEAAgE;IAChE,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,2DAA2D;IAC3D,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAiDD,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;CAC1C;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,EAAE,GAAG,EAAE,OAAO,GAAG,eAAe,CAiD5F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,EAAE,GAChB,MAAM,CAaR;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,kBAAkB,EAC1B,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,EAC5E,OAAO,GAAE,YAAiB,GACzB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAuBxD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,CAAC,SAAS;IACR,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAChE,EACD,IAAI,EAAE,CAAC,EAAE,OAAO,GAAE,YAAiB,GAAG,CAAC,CAaxC"}
|
package/dist/input-normalizer.js
CHANGED
|
@@ -5,6 +5,13 @@
|
|
|
5
5
|
* @module
|
|
6
6
|
*/
|
|
7
7
|
import { redactSecrets } from './utilities.js';
|
|
8
|
+
/**
|
|
9
|
+
* A tool call the handler refuses with a message meant for the model as is —
|
|
10
|
+
* thrown from deep inside a handler (a path check), returned by the guard as the
|
|
11
|
+
* call's `error` without the generic "check your parameters" wrapping.
|
|
12
|
+
*/
|
|
13
|
+
export class ToolInputError extends Error {
|
|
14
|
+
}
|
|
8
15
|
/**
|
|
9
16
|
* Other names a model sends for a declared parameter, most likely first.
|
|
10
17
|
*
|
|
@@ -157,6 +164,8 @@ export function guardToolExecute(schema, impl, options = {}) {
|
|
|
157
164
|
return await impl(input, ...rest);
|
|
158
165
|
}
|
|
159
166
|
catch (e) {
|
|
167
|
+
if (e instanceof ToolInputError)
|
|
168
|
+
return { error: e.message };
|
|
160
169
|
if (missing.length > 0)
|
|
161
170
|
return { error: missingParamError(schema, input, missing) };
|
|
162
171
|
const message = redactError(e instanceof Error ? e.message : String(e));
|
package/dist/schemas.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAElD;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,UAAU,CAAA;CACvB;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAElD;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,UAAU,CAAA;CACvB;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CA+T1C,CAAA"}
|
package/dist/schemas.js
CHANGED
|
@@ -79,7 +79,7 @@ export const TOOL_SCHEMAS = {
|
|
|
79
79
|
},
|
|
80
80
|
edit_file: {
|
|
81
81
|
name: 'edit_file',
|
|
82
|
-
description: 'Make targeted search-and-replace edits to a file. Preferred over write_file for small changes. Each old_string must match exactly once in the file. The result shows each edited region as it now reads, with line numbers, so there is no need to re-read the file to check an edit.',
|
|
82
|
+
description: 'Make targeted search-and-replace edits to a file. Preferred over write_file for small changes. Each old_string must match exactly once in the file, unless that replacement sets replace_all to change every occurrence (a rename, a repeated call). The result shows each edited region as it now reads, with line numbers, so there is no need to re-read the file to check an edit.',
|
|
83
83
|
parameters: {
|
|
84
84
|
type: 'object',
|
|
85
85
|
properties: {
|
|
@@ -101,6 +101,10 @@ export const TOOL_SCHEMAS = {
|
|
|
101
101
|
type: 'string',
|
|
102
102
|
description: 'Replacement string.',
|
|
103
103
|
},
|
|
104
|
+
replace_all: {
|
|
105
|
+
type: 'boolean',
|
|
106
|
+
description: 'Replace every occurrence of old_string instead of requiring exactly one (default false).',
|
|
107
|
+
},
|
|
104
108
|
},
|
|
105
109
|
required: ['old_string', 'new_string'],
|
|
106
110
|
},
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAoB9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAoB9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAgCnE;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CAu3CxF"}
|
package/dist/tools.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
import { addChangeSpan, changedSpan, countLines, renderEditExcerpt, renderSmallFile, } from './edit-excerpt.js';
|
|
10
|
-
import { guardToolExecute, missingParamError } from './input-normalizer.js';
|
|
10
|
+
import { guardToolExecute, missingParamError, ToolInputError } from './input-normalizer.js';
|
|
11
11
|
import { TOOL_SCHEMAS } from './schemas.js';
|
|
12
12
|
/**
|
|
13
13
|
* Total waiting, per background task, after which wait_for_task reports it
|
|
@@ -16,7 +16,7 @@ import { TOOL_SCHEMAS } from './schemas.js';
|
|
|
16
16
|
const stuckAfterMs = () => Number(process.env.MOL_AI_BG_STUCK_AFTER_MS) || 5 * 60 * 1000;
|
|
17
17
|
/** Cumulative wait_for_task time per task id, this process. */
|
|
18
18
|
const waitedByTask = new Map();
|
|
19
|
-
import { checkBlockedCommand, declaredBudgetSeconds, DEFAULT_SEARCH_EXCLUDED_DIRS, directoryReadHint, isEnvFilePath, isValidGlob, MAX_BATCH_READ_BYTES, MAX_BATCH_READ_FILES, MAX_FIND_RESULTS, MAX_OUTPUT_SIZE, MAX_READ_RETURN_CHARS, MAX_READ_SIZE, MAX_SEARCH_CONTEXT_LINES, MAX_SEARCH_RESULTS, MAX_WRITE_SIZE, outputIsWithheldUntilExit, parseCheckCommand, parseCheckProblem, pathArgError, redactSecrets, redactSecretsInCode, resolvePath, shellQuote, sliceLines, stripControlChars, truncateMiddle, whitespaceTolerantReplace, } from './utilities.js';
|
|
19
|
+
import { checkBlockedCommand, declaredBudgetSeconds, DEFAULT_SEARCH_EXCLUDED_DIRS, directoryReadHint, isEnvFilePath, isOutsideRoot, isValidGlob, MAX_BATCH_READ_BYTES, MAX_BATCH_READ_FILES, MAX_FIND_RESULTS, MAX_OUTPUT_SIZE, MAX_READ_RETURN_CHARS, MAX_READ_SIZE, MAX_SEARCH_CONTEXT_LINES, MAX_SEARCH_RESULTS, MAX_WRITE_SIZE, outputIsWithheldUntilExit, parseCheckCommand, parseCheckProblem, pathArgError, redactSecrets, redactSecretsInCode, resolvePath, shellQuote, sliceLines, stripControlChars, truncateMiddle, whitespaceTolerantReplace, } from './utilities.js';
|
|
20
20
|
/**
|
|
21
21
|
* Build a complete set of AI agent tools bound to an execution backend.
|
|
22
22
|
*
|
|
@@ -49,8 +49,30 @@ export function buildTools(backend, config) {
|
|
|
49
49
|
const excludedDirs = (searchExcludedDirs && searchExcludedDirs.length > 0
|
|
50
50
|
? searchExcludedDirs
|
|
51
51
|
: DEFAULT_SEARCH_EXCLUDED_DIRS).filter((d) => /^[A-Za-z0-9._-]+$/.test(d));
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
/**
|
|
53
|
+
* The excluded dirs for a search rooted at `path`. Inside an installed
|
|
54
|
+
* package the build output IS the code: excluding `dist` there returned
|
|
55
|
+
* nothing for `IconName` in `node_modules/@molecule/app-icons` (it lives in
|
|
56
|
+
* `dist/types.d.ts`; X1 run sol-m04).
|
|
57
|
+
*
|
|
58
|
+
* @param path - The resolved search root.
|
|
59
|
+
* @returns The dir names to skip below it.
|
|
60
|
+
*/
|
|
61
|
+
const excludesFor = (path) => /(?:^|\/)node_modules(?:\/|$)/.test(path)
|
|
62
|
+
? excludedDirs.filter((d) => d !== 'dist')
|
|
63
|
+
: excludedDirs;
|
|
64
|
+
const grepExcludeArgs = (path) => excludesFor(path)
|
|
65
|
+
.map((d) => `--exclude-dir=${shellQuote(d)}`)
|
|
66
|
+
.join(' ');
|
|
67
|
+
// Pruned BELOW the root only (-mindepth 1): a `-not -path '*/node_modules/*'`
|
|
68
|
+
// filter matched every result of a search rooted inside node_modules and
|
|
69
|
+
// returned none.
|
|
70
|
+
const findPruneArgs = (path) => {
|
|
71
|
+
const dirs = excludesFor(path);
|
|
72
|
+
if (dirs.length === 0)
|
|
73
|
+
return '';
|
|
74
|
+
return `-mindepth 1 \\( -type d \\( ${dirs.map((d) => `-name ${shellQuote(d)}`).join(' -o ')} \\) -prune \\) -o`;
|
|
75
|
+
};
|
|
54
76
|
// ── Path helpers ───────────────────────────────────────────────
|
|
55
77
|
/**
|
|
56
78
|
* After a write or edit, parse the file and hand the error back IN THE SAME
|
|
@@ -81,7 +103,14 @@ export function buildTools(backend, config) {
|
|
|
81
103
|
* @param path - Relative or absolute path requested by the tool input.
|
|
82
104
|
* @returns A normalized path honoring `pathGuards` and the backend root.
|
|
83
105
|
*/
|
|
84
|
-
function resolve(path) {
|
|
106
|
+
function resolve(path, use = 'file') {
|
|
107
|
+
if (pathGuards && typeof path === 'string' && isOutsideRoot(path, root)) {
|
|
108
|
+
throw new ToolInputError(use === 'cwd'
|
|
109
|
+
? `${path} is outside the workspace (${root}), so it cannot be the working directory — ` +
|
|
110
|
+
`start the command with \`cd ${path} && \` instead.`
|
|
111
|
+
: `${path} is outside the workspace (${root}). The file tools only reach files inside it — ` +
|
|
112
|
+
`read or list anything else with exec_command (e.g. \`cat ${path}\` or \`ls ${path}\`).`);
|
|
113
|
+
}
|
|
85
114
|
return pathGuards ? resolvePath(path, root) : path;
|
|
86
115
|
}
|
|
87
116
|
/**
|
|
@@ -102,8 +131,12 @@ export function buildTools(backend, config) {
|
|
|
102
131
|
return null;
|
|
103
132
|
}
|
|
104
133
|
catch (_error) {
|
|
105
|
-
// readlink unavailable or timed out — deny access conservatively
|
|
106
|
-
|
|
134
|
+
// readlink unavailable or timed out — deny access conservatively, and say
|
|
135
|
+
// why: "access denied" read as a forbidden path, and builders spent call
|
|
136
|
+
// after call on paths that were fine while the sandbox was not answering.
|
|
137
|
+
return ('Unable to verify path safety: the sandbox did not answer the check within 5 s (it may be ' +
|
|
138
|
+
'restarting or out of memory), so the path was refused to stay safe. Retry in a moment; if ' +
|
|
139
|
+
'every file tool fails this way, the sandbox needs a restart.');
|
|
107
140
|
}
|
|
108
141
|
}
|
|
109
142
|
/**
|
|
@@ -543,7 +576,13 @@ export function buildTools(backend, config) {
|
|
|
543
576
|
if (!replacements?.length &&
|
|
544
577
|
typeof input.old_string === 'string' &&
|
|
545
578
|
typeof input.new_string === 'string') {
|
|
546
|
-
replacements = [
|
|
579
|
+
replacements = [
|
|
580
|
+
{
|
|
581
|
+
old_string: input.old_string,
|
|
582
|
+
new_string: input.new_string,
|
|
583
|
+
replace_all: input.replace_all,
|
|
584
|
+
},
|
|
585
|
+
];
|
|
547
586
|
}
|
|
548
587
|
if (!replacements?.length)
|
|
549
588
|
return {
|
|
@@ -568,7 +607,10 @@ export function buildTools(backend, config) {
|
|
|
568
607
|
// Where each applied replacement landed, in the coordinates of the
|
|
569
608
|
// latest content — rendered into the result so the model sees the edit.
|
|
570
609
|
let spans = [];
|
|
571
|
-
for (const { old_string: oldString, new_string: newString
|
|
610
|
+
for (const { old_string: oldString, new_string: newString, replace_all: replaceAll,
|
|
611
|
+
// Models sometimes send the camelCase spelling inside an item, where
|
|
612
|
+
// the input normalizer (top-level keys only) does not reach.
|
|
613
|
+
replaceAll: replaceAllCamel, } of replacements) {
|
|
572
614
|
// Validate each replacement is well-formed BEFORE touching content. A
|
|
573
615
|
// weak model sometimes emits a batch element missing a field or with a
|
|
574
616
|
// non-string value; left unchecked that either crashes (`undefined`
|
|
@@ -690,13 +732,22 @@ export function buildTools(backend, config) {
|
|
|
690
732
|
`indentation — or include more surrounding context to make it unique.`,
|
|
691
733
|
};
|
|
692
734
|
}
|
|
735
|
+
if (count > 1 && (replaceAll === true || replaceAllCamel === true)) {
|
|
736
|
+
// Every occurrence, by split/join — never String.replace, whose
|
|
737
|
+
// replacement string treats ` if (count > 1) {
|
|
738
|
+
// Say WHERE,` and friends as patterns (see below).
|
|
739
|
+
const replaced = content.split(oldString).join(newString);
|
|
740
|
+
spans = addChangeSpan(spans, content, replaced);
|
|
741
|
+
content = replaced;
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
693
744
|
if (count > 1) {
|
|
694
745
|
// Say WHERE, so the retry can widen old_string around the one it means.
|
|
695
746
|
const lines = [];
|
|
696
747
|
for (let at = content.indexOf(oldString); at >= 0 && lines.length < 10; at = content.indexOf(oldString, at + 1))
|
|
697
748
|
lines.push(content.slice(0, at).split('\n').length);
|
|
698
749
|
return {
|
|
699
|
-
error: `old_string found ${count} times in ${path} (starting at lines ${lines.join(', ')}${count > lines.length ? ', …' : ''}) — must be unique. Include more surrounding context from the one you mean, or
|
|
750
|
+
error: `old_string found ${count} times in ${path} (starting at lines ${lines.join(', ')}${count > lines.length ? ', …' : ''}) — must be unique. Include more surrounding context from the one you mean, or set replace_all: true on this replacement to change every occurrence.`,
|
|
700
751
|
};
|
|
701
752
|
}
|
|
702
753
|
// Splice by index rather than `content.replace(oldString, newString)`:
|
|
@@ -776,7 +827,7 @@ export function buildTools(backend, config) {
|
|
|
776
827
|
// `file:line:content` for matches, plus `--` group separators. Both
|
|
777
828
|
// shapes are parsed below; without context the output is unchanged.
|
|
778
829
|
const contextArg = contextLines > 0 ? `-C ${contextLines}` : '';
|
|
779
|
-
const result = await backend.run(`grep -rn ${globArg} ${contextArg} ${grepExcludeArgs} --max-count=${MAX_SEARCH_RESULTS} -- ${shellQuote(pattern)} ${shellQuote(path)} 2>/dev/null || true`, { timeout: 10000 });
|
|
830
|
+
const result = await backend.run(`grep -rn ${globArg} ${contextArg} ${grepExcludeArgs(path)} --max-count=${MAX_SEARCH_RESULTS} -- ${shellQuote(pattern)} ${shellQuote(path)} 2>/dev/null || true`, { timeout: 10000 });
|
|
780
831
|
// grep emits `<file>:<line>:<content>`, so redaction runs PER MATCH on the
|
|
781
832
|
// content alone: the file prefix would otherwise hide an env assignment from
|
|
782
833
|
// the line-anchored env pattern, and knowing each match's own path is what
|
|
@@ -835,7 +886,7 @@ export function buildTools(backend, config) {
|
|
|
835
886
|
if (symlinkErr)
|
|
836
887
|
return { error: symlinkErr };
|
|
837
888
|
try {
|
|
838
|
-
const result = await backend.run(`find ${shellQuote(path)} -name ${shellQuote(pattern)}
|
|
889
|
+
const result = await backend.run(`find ${shellQuote(path)} ${findPruneArgs(path)} -name ${shellQuote(pattern)} -print 2>/dev/null | head -${MAX_FIND_RESULTS}`, { timeout: 10000 });
|
|
839
890
|
const files = result.stdout.trim().split('\n').filter(Boolean);
|
|
840
891
|
return { pattern, path, files };
|
|
841
892
|
}
|
|
@@ -899,7 +950,7 @@ export function buildTools(backend, config) {
|
|
|
899
950
|
},
|
|
900
951
|
async exec_command(input) {
|
|
901
952
|
const command = input.command;
|
|
902
|
-
const cwd = input.cwd ? resolve(input.cwd) : root;
|
|
953
|
+
const cwd = input.cwd ? resolve(input.cwd, 'cwd') : root;
|
|
903
954
|
// A model that sent the command under another name (`cmd`, `script`, …)
|
|
904
955
|
// has already been normalized by guardToolExecute; reaching here with
|
|
905
956
|
// nothing means there is genuinely no command. Say so in the terms that
|
package/dist/utilities.d.ts
CHANGED
|
@@ -106,6 +106,19 @@ export declare function checkBlockedCommand(command: string): string | null;
|
|
|
106
106
|
* @returns A normalized absolute path confined to `projectRoot`.
|
|
107
107
|
*/
|
|
108
108
|
export declare function resolvePath(path: string, projectRoot: string): string;
|
|
109
|
+
/**
|
|
110
|
+
* Whether a tool path points outside the project root once normalized (an
|
|
111
|
+
* absolute path elsewhere, or a `..` that climbs out). {@link resolvePath}
|
|
112
|
+
* clamps such a path to the root; a file tool must refuse it instead of
|
|
113
|
+
* answering about the root — a `list_files /tmp/x` used to return the
|
|
114
|
+
* workspace listing as if it were `/tmp/x` (X1 runs sonnet-m17 and sol-m05,
|
|
115
|
+
* 2026-10-14).
|
|
116
|
+
*
|
|
117
|
+
* @param path - The path the tool was given.
|
|
118
|
+
* @param projectRoot - The project root.
|
|
119
|
+
* @returns True when the path leaves the root.
|
|
120
|
+
*/
|
|
121
|
+
export declare function isOutsideRoot(path: string, projectRoot: string): boolean;
|
|
109
122
|
/**
|
|
110
123
|
* Validate that a glob/include pattern is safe (no shell metacharacters that could
|
|
111
124
|
* inject). Allows alphanumeric, `* ? . _ - /` and the bracket/paren glob chars `[] ()`.
|
package/dist/utilities.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"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"}
|
package/dist/utilities.js
CHANGED
|
@@ -256,6 +256,27 @@ export function resolvePath(path, projectRoot) {
|
|
|
256
256
|
return projectRoot;
|
|
257
257
|
return resolved;
|
|
258
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Whether a tool path points outside the project root once normalized (an
|
|
261
|
+
* absolute path elsewhere, or a `..` that climbs out). {@link resolvePath}
|
|
262
|
+
* clamps such a path to the root; a file tool must refuse it instead of
|
|
263
|
+
* answering about the root — a `list_files /tmp/x` used to return the
|
|
264
|
+
* workspace listing as if it were `/tmp/x` (X1 runs sonnet-m17 and sol-m05,
|
|
265
|
+
* 2026-10-14).
|
|
266
|
+
*
|
|
267
|
+
* @param path - The path the tool was given.
|
|
268
|
+
* @param projectRoot - The project root.
|
|
269
|
+
* @returns True when the path leaves the root.
|
|
270
|
+
*/
|
|
271
|
+
export function isOutsideRoot(path, projectRoot) {
|
|
272
|
+
if (path === '' || path === '/')
|
|
273
|
+
return false;
|
|
274
|
+
const clean = path.replace(/\0/g, '');
|
|
275
|
+
const resolved = clean.startsWith('/')
|
|
276
|
+
? posix.normalize(clean)
|
|
277
|
+
: posix.normalize(`${projectRoot}/${clean}`);
|
|
278
|
+
return resolved !== projectRoot && !resolved.startsWith(projectRoot + '/');
|
|
279
|
+
}
|
|
259
280
|
/**
|
|
260
281
|
* Validate that a glob/include pattern is safe (no shell metacharacters that could
|
|
261
282
|
* inject). Allows alphanumeric, `* ? . _ - /` and the bracket/paren glob chars `[] ()`.
|