@molecule/api-ai-tools 1.5.12 → 1.5.14
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 +9 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +54 -4
- package/dist/utilities.d.ts +2 -0
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +7 -2
- 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-10-
|
|
6
|
+
Generated: 2026-10-16T03:58:22.247Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/api-ai-tools
|
|
@@ -967,6 +967,14 @@ needs.
|
|
|
967
967
|
const MAX_READ_RETURN_CHARS: number
|
|
968
968
|
```
|
|
969
969
|
|
|
970
|
+
#### `SMALL_FILE_WHOLE_CHARS`
|
|
971
|
+
|
|
972
|
+
A file this small is returned whole even when a window is asked for (paging it costs a call each).
|
|
973
|
+
|
|
974
|
+
```typescript
|
|
975
|
+
const SMALL_FILE_WHOLE_CHARS: number
|
|
976
|
+
```
|
|
977
|
+
|
|
970
978
|
#### `MAX_SEARCH_RESULTS`
|
|
971
979
|
|
|
972
980
|
Max search results.
|
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;AA8B9C,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;AA8B9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAyCnE;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CA6lDxF"}
|
package/dist/tools.js
CHANGED
|
@@ -25,7 +25,7 @@ const waitedByTask = new Map();
|
|
|
25
25
|
const logBytesByTask = new Map();
|
|
26
26
|
/** The file a background command writes its own output to, when it redirects it. */
|
|
27
27
|
const outputFileByTask = new Map();
|
|
28
|
-
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, outputRedirectTarget, parseCheckCommand, parseCheckProblem, pathArgError, redactSecrets, redactSecretsInCode, resolvePath, shellQuote, sliceLines, streamingBackgroundCommand, stripControlChars, truncateMiddle, whitespaceTolerantReplace, } from './utilities.js';
|
|
28
|
+
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, outputRedirectTarget, parseCheckCommand, parseCheckProblem, pathArgError, redactSecrets, redactSecretsInCode, resolvePath, shellQuote, sliceLines, SMALL_FILE_WHOLE_CHARS, streamingBackgroundCommand, stripControlChars, truncateMiddle, whitespaceTolerantReplace, } from './utilities.js';
|
|
29
29
|
/**
|
|
30
30
|
* How long exec_command waits for a command it backgrounded only because the
|
|
31
31
|
* command asked for more than the tool's ceiling, before handing back a task id.
|
|
@@ -133,6 +133,8 @@ export function buildTools(backend, config) {
|
|
|
133
133
|
}
|
|
134
134
|
return pathGuards ? resolvePath(path, root) : path;
|
|
135
135
|
}
|
|
136
|
+
/** Paths read with a window in this tool set, to tell paging from one targeted read. */
|
|
137
|
+
const windowedReads = new Set();
|
|
136
138
|
/**
|
|
137
139
|
* Verify that `path` does not symlink outside the project root when guards are enabled.
|
|
138
140
|
*
|
|
@@ -391,6 +393,20 @@ export function buildTools(backend, config) {
|
|
|
391
393
|
const effectiveWindow = !sliceLines(content, window) && content.length > MAX_READ_RETURN_CHARS
|
|
392
394
|
? { offset: 1, limit: linesWithin(content, MAX_READ_RETURN_CHARS) }
|
|
393
395
|
: window;
|
|
396
|
+
// A SECOND window of the same small file: hand back all of it (one window is a targeted read). X1 flash-m50 paged files that fit in one
|
|
397
|
+
// call 13 times in a row, each window answered with "read it again with no offset/limit".
|
|
398
|
+
const small = sliceLines(content, window);
|
|
399
|
+
const pagedBefore = small?.truncated ? windowedReads.has(path) : false;
|
|
400
|
+
if (small?.truncated)
|
|
401
|
+
windowedReads.add(path);
|
|
402
|
+
if (small?.truncated && pagedBefore && content.length <= SMALL_FILE_WHOLE_CHARS) {
|
|
403
|
+
return {
|
|
404
|
+
path,
|
|
405
|
+
content: sanitizeFileContent(content, path),
|
|
406
|
+
totalLines: small.totalLines,
|
|
407
|
+
note: `The whole file is returned (${Math.round(content.length / 1024)} KB, ${small.totalLines} lines): a file this small fits in one call, so it is not paged.`,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
394
410
|
const windowed = sliceLines(content, effectiveWindow);
|
|
395
411
|
if (windowed) {
|
|
396
412
|
return {
|
|
@@ -826,7 +842,14 @@ export function buildTools(backend, config) {
|
|
|
826
842
|
? `${alreadyApplied} of these edits ${alreadyApplied === 1 ? 'was' : 'were'} already in the file (its new_string is there and its old_string is not) — nothing to do for ${alreadyApplied === 1 ? 'it' : 'them'}.`
|
|
827
843
|
: null;
|
|
828
844
|
if (content === oldContent) {
|
|
829
|
-
|
|
845
|
+
// A replacement whose new_string IS its old_string changes nothing; `ok` alone read
|
|
846
|
+
// as a fix applied (X1 flash-m51).
|
|
847
|
+
const identical = replacements.filter((r) => typeof r.old_string === 'string' && r.old_string === r.new_string).length;
|
|
848
|
+
const note = alreadyNote ??
|
|
849
|
+
(identical > 0
|
|
850
|
+
? `Nothing changed: ${identical === 1 ? 'the replacement’s new_string is' : `${identical} replacements’ new_string are`} identical to old_string, so the file is exactly as it was. Write the text you want in new_string.`
|
|
851
|
+
: 'Nothing changed: the file is exactly as it was.');
|
|
852
|
+
return { path, ok: true, replacementsApplied: 0, alreadyApplied, note };
|
|
830
853
|
}
|
|
831
854
|
if (onFileDiff)
|
|
832
855
|
onFileDiff({ path, oldContent, newContent: content });
|
|
@@ -1395,13 +1418,26 @@ export function buildTools(backend, config) {
|
|
|
1395
1418
|
`kill $(cat /tmp/${taskId}.pid) 2>/dev/null; pkill -f ${taskId}.sh) and move on; do not wait on it again.`,
|
|
1396
1419
|
};
|
|
1397
1420
|
}
|
|
1421
|
+
// The output so far rides the result: a wait that shows nothing was waited blind
|
|
1422
|
+
// (X1 flash-m50: 4m42s across two waits on an 11-minute run).
|
|
1423
|
+
const own = outputFileByTask.get(taskId);
|
|
1424
|
+
let soFar;
|
|
1425
|
+
try {
|
|
1426
|
+
const r = await backend.run(`cat ${log}${own ? ` ${shellQuote(own)}` : ''} 2>/dev/null | tail -c 3000`, { timeout: 5_000 });
|
|
1427
|
+
soFar = r.stdout ?? '';
|
|
1428
|
+
}
|
|
1429
|
+
catch (_error) {
|
|
1430
|
+
// No tail: the note still says how to peek.
|
|
1431
|
+
soFar = '';
|
|
1432
|
+
}
|
|
1398
1433
|
return {
|
|
1399
1434
|
taskId,
|
|
1400
1435
|
status: 'running',
|
|
1401
1436
|
waitedMs,
|
|
1437
|
+
...(soFar.trim() ? { outputSoFar: sanitizeOutput(soFar) } : {}),
|
|
1402
1438
|
note: `Still running after ${Math.round(waitedMs / 1000)}s. Call wait_for_task again to ` +
|
|
1403
|
-
`keep waiting — its result carries the log output.
|
|
1404
|
-
`
|
|
1439
|
+
`keep waiting — its result carries the log output${soFar.trim() ? '; outputSoFar is the end of the log as it stands, so there is no need to tail it' : ''}. ` +
|
|
1440
|
+
`Do other work in the meantime (another suite, a fix you have already found) rather than waiting.`,
|
|
1405
1441
|
};
|
|
1406
1442
|
}
|
|
1407
1443
|
let output;
|
|
@@ -1413,6 +1449,20 @@ export function buildTools(backend, config) {
|
|
|
1413
1449
|
// an empty output with a real exit code is still a result.
|
|
1414
1450
|
output = '';
|
|
1415
1451
|
}
|
|
1452
|
+
// A command that wrote its output to its own file (`> /tmp/e2e.log`) left the task log
|
|
1453
|
+
// holding a word: read that file too, so the result (and every note built on it) has the
|
|
1454
|
+
// run's output (X1 flash-m48: 60 passed / 14 failed, and the notes saw "started").
|
|
1455
|
+
const ownFile = outputFileByTask.get(taskId);
|
|
1456
|
+
if (ownFile && output.trim().length < 2000) {
|
|
1457
|
+
try {
|
|
1458
|
+
const ownText = await backend.readFile(ownFile);
|
|
1459
|
+
if (ownText.trim())
|
|
1460
|
+
output = `${output.trim() ? `${output.trimEnd()}\n` : ''}${ownText}`;
|
|
1461
|
+
}
|
|
1462
|
+
catch (_error) {
|
|
1463
|
+
// The file is gone or unreadable: the task log is the whole result, as before.
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1416
1466
|
// Same shaping as a foreground exec result, so the executor reads both
|
|
1417
1467
|
// the same way.
|
|
1418
1468
|
return {
|
package/dist/utilities.d.ts
CHANGED
|
@@ -249,6 +249,8 @@ export declare const MAX_BATCH_READ_BYTES: number;
|
|
|
249
249
|
* needs.
|
|
250
250
|
*/
|
|
251
251
|
export declare const MAX_READ_RETURN_CHARS: number;
|
|
252
|
+
/** A file this small is returned whole even when a window is asked for (paging it costs a call each). */
|
|
253
|
+
export declare const SMALL_FILE_WHOLE_CHARS: number;
|
|
252
254
|
/** Max search results. */
|
|
253
255
|
export declare const MAX_SEARCH_RESULTS = 50;
|
|
254
256
|
/** Max find results. */
|
package/dist/utilities.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAS5C;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,GAAG,MAAM,KAAG,MAEE,CAAA;
|
|
1
|
+
{"version":3,"file":"utilities.d.ts","sourceRoot":"","sources":["../src/utilities.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAS5C;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,GAAG,MAAM,KAAG,MAEE,CAAA;AAgGhD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGnD;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,GAAG,MAAM,CAUpF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAElE;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAWnE;AAKD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG;IAC3D,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB,CAkCA;AA2BD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAuBlE;AAID;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAQrE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAOxE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQpD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIvE;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI9E;AAID,yCAAyC;AACzC,eAAO,MAAM,aAAa,QAAkB,CAAA;AAC5C,8CAA8C;AAC9C,eAAO,MAAM,cAAc,QAAmB,CAAA;AAC9C,kDAAkD;AAClD,eAAO,MAAM,eAAe,QAAa,CAAA;AACzC;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,KAAK,CAAA;AAE1C;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CA6BhG;AAED,0BAA0B;AAC1B;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,KAAK,CAAA;AAEtC;;;GAGG;AACH,eAAO,MAAM,oBAAoB,QAAa,CAAA;AAE9C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,QAAY,CAAA;AAE9C,yGAAyG;AACzG,eAAO,MAAM,sBAAsB,QAAY,CAAA;AAE/C,0BAA0B;AAC1B,eAAO,MAAM,kBAAkB,KAAK,CAAA;AACpC,wBAAwB;AACxB,eAAO,MAAM,gBAAgB,MAAM,CAAA;AAEnC;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,mHAW/B,CAAA;AAEV;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CA2CnE;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAiBjD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,IAAI,CAsCf;AAED,qEAAqE;AACrE,eAAO,MAAM,eAAe,QAA0C,CAAA;AAEtE;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgB7D;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IACxC,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;CACjB,GAAG,MAAM,GAAG,IAAI,CAKhB"}
|
package/dist/utilities.js
CHANGED
|
@@ -38,7 +38,8 @@ s.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
|
|
|
38
38
|
// access controls than the vault). PWD (DB_PWD/MYSQL_PWD), APIKEY (no underscore,
|
|
39
39
|
// e.g. MAILGUN_APIKEY), and SERVICE_ACCOUNT were the gaps; `_KEY` already covers
|
|
40
40
|
// OPENAI_KEY / *_ROLE_KEY.
|
|
41
|
-
|
|
41
|
+
// AUTH but not AUTHOR (usernames printed as `author=…` were masked, X1 flash-m44); AUTHORIZATION still counts.
|
|
42
|
+
const SECRET_KEYWORDS = 'SECRET|PASSWORD|PASSWD|PWD|TOKEN|API_KEY|APIKEY|PRIVATE_KEY|DATABASE_URL|REDIS_URL|AUTH(?!OR(?!IZ))|CREDENTIAL|ACCESS_KEY|SIGNING_KEY|ENCRYPTION_KEY|CONNECTION_STRING|SERVICE_ACCOUNT|DSN|SMTP_PASS|_KEY';
|
|
42
43
|
/**
|
|
43
44
|
* A keyword-named `NAME=value` anywhere on a line — the ENV-DUMP grade pattern,
|
|
44
45
|
* used by {@link redactSecrets} for command output and `.env` reads. The permissive
|
|
@@ -100,7 +101,9 @@ const NON_SECRET_VALUES = new Set([
|
|
|
100
101
|
const jsonValueReplacer = (quote) => (match, prefix) => {
|
|
101
102
|
const value = match.slice(prefix.length);
|
|
102
103
|
const inner = value.slice(1, -1);
|
|
103
|
-
|
|
104
|
+
// An empty value hides nothing, and a suite fixture's `"password": ""` is the fact a
|
|
105
|
+
// failing assertion turns on (X1 flash-m52).
|
|
106
|
+
if (inner === '' || NON_SECRET_VALUES.has(inner.toLowerCase()))
|
|
104
107
|
return match;
|
|
105
108
|
return `${prefix}${quote}[REDACTED]${quote}`;
|
|
106
109
|
};
|
|
@@ -491,6 +494,8 @@ export const MAX_BATCH_READ_BYTES = 200 * 1024;
|
|
|
491
494
|
* needs.
|
|
492
495
|
*/
|
|
493
496
|
export const MAX_READ_RETURN_CHARS = 80 * 1024;
|
|
497
|
+
/** A file this small is returned whole even when a window is asked for (paging it costs a call each). */
|
|
498
|
+
export const SMALL_FILE_WHOLE_CHARS = 24 * 1024;
|
|
494
499
|
/** Max search results. */
|
|
495
500
|
export const MAX_SEARCH_RESULTS = 50;
|
|
496
501
|
/** Max find results. */
|