oh-my-opencode-slim 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja-JP.md +13 -12
- package/README.ko-KR.md +13 -12
- package/README.md +29 -16
- package/README.zh-CN.md +13 -12
- package/dist/agents/index.d.ts +0 -4
- package/dist/cli/index.js +49 -6
- package/dist/config/constants.d.ts +10 -0
- package/dist/config/schema.d.ts +680 -2
- package/dist/config/strip-orchestrator-model.d.ts +9 -0
- package/dist/hooks/command-hook-utils.d.ts +5 -0
- package/dist/hooks/foreground-fallback/index.d.ts +40 -28
- package/dist/hooks/image-hook.d.ts +1 -0
- package/dist/hooks/index.d.ts +1 -1
- package/dist/index.js +1871 -326
- package/dist/interview/document.d.ts +2 -1
- package/dist/interview/ui.d.ts +0 -1
- package/dist/multiplexer/cmux/close-policy.d.ts +20 -0
- package/dist/multiplexer/cmux/index.d.ts +102 -0
- package/dist/multiplexer/cmux/session-lifecycle.d.ts +92 -0
- package/dist/multiplexer/cmux/session-state.d.ts +45 -0
- package/dist/multiplexer/factory.d.ts +0 -9
- package/dist/multiplexer/herdr/index.d.ts +2 -0
- package/dist/multiplexer/index.d.ts +3 -1
- package/dist/multiplexer/session-manager.d.ts +10 -2
- package/dist/multiplexer/shared.d.ts +8 -1
- package/dist/multiplexer/types.d.ts +5 -2
- package/dist/tools/smartfetch/utils.d.ts +3 -1
- package/dist/tui-state.d.ts +5 -5
- package/dist/tui.js +86 -20
- package/dist/utils/escape-html.d.ts +1 -0
- package/dist/utils/frontmatter.d.ts +6 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/logger.d.ts +0 -2
- package/oh-my-opencode-slim.schema.json +717 -2
- package/package.json +1 -1
- package/src/skills/clonedeps/SKILL.md +32 -29
- package/src/skills/codemap.md +7 -3
- package/src/skills/deepwork/SKILL.md +17 -6
- package/src/skills/verification-planning/SKILL.md +103 -0
- package/src/skills/worktrees/SKILL.md +14 -7
package/dist/index.js
CHANGED
|
@@ -184,7 +184,7 @@ var init_zip_extractor = __esm(() => {
|
|
|
184
184
|
init_compat();
|
|
185
185
|
});
|
|
186
186
|
|
|
187
|
-
// node_modules/@mozilla/readability/Readability.js
|
|
187
|
+
// node_modules/.pnpm/@mozilla+readability@0.6.0/node_modules/@mozilla/readability/Readability.js
|
|
188
188
|
var require_Readability = __commonJS((exports, module) => {
|
|
189
189
|
function Readability(doc, options) {
|
|
190
190
|
if (options && options.documentElement) {
|
|
@@ -1755,7 +1755,7 @@ var require_Readability = __commonJS((exports, module) => {
|
|
|
1755
1755
|
}
|
|
1756
1756
|
});
|
|
1757
1757
|
|
|
1758
|
-
// node_modules/@mozilla/readability/Readability-readerable.js
|
|
1758
|
+
// node_modules/.pnpm/@mozilla+readability@0.6.0/node_modules/@mozilla/readability/Readability-readerable.js
|
|
1759
1759
|
var require_Readability_readerable = __commonJS((exports, module) => {
|
|
1760
1760
|
var REGEXPS = {
|
|
1761
1761
|
unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i,
|
|
@@ -1811,7 +1811,7 @@ var require_Readability_readerable = __commonJS((exports, module) => {
|
|
|
1811
1811
|
}
|
|
1812
1812
|
});
|
|
1813
1813
|
|
|
1814
|
-
// node_modules/@mozilla/readability/index.js
|
|
1814
|
+
// node_modules/.pnpm/@mozilla+readability@0.6.0/node_modules/@mozilla/readability/index.js
|
|
1815
1815
|
var require_readability = __commonJS((exports, module) => {
|
|
1816
1816
|
var Readability = require_Readability();
|
|
1817
1817
|
var isProbablyReaderable = require_Readability_readerable();
|
|
@@ -1821,7 +1821,7 @@ var require_readability = __commonJS((exports, module) => {
|
|
|
1821
1821
|
};
|
|
1822
1822
|
});
|
|
1823
1823
|
|
|
1824
|
-
// node_modules/@mixmark-io/domino/lib/Event.js
|
|
1824
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Event.js
|
|
1825
1825
|
var require_Event = __commonJS((exports, module) => {
|
|
1826
1826
|
module.exports = Event;
|
|
1827
1827
|
Event.CAPTURING_PHASE = 1;
|
|
@@ -1878,7 +1878,7 @@ var require_Event = __commonJS((exports, module) => {
|
|
|
1878
1878
|
});
|
|
1879
1879
|
});
|
|
1880
1880
|
|
|
1881
|
-
// node_modules/@mixmark-io/domino/lib/UIEvent.js
|
|
1881
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/UIEvent.js
|
|
1882
1882
|
var require_UIEvent = __commonJS((exports, module) => {
|
|
1883
1883
|
var Event = require_Event();
|
|
1884
1884
|
module.exports = UIEvent;
|
|
@@ -1897,7 +1897,7 @@ var require_UIEvent = __commonJS((exports, module) => {
|
|
|
1897
1897
|
});
|
|
1898
1898
|
});
|
|
1899
1899
|
|
|
1900
|
-
// node_modules/@mixmark-io/domino/lib/MouseEvent.js
|
|
1900
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/MouseEvent.js
|
|
1901
1901
|
var require_MouseEvent = __commonJS((exports, module) => {
|
|
1902
1902
|
var UIEvent = require_UIEvent();
|
|
1903
1903
|
module.exports = MouseEvent;
|
|
@@ -1955,7 +1955,7 @@ var require_MouseEvent = __commonJS((exports, module) => {
|
|
|
1955
1955
|
});
|
|
1956
1956
|
});
|
|
1957
1957
|
|
|
1958
|
-
// node_modules/@mixmark-io/domino/lib/DOMException.js
|
|
1958
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/DOMException.js
|
|
1959
1959
|
var require_DOMException = __commonJS((exports, module) => {
|
|
1960
1960
|
module.exports = DOMException;
|
|
1961
1961
|
var INDEX_SIZE_ERR = 1;
|
|
@@ -2079,12 +2079,12 @@ var require_DOMException = __commonJS((exports, module) => {
|
|
|
2079
2079
|
var c;
|
|
2080
2080
|
});
|
|
2081
2081
|
|
|
2082
|
-
// node_modules/@mixmark-io/domino/lib/config.js
|
|
2082
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/config.js
|
|
2083
2083
|
var require_config = __commonJS((exports) => {
|
|
2084
2084
|
exports.isApiWritable = !globalThis.__domino_frozen__;
|
|
2085
2085
|
});
|
|
2086
2086
|
|
|
2087
|
-
// node_modules/@mixmark-io/domino/lib/utils.js
|
|
2087
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/utils.js
|
|
2088
2088
|
var require_utils = __commonJS((exports) => {
|
|
2089
2089
|
var DOMException = require_DOMException();
|
|
2090
2090
|
var ERR = DOMException;
|
|
@@ -2197,7 +2197,7 @@ var require_utils = __commonJS((exports) => {
|
|
|
2197
2197
|
};
|
|
2198
2198
|
});
|
|
2199
2199
|
|
|
2200
|
-
// node_modules/@mixmark-io/domino/lib/EventTarget.js
|
|
2200
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/EventTarget.js
|
|
2201
2201
|
var require_EventTarget = __commonJS((exports, module) => {
|
|
2202
2202
|
var Event = require_Event();
|
|
2203
2203
|
var MouseEvent = require_MouseEvent();
|
|
@@ -2385,7 +2385,7 @@ var require_EventTarget = __commonJS((exports, module) => {
|
|
|
2385
2385
|
};
|
|
2386
2386
|
});
|
|
2387
2387
|
|
|
2388
|
-
// node_modules/@mixmark-io/domino/lib/LinkedList.js
|
|
2388
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/LinkedList.js
|
|
2389
2389
|
var require_LinkedList = __commonJS((exports, module) => {
|
|
2390
2390
|
var utils = require_utils();
|
|
2391
2391
|
var LinkedList = module.exports = {
|
|
@@ -2428,7 +2428,7 @@ var require_LinkedList = __commonJS((exports, module) => {
|
|
|
2428
2428
|
};
|
|
2429
2429
|
});
|
|
2430
2430
|
|
|
2431
|
-
// node_modules/@mixmark-io/domino/lib/NodeUtils.js
|
|
2431
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NodeUtils.js
|
|
2432
2432
|
var require_NodeUtils = __commonJS((exports, module) => {
|
|
2433
2433
|
module.exports = {
|
|
2434
2434
|
serializeOne,
|
|
@@ -2604,7 +2604,7 @@ var require_NodeUtils = __commonJS((exports, module) => {
|
|
|
2604
2604
|
}
|
|
2605
2605
|
});
|
|
2606
2606
|
|
|
2607
|
-
// node_modules/@mixmark-io/domino/lib/Node.js
|
|
2607
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Node.js
|
|
2608
2608
|
var require_Node = __commonJS((exports, module) => {
|
|
2609
2609
|
module.exports = Node;
|
|
2610
2610
|
var EventTarget = require_EventTarget();
|
|
@@ -3165,7 +3165,7 @@ var require_Node = __commonJS((exports, module) => {
|
|
|
3165
3165
|
});
|
|
3166
3166
|
});
|
|
3167
3167
|
|
|
3168
|
-
// node_modules/@mixmark-io/domino/lib/NodeList.es6.js
|
|
3168
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NodeList.es6.js
|
|
3169
3169
|
var require_NodeList_es6 = __commonJS((exports, module) => {
|
|
3170
3170
|
module.exports = class NodeList extends Array {
|
|
3171
3171
|
constructor(a) {
|
|
@@ -3182,7 +3182,7 @@ var require_NodeList_es6 = __commonJS((exports, module) => {
|
|
|
3182
3182
|
};
|
|
3183
3183
|
});
|
|
3184
3184
|
|
|
3185
|
-
// node_modules/@mixmark-io/domino/lib/NodeList.es5.js
|
|
3185
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NodeList.es5.js
|
|
3186
3186
|
var require_NodeList_es5 = __commonJS((exports, module) => {
|
|
3187
3187
|
function item(i) {
|
|
3188
3188
|
return this[i] || null;
|
|
@@ -3196,7 +3196,7 @@ var require_NodeList_es5 = __commonJS((exports, module) => {
|
|
|
3196
3196
|
module.exports = NodeList;
|
|
3197
3197
|
});
|
|
3198
3198
|
|
|
3199
|
-
// node_modules/@mixmark-io/domino/lib/NodeList.js
|
|
3199
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NodeList.js
|
|
3200
3200
|
var require_NodeList = __commonJS((exports, module) => {
|
|
3201
3201
|
var NodeList;
|
|
3202
3202
|
try {
|
|
@@ -3207,7 +3207,7 @@ var require_NodeList = __commonJS((exports, module) => {
|
|
|
3207
3207
|
module.exports = NodeList;
|
|
3208
3208
|
});
|
|
3209
3209
|
|
|
3210
|
-
// node_modules/@mixmark-io/domino/lib/ContainerNode.js
|
|
3210
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/ContainerNode.js
|
|
3211
3211
|
var require_ContainerNode = __commonJS((exports, module) => {
|
|
3212
3212
|
module.exports = ContainerNode;
|
|
3213
3213
|
var Node = require_Node();
|
|
@@ -3275,7 +3275,7 @@ var require_ContainerNode = __commonJS((exports, module) => {
|
|
|
3275
3275
|
});
|
|
3276
3276
|
});
|
|
3277
3277
|
|
|
3278
|
-
// node_modules/@mixmark-io/domino/lib/xmlnames.js
|
|
3278
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/xmlnames.js
|
|
3279
3279
|
var require_xmlnames = __commonJS((exports) => {
|
|
3280
3280
|
exports.isValidName = isValidName;
|
|
3281
3281
|
exports.isValidQName = isValidQName;
|
|
@@ -3324,7 +3324,7 @@ var require_xmlnames = __commonJS((exports) => {
|
|
|
3324
3324
|
}
|
|
3325
3325
|
});
|
|
3326
3326
|
|
|
3327
|
-
// node_modules/@mixmark-io/domino/lib/attributes.js
|
|
3327
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/attributes.js
|
|
3328
3328
|
var require_attributes = __commonJS((exports) => {
|
|
3329
3329
|
var utils = require_utils();
|
|
3330
3330
|
exports.property = function(attr) {
|
|
@@ -3460,7 +3460,7 @@ var require_attributes = __commonJS((exports) => {
|
|
|
3460
3460
|
};
|
|
3461
3461
|
});
|
|
3462
3462
|
|
|
3463
|
-
// node_modules/@mixmark-io/domino/lib/FilteredElementList.js
|
|
3463
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/FilteredElementList.js
|
|
3464
3464
|
var require_FilteredElementList = __commonJS((exports, module) => {
|
|
3465
3465
|
module.exports = FilteredElementList;
|
|
3466
3466
|
var Node = require_Node();
|
|
@@ -3526,7 +3526,7 @@ var require_FilteredElementList = __commonJS((exports, module) => {
|
|
|
3526
3526
|
});
|
|
3527
3527
|
});
|
|
3528
3528
|
|
|
3529
|
-
// node_modules/@mixmark-io/domino/lib/DOMTokenList.js
|
|
3529
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/DOMTokenList.js
|
|
3530
3530
|
var require_DOMTokenList = __commonJS((exports, module) => {
|
|
3531
3531
|
var utils = require_utils();
|
|
3532
3532
|
module.exports = DOMTokenList;
|
|
@@ -3688,7 +3688,7 @@ var require_DOMTokenList = __commonJS((exports, module) => {
|
|
|
3688
3688
|
}
|
|
3689
3689
|
});
|
|
3690
3690
|
|
|
3691
|
-
// node_modules/@mixmark-io/domino/lib/select.js
|
|
3691
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/select.js
|
|
3692
3692
|
var require_select = __commonJS((exports, module) => {
|
|
3693
3693
|
var window2 = Object.create(null, {
|
|
3694
3694
|
location: { get: function() {
|
|
@@ -4431,7 +4431,7 @@ var require_select = __commonJS((exports, module) => {
|
|
|
4431
4431
|
};
|
|
4432
4432
|
});
|
|
4433
4433
|
|
|
4434
|
-
// node_modules/@mixmark-io/domino/lib/ChildNode.js
|
|
4434
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/ChildNode.js
|
|
4435
4435
|
var require_ChildNode = __commonJS((exports, module) => {
|
|
4436
4436
|
var Node = require_Node();
|
|
4437
4437
|
var LinkedList = require_LinkedList();
|
|
@@ -4521,7 +4521,7 @@ var require_ChildNode = __commonJS((exports, module) => {
|
|
|
4521
4521
|
module.exports = ChildNode;
|
|
4522
4522
|
});
|
|
4523
4523
|
|
|
4524
|
-
// node_modules/@mixmark-io/domino/lib/NonDocumentTypeChildNode.js
|
|
4524
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NonDocumentTypeChildNode.js
|
|
4525
4525
|
var require_NonDocumentTypeChildNode = __commonJS((exports, module) => {
|
|
4526
4526
|
var Node = require_Node();
|
|
4527
4527
|
var NonDocumentTypeChildNode = {
|
|
@@ -4547,7 +4547,7 @@ var require_NonDocumentTypeChildNode = __commonJS((exports, module) => {
|
|
|
4547
4547
|
module.exports = NonDocumentTypeChildNode;
|
|
4548
4548
|
});
|
|
4549
4549
|
|
|
4550
|
-
// node_modules/@mixmark-io/domino/lib/NamedNodeMap.js
|
|
4550
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NamedNodeMap.js
|
|
4551
4551
|
var require_NamedNodeMap = __commonJS((exports, module) => {
|
|
4552
4552
|
module.exports = NamedNodeMap;
|
|
4553
4553
|
var utils = require_utils();
|
|
@@ -4584,7 +4584,7 @@ var require_NamedNodeMap = __commonJS((exports, module) => {
|
|
|
4584
4584
|
});
|
|
4585
4585
|
});
|
|
4586
4586
|
|
|
4587
|
-
// node_modules/@mixmark-io/domino/lib/Element.js
|
|
4587
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Element.js
|
|
4588
4588
|
var require_Element = __commonJS((exports, module) => {
|
|
4589
4589
|
module.exports = Element;
|
|
4590
4590
|
var xml = require_xmlnames();
|
|
@@ -5485,7 +5485,7 @@ var require_Element = __commonJS((exports, module) => {
|
|
|
5485
5485
|
}
|
|
5486
5486
|
});
|
|
5487
5487
|
|
|
5488
|
-
// node_modules/@mixmark-io/domino/lib/Leaf.js
|
|
5488
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Leaf.js
|
|
5489
5489
|
var require_Leaf = __commonJS((exports, module) => {
|
|
5490
5490
|
module.exports = Leaf;
|
|
5491
5491
|
var Node = require_Node();
|
|
@@ -5526,7 +5526,7 @@ var require_Leaf = __commonJS((exports, module) => {
|
|
|
5526
5526
|
});
|
|
5527
5527
|
});
|
|
5528
5528
|
|
|
5529
|
-
// node_modules/@mixmark-io/domino/lib/CharacterData.js
|
|
5529
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/CharacterData.js
|
|
5530
5530
|
var require_CharacterData = __commonJS((exports, module) => {
|
|
5531
5531
|
module.exports = CharacterData;
|
|
5532
5532
|
var Leaf = require_Leaf();
|
|
@@ -5583,7 +5583,7 @@ var require_CharacterData = __commonJS((exports, module) => {
|
|
|
5583
5583
|
Object.defineProperties(CharacterData.prototype, NonDocumentTypeChildNode);
|
|
5584
5584
|
});
|
|
5585
5585
|
|
|
5586
|
-
// node_modules/@mixmark-io/domino/lib/Text.js
|
|
5586
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Text.js
|
|
5587
5587
|
var require_Text = __commonJS((exports, module) => {
|
|
5588
5588
|
module.exports = Text;
|
|
5589
5589
|
var utils = require_utils();
|
|
@@ -5653,7 +5653,7 @@ var require_Text = __commonJS((exports, module) => {
|
|
|
5653
5653
|
});
|
|
5654
5654
|
});
|
|
5655
5655
|
|
|
5656
|
-
// node_modules/@mixmark-io/domino/lib/Comment.js
|
|
5656
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Comment.js
|
|
5657
5657
|
var require_Comment = __commonJS((exports, module) => {
|
|
5658
5658
|
module.exports = Comment;
|
|
5659
5659
|
var Node = require_Node();
|
|
@@ -5696,7 +5696,7 @@ var require_Comment = __commonJS((exports, module) => {
|
|
|
5696
5696
|
});
|
|
5697
5697
|
});
|
|
5698
5698
|
|
|
5699
|
-
// node_modules/@mixmark-io/domino/lib/DocumentFragment.js
|
|
5699
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/DocumentFragment.js
|
|
5700
5700
|
var require_DocumentFragment = __commonJS((exports, module) => {
|
|
5701
5701
|
module.exports = DocumentFragment;
|
|
5702
5702
|
var Node = require_Node();
|
|
@@ -5753,7 +5753,7 @@ var require_DocumentFragment = __commonJS((exports, module) => {
|
|
|
5753
5753
|
});
|
|
5754
5754
|
});
|
|
5755
5755
|
|
|
5756
|
-
// node_modules/@mixmark-io/domino/lib/ProcessingInstruction.js
|
|
5756
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/ProcessingInstruction.js
|
|
5757
5757
|
var require_ProcessingInstruction = __commonJS((exports, module) => {
|
|
5758
5758
|
module.exports = ProcessingInstruction;
|
|
5759
5759
|
var Node = require_Node();
|
|
@@ -5802,7 +5802,7 @@ var require_ProcessingInstruction = __commonJS((exports, module) => {
|
|
|
5802
5802
|
});
|
|
5803
5803
|
});
|
|
5804
5804
|
|
|
5805
|
-
// node_modules/@mixmark-io/domino/lib/NodeFilter.js
|
|
5805
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NodeFilter.js
|
|
5806
5806
|
var require_NodeFilter = __commonJS((exports, module) => {
|
|
5807
5807
|
var NodeFilter = {
|
|
5808
5808
|
FILTER_ACCEPT: 1,
|
|
@@ -5825,7 +5825,7 @@ var require_NodeFilter = __commonJS((exports, module) => {
|
|
|
5825
5825
|
module.exports = NodeFilter.constructor = NodeFilter.prototype = NodeFilter;
|
|
5826
5826
|
});
|
|
5827
5827
|
|
|
5828
|
-
// node_modules/@mixmark-io/domino/lib/NodeTraversal.js
|
|
5828
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NodeTraversal.js
|
|
5829
5829
|
var require_NodeTraversal = __commonJS((exports, module) => {
|
|
5830
5830
|
var NodeTraversal = module.exports = {
|
|
5831
5831
|
nextSkippingChildren,
|
|
@@ -5889,7 +5889,7 @@ var require_NodeTraversal = __commonJS((exports, module) => {
|
|
|
5889
5889
|
}
|
|
5890
5890
|
});
|
|
5891
5891
|
|
|
5892
|
-
// node_modules/@mixmark-io/domino/lib/TreeWalker.js
|
|
5892
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/TreeWalker.js
|
|
5893
5893
|
var require_TreeWalker = __commonJS((exports, module) => {
|
|
5894
5894
|
module.exports = TreeWalker;
|
|
5895
5895
|
var Node = require_Node();
|
|
@@ -6119,7 +6119,7 @@ var require_TreeWalker = __commonJS((exports, module) => {
|
|
|
6119
6119
|
});
|
|
6120
6120
|
});
|
|
6121
6121
|
|
|
6122
|
-
// node_modules/@mixmark-io/domino/lib/NodeIterator.js
|
|
6122
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NodeIterator.js
|
|
6123
6123
|
var require_NodeIterator = __commonJS((exports, module) => {
|
|
6124
6124
|
module.exports = NodeIterator;
|
|
6125
6125
|
var NodeFilter = require_NodeFilter();
|
|
@@ -6260,7 +6260,7 @@ var require_NodeIterator = __commonJS((exports, module) => {
|
|
|
6260
6260
|
});
|
|
6261
6261
|
});
|
|
6262
6262
|
|
|
6263
|
-
// node_modules/@mixmark-io/domino/lib/URL.js
|
|
6263
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/URL.js
|
|
6264
6264
|
var require_URL = __commonJS((exports, module) => {
|
|
6265
6265
|
module.exports = URL4;
|
|
6266
6266
|
function URL4(url) {
|
|
@@ -6433,7 +6433,7 @@ var require_URL = __commonJS((exports, module) => {
|
|
|
6433
6433
|
};
|
|
6434
6434
|
});
|
|
6435
6435
|
|
|
6436
|
-
// node_modules/@mixmark-io/domino/lib/CustomEvent.js
|
|
6436
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/CustomEvent.js
|
|
6437
6437
|
var require_CustomEvent = __commonJS((exports, module) => {
|
|
6438
6438
|
module.exports = CustomEvent;
|
|
6439
6439
|
var Event = require_Event();
|
|
@@ -6445,7 +6445,7 @@ var require_CustomEvent = __commonJS((exports, module) => {
|
|
|
6445
6445
|
});
|
|
6446
6446
|
});
|
|
6447
6447
|
|
|
6448
|
-
// node_modules/@mixmark-io/domino/lib/events.js
|
|
6448
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/events.js
|
|
6449
6449
|
var require_events = __commonJS((exports, module) => {
|
|
6450
6450
|
module.exports = {
|
|
6451
6451
|
Event: require_Event(),
|
|
@@ -6455,7 +6455,7 @@ var require_events = __commonJS((exports, module) => {
|
|
|
6455
6455
|
};
|
|
6456
6456
|
});
|
|
6457
6457
|
|
|
6458
|
-
// node_modules/@mixmark-io/domino/lib/style_parser.js
|
|
6458
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/style_parser.js
|
|
6459
6459
|
var require_style_parser = __commonJS((exports) => {
|
|
6460
6460
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6461
6461
|
exports.hyphenate = exports.parse = undefined;
|
|
@@ -6522,7 +6522,7 @@ var require_style_parser = __commonJS((exports) => {
|
|
|
6522
6522
|
exports.hyphenate = hyphenate;
|
|
6523
6523
|
});
|
|
6524
6524
|
|
|
6525
|
-
// node_modules/@mixmark-io/domino/lib/CSSStyleDeclaration.js
|
|
6525
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/CSSStyleDeclaration.js
|
|
6526
6526
|
var require_CSSStyleDeclaration = __commonJS((exports, module) => {
|
|
6527
6527
|
var { parse } = require_style_parser();
|
|
6528
6528
|
module.exports = function(elt) {
|
|
@@ -6698,7 +6698,7 @@ var require_CSSStyleDeclaration = __commonJS((exports, module) => {
|
|
|
6698
6698
|
});
|
|
6699
6699
|
});
|
|
6700
6700
|
|
|
6701
|
-
// node_modules/@mixmark-io/domino/lib/URLUtils.js
|
|
6701
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/URLUtils.js
|
|
6702
6702
|
var require_URLUtils = __commonJS((exports, module) => {
|
|
6703
6703
|
var URL4 = require_URL();
|
|
6704
6704
|
module.exports = URLUtils;
|
|
@@ -6932,7 +6932,7 @@ var require_URLUtils = __commonJS((exports, module) => {
|
|
|
6932
6932
|
};
|
|
6933
6933
|
});
|
|
6934
6934
|
|
|
6935
|
-
// node_modules/@mixmark-io/domino/lib/defineElement.js
|
|
6935
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/defineElement.js
|
|
6936
6936
|
var require_defineElement = __commonJS((exports, module) => {
|
|
6937
6937
|
var attributes = require_attributes();
|
|
6938
6938
|
var isApiWritable = require_config().isApiWritable;
|
|
@@ -6994,7 +6994,7 @@ var require_defineElement = __commonJS((exports, module) => {
|
|
|
6994
6994
|
}
|
|
6995
6995
|
});
|
|
6996
6996
|
|
|
6997
|
-
// node_modules/@mixmark-io/domino/lib/htmlelts.js
|
|
6997
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/htmlelts.js
|
|
6998
6998
|
var require_htmlelts = __commonJS((exports) => {
|
|
6999
6999
|
var Node = require_Node();
|
|
7000
7000
|
var Element = require_Element();
|
|
@@ -8458,7 +8458,7 @@ var require_htmlelts = __commonJS((exports) => {
|
|
|
8458
8458
|
});
|
|
8459
8459
|
});
|
|
8460
8460
|
|
|
8461
|
-
// node_modules/@mixmark-io/domino/lib/svg.js
|
|
8461
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/svg.js
|
|
8462
8462
|
var require_svg = __commonJS((exports) => {
|
|
8463
8463
|
var Element = require_Element();
|
|
8464
8464
|
var defineElement = require_defineElement();
|
|
@@ -8584,7 +8584,7 @@ var require_svg = __commonJS((exports) => {
|
|
|
8584
8584
|
});
|
|
8585
8585
|
});
|
|
8586
8586
|
|
|
8587
|
-
// node_modules/@mixmark-io/domino/lib/MutationConstants.js
|
|
8587
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/MutationConstants.js
|
|
8588
8588
|
var require_MutationConstants = __commonJS((exports, module) => {
|
|
8589
8589
|
module.exports = {
|
|
8590
8590
|
VALUE: 1,
|
|
@@ -8596,7 +8596,7 @@ var require_MutationConstants = __commonJS((exports, module) => {
|
|
|
8596
8596
|
};
|
|
8597
8597
|
});
|
|
8598
8598
|
|
|
8599
|
-
// node_modules/@mixmark-io/domino/lib/Document.js
|
|
8599
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Document.js
|
|
8600
8600
|
var require_Document = __commonJS((exports, module) => {
|
|
8601
8601
|
module.exports = Document;
|
|
8602
8602
|
var Node = require_Node();
|
|
@@ -9308,7 +9308,7 @@ var require_Document = __commonJS((exports, module) => {
|
|
|
9308
9308
|
};
|
|
9309
9309
|
});
|
|
9310
9310
|
|
|
9311
|
-
// node_modules/@mixmark-io/domino/lib/DocumentType.js
|
|
9311
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/DocumentType.js
|
|
9312
9312
|
var require_DocumentType = __commonJS((exports, module) => {
|
|
9313
9313
|
module.exports = DocumentType;
|
|
9314
9314
|
var Node = require_Node();
|
|
@@ -9342,7 +9342,7 @@ var require_DocumentType = __commonJS((exports, module) => {
|
|
|
9342
9342
|
Object.defineProperties(DocumentType.prototype, ChildNode);
|
|
9343
9343
|
});
|
|
9344
9344
|
|
|
9345
|
-
// node_modules/@mixmark-io/domino/lib/HTMLParser.js
|
|
9345
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/HTMLParser.js
|
|
9346
9346
|
var require_HTMLParser = __commonJS((exports, module) => {
|
|
9347
9347
|
module.exports = HTMLParser;
|
|
9348
9348
|
var Document = require_Document();
|
|
@@ -17393,7 +17393,7 @@ var require_HTMLParser = __commonJS((exports, module) => {
|
|
|
17393
17393
|
}
|
|
17394
17394
|
});
|
|
17395
17395
|
|
|
17396
|
-
// node_modules/@mixmark-io/domino/lib/DOMImplementation.js
|
|
17396
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/DOMImplementation.js
|
|
17397
17397
|
var require_DOMImplementation = __commonJS((exports, module) => {
|
|
17398
17398
|
module.exports = DOMImplementation;
|
|
17399
17399
|
var Document = require_Document();
|
|
@@ -17467,7 +17467,7 @@ var require_DOMImplementation = __commonJS((exports, module) => {
|
|
|
17467
17467
|
};
|
|
17468
17468
|
});
|
|
17469
17469
|
|
|
17470
|
-
// node_modules/@mixmark-io/domino/lib/Location.js
|
|
17470
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Location.js
|
|
17471
17471
|
var require_Location = __commonJS((exports, module) => {
|
|
17472
17472
|
var URL4 = require_URL();
|
|
17473
17473
|
var URLUtils = require_URLUtils();
|
|
@@ -17503,7 +17503,7 @@ var require_Location = __commonJS((exports, module) => {
|
|
|
17503
17503
|
});
|
|
17504
17504
|
});
|
|
17505
17505
|
|
|
17506
|
-
// node_modules/@mixmark-io/domino/lib/NavigatorID.js
|
|
17506
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NavigatorID.js
|
|
17507
17507
|
var require_NavigatorID = __commonJS((exports, module) => {
|
|
17508
17508
|
var NavigatorID = Object.create(null, {
|
|
17509
17509
|
appCodeName: { value: "Mozilla" },
|
|
@@ -17522,7 +17522,7 @@ var require_NavigatorID = __commonJS((exports, module) => {
|
|
|
17522
17522
|
module.exports = NavigatorID;
|
|
17523
17523
|
});
|
|
17524
17524
|
|
|
17525
|
-
// node_modules/@mixmark-io/domino/lib/WindowTimers.js
|
|
17525
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/WindowTimers.js
|
|
17526
17526
|
var require_WindowTimers = __commonJS((exports, module) => {
|
|
17527
17527
|
var WindowTimers = {
|
|
17528
17528
|
setTimeout,
|
|
@@ -17533,7 +17533,7 @@ var require_WindowTimers = __commonJS((exports, module) => {
|
|
|
17533
17533
|
module.exports = WindowTimers;
|
|
17534
17534
|
});
|
|
17535
17535
|
|
|
17536
|
-
// node_modules/@mixmark-io/domino/lib/impl.js
|
|
17536
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/impl.js
|
|
17537
17537
|
var require_impl = __commonJS((exports, module) => {
|
|
17538
17538
|
var utils = require_utils();
|
|
17539
17539
|
exports = module.exports = {
|
|
@@ -17561,7 +17561,7 @@ var require_impl = __commonJS((exports, module) => {
|
|
|
17561
17561
|
utils.merge(exports, require_svg().elements);
|
|
17562
17562
|
});
|
|
17563
17563
|
|
|
17564
|
-
// node_modules/@mixmark-io/domino/lib/Window.js
|
|
17564
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Window.js
|
|
17565
17565
|
var require_Window = __commonJS((exports, module) => {
|
|
17566
17566
|
var DOMImplementation = require_DOMImplementation();
|
|
17567
17567
|
var EventTarget = require_EventTarget();
|
|
@@ -17616,7 +17616,7 @@ var require_Window = __commonJS((exports, module) => {
|
|
|
17616
17616
|
utils.expose(require_impl(), Window);
|
|
17617
17617
|
});
|
|
17618
17618
|
|
|
17619
|
-
// node_modules/@mixmark-io/domino/lib/index.js
|
|
17619
|
+
// node_modules/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/index.js
|
|
17620
17620
|
var require_lib = __commonJS((exports) => {
|
|
17621
17621
|
var DOMImplementation = require_DOMImplementation();
|
|
17622
17622
|
var HTMLParser = require_HTMLParser();
|
|
@@ -17666,7 +17666,7 @@ var require_lib = __commonJS((exports) => {
|
|
|
17666
17666
|
exports.impl = impl;
|
|
17667
17667
|
});
|
|
17668
17668
|
|
|
17669
|
-
// node_modules/turndown/lib/turndown.cjs.js
|
|
17669
|
+
// node_modules/.pnpm/turndown@7.2.4/node_modules/turndown/lib/turndown.cjs.js
|
|
17670
17670
|
var require_turndown_cjs = __commonJS((exports, module) => {
|
|
17671
17671
|
function extend(destination) {
|
|
17672
17672
|
for (var i = 1;i < arguments.length; i++) {
|
|
@@ -18363,6 +18363,12 @@ var CUSTOM_SKILLS = [
|
|
|
18363
18363
|
allowedAgents: ["orchestrator"],
|
|
18364
18364
|
sourcePath: "src/skills/deepwork"
|
|
18365
18365
|
},
|
|
18366
|
+
{
|
|
18367
|
+
name: "verification-planning",
|
|
18368
|
+
description: "Plan credible, proportionate evidence before non-trivial implementation",
|
|
18369
|
+
allowedAgents: ["orchestrator"],
|
|
18370
|
+
sourcePath: "src/skills/verification-planning"
|
|
18371
|
+
},
|
|
18366
18372
|
{
|
|
18367
18373
|
name: "reflect",
|
|
18368
18374
|
description: "Review repeated work and suggest reusable workflow improvements",
|
|
@@ -18521,6 +18527,13 @@ var NO_SHELL_READONLY_FILE_OPERATIONS_RULES = `**File Operations Rules**:
|
|
|
18521
18527
|
var TMUX_SPAWN_DELAY_MS = 500;
|
|
18522
18528
|
var COUNCILLOR_STAGGER_MS = 250;
|
|
18523
18529
|
var DEFAULT_DISABLED_AGENTS = ["observer"];
|
|
18530
|
+
var DEFAULT_MAX_SESSIONS_PER_AGENT = 2;
|
|
18531
|
+
var DEFAULT_READ_CONTEXT_MIN_LINES = 10;
|
|
18532
|
+
var DEFAULT_READ_CONTEXT_MAX_FILES = 8;
|
|
18533
|
+
var DEFAULT_IMAGE_ROUTING = "auto";
|
|
18534
|
+
function resolveImageRouting(imageRouting) {
|
|
18535
|
+
return imageRouting ?? DEFAULT_IMAGE_ROUTING;
|
|
18536
|
+
}
|
|
18524
18537
|
// src/config/council-schema.ts
|
|
18525
18538
|
import { z } from "zod";
|
|
18526
18539
|
|
|
@@ -18686,6 +18699,33 @@ var ManualPlanSchema = z2.object({
|
|
|
18686
18699
|
librarian: ManualAgentPlanSchema,
|
|
18687
18700
|
fixer: ManualAgentPlanSchema
|
|
18688
18701
|
}).strict();
|
|
18702
|
+
var PermissionActionSchema = z2.enum(["ask", "allow", "deny"]);
|
|
18703
|
+
var PermissionRuleSchema = z2.union([
|
|
18704
|
+
PermissionActionSchema,
|
|
18705
|
+
z2.record(z2.string(), PermissionActionSchema)
|
|
18706
|
+
]);
|
|
18707
|
+
var PermissionObjectSchema = z2.object({
|
|
18708
|
+
read: PermissionRuleSchema.optional(),
|
|
18709
|
+
edit: PermissionRuleSchema.optional(),
|
|
18710
|
+
glob: PermissionRuleSchema.optional(),
|
|
18711
|
+
grep: PermissionRuleSchema.optional(),
|
|
18712
|
+
list: PermissionRuleSchema.optional(),
|
|
18713
|
+
bash: PermissionRuleSchema.optional(),
|
|
18714
|
+
task: PermissionRuleSchema.optional(),
|
|
18715
|
+
external_directory: PermissionRuleSchema.optional(),
|
|
18716
|
+
lsp: PermissionRuleSchema.optional(),
|
|
18717
|
+
skill: PermissionRuleSchema.optional(),
|
|
18718
|
+
todowrite: PermissionActionSchema.optional(),
|
|
18719
|
+
question: PermissionActionSchema.optional(),
|
|
18720
|
+
webfetch: PermissionActionSchema.optional(),
|
|
18721
|
+
websearch: PermissionActionSchema.optional(),
|
|
18722
|
+
codesearch: PermissionActionSchema.optional(),
|
|
18723
|
+
doom_loop: PermissionActionSchema.optional()
|
|
18724
|
+
}).catchall(PermissionRuleSchema);
|
|
18725
|
+
var PermissionConfigSchema = z2.union([
|
|
18726
|
+
PermissionActionSchema,
|
|
18727
|
+
PermissionObjectSchema
|
|
18728
|
+
]);
|
|
18689
18729
|
var AgentOverrideConfigSchema = z2.object({
|
|
18690
18730
|
model: z2.union([
|
|
18691
18731
|
z2.string(),
|
|
@@ -18704,13 +18744,15 @@ var AgentOverrideConfigSchema = z2.object({
|
|
|
18704
18744
|
prompt: z2.string().min(1).optional(),
|
|
18705
18745
|
orchestratorPrompt: z2.string().min(1).optional(),
|
|
18706
18746
|
options: z2.record(z2.string(), z2.unknown()).optional(),
|
|
18707
|
-
displayName: z2.string().min(1).optional()
|
|
18747
|
+
displayName: z2.string().min(1).optional(),
|
|
18748
|
+
permission: PermissionConfigSchema.optional()
|
|
18708
18749
|
}).strict();
|
|
18709
18750
|
var MultiplexerTypeSchema = z2.enum([
|
|
18710
18751
|
"auto",
|
|
18711
18752
|
"tmux",
|
|
18712
18753
|
"zellij",
|
|
18713
18754
|
"herdr",
|
|
18755
|
+
"cmux",
|
|
18714
18756
|
"none"
|
|
18715
18757
|
]);
|
|
18716
18758
|
var MultiplexerLayoutSchema = z2.enum([
|
|
@@ -18756,7 +18798,7 @@ var FailoverConfigSchema = z2.object({
|
|
|
18756
18798
|
retryDelayMs: z2.number().min(0).default(500),
|
|
18757
18799
|
maxRetries: z2.number().int().min(0).default(3).describe("Number of consecutive 429/rate-limit responses tolerated on the " + "same model before aborting (or swapping to the next fallback " + "model when a chain is configured)."),
|
|
18758
18800
|
retry_on_empty: z2.boolean().default(true).describe("When true (default), empty provider responses are treated as failures, " + "triggering fallback/retry. Set to false to treat them as successes."),
|
|
18759
|
-
runtimeOverride: z2.boolean().
|
|
18801
|
+
runtimeOverride: z2.boolean().optional().describe("DEPRECATED: no longer used. Previously controlled whether out-of-chain " + "runtime model picks triggered fallback. Fallback is now always " + "disabled when a user explicitly selects a model via /model.")
|
|
18760
18802
|
}).strict();
|
|
18761
18803
|
var CompanionConfigSchema = z2.object({
|
|
18762
18804
|
enabled: z2.boolean().optional(),
|
|
@@ -18798,11 +18840,13 @@ var PluginConfigSchema = z2.object({
|
|
|
18798
18840
|
preset: z2.string().optional(),
|
|
18799
18841
|
setDefaultAgent: z2.boolean().optional(),
|
|
18800
18842
|
compactSidebar: z2.boolean().optional().describe("Use the compact TUI sidebar layout. Defaults to true; set false to use the expanded layout."),
|
|
18843
|
+
stripOrchestratorModel: z2.boolean().optional().describe("When true, omit orchestrator.model and orchestrator.variant from the SDK config so OpenCode uses the session model selected with /model after subagent dispatch. An explicitly selected preset that sets orchestrator.model is preserved. Defaults to false."),
|
|
18801
18844
|
autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
|
|
18802
18845
|
presets: z2.record(z2.string(), PresetSchema).optional(),
|
|
18803
18846
|
agents: z2.record(z2.string(), AgentOverrideConfigSchema).optional(),
|
|
18804
18847
|
disabled_agents: z2.array(z2.string()).optional().describe("Agent names to disable completely. " + "Disabled agents are not instantiated and cannot be delegated to. " + "Orchestrator and council internal agents (councillor) cannot be disabled. " + "By default, 'observer' is disabled. Remove it from this list and configure a vision-capable model to enable."),
|
|
18805
|
-
|
|
18848
|
+
image_routing: z2.enum(["auto", "direct"]).optional().describe("How image attachments are handled. " + "When omitted, preserves legacy conditional behavior: intercept " + 'attachments only when observer is enabled. "auto": requires ' + "observer to be enabled and saves attachments to disk before " + 'nudging delegation to @observer. "direct": always passes ' + "attachments to the orchestrator untouched."),
|
|
18849
|
+
disabled_mcps: z2.array(z2.string()).optional().describe("MCP server names to disable completely. Disabled servers are not " + "started and cannot be used by agents."),
|
|
18806
18850
|
disabled_tools: z2.array(z2.string()).optional().describe("Tool names to disable completely. Disabled tools are not registered with OpenCode and cannot be used by agents."),
|
|
18807
18851
|
disabled_skills: z2.array(z2.string()).optional().describe("Skill names to disable completely. Disabled skills are not granted to agents, even when referenced by presets or agent overrides."),
|
|
18808
18852
|
multiplexer: MultiplexerConfigSchema.optional(),
|
|
@@ -18899,6 +18943,23 @@ function findConfigPathInDirs(configDirs, baseName) {
|
|
|
18899
18943
|
}
|
|
18900
18944
|
return null;
|
|
18901
18945
|
}
|
|
18946
|
+
function validateFinalImageRouting(config, configPath, options) {
|
|
18947
|
+
if (config.image_routing !== "auto")
|
|
18948
|
+
return true;
|
|
18949
|
+
const disabledAgents = config.disabled_agents ?? DEFAULT_DISABLED_AGENTS;
|
|
18950
|
+
if (!disabledAgents.includes("observer"))
|
|
18951
|
+
return true;
|
|
18952
|
+
const message = 'image_routing "auto" requires observer to be enabled. ' + 'Remove "observer" from disabled_agents.';
|
|
18953
|
+
options?.onWarning?.({
|
|
18954
|
+
path: configPath,
|
|
18955
|
+
kind: "invalid-schema",
|
|
18956
|
+
message
|
|
18957
|
+
});
|
|
18958
|
+
if (!options?.silent) {
|
|
18959
|
+
console.warn(`[oh-my-opencode-slim] Invalid config: ${message}`);
|
|
18960
|
+
}
|
|
18961
|
+
return false;
|
|
18962
|
+
}
|
|
18902
18963
|
function findPluginConfigPaths(directory) {
|
|
18903
18964
|
const userConfigPath = findConfigPathInDirs(getConfigSearchDirs(), "oh-my-opencode-slim");
|
|
18904
18965
|
const projectConfigBasePath = path.join(directory, ".opencode", "oh-my-opencode-slim");
|
|
@@ -18980,6 +19041,7 @@ function loadPluginConfig(directory, options) {
|
|
|
18980
19041
|
debug: config.companion.debug ?? false
|
|
18981
19042
|
};
|
|
18982
19043
|
}
|
|
19044
|
+
validateFinalImageRouting(config, projectConfigPath ?? userConfigPath ?? "", options);
|
|
18983
19045
|
return config;
|
|
18984
19046
|
}
|
|
18985
19047
|
function loadAgentPrompt(agentName, optionsOrPreset) {
|
|
@@ -19241,13 +19303,6 @@ var AGENT_DESCRIPTIONS = {
|
|
|
19241
19303
|
- **Rule of thumb:** Even if your model supports vision, delegate visual analysis to @observer - it isolates large image/PDF bytes from your context window, returning only concise structured text. Need exact file contents for routing? → Read only the minimal context yourself.
|
|
19242
19304
|
- **IMPORTANT:** When delegating to @observer, always include the **full file path** in the prompt so it can read the file. Example: "Analyze the screenshot at /path/to/file.png - describe the UI elements and error messages."`
|
|
19243
19305
|
};
|
|
19244
|
-
var VALIDATION_ROUTING = [
|
|
19245
|
-
"- Route UI/UX validation and review to @designer",
|
|
19246
|
-
"- Route code review, code simplification and maintainability review checks to @oracle",
|
|
19247
|
-
"- Route implementation to @fixer or multiple @fixer instances for maximum parallel execution",
|
|
19248
|
-
"- Route visual/media analysis and interpretation to @observer",
|
|
19249
|
-
"- If a request spans multiple lanes, delegate only the lanes that add clear value"
|
|
19250
|
-
];
|
|
19251
19306
|
var PARALLEL_DELEGATION_EXAMPLES = [
|
|
19252
19307
|
"- Multiple @explorer searches across different domains?",
|
|
19253
19308
|
"- @explorer + @librarian research in parallel?",
|
|
@@ -19257,13 +19312,6 @@ var PARALLEL_DELEGATION_EXAMPLES = [
|
|
|
19257
19312
|
function buildOrchestratorPrompt(disabledAgents) {
|
|
19258
19313
|
const enabledAgents = Object.entries(AGENT_DESCRIPTIONS).filter(([name]) => !disabledAgents?.has(name)).map(([, desc]) => desc).join(`
|
|
19259
19314
|
|
|
19260
|
-
`);
|
|
19261
|
-
const enabledValidationRouting = VALIDATION_ROUTING.filter((line) => {
|
|
19262
|
-
const mentions = [...line.matchAll(/@(\w+)/g)].map((m) => m[1]);
|
|
19263
|
-
if (mentions.length === 0)
|
|
19264
|
-
return true;
|
|
19265
|
-
return mentions.every((name) => !disabledAgents?.has(name));
|
|
19266
|
-
}).join(`
|
|
19267
19315
|
`);
|
|
19268
19316
|
const enabledParallelExamples = PARALLEL_DELEGATION_EXAMPLES.filter((line) => {
|
|
19269
19317
|
const mentions = [...line.matchAll(/@(\w+)/g)].map((m) => m[1]);
|
|
@@ -19351,16 +19399,13 @@ Balance: respect dependencies, avoid parallelizing what must be sequential, and
|
|
|
19351
19399
|
- If the Background Job Board lists \`fix-1 / ses_abc / fixer\`, call task with \`subagent_type: "fixer"\` and \`task_id: "fix-1"\` or \`task_id: "ses_abc"\`.
|
|
19352
19400
|
- Do not leave \`task_id\` empty when intending to reuse; omitted or empty \`task_id\` creates a new specialist session.
|
|
19353
19401
|
|
|
19354
|
-
### Validation routing
|
|
19355
|
-
- Validation is a workflow stage owned by the Orchestrator, not a separate specialist
|
|
19356
|
-
${enabledValidationRouting}
|
|
19357
|
-
|
|
19358
19402
|
## 6. Verify
|
|
19359
|
-
-
|
|
19360
|
-
-
|
|
19361
|
-
-
|
|
19362
|
-
-
|
|
19363
|
-
-
|
|
19403
|
+
- Define the observable success criteria from the user's request.
|
|
19404
|
+
- Run the smallest relevant checks: targeted tests, typecheck, lint, build, or a manual behavior check.
|
|
19405
|
+
- Inspect the changed diff and verify behavior; passing checks alone are not sufficient.
|
|
19406
|
+
- If a check fails, diagnose, fix, and rerun the relevant verification.
|
|
19407
|
+
- For risky or ambiguous changes, obtain an independent review when its value justifies the cost.
|
|
19408
|
+
- Report verification performed and any remaining limitations.
|
|
19364
19409
|
|
|
19365
19410
|
</Workflow>
|
|
19366
19411
|
|
|
@@ -20045,6 +20090,9 @@ function applyOverrides(agent, override) {
|
|
|
20045
20090
|
if (override.displayName) {
|
|
20046
20091
|
agent.displayName = override.displayName;
|
|
20047
20092
|
}
|
|
20093
|
+
if (override.permission) {
|
|
20094
|
+
agent.config.permission = override.permission;
|
|
20095
|
+
}
|
|
20048
20096
|
}
|
|
20049
20097
|
function isKnownAgentName(name) {
|
|
20050
20098
|
return ALL_AGENT_NAMES.includes(name);
|
|
@@ -20085,6 +20133,9 @@ function injectDisplayNames(orchestrator, nameMap) {
|
|
|
20085
20133
|
orchestrator.config.prompt = prompt;
|
|
20086
20134
|
}
|
|
20087
20135
|
function applyDefaultPermissions(agent, configuredSkills, disabledSkills) {
|
|
20136
|
+
if (typeof agent.config.permission === "string") {
|
|
20137
|
+
return;
|
|
20138
|
+
}
|
|
20088
20139
|
const existing = agent.config.permission ?? {};
|
|
20089
20140
|
const skillPermissions = getSkillPermissionsForAgent(agent.name, configuredSkills, disabledSkills);
|
|
20090
20141
|
const questionPerm = existing.question === "deny" ? "deny" : "allow";
|
|
@@ -20231,10 +20282,10 @@ function createAgents(config, options) {
|
|
|
20231
20282
|
const defaultOrchestratorPrompt = orchestrator.config.prompt ?? "";
|
|
20232
20283
|
const baseOrchestratorPrompt = inlineOrchestratorPrompt !== undefined ? inlineOrchestratorPrompt : defaultOrchestratorPrompt;
|
|
20233
20284
|
orchestrator.config.prompt = resolvePrompt(baseOrchestratorPrompt, orchestratorPrompts.prompt, orchestratorPrompts.appendPrompt);
|
|
20234
|
-
applyDefaultPermissions(orchestrator, orchestratorOverride?.skills, config?.disabled_skills);
|
|
20235
20285
|
if (orchestratorOverride) {
|
|
20236
20286
|
applyOverrides(orchestrator, orchestratorOverride);
|
|
20237
20287
|
}
|
|
20288
|
+
applyDefaultPermissions(orchestrator, orchestratorOverride?.skills, config?.disabled_skills);
|
|
20238
20289
|
const displayNameMap = new Map;
|
|
20239
20290
|
if (orchestrator.displayName) {
|
|
20240
20291
|
displayNameMap.set("orchestrator", orchestrator.displayName);
|
|
@@ -20488,7 +20539,9 @@ function acquirePidFileLock(file) {
|
|
|
20488
20539
|
return () => {
|
|
20489
20540
|
try {
|
|
20490
20541
|
rmSync(lock, { recursive: true, force: true });
|
|
20491
|
-
} catch {
|
|
20542
|
+
} catch (err) {
|
|
20543
|
+
log("[companion] lock release failed", String(err));
|
|
20544
|
+
}
|
|
20492
20545
|
};
|
|
20493
20546
|
} catch (err) {
|
|
20494
20547
|
const code = err.code;
|
|
@@ -20516,10 +20569,13 @@ function pidFileLockHasLiveOwner(lock) {
|
|
|
20516
20569
|
const owner = parsePidFile(readFileSync2(path3.join(lock, "owner"), "utf8"));
|
|
20517
20570
|
if (owner !== null)
|
|
20518
20571
|
return isProcessAlive(owner);
|
|
20519
|
-
} catch {
|
|
20572
|
+
} catch (err) {
|
|
20573
|
+
log("[companion] lock owner check failed", String(err));
|
|
20520
20574
|
try {
|
|
20521
20575
|
return Date.now() - statSync2(lock).mtimeMs < 5000;
|
|
20522
|
-
} catch {
|
|
20576
|
+
} catch (err2) {
|
|
20577
|
+
log("[companion] lock owner check failed", String(err2));
|
|
20578
|
+
}
|
|
20523
20579
|
}
|
|
20524
20580
|
return false;
|
|
20525
20581
|
}
|
|
@@ -20541,7 +20597,9 @@ function readState() {
|
|
|
20541
20597
|
if (parsed?.version === 1 && Array.isArray(parsed.sessions)) {
|
|
20542
20598
|
return parsed;
|
|
20543
20599
|
}
|
|
20544
|
-
} catch {
|
|
20600
|
+
} catch (err) {
|
|
20601
|
+
log("[companion] state load failed", String(err));
|
|
20602
|
+
}
|
|
20545
20603
|
return { version: 1, sessions: [] };
|
|
20546
20604
|
}
|
|
20547
20605
|
function writeState(mutator) {
|
|
@@ -20570,7 +20628,9 @@ function acquireStateLock(file) {
|
|
|
20570
20628
|
return () => {
|
|
20571
20629
|
try {
|
|
20572
20630
|
rmSync(lock, { recursive: true, force: true });
|
|
20573
|
-
} catch {
|
|
20631
|
+
} catch (err) {
|
|
20632
|
+
log("[companion] lock release failed", String(err));
|
|
20633
|
+
}
|
|
20574
20634
|
};
|
|
20575
20635
|
} catch (err) {
|
|
20576
20636
|
const code = err.code;
|
|
@@ -20605,7 +20665,9 @@ class CompanionManager {
|
|
|
20605
20665
|
writeState((state) => {
|
|
20606
20666
|
state.sessions = state.sessions.filter((s) => s.session_id !== this.id);
|
|
20607
20667
|
});
|
|
20608
|
-
} catch {
|
|
20668
|
+
} catch (err) {
|
|
20669
|
+
log("[companion] status update failed", String(err));
|
|
20670
|
+
}
|
|
20609
20671
|
return;
|
|
20610
20672
|
}
|
|
20611
20673
|
this.registerActiveManager();
|
|
@@ -20643,9 +20705,7 @@ class CompanionManager {
|
|
|
20643
20705
|
return;
|
|
20644
20706
|
}
|
|
20645
20707
|
if (status === "busy") {
|
|
20646
|
-
|
|
20647
|
-
return;
|
|
20648
|
-
this.busyAgentSessions.set(sessionId, agent);
|
|
20708
|
+
this.busyAgentSessions.set(sessionId, agent ?? sessionId);
|
|
20649
20709
|
} else {
|
|
20650
20710
|
this.busyAgentSessions.delete(sessionId);
|
|
20651
20711
|
}
|
|
@@ -20677,7 +20737,9 @@ class CompanionManager {
|
|
|
20677
20737
|
if (activeManagers.size === 0 && activeExitListener) {
|
|
20678
20738
|
try {
|
|
20679
20739
|
process.removeListener("exit", activeExitListener);
|
|
20680
|
-
} catch {
|
|
20740
|
+
} catch (err) {
|
|
20741
|
+
log("[companion] exit listener removal failed", String(err));
|
|
20742
|
+
}
|
|
20681
20743
|
activeExitListener = null;
|
|
20682
20744
|
}
|
|
20683
20745
|
if (this.config?.enabled !== true)
|
|
@@ -20835,7 +20897,9 @@ class CompanionManager {
|
|
|
20835
20897
|
if (spawnedChild && !this.wasSpawner) {
|
|
20836
20898
|
try {
|
|
20837
20899
|
spawnedChild.kill();
|
|
20838
|
-
} catch {
|
|
20900
|
+
} catch (killErr) {
|
|
20901
|
+
log("[companion] spawn failed", String(killErr));
|
|
20902
|
+
}
|
|
20839
20903
|
}
|
|
20840
20904
|
log("[companion] spawn guard failed", String(err));
|
|
20841
20905
|
} finally {
|
|
@@ -20914,7 +20978,9 @@ function loadCompanionManifestFromPackageRoot(packageRoot) {
|
|
|
20914
20978
|
checksums: parsed.checksums
|
|
20915
20979
|
};
|
|
20916
20980
|
}
|
|
20917
|
-
} catch {
|
|
20981
|
+
} catch (err) {
|
|
20982
|
+
log("[updater] manifest read failed", String(err));
|
|
20983
|
+
}
|
|
20918
20984
|
return null;
|
|
20919
20985
|
}
|
|
20920
20986
|
async function ensureCompanionVersion(options) {
|
|
@@ -21082,7 +21148,9 @@ async function installCompanionArchive(finalBinaryPath, target, manifest, downlo
|
|
|
21082
21148
|
if (tempDir) {
|
|
21083
21149
|
try {
|
|
21084
21150
|
rmSync2(tempDir, { recursive: true, force: true });
|
|
21085
|
-
} catch {
|
|
21151
|
+
} catch (err) {
|
|
21152
|
+
log("[updater] install cleanup failed", String(err));
|
|
21153
|
+
}
|
|
21086
21154
|
}
|
|
21087
21155
|
}
|
|
21088
21156
|
}
|
|
@@ -21092,7 +21160,9 @@ function readInstallMetadata(binaryPath) {
|
|
|
21092
21160
|
if (parsed?.version && parsed.tag && parsed.target) {
|
|
21093
21161
|
return parsed;
|
|
21094
21162
|
}
|
|
21095
|
-
} catch {
|
|
21163
|
+
} catch (err) {
|
|
21164
|
+
log("[updater] metadata read failed", String(err));
|
|
21165
|
+
}
|
|
21096
21166
|
return null;
|
|
21097
21167
|
}
|
|
21098
21168
|
function writeInstallMetadata(binaryPath, metadata) {
|
|
@@ -21114,7 +21184,9 @@ async function withCompanionInstallLock(binaryPath, timeoutMs, staleMs, run) {
|
|
|
21114
21184
|
} finally {
|
|
21115
21185
|
try {
|
|
21116
21186
|
rmSync2(lock, { recursive: true, force: true });
|
|
21117
|
-
} catch {
|
|
21187
|
+
} catch (err) {
|
|
21188
|
+
log("[updater] lock release failed", String(err));
|
|
21189
|
+
}
|
|
21118
21190
|
}
|
|
21119
21191
|
} catch (err) {
|
|
21120
21192
|
const code = err.code;
|
|
@@ -21185,6 +21257,24 @@ function setActiveRuntimePresetWithPrevious(name) {
|
|
|
21185
21257
|
activeRuntimePreset = name;
|
|
21186
21258
|
}
|
|
21187
21259
|
|
|
21260
|
+
// src/config/strip-orchestrator-model.ts
|
|
21261
|
+
function isRecord(value) {
|
|
21262
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21263
|
+
}
|
|
21264
|
+
function stripOrchestratorModel(agents, enabled, preset) {
|
|
21265
|
+
if (enabled !== true || preset?.orchestrator?.model !== undefined)
|
|
21266
|
+
return;
|
|
21267
|
+
const orchestrator = agents.orchestrator;
|
|
21268
|
+
if (!isRecord(orchestrator))
|
|
21269
|
+
return;
|
|
21270
|
+
delete orchestrator.model;
|
|
21271
|
+
delete orchestrator.variant;
|
|
21272
|
+
}
|
|
21273
|
+
function applyOrchestratorModelConfig(input) {
|
|
21274
|
+
const presetName = input.runtimePreset ?? input.configPreset;
|
|
21275
|
+
stripOrchestratorModel(input.agents, input.enabled, presetName ? input.presets?.[presetName] : undefined);
|
|
21276
|
+
}
|
|
21277
|
+
|
|
21188
21278
|
// src/council/council-manager.ts
|
|
21189
21279
|
class CouncilManager {
|
|
21190
21280
|
client;
|
|
@@ -24696,9 +24786,9 @@ class BackgroundJobBoard {
|
|
|
24696
24786
|
readContextMinLines;
|
|
24697
24787
|
readContextMaxFiles;
|
|
24698
24788
|
constructor(options = {}) {
|
|
24699
|
-
this.maxReusablePerAgent = options.maxReusablePerAgent ??
|
|
24700
|
-
this.readContextMinLines = options.readContextMinLines ??
|
|
24701
|
-
this.readContextMaxFiles = options.readContextMaxFiles ??
|
|
24789
|
+
this.maxReusablePerAgent = options.maxReusablePerAgent ?? DEFAULT_MAX_SESSIONS_PER_AGENT;
|
|
24790
|
+
this.readContextMinLines = options.readContextMinLines ?? DEFAULT_READ_CONTEXT_MIN_LINES;
|
|
24791
|
+
this.readContextMaxFiles = options.readContextMaxFiles ?? DEFAULT_READ_CONTEXT_MAX_FILES;
|
|
24702
24792
|
}
|
|
24703
24793
|
addTerminalStateListener(listener) {
|
|
24704
24794
|
this.terminalStateListeners.push(listener);
|
|
@@ -25239,7 +25329,7 @@ class BackgroundJobCoordinator {
|
|
|
25239
25329
|
}
|
|
25240
25330
|
}
|
|
25241
25331
|
// src/utils/guards.ts
|
|
25242
|
-
function
|
|
25332
|
+
function isRecord2(value) {
|
|
25243
25333
|
return typeof value === "object" && value !== null;
|
|
25244
25334
|
}
|
|
25245
25335
|
|
|
@@ -25256,10 +25346,10 @@ ${SLIM_INTERNAL_INITIATOR_MARKER}`,
|
|
|
25256
25346
|
};
|
|
25257
25347
|
}
|
|
25258
25348
|
function isInternalInitiatorPart(part) {
|
|
25259
|
-
if (!
|
|
25349
|
+
if (!isRecord2(part) || part.type !== "text") {
|
|
25260
25350
|
return false;
|
|
25261
25351
|
}
|
|
25262
|
-
if (part.synthetic !== true || !
|
|
25352
|
+
if (part.synthetic !== true || !isRecord2(part.metadata)) {
|
|
25263
25353
|
return false;
|
|
25264
25354
|
}
|
|
25265
25355
|
return part.metadata[INTERNAL_INITIATOR_METADATA_KEY] === true;
|
|
@@ -25318,6 +25408,17 @@ function createChatHeadersHook(ctx) {
|
|
|
25318
25408
|
}
|
|
25319
25409
|
};
|
|
25320
25410
|
}
|
|
25411
|
+
// src/hooks/command-hook-utils.ts
|
|
25412
|
+
function registerCommandHook(opencodeConfig, commandName, template, description) {
|
|
25413
|
+
const cmdConfig = opencodeConfig.command;
|
|
25414
|
+
if (cmdConfig?.[commandName])
|
|
25415
|
+
return false;
|
|
25416
|
+
if (!opencodeConfig.command)
|
|
25417
|
+
opencodeConfig.command = {};
|
|
25418
|
+
opencodeConfig.command[commandName] = { template, description };
|
|
25419
|
+
return true;
|
|
25420
|
+
}
|
|
25421
|
+
|
|
25321
25422
|
// src/hooks/deepwork/index.ts
|
|
25322
25423
|
var COMMAND_NAME = "deepwork";
|
|
25323
25424
|
function activationPrompt(task2) {
|
|
@@ -25325,6 +25426,7 @@ function activationPrompt(task2) {
|
|
|
25325
25426
|
"Use the deepwork skill for this task. Treat it as a heavy coding session.",
|
|
25326
25427
|
"",
|
|
25327
25428
|
"Deepwork requirements:",
|
|
25429
|
+
"- before planning, delegation, or creating state, inspect existing `.gitignore` and `.ignore`; add only missing entries without duplicates: `.gitignore` must contain `.slim/deepwork/`, and `.ignore` must contain `!.slim/deepwork/` and `!.slim/deepwork/**`; this keeps state git-local yet OpenCode-readable;",
|
|
25328
25430
|
"- create/update a `.slim/deepwork/` progress file;",
|
|
25329
25431
|
"- keep OpenCode todos synced with the current phase;",
|
|
25330
25432
|
"- draft a plan and get `@oracle` review before implementation;",
|
|
@@ -25342,15 +25444,7 @@ function activationPrompt(task2) {
|
|
|
25342
25444
|
function createDeepworkCommandHook() {
|
|
25343
25445
|
return {
|
|
25344
25446
|
registerCommand: (opencodeConfig) => {
|
|
25345
|
-
|
|
25346
|
-
if (commandConfig?.[COMMAND_NAME])
|
|
25347
|
-
return;
|
|
25348
|
-
if (!opencodeConfig.command)
|
|
25349
|
-
opencodeConfig.command = {};
|
|
25350
|
-
opencodeConfig.command[COMMAND_NAME] = {
|
|
25351
|
-
template: "Start a deepwork session for a complex coding task",
|
|
25352
|
-
description: "Use the deepwork workflow for heavy multi-phase coding work"
|
|
25353
|
-
};
|
|
25447
|
+
registerCommandHook(opencodeConfig, COMMAND_NAME, "Start a deepwork session for a complex coding task", "Use the deepwork workflow for heavy multi-phase coding work");
|
|
25354
25448
|
},
|
|
25355
25449
|
handleCommandExecuteBefore: async (input, output) => {
|
|
25356
25450
|
if (input.command !== COMMAND_NAME)
|
|
@@ -25565,7 +25659,7 @@ function createFilterAvailableSkillsHook(_ctx, config) {
|
|
|
25565
25659
|
};
|
|
25566
25660
|
}
|
|
25567
25661
|
// src/hooks/foreground-fallback/index.ts
|
|
25568
|
-
var
|
|
25662
|
+
var RETRYABLE_ERROR_PATTERNS = [
|
|
25569
25663
|
/\b429\b/,
|
|
25570
25664
|
/rate.?limit/i,
|
|
25571
25665
|
/too many requests/i,
|
|
@@ -25579,22 +25673,76 @@ var RATE_LIMIT_PATTERNS = [
|
|
|
25579
25673
|
/insufficient.?(quota|balance)/i,
|
|
25580
25674
|
/high concurrency/i,
|
|
25581
25675
|
/reduce concurrency/i,
|
|
25582
|
-
/service unavailable/i,
|
|
25583
25676
|
/monthly usage limit/i,
|
|
25584
25677
|
/5-hour usage limit/i,
|
|
25585
|
-
/weekly usage limit/i
|
|
25678
|
+
/weekly usage limit/i,
|
|
25679
|
+
/\b403\b/,
|
|
25680
|
+
/forbidden/i,
|
|
25681
|
+
/blocked by gateway/i
|
|
25682
|
+
];
|
|
25683
|
+
var OUTAGE_STATUS_CODES = new Set([500, 502, 503, 504]);
|
|
25684
|
+
var TRANSPORT_CODES = new Set([
|
|
25685
|
+
"ECONNREFUSED",
|
|
25686
|
+
"ECONNRESET",
|
|
25687
|
+
"ENOTFOUND",
|
|
25688
|
+
"ETIMEDOUT",
|
|
25689
|
+
"EAI_AGAIN"
|
|
25690
|
+
]);
|
|
25691
|
+
var TRANSPORT_MESSAGE_PATTERNS = [
|
|
25692
|
+
/^fetch failed$/i,
|
|
25693
|
+
/^socket hang up$/i,
|
|
25694
|
+
/^provider request timeout$/i,
|
|
25695
|
+
/^request timeout$/i,
|
|
25696
|
+
/^connect ECONNREFUSED\b/i,
|
|
25697
|
+
/^getaddrinfo ENOTFOUND\b/i
|
|
25586
25698
|
];
|
|
25587
|
-
|
|
25588
|
-
|
|
25699
|
+
var PROVIDER_OUTAGE_PATTERNS = [
|
|
25700
|
+
/\binternal server error\b/i,
|
|
25701
|
+
/\bbad gateway\b/i,
|
|
25702
|
+
/\bgateway timeout\b/i,
|
|
25703
|
+
/\bservice unavailable\b/i,
|
|
25704
|
+
/\bupstream outage\b/i,
|
|
25705
|
+
/\bprovider outage\b/i,
|
|
25706
|
+
/\bprovider unavailable\b/i
|
|
25707
|
+
];
|
|
25708
|
+
function extractStatusCode(error) {
|
|
25709
|
+
const value = error.statusCode ?? error.data?.statusCode;
|
|
25710
|
+
return typeof value === "number" ? value : undefined;
|
|
25711
|
+
}
|
|
25712
|
+
function eventSessionID(props) {
|
|
25713
|
+
return props.sessionID ?? props.info?.id;
|
|
25714
|
+
}
|
|
25715
|
+
function isFailoverError(error) {
|
|
25716
|
+
if (!error)
|
|
25717
|
+
return false;
|
|
25718
|
+
if (typeof error === "string") {
|
|
25719
|
+
return RETRYABLE_ERROR_PATTERNS.some((pattern) => pattern.test(error)) || PROVIDER_OUTAGE_PATTERNS.some((pattern) => pattern.test(error)) || TRANSPORT_MESSAGE_PATTERNS.some((pattern) => pattern.test(error));
|
|
25720
|
+
}
|
|
25721
|
+
if (typeof error !== "object")
|
|
25589
25722
|
return false;
|
|
25590
25723
|
const err = error;
|
|
25724
|
+
const statusCode = extractStatusCode(err);
|
|
25725
|
+
if (statusCode === 429 || statusCode === 403 || statusCode !== undefined && OUTAGE_STATUS_CODES.has(statusCode)) {
|
|
25726
|
+
return true;
|
|
25727
|
+
}
|
|
25728
|
+
if ([err.code, err.cause?.code, err.data?.code].some((code) => typeof code === "string" && TRANSPORT_CODES.has(code))) {
|
|
25729
|
+
return true;
|
|
25730
|
+
}
|
|
25731
|
+
const messages = [
|
|
25732
|
+
err.message ?? "",
|
|
25733
|
+
err.data?.message ?? "",
|
|
25734
|
+
err.data?.responseBody ?? ""
|
|
25735
|
+
];
|
|
25736
|
+
if (messages.some((message) => TRANSPORT_MESSAGE_PATTERNS.some((p) => p.test(message)))) {
|
|
25737
|
+
return true;
|
|
25738
|
+
}
|
|
25591
25739
|
const text = [
|
|
25592
25740
|
err.message ?? "",
|
|
25593
|
-
String(err.data?.statusCode ?? ""),
|
|
25594
25741
|
err.data?.message ?? "",
|
|
25595
25742
|
err.data?.responseBody ?? ""
|
|
25596
25743
|
].join(" ");
|
|
25597
|
-
|
|
25744
|
+
const hasFailoverReason = RETRYABLE_ERROR_PATTERNS.some((p) => p.test(text)) || PROVIDER_OUTAGE_PATTERNS.some((p) => p.test(text));
|
|
25745
|
+
return hasFailoverReason;
|
|
25598
25746
|
}
|
|
25599
25747
|
var DEDUP_WINDOW_MS = 5000;
|
|
25600
25748
|
var REPROMPT_DELAY_MS = 500;
|
|
@@ -25604,7 +25752,6 @@ class ForegroundFallbackManager {
|
|
|
25604
25752
|
chains;
|
|
25605
25753
|
enabled;
|
|
25606
25754
|
maxRetries;
|
|
25607
|
-
runtimeOverride;
|
|
25608
25755
|
sessionModel = new Map;
|
|
25609
25756
|
sessionAgent = new Map;
|
|
25610
25757
|
sessionTried = new Map;
|
|
@@ -25615,18 +25762,26 @@ class ForegroundFallbackManager {
|
|
|
25615
25762
|
isFallbackInProgress(sessionID) {
|
|
25616
25763
|
return this.inProgress.has(sessionID);
|
|
25617
25764
|
}
|
|
25618
|
-
|
|
25765
|
+
disableChain(agentName) {
|
|
25766
|
+
this.chains[agentName] = [];
|
|
25767
|
+
}
|
|
25768
|
+
registerSessionAgent(sessionID, agentName) {
|
|
25769
|
+
const normalizedAgentName = agentName.trim();
|
|
25770
|
+
if (!sessionID || !normalizedAgentName || this.sessionAgent.has(sessionID)) {
|
|
25771
|
+
return;
|
|
25772
|
+
}
|
|
25773
|
+
this.sessionAgent.set(sessionID, normalizedAgentName);
|
|
25774
|
+
}
|
|
25775
|
+
constructor(client, chains, enabled, maxRetries = 3, coordinator) {
|
|
25619
25776
|
this.client = client;
|
|
25620
25777
|
this.chains = chains;
|
|
25621
25778
|
this.enabled = enabled;
|
|
25622
25779
|
this.maxRetries = maxRetries;
|
|
25623
|
-
this.runtimeOverride = runtimeOverride;
|
|
25624
25780
|
if (coordinator) {
|
|
25625
25781
|
coordinator.onSessionDeleted((id) => {
|
|
25626
25782
|
this.sessionModel.delete(id);
|
|
25627
25783
|
this.sessionAgent.delete(id);
|
|
25628
25784
|
this.sessionTried.delete(id);
|
|
25629
|
-
this.inProgress.delete(id);
|
|
25630
25785
|
this.lastTrigger.delete(id);
|
|
25631
25786
|
this.lastTriggerModel.delete(id);
|
|
25632
25787
|
this.sessionRetries.delete(id);
|
|
@@ -25648,13 +25803,13 @@ class ForegroundFallbackManager {
|
|
|
25648
25803
|
if (!sessionID)
|
|
25649
25804
|
break;
|
|
25650
25805
|
if (typeof info.agent === "string") {
|
|
25651
|
-
this.
|
|
25806
|
+
this.registerSessionAgent(sessionID, info.agent);
|
|
25652
25807
|
}
|
|
25653
25808
|
if (typeof info.providerID === "string" && typeof info.modelID === "string") {
|
|
25654
25809
|
this.sessionModel.set(sessionID, `${info.providerID}/${info.modelID}`);
|
|
25655
25810
|
}
|
|
25656
|
-
if (info.error &&
|
|
25657
|
-
if (this.
|
|
25811
|
+
if (info.error && isFailoverError(info.error)) {
|
|
25812
|
+
if (this.shouldTriggerFallback(sessionID)) {
|
|
25658
25813
|
await this.tryFallback(sessionID);
|
|
25659
25814
|
}
|
|
25660
25815
|
} else {
|
|
@@ -25664,29 +25819,46 @@ class ForegroundFallbackManager {
|
|
|
25664
25819
|
}
|
|
25665
25820
|
case "session.error": {
|
|
25666
25821
|
const props = event.properties;
|
|
25667
|
-
if (props
|
|
25668
|
-
|
|
25822
|
+
if (!props)
|
|
25823
|
+
break;
|
|
25824
|
+
const sessionID = eventSessionID(props);
|
|
25825
|
+
if (sessionID && props.error && isFailoverError(props.error) && this.shouldTriggerFallback(sessionID)) {
|
|
25826
|
+
await this.tryFallback(sessionID);
|
|
25669
25827
|
}
|
|
25670
25828
|
break;
|
|
25671
25829
|
}
|
|
25672
25830
|
case "session.status": {
|
|
25673
25831
|
const props = event.properties;
|
|
25674
|
-
if (!props
|
|
25832
|
+
if (!props)
|
|
25675
25833
|
break;
|
|
25676
|
-
const
|
|
25677
|
-
if (
|
|
25678
|
-
|
|
25679
|
-
|
|
25834
|
+
const sessionID = eventSessionID(props);
|
|
25835
|
+
if (!sessionID)
|
|
25836
|
+
break;
|
|
25837
|
+
const isFailoverRetry = props.status?.type === "retry" && (isFailoverError(props.error) || props.status.message !== undefined && isFailoverError({ message: props.status.message }));
|
|
25838
|
+
if (isFailoverRetry) {
|
|
25839
|
+
const prevModel = this.lastTriggerModel.get(sessionID);
|
|
25840
|
+
const curModel = this.sessionModel.get(sessionID);
|
|
25841
|
+
const lastTriggerTime = this.lastTrigger.get(sessionID) ?? 0;
|
|
25842
|
+
const attempt = props.status?.attempt ?? 1;
|
|
25843
|
+
const modelChanged = prevModel !== undefined && curModel !== undefined && prevModel !== curModel;
|
|
25844
|
+
const withinDedupWindow = Date.now() - lastTriggerTime < DEDUP_WINDOW_MS;
|
|
25845
|
+
if (modelChanged && withinDedupWindow && attempt > 1) {
|
|
25846
|
+
break;
|
|
25680
25847
|
}
|
|
25681
|
-
|
|
25682
|
-
|
|
25848
|
+
if (this.shouldTriggerFallback(sessionID)) {
|
|
25849
|
+
await this.tryFallbackWithAbort(sessionID);
|
|
25850
|
+
}
|
|
25851
|
+
break;
|
|
25852
|
+
}
|
|
25853
|
+
if (this.isRecoveredStatus(props.status?.type)) {
|
|
25854
|
+
this.sessionRetries.delete(sessionID);
|
|
25683
25855
|
}
|
|
25684
25856
|
break;
|
|
25685
25857
|
}
|
|
25686
25858
|
case "subagent.session.created": {
|
|
25687
25859
|
const props = event.properties;
|
|
25688
25860
|
if (props?.sessionID && typeof props.agentName === "string") {
|
|
25689
|
-
this.
|
|
25861
|
+
this.registerSessionAgent(props.sessionID, props.agentName);
|
|
25690
25862
|
}
|
|
25691
25863
|
break;
|
|
25692
25864
|
}
|
|
@@ -25702,7 +25874,7 @@ class ForegroundFallbackManager {
|
|
|
25702
25874
|
}
|
|
25703
25875
|
}
|
|
25704
25876
|
}
|
|
25705
|
-
|
|
25877
|
+
consumeRetryBudget(sessionID) {
|
|
25706
25878
|
const tried = this.sessionRetries.get(sessionID) ?? 0;
|
|
25707
25879
|
if (tried < this.maxRetries - 1) {
|
|
25708
25880
|
this.sessionRetries.set(sessionID, tried + 1);
|
|
@@ -25716,27 +25888,57 @@ class ForegroundFallbackManager {
|
|
|
25716
25888
|
this.sessionRetries.delete(sessionID);
|
|
25717
25889
|
return true;
|
|
25718
25890
|
}
|
|
25719
|
-
|
|
25891
|
+
shouldTriggerFallback(sessionID) {
|
|
25720
25892
|
const tried = this.sessionRetries.get(sessionID) ?? 0;
|
|
25721
25893
|
if (tried === 0)
|
|
25722
25894
|
return true;
|
|
25723
|
-
return this.
|
|
25895
|
+
return this.consumeRetryBudget(sessionID);
|
|
25896
|
+
}
|
|
25897
|
+
isRecoveredStatus(statusType) {
|
|
25898
|
+
return statusType === "idle" || statusType === "complete" || statusType === "completed" || statusType === "success" || statusType === "terminal";
|
|
25724
25899
|
}
|
|
25725
25900
|
async tryFallback(sessionID) {
|
|
25726
25901
|
if (!sessionID)
|
|
25727
25902
|
return;
|
|
25728
25903
|
if (this.inProgress.has(sessionID))
|
|
25729
25904
|
return;
|
|
25905
|
+
if (this.isDeduped(sessionID))
|
|
25906
|
+
return;
|
|
25907
|
+
this.inProgress.add(sessionID);
|
|
25908
|
+
try {
|
|
25909
|
+
await this.execFallback(sessionID);
|
|
25910
|
+
} finally {
|
|
25911
|
+
this.inProgress.delete(sessionID);
|
|
25912
|
+
}
|
|
25913
|
+
}
|
|
25914
|
+
async tryFallbackWithAbort(sessionID) {
|
|
25915
|
+
if (!sessionID)
|
|
25916
|
+
return;
|
|
25917
|
+
if (this.inProgress.has(sessionID))
|
|
25918
|
+
return;
|
|
25919
|
+
if (this.isDeduped(sessionID))
|
|
25920
|
+
return;
|
|
25921
|
+
this.inProgress.add(sessionID);
|
|
25922
|
+
try {
|
|
25923
|
+
await abortSessionWithTimeout(this.client, sessionID);
|
|
25924
|
+
await this.execFallback(sessionID);
|
|
25925
|
+
} finally {
|
|
25926
|
+
this.inProgress.delete(sessionID);
|
|
25927
|
+
}
|
|
25928
|
+
}
|
|
25929
|
+
isDeduped(sessionID) {
|
|
25730
25930
|
const now = Date.now();
|
|
25731
25931
|
const curModel = this.sessionModel.get(sessionID);
|
|
25732
25932
|
const modelChanged = this.lastTriggerModel.has(sessionID) && this.lastTriggerModel.get(sessionID) !== curModel;
|
|
25733
25933
|
if (!modelChanged && now - (this.lastTrigger.get(sessionID) ?? 0) < DEDUP_WINDOW_MS)
|
|
25734
|
-
return;
|
|
25934
|
+
return true;
|
|
25735
25935
|
this.lastTrigger.set(sessionID, now);
|
|
25736
25936
|
if (curModel !== undefined) {
|
|
25737
25937
|
this.lastTriggerModel.set(sessionID, curModel);
|
|
25738
25938
|
}
|
|
25739
|
-
|
|
25939
|
+
return false;
|
|
25940
|
+
}
|
|
25941
|
+
async execFallback(sessionID) {
|
|
25740
25942
|
try {
|
|
25741
25943
|
let currentModel = this.sessionModel.get(sessionID);
|
|
25742
25944
|
const agentName = this.sessionAgent.get(sessionID);
|
|
@@ -25751,16 +25953,6 @@ class ForegroundFallbackManager {
|
|
|
25751
25953
|
if (!currentModel && agentName && chain.length > 0) {
|
|
25752
25954
|
currentModel = chain[0];
|
|
25753
25955
|
}
|
|
25754
|
-
if (!this.runtimeOverride && currentModel && !chain.includes(currentModel)) {
|
|
25755
|
-
log("[foreground-fallback] current model not in chain, skipping fallback (runtimeOverride=false)", {
|
|
25756
|
-
sessionID,
|
|
25757
|
-
agentName,
|
|
25758
|
-
currentModel,
|
|
25759
|
-
chain
|
|
25760
|
-
});
|
|
25761
|
-
await abortSessionWithTimeout(this.client, sessionID);
|
|
25762
|
-
return;
|
|
25763
|
-
}
|
|
25764
25956
|
if (!this.sessionTried.has(sessionID)) {
|
|
25765
25957
|
this.sessionTried.set(sessionID, new Set);
|
|
25766
25958
|
}
|
|
@@ -25820,10 +26012,15 @@ class ForegroundFallbackManager {
|
|
|
25820
26012
|
log("[foreground-fallback] promptAsync unavailable", { sessionID });
|
|
25821
26013
|
return;
|
|
25822
26014
|
}
|
|
26015
|
+
const promptBody = {
|
|
26016
|
+
parts: lastUser.parts,
|
|
26017
|
+
model: ref,
|
|
26018
|
+
...agentName ? { agent: agentName } : {}
|
|
26019
|
+
};
|
|
25823
26020
|
try {
|
|
25824
26021
|
await sessionClient.promptAsync({
|
|
25825
26022
|
path: { id: sessionID },
|
|
25826
|
-
body:
|
|
26023
|
+
body: promptBody
|
|
25827
26024
|
});
|
|
25828
26025
|
} catch (_promptErr) {
|
|
25829
26026
|
log("[foreground-fallback] promptAsync on busy session, aborting", {
|
|
@@ -25833,7 +26030,7 @@ class ForegroundFallbackManager {
|
|
|
25833
26030
|
await new Promise((r) => setTimeout(r, REPROMPT_DELAY_MS));
|
|
25834
26031
|
await sessionClient.promptAsync({
|
|
25835
26032
|
path: { id: sessionID },
|
|
25836
|
-
body:
|
|
26033
|
+
body: promptBody
|
|
25837
26034
|
});
|
|
25838
26035
|
}
|
|
25839
26036
|
this.sessionModel.set(sessionID, nextModel);
|
|
@@ -25848,8 +26045,6 @@ class ForegroundFallbackManager {
|
|
|
25848
26045
|
sessionID,
|
|
25849
26046
|
error: err instanceof Error ? err.message : String(err)
|
|
25850
26047
|
});
|
|
25851
|
-
} finally {
|
|
25852
|
-
this.inProgress.delete(sessionID);
|
|
25853
26048
|
}
|
|
25854
26049
|
}
|
|
25855
26050
|
resolveChain(agentName, currentModel) {
|
|
@@ -25857,8 +26052,7 @@ class ForegroundFallbackManager {
|
|
|
25857
26052
|
const chain = this.chains[agentName];
|
|
25858
26053
|
if (chain)
|
|
25859
26054
|
return chain;
|
|
25860
|
-
|
|
25861
|
-
return [];
|
|
26055
|
+
return [];
|
|
25862
26056
|
}
|
|
25863
26057
|
if (currentModel) {
|
|
25864
26058
|
for (const chain of Object.values(this.chains)) {
|
|
@@ -25945,10 +26139,14 @@ function cleanupAllSessions(saveDir) {
|
|
|
25945
26139
|
try {
|
|
25946
26140
|
if (now - statSync5(fp).mtimeMs > maxAge)
|
|
25947
26141
|
unlinkSync3(fp);
|
|
25948
|
-
} catch {
|
|
26142
|
+
} catch (err) {
|
|
26143
|
+
log("[image-hook] file cleanup failed", String(err));
|
|
26144
|
+
}
|
|
25949
26145
|
}
|
|
25950
26146
|
}
|
|
25951
|
-
} catch {
|
|
26147
|
+
} catch (err) {
|
|
26148
|
+
log("[image-hook] directory scan failed", String(err));
|
|
26149
|
+
}
|
|
25952
26150
|
for (const dir of dirsToScan) {
|
|
25953
26151
|
try {
|
|
25954
26152
|
let isEmpty = true;
|
|
@@ -25962,16 +26160,21 @@ function cleanupAllSessions(saveDir) {
|
|
|
25962
26160
|
} else {
|
|
25963
26161
|
allRemoved = false;
|
|
25964
26162
|
}
|
|
25965
|
-
} catch {
|
|
26163
|
+
} catch (err) {
|
|
26164
|
+
log("[image-hook] file cleanup failed", String(err));
|
|
25966
26165
|
allRemoved = false;
|
|
25967
26166
|
}
|
|
25968
26167
|
}
|
|
25969
26168
|
if (!isEmpty && allRemoved) {
|
|
25970
26169
|
try {
|
|
25971
26170
|
rmdirSync(dir);
|
|
25972
|
-
} catch {
|
|
26171
|
+
} catch (err) {
|
|
26172
|
+
log("[image-hook] directory removal failed", String(err));
|
|
26173
|
+
}
|
|
25973
26174
|
}
|
|
25974
|
-
} catch {
|
|
26175
|
+
} catch (err) {
|
|
26176
|
+
log("[image-hook] session cleanup failed", String(err));
|
|
26177
|
+
}
|
|
25975
26178
|
}
|
|
25976
26179
|
}
|
|
25977
26180
|
function writeUniqueFile(dir, name, data, log2) {
|
|
@@ -26001,7 +26204,9 @@ function writeUniqueFile(dir, name, data, log2) {
|
|
|
26001
26204
|
return null;
|
|
26002
26205
|
}
|
|
26003
26206
|
function processImageAttachments(args) {
|
|
26004
|
-
const { messages, workDir, disabledAgents, log: log2 } = args;
|
|
26207
|
+
const { messages, workDir, imageRouting, disabledAgents, log: log2 } = args;
|
|
26208
|
+
if (imageRouting === "direct")
|
|
26209
|
+
return;
|
|
26005
26210
|
const observerEnabled = !disabledAgents.has("observer");
|
|
26006
26211
|
if (!observerEnabled)
|
|
26007
26212
|
return;
|
|
@@ -26039,6 +26244,7 @@ function processImageAttachments(args) {
|
|
|
26039
26244
|
log2(`[image-hook] failed to create target image directory: ${e}`);
|
|
26040
26245
|
}
|
|
26041
26246
|
const savedPaths = [];
|
|
26247
|
+
const savedImageParts = new Set;
|
|
26042
26248
|
for (const p of imageParts) {
|
|
26043
26249
|
const url = p.url;
|
|
26044
26250
|
const filename = p.filename ?? p.name;
|
|
@@ -26051,14 +26257,21 @@ function processImageAttachments(args) {
|
|
|
26051
26257
|
const ext = sanitizedFilename ? extname(sanitizedFilename) || extFromMime(decoded.mime) : extFromMime(decoded.mime);
|
|
26052
26258
|
const name = `${baseName}-${hash}${ext}`;
|
|
26053
26259
|
const filePath = writeUniqueFile(targetDir, name, decoded.data, log2);
|
|
26054
|
-
if (filePath)
|
|
26260
|
+
if (filePath) {
|
|
26055
26261
|
savedPaths.push(filePath);
|
|
26262
|
+
savedImageParts.add(p);
|
|
26263
|
+
}
|
|
26056
26264
|
}
|
|
26057
26265
|
}
|
|
26058
26266
|
}
|
|
26059
|
-
|
|
26060
|
-
|
|
26061
|
-
|
|
26267
|
+
if (savedPaths.length === 0) {
|
|
26268
|
+
log2("[image-hook] no images saved; leaving original parts in message");
|
|
26269
|
+
continue;
|
|
26270
|
+
}
|
|
26271
|
+
const pathsText = ` Saved to: ${savedPaths.join(", ")}`;
|
|
26272
|
+
log2(`[image-hook] saved image/file parts to disk${pathsText}`);
|
|
26273
|
+
log2(`[image-routing] auto mode: intercepted ${savedImageParts.size} image(s), delegating to @observer`);
|
|
26274
|
+
msg.parts = msg.parts.filter((p) => !savedImageParts.has(p)).concat([
|
|
26062
26275
|
{
|
|
26063
26276
|
type: "text",
|
|
26064
26277
|
text: `[Image attachment detected.${pathsText} Your model may not support image input. Delegate to @observer with the file path(s) above so it can read the file with its read tool.]`
|
|
@@ -26161,15 +26374,7 @@ function helpPrompt() {
|
|
|
26161
26374
|
function createLoopCommandHook() {
|
|
26162
26375
|
return {
|
|
26163
26376
|
registerCommand: (opencodeConfig) => {
|
|
26164
|
-
|
|
26165
|
-
if (cfg?.[COMMAND_NAME2])
|
|
26166
|
-
return;
|
|
26167
|
-
if (!opencodeConfig.command)
|
|
26168
|
-
opencodeConfig.command = {};
|
|
26169
|
-
opencodeConfig.command[COMMAND_NAME2] = {
|
|
26170
|
-
template: "Run an automated execute-verify loop",
|
|
26171
|
-
description: "Dispatch fixer, verify, iterate with file-based history on disk."
|
|
26172
|
-
};
|
|
26377
|
+
registerCommandHook(opencodeConfig, COMMAND_NAME2, "Run an automated execute-verify loop", "Dispatch fixer, verify, iterate with file-based history on disk.");
|
|
26173
26378
|
},
|
|
26174
26379
|
handleCommandExecuteBefore: async (input, output) => {
|
|
26175
26380
|
if (input.command !== COMMAND_NAME2)
|
|
@@ -26223,7 +26428,7 @@ function createPhaseReminderHook(coordinator) {
|
|
|
26223
26428
|
if (isInternalInitiatorPart(originalPart)) {
|
|
26224
26429
|
return;
|
|
26225
26430
|
}
|
|
26226
|
-
if (lastUserMessage.parts.some((part) => part.synthetic === true &&
|
|
26431
|
+
if (lastUserMessage.parts.some((part) => part.synthetic === true && isRecord2(part.metadata) && part.metadata[PHASE_REMINDER_METADATA_KEY] === true)) {
|
|
26227
26432
|
return;
|
|
26228
26433
|
}
|
|
26229
26434
|
lastUserMessage.parts.push({
|
|
@@ -26297,18 +26502,7 @@ function createReflectCommandHook() {
|
|
|
26297
26502
|
let shouldHandleCommand = false;
|
|
26298
26503
|
return {
|
|
26299
26504
|
registerCommand: (opencodeConfig) => {
|
|
26300
|
-
|
|
26301
|
-
if (commandConfig?.[COMMAND_NAME3]) {
|
|
26302
|
-
shouldHandleCommand = false;
|
|
26303
|
-
return;
|
|
26304
|
-
}
|
|
26305
|
-
if (!opencodeConfig.command)
|
|
26306
|
-
opencodeConfig.command = {};
|
|
26307
|
-
opencodeConfig.command[COMMAND_NAME3] = {
|
|
26308
|
-
template: "Review repeated work and suggest workflow improvements",
|
|
26309
|
-
description: "Use reflect to learn from repeated workflows and suggest reusable improvements"
|
|
26310
|
-
};
|
|
26311
|
-
shouldHandleCommand = true;
|
|
26505
|
+
shouldHandleCommand ||= registerCommandHook(opencodeConfig, COMMAND_NAME3, "Review repeated work and suggest workflow improvements", "Use reflect to learn from repeated workflows and suggest reusable improvements");
|
|
26312
26506
|
},
|
|
26313
26507
|
handleCommandExecuteBefore: async (input, output) => {
|
|
26314
26508
|
if (input.command !== COMMAND_NAME3 || !shouldHandleCommand)
|
|
@@ -26534,8 +26728,10 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
26534
26728
|
const terminalJobsInjectedByParent = new Map;
|
|
26535
26729
|
if (options.coordinator) {
|
|
26536
26730
|
options.coordinator.onSessionDeleted((sessionId) => {
|
|
26537
|
-
|
|
26538
|
-
|
|
26731
|
+
if (!options.isFallbackInProgress?.(sessionId)) {
|
|
26732
|
+
backgroundJobBoard.drop(sessionId);
|
|
26733
|
+
backgroundJobBoard.clearParent(sessionId);
|
|
26734
|
+
}
|
|
26539
26735
|
terminalJobsInjectedByParent.delete(sessionId);
|
|
26540
26736
|
taskContextTracker.clearSession(sessionId);
|
|
26541
26737
|
taskContextTracker.prune(backgroundJobBoard);
|
|
@@ -26705,7 +26901,7 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
26705
26901
|
sessionID: input.sessionID
|
|
26706
26902
|
});
|
|
26707
26903
|
}
|
|
26708
|
-
if (!
|
|
26904
|
+
if (!isRecord2(output.args))
|
|
26709
26905
|
return;
|
|
26710
26906
|
const args = output.args;
|
|
26711
26907
|
if (typeof args.subagent_type !== "string" || args.subagent_type.trim() === "") {
|
|
@@ -26890,7 +27086,7 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
26890
27086
|
if (isInternalInitiatorPart(textPart)) {
|
|
26891
27087
|
return;
|
|
26892
27088
|
}
|
|
26893
|
-
if (message.parts.some((part) => part.synthetic === true &&
|
|
27089
|
+
if (message.parts.some((part) => part.synthetic === true && isRecord2(part.metadata) && part.metadata[BACKGROUND_JOB_BOARD_METADATA_KEY] === true)) {
|
|
26894
27090
|
return;
|
|
26895
27091
|
}
|
|
26896
27092
|
rememberInjectedTerminalJobs(message.info.sessionID);
|
|
@@ -26964,7 +27160,7 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
26964
27160
|
}
|
|
26965
27161
|
if (sessionId2 && options.shouldManageSession(sessionId2)) {
|
|
26966
27162
|
const props = input.event.properties;
|
|
26967
|
-
if (!props?.error || !
|
|
27163
|
+
if (!props?.error || !isFailoverError(props.error)) {
|
|
26968
27164
|
terminalJobsInjectedByParent.delete(sessionId2);
|
|
26969
27165
|
}
|
|
26970
27166
|
}
|
|
@@ -27035,7 +27231,7 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
27035
27231
|
result: status.result
|
|
27036
27232
|
});
|
|
27037
27233
|
output.output = formatCancelledTaskStatusOutput(status.taskID, backgroundJobBoard.getResultSummary(status.taskID));
|
|
27038
|
-
if (
|
|
27234
|
+
if (isRecord2(output) && isRecord2(output.metadata)) {
|
|
27039
27235
|
output.metadata.state = "cancelled";
|
|
27040
27236
|
}
|
|
27041
27237
|
}
|
|
@@ -27073,6 +27269,23 @@ import { URL as URL2 } from "node:url";
|
|
|
27073
27269
|
import * as fsSync from "node:fs";
|
|
27074
27270
|
import * as fs6 from "node:fs/promises";
|
|
27075
27271
|
import * as path13 from "node:path";
|
|
27272
|
+
|
|
27273
|
+
// src/utils/frontmatter.ts
|
|
27274
|
+
function parseFrontmatter(content) {
|
|
27275
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
27276
|
+
if (!match)
|
|
27277
|
+
return null;
|
|
27278
|
+
const result = {};
|
|
27279
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
27280
|
+
const kv = line.match(/^([A-Za-z0-9_-]+):\s*(.+?)\s*$/);
|
|
27281
|
+
if (!kv)
|
|
27282
|
+
continue;
|
|
27283
|
+
result[kv[1]] = kv[2].replace(/^(['"])(.*)\1$/, "$2");
|
|
27284
|
+
}
|
|
27285
|
+
return result;
|
|
27286
|
+
}
|
|
27287
|
+
|
|
27288
|
+
// src/interview/document.ts
|
|
27076
27289
|
var DEFAULT_OUTPUT_FOLDER = "interview";
|
|
27077
27290
|
function normalizeOutputFolder(outputFolder) {
|
|
27078
27291
|
const normalized = outputFolder.trim().replace(/^\/+|\/+$/g, "");
|
|
@@ -27182,20 +27395,7 @@ function buildInterviewDocument(idea, summary, history, meta) {
|
|
|
27182
27395
|
].join(`
|
|
27183
27396
|
`);
|
|
27184
27397
|
}
|
|
27185
|
-
|
|
27186
|
-
const match = content.match(/^---\n([\s\S]*?)\n---\n/);
|
|
27187
|
-
if (!match)
|
|
27188
|
-
return null;
|
|
27189
|
-
const result = {};
|
|
27190
|
-
for (const line of match[1].split(`
|
|
27191
|
-
`)) {
|
|
27192
|
-
const colonIdx = line.indexOf(":");
|
|
27193
|
-
if (colonIdx > 0) {
|
|
27194
|
-
result[line.slice(0, colonIdx).trim()] = line.slice(colonIdx + 1).trim();
|
|
27195
|
-
}
|
|
27196
|
-
}
|
|
27197
|
-
return result;
|
|
27198
|
-
}
|
|
27398
|
+
var parseFrontmatter2 = parseFrontmatter;
|
|
27199
27399
|
async function ensureInterviewFile(record) {
|
|
27200
27400
|
await fs6.mkdir(path13.dirname(record.markdownPath), { recursive: true });
|
|
27201
27401
|
try {
|
|
@@ -27333,11 +27533,13 @@ async function readJsonBody(request) {
|
|
|
27333
27533
|
return raw ? JSON.parse(raw) : {};
|
|
27334
27534
|
}
|
|
27335
27535
|
|
|
27336
|
-
// src/
|
|
27337
|
-
var BRAND_LOGO_URL = "https://ohmyopencodeslim.com/android-chrome-512x512.png";
|
|
27536
|
+
// src/utils/escape-html.ts
|
|
27338
27537
|
function escapeHtml(value) {
|
|
27339
|
-
return value.
|
|
27538
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
27340
27539
|
}
|
|
27540
|
+
|
|
27541
|
+
// src/interview/ui.ts
|
|
27542
|
+
var BRAND_LOGO_URL = "https://ohmyopencodeslim.com/android-chrome-512x512.png";
|
|
27341
27543
|
function clipboardHelperJs() {
|
|
27342
27544
|
return `
|
|
27343
27545
|
function copyCommand(text, btn) {
|
|
@@ -29513,7 +29715,7 @@ data: ${JSON.stringify(formatSseState(entry))}
|
|
|
29513
29715
|
const title = extractTitle(content) || entry.replace(/\.md$/, "");
|
|
29514
29716
|
const summary = extractSummarySection(content);
|
|
29515
29717
|
const baseName = entry.replace(/\.md$/, "");
|
|
29516
|
-
const fm =
|
|
29718
|
+
const fm = parseFrontmatter2(content);
|
|
29517
29719
|
items.push({
|
|
29518
29720
|
fileName: entry,
|
|
29519
29721
|
resumeCommand: `/interview ${baseName}`,
|
|
@@ -29548,7 +29750,7 @@ data: ${JSON.stringify(formatSseState(entry))}
|
|
|
29548
29750
|
} catch {
|
|
29549
29751
|
continue;
|
|
29550
29752
|
}
|
|
29551
|
-
const fm =
|
|
29753
|
+
const fm = parseFrontmatter2(content);
|
|
29552
29754
|
if (!fm?.sessionID)
|
|
29553
29755
|
continue;
|
|
29554
29756
|
const title = extractTitle(content) || entry.replace(/\.md$/, "");
|
|
@@ -31301,6 +31503,14 @@ function createInterviewService(ctx, config, deps) {
|
|
|
31301
31503
|
const interview = await createInterview(input.sessionID, idea);
|
|
31302
31504
|
await notifyInterviewUrl(input.sessionID, interview);
|
|
31303
31505
|
output.parts.push(createInternalAgentTextPart(buildKickoffPrompt(idea, maxQuestions)));
|
|
31506
|
+
let sessionTitle = `Interview: ${idea}`;
|
|
31507
|
+
if (sessionTitle.length > 50) {
|
|
31508
|
+
sessionTitle = `${sessionTitle.slice(0, 49)}…`;
|
|
31509
|
+
}
|
|
31510
|
+
ctx.client.session.update?.({
|
|
31511
|
+
path: { id: input.sessionID },
|
|
31512
|
+
body: { title: sessionTitle }
|
|
31513
|
+
})?.catch(() => {});
|
|
31304
31514
|
}
|
|
31305
31515
|
async function handleEvent(input) {
|
|
31306
31516
|
const { event } = input;
|
|
@@ -32061,21 +32271,23 @@ function createBuiltinMcps(disabledMcps = [], websearchConfig) {
|
|
|
32061
32271
|
return mcps;
|
|
32062
32272
|
}
|
|
32063
32273
|
|
|
32064
|
-
// src/multiplexer/
|
|
32274
|
+
// src/multiplexer/cmux/index.ts
|
|
32065
32275
|
init_compat();
|
|
32066
32276
|
|
|
32067
32277
|
// src/multiplexer/shared.ts
|
|
32068
32278
|
init_compat();
|
|
32279
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
32280
|
+
import { basename as basename6, isAbsolute as isAbsolute5 } from "node:path";
|
|
32069
32281
|
function quoteShellArg(value) {
|
|
32070
32282
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
32071
32283
|
}
|
|
32072
32284
|
function normalizePathForShell(directory) {
|
|
32073
32285
|
return process.platform === "win32" ? directory.replace(/\\/g, "/") : directory;
|
|
32074
32286
|
}
|
|
32075
|
-
function buildOpencodeAttachCommand(sessionId, serverUrl, directory) {
|
|
32287
|
+
function buildOpencodeAttachCommand(sessionId, serverUrl, directory, executable = "opencode") {
|
|
32076
32288
|
const attachDir = normalizePathForShell(directory);
|
|
32077
32289
|
return [
|
|
32078
|
-
"opencode",
|
|
32290
|
+
executable === "opencode" ? executable : quoteShellArg(executable),
|
|
32079
32291
|
"attach",
|
|
32080
32292
|
quoteShellArg(serverUrl),
|
|
32081
32293
|
"--session",
|
|
@@ -32084,6 +32296,20 @@ function buildOpencodeAttachCommand(sessionId, serverUrl, directory) {
|
|
|
32084
32296
|
quoteShellArg(attachDir)
|
|
32085
32297
|
].join(" ");
|
|
32086
32298
|
}
|
|
32299
|
+
function resolveHostOpencodeBinary(options = {}) {
|
|
32300
|
+
const pathExists = options.pathExists ?? existsSync9;
|
|
32301
|
+
for (const candidate of [
|
|
32302
|
+
options.override,
|
|
32303
|
+
options.envOverride ?? process.env.OPENCODE_BIN,
|
|
32304
|
+
options.execPath ?? process.execPath,
|
|
32305
|
+
options.argv0 ?? process.argv[0]
|
|
32306
|
+
]) {
|
|
32307
|
+
if (candidate && isAbsolute5(candidate) && /^opencode(?:\.exe)?$/i.test(basename6(candidate)) && pathExists(candidate)) {
|
|
32308
|
+
return candidate;
|
|
32309
|
+
}
|
|
32310
|
+
}
|
|
32311
|
+
return null;
|
|
32312
|
+
}
|
|
32087
32313
|
async function findBinary(binaryName, options = {}) {
|
|
32088
32314
|
const isWindows = process.platform === "win32";
|
|
32089
32315
|
const cmd = isWindows ? "where" : "which";
|
|
@@ -32165,7 +32391,536 @@ async function gracefulClosePane(binary, paneId, options) {
|
|
|
32165
32391
|
}
|
|
32166
32392
|
}
|
|
32167
32393
|
|
|
32394
|
+
// src/multiplexer/cmux/index.ts
|
|
32395
|
+
var MINIMUM_VERSION = "0.64.14";
|
|
32396
|
+
var READINESS_DELAYS_MS = [50, 100, 200, 400, 500, 500, 250];
|
|
32397
|
+
var registries = new Map;
|
|
32398
|
+
var mutationQueue = Promise.resolve();
|
|
32399
|
+
var mutationSequence = 0;
|
|
32400
|
+
|
|
32401
|
+
class CmuxMultiplexer {
|
|
32402
|
+
client;
|
|
32403
|
+
type = "cmux";
|
|
32404
|
+
versionAvailable = false;
|
|
32405
|
+
availabilityError = "unavailable";
|
|
32406
|
+
checkSessionReady;
|
|
32407
|
+
delay;
|
|
32408
|
+
readinessAttemptTimeoutMs;
|
|
32409
|
+
opencodeBinary;
|
|
32410
|
+
constructor(client = new CliCmuxClient, options = {}) {
|
|
32411
|
+
this.client = client;
|
|
32412
|
+
this.checkSessionReady = options.checkSessionReady ?? defaultSessionReady;
|
|
32413
|
+
this.delay = options.delay ?? defaultDelay;
|
|
32414
|
+
this.readinessAttemptTimeoutMs = options.readinessAttemptTimeoutMs ?? 1000;
|
|
32415
|
+
this.opencodeBinary = resolveHostOpencodeBinary({
|
|
32416
|
+
override: options.opencodeBinary,
|
|
32417
|
+
pathExists: options.pathExists
|
|
32418
|
+
});
|
|
32419
|
+
}
|
|
32420
|
+
async isAvailable() {
|
|
32421
|
+
if (this.versionAvailable)
|
|
32422
|
+
return true;
|
|
32423
|
+
const version = await this.client.version().catch(() => null);
|
|
32424
|
+
if (version && compareVersions2(version, MINIMUM_VERSION) >= 0) {
|
|
32425
|
+
this.versionAvailable = true;
|
|
32426
|
+
return true;
|
|
32427
|
+
}
|
|
32428
|
+
this.availabilityError = version ? "hard" : this.client.getVersionError?.() ?? "unavailable";
|
|
32429
|
+
return false;
|
|
32430
|
+
}
|
|
32431
|
+
isInsideSession() {
|
|
32432
|
+
return Boolean(process.env.CMUX_SOCKET_PATH && process.env.CMUX_WORKSPACE_ID && process.env.CMUX_SURFACE_ID);
|
|
32433
|
+
}
|
|
32434
|
+
async spawnPane(sessionId, _description, serverUrl, directory) {
|
|
32435
|
+
if (!this.opencodeBinary)
|
|
32436
|
+
return { success: false, error: "hard" };
|
|
32437
|
+
const opencodeBinary = this.opencodeBinary;
|
|
32438
|
+
const statusUrl = new URL("/session/status", serverUrl);
|
|
32439
|
+
if (!await this.waitForSession(statusUrl, sessionId)) {
|
|
32440
|
+
log("[cmux] spawnPane failed", {
|
|
32441
|
+
stage: "readinessTimeout",
|
|
32442
|
+
sessionId
|
|
32443
|
+
});
|
|
32444
|
+
return { success: false, error: "unavailable" };
|
|
32445
|
+
}
|
|
32446
|
+
return enqueueMutation("spawn", async (sequence) => {
|
|
32447
|
+
if (!await this.isAvailable()) {
|
|
32448
|
+
log("[cmux] spawnPane failed", {
|
|
32449
|
+
sequence,
|
|
32450
|
+
stage: "version",
|
|
32451
|
+
sessionId
|
|
32452
|
+
});
|
|
32453
|
+
return {
|
|
32454
|
+
success: false,
|
|
32455
|
+
error: this.availabilityError
|
|
32456
|
+
};
|
|
32457
|
+
}
|
|
32458
|
+
const root = await this.client.identify();
|
|
32459
|
+
if (!root) {
|
|
32460
|
+
log("[cmux] spawnPane failed", {
|
|
32461
|
+
sequence,
|
|
32462
|
+
stage: "identify",
|
|
32463
|
+
sessionId
|
|
32464
|
+
});
|
|
32465
|
+
return {
|
|
32466
|
+
success: false,
|
|
32467
|
+
error: this.client.getIdentifyError?.() ?? "unavailable"
|
|
32468
|
+
};
|
|
32469
|
+
}
|
|
32470
|
+
const key = registryKey(root.socketPath, root.workspaceId);
|
|
32471
|
+
const registry = registries.get(key) ?? { root, agents: [] };
|
|
32472
|
+
registries.set(key, registry);
|
|
32473
|
+
const previous = registry.agents.at(-1);
|
|
32474
|
+
const targetSurfaceId = previous?.surfaceId ?? registry.root.surfaceId;
|
|
32475
|
+
const direction = previous ? "down" : "right";
|
|
32476
|
+
const created = await this.client.createSurface({
|
|
32477
|
+
workspaceId: root.workspaceId,
|
|
32478
|
+
targetSurfaceId,
|
|
32479
|
+
direction,
|
|
32480
|
+
focus: false
|
|
32481
|
+
}, root.socketPath);
|
|
32482
|
+
if (!created) {
|
|
32483
|
+
log("[cmux] spawnPane failed", {
|
|
32484
|
+
sequence,
|
|
32485
|
+
stage: "createSurface",
|
|
32486
|
+
sessionId,
|
|
32487
|
+
workspaceId: root.workspaceId,
|
|
32488
|
+
targetSurfaceId,
|
|
32489
|
+
direction
|
|
32490
|
+
});
|
|
32491
|
+
return {
|
|
32492
|
+
success: false,
|
|
32493
|
+
error: this.client.getCreateError?.() ?? "hard"
|
|
32494
|
+
};
|
|
32495
|
+
}
|
|
32496
|
+
const handle = {
|
|
32497
|
+
v: 1,
|
|
32498
|
+
socketPath: root.socketPath,
|
|
32499
|
+
workspaceId: root.workspaceId,
|
|
32500
|
+
paneId: created.paneId,
|
|
32501
|
+
surfaceId: created.surfaceId
|
|
32502
|
+
};
|
|
32503
|
+
const encodedHandle = encodeHandle(handle);
|
|
32504
|
+
const command = buildOpencodeAttachCommand(sessionId, serverUrl, directory, opencodeBinary);
|
|
32505
|
+
try {
|
|
32506
|
+
const started = await this.client.respawnSurface(root.workspaceId, created.surfaceId, command, root.socketPath);
|
|
32507
|
+
if (!started) {
|
|
32508
|
+
log("[cmux] spawnPane failed", {
|
|
32509
|
+
sequence,
|
|
32510
|
+
stage: "respawn",
|
|
32511
|
+
sessionId,
|
|
32512
|
+
workspaceId: root.workspaceId,
|
|
32513
|
+
targetSurfaceId,
|
|
32514
|
+
direction
|
|
32515
|
+
});
|
|
32516
|
+
throw new Error("cmux respawn-pane failed");
|
|
32517
|
+
}
|
|
32518
|
+
} catch (error) {
|
|
32519
|
+
log("[cmux] spawnPane respawn exception", {
|
|
32520
|
+
sequence,
|
|
32521
|
+
stage: "respawn",
|
|
32522
|
+
sessionId,
|
|
32523
|
+
workspaceId: root.workspaceId,
|
|
32524
|
+
targetSurfaceId,
|
|
32525
|
+
direction,
|
|
32526
|
+
errorType: errorName(error)
|
|
32527
|
+
});
|
|
32528
|
+
const cleaned = await this.cleanupPane(root.workspaceId, created.surfaceId, root.socketPath);
|
|
32529
|
+
return cleaned ? { success: false, error: "unavailable" } : {
|
|
32530
|
+
success: false,
|
|
32531
|
+
error: "unavailable",
|
|
32532
|
+
orphanPaneId: encodedHandle
|
|
32533
|
+
};
|
|
32534
|
+
}
|
|
32535
|
+
registry.agents.push(handle);
|
|
32536
|
+
await this.equalize(root.workspaceId, root.socketPath);
|
|
32537
|
+
return { success: true, paneId: encodeHandle(handle) };
|
|
32538
|
+
});
|
|
32539
|
+
}
|
|
32540
|
+
closePane(paneId) {
|
|
32541
|
+
const handle = decodeHandle(paneId);
|
|
32542
|
+
if (!handle)
|
|
32543
|
+
return Promise.resolve(false);
|
|
32544
|
+
return enqueueMutation("close", async () => {
|
|
32545
|
+
if (!await this.isAvailable())
|
|
32546
|
+
return false;
|
|
32547
|
+
const result = await this.client.closeSurface(handle.workspaceId, handle.surfaceId, handle.socketPath);
|
|
32548
|
+
if (result === "failed")
|
|
32549
|
+
return false;
|
|
32550
|
+
const key = registryKey(handle.socketPath, handle.workspaceId);
|
|
32551
|
+
const registry = registries.get(key);
|
|
32552
|
+
if (registry) {
|
|
32553
|
+
const index = registry.agents.findIndex((agent) => agent.surfaceId === handle.surfaceId);
|
|
32554
|
+
if (index >= 0)
|
|
32555
|
+
registry.agents.splice(index, 1);
|
|
32556
|
+
if (registry.agents.length === 0)
|
|
32557
|
+
registries.delete(key);
|
|
32558
|
+
}
|
|
32559
|
+
await this.equalize(handle.workspaceId, handle.socketPath);
|
|
32560
|
+
return true;
|
|
32561
|
+
});
|
|
32562
|
+
}
|
|
32563
|
+
async applyLayout(_layout, _mainPaneSize) {}
|
|
32564
|
+
async cleanupPane(workspaceId, surfaceId, socketPath) {
|
|
32565
|
+
try {
|
|
32566
|
+
const result = await this.client.closeSurface(workspaceId, surfaceId, socketPath);
|
|
32567
|
+
if (result === "failed") {
|
|
32568
|
+
log("[cmux] failed to close pre-respawn surface", {
|
|
32569
|
+
workspaceId,
|
|
32570
|
+
surfaceId
|
|
32571
|
+
});
|
|
32572
|
+
return false;
|
|
32573
|
+
}
|
|
32574
|
+
return true;
|
|
32575
|
+
} catch (error) {
|
|
32576
|
+
log("[cmux] failed to close pre-respawn surface", {
|
|
32577
|
+
workspaceId,
|
|
32578
|
+
surfaceId,
|
|
32579
|
+
errorType: errorName(error)
|
|
32580
|
+
});
|
|
32581
|
+
return false;
|
|
32582
|
+
}
|
|
32583
|
+
}
|
|
32584
|
+
async waitForSession(url, sessionId) {
|
|
32585
|
+
for (let attempt = 0;attempt <= READINESS_DELAYS_MS.length; attempt++) {
|
|
32586
|
+
const controller = new AbortController;
|
|
32587
|
+
const timeout = setTimeout(() => controller.abort(), this.readinessAttemptTimeoutMs);
|
|
32588
|
+
timeout.unref?.();
|
|
32589
|
+
try {
|
|
32590
|
+
if (await Promise.race([
|
|
32591
|
+
this.checkSessionReady(url, sessionId, controller.signal),
|
|
32592
|
+
new Promise((resolve4) => controller.signal.addEventListener("abort", () => resolve4(false), {
|
|
32593
|
+
once: true
|
|
32594
|
+
}))
|
|
32595
|
+
])) {
|
|
32596
|
+
return true;
|
|
32597
|
+
}
|
|
32598
|
+
} catch {} finally {
|
|
32599
|
+
clearTimeout(timeout);
|
|
32600
|
+
}
|
|
32601
|
+
const delay2 = READINESS_DELAYS_MS[attempt];
|
|
32602
|
+
if (delay2 === undefined)
|
|
32603
|
+
return false;
|
|
32604
|
+
await this.delay(delay2);
|
|
32605
|
+
}
|
|
32606
|
+
return false;
|
|
32607
|
+
}
|
|
32608
|
+
async equalize(workspaceId, socketPath) {
|
|
32609
|
+
try {
|
|
32610
|
+
const success = await this.client.equalizeSplits({
|
|
32611
|
+
workspace_id: workspaceId,
|
|
32612
|
+
orientation: "vertical"
|
|
32613
|
+
}, socketPath);
|
|
32614
|
+
if (!success)
|
|
32615
|
+
log("[cmux] workspace.equalize_splits failed", { workspaceId });
|
|
32616
|
+
} catch (error) {
|
|
32617
|
+
log("[cmux] workspace.equalize_splits failed", {
|
|
32618
|
+
workspaceId,
|
|
32619
|
+
error: String(error)
|
|
32620
|
+
});
|
|
32621
|
+
}
|
|
32622
|
+
}
|
|
32623
|
+
}
|
|
32624
|
+
|
|
32625
|
+
class SpawnCommandRunner {
|
|
32626
|
+
timeoutMs;
|
|
32627
|
+
spawn;
|
|
32628
|
+
constructor(timeoutMs = 5000, spawn3 = crossSpawn) {
|
|
32629
|
+
this.timeoutMs = timeoutMs;
|
|
32630
|
+
this.spawn = spawn3;
|
|
32631
|
+
}
|
|
32632
|
+
async run(argv) {
|
|
32633
|
+
const proc = this.spawn(argv, { stdout: "pipe", stderr: "pipe" });
|
|
32634
|
+
let timeout;
|
|
32635
|
+
try {
|
|
32636
|
+
return await Promise.race([
|
|
32637
|
+
Promise.all([proc.exited, proc.stdout(), proc.stderr()]).then(([exitCode, stdout, stderr]) => ({ exitCode, stdout, stderr })),
|
|
32638
|
+
new Promise((resolve4) => {
|
|
32639
|
+
timeout = setTimeout(() => {
|
|
32640
|
+
proc.kill("SIGTERM");
|
|
32641
|
+
resolve4({
|
|
32642
|
+
exitCode: 124,
|
|
32643
|
+
stdout: "",
|
|
32644
|
+
stderr: "unavailable: cmux command timed out"
|
|
32645
|
+
});
|
|
32646
|
+
}, this.timeoutMs);
|
|
32647
|
+
timeout.unref?.();
|
|
32648
|
+
})
|
|
32649
|
+
]);
|
|
32650
|
+
} finally {
|
|
32651
|
+
if (timeout)
|
|
32652
|
+
clearTimeout(timeout);
|
|
32653
|
+
}
|
|
32654
|
+
}
|
|
32655
|
+
}
|
|
32656
|
+
|
|
32657
|
+
class CliCmuxClient {
|
|
32658
|
+
runner;
|
|
32659
|
+
binary = null;
|
|
32660
|
+
versionError = "unavailable";
|
|
32661
|
+
identifyError = "unavailable";
|
|
32662
|
+
lastRunThrew = false;
|
|
32663
|
+
createError = "hard";
|
|
32664
|
+
constructor(runner = new SpawnCommandRunner, binary) {
|
|
32665
|
+
this.runner = runner;
|
|
32666
|
+
this.binary = binary ?? null;
|
|
32667
|
+
}
|
|
32668
|
+
async version() {
|
|
32669
|
+
this.versionError = "unavailable";
|
|
32670
|
+
const result = await this.run(["--version"]);
|
|
32671
|
+
if (!result || result.exitCode !== 0) {
|
|
32672
|
+
this.versionError = "unavailable";
|
|
32673
|
+
return null;
|
|
32674
|
+
}
|
|
32675
|
+
const version = result.stdout.match(/\d+\.\d+\.\d+/)?.[0] ?? null;
|
|
32676
|
+
this.versionError = "hard";
|
|
32677
|
+
return version;
|
|
32678
|
+
}
|
|
32679
|
+
getVersionError() {
|
|
32680
|
+
return this.versionError;
|
|
32681
|
+
}
|
|
32682
|
+
async identify() {
|
|
32683
|
+
this.identifyError = "unavailable";
|
|
32684
|
+
const result = await this.run(["--id-format", "uuids", "identify"]);
|
|
32685
|
+
if (!result || result.exitCode !== 0) {
|
|
32686
|
+
this.identifyError = "unavailable";
|
|
32687
|
+
return null;
|
|
32688
|
+
}
|
|
32689
|
+
const value = asRecord(parseJson(result.stdout));
|
|
32690
|
+
const caller = asRecord(value?.caller);
|
|
32691
|
+
const focused = asRecord(value?.focused);
|
|
32692
|
+
const workspaceId = stringField(caller, "workspace_id") ?? stringField(focused, "workspace_id");
|
|
32693
|
+
const paneId = stringField(caller, "pane_id") ?? stringField(focused, "pane_id");
|
|
32694
|
+
const surfaceId = stringField(caller, "surface_id") ?? stringField(focused, "surface_id");
|
|
32695
|
+
const socketPath = stringField(value, "socket_path");
|
|
32696
|
+
if (workspaceId && paneId && surfaceId && socketPath) {
|
|
32697
|
+
return { workspaceId, paneId, surfaceId, socketPath };
|
|
32698
|
+
}
|
|
32699
|
+
log("[cmux] response parse failed", {
|
|
32700
|
+
operation: "identify",
|
|
32701
|
+
reason: value ? "missing_fields" : "invalid_json",
|
|
32702
|
+
stdoutLength: result.stdout.length
|
|
32703
|
+
});
|
|
32704
|
+
this.identifyError = "hard";
|
|
32705
|
+
return null;
|
|
32706
|
+
}
|
|
32707
|
+
getIdentifyError() {
|
|
32708
|
+
return this.identifyError;
|
|
32709
|
+
}
|
|
32710
|
+
async createSurface(input, socketPath) {
|
|
32711
|
+
this.createError = "hard";
|
|
32712
|
+
const result = await this.run(withSocket(socketPath, [
|
|
32713
|
+
"--json",
|
|
32714
|
+
"--id-format",
|
|
32715
|
+
"uuids",
|
|
32716
|
+
"new-split",
|
|
32717
|
+
input.direction,
|
|
32718
|
+
"--workspace",
|
|
32719
|
+
input.workspaceId,
|
|
32720
|
+
"--surface",
|
|
32721
|
+
input.targetSurfaceId,
|
|
32722
|
+
"--focus",
|
|
32723
|
+
"false"
|
|
32724
|
+
]));
|
|
32725
|
+
if (!result || result.exitCode !== 0) {
|
|
32726
|
+
this.createError = this.lastRunThrew ? "unavailable" : classifyCreateError(result?.stderr ?? "");
|
|
32727
|
+
return null;
|
|
32728
|
+
}
|
|
32729
|
+
const value = asRecord(parseJson(result.stdout));
|
|
32730
|
+
const resultValue = asRecord(value?.result);
|
|
32731
|
+
const pane = asRecord(resultValue?.pane);
|
|
32732
|
+
const paneId = stringField(value, "pane_id") ?? stringField(resultValue, "pane_id") ?? stringField(pane, "pane_id");
|
|
32733
|
+
const surfaceId = stringField(value, "surface_id") ?? stringField(resultValue, "surface_id") ?? stringField(pane, "surface_id");
|
|
32734
|
+
if (paneId && surfaceId)
|
|
32735
|
+
return { paneId, surfaceId };
|
|
32736
|
+
log("[cmux] response parse failed", {
|
|
32737
|
+
operation: "new-split",
|
|
32738
|
+
reason: value ? "missing_fields" : "invalid_json",
|
|
32739
|
+
stdoutLength: result.stdout.length
|
|
32740
|
+
});
|
|
32741
|
+
return null;
|
|
32742
|
+
}
|
|
32743
|
+
getCreateError() {
|
|
32744
|
+
return this.createError;
|
|
32745
|
+
}
|
|
32746
|
+
async respawnSurface(workspaceId, surfaceId, command, socketPath) {
|
|
32747
|
+
const result = await this.run(withSocket(socketPath, [
|
|
32748
|
+
"respawn-pane",
|
|
32749
|
+
"--workspace",
|
|
32750
|
+
workspaceId,
|
|
32751
|
+
"--surface",
|
|
32752
|
+
surfaceId,
|
|
32753
|
+
"--command",
|
|
32754
|
+
command
|
|
32755
|
+
]));
|
|
32756
|
+
return result?.exitCode === 0;
|
|
32757
|
+
}
|
|
32758
|
+
async closeSurface(workspaceId, surfaceId, socketPath) {
|
|
32759
|
+
const result = await this.run(withSocket(socketPath, [
|
|
32760
|
+
"close-surface",
|
|
32761
|
+
"--workspace",
|
|
32762
|
+
workspaceId,
|
|
32763
|
+
"--surface",
|
|
32764
|
+
surfaceId
|
|
32765
|
+
]));
|
|
32766
|
+
if (result?.exitCode === 0)
|
|
32767
|
+
return "closed";
|
|
32768
|
+
return result?.stderr.toLowerCase().includes("not_found") || result?.stderr.toLowerCase().includes("not found") ? "not_found" : "failed";
|
|
32769
|
+
}
|
|
32770
|
+
async equalizeSplits(params, socketPath) {
|
|
32771
|
+
const result = await this.run(withSocket(socketPath, [
|
|
32772
|
+
"rpc",
|
|
32773
|
+
"workspace.equalize_splits",
|
|
32774
|
+
JSON.stringify(params)
|
|
32775
|
+
]));
|
|
32776
|
+
return result?.exitCode === 0;
|
|
32777
|
+
}
|
|
32778
|
+
async run(args) {
|
|
32779
|
+
this.lastRunThrew = false;
|
|
32780
|
+
this.binary ??= await findBinary("cmux");
|
|
32781
|
+
if (!this.binary)
|
|
32782
|
+
return null;
|
|
32783
|
+
let result;
|
|
32784
|
+
try {
|
|
32785
|
+
result = await this.runner.run([this.binary, ...args]);
|
|
32786
|
+
} catch (error) {
|
|
32787
|
+
this.lastRunThrew = true;
|
|
32788
|
+
log("[cmux] command threw", {
|
|
32789
|
+
operation: commandOperation(args),
|
|
32790
|
+
errorType: errorName(error)
|
|
32791
|
+
});
|
|
32792
|
+
return null;
|
|
32793
|
+
}
|
|
32794
|
+
if (result.exitCode !== 0) {
|
|
32795
|
+
const operation = commandOperation(args);
|
|
32796
|
+
log("[cmux] command failed", {
|
|
32797
|
+
operation,
|
|
32798
|
+
exitCode: result.exitCode,
|
|
32799
|
+
stderr: operation === "respawn-pane" ? "[redacted: may contain attach command]" : safeSummary(result.stderr)
|
|
32800
|
+
});
|
|
32801
|
+
}
|
|
32802
|
+
return result;
|
|
32803
|
+
}
|
|
32804
|
+
}
|
|
32805
|
+
function enqueueMutation(operation, mutation) {
|
|
32806
|
+
const sequence = ++mutationSequence;
|
|
32807
|
+
log("[cmux] mutation enqueue", {
|
|
32808
|
+
sequence,
|
|
32809
|
+
operation,
|
|
32810
|
+
agentCount: registryAgentCount()
|
|
32811
|
+
});
|
|
32812
|
+
const run = async () => {
|
|
32813
|
+
log("[cmux] mutation start", {
|
|
32814
|
+
sequence,
|
|
32815
|
+
operation,
|
|
32816
|
+
agentCount: registryAgentCount()
|
|
32817
|
+
});
|
|
32818
|
+
try {
|
|
32819
|
+
return await mutation(sequence);
|
|
32820
|
+
} finally {
|
|
32821
|
+
log("[cmux] mutation end", {
|
|
32822
|
+
sequence,
|
|
32823
|
+
operation,
|
|
32824
|
+
agentCount: registryAgentCount()
|
|
32825
|
+
});
|
|
32826
|
+
}
|
|
32827
|
+
};
|
|
32828
|
+
const result = mutationQueue.then(run, run);
|
|
32829
|
+
mutationQueue = result.then(() => {
|
|
32830
|
+
return;
|
|
32831
|
+
}, () => {
|
|
32832
|
+
return;
|
|
32833
|
+
});
|
|
32834
|
+
return result;
|
|
32835
|
+
}
|
|
32836
|
+
function registryAgentCount() {
|
|
32837
|
+
let count = 0;
|
|
32838
|
+
for (const registry of registries.values())
|
|
32839
|
+
count += registry.agents.length;
|
|
32840
|
+
return count;
|
|
32841
|
+
}
|
|
32842
|
+
function commandOperation(args) {
|
|
32843
|
+
return args.find((arg) => [
|
|
32844
|
+
"identify",
|
|
32845
|
+
"new-split",
|
|
32846
|
+
"respawn-pane",
|
|
32847
|
+
"close-surface",
|
|
32848
|
+
"rpc"
|
|
32849
|
+
].includes(arg)) ?? "version";
|
|
32850
|
+
}
|
|
32851
|
+
function withSocket(socketPath, args) {
|
|
32852
|
+
return socketPath ? ["--socket", socketPath, ...args] : args;
|
|
32853
|
+
}
|
|
32854
|
+
function safeSummary(value) {
|
|
32855
|
+
const trimmed = value.trim();
|
|
32856
|
+
return trimmed.length > 300 ? `${trimmed.slice(0, 300)}…` : trimmed;
|
|
32857
|
+
}
|
|
32858
|
+
function classifyCreateError(stderr) {
|
|
32859
|
+
const normalized = stderr.toLowerCase();
|
|
32860
|
+
if (normalized.includes("not_found") || normalized.includes("not found")) {
|
|
32861
|
+
return "not_found";
|
|
32862
|
+
}
|
|
32863
|
+
if (normalized.includes("unavailable"))
|
|
32864
|
+
return "unavailable";
|
|
32865
|
+
if (normalized.includes("invalid_state") || normalized.includes("invalid state")) {
|
|
32866
|
+
return "invalid_state";
|
|
32867
|
+
}
|
|
32868
|
+
return "hard";
|
|
32869
|
+
}
|
|
32870
|
+
function errorName(error) {
|
|
32871
|
+
return error instanceof Error ? error.name : typeof error;
|
|
32872
|
+
}
|
|
32873
|
+
async function defaultSessionReady(url, sessionId, signal) {
|
|
32874
|
+
const response = await fetch(url, { signal });
|
|
32875
|
+
if (!response.ok)
|
|
32876
|
+
return false;
|
|
32877
|
+
const statuses = await response.json();
|
|
32878
|
+
return ["idle", "running", "busy", "retry"].includes(statuses[sessionId]?.type ?? "");
|
|
32879
|
+
}
|
|
32880
|
+
function defaultDelay(milliseconds) {
|
|
32881
|
+
return new Promise((resolve4) => setTimeout(resolve4, milliseconds));
|
|
32882
|
+
}
|
|
32883
|
+
function registryKey(socketPath, workspaceId) {
|
|
32884
|
+
return `${socketPath}\x00${workspaceId}`;
|
|
32885
|
+
}
|
|
32886
|
+
function encodeHandle(handle) {
|
|
32887
|
+
return `cmux:v1:${Buffer.from(JSON.stringify(handle)).toString("base64url")}`;
|
|
32888
|
+
}
|
|
32889
|
+
function decodeHandle(value) {
|
|
32890
|
+
if (!value.startsWith("cmux:v1:"))
|
|
32891
|
+
return null;
|
|
32892
|
+
try {
|
|
32893
|
+
const parsed = JSON.parse(Buffer.from(value.slice("cmux:v1:".length), "base64url").toString());
|
|
32894
|
+
return parsed.v === 1 && typeof parsed.socketPath === "string" && typeof parsed.workspaceId === "string" && typeof parsed.paneId === "string" && typeof parsed.surfaceId === "string" ? parsed : null;
|
|
32895
|
+
} catch {
|
|
32896
|
+
return null;
|
|
32897
|
+
}
|
|
32898
|
+
}
|
|
32899
|
+
function compareVersions2(left, right) {
|
|
32900
|
+
const a = left.split(".").map(Number);
|
|
32901
|
+
const b = right.split(".").map(Number);
|
|
32902
|
+
for (let index = 0;index < 3; index++) {
|
|
32903
|
+
if (a[index] !== b[index])
|
|
32904
|
+
return (a[index] ?? 0) - (b[index] ?? 0);
|
|
32905
|
+
}
|
|
32906
|
+
return 0;
|
|
32907
|
+
}
|
|
32908
|
+
function parseJson(stdout) {
|
|
32909
|
+
try {
|
|
32910
|
+
return JSON.parse(stdout);
|
|
32911
|
+
} catch {
|
|
32912
|
+
return null;
|
|
32913
|
+
}
|
|
32914
|
+
}
|
|
32915
|
+
function asRecord(value) {
|
|
32916
|
+
return value && typeof value === "object" ? value : null;
|
|
32917
|
+
}
|
|
32918
|
+
function stringField(value, field) {
|
|
32919
|
+
const candidate = value?.[field];
|
|
32920
|
+
return typeof candidate === "string" ? candidate : null;
|
|
32921
|
+
}
|
|
32168
32922
|
// src/multiplexer/herdr/index.ts
|
|
32923
|
+
init_compat();
|
|
32169
32924
|
class HerdrMultiplexer {
|
|
32170
32925
|
type = "herdr";
|
|
32171
32926
|
binaryPath = null;
|
|
@@ -32174,6 +32929,7 @@ class HerdrMultiplexer {
|
|
|
32174
32929
|
layout;
|
|
32175
32930
|
paneDirection;
|
|
32176
32931
|
agentAreaPaneId = null;
|
|
32932
|
+
spawnMutex = Promise.resolve();
|
|
32177
32933
|
constructor(layout = "main-vertical", mainPaneSize = 60) {
|
|
32178
32934
|
this.layout = layout;
|
|
32179
32935
|
this.paneDirection = getPaneDirection(layout);
|
|
@@ -32190,6 +32946,17 @@ class HerdrMultiplexer {
|
|
|
32190
32946
|
return !!(process.env.HERDR_ENV || process.env.HERDR_PANE_ID);
|
|
32191
32947
|
}
|
|
32192
32948
|
async spawnPane(sessionId, description, serverUrl, directory) {
|
|
32949
|
+
const prev = this.spawnMutex;
|
|
32950
|
+
let release2;
|
|
32951
|
+
this.spawnMutex = new Promise((r) => release2 = r);
|
|
32952
|
+
await prev;
|
|
32953
|
+
try {
|
|
32954
|
+
return await this.doSpawn(sessionId, description, serverUrl, directory);
|
|
32955
|
+
} finally {
|
|
32956
|
+
release2();
|
|
32957
|
+
}
|
|
32958
|
+
}
|
|
32959
|
+
async doSpawn(sessionId, description, serverUrl, directory) {
|
|
32193
32960
|
const herdr = await this.getBinary();
|
|
32194
32961
|
if (!herdr) {
|
|
32195
32962
|
log("[herdr] spawnPane: herdr binary not found");
|
|
@@ -32900,8 +33667,15 @@ function getMultiplexer(config) {
|
|
|
32900
33667
|
multiplexer = new HerdrMultiplexer(config.layout, config.main_pane_size);
|
|
32901
33668
|
actualType = "herdr";
|
|
32902
33669
|
break;
|
|
33670
|
+
case "cmux":
|
|
33671
|
+
multiplexer = new CmuxMultiplexer;
|
|
33672
|
+
actualType = "cmux";
|
|
33673
|
+
break;
|
|
32903
33674
|
case "auto": {
|
|
32904
|
-
if (process.env.
|
|
33675
|
+
if (process.env.CMUX_SOCKET_PATH && process.env.CMUX_WORKSPACE_ID && process.env.CMUX_SURFACE_ID) {
|
|
33676
|
+
multiplexer = new CmuxMultiplexer;
|
|
33677
|
+
actualType = "cmux";
|
|
33678
|
+
} else if (process.env.TMUX) {
|
|
32905
33679
|
multiplexer = new TmuxMultiplexer(config.layout, config.main_pane_size);
|
|
32906
33680
|
actualType = "tmux";
|
|
32907
33681
|
} else if (process.env.ZELLIJ) {
|
|
@@ -32929,6 +33703,692 @@ function startAvailabilityCheck(config) {
|
|
|
32929
33703
|
multiplexer.isAvailable().catch(() => {});
|
|
32930
33704
|
}
|
|
32931
33705
|
}
|
|
33706
|
+
// src/multiplexer/types.ts
|
|
33707
|
+
async function isServerRunning(serverUrl, timeoutMs = 3000, maxAttempts = 2) {
|
|
33708
|
+
const healthUrl = new URL("/health", serverUrl).toString();
|
|
33709
|
+
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
33710
|
+
const controller = new AbortController;
|
|
33711
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
33712
|
+
let response = null;
|
|
33713
|
+
try {
|
|
33714
|
+
response = await fetch(healthUrl, { signal: controller.signal }).catch(() => null);
|
|
33715
|
+
} finally {
|
|
33716
|
+
clearTimeout(timeout);
|
|
33717
|
+
}
|
|
33718
|
+
if (response?.ok) {
|
|
33719
|
+
return true;
|
|
33720
|
+
}
|
|
33721
|
+
if (attempt < maxAttempts) {
|
|
33722
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
33723
|
+
}
|
|
33724
|
+
}
|
|
33725
|
+
return false;
|
|
33726
|
+
}
|
|
33727
|
+
|
|
33728
|
+
// src/multiplexer/cmux/close-policy.ts
|
|
33729
|
+
class CmuxClosePolicy {
|
|
33730
|
+
budgetMs;
|
|
33731
|
+
maxAttempts;
|
|
33732
|
+
constructor(budgetMs = 30000, maxAttempts = 4) {
|
|
33733
|
+
this.budgetMs = budgetMs;
|
|
33734
|
+
this.maxAttempts = maxAttempts;
|
|
33735
|
+
}
|
|
33736
|
+
request(reason, version, now, current) {
|
|
33737
|
+
if (!current || reason === "deleted" && current.reason === "idle") {
|
|
33738
|
+
return {
|
|
33739
|
+
reason,
|
|
33740
|
+
expectedActivityVersion: version,
|
|
33741
|
+
attempts: 0,
|
|
33742
|
+
deadline: now + this.budgetMs,
|
|
33743
|
+
phase: "pending",
|
|
33744
|
+
nextAttemptAt: now,
|
|
33745
|
+
cooldowns: 0
|
|
33746
|
+
};
|
|
33747
|
+
}
|
|
33748
|
+
return current;
|
|
33749
|
+
}
|
|
33750
|
+
activity(intent) {
|
|
33751
|
+
return intent?.reason === "idle" ? undefined : intent;
|
|
33752
|
+
}
|
|
33753
|
+
failed(intent, now) {
|
|
33754
|
+
const attempts = intent.attempts + 1;
|
|
33755
|
+
if (attempts >= this.maxAttempts || now >= intent.deadline) {
|
|
33756
|
+
const cooldowns = intent.cooldowns + 1;
|
|
33757
|
+
const delay2 = cooldowns === 1 ? 30000 : cooldowns === 2 ? 60000 : Infinity;
|
|
33758
|
+
return {
|
|
33759
|
+
...intent,
|
|
33760
|
+
attempts,
|
|
33761
|
+
phase: "cooldown",
|
|
33762
|
+
nextAttemptAt: now + delay2,
|
|
33763
|
+
cooldowns
|
|
33764
|
+
};
|
|
33765
|
+
}
|
|
33766
|
+
return { ...intent, attempts, nextAttemptAt: now + 1000 };
|
|
33767
|
+
}
|
|
33768
|
+
resume(intent, now) {
|
|
33769
|
+
if (intent.phase !== "cooldown" || now < intent.nextAttemptAt)
|
|
33770
|
+
return intent;
|
|
33771
|
+
return {
|
|
33772
|
+
...intent,
|
|
33773
|
+
attempts: 0,
|
|
33774
|
+
deadline: now + this.budgetMs,
|
|
33775
|
+
phase: "pending",
|
|
33776
|
+
nextAttemptAt: now
|
|
33777
|
+
};
|
|
33778
|
+
}
|
|
33779
|
+
complete() {
|
|
33780
|
+
return;
|
|
33781
|
+
}
|
|
33782
|
+
}
|
|
33783
|
+
|
|
33784
|
+
// src/multiplexer/cmux/session-state.ts
|
|
33785
|
+
var STORE_KEY = Symbol.for("oh-my-opencode-slim.cmux-session-store");
|
|
33786
|
+
function records() {
|
|
33787
|
+
const globalStore = globalThis;
|
|
33788
|
+
globalStore[STORE_KEY] ??= new Map;
|
|
33789
|
+
return globalStore[STORE_KEY];
|
|
33790
|
+
}
|
|
33791
|
+
|
|
33792
|
+
class CmuxSessionStore {
|
|
33793
|
+
claimCreated(record) {
|
|
33794
|
+
const existing = records().get(record.session);
|
|
33795
|
+
if (existing) {
|
|
33796
|
+
if (existing.directory !== record.directory || existing.lifecycle !== "orphaned" && existing.lifecycle !== "deleted")
|
|
33797
|
+
return false;
|
|
33798
|
+
existing.closeTimer?.cancel();
|
|
33799
|
+
Object.assign(record, existing, {
|
|
33800
|
+
owner: record.owner,
|
|
33801
|
+
closeTimer: undefined
|
|
33802
|
+
});
|
|
33803
|
+
}
|
|
33804
|
+
records().set(record.session, record);
|
|
33805
|
+
return true;
|
|
33806
|
+
}
|
|
33807
|
+
get(session2) {
|
|
33808
|
+
return records().get(session2);
|
|
33809
|
+
}
|
|
33810
|
+
ownedBy(owner) {
|
|
33811
|
+
return [...records().values()].filter((record) => record.owner === owner);
|
|
33812
|
+
}
|
|
33813
|
+
claimOrphans(owner, directory) {
|
|
33814
|
+
const claimed = [...records().values()].filter((record) => record.directory === directory && Boolean(record.paneId) && (record.lifecycle === "orphaned" || record.lifecycle === "deleted"));
|
|
33815
|
+
for (const record of claimed) {
|
|
33816
|
+
record.closeTimer?.cancel();
|
|
33817
|
+
record.closeTimer = undefined;
|
|
33818
|
+
record.owner = owner;
|
|
33819
|
+
}
|
|
33820
|
+
return claimed;
|
|
33821
|
+
}
|
|
33822
|
+
markAttached(session2, paneId, now) {
|
|
33823
|
+
const record = records().get(session2);
|
|
33824
|
+
if (!record)
|
|
33825
|
+
return;
|
|
33826
|
+
Object.assign(record, {
|
|
33827
|
+
paneId,
|
|
33828
|
+
attachedAt: now,
|
|
33829
|
+
lastActivityAt: now,
|
|
33830
|
+
spawnState: "attached",
|
|
33831
|
+
deferredSpawn: undefined
|
|
33832
|
+
});
|
|
33833
|
+
}
|
|
33834
|
+
markActivity(session2, now) {
|
|
33835
|
+
const record = records().get(session2);
|
|
33836
|
+
if (!record)
|
|
33837
|
+
return;
|
|
33838
|
+
record.lastActivityAt = now;
|
|
33839
|
+
record.activityVersion += 1;
|
|
33840
|
+
record.idleConsecutive = 0;
|
|
33841
|
+
record.statusMissingSince = undefined;
|
|
33842
|
+
}
|
|
33843
|
+
markDeleted(session2) {
|
|
33844
|
+
const record = records().get(session2);
|
|
33845
|
+
if (record)
|
|
33846
|
+
record.lifecycle = "deleted";
|
|
33847
|
+
}
|
|
33848
|
+
markOrphaned(session2) {
|
|
33849
|
+
const record = records().get(session2);
|
|
33850
|
+
if (record)
|
|
33851
|
+
record.lifecycle = "orphaned";
|
|
33852
|
+
}
|
|
33853
|
+
removeAfterConfirmedClose(session2) {
|
|
33854
|
+
const record = records().get(session2);
|
|
33855
|
+
return record?.paneId ? records().delete(session2) : false;
|
|
33856
|
+
}
|
|
33857
|
+
removeWithoutPane(session2) {
|
|
33858
|
+
const record = records().get(session2);
|
|
33859
|
+
return record && !record.paneId ? records().delete(session2) : false;
|
|
33860
|
+
}
|
|
33861
|
+
resetForTests() {
|
|
33862
|
+
for (const record of records().values()) {
|
|
33863
|
+
record.deferredSpawn?.timer?.cancel();
|
|
33864
|
+
record.closeTimer?.cancel();
|
|
33865
|
+
}
|
|
33866
|
+
records().clear();
|
|
33867
|
+
}
|
|
33868
|
+
}
|
|
33869
|
+
|
|
33870
|
+
// src/multiplexer/cmux/session-lifecycle.ts
|
|
33871
|
+
var ACTIVITY_EVENTS = new Set([
|
|
33872
|
+
"message.updated",
|
|
33873
|
+
"message.removed",
|
|
33874
|
+
"message.part.updated",
|
|
33875
|
+
"message.part.delta",
|
|
33876
|
+
"message.part.removed"
|
|
33877
|
+
]);
|
|
33878
|
+
var MIN_LIFETIME_MS = 1e4;
|
|
33879
|
+
var IDLE_CONFIRMATIONS = 3;
|
|
33880
|
+
|
|
33881
|
+
class ServerUrlUnavailableError extends Error {
|
|
33882
|
+
constructor() {
|
|
33883
|
+
super("OpenCode server URL is unavailable");
|
|
33884
|
+
this.name = "ServerUrlUnavailableError";
|
|
33885
|
+
}
|
|
33886
|
+
}
|
|
33887
|
+
|
|
33888
|
+
class CmuxSessionLifecycle {
|
|
33889
|
+
owner;
|
|
33890
|
+
multiplexer;
|
|
33891
|
+
resolveServerUrl;
|
|
33892
|
+
defaultDirectory;
|
|
33893
|
+
backgroundJobs;
|
|
33894
|
+
store = new CmuxSessionStore;
|
|
33895
|
+
policy;
|
|
33896
|
+
now;
|
|
33897
|
+
delay;
|
|
33898
|
+
injectedDelay;
|
|
33899
|
+
deferredRetryMs;
|
|
33900
|
+
deferredTtlMs;
|
|
33901
|
+
missingGraceMs;
|
|
33902
|
+
closeRetryMs;
|
|
33903
|
+
shutdownTimeoutMs;
|
|
33904
|
+
serverCheck;
|
|
33905
|
+
fetchStatuses;
|
|
33906
|
+
pollTimer;
|
|
33907
|
+
polling = false;
|
|
33908
|
+
cleanupPromise;
|
|
33909
|
+
disposed = false;
|
|
33910
|
+
spawnGeneration = 0;
|
|
33911
|
+
constructor(owner, multiplexer, resolveServerUrl, defaultDirectory, backgroundJobs, options = {}) {
|
|
33912
|
+
this.owner = owner;
|
|
33913
|
+
this.multiplexer = multiplexer;
|
|
33914
|
+
this.resolveServerUrl = resolveServerUrl;
|
|
33915
|
+
this.defaultDirectory = defaultDirectory;
|
|
33916
|
+
this.backgroundJobs = backgroundJobs;
|
|
33917
|
+
this.now = options.now ?? Date.now;
|
|
33918
|
+
this.injectedDelay = Boolean(options.delay);
|
|
33919
|
+
this.delay = options.delay ?? ((milliseconds) => new Promise((resolve4) => setTimeout(resolve4, milliseconds)));
|
|
33920
|
+
this.deferredRetryMs = options.deferredRetryMs ?? 2000;
|
|
33921
|
+
this.deferredTtlMs = options.deferredTtlMs ?? 300000;
|
|
33922
|
+
this.missingGraceMs = options.missingGraceMs ?? 30000;
|
|
33923
|
+
this.closeRetryMs = options.closeRetryMs ?? 1000;
|
|
33924
|
+
this.shutdownTimeoutMs = options.shutdownTimeoutMs ?? 5000;
|
|
33925
|
+
this.policy = new CmuxClosePolicy(options.closeRetryTtlMs, options.closeRetryMaxAttempts);
|
|
33926
|
+
this.serverCheck = options.isServerRunning ?? isServerRunning;
|
|
33927
|
+
this.fetchStatuses = options.fetchStatuses ?? (() => this.loadStatuses());
|
|
33928
|
+
for (const orphan of this.store.claimOrphans(owner, defaultDirectory)) {
|
|
33929
|
+
if (orphan.closeIntent?.phase === "cooldown" && Number.isFinite(orphan.closeIntent.nextAttemptAt)) {
|
|
33930
|
+
this.scheduleCooldown(orphan);
|
|
33931
|
+
} else {
|
|
33932
|
+
orphan.closeIntent = undefined;
|
|
33933
|
+
this.requestClose(orphan, "cleanup");
|
|
33934
|
+
}
|
|
33935
|
+
}
|
|
33936
|
+
}
|
|
33937
|
+
async onSessionCreated(event) {
|
|
33938
|
+
if (this.disposed)
|
|
33939
|
+
return;
|
|
33940
|
+
if (event.type !== "session.created")
|
|
33941
|
+
return;
|
|
33942
|
+
const info = event.properties?.info;
|
|
33943
|
+
if (!info?.id || !info.parentID)
|
|
33944
|
+
return;
|
|
33945
|
+
const now = this.now();
|
|
33946
|
+
const record = {
|
|
33947
|
+
session: info.id,
|
|
33948
|
+
owner: this.owner,
|
|
33949
|
+
parent: info.parentID,
|
|
33950
|
+
title: info.title ?? "Subagent",
|
|
33951
|
+
directory: info.directory ?? this.defaultDirectory,
|
|
33952
|
+
spawnState: "known",
|
|
33953
|
+
lifecycle: "active",
|
|
33954
|
+
lastActivityAt: now,
|
|
33955
|
+
activityVersion: 0,
|
|
33956
|
+
idleConsecutive: 0
|
|
33957
|
+
};
|
|
33958
|
+
if (!this.store.claimCreated(record))
|
|
33959
|
+
return;
|
|
33960
|
+
if (record.paneId && record.lifecycle !== "active") {
|
|
33961
|
+
record.closeIntent = undefined;
|
|
33962
|
+
await this.requestClose(record, "cleanup");
|
|
33963
|
+
return;
|
|
33964
|
+
}
|
|
33965
|
+
await this.spawn(record);
|
|
33966
|
+
}
|
|
33967
|
+
async onSessionStatus(event) {
|
|
33968
|
+
if (this.disposed)
|
|
33969
|
+
return;
|
|
33970
|
+
const session2 = this.eventSession(event);
|
|
33971
|
+
if (!session2)
|
|
33972
|
+
return;
|
|
33973
|
+
const owned = this.store.get(session2);
|
|
33974
|
+
if (!owned || owned.owner !== this.owner)
|
|
33975
|
+
return;
|
|
33976
|
+
if (ACTIVITY_EVENTS.has(event.type)) {
|
|
33977
|
+
this.activity(session2);
|
|
33978
|
+
return;
|
|
33979
|
+
}
|
|
33980
|
+
const status = event.type === "session.idle" ? "idle" : event.type === "session.status" ? event.properties?.status?.type : undefined;
|
|
33981
|
+
if (!status)
|
|
33982
|
+
return;
|
|
33983
|
+
if (status !== "idle") {
|
|
33984
|
+
this.activity(session2);
|
|
33985
|
+
this.backgroundJobs?.clearDeferredClose(session2);
|
|
33986
|
+
const record = this.store.get(session2);
|
|
33987
|
+
if (status === "busy" && record && !record.paneId)
|
|
33988
|
+
await this.spawn(record);
|
|
33989
|
+
}
|
|
33990
|
+
if (owned.paneId)
|
|
33991
|
+
this.startPolling();
|
|
33992
|
+
}
|
|
33993
|
+
async onSessionDeleted(event) {
|
|
33994
|
+
if (event.type !== "session.deleted")
|
|
33995
|
+
return;
|
|
33996
|
+
const session2 = this.eventSession(event);
|
|
33997
|
+
if (!session2)
|
|
33998
|
+
return;
|
|
33999
|
+
const record = this.store.get(session2);
|
|
34000
|
+
if (!record)
|
|
34001
|
+
return;
|
|
34002
|
+
if (record.owner !== this.owner)
|
|
34003
|
+
return;
|
|
34004
|
+
this.store.markDeleted(session2);
|
|
34005
|
+
this.cancelDeferred(record);
|
|
34006
|
+
this.backgroundJobs?.clearDeferredClose(session2);
|
|
34007
|
+
if (!record.paneId) {
|
|
34008
|
+
if (!record.spawnPromise)
|
|
34009
|
+
this.store.removeWithoutPane(session2);
|
|
34010
|
+
return;
|
|
34011
|
+
}
|
|
34012
|
+
await this.requestClose(record, "deleted");
|
|
34013
|
+
}
|
|
34014
|
+
async closeSessionFromCoordinator(session2) {
|
|
34015
|
+
if (this.disposed)
|
|
34016
|
+
return;
|
|
34017
|
+
const record = this.store.get(session2);
|
|
34018
|
+
if (record?.paneId && record.owner === this.owner)
|
|
34019
|
+
this.startPolling();
|
|
34020
|
+
}
|
|
34021
|
+
cleanup() {
|
|
34022
|
+
this.cleanupPromise ??= this.runCleanup();
|
|
34023
|
+
return this.cleanupPromise;
|
|
34024
|
+
}
|
|
34025
|
+
pollOnce() {
|
|
34026
|
+
return this.poll();
|
|
34027
|
+
}
|
|
34028
|
+
async spawn(record, deferred = false) {
|
|
34029
|
+
if (this.disposed || record.owner !== this.owner)
|
|
34030
|
+
return;
|
|
34031
|
+
if (record.spawnState === "spawning" || record.paneId)
|
|
34032
|
+
return;
|
|
34033
|
+
const generation = this.spawnGeneration;
|
|
34034
|
+
const token = record.deferredSpawn?.generation;
|
|
34035
|
+
record.spawnState = "spawning";
|
|
34036
|
+
const operation = this.spawnOperation(record);
|
|
34037
|
+
record.spawnPromise = operation;
|
|
34038
|
+
const result = await operation;
|
|
34039
|
+
if (record.spawnPromise === operation)
|
|
34040
|
+
record.spawnPromise = undefined;
|
|
34041
|
+
const current = this.store.get(record.session);
|
|
34042
|
+
if (this.disposed || generation !== this.spawnGeneration) {
|
|
34043
|
+
const latePane = result.paneId ?? result.orphanPaneId;
|
|
34044
|
+
if (latePane)
|
|
34045
|
+
await this.closeLatePane(record, latePane);
|
|
34046
|
+
else if (current && !current.paneId)
|
|
34047
|
+
this.store.removeWithoutPane(record.session);
|
|
34048
|
+
return;
|
|
34049
|
+
}
|
|
34050
|
+
if (!current) {
|
|
34051
|
+
if (result.success && result.paneId)
|
|
34052
|
+
await this.adoptAndClose(record, result.paneId);
|
|
34053
|
+
if (result.orphanPaneId)
|
|
34054
|
+
await this.adoptAndClose(record, result.orphanPaneId);
|
|
34055
|
+
return;
|
|
34056
|
+
}
|
|
34057
|
+
if (deferred && token !== undefined && current.deferredSpawn?.generation !== token) {
|
|
34058
|
+
const stalePane = result.paneId ?? result.orphanPaneId;
|
|
34059
|
+
if (stalePane)
|
|
34060
|
+
await this.adoptAndClose(current, stalePane);
|
|
34061
|
+
return;
|
|
34062
|
+
}
|
|
34063
|
+
const paneId = result.paneId ?? result.orphanPaneId;
|
|
34064
|
+
if (paneId) {
|
|
34065
|
+
this.store.markAttached(record.session, paneId, this.now());
|
|
34066
|
+
if (result.orphanPaneId)
|
|
34067
|
+
this.store.markOrphaned(record.session);
|
|
34068
|
+
if (current.lifecycle !== "active" || result.orphanPaneId) {
|
|
34069
|
+
await this.requestClose(current, current.lifecycle === "active" ? "cleanup" : "deleted");
|
|
34070
|
+
} else
|
|
34071
|
+
this.startPolling();
|
|
34072
|
+
return;
|
|
34073
|
+
}
|
|
34074
|
+
current.spawnState = "failed";
|
|
34075
|
+
if (current.lifecycle !== "active") {
|
|
34076
|
+
this.store.removeWithoutPane(current.session);
|
|
34077
|
+
} else if (result.error === "unavailable" || result.error === "not_found" || result.error === "invalid_state") {
|
|
34078
|
+
this.deferSpawn(current);
|
|
34079
|
+
}
|
|
34080
|
+
}
|
|
34081
|
+
async spawnOperation(record) {
|
|
34082
|
+
const serverUrl = this.resolveServerUrl();
|
|
34083
|
+
if (!serverUrl) {
|
|
34084
|
+
log("[cmux-session-lifecycle] no valid server URL; skipping spawn");
|
|
34085
|
+
return { success: false, error: "unavailable" };
|
|
34086
|
+
}
|
|
34087
|
+
if (!await this.serverCheck(serverUrl)) {
|
|
34088
|
+
return { success: false, error: "unavailable" };
|
|
34089
|
+
}
|
|
34090
|
+
try {
|
|
34091
|
+
return await this.multiplexer.spawnPane(record.session, record.title, serverUrl, record.directory);
|
|
34092
|
+
} catch {
|
|
34093
|
+
return { success: false, error: "hard" };
|
|
34094
|
+
}
|
|
34095
|
+
}
|
|
34096
|
+
deferSpawn(record) {
|
|
34097
|
+
if (this.disposed || record.owner !== this.owner)
|
|
34098
|
+
return;
|
|
34099
|
+
const existing = record.deferredSpawn;
|
|
34100
|
+
const deferred = existing ?? {
|
|
34101
|
+
deadline: this.now() + this.deferredTtlMs,
|
|
34102
|
+
generation: 0
|
|
34103
|
+
};
|
|
34104
|
+
deferred.generation += 1;
|
|
34105
|
+
deferred.timer?.cancel();
|
|
34106
|
+
record.deferredSpawn = deferred;
|
|
34107
|
+
if (this.now() >= deferred.deadline) {
|
|
34108
|
+
this.cancelDeferred(record);
|
|
34109
|
+
return;
|
|
34110
|
+
}
|
|
34111
|
+
deferred.timer = this.timer(async () => {
|
|
34112
|
+
deferred.timer = undefined;
|
|
34113
|
+
if (this.disposed || this.store.get(record.session) !== record || record.lifecycle !== "active" || record.owner !== this.owner)
|
|
34114
|
+
return;
|
|
34115
|
+
await this.spawn(record, true);
|
|
34116
|
+
}, this.deferredRetryMs);
|
|
34117
|
+
}
|
|
34118
|
+
cancelDeferred(record) {
|
|
34119
|
+
record.deferredSpawn?.timer?.cancel();
|
|
34120
|
+
record.deferredSpawn = undefined;
|
|
34121
|
+
}
|
|
34122
|
+
activity(session2) {
|
|
34123
|
+
const record = this.store.get(session2);
|
|
34124
|
+
if (!record || record.owner !== this.owner || this.disposed)
|
|
34125
|
+
return;
|
|
34126
|
+
this.store.markActivity(session2, this.now());
|
|
34127
|
+
const next = this.policy.activity(record.closeIntent);
|
|
34128
|
+
if (!next && record.closeIntent)
|
|
34129
|
+
record.closeTimer?.cancel();
|
|
34130
|
+
record.closeIntent = next;
|
|
34131
|
+
if (!next)
|
|
34132
|
+
record.closeTimer = undefined;
|
|
34133
|
+
}
|
|
34134
|
+
async requestClose(record, reason) {
|
|
34135
|
+
if (!record.paneId || record.owner !== this.owner)
|
|
34136
|
+
return;
|
|
34137
|
+
if (reason === "idle" && !(this.backgroundJobs?.deferIfRunning(record.session) ?? true))
|
|
34138
|
+
return;
|
|
34139
|
+
const previous = record.closeIntent;
|
|
34140
|
+
record.closeIntent = this.policy.request(reason, record.activityVersion, this.now(), previous);
|
|
34141
|
+
if (previous !== record.closeIntent)
|
|
34142
|
+
record.closeTimer?.cancel();
|
|
34143
|
+
await this.attemptClose(record);
|
|
34144
|
+
}
|
|
34145
|
+
async attemptClose(record) {
|
|
34146
|
+
const intent = record.closeIntent;
|
|
34147
|
+
if (!intent || !record.paneId || record.owner !== this.owner || this.store.get(record.session) !== record)
|
|
34148
|
+
return;
|
|
34149
|
+
if (intent.phase === "cooldown" && this.now() < intent.nextAttemptAt) {
|
|
34150
|
+
this.scheduleCooldown(record);
|
|
34151
|
+
return;
|
|
34152
|
+
}
|
|
34153
|
+
record.closeIntent = this.policy.resume(intent, this.now());
|
|
34154
|
+
if (record.closeIntent !== intent)
|
|
34155
|
+
return this.attemptClose(record);
|
|
34156
|
+
if (intent.reason === "idle" && intent.expectedActivityVersion !== record.activityVersion) {
|
|
34157
|
+
record.closeIntent = this.policy.activity(intent);
|
|
34158
|
+
return;
|
|
34159
|
+
}
|
|
34160
|
+
let closed = false;
|
|
34161
|
+
try {
|
|
34162
|
+
closed = await this.multiplexer.closePane(record.paneId);
|
|
34163
|
+
} catch {}
|
|
34164
|
+
if (this.disposed || this.store.get(record.session) !== record || record.owner !== this.owner || record.closeIntent !== intent)
|
|
34165
|
+
return;
|
|
34166
|
+
const intentStillCurrent = record.closeIntent === intent;
|
|
34167
|
+
const idleStillCurrent = intent.reason !== "idle" || intent.expectedActivityVersion === record.activityVersion;
|
|
34168
|
+
if (closed) {
|
|
34169
|
+
record.closeTimer?.cancel();
|
|
34170
|
+
record.closeTimer = undefined;
|
|
34171
|
+
if (record.lifecycle !== "active") {
|
|
34172
|
+
this.store.removeAfterConfirmedClose(record.session);
|
|
34173
|
+
} else {
|
|
34174
|
+
record.paneId = undefined;
|
|
34175
|
+
record.spawnState = "known";
|
|
34176
|
+
if (intentStillCurrent)
|
|
34177
|
+
record.closeIntent = this.policy.complete();
|
|
34178
|
+
if (intent.reason === "idle" && record.owner === this.owner && !this.disposed && record.activityVersion !== intent.expectedActivityVersion) {
|
|
34179
|
+
await this.spawn(record);
|
|
34180
|
+
}
|
|
34181
|
+
}
|
|
34182
|
+
this.updatePolling();
|
|
34183
|
+
return;
|
|
34184
|
+
}
|
|
34185
|
+
if (!intentStillCurrent || !idleStillCurrent)
|
|
34186
|
+
return;
|
|
34187
|
+
record.closeIntent = this.policy.failed(intent, this.now());
|
|
34188
|
+
if (record.closeIntent.phase === "cooldown") {
|
|
34189
|
+
if (!Number.isFinite(record.closeIntent.nextAttemptAt))
|
|
34190
|
+
this.store.markOrphaned(record.session);
|
|
34191
|
+
this.scheduleCooldown(record);
|
|
34192
|
+
return;
|
|
34193
|
+
}
|
|
34194
|
+
record.closeTimer?.cancel();
|
|
34195
|
+
record.closeTimer = this.timer(() => this.attemptClose(record), this.closeRetryMs);
|
|
34196
|
+
}
|
|
34197
|
+
scheduleCooldown(record) {
|
|
34198
|
+
record.closeTimer?.cancel();
|
|
34199
|
+
record.closeTimer = undefined;
|
|
34200
|
+
const intent = record.closeIntent;
|
|
34201
|
+
if (!intent || !Number.isFinite(intent.nextAttemptAt) || this.disposed)
|
|
34202
|
+
return;
|
|
34203
|
+
record.closeTimer = this.timer(() => this.attemptClose(record), Math.max(0, intent.nextAttemptAt - this.now()));
|
|
34204
|
+
}
|
|
34205
|
+
async poll() {
|
|
34206
|
+
if (this.polling || this.disposed)
|
|
34207
|
+
return;
|
|
34208
|
+
this.polling = true;
|
|
34209
|
+
try {
|
|
34210
|
+
const statuses = await this.fetchStatuses();
|
|
34211
|
+
for (const record of this.store.ownedBy(this.owner)) {
|
|
34212
|
+
if (!record.paneId || record.lifecycle !== "active")
|
|
34213
|
+
continue;
|
|
34214
|
+
const status = statuses[record.session];
|
|
34215
|
+
if (!status) {
|
|
34216
|
+
record.statusMissingSince ??= this.now();
|
|
34217
|
+
if (this.now() - record.statusMissingSince < this.missingGraceMs) {
|
|
34218
|
+
record.idleConsecutive = 0;
|
|
34219
|
+
continue;
|
|
34220
|
+
}
|
|
34221
|
+
}
|
|
34222
|
+
if (status)
|
|
34223
|
+
record.statusMissingSince = undefined;
|
|
34224
|
+
if (status && status.type !== "idle") {
|
|
34225
|
+
this.activity(record.session);
|
|
34226
|
+
continue;
|
|
34227
|
+
}
|
|
34228
|
+
if (this.now() - (record.attachedAt ?? this.now()) < MIN_LIFETIME_MS || this.now() - record.lastActivityAt < MIN_LIFETIME_MS) {
|
|
34229
|
+
record.idleConsecutive = 0;
|
|
34230
|
+
continue;
|
|
34231
|
+
}
|
|
34232
|
+
record.idleConsecutive += 1;
|
|
34233
|
+
if (record.idleConsecutive < IDLE_CONFIRMATIONS)
|
|
34234
|
+
continue;
|
|
34235
|
+
const version = record.activityVersion;
|
|
34236
|
+
const final = await this.fetchStatuses();
|
|
34237
|
+
if ((final[record.session]?.type === "idle" || !final[record.session] && record.statusMissingSince !== undefined && this.now() - record.statusMissingSince >= this.missingGraceMs) && version === record.activityVersion)
|
|
34238
|
+
await this.requestClose(record, "idle");
|
|
34239
|
+
else
|
|
34240
|
+
this.activity(record.session);
|
|
34241
|
+
}
|
|
34242
|
+
} catch {} finally {
|
|
34243
|
+
this.polling = false;
|
|
34244
|
+
}
|
|
34245
|
+
}
|
|
34246
|
+
startPolling() {
|
|
34247
|
+
if (this.pollTimer || this.disposed)
|
|
34248
|
+
return;
|
|
34249
|
+
this.pollTimer = setInterval(() => void this.poll().catch(() => {
|
|
34250
|
+
return;
|
|
34251
|
+
}), POLL_INTERVAL_BACKGROUND_MS);
|
|
34252
|
+
this.pollTimer.unref?.();
|
|
34253
|
+
}
|
|
34254
|
+
updatePolling() {
|
|
34255
|
+
if (this.store.ownedBy(this.owner).some((record) => record.paneId))
|
|
34256
|
+
this.startPolling();
|
|
34257
|
+
else if (this.pollTimer) {
|
|
34258
|
+
clearInterval(this.pollTimer);
|
|
34259
|
+
this.pollTimer = undefined;
|
|
34260
|
+
}
|
|
34261
|
+
}
|
|
34262
|
+
async runCleanup() {
|
|
34263
|
+
this.disposed = true;
|
|
34264
|
+
this.spawnGeneration += 1;
|
|
34265
|
+
if (this.pollTimer)
|
|
34266
|
+
clearInterval(this.pollTimer);
|
|
34267
|
+
this.pollTimer = undefined;
|
|
34268
|
+
const records2 = this.store.ownedBy(this.owner);
|
|
34269
|
+
for (const record of records2)
|
|
34270
|
+
this.cancelDeferred(record);
|
|
34271
|
+
const pending = records2.flatMap((record) => record.spawnPromise ? [record.spawnPromise] : []);
|
|
34272
|
+
if (pending.length) {
|
|
34273
|
+
await Promise.race([
|
|
34274
|
+
Promise.allSettled(pending),
|
|
34275
|
+
this.delay(this.shutdownTimeoutMs)
|
|
34276
|
+
]);
|
|
34277
|
+
}
|
|
34278
|
+
for (const record of this.store.ownedBy(this.owner)) {
|
|
34279
|
+
if (!record.paneId) {
|
|
34280
|
+
if (!record.spawnPromise)
|
|
34281
|
+
this.store.removeWithoutPane(record.session);
|
|
34282
|
+
continue;
|
|
34283
|
+
}
|
|
34284
|
+
record.closeTimer?.cancel();
|
|
34285
|
+
record.closeTimer = undefined;
|
|
34286
|
+
record.closeIntent = this.policy.request("cleanup", record.activityVersion, this.now());
|
|
34287
|
+
while (record.closeIntent?.phase === "pending" && this.store.get(record.session) === record && record.owner === this.owner) {
|
|
34288
|
+
await this.attemptCloseWithoutTimer(record);
|
|
34289
|
+
if (record.closeIntent?.phase === "pending")
|
|
34290
|
+
await this.delay(this.closeRetryMs);
|
|
34291
|
+
}
|
|
34292
|
+
if (record.closeIntent && this.store.get(record.session) === record && record.owner === this.owner)
|
|
34293
|
+
this.store.markOrphaned(record.session);
|
|
34294
|
+
}
|
|
34295
|
+
}
|
|
34296
|
+
async attemptCloseWithoutTimer(record) {
|
|
34297
|
+
const intent = record.closeIntent;
|
|
34298
|
+
if (!intent || !record.paneId)
|
|
34299
|
+
return;
|
|
34300
|
+
const paneId = record.paneId;
|
|
34301
|
+
let closed = false;
|
|
34302
|
+
try {
|
|
34303
|
+
closed = await this.multiplexer.closePane(paneId);
|
|
34304
|
+
} catch {}
|
|
34305
|
+
if (this.store.get(record.session) !== record || record.owner !== this.owner || record.closeIntent !== intent || record.paneId !== paneId)
|
|
34306
|
+
return;
|
|
34307
|
+
if (closed) {
|
|
34308
|
+
record.closeIntent = undefined;
|
|
34309
|
+
this.store.removeAfterConfirmedClose(record.session);
|
|
34310
|
+
} else
|
|
34311
|
+
record.closeIntent = this.policy.failed(intent, this.now());
|
|
34312
|
+
}
|
|
34313
|
+
async adoptAndClose(record, paneId) {
|
|
34314
|
+
if (!this.store.get(record.session))
|
|
34315
|
+
this.store.claimCreated(record);
|
|
34316
|
+
this.store.markAttached(record.session, paneId, this.now());
|
|
34317
|
+
this.store.markOrphaned(record.session);
|
|
34318
|
+
await this.requestClose(record, "cleanup");
|
|
34319
|
+
}
|
|
34320
|
+
async closeLatePane(source, paneId) {
|
|
34321
|
+
const existing = this.store.get(source.session);
|
|
34322
|
+
if (existing && existing.owner !== this.owner) {
|
|
34323
|
+
let closed = false;
|
|
34324
|
+
try {
|
|
34325
|
+
closed = await this.multiplexer.closePane(paneId);
|
|
34326
|
+
} catch {}
|
|
34327
|
+
if (!closed)
|
|
34328
|
+
this.trackStalePane(source, paneId);
|
|
34329
|
+
return;
|
|
34330
|
+
}
|
|
34331
|
+
const record = existing ?? source;
|
|
34332
|
+
if (!existing)
|
|
34333
|
+
this.store.claimCreated(record);
|
|
34334
|
+
record.paneId = paneId;
|
|
34335
|
+
record.spawnState = "attached";
|
|
34336
|
+
record.lifecycle = "orphaned";
|
|
34337
|
+
record.closeIntent = this.policy.request("cleanup", record.activityVersion, this.now());
|
|
34338
|
+
while (record.closeIntent?.phase === "pending") {
|
|
34339
|
+
await this.attemptCloseWithoutTimer(record);
|
|
34340
|
+
if (record.closeIntent?.phase === "pending")
|
|
34341
|
+
await this.delay(this.closeRetryMs);
|
|
34342
|
+
}
|
|
34343
|
+
}
|
|
34344
|
+
trackStalePane(source, paneId) {
|
|
34345
|
+
const session2 = `${source.session}\x00late\x00${paneId}`;
|
|
34346
|
+
this.store.claimCreated({
|
|
34347
|
+
session: session2,
|
|
34348
|
+
owner: this.owner,
|
|
34349
|
+
parent: source.parent,
|
|
34350
|
+
title: source.title,
|
|
34351
|
+
directory: source.directory,
|
|
34352
|
+
paneId,
|
|
34353
|
+
spawnState: "attached",
|
|
34354
|
+
lifecycle: "orphaned",
|
|
34355
|
+
attachedAt: this.now(),
|
|
34356
|
+
lastActivityAt: source.lastActivityAt,
|
|
34357
|
+
activityVersion: source.activityVersion,
|
|
34358
|
+
idleConsecutive: 0
|
|
34359
|
+
});
|
|
34360
|
+
}
|
|
34361
|
+
eventSession(event) {
|
|
34362
|
+
return event.properties?.sessionID ?? event.properties?.info?.sessionID ?? event.properties?.part?.sessionID ?? event.properties?.info?.id;
|
|
34363
|
+
}
|
|
34364
|
+
timer(callback, milliseconds) {
|
|
34365
|
+
let cancelled = false;
|
|
34366
|
+
if (this.injectedDelay) {
|
|
34367
|
+
this.delay(milliseconds).then(() => {
|
|
34368
|
+
if (!cancelled)
|
|
34369
|
+
callback();
|
|
34370
|
+
});
|
|
34371
|
+
return { cancel: () => cancelled = true };
|
|
34372
|
+
}
|
|
34373
|
+
const timer = setTimeout(() => void callback(), milliseconds);
|
|
34374
|
+
timer.unref?.();
|
|
34375
|
+
return { cancel: () => clearTimeout(timer) };
|
|
34376
|
+
}
|
|
34377
|
+
async loadStatuses() {
|
|
34378
|
+
const serverUrl = this.resolveServerUrl();
|
|
34379
|
+
if (!serverUrl) {
|
|
34380
|
+
log("[cmux-session-lifecycle] no valid server URL; skipping poll");
|
|
34381
|
+
throw new ServerUrlUnavailableError;
|
|
34382
|
+
}
|
|
34383
|
+
const response = await fetch(new URL("/session/status", serverUrl), {
|
|
34384
|
+
signal: AbortSignal.timeout(2000)
|
|
34385
|
+
});
|
|
34386
|
+
if (!response.ok)
|
|
34387
|
+
throw new Error(`session status failed: ${response.status}`);
|
|
34388
|
+
return await response.json();
|
|
34389
|
+
}
|
|
34390
|
+
}
|
|
34391
|
+
|
|
32932
34392
|
// src/multiplexer/session-manager.ts
|
|
32933
34393
|
var SHARED_STATE_KEY = Symbol.for("oh-my-opencode-slim.multiplexer-session-manager.state");
|
|
32934
34394
|
function getSharedState() {
|
|
@@ -32941,10 +34401,53 @@ function getSharedState() {
|
|
|
32941
34401
|
};
|
|
32942
34402
|
return globalWithState[SHARED_STATE_KEY];
|
|
32943
34403
|
}
|
|
34404
|
+
function validServerUrl(value) {
|
|
34405
|
+
if (typeof value !== "string" && !(value instanceof URL))
|
|
34406
|
+
return null;
|
|
34407
|
+
try {
|
|
34408
|
+
const url = new URL(value.toString());
|
|
34409
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.toString() : null;
|
|
34410
|
+
} catch {
|
|
34411
|
+
return null;
|
|
34412
|
+
}
|
|
34413
|
+
}
|
|
34414
|
+
function clientBaseUrl(client) {
|
|
34415
|
+
try {
|
|
34416
|
+
if (!client || typeof client !== "object" || !("_client" in client))
|
|
34417
|
+
return null;
|
|
34418
|
+
const internal = client._client;
|
|
34419
|
+
if (!internal || typeof internal !== "object" || !("getConfig" in internal))
|
|
34420
|
+
return null;
|
|
34421
|
+
const getConfig = internal.getConfig;
|
|
34422
|
+
if (typeof getConfig !== "function")
|
|
34423
|
+
return null;
|
|
34424
|
+
const config = getConfig.call(internal);
|
|
34425
|
+
if (!config || typeof config !== "object" || !("baseUrl" in config))
|
|
34426
|
+
return null;
|
|
34427
|
+
return validServerUrl(config.baseUrl);
|
|
34428
|
+
} catch {
|
|
34429
|
+
return null;
|
|
34430
|
+
}
|
|
34431
|
+
}
|
|
34432
|
+
function createServerUrlResolver(ctx) {
|
|
34433
|
+
return () => {
|
|
34434
|
+
try {
|
|
34435
|
+
const serverUrl = validServerUrl(ctx.serverUrl);
|
|
34436
|
+
if (serverUrl)
|
|
34437
|
+
return serverUrl;
|
|
34438
|
+
} catch {}
|
|
34439
|
+
try {
|
|
34440
|
+
return clientBaseUrl(ctx.client);
|
|
34441
|
+
} catch {
|
|
34442
|
+
return null;
|
|
34443
|
+
}
|
|
34444
|
+
};
|
|
34445
|
+
}
|
|
34446
|
+
|
|
32944
34447
|
class MultiplexerSessionManager {
|
|
32945
34448
|
backgroundJobBoard;
|
|
32946
34449
|
instanceId = Math.random().toString(36).slice(2, 8);
|
|
32947
|
-
|
|
34450
|
+
resolveServerUrl;
|
|
32948
34451
|
directory;
|
|
32949
34452
|
multiplexer = null;
|
|
32950
34453
|
sessions;
|
|
@@ -32953,7 +34456,8 @@ class MultiplexerSessionManager {
|
|
|
32953
34456
|
closingSessions;
|
|
32954
34457
|
pollInterval;
|
|
32955
34458
|
enabled = false;
|
|
32956
|
-
|
|
34459
|
+
cmuxLifecycle;
|
|
34460
|
+
constructor(ctx, config, backgroundJobBoard, options = {}) {
|
|
32957
34461
|
this.backgroundJobBoard = backgroundJobBoard;
|
|
32958
34462
|
const sharedState = getSharedState();
|
|
32959
34463
|
this.sessions = sharedState.sessions;
|
|
@@ -32961,20 +34465,24 @@ class MultiplexerSessionManager {
|
|
|
32961
34465
|
this.spawningSessions = sharedState.spawningSessions;
|
|
32962
34466
|
this.closingSessions = sharedState.closingSessions;
|
|
32963
34467
|
this.directory = ctx.directory;
|
|
32964
|
-
|
|
32965
|
-
this.serverUrl = ctx.serverUrl?.toString() ?? `http://localhost:${defaultPort}`;
|
|
34468
|
+
this.resolveServerUrl = createServerUrlResolver(ctx);
|
|
32966
34469
|
this.multiplexer = getMultiplexer(config);
|
|
32967
34470
|
this.enabled = config.type !== "none" && this.multiplexer !== null && this.multiplexer.isInsideSession();
|
|
34471
|
+
if (this.enabled && this.multiplexer?.type === "cmux") {
|
|
34472
|
+
this.cmuxLifecycle = new CmuxSessionLifecycle(this.instanceId, this.multiplexer, this.resolveServerUrl, this.directory, this.backgroundJobBoard, options);
|
|
34473
|
+
}
|
|
32968
34474
|
log("[multiplexer-session-manager] initialized", {
|
|
32969
34475
|
instanceId: this.instanceId,
|
|
32970
34476
|
enabled: this.enabled,
|
|
32971
34477
|
type: config.type,
|
|
32972
|
-
serverUrl:
|
|
34478
|
+
serverUrl: "dynamic",
|
|
32973
34479
|
trackedSessions: this.sessions.size,
|
|
32974
34480
|
knownSessions: this.knownSessions.size
|
|
32975
34481
|
});
|
|
32976
34482
|
}
|
|
32977
34483
|
async onSessionCreated(event) {
|
|
34484
|
+
if (this.cmuxLifecycle)
|
|
34485
|
+
return this.cmuxLifecycle.onSessionCreated(event);
|
|
32978
34486
|
if (!this.enabled || !this.multiplexer)
|
|
32979
34487
|
return;
|
|
32980
34488
|
if (event.type !== "session.created")
|
|
@@ -33006,11 +34514,19 @@ class MultiplexerSessionManager {
|
|
|
33006
34514
|
});
|
|
33007
34515
|
this.spawningSessions.add(sessionId);
|
|
33008
34516
|
try {
|
|
33009
|
-
const
|
|
34517
|
+
const serverUrl = this.resolveServerUrl();
|
|
34518
|
+
if (!serverUrl) {
|
|
34519
|
+
log("[multiplexer-session-manager] no valid server URL, skipping spawn", {
|
|
34520
|
+
instanceId: this.instanceId,
|
|
34521
|
+
sessionId
|
|
34522
|
+
});
|
|
34523
|
+
return;
|
|
34524
|
+
}
|
|
34525
|
+
const serverRunning = await isServerRunning(serverUrl);
|
|
33010
34526
|
if (!serverRunning) {
|
|
33011
34527
|
log("[multiplexer-session-manager] server not running, skipping", {
|
|
33012
34528
|
instanceId: this.instanceId,
|
|
33013
|
-
serverUrl
|
|
34529
|
+
serverUrl
|
|
33014
34530
|
});
|
|
33015
34531
|
return;
|
|
33016
34532
|
}
|
|
@@ -33023,7 +34539,7 @@ class MultiplexerSessionManager {
|
|
|
33023
34539
|
title,
|
|
33024
34540
|
instanceId: this.instanceId
|
|
33025
34541
|
});
|
|
33026
|
-
const paneResult = await this.multiplexer.spawnPane(sessionId, title,
|
|
34542
|
+
const paneResult = await this.multiplexer.spawnPane(sessionId, title, serverUrl, directory).catch((err) => {
|
|
33027
34543
|
log("[multiplexer-session-manager] failed to spawn pane", {
|
|
33028
34544
|
instanceId: this.instanceId,
|
|
33029
34545
|
error: String(err)
|
|
@@ -33060,6 +34576,8 @@ class MultiplexerSessionManager {
|
|
|
33060
34576
|
}
|
|
33061
34577
|
}
|
|
33062
34578
|
async onSessionStatus(event) {
|
|
34579
|
+
if (this.cmuxLifecycle)
|
|
34580
|
+
return this.cmuxLifecycle.onSessionStatus(event);
|
|
33063
34581
|
if (!this.enabled)
|
|
33064
34582
|
return;
|
|
33065
34583
|
if (event.type === "session.idle") {
|
|
@@ -33112,6 +34630,8 @@ class MultiplexerSessionManager {
|
|
|
33112
34630
|
}
|
|
33113
34631
|
}
|
|
33114
34632
|
async onSessionDeleted(event) {
|
|
34633
|
+
if (this.cmuxLifecycle)
|
|
34634
|
+
return this.cmuxLifecycle.onSessionDeleted(event);
|
|
33115
34635
|
if (!this.enabled)
|
|
33116
34636
|
return;
|
|
33117
34637
|
if (event.type !== "session.deleted")
|
|
@@ -33147,6 +34667,8 @@ class MultiplexerSessionManager {
|
|
|
33147
34667
|
}
|
|
33148
34668
|
}
|
|
33149
34669
|
async pollSessions() {
|
|
34670
|
+
if (this.cmuxLifecycle)
|
|
34671
|
+
return this.cmuxLifecycle.pollOnce();
|
|
33150
34672
|
if (this.sessions.size === 0) {
|
|
33151
34673
|
this.stopPolling();
|
|
33152
34674
|
return;
|
|
@@ -33181,7 +34703,14 @@ class MultiplexerSessionManager {
|
|
|
33181
34703
|
}
|
|
33182
34704
|
}
|
|
33183
34705
|
async fetchSessionStatuses() {
|
|
33184
|
-
const
|
|
34706
|
+
const serverUrl = this.resolveServerUrl();
|
|
34707
|
+
if (!serverUrl) {
|
|
34708
|
+
log("[multiplexer-session-manager] no valid server URL, skipping poll", {
|
|
34709
|
+
instanceId: this.instanceId
|
|
34710
|
+
});
|
|
34711
|
+
return {};
|
|
34712
|
+
}
|
|
34713
|
+
const url = new URL("/session/status", serverUrl);
|
|
33185
34714
|
const response = await fetch(url, { signal: AbortSignal.timeout(2000) });
|
|
33186
34715
|
if (!response.ok) {
|
|
33187
34716
|
throw new Error(`session status request failed: ${response.status} ${response.statusText}`);
|
|
@@ -33283,11 +34812,19 @@ class MultiplexerSessionManager {
|
|
|
33283
34812
|
return;
|
|
33284
34813
|
this.spawningSessions.add(sessionId);
|
|
33285
34814
|
try {
|
|
33286
|
-
const
|
|
34815
|
+
const serverUrl = this.resolveServerUrl();
|
|
34816
|
+
if (!serverUrl) {
|
|
34817
|
+
log("[multiplexer-session-manager] no valid server URL, skipping respawn", {
|
|
34818
|
+
instanceId: this.instanceId,
|
|
34819
|
+
sessionId
|
|
34820
|
+
});
|
|
34821
|
+
return;
|
|
34822
|
+
}
|
|
34823
|
+
const serverRunning = await isServerRunning(serverUrl);
|
|
33287
34824
|
if (!serverRunning) {
|
|
33288
34825
|
log("[multiplexer-session-manager] server not running, skipping busy respawn", {
|
|
33289
34826
|
instanceId: this.instanceId,
|
|
33290
|
-
serverUrl
|
|
34827
|
+
serverUrl,
|
|
33291
34828
|
sessionId
|
|
33292
34829
|
});
|
|
33293
34830
|
return;
|
|
@@ -33301,7 +34838,7 @@ class MultiplexerSessionManager {
|
|
|
33301
34838
|
parentId: known.parentId,
|
|
33302
34839
|
title: known.title
|
|
33303
34840
|
});
|
|
33304
|
-
const paneResult = await this.multiplexer.spawnPane(sessionId, known.title,
|
|
34841
|
+
const paneResult = await this.multiplexer.spawnPane(sessionId, known.title, serverUrl, known.directory).catch((err) => {
|
|
33305
34842
|
log("[multiplexer-session-manager] failed to respawn pane", {
|
|
33306
34843
|
instanceId: this.instanceId,
|
|
33307
34844
|
error: String(err)
|
|
@@ -33358,11 +34895,15 @@ class MultiplexerSessionManager {
|
|
|
33358
34895
|
return this.backgroundJobBoard?.deferIfRunning(sessionId) ?? true;
|
|
33359
34896
|
}
|
|
33360
34897
|
async closeSessionFromCoordinator(sessionId) {
|
|
34898
|
+
if (this.cmuxLifecycle)
|
|
34899
|
+
return this.cmuxLifecycle.closeSessionFromCoordinator(sessionId);
|
|
33361
34900
|
if (!this.enabled)
|
|
33362
34901
|
return;
|
|
33363
34902
|
await this.closeSession(sessionId, "idle", true);
|
|
33364
34903
|
}
|
|
33365
34904
|
async cleanup() {
|
|
34905
|
+
if (this.cmuxLifecycle)
|
|
34906
|
+
return this.cmuxLifecycle.cleanup();
|
|
33366
34907
|
this.stopPolling();
|
|
33367
34908
|
if (this.closingSessions.size > 0) {
|
|
33368
34909
|
await Promise.all(this.closingSessions.values());
|
|
@@ -33384,27 +34925,10 @@ class MultiplexerSessionManager {
|
|
|
33384
34925
|
this.closingSessions.clear();
|
|
33385
34926
|
log("[multiplexer-session-manager] cleanup complete");
|
|
33386
34927
|
}
|
|
33387
|
-
|
|
33388
|
-
|
|
33389
|
-
|
|
33390
|
-
const healthUrl = new URL("/health", serverUrl).toString();
|
|
33391
|
-
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
33392
|
-
const controller = new AbortController;
|
|
33393
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
33394
|
-
let response = null;
|
|
33395
|
-
try {
|
|
33396
|
-
response = await fetch(healthUrl, { signal: controller.signal }).catch(() => null);
|
|
33397
|
-
} finally {
|
|
33398
|
-
clearTimeout(timeout);
|
|
33399
|
-
}
|
|
33400
|
-
if (response?.ok) {
|
|
33401
|
-
return true;
|
|
33402
|
-
}
|
|
33403
|
-
if (attempt < maxAttempts) {
|
|
33404
|
-
await new Promise((r) => setTimeout(r, 250));
|
|
33405
|
-
}
|
|
34928
|
+
async cleanupOnInstanceDisposed() {
|
|
34929
|
+
if (this.cmuxLifecycle)
|
|
34930
|
+
await this.cmuxLifecycle.cleanup();
|
|
33406
34931
|
}
|
|
33407
|
-
return false;
|
|
33408
34932
|
}
|
|
33409
34933
|
// src/tools/acp-run.ts
|
|
33410
34934
|
import { spawn as spawn3 } from "node:child_process";
|
|
@@ -33605,7 +35129,7 @@ class AcpClient {
|
|
|
33605
35129
|
return;
|
|
33606
35130
|
this.lastUpdate = Date.now();
|
|
33607
35131
|
const update = message.params?.update;
|
|
33608
|
-
if (!
|
|
35132
|
+
if (!isRecord3(update))
|
|
33609
35133
|
return;
|
|
33610
35134
|
collectText(update, this.chunks);
|
|
33611
35135
|
}
|
|
@@ -33685,16 +35209,16 @@ function createAcpRunTool(agents = {}) {
|
|
|
33685
35209
|
});
|
|
33686
35210
|
}
|
|
33687
35211
|
function readSessionId(value) {
|
|
33688
|
-
if (!
|
|
35212
|
+
if (!isRecord3(value) || typeof value.sessionId !== "string") {
|
|
33689
35213
|
throw new Error("ACP agent did not return a sessionId");
|
|
33690
35214
|
}
|
|
33691
35215
|
return value.sessionId;
|
|
33692
35216
|
}
|
|
33693
35217
|
function readAuthMethods(value) {
|
|
33694
|
-
if (!
|
|
35218
|
+
if (!isRecord3(value) || !Array.isArray(value.authMethods))
|
|
33695
35219
|
return [];
|
|
33696
35220
|
const methods = value.authMethods;
|
|
33697
|
-
return methods.filter(
|
|
35221
|
+
return methods.filter(isRecord3);
|
|
33698
35222
|
}
|
|
33699
35223
|
function rpcError(error) {
|
|
33700
35224
|
const err = new Error(error.message ?? "ACP request failed");
|
|
@@ -33708,11 +35232,11 @@ function isAuthError(error) {
|
|
|
33708
35232
|
const meta = error;
|
|
33709
35233
|
return meta.code === -32001 || error.message.toLowerCase().includes("auth_required") || error.message.toLowerCase().includes("auth required");
|
|
33710
35234
|
}
|
|
33711
|
-
function
|
|
35235
|
+
function isRecord3(value) {
|
|
33712
35236
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33713
35237
|
}
|
|
33714
35238
|
function readPermissionTitle(params) {
|
|
33715
|
-
const tool2 =
|
|
35239
|
+
const tool2 = isRecord3(params?.toolCall) ? params.toolCall : undefined;
|
|
33716
35240
|
if (typeof tool2?.title === "string")
|
|
33717
35241
|
return tool2.title;
|
|
33718
35242
|
if (typeof params?.permission === "string")
|
|
@@ -33721,7 +35245,7 @@ function readPermissionTitle(params) {
|
|
|
33721
35245
|
}
|
|
33722
35246
|
function selectPermissionOption(params, mode) {
|
|
33723
35247
|
const options = Array.isArray(params?.options) ? params.options : [];
|
|
33724
|
-
const choices = options.filter(
|
|
35248
|
+
const choices = options.filter(isRecord3).filter((item) => typeof item.optionId === "string");
|
|
33725
35249
|
const reject = choices.find((item) => typeof item.kind === "string" && item.kind.startsWith("reject"));
|
|
33726
35250
|
if (mode === "reject")
|
|
33727
35251
|
return reject?.optionId;
|
|
@@ -33738,7 +35262,7 @@ function collectText(update, chunks) {
|
|
|
33738
35262
|
function readText(value) {
|
|
33739
35263
|
if (typeof value === "string")
|
|
33740
35264
|
return value;
|
|
33741
|
-
if (
|
|
35265
|
+
if (isRecord3(value) && typeof value.text === "string")
|
|
33742
35266
|
return value.text;
|
|
33743
35267
|
return;
|
|
33744
35268
|
}
|
|
@@ -33747,15 +35271,15 @@ import { tool as tool2 } from "@opencode-ai/plugin";
|
|
|
33747
35271
|
|
|
33748
35272
|
// src/tools/ast-grep/cli.ts
|
|
33749
35273
|
init_compat();
|
|
33750
|
-
import { existsSync as
|
|
35274
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
33751
35275
|
|
|
33752
35276
|
// src/tools/ast-grep/constants.ts
|
|
33753
|
-
import { existsSync as
|
|
35277
|
+
import { existsSync as existsSync11, statSync as statSync6 } from "node:fs";
|
|
33754
35278
|
import { createRequire as createRequire3 } from "node:module";
|
|
33755
35279
|
import { dirname as dirname10, join as join15 } from "node:path";
|
|
33756
35280
|
|
|
33757
35281
|
// src/tools/ast-grep/downloader.ts
|
|
33758
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
35282
|
+
import { chmodSync as chmodSync2, existsSync as existsSync10, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "node:fs";
|
|
33759
35283
|
import { createRequire as createRequire2 } from "node:module";
|
|
33760
35284
|
import { homedir as homedir6 } from "node:os";
|
|
33761
35285
|
import { join as join14 } from "node:path";
|
|
@@ -33795,7 +35319,7 @@ function getBinaryName() {
|
|
|
33795
35319
|
}
|
|
33796
35320
|
function getCachedBinaryPath() {
|
|
33797
35321
|
const binaryPath = join14(getCacheDir2(), getBinaryName());
|
|
33798
|
-
return
|
|
35322
|
+
return existsSync10(binaryPath) ? binaryPath : null;
|
|
33799
35323
|
}
|
|
33800
35324
|
async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
33801
35325
|
const platformKey = `${process.platform}-${process.arch}`;
|
|
@@ -33807,7 +35331,7 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
|
33807
35331
|
const cacheDir = getCacheDir2();
|
|
33808
35332
|
const binaryName = getBinaryName();
|
|
33809
35333
|
const binaryPath = join14(cacheDir, binaryName);
|
|
33810
|
-
if (
|
|
35334
|
+
if (existsSync10(binaryPath)) {
|
|
33811
35335
|
return binaryPath;
|
|
33812
35336
|
}
|
|
33813
35337
|
const { arch, os: os6 } = platformInfo;
|
|
@@ -33815,7 +35339,7 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
|
33815
35339
|
const downloadUrl = `https://github.com/${REPO}/releases/download/${version}/${assetName}`;
|
|
33816
35340
|
console.log(`[oh-my-opencode-slim] Downloading ast-grep binary...`);
|
|
33817
35341
|
try {
|
|
33818
|
-
if (!
|
|
35342
|
+
if (!existsSync10(cacheDir)) {
|
|
33819
35343
|
mkdirSync7(cacheDir, { recursive: true });
|
|
33820
35344
|
}
|
|
33821
35345
|
const response = await fetch(downloadUrl, { redirect: "follow" });
|
|
@@ -33826,10 +35350,10 @@ async function downloadAstGrep(version = DEFAULT_VERSION) {
|
|
|
33826
35350
|
const arrayBuffer = await response.arrayBuffer();
|
|
33827
35351
|
await crossWrite(archivePath, arrayBuffer);
|
|
33828
35352
|
await extractZip(archivePath, cacheDir);
|
|
33829
|
-
if (
|
|
35353
|
+
if (existsSync10(archivePath)) {
|
|
33830
35354
|
unlinkSync5(archivePath);
|
|
33831
35355
|
}
|
|
33832
|
-
if (process.platform !== "win32" &&
|
|
35356
|
+
if (process.platform !== "win32" && existsSync10(binaryPath)) {
|
|
33833
35357
|
chmodSync2(binaryPath, 493);
|
|
33834
35358
|
}
|
|
33835
35359
|
console.log(`[oh-my-opencode-slim] ast-grep binary ready.`);
|
|
@@ -33912,7 +35436,7 @@ function findSgCliPathSync() {
|
|
|
33912
35436
|
const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
|
|
33913
35437
|
const cliDir = dirname10(cliPkgPath);
|
|
33914
35438
|
const sgPath = join15(cliDir, binaryName);
|
|
33915
|
-
if (
|
|
35439
|
+
if (existsSync11(sgPath) && isValidBinary(sgPath)) {
|
|
33916
35440
|
return sgPath;
|
|
33917
35441
|
}
|
|
33918
35442
|
} catch {}
|
|
@@ -33924,7 +35448,7 @@ function findSgCliPathSync() {
|
|
|
33924
35448
|
const pkgDir = dirname10(pkgPath);
|
|
33925
35449
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
33926
35450
|
const binaryPath = join15(pkgDir, astGrepName);
|
|
33927
|
-
if (
|
|
35451
|
+
if (existsSync11(binaryPath) && isValidBinary(binaryPath)) {
|
|
33928
35452
|
return binaryPath;
|
|
33929
35453
|
}
|
|
33930
35454
|
} catch {}
|
|
@@ -33932,7 +35456,7 @@ function findSgCliPathSync() {
|
|
|
33932
35456
|
if (process.platform === "darwin") {
|
|
33933
35457
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
33934
35458
|
for (const path18 of homebrewPaths) {
|
|
33935
|
-
if (
|
|
35459
|
+
if (existsSync11(path18) && isValidBinary(path18)) {
|
|
33936
35460
|
return path18;
|
|
33937
35461
|
}
|
|
33938
35462
|
}
|
|
@@ -33961,7 +35485,7 @@ var DEFAULT_MAX_MATCHES = 500;
|
|
|
33961
35485
|
var initPromise = null;
|
|
33962
35486
|
async function getAstGrepPath() {
|
|
33963
35487
|
const currentPath = getSgCliPath();
|
|
33964
|
-
if (currentPath !== "sg" &&
|
|
35488
|
+
if (currentPath !== "sg" && existsSync12(currentPath)) {
|
|
33965
35489
|
return currentPath;
|
|
33966
35490
|
}
|
|
33967
35491
|
if (initPromise) {
|
|
@@ -33969,7 +35493,7 @@ async function getAstGrepPath() {
|
|
|
33969
35493
|
}
|
|
33970
35494
|
initPromise = (async () => {
|
|
33971
35495
|
const syncPath = findSgCliPathSync();
|
|
33972
|
-
if (syncPath &&
|
|
35496
|
+
if (syncPath && existsSync12(syncPath)) {
|
|
33973
35497
|
setSgCliPath(syncPath);
|
|
33974
35498
|
return syncPath;
|
|
33975
35499
|
}
|
|
@@ -34008,7 +35532,7 @@ async function runSg(options) {
|
|
|
34008
35532
|
const paths2 = options.paths && options.paths.length > 0 ? options.paths : ["."];
|
|
34009
35533
|
args.push(...paths2);
|
|
34010
35534
|
let cliPath = getSgCliPath();
|
|
34011
|
-
if (!
|
|
35535
|
+
if (!existsSync12(cliPath) && cliPath !== "sg") {
|
|
34012
35536
|
const downloadedPath = await getAstGrepPath();
|
|
34013
35537
|
if (downloadedPath) {
|
|
34014
35538
|
cliPath = downloadedPath;
|
|
@@ -34620,7 +36144,7 @@ async function getSessionStatus(client, taskID) {
|
|
|
34620
36144
|
try {
|
|
34621
36145
|
const result = await client.session.status();
|
|
34622
36146
|
const data = result.data;
|
|
34623
|
-
if (!
|
|
36147
|
+
if (!isRecord2(data)) {
|
|
34624
36148
|
return { status: undefined, source: "invalid-data", keys: [] };
|
|
34625
36149
|
}
|
|
34626
36150
|
const keys = Object.keys(data).slice(0, 20);
|
|
@@ -34628,14 +36152,14 @@ async function getSessionStatus(client, taskID) {
|
|
|
34628
36152
|
if (item === undefined) {
|
|
34629
36153
|
return { status: "idle", source: "missing-from-map", keys };
|
|
34630
36154
|
}
|
|
34631
|
-
if (
|
|
36155
|
+
if (isRecord2(item) && typeof item.type === "string") {
|
|
34632
36156
|
return { status: item.type, source: "task-map-entry", keys };
|
|
34633
36157
|
}
|
|
34634
36158
|
if (typeof data.type === "string") {
|
|
34635
36159
|
return { status: data.type, source: "legacy-data-type", keys };
|
|
34636
36160
|
}
|
|
34637
36161
|
const nested = data.status;
|
|
34638
|
-
if (
|
|
36162
|
+
if (isRecord2(nested) && typeof nested.type === "string") {
|
|
34639
36163
|
return { status: nested.type, source: "legacy-data-status", keys };
|
|
34640
36164
|
}
|
|
34641
36165
|
return { status: undefined, source: "unknown-shape", keys };
|
|
@@ -34664,7 +36188,7 @@ async function getSessionParentID(client, taskID) {
|
|
|
34664
36188
|
try {
|
|
34665
36189
|
const response = await session2.get({ path: { id: taskID } });
|
|
34666
36190
|
const data = response.data;
|
|
34667
|
-
if (!
|
|
36191
|
+
if (!isRecord2(data))
|
|
34668
36192
|
return;
|
|
34669
36193
|
const parentID = data.parentID;
|
|
34670
36194
|
return typeof parentID === "string" ? parentID : undefined;
|
|
@@ -34757,6 +36281,7 @@ Returns the councillor responses with a summary footer.`,
|
|
|
34757
36281
|
import * as fs10 from "node:fs";
|
|
34758
36282
|
|
|
34759
36283
|
// src/tui-state.ts
|
|
36284
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
34760
36285
|
import * as fs9 from "node:fs";
|
|
34761
36286
|
import * as os6 from "node:os";
|
|
34762
36287
|
import * as path18 from "node:path";
|
|
@@ -34765,8 +36290,11 @@ var STATE_FILE = "tui-state.json";
|
|
|
34765
36290
|
function dataDir() {
|
|
34766
36291
|
return process.env.XDG_DATA_HOME ?? path18.join(os6.homedir(), ".local", "share");
|
|
34767
36292
|
}
|
|
34768
|
-
function
|
|
34769
|
-
return path18.
|
|
36293
|
+
function projectScope(projectDir) {
|
|
36294
|
+
return createHash4("sha256").update(path18.resolve(projectDir)).digest("hex").slice(0, 12);
|
|
36295
|
+
}
|
|
36296
|
+
function getTuiStatePath(projectDir) {
|
|
36297
|
+
return path18.join(dataDir(), "opencode", "storage", STATE_DIR, projectScope(projectDir), STATE_FILE);
|
|
34770
36298
|
}
|
|
34771
36299
|
function emptySnapshot() {
|
|
34772
36300
|
return {
|
|
@@ -34787,42 +36315,42 @@ function parseSnapshot(value) {
|
|
|
34787
36315
|
agentVariants: parsed.agentVariants ?? {}
|
|
34788
36316
|
};
|
|
34789
36317
|
}
|
|
34790
|
-
function readTuiSnapshot() {
|
|
36318
|
+
function readTuiSnapshot(projectDir) {
|
|
34791
36319
|
try {
|
|
34792
|
-
return parseSnapshot(fs9.readFileSync(getTuiStatePath(), "utf8"));
|
|
36320
|
+
return parseSnapshot(fs9.readFileSync(getTuiStatePath(projectDir), "utf8"));
|
|
34793
36321
|
} catch {
|
|
34794
36322
|
return emptySnapshot();
|
|
34795
36323
|
}
|
|
34796
36324
|
}
|
|
34797
|
-
async function readTuiSnapshotAsync() {
|
|
36325
|
+
async function readTuiSnapshotAsync(projectDir) {
|
|
34798
36326
|
try {
|
|
34799
|
-
return parseSnapshot(await fs9.promises.readFile(getTuiStatePath(), "utf8"));
|
|
36327
|
+
return parseSnapshot(await fs9.promises.readFile(getTuiStatePath(projectDir), "utf8"));
|
|
34800
36328
|
} catch {
|
|
34801
36329
|
return emptySnapshot();
|
|
34802
36330
|
}
|
|
34803
36331
|
}
|
|
34804
|
-
function writeTuiSnapshot(snapshot) {
|
|
36332
|
+
function writeTuiSnapshot(snapshot, projectDir) {
|
|
34805
36333
|
try {
|
|
34806
|
-
const filePath = getTuiStatePath();
|
|
36334
|
+
const filePath = getTuiStatePath(projectDir);
|
|
34807
36335
|
fs9.mkdirSync(path18.dirname(filePath), { recursive: true });
|
|
34808
36336
|
fs9.writeFileSync(filePath, `${JSON.stringify(snapshot)}
|
|
34809
36337
|
`);
|
|
34810
36338
|
} catch {}
|
|
34811
36339
|
}
|
|
34812
|
-
function updateSnapshot(mutator) {
|
|
34813
|
-
const snapshot = readTuiSnapshot();
|
|
36340
|
+
function updateSnapshot(projectDir, mutator) {
|
|
36341
|
+
const snapshot = readTuiSnapshot(projectDir);
|
|
34814
36342
|
mutator(snapshot);
|
|
34815
36343
|
snapshot.updatedAt = Date.now();
|
|
34816
|
-
writeTuiSnapshot(snapshot);
|
|
36344
|
+
writeTuiSnapshot(snapshot, projectDir);
|
|
34817
36345
|
}
|
|
34818
|
-
function recordTuiAgentModels(input) {
|
|
34819
|
-
updateSnapshot((snapshot) => {
|
|
36346
|
+
function recordTuiAgentModels(input, projectDir) {
|
|
36347
|
+
updateSnapshot(projectDir, (snapshot) => {
|
|
34820
36348
|
snapshot.agentModels = { ...input.agentModels };
|
|
34821
36349
|
snapshot.agentVariants = { ...input.agentVariants ?? {} };
|
|
34822
36350
|
});
|
|
34823
36351
|
}
|
|
34824
|
-
function recordTuiAgentModel(input) {
|
|
34825
|
-
updateSnapshot((snapshot) => {
|
|
36352
|
+
function recordTuiAgentModel(input, projectDir) {
|
|
36353
|
+
updateSnapshot(projectDir, (snapshot) => {
|
|
34826
36354
|
snapshot.agentModels[input.agentName] = input.model;
|
|
34827
36355
|
if (input.variant !== undefined) {
|
|
34828
36356
|
if (input.variant === null) {
|
|
@@ -34900,7 +36428,7 @@ function createPresetManager(ctx, config) {
|
|
|
34900
36428
|
`);
|
|
34901
36429
|
}
|
|
34902
36430
|
} catch {}
|
|
34903
|
-
const snapshot = readTuiSnapshot();
|
|
36431
|
+
const snapshot = readTuiSnapshot(ctx.directory);
|
|
34904
36432
|
const agentModels = { ...snapshot.agentModels };
|
|
34905
36433
|
const agentVariants = { ...snapshot.agentVariants };
|
|
34906
36434
|
for (const [agentName, agentConfig] of Object.entries(agentUpdates)) {
|
|
@@ -34913,7 +36441,7 @@ function createPresetManager(ctx, config) {
|
|
|
34913
36441
|
delete agentVariants[agentName];
|
|
34914
36442
|
}
|
|
34915
36443
|
}
|
|
34916
|
-
recordTuiAgentModels({ agentModels, agentVariants });
|
|
36444
|
+
recordTuiAgentModels({ agentModels, agentVariants }, ctx.directory);
|
|
34917
36445
|
activePreset = presetName;
|
|
34918
36446
|
const summaryParts = [];
|
|
34919
36447
|
for (const [name, cfg] of Object.entries(agentUpdates)) {
|
|
@@ -35878,9 +37406,6 @@ function cleanFetchedMarkdown(input) {
|
|
|
35878
37406
|
function cleanFetchedText(input) {
|
|
35879
37407
|
return trimBlankRuns(input);
|
|
35880
37408
|
}
|
|
35881
|
-
function escapeHtml2(input) {
|
|
35882
|
-
return input.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
35883
|
-
}
|
|
35884
37409
|
function withTruncationMarker(content, format, truncated) {
|
|
35885
37410
|
if (!truncated)
|
|
35886
37411
|
return content;
|
|
@@ -35919,7 +37444,7 @@ ${content}`;
|
|
|
35919
37444
|
}
|
|
35920
37445
|
function renderMessageForFormat(content, format) {
|
|
35921
37446
|
if (format === "html")
|
|
35922
|
-
return `<pre>${
|
|
37447
|
+
return `<pre>${escapeHtml(content)}</pre>`;
|
|
35923
37448
|
return content;
|
|
35924
37449
|
}
|
|
35925
37450
|
function buildRedirectResultMessage(originalUrl, redirectUrl, statusCode) {
|
|
@@ -36016,22 +37541,9 @@ async function extractFromHtml(html, finalUrl, extractMain) {
|
|
|
36016
37541
|
headings
|
|
36017
37542
|
};
|
|
36018
37543
|
}
|
|
36019
|
-
function parseFrontmatterBlock(content) {
|
|
36020
|
-
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
36021
|
-
if (!match)
|
|
36022
|
-
return;
|
|
36023
|
-
const result = {};
|
|
36024
|
-
for (const line of match[1].split(/\r?\n/)) {
|
|
36025
|
-
const kv = line.match(/^([A-Za-z0-9_-]+):\s*(.+?)\s*$/);
|
|
36026
|
-
if (!kv)
|
|
36027
|
-
continue;
|
|
36028
|
-
result[kv[1]] = kv[2].replace(/^(['"])(.*)\1$/, "$2");
|
|
36029
|
-
}
|
|
36030
|
-
return result;
|
|
36031
|
-
}
|
|
36032
37544
|
function inferCanonicalUrlFromText(content, finalUrl) {
|
|
36033
|
-
const
|
|
36034
|
-
const raw =
|
|
37545
|
+
const frontmatterData = parseFrontmatter(content);
|
|
37546
|
+
const raw = frontmatterData?.url;
|
|
36035
37547
|
if (!raw)
|
|
36036
37548
|
return;
|
|
36037
37549
|
try {
|
|
@@ -36598,7 +38110,7 @@ function isInvalidLlmsResult(fetchResult) {
|
|
|
36598
38110
|
}
|
|
36599
38111
|
|
|
36600
38112
|
// src/tools/smartfetch/secondary-model.ts
|
|
36601
|
-
import { existsSync as
|
|
38113
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
36602
38114
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
36603
38115
|
import path21 from "node:path";
|
|
36604
38116
|
function parseModelRef(value) {
|
|
@@ -36627,7 +38139,7 @@ function pickAgentModelRef(value) {
|
|
|
36627
38139
|
function findPreferredOpenCodeConfigPath(baseDir) {
|
|
36628
38140
|
for (const file of ["opencode.jsonc", "opencode.json"]) {
|
|
36629
38141
|
const fullPath = path21.join(baseDir, file);
|
|
36630
|
-
if (
|
|
38142
|
+
if (existsSync13(fullPath))
|
|
36631
38143
|
return fullPath;
|
|
36632
38144
|
}
|
|
36633
38145
|
return;
|
|
@@ -36743,7 +38255,7 @@ async function deleteSessionSafely(client, sessionId, directory) {
|
|
|
36743
38255
|
console.warn(`[smartfetch] Failed to clean up secondary session ${sessionId} ` + `after ${SESSION_DELETE_RETRIES} attempts: ` + (error instanceof Error ? error.message : String(error)));
|
|
36744
38256
|
return;
|
|
36745
38257
|
}
|
|
36746
|
-
await new Promise((
|
|
38258
|
+
await new Promise((resolve5) => setTimeout(resolve5, _testConfig.deleteRetryDelayMs));
|
|
36747
38259
|
}
|
|
36748
38260
|
}
|
|
36749
38261
|
}
|
|
@@ -37297,7 +38809,7 @@ function createWebfetchTool(pluginCtx, options = {}) {
|
|
|
37297
38809
|
secondary_model: `${secondaryRun.model.providerID}/${secondaryRun.model.modelID}`
|
|
37298
38810
|
}) : "";
|
|
37299
38811
|
const secondaryRaw = secondaryRun.text || "No response from secondary model.";
|
|
37300
|
-
const secondaryContent = args.format === "html" ? withTruncationMarker(`<pre>${
|
|
38812
|
+
const secondaryContent = args.format === "html" ? withTruncationMarker(`<pre>${escapeHtml(secondaryRaw)}</pre>`, "html", false) : withTruncationMarker(secondaryRaw, args.format, false);
|
|
37301
38813
|
return joinRenderedContent(metadataWithSecondary, secondaryContent, args.format);
|
|
37302
38814
|
} finally {
|
|
37303
38815
|
clearTimeout(timeout);
|
|
@@ -37416,6 +38928,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
37416
38928
|
let agents;
|
|
37417
38929
|
let mcps;
|
|
37418
38930
|
let modelArrayMap;
|
|
38931
|
+
let everModelSwitched;
|
|
37419
38932
|
let runtimeChains;
|
|
37420
38933
|
let multiplexerConfig;
|
|
37421
38934
|
let multiplexerEnabled;
|
|
@@ -37423,6 +38936,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
37423
38936
|
let multiplexerSessionManager;
|
|
37424
38937
|
let autoUpdateChecker;
|
|
37425
38938
|
let sessionAgentMap;
|
|
38939
|
+
const sessionDirectories = new Map;
|
|
37426
38940
|
let sessionLifecycle;
|
|
37427
38941
|
let chatHeadersHook;
|
|
37428
38942
|
let foregroundFallback;
|
|
@@ -37466,6 +38980,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
37466
38980
|
agentDefs = createAgents(config, { projectDirectory: ctx.directory });
|
|
37467
38981
|
agents = getAgentConfigs(config, { projectDirectory: ctx.directory });
|
|
37468
38982
|
modelArrayMap = {};
|
|
38983
|
+
everModelSwitched = new Set;
|
|
37469
38984
|
runtimeChains = {};
|
|
37470
38985
|
for (const agentDef of agentDefs) {
|
|
37471
38986
|
if (agentDef._modelArray?.length) {
|
|
@@ -37495,9 +39010,9 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
37495
39010
|
acpRunTools = Object.keys(config.acpAgents ?? {}).length > 0 ? { acp_run: createAcpRunTool(config.acpAgents) } : {};
|
|
37496
39011
|
webfetch = createWebfetchTool(ctx);
|
|
37497
39012
|
backgroundJobBoard = new BackgroundJobBoard({
|
|
37498
|
-
maxReusablePerAgent: config.backgroundJobs?.maxSessionsPerAgent ??
|
|
37499
|
-
readContextMinLines: config.backgroundJobs?.readContextMinLines ??
|
|
37500
|
-
readContextMaxFiles: config.backgroundJobs?.readContextMaxFiles ??
|
|
39013
|
+
maxReusablePerAgent: config.backgroundJobs?.maxSessionsPerAgent ?? DEFAULT_MAX_SESSIONS_PER_AGENT,
|
|
39014
|
+
readContextMinLines: config.backgroundJobs?.readContextMinLines ?? DEFAULT_READ_CONTEXT_MIN_LINES,
|
|
39015
|
+
readContextMaxFiles: config.backgroundJobs?.readContextMaxFiles ?? DEFAULT_READ_CONTEXT_MAX_FILES
|
|
37501
39016
|
});
|
|
37502
39017
|
const backgroundJobCoordinator = new BackgroundJobCoordinator(backgroundJobBoard);
|
|
37503
39018
|
multiplexerSessionManager = new MultiplexerSessionManager(ctx, multiplexerConfig, backgroundJobCoordinator);
|
|
@@ -37511,14 +39026,14 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
37511
39026
|
});
|
|
37512
39027
|
sessionAgentMap = new Map;
|
|
37513
39028
|
chatHeadersHook = createChatHeadersHook(ctx);
|
|
37514
|
-
foregroundFallback = new ForegroundFallbackManager(ctx.client, runtimeChains, config.fallback?.enabled !== false, config.fallback?.maxRetries ?? 3, sessionLifecycle
|
|
39029
|
+
foregroundFallback = new ForegroundFallbackManager(ctx.client, runtimeChains, config.fallback?.enabled !== false, config.fallback?.maxRetries ?? 3, sessionLifecycle);
|
|
37515
39030
|
deepworkCommandHook = createDeepworkCommandHook();
|
|
37516
39031
|
reflectCommandHook = createReflectCommandHook();
|
|
37517
39032
|
loopCommandHook = createLoopCommandHook();
|
|
37518
39033
|
taskSessionManagerHook = createTaskSessionManagerHook(ctx, {
|
|
37519
|
-
maxSessionsPerAgent: config.backgroundJobs?.maxSessionsPerAgent ??
|
|
37520
|
-
readContextMinLines: config.backgroundJobs?.readContextMinLines ??
|
|
37521
|
-
readContextMaxFiles: config.backgroundJobs?.readContextMaxFiles ??
|
|
39034
|
+
maxSessionsPerAgent: config.backgroundJobs?.maxSessionsPerAgent ?? DEFAULT_MAX_SESSIONS_PER_AGENT,
|
|
39035
|
+
readContextMinLines: config.backgroundJobs?.readContextMinLines ?? DEFAULT_READ_CONTEXT_MIN_LINES,
|
|
39036
|
+
readContextMaxFiles: config.backgroundJobs?.readContextMaxFiles ?? DEFAULT_READ_CONTEXT_MAX_FILES,
|
|
37522
39037
|
backgroundJobBoard: backgroundJobCoordinator,
|
|
37523
39038
|
shouldManageSession: (sessionID) => sessionAgentMap.get(sessionID) === "orchestrator",
|
|
37524
39039
|
registerSessionAsOrchestrator: (sessionID) => {
|
|
@@ -37658,6 +39173,15 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
37658
39173
|
} else {
|
|
37659
39174
|
for (const [name, pluginAgent] of Object.entries(agents)) {
|
|
37660
39175
|
const existing = opencodeConfig.agent[name];
|
|
39176
|
+
if (existing && typeof existing.model === "string") {
|
|
39177
|
+
const primary = modelArrayMap[name]?.[0]?.id;
|
|
39178
|
+
if (primary && existing.model !== primary) {
|
|
39179
|
+
everModelSwitched.add(name);
|
|
39180
|
+
}
|
|
39181
|
+
if (everModelSwitched.has(name)) {
|
|
39182
|
+
foregroundFallback.disableChain(name);
|
|
39183
|
+
}
|
|
39184
|
+
}
|
|
37661
39185
|
if (existing) {
|
|
37662
39186
|
opencodeConfig.agent[name] = {
|
|
37663
39187
|
...pluginAgent,
|
|
@@ -37790,6 +39314,13 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
37790
39314
|
recordTuiAgentModels({
|
|
37791
39315
|
agentModels: tuiAgentModels,
|
|
37792
39316
|
agentVariants: tuiAgentVariants
|
|
39317
|
+
}, ctx.directory);
|
|
39318
|
+
applyOrchestratorModelConfig({
|
|
39319
|
+
agents: configAgent,
|
|
39320
|
+
enabled: config.stripOrchestratorModel,
|
|
39321
|
+
presets: config.presets,
|
|
39322
|
+
configPreset: config.preset,
|
|
39323
|
+
runtimePreset: runtimePresetName
|
|
37793
39324
|
});
|
|
37794
39325
|
const configMcp = opencodeConfig.mcp;
|
|
37795
39326
|
if (!configMcp) {
|
|
@@ -37839,7 +39370,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
37839
39370
|
agentName,
|
|
37840
39371
|
model,
|
|
37841
39372
|
variant: variant ?? null
|
|
37842
|
-
});
|
|
39373
|
+
}, (info?.sessionID && sessionDirectories.get(info.sessionID)) ?? ctx.directory);
|
|
37843
39374
|
}
|
|
37844
39375
|
}
|
|
37845
39376
|
if (event.type === "session.created") {
|
|
@@ -37848,10 +39379,18 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
37848
39379
|
if (depthTracker && childSessionId && parentSessionId) {
|
|
37849
39380
|
depthTracker.registerChild(parentSessionId, childSessionId);
|
|
37850
39381
|
}
|
|
39382
|
+
const createdSessionId = event.properties?.info?.id;
|
|
39383
|
+
const createdSessionDir = event.properties?.info?.directory;
|
|
39384
|
+
if (createdSessionId && createdSessionDir) {
|
|
39385
|
+
sessionDirectories.set(createdSessionId, createdSessionDir);
|
|
39386
|
+
}
|
|
37851
39387
|
}
|
|
37852
39388
|
await multiplexerSessionManager.onSessionCreated(event);
|
|
37853
39389
|
await multiplexerSessionManager.onSessionStatus(event);
|
|
37854
39390
|
await multiplexerSessionManager.onSessionDeleted(event);
|
|
39391
|
+
if (event.type === "server.instance.disposed") {
|
|
39392
|
+
await multiplexerSessionManager.cleanupOnInstanceDisposed();
|
|
39393
|
+
}
|
|
37855
39394
|
await foregroundFallback.handleEvent(input.event);
|
|
37856
39395
|
await autoUpdateChecker.event(input);
|
|
37857
39396
|
await interviewManager.handleEvent(input);
|
|
@@ -37883,6 +39422,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
37883
39422
|
}
|
|
37884
39423
|
if (sessionID) {
|
|
37885
39424
|
sessionAgentMap.delete(sessionID);
|
|
39425
|
+
sessionDirectories.delete(sessionID);
|
|
37886
39426
|
}
|
|
37887
39427
|
}
|
|
37888
39428
|
},
|
|
@@ -37905,6 +39445,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
37905
39445
|
output.message.agent = agent;
|
|
37906
39446
|
}
|
|
37907
39447
|
if (agent) {
|
|
39448
|
+
foregroundFallback.registerSessionAgent(input.sessionID, agent);
|
|
37908
39449
|
sessionAgentMap.set(input.sessionID, agent);
|
|
37909
39450
|
companionManager.onSessionStatus({
|
|
37910
39451
|
sessionId: input.sessionID,
|
|
@@ -37931,6 +39472,9 @@ ${output.system[0]}` : "");
|
|
|
37931
39472
|
"experimental.chat.messages.transform": async (input, output) => {
|
|
37932
39473
|
const typedOutput = output;
|
|
37933
39474
|
for (const message of typedOutput.messages) {
|
|
39475
|
+
if (!isMessageWithParts(message)) {
|
|
39476
|
+
continue;
|
|
39477
|
+
}
|
|
37934
39478
|
if (message.info.role !== "user") {
|
|
37935
39479
|
continue;
|
|
37936
39480
|
}
|
|
@@ -37944,6 +39488,7 @@ ${output.system[0]}` : "");
|
|
|
37944
39488
|
processImageAttachments({
|
|
37945
39489
|
messages: typedOutput.messages,
|
|
37946
39490
|
workDir: ctx.directory,
|
|
39491
|
+
imageRouting: resolveImageRouting(config.image_routing),
|
|
37947
39492
|
disabledAgents,
|
|
37948
39493
|
log
|
|
37949
39494
|
});
|