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/agents/dynamic-agent-prompt-builder.d.ts +1 -0
- package/dist/cli/index.js +8 -8
- package/dist/config/schema/categories.d.ts +1 -1
- package/dist/features/background-agent/compaction-aware-message-resolver.d.ts +3 -0
- package/dist/features/background-agent/error-classifier.d.ts +8 -0
- package/dist/features/background-agent/fallback-retry-handler.d.ts +16 -0
- package/dist/features/background-agent/manager.d.ts +0 -8
- package/dist/features/background-agent/process-cleanup.d.ts +8 -0
- package/dist/index.js +877 -608
- package/dist/tools/hashline-edit/edit-operations.d.ts +8 -0
- package/dist/tools/hashline-edit/edit-text-normalization.d.ts +7 -0
- package/dist/tools/hashline-edit/hash-computation.d.ts +7 -0
- package/dist/tools/hashline-edit/hashline-chunk-formatter.d.ts +10 -0
- package/dist/tools/hashline-edit/index.d.ts +3 -3
- package/dist/tools/hashline-edit/tool-description.d.ts +1 -0
- package/dist/tools/hashline-edit/types.d.ts +12 -1
- package/dist/tools/hashline-edit/validation.d.ts +12 -0
- package/package.json +8 -8
- package/dist/features/background-agent/notification-builder.d.ts +0 -8
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
|
|
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
|
-
|
|
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(
|
|
56733
|
+
mismatches.push({ line, expected: hash2 });
|
|
56683
56734
|
}
|
|
56684
56735
|
}
|
|
56685
56736
|
if (mismatches.length > 0) {
|
|
56686
|
-
throw new
|
|
56687
|
-
- ${mismatches.join(`
|
|
56688
|
-
- `)}`);
|
|
56737
|
+
throw new HashlineMismatchError(mismatches, lines);
|
|
56689
56738
|
}
|
|
56690
56739
|
}
|
|
56691
|
-
// src/tools/hashline-edit/edit-
|
|
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
|
|
56724
|
-
if (
|
|
56725
|
-
return
|
|
56726
|
-
|
|
56727
|
-
|
|
56728
|
-
|
|
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
|
|
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
|
-
|
|
56767
|
-
|
|
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
|
|
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
|
|
56964
|
+
return {
|
|
56965
|
+
content,
|
|
56966
|
+
noopEdits: 0,
|
|
56967
|
+
deduplicatedEdits: 0
|
|
56968
|
+
};
|
|
56828
56969
|
}
|
|
56829
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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 (
|
|
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
|
|
57206
|
+
const applyResult = applyHashlineEditsWithReport(oldContent, edits);
|
|
57207
|
+
const newContent = applyResult.content;
|
|
57016
57208
|
await Bun.write(filePath, newContent);
|
|
57017
|
-
|
|
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,
|
|
57216
|
+
const unifiedDiff = generateUnifiedDiff(oldContent, newContent, effectivePath);
|
|
57020
57217
|
const { additions, deletions } = countLineDiffs(oldContent, newContent);
|
|
57021
57218
|
const meta = {
|
|
57022
|
-
title:
|
|
57219
|
+
title: effectivePath,
|
|
57023
57220
|
metadata: {
|
|
57024
|
-
filePath,
|
|
57025
|
-
path:
|
|
57026
|
-
file:
|
|
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:
|
|
57030
|
-
path:
|
|
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 ${
|
|
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
|
-
|
|
57711
|
-
|
|
57712
|
-
|
|
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:
|
|
58223
|
-
message:
|
|
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 ?
|
|
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
|
|
58416
|
-
const
|
|
58417
|
-
|
|
58418
|
-
|
|
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
|
-
|
|
58454
|
-
|
|
58455
|
-
|
|
58456
|
-
|
|
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 (
|
|
58459
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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 =
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58965
|
-
|
|
58966
|
-
|
|
58967
|
-
|
|
58968
|
-
|
|
58969
|
-
|
|
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
|
-
|
|
59028
|
-
|
|
59029
|
-
|
|
59030
|
-
|
|
59031
|
-
|
|
59032
|
-
|
|
59033
|
-
|
|
59034
|
-
|
|
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
|
|
63269
|
-
import { dirname as dirname21, join as
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
71424
|
-
import { join as
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
71907
|
-
import { join as
|
|
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:
|
|
71950
|
-
{ path:
|
|
71951
|
-
{ path:
|
|
71952
|
-
{ path:
|
|
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 (!
|
|
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 (!
|
|
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
|
|
72417
|
+
import { existsSync as existsSync67, readFileSync as readFileSync47 } from "fs";
|
|
72149
72418
|
import { homedir as homedir15 } from "os";
|
|
72150
|
-
import { join as
|
|
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
|
|
72424
|
+
return join80(homedir15(), ".claude", "plugins");
|
|
72156
72425
|
}
|
|
72157
72426
|
function getInstalledPluginsPath() {
|
|
72158
|
-
return
|
|
72427
|
+
return join80(getPluginsBaseDir(), "installed_plugins.json");
|
|
72159
72428
|
}
|
|
72160
72429
|
function loadInstalledPlugins() {
|
|
72161
72430
|
const dbPath = getInstalledPluginsPath();
|
|
72162
|
-
if (!
|
|
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
|
|
72446
|
+
return join80(homedir15(), ".claude", "settings.json");
|
|
72178
72447
|
}
|
|
72179
72448
|
function loadClaudeSettings() {
|
|
72180
72449
|
const settingsPath = getClaudeSettingsPath();
|
|
72181
|
-
if (!
|
|
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 =
|
|
72194
|
-
if (!
|
|
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 (!
|
|
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 (
|
|
72261
|
-
loadedPlugin.commandsDir =
|
|
72529
|
+
if (existsSync67(join80(installPath, "commands"))) {
|
|
72530
|
+
loadedPlugin.commandsDir = join80(installPath, "commands");
|
|
72262
72531
|
}
|
|
72263
|
-
if (
|
|
72264
|
-
loadedPlugin.agentsDir =
|
|
72532
|
+
if (existsSync67(join80(installPath, "agents"))) {
|
|
72533
|
+
loadedPlugin.agentsDir = join80(installPath, "agents");
|
|
72265
72534
|
}
|
|
72266
|
-
if (
|
|
72267
|
-
loadedPlugin.skillsDir =
|
|
72535
|
+
if (existsSync67(join80(installPath, "skills"))) {
|
|
72536
|
+
loadedPlugin.skillsDir = join80(installPath, "skills");
|
|
72268
72537
|
}
|
|
72269
|
-
const hooksPath =
|
|
72270
|
-
if (
|
|
72538
|
+
const hooksPath = join80(installPath, "hooks", "hooks.json");
|
|
72539
|
+
if (existsSync67(hooksPath)) {
|
|
72271
72540
|
loadedPlugin.hooksPath = hooksPath;
|
|
72272
72541
|
}
|
|
72273
|
-
const mcpPath =
|
|
72274
|
-
if (
|
|
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
|
|
72288
|
-
import { basename as basename10, join as
|
|
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 || !
|
|
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 =
|
|
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
|
|
72335
|
-
import { join as
|
|
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 || !
|
|
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 =
|
|
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 =
|
|
72351
|
-
if (!
|
|
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
|
|
72390
|
-
import { basename as basename11, join as
|
|
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 || !
|
|
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 =
|
|
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
|
|
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 || !
|
|
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
|
|
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 || !
|
|
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
|
|
73205
|
+
function isRecord7(value) {
|
|
72937
73206
|
return typeof value === "object" && value !== null;
|
|
72938
73207
|
}
|
|
72939
73208
|
function buildChatParamsInput(raw) {
|
|
72940
|
-
if (!
|
|
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 (!
|
|
73218
|
+
if (!isRecord7(model))
|
|
72950
73219
|
return null;
|
|
72951
|
-
if (!
|
|
73220
|
+
if (!isRecord7(provider))
|
|
72952
73221
|
return null;
|
|
72953
|
-
if (!
|
|
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 (
|
|
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 (!
|
|
73254
|
+
if (!isRecord7(raw))
|
|
72986
73255
|
return false;
|
|
72987
|
-
if (!
|
|
73256
|
+
if (!isRecord7(raw.options)) {
|
|
72988
73257
|
raw.options = {};
|
|
72989
73258
|
}
|
|
72990
|
-
return
|
|
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
|
|
73275
|
+
function isRecord8(value) {
|
|
73007
73276
|
return typeof value === "object" && value !== null;
|
|
73008
73277
|
}
|
|
73009
73278
|
function buildChatHeadersInput(raw) {
|
|
73010
|
-
if (!
|
|
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 (!
|
|
73286
|
+
if (!isRecord8(provider) || typeof provider.id !== "string")
|
|
73018
73287
|
return null;
|
|
73019
|
-
if (!
|
|
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 (!
|
|
73300
|
+
if (!isRecord8(raw))
|
|
73032
73301
|
return false;
|
|
73033
|
-
if (!
|
|
73302
|
+
if (!isRecord8(raw.headers)) {
|
|
73034
73303
|
raw.headers = {};
|
|
73035
73304
|
}
|
|
73036
|
-
return
|
|
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 (!
|
|
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 (!
|
|
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
|
|
73115
|
-
import { existsSync as
|
|
73383
|
+
import { join as join84 } from "path";
|
|
73384
|
+
import { existsSync as existsSync73 } from "fs";
|
|
73116
73385
|
function getDbPath() {
|
|
73117
|
-
return
|
|
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 (!
|
|
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
|
|
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
|
|
73469
|
-
if (
|
|
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
|
|
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 (
|
|
73751
|
+
if (isRecord9(error45)) {
|
|
73483
73752
|
const candidates = [
|
|
73484
73753
|
error45,
|
|
73485
73754
|
error45.data,
|
|
73486
73755
|
error45.error,
|
|
73487
|
-
|
|
73756
|
+
isRecord9(error45.data) ? error45.data.error : undefined,
|
|
73488
73757
|
error45.cause
|
|
73489
73758
|
];
|
|
73490
73759
|
for (const candidate of candidates) {
|
|
73491
|
-
if (
|
|
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 =
|
|
73649
|
-
const errorMessage =
|
|
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 =
|
|
73734
|
-
const errorMessage =
|
|
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 = {
|