@molecule/api-ai-tools 1.1.0 → 1.2.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 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-09-22T18:02:23.461Z
6
+ Generated: 2026-09-22T21:57:22.227Z
7
7
  -->
8
8
 
9
9
  # @molecule/api-ai-tools
@@ -581,6 +581,37 @@ function outputIsWithheldUntilExit(command: string): boolean
581
581
 
582
582
  **Returns:** True when a kill would return no useful output.
583
583
 
584
+ #### `parseCheckCommand(path)`
585
+
586
+ The shell command that parses one file and exits non-zero with the error
587
+ when it does not parse: `node --check` for JavaScript, `JSON.parse` for
588
+ JSON, esbuild's transform (present wherever Vite is) for TypeScript.
589
+
590
+ ```typescript
591
+ function parseCheckCommand(path: string): string | null
592
+ ```
593
+
594
+ - `path` — The file's absolute path.
595
+
596
+ **Returns:** The command, or null when the file type has no cheap parser.
597
+
598
+ #### `parseCheckProblem(result)`
599
+
600
+ What a failed parse check says to the executor, or null when the check
601
+ passed or could not run (no parser available is not a syntax error).
602
+
603
+ ```typescript
604
+ function parseCheckProblem(result: {
605
+ stdout: string
606
+ stderr: string
607
+ exitCode: number
608
+ }): string | null
609
+ ```
610
+
611
+ - `result` — The check command's result.
612
+
613
+ **Returns:** The message for the tool result, or null.
614
+
584
615
  #### `pathArgError(path, tool)`
585
616
 
586
617
  Validate a file tool's `path` argument is a non-empty string. A weak model
@@ -881,6 +912,14 @@ Max content size for write_file (10MB).
881
912
  const MAX_WRITE_SIZE: number
882
913
  ```
883
914
 
915
+ #### `PARSE_CHECKABLE`
916
+
917
+ Files a parse check can vouch for cheaply right after a write.
918
+
919
+ ```typescript
920
+ const PARSE_CHECKABLE: RegExp
921
+ ```
922
+
884
923
  #### `TOOL_SCHEMAS`
885
924
 
886
925
  Canonical tool schemas shared by the agent runtime and documentation.
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../src/backends/sandbox.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAGnD,sFAAsF;AACtF,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACvC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;KAAE,CAAC,CAAC,CAAA;IACnF,mEAAmE;IAEnE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,WAAW,EACpB,WAAW,SAAe,GACzB,gBAAgB,CA2ClB"}
1
+ {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../src/backends/sandbox.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAGnD,sFAAsF;AACtF,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACvC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvD,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;KAAE,CAAC,CAAC,CAAA;IACnF,mEAAmE;IAEnE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,WAAW,EACpB,WAAW,SAAe,GACzB,gBAAgB,CAmDlB"}
@@ -42,9 +42,17 @@ export function createSandboxBackend(sandbox, projectRoot = '/workspace') {
42
42
  // printed still comes back, with exit code 124. Wrapping only the inner
43
43
  // command would run it in a child shell that never sees unexported variables
44
44
  // the consumer's sourcing set.
45
+ // Its output goes to files that are printed once the shell exits, never to
46
+ // the exec's own pipe: `cd app && server > log &` backgrounds a SUBSHELL whose
47
+ // stdout is still that pipe (the redirect covers the server only), and the
48
+ // exec waits for every holder of the pipe — past the budget, which kills the
49
+ // shell but not the detached subshell, until the caller's outer timeout
50
+ // throws the output away. Measured: three 300 s calls in two runs (X0 x31,
51
+ // x33). A background child now holds a file, and the call returns when the
52
+ // command does.
45
53
  const budgetSeconds = opts?.budgetMs ? Math.max(1, Math.round(opts.budgetMs / 1000)) : 0;
46
54
  const fullCommand = budgetSeconds
47
- ? `timeout -k 5 ${budgetSeconds} bash -c ${shellQuote(anchored)}`
55
+ ? `o=$(mktemp); e=$(mktemp); timeout -k 5 ${budgetSeconds} bash -c ${shellQuote(anchored)} > "$o" 2> "$e" < /dev/null; c=$?; cat "$o"; cat "$e" >&2; rm -f "$o" "$e"; exit $c`
48
56
  : anchored;
49
57
  // sandbox.exec is the Sandbox interface method — runs inside Docker, inherently sandboxed
50
58
  const result = await sandbox.exec(fullCommand, { timeout: opts?.timeout });
@@ -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;AAY9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AA6BnE;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CA8rCxF"}
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;AAY9C,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AA+BnE;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CAmvCxF"}
package/dist/tools.js CHANGED
@@ -15,7 +15,7 @@ import { TOOL_SCHEMAS } from './schemas.js';
15
15
  const stuckAfterMs = () => Number(process.env.MOL_AI_BG_STUCK_AFTER_MS) || 5 * 60 * 1000;
16
16
  /** Cumulative wait_for_task time per task id, this process. */
17
17
  const waitedByTask = new Map();
18
- import { checkBlockedCommand, declaredBudgetSeconds, DEFAULT_SEARCH_EXCLUDED_DIRS, directoryReadHint, isEnvFilePath, 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, pathArgError, redactSecrets, redactSecretsInCode, resolvePath, shellQuote, sliceLines, stripControlChars, truncateMiddle, whitespaceTolerantReplace, } from './utilities.js';
18
+ import { checkBlockedCommand, declaredBudgetSeconds, DEFAULT_SEARCH_EXCLUDED_DIRS, directoryReadHint, isEnvFilePath, 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';
19
19
  /**
20
20
  * Build a complete set of AI agent tools bound to an execution backend.
21
21
  *
@@ -51,6 +51,29 @@ export function buildTools(backend, config) {
51
51
  const grepExcludeArgs = excludedDirs.map((d) => `--exclude-dir=${shellQuote(d)}`).join(' ');
52
52
  const findExcludeArgs = excludedDirs.map((d) => `-not -path ${shellQuote(`*/${d}/*`)}`).join(' ');
53
53
  // ── Path helpers ───────────────────────────────────────────────
54
+ /**
55
+ * After a write or edit, parse the file and hand the error back IN THE SAME
56
+ * RESULT. An edit that leaves a file unparsable used to surface only at the
57
+ * next build or type-check, minutes and many calls later — x13 (2026-09-22)
58
+ * mangled a template literal in `scripts/prerender.mjs`, the site stopped
59
+ * building, and the fix round spent 500 calls on the fallout. `node --check`
60
+ * is ~50 ms; the executor sees the exact line while the edit is still in
61
+ * front of it.
62
+ */
63
+ async function syntaxErrorAfterWrite(path) {
64
+ const command = parseCheckCommand(path);
65
+ if (!command)
66
+ return {};
67
+ const result = await backend
68
+ .run(command, { cwd: path.slice(0, path.lastIndexOf('/')) || '/', timeout: 15_000 })
69
+ .catch(() => null);
70
+ const problem = result ? parseCheckProblem(result) : null;
71
+ return problem
72
+ ? {
73
+ syntaxError: `The file no longer parses after this change — fix it before anything else, every build and test fails until it does:\n${problem}`,
74
+ }
75
+ : {};
76
+ }
54
77
  /**
55
78
  * Resolve a workspace-relative path using optional path guards.
56
79
  *
@@ -464,7 +487,7 @@ export function buildTools(backend, config) {
464
487
  const diff = computeDiff(oldContent, finalContent);
465
488
  if (onFileChange)
466
489
  onFileChange({ type: oldContent === null ? 'created' : 'modified', path });
467
- return { path, ok: true, diff };
490
+ return { path, ok: true, diff, ...(await syntaxErrorAfterWrite(path)) };
468
491
  }
469
492
  catch (e) {
470
493
  return { error: `Failed to write ${path}: ${e.message}` };
@@ -504,6 +527,7 @@ export function buildTools(backend, config) {
504
527
  content = verdict.content;
505
528
  }
506
529
  const oldContent = content;
530
+ let alreadyApplied = 0;
507
531
  for (const { old_string: oldString, new_string: newString } of replacements) {
508
532
  // Validate each replacement is well-formed BEFORE touching content. A
509
533
  // weak model sometimes emits a batch element missing a field or with a
@@ -516,6 +540,19 @@ export function buildTools(backend, config) {
516
540
  };
517
541
  }
518
542
  const count = content.split(oldString).length - 1;
543
+ // The edit is ALREADY in the file: old_string is gone and a distinctive
544
+ // new_string is there exactly once. The executor re-sends an edit it
545
+ // made a few calls ago (it works from its memory of the file), and the
546
+ // "not found" error cost it a read_file plus a retry each time — 6 to 11
547
+ // times a build in X0 x30/x34. Report it as applied instead. Short or
548
+ // repeated new_strings still error: they could be there by coincidence.
549
+ if (count === 0 &&
550
+ newString !== oldString &&
551
+ newString.trim().length >= 20 &&
552
+ content.split(newString).length - 1 === 1) {
553
+ alreadyApplied++;
554
+ continue;
555
+ }
519
556
  if (count === 0) {
520
557
  // A bare "old_string not found" gives the model nothing to correct, so
521
558
  // it retries blindly (the #1 edit_file failure mode). Diagnose WHY the
@@ -625,6 +662,12 @@ export function buildTools(backend, config) {
625
662
  const at = content.indexOf(oldString);
626
663
  content = content.slice(0, at) + newString + content.slice(at + oldString.length);
627
664
  }
665
+ const alreadyNote = alreadyApplied > 0
666
+ ? `${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'}.`
667
+ : null;
668
+ if (content === oldContent) {
669
+ return { path, ok: true, replacementsApplied: 0, alreadyApplied, note: alreadyNote };
670
+ }
628
671
  if (onFileDiff)
629
672
  onFileDiff({ path, oldContent, newContent: content });
630
673
  await backend.writeFile(path, content);
@@ -632,7 +675,13 @@ export function buildTools(backend, config) {
632
675
  await onAfterWrite(path).catch(() => { });
633
676
  if (onFileChange)
634
677
  onFileChange({ type: 'modified', path });
635
- return { path, ok: true, replacementsApplied: replacements.length };
678
+ return {
679
+ path,
680
+ ok: true,
681
+ replacementsApplied: replacements.length - alreadyApplied,
682
+ ...(alreadyApplied > 0 ? { alreadyApplied, note: alreadyNote } : {}),
683
+ ...(await syntaxErrorAfterWrite(path)),
684
+ };
636
685
  }
637
686
  catch (e) {
638
687
  return { error: `Failed to edit ${path}: ${e.message}` };
@@ -263,4 +263,27 @@ export declare function truncateMiddle(s: string, maxLength: number): string;
263
263
  * @returns The new content if a unique fuzzy run matched, else null.
264
264
  */
265
265
  export declare function whitespaceTolerantReplace(content: string, oldString: string, newString: string): string | null;
266
+ /** Files a parse check can vouch for cheaply right after a write. */
267
+ export declare const PARSE_CHECKABLE: RegExp;
268
+ /**
269
+ * The shell command that parses one file and exits non-zero with the error
270
+ * when it does not parse: `node --check` for JavaScript, `JSON.parse` for
271
+ * JSON, esbuild's transform (present wherever Vite is) for TypeScript.
272
+ *
273
+ * @param path - The file's absolute path.
274
+ * @returns The command, or null when the file type has no cheap parser.
275
+ */
276
+ export declare function parseCheckCommand(path: string): string | null;
277
+ /**
278
+ * What a failed parse check says to the executor, or null when the check
279
+ * passed or could not run (no parser available is not a syntax error).
280
+ *
281
+ * @param result - The check command's result.
282
+ * @returns The message for the tool result, or null.
283
+ */
284
+ export declare function parseCheckProblem(result: {
285
+ stdout: string;
286
+ stderr: string;
287
+ exitCode: number;
288
+ }): string | null;
266
289
  //# sourceMappingURL=utilities.d.ts.map
@@ -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
@@ -521,3 +521,39 @@ export function whitespaceTolerantReplace(content, oldString, newString) {
521
521
  const start = matches[0];
522
522
  return [...fileLines.slice(0, start), newString, ...fileLines.slice(start + normOld.length)].join('\n');
523
523
  }
524
+ /** Files a parse check can vouch for cheaply right after a write. */
525
+ export const PARSE_CHECKABLE = /\.(m?js|cjs|jsx|ts|tsx|mts|cts|json)$/;
526
+ /**
527
+ * The shell command that parses one file and exits non-zero with the error
528
+ * when it does not parse: `node --check` for JavaScript, `JSON.parse` for
529
+ * JSON, esbuild's transform (present wherever Vite is) for TypeScript.
530
+ *
531
+ * @param path - The file's absolute path.
532
+ * @returns The command, or null when the file type has no cheap parser.
533
+ */
534
+ export function parseCheckCommand(path) {
535
+ if (!PARSE_CHECKABLE.test(path))
536
+ return null;
537
+ const q = shellQuote(path);
538
+ if (/\.(m?js|cjs)$/.test(path))
539
+ return `node --check ${q}`;
540
+ if (/\.json$/.test(path))
541
+ return `node -e "JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'))" ${q}`;
542
+ return (`node -e "const p=process.argv[1];const src=require('fs').readFileSync(p,'utf8');` +
543
+ `require('esbuild').transformSync(src,{loader:p.endsWith('x')?'tsx':'ts',logLevel:'silent'})" ${q}`);
544
+ }
545
+ /**
546
+ * What a failed parse check says to the executor, or null when the check
547
+ * passed or could not run (no parser available is not a syntax error).
548
+ *
549
+ * @param result - The check command's result.
550
+ * @returns The message for the tool result, or null.
551
+ */
552
+ export function parseCheckProblem(result) {
553
+ if (result.exitCode === 0)
554
+ return null;
555
+ const text = (result.stderr || result.stdout).trim();
556
+ if (/Cannot find module|not found/i.test(text) && !/SyntaxError/.test(text))
557
+ return null;
558
+ return text.slice(0, 1200);
559
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/api-ai-tools",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
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",