@molecule/api-ai-tools 1.5.6 → 1.5.8
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 +16 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +48 -8
- package/dist/utilities.d.ts +10 -0
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +25 -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-15T23:00:29.499Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/api-ai-tools
|
|
@@ -614,6 +614,21 @@ function outputIsWithheldUntilExit(command: string): boolean
|
|
|
614
614
|
|
|
615
615
|
**Returns:** True when a kill would return no useful output.
|
|
616
616
|
|
|
617
|
+
#### `outputRedirectTarget(command)`
|
|
618
|
+
|
|
619
|
+
The file a command sends its own output to, when it redirects it (`> out.txt`,
|
|
620
|
+
`>> log`, `&> run.log`, `| tee [-a] file`), or null. A background command that does
|
|
621
|
+
this leaves its task log empty, so the log is the wrong place to look and an empty
|
|
622
|
+
log proves nothing (X1 flash-m29).
|
|
623
|
+
|
|
624
|
+
```typescript
|
|
625
|
+
function outputRedirectTarget(command: string): string | null
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
- `command` — The shell command.
|
|
629
|
+
|
|
630
|
+
**Returns:** The target as written, or null (`/dev/null` and `&1`/`&2` are not files).
|
|
631
|
+
|
|
617
632
|
#### `parseCheckCommand(path)`
|
|
618
633
|
|
|
619
634
|
The shell command that parses one file and exits non-zero with the error
|
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;AA8B9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAwCnE;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CA2/CxF"}
|
package/dist/tools.js
CHANGED
|
@@ -23,7 +23,9 @@ const waitedByTask = new Map();
|
|
|
23
23
|
* m21, 2026-10-15).
|
|
24
24
|
*/
|
|
25
25
|
const logBytesByTask = new Map();
|
|
26
|
-
|
|
26
|
+
/** The file a background command writes its own output to, when it redirects it. */
|
|
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';
|
|
27
29
|
/**
|
|
28
30
|
* How long exec_command waits for a command it backgrounded only because the
|
|
29
31
|
* command asked for more than the tool's ceiling, before handing back a task id.
|
|
@@ -103,9 +105,15 @@ export function buildTools(backend, config) {
|
|
|
103
105
|
.run(command, { cwd: path.slice(0, path.lastIndexOf('/')) || '/', timeout: 15_000 })
|
|
104
106
|
.catch(() => null);
|
|
105
107
|
const problem = result ? parseCheckProblem(result) : null;
|
|
108
|
+
// JSX in a `.ts` file parses as a type assertion and fails on the first
|
|
109
|
+
// attribute — `Expected ">" but found "className"` — which does not say the
|
|
110
|
+
// file just needs to be `.tsx` (X1 flash-m28: three writes and 2.5 minutes).
|
|
111
|
+
const jsxInTs = problem !== null && /\.ts$/.test(path) && /Expected ">" but found|JSX/i.test(problem)
|
|
112
|
+
? '\nThis looks like JSX in a `.ts` file: JSX only parses in a `.tsx` file — rename it (and its imports keep the `.js` extension).'
|
|
113
|
+
: '';
|
|
106
114
|
return problem
|
|
107
115
|
? {
|
|
108
|
-
syntaxError: `The file no longer parses after this change — fix it before anything else, every build and test fails until it does:\n${problem}`,
|
|
116
|
+
syntaxError: `The file no longer parses after this change — fix it before anything else, every build and test fails until it does:\n${problem}${jsxInTs}`,
|
|
109
117
|
}
|
|
110
118
|
: {};
|
|
111
119
|
}
|
|
@@ -801,10 +809,23 @@ export function buildTools(backend, config) {
|
|
|
801
809
|
}
|
|
802
810
|
}
|
|
803
811
|
const excerpt = renderEditExcerpt(finalContent, spans);
|
|
812
|
+
// A replacement whose old text ended in a newline and whose new text does not
|
|
813
|
+
// joins the line after it onto its last line. Applied as written, but said:
|
|
814
|
+
// flash-m28 saw two lines merged only one round trip later.
|
|
815
|
+
const joined = replacements.filter((r) => typeof r.old_string === 'string' &&
|
|
816
|
+
typeof r.new_string === 'string' &&
|
|
817
|
+
r.old_string.endsWith('\n') &&
|
|
818
|
+
r.new_string !== '' &&
|
|
819
|
+
!r.new_string.endsWith('\n')).length;
|
|
804
820
|
return {
|
|
805
821
|
path,
|
|
806
822
|
ok: true,
|
|
807
823
|
replacementsApplied: replacements.length - alreadyApplied,
|
|
824
|
+
...(joined > 0
|
|
825
|
+
? {
|
|
826
|
+
newlineNote: `${joined === 1 ? 'A replacement' : `${joined} replacements`} removed the line break after the replaced text (old_string ended with a newline, new_string does not), so the next line now continues on the same line. Check the excerpt; add the newline back if that was not intended.`,
|
|
827
|
+
}
|
|
828
|
+
: {}),
|
|
808
829
|
...(alreadyApplied > 0 ? { alreadyApplied, note: alreadyNote } : {}),
|
|
809
830
|
totalLines: countLines(finalContent),
|
|
810
831
|
...(excerpt ? { excerpt: sanitizeFileContent(excerpt, path) } : {}),
|
|
@@ -1013,9 +1034,13 @@ export function buildTools(backend, config) {
|
|
|
1013
1034
|
// executor gets a handle to wait on.
|
|
1014
1035
|
const ceilingSeconds = commandBudgetMs ? Math.max(1, Math.round(commandBudgetMs / 1000)) : 0;
|
|
1015
1036
|
const declaredSeconds = commandBudgetMs ? declaredBudgetSeconds(command, input.timeout) : 0;
|
|
1037
|
+
// A Playwright run with its output held back by a pipe is backgrounded without
|
|
1038
|
+
// declaring a budget too: a suite routinely outlasts the ceiling, and killed there
|
|
1039
|
+
// behind `| tail` it returns nothing (X1 flash-m29 lost a 290 s run that way).
|
|
1040
|
+
// A short one still comes back as a foreground result, through the grace below.
|
|
1016
1041
|
const overCeiling = commandBudgetMs !== undefined &&
|
|
1017
|
-
|
|
1018
|
-
|
|
1042
|
+
outputIsWithheldUntilExit(command) &&
|
|
1043
|
+
(declaredSeconds > ceilingSeconds || /\bplaywright\s+test\b/.test(command));
|
|
1019
1044
|
if (input.run_in_background === true || overCeiling) {
|
|
1020
1045
|
const id = `mol-bg-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1021
1046
|
const log = `/tmp/${id}.log`;
|
|
@@ -1029,6 +1054,14 @@ export function buildTools(backend, config) {
|
|
|
1029
1054
|
// Keep the log growing while it runs (see streamingBackgroundCommand):
|
|
1030
1055
|
// an empty log is what wait_for_task reads as stuck.
|
|
1031
1056
|
const streaming = streamingBackgroundCommand(command);
|
|
1057
|
+
const ownTarget = outputRedirectTarget(streaming.command);
|
|
1058
|
+
const ownFile = ownTarget
|
|
1059
|
+
? ownTarget.startsWith('/')
|
|
1060
|
+
? ownTarget
|
|
1061
|
+
: `${cwd}/${ownTarget}`
|
|
1062
|
+
: null;
|
|
1063
|
+
if (ownFile)
|
|
1064
|
+
outputFileByTask.set(id, ownFile);
|
|
1032
1065
|
await backend.writeFile(script, `${streaming.command}\n`);
|
|
1033
1066
|
await backend.run(`nohup sh -c ${shellQuote(`sh ${script} > ${log} 2>&1; echo $? > ${exitFile}`)} ` +
|
|
1034
1067
|
`> /dev/null 2>&1 & echo $! > /tmp/${id}.pid`, { cwd, timeout: 15_000 });
|
|
@@ -1075,15 +1108,21 @@ export function buildTools(backend, config) {
|
|
|
1075
1108
|
log,
|
|
1076
1109
|
exitFile,
|
|
1077
1110
|
note: (overCeiling
|
|
1078
|
-
?
|
|
1079
|
-
|
|
1111
|
+
? declaredSeconds > ceilingSeconds
|
|
1112
|
+
? `This command asks for ${declaredSeconds}s, more than this tool's ${ceilingSeconds}s, and its ` +
|
|
1113
|
+
'output only appears when it ends — so it was started in the background instead. '
|
|
1114
|
+
: `A Playwright run can take longer than this tool's ${ceilingSeconds}s, and behind a pipe its ` +
|
|
1115
|
+
'output only appears when it ends — so it was started in the background instead. '
|
|
1080
1116
|
: '') +
|
|
1081
1117
|
`Started in the background. Its output is written to ${log} and comes back as the ` +
|
|
1082
1118
|
`stdout of wait_for_task when the command finishes (read_file cannot reach /tmp — ` +
|
|
1083
1119
|
`to peek mid-run, exec_command: tail ${log}). If you have other work, do it and ` +
|
|
1084
1120
|
`check back. If you need this result before you can continue, call wait_for_task ` +
|
|
1085
1121
|
`with this taskId — never sleep and read the log by hand.` +
|
|
1086
|
-
(streaming.changes.length ? ` ${streaming.changes.join(' ')}` : '')
|
|
1122
|
+
(streaming.changes.length ? ` ${streaming.changes.join(' ')}` : '') +
|
|
1123
|
+
(ownFile
|
|
1124
|
+
? ` This command writes its own output to ${ownFile}, so the task log stays empty: peek with exec_command: tail ${ownFile}.`
|
|
1125
|
+
: ''),
|
|
1087
1126
|
};
|
|
1088
1127
|
}
|
|
1089
1128
|
catch (e) {
|
|
@@ -1220,7 +1259,8 @@ export function buildTools(backend, config) {
|
|
|
1220
1259
|
return null;
|
|
1221
1260
|
let r;
|
|
1222
1261
|
try {
|
|
1223
|
-
|
|
1262
|
+
const own = outputFileByTask.get(taskId);
|
|
1263
|
+
r = await backend.run(`cat /tmp/${taskId}.log${own ? ` ${shellQuote(own)}` : ''} 2>/dev/null | wc -c`, { timeout: 5_000 });
|
|
1224
1264
|
}
|
|
1225
1265
|
catch (_error) {
|
|
1226
1266
|
// No answer about the log: treat the size as unknown, which keeps the
|
package/dist/utilities.d.ts
CHANGED
|
@@ -89,6 +89,16 @@ export declare function declaredBudgetSeconds(command: string, timeoutParam: unk
|
|
|
89
89
|
* @returns True when a kill would return no useful output.
|
|
90
90
|
*/
|
|
91
91
|
export declare function outputIsWithheldUntilExit(command: string): boolean;
|
|
92
|
+
/**
|
|
93
|
+
* The file a command sends its own output to, when it redirects it (`> out.txt`,
|
|
94
|
+
* `>> log`, `&> run.log`, `| tee [-a] file`), or null. A background command that does
|
|
95
|
+
* this leaves its task log empty, so the log is the wrong place to look and an empty
|
|
96
|
+
* log proves nothing (X1 flash-m29).
|
|
97
|
+
*
|
|
98
|
+
* @param command - The shell command.
|
|
99
|
+
* @returns The target as written, or null (`/dev/null` and `&1`/`&2` are not files).
|
|
100
|
+
*/
|
|
101
|
+
export declare function outputRedirectTarget(command: string): string | null;
|
|
92
102
|
/**
|
|
93
103
|
* Rewrite a command about to run in the background so its log grows while it
|
|
94
104
|
* runs. Two shapes left the log empty until exit, and an empty log is what
|
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":"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"}
|
package/dist/utilities.js
CHANGED
|
@@ -67,8 +67,11 @@ const SECRET_ENV_ASSIGNMENT = new RegExp(
|
|
|
67
67
|
// un-masked exactly the plainest env vars.
|
|
68
68
|
`^((?:export[ \\t]+)?[A-Za-z0-9_]*(?:${SECRET_KEYWORDS})[A-Za-z0-9_]*)=(?![{<])(.+)$`, 'gim');
|
|
69
69
|
/** Catch JSON-formatted env dumps like { KEY: 'value' } from node/python. */
|
|
70
|
-
const SECRET_JSON_DQ = new RegExp(
|
|
71
|
-
|
|
70
|
+
const SECRET_JSON_DQ = new RegExp(
|
|
71
|
+
// A value ends at its closing quote OR the end of its line: cut off mid-value (`head -c`),
|
|
72
|
+
// the old pattern ran on to a quote on a LATER line and swallowed the lines between (flash-m32).
|
|
73
|
+
`(['"]?(?:\\w*(?:${SECRET_KEYWORDS})\\w*)['"]?\\s*[:=]\\s*)"(?:[^"\\\\\\n]|\\\\.)*(?:"|(?=\\n)|$)`, 'gi');
|
|
74
|
+
const SECRET_JSON_SQ = new RegExp(`(['"]?(?:\\w*(?:${SECRET_KEYWORDS})\\w*)['"]?\\s*[:=]\\s*)'(?:[^'\\\\\\n]|\\\\.)*(?:'|(?=\\n)|$)`, 'gi');
|
|
72
75
|
/**
|
|
73
76
|
* Exact VALUES that are UI vocabulary, never credentials — kept verbatim even when they
|
|
74
77
|
* sit next to a keyword-looking name. Frontend auth code is full of these: a JSX ternary
|
|
@@ -189,6 +192,26 @@ export function declaredBudgetSeconds(command, timeoutParam) {
|
|
|
189
192
|
export function outputIsWithheldUntilExit(command) {
|
|
190
193
|
return /\|\s*(?:tail|head|grep|egrep|fgrep|sort|uniq|wc|jq)\b/.test(command);
|
|
191
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* The file a command sends its own output to, when it redirects it (`> out.txt`,
|
|
197
|
+
* `>> log`, `&> run.log`, `| tee [-a] file`), or null. A background command that does
|
|
198
|
+
* this leaves its task log empty, so the log is the wrong place to look and an empty
|
|
199
|
+
* log proves nothing (X1 flash-m29).
|
|
200
|
+
*
|
|
201
|
+
* @param command - The shell command.
|
|
202
|
+
* @returns The target as written, or null (`/dev/null` and `&1`/`&2` are not files).
|
|
203
|
+
*/
|
|
204
|
+
export function outputRedirectTarget(command) {
|
|
205
|
+
const tee = /\|\s*tee\s+(?:-a\s+)?([^\s;&|]+)/.exec(command);
|
|
206
|
+
if (tee)
|
|
207
|
+
return tee[1];
|
|
208
|
+
let last = null;
|
|
209
|
+
for (const m of command.matchAll(/(?:^|[^0-9&>])(?:&>|1?>>?)\s*([^\s;&|]+)/g)) {
|
|
210
|
+
if (m[1] !== '/dev/null' && !m[1].startsWith('&'))
|
|
211
|
+
last = m[1];
|
|
212
|
+
}
|
|
213
|
+
return last;
|
|
214
|
+
}
|
|
192
215
|
/** A trailing `| tail -N` / `| tail -n N` (stderr redirects before it stay). */
|
|
193
216
|
const TRAILING_TAIL = /\s*\|\s*tail(?:\s+-n)?\s+[-+]?\d+\s*$/;
|
|
194
217
|
/**
|