@molecule/api-ai-tools 1.5.7 → 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,CAs+CxF"}
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.
@@ -1032,9 +1034,13 @@ export function buildTools(backend, config) {
1032
1034
  // executor gets a handle to wait on.
1033
1035
  const ceilingSeconds = commandBudgetMs ? Math.max(1, Math.round(commandBudgetMs / 1000)) : 0;
1034
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.
1035
1041
  const overCeiling = commandBudgetMs !== undefined &&
1036
- declaredSeconds > ceilingSeconds &&
1037
- outputIsWithheldUntilExit(command);
1042
+ outputIsWithheldUntilExit(command) &&
1043
+ (declaredSeconds > ceilingSeconds || /\bplaywright\s+test\b/.test(command));
1038
1044
  if (input.run_in_background === true || overCeiling) {
1039
1045
  const id = `mol-bg-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
1040
1046
  const log = `/tmp/${id}.log`;
@@ -1048,6 +1054,14 @@ export function buildTools(backend, config) {
1048
1054
  // Keep the log growing while it runs (see streamingBackgroundCommand):
1049
1055
  // an empty log is what wait_for_task reads as stuck.
1050
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);
1051
1065
  await backend.writeFile(script, `${streaming.command}\n`);
1052
1066
  await backend.run(`nohup sh -c ${shellQuote(`sh ${script} > ${log} 2>&1; echo $? > ${exitFile}`)} ` +
1053
1067
  `> /dev/null 2>&1 & echo $! > /tmp/${id}.pid`, { cwd, timeout: 15_000 });
@@ -1094,15 +1108,21 @@ export function buildTools(backend, config) {
1094
1108
  log,
1095
1109
  exitFile,
1096
1110
  note: (overCeiling
1097
- ? `This command asks for ${declaredSeconds}s, more than this tool's ${ceilingSeconds}s, and its ` +
1098
- '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. '
1099
1116
  : '') +
1100
1117
  `Started in the background. Its output is written to ${log} and comes back as the ` +
1101
1118
  `stdout of wait_for_task when the command finishes (read_file cannot reach /tmp — ` +
1102
1119
  `to peek mid-run, exec_command: tail ${log}). If you have other work, do it and ` +
1103
1120
  `check back. If you need this result before you can continue, call wait_for_task ` +
1104
1121
  `with this taskId — never sleep and read the log by hand.` +
1105
- (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
+ : ''),
1106
1126
  };
1107
1127
  }
1108
1128
  catch (e) {
@@ -1239,7 +1259,8 @@ export function buildTools(backend, config) {
1239
1259
  return null;
1240
1260
  let r;
1241
1261
  try {
1242
- 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 });
1243
1264
  }
1244
1265
  catch (_error) {
1245
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.7",
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",