oh-my-opencode-slim 2.1.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja-JP.md +18 -18
- package/README.ko-KR.md +18 -18
- package/README.md +29 -18
- package/README.zh-CN.md +16 -16
- package/dist/cli/index.js +42 -11
- package/dist/cli/providers.d.ts +7 -7
- package/dist/companion/manager.d.ts +3 -0
- package/dist/config/constants.d.ts +2 -1
- package/dist/config/council-schema.d.ts +37 -12
- package/dist/config/schema.d.ts +13 -6
- package/dist/council/council-manager.d.ts +7 -3
- package/dist/hooks/foreground-fallback/index.d.ts +35 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/phase-reminder/index.d.ts +3 -1
- package/dist/hooks/post-file-tool-nudge/index.d.ts +15 -9
- package/dist/hooks/session-lifecycle.d.ts +11 -0
- package/dist/hooks/task-session-manager/index.d.ts +14 -2
- package/dist/index.js +972 -514
- package/dist/interview/service.d.ts +2 -0
- package/dist/multiplexer/herdr/index.d.ts +5 -3
- package/dist/multiplexer/session-manager.d.ts +5 -5
- package/dist/multiplexer/shared.d.ts +24 -0
- package/dist/multiplexer/tmux/index.d.ts +0 -1
- package/dist/multiplexer/zellij/index.d.ts +0 -1
- package/dist/tools/cancel-task.d.ts +2 -2
- package/dist/tui.d.ts +1 -0
- package/dist/tui.js +45 -10
- package/dist/utils/background-job-board.d.ts +5 -1
- package/dist/utils/background-job-coordinator.d.ts +72 -0
- package/dist/utils/background-job-store.d.ts +46 -0
- package/dist/utils/councillor-models.d.ts +20 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/internal-initiator.d.ts +6 -1
- package/dist/utils/session.d.ts +1 -1
- package/oh-my-opencode-slim.schema.json +13 -1
- package/package.json +1 -1
- package/src/skills/oh-my-opencode-slim/SKILL.md +3 -3
- package/src/skills/release-smoke-test/SKILL.md +2 -2
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
|
|
187
|
+
// 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
|
|
1758
|
+
// 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
|
|
1814
|
+
// 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
|
|
1824
|
+
// 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
|
|
1881
|
+
// 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
|
|
1900
|
+
// 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
|
|
1958
|
+
// 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
|
|
2082
|
+
// 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
|
|
2087
|
+
// 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
|
|
2200
|
+
// 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
|
|
2388
|
+
// 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
|
|
2431
|
+
// 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
|
|
2607
|
+
// 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
|
|
3168
|
+
// 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
|
|
3185
|
+
// 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
|
|
3199
|
+
// 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
|
|
3210
|
+
// 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
|
|
3278
|
+
// 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
|
|
3327
|
+
// 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
|
|
3463
|
+
// 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
|
|
3529
|
+
// 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
|
|
3691
|
+
// 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
|
|
4434
|
+
// 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
|
|
4524
|
+
// 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
|
|
4550
|
+
// 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
|
|
4587
|
+
// 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
|
|
5488
|
+
// 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
|
|
5529
|
+
// 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
|
|
5586
|
+
// 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
|
|
5656
|
+
// 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
|
|
5699
|
+
// 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
|
|
5756
|
+
// 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
|
|
5805
|
+
// 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
|
|
5828
|
+
// 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
|
|
5892
|
+
// 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
|
|
6122
|
+
// 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
|
|
6263
|
+
// 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
|
|
6436
|
+
// 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
|
|
6448
|
+
// 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
|
|
6458
|
+
// 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
|
|
6525
|
+
// 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
|
|
6701
|
+
// 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
|
|
6935
|
+
// 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
|
|
6997
|
+
// 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
|
|
8461
|
+
// 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
|
|
8587
|
+
// 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
|
|
8599
|
+
// 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
|
|
9311
|
+
// 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
|
|
9345
|
+
// 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
|
|
17396
|
+
// 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
|
|
17470
|
+
// 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
|
|
17506
|
+
// 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
|
|
17525
|
+
// 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
|
|
17536
|
+
// 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
|
|
17564
|
+
// 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
|
|
17619
|
+
// 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
|
|
17669
|
+
// 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++) {
|
|
@@ -18497,7 +18497,12 @@ var POLL_INTERVAL_BACKGROUND_MS = 2000;
|
|
|
18497
18497
|
var MAX_POLL_TIME_MS = 5 * 60 * 1000;
|
|
18498
18498
|
var DEFAULT_MAX_SUBAGENT_DEPTH = 3;
|
|
18499
18499
|
var PHASE_REMINDER_TEXT = `!IMPORTANT! Scheduler workflow: plan lanes/dependencies → dispatch background specialists → track task IDs → wait for hook-driven completion → reconcile terminal results → verify. Do not poll running jobs, consume running-job output, or advance dependent work. !END!`;
|
|
18500
|
-
|
|
18500
|
+
function formatSystemReminder(text) {
|
|
18501
|
+
return `<system-reminder>
|
|
18502
|
+
${text}
|
|
18503
|
+
</system-reminder>`;
|
|
18504
|
+
}
|
|
18505
|
+
var PHASE_REMINDER = formatSystemReminder(PHASE_REMINDER_TEXT);
|
|
18501
18506
|
var WRITABLE_FILE_OPERATIONS_RULES = `**File Operations Rules**:
|
|
18502
18507
|
- Prefer dedicated file tools for normal code work: glob/grep/ast_grep_search for discovery, read for file contents, and edit/write/apply_patch for targeted source changes.
|
|
18503
18508
|
- Use bash for execution and automation: git, package managers, tests, builds, scripts, diagnostics, and shell-native filesystem operations.
|
|
@@ -18518,11 +18523,35 @@ var COUNCILLOR_STAGGER_MS = 250;
|
|
|
18518
18523
|
var DEFAULT_DISABLED_AGENTS = ["observer"];
|
|
18519
18524
|
// src/config/council-schema.ts
|
|
18520
18525
|
import { z } from "zod";
|
|
18521
|
-
|
|
18526
|
+
|
|
18527
|
+
// src/utils/councillor-models.ts
|
|
18528
|
+
function normalizeCouncillorModels(model, fallbackVariant) {
|
|
18529
|
+
const raw = Array.isArray(model) ? model : [model];
|
|
18530
|
+
return raw.map((entry) => typeof entry === "string" ? { id: entry, variant: fallbackVariant } : { id: entry.id, variant: entry.variant ?? fallbackVariant });
|
|
18531
|
+
}
|
|
18532
|
+
|
|
18533
|
+
// src/config/council-schema.ts
|
|
18534
|
+
var ModelIdSchema = z.string().regex(/^[^/\s]+\/[^\s]+$/, 'Expected provider/model format (e.g. "openai/gpt-5.6-luna")');
|
|
18535
|
+
var CouncillorModelEntrySchema = z.object({
|
|
18536
|
+
id: ModelIdSchema,
|
|
18537
|
+
variant: z.string().optional()
|
|
18538
|
+
});
|
|
18539
|
+
var CouncillorModelSchema = z.union([
|
|
18540
|
+
ModelIdSchema,
|
|
18541
|
+
z.array(z.union([ModelIdSchema, CouncillorModelEntrySchema])).min(1)
|
|
18542
|
+
]).describe('Model ID in provider/model format (e.g. "openai/gpt-5.6-luna"), or an ' + "ordered fallback chain (array of model IDs or { id, variant } entries) " + "tried in order until one responds.");
|
|
18522
18543
|
var CouncillorConfigSchema = z.object({
|
|
18523
|
-
model:
|
|
18544
|
+
model: CouncillorModelSchema,
|
|
18524
18545
|
variant: z.string().optional(),
|
|
18525
18546
|
prompt: z.string().optional().describe("Optional role/guidance injected into the councillor user prompt")
|
|
18547
|
+
}).transform((c) => {
|
|
18548
|
+
const models = normalizeCouncillorModels(c.model, c.variant);
|
|
18549
|
+
return {
|
|
18550
|
+
model: models[0].id,
|
|
18551
|
+
variant: c.variant,
|
|
18552
|
+
prompt: c.prompt,
|
|
18553
|
+
models
|
|
18554
|
+
};
|
|
18526
18555
|
});
|
|
18527
18556
|
var CouncilPresetSchema = z.record(z.string(), z.record(z.string(), z.unknown())).transform((entries, ctx) => {
|
|
18528
18557
|
const councillors = {};
|
|
@@ -18725,7 +18754,9 @@ var FailoverConfigSchema = z2.object({
|
|
|
18725
18754
|
enabled: z2.boolean().default(true),
|
|
18726
18755
|
timeoutMs: z2.number().min(0).default(15000),
|
|
18727
18756
|
retryDelayMs: z2.number().min(0).default(500),
|
|
18728
|
-
|
|
18757
|
+
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
|
+
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().default(true).describe("When true (default), a runtime model selected via /model that is " + "outside the configured fallback chain will still trigger the chain " + "on rate-limit errors. When false, out-of-chain runtime picks are " + "respected and the error surfaces instead of silently falling back " + "to the chain. Models that are members of the chain always fall back " + "regardless of this setting.")
|
|
18729
18760
|
}).strict();
|
|
18730
18761
|
var CompanionConfigSchema = z2.object({
|
|
18731
18762
|
enabled: z2.boolean().optional(),
|
|
@@ -18766,7 +18797,7 @@ function rejectOrchestratorPromptOnOrchestrator(overrides, ctx, pathPrefix) {
|
|
|
18766
18797
|
var PluginConfigSchema = z2.object({
|
|
18767
18798
|
preset: z2.string().optional(),
|
|
18768
18799
|
setDefaultAgent: z2.boolean().optional(),
|
|
18769
|
-
compactSidebar: z2.boolean().optional().describe("Use the compact TUI sidebar layout
|
|
18800
|
+
compactSidebar: z2.boolean().optional().describe("Use the compact TUI sidebar layout. Defaults to true; set false to use the expanded layout."),
|
|
18770
18801
|
autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
|
|
18771
18802
|
presets: z2.record(z2.string(), PresetSchema).optional(),
|
|
18772
18803
|
agents: z2.record(z2.string(), AgentOverrideConfigSchema).optional(),
|
|
@@ -19417,7 +19448,7 @@ var COUNCIL_AGENT_PROMPT = `You are the Council agent - a multi-LLM orchestratio
|
|
|
19417
19448
|
|
|
19418
19449
|
**Usage**:
|
|
19419
19450
|
1. Call the \`council_session\` tool with the user's prompt
|
|
19420
|
-
2. Optionally specify a preset (
|
|
19451
|
+
2. Optionally specify a preset (omit to use the configured default)
|
|
19421
19452
|
3. Receive the councillor responses formatted for synthesis
|
|
19422
19453
|
4. Follow the Synthesis Process below
|
|
19423
19454
|
5. Present the result to the user
|
|
@@ -19996,7 +20027,7 @@ function applyOverrides(agent, override) {
|
|
|
19996
20027
|
if (override.model) {
|
|
19997
20028
|
if (Array.isArray(override.model)) {
|
|
19998
20029
|
agent._modelArray = override.model.map((m) => typeof m === "string" ? { id: m } : m);
|
|
19999
|
-
agent.config.model = agent._modelArray[0].id;
|
|
20030
|
+
agent.config.model = agent.name === "orchestrator" ? undefined : agent._modelArray[0].id;
|
|
20000
20031
|
} else {
|
|
20001
20032
|
agent.config.model = override.model;
|
|
20002
20033
|
}
|
|
@@ -20335,6 +20366,7 @@ import {
|
|
|
20335
20366
|
readFileSync as readFileSync2,
|
|
20336
20367
|
renameSync,
|
|
20337
20368
|
rmSync,
|
|
20369
|
+
statSync as statSync2,
|
|
20338
20370
|
writeFileSync
|
|
20339
20371
|
} from "node:fs";
|
|
20340
20372
|
import * as os2 from "node:os";
|
|
@@ -20425,6 +20457,72 @@ function stateFilePath() {
|
|
|
20425
20457
|
const base = xdg && path3.isAbsolute(xdg) ? xdg : path3.join(os2.homedir(), ".local", "share");
|
|
20426
20458
|
return path3.join(base, "opencode", "storage", "oh-my-opencode-slim", "companion-state.json");
|
|
20427
20459
|
}
|
|
20460
|
+
function pidFilePath() {
|
|
20461
|
+
const xdg = process.env.XDG_DATA_HOME?.trim();
|
|
20462
|
+
const base = xdg && path3.isAbsolute(xdg) ? xdg : path3.join(os2.homedir(), ".local", "share");
|
|
20463
|
+
return path3.join(base, "opencode", "storage", "oh-my-opencode-slim", "companion.pid");
|
|
20464
|
+
}
|
|
20465
|
+
function isProcessAlive(pid) {
|
|
20466
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
20467
|
+
return false;
|
|
20468
|
+
try {
|
|
20469
|
+
process.kill(pid, 0);
|
|
20470
|
+
return true;
|
|
20471
|
+
} catch (err) {
|
|
20472
|
+
return err.code === "EPERM";
|
|
20473
|
+
}
|
|
20474
|
+
}
|
|
20475
|
+
function parsePidFile(raw) {
|
|
20476
|
+
const pid = Number(raw.trim());
|
|
20477
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
20478
|
+
return null;
|
|
20479
|
+
return pid;
|
|
20480
|
+
}
|
|
20481
|
+
function acquirePidFileLock(file) {
|
|
20482
|
+
const lock = `${file}.lock`;
|
|
20483
|
+
mkdirSync2(path3.dirname(lock), { recursive: true });
|
|
20484
|
+
for (let attempt = 0;attempt < 2; attempt++) {
|
|
20485
|
+
try {
|
|
20486
|
+
mkdirSync2(lock);
|
|
20487
|
+
writeFileSync(path3.join(lock, "owner"), String(process.pid));
|
|
20488
|
+
return () => {
|
|
20489
|
+
try {
|
|
20490
|
+
rmSync(lock, { recursive: true, force: true });
|
|
20491
|
+
} catch {}
|
|
20492
|
+
};
|
|
20493
|
+
} catch (err) {
|
|
20494
|
+
const code = err.code;
|
|
20495
|
+
if (code !== "EEXIST")
|
|
20496
|
+
throw err;
|
|
20497
|
+
if (pidFileLockHasLiveOwner(lock))
|
|
20498
|
+
return null;
|
|
20499
|
+
log("[companion] removing stale PID file lock for dead process");
|
|
20500
|
+
rmSync(lock, { recursive: true, force: true });
|
|
20501
|
+
}
|
|
20502
|
+
}
|
|
20503
|
+
return null;
|
|
20504
|
+
}
|
|
20505
|
+
function acquirePidFileLockWithRetry(file, attempts) {
|
|
20506
|
+
for (let attempt = 0;attempt < attempts; attempt++) {
|
|
20507
|
+
const release = acquirePidFileLock(file);
|
|
20508
|
+
if (release)
|
|
20509
|
+
return release;
|
|
20510
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
20511
|
+
}
|
|
20512
|
+
return null;
|
|
20513
|
+
}
|
|
20514
|
+
function pidFileLockHasLiveOwner(lock) {
|
|
20515
|
+
try {
|
|
20516
|
+
const owner = parsePidFile(readFileSync2(path3.join(lock, "owner"), "utf8"));
|
|
20517
|
+
if (owner !== null)
|
|
20518
|
+
return isProcessAlive(owner);
|
|
20519
|
+
} catch {
|
|
20520
|
+
try {
|
|
20521
|
+
return Date.now() - statSync2(lock).mtimeMs < 5000;
|
|
20522
|
+
} catch {}
|
|
20523
|
+
}
|
|
20524
|
+
return false;
|
|
20525
|
+
}
|
|
20428
20526
|
function defaultBinaryPath() {
|
|
20429
20527
|
const xdg = process.env.XDG_DATA_HOME?.trim();
|
|
20430
20528
|
const base = xdg && path3.isAbsolute(xdg) ? xdg : path3.join(os2.homedir(), ".local", "share");
|
|
@@ -20491,6 +20589,8 @@ class CompanionManager {
|
|
|
20491
20589
|
busyAgentSessions = new Map;
|
|
20492
20590
|
config;
|
|
20493
20591
|
companionProcess = null;
|
|
20592
|
+
wasSpawner = false;
|
|
20593
|
+
spawnedCompanionPid = null;
|
|
20494
20594
|
constructor(sessionId, cwd, config) {
|
|
20495
20595
|
this.id = sessionId;
|
|
20496
20596
|
this.cwd = cwd;
|
|
@@ -20574,12 +20674,6 @@ class CompanionManager {
|
|
|
20574
20674
|
}
|
|
20575
20675
|
onExit() {
|
|
20576
20676
|
activeManagers.delete(this);
|
|
20577
|
-
if (this.companionProcess) {
|
|
20578
|
-
try {
|
|
20579
|
-
this.companionProcess.kill();
|
|
20580
|
-
} catch {}
|
|
20581
|
-
this.companionProcess = null;
|
|
20582
|
-
}
|
|
20583
20677
|
if (activeManagers.size === 0 && activeExitListener) {
|
|
20584
20678
|
try {
|
|
20585
20679
|
process.removeListener("exit", activeExitListener);
|
|
@@ -20591,6 +20685,39 @@ class CompanionManager {
|
|
|
20591
20685
|
writeState((state) => {
|
|
20592
20686
|
state.sessions = state.sessions.filter((s) => s.session_id !== this.id);
|
|
20593
20687
|
});
|
|
20688
|
+
if (this.wasSpawner && this.removeOwnedPidFileIfNoSessionsRemain()) {
|
|
20689
|
+
if (this.companionProcess) {
|
|
20690
|
+
try {
|
|
20691
|
+
this.companionProcess.kill();
|
|
20692
|
+
} catch {}
|
|
20693
|
+
}
|
|
20694
|
+
}
|
|
20695
|
+
this.companionProcess = null;
|
|
20696
|
+
}
|
|
20697
|
+
removeOwnedPidFileIfNoSessionsRemain() {
|
|
20698
|
+
if (this.spawnedCompanionPid == null)
|
|
20699
|
+
return true;
|
|
20700
|
+
const file = pidFilePath();
|
|
20701
|
+
const release = acquirePidFileLockWithRetry(file, 80);
|
|
20702
|
+
if (!release) {
|
|
20703
|
+
log("[companion] PID file lock busy during exit; leaving guard intact");
|
|
20704
|
+
return false;
|
|
20705
|
+
}
|
|
20706
|
+
try {
|
|
20707
|
+
if (readState().sessions.length > 0)
|
|
20708
|
+
return false;
|
|
20709
|
+
if (!existsSync2(file))
|
|
20710
|
+
return true;
|
|
20711
|
+
const parsed = parsePidFile(readFileSync2(file, "utf8"));
|
|
20712
|
+
if (parsed === this.spawnedCompanionPid) {
|
|
20713
|
+
rmSync(file, { force: true });
|
|
20714
|
+
}
|
|
20715
|
+
return true;
|
|
20716
|
+
} catch {
|
|
20717
|
+
return false;
|
|
20718
|
+
} finally {
|
|
20719
|
+
release();
|
|
20720
|
+
}
|
|
20594
20721
|
}
|
|
20595
20722
|
activeAgents() {
|
|
20596
20723
|
const agents = Array.from(this.busyAgentSessions.values());
|
|
@@ -20648,13 +20775,35 @@ class CompanionManager {
|
|
|
20648
20775
|
spawnIfAvailable() {
|
|
20649
20776
|
if (this.config?.enabled !== true)
|
|
20650
20777
|
return;
|
|
20651
|
-
const
|
|
20652
|
-
|
|
20653
|
-
|
|
20654
|
-
|
|
20778
|
+
const pidFile = pidFilePath();
|
|
20779
|
+
let releasePidFileLock = null;
|
|
20780
|
+
try {
|
|
20781
|
+
releasePidFileLock = acquirePidFileLockWithRetry(pidFile, 80);
|
|
20782
|
+
if (releasePidFileLock === null) {
|
|
20783
|
+
log("[companion] another instance already running, skipping spawn");
|
|
20784
|
+
return;
|
|
20785
|
+
}
|
|
20786
|
+
} catch (err) {
|
|
20787
|
+
log("[companion] PID file lock failed", String(err));
|
|
20655
20788
|
return;
|
|
20656
20789
|
}
|
|
20790
|
+
let spawnedChild = null;
|
|
20657
20791
|
try {
|
|
20792
|
+
if (existsSync2(pidFile)) {
|
|
20793
|
+
const existingPid = parsePidFile(readFileSync2(pidFile, "utf8"));
|
|
20794
|
+
if (existingPid !== null && isProcessAlive(existingPid)) {
|
|
20795
|
+
log("[companion] another instance already running, skipping spawn");
|
|
20796
|
+
return;
|
|
20797
|
+
}
|
|
20798
|
+
log("[companion] removing stale PID file for dead process");
|
|
20799
|
+
rmSync(pidFile, { force: true });
|
|
20800
|
+
}
|
|
20801
|
+
const bin = resolveCompanionBinaryPath(this.config);
|
|
20802
|
+
if (!bin) {
|
|
20803
|
+
const expected = this.config.binaryPath?.trim() || defaultBinaryPath();
|
|
20804
|
+
log(`[companion] enabled but companion binary not found at expected path: ${expected}. Please install/download the companion binary separately.`);
|
|
20805
|
+
return;
|
|
20806
|
+
}
|
|
20658
20807
|
const child = spawn(bin, [], {
|
|
20659
20808
|
detached: true,
|
|
20660
20809
|
env: {
|
|
@@ -20664,15 +20813,33 @@ class CompanionManager {
|
|
|
20664
20813
|
},
|
|
20665
20814
|
stdio: "ignore"
|
|
20666
20815
|
});
|
|
20816
|
+
spawnedChild = child;
|
|
20817
|
+
child.once("error", (err) => {
|
|
20818
|
+
log("[companion] spawn failed", String(err));
|
|
20819
|
+
});
|
|
20667
20820
|
this.companionProcess = child;
|
|
20668
20821
|
child.unref();
|
|
20822
|
+
if (child.pid == null) {
|
|
20823
|
+
log("[companion] spawn returned without a child PID, skipping guard");
|
|
20824
|
+
return;
|
|
20825
|
+
}
|
|
20826
|
+
writeFileSync(pidFile, String(child.pid));
|
|
20827
|
+
this.wasSpawner = true;
|
|
20828
|
+
this.spawnedCompanionPid = child.pid;
|
|
20669
20829
|
log("[companion] spawned", JSON.stringify({
|
|
20670
20830
|
bin,
|
|
20671
20831
|
sessionId: this.id,
|
|
20672
20832
|
debug: this.config.debug === true
|
|
20673
20833
|
}));
|
|
20674
20834
|
} catch (err) {
|
|
20675
|
-
|
|
20835
|
+
if (spawnedChild && !this.wasSpawner) {
|
|
20836
|
+
try {
|
|
20837
|
+
spawnedChild.kill();
|
|
20838
|
+
} catch {}
|
|
20839
|
+
}
|
|
20840
|
+
log("[companion] spawn guard failed", String(err));
|
|
20841
|
+
} finally {
|
|
20842
|
+
releasePidFileLock?.();
|
|
20676
20843
|
}
|
|
20677
20844
|
}
|
|
20678
20845
|
}
|
|
@@ -20689,7 +20856,7 @@ import {
|
|
|
20689
20856
|
readFileSync as readFileSync3,
|
|
20690
20857
|
renameSync as renameSync2,
|
|
20691
20858
|
rmSync as rmSync2,
|
|
20692
|
-
statSync as
|
|
20859
|
+
statSync as statSync3,
|
|
20693
20860
|
writeFileSync as writeFileSync2
|
|
20694
20861
|
} from "node:fs";
|
|
20695
20862
|
import { homedir as homedir4, platform as platform2, tmpdir } from "node:os";
|
|
@@ -20954,7 +21121,7 @@ async function withCompanionInstallLock(binaryPath, timeoutMs, staleMs, run) {
|
|
|
20954
21121
|
if (code !== "EEXIST")
|
|
20955
21122
|
throw err;
|
|
20956
21123
|
try {
|
|
20957
|
-
const ageMs = Date.now() -
|
|
21124
|
+
const ageMs = Date.now() - statSync3(lock).mtimeMs;
|
|
20958
21125
|
if (ageMs > staleAfterMs) {
|
|
20959
21126
|
rmSync2(lock, { recursive: true, force: true });
|
|
20960
21127
|
log("[companion] removed stale install lock", lock);
|
|
@@ -21232,48 +21399,53 @@ class CouncilManager {
|
|
|
21232
21399
|
return results;
|
|
21233
21400
|
}
|
|
21234
21401
|
async runCouncillorWithRetry(name, config, prompt, parentSessionId, timeout, maxRetries) {
|
|
21235
|
-
const
|
|
21402
|
+
const models = config.models ?? normalizeCouncillorModels(config.model, config.variant);
|
|
21236
21403
|
const totalAttempts = 1 + maxRetries;
|
|
21237
|
-
|
|
21238
|
-
|
|
21239
|
-
|
|
21240
|
-
|
|
21241
|
-
|
|
21242
|
-
|
|
21243
|
-
|
|
21244
|
-
|
|
21245
|
-
|
|
21246
|
-
|
|
21247
|
-
|
|
21248
|
-
|
|
21249
|
-
|
|
21250
|
-
|
|
21251
|
-
|
|
21252
|
-
|
|
21253
|
-
|
|
21254
|
-
|
|
21255
|
-
|
|
21256
|
-
|
|
21257
|
-
|
|
21258
|
-
|
|
21259
|
-
|
|
21260
|
-
|
|
21261
|
-
const canRetry = attempt < totalAttempts && isEmptyResponse;
|
|
21262
|
-
if (!canRetry) {
|
|
21404
|
+
let lastModel = models[0].id;
|
|
21405
|
+
let lastStatus = "failed";
|
|
21406
|
+
let lastError = `Councillor "${name}": no model responded`;
|
|
21407
|
+
for (let modelIndex = 0;modelIndex < models.length; modelIndex++) {
|
|
21408
|
+
const entry = models[modelIndex];
|
|
21409
|
+
const modelLabel = shortModelLabel(entry.id);
|
|
21410
|
+
lastModel = entry.id;
|
|
21411
|
+
for (let attempt = 1;attempt <= totalAttempts; attempt++) {
|
|
21412
|
+
if (attempt > 1) {
|
|
21413
|
+
log(`[council-manager] Retrying councillor "${name}" (${modelLabel}), attempt ${attempt}/${totalAttempts}`);
|
|
21414
|
+
} else if (modelIndex > 0) {
|
|
21415
|
+
log(`[council-manager] Councillor "${name}" falling back to ${modelLabel} (model ${modelIndex + 1}/${models.length})`);
|
|
21416
|
+
}
|
|
21417
|
+
try {
|
|
21418
|
+
const result = await this.runAgentSession({
|
|
21419
|
+
parentSessionId,
|
|
21420
|
+
title: `Council ${name} (${modelLabel})`,
|
|
21421
|
+
agent: "councillor",
|
|
21422
|
+
model: entry.id,
|
|
21423
|
+
promptText: formatCouncillorPrompt(prompt, config.prompt),
|
|
21424
|
+
variant: entry.variant,
|
|
21425
|
+
timeout,
|
|
21426
|
+
includeReasoning: false
|
|
21427
|
+
});
|
|
21263
21428
|
return {
|
|
21264
21429
|
name,
|
|
21265
|
-
model:
|
|
21266
|
-
status:
|
|
21267
|
-
|
|
21430
|
+
model: entry.id,
|
|
21431
|
+
status: "completed",
|
|
21432
|
+
result
|
|
21268
21433
|
};
|
|
21434
|
+
} catch (error) {
|
|
21435
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
21436
|
+
lastStatus = msg.includes("timed out") ? "timed_out" : "failed";
|
|
21437
|
+
lastError = `Councillor "${name}": ${msg}`;
|
|
21438
|
+
const isEmptyResponse = msg.includes("Empty response from provider");
|
|
21439
|
+
if (!(attempt < totalAttempts && isEmptyResponse))
|
|
21440
|
+
break;
|
|
21269
21441
|
}
|
|
21270
21442
|
}
|
|
21271
21443
|
}
|
|
21272
21444
|
return {
|
|
21273
21445
|
name,
|
|
21274
|
-
model:
|
|
21275
|
-
status:
|
|
21276
|
-
error:
|
|
21446
|
+
model: lastModel,
|
|
21447
|
+
status: lastStatus,
|
|
21448
|
+
error: lastError
|
|
21277
21449
|
};
|
|
21278
21450
|
}
|
|
21279
21451
|
}
|
|
@@ -24825,7 +24997,7 @@ class BackgroundJobBoard {
|
|
|
24825
24997
|
const reusable = this.list(parentSessionID).filter(isReusable);
|
|
24826
24998
|
if (active.length === 0 && reusable.length === 0)
|
|
24827
24999
|
return;
|
|
24828
|
-
return [
|
|
25000
|
+
return formatSystemReminder([
|
|
24829
25001
|
"### Background Job Board",
|
|
24830
25002
|
"SENTINEL: background-job-board-v2",
|
|
24831
25003
|
"Do not poll running jobs. Wait for hook-driven completion, or use cancel_task only for explicit cancellation. Reconcile terminal jobs before final response.",
|
|
@@ -24839,7 +25011,7 @@ class BackgroundJobBoard {
|
|
|
24839
25011
|
"#### Reusable Sessions",
|
|
24840
25012
|
...reusable.length > 0 ? reusable.map((job) => this.formatReusableJob(job)) : ["- none"]
|
|
24841
25013
|
].join(`
|
|
24842
|
-
`);
|
|
25014
|
+
`));
|
|
24843
25015
|
}
|
|
24844
25016
|
clearParent(parentSessionID) {
|
|
24845
25017
|
for (const job of this.list(parentSessionID)) {
|
|
@@ -24849,6 +25021,13 @@ class BackgroundJobBoard {
|
|
|
24849
25021
|
drop(taskID) {
|
|
24850
25022
|
this.jobs.delete(taskID);
|
|
24851
25023
|
}
|
|
25024
|
+
deferIfRunning(_sessionId) {
|
|
25025
|
+
return false;
|
|
25026
|
+
}
|
|
25027
|
+
retryDeferredClose(_sessionId) {
|
|
25028
|
+
return false;
|
|
25029
|
+
}
|
|
25030
|
+
clearDeferredClose(_sessionId) {}
|
|
24852
25031
|
trimReusable(taskID) {
|
|
24853
25032
|
const job = this.jobs.get(taskID);
|
|
24854
25033
|
if (!job || !isReusable(job))
|
|
@@ -24862,8 +25041,8 @@ class BackgroundJobBoard {
|
|
|
24862
25041
|
const terminal = job.terminalState ?? terminalStateOf(job.state);
|
|
24863
25042
|
const reconciliation = job.terminalUnreconciled ? "unreconciled" : "reconciled";
|
|
24864
25043
|
const lines = [
|
|
24865
|
-
`- ${job.alias} / ${job.taskID} / ${job.agent} / ${terminal ?? job.state}, ${reconciliation}`,
|
|
24866
|
-
` Objective: ${job.objective || job.description}`
|
|
25044
|
+
`- ${promptSafe(job.alias)} / ${promptSafe(job.taskID)} / ${promptSafe(job.agent)} / ${promptSafe(terminal ?? job.state)}, ${reconciliation}`,
|
|
25045
|
+
` Objective: ${promptSafe(job.objective || job.description)}`
|
|
24867
25046
|
];
|
|
24868
25047
|
const context = formatContextFiles(job.contextFiles, this.readContextMaxFiles);
|
|
24869
25048
|
if (context)
|
|
@@ -24898,7 +25077,7 @@ function formatContextFiles(files, maxFiles) {
|
|
|
24898
25077
|
return "";
|
|
24899
25078
|
const shown = files.slice(0, maxFiles);
|
|
24900
25079
|
const rest = files.length - shown.length;
|
|
24901
|
-
const rendered = shown.map((file) => `${file.path} (${file.lineCount} lines)`);
|
|
25080
|
+
const rendered = shown.map((file) => `${promptSafe(file.path)} (${file.lineCount} lines)`);
|
|
24902
25081
|
return `${rendered.join(", ")}${rest > 0 ? ` (+${rest} more)` : ""}`;
|
|
24903
25082
|
}
|
|
24904
25083
|
function normalizeWhitespace(value) {
|
|
@@ -24910,13 +25089,13 @@ function formatJob(job, now = Date.now()) {
|
|
|
24910
25089
|
const ageLabel = job.state === "running" && ageMs < 30000 ? ` [${isResume ? "resumed" : "just launched"}, ${Math.floor(ageMs / 1000)}s ago]` : "";
|
|
24911
25090
|
const status = job.terminalUnreconciled ? `${job.state}, unreconciled` : job.statusUncertain ? `${job.state}, status uncertain` : job.timedOut ? `${job.state}, timed out` : `${job.state}${ageLabel}`;
|
|
24912
25091
|
const lines = [
|
|
24913
|
-
`- ${job.alias} / ${job.taskID} / ${job.agent} / ${status}`,
|
|
24914
|
-
` Objective: ${job.objective || job.description}`
|
|
25092
|
+
`- ${promptSafe(job.alias)} / ${promptSafe(job.taskID)} / ${promptSafe(job.agent)} / ${promptSafe(status)}`,
|
|
25093
|
+
` Objective: ${promptSafe(job.objective || job.description)}`
|
|
24915
25094
|
];
|
|
24916
25095
|
if (job.resultSummary && job.terminalUnreconciled) {
|
|
24917
|
-
lines.push(` Result: ${
|
|
25096
|
+
lines.push(` Result: ${promptSafe(job.resultSummary)}`);
|
|
24918
25097
|
} else if (job.lastStatusError && job.statusUncertain) {
|
|
24919
|
-
lines.push(` Status: ${
|
|
25098
|
+
lines.push(` Status: ${promptSafe(job.lastStatusError)}`);
|
|
24920
25099
|
}
|
|
24921
25100
|
return lines.join(`
|
|
24922
25101
|
`);
|
|
@@ -24927,10 +25106,138 @@ function singleLine(value) {
|
|
|
24927
25106
|
return normalized;
|
|
24928
25107
|
return `${normalized.slice(0, 157)}...`;
|
|
24929
25108
|
}
|
|
25109
|
+
function promptSafe(value) {
|
|
25110
|
+
return singleLine(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
25111
|
+
}
|
|
24930
25112
|
function normalizeCancelReason(reason) {
|
|
24931
25113
|
const normalized = reason?.replace(/\s+/g, " ").trim();
|
|
24932
25114
|
return normalized ? `cancelled: ${normalized}` : "cancelled";
|
|
24933
25115
|
}
|
|
25116
|
+
// src/utils/background-job-coordinator.ts
|
|
25117
|
+
class BackgroundJobCoordinator {
|
|
25118
|
+
board;
|
|
25119
|
+
terminalStateListeners = [];
|
|
25120
|
+
deferredIdleCloses = new Set;
|
|
25121
|
+
constructor(board) {
|
|
25122
|
+
this.board = board;
|
|
25123
|
+
this.board.addTerminalStateListener((taskID) => {
|
|
25124
|
+
this.handleTerminalState(taskID);
|
|
25125
|
+
});
|
|
25126
|
+
}
|
|
25127
|
+
addTerminalStateListener(listener) {
|
|
25128
|
+
this.terminalStateListeners.push(listener);
|
|
25129
|
+
}
|
|
25130
|
+
removeTerminalStateListener(listener) {
|
|
25131
|
+
this.terminalStateListeners = this.terminalStateListeners.filter((entry) => entry !== listener);
|
|
25132
|
+
}
|
|
25133
|
+
handleTerminalState(taskID) {
|
|
25134
|
+
const state = this.board.getState(taskID);
|
|
25135
|
+
if (state === undefined)
|
|
25136
|
+
return;
|
|
25137
|
+
if (this.retryDeferredClose(taskID)) {
|
|
25138
|
+
for (const listener of this.terminalStateListeners) {
|
|
25139
|
+
listener(taskID);
|
|
25140
|
+
}
|
|
25141
|
+
}
|
|
25142
|
+
}
|
|
25143
|
+
deferIfRunning(sessionId) {
|
|
25144
|
+
if (!this.board.isRunning(sessionId)) {
|
|
25145
|
+
this.deferredIdleCloses.delete(sessionId);
|
|
25146
|
+
return true;
|
|
25147
|
+
}
|
|
25148
|
+
this.deferredIdleCloses.add(sessionId);
|
|
25149
|
+
return false;
|
|
25150
|
+
}
|
|
25151
|
+
retryDeferredClose(sessionId) {
|
|
25152
|
+
if (!this.deferredIdleCloses.has(sessionId))
|
|
25153
|
+
return false;
|
|
25154
|
+
return this.deferIfRunning(sessionId);
|
|
25155
|
+
}
|
|
25156
|
+
clearDeferredClose(sessionId) {
|
|
25157
|
+
this.deferredIdleCloses.delete(sessionId);
|
|
25158
|
+
}
|
|
25159
|
+
registerLaunch(input) {
|
|
25160
|
+
return this.board.registerLaunch(input);
|
|
25161
|
+
}
|
|
25162
|
+
updateStatus(input) {
|
|
25163
|
+
return this.board.updateStatus(input);
|
|
25164
|
+
}
|
|
25165
|
+
updateFromStatusOutput(output) {
|
|
25166
|
+
return this.board.updateFromStatusOutput(output);
|
|
25167
|
+
}
|
|
25168
|
+
markRunningFromLiveSession(taskID, now = Date.now()) {
|
|
25169
|
+
return this.board.markRunningFromLiveSession(taskID, now);
|
|
25170
|
+
}
|
|
25171
|
+
markReconciled(taskID, now = Date.now()) {
|
|
25172
|
+
return this.board.markReconciled(taskID, now);
|
|
25173
|
+
}
|
|
25174
|
+
markCancelled(taskID, reason, now = Date.now(), options = {}) {
|
|
25175
|
+
return this.board.markCancelled(taskID, reason, now, options);
|
|
25176
|
+
}
|
|
25177
|
+
get(taskID) {
|
|
25178
|
+
return this.board.get(taskID);
|
|
25179
|
+
}
|
|
25180
|
+
field(taskID, key) {
|
|
25181
|
+
return this.board.field(taskID, key);
|
|
25182
|
+
}
|
|
25183
|
+
isRunning(taskID) {
|
|
25184
|
+
return this.board.isRunning(taskID);
|
|
25185
|
+
}
|
|
25186
|
+
isTerminalUnreconciled(taskID) {
|
|
25187
|
+
return this.board.isTerminalUnreconciled(taskID);
|
|
25188
|
+
}
|
|
25189
|
+
getResultSummary(taskID) {
|
|
25190
|
+
return this.board.getResultSummary(taskID);
|
|
25191
|
+
}
|
|
25192
|
+
getLastLiveBusyAt(taskID) {
|
|
25193
|
+
return this.board.getLastLiveBusyAt(taskID);
|
|
25194
|
+
}
|
|
25195
|
+
getParentSessionID(taskID) {
|
|
25196
|
+
return this.board.getParentSessionID(taskID);
|
|
25197
|
+
}
|
|
25198
|
+
getState(taskID) {
|
|
25199
|
+
return this.board.getState(taskID);
|
|
25200
|
+
}
|
|
25201
|
+
resolve(parentSessionID, taskIDOrAlias) {
|
|
25202
|
+
return this.board.resolve(parentSessionID, taskIDOrAlias);
|
|
25203
|
+
}
|
|
25204
|
+
resolveReusable(parentSessionID, taskIDOrAlias, agent) {
|
|
25205
|
+
return this.board.resolveReusable(parentSessionID, taskIDOrAlias, agent);
|
|
25206
|
+
}
|
|
25207
|
+
resolveRecoverable(parentSessionID, taskIDOrAlias, agent) {
|
|
25208
|
+
return this.board.resolveRecoverable(parentSessionID, taskIDOrAlias, agent);
|
|
25209
|
+
}
|
|
25210
|
+
markUsed(parentSessionID, key, now = Date.now()) {
|
|
25211
|
+
this.board.markUsed(parentSessionID, key, now);
|
|
25212
|
+
}
|
|
25213
|
+
taskIDs() {
|
|
25214
|
+
return this.board.taskIDs();
|
|
25215
|
+
}
|
|
25216
|
+
addContext(taskID, files) {
|
|
25217
|
+
this.board.addContext(taskID, files);
|
|
25218
|
+
}
|
|
25219
|
+
list(parentSessionID) {
|
|
25220
|
+
return this.board.list(parentSessionID);
|
|
25221
|
+
}
|
|
25222
|
+
hasRunning(parentSessionID) {
|
|
25223
|
+
return this.board.hasRunning(parentSessionID);
|
|
25224
|
+
}
|
|
25225
|
+
hasTerminalUnreconciled(parentSessionID) {
|
|
25226
|
+
return this.board.hasTerminalUnreconciled(parentSessionID);
|
|
25227
|
+
}
|
|
25228
|
+
hasConvergenceSignals(taskID, threshold = 3) {
|
|
25229
|
+
return this.board.hasConvergenceSignals(taskID, threshold);
|
|
25230
|
+
}
|
|
25231
|
+
formatForPrompt(parentSessionID, now = Date.now()) {
|
|
25232
|
+
return this.board.formatForPrompt(parentSessionID, now);
|
|
25233
|
+
}
|
|
25234
|
+
clearParent(parentSessionID) {
|
|
25235
|
+
this.board.clearParent(parentSessionID);
|
|
25236
|
+
}
|
|
25237
|
+
drop(taskID) {
|
|
25238
|
+
this.board.drop(taskID);
|
|
25239
|
+
}
|
|
25240
|
+
}
|
|
24934
25241
|
// src/utils/guards.ts
|
|
24935
25242
|
function isRecord(value) {
|
|
24936
25243
|
return typeof value === "object" && value !== null;
|
|
@@ -24938,21 +25245,24 @@ function isRecord(value) {
|
|
|
24938
25245
|
|
|
24939
25246
|
// src/utils/internal-initiator.ts
|
|
24940
25247
|
var SLIM_INTERNAL_INITIATOR_MARKER = "<!-- SLIM_INTERNAL_INITIATOR -->";
|
|
25248
|
+
var INTERNAL_INITIATOR_METADATA_KEY = "oh-my-opencode-slim.internalInitiator";
|
|
24941
25249
|
function createInternalAgentTextPart(text) {
|
|
24942
25250
|
return {
|
|
24943
25251
|
type: "text",
|
|
25252
|
+
synthetic: true,
|
|
24944
25253
|
text: `${text}
|
|
24945
|
-
${SLIM_INTERNAL_INITIATOR_MARKER}
|
|
25254
|
+
${SLIM_INTERNAL_INITIATOR_MARKER}`,
|
|
25255
|
+
metadata: { [INTERNAL_INITIATOR_METADATA_KEY]: true }
|
|
24946
25256
|
};
|
|
24947
25257
|
}
|
|
24948
|
-
function
|
|
25258
|
+
function isInternalInitiatorPart(part) {
|
|
24949
25259
|
if (!isRecord(part) || part.type !== "text") {
|
|
24950
25260
|
return false;
|
|
24951
25261
|
}
|
|
24952
|
-
if (
|
|
25262
|
+
if (part.synthetic !== true || !isRecord(part.metadata)) {
|
|
24953
25263
|
return false;
|
|
24954
25264
|
}
|
|
24955
|
-
return part.
|
|
25265
|
+
return part.metadata[INTERNAL_INITIATOR_METADATA_KEY] === true;
|
|
24956
25266
|
}
|
|
24957
25267
|
|
|
24958
25268
|
// src/utils/index.ts
|
|
@@ -24977,7 +25287,7 @@ async function hasInternalMarker(client, sessionID, messageID) {
|
|
|
24977
25287
|
const response = await client.session.message({
|
|
24978
25288
|
path: { id: sessionID, messageID }
|
|
24979
25289
|
});
|
|
24980
|
-
const hasMarker = (response.data?.parts ?? []).some(
|
|
25290
|
+
const hasMarker = (response.data?.parts ?? []).some(isInternalInitiatorPart);
|
|
24981
25291
|
if (hasMarker) {
|
|
24982
25292
|
if (internalMarkerCache.size >= INTERNAL_MARKER_CACHE_LIMIT) {
|
|
24983
25293
|
internalMarkerCache.clear();
|
|
@@ -25293,16 +25603,35 @@ class ForegroundFallbackManager {
|
|
|
25293
25603
|
client;
|
|
25294
25604
|
chains;
|
|
25295
25605
|
enabled;
|
|
25606
|
+
maxRetries;
|
|
25607
|
+
runtimeOverride;
|
|
25296
25608
|
sessionModel = new Map;
|
|
25297
25609
|
sessionAgent = new Map;
|
|
25298
25610
|
sessionTried = new Map;
|
|
25299
25611
|
inProgress = new Set;
|
|
25300
25612
|
lastTrigger = new Map;
|
|
25301
25613
|
lastTriggerModel = new Map;
|
|
25302
|
-
|
|
25614
|
+
sessionRetries = new Map;
|
|
25615
|
+
isFallbackInProgress(sessionID) {
|
|
25616
|
+
return this.inProgress.has(sessionID);
|
|
25617
|
+
}
|
|
25618
|
+
constructor(client, chains, enabled, maxRetries = 3, coordinator, runtimeOverride = true) {
|
|
25303
25619
|
this.client = client;
|
|
25304
25620
|
this.chains = chains;
|
|
25305
25621
|
this.enabled = enabled;
|
|
25622
|
+
this.maxRetries = maxRetries;
|
|
25623
|
+
this.runtimeOverride = runtimeOverride;
|
|
25624
|
+
if (coordinator) {
|
|
25625
|
+
coordinator.onSessionDeleted((id) => {
|
|
25626
|
+
this.sessionModel.delete(id);
|
|
25627
|
+
this.sessionAgent.delete(id);
|
|
25628
|
+
this.sessionTried.delete(id);
|
|
25629
|
+
this.inProgress.delete(id);
|
|
25630
|
+
this.lastTrigger.delete(id);
|
|
25631
|
+
this.lastTriggerModel.delete(id);
|
|
25632
|
+
this.sessionRetries.delete(id);
|
|
25633
|
+
});
|
|
25634
|
+
}
|
|
25306
25635
|
}
|
|
25307
25636
|
async handleEvent(rawEvent) {
|
|
25308
25637
|
if (!this.enabled)
|
|
@@ -25325,13 +25654,17 @@ class ForegroundFallbackManager {
|
|
|
25325
25654
|
this.sessionModel.set(sessionID, `${info.providerID}/${info.modelID}`);
|
|
25326
25655
|
}
|
|
25327
25656
|
if (info.error && isRateLimitError(info.error)) {
|
|
25328
|
-
|
|
25657
|
+
if (this.shouldIntervene(sessionID)) {
|
|
25658
|
+
await this.tryFallback(sessionID);
|
|
25659
|
+
}
|
|
25660
|
+
} else {
|
|
25661
|
+
this.sessionRetries.delete(sessionID);
|
|
25329
25662
|
}
|
|
25330
25663
|
break;
|
|
25331
25664
|
}
|
|
25332
25665
|
case "session.error": {
|
|
25333
25666
|
const props = event.properties;
|
|
25334
|
-
if (props?.sessionID && props.error && isRateLimitError(props.error)) {
|
|
25667
|
+
if (props?.sessionID && props.error && isRateLimitError(props.error) && this.shouldIntervene(props.sessionID)) {
|
|
25335
25668
|
await this.tryFallback(props.sessionID);
|
|
25336
25669
|
}
|
|
25337
25670
|
break;
|
|
@@ -25342,7 +25675,11 @@ class ForegroundFallbackManager {
|
|
|
25342
25675
|
break;
|
|
25343
25676
|
const msg = props.status.message.toLowerCase();
|
|
25344
25677
|
if (msg.includes("rate limit") || msg.includes("usage limit") || msg.includes("usage exceeded") || msg.includes("quota exceeded") || msg.includes("exceededbudget") || msg.includes("over budget") || msg.includes("insufficient") || msg.includes("high concurrency") || msg.includes("reduce concurrency")) {
|
|
25345
|
-
|
|
25678
|
+
if (this.checkRetryBudget(props.sessionID)) {
|
|
25679
|
+
await this.tryFallback(props.sessionID);
|
|
25680
|
+
}
|
|
25681
|
+
} else {
|
|
25682
|
+
this.sessionRetries.delete(props.sessionID);
|
|
25346
25683
|
}
|
|
25347
25684
|
break;
|
|
25348
25685
|
}
|
|
@@ -25355,19 +25692,36 @@ class ForegroundFallbackManager {
|
|
|
25355
25692
|
}
|
|
25356
25693
|
case "session.deleted": {
|
|
25357
25694
|
const props = event.properties;
|
|
25358
|
-
const id = props?.info?.id
|
|
25695
|
+
const id = props?.info?.id || props?.sessionID;
|
|
25359
25696
|
if (id) {
|
|
25360
|
-
|
|
25361
|
-
|
|
25362
|
-
|
|
25363
|
-
this.inProgress.delete(id);
|
|
25364
|
-
this.lastTrigger.delete(id);
|
|
25365
|
-
this.lastTriggerModel.delete(id);
|
|
25697
|
+
log("[foreground-fallback] session.deleted observed", {
|
|
25698
|
+
sessionID: id
|
|
25699
|
+
});
|
|
25366
25700
|
}
|
|
25367
25701
|
break;
|
|
25368
25702
|
}
|
|
25369
25703
|
}
|
|
25370
25704
|
}
|
|
25705
|
+
checkRetryBudget(sessionID) {
|
|
25706
|
+
const tried = this.sessionRetries.get(sessionID) ?? 0;
|
|
25707
|
+
if (tried < this.maxRetries - 1) {
|
|
25708
|
+
this.sessionRetries.set(sessionID, tried + 1);
|
|
25709
|
+
log("[foreground-fallback] rate-limit retry", {
|
|
25710
|
+
sessionID,
|
|
25711
|
+
attempt: tried + 1,
|
|
25712
|
+
remaining: this.maxRetries - tried - 1
|
|
25713
|
+
});
|
|
25714
|
+
return false;
|
|
25715
|
+
}
|
|
25716
|
+
this.sessionRetries.delete(sessionID);
|
|
25717
|
+
return true;
|
|
25718
|
+
}
|
|
25719
|
+
shouldIntervene(sessionID) {
|
|
25720
|
+
const tried = this.sessionRetries.get(sessionID) ?? 0;
|
|
25721
|
+
if (tried === 0)
|
|
25722
|
+
return true;
|
|
25723
|
+
return this.checkRetryBudget(sessionID);
|
|
25724
|
+
}
|
|
25371
25725
|
async tryFallback(sessionID) {
|
|
25372
25726
|
if (!sessionID)
|
|
25373
25727
|
return;
|
|
@@ -25397,6 +25751,16 @@ class ForegroundFallbackManager {
|
|
|
25397
25751
|
if (!currentModel && agentName && chain.length > 0) {
|
|
25398
25752
|
currentModel = chain[0];
|
|
25399
25753
|
}
|
|
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
|
+
}
|
|
25400
25764
|
if (!this.sessionTried.has(sessionID)) {
|
|
25401
25765
|
this.sessionTried.set(sessionID, new Set);
|
|
25402
25766
|
}
|
|
@@ -25423,15 +25787,17 @@ class ForegroundFallbackManager {
|
|
|
25423
25787
|
this.sessionTried.set(sessionID, tried);
|
|
25424
25788
|
nextModel = stickyFallback;
|
|
25425
25789
|
} else {
|
|
25426
|
-
log("[foreground-fallback] fallback chain exhausted", {
|
|
25790
|
+
log("[foreground-fallback] fallback chain exhausted, aborting", {
|
|
25427
25791
|
sessionID,
|
|
25428
25792
|
agentName,
|
|
25429
25793
|
tried: [...tried]
|
|
25430
25794
|
});
|
|
25795
|
+
await abortSessionWithTimeout(this.client, sessionID);
|
|
25431
25796
|
return;
|
|
25432
25797
|
}
|
|
25433
25798
|
}
|
|
25434
25799
|
tried.add(nextModel);
|
|
25800
|
+
this.sessionRetries.delete(sessionID);
|
|
25435
25801
|
const ref = parseModelReference(nextModel);
|
|
25436
25802
|
if (!ref) {
|
|
25437
25803
|
log("[foreground-fallback] invalid model format", {
|
|
@@ -25455,18 +25821,21 @@ class ForegroundFallbackManager {
|
|
|
25455
25821
|
return;
|
|
25456
25822
|
}
|
|
25457
25823
|
try {
|
|
25824
|
+
await sessionClient.promptAsync({
|
|
25825
|
+
path: { id: sessionID },
|
|
25826
|
+
body: { parts: lastUser.parts, model: ref }
|
|
25827
|
+
});
|
|
25828
|
+
} catch (_promptErr) {
|
|
25829
|
+
log("[foreground-fallback] promptAsync on busy session, aborting", {
|
|
25830
|
+
sessionID
|
|
25831
|
+
});
|
|
25458
25832
|
await abortSessionWithTimeout(this.client, sessionID);
|
|
25459
|
-
|
|
25460
|
-
|
|
25461
|
-
sessionID,
|
|
25462
|
-
|
|
25833
|
+
await new Promise((r) => setTimeout(r, REPROMPT_DELAY_MS));
|
|
25834
|
+
await sessionClient.promptAsync({
|
|
25835
|
+
path: { id: sessionID },
|
|
25836
|
+
body: { parts: lastUser.parts, model: ref }
|
|
25463
25837
|
});
|
|
25464
25838
|
}
|
|
25465
|
-
await new Promise((r) => setTimeout(r, REPROMPT_DELAY_MS));
|
|
25466
|
-
await sessionClient.promptAsync({
|
|
25467
|
-
path: { id: sessionID },
|
|
25468
|
-
body: { parts: lastUser.parts, model: ref }
|
|
25469
|
-
});
|
|
25470
25839
|
this.sessionModel.set(sessionID, nextModel);
|
|
25471
25840
|
log("[foreground-fallback] switched to fallback model", {
|
|
25472
25841
|
sessionID,
|
|
@@ -25517,7 +25886,7 @@ import {
|
|
|
25517
25886
|
mkdirSync as mkdirSync5,
|
|
25518
25887
|
readdirSync as readdirSync3,
|
|
25519
25888
|
rmdirSync,
|
|
25520
|
-
statSync as
|
|
25889
|
+
statSync as statSync5,
|
|
25521
25890
|
unlinkSync as unlinkSync3,
|
|
25522
25891
|
writeFileSync as writeFileSync6
|
|
25523
25892
|
} from "node:fs";
|
|
@@ -25574,7 +25943,7 @@ function cleanupAllSessions(saveDir) {
|
|
|
25574
25943
|
dirsToScan.push(fp);
|
|
25575
25944
|
} else {
|
|
25576
25945
|
try {
|
|
25577
|
-
if (now -
|
|
25946
|
+
if (now - statSync5(fp).mtimeMs > maxAge)
|
|
25578
25947
|
unlinkSync3(fp);
|
|
25579
25948
|
} catch {}
|
|
25580
25949
|
}
|
|
@@ -25588,7 +25957,7 @@ function cleanupAllSessions(saveDir) {
|
|
|
25588
25957
|
isEmpty = false;
|
|
25589
25958
|
const fp = join11(dir, f);
|
|
25590
25959
|
try {
|
|
25591
|
-
if (now -
|
|
25960
|
+
if (now - statSync5(fp).mtimeMs > maxAge) {
|
|
25592
25961
|
unlinkSync3(fp);
|
|
25593
25962
|
} else {
|
|
25594
25963
|
allRemoved = false;
|
|
@@ -25816,7 +26185,8 @@ function createLoopCommandHook() {
|
|
|
25816
26185
|
};
|
|
25817
26186
|
}
|
|
25818
26187
|
// src/hooks/phase-reminder/index.ts
|
|
25819
|
-
|
|
26188
|
+
var PHASE_REMINDER_METADATA_KEY = "oh-my-opencode-slim.phaseReminder";
|
|
26189
|
+
function createPhaseReminderHook(coordinator) {
|
|
25820
26190
|
return {
|
|
25821
26191
|
"experimental.chat.messages.transform": async (_input, output) => {
|
|
25822
26192
|
const messages = Array.isArray(output.messages) ? output.messages : [];
|
|
@@ -25841,20 +26211,26 @@ function createPhaseReminderHook() {
|
|
|
25841
26211
|
if (agent && agent !== "orchestrator") {
|
|
25842
26212
|
return;
|
|
25843
26213
|
}
|
|
26214
|
+
const sessionId = lastUserMessage?.info?.sessionID;
|
|
26215
|
+
if (sessionId && coordinator?.hasPendingSession(sessionId)) {
|
|
26216
|
+
return;
|
|
26217
|
+
}
|
|
25844
26218
|
const textPartIndex = lastUserMessage.parts.findIndex((p) => p.type === "text" && p.text !== undefined);
|
|
25845
26219
|
if (textPartIndex === -1) {
|
|
25846
26220
|
return;
|
|
25847
26221
|
}
|
|
25848
|
-
const
|
|
25849
|
-
if (
|
|
26222
|
+
const originalPart = lastUserMessage.parts[textPartIndex];
|
|
26223
|
+
if (isInternalInitiatorPart(originalPart)) {
|
|
25850
26224
|
return;
|
|
25851
26225
|
}
|
|
25852
|
-
if (lastUserMessage.parts.some((
|
|
26226
|
+
if (lastUserMessage.parts.some((part) => part.synthetic === true && isRecord(part.metadata) && part.metadata[PHASE_REMINDER_METADATA_KEY] === true)) {
|
|
25853
26227
|
return;
|
|
25854
26228
|
}
|
|
25855
26229
|
lastUserMessage.parts.push({
|
|
25856
26230
|
type: "text",
|
|
25857
|
-
|
|
26231
|
+
synthetic: true,
|
|
26232
|
+
text: PHASE_REMINDER,
|
|
26233
|
+
metadata: { [PHASE_REMINDER_METADATA_KEY]: true }
|
|
25858
26234
|
});
|
|
25859
26235
|
}
|
|
25860
26236
|
};
|
|
@@ -25862,26 +26238,24 @@ function createPhaseReminderHook() {
|
|
|
25862
26238
|
// src/hooks/post-file-tool-nudge/index.ts
|
|
25863
26239
|
var FILE_TOOLS = new Set(["Read", "read", "Write", "write"]);
|
|
25864
26240
|
function createPostFileToolNudgeHook(options = {}) {
|
|
25865
|
-
|
|
25866
|
-
|
|
25867
|
-
|
|
25868
|
-
}
|
|
25869
|
-
if (output.output.includes(PHASE_REMINDER)) {
|
|
25870
|
-
return;
|
|
25871
|
-
}
|
|
25872
|
-
output.output = `${output.output}
|
|
25873
|
-
|
|
25874
|
-
${PHASE_REMINDER}`;
|
|
26241
|
+
const { coordinator } = options;
|
|
26242
|
+
if (coordinator) {
|
|
26243
|
+
coordinator.onSessionDeleted((sid) => coordinator.clearSession(sid));
|
|
25875
26244
|
}
|
|
25876
26245
|
return {
|
|
25877
|
-
"tool.execute.after": async (input,
|
|
25878
|
-
if (!FILE_TOOLS.has(input.tool) || !input.sessionID)
|
|
26246
|
+
"tool.execute.after": async (input, _output) => {
|
|
26247
|
+
if (!FILE_TOOLS.has(input.tool) || !input.sessionID)
|
|
26248
|
+
return;
|
|
26249
|
+
coordinator?.markPending(input.sessionID);
|
|
26250
|
+
},
|
|
26251
|
+
"experimental.chat.system.transform": async (input, output) => {
|
|
26252
|
+
if (!input.sessionID || !coordinator?.consumePending(input.sessionID)) {
|
|
25879
26253
|
return;
|
|
25880
26254
|
}
|
|
25881
26255
|
if (options.shouldInject && !options.shouldInject(input.sessionID)) {
|
|
25882
26256
|
return;
|
|
25883
26257
|
}
|
|
25884
|
-
|
|
26258
|
+
output.system.push(PHASE_REMINDER);
|
|
25885
26259
|
}
|
|
25886
26260
|
};
|
|
25887
26261
|
}
|
|
@@ -25952,6 +26326,44 @@ function createReflectCommandHook() {
|
|
|
25952
26326
|
}
|
|
25953
26327
|
};
|
|
25954
26328
|
}
|
|
26329
|
+
// src/hooks/session-lifecycle.ts
|
|
26330
|
+
class SessionLifecycle {
|
|
26331
|
+
#cleanupCallbacks = [];
|
|
26332
|
+
#pendingSessionIds = new Set;
|
|
26333
|
+
#everPendingSessionIds = new Set;
|
|
26334
|
+
#log;
|
|
26335
|
+
constructor(log2) {
|
|
26336
|
+
this.#log = log2;
|
|
26337
|
+
}
|
|
26338
|
+
onSessionDeleted(callback) {
|
|
26339
|
+
this.#cleanupCallbacks.push(callback);
|
|
26340
|
+
}
|
|
26341
|
+
dispatchSessionDeleted(sessionId) {
|
|
26342
|
+
for (const cb of this.#cleanupCallbacks) {
|
|
26343
|
+
try {
|
|
26344
|
+
cb(sessionId);
|
|
26345
|
+
} catch (error) {
|
|
26346
|
+
this.#log(`[session-lifecycle] cleanup callback failed for session ${sessionId}`, { error });
|
|
26347
|
+
}
|
|
26348
|
+
}
|
|
26349
|
+
}
|
|
26350
|
+
markPending(sessionId) {
|
|
26351
|
+
this.#pendingSessionIds.add(sessionId);
|
|
26352
|
+
this.#everPendingSessionIds.add(sessionId);
|
|
26353
|
+
}
|
|
26354
|
+
consumePending(sessionId) {
|
|
26355
|
+
const had = this.#pendingSessionIds.has(sessionId);
|
|
26356
|
+
this.#pendingSessionIds.delete(sessionId);
|
|
26357
|
+
return had;
|
|
26358
|
+
}
|
|
26359
|
+
hasPendingSession(sessionId) {
|
|
26360
|
+
return this.#everPendingSessionIds.has(sessionId) && !this.#pendingSessionIds.has(sessionId);
|
|
26361
|
+
}
|
|
26362
|
+
clearSession(sessionId) {
|
|
26363
|
+
this.#pendingSessionIds.delete(sessionId);
|
|
26364
|
+
this.#everPendingSessionIds.delete(sessionId);
|
|
26365
|
+
}
|
|
26366
|
+
}
|
|
25955
26367
|
// src/hooks/task-session-manager/pending-call-tracker.ts
|
|
25956
26368
|
var MAX_PENDING_TASK_CALLS = 100;
|
|
25957
26369
|
function createPendingCallTracker() {
|
|
@@ -26073,11 +26485,13 @@ function extractReadFiles(root, output) {
|
|
|
26073
26485
|
}
|
|
26074
26486
|
|
|
26075
26487
|
// src/hooks/task-session-manager/index.ts
|
|
26076
|
-
var
|
|
26488
|
+
var BACKGROUND_JOB_BOARD_METADATA_KEY = "oh-my-opencode-slim.backgroundJobBoard";
|
|
26077
26489
|
var BACKGROUND_COMPLETION_COMPLETED = /^Background task completed: /;
|
|
26078
26490
|
var BACKGROUND_COMPLETION_FAILED = /^Background task failed: /;
|
|
26079
26491
|
var MAX_PROCESSED_INJECTED_COMPLETIONS = 500;
|
|
26080
26492
|
var RAW_SESSION_ID_PATTERN = /^ses_[A-Za-z0-9_-]+$/;
|
|
26493
|
+
var IDLE_RECONCILE_DELAY_MS = 2000;
|
|
26494
|
+
var idleReconcileTimers = new Map;
|
|
26081
26495
|
function djb2Hash(str) {
|
|
26082
26496
|
let hash = 5381;
|
|
26083
26497
|
for (let i = 0;i < str.length; i++) {
|
|
@@ -26118,6 +26532,16 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
26118
26532
|
const processedInjectedCompletions = new Set;
|
|
26119
26533
|
const processedInjectedCompletionOrder = [];
|
|
26120
26534
|
const terminalJobsInjectedByParent = new Map;
|
|
26535
|
+
if (options.coordinator) {
|
|
26536
|
+
options.coordinator.onSessionDeleted((sessionId) => {
|
|
26537
|
+
backgroundJobBoard.drop(sessionId);
|
|
26538
|
+
backgroundJobBoard.clearParent(sessionId);
|
|
26539
|
+
terminalJobsInjectedByParent.delete(sessionId);
|
|
26540
|
+
taskContextTracker.clearSession(sessionId);
|
|
26541
|
+
taskContextTracker.prune(backgroundJobBoard);
|
|
26542
|
+
pendingCallTracker.clearSession(sessionId);
|
|
26543
|
+
});
|
|
26544
|
+
}
|
|
26121
26545
|
function updateBackgroundJobFromOutput(output) {
|
|
26122
26546
|
if (typeof output !== "string")
|
|
26123
26547
|
return;
|
|
@@ -26177,8 +26601,12 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
26177
26601
|
if (part.synthetic !== true)
|
|
26178
26602
|
return;
|
|
26179
26603
|
const status = parseTaskStatusOutput(part.text);
|
|
26180
|
-
if (!status)
|
|
26604
|
+
if (!status) {
|
|
26605
|
+
log("[task-session-manager] synthetic part missing task status", {
|
|
26606
|
+
textPreview: part.text.slice(0, 120)
|
|
26607
|
+
});
|
|
26181
26608
|
return;
|
|
26609
|
+
}
|
|
26182
26610
|
if (status.state !== "completed" && status.state !== "error") {
|
|
26183
26611
|
return;
|
|
26184
26612
|
}
|
|
@@ -26267,8 +26695,15 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
26267
26695
|
const toolName = input.tool.toLowerCase();
|
|
26268
26696
|
if (toolName !== "task")
|
|
26269
26697
|
return;
|
|
26270
|
-
if (!input.sessionID
|
|
26698
|
+
if (!input.sessionID)
|
|
26271
26699
|
return;
|
|
26700
|
+
if (!options.shouldManageSession(input.sessionID)) {
|
|
26701
|
+
options.registerSessionAsOrchestrator?.(input.sessionID);
|
|
26702
|
+
if (!options.shouldManageSession(input.sessionID))
|
|
26703
|
+
return;
|
|
26704
|
+
log("[task-session-manager] recovered stale orchestrator mapping", {
|
|
26705
|
+
sessionID: input.sessionID
|
|
26706
|
+
});
|
|
26272
26707
|
}
|
|
26273
26708
|
if (!isRecord(output.args))
|
|
26274
26709
|
return;
|
|
@@ -26292,6 +26727,14 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
26292
26727
|
label
|
|
26293
26728
|
};
|
|
26294
26729
|
pendingCallTracker.add(pendingCall);
|
|
26730
|
+
log("[task-session-manager] tool.execute.before task — pending call created", {
|
|
26731
|
+
callId: pendingCall.callId,
|
|
26732
|
+
parentSessionId: pendingCall.parentSessionId,
|
|
26733
|
+
agentType: pendingCall.agentType,
|
|
26734
|
+
label: pendingCall.label,
|
|
26735
|
+
inputCallID: input.callID,
|
|
26736
|
+
inputSessionID: input.sessionID
|
|
26737
|
+
});
|
|
26295
26738
|
if (typeof args.task_id !== "string" || args.task_id.trim() === "") {
|
|
26296
26739
|
return;
|
|
26297
26740
|
}
|
|
@@ -26330,6 +26773,13 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
26330
26773
|
if (input.tool.toLowerCase() !== "task")
|
|
26331
26774
|
return;
|
|
26332
26775
|
const pending = pendingCallTracker.take(input.callID, input.sessionID);
|
|
26776
|
+
log("[task-session-manager] tool.execute.after task", {
|
|
26777
|
+
callID: input.callID,
|
|
26778
|
+
sessionID: input.sessionID,
|
|
26779
|
+
hasPending: !!pending,
|
|
26780
|
+
outputType: typeof output.output,
|
|
26781
|
+
outputPreview: typeof output.output === "string" ? output.output.slice(0, 120) : undefined
|
|
26782
|
+
});
|
|
26333
26783
|
if (!pending || typeof output.output !== "string")
|
|
26334
26784
|
return;
|
|
26335
26785
|
const launch = parseTaskLaunchOutput(output.output);
|
|
@@ -26408,7 +26858,13 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
26408
26858
|
continue;
|
|
26409
26859
|
}
|
|
26410
26860
|
if (!message.info.sessionID || !options.shouldManageSession(message.info.sessionID)) {
|
|
26411
|
-
|
|
26861
|
+
const sessionID = message.info.sessionID;
|
|
26862
|
+
if (!sessionID || message.info.agent !== "orchestrator") {
|
|
26863
|
+
continue;
|
|
26864
|
+
}
|
|
26865
|
+
options.registerSessionAsOrchestrator?.(sessionID);
|
|
26866
|
+
if (!options.shouldManageSession(sessionID))
|
|
26867
|
+
continue;
|
|
26412
26868
|
}
|
|
26413
26869
|
for (const [partIndex, part] of message.parts.entries()) {
|
|
26414
26870
|
updateFromInjectedCompletion(part, message, messageIndex, partIndex);
|
|
@@ -26431,15 +26887,22 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
26431
26887
|
const textPart = message.parts.find((part) => part.type === "text" && typeof part.text === "string");
|
|
26432
26888
|
if (!textPart)
|
|
26433
26889
|
return;
|
|
26434
|
-
if (textPart
|
|
26890
|
+
if (isInternalInitiatorPart(textPart)) {
|
|
26435
26891
|
return;
|
|
26436
|
-
|
|
26892
|
+
}
|
|
26893
|
+
if (message.parts.some((part) => part.synthetic === true && isRecord(part.metadata) && part.metadata[BACKGROUND_JOB_BOARD_METADATA_KEY] === true)) {
|
|
26437
26894
|
return;
|
|
26895
|
+
}
|
|
26438
26896
|
rememberInjectedTerminalJobs(message.info.sessionID);
|
|
26439
|
-
|
|
26897
|
+
const boardPart = {
|
|
26898
|
+
type: "text",
|
|
26899
|
+
synthetic: true,
|
|
26900
|
+
text: reminders.join(`
|
|
26440
26901
|
|
|
26441
|
-
`)
|
|
26442
|
-
|
|
26902
|
+
`),
|
|
26903
|
+
metadata: { [BACKGROUND_JOB_BOARD_METADATA_KEY]: true }
|
|
26904
|
+
};
|
|
26905
|
+
message.parts.unshift(boardPart);
|
|
26443
26906
|
return;
|
|
26444
26907
|
}
|
|
26445
26908
|
},
|
|
@@ -26457,19 +26920,48 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
26457
26920
|
return;
|
|
26458
26921
|
}
|
|
26459
26922
|
if (input.event.type === "session.idle" || input.event.type === "session.status" && input.event.properties?.status?.type === "idle") {
|
|
26460
|
-
const sessionId2 = input.event.properties?.info?.id
|
|
26923
|
+
const sessionId2 = input.event.properties?.info?.id || input.event.properties?.sessionID;
|
|
26924
|
+
const job = sessionId2 ? backgroundJobBoard.get(sessionId2) : undefined;
|
|
26461
26925
|
log("[task-session-manager] idle/status idle observed", {
|
|
26462
26926
|
sessionID: sessionId2,
|
|
26463
26927
|
managesSession: sessionId2 ? options.shouldManageSession(sessionId2) : false,
|
|
26464
|
-
terminalJobsPending: sessionId2 ? terminalJobsInjectedByParent.get(sessionId2)?.size ?? 0 : 0
|
|
26928
|
+
terminalJobsPending: sessionId2 ? terminalJobsInjectedByParent.get(sessionId2)?.size ?? 0 : 0,
|
|
26929
|
+
runningJobForSession: job?.state === "running" || false
|
|
26465
26930
|
});
|
|
26466
26931
|
if (sessionId2 && options.shouldManageSession(sessionId2)) {
|
|
26467
|
-
|
|
26932
|
+
const timer2 = setTimeout(() => {
|
|
26933
|
+
idleReconcileTimers.delete(sessionId2);
|
|
26934
|
+
reconcileInjectedTerminalJobs(sessionId2);
|
|
26935
|
+
}, IDLE_RECONCILE_DELAY_MS).unref?.();
|
|
26936
|
+
idleReconcileTimers.set(sessionId2, timer2);
|
|
26937
|
+
}
|
|
26938
|
+
if (job && sessionId2 && job.state === "running" && !options.isFallbackInProgress?.(sessionId2)) {
|
|
26939
|
+
log("[task-session-manager] reconciled running job from idle", {
|
|
26940
|
+
sessionID: sessionId2,
|
|
26941
|
+
alias: job.alias,
|
|
26942
|
+
parentSessionID: job.parentSessionID
|
|
26943
|
+
});
|
|
26944
|
+
backgroundJobBoard.updateStatus({
|
|
26945
|
+
taskID: sessionId2,
|
|
26946
|
+
state: "completed",
|
|
26947
|
+
resultSummary: "Background task completed (reconciled from idle event)"
|
|
26948
|
+
});
|
|
26949
|
+
backgroundJobBoard.markReconciled(sessionId2);
|
|
26950
|
+
taskContextTracker.pendingManagedTaskIds.delete(sessionId2);
|
|
26951
|
+
backgroundJobBoard.addContext(sessionId2, taskContextTracker.contextFilesForPrompt(sessionId2));
|
|
26952
|
+
taskContextTracker.prune(backgroundJobBoard);
|
|
26468
26953
|
}
|
|
26469
26954
|
return;
|
|
26470
26955
|
}
|
|
26471
26956
|
if (input.event.type === "session.error") {
|
|
26472
|
-
const sessionId2 = input.event.properties?.info?.id
|
|
26957
|
+
const sessionId2 = input.event.properties?.info?.id || input.event.properties?.sessionID;
|
|
26958
|
+
if (sessionId2) {
|
|
26959
|
+
const timer2 = idleReconcileTimers.get(sessionId2);
|
|
26960
|
+
if (timer2) {
|
|
26961
|
+
clearTimeout(timer2);
|
|
26962
|
+
idleReconcileTimers.delete(sessionId2);
|
|
26963
|
+
}
|
|
26964
|
+
}
|
|
26473
26965
|
if (sessionId2 && options.shouldManageSession(sessionId2)) {
|
|
26474
26966
|
const props = input.event.properties;
|
|
26475
26967
|
if (!props?.error || !isRateLimitError(props.error)) {
|
|
@@ -26479,7 +26971,14 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
26479
26971
|
return;
|
|
26480
26972
|
}
|
|
26481
26973
|
if (input.event.type === "session.status" && input.event.properties?.status?.type === "busy") {
|
|
26482
|
-
const sessionId2 = input.event.properties?.info?.id
|
|
26974
|
+
const sessionId2 = input.event.properties?.info?.id || input.event.properties?.sessionID;
|
|
26975
|
+
if (sessionId2) {
|
|
26976
|
+
const timer2 = idleReconcileTimers.get(sessionId2);
|
|
26977
|
+
if (timer2) {
|
|
26978
|
+
clearTimeout(timer2);
|
|
26979
|
+
idleReconcileTimers.delete(sessionId2);
|
|
26980
|
+
}
|
|
26981
|
+
}
|
|
26483
26982
|
const before = sessionId2 ? backgroundJobBoard.get(sessionId2) : undefined;
|
|
26484
26983
|
const updated = sessionId2 ? backgroundJobBoard.markRunningFromLiveSession(sessionId2) : undefined;
|
|
26485
26984
|
if (before?.cancellationRequested) {
|
|
@@ -26506,28 +27005,17 @@ function createTaskSessionManagerHook(_ctx, options) {
|
|
|
26506
27005
|
}
|
|
26507
27006
|
if (input.event.type !== "session.deleted")
|
|
26508
27007
|
return;
|
|
26509
|
-
const sessionId = input.event.properties?.info?.id
|
|
27008
|
+
const sessionId = input.event.properties?.info?.id || input.event.properties?.sessionID;
|
|
26510
27009
|
if (!sessionId)
|
|
26511
27010
|
return;
|
|
26512
|
-
|
|
26513
|
-
|
|
26514
|
-
|
|
26515
|
-
|
|
26516
|
-
|
|
26517
|
-
|
|
26518
|
-
|
|
26519
|
-
alias: record.alias
|
|
26520
|
-
} : undefined;
|
|
26521
|
-
})(),
|
|
26522
|
-
childJobCount: backgroundJobBoard.list(sessionId).length,
|
|
26523
|
-
managesSession: options.shouldManageSession(sessionId)
|
|
27011
|
+
const timer = idleReconcileTimers.get(sessionId);
|
|
27012
|
+
if (timer) {
|
|
27013
|
+
clearTimeout(timer);
|
|
27014
|
+
idleReconcileTimers.delete(sessionId);
|
|
27015
|
+
}
|
|
27016
|
+
log("[task-session-manager] session.deleted observed", {
|
|
27017
|
+
sessionID: sessionId
|
|
26524
27018
|
});
|
|
26525
|
-
backgroundJobBoard.drop(sessionId);
|
|
26526
|
-
backgroundJobBoard.clearParent(sessionId);
|
|
26527
|
-
terminalJobsInjectedByParent.delete(sessionId);
|
|
26528
|
-
taskContextTracker.clearSession(sessionId);
|
|
26529
|
-
taskContextTracker.prune(backgroundJobBoard);
|
|
26530
|
-
pendingCallTracker.clearSession(sessionId);
|
|
26531
27019
|
}
|
|
26532
27020
|
};
|
|
26533
27021
|
function normalizeLateCancelledTaskOutput(output) {
|
|
@@ -30545,7 +31033,7 @@ function createInterviewService(ctx, config, deps) {
|
|
|
30545
31033
|
return loadMessages(sessionID);
|
|
30546
31034
|
}
|
|
30547
31035
|
function isUserVisibleMessage(message) {
|
|
30548
|
-
return !(message.parts ?? []).some((part) =>
|
|
31036
|
+
return !(message.parts ?? []).some((part) => isInternalInitiatorPart(part));
|
|
30549
31037
|
}
|
|
30550
31038
|
function getInterviewById(interviewId) {
|
|
30551
31039
|
return interviewsById.get(interviewId) ?? null;
|
|
@@ -31575,20 +32063,126 @@ function createBuiltinMcps(disabledMcps = [], websearchConfig) {
|
|
|
31575
32063
|
|
|
31576
32064
|
// src/multiplexer/herdr/index.ts
|
|
31577
32065
|
init_compat();
|
|
32066
|
+
|
|
32067
|
+
// src/multiplexer/shared.ts
|
|
32068
|
+
init_compat();
|
|
32069
|
+
function quoteShellArg(value) {
|
|
32070
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
32071
|
+
}
|
|
32072
|
+
function normalizePathForShell(directory) {
|
|
32073
|
+
return process.platform === "win32" ? directory.replace(/\\/g, "/") : directory;
|
|
32074
|
+
}
|
|
32075
|
+
function buildOpencodeAttachCommand(sessionId, serverUrl, directory) {
|
|
32076
|
+
const attachDir = normalizePathForShell(directory);
|
|
32077
|
+
return [
|
|
32078
|
+
"opencode",
|
|
32079
|
+
"attach",
|
|
32080
|
+
quoteShellArg(serverUrl),
|
|
32081
|
+
"--session",
|
|
32082
|
+
quoteShellArg(sessionId),
|
|
32083
|
+
"--dir",
|
|
32084
|
+
quoteShellArg(attachDir)
|
|
32085
|
+
].join(" ");
|
|
32086
|
+
}
|
|
32087
|
+
async function findBinary(binaryName, options = {}) {
|
|
32088
|
+
const isWindows = process.platform === "win32";
|
|
32089
|
+
const cmd = isWindows ? "where" : "which";
|
|
32090
|
+
const logPrefix = `[${binaryName}]`;
|
|
32091
|
+
try {
|
|
32092
|
+
const proc = crossSpawn([cmd, binaryName], {
|
|
32093
|
+
stdout: "pipe",
|
|
32094
|
+
stderr: "pipe"
|
|
32095
|
+
});
|
|
32096
|
+
const exitCode = await proc.exited;
|
|
32097
|
+
if (exitCode !== 0) {
|
|
32098
|
+
log(`${logPrefix} findBinary: '${cmd} ${binaryName}' failed`, {
|
|
32099
|
+
exitCode
|
|
32100
|
+
});
|
|
32101
|
+
return null;
|
|
32102
|
+
}
|
|
32103
|
+
const stdout = await proc.stdout();
|
|
32104
|
+
const path18 = stdout.trim().split(`
|
|
32105
|
+
`)[0];
|
|
32106
|
+
if (!path18) {
|
|
32107
|
+
log(`${logPrefix} findBinary: no path in output`);
|
|
32108
|
+
return null;
|
|
32109
|
+
}
|
|
32110
|
+
log(`${logPrefix} findBinary: found`, { path: path18 });
|
|
32111
|
+
if (options.verify) {
|
|
32112
|
+
try {
|
|
32113
|
+
const verifyProc = crossSpawn([path18, "-V"], {
|
|
32114
|
+
stdout: "pipe",
|
|
32115
|
+
stderr: "pipe"
|
|
32116
|
+
});
|
|
32117
|
+
const verifyExitCode = await verifyProc.exited;
|
|
32118
|
+
if (verifyExitCode !== 0) {
|
|
32119
|
+
log(`${logPrefix} findBinary: verification failed for ${path18}`);
|
|
32120
|
+
return null;
|
|
32121
|
+
}
|
|
32122
|
+
const verifyStdout = await verifyProc.stdout();
|
|
32123
|
+
log(`${logPrefix} findBinary: verified`, {
|
|
32124
|
+
version: verifyStdout.trim()
|
|
32125
|
+
});
|
|
32126
|
+
} catch (verifyErr) {
|
|
32127
|
+
log(`${logPrefix} findBinary: verification exception`, {
|
|
32128
|
+
error: String(verifyErr)
|
|
32129
|
+
});
|
|
32130
|
+
return null;
|
|
32131
|
+
}
|
|
32132
|
+
}
|
|
32133
|
+
return path18;
|
|
32134
|
+
} catch (err) {
|
|
32135
|
+
log(`${logPrefix} findBinary: exception`, { error: String(err) });
|
|
32136
|
+
return null;
|
|
32137
|
+
}
|
|
32138
|
+
}
|
|
32139
|
+
var GRACEFUL_SHUTDOWN_DELAY_MS = 250;
|
|
32140
|
+
async function gracefulClosePane(binary, paneId, options) {
|
|
32141
|
+
if (!binary)
|
|
32142
|
+
return false;
|
|
32143
|
+
const isEmpty = !paneId || paneId === "unknown";
|
|
32144
|
+
if (isEmpty)
|
|
32145
|
+
return options.emptyPaneReturnsTrue ?? false;
|
|
32146
|
+
try {
|
|
32147
|
+
const ctrlCProc = crossSpawn([binary, ...options.ctrlC], {
|
|
32148
|
+
stdout: "ignore",
|
|
32149
|
+
stderr: "ignore"
|
|
32150
|
+
});
|
|
32151
|
+
await ctrlCProc.exited;
|
|
32152
|
+
await new Promise((r) => setTimeout(r, GRACEFUL_SHUTDOWN_DELAY_MS));
|
|
32153
|
+
const proc = crossSpawn([binary, ...options.close], {
|
|
32154
|
+
stdout: "ignore",
|
|
32155
|
+
stderr: "ignore"
|
|
32156
|
+
});
|
|
32157
|
+
const exitCode = await proc.exited;
|
|
32158
|
+
if (exitCode === 0)
|
|
32159
|
+
return true;
|
|
32160
|
+
if (options.acceptExitCode1 && exitCode === 1)
|
|
32161
|
+
return true;
|
|
32162
|
+
return false;
|
|
32163
|
+
} catch {
|
|
32164
|
+
return false;
|
|
32165
|
+
}
|
|
32166
|
+
}
|
|
32167
|
+
|
|
32168
|
+
// src/multiplexer/herdr/index.ts
|
|
31578
32169
|
class HerdrMultiplexer {
|
|
31579
32170
|
type = "herdr";
|
|
31580
32171
|
binaryPath = null;
|
|
31581
32172
|
hasChecked = false;
|
|
31582
32173
|
parentPaneId = process.env.HERDR_PANE_ID;
|
|
32174
|
+
layout;
|
|
31583
32175
|
paneDirection;
|
|
32176
|
+
agentAreaPaneId = null;
|
|
31584
32177
|
constructor(layout = "main-vertical", mainPaneSize = 60) {
|
|
32178
|
+
this.layout = layout;
|
|
31585
32179
|
this.paneDirection = getPaneDirection(layout);
|
|
31586
32180
|
}
|
|
31587
32181
|
async isAvailable() {
|
|
31588
32182
|
if (this.hasChecked) {
|
|
31589
32183
|
return this.binaryPath !== null;
|
|
31590
32184
|
}
|
|
31591
|
-
this.binaryPath = await
|
|
32185
|
+
this.binaryPath = await findBinary("herdr");
|
|
31592
32186
|
this.hasChecked = true;
|
|
31593
32187
|
return this.binaryPath !== null;
|
|
31594
32188
|
}
|
|
@@ -31602,45 +32196,32 @@ class HerdrMultiplexer {
|
|
|
31602
32196
|
return { success: false };
|
|
31603
32197
|
}
|
|
31604
32198
|
try {
|
|
31605
|
-
const
|
|
31606
|
-
|
|
31607
|
-
|
|
31608
|
-
|
|
31609
|
-
|
|
31610
|
-
|
|
31611
|
-
|
|
31612
|
-
|
|
31613
|
-
|
|
31614
|
-
|
|
31615
|
-
|
|
31616
|
-
|
|
31617
|
-
|
|
31618
|
-
|
|
31619
|
-
|
|
31620
|
-
|
|
31621
|
-
|
|
31622
|
-
const splitStdout = await splitProc.stdout();
|
|
31623
|
-
const splitStderr = await splitProc.stderr();
|
|
31624
|
-
if (splitExitCode !== 0) {
|
|
31625
|
-
log("[herdr] spawnPane: split failed", {
|
|
31626
|
-
exitCode: splitExitCode,
|
|
31627
|
-
stderr: splitStderr.trim()
|
|
31628
|
-
});
|
|
31629
|
-
return { success: false };
|
|
32199
|
+
const attachDir = normalizePathForShell(directory);
|
|
32200
|
+
let paneId = null;
|
|
32201
|
+
let lastRawOutput = "";
|
|
32202
|
+
if (this.layout === "main-vertical" && this.agentAreaPaneId) {
|
|
32203
|
+
const result = await this.runSplit([this.agentAreaPaneId], "down", attachDir);
|
|
32204
|
+
paneId = result.paneId;
|
|
32205
|
+
if (!paneId) {
|
|
32206
|
+
log("[herdr] agent area split failed, falling back to parent", {
|
|
32207
|
+
agentAreaPaneId: this.agentAreaPaneId
|
|
32208
|
+
});
|
|
32209
|
+
this.agentAreaPaneId = null;
|
|
32210
|
+
}
|
|
32211
|
+
}
|
|
32212
|
+
if (!this.agentAreaPaneId) {
|
|
32213
|
+
const result = await this.runSplit(this.targetPaneArg(), this.paneDirection, attachDir);
|
|
32214
|
+
paneId = result.paneId;
|
|
32215
|
+
lastRawOutput = result.rawOutput;
|
|
31630
32216
|
}
|
|
31631
|
-
const paneId = parsePaneId(splitStdout);
|
|
31632
32217
|
if (!paneId) {
|
|
31633
32218
|
log("[herdr] spawnPane: could not parse pane_id from output", {
|
|
31634
|
-
stdout:
|
|
32219
|
+
stdout: lastRawOutput
|
|
31635
32220
|
});
|
|
31636
32221
|
return { success: false };
|
|
31637
32222
|
}
|
|
31638
32223
|
await crossSpawn([herdr, "pane", "rename", paneId, description.slice(0, 30)], { stdout: "ignore", stderr: "ignore" }).exited;
|
|
31639
|
-
const opencodeCmd = buildOpencodeAttachCommand(sessionId, serverUrl,
|
|
31640
|
-
log("[herdr] spawnPane: running attach command", {
|
|
31641
|
-
paneId,
|
|
31642
|
-
command: opencodeCmd
|
|
31643
|
-
});
|
|
32224
|
+
const opencodeCmd = buildOpencodeAttachCommand(sessionId, serverUrl, attachDir);
|
|
31644
32225
|
const runProc = crossSpawn([herdr, "pane", "run", paneId, opencodeCmd], {
|
|
31645
32226
|
stdout: "pipe",
|
|
31646
32227
|
stderr: "pipe"
|
|
@@ -31652,8 +32233,19 @@ class HerdrMultiplexer {
|
|
|
31652
32233
|
exitCode: runExitCode,
|
|
31653
32234
|
stderr: runStderr.trim()
|
|
31654
32235
|
});
|
|
32236
|
+
try {
|
|
32237
|
+
await this.closePane(paneId);
|
|
32238
|
+
} catch (closeErr) {
|
|
32239
|
+
log("[herdr] spawnPane: failed to close orphaned pane", {
|
|
32240
|
+
paneId,
|
|
32241
|
+
error: String(closeErr)
|
|
32242
|
+
});
|
|
32243
|
+
}
|
|
31655
32244
|
return { success: false };
|
|
31656
32245
|
}
|
|
32246
|
+
if (this.layout === "main-vertical" && !this.agentAreaPaneId) {
|
|
32247
|
+
this.agentAreaPaneId = paneId;
|
|
32248
|
+
}
|
|
31657
32249
|
log("[herdr] spawnPane: SUCCESS", { paneId });
|
|
31658
32250
|
return { success: true, paneId };
|
|
31659
32251
|
} catch (err) {
|
|
@@ -31662,41 +32254,55 @@ class HerdrMultiplexer {
|
|
|
31662
32254
|
}
|
|
31663
32255
|
}
|
|
31664
32256
|
async closePane(paneId) {
|
|
31665
|
-
if (!paneId || paneId === "unknown")
|
|
31666
|
-
return true;
|
|
31667
32257
|
const herdr = await this.getBinary();
|
|
31668
|
-
|
|
31669
|
-
|
|
31670
|
-
|
|
32258
|
+
const closed = await gracefulClosePane(herdr, paneId, {
|
|
32259
|
+
ctrlC: ["pane", "send-keys", paneId, "ctrl+c"],
|
|
32260
|
+
close: ["pane", "close", paneId],
|
|
32261
|
+
acceptExitCode1: true,
|
|
32262
|
+
emptyPaneReturnsTrue: true
|
|
32263
|
+
});
|
|
32264
|
+
if (closed && paneId === this.agentAreaPaneId) {
|
|
32265
|
+
this.agentAreaPaneId = null;
|
|
31671
32266
|
}
|
|
31672
|
-
|
|
31673
|
-
|
|
31674
|
-
|
|
31675
|
-
|
|
31676
|
-
|
|
31677
|
-
|
|
31678
|
-
|
|
31679
|
-
|
|
31680
|
-
|
|
31681
|
-
|
|
31682
|
-
|
|
31683
|
-
|
|
31684
|
-
|
|
31685
|
-
|
|
31686
|
-
|
|
31687
|
-
|
|
31688
|
-
|
|
31689
|
-
|
|
31690
|
-
|
|
31691
|
-
|
|
32267
|
+
return closed;
|
|
32268
|
+
}
|
|
32269
|
+
async applyLayout(layout, _mainPaneSize) {
|
|
32270
|
+
this.agentAreaPaneId = null;
|
|
32271
|
+
this.layout = layout;
|
|
32272
|
+
this.paneDirection = getPaneDirection(layout);
|
|
32273
|
+
}
|
|
32274
|
+
async runSplit(target, direction, directory) {
|
|
32275
|
+
const herdr = await this.getBinary();
|
|
32276
|
+
if (!herdr)
|
|
32277
|
+
return { paneId: null, rawOutput: "" };
|
|
32278
|
+
const splitArgs = [
|
|
32279
|
+
herdr,
|
|
32280
|
+
"pane",
|
|
32281
|
+
"split",
|
|
32282
|
+
...target,
|
|
32283
|
+
"--direction",
|
|
32284
|
+
direction,
|
|
32285
|
+
"--cwd",
|
|
32286
|
+
directory,
|
|
32287
|
+
"--no-focus"
|
|
32288
|
+
];
|
|
32289
|
+
log("[herdr] spawnPane: splitting pane", { args: splitArgs });
|
|
32290
|
+
const splitProc = crossSpawn(splitArgs, {
|
|
32291
|
+
stdout: "pipe",
|
|
32292
|
+
stderr: "pipe"
|
|
32293
|
+
});
|
|
32294
|
+
const splitExitCode = await splitProc.exited;
|
|
32295
|
+
const splitStdout = await splitProc.stdout();
|
|
32296
|
+
const splitStderr = await splitProc.stderr();
|
|
32297
|
+
if (splitExitCode !== 0) {
|
|
32298
|
+
log("[herdr] spawnPane: split failed", {
|
|
32299
|
+
exitCode: splitExitCode,
|
|
32300
|
+
stderr: splitStderr.trim()
|
|
31692
32301
|
});
|
|
31693
|
-
return
|
|
31694
|
-
} catch (err) {
|
|
31695
|
-
log("[herdr] closePane: exception", { error: String(err) });
|
|
31696
|
-
return false;
|
|
32302
|
+
return { paneId: null, rawOutput: splitStdout.trim() };
|
|
31697
32303
|
}
|
|
32304
|
+
return { paneId: parsePaneId(splitStdout), rawOutput: splitStdout.trim() };
|
|
31698
32305
|
}
|
|
31699
|
-
async applyLayout(_layout, _mainPaneSize) {}
|
|
31700
32306
|
targetPaneArg() {
|
|
31701
32307
|
return this.parentPaneId ? [this.parentPaneId] : ["--current"];
|
|
31702
32308
|
}
|
|
@@ -31704,32 +32310,6 @@ class HerdrMultiplexer {
|
|
|
31704
32310
|
await this.isAvailable();
|
|
31705
32311
|
return this.binaryPath;
|
|
31706
32312
|
}
|
|
31707
|
-
async findBinary() {
|
|
31708
|
-
const cmd = process.platform === "win32" ? "where" : "which";
|
|
31709
|
-
try {
|
|
31710
|
-
const proc = crossSpawn([cmd, "herdr"], {
|
|
31711
|
-
stdout: "pipe",
|
|
31712
|
-
stderr: "pipe"
|
|
31713
|
-
});
|
|
31714
|
-
const exitCode = await proc.exited;
|
|
31715
|
-
if (exitCode !== 0) {
|
|
31716
|
-
log("[herdr] findBinary: 'which herdr' failed", { exitCode });
|
|
31717
|
-
return null;
|
|
31718
|
-
}
|
|
31719
|
-
const stdout = await proc.stdout();
|
|
31720
|
-
const path18 = stdout.trim().split(`
|
|
31721
|
-
`)[0];
|
|
31722
|
-
if (!path18) {
|
|
31723
|
-
log("[herdr] findBinary: no path in output");
|
|
31724
|
-
return null;
|
|
31725
|
-
}
|
|
31726
|
-
log("[herdr] findBinary: found", { path: path18 });
|
|
31727
|
-
return path18;
|
|
31728
|
-
} catch (err) {
|
|
31729
|
-
log("[herdr] findBinary: exception", { error: String(err) });
|
|
31730
|
-
return null;
|
|
31731
|
-
}
|
|
31732
|
-
}
|
|
31733
32313
|
}
|
|
31734
32314
|
function parsePaneId(stdout) {
|
|
31735
32315
|
const trimmed = stdout.trim();
|
|
@@ -31761,20 +32341,6 @@ function getPaneDirection(layout) {
|
|
|
31761
32341
|
return "right";
|
|
31762
32342
|
}
|
|
31763
32343
|
}
|
|
31764
|
-
function buildOpencodeAttachCommand(sessionId, serverUrl, directory) {
|
|
31765
|
-
return [
|
|
31766
|
-
"opencode",
|
|
31767
|
-
"attach",
|
|
31768
|
-
quoteShellArg(serverUrl),
|
|
31769
|
-
"--session",
|
|
31770
|
-
quoteShellArg(sessionId),
|
|
31771
|
-
"--dir",
|
|
31772
|
-
quoteShellArg(directory)
|
|
31773
|
-
].join(" ");
|
|
31774
|
-
}
|
|
31775
|
-
function quoteShellArg(value) {
|
|
31776
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
31777
|
-
}
|
|
31778
32344
|
|
|
31779
32345
|
// src/multiplexer/tmux/index.ts
|
|
31780
32346
|
init_compat();
|
|
@@ -31797,7 +32363,7 @@ class TmuxMultiplexer {
|
|
|
31797
32363
|
if (this.hasChecked) {
|
|
31798
32364
|
return this.binaryPath !== null;
|
|
31799
32365
|
}
|
|
31800
|
-
this.binaryPath = await
|
|
32366
|
+
this.binaryPath = await findBinary("tmux", { verify: true });
|
|
31801
32367
|
this.hasChecked = true;
|
|
31802
32368
|
return this.binaryPath !== null;
|
|
31803
32369
|
}
|
|
@@ -31811,18 +32377,7 @@ class TmuxMultiplexer {
|
|
|
31811
32377
|
return { success: false };
|
|
31812
32378
|
}
|
|
31813
32379
|
try {
|
|
31814
|
-
const
|
|
31815
|
-
const quotedUrl = quoteShellArg2(serverUrl);
|
|
31816
|
-
const quotedSessionId = quoteShellArg2(sessionId);
|
|
31817
|
-
const opencodeCmd = [
|
|
31818
|
-
"opencode",
|
|
31819
|
-
"attach",
|
|
31820
|
-
quotedUrl,
|
|
31821
|
-
"--session",
|
|
31822
|
-
quotedSessionId,
|
|
31823
|
-
"--dir",
|
|
31824
|
-
quotedDirectory
|
|
31825
|
-
].join(" ");
|
|
32380
|
+
const opencodeCmd = buildOpencodeAttachCommand(sessionId, serverUrl, directory);
|
|
31826
32381
|
const args = [
|
|
31827
32382
|
"split-window",
|
|
31828
32383
|
"-h",
|
|
@@ -31861,41 +32416,14 @@ class TmuxMultiplexer {
|
|
|
31861
32416
|
}
|
|
31862
32417
|
}
|
|
31863
32418
|
async closePane(paneId) {
|
|
31864
|
-
if (!paneId) {
|
|
31865
|
-
log("[tmux] closePane: no paneId provided");
|
|
31866
|
-
return false;
|
|
31867
|
-
}
|
|
31868
32419
|
const tmux = await this.getBinary();
|
|
31869
|
-
|
|
31870
|
-
|
|
31871
|
-
|
|
31872
|
-
}
|
|
31873
|
-
|
|
31874
|
-
|
|
31875
|
-
|
|
31876
|
-
stdout: "pipe",
|
|
31877
|
-
stderr: "pipe"
|
|
31878
|
-
});
|
|
31879
|
-
await ctrlCProc.exited;
|
|
31880
|
-
await new Promise((r) => setTimeout(r, 250));
|
|
31881
|
-
log("[tmux] closePane: killing pane", { paneId });
|
|
31882
|
-
const proc = crossSpawn([tmux, "kill-pane", "-t", paneId], {
|
|
31883
|
-
stdout: "pipe",
|
|
31884
|
-
stderr: "pipe"
|
|
31885
|
-
});
|
|
31886
|
-
const exitCode = await proc.exited;
|
|
31887
|
-
const stderr = await proc.stderr();
|
|
31888
|
-
log("[tmux] closePane: result", { exitCode, stderr: stderr.trim() });
|
|
31889
|
-
if (exitCode === 0) {
|
|
31890
|
-
this.scheduleLayout();
|
|
31891
|
-
return true;
|
|
31892
|
-
}
|
|
31893
|
-
log("[tmux] closePane: failed (pane may already be closed)", { paneId });
|
|
31894
|
-
return false;
|
|
31895
|
-
} catch (err) {
|
|
31896
|
-
log("[tmux] closePane: exception", { error: String(err) });
|
|
31897
|
-
return false;
|
|
31898
|
-
}
|
|
32420
|
+
const closed = await gracefulClosePane(tmux, paneId, {
|
|
32421
|
+
ctrlC: ["send-keys", "-t", paneId, "C-c"],
|
|
32422
|
+
close: ["kill-pane", "-t", paneId]
|
|
32423
|
+
});
|
|
32424
|
+
if (closed)
|
|
32425
|
+
this.scheduleLayout();
|
|
32426
|
+
return closed;
|
|
31899
32427
|
}
|
|
31900
32428
|
async applyLayout(layout, mainPaneSize) {
|
|
31901
32429
|
if (this.layoutTimer) {
|
|
@@ -31981,50 +32509,10 @@ class TmuxMultiplexer {
|
|
|
31981
32509
|
targetArgs() {
|
|
31982
32510
|
return this.targetPane ? ["-t", this.targetPane] : [];
|
|
31983
32511
|
}
|
|
31984
|
-
async findBinary() {
|
|
31985
|
-
const isWindows = process.platform === "win32";
|
|
31986
|
-
const cmd = isWindows ? "where" : "which";
|
|
31987
|
-
try {
|
|
31988
|
-
const proc = crossSpawn([cmd, "tmux"], {
|
|
31989
|
-
stdout: "pipe",
|
|
31990
|
-
stderr: "pipe"
|
|
31991
|
-
});
|
|
31992
|
-
const exitCode = await proc.exited;
|
|
31993
|
-
if (exitCode !== 0) {
|
|
31994
|
-
log("[tmux] findBinary: 'which tmux' failed", { exitCode });
|
|
31995
|
-
return null;
|
|
31996
|
-
}
|
|
31997
|
-
const stdout = await proc.stdout();
|
|
31998
|
-
const path18 = stdout.trim().split(`
|
|
31999
|
-
`)[0];
|
|
32000
|
-
if (!path18) {
|
|
32001
|
-
log("[tmux] findBinary: no path in output");
|
|
32002
|
-
return null;
|
|
32003
|
-
}
|
|
32004
|
-
const verifyProc = crossSpawn([path18, "-V"], {
|
|
32005
|
-
stdout: "pipe",
|
|
32006
|
-
stderr: "pipe"
|
|
32007
|
-
});
|
|
32008
|
-
const verifyExit = await verifyProc.exited;
|
|
32009
|
-
if (verifyExit !== 0) {
|
|
32010
|
-
log("[tmux] findBinary: tmux -V failed", { path: path18, verifyExit });
|
|
32011
|
-
return null;
|
|
32012
|
-
}
|
|
32013
|
-
log("[tmux] findBinary: found", { path: path18 });
|
|
32014
|
-
return path18;
|
|
32015
|
-
} catch (err) {
|
|
32016
|
-
log("[tmux] findBinary: exception", { error: String(err) });
|
|
32017
|
-
return null;
|
|
32018
|
-
}
|
|
32019
|
-
}
|
|
32020
|
-
}
|
|
32021
|
-
function quoteShellArg2(value) {
|
|
32022
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
32023
32512
|
}
|
|
32024
32513
|
|
|
32025
32514
|
// src/multiplexer/zellij/index.ts
|
|
32026
32515
|
init_compat();
|
|
32027
|
-
|
|
32028
32516
|
class ZellijMultiplexer {
|
|
32029
32517
|
paneMode;
|
|
32030
32518
|
type = "zellij";
|
|
@@ -32044,7 +32532,7 @@ class ZellijMultiplexer {
|
|
|
32044
32532
|
if (this.hasChecked) {
|
|
32045
32533
|
return this.binaryPath !== null;
|
|
32046
32534
|
}
|
|
32047
|
-
this.binaryPath = await
|
|
32535
|
+
this.binaryPath = await findBinary("zellij");
|
|
32048
32536
|
this.hasChecked = true;
|
|
32049
32537
|
return this.binaryPath !== null;
|
|
32050
32538
|
}
|
|
@@ -32079,7 +32567,7 @@ class ZellijMultiplexer {
|
|
|
32079
32567
|
}
|
|
32080
32568
|
}
|
|
32081
32569
|
async createPaneInCurrentTab(zellij, sessionId, serverUrl, directory, description) {
|
|
32082
|
-
const opencodeCmd =
|
|
32570
|
+
const opencodeCmd = buildOpencodeAttachCommand(sessionId, serverUrl, directory);
|
|
32083
32571
|
const paneName = description.slice(0, 30).replace(/"/g, "\\\"");
|
|
32084
32572
|
const targetTabId = await this.getParentTabId(zellij);
|
|
32085
32573
|
const args = [
|
|
@@ -32108,7 +32596,7 @@ class ZellijMultiplexer {
|
|
|
32108
32596
|
return { success: false };
|
|
32109
32597
|
}
|
|
32110
32598
|
async createPaneInAgentTab(zellij, sessionId, serverUrl, directory, description) {
|
|
32111
|
-
const opencodeCmd =
|
|
32599
|
+
const opencodeCmd = buildOpencodeAttachCommand(sessionId, serverUrl, directory);
|
|
32112
32600
|
const paneName = description.slice(0, 30).replace(/"/g, "\\\"");
|
|
32113
32601
|
const currentTabId = await this.getCurrentTabId(zellij);
|
|
32114
32602
|
const inAgentTab = currentTabId === this.agentTabId;
|
|
@@ -32177,7 +32665,7 @@ class ZellijMultiplexer {
|
|
|
32177
32665
|
}
|
|
32178
32666
|
async runInPane(zellij, paneId, sessionId, serverUrl, directory, description) {
|
|
32179
32667
|
try {
|
|
32180
|
-
const opencodeCmd =
|
|
32668
|
+
const opencodeCmd = buildOpencodeAttachCommand(sessionId, serverUrl, directory);
|
|
32181
32669
|
await crossSpawn([zellij, "action", "focus-pane", "--pane-id", paneId], {
|
|
32182
32670
|
stdout: "ignore",
|
|
32183
32671
|
stderr: "ignore"
|
|
@@ -32322,23 +32810,13 @@ class ZellijMultiplexer {
|
|
|
32322
32810
|
}
|
|
32323
32811
|
}
|
|
32324
32812
|
async closePane(paneId) {
|
|
32325
|
-
if (!paneId || paneId === "unknown")
|
|
32326
|
-
return true;
|
|
32327
32813
|
const zellij = await this.getBinary();
|
|
32328
|
-
|
|
32329
|
-
|
|
32330
|
-
|
|
32331
|
-
|
|
32332
|
-
|
|
32333
|
-
|
|
32334
|
-
}).exited;
|
|
32335
|
-
await new Promise((r) => setTimeout(r, 250));
|
|
32336
|
-
const proc = crossSpawn([zellij, "action", "close-pane", "--pane-id", paneId], { stdout: "pipe", stderr: "pipe" });
|
|
32337
|
-
const exitCode = await proc.exited;
|
|
32338
|
-
return exitCode === 0 || exitCode === 1;
|
|
32339
|
-
} catch {
|
|
32340
|
-
return false;
|
|
32341
|
-
}
|
|
32814
|
+
return gracefulClosePane(zellij, paneId, {
|
|
32815
|
+
ctrlC: ["action", "write", "--pane-id", paneId, "\x03"],
|
|
32816
|
+
close: ["action", "close-pane", "--pane-id", paneId],
|
|
32817
|
+
acceptExitCode1: true,
|
|
32818
|
+
emptyPaneReturnsTrue: true
|
|
32819
|
+
});
|
|
32342
32820
|
}
|
|
32343
32821
|
async applyLayout(_layout, _mainPaneSize) {}
|
|
32344
32822
|
directionArgs() {
|
|
@@ -32381,22 +32859,6 @@ class ZellijMultiplexer {
|
|
|
32381
32859
|
await this.isAvailable();
|
|
32382
32860
|
return this.binaryPath;
|
|
32383
32861
|
}
|
|
32384
|
-
async findBinary() {
|
|
32385
|
-
const cmd = process.platform === "win32" ? "where" : "which";
|
|
32386
|
-
try {
|
|
32387
|
-
const proc = crossSpawn([cmd, "zellij"], {
|
|
32388
|
-
stdout: "pipe",
|
|
32389
|
-
stderr: "pipe"
|
|
32390
|
-
});
|
|
32391
|
-
if (await proc.exited !== 0)
|
|
32392
|
-
return null;
|
|
32393
|
-
const stdout = await proc.stdout();
|
|
32394
|
-
return stdout.trim().split(`
|
|
32395
|
-
`)[0] || null;
|
|
32396
|
-
} catch {
|
|
32397
|
-
return null;
|
|
32398
|
-
}
|
|
32399
|
-
}
|
|
32400
32862
|
}
|
|
32401
32863
|
function normalizePaneId(paneId) {
|
|
32402
32864
|
return paneId.replace(/^terminal_/, "");
|
|
@@ -32413,22 +32875,8 @@ function getPaneDirection2(layout) {
|
|
|
32413
32875
|
return null;
|
|
32414
32876
|
}
|
|
32415
32877
|
}
|
|
32416
|
-
function buildOpencodeAttachCommand2(sessionId, serverUrl, directory) {
|
|
32417
|
-
return [
|
|
32418
|
-
"opencode",
|
|
32419
|
-
"attach",
|
|
32420
|
-
quoteShellArg3(serverUrl),
|
|
32421
|
-
"--session",
|
|
32422
|
-
quoteShellArg3(sessionId),
|
|
32423
|
-
"--dir",
|
|
32424
|
-
quoteShellArg3(directory)
|
|
32425
|
-
].join(" ");
|
|
32426
|
-
}
|
|
32427
32878
|
function buildShellLaunchCommand(command) {
|
|
32428
|
-
return ["sh", "-lc",
|
|
32429
|
-
}
|
|
32430
|
-
function quoteShellArg3(value) {
|
|
32431
|
-
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
32879
|
+
return ["sh", "-lc", quoteShellArg(command)].join(" ");
|
|
32432
32880
|
}
|
|
32433
32881
|
|
|
32434
32882
|
// src/multiplexer/factory.ts
|
|
@@ -32489,8 +32937,7 @@ function getSharedState() {
|
|
|
32489
32937
|
sessions: new Map,
|
|
32490
32938
|
knownSessions: new Map,
|
|
32491
32939
|
spawningSessions: new Set,
|
|
32492
|
-
closingSessions: new Map
|
|
32493
|
-
deferredIdleCloses: new Set
|
|
32940
|
+
closingSessions: new Map
|
|
32494
32941
|
};
|
|
32495
32942
|
return globalWithState[SHARED_STATE_KEY];
|
|
32496
32943
|
}
|
|
@@ -32504,7 +32951,6 @@ class MultiplexerSessionManager {
|
|
|
32504
32951
|
knownSessions;
|
|
32505
32952
|
spawningSessions;
|
|
32506
32953
|
closingSessions;
|
|
32507
|
-
deferredIdleCloses;
|
|
32508
32954
|
pollInterval;
|
|
32509
32955
|
enabled = false;
|
|
32510
32956
|
constructor(ctx, config, backgroundJobBoard) {
|
|
@@ -32514,7 +32960,6 @@ class MultiplexerSessionManager {
|
|
|
32514
32960
|
this.knownSessions = sharedState.knownSessions;
|
|
32515
32961
|
this.spawningSessions = sharedState.spawningSessions;
|
|
32516
32962
|
this.closingSessions = sharedState.closingSessions;
|
|
32517
|
-
this.deferredIdleCloses = sharedState.deferredIdleCloses;
|
|
32518
32963
|
this.directory = ctx.directory;
|
|
32519
32964
|
const defaultPort = process.env.OPENCODE_PORT ?? "4096";
|
|
32520
32965
|
this.serverUrl = ctx.serverUrl?.toString() ?? `http://localhost:${defaultPort}`;
|
|
@@ -32652,7 +33097,7 @@ class MultiplexerSessionManager {
|
|
|
32652
33097
|
}
|
|
32653
33098
|
if (statusType) {
|
|
32654
33099
|
if (statusType !== "busy") {
|
|
32655
|
-
this.
|
|
33100
|
+
this.backgroundJobBoard?.clearDeferredClose(sessionId);
|
|
32656
33101
|
return;
|
|
32657
33102
|
}
|
|
32658
33103
|
log("[multiplexer-session-manager] session busy event received", {
|
|
@@ -32682,7 +33127,6 @@ class MultiplexerSessionManager {
|
|
|
32682
33127
|
ownerInstanceId: this.sessions.get(sessionId)?.ownerInstanceId,
|
|
32683
33128
|
backgroundJobState: this.backgroundJobState(sessionId)
|
|
32684
33129
|
});
|
|
32685
|
-
this.deferredIdleCloses.delete(sessionId);
|
|
32686
33130
|
await this.closeSession(sessionId, "deleted");
|
|
32687
33131
|
}
|
|
32688
33132
|
startPolling() {
|
|
@@ -32724,7 +33168,7 @@ class MultiplexerSessionManager {
|
|
|
32724
33168
|
if (!status)
|
|
32725
33169
|
continue;
|
|
32726
33170
|
if (status.type !== "idle") {
|
|
32727
|
-
this.
|
|
33171
|
+
this.backgroundJobBoard?.clearDeferredClose(sessionId);
|
|
32728
33172
|
continue;
|
|
32729
33173
|
}
|
|
32730
33174
|
sessionsToClose.push(sessionId);
|
|
@@ -32752,10 +33196,10 @@ class MultiplexerSessionManager {
|
|
|
32752
33196
|
throw new Error(`session status response was not valid JSON: ${err}`);
|
|
32753
33197
|
}
|
|
32754
33198
|
}
|
|
32755
|
-
async closeSession(sessionId, reason) {
|
|
33199
|
+
async closeSession(sessionId, reason, skipPolicyCheck = false) {
|
|
32756
33200
|
if (reason === "deleted") {
|
|
32757
33201
|
this.knownSessions.delete(sessionId);
|
|
32758
|
-
this.
|
|
33202
|
+
this.backgroundJobBoard?.clearDeferredClose(sessionId);
|
|
32759
33203
|
}
|
|
32760
33204
|
const existingClose = this.closingSessions.get(sessionId);
|
|
32761
33205
|
if (existingClose)
|
|
@@ -32790,8 +33234,7 @@ class MultiplexerSessionManager {
|
|
|
32790
33234
|
reason
|
|
32791
33235
|
});
|
|
32792
33236
|
}
|
|
32793
|
-
if (reason === "idle" && this.
|
|
32794
|
-
this.deferredIdleCloses.add(sessionId);
|
|
33237
|
+
if (reason === "idle" && !skipPolicyCheck && !this.shouldCloseNow(sessionId)) {
|
|
32795
33238
|
log("[multiplexer-session-manager] close skipped; background job running", {
|
|
32796
33239
|
instanceId: this.instanceId,
|
|
32797
33240
|
sessionId,
|
|
@@ -32801,7 +33244,6 @@ class MultiplexerSessionManager {
|
|
|
32801
33244
|
});
|
|
32802
33245
|
return;
|
|
32803
33246
|
}
|
|
32804
|
-
this.deferredIdleCloses.delete(sessionId);
|
|
32805
33247
|
this.sessions.delete(sessionId);
|
|
32806
33248
|
log("[multiplexer-session-manager] closing session pane", {
|
|
32807
33249
|
instanceId: this.instanceId,
|
|
@@ -32885,7 +33327,7 @@ class MultiplexerSessionManager {
|
|
|
32885
33327
|
directory: known.directory,
|
|
32886
33328
|
ownerInstanceId: this.instanceId
|
|
32887
33329
|
});
|
|
32888
|
-
this.
|
|
33330
|
+
this.backgroundJobBoard?.clearDeferredClose(sessionId);
|
|
32889
33331
|
log("[multiplexer-session-manager] pane respawned on busy", {
|
|
32890
33332
|
instanceId: this.instanceId,
|
|
32891
33333
|
sessionId,
|
|
@@ -32907,20 +33349,18 @@ class MultiplexerSessionManager {
|
|
|
32907
33349
|
}
|
|
32908
33350
|
}
|
|
32909
33351
|
getSessionId(event) {
|
|
32910
|
-
return event.properties?.info?.id
|
|
33352
|
+
return event.properties?.info?.id || event.properties?.sessionID;
|
|
32911
33353
|
}
|
|
32912
33354
|
backgroundJobState(sessionId) {
|
|
32913
33355
|
return this.backgroundJobBoard?.getState(sessionId);
|
|
32914
33356
|
}
|
|
32915
|
-
|
|
32916
|
-
return this.backgroundJobBoard?.
|
|
33357
|
+
shouldCloseNow(sessionId) {
|
|
33358
|
+
return this.backgroundJobBoard?.deferIfRunning(sessionId) ?? true;
|
|
32917
33359
|
}
|
|
32918
|
-
async
|
|
33360
|
+
async closeSessionFromCoordinator(sessionId) {
|
|
32919
33361
|
if (!this.enabled)
|
|
32920
33362
|
return;
|
|
32921
|
-
|
|
32922
|
-
return;
|
|
32923
|
-
await this.closeSession(sessionId, "idle");
|
|
33363
|
+
await this.closeSession(sessionId, "idle", true);
|
|
32924
33364
|
}
|
|
32925
33365
|
async cleanup() {
|
|
32926
33366
|
this.stopPolling();
|
|
@@ -32942,7 +33382,6 @@ class MultiplexerSessionManager {
|
|
|
32942
33382
|
this.knownSessions.clear();
|
|
32943
33383
|
this.spawningSessions.clear();
|
|
32944
33384
|
this.closingSessions.clear();
|
|
32945
|
-
this.deferredIdleCloses.clear();
|
|
32946
33385
|
log("[multiplexer-session-manager] cleanup complete");
|
|
32947
33386
|
}
|
|
32948
33387
|
}
|
|
@@ -33208,7 +33647,7 @@ function createAcpRunTool(agents = {}) {
|
|
|
33208
33647
|
if (!cwd)
|
|
33209
33648
|
throw new Error("acp_run requires a working directory");
|
|
33210
33649
|
await ctx.ask({
|
|
33211
|
-
permission: "
|
|
33650
|
+
permission: "acp_run",
|
|
33212
33651
|
patterns: [`${config.command} ${config.args.join(" ")}`.trim()],
|
|
33213
33652
|
always: [],
|
|
33214
33653
|
metadata: {
|
|
@@ -33222,7 +33661,7 @@ function createAcpRunTool(agents = {}) {
|
|
|
33222
33661
|
if (config.permissionMode === "reject")
|
|
33223
33662
|
return;
|
|
33224
33663
|
await ctx.ask({
|
|
33225
|
-
permission: "
|
|
33664
|
+
permission: "acp_run",
|
|
33226
33665
|
patterns: [`acp:${args.agent}:${title}`],
|
|
33227
33666
|
always: [],
|
|
33228
33667
|
metadata
|
|
@@ -33311,7 +33750,7 @@ init_compat();
|
|
|
33311
33750
|
import { existsSync as existsSync11 } from "node:fs";
|
|
33312
33751
|
|
|
33313
33752
|
// src/tools/ast-grep/constants.ts
|
|
33314
|
-
import { existsSync as existsSync10, statSync as
|
|
33753
|
+
import { existsSync as existsSync10, statSync as statSync6 } from "node:fs";
|
|
33315
33754
|
import { createRequire as createRequire3 } from "node:module";
|
|
33316
33755
|
import { dirname as dirname10, join as join15 } from "node:path";
|
|
33317
33756
|
|
|
@@ -33442,7 +33881,7 @@ var CLI_LANGUAGES = [
|
|
|
33442
33881
|
var MIN_BINARY_SIZE = 1e4;
|
|
33443
33882
|
function isValidBinary(filePath) {
|
|
33444
33883
|
try {
|
|
33445
|
-
return
|
|
33884
|
+
return statSync6(filePath).size > MIN_BINARY_SIZE;
|
|
33446
33885
|
} catch {
|
|
33447
33886
|
return false;
|
|
33448
33887
|
}
|
|
@@ -34268,7 +34707,7 @@ Sends the prompt to multiple models (councillors) in parallel and returns their
|
|
|
34268
34707
|
Returns the councillor responses with a summary footer.`,
|
|
34269
34708
|
args: {
|
|
34270
34709
|
prompt: z6.string().describe("The prompt to send to all councillors"),
|
|
34271
|
-
preset: z6.string().optional().describe(
|
|
34710
|
+
preset: z6.string().optional().describe("Council preset to use. Omit to use the configured default. Must match a preset in the council config.")
|
|
34272
34711
|
},
|
|
34273
34712
|
async execute(args, toolContext) {
|
|
34274
34713
|
if (!toolContext || typeof toolContext !== "object" || !("sessionID" in toolContext)) {
|
|
@@ -36983,19 +37422,24 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
36983
37422
|
let depthTracker;
|
|
36984
37423
|
let multiplexerSessionManager;
|
|
36985
37424
|
let autoUpdateChecker;
|
|
36986
|
-
let phaseReminderHook;
|
|
36987
|
-
let filterAvailableSkillsHook;
|
|
36988
37425
|
let sessionAgentMap;
|
|
36989
|
-
let
|
|
37426
|
+
let sessionLifecycle;
|
|
36990
37427
|
let chatHeadersHook;
|
|
36991
|
-
let delegateTaskRetryHook;
|
|
36992
|
-
let applyPatchHook;
|
|
36993
|
-
let jsonErrorRecoveryHook;
|
|
36994
37428
|
let foregroundFallback;
|
|
36995
37429
|
let deepworkCommandHook;
|
|
36996
37430
|
let reflectCommandHook;
|
|
36997
37431
|
let loopCommandHook;
|
|
36998
37432
|
let taskSessionManagerHook;
|
|
37433
|
+
let phaseReminder;
|
|
37434
|
+
let filterAvailableSkills;
|
|
37435
|
+
let postFileToolNudge;
|
|
37436
|
+
let delegateTaskRetry;
|
|
37437
|
+
let applyPatch;
|
|
37438
|
+
let jsonErrorRecovery;
|
|
37439
|
+
let postFileToolNudgeAfter;
|
|
37440
|
+
let delegateTaskRetryAfter;
|
|
37441
|
+
let jsonErrorRecoveryAfter;
|
|
37442
|
+
let taskSessionManagerAfter;
|
|
36999
37443
|
let backgroundJobBoard;
|
|
37000
37444
|
let interviewManager;
|
|
37001
37445
|
let presetManager;
|
|
@@ -37055,25 +37499,19 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
37055
37499
|
readContextMinLines: config.backgroundJobs?.readContextMinLines ?? 10,
|
|
37056
37500
|
readContextMaxFiles: config.backgroundJobs?.readContextMaxFiles ?? 8
|
|
37057
37501
|
});
|
|
37058
|
-
|
|
37059
|
-
|
|
37060
|
-
|
|
37502
|
+
const backgroundJobCoordinator = new BackgroundJobCoordinator(backgroundJobBoard);
|
|
37503
|
+
multiplexerSessionManager = new MultiplexerSessionManager(ctx, multiplexerConfig, backgroundJobCoordinator);
|
|
37504
|
+
backgroundJobCoordinator.addTerminalStateListener((taskID) => {
|
|
37505
|
+
multiplexerSessionManager.closeSessionFromCoordinator(taskID);
|
|
37061
37506
|
});
|
|
37507
|
+
sessionLifecycle = new SessionLifecycle(log);
|
|
37062
37508
|
autoUpdateChecker = createAutoUpdateCheckerHook(ctx, {
|
|
37063
37509
|
autoUpdate: config.autoUpdate ?? true,
|
|
37064
37510
|
companion: config.companion
|
|
37065
37511
|
});
|
|
37066
|
-
phaseReminderHook = createPhaseReminderHook();
|
|
37067
|
-
filterAvailableSkillsHook = createFilterAvailableSkillsHook(ctx, config);
|
|
37068
37512
|
sessionAgentMap = new Map;
|
|
37069
|
-
postFileToolNudgeHook = createPostFileToolNudgeHook({
|
|
37070
|
-
shouldInject: (sessionID) => sessionAgentMap.get(sessionID) === "orchestrator"
|
|
37071
|
-
});
|
|
37072
37513
|
chatHeadersHook = createChatHeadersHook(ctx);
|
|
37073
|
-
|
|
37074
|
-
applyPatchHook = createApplyPatchHook(ctx);
|
|
37075
|
-
jsonErrorRecoveryHook = createJsonErrorRecoveryHook(ctx);
|
|
37076
|
-
foregroundFallback = new ForegroundFallbackManager(ctx.client, runtimeChains, config.fallback?.enabled !== false && Object.keys(runtimeChains).length > 0);
|
|
37514
|
+
foregroundFallback = new ForegroundFallbackManager(ctx.client, runtimeChains, config.fallback?.enabled !== false, config.fallback?.maxRetries ?? 3, sessionLifecycle, config.fallback?.runtimeOverride ?? true);
|
|
37077
37515
|
deepworkCommandHook = createDeepworkCommandHook();
|
|
37078
37516
|
reflectCommandHook = createReflectCommandHook();
|
|
37079
37517
|
loopCommandHook = createLoopCommandHook();
|
|
@@ -37081,15 +37519,49 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
37081
37519
|
maxSessionsPerAgent: config.backgroundJobs?.maxSessionsPerAgent ?? 2,
|
|
37082
37520
|
readContextMinLines: config.backgroundJobs?.readContextMinLines ?? 10,
|
|
37083
37521
|
readContextMaxFiles: config.backgroundJobs?.readContextMaxFiles ?? 8,
|
|
37084
|
-
backgroundJobBoard,
|
|
37085
|
-
shouldManageSession: (sessionID) => sessionAgentMap.get(sessionID) === "orchestrator"
|
|
37522
|
+
backgroundJobBoard: backgroundJobCoordinator,
|
|
37523
|
+
shouldManageSession: (sessionID) => sessionAgentMap.get(sessionID) === "orchestrator",
|
|
37524
|
+
registerSessionAsOrchestrator: (sessionID) => {
|
|
37525
|
+
sessionAgentMap.set(sessionID, "orchestrator");
|
|
37526
|
+
},
|
|
37527
|
+
isFallbackInProgress: (sessionID) => foregroundFallback.isFallbackInProgress(sessionID),
|
|
37528
|
+
coordinator: sessionLifecycle
|
|
37529
|
+
});
|
|
37530
|
+
const wrapPostToolHook = (name, fn) => {
|
|
37531
|
+
return async (i, o) => {
|
|
37532
|
+
try {
|
|
37533
|
+
await fn(i, o);
|
|
37534
|
+
} catch (error) {
|
|
37535
|
+
const meta = i;
|
|
37536
|
+
log("[plugin] post-tool hook failed open", {
|
|
37537
|
+
hook: name,
|
|
37538
|
+
tool: meta.tool,
|
|
37539
|
+
sessionID: meta.sessionID,
|
|
37540
|
+
callID: meta.callID,
|
|
37541
|
+
error: error instanceof Error ? error.message : String(error)
|
|
37542
|
+
});
|
|
37543
|
+
}
|
|
37544
|
+
};
|
|
37545
|
+
};
|
|
37546
|
+
phaseReminder = createPhaseReminderHook(sessionLifecycle);
|
|
37547
|
+
filterAvailableSkills = createFilterAvailableSkillsHook(ctx, config);
|
|
37548
|
+
postFileToolNudge = createPostFileToolNudgeHook({
|
|
37549
|
+
shouldInject: (sessionID) => sessionAgentMap.get(sessionID) === "orchestrator",
|
|
37550
|
+
coordinator: sessionLifecycle
|
|
37086
37551
|
});
|
|
37552
|
+
delegateTaskRetry = createDelegateTaskRetryHook(ctx);
|
|
37553
|
+
applyPatch = createApplyPatchHook(ctx);
|
|
37554
|
+
jsonErrorRecovery = createJsonErrorRecoveryHook(ctx);
|
|
37555
|
+
postFileToolNudgeAfter = wrapPostToolHook("post-file-tool-nudge", (i, o) => postFileToolNudge["tool.execute.after"](i, o));
|
|
37556
|
+
delegateTaskRetryAfter = wrapPostToolHook("delegate-task-retry", (i, o) => delegateTaskRetry["tool.execute.after"](i, o));
|
|
37557
|
+
jsonErrorRecoveryAfter = wrapPostToolHook("json-error-recovery", (i, o) => jsonErrorRecovery["tool.execute.after"](i, o));
|
|
37558
|
+
taskSessionManagerAfter = wrapPostToolHook("task-session-manager", (i, o) => taskSessionManagerHook["tool.execute.after"](i, o));
|
|
37087
37559
|
interviewManager = createInterviewManager(ctx, config);
|
|
37088
37560
|
presetManager = createPresetManager(ctx, config);
|
|
37089
37561
|
companionManager = new CompanionManager(`proc_${process.pid}`, ctx.directory, config.companion);
|
|
37090
37562
|
cancelTaskTools = createCancelTaskTool({
|
|
37091
37563
|
client: ctx.client,
|
|
37092
|
-
backgroundJobBoard,
|
|
37564
|
+
backgroundJobBoard: backgroundJobCoordinator,
|
|
37093
37565
|
shouldManageSession: (sessionID) => sessionAgentMap.get(sessionID) === "orchestrator"
|
|
37094
37566
|
});
|
|
37095
37567
|
tools = {
|
|
@@ -37401,12 +37873,11 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
37401
37873
|
}
|
|
37402
37874
|
if (input.event.type === "session.deleted") {
|
|
37403
37875
|
const props = input.event.properties;
|
|
37404
|
-
const sessionID = props?.info?.id
|
|
37876
|
+
const sessionID = props?.info?.id || props?.sessionID;
|
|
37877
|
+
if (sessionID) {
|
|
37878
|
+
sessionLifecycle.dispatchSessionDeleted(sessionID);
|
|
37879
|
+
}
|
|
37405
37880
|
companionManager.onSessionDeleted(sessionID);
|
|
37406
|
-
}
|
|
37407
|
-
if (input.event.type === "session.deleted") {
|
|
37408
|
-
const props = input.event.properties;
|
|
37409
|
-
const sessionID = props?.info?.id ?? props?.sessionID;
|
|
37410
37881
|
if (depthTracker && sessionID) {
|
|
37411
37882
|
depthTracker.cleanup(sessionID);
|
|
37412
37883
|
}
|
|
@@ -37416,7 +37887,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
37416
37887
|
}
|
|
37417
37888
|
},
|
|
37418
37889
|
"tool.execute.before": async (input, output) => {
|
|
37419
|
-
await
|
|
37890
|
+
await applyPatch["tool.execute.before"](input, output);
|
|
37420
37891
|
await taskSessionManagerHook["tool.execute.before"](input, output);
|
|
37421
37892
|
},
|
|
37422
37893
|
"command.execute.before": async (input, output) => {
|
|
@@ -37454,6 +37925,7 @@ var OhMyOpenCodeLite = async (ctx) => {
|
|
|
37454
37925
|
${output.system[0]}` : "");
|
|
37455
37926
|
}
|
|
37456
37927
|
}
|
|
37928
|
+
await postFileToolNudge["experimental.chat.system.transform"](input, output);
|
|
37457
37929
|
collapseSystemInPlace(output.system);
|
|
37458
37930
|
},
|
|
37459
37931
|
"experimental.chat.messages.transform": async (input, output) => {
|
|
@@ -37475,29 +37947,15 @@ ${output.system[0]}` : "");
|
|
|
37475
37947
|
disabledAgents,
|
|
37476
37948
|
log
|
|
37477
37949
|
});
|
|
37950
|
+
await phaseReminder["experimental.chat.messages.transform"](input, typedOutput);
|
|
37951
|
+
await filterAvailableSkills["experimental.chat.messages.transform"](input, typedOutput);
|
|
37478
37952
|
await taskSessionManagerHook["experimental.chat.messages.transform"](input, typedOutput);
|
|
37479
|
-
await phaseReminderHook["experimental.chat.messages.transform"](input, typedOutput);
|
|
37480
|
-
await filterAvailableSkillsHook["experimental.chat.messages.transform"](input, typedOutput);
|
|
37481
37953
|
},
|
|
37482
37954
|
"tool.execute.after": async (input, output) => {
|
|
37483
|
-
|
|
37484
|
-
|
|
37485
|
-
|
|
37486
|
-
|
|
37487
|
-
} catch (error) {
|
|
37488
|
-
log("[plugin] post-tool hook failed open", {
|
|
37489
|
-
hook: name,
|
|
37490
|
-
tool: meta.tool,
|
|
37491
|
-
sessionID: meta.sessionID,
|
|
37492
|
-
callID: meta.callID,
|
|
37493
|
-
error: error instanceof Error ? error.message : String(error)
|
|
37494
|
-
});
|
|
37495
|
-
}
|
|
37496
|
-
};
|
|
37497
|
-
await runPostToolHook("delegate-task-retry", () => delegateTaskRetryHook["tool.execute.after"](input, output));
|
|
37498
|
-
await runPostToolHook("json-error-recovery", () => jsonErrorRecoveryHook["tool.execute.after"](input, output));
|
|
37499
|
-
await runPostToolHook("post-file-tool-nudge", () => postFileToolNudgeHook["tool.execute.after"](input, output));
|
|
37500
|
-
await runPostToolHook("task-session-manager", () => taskSessionManagerHook["tool.execute.after"](input, output));
|
|
37955
|
+
await postFileToolNudgeAfter(input, output);
|
|
37956
|
+
await delegateTaskRetryAfter(input, output);
|
|
37957
|
+
await jsonErrorRecoveryAfter(input, output);
|
|
37958
|
+
await taskSessionManagerAfter(input, output);
|
|
37501
37959
|
}
|
|
37502
37960
|
};
|
|
37503
37961
|
};
|