@molecule/api-ai-tools 1.5.14 → 1.5.15

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-16T03:58:22.247Z
6
+ Generated: 2026-10-16T05:16:44.196Z
7
7
  -->
8
8
 
9
9
  # @molecule/api-ai-tools
@@ -887,6 +887,22 @@ function parseCheckProblem(result: {
887
887
 
888
888
  **Returns:** The message for the tool result, or null.
889
889
 
890
+ #### `fusedLine(content, newString)`
891
+
892
+ The line a replacement fused when it dropped the newline its `old_string`
893
+ ended with: `new_string`'s text is followed on the same line by what used to
894
+ be the next line. Read off the file as it now is, so the line shown is the
895
+ line the parser is actually complaining about.
896
+
897
+ ```typescript
898
+ function fusedLine(content: string, newString: string): { line: number; text: string } | null
899
+ ```
900
+
901
+ - `content` — The file's content after the edit.
902
+ - `newString` — A replacement's `new_string`, whose `old_string` ended with a newline.
903
+
904
+ **Returns:** The fused line's 1-based number and its text, or null when the replacement's text is not followed by more of the same line (it ends the file, the following newline survived, or another replacement rewrote it).
905
+
890
906
  ### Constants
891
907
 
892
908
  #### `TOOL_SCHEMAS`
@@ -1061,6 +1077,15 @@ Peer dependencies:
1061
1077
  Line numbers count the way `read_file` does, so they are valid offsets. The
1062
1078
  excerpt is redacted like a read. The existing fields (`ok`,
1063
1079
  `replacementsApplied`, `alreadyApplied`, `syntaxError`) are unchanged.
1080
+ - **A dropped line break and the parse error it causes are reported as ONE
1081
+ thing.** When a replacement's `old_string` ended with a newline and its
1082
+ `new_string` does not (so the next line is pulled up onto it) and the file
1083
+ then fails its parse check, `edit_file` returns a single `syntaxError` that
1084
+ names the replacement, shows the fused line with its number, and says to
1085
+ re-send that replacement with the newline restored — instead of the
1086
+ `newlineNote` hint beside an unrelated-looking parse error, which an executor
1087
+ repairs by rewriting the fused line's text and fuses the next one. The
1088
+ `newlineNote` alone is still returned when the file parses.
1064
1089
  - **A parameter under another name is not a broken tool.** Every tool's input
1065
1090
  is normalized first (`cmd` → `command`, `file` → `path`, camelCase ⇄
1066
1091
  snake_case), and a handler that throws returns an error naming the missing
package/dist/index.d.ts CHANGED
@@ -38,6 +38,15 @@
38
38
  * Line numbers count the way `read_file` does, so they are valid offsets. The
39
39
  * excerpt is redacted like a read. The existing fields (`ok`,
40
40
  * `replacementsApplied`, `alreadyApplied`, `syntaxError`) are unchanged.
41
+ * - **A dropped line break and the parse error it causes are reported as ONE
42
+ * thing.** When a replacement's `old_string` ended with a newline and its
43
+ * `new_string` does not (so the next line is pulled up onto it) and the file
44
+ * then fails its parse check, `edit_file` returns a single `syntaxError` that
45
+ * names the replacement, shows the fused line with its number, and says to
46
+ * re-send that replacement with the newline restored — instead of the
47
+ * `newlineNote` hint beside an unrelated-looking parse error, which an executor
48
+ * repairs by rewriting the fused line's text and fuses the next one. The
49
+ * `newlineNote` alone is still returned when the file parses.
41
50
  * - **A parameter under another name is not a broken tool.** Every tool's input
42
51
  * is normalized first (`cmd` → `command`, `file` → `path`, camelCase ⇄
43
52
  * snake_case), and a handler that throws returns an error naming the missing
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyGG;AAEH,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkHG;AAEH,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA"}
package/dist/index.js CHANGED
@@ -38,6 +38,15 @@
38
38
  * Line numbers count the way `read_file` does, so they are valid offsets. The
39
39
  * excerpt is redacted like a read. The existing fields (`ok`,
40
40
  * `replacementsApplied`, `alreadyApplied`, `syntaxError`) are unchanged.
41
+ * - **A dropped line break and the parse error it causes are reported as ONE
42
+ * thing.** When a replacement's `old_string` ended with a newline and its
43
+ * `new_string` does not (so the next line is pulled up onto it) and the file
44
+ * then fails its parse check, `edit_file` returns a single `syntaxError` that
45
+ * names the replacement, shows the fused line with its number, and says to
46
+ * re-send that replacement with the newline restored — instead of the
47
+ * `newlineNote` hint beside an unrelated-looking parse error, which an executor
48
+ * repairs by rewriting the fused line's text and fuses the next one. The
49
+ * `newlineNote` alone is still returned when the file parses.
41
50
  * - **A parameter under another name is not a broken tool.** Every tool's input
42
51
  * is normalized first (`cmd` → `command`, `file` → `path`, camelCase ⇄
43
52
  * snake_case), and a handler that throws returns an error naming the missing
@@ -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;AAyCnE;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CA6lDxF"}
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;AA0CnE;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CAooDxF"}
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, SMALL_FILE_WHOLE_CHARS, streamingBackgroundCommand, stripControlChars, truncateMiddle, whitespaceTolerantReplace, } from './utilities.js';
28
+ import { checkBlockedCommand, declaredBudgetSeconds, DEFAULT_SEARCH_EXCLUDED_DIRS, directoryReadHint, fusedLine, 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.
@@ -876,24 +876,61 @@ export function buildTools(backend, config) {
876
876
  // A replacement whose old text ended in a newline and whose new text does not
877
877
  // joins the line after it onto its last line. Applied as written, but said:
878
878
  // flash-m28 saw two lines merged only one round trip later.
879
- const joined = replacements.filter((r) => typeof r.old_string === 'string' &&
879
+ const dropped = replacements
880
+ .map((r, i) => ({ ...r, position: i + 1 }))
881
+ .filter((r) => typeof r.old_string === 'string' &&
880
882
  typeof r.new_string === 'string' &&
881
883
  r.old_string.endsWith('\n') &&
882
884
  r.new_string !== '' &&
883
- !r.new_string.endsWith('\n')).length;
885
+ !r.new_string.endsWith('\n'));
886
+ const syntax = await syntaxErrorAfterWrite(path);
887
+ // Those two facts are ONE accident, and reported apart the model repairs the
888
+ // wrong one: flash-m55 dropped the boundary on two consecutive edit_file calls,
889
+ // both left `handlers/conduit.ts` unparseable, and it fixed the fused LINE's
890
+ // text — which re-fused the next line in the same batch. When the parse error
891
+ // follows a dropped newline, name the fused line and the single edit that
892
+ // undoes it, instead of a style note beside an unrelated-looking parse error.
893
+ const which = dropped.length === 1
894
+ ? `replacement ${dropped[0].position}`
895
+ : `replacements ${dropped.map((r) => r.position).join(', ')}`;
896
+ const located = dropped.flatMap((r) => {
897
+ const line = fusedLine(finalContent, r.new_string);
898
+ return line ? [line] : [];
899
+ });
900
+ const fused = located.slice(0, 3);
901
+ const fusedOmitted = located.length - fused.length;
902
+ const shownFused = fused
903
+ .map((l) => ` ${l.line}: ${l.text.length > 200 ? `${l.text.slice(0, 200)}…` : l.text}`)
904
+ .join('\n');
905
+ const fusedNote = syntax.syntaxError && dropped.length > 0
906
+ ? 'The file no longer parses after this change, and ' +
907
+ `${which} removed the line break that followed the replaced text — so the next line ` +
908
+ 'now continues on the line before it, and that fused line is what the parser rejects:\n' +
909
+ (shownFused || ` (not located as the file now reads — read_file ${path})`) +
910
+ (fusedOmitted > 0
911
+ ? `\n (${fusedOmitted} more fused line(s) — read_file ${path})`
912
+ : '') +
913
+ `\nRe-send ${which} with the trailing newline restored on new_string (change nothing ` +
914
+ "else about it). Editing the FUSED LINE's text instead fuses the next line the same " +
915
+ `way (flash-m55: three edits in a row).\n${syntax.syntaxError}`
916
+ : null;
884
917
  return {
885
918
  path,
886
919
  ok: true,
887
920
  replacementsApplied: replacements.length - alreadyApplied,
888
- ...(joined > 0
889
- ? {
890
- 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.`,
891
- }
892
- : {}),
921
+ ...(fusedNote
922
+ ? { syntaxError: fusedNote }
923
+ : {
924
+ ...(dropped.length > 0
925
+ ? {
926
+ newlineNote: `${dropped.length === 1 ? 'A replacement' : `${dropped.length} 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.`,
927
+ }
928
+ : {}),
929
+ ...syntax,
930
+ }),
893
931
  ...(alreadyApplied > 0 ? { alreadyApplied, note: alreadyNote } : {}),
894
932
  totalLines: countLines(finalContent),
895
933
  ...(excerpt ? { excerpt: sanitizeFileContent(excerpt, path) } : {}),
896
- ...(await syntaxErrorAfterWrite(path)),
897
934
  };
898
935
  }
899
936
  catch (e) {
@@ -339,4 +339,20 @@ export declare function parseCheckProblem(result: {
339
339
  stderr: string;
340
340
  exitCode: number;
341
341
  }): string | null;
342
+ /**
343
+ * The line a replacement fused when it dropped the newline its `old_string`
344
+ * ended with: `new_string`'s text is followed on the same line by what used to
345
+ * be the next line. Read off the file as it now is, so the line shown is the
346
+ * line the parser is actually complaining about.
347
+ *
348
+ * @param content - The file's content after the edit.
349
+ * @param newString - A replacement's `new_string`, whose `old_string` ended with a newline.
350
+ * @returns The fused line's 1-based number and its text, or null when the
351
+ * replacement's text is not followed by more of the same line (it ends the
352
+ * file, the following newline survived, or another replacement rewrote it).
353
+ */
354
+ export declare function fusedLine(content: string, newString: string): {
355
+ line: number;
356
+ text: string;
357
+ } | null;
342
358
  //# 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
@@ -103,6 +103,10 @@ const jsonValueReplacer = (quote) => (match, prefix) => {
103
103
  const inner = value.slice(1, -1);
104
104
  // An empty value hides nothing, and a suite fixture's `"password": ""` is the fact a
105
105
  // failing assertion turns on (X1 flash-m52).
106
+ // A version (`"jsonwebtoken": "9.0.2"`, a package name that holds a keyword) is not a
107
+ // credential, and a masked one hid which version is installed (X1 flash-m57).
108
+ if (/^[\^~]?\d+\.\d+\.\d+(?:[-+][\w.-]+)?$/.test(inner))
109
+ return match;
106
110
  if (inner === '' || NON_SECRET_VALUES.has(inner.toLowerCase()))
107
111
  return match;
108
112
  return `${prefix}${quote}[REDACTED]${quote}`;
@@ -720,3 +724,33 @@ export function parseCheckProblem(result) {
720
724
  return null;
721
725
  return text.slice(0, 1200);
722
726
  }
727
+ /**
728
+ * The line a replacement fused when it dropped the newline its `old_string`
729
+ * ended with: `new_string`'s text is followed on the same line by what used to
730
+ * be the next line. Read off the file as it now is, so the line shown is the
731
+ * line the parser is actually complaining about.
732
+ *
733
+ * @param content - The file's content after the edit.
734
+ * @param newString - A replacement's `new_string`, whose `old_string` ended with a newline.
735
+ * @returns The fused line's 1-based number and its text, or null when the
736
+ * replacement's text is not followed by more of the same line (it ends the
737
+ * file, the following newline survived, or another replacement rewrote it).
738
+ */
739
+ export function fusedLine(content, newString) {
740
+ for (let at = content.indexOf(newString); at >= 0; at = content.indexOf(newString, at + 1)) {
741
+ const endsAt = at + newString.length;
742
+ const next = content[endsAt];
743
+ // End of file (nothing was joined) or a newline (the boundary survived) are
744
+ // both "not fused". A later replacement in the same batch may have rewritten
745
+ // this text, so keep looking rather than giving up on the first occurrence.
746
+ if (next === undefined || next === '\n')
747
+ continue;
748
+ const lineStart = content.lastIndexOf('\n', at) + 1;
749
+ const lineEnd = content.indexOf('\n', endsAt);
750
+ return {
751
+ line: content.slice(0, lineStart).split('\n').length,
752
+ text: content.slice(lineStart, lineEnd === -1 ? undefined : lineEnd),
753
+ };
754
+ }
755
+ return null;
756
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/api-ai-tools",
3
- "version": "1.5.14",
3
+ "version": "1.5.15",
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",