@molecule/api-ai-tools 1.4.4 → 1.5.1
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 +108 -12
- 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;
|
|
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;AA2B9C,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,CAg6CxF"}
|
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,14 @@ 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
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Each waited task's log size at the end of its last wait. A command whose log
|
|
21
|
+
* keeps growing is slow, not stuck: a serial 139-test suite was called stuck at
|
|
22
|
+
* 560 s and the executor was told to kill it while its log still grew (X1 run
|
|
23
|
+
* m21, 2026-10-15).
|
|
24
|
+
*/
|
|
25
|
+
const logBytesByTask = new Map();
|
|
26
|
+
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
27
|
/**
|
|
21
28
|
* Build a complete set of AI agent tools bound to an execution backend.
|
|
22
29
|
*
|
|
@@ -49,8 +56,30 @@ export function buildTools(backend, config) {
|
|
|
49
56
|
const excludedDirs = (searchExcludedDirs && searchExcludedDirs.length > 0
|
|
50
57
|
? searchExcludedDirs
|
|
51
58
|
: DEFAULT_SEARCH_EXCLUDED_DIRS).filter((d) => /^[A-Za-z0-9._-]+$/.test(d));
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
/**
|
|
60
|
+
* The excluded dirs for a search rooted at `path`. Inside an installed
|
|
61
|
+
* package the build output IS the code: excluding `dist` there returned
|
|
62
|
+
* nothing for `IconName` in `node_modules/@molecule/app-icons` (it lives in
|
|
63
|
+
* `dist/types.d.ts`; X1 run sol-m04).
|
|
64
|
+
*
|
|
65
|
+
* @param path - The resolved search root.
|
|
66
|
+
* @returns The dir names to skip below it.
|
|
67
|
+
*/
|
|
68
|
+
const excludesFor = (path) => /(?:^|\/)node_modules(?:\/|$)/.test(path)
|
|
69
|
+
? excludedDirs.filter((d) => d !== 'dist')
|
|
70
|
+
: excludedDirs;
|
|
71
|
+
const grepExcludeArgs = (path) => excludesFor(path)
|
|
72
|
+
.map((d) => `--exclude-dir=${shellQuote(d)}`)
|
|
73
|
+
.join(' ');
|
|
74
|
+
// Pruned BELOW the root only (-mindepth 1): a `-not -path '*/node_modules/*'`
|
|
75
|
+
// filter matched every result of a search rooted inside node_modules and
|
|
76
|
+
// returned none.
|
|
77
|
+
const findPruneArgs = (path) => {
|
|
78
|
+
const dirs = excludesFor(path);
|
|
79
|
+
if (dirs.length === 0)
|
|
80
|
+
return '';
|
|
81
|
+
return `-mindepth 1 \\( -type d \\( ${dirs.map((d) => `-name ${shellQuote(d)}`).join(' -o ')} \\) -prune \\) -o`;
|
|
82
|
+
};
|
|
54
83
|
// ── Path helpers ───────────────────────────────────────────────
|
|
55
84
|
/**
|
|
56
85
|
* After a write or edit, parse the file and hand the error back IN THE SAME
|
|
@@ -81,7 +110,14 @@ export function buildTools(backend, config) {
|
|
|
81
110
|
* @param path - Relative or absolute path requested by the tool input.
|
|
82
111
|
* @returns A normalized path honoring `pathGuards` and the backend root.
|
|
83
112
|
*/
|
|
84
|
-
function resolve(path) {
|
|
113
|
+
function resolve(path, use = 'file') {
|
|
114
|
+
if (pathGuards && typeof path === 'string' && isOutsideRoot(path, root)) {
|
|
115
|
+
throw new ToolInputError(use === 'cwd'
|
|
116
|
+
? `${path} is outside the workspace (${root}), so it cannot be the working directory — ` +
|
|
117
|
+
`start the command with \`cd ${path} && \` instead.`
|
|
118
|
+
: `${path} is outside the workspace (${root}). The file tools only reach files inside it — ` +
|
|
119
|
+
`read or list anything else with exec_command (e.g. \`cat ${path}\` or \`ls ${path}\`).`);
|
|
120
|
+
}
|
|
85
121
|
return pathGuards ? resolvePath(path, root) : path;
|
|
86
122
|
}
|
|
87
123
|
/**
|
|
@@ -547,7 +583,13 @@ export function buildTools(backend, config) {
|
|
|
547
583
|
if (!replacements?.length &&
|
|
548
584
|
typeof input.old_string === 'string' &&
|
|
549
585
|
typeof input.new_string === 'string') {
|
|
550
|
-
replacements = [
|
|
586
|
+
replacements = [
|
|
587
|
+
{
|
|
588
|
+
old_string: input.old_string,
|
|
589
|
+
new_string: input.new_string,
|
|
590
|
+
replace_all: input.replace_all,
|
|
591
|
+
},
|
|
592
|
+
];
|
|
551
593
|
}
|
|
552
594
|
if (!replacements?.length)
|
|
553
595
|
return {
|
|
@@ -572,7 +614,10 @@ export function buildTools(backend, config) {
|
|
|
572
614
|
// Where each applied replacement landed, in the coordinates of the
|
|
573
615
|
// latest content — rendered into the result so the model sees the edit.
|
|
574
616
|
let spans = [];
|
|
575
|
-
for (const { old_string: oldString, new_string: newString
|
|
617
|
+
for (const { old_string: oldString, new_string: newString, replace_all: replaceAll,
|
|
618
|
+
// Models sometimes send the camelCase spelling inside an item, where
|
|
619
|
+
// the input normalizer (top-level keys only) does not reach.
|
|
620
|
+
replaceAll: replaceAllCamel, } of replacements) {
|
|
576
621
|
// Validate each replacement is well-formed BEFORE touching content. A
|
|
577
622
|
// weak model sometimes emits a batch element missing a field or with a
|
|
578
623
|
// non-string value; left unchecked that either crashes (`undefined`
|
|
@@ -694,13 +739,22 @@ export function buildTools(backend, config) {
|
|
|
694
739
|
`indentation — or include more surrounding context to make it unique.`,
|
|
695
740
|
};
|
|
696
741
|
}
|
|
742
|
+
if (count > 1 && (replaceAll === true || replaceAllCamel === true)) {
|
|
743
|
+
// Every occurrence, by split/join — never String.replace, whose
|
|
744
|
+
// replacement string treats ` if (count > 1) {
|
|
745
|
+
// Say WHERE,` and friends as patterns (see below).
|
|
746
|
+
const replaced = content.split(oldString).join(newString);
|
|
747
|
+
spans = addChangeSpan(spans, content, replaced);
|
|
748
|
+
content = replaced;
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
697
751
|
if (count > 1) {
|
|
698
752
|
// Say WHERE, so the retry can widen old_string around the one it means.
|
|
699
753
|
const lines = [];
|
|
700
754
|
for (let at = content.indexOf(oldString); at >= 0 && lines.length < 10; at = content.indexOf(oldString, at + 1))
|
|
701
755
|
lines.push(content.slice(0, at).split('\n').length);
|
|
702
756
|
return {
|
|
703
|
-
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
|
|
757
|
+
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.`,
|
|
704
758
|
};
|
|
705
759
|
}
|
|
706
760
|
// Splice by index rather than `content.replace(oldString, newString)`:
|
|
@@ -780,7 +834,7 @@ export function buildTools(backend, config) {
|
|
|
780
834
|
// `file:line:content` for matches, plus `--` group separators. Both
|
|
781
835
|
// shapes are parsed below; without context the output is unchanged.
|
|
782
836
|
const contextArg = contextLines > 0 ? `-C ${contextLines}` : '';
|
|
783
|
-
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 });
|
|
837
|
+
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 });
|
|
784
838
|
// grep emits `<file>:<line>:<content>`, so redaction runs PER MATCH on the
|
|
785
839
|
// content alone: the file prefix would otherwise hide an env assignment from
|
|
786
840
|
// the line-anchored env pattern, and knowing each match's own path is what
|
|
@@ -839,7 +893,7 @@ export function buildTools(backend, config) {
|
|
|
839
893
|
if (symlinkErr)
|
|
840
894
|
return { error: symlinkErr };
|
|
841
895
|
try {
|
|
842
|
-
const result = await backend.run(`find ${shellQuote(path)} -name ${shellQuote(pattern)}
|
|
896
|
+
const result = await backend.run(`find ${shellQuote(path)} ${findPruneArgs(path)} -name ${shellQuote(pattern)} -print 2>/dev/null | head -${MAX_FIND_RESULTS}`, { timeout: 10000 });
|
|
843
897
|
const files = result.stdout.trim().split('\n').filter(Boolean);
|
|
844
898
|
return { pattern, path, files };
|
|
845
899
|
}
|
|
@@ -903,7 +957,7 @@ export function buildTools(backend, config) {
|
|
|
903
957
|
},
|
|
904
958
|
async exec_command(input) {
|
|
905
959
|
const command = input.command;
|
|
906
|
-
const cwd = input.cwd ? resolve(input.cwd) : root;
|
|
960
|
+
const cwd = input.cwd ? resolve(input.cwd, 'cwd') : root;
|
|
907
961
|
// A model that sent the command under another name (`cmd`, `script`, …)
|
|
908
962
|
// has already been normalized by guardToolExecute; reaching here with
|
|
909
963
|
// nothing means there is genuinely no command. Say so in the terms that
|
|
@@ -1105,7 +1159,33 @@ export function buildTools(backend, config) {
|
|
|
1105
1159
|
// exiting is not going to: on one run the executor waited five times,
|
|
1106
1160
|
// 120 s each, on an e2e suite that could never start (no browser page
|
|
1107
1161
|
// attached). Say so once, name the pid, and refuse to wait on it again.
|
|
1108
|
-
|
|
1162
|
+
let waitedBefore = waitedByTask.get(taskId) ?? 0;
|
|
1163
|
+
// Output since the last wait means the command is working: the stuck
|
|
1164
|
+
// clock starts again from here.
|
|
1165
|
+
const logBytes = async () => {
|
|
1166
|
+
if (!/^mol-bg-[a-z0-9]+-[a-z0-9]+$/.test(taskId))
|
|
1167
|
+
return null;
|
|
1168
|
+
let r;
|
|
1169
|
+
try {
|
|
1170
|
+
r = await backend.run(`wc -c < /tmp/${taskId}.log 2>/dev/null`, { timeout: 5_000 });
|
|
1171
|
+
}
|
|
1172
|
+
catch (_error) {
|
|
1173
|
+
// No answer about the log: treat the size as unknown, which keeps the
|
|
1174
|
+
// stuck verdict exactly as before.
|
|
1175
|
+
r = null;
|
|
1176
|
+
}
|
|
1177
|
+
const n = Number.parseInt(r?.stdout?.trim() ?? '', 10);
|
|
1178
|
+
return Number.isFinite(n) ? n : null;
|
|
1179
|
+
};
|
|
1180
|
+
if (taskId && waitedBefore >= stuckAfterMs()) {
|
|
1181
|
+
const now = await logBytes();
|
|
1182
|
+
const before = logBytesByTask.get(taskId);
|
|
1183
|
+
if (now !== null && before !== undefined && now > before) {
|
|
1184
|
+
logBytesByTask.set(taskId, now);
|
|
1185
|
+
waitedByTask.set(taskId, 0);
|
|
1186
|
+
waitedBefore = 0;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1109
1189
|
if (taskId && waitedBefore >= stuckAfterMs()) {
|
|
1110
1190
|
return {
|
|
1111
1191
|
taskId,
|
|
@@ -1151,6 +1231,22 @@ export function buildTools(backend, config) {
|
|
|
1151
1231
|
const waitedMs = Date.now() - startedAt;
|
|
1152
1232
|
waitedByTask.set(taskId, waitedBefore + waitedMs);
|
|
1153
1233
|
if (exitCode === null) {
|
|
1234
|
+
const grownTo = await logBytes();
|
|
1235
|
+
const lastSize = logBytesByTask.get(taskId);
|
|
1236
|
+
if (grownTo !== null)
|
|
1237
|
+
logBytesByTask.set(taskId, grownTo);
|
|
1238
|
+
const stillWriting = grownTo !== null && (lastSize === undefined ? grownTo > 0 : grownTo > lastSize);
|
|
1239
|
+
if (stillWriting && waitedBefore + waitedMs >= stuckAfterMs()) {
|
|
1240
|
+
waitedByTask.set(taskId, 0);
|
|
1241
|
+
return {
|
|
1242
|
+
taskId,
|
|
1243
|
+
status: 'running',
|
|
1244
|
+
waitedMs,
|
|
1245
|
+
note: `Still running after ${Math.round((waitedBefore + waitedMs) / 1000)}s of waiting, and its ` +
|
|
1246
|
+
`output is still growing (${grownTo} bytes) — slow, not stuck. Call wait_for_task again to ` +
|
|
1247
|
+
`keep waiting; peek with exec_command: tail ${log}.`,
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1154
1250
|
if (waitedBefore + waitedMs >= stuckAfterMs()) {
|
|
1155
1251
|
return {
|
|
1156
1252
|
taskId,
|
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 `[] ()`.
|