@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 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-15T21:29:34.478Z
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
@@ -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;AA2B9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAuCnE;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CA+8CxF"}
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
- 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, streamingBackgroundCommand, stripControlChars, truncateMiddle, whitespaceTolerantReplace, } from './utilities.js';
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
- declaredSeconds > ceilingSeconds &&
1018
- outputIsWithheldUntilExit(command);
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
- ? `This command asks for ${declaredSeconds}s, more than this tool's ${ceilingSeconds}s, and its ` +
1079
- 'output only appears when it ends — so it was started in the background instead. '
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
- r = await backend.run(`wc -c < /tmp/${taskId}.log 2>/dev/null`, { timeout: 5_000 });
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
@@ -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
@@ -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
@@ -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(`(['"]?(?:\\w*(?:${SECRET_KEYWORDS})\\w*)['"]?\\s*[:=]\\s*)"(?:[^"\\\\]|\\\\.)*"`, 'gi');
71
- const SECRET_JSON_SQ = new RegExp(`(['"]?(?:\\w*(?:${SECRET_KEYWORDS})\\w*)['"]?\\s*[:=]\\s*)'(?:[^'\\\\]|\\\\.)*'`, 'gi');
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
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/api-ai-tools",
3
- "version": "1.5.6",
3
+ "version": "1.5.8",
4
4
  "description": "Shared AI agent tools with backend abstraction for sandbox and local execution",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",