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.
Files changed (38) hide show
  1. package/README.ja-JP.md +18 -18
  2. package/README.ko-KR.md +18 -18
  3. package/README.md +29 -18
  4. package/README.zh-CN.md +16 -16
  5. package/dist/cli/index.js +42 -11
  6. package/dist/cli/providers.d.ts +7 -7
  7. package/dist/companion/manager.d.ts +3 -0
  8. package/dist/config/constants.d.ts +2 -1
  9. package/dist/config/council-schema.d.ts +37 -12
  10. package/dist/config/schema.d.ts +13 -6
  11. package/dist/council/council-manager.d.ts +7 -3
  12. package/dist/hooks/foreground-fallback/index.d.ts +35 -1
  13. package/dist/hooks/index.d.ts +1 -0
  14. package/dist/hooks/phase-reminder/index.d.ts +3 -1
  15. package/dist/hooks/post-file-tool-nudge/index.d.ts +15 -9
  16. package/dist/hooks/session-lifecycle.d.ts +11 -0
  17. package/dist/hooks/task-session-manager/index.d.ts +14 -2
  18. package/dist/index.js +972 -514
  19. package/dist/interview/service.d.ts +2 -0
  20. package/dist/multiplexer/herdr/index.d.ts +5 -3
  21. package/dist/multiplexer/session-manager.d.ts +5 -5
  22. package/dist/multiplexer/shared.d.ts +24 -0
  23. package/dist/multiplexer/tmux/index.d.ts +0 -1
  24. package/dist/multiplexer/zellij/index.d.ts +0 -1
  25. package/dist/tools/cancel-task.d.ts +2 -2
  26. package/dist/tui.d.ts +1 -0
  27. package/dist/tui.js +45 -10
  28. package/dist/utils/background-job-board.d.ts +5 -1
  29. package/dist/utils/background-job-coordinator.d.ts +72 -0
  30. package/dist/utils/background-job-store.d.ts +46 -0
  31. package/dist/utils/councillor-models.d.ts +20 -0
  32. package/dist/utils/index.d.ts +2 -0
  33. package/dist/utils/internal-initiator.d.ts +6 -1
  34. package/dist/utils/session.d.ts +1 -1
  35. package/oh-my-opencode-slim.schema.json +13 -1
  36. package/package.json +1 -1
  37. package/src/skills/oh-my-opencode-slim/SKILL.md +3 -3
  38. 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/.pnpm/@mozilla+readability@0.6.0/node_modules/@mozilla/readability/Readability.js
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/.pnpm/@mozilla+readability@0.6.0/node_modules/@mozilla/readability/Readability-readerable.js
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/.pnpm/@mozilla+readability@0.6.0/node_modules/@mozilla/readability/index.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Event.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/UIEvent.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/MouseEvent.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/DOMException.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/config.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/utils.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/EventTarget.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/LinkedList.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NodeUtils.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Node.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NodeList.es6.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NodeList.es5.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NodeList.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/ContainerNode.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/xmlnames.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/attributes.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/FilteredElementList.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/DOMTokenList.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/select.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/ChildNode.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NonDocumentTypeChildNode.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NamedNodeMap.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Element.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Leaf.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/CharacterData.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Text.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Comment.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/DocumentFragment.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/ProcessingInstruction.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NodeFilter.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NodeTraversal.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/TreeWalker.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NodeIterator.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/URL.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/CustomEvent.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/events.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/style_parser.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/CSSStyleDeclaration.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/URLUtils.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/defineElement.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/htmlelts.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/svg.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/MutationConstants.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Document.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/DocumentType.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/HTMLParser.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/DOMImplementation.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Location.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/NavigatorID.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/WindowTimers.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/impl.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/Window.js
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/.pnpm/@mixmark-io+domino@2.2.0/node_modules/@mixmark-io/domino/lib/index.js
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/.pnpm/turndown@7.2.4/node_modules/turndown/lib/turndown.cjs.js
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
- var PHASE_REMINDER = `<internal_reminder>${PHASE_REMINDER_TEXT}</internal_reminder>`;
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
- var ModelIdSchema = z.string().regex(/^[^/\s]+\/[^\s]+$/, 'Expected provider/model format (e.g. "openai/gpt-5.4-mini")');
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: ModelIdSchema.describe('Model ID in provider/model format (e.g. "openai/gpt-5.4-mini")'),
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
- 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.")
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 when enabled."),
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 (default: "default")
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 bin = resolveCompanionBinaryPath(this.config);
20652
- if (!bin) {
20653
- const expected = this.config.binaryPath?.trim() || defaultBinaryPath();
20654
- log(`[companion] enabled but companion binary not found at expected path: ${expected}. Please install/download the companion binary separately.`);
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
- log("[companion] spawn failed", String(err));
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 statSync2,
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() - statSync2(lock).mtimeMs;
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 modelLabel = shortModelLabel(config.model);
21402
+ const models = config.models ?? normalizeCouncillorModels(config.model, config.variant);
21236
21403
  const totalAttempts = 1 + maxRetries;
21237
- for (let attempt = 1;attempt <= totalAttempts; attempt++) {
21238
- if (attempt > 1) {
21239
- log(`[council-manager] Retrying councillor "${name}" (${modelLabel}), attempt ${attempt}/${totalAttempts}`);
21240
- }
21241
- try {
21242
- const result = await this.runAgentSession({
21243
- parentSessionId,
21244
- title: `Council ${name} (${modelLabel})`,
21245
- agent: "councillor",
21246
- model: config.model,
21247
- promptText: formatCouncillorPrompt(prompt, config.prompt),
21248
- variant: config.variant,
21249
- timeout,
21250
- includeReasoning: false
21251
- });
21252
- return {
21253
- name,
21254
- model: config.model,
21255
- status: "completed",
21256
- result
21257
- };
21258
- } catch (error) {
21259
- const msg = error instanceof Error ? error.message : String(error);
21260
- const isEmptyResponse = msg.includes("Empty response from provider");
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: config.model,
21266
- status: msg.includes("timed out") ? "timed_out" : "failed",
21267
- error: `Councillor "${name}": ${msg}`
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: config.model,
21275
- status: "failed",
21276
- error: `Councillor "${name}": max retries exhausted`
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: ${singleLine(job.resultSummary)}`);
25096
+ lines.push(` Result: ${promptSafe(job.resultSummary)}`);
24918
25097
  } else if (job.lastStatusError && job.statusUncertain) {
24919
- lines.push(` Status: ${singleLine(job.lastStatusError)}`);
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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
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 hasInternalInitiatorMarker(part) {
25258
+ function isInternalInitiatorPart(part) {
24949
25259
  if (!isRecord(part) || part.type !== "text") {
24950
25260
  return false;
24951
25261
  }
24952
- if (typeof part.text !== "string") {
25262
+ if (part.synthetic !== true || !isRecord(part.metadata)) {
24953
25263
  return false;
24954
25264
  }
24955
- return part.text.includes(SLIM_INTERNAL_INITIATOR_MARKER);
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(hasInternalInitiatorMarker);
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
- constructor(client, chains, enabled) {
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
- await this.tryFallback(sessionID);
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
- await this.tryFallback(props.sessionID);
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 ?? props?.sessionID;
25695
+ const id = props?.info?.id || props?.sessionID;
25359
25696
  if (id) {
25360
- this.sessionModel.delete(id);
25361
- this.sessionAgent.delete(id);
25362
- this.sessionTried.delete(id);
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
- } catch (error) {
25460
- log("[foreground-fallback] abort did not complete cleanly", {
25461
- sessionID,
25462
- error: error instanceof Error ? error.message : String(error)
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 statSync4,
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 - statSync4(fp).mtimeMs > maxAge)
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 - statSync4(fp).mtimeMs > maxAge) {
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
- function createPhaseReminderHook() {
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 originalText = lastUserMessage.parts[textPartIndex].text ?? "";
25849
- if (originalText.includes(SLIM_INTERNAL_INITIATOR_MARKER)) {
26222
+ const originalPart = lastUserMessage.parts[textPartIndex];
26223
+ if (isInternalInitiatorPart(originalPart)) {
25850
26224
  return;
25851
26225
  }
25852
- if (lastUserMessage.parts.some((p) => p.text?.includes(PHASE_REMINDER))) {
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
- text: PHASE_REMINDER
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
- function appendReminder(output) {
25866
- if (typeof output.output !== "string") {
25867
- return;
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, output) => {
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
- appendReminder(output);
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 BACKGROUND_JOB_BOARD_SENTINEL = "SENTINEL: background-job-board-v2";
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 || !options.shouldManageSession(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
- continue;
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.text?.includes(SLIM_INTERNAL_INITIATOR_MARKER))
26890
+ if (isInternalInitiatorPart(textPart)) {
26435
26891
  return;
26436
- if (textPart.text?.includes(BACKGROUND_JOB_BOARD_SENTINEL))
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
- textPart.text = [textPart.text ?? "", "", reminders.join(`
26897
+ const boardPart = {
26898
+ type: "text",
26899
+ synthetic: true,
26900
+ text: reminders.join(`
26440
26901
 
26441
- `)].join(`
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 ?? input.event.properties?.sessionID;
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
- reconcileInjectedTerminalJobs(sessionId2);
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 ?? input.event.properties?.sessionID;
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 ?? input.event.properties?.sessionID;
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 ?? input.event.properties?.sessionID;
27008
+ const sessionId = input.event.properties?.info?.id || input.event.properties?.sessionID;
26510
27009
  if (!sessionId)
26511
27010
  return;
26512
- log("[task-session-manager] session.deleted observed; clearing job state", {
26513
- sessionID: sessionId,
26514
- deletedJob: (() => {
26515
- const record = backgroundJobBoard.get(sessionId);
26516
- return record ? {
26517
- state: record.state,
26518
- parentSessionID: record.parentSessionID,
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) => hasInternalInitiatorMarker(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 this.findBinary();
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 splitArgs = [
31606
- herdr,
31607
- "pane",
31608
- "split",
31609
- ...this.targetPaneArg(),
31610
- "--direction",
31611
- this.paneDirection,
31612
- "--cwd",
31613
- directory,
31614
- "--no-focus"
31615
- ];
31616
- log("[herdr] spawnPane: splitting pane", { args: splitArgs });
31617
- const splitProc = crossSpawn(splitArgs, {
31618
- stdout: "pipe",
31619
- stderr: "pipe"
31620
- });
31621
- const splitExitCode = await splitProc.exited;
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: splitStdout.trim()
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, directory);
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
- if (!herdr) {
31669
- log("[herdr] closePane: herdr binary not found");
31670
- return false;
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
- try {
31673
- log("[herdr] closePane: sending Ctrl+C", { paneId });
31674
- await crossSpawn([herdr, "pane", "send-keys", paneId, "ctrl+c"], {
31675
- stdout: "ignore",
31676
- stderr: "ignore"
31677
- }).exited;
31678
- await new Promise((r) => setTimeout(r, 250));
31679
- log("[herdr] closePane: closing pane", { paneId });
31680
- const proc = crossSpawn([herdr, "pane", "close", paneId], {
31681
- stdout: "pipe",
31682
- stderr: "pipe"
31683
- });
31684
- const exitCode = await proc.exited;
31685
- const stderr = await proc.stderr();
31686
- log("[herdr] closePane: result", { exitCode, stderr: stderr.trim() });
31687
- if (exitCode === 0 || exitCode === 1) {
31688
- return true;
31689
- }
31690
- log("[herdr] closePane: failed (pane may already be closed)", {
31691
- paneId
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 false;
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 this.findBinary();
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 quotedDirectory = quoteShellArg2(directory);
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
- if (!tmux) {
31870
- log("[tmux] closePane: tmux binary not found");
31871
- return false;
31872
- }
31873
- try {
31874
- log("[tmux] closePane: sending Ctrl+C", { paneId });
31875
- const ctrlCProc = crossSpawn([tmux, "send-keys", "-t", paneId, "C-c"], {
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 this.findBinary();
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 = buildOpencodeAttachCommand2(sessionId, serverUrl, directory);
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 = buildOpencodeAttachCommand2(sessionId, serverUrl, directory);
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 = buildOpencodeAttachCommand2(sessionId, serverUrl, directory);
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
- if (!zellij)
32329
- return false;
32330
- try {
32331
- await crossSpawn([zellij, "action", "write", "--pane-id", paneId, "\x03"], {
32332
- stdout: "ignore",
32333
- stderr: "ignore"
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", quoteShellArg3(command)].join(" ");
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.deferredIdleCloses.delete(sessionId);
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.deferredIdleCloses.delete(sessionId);
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.deferredIdleCloses.delete(sessionId);
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.isRunningBackgroundJob(sessionId)) {
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.deferredIdleCloses.delete(sessionId);
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 ?? event.properties?.sessionID;
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
- isRunningBackgroundJob(sessionId) {
32916
- return this.backgroundJobBoard?.isRunning(sessionId) ?? false;
33357
+ shouldCloseNow(sessionId) {
33358
+ return this.backgroundJobBoard?.deferIfRunning(sessionId) ?? true;
32917
33359
  }
32918
- async retryDeferredIdleClose(sessionId) {
33360
+ async closeSessionFromCoordinator(sessionId) {
32919
33361
  if (!this.enabled)
32920
33362
  return;
32921
- if (!this.deferredIdleCloses.has(sessionId))
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: "bash",
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: "bash",
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 statSync5 } from "node:fs";
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 statSync5(filePath).size > MIN_BINARY_SIZE;
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('Council preset to use (default: "default"). Must match a preset in the council config.')
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 postFileToolNudgeHook;
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
- multiplexerSessionManager = new MultiplexerSessionManager(ctx, multiplexerConfig, backgroundJobBoard);
37059
- backgroundJobBoard.addTerminalStateListener((taskID) => {
37060
- multiplexerSessionManager.retryDeferredIdleClose(taskID);
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
- delegateTaskRetryHook = createDelegateTaskRetryHook(ctx);
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 ?? props?.sessionID;
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 applyPatchHook["tool.execute.before"](input, output);
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
- const meta = input;
37484
- const runPostToolHook = async (name, fn) => {
37485
- try {
37486
- await fn();
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
  };