oh-my-opencode 3.8.0 → 3.8.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/dist/index.js CHANGED
@@ -56624,6 +56624,7 @@ async function handleUpdate(args, config3, ctx, context) {
56624
56624
  }
56625
56625
  }
56626
56626
  // src/tools/hashline-edit/validation.ts
56627
+ var MISMATCH_CONTEXT = 2;
56627
56628
  var LINE_REF_EXTRACT_PATTERN = /([0-9]+#[ZPMQVRWSNKTXJBYH]{2}|[0-9]+:[0-9a-fA-F]{2,})/;
56628
56629
  function normalizeLineRef(ref) {
56629
56630
  const trimmed = ref.trim();
@@ -56665,7 +56666,58 @@ function validateLineRef(lines, ref) {
56665
56666
  const content = lines[line - 1];
56666
56667
  const currentHash = computeLineHash(line, content);
56667
56668
  if (currentHash !== hash2) {
56668
- throw new Error(`Hash mismatch at line ${line}. Expected hash: ${hash2}, current hash: ${currentHash}. ` + `Line content may have changed. Current content: "${content}"`);
56669
+ throw new HashlineMismatchError([{ line, expected: hash2 }], lines);
56670
+ }
56671
+ }
56672
+
56673
+ class HashlineMismatchError extends Error {
56674
+ mismatches;
56675
+ fileLines;
56676
+ remaps;
56677
+ constructor(mismatches, fileLines) {
56678
+ super(HashlineMismatchError.formatMessage(mismatches, fileLines));
56679
+ this.mismatches = mismatches;
56680
+ this.fileLines = fileLines;
56681
+ this.name = "HashlineMismatchError";
56682
+ const remaps = new Map;
56683
+ for (const mismatch of mismatches) {
56684
+ const actual = computeLineHash(mismatch.line, fileLines[mismatch.line - 1] ?? "");
56685
+ remaps.set(`${mismatch.line}#${mismatch.expected}`, `${mismatch.line}#${actual}`);
56686
+ }
56687
+ this.remaps = remaps;
56688
+ }
56689
+ static formatMessage(mismatches, fileLines) {
56690
+ const mismatchByLine = new Map;
56691
+ for (const mismatch of mismatches)
56692
+ mismatchByLine.set(mismatch.line, mismatch);
56693
+ const displayLines = new Set;
56694
+ for (const mismatch of mismatches) {
56695
+ const low = Math.max(1, mismatch.line - MISMATCH_CONTEXT);
56696
+ const high = Math.min(fileLines.length, mismatch.line + MISMATCH_CONTEXT);
56697
+ for (let line = low;line <= high; line++)
56698
+ displayLines.add(line);
56699
+ }
56700
+ const sortedLines = [...displayLines].sort((a, b) => a - b);
56701
+ const output = [];
56702
+ output.push(`${mismatches.length} line${mismatches.length > 1 ? "s have" : " has"} changed since last read. ` + "Use updated LINE#ID references below (>>> marks changed lines).");
56703
+ output.push("");
56704
+ let previousLine = -1;
56705
+ for (const line of sortedLines) {
56706
+ if (previousLine !== -1 && line > previousLine + 1) {
56707
+ output.push(" ...");
56708
+ }
56709
+ previousLine = line;
56710
+ const content = fileLines[line - 1] ?? "";
56711
+ const hash2 = computeLineHash(line, content);
56712
+ const prefix = `${line}#${hash2}:${content}`;
56713
+ if (mismatchByLine.has(line)) {
56714
+ output.push(`>>> ${prefix}`);
56715
+ } else {
56716
+ output.push(` ${prefix}`);
56717
+ }
56718
+ }
56719
+ return output.join(`
56720
+ `);
56669
56721
  }
56670
56722
  }
56671
56723
  function validateLineRefs(lines, refs) {
@@ -56673,24 +56725,30 @@ function validateLineRefs(lines, refs) {
56673
56725
  for (const ref of refs) {
56674
56726
  const { line, hash: hash2 } = parseLineRef(ref);
56675
56727
  if (line < 1 || line > lines.length) {
56676
- mismatches.push(`Line number ${line} out of bounds (file has ${lines.length} lines)`);
56677
- continue;
56728
+ throw new Error(`Line number ${line} out of bounds (file has ${lines.length} lines)`);
56678
56729
  }
56679
56730
  const content = lines[line - 1];
56680
56731
  const currentHash = computeLineHash(line, content);
56681
56732
  if (currentHash !== hash2) {
56682
- mismatches.push(`line ${line}: expected ${hash2}, current ${currentHash} (${line}#${currentHash}) content: "${content}"`);
56733
+ mismatches.push({ line, expected: hash2 });
56683
56734
  }
56684
56735
  }
56685
56736
  if (mismatches.length > 0) {
56686
- throw new Error(`Hash mismatches:
56687
- - ${mismatches.join(`
56688
- - `)}`);
56737
+ throw new HashlineMismatchError(mismatches, lines);
56689
56738
  }
56690
56739
  }
56691
- // src/tools/hashline-edit/edit-operations.ts
56740
+ // src/tools/hashline-edit/edit-text-normalization.ts
56692
56741
  var HASHLINE_PREFIX_RE = /^\s*(?:>>>|>>)?\s*\d+#[A-Z]{2}:/;
56693
56742
  var DIFF_PLUS_RE = /^[+-](?![+-])/;
56743
+ function equalsIgnoringWhitespace(a, b) {
56744
+ if (a === b)
56745
+ return true;
56746
+ return a.replace(/\s+/g, "") === b.replace(/\s+/g, "");
56747
+ }
56748
+ function leadingWhitespace(text) {
56749
+ const match = text.match(/^\s*/);
56750
+ return match ? match[0] : "";
56751
+ }
56694
56752
  function stripLinePrefixes(lines) {
56695
56753
  let hashPrefixCount = 0;
56696
56754
  let diffPlusCount = 0;
@@ -56720,14 +56778,12 @@ function stripLinePrefixes(lines) {
56720
56778
  return line;
56721
56779
  });
56722
56780
  }
56723
- function equalsIgnoringWhitespace(a, b) {
56724
- if (a === b)
56725
- return true;
56726
- return a.replace(/\s+/g, "") === b.replace(/\s+/g, "");
56727
- }
56728
- function leadingWhitespace(text) {
56729
- const match = text.match(/^\s*/);
56730
- return match ? match[0] : "";
56781
+ function toNewLines(input) {
56782
+ if (Array.isArray(input)) {
56783
+ return stripLinePrefixes(input);
56784
+ }
56785
+ return stripLinePrefixes(input.split(`
56786
+ `));
56731
56787
  }
56732
56788
  function restoreLeadingIndent(templateLine, line) {
56733
56789
  if (line.length === 0)
@@ -56740,13 +56796,31 @@ function restoreLeadingIndent(templateLine, line) {
56740
56796
  return `${templateIndent}${line}`;
56741
56797
  }
56742
56798
  function stripInsertAnchorEcho(anchorLine, newLines) {
56743
- if (newLines.length <= 1)
56799
+ if (newLines.length === 0)
56744
56800
  return newLines;
56745
56801
  if (equalsIgnoringWhitespace(newLines[0], anchorLine)) {
56746
56802
  return newLines.slice(1);
56747
56803
  }
56748
56804
  return newLines;
56749
56805
  }
56806
+ function stripInsertBeforeEcho(anchorLine, newLines) {
56807
+ if (newLines.length <= 1)
56808
+ return newLines;
56809
+ if (equalsIgnoringWhitespace(newLines[newLines.length - 1], anchorLine)) {
56810
+ return newLines.slice(0, -1);
56811
+ }
56812
+ return newLines;
56813
+ }
56814
+ function stripInsertBoundaryEcho(afterLine, beforeLine, newLines) {
56815
+ let out = newLines;
56816
+ if (out.length > 0 && equalsIgnoringWhitespace(out[0], afterLine)) {
56817
+ out = out.slice(1);
56818
+ }
56819
+ if (out.length > 0 && equalsIgnoringWhitespace(out[out.length - 1], beforeLine)) {
56820
+ out = out.slice(0, -1);
56821
+ }
56822
+ return out;
56823
+ }
56750
56824
  function stripRangeBoundaryEcho(lines, startLine, endLine, newLines) {
56751
56825
  const replacedCount = endLine - startLine + 1;
56752
56826
  if (newLines.length <= 1 || newLines.length <= replacedCount) {
@@ -56763,13 +56837,8 @@ function stripRangeBoundaryEcho(lines, startLine, endLine, newLines) {
56763
56837
  }
56764
56838
  return out;
56765
56839
  }
56766
- function toNewLines(input) {
56767
- if (Array.isArray(input)) {
56768
- return stripLinePrefixes(input);
56769
- }
56770
- return stripLinePrefixes(input.split(`
56771
- `));
56772
- }
56840
+
56841
+ // src/tools/hashline-edit/edit-operations.ts
56773
56842
  function applySetLine(lines, anchor, newText) {
56774
56843
  validateLineRef(lines, anchor);
56775
56844
  const { line } = parseLineRef(anchor);
@@ -56805,9 +56874,39 @@ function applyInsertAfter(lines, anchor, text) {
56805
56874
  const { line } = parseLineRef(anchor);
56806
56875
  const result = [...lines];
56807
56876
  const newLines = stripInsertAnchorEcho(lines[line - 1], toNewLines(text));
56877
+ if (newLines.length === 0) {
56878
+ throw new Error(`insert_after requires non-empty text for ${anchor}`);
56879
+ }
56808
56880
  result.splice(line, 0, ...newLines);
56809
56881
  return result;
56810
56882
  }
56883
+ function applyInsertBefore(lines, anchor, text) {
56884
+ validateLineRef(lines, anchor);
56885
+ const { line } = parseLineRef(anchor);
56886
+ const result = [...lines];
56887
+ const newLines = stripInsertBeforeEcho(lines[line - 1], toNewLines(text));
56888
+ if (newLines.length === 0) {
56889
+ throw new Error(`insert_before requires non-empty text for ${anchor}`);
56890
+ }
56891
+ result.splice(line - 1, 0, ...newLines);
56892
+ return result;
56893
+ }
56894
+ function applyInsertBetween(lines, afterAnchor, beforeAnchor, text) {
56895
+ validateLineRef(lines, afterAnchor);
56896
+ validateLineRef(lines, beforeAnchor);
56897
+ const { line: afterLine } = parseLineRef(afterAnchor);
56898
+ const { line: beforeLine } = parseLineRef(beforeAnchor);
56899
+ if (beforeLine <= afterLine) {
56900
+ throw new Error(`insert_between requires after_line (${afterLine}) < before_line (${beforeLine})`);
56901
+ }
56902
+ const result = [...lines];
56903
+ const newLines = stripInsertBoundaryEcho(lines[afterLine - 1], lines[beforeLine - 1], toNewLines(text));
56904
+ if (newLines.length === 0) {
56905
+ throw new Error(`insert_between requires non-empty text for ${afterAnchor}..${beforeAnchor}`);
56906
+ }
56907
+ result.splice(beforeLine - 1, 0, ...newLines);
56908
+ return result;
56909
+ }
56811
56910
  function getEditLineNumber(edit) {
56812
56911
  switch (edit.type) {
56813
56912
  case "set_line":
@@ -56816,17 +56915,61 @@ function getEditLineNumber(edit) {
56816
56915
  return parseLineRef(edit.end_line).line;
56817
56916
  case "insert_after":
56818
56917
  return parseLineRef(edit.line).line;
56918
+ case "insert_before":
56919
+ return parseLineRef(edit.line).line;
56920
+ case "insert_between":
56921
+ return parseLineRef(edit.before_line).line;
56819
56922
  case "replace":
56820
56923
  return Number.NEGATIVE_INFINITY;
56821
56924
  default:
56822
56925
  return Number.POSITIVE_INFINITY;
56823
56926
  }
56824
56927
  }
56825
- function applyHashlineEdits(content, edits) {
56928
+ function normalizeEditPayload(payload) {
56929
+ return toNewLines(payload).join(`
56930
+ `);
56931
+ }
56932
+ function dedupeEdits(edits) {
56933
+ const seen = new Set;
56934
+ const deduped = [];
56935
+ let deduplicatedEdits = 0;
56936
+ for (const edit of edits) {
56937
+ const key = (() => {
56938
+ switch (edit.type) {
56939
+ case "set_line":
56940
+ return `set_line|${edit.line}|${normalizeEditPayload(edit.text)}`;
56941
+ case "replace_lines":
56942
+ return `replace_lines|${edit.start_line}|${edit.end_line}|${normalizeEditPayload(edit.text)}`;
56943
+ case "insert_after":
56944
+ return `insert_after|${edit.line}|${normalizeEditPayload(edit.text)}`;
56945
+ case "insert_before":
56946
+ return `insert_before|${edit.line}|${normalizeEditPayload(edit.text)}`;
56947
+ case "insert_between":
56948
+ return `insert_between|${edit.after_line}|${edit.before_line}|${normalizeEditPayload(edit.text)}`;
56949
+ case "replace":
56950
+ return `replace|${edit.old_text}|${normalizeEditPayload(edit.new_text)}`;
56951
+ }
56952
+ })();
56953
+ if (seen.has(key)) {
56954
+ deduplicatedEdits += 1;
56955
+ continue;
56956
+ }
56957
+ seen.add(key);
56958
+ deduped.push(edit);
56959
+ }
56960
+ return { edits: deduped, deduplicatedEdits };
56961
+ }
56962
+ function applyHashlineEditsWithReport(content, edits) {
56826
56963
  if (edits.length === 0) {
56827
- return content;
56964
+ return {
56965
+ content,
56966
+ noopEdits: 0,
56967
+ deduplicatedEdits: 0
56968
+ };
56828
56969
  }
56829
- const sortedEdits = [...edits].sort((a, b) => getEditLineNumber(b) - getEditLineNumber(a));
56970
+ const dedupeResult = dedupeEdits(edits);
56971
+ const sortedEdits = [...dedupeResult.edits].sort((a, b) => getEditLineNumber(b) - getEditLineNumber(a));
56972
+ let noopEdits = 0;
56830
56973
  let result = content;
56831
56974
  let lines = result.split(`
56832
56975
  `);
@@ -56838,6 +56981,10 @@ function applyHashlineEdits(content, edits) {
56838
56981
  return [edit.start_line, edit.end_line];
56839
56982
  case "insert_after":
56840
56983
  return [edit.line];
56984
+ case "insert_before":
56985
+ return [edit.line];
56986
+ case "insert_between":
56987
+ return [edit.after_line, edit.before_line];
56841
56988
  case "replace":
56842
56989
  return [];
56843
56990
  default:
@@ -56856,7 +57003,36 @@ function applyHashlineEdits(content, edits) {
56856
57003
  break;
56857
57004
  }
56858
57005
  case "insert_after": {
56859
- lines = applyInsertAfter(lines, edit.line, edit.text);
57006
+ const next = applyInsertAfter(lines, edit.line, edit.text);
57007
+ if (next.join(`
57008
+ `) === lines.join(`
57009
+ `)) {
57010
+ noopEdits += 1;
57011
+ break;
57012
+ }
57013
+ lines = next;
57014
+ break;
57015
+ }
57016
+ case "insert_before": {
57017
+ const next = applyInsertBefore(lines, edit.line, edit.text);
57018
+ if (next.join(`
57019
+ `) === lines.join(`
57020
+ `)) {
57021
+ noopEdits += 1;
57022
+ break;
57023
+ }
57024
+ lines = next;
57025
+ break;
57026
+ }
57027
+ case "insert_between": {
57028
+ const next = applyInsertBetween(lines, edit.after_line, edit.before_line, edit.text);
57029
+ if (next.join(`
57030
+ `) === lines.join(`
57031
+ `)) {
57032
+ noopEdits += 1;
57033
+ break;
57034
+ }
57035
+ lines = next;
56860
57036
  break;
56861
57037
  }
56862
57038
  case "replace": {
@@ -56867,16 +57043,61 @@ function applyHashlineEdits(content, edits) {
56867
57043
  }
56868
57044
  const replacement = Array.isArray(edit.new_text) ? edit.new_text.join(`
56869
57045
  `) : edit.new_text;
56870
- result = result.replaceAll(edit.old_text, replacement);
57046
+ const replaced = result.replaceAll(edit.old_text, replacement);
57047
+ if (replaced === result) {
57048
+ noopEdits += 1;
57049
+ break;
57050
+ }
57051
+ result = replaced;
56871
57052
  lines = result.split(`
56872
57053
  `);
56873
57054
  break;
56874
57055
  }
56875
57056
  }
56876
57057
  }
56877
- return lines.join(`
56878
- `);
57058
+ return {
57059
+ content: lines.join(`
57060
+ `),
57061
+ noopEdits,
57062
+ deduplicatedEdits: dedupeResult.deduplicatedEdits
57063
+ };
56879
57064
  }
57065
+ // src/tools/hashline-edit/tool-description.ts
57066
+ var HASHLINE_EDIT_DESCRIPTION = `Edit files using LINE#ID format for precise, safe modifications.
57067
+
57068
+ WORKFLOW:
57069
+ 1. Read the file and copy exact LINE#ID anchors.
57070
+ 2. Submit one edit call with all related operations for that file.
57071
+ 3. If more edits are needed after success, use the latest anchors from read/edit output.
57072
+ 4. Use anchors as "LINE#ID" only (never include trailing ":content").
57073
+
57074
+ VALIDATION:
57075
+ - Payload shape: { "filePath": string, "edits": [...], "delete"?: boolean, "rename"?: string }
57076
+ - Each edit must be one of: set_line, replace_lines, insert_after, insert_before, insert_between, replace
57077
+ - text/new_text must contain plain replacement text only (no LINE#ID prefixes, no diff + markers)
57078
+
57079
+ LINE#ID FORMAT (CRITICAL - READ CAREFULLY):
57080
+ Each line reference must be in "LINE#ID" format where:
57081
+ - LINE: 1-based line number
57082
+ - ID: Two CID letters from the set ZPMQVRWSNKTXJBYH
57083
+
57084
+ OPERATION TYPES:
57085
+ 1. set_line
57086
+ 2. replace_lines
57087
+ 3. insert_after
57088
+ 4. insert_before
57089
+ 5. insert_between
57090
+ 6. replace
57091
+
57092
+ FILE MODES:
57093
+ - delete=true deletes file and requires edits=[] with no rename
57094
+ - rename moves final content to a new path and removes old path
57095
+
57096
+ CONTENT FORMAT:
57097
+ - text/new_text can be a string (single line) or string[] (multi-line, preferred).
57098
+ - If you pass a multi-line string, it is split by real newline characters.
57099
+ - Literal "\\n" is preserved as text.`;
57100
+
56880
57101
  // src/tools/hashline-edit/tools.ts
56881
57102
  function resolveToolCallID2(ctx) {
56882
57103
  if (typeof ctx.callID === "string" && ctx.callID.trim() !== "")
@@ -56918,62 +57139,11 @@ function generateDiff(oldContent, newContent, filePath) {
56918
57139
  }
56919
57140
  function createHashlineEditTool() {
56920
57141
  return tool({
56921
- description: `Edit files using LINE#ID format for precise, safe modifications.
56922
-
56923
- WORKFLOW:
56924
- 1. Read the file and copy exact LINE#ID anchors.
56925
- 2. Submit one edit call with all related operations for that file.
56926
- 3. If more edits are needed after success, use the latest anchors from read/edit output.
56927
- 4. Use anchors as "LINE#ID" only (never include trailing ":content").
56928
-
56929
- VALIDATION:
56930
- - Payload shape: { "filePath": string, "edits": [...] }
56931
- - Each edit must be one of: set_line, replace_lines, insert_after, replace
56932
- - text/new_text must contain plain replacement text only (no LINE#ID prefixes, no diff + markers)
56933
-
56934
- LINE#ID FORMAT (CRITICAL - READ CAREFULLY):
56935
- Each line reference must be in "LINE#ID" format where:
56936
- - LINE: 1-based line number
56937
- - ID: Two CID letters from the set ZPMQVRWSNKTXJBYH
56938
- - Example: "5#VK" means line 5 with hash id "VK"
56939
- - WRONG: "2#aa" (invalid characters) - will fail!
56940
- - CORRECT: "2#VK"
56941
-
56942
- GETTING HASHES:
56943
- Use the read tool - it returns lines in "LINE#ID:content" format.
56944
- Successful edit output also includes updated file content in "LINE#ID:content" format.
56945
-
56946
- FOUR OPERATION TYPES:
56947
-
56948
- 1. set_line: Replace a single line
56949
- { "type": "set_line", "line": "5#VK", "text": "const y = 2" }
56950
-
56951
- 2. replace_lines: Replace a range of lines
56952
- { "type": "replace_lines", "start_line": "5#VK", "end_line": "7#NP", "text": ["new", "content"] }
56953
-
56954
- 3. insert_after: Insert lines after a specific line
56955
- { "type": "insert_after", "line": "5#VK", "text": "console.log('hi')" }
56956
-
56957
- 4. replace: Simple text replacement (no hash validation)
56958
- { "type": "replace", "old_text": "foo", "new_text": "bar" }
56959
-
56960
- HASH MISMATCH HANDLING:
56961
- If the hash doesn't match the current content, the edit fails with a hash mismatch error. This prevents editing stale content.
56962
-
56963
- SEQUENTIAL EDITS (ANTI-FLAKE):
56964
- - Always copy anchors exactly from the latest read/edit output.
56965
- - Never infer or guess hashes.
56966
- - For related edits, prefer batching them in one call.
56967
-
56968
- BOTTOM-UP APPLICATION:
56969
- Edits are applied from bottom to top (highest line numbers first) to preserve line number references.
56970
-
56971
- CONTENT FORMAT:
56972
- - text/new_text can be a string (single line) or string[] (multi-line, preferred).
56973
- - If you pass a multi-line string, it is split by real newline characters.
56974
- - Literal "\\n" is preserved as text.`,
57142
+ description: HASHLINE_EDIT_DESCRIPTION,
56975
57143
  args: {
56976
57144
  filePath: tool.schema.string().describe("Absolute path to the file to edit"),
57145
+ delete: tool.schema.boolean().optional().describe("Delete file instead of editing"),
57146
+ rename: tool.schema.string().optional().describe("Rename output file path after edits"),
56977
57147
  edits: tool.schema.array(tool.schema.union([
56978
57148
  tool.schema.object({
56979
57149
  type: tool.schema.literal("set_line"),
@@ -56991,44 +57161,73 @@ CONTENT FORMAT:
56991
57161
  line: tool.schema.string().describe("Line reference in LINE#ID format"),
56992
57162
  text: tool.schema.union([tool.schema.string(), tool.schema.array(tool.schema.string())]).describe("Content to insert after the line (string or string[] for multiline)")
56993
57163
  }),
57164
+ tool.schema.object({
57165
+ type: tool.schema.literal("insert_before"),
57166
+ line: tool.schema.string().describe("Line reference in LINE#ID format"),
57167
+ text: tool.schema.union([tool.schema.string(), tool.schema.array(tool.schema.string())]).describe("Content to insert before the line (string or string[] for multiline)")
57168
+ }),
57169
+ tool.schema.object({
57170
+ type: tool.schema.literal("insert_between"),
57171
+ after_line: tool.schema.string().describe("After line in LINE#ID format"),
57172
+ before_line: tool.schema.string().describe("Before line in LINE#ID format"),
57173
+ text: tool.schema.union([tool.schema.string(), tool.schema.array(tool.schema.string())]).describe("Content to insert between anchor lines (string or string[] for multiline)")
57174
+ }),
56994
57175
  tool.schema.object({
56995
57176
  type: tool.schema.literal("replace"),
56996
57177
  old_text: tool.schema.string().describe("Text to find"),
56997
57178
  new_text: tool.schema.union([tool.schema.string(), tool.schema.array(tool.schema.string())]).describe("Replacement text (string or string[] for multiline)")
56998
57179
  })
56999
- ])).describe("Array of edit operations to apply")
57180
+ ])).describe("Array of edit operations to apply (empty when delete=true)")
57000
57181
  },
57001
57182
  execute: async (args, context) => {
57002
57183
  try {
57003
57184
  const metadataContext = context;
57004
57185
  const filePath = args.filePath;
57005
- const { edits } = args;
57006
- if (!edits || !Array.isArray(edits) || edits.length === 0) {
57186
+ const { edits, delete: deleteMode, rename } = args;
57187
+ if (deleteMode && rename) {
57188
+ return "Error: delete and rename cannot be used together";
57189
+ }
57190
+ if (!deleteMode && (!edits || !Array.isArray(edits) || edits.length === 0)) {
57007
57191
  return "Error: edits parameter must be a non-empty array";
57008
57192
  }
57193
+ if (deleteMode && edits.length > 0) {
57194
+ return "Error: delete mode requires edits to be an empty array";
57195
+ }
57009
57196
  const file2 = Bun.file(filePath);
57010
57197
  const exists = await file2.exists();
57011
57198
  if (!exists) {
57012
57199
  return `Error: File not found: ${filePath}`;
57013
57200
  }
57201
+ if (deleteMode) {
57202
+ await Bun.file(filePath).delete();
57203
+ return `Successfully deleted ${filePath}`;
57204
+ }
57014
57205
  const oldContent = await file2.text();
57015
- const newContent = applyHashlineEdits(oldContent, edits);
57206
+ const applyResult = applyHashlineEditsWithReport(oldContent, edits);
57207
+ const newContent = applyResult.content;
57016
57208
  await Bun.write(filePath, newContent);
57017
- const diff = generateDiff(oldContent, newContent, filePath);
57209
+ if (rename && rename !== filePath) {
57210
+ await Bun.write(rename, newContent);
57211
+ await Bun.file(filePath).delete();
57212
+ }
57213
+ const effectivePath = rename && rename !== filePath ? rename : filePath;
57214
+ const diff = generateDiff(oldContent, newContent, effectivePath);
57018
57215
  const newHashlined = toHashlineContent(newContent);
57019
- const unifiedDiff = generateUnifiedDiff(oldContent, newContent, filePath);
57216
+ const unifiedDiff = generateUnifiedDiff(oldContent, newContent, effectivePath);
57020
57217
  const { additions, deletions } = countLineDiffs(oldContent, newContent);
57021
57218
  const meta = {
57022
- title: filePath,
57219
+ title: effectivePath,
57023
57220
  metadata: {
57024
- filePath,
57025
- path: filePath,
57026
- file: filePath,
57221
+ filePath: effectivePath,
57222
+ path: effectivePath,
57223
+ file: effectivePath,
57027
57224
  diff: unifiedDiff,
57225
+ noopEdits: applyResult.noopEdits,
57226
+ deduplicatedEdits: applyResult.deduplicatedEdits,
57028
57227
  filediff: {
57029
- file: filePath,
57030
- path: filePath,
57031
- filePath,
57228
+ file: effectivePath,
57229
+ path: effectivePath,
57230
+ filePath: effectivePath,
57032
57231
  before: oldContent,
57033
57232
  after: newContent,
57034
57233
  additions,
@@ -57043,7 +57242,8 @@ CONTENT FORMAT:
57043
57242
  if (callID) {
57044
57243
  storeToolMetadata(context.sessionID, callID, meta);
57045
57244
  }
57046
- return `Successfully applied ${edits.length} edit(s) to ${filePath}
57245
+ return `Successfully applied ${edits.length} edit(s) to ${effectivePath}
57246
+ No-op edits: ${applyResult.noopEdits}, deduplicated edits: ${applyResult.deduplicatedEdits}
57047
57247
 
57048
57248
  ${diff}
57049
57249
 
@@ -57690,56 +57890,430 @@ class ConcurrencyManager {
57690
57890
  return;
57691
57891
  }
57692
57892
  }
57693
- const current = this.counts.get(model) ?? 0;
57694
- if (current > 0) {
57695
- this.counts.set(model, current - 1);
57696
- }
57697
- }
57698
- cancelWaiters(model) {
57699
- const queue = this.queues.get(model);
57700
- if (queue) {
57701
- for (const entry of queue) {
57702
- if (!entry.settled) {
57703
- entry.settled = true;
57704
- entry.rawReject(new Error(`Concurrency queue cancelled for model: ${model}`));
57705
- }
57706
- }
57707
- this.queues.delete(model);
57893
+ const current = this.counts.get(model) ?? 0;
57894
+ if (current > 0) {
57895
+ this.counts.set(model, current - 1);
57896
+ }
57897
+ }
57898
+ cancelWaiters(model) {
57899
+ const queue = this.queues.get(model);
57900
+ if (queue) {
57901
+ for (const entry of queue) {
57902
+ if (!entry.settled) {
57903
+ entry.settled = true;
57904
+ entry.rawReject(new Error(`Concurrency queue cancelled for model: ${model}`));
57905
+ }
57906
+ }
57907
+ this.queues.delete(model);
57908
+ }
57909
+ }
57910
+ clear() {
57911
+ for (const [model] of this.queues) {
57912
+ this.cancelWaiters(model);
57913
+ }
57914
+ this.counts.clear();
57915
+ this.queues.clear();
57916
+ }
57917
+ getCount(model) {
57918
+ return this.counts.get(model) ?? 0;
57919
+ }
57920
+ getQueueLength(model) {
57921
+ return this.queues.get(model)?.length ?? 0;
57922
+ }
57923
+ }
57924
+
57925
+ // src/features/background-agent/constants.ts
57926
+ var TASK_TTL_MS = 30 * 60 * 1000;
57927
+ var MIN_STABILITY_TIME_MS2 = 10 * 1000;
57928
+ var DEFAULT_STALE_TIMEOUT_MS = 180000;
57929
+ var DEFAULT_MESSAGE_STALENESS_TIMEOUT_MS = 600000;
57930
+ var MIN_RUNTIME_BEFORE_STALE_MS = 30000;
57931
+ var MIN_IDLE_TIME_MS = 5000;
57932
+ var POLLING_INTERVAL_MS = 3000;
57933
+ var TASK_CLEANUP_DELAY_MS = 10 * 60 * 1000;
57934
+
57935
+ // src/features/background-agent/duration-formatter.ts
57936
+ function formatDuration3(start, end) {
57937
+ const duration3 = (end ?? new Date).getTime() - start.getTime();
57938
+ const seconds = Math.floor(duration3 / 1000);
57939
+ const minutes = Math.floor(seconds / 60);
57940
+ const hours = Math.floor(minutes / 60);
57941
+ if (hours > 0) {
57942
+ return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
57943
+ }
57944
+ if (minutes > 0) {
57945
+ return `${minutes}m ${seconds % 60}s`;
57946
+ }
57947
+ return `${seconds}s`;
57948
+ }
57949
+
57950
+ // src/features/background-agent/error-classifier.ts
57951
+ function isRecord4(value) {
57952
+ return typeof value === "object" && value !== null;
57953
+ }
57954
+ function isAbortedSessionError(error45) {
57955
+ const message = getErrorText(error45);
57956
+ return message.toLowerCase().includes("aborted");
57957
+ }
57958
+ function getErrorText(error45) {
57959
+ if (!error45)
57960
+ return "";
57961
+ if (typeof error45 === "string")
57962
+ return error45;
57963
+ if (error45 instanceof Error) {
57964
+ return `${error45.name}: ${error45.message}`;
57965
+ }
57966
+ if (typeof error45 === "object" && error45 !== null) {
57967
+ if ("message" in error45 && typeof error45.message === "string") {
57968
+ return error45.message;
57969
+ }
57970
+ if ("name" in error45 && typeof error45.name === "string") {
57971
+ return error45.name;
57972
+ }
57973
+ }
57974
+ return "";
57975
+ }
57976
+ function extractErrorName2(error45) {
57977
+ if (isRecord4(error45) && typeof error45["name"] === "string")
57978
+ return error45["name"];
57979
+ if (error45 instanceof Error)
57980
+ return error45.name;
57981
+ return;
57982
+ }
57983
+ function extractErrorMessage(error45) {
57984
+ if (!error45)
57985
+ return;
57986
+ if (typeof error45 === "string")
57987
+ return error45;
57988
+ if (error45 instanceof Error)
57989
+ return error45.message;
57990
+ if (isRecord4(error45)) {
57991
+ const dataRaw = error45["data"];
57992
+ const candidates = [
57993
+ error45,
57994
+ dataRaw,
57995
+ error45["error"],
57996
+ isRecord4(dataRaw) ? dataRaw["error"] : undefined,
57997
+ error45["cause"]
57998
+ ];
57999
+ for (const candidate of candidates) {
58000
+ if (typeof candidate === "string" && candidate.length > 0)
58001
+ return candidate;
58002
+ if (isRecord4(candidate) && typeof candidate["message"] === "string" && candidate["message"].length > 0) {
58003
+ return candidate["message"];
58004
+ }
58005
+ }
58006
+ }
58007
+ try {
58008
+ return JSON.stringify(error45);
58009
+ } catch {
58010
+ return String(error45);
58011
+ }
58012
+ }
58013
+ function getSessionErrorMessage(properties) {
58014
+ const errorRaw = properties["error"];
58015
+ if (!isRecord4(errorRaw))
58016
+ return;
58017
+ const dataRaw = errorRaw["data"];
58018
+ if (isRecord4(dataRaw)) {
58019
+ const message2 = dataRaw["message"];
58020
+ if (typeof message2 === "string")
58021
+ return message2;
58022
+ }
58023
+ const message = errorRaw["message"];
58024
+ return typeof message === "string" ? message : undefined;
58025
+ }
58026
+
58027
+ // src/features/background-agent/fallback-retry-handler.ts
58028
+ function tryFallbackRetry(args) {
58029
+ const { task, errorInfo, source, concurrencyManager, client: client2, idleDeferralTimers, queuesByKey, processKey } = args;
58030
+ const fallbackChain = task.fallbackChain;
58031
+ const canRetry = shouldRetryError(errorInfo) && fallbackChain && fallbackChain.length > 0 && hasMoreFallbacks(fallbackChain, task.attemptCount ?? 0);
58032
+ if (!canRetry)
58033
+ return false;
58034
+ const attemptCount = task.attemptCount ?? 0;
58035
+ const providerModelsCache = readProviderModelsCache();
58036
+ const connectedProviders = providerModelsCache?.connected ?? readConnectedProvidersCache();
58037
+ const connectedSet = connectedProviders ? new Set(connectedProviders.map((p) => p.toLowerCase())) : null;
58038
+ const isReachable = (entry) => {
58039
+ if (!connectedSet)
58040
+ return true;
58041
+ return entry.providers.some((p) => connectedSet.has(p.toLowerCase()));
58042
+ };
58043
+ let selectedAttemptCount = attemptCount;
58044
+ let nextFallback;
58045
+ while (fallbackChain && selectedAttemptCount < fallbackChain.length) {
58046
+ const candidate = getNextFallback(fallbackChain, selectedAttemptCount);
58047
+ if (!candidate)
58048
+ break;
58049
+ selectedAttemptCount++;
58050
+ if (!isReachable(candidate)) {
58051
+ log("[background-agent] Skipping unreachable fallback:", {
58052
+ taskId: task.id,
58053
+ source,
58054
+ model: candidate.model,
58055
+ providers: candidate.providers
58056
+ });
58057
+ continue;
58058
+ }
58059
+ nextFallback = candidate;
58060
+ break;
58061
+ }
58062
+ if (!nextFallback)
58063
+ return false;
58064
+ const providerID = selectFallbackProvider(nextFallback.providers, task.model?.providerID);
58065
+ log("[background-agent] Retryable error, attempting fallback:", {
58066
+ taskId: task.id,
58067
+ source,
58068
+ errorName: errorInfo.name,
58069
+ errorMessage: errorInfo.message?.slice(0, 100),
58070
+ attemptCount: selectedAttemptCount,
58071
+ nextModel: `${providerID}/${nextFallback.model}`
58072
+ });
58073
+ if (task.concurrencyKey) {
58074
+ concurrencyManager.release(task.concurrencyKey);
58075
+ task.concurrencyKey = undefined;
58076
+ }
58077
+ if (task.sessionID) {
58078
+ client2.session.abort({ path: { id: task.sessionID } }).catch(() => {});
58079
+ }
58080
+ const idleTimer = idleDeferralTimers.get(task.id);
58081
+ if (idleTimer) {
58082
+ clearTimeout(idleTimer);
58083
+ idleDeferralTimers.delete(task.id);
58084
+ }
58085
+ task.attemptCount = selectedAttemptCount;
58086
+ const transformedModelId = transformModelForProvider(providerID, nextFallback.model);
58087
+ task.model = {
58088
+ providerID,
58089
+ modelID: transformedModelId,
58090
+ variant: nextFallback.variant
58091
+ };
58092
+ task.status = "pending";
58093
+ task.sessionID = undefined;
58094
+ task.startedAt = undefined;
58095
+ task.queuedAt = new Date;
58096
+ task.error = undefined;
58097
+ const key = task.model ? `${task.model.providerID}/${task.model.modelID}` : task.agent;
58098
+ const queue = queuesByKey.get(key) ?? [];
58099
+ const retryInput = {
58100
+ description: task.description,
58101
+ prompt: task.prompt,
58102
+ agent: task.agent,
58103
+ parentSessionID: task.parentSessionID,
58104
+ parentMessageID: task.parentMessageID,
58105
+ parentModel: task.parentModel,
58106
+ parentAgent: task.parentAgent,
58107
+ parentTools: task.parentTools,
58108
+ model: task.model,
58109
+ fallbackChain: task.fallbackChain,
58110
+ category: task.category,
58111
+ isUnstableAgent: task.isUnstableAgent
58112
+ };
58113
+ queue.push({ task, input: retryInput });
58114
+ queuesByKey.set(key, queue);
58115
+ processKey(key);
58116
+ return true;
58117
+ }
58118
+
58119
+ // src/features/background-agent/process-cleanup.ts
58120
+ function registerProcessSignal(signal, handler, exitAfter) {
58121
+ const listener = () => {
58122
+ handler();
58123
+ if (exitAfter) {
58124
+ process.exitCode = 0;
58125
+ setTimeout(() => process.exit(), 6000).unref();
58126
+ }
58127
+ };
58128
+ process.on(signal, listener);
58129
+ return listener;
58130
+ }
58131
+ var cleanupManagers = new Set;
58132
+ var cleanupRegistered = false;
58133
+ var cleanupHandlers = new Map;
58134
+ function registerManagerForCleanup(manager) {
58135
+ cleanupManagers.add(manager);
58136
+ if (cleanupRegistered)
58137
+ return;
58138
+ cleanupRegistered = true;
58139
+ const cleanupAll = () => {
58140
+ for (const m of cleanupManagers) {
58141
+ try {
58142
+ m.shutdown();
58143
+ } catch (error45) {
58144
+ log("[background-agent] Error during shutdown cleanup:", error45);
58145
+ }
58146
+ }
58147
+ };
58148
+ const registerSignal = (signal, exitAfter) => {
58149
+ const listener = registerProcessSignal(signal, cleanupAll, exitAfter);
58150
+ cleanupHandlers.set(signal, listener);
58151
+ };
58152
+ registerSignal("SIGINT", true);
58153
+ registerSignal("SIGTERM", true);
58154
+ if (process.platform === "win32") {
58155
+ registerSignal("SIGBREAK", true);
58156
+ }
58157
+ registerSignal("beforeExit", false);
58158
+ registerSignal("exit", false);
58159
+ }
58160
+ function unregisterManagerForCleanup(manager) {
58161
+ cleanupManagers.delete(manager);
58162
+ if (cleanupManagers.size > 0)
58163
+ return;
58164
+ for (const [signal, listener] of cleanupHandlers.entries()) {
58165
+ process.off(signal, listener);
58166
+ }
58167
+ cleanupHandlers.clear();
58168
+ cleanupRegistered = false;
58169
+ }
58170
+
58171
+ // src/features/background-agent/compaction-aware-message-resolver.ts
58172
+ import { readdirSync as readdirSync18, readFileSync as readFileSync42 } from "fs";
58173
+ import { join as join74 } from "path";
58174
+ function isCompactionAgent(agent) {
58175
+ return agent?.trim().toLowerCase() === "compaction";
58176
+ }
58177
+ function hasFullAgentAndModel(message) {
58178
+ return !!message.agent && !isCompactionAgent(message.agent) && !!message.model?.providerID && !!message.model?.modelID;
58179
+ }
58180
+ function hasPartialAgentOrModel(message) {
58181
+ const hasAgent = !!message.agent && !isCompactionAgent(message.agent);
58182
+ const hasModel = !!message.model?.providerID && !!message.model?.modelID;
58183
+ return hasAgent || hasModel;
58184
+ }
58185
+ function findNearestMessageExcludingCompaction(messageDir) {
58186
+ try {
58187
+ const files = readdirSync18(messageDir).filter((name) => name.endsWith(".json")).sort().reverse();
58188
+ for (const file2 of files) {
58189
+ try {
58190
+ const content = readFileSync42(join74(messageDir, file2), "utf-8");
58191
+ const parsed = JSON.parse(content);
58192
+ if (hasFullAgentAndModel(parsed)) {
58193
+ return parsed;
58194
+ }
58195
+ } catch {
58196
+ continue;
58197
+ }
58198
+ }
58199
+ for (const file2 of files) {
58200
+ try {
58201
+ const content = readFileSync42(join74(messageDir, file2), "utf-8");
58202
+ const parsed = JSON.parse(content);
58203
+ if (hasPartialAgentOrModel(parsed)) {
58204
+ return parsed;
58205
+ }
58206
+ } catch {
58207
+ continue;
58208
+ }
58209
+ }
58210
+ } catch {
58211
+ return null;
58212
+ }
58213
+ return null;
58214
+ }
58215
+
58216
+ // src/features/background-agent/manager.ts
58217
+ import { join as join75 } from "path";
58218
+
58219
+ // src/features/background-agent/task-poller.ts
58220
+ function pruneStaleTasksAndNotifications(args) {
58221
+ const { tasks, notifications, onTaskPruned } = args;
58222
+ const now = Date.now();
58223
+ for (const [taskId, task] of tasks.entries()) {
58224
+ const timestamp2 = task.status === "pending" ? task.queuedAt?.getTime() : task.startedAt?.getTime();
58225
+ if (!timestamp2)
58226
+ continue;
58227
+ const age = now - timestamp2;
58228
+ if (age <= TASK_TTL_MS)
58229
+ continue;
58230
+ const errorMessage = task.status === "pending" ? "Task timed out while queued (30 minutes)" : "Task timed out after 30 minutes";
58231
+ onTaskPruned(taskId, task, errorMessage);
58232
+ }
58233
+ for (const [sessionID, queued] of notifications.entries()) {
58234
+ if (queued.length === 0) {
58235
+ notifications.delete(sessionID);
58236
+ continue;
58237
+ }
58238
+ const validNotifications = queued.filter((task) => {
58239
+ if (!task.startedAt)
58240
+ return false;
58241
+ const age = now - task.startedAt.getTime();
58242
+ return age <= TASK_TTL_MS;
58243
+ });
58244
+ if (validNotifications.length === 0) {
58245
+ notifications.delete(sessionID);
58246
+ } else if (validNotifications.length !== queued.length) {
58247
+ notifications.set(sessionID, validNotifications);
58248
+ }
58249
+ }
58250
+ }
58251
+ async function checkAndInterruptStaleTasks(args) {
58252
+ const { tasks, client: client2, config: config3, concurrencyManager, notifyParentSession, sessionStatuses } = args;
58253
+ const staleTimeoutMs = config3?.staleTimeoutMs ?? DEFAULT_STALE_TIMEOUT_MS;
58254
+ const now = Date.now();
58255
+ const messageStalenessMs = config3?.messageStalenessTimeoutMs ?? DEFAULT_MESSAGE_STALENESS_TIMEOUT_MS;
58256
+ for (const task of tasks) {
58257
+ if (task.status !== "running")
58258
+ continue;
58259
+ const startedAt = task.startedAt;
58260
+ const sessionID = task.sessionID;
58261
+ if (!startedAt || !sessionID)
58262
+ continue;
58263
+ const sessionStatus = sessionStatuses?.[sessionID]?.type;
58264
+ const sessionIsRunning = sessionStatus !== undefined && sessionStatus !== "idle";
58265
+ const runtime = now - startedAt.getTime();
58266
+ if (!task.progress?.lastUpdate) {
58267
+ if (sessionIsRunning)
58268
+ continue;
58269
+ if (runtime <= messageStalenessMs)
58270
+ continue;
58271
+ const staleMinutes2 = Math.round(runtime / 60000);
58272
+ task.status = "cancelled";
58273
+ task.error = `Stale timeout (no activity for ${staleMinutes2}min since start)`;
58274
+ task.completedAt = new Date;
58275
+ if (task.concurrencyKey) {
58276
+ concurrencyManager.release(task.concurrencyKey);
58277
+ task.concurrencyKey = undefined;
58278
+ }
58279
+ client2.session.abort({ path: { id: sessionID } }).catch(() => {});
58280
+ log(`[background-agent] Task ${task.id} interrupted: no progress since start`);
58281
+ try {
58282
+ await notifyParentSession(task);
58283
+ } catch (err) {
58284
+ log("[background-agent] Error in notifyParentSession for stale task:", { taskId: task.id, error: err });
58285
+ }
58286
+ continue;
58287
+ }
58288
+ if (sessionIsRunning)
58289
+ continue;
58290
+ if (runtime < MIN_RUNTIME_BEFORE_STALE_MS)
58291
+ continue;
58292
+ const timeSinceLastUpdate = now - task.progress.lastUpdate.getTime();
58293
+ if (timeSinceLastUpdate <= staleTimeoutMs)
58294
+ continue;
58295
+ if (task.status !== "running")
58296
+ continue;
58297
+ const staleMinutes = Math.round(timeSinceLastUpdate / 60000);
58298
+ task.status = "cancelled";
58299
+ task.error = `Stale timeout (no activity for ${staleMinutes}min)`;
58300
+ task.completedAt = new Date;
58301
+ if (task.concurrencyKey) {
58302
+ concurrencyManager.release(task.concurrencyKey);
58303
+ task.concurrencyKey = undefined;
57708
58304
  }
57709
- }
57710
- clear() {
57711
- for (const [model] of this.queues) {
57712
- this.cancelWaiters(model);
58305
+ client2.session.abort({ path: { id: sessionID } }).catch(() => {});
58306
+ log(`[background-agent] Task ${task.id} interrupted: stale timeout`);
58307
+ try {
58308
+ await notifyParentSession(task);
58309
+ } catch (err) {
58310
+ log("[background-agent] Error in notifyParentSession for stale task:", { taskId: task.id, error: err });
57713
58311
  }
57714
- this.counts.clear();
57715
- this.queues.clear();
57716
- }
57717
- getCount(model) {
57718
- return this.counts.get(model) ?? 0;
57719
- }
57720
- getQueueLength(model) {
57721
- return this.queues.get(model)?.length ?? 0;
57722
58312
  }
57723
58313
  }
57724
58314
 
57725
- // src/features/background-agent/constants.ts
57726
- var TASK_TTL_MS = 30 * 60 * 1000;
57727
- var MIN_STABILITY_TIME_MS2 = 10 * 1000;
57728
- var DEFAULT_STALE_TIMEOUT_MS = 180000;
57729
- var DEFAULT_MESSAGE_STALENESS_TIMEOUT_MS = 600000;
57730
- var MIN_RUNTIME_BEFORE_STALE_MS = 30000;
57731
- var MIN_IDLE_TIME_MS = 5000;
57732
- var POLLING_INTERVAL_MS = 3000;
57733
- var TASK_CLEANUP_DELAY_MS = 10 * 60 * 1000;
57734
-
57735
58315
  // src/features/background-agent/manager.ts
57736
- import { existsSync as existsSync63, readFileSync as readFileSync42, readdirSync as readdirSync18 } from "fs";
57737
- import { join as join74 } from "path";
57738
-
57739
58316
  class BackgroundManager {
57740
- static cleanupManagers = new Set;
57741
- static cleanupRegistered = false;
57742
- static cleanupHandlers = new Map;
57743
58317
  tasks;
57744
58318
  notifications;
57745
58319
  pendingByParent;
@@ -58219,8 +58793,8 @@ class BackgroundManager {
58219
58793
  if (!assistantError)
58220
58794
  return;
58221
58795
  const errorInfo = {
58222
- name: this.extractErrorName(assistantError),
58223
- message: this.extractErrorMessage(assistantError)
58796
+ name: extractErrorName2(assistantError),
58797
+ message: extractErrorMessage(assistantError)
58224
58798
  };
58225
58799
  this.tryFallbackRetry(task, errorInfo, "message.updated");
58226
58800
  }
@@ -58308,7 +58882,7 @@ class BackgroundManager {
58308
58882
  return;
58309
58883
  const errorObj = props?.error;
58310
58884
  const errorName = errorObj?.name;
58311
- const errorMessage = props ? this.getSessionErrorMessage(props) : undefined;
58885
+ const errorMessage = props ? getSessionErrorMessage(props) : undefined;
58312
58886
  const errorInfo = { name: errorName, message: errorMessage };
58313
58887
  if (this.tryFallbackRetry(task, errorInfo, "session.error"))
58314
58888
  return;
@@ -58412,93 +58986,21 @@ class BackgroundManager {
58412
58986
  }
58413
58987
  }
58414
58988
  tryFallbackRetry(task, errorInfo, source) {
58415
- const fallbackChain = task.fallbackChain;
58416
- const canRetry = shouldRetryError(errorInfo) && fallbackChain && fallbackChain.length > 0 && hasMoreFallbacks(fallbackChain, task.attemptCount ?? 0);
58417
- if (!canRetry)
58418
- return false;
58419
- const attemptCount = task.attemptCount ?? 0;
58420
- const providerModelsCache = readProviderModelsCache();
58421
- const connectedProviders = providerModelsCache?.connected ?? readConnectedProvidersCache();
58422
- const connectedSet = connectedProviders ? new Set(connectedProviders.map((p) => p.toLowerCase())) : null;
58423
- const isReachable = (entry) => {
58424
- if (!connectedSet)
58425
- return true;
58426
- return entry.providers.some((p) => connectedSet.has(p.toLowerCase()));
58427
- };
58428
- let selectedAttemptCount = attemptCount;
58429
- let nextFallback;
58430
- while (fallbackChain && selectedAttemptCount < fallbackChain.length) {
58431
- const candidate = getNextFallback(fallbackChain, selectedAttemptCount);
58432
- if (!candidate)
58433
- break;
58434
- selectedAttemptCount++;
58435
- if (!isReachable(candidate)) {
58436
- log("[background-agent] Skipping unreachable fallback:", {
58437
- taskId: task.id,
58438
- source,
58439
- model: candidate.model,
58440
- providers: candidate.providers
58441
- });
58442
- continue;
58443
- }
58444
- nextFallback = candidate;
58445
- break;
58446
- }
58447
- if (!nextFallback)
58448
- return false;
58449
- const providerID = selectFallbackProvider(nextFallback.providers, task.model?.providerID);
58450
- log("[background-agent] Retryable error, attempting fallback:", {
58451
- taskId: task.id,
58989
+ const previousSessionID = task.sessionID;
58990
+ const result = tryFallbackRetry({
58991
+ task,
58992
+ errorInfo,
58452
58993
  source,
58453
- errorName: errorInfo.name,
58454
- errorMessage: errorInfo.message?.slice(0, 100),
58455
- attemptCount: selectedAttemptCount,
58456
- nextModel: `${providerID}/${nextFallback.model}`
58994
+ concurrencyManager: this.concurrencyManager,
58995
+ client: this.client,
58996
+ idleDeferralTimers: this.idleDeferralTimers,
58997
+ queuesByKey: this.queuesByKey,
58998
+ processKey: (key) => this.processKey(key)
58457
58999
  });
58458
- if (task.concurrencyKey) {
58459
- this.concurrencyManager.release(task.concurrencyKey);
58460
- task.concurrencyKey = undefined;
58461
- }
58462
- if (task.sessionID) {
58463
- this.client.session.abort({ path: { id: task.sessionID } }).catch(() => {});
58464
- subagentSessions.delete(task.sessionID);
58465
- }
58466
- const idleTimer = this.idleDeferralTimers.get(task.id);
58467
- if (idleTimer) {
58468
- clearTimeout(idleTimer);
58469
- this.idleDeferralTimers.delete(task.id);
59000
+ if (result && previousSessionID) {
59001
+ subagentSessions.delete(previousSessionID);
58470
59002
  }
58471
- task.attemptCount = selectedAttemptCount;
58472
- const transformedModelId = transformModelForProvider(providerID, nextFallback.model);
58473
- task.model = {
58474
- providerID,
58475
- modelID: transformedModelId,
58476
- variant: nextFallback.variant
58477
- };
58478
- task.status = "pending";
58479
- task.sessionID = undefined;
58480
- task.startedAt = undefined;
58481
- task.queuedAt = new Date;
58482
- task.error = undefined;
58483
- const key = task.model ? `${task.model.providerID}/${task.model.modelID}` : task.agent;
58484
- const queue = this.queuesByKey.get(key) ?? [];
58485
- const retryInput = {
58486
- description: task.description,
58487
- prompt: task.prompt,
58488
- agent: task.agent,
58489
- parentSessionID: task.parentSessionID,
58490
- parentMessageID: task.parentMessageID,
58491
- parentModel: task.parentModel,
58492
- parentAgent: task.parentAgent,
58493
- parentTools: task.parentTools,
58494
- model: task.model,
58495
- fallbackChain: task.fallbackChain,
58496
- category: task.category
58497
- };
58498
- queue.push({ task, input: retryInput });
58499
- this.queuesByKey.set(key, queue);
58500
- this.processKey(key);
58501
- return true;
59003
+ return result;
58502
59004
  }
58503
59005
  markForNotification(task) {
58504
59006
  const queue = this.notifications.get(task.parentSessionID) ?? [];
@@ -58648,40 +59150,10 @@ class BackgroundManager {
58648
59150
  }
58649
59151
  }
58650
59152
  registerProcessCleanup() {
58651
- BackgroundManager.cleanupManagers.add(this);
58652
- if (BackgroundManager.cleanupRegistered)
58653
- return;
58654
- BackgroundManager.cleanupRegistered = true;
58655
- const cleanupAll = () => {
58656
- for (const manager of BackgroundManager.cleanupManagers) {
58657
- try {
58658
- manager.shutdown();
58659
- } catch (error45) {
58660
- log("[background-agent] Error during shutdown cleanup:", error45);
58661
- }
58662
- }
58663
- };
58664
- const registerSignal = (signal, exitAfter) => {
58665
- const listener = registerProcessSignal(signal, cleanupAll, exitAfter);
58666
- BackgroundManager.cleanupHandlers.set(signal, listener);
58667
- };
58668
- registerSignal("SIGINT", true);
58669
- registerSignal("SIGTERM", true);
58670
- if (process.platform === "win32") {
58671
- registerSignal("SIGBREAK", true);
58672
- }
58673
- registerSignal("beforeExit", false);
58674
- registerSignal("exit", false);
59153
+ registerManagerForCleanup(this);
58675
59154
  }
58676
59155
  unregisterProcessCleanup() {
58677
- BackgroundManager.cleanupManagers.delete(this);
58678
- if (BackgroundManager.cleanupManagers.size > 0)
58679
- return;
58680
- for (const [signal, listener] of BackgroundManager.cleanupHandlers.entries()) {
58681
- process.off(signal, listener);
58682
- }
58683
- BackgroundManager.cleanupHandlers.clear();
58684
- BackgroundManager.cleanupRegistered = false;
59156
+ unregisterManagerForCleanup(this);
58685
59157
  }
58686
59158
  getRunningTasks() {
58687
59159
  return Array.from(this.tasks.values()).filter((t) => t.status === "running");
@@ -58723,7 +59195,7 @@ class BackgroundManager {
58723
59195
  return true;
58724
59196
  }
58725
59197
  async notifyParentSession(task) {
58726
- const duration3 = this.formatDuration(task.startedAt ?? new Date, task.completedAt);
59198
+ const duration3 = formatDuration3(task.startedAt ?? new Date, task.completedAt);
58727
59199
  log("[background-agent] notifyParentSession called for task:", task.id);
58728
59200
  const toastManager = getTaskToastManager();
58729
59201
  if (toastManager) {
@@ -58787,7 +59259,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
58787
59259
  if (isCompactionAgent(info?.agent)) {
58788
59260
  continue;
58789
59261
  }
58790
- const normalizedTools = this.isRecord(info?.tools) ? normalizePromptTools(info.tools) : undefined;
59262
+ const normalizedTools = isRecord4(info?.tools) ? normalizePromptTools(info.tools) : undefined;
58791
59263
  if (info?.agent || info?.model || info?.modelID && info?.providerID || normalizedTools) {
58792
59264
  agent = info?.agent ?? task.parentAgent;
58793
59265
  model = info?.model ?? (info?.providerID && info?.modelID ? { providerID: info.providerID, modelID: info.modelID } : undefined);
@@ -58796,13 +59268,13 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
58796
59268
  }
58797
59269
  }
58798
59270
  } catch (error45) {
58799
- if (this.isAbortedSessionError(error45)) {
59271
+ if (isAbortedSessionError(error45)) {
58800
59272
  log("[background-agent] Parent session aborted while loading messages; using messageDir fallback:", {
58801
59273
  taskId: task.id,
58802
59274
  parentSessionID: task.parentSessionID
58803
59275
  });
58804
59276
  }
58805
- const messageDir = getMessageDir2(task.parentSessionID);
59277
+ const messageDir = join75(MESSAGE_STORAGE, task.parentSessionID);
58806
59278
  const currentMessage = messageDir ? findNearestMessageExcludingCompaction(messageDir) : null;
58807
59279
  agent = currentMessage?.agent ?? task.parentAgent;
58808
59280
  model = currentMessage?.model?.providerID && currentMessage?.model?.modelID ? { providerID: currentMessage.model.providerID, modelID: currentMessage.model.modelID } : undefined;
@@ -58831,7 +59303,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
58831
59303
  noReply: !allComplete
58832
59304
  });
58833
59305
  } catch (error45) {
58834
- if (this.isAbortedSessionError(error45)) {
59306
+ if (isAbortedSessionError(error45)) {
58835
59307
  log("[background-agent] Parent session aborted while sending notification; continuing cleanup:", {
58836
59308
  taskId: task.id,
58837
59309
  parentSessionID: task.parentSessionID
@@ -58867,91 +59339,10 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
58867
59339
  }
58868
59340
  }
58869
59341
  formatDuration(start, end) {
58870
- const duration3 = (end ?? new Date).getTime() - start.getTime();
58871
- const seconds = Math.floor(duration3 / 1000);
58872
- const minutes = Math.floor(seconds / 60);
58873
- const hours = Math.floor(minutes / 60);
58874
- if (hours > 0) {
58875
- return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
58876
- } else if (minutes > 0) {
58877
- return `${minutes}m ${seconds % 60}s`;
58878
- }
58879
- return `${seconds}s`;
59342
+ return formatDuration3(start, end);
58880
59343
  }
58881
59344
  isAbortedSessionError(error45) {
58882
- const message = this.getErrorText(error45);
58883
- return message.toLowerCase().includes("aborted");
58884
- }
58885
- getErrorText(error45) {
58886
- if (!error45)
58887
- return "";
58888
- if (typeof error45 === "string")
58889
- return error45;
58890
- if (error45 instanceof Error) {
58891
- return `${error45.name}: ${error45.message}`;
58892
- }
58893
- if (typeof error45 === "object" && error45 !== null) {
58894
- if ("message" in error45 && typeof error45.message === "string") {
58895
- return error45.message;
58896
- }
58897
- if ("name" in error45 && typeof error45.name === "string") {
58898
- return error45.name;
58899
- }
58900
- }
58901
- return "";
58902
- }
58903
- extractErrorName(error45) {
58904
- if (this.isRecord(error45) && typeof error45["name"] === "string")
58905
- return error45["name"];
58906
- if (error45 instanceof Error)
58907
- return error45.name;
58908
- return;
58909
- }
58910
- extractErrorMessage(error45) {
58911
- if (!error45)
58912
- return;
58913
- if (typeof error45 === "string")
58914
- return error45;
58915
- if (error45 instanceof Error)
58916
- return error45.message;
58917
- if (this.isRecord(error45)) {
58918
- const dataRaw = error45["data"];
58919
- const candidates = [
58920
- error45,
58921
- dataRaw,
58922
- error45["error"],
58923
- this.isRecord(dataRaw) ? dataRaw["error"] : undefined,
58924
- error45["cause"]
58925
- ];
58926
- for (const candidate of candidates) {
58927
- if (typeof candidate === "string" && candidate.length > 0)
58928
- return candidate;
58929
- if (this.isRecord(candidate) && typeof candidate["message"] === "string" && candidate["message"].length > 0) {
58930
- return candidate["message"];
58931
- }
58932
- }
58933
- }
58934
- try {
58935
- return JSON.stringify(error45);
58936
- } catch {
58937
- return String(error45);
58938
- }
58939
- }
58940
- isRecord(value) {
58941
- return typeof value === "object" && value !== null;
58942
- }
58943
- getSessionErrorMessage(properties) {
58944
- const errorRaw = properties["error"];
58945
- if (!this.isRecord(errorRaw))
58946
- return;
58947
- const dataRaw = errorRaw["data"];
58948
- if (this.isRecord(dataRaw)) {
58949
- const message2 = dataRaw["message"];
58950
- if (typeof message2 === "string")
58951
- return message2;
58952
- }
58953
- const message = errorRaw["message"];
58954
- return typeof message === "string" ? message : undefined;
59345
+ return isAbortedSessionError(error45);
58955
59346
  }
58956
59347
  hasRunningTasks() {
58957
59348
  for (const task of this.tasks.values()) {
@@ -58961,17 +59352,12 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
58961
59352
  return false;
58962
59353
  }
58963
59354
  pruneStaleTasksAndNotifications() {
58964
- const now = Date.now();
58965
- for (const [taskId, task] of this.tasks.entries()) {
58966
- const wasPending = task.status === "pending";
58967
- const timestamp2 = task.status === "pending" ? task.queuedAt?.getTime() : task.startedAt?.getTime();
58968
- if (!timestamp2) {
58969
- continue;
58970
- }
58971
- const age = now - timestamp2;
58972
- if (age > TASK_TTL_MS) {
58973
- const errorMessage = task.status === "pending" ? "Task timed out while queued (30 minutes)" : "Task timed out after 30 minutes";
58974
- log("[background-agent] Pruning stale task:", { taskId, status: task.status, age: Math.round(age / 1000) + "s" });
59355
+ pruneStaleTasksAndNotifications({
59356
+ tasks: this.tasks,
59357
+ notifications: this.notifications,
59358
+ onTaskPruned: (taskId, task, errorMessage) => {
59359
+ const wasPending = task.status === "pending";
59360
+ log("[background-agent] Pruning stale task:", { taskId, status: task.status, age: Math.round(((wasPending ? task.queuedAt?.getTime() : task.startedAt?.getTime()) ? Date.now() - (wasPending ? task.queuedAt.getTime() : task.startedAt.getTime()) : 0) / 1000) + "s" });
58975
59361
  task.status = "error";
58976
59362
  task.error = errorMessage;
58977
59363
  task.completedAt = new Date;
@@ -59004,86 +59390,17 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
59004
59390
  SessionCategoryRegistry.remove(task.sessionID);
59005
59391
  }
59006
59392
  }
59007
- }
59008
- for (const [sessionID, notifications] of this.notifications.entries()) {
59009
- if (notifications.length === 0) {
59010
- this.notifications.delete(sessionID);
59011
- continue;
59012
- }
59013
- const validNotifications = notifications.filter((task) => {
59014
- if (!task.startedAt)
59015
- return false;
59016
- const age = now - task.startedAt.getTime();
59017
- return age <= TASK_TTL_MS;
59018
- });
59019
- if (validNotifications.length === 0) {
59020
- this.notifications.delete(sessionID);
59021
- } else if (validNotifications.length !== notifications.length) {
59022
- this.notifications.set(sessionID, validNotifications);
59023
- }
59024
- }
59393
+ });
59025
59394
  }
59026
59395
  async checkAndInterruptStaleTasks(allStatuses = {}) {
59027
- const staleTimeoutMs = this.config?.staleTimeoutMs ?? DEFAULT_STALE_TIMEOUT_MS;
59028
- const messageStalenessMs = this.config?.messageStalenessTimeoutMs ?? DEFAULT_MESSAGE_STALENESS_TIMEOUT_MS;
59029
- const now = Date.now();
59030
- for (const task of this.tasks.values()) {
59031
- if (task.status !== "running")
59032
- continue;
59033
- const startedAt = task.startedAt;
59034
- const sessionID = task.sessionID;
59035
- if (!startedAt || !sessionID)
59036
- continue;
59037
- const sessionStatus = allStatuses[sessionID]?.type;
59038
- const sessionIsRunning = sessionStatus !== undefined && sessionStatus !== "idle";
59039
- const runtime = now - startedAt.getTime();
59040
- if (!task.progress?.lastUpdate) {
59041
- if (sessionIsRunning)
59042
- continue;
59043
- if (runtime <= messageStalenessMs)
59044
- continue;
59045
- const staleMinutes2 = Math.round(runtime / 60000);
59046
- task.status = "cancelled";
59047
- task.error = `Stale timeout (no activity for ${staleMinutes2}min since start)`;
59048
- task.completedAt = new Date;
59049
- if (task.concurrencyKey) {
59050
- this.concurrencyManager.release(task.concurrencyKey);
59051
- task.concurrencyKey = undefined;
59052
- }
59053
- this.client.session.abort({ path: { id: sessionID } }).catch(() => {});
59054
- log(`[background-agent] Task ${task.id} interrupted: no progress since start`);
59055
- try {
59056
- await this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task));
59057
- } catch (err) {
59058
- log("[background-agent] Error in notifyParentSession for stale task:", { taskId: task.id, error: err });
59059
- }
59060
- continue;
59061
- }
59062
- if (sessionIsRunning)
59063
- continue;
59064
- if (runtime < MIN_RUNTIME_BEFORE_STALE_MS)
59065
- continue;
59066
- const timeSinceLastUpdate = now - task.progress.lastUpdate.getTime();
59067
- if (timeSinceLastUpdate <= staleTimeoutMs)
59068
- continue;
59069
- if (task.status !== "running")
59070
- continue;
59071
- const staleMinutes = Math.round(timeSinceLastUpdate / 60000);
59072
- task.status = "cancelled";
59073
- task.error = `Stale timeout (no activity for ${staleMinutes}min)`;
59074
- task.completedAt = new Date;
59075
- if (task.concurrencyKey) {
59076
- this.concurrencyManager.release(task.concurrencyKey);
59077
- task.concurrencyKey = undefined;
59078
- }
59079
- this.client.session.abort({ path: { id: sessionID } }).catch(() => {});
59080
- log(`[background-agent] Task ${task.id} interrupted: stale timeout`);
59081
- try {
59082
- await this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task));
59083
- } catch (err) {
59084
- log("[background-agent] Error in notifyParentSession for stale task:", { taskId: task.id, error: err });
59085
- }
59086
- }
59396
+ await checkAndInterruptStaleTasks({
59397
+ tasks: this.tasks.values(),
59398
+ client: this.client,
59399
+ config: this.config,
59400
+ concurrencyManager: this.concurrencyManager,
59401
+ notifyParentSession: (task) => this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task)),
59402
+ sessionStatuses: allStatuses
59403
+ });
59087
59404
  }
59088
59405
  async pollRunningTasks() {
59089
59406
  if (this.pollingInFlight)
@@ -59201,71 +59518,6 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
59201
59518
  return current;
59202
59519
  }
59203
59520
  }
59204
- function registerProcessSignal(signal, handler, exitAfter) {
59205
- const listener = () => {
59206
- handler();
59207
- if (exitAfter) {
59208
- process.exitCode = 0;
59209
- setTimeout(() => process.exit(), 6000);
59210
- }
59211
- };
59212
- process.on(signal, listener);
59213
- return listener;
59214
- }
59215
- function getMessageDir2(sessionID) {
59216
- if (!existsSync63(MESSAGE_STORAGE))
59217
- return null;
59218
- const directPath = join74(MESSAGE_STORAGE, sessionID);
59219
- if (existsSync63(directPath))
59220
- return directPath;
59221
- for (const dir of readdirSync18(MESSAGE_STORAGE)) {
59222
- const sessionPath = join74(MESSAGE_STORAGE, dir, sessionID);
59223
- if (existsSync63(sessionPath))
59224
- return sessionPath;
59225
- }
59226
- return null;
59227
- }
59228
- function isCompactionAgent(agent) {
59229
- return agent?.trim().toLowerCase() === "compaction";
59230
- }
59231
- function hasFullAgentAndModel(message) {
59232
- return !!message.agent && !isCompactionAgent(message.agent) && !!message.model?.providerID && !!message.model?.modelID;
59233
- }
59234
- function hasPartialAgentOrModel(message) {
59235
- const hasAgent = !!message.agent && !isCompactionAgent(message.agent);
59236
- const hasModel = !!message.model?.providerID && !!message.model?.modelID;
59237
- return hasAgent || hasModel;
59238
- }
59239
- function findNearestMessageExcludingCompaction(messageDir) {
59240
- try {
59241
- const files = readdirSync18(messageDir).filter((name) => name.endsWith(".json")).sort().reverse();
59242
- for (const file2 of files) {
59243
- try {
59244
- const content = readFileSync42(join74(messageDir, file2), "utf-8");
59245
- const parsed = JSON.parse(content);
59246
- if (hasFullAgentAndModel(parsed)) {
59247
- return parsed;
59248
- }
59249
- } catch {
59250
- continue;
59251
- }
59252
- }
59253
- for (const file2 of files) {
59254
- try {
59255
- const content = readFileSync42(join74(messageDir, file2), "utf-8");
59256
- const parsed = JSON.parse(content);
59257
- if (hasPartialAgentOrModel(parsed)) {
59258
- return parsed;
59259
- }
59260
- } catch {
59261
- continue;
59262
- }
59263
- }
59264
- } catch {
59265
- return null;
59266
- }
59267
- return null;
59268
- }
59269
59521
  // src/features/background-agent/state.ts
59270
59522
  class TaskStateManager {
59271
59523
  tasks = new Map;
@@ -63265,11 +63517,11 @@ class StreamableHTTPClientTransport {
63265
63517
  }
63266
63518
 
63267
63519
  // src/features/mcp-oauth/storage.ts
63268
- import { chmodSync as chmodSync2, existsSync as existsSync64, mkdirSync as mkdirSync15, readFileSync as readFileSync43, unlinkSync as unlinkSync10, writeFileSync as writeFileSync20 } from "fs";
63269
- import { dirname as dirname21, join as join75 } from "path";
63520
+ import { chmodSync as chmodSync2, existsSync as existsSync63, mkdirSync as mkdirSync15, readFileSync as readFileSync43, unlinkSync as unlinkSync10, writeFileSync as writeFileSync20 } from "fs";
63521
+ import { dirname as dirname21, join as join76 } from "path";
63270
63522
  var STORAGE_FILE_NAME = "mcp-oauth.json";
63271
63523
  function getMcpOauthStoragePath() {
63272
- return join75(getOpenCodeConfigDir({ binary: "opencode" }), STORAGE_FILE_NAME);
63524
+ return join76(getOpenCodeConfigDir({ binary: "opencode" }), STORAGE_FILE_NAME);
63273
63525
  }
63274
63526
  function normalizeHost(serverHost) {
63275
63527
  let host = serverHost.trim();
@@ -63306,7 +63558,7 @@ function buildKey(serverHost, resource) {
63306
63558
  }
63307
63559
  function readStore() {
63308
63560
  const filePath = getMcpOauthStoragePath();
63309
- if (!existsSync64(filePath)) {
63561
+ if (!existsSync63(filePath)) {
63310
63562
  return null;
63311
63563
  }
63312
63564
  try {
@@ -63320,7 +63572,7 @@ function writeStore(store2) {
63320
63572
  const filePath = getMcpOauthStoragePath();
63321
63573
  try {
63322
63574
  const dir = dirname21(filePath);
63323
- if (!existsSync64(dir)) {
63575
+ if (!existsSync63(dir)) {
63324
63576
  mkdirSync15(dir, { recursive: true });
63325
63577
  }
63326
63578
  writeFileSync20(filePath, JSON.stringify(store2, null, 2), { encoding: "utf-8", mode: 384 });
@@ -63472,7 +63724,7 @@ async function getOrRegisterClient(options) {
63472
63724
  }
63473
63725
  }
63474
63726
  function parseRegistrationResponse(data) {
63475
- if (!isRecord4(data))
63727
+ if (!isRecord5(data))
63476
63728
  return null;
63477
63729
  const clientId = data.client_id;
63478
63730
  if (typeof clientId !== "string" || clientId.length === 0)
@@ -63483,7 +63735,7 @@ function parseRegistrationResponse(data) {
63483
63735
  }
63484
63736
  return { clientId };
63485
63737
  }
63486
- function isRecord4(value) {
63738
+ function isRecord5(value) {
63487
63739
  return typeof value === "object" && value !== null;
63488
63740
  }
63489
63741
 
@@ -65680,6 +65932,20 @@ function buildAntiPatternsSection() {
65680
65932
  ${patterns.join(`
65681
65933
  `)}`;
65682
65934
  }
65935
+ function buildDeepParallelSection(model, categories2) {
65936
+ const isNonClaude = !model.toLowerCase().includes("claude");
65937
+ const hasDeepCategory = categories2.some((c) => c.name === "deep");
65938
+ if (!isNonClaude || !hasDeepCategory)
65939
+ return "";
65940
+ return `### Deep Parallel Delegation
65941
+
65942
+ For implementation tasks, actively decompose and delegate to \`deep\` category agents in parallel.
65943
+
65944
+ 1. Break the implementation into independent work units
65945
+ 2. Maximize parallel deep agents \u2014 spawn one per independent unit (\`run_in_background=true\`)
65946
+ 3. Give each agent a GOAL, not step-by-step instructions \u2014 deep agents explore and solve autonomously
65947
+ 4. Collect results, integrate, verify coherence`;
65948
+ }
65683
65949
 
65684
65950
  // src/agents/sisyphus.ts
65685
65951
  var MODE = "primary";
@@ -65791,7 +66057,7 @@ Should I proceed with [recommendation], or would you prefer differently?
65791
66057
  \`\`\`
65792
66058
  </Task_Management>`;
65793
66059
  }
65794
- function buildDynamicSisyphusPrompt(availableAgents, availableTools = [], availableSkills = [], availableCategories = [], useTaskSystem = false) {
66060
+ function buildDynamicSisyphusPrompt(model, availableAgents, availableTools = [], availableSkills = [], availableCategories = [], useTaskSystem = false) {
65795
66061
  const keyTriggers = buildKeyTriggersSection(availableAgents, availableSkills);
65796
66062
  const toolSelection = buildToolSelectionTable(availableAgents, availableTools, availableSkills);
65797
66063
  const exploreSection = buildExploreSection(availableAgents);
@@ -65801,6 +66067,7 @@ function buildDynamicSisyphusPrompt(availableAgents, availableTools = [], availa
65801
66067
  const oracleSection = buildOracleSection(availableAgents);
65802
66068
  const hardBlocks = buildHardBlocksSection();
65803
66069
  const antiPatterns = buildAntiPatternsSection();
66070
+ const deepParallelSection = buildDeepParallelSection(model, availableCategories);
65804
66071
  const taskManagementSection = buildTaskManagementSection(useTaskSystem);
65805
66072
  const todoHookNote = useTaskSystem ? "YOUR TASK CREATION WOULD BE TRACKED BY HOOK([SYSTEM REMINDER - TASK CONTINUATION])" : "YOUR TODO CREATION WOULD BE TRACKED BY HOOK([SYSTEM REMINDER - TODO CONTINUATION])";
65806
66073
  return `<Role>
@@ -65993,6 +66260,8 @@ STOP searching when:
65993
66260
 
65994
66261
  ${categorySkillsGuide}
65995
66262
 
66263
+ ${deepParallelSection}
66264
+
65996
66265
  ${delegationTable}
65997
66266
 
65998
66267
  ### Delegation Prompt Structure (MANDATORY - ALL 6 sections):
@@ -66172,7 +66441,7 @@ function createSisyphusAgent(model, availableAgents, availableToolNames, availab
66172
66441
  const tools = availableToolNames ? categorizeTools(availableToolNames) : [];
66173
66442
  const skills2 = availableSkills ?? [];
66174
66443
  const categories2 = availableCategories ?? [];
66175
- const prompt = availableAgents ? buildDynamicSisyphusPrompt(availableAgents, tools, skills2, categories2, useTaskSystem) : buildDynamicSisyphusPrompt([], tools, skills2, categories2, useTaskSystem);
66444
+ const prompt = availableAgents ? buildDynamicSisyphusPrompt(model, availableAgents, tools, skills2, categories2, useTaskSystem) : buildDynamicSisyphusPrompt(model, [], tools, skills2, categories2, useTaskSystem);
66176
66445
  const permission = {
66177
66446
  question: "allow",
66178
66447
  call_omo_agent: "deny"
@@ -68839,7 +69108,7 @@ function buildAgent(source, model, categories2, gitMasterConfig, browserProvider
68839
69108
  }
68840
69109
 
68841
69110
  // src/agents/builtin-agents/resolve-file-uri.ts
68842
- import { existsSync as existsSync65, readFileSync as readFileSync44 } from "fs";
69111
+ import { existsSync as existsSync64, readFileSync as readFileSync44 } from "fs";
68843
69112
  import { homedir as homedir13 } from "os";
68844
69113
  import { isAbsolute as isAbsolute9, resolve as resolve10 } from "path";
68845
69114
  function resolvePromptAppend(promptAppend, configDir) {
@@ -68854,7 +69123,7 @@ function resolvePromptAppend(promptAppend, configDir) {
68854
69123
  } catch {
68855
69124
  return `[WARNING: Malformed file URI (invalid percent-encoding): ${promptAppend}]`;
68856
69125
  }
68857
- if (!existsSync65(filePath)) {
69126
+ if (!existsSync64(filePath)) {
68858
69127
  return `[WARNING: Could not resolve file URI: ${promptAppend}]`;
68859
69128
  }
68860
69129
  try {
@@ -69179,7 +69448,7 @@ function maybeCreateAtlasConfig(input) {
69179
69448
  function sanitizeMarkdownTableCell(value) {
69180
69449
  return value.replace(/\r?\n/g, " ").replace(/\|/g, "\\|").replace(/\s+/g, " ").trim();
69181
69450
  }
69182
- function isRecord5(value) {
69451
+ function isRecord6(value) {
69183
69452
  return typeof value === "object" && value !== null;
69184
69453
  }
69185
69454
  function parseRegisteredAgentSummaries(input) {
@@ -69187,7 +69456,7 @@ function parseRegisteredAgentSummaries(input) {
69187
69456
  return [];
69188
69457
  const result = [];
69189
69458
  for (const item of input) {
69190
- if (!isRecord5(item))
69459
+ if (!isRecord6(item))
69191
69460
  continue;
69192
69461
  const name = typeof item.name === "string" ? item.name : undefined;
69193
69462
  if (!name)
@@ -71420,8 +71689,8 @@ function createSisyphusJuniorAgentWithOverrides(override, systemDefaultModel, us
71420
71689
  }
71421
71690
  createSisyphusJuniorAgentWithOverrides.mode = MODE10;
71422
71691
  // src/features/claude-code-agent-loader/loader.ts
71423
- import { existsSync as existsSync66, readdirSync as readdirSync19, readFileSync as readFileSync45 } from "fs";
71424
- import { join as join76, basename as basename8 } from "path";
71692
+ import { existsSync as existsSync65, readdirSync as readdirSync19, readFileSync as readFileSync45 } from "fs";
71693
+ import { join as join77, basename as basename8 } from "path";
71425
71694
  function parseToolsConfig(toolsStr) {
71426
71695
  if (!toolsStr)
71427
71696
  return;
@@ -71435,7 +71704,7 @@ function parseToolsConfig(toolsStr) {
71435
71704
  return result;
71436
71705
  }
71437
71706
  function loadAgentsFromDir(agentsDir, scope) {
71438
- if (!existsSync66(agentsDir)) {
71707
+ if (!existsSync65(agentsDir)) {
71439
71708
  return [];
71440
71709
  }
71441
71710
  const entries = readdirSync19(agentsDir, { withFileTypes: true });
@@ -71443,7 +71712,7 @@ function loadAgentsFromDir(agentsDir, scope) {
71443
71712
  for (const entry of entries) {
71444
71713
  if (!isMarkdownFile(entry))
71445
71714
  continue;
71446
- const agentPath = join76(agentsDir, entry.name);
71715
+ const agentPath = join77(agentsDir, entry.name);
71447
71716
  const agentName = basename8(entry.name, ".md");
71448
71717
  try {
71449
71718
  const content = readFileSync45(agentPath, "utf-8");
@@ -71473,7 +71742,7 @@ function loadAgentsFromDir(agentsDir, scope) {
71473
71742
  return agents;
71474
71743
  }
71475
71744
  function loadUserAgents() {
71476
- const userAgentsDir = join76(getClaudeConfigDir(), "agents");
71745
+ const userAgentsDir = join77(getClaudeConfigDir(), "agents");
71477
71746
  const agents = loadAgentsFromDir(userAgentsDir, "user");
71478
71747
  const result = {};
71479
71748
  for (const agent of agents) {
@@ -71482,7 +71751,7 @@ function loadUserAgents() {
71482
71751
  return result;
71483
71752
  }
71484
71753
  function loadProjectAgents(directory) {
71485
- const projectAgentsDir = join76(directory ?? process.cwd(), ".claude", "agents");
71754
+ const projectAgentsDir = join77(directory ?? process.cwd(), ".claude", "agents");
71486
71755
  const agents = loadAgentsFromDir(projectAgentsDir, "project");
71487
71756
  const result = {};
71488
71757
  for (const agent of agents) {
@@ -71742,7 +72011,7 @@ async function applyAgentConfig(params) {
71742
72011
  }
71743
72012
  // src/features/claude-code-command-loader/loader.ts
71744
72013
  import { promises as fs19 } from "fs";
71745
- import { join as join77, basename as basename9 } from "path";
72014
+ import { join as join78, basename as basename9 } from "path";
71746
72015
  init_logger();
71747
72016
  async function loadCommandsFromDir(commandsDir, scope, visited = new Set, prefix = "") {
71748
72017
  try {
@@ -71773,7 +72042,7 @@ async function loadCommandsFromDir(commandsDir, scope, visited = new Set, prefix
71773
72042
  if (entry.isDirectory()) {
71774
72043
  if (entry.name.startsWith("."))
71775
72044
  continue;
71776
- const subDirPath = join77(commandsDir, entry.name);
72045
+ const subDirPath = join78(commandsDir, entry.name);
71777
72046
  const subPrefix = prefix ? `${prefix}:${entry.name}` : entry.name;
71778
72047
  const subCommands = await loadCommandsFromDir(subDirPath, scope, visited, subPrefix);
71779
72048
  commands3.push(...subCommands);
@@ -71781,7 +72050,7 @@ async function loadCommandsFromDir(commandsDir, scope, visited = new Set, prefix
71781
72050
  }
71782
72051
  if (!isMarkdownFile(entry))
71783
72052
  continue;
71784
- const commandPath = join77(commandsDir, entry.name);
72053
+ const commandPath = join78(commandsDir, entry.name);
71785
72054
  const baseCommandName = basename9(entry.name, ".md");
71786
72055
  const commandName = prefix ? `${prefix}:${baseCommandName}` : baseCommandName;
71787
72056
  try {
@@ -71828,23 +72097,23 @@ function commandsToRecord(commands3) {
71828
72097
  return result;
71829
72098
  }
71830
72099
  async function loadUserCommands() {
71831
- const userCommandsDir = join77(getClaudeConfigDir(), "commands");
72100
+ const userCommandsDir = join78(getClaudeConfigDir(), "commands");
71832
72101
  const commands3 = await loadCommandsFromDir(userCommandsDir, "user");
71833
72102
  return commandsToRecord(commands3);
71834
72103
  }
71835
72104
  async function loadProjectCommands(directory) {
71836
- const projectCommandsDir = join77(directory ?? process.cwd(), ".claude", "commands");
72105
+ const projectCommandsDir = join78(directory ?? process.cwd(), ".claude", "commands");
71837
72106
  const commands3 = await loadCommandsFromDir(projectCommandsDir, "project");
71838
72107
  return commandsToRecord(commands3);
71839
72108
  }
71840
72109
  async function loadOpencodeGlobalCommands() {
71841
72110
  const configDir = getOpenCodeConfigDir({ binary: "opencode" });
71842
- const opencodeCommandsDir = join77(configDir, "command");
72111
+ const opencodeCommandsDir = join78(configDir, "command");
71843
72112
  const commands3 = await loadCommandsFromDir(opencodeCommandsDir, "opencode");
71844
72113
  return commandsToRecord(commands3);
71845
72114
  }
71846
72115
  async function loadOpencodeProjectCommands(directory) {
71847
- const opencodeProjectDir = join77(directory ?? process.cwd(), ".opencode", "command");
72116
+ const opencodeProjectDir = join78(directory ?? process.cwd(), ".opencode", "command");
71848
72117
  const commands3 = await loadCommandsFromDir(opencodeProjectDir, "opencode-project");
71849
72118
  return commandsToRecord(commands3);
71850
72119
  }
@@ -71903,8 +72172,8 @@ function remapCommandAgentFields(commands3) {
71903
72172
  }
71904
72173
  }
71905
72174
  // src/features/claude-code-mcp-loader/loader.ts
71906
- import { existsSync as existsSync67, readFileSync as readFileSync46 } from "fs";
71907
- import { join as join78 } from "path";
72175
+ import { existsSync as existsSync66, readFileSync as readFileSync46 } from "fs";
72176
+ import { join as join79 } from "path";
71908
72177
  import { homedir as homedir14 } from "os";
71909
72178
 
71910
72179
  // src/features/claude-code-mcp-loader/transformer.ts
@@ -71946,14 +72215,14 @@ function getMcpConfigPaths() {
71946
72215
  const claudeConfigDir = getClaudeConfigDir();
71947
72216
  const cwd = process.cwd();
71948
72217
  return [
71949
- { path: join78(homedir14(), ".claude.json"), scope: "user" },
71950
- { path: join78(claudeConfigDir, ".mcp.json"), scope: "user" },
71951
- { path: join78(cwd, ".mcp.json"), scope: "project" },
71952
- { path: join78(cwd, ".claude", ".mcp.json"), scope: "local" }
72218
+ { path: join79(homedir14(), ".claude.json"), scope: "user" },
72219
+ { path: join79(claudeConfigDir, ".mcp.json"), scope: "user" },
72220
+ { path: join79(cwd, ".mcp.json"), scope: "project" },
72221
+ { path: join79(cwd, ".claude", ".mcp.json"), scope: "local" }
71953
72222
  ];
71954
72223
  }
71955
72224
  async function loadMcpConfigFile(filePath) {
71956
- if (!existsSync67(filePath)) {
72225
+ if (!existsSync66(filePath)) {
71957
72226
  return null;
71958
72227
  }
71959
72228
  try {
@@ -71968,7 +72237,7 @@ function getSystemMcpServerNames() {
71968
72237
  const names = new Set;
71969
72238
  const paths = getMcpConfigPaths();
71970
72239
  for (const { path: path11 } of paths) {
71971
- if (!existsSync67(path11))
72240
+ if (!existsSync66(path11))
71972
72241
  continue;
71973
72242
  try {
71974
72243
  const content = readFileSync46(path11, "utf-8");
@@ -72145,21 +72414,21 @@ init_logger();
72145
72414
 
72146
72415
  // src/features/claude-code-plugin-loader/discovery.ts
72147
72416
  init_logger();
72148
- import { existsSync as existsSync68, readFileSync as readFileSync47 } from "fs";
72417
+ import { existsSync as existsSync67, readFileSync as readFileSync47 } from "fs";
72149
72418
  import { homedir as homedir15 } from "os";
72150
- import { join as join79 } from "path";
72419
+ import { join as join80 } from "path";
72151
72420
  function getPluginsBaseDir() {
72152
72421
  if (process.env.CLAUDE_PLUGINS_HOME) {
72153
72422
  return process.env.CLAUDE_PLUGINS_HOME;
72154
72423
  }
72155
- return join79(homedir15(), ".claude", "plugins");
72424
+ return join80(homedir15(), ".claude", "plugins");
72156
72425
  }
72157
72426
  function getInstalledPluginsPath() {
72158
- return join79(getPluginsBaseDir(), "installed_plugins.json");
72427
+ return join80(getPluginsBaseDir(), "installed_plugins.json");
72159
72428
  }
72160
72429
  function loadInstalledPlugins() {
72161
72430
  const dbPath = getInstalledPluginsPath();
72162
- if (!existsSync68(dbPath)) {
72431
+ if (!existsSync67(dbPath)) {
72163
72432
  return null;
72164
72433
  }
72165
72434
  try {
@@ -72174,11 +72443,11 @@ function getClaudeSettingsPath() {
72174
72443
  if (process.env.CLAUDE_SETTINGS_PATH) {
72175
72444
  return process.env.CLAUDE_SETTINGS_PATH;
72176
72445
  }
72177
- return join79(homedir15(), ".claude", "settings.json");
72446
+ return join80(homedir15(), ".claude", "settings.json");
72178
72447
  }
72179
72448
  function loadClaudeSettings() {
72180
72449
  const settingsPath = getClaudeSettingsPath();
72181
- if (!existsSync68(settingsPath)) {
72450
+ if (!existsSync67(settingsPath)) {
72182
72451
  return null;
72183
72452
  }
72184
72453
  try {
@@ -72190,8 +72459,8 @@ function loadClaudeSettings() {
72190
72459
  }
72191
72460
  }
72192
72461
  function loadPluginManifest(installPath) {
72193
- const manifestPath = join79(installPath, ".claude-plugin", "plugin.json");
72194
- if (!existsSync68(manifestPath)) {
72462
+ const manifestPath = join80(installPath, ".claude-plugin", "plugin.json");
72463
+ if (!existsSync67(manifestPath)) {
72195
72464
  return null;
72196
72465
  }
72197
72466
  try {
@@ -72239,7 +72508,7 @@ function discoverInstalledPlugins(options) {
72239
72508
  continue;
72240
72509
  }
72241
72510
  const { installPath, scope, version: version2 } = installation;
72242
- if (!existsSync68(installPath)) {
72511
+ if (!existsSync67(installPath)) {
72243
72512
  errors3.push({
72244
72513
  pluginKey,
72245
72514
  installPath,
@@ -72257,21 +72526,21 @@ function discoverInstalledPlugins(options) {
72257
72526
  pluginKey,
72258
72527
  manifest: manifest ?? undefined
72259
72528
  };
72260
- if (existsSync68(join79(installPath, "commands"))) {
72261
- loadedPlugin.commandsDir = join79(installPath, "commands");
72529
+ if (existsSync67(join80(installPath, "commands"))) {
72530
+ loadedPlugin.commandsDir = join80(installPath, "commands");
72262
72531
  }
72263
- if (existsSync68(join79(installPath, "agents"))) {
72264
- loadedPlugin.agentsDir = join79(installPath, "agents");
72532
+ if (existsSync67(join80(installPath, "agents"))) {
72533
+ loadedPlugin.agentsDir = join80(installPath, "agents");
72265
72534
  }
72266
- if (existsSync68(join79(installPath, "skills"))) {
72267
- loadedPlugin.skillsDir = join79(installPath, "skills");
72535
+ if (existsSync67(join80(installPath, "skills"))) {
72536
+ loadedPlugin.skillsDir = join80(installPath, "skills");
72268
72537
  }
72269
- const hooksPath = join79(installPath, "hooks", "hooks.json");
72270
- if (existsSync68(hooksPath)) {
72538
+ const hooksPath = join80(installPath, "hooks", "hooks.json");
72539
+ if (existsSync67(hooksPath)) {
72271
72540
  loadedPlugin.hooksPath = hooksPath;
72272
72541
  }
72273
- const mcpPath = join79(installPath, ".mcp.json");
72274
- if (existsSync68(mcpPath)) {
72542
+ const mcpPath = join80(installPath, ".mcp.json");
72543
+ if (existsSync67(mcpPath)) {
72275
72544
  loadedPlugin.mcpPath = mcpPath;
72276
72545
  }
72277
72546
  plugins.push(loadedPlugin);
@@ -72284,19 +72553,19 @@ function discoverInstalledPlugins(options) {
72284
72553
  }
72285
72554
 
72286
72555
  // src/features/claude-code-plugin-loader/command-loader.ts
72287
- import { existsSync as existsSync69, readdirSync as readdirSync20, readFileSync as readFileSync48 } from "fs";
72288
- import { basename as basename10, join as join80 } from "path";
72556
+ import { existsSync as existsSync68, readdirSync as readdirSync20, readFileSync as readFileSync48 } from "fs";
72557
+ import { basename as basename10, join as join81 } from "path";
72289
72558
  init_logger();
72290
72559
  function loadPluginCommands(plugins) {
72291
72560
  const commands3 = {};
72292
72561
  for (const plugin of plugins) {
72293
- if (!plugin.commandsDir || !existsSync69(plugin.commandsDir))
72562
+ if (!plugin.commandsDir || !existsSync68(plugin.commandsDir))
72294
72563
  continue;
72295
72564
  const entries = readdirSync20(plugin.commandsDir, { withFileTypes: true });
72296
72565
  for (const entry of entries) {
72297
72566
  if (!isMarkdownFile(entry))
72298
72567
  continue;
72299
- const commandPath = join80(plugin.commandsDir, entry.name);
72568
+ const commandPath = join81(plugin.commandsDir, entry.name);
72300
72569
  const commandName = basename10(entry.name, ".md");
72301
72570
  const namespacedName = `${plugin.name}:${commandName}`;
72302
72571
  try {
@@ -72331,24 +72600,24 @@ $ARGUMENTS
72331
72600
  }
72332
72601
 
72333
72602
  // src/features/claude-code-plugin-loader/skill-loader.ts
72334
- import { existsSync as existsSync70, readdirSync as readdirSync21, readFileSync as readFileSync49 } from "fs";
72335
- import { join as join81 } from "path";
72603
+ import { existsSync as existsSync69, readdirSync as readdirSync21, readFileSync as readFileSync49 } from "fs";
72604
+ import { join as join82 } from "path";
72336
72605
  init_logger();
72337
72606
  function loadPluginSkillsAsCommands(plugins) {
72338
72607
  const skills2 = {};
72339
72608
  for (const plugin of plugins) {
72340
- if (!plugin.skillsDir || !existsSync70(plugin.skillsDir))
72609
+ if (!plugin.skillsDir || !existsSync69(plugin.skillsDir))
72341
72610
  continue;
72342
72611
  const entries = readdirSync21(plugin.skillsDir, { withFileTypes: true });
72343
72612
  for (const entry of entries) {
72344
72613
  if (entry.name.startsWith("."))
72345
72614
  continue;
72346
- const skillPath = join81(plugin.skillsDir, entry.name);
72615
+ const skillPath = join82(plugin.skillsDir, entry.name);
72347
72616
  if (!entry.isDirectory() && !entry.isSymbolicLink())
72348
72617
  continue;
72349
72618
  const resolvedPath = resolveSymlink(skillPath);
72350
- const skillMdPath = join81(resolvedPath, "SKILL.md");
72351
- if (!existsSync70(skillMdPath))
72619
+ const skillMdPath = join82(resolvedPath, "SKILL.md");
72620
+ if (!existsSync69(skillMdPath))
72352
72621
  continue;
72353
72622
  try {
72354
72623
  const content = readFileSync49(skillMdPath, "utf-8");
@@ -72386,8 +72655,8 @@ $ARGUMENTS
72386
72655
  }
72387
72656
 
72388
72657
  // src/features/claude-code-plugin-loader/agent-loader.ts
72389
- import { existsSync as existsSync71, readdirSync as readdirSync22, readFileSync as readFileSync50 } from "fs";
72390
- import { basename as basename11, join as join82 } from "path";
72658
+ import { existsSync as existsSync70, readdirSync as readdirSync22, readFileSync as readFileSync50 } from "fs";
72659
+ import { basename as basename11, join as join83 } from "path";
72391
72660
  init_logger();
72392
72661
  function parseToolsConfig2(toolsStr) {
72393
72662
  if (!toolsStr)
@@ -72404,13 +72673,13 @@ function parseToolsConfig2(toolsStr) {
72404
72673
  function loadPluginAgents(plugins) {
72405
72674
  const agents = {};
72406
72675
  for (const plugin of plugins) {
72407
- if (!plugin.agentsDir || !existsSync71(plugin.agentsDir))
72676
+ if (!plugin.agentsDir || !existsSync70(plugin.agentsDir))
72408
72677
  continue;
72409
72678
  const entries = readdirSync22(plugin.agentsDir, { withFileTypes: true });
72410
72679
  for (const entry of entries) {
72411
72680
  if (!isMarkdownFile(entry))
72412
72681
  continue;
72413
- const agentPath = join82(plugin.agentsDir, entry.name);
72682
+ const agentPath = join83(plugin.agentsDir, entry.name);
72414
72683
  const agentName = basename11(entry.name, ".md");
72415
72684
  const namespacedName = `${plugin.name}:${agentName}`;
72416
72685
  try {
@@ -72438,7 +72707,7 @@ function loadPluginAgents(plugins) {
72438
72707
  }
72439
72708
 
72440
72709
  // src/features/claude-code-plugin-loader/mcp-server-loader.ts
72441
- import { existsSync as existsSync72 } from "fs";
72710
+ import { existsSync as existsSync71 } from "fs";
72442
72711
  init_logger();
72443
72712
 
72444
72713
  // src/features/claude-code-plugin-loader/plugin-path-resolver.ts
@@ -72469,7 +72738,7 @@ function resolvePluginPaths(obj, pluginRoot) {
72469
72738
  async function loadPluginMcpServers(plugins) {
72470
72739
  const servers = {};
72471
72740
  for (const plugin of plugins) {
72472
- if (!plugin.mcpPath || !existsSync72(plugin.mcpPath))
72741
+ if (!plugin.mcpPath || !existsSync71(plugin.mcpPath))
72473
72742
  continue;
72474
72743
  try {
72475
72744
  const content = await Bun.file(plugin.mcpPath).text();
@@ -72501,11 +72770,11 @@ async function loadPluginMcpServers(plugins) {
72501
72770
 
72502
72771
  // src/features/claude-code-plugin-loader/hook-loader.ts
72503
72772
  init_logger();
72504
- import { existsSync as existsSync73, readFileSync as readFileSync51 } from "fs";
72773
+ import { existsSync as existsSync72, readFileSync as readFileSync51 } from "fs";
72505
72774
  function loadPluginHooksConfigs(plugins) {
72506
72775
  const configs = [];
72507
72776
  for (const plugin of plugins) {
72508
- if (!plugin.hooksPath || !existsSync73(plugin.hooksPath))
72777
+ if (!plugin.hooksPath || !existsSync72(plugin.hooksPath))
72509
72778
  continue;
72510
72779
  try {
72511
72780
  const content = readFileSync51(plugin.hooksPath, "utf-8");
@@ -72933,11 +73202,11 @@ async function createTools(args) {
72933
73202
  }
72934
73203
 
72935
73204
  // src/plugin/chat-params.ts
72936
- function isRecord6(value) {
73205
+ function isRecord7(value) {
72937
73206
  return typeof value === "object" && value !== null;
72938
73207
  }
72939
73208
  function buildChatParamsInput(raw) {
72940
- if (!isRecord6(raw))
73209
+ if (!isRecord7(raw))
72941
73210
  return null;
72942
73211
  const sessionID = raw.sessionID;
72943
73212
  const agent = raw.agent;
@@ -72946,16 +73215,16 @@ function buildChatParamsInput(raw) {
72946
73215
  const message = raw.message;
72947
73216
  if (typeof sessionID !== "string")
72948
73217
  return null;
72949
- if (!isRecord6(model))
73218
+ if (!isRecord7(model))
72950
73219
  return null;
72951
- if (!isRecord6(provider))
73220
+ if (!isRecord7(provider))
72952
73221
  return null;
72953
- if (!isRecord6(message))
73222
+ if (!isRecord7(message))
72954
73223
  return null;
72955
73224
  let agentName;
72956
73225
  if (typeof agent === "string") {
72957
73226
  agentName = agent;
72958
- } else if (isRecord6(agent)) {
73227
+ } else if (isRecord7(agent)) {
72959
73228
  const name = agent.name;
72960
73229
  if (typeof name === "string") {
72961
73230
  agentName = name;
@@ -72982,12 +73251,12 @@ function buildChatParamsInput(raw) {
72982
73251
  };
72983
73252
  }
72984
73253
  function isChatParamsOutput(raw) {
72985
- if (!isRecord6(raw))
73254
+ if (!isRecord7(raw))
72986
73255
  return false;
72987
- if (!isRecord6(raw.options)) {
73256
+ if (!isRecord7(raw.options)) {
72988
73257
  raw.options = {};
72989
73258
  }
72990
- return isRecord6(raw.options);
73259
+ return isRecord7(raw.options);
72991
73260
  }
72992
73261
  function createChatParamsHandler(args) {
72993
73262
  return async (input, output) => {
@@ -73003,20 +73272,20 @@ function createChatParamsHandler(args) {
73003
73272
  // src/plugin/chat-headers.ts
73004
73273
  var INTERNAL_MARKER_CACHE_LIMIT = 1000;
73005
73274
  var internalMarkerCache = new Map;
73006
- function isRecord7(value) {
73275
+ function isRecord8(value) {
73007
73276
  return typeof value === "object" && value !== null;
73008
73277
  }
73009
73278
  function buildChatHeadersInput(raw) {
73010
- if (!isRecord7(raw))
73279
+ if (!isRecord8(raw))
73011
73280
  return null;
73012
73281
  const sessionID = raw.sessionID;
73013
73282
  const provider = raw.provider;
73014
73283
  const message = raw.message;
73015
73284
  if (typeof sessionID !== "string")
73016
73285
  return null;
73017
- if (!isRecord7(provider) || typeof provider.id !== "string")
73286
+ if (!isRecord8(provider) || typeof provider.id !== "string")
73018
73287
  return null;
73019
- if (!isRecord7(message))
73288
+ if (!isRecord8(message))
73020
73289
  return null;
73021
73290
  return {
73022
73291
  sessionID,
@@ -73028,12 +73297,12 @@ function buildChatHeadersInput(raw) {
73028
73297
  };
73029
73298
  }
73030
73299
  function isChatHeadersOutput(raw) {
73031
- if (!isRecord7(raw))
73300
+ if (!isRecord8(raw))
73032
73301
  return false;
73033
- if (!isRecord7(raw.headers)) {
73302
+ if (!isRecord8(raw.headers)) {
73034
73303
  raw.headers = {};
73035
73304
  }
73036
- return isRecord7(raw.headers);
73305
+ return isRecord8(raw.headers);
73037
73306
  }
73038
73307
  function isCopilotProvider(providerID) {
73039
73308
  return providerID === "github-copilot" || providerID === "github-copilot-enterprise";
@@ -73049,7 +73318,7 @@ async function hasInternalMarker(client2, sessionID, messageID) {
73049
73318
  path: { id: sessionID, messageID }
73050
73319
  });
73051
73320
  const data = response.data;
73052
- if (!isRecord7(data) || !Array.isArray(data.parts)) {
73321
+ if (!isRecord8(data) || !Array.isArray(data.parts)) {
73053
73322
  internalMarkerCache.set(cacheKey, false);
73054
73323
  if (internalMarkerCache.size > INTERNAL_MARKER_CACHE_LIMIT) {
73055
73324
  internalMarkerCache.clear();
@@ -73057,7 +73326,7 @@ async function hasInternalMarker(client2, sessionID, messageID) {
73057
73326
  return false;
73058
73327
  }
73059
73328
  const hasMarker = data.parts.some((part) => {
73060
- if (!isRecord7(part) || part.type !== "text" || typeof part.text !== "string") {
73329
+ if (!isRecord8(part) || part.type !== "text" || typeof part.text !== "string") {
73061
73330
  return false;
73062
73331
  }
73063
73332
  return part.text.includes(OMO_INTERNAL_INITIATOR_MARKER);
@@ -73111,10 +73380,10 @@ function clearSessionModel(sessionID) {
73111
73380
 
73112
73381
  // src/plugin/ultrawork-db-model-override.ts
73113
73382
  import { Database } from "bun:sqlite";
73114
- import { join as join83 } from "path";
73115
- import { existsSync as existsSync74 } from "fs";
73383
+ import { join as join84 } from "path";
73384
+ import { existsSync as existsSync73 } from "fs";
73116
73385
  function getDbPath() {
73117
- return join83(getDataDir(), "opencode", "opencode.db");
73386
+ return join84(getDataDir(), "opencode", "opencode.db");
73118
73387
  }
73119
73388
  var MAX_MICROTASK_RETRIES = 10;
73120
73389
  function tryUpdateMessageModel(db, messageId, targetModel, variant) {
@@ -73191,7 +73460,7 @@ function retryViaMicrotask(db, messageId, targetModel, variant, attempt) {
73191
73460
  function scheduleDeferredModelOverride(messageId, targetModel, variant) {
73192
73461
  queueMicrotask(() => {
73193
73462
  const dbPath = getDbPath();
73194
- if (!existsSync74(dbPath)) {
73463
+ if (!existsSync73(dbPath)) {
73195
73464
  log("[ultrawork-db-override] DB not found, skipping deferred override");
73196
73465
  return;
73197
73466
  }
@@ -73459,36 +73728,36 @@ function pruneRecentSyntheticIdles(args) {
73459
73728
  }
73460
73729
 
73461
73730
  // src/plugin/event.ts
73462
- function isRecord8(value) {
73731
+ function isRecord9(value) {
73463
73732
  return typeof value === "object" && value !== null;
73464
73733
  }
73465
73734
  function normalizeFallbackModelID(modelID) {
73466
73735
  return modelID.replace(/-thinking$/i, "").replace(/-max$/i, "").replace(/-high$/i, "");
73467
73736
  }
73468
- function extractErrorName2(error45) {
73469
- if (isRecord8(error45) && typeof error45.name === "string")
73737
+ function extractErrorName3(error45) {
73738
+ if (isRecord9(error45) && typeof error45.name === "string")
73470
73739
  return error45.name;
73471
73740
  if (error45 instanceof Error)
73472
73741
  return error45.name;
73473
73742
  return;
73474
73743
  }
73475
- function extractErrorMessage(error45) {
73744
+ function extractErrorMessage2(error45) {
73476
73745
  if (!error45)
73477
73746
  return "";
73478
73747
  if (typeof error45 === "string")
73479
73748
  return error45;
73480
73749
  if (error45 instanceof Error)
73481
73750
  return error45.message;
73482
- if (isRecord8(error45)) {
73751
+ if (isRecord9(error45)) {
73483
73752
  const candidates = [
73484
73753
  error45,
73485
73754
  error45.data,
73486
73755
  error45.error,
73487
- isRecord8(error45.data) ? error45.data.error : undefined,
73756
+ isRecord9(error45.data) ? error45.data.error : undefined,
73488
73757
  error45.cause
73489
73758
  ];
73490
73759
  for (const candidate of candidates) {
73491
- if (isRecord8(candidate) && typeof candidate.message === "string" && candidate.message.length > 0) {
73760
+ if (isRecord9(candidate) && typeof candidate.message === "string" && candidate.message.length > 0) {
73492
73761
  return candidate.message;
73493
73762
  }
73494
73763
  }
@@ -73645,8 +73914,8 @@ function createEventHandler2(args) {
73645
73914
  if (lastHandled === assistantMessageID) {
73646
73915
  return;
73647
73916
  }
73648
- const errorName = extractErrorName2(assistantError);
73649
- const errorMessage = extractErrorMessage(assistantError);
73917
+ const errorName = extractErrorName3(assistantError);
73918
+ const errorMessage = extractErrorMessage2(assistantError);
73650
73919
  const errorInfo = { name: errorName, message: errorMessage };
73651
73920
  if (shouldRetryError(errorInfo)) {
73652
73921
  let agentName = agent ?? getSessionAgent(sessionID);
@@ -73730,8 +73999,8 @@ function createEventHandler2(args) {
73730
73999
  try {
73731
74000
  const sessionID = props?.sessionID;
73732
74001
  const error45 = props?.error;
73733
- const errorName = extractErrorName2(error45);
73734
- const errorMessage = extractErrorMessage(error45);
74002
+ const errorName = extractErrorName3(error45);
74003
+ const errorMessage = extractErrorMessage2(error45);
73735
74004
  const errorInfo = { name: errorName, message: errorMessage };
73736
74005
  if (hooks2.sessionRecovery?.isRecoverableError(error45)) {
73737
74006
  const messageInfo = {