switchroom 0.15.44 → 0.16.4

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 (150) hide show
  1. package/dist/agent-scheduler/index.js +122 -88
  2. package/dist/auth-broker/index.js +463 -177
  3. package/dist/cli/autoaccept-poll.js +4842 -35
  4. package/dist/cli/drive-write-pretool.mjs +17 -14
  5. package/dist/cli/notion-write-pretool.mjs +117 -86
  6. package/dist/cli/self-improve-apply-guard-pretool.mjs +626 -0
  7. package/dist/cli/self-improve-stop.mjs +428 -0
  8. package/dist/cli/skill-validate-pretool.mjs +72 -72
  9. package/dist/cli/switchroom.js +3249 -1241
  10. package/dist/cli/ui/index.html +1 -1
  11. package/dist/host-control/main.js +2833 -355
  12. package/dist/vault/approvals/kernel-server.js +7482 -7439
  13. package/dist/vault/broker/server.js +11315 -11272
  14. package/examples/minimal.yaml +1 -0
  15. package/examples/switchroom.yaml +1 -0
  16. package/package.json +3 -3
  17. package/profiles/_base/start.sh.hbs +88 -1
  18. package/profiles/_shared/execution-discipline.md.hbs +18 -0
  19. package/profiles/default/CLAUDE.md.hbs +3 -22
  20. package/telegram-plugin/.claude-plugin/plugin.json +2 -2
  21. package/telegram-plugin/answer-stream-flag.ts +12 -49
  22. package/telegram-plugin/answer-stream.ts +5 -150
  23. package/telegram-plugin/auth-snapshot-format.ts +280 -48
  24. package/telegram-plugin/auto-fallback-fleet.ts +44 -1
  25. package/telegram-plugin/context-exhaustion.ts +12 -0
  26. package/telegram-plugin/demo-mask.ts +154 -0
  27. package/telegram-plugin/dist/bridge/bridge.js +167 -124
  28. package/telegram-plugin/dist/gateway/gateway.js +3039 -1159
  29. package/telegram-plugin/dist/server.js +215 -172
  30. package/telegram-plugin/docs/waiting-ux-spec.md +2 -2
  31. package/telegram-plugin/draft-stream.ts +47 -410
  32. package/telegram-plugin/final-answer-detect.ts +17 -12
  33. package/telegram-plugin/fleet-fallback-resume.ts +131 -0
  34. package/telegram-plugin/format.ts +56 -19
  35. package/telegram-plugin/gateway/auth-add-flow.ts +332 -127
  36. package/telegram-plugin/gateway/auth-broker-client.ts +2 -2
  37. package/telegram-plugin/gateway/auth-command.ts +70 -14
  38. package/telegram-plugin/gateway/clean-shutdown-marker.ts +44 -0
  39. package/telegram-plugin/gateway/config-approval-handler.test.ts +91 -4
  40. package/telegram-plugin/gateway/config-approval-handler.ts +94 -13
  41. package/telegram-plugin/gateway/current-turn-map.ts +188 -0
  42. package/telegram-plugin/gateway/disconnect-flush.ts +3 -1
  43. package/telegram-plugin/gateway/effort-command.ts +8 -3
  44. package/telegram-plugin/gateway/emission-authority.ts +369 -0
  45. package/telegram-plugin/gateway/feed-open-gate.ts +292 -0
  46. package/telegram-plugin/gateway/gateway.ts +1837 -291
  47. package/telegram-plugin/gateway/inject-handler.test.ts +2 -1
  48. package/telegram-plugin/gateway/ms365-write-approval.test.ts +4 -4
  49. package/telegram-plugin/gateway/represent-guard.ts +72 -0
  50. package/telegram-plugin/gateway/status-surface-log.test.ts +5 -4
  51. package/telegram-plugin/gateway/status-surface-log.ts +14 -3
  52. package/telegram-plugin/history.ts +33 -11
  53. package/telegram-plugin/hooks/repo-context-pretool.mjs +26 -0
  54. package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +5 -0
  55. package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +8 -0
  56. package/telegram-plugin/hooks/tool-label-pretool.mjs +39 -15
  57. package/telegram-plugin/issues-card.ts +4 -0
  58. package/telegram-plugin/model-unavailable.ts +124 -0
  59. package/telegram-plugin/narrative-dedup.ts +69 -0
  60. package/telegram-plugin/over-ping-safety-net.ts +70 -4
  61. package/telegram-plugin/package.json +3 -3
  62. package/telegram-plugin/pending-work-progress.ts +12 -0
  63. package/telegram-plugin/permission-rule.ts +32 -5
  64. package/telegram-plugin/permission-title.ts +152 -9
  65. package/telegram-plugin/quota-check.ts +13 -0
  66. package/telegram-plugin/quota-watch.ts +135 -7
  67. package/telegram-plugin/registry/turns-schema.test.ts +24 -0
  68. package/telegram-plugin/registry/turns-schema.ts +9 -0
  69. package/telegram-plugin/runtime-metrics.ts +13 -0
  70. package/telegram-plugin/session-tail.ts +96 -11
  71. package/telegram-plugin/silence-poke.ts +170 -24
  72. package/telegram-plugin/slot-banner-driver.ts +3 -0
  73. package/telegram-plugin/status-no-truncate.ts +44 -0
  74. package/telegram-plugin/status-reactions.ts +20 -3
  75. package/telegram-plugin/stream-controller.ts +4 -23
  76. package/telegram-plugin/stream-reply-handler.ts +6 -24
  77. package/telegram-plugin/streaming-metrics.ts +91 -0
  78. package/telegram-plugin/subagent-watcher.ts +212 -66
  79. package/telegram-plugin/tests/activity-ever-opened-sticky.test.ts +47 -0
  80. package/telegram-plugin/tests/answer-stream-dedup.test.ts +9 -26
  81. package/telegram-plugin/tests/answer-stream-flag.test.ts +25 -58
  82. package/telegram-plugin/tests/answer-stream-silent-markers.test.ts +41 -51
  83. package/telegram-plugin/tests/answer-stream.test.ts +2 -411
  84. package/telegram-plugin/tests/auth-add-flow.test.ts +488 -253
  85. package/telegram-plugin/tests/auth-command-format2.test.ts +71 -1
  86. package/telegram-plugin/tests/auth-snapshot-format.test.ts +376 -6
  87. package/telegram-plugin/tests/auto-fallback-fleet.test.ts +120 -0
  88. package/telegram-plugin/tests/cross-turn-card-gate.test.ts +424 -0
  89. package/telegram-plugin/tests/demo-mask.test.ts +127 -0
  90. package/telegram-plugin/tests/draft-stream.test.ts +0 -827
  91. package/telegram-plugin/tests/emission-authority-card-drain-gate.test.ts +236 -0
  92. package/telegram-plugin/tests/emission-authority-facade.test.ts +488 -0
  93. package/telegram-plugin/tests/emission-authority-open-gate.test.ts +179 -0
  94. package/telegram-plugin/tests/emission-authority-ping-gate.test.ts +395 -0
  95. package/telegram-plugin/tests/emission-determinism-wiring.test.ts +177 -0
  96. package/telegram-plugin/tests/feed-heartbeat-liveness-open.test.ts +146 -0
  97. package/telegram-plugin/tests/feed-open-gate.test.ts +259 -0
  98. package/telegram-plugin/tests/feed-survival.test.ts +526 -0
  99. package/telegram-plugin/tests/fleet-fallback-resume.test.ts +197 -0
  100. package/telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts +117 -0
  101. package/telegram-plugin/tests/gateway-no-reply-single-emit.test.ts +4 -11
  102. package/telegram-plugin/tests/history.test.ts +60 -0
  103. package/telegram-plugin/tests/model-unavailable.test.ts +118 -0
  104. package/telegram-plugin/tests/narrative-dedup.test.ts +118 -0
  105. package/telegram-plugin/tests/orphaned-reply-rearm.test.ts +285 -0
  106. package/telegram-plugin/tests/over-ping-final-answer-decoupling.test.ts +194 -0
  107. package/telegram-plugin/tests/over-ping-safety-net.test.ts +2 -2
  108. package/telegram-plugin/tests/per-topic-current-turn.test.ts +373 -0
  109. package/telegram-plugin/tests/permission-card-origin-kill-switch.test.ts +42 -0
  110. package/telegram-plugin/tests/permission-rule.test.ts +17 -0
  111. package/telegram-plugin/tests/permission-title.test.ts +206 -17
  112. package/telegram-plugin/tests/quota-watch.test.ts +252 -9
  113. package/telegram-plugin/tests/reply-terminal-reaction.test.ts +6 -1
  114. package/telegram-plugin/tests/repo-context-pretool.test.ts +62 -0
  115. package/telegram-plugin/tests/represent-guard.test.ts +162 -0
  116. package/telegram-plugin/tests/session-tail.test.ts +147 -3
  117. package/telegram-plugin/tests/silence-liveness-wiring.test.ts +18 -0
  118. package/telegram-plugin/tests/status-card-budget-parity.test.ts +72 -0
  119. package/telegram-plugin/tests/status-surface-log.test.ts +146 -0
  120. package/telegram-plugin/tests/subagent-watcher-clip-narrative.test.ts +58 -0
  121. package/telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts +102 -0
  122. package/telegram-plugin/tests/subagent-watcher-workflow-visibility.test.ts +225 -0
  123. package/telegram-plugin/tests/subagent-watcher.test.ts +147 -0
  124. package/telegram-plugin/tests/telegram-activity-visibility-integration.test.ts +597 -0
  125. package/telegram-plugin/tests/telegram-format.test.ts +101 -6
  126. package/telegram-plugin/tests/tool-activity-summary.test.ts +550 -15
  127. package/telegram-plugin/tests/tool-label-pretool.test.ts +73 -0
  128. package/telegram-plugin/tests/tool-label-sidecar.test.ts +44 -0
  129. package/telegram-plugin/tests/tool-labels.test.ts +67 -0
  130. package/telegram-plugin/tests/turn-liveness-floor.test.ts +196 -0
  131. package/telegram-plugin/tests/turn-liveness-invariant.test.ts +340 -0
  132. package/telegram-plugin/tests/welcome-text.test.ts +32 -3
  133. package/telegram-plugin/tests/worker-activity-feed.test.ts +470 -22
  134. package/telegram-plugin/tool-activity-summary.ts +375 -58
  135. package/telegram-plugin/turn-liveness-floor.ts +240 -0
  136. package/telegram-plugin/uat/assertions.ts +115 -0
  137. package/telegram-plugin/uat/driver.ts +68 -0
  138. package/telegram-plugin/uat/scenarios/bg-sub-agent-dispatch-dm.test.ts +119 -133
  139. package/telegram-plugin/uat/scenarios/jtbd-answer-pings.test.ts +94 -0
  140. package/telegram-plugin/uat/scenarios/jtbd-cross-turn-card-dm.test.ts +109 -0
  141. package/telegram-plugin/uat/scenarios/jtbd-foreground-feed-thinkgap-dm.test.ts +478 -0
  142. package/telegram-plugin/uat/scenarios/jtbd-foreground-feed-visibility-dm.test.ts +396 -0
  143. package/telegram-plugin/uat/scenarios/jtbd-liveness-feed-open-dm.test.ts +202 -0
  144. package/telegram-plugin/uat/scenarios/jtbd-reply-is-last-dm.test.ts +202 -0
  145. package/telegram-plugin/uat/scenarios/reactions-dm.test.ts +93 -87
  146. package/telegram-plugin/welcome-text.ts +13 -1
  147. package/telegram-plugin/worker-activity-feed.ts +157 -82
  148. package/telegram-plugin/draft-transport.ts +0 -122
  149. package/telegram-plugin/tests/draft-retirement-wiring.test.ts +0 -82
  150. package/telegram-plugin/tests/draft-transport.test.ts +0 -211
@@ -16,7 +16,7 @@ var __export = (target, all) => {
16
16
  };
17
17
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
18
18
 
19
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js
19
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js
20
20
  var require_identity = __commonJS((exports) => {
21
21
  var ALIAS = Symbol.for("yaml.alias");
22
22
  var DOC = Symbol.for("yaml.document");
@@ -70,7 +70,7 @@ var require_identity = __commonJS((exports) => {
70
70
  exports.isSeq = isSeq;
71
71
  });
72
72
 
73
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/visit.js
73
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/visit.js
74
74
  var require_visit = __commonJS((exports) => {
75
75
  var identity = require_identity();
76
76
  var BREAK = Symbol("break visit");
@@ -225,7 +225,7 @@ var require_visit = __commonJS((exports) => {
225
225
  exports.visitAsync = visitAsync;
226
226
  });
227
227
 
228
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/directives.js
228
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/directives.js
229
229
  var require_directives = __commonJS((exports) => {
230
230
  var identity = require_identity();
231
231
  var visit = require_visit();
@@ -377,7 +377,7 @@ var require_directives = __commonJS((exports) => {
377
377
  exports.Directives = Directives;
378
378
  });
379
379
 
380
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/anchors.js
380
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/anchors.js
381
381
  var require_anchors = __commonJS((exports) => {
382
382
  var identity = require_identity();
383
383
  var visit = require_visit();
@@ -439,7 +439,7 @@ var require_anchors = __commonJS((exports) => {
439
439
  exports.findNewAnchor = findNewAnchor;
440
440
  });
441
441
 
442
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/applyReviver.js
442
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/applyReviver.js
443
443
  var require_applyReviver = __commonJS((exports) => {
444
444
  function applyReviver(reviver, obj, key, val) {
445
445
  if (val && typeof val === "object") {
@@ -486,7 +486,7 @@ var require_applyReviver = __commonJS((exports) => {
486
486
  exports.applyReviver = applyReviver;
487
487
  });
488
488
 
489
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/toJS.js
489
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/toJS.js
490
490
  var require_toJS = __commonJS((exports) => {
491
491
  var identity = require_identity();
492
492
  function toJS(value, arg, ctx) {
@@ -513,7 +513,7 @@ var require_toJS = __commonJS((exports) => {
513
513
  exports.toJS = toJS;
514
514
  });
515
515
 
516
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Node.js
516
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Node.js
517
517
  var require_Node = __commonJS((exports) => {
518
518
  var applyReviver = require_applyReviver();
519
519
  var identity = require_identity();
@@ -550,7 +550,7 @@ var require_Node = __commonJS((exports) => {
550
550
  exports.NodeBase = NodeBase;
551
551
  });
552
552
 
553
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Alias.js
553
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Alias.js
554
554
  var require_Alias = __commonJS((exports) => {
555
555
  var anchors = require_anchors();
556
556
  var visit = require_visit();
@@ -658,7 +658,7 @@ var require_Alias = __commonJS((exports) => {
658
658
  exports.Alias = Alias;
659
659
  });
660
660
 
661
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Scalar.js
661
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Scalar.js
662
662
  var require_Scalar = __commonJS((exports) => {
663
663
  var identity = require_identity();
664
664
  var Node = require_Node();
@@ -686,7 +686,7 @@ var require_Scalar = __commonJS((exports) => {
686
686
  exports.isScalarValue = isScalarValue;
687
687
  });
688
688
 
689
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/createNode.js
689
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/createNode.js
690
690
  var require_createNode = __commonJS((exports) => {
691
691
  var Alias = require_Alias();
692
692
  var identity = require_identity();
@@ -758,7 +758,7 @@ var require_createNode = __commonJS((exports) => {
758
758
  exports.createNode = createNode;
759
759
  });
760
760
 
761
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Collection.js
761
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Collection.js
762
762
  var require_Collection = __commonJS((exports) => {
763
763
  var createNode = require_createNode();
764
764
  var identity = require_identity();
@@ -873,7 +873,7 @@ var require_Collection = __commonJS((exports) => {
873
873
  exports.isEmptyPath = isEmptyPath;
874
874
  });
875
875
 
876
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyComment.js
876
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyComment.js
877
877
  var require_stringifyComment = __commonJS((exports) => {
878
878
  var stringifyComment = (str) => str.replace(/^(?!$)(?: $)?/gm, "#");
879
879
  function indentComment(comment, indent) {
@@ -890,7 +890,7 @@ var require_stringifyComment = __commonJS((exports) => {
890
890
  exports.stringifyComment = stringifyComment;
891
891
  });
892
892
 
893
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/foldFlowLines.js
893
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/foldFlowLines.js
894
894
  var require_foldFlowLines = __commonJS((exports) => {
895
895
  var FOLD_FLOW = "flow";
896
896
  var FOLD_BLOCK = "block";
@@ -1027,7 +1027,7 @@ ${indent}${text.slice(fold + 1, end2)}`;
1027
1027
  exports.foldFlowLines = foldFlowLines;
1028
1028
  });
1029
1029
 
1030
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyString.js
1030
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyString.js
1031
1031
  var require_stringifyString = __commonJS((exports) => {
1032
1032
  var Scalar = require_Scalar();
1033
1033
  var foldFlowLines = require_foldFlowLines();
@@ -1325,7 +1325,7 @@ ${indent}`);
1325
1325
  exports.stringifyString = stringifyString;
1326
1326
  });
1327
1327
 
1328
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringify.js
1328
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringify.js
1329
1329
  var require_stringify = __commonJS((exports) => {
1330
1330
  var anchors = require_anchors();
1331
1331
  var identity = require_identity();
@@ -1446,7 +1446,7 @@ ${ctx.indent}${str}`;
1446
1446
  exports.stringify = stringify;
1447
1447
  });
1448
1448
 
1449
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyPair.js
1449
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyPair.js
1450
1450
  var require_stringifyPair = __commonJS((exports) => {
1451
1451
  var identity = require_identity();
1452
1452
  var Scalar = require_Scalar();
@@ -1582,7 +1582,7 @@ ${ctx.indent}`;
1582
1582
  exports.stringifyPair = stringifyPair;
1583
1583
  });
1584
1584
 
1585
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/log.js
1585
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/log.js
1586
1586
  var require_log = __commonJS((exports) => {
1587
1587
  var node_process = __require("process");
1588
1588
  function debug(logLevel, ...messages) {
@@ -1601,7 +1601,7 @@ var require_log = __commonJS((exports) => {
1601
1601
  exports.warn = warn;
1602
1602
  });
1603
1603
 
1604
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/merge.js
1604
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/merge.js
1605
1605
  var require_merge = __commonJS((exports) => {
1606
1606
  var identity = require_identity();
1607
1607
  var Scalar = require_Scalar();
@@ -1655,7 +1655,7 @@ var require_merge = __commonJS((exports) => {
1655
1655
  exports.merge = merge;
1656
1656
  });
1657
1657
 
1658
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/addPairToJSMap.js
1658
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/addPairToJSMap.js
1659
1659
  var require_addPairToJSMap = __commonJS((exports) => {
1660
1660
  var log = require_log();
1661
1661
  var merge = require_merge();
@@ -1716,7 +1716,7 @@ var require_addPairToJSMap = __commonJS((exports) => {
1716
1716
  exports.addPairToJSMap = addPairToJSMap;
1717
1717
  });
1718
1718
 
1719
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Pair.js
1719
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/Pair.js
1720
1720
  var require_Pair = __commonJS((exports) => {
1721
1721
  var createNode = require_createNode();
1722
1722
  var stringifyPair = require_stringifyPair();
@@ -1754,7 +1754,7 @@ var require_Pair = __commonJS((exports) => {
1754
1754
  exports.createPair = createPair;
1755
1755
  });
1756
1756
 
1757
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyCollection.js
1757
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyCollection.js
1758
1758
  var require_stringifyCollection = __commonJS((exports) => {
1759
1759
  var identity = require_identity();
1760
1760
  var stringify = require_stringify();
@@ -1906,7 +1906,7 @@ ${indent}${end}`;
1906
1906
  exports.stringifyCollection = stringifyCollection;
1907
1907
  });
1908
1908
 
1909
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/YAMLMap.js
1909
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/YAMLMap.js
1910
1910
  var require_YAMLMap = __commonJS((exports) => {
1911
1911
  var stringifyCollection = require_stringifyCollection();
1912
1912
  var addPairToJSMap = require_addPairToJSMap();
@@ -2033,7 +2033,7 @@ var require_YAMLMap = __commonJS((exports) => {
2033
2033
  exports.findPair = findPair;
2034
2034
  });
2035
2035
 
2036
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/map.js
2036
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/map.js
2037
2037
  var require_map = __commonJS((exports) => {
2038
2038
  var identity = require_identity();
2039
2039
  var YAMLMap = require_YAMLMap();
@@ -2052,7 +2052,7 @@ var require_map = __commonJS((exports) => {
2052
2052
  exports.map = map;
2053
2053
  });
2054
2054
 
2055
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/YAMLSeq.js
2055
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/YAMLSeq.js
2056
2056
  var require_YAMLSeq = __commonJS((exports) => {
2057
2057
  var createNode = require_createNode();
2058
2058
  var stringifyCollection = require_stringifyCollection();
@@ -2145,7 +2145,7 @@ var require_YAMLSeq = __commonJS((exports) => {
2145
2145
  exports.YAMLSeq = YAMLSeq;
2146
2146
  });
2147
2147
 
2148
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/seq.js
2148
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/seq.js
2149
2149
  var require_seq = __commonJS((exports) => {
2150
2150
  var identity = require_identity();
2151
2151
  var YAMLSeq = require_YAMLSeq();
@@ -2164,7 +2164,7 @@ var require_seq = __commonJS((exports) => {
2164
2164
  exports.seq = seq;
2165
2165
  });
2166
2166
 
2167
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/string.js
2167
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/string.js
2168
2168
  var require_string = __commonJS((exports) => {
2169
2169
  var stringifyString = require_stringifyString();
2170
2170
  var string = {
@@ -2180,7 +2180,7 @@ var require_string = __commonJS((exports) => {
2180
2180
  exports.string = string;
2181
2181
  });
2182
2182
 
2183
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/null.js
2183
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/common/null.js
2184
2184
  var require_null = __commonJS((exports) => {
2185
2185
  var Scalar = require_Scalar();
2186
2186
  var nullTag = {
@@ -2195,7 +2195,7 @@ var require_null = __commonJS((exports) => {
2195
2195
  exports.nullTag = nullTag;
2196
2196
  });
2197
2197
 
2198
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/bool.js
2198
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/bool.js
2199
2199
  var require_bool = __commonJS((exports) => {
2200
2200
  var Scalar = require_Scalar();
2201
2201
  var boolTag = {
@@ -2216,7 +2216,7 @@ var require_bool = __commonJS((exports) => {
2216
2216
  exports.boolTag = boolTag;
2217
2217
  });
2218
2218
 
2219
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyNumber.js
2219
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyNumber.js
2220
2220
  var require_stringifyNumber = __commonJS((exports) => {
2221
2221
  function stringifyNumber({ format, minFractionDigits, tag, value }) {
2222
2222
  if (typeof value === "bigint")
@@ -2240,7 +2240,7 @@ var require_stringifyNumber = __commonJS((exports) => {
2240
2240
  exports.stringifyNumber = stringifyNumber;
2241
2241
  });
2242
2242
 
2243
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/float.js
2243
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/float.js
2244
2244
  var require_float = __commonJS((exports) => {
2245
2245
  var Scalar = require_Scalar();
2246
2246
  var stringifyNumber = require_stringifyNumber();
@@ -2283,7 +2283,7 @@ var require_float = __commonJS((exports) => {
2283
2283
  exports.floatNaN = floatNaN;
2284
2284
  });
2285
2285
 
2286
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/int.js
2286
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/int.js
2287
2287
  var require_int = __commonJS((exports) => {
2288
2288
  var stringifyNumber = require_stringifyNumber();
2289
2289
  var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
@@ -2325,7 +2325,7 @@ var require_int = __commonJS((exports) => {
2325
2325
  exports.intOct = intOct;
2326
2326
  });
2327
2327
 
2328
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/schema.js
2328
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/core/schema.js
2329
2329
  var require_schema = __commonJS((exports) => {
2330
2330
  var map = require_map();
2331
2331
  var _null = require_null();
@@ -2350,7 +2350,7 @@ var require_schema = __commonJS((exports) => {
2350
2350
  exports.schema = schema;
2351
2351
  });
2352
2352
 
2353
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/json/schema.js
2353
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/json/schema.js
2354
2354
  var require_schema2 = __commonJS((exports) => {
2355
2355
  var Scalar = require_Scalar();
2356
2356
  var map = require_map();
@@ -2414,7 +2414,7 @@ var require_schema2 = __commonJS((exports) => {
2414
2414
  exports.schema = schema;
2415
2415
  });
2416
2416
 
2417
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/binary.js
2417
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/binary.js
2418
2418
  var require_binary = __commonJS((exports) => {
2419
2419
  var node_buffer = __require("buffer");
2420
2420
  var Scalar = require_Scalar();
@@ -2469,7 +2469,7 @@ var require_binary = __commonJS((exports) => {
2469
2469
  exports.binary = binary;
2470
2470
  });
2471
2471
 
2472
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/pairs.js
2472
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/pairs.js
2473
2473
  var require_pairs = __commonJS((exports) => {
2474
2474
  var identity = require_identity();
2475
2475
  var Pair = require_Pair();
@@ -2544,7 +2544,7 @@ ${cn.comment}` : item.comment;
2544
2544
  exports.resolvePairs = resolvePairs;
2545
2545
  });
2546
2546
 
2547
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/omap.js
2547
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/omap.js
2548
2548
  var require_omap = __commonJS((exports) => {
2549
2549
  var identity = require_identity();
2550
2550
  var toJS = require_toJS();
@@ -2616,7 +2616,7 @@ var require_omap = __commonJS((exports) => {
2616
2616
  exports.omap = omap;
2617
2617
  });
2618
2618
 
2619
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/bool.js
2619
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/bool.js
2620
2620
  var require_bool2 = __commonJS((exports) => {
2621
2621
  var Scalar = require_Scalar();
2622
2622
  function boolStringify({ value, source }, ctx) {
@@ -2645,7 +2645,7 @@ var require_bool2 = __commonJS((exports) => {
2645
2645
  exports.trueTag = trueTag;
2646
2646
  });
2647
2647
 
2648
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/float.js
2648
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/float.js
2649
2649
  var require_float2 = __commonJS((exports) => {
2650
2650
  var Scalar = require_Scalar();
2651
2651
  var stringifyNumber = require_stringifyNumber();
@@ -2691,7 +2691,7 @@ var require_float2 = __commonJS((exports) => {
2691
2691
  exports.floatNaN = floatNaN;
2692
2692
  });
2693
2693
 
2694
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/int.js
2694
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/int.js
2695
2695
  var require_int2 = __commonJS((exports) => {
2696
2696
  var stringifyNumber = require_stringifyNumber();
2697
2697
  var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
@@ -2767,7 +2767,7 @@ var require_int2 = __commonJS((exports) => {
2767
2767
  exports.intOct = intOct;
2768
2768
  });
2769
2769
 
2770
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/set.js
2770
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/set.js
2771
2771
  var require_set = __commonJS((exports) => {
2772
2772
  var identity = require_identity();
2773
2773
  var Pair = require_Pair();
@@ -2850,7 +2850,7 @@ var require_set = __commonJS((exports) => {
2850
2850
  exports.set = set;
2851
2851
  });
2852
2852
 
2853
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js
2853
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js
2854
2854
  var require_timestamp = __commonJS((exports) => {
2855
2855
  var stringifyNumber = require_stringifyNumber();
2856
2856
  function parseSexagesimal(str, asBigInt) {
@@ -2932,7 +2932,7 @@ var require_timestamp = __commonJS((exports) => {
2932
2932
  exports.timestamp = timestamp;
2933
2933
  });
2934
2934
 
2935
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/schema.js
2935
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/yaml-1.1/schema.js
2936
2936
  var require_schema3 = __commonJS((exports) => {
2937
2937
  var map = require_map();
2938
2938
  var _null = require_null();
@@ -2973,7 +2973,7 @@ var require_schema3 = __commonJS((exports) => {
2973
2973
  exports.schema = schema;
2974
2974
  });
2975
2975
 
2976
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/tags.js
2976
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/tags.js
2977
2977
  var require_tags = __commonJS((exports) => {
2978
2978
  var map = require_map();
2979
2979
  var _null = require_null();
@@ -3064,7 +3064,7 @@ var require_tags = __commonJS((exports) => {
3064
3064
  exports.getTags = getTags;
3065
3065
  });
3066
3066
 
3067
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/Schema.js
3067
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/schema/Schema.js
3068
3068
  var require_Schema = __commonJS((exports) => {
3069
3069
  var identity = require_identity();
3070
3070
  var map = require_map();
@@ -3094,7 +3094,7 @@ var require_Schema = __commonJS((exports) => {
3094
3094
  exports.Schema = Schema;
3095
3095
  });
3096
3096
 
3097
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyDocument.js
3097
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/stringify/stringifyDocument.js
3098
3098
  var require_stringifyDocument = __commonJS((exports) => {
3099
3099
  var identity = require_identity();
3100
3100
  var stringify = require_stringify();
@@ -3174,7 +3174,7 @@ var require_stringifyDocument = __commonJS((exports) => {
3174
3174
  exports.stringifyDocument = stringifyDocument;
3175
3175
  });
3176
3176
 
3177
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/Document.js
3177
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/doc/Document.js
3178
3178
  var require_Document = __commonJS((exports) => {
3179
3179
  var Alias = require_Alias();
3180
3180
  var Collection = require_Collection();
@@ -3409,7 +3409,7 @@ var require_Document = __commonJS((exports) => {
3409
3409
  exports.Document = Document;
3410
3410
  });
3411
3411
 
3412
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/errors.js
3412
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/errors.js
3413
3413
  var require_errors = __commonJS((exports) => {
3414
3414
  class YAMLError extends Error {
3415
3415
  constructor(name, pos, code, message) {
@@ -3474,7 +3474,7 @@ ${pointer}
3474
3474
  exports.prettifyError = prettifyError;
3475
3475
  });
3476
3476
 
3477
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-props.js
3477
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-props.js
3478
3478
  var require_resolve_props = __commonJS((exports) => {
3479
3479
  function resolveProps(tokens, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) {
3480
3480
  let spaceBefore = false;
@@ -3604,7 +3604,7 @@ var require_resolve_props = __commonJS((exports) => {
3604
3604
  exports.resolveProps = resolveProps;
3605
3605
  });
3606
3606
 
3607
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-contains-newline.js
3607
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-contains-newline.js
3608
3608
  var require_util_contains_newline = __commonJS((exports) => {
3609
3609
  function containsNewline(key) {
3610
3610
  if (!key)
@@ -3644,7 +3644,7 @@ var require_util_contains_newline = __commonJS((exports) => {
3644
3644
  exports.containsNewline = containsNewline;
3645
3645
  });
3646
3646
 
3647
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-flow-indent-check.js
3647
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-flow-indent-check.js
3648
3648
  var require_util_flow_indent_check = __commonJS((exports) => {
3649
3649
  var utilContainsNewline = require_util_contains_newline();
3650
3650
  function flowIndentCheck(indent, fc, onError) {
@@ -3659,7 +3659,7 @@ var require_util_flow_indent_check = __commonJS((exports) => {
3659
3659
  exports.flowIndentCheck = flowIndentCheck;
3660
3660
  });
3661
3661
 
3662
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-map-includes.js
3662
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-map-includes.js
3663
3663
  var require_util_map_includes = __commonJS((exports) => {
3664
3664
  var identity = require_identity();
3665
3665
  function mapIncludes(ctx, items, search) {
@@ -3672,7 +3672,7 @@ var require_util_map_includes = __commonJS((exports) => {
3672
3672
  exports.mapIncludes = mapIncludes;
3673
3673
  });
3674
3674
 
3675
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-block-map.js
3675
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-block-map.js
3676
3676
  var require_resolve_block_map = __commonJS((exports) => {
3677
3677
  var Pair = require_Pair();
3678
3678
  var YAMLMap = require_YAMLMap();
@@ -3779,7 +3779,7 @@ var require_resolve_block_map = __commonJS((exports) => {
3779
3779
  exports.resolveBlockMap = resolveBlockMap;
3780
3780
  });
3781
3781
 
3782
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-block-seq.js
3782
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-block-seq.js
3783
3783
  var require_resolve_block_seq = __commonJS((exports) => {
3784
3784
  var YAMLSeq = require_YAMLSeq();
3785
3785
  var resolveProps = require_resolve_props();
@@ -3827,7 +3827,7 @@ var require_resolve_block_seq = __commonJS((exports) => {
3827
3827
  exports.resolveBlockSeq = resolveBlockSeq;
3828
3828
  });
3829
3829
 
3830
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-end.js
3830
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-end.js
3831
3831
  var require_resolve_end = __commonJS((exports) => {
3832
3832
  function resolveEnd(end, offset, reqSpace, onError) {
3833
3833
  let comment = "";
@@ -3867,7 +3867,7 @@ var require_resolve_end = __commonJS((exports) => {
3867
3867
  exports.resolveEnd = resolveEnd;
3868
3868
  });
3869
3869
 
3870
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-flow-collection.js
3870
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-flow-collection.js
3871
3871
  var require_resolve_flow_collection = __commonJS((exports) => {
3872
3872
  var identity = require_identity();
3873
3873
  var Pair = require_Pair();
@@ -4058,7 +4058,7 @@ var require_resolve_flow_collection = __commonJS((exports) => {
4058
4058
  exports.resolveFlowCollection = resolveFlowCollection;
4059
4059
  });
4060
4060
 
4061
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-collection.js
4061
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-collection.js
4062
4062
  var require_compose_collection = __commonJS((exports) => {
4063
4063
  var identity = require_identity();
4064
4064
  var Scalar = require_Scalar();
@@ -4120,7 +4120,7 @@ var require_compose_collection = __commonJS((exports) => {
4120
4120
  exports.composeCollection = composeCollection;
4121
4121
  });
4122
4122
 
4123
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-block-scalar.js
4123
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-block-scalar.js
4124
4124
  var require_resolve_block_scalar = __commonJS((exports) => {
4125
4125
  var Scalar = require_Scalar();
4126
4126
  function resolveBlockScalar(ctx, scalar, onError) {
@@ -4313,7 +4313,7 @@ var require_resolve_block_scalar = __commonJS((exports) => {
4313
4313
  exports.resolveBlockScalar = resolveBlockScalar;
4314
4314
  });
4315
4315
 
4316
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-flow-scalar.js
4316
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/resolve-flow-scalar.js
4317
4317
  var require_resolve_flow_scalar = __commonJS((exports) => {
4318
4318
  var Scalar = require_Scalar();
4319
4319
  var resolveEnd = require_resolve_end();
@@ -4529,7 +4529,7 @@ var require_resolve_flow_scalar = __commonJS((exports) => {
4529
4529
  exports.resolveFlowScalar = resolveFlowScalar;
4530
4530
  });
4531
4531
 
4532
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-scalar.js
4532
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-scalar.js
4533
4533
  var require_compose_scalar = __commonJS((exports) => {
4534
4534
  var identity = require_identity();
4535
4535
  var Scalar = require_Scalar();
@@ -4607,7 +4607,7 @@ var require_compose_scalar = __commonJS((exports) => {
4607
4607
  exports.composeScalar = composeScalar;
4608
4608
  });
4609
4609
 
4610
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-empty-scalar-position.js
4610
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/util-empty-scalar-position.js
4611
4611
  var require_util_empty_scalar_position = __commonJS((exports) => {
4612
4612
  function emptyScalarPosition(offset, before, pos) {
4613
4613
  if (before) {
@@ -4634,7 +4634,7 @@ var require_util_empty_scalar_position = __commonJS((exports) => {
4634
4634
  exports.emptyScalarPosition = emptyScalarPosition;
4635
4635
  });
4636
4636
 
4637
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-node.js
4637
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-node.js
4638
4638
  var require_compose_node = __commonJS((exports) => {
4639
4639
  var Alias = require_Alias();
4640
4640
  var identity = require_identity();
@@ -4737,7 +4737,7 @@ var require_compose_node = __commonJS((exports) => {
4737
4737
  exports.composeNode = composeNode;
4738
4738
  });
4739
4739
 
4740
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-doc.js
4740
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/compose-doc.js
4741
4741
  var require_compose_doc = __commonJS((exports) => {
4742
4742
  var Document = require_Document();
4743
4743
  var composeNode = require_compose_node();
@@ -4777,7 +4777,7 @@ var require_compose_doc = __commonJS((exports) => {
4777
4777
  exports.composeDoc = composeDoc;
4778
4778
  });
4779
4779
 
4780
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/composer.js
4780
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/compose/composer.js
4781
4781
  var require_composer = __commonJS((exports) => {
4782
4782
  var node_process = __require("process");
4783
4783
  var directives = require_directives();
@@ -4966,7 +4966,7 @@ ${end.comment}` : end.comment;
4966
4966
  exports.Composer = Composer;
4967
4967
  });
4968
4968
 
4969
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst-scalar.js
4969
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst-scalar.js
4970
4970
  var require_cst_scalar = __commonJS((exports) => {
4971
4971
  var resolveBlockScalar = require_resolve_block_scalar();
4972
4972
  var resolveFlowScalar = require_resolve_flow_scalar();
@@ -5156,7 +5156,7 @@ var require_cst_scalar = __commonJS((exports) => {
5156
5156
  exports.setScalarValue = setScalarValue;
5157
5157
  });
5158
5158
 
5159
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst-stringify.js
5159
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst-stringify.js
5160
5160
  var require_cst_stringify = __commonJS((exports) => {
5161
5161
  var stringify = (cst) => ("type" in cst) ? stringifyToken(cst) : stringifyItem(cst);
5162
5162
  function stringifyToken(token) {
@@ -5214,7 +5214,7 @@ var require_cst_stringify = __commonJS((exports) => {
5214
5214
  exports.stringify = stringify;
5215
5215
  });
5216
5216
 
5217
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst-visit.js
5217
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst-visit.js
5218
5218
  var require_cst_visit = __commonJS((exports) => {
5219
5219
  var BREAK = Symbol("break visit");
5220
5220
  var SKIP = Symbol("skip children");
@@ -5273,7 +5273,7 @@ var require_cst_visit = __commonJS((exports) => {
5273
5273
  exports.visit = visit;
5274
5274
  });
5275
5275
 
5276
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst.js
5276
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/cst.js
5277
5277
  var require_cst = __commonJS((exports) => {
5278
5278
  var cstScalar = require_cst_scalar();
5279
5279
  var cstStringify = require_cst_stringify();
@@ -5374,7 +5374,7 @@ var require_cst = __commonJS((exports) => {
5374
5374
  exports.tokenType = tokenType;
5375
5375
  });
5376
5376
 
5377
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/lexer.js
5377
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/lexer.js
5378
5378
  var require_lexer = __commonJS((exports) => {
5379
5379
  var cst = require_cst();
5380
5380
  function isEmpty(ch) {
@@ -5960,7 +5960,7 @@ var require_lexer = __commonJS((exports) => {
5960
5960
  exports.Lexer = Lexer;
5961
5961
  });
5962
5962
 
5963
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/line-counter.js
5963
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/line-counter.js
5964
5964
  var require_line_counter = __commonJS((exports) => {
5965
5965
  class LineCounter {
5966
5966
  constructor() {
@@ -5988,7 +5988,7 @@ var require_line_counter = __commonJS((exports) => {
5988
5988
  exports.LineCounter = LineCounter;
5989
5989
  });
5990
5990
 
5991
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/parser.js
5991
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/parse/parser.js
5992
5992
  var require_parser = __commonJS((exports) => {
5993
5993
  var node_process = __require("process");
5994
5994
  var cst = require_cst();
@@ -6837,7 +6837,7 @@ var require_parser = __commonJS((exports) => {
6837
6837
  exports.Parser = Parser;
6838
6838
  });
6839
6839
 
6840
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/public-api.js
6840
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/public-api.js
6841
6841
  var require_public_api = __commonJS((exports) => {
6842
6842
  var composer = require_composer();
6843
6843
  var Document = require_Document();
@@ -6939,7 +6939,7 @@ import { readFileSync as readFileSync2, existsSync as existsSync3 } from "node:f
6939
6939
  import { homedir } from "node:os";
6940
6940
  import { resolve as resolve3 } from "node:path";
6941
6941
 
6942
- // node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/index.js
6942
+ // ../../switchroom/node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/index.js
6943
6943
  var composer = require_composer();
6944
6944
  var Document = require_Document();
6945
6945
  var Schema = require_Schema();
@@ -6985,7 +6985,7 @@ var $stringify = publicApi.stringify;
6985
6985
  var $visit = visit.visit;
6986
6986
  var $visitAsync = visit.visitAsync;
6987
6987
 
6988
- // node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
6988
+ // ../../switchroom/node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
6989
6989
  var exports_external = {};
6990
6990
  __export(exports_external, {
6991
6991
  void: () => voidType,
@@ -7097,7 +7097,7 @@ __export(exports_external, {
7097
7097
  BRAND: () => BRAND
7098
7098
  });
7099
7099
 
7100
- // node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
7100
+ // ../../switchroom/node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
7101
7101
  var util;
7102
7102
  (function(util2) {
7103
7103
  util2.assertEqual = (_) => {};
@@ -7228,7 +7228,7 @@ var getParsedType = (data) => {
7228
7228
  }
7229
7229
  };
7230
7230
 
7231
- // node_modules/.bun/zod@3.25.76/node_modules/zod/v3/ZodError.js
7231
+ // ../../switchroom/node_modules/.bun/zod@3.25.76/node_modules/zod/v3/ZodError.js
7232
7232
  var ZodIssueCode = util.arrayToEnum([
7233
7233
  "invalid_type",
7234
7234
  "invalid_literal",
@@ -7347,7 +7347,7 @@ ZodError.create = (issues) => {
7347
7347
  return error;
7348
7348
  };
7349
7349
 
7350
- // node_modules/.bun/zod@3.25.76/node_modules/zod/v3/locales/en.js
7350
+ // ../../switchroom/node_modules/.bun/zod@3.25.76/node_modules/zod/v3/locales/en.js
7351
7351
  var errorMap = (issue, _ctx) => {
7352
7352
  let message;
7353
7353
  switch (issue.code) {
@@ -7450,7 +7450,7 @@ var errorMap = (issue, _ctx) => {
7450
7450
  };
7451
7451
  var en_default = errorMap;
7452
7452
 
7453
- // node_modules/.bun/zod@3.25.76/node_modules/zod/v3/errors.js
7453
+ // ../../switchroom/node_modules/.bun/zod@3.25.76/node_modules/zod/v3/errors.js
7454
7454
  var overrideErrorMap = en_default;
7455
7455
  function setErrorMap(map) {
7456
7456
  overrideErrorMap = map;
@@ -7458,7 +7458,7 @@ function setErrorMap(map) {
7458
7458
  function getErrorMap() {
7459
7459
  return overrideErrorMap;
7460
7460
  }
7461
- // node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
7461
+ // ../../switchroom/node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
7462
7462
  var makeIssue = (params) => {
7463
7463
  const { data, path, errorMaps, issueData } = params;
7464
7464
  const fullPath = [...path, ...issueData.path || []];
@@ -7564,14 +7564,14 @@ var isAborted = (x) => x.status === "aborted";
7564
7564
  var isDirty = (x) => x.status === "dirty";
7565
7565
  var isValid = (x) => x.status === "valid";
7566
7566
  var isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise;
7567
- // node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
7567
+ // ../../switchroom/node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
7568
7568
  var errorUtil;
7569
7569
  (function(errorUtil2) {
7570
7570
  errorUtil2.errToObj = (message) => typeof message === "string" ? { message } : message || {};
7571
7571
  errorUtil2.toString = (message) => typeof message === "string" ? message : message?.message;
7572
7572
  })(errorUtil || (errorUtil = {}));
7573
7573
 
7574
- // node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.js
7574
+ // ../../switchroom/node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.js
7575
7575
  class ParseInputLazyPath {
7576
7576
  constructor(parent, value, path, key) {
7577
7577
  this._cachedPath = [];
@@ -11150,7 +11150,7 @@ var TelegramChannelSchema = exports_external.object({
11150
11150
  format: exports_external.enum(["html", "markdownv2", "text"]).optional().describe("Default reply format passed to the plugin"),
11151
11151
  rate_limit_ms: exports_external.number().optional().describe("Minimum delay between outgoing messages in ms"),
11152
11152
  stream_mode: exports_external.enum(["pty", "checklist"]).optional().describe("How live progress is streamed to Telegram during a turn. " + "'pty' (default) surfaces text snapshots of Claude Code's TUI — " + "compatible but can flicker as Ink re-renders. 'checklist' drives " + "a structured progress card from session-tail events — stable " + "order, per-tool status emojis, fires only on semantic transitions."),
11153
- stream_throttle_ms: exports_external.number().int().nonnegative().optional().describe("Throttle window in ms between successive stream edits (or " + "sendMessageDraft tics) during a turn. Lower = more responsive " + "stream, higher = fewer API calls. Floored at 250 by draft-stream " + "itself. Default 300 for draft transport (DMs) and 1000 for " + "message transport (groups/forums). Override per-agent if a " + "particular agent needs snappier or quieter streaming."),
11153
+ stream_throttle_ms: exports_external.number().int().nonnegative().optional().describe("Throttle window in ms between successive in-place stream edits " + "during a turn. Lower = more responsive stream, higher = fewer API " + "calls. Floored at 250 by draft-stream itself. Default 400 ms for DMs " + "and 1000 ms for groups/forums (respects Telegram's ~1 edit/sec/message " + "practical ceiling). Override per-agent if a particular agent needs " + "snappier or quieter streaming."),
11154
11154
  clear_status_on_completion: exports_external.boolean().optional().describe("When true, the live activity/status feed (the in-place 'what it's " + "doing' message — Reading X, Searching the web for Y, …) is DELETED " + "when the turn's final answer lands, so only the reply remains. " + "Default false: the status message is left in the chat as a record " + "(its last step marked done) — no post-then-delete. Per-agent " + "override; cascades defaults → profile → agent (per-key)."),
11155
11155
  hotReloadStable: exports_external.boolean().optional().describe("If true, the stable workspace prefix (AGENTS.md, SOUL.md, USER.md, " + "IDENTITY.md, TOOLS.md) is re-injected on every turn via " + "the UserPromptSubmit hook instead of baked into --append-system-prompt " + "at session start. Lets workspace edits propagate without a restart. " + "Costs ~5-10% per-turn latency/spend since the stable prefix is no " + "longer prompt-cached."),
11156
11156
  inject_on_change: exports_external.boolean().optional().describe("Context-efficiency gate for per-turn hook injection (default true). " + "When true (the default), the turn-pacing directive and dynamic " + "workspace content are only re-emitted when their content changes or " + "the session_id changes — suppressing redundant injection that " + "otherwise triples compaction frequency. Set to false to revert to " + "the legacy always-emit behaviour (every turn injects the full " + "content regardless of whether it changed)."),
@@ -11238,6 +11238,14 @@ var GoogleWorkspaceConfigSchema = exports_external.object({
11238
11238
  approvers: exports_external.array(ApproverIdSchema).min(1).describe("Array of numeric Telegram user IDs authorized to approve drive onboarding. " + "At least one must be specified."),
11239
11239
  tier: GoogleWorkspaceTierSchema.optional().describe("RFC G Phase 1: which upstream MCP tier to expose. " + "core (default) = ~16 tools (Drive+Docs+Sheets+Calendar). " + "extended = ~40 tools (+Slides, Forms, Tasks, Chat). " + "complete = ~60+ tools (+Gmail; not recommended yet — see RFC G §5).")
11240
11240
  }).optional();
11241
+ var LiteLLMConfigSchema = exports_external.object({
11242
+ enabled: exports_external.boolean().optional().describe("Opt-in toggle. When true, `switchroom apply` provisions a per-agent " + "LiteLLM virtual key and injects routing env into the container. " + "Default OFF."),
11243
+ base_url: exports_external.string().optional().describe("LiteLLM proxy base URL the agent's claude CLI routes through, e.g. " + "'http://127.0.0.1:4010'. Agents use network_mode:host, so loopback " + "reaches a host-bound proxy. Exported as ANTHROPIC_BASE_URL."),
11244
+ admin_key: exports_external.string().optional().describe("LiteLLM master/admin key used at apply time to provision the team + " + "virtual key. Supports a vault reference (e.g. " + "'vault:litellm/master-key') — resolution happens at apply time via " + "the vault-broker. Never injected into the agent container."),
11245
+ team: exports_external.string().optional().describe("LiteLLM team alias the per-agent key is created under. Defaults to " + "'switchroom' (applied in code, not as a schema default)."),
11246
+ small_fast_model: exports_external.string().optional().describe("Model id exported as ANTHROPIC_SMALL_FAST_MODEL for the claude CLI's " + "background/fast lane, e.g. 'claude-haiku-4-5-20251001'."),
11247
+ tags: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Extra key/value metadata tags attached to the provisioned LiteLLM " + "virtual key. Merged per-key across cascade layers (agent wins).")
11248
+ }).optional().describe("LiteLLM routing config — opt-in per-agent virtual-key auto-provisioning " + "+ routing env. Default OFF. See LiteLLMConfigSchema doc for the full flow.");
11241
11249
  var MicrosoftWorkspaceConfigSchema = exports_external.object({
11242
11250
  microsoft_client_id: exports_external.string().min(1).optional().describe("Microsoft OAuth application (client) ID from Entra portal " + "(literal string or vault reference e.g. " + "'vault:microsoft-oauth-client-id'). OPTIONAL — omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
11243
11251
  microsoft_client_secret: exports_external.string().min(1).optional().describe("Microsoft OAuth client secret. Optional — public-client apps " + "(Mobile + Desktop platform with 'Allow public client flows' " + "enabled) work without a secret; confidential clients pass " + "one. Either literal or vault reference e.g. " + "'vault:microsoft-oauth-client-secret'."),
@@ -11334,6 +11342,7 @@ var profileFields = {
11334
11342
  mcp_servers: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
11335
11343
  hooks: AgentHooksSchema,
11336
11344
  env: exports_external.record(exports_external.string(), exports_external.string()).optional(),
11345
+ litellm: LiteLLMConfigSchema,
11337
11346
  system_prompt_append: exports_external.string().optional(),
11338
11347
  skills: exports_external.array(exports_external.string()).optional(),
11339
11348
  bundled_skills: exports_external.record(exports_external.string(), exports_external.boolean()).optional().describe("Opt-out map for switchroom's bundled-default skills " + "(e.g. skill-creator, mcp-builder, webapp-testing, pdf, docx, " + "xlsx, pptx, switchroom-cli, switchroom-status, switchroom-health). " + "Set a key to `false` to suppress that default for this agent. " + "Cascades from defaults.bundled_skills."),
@@ -11406,6 +11415,7 @@ var AgentSchema = exports_external.object({
11406
11415
  mcp_servers: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe("Additional MCP server configurations"),
11407
11416
  hooks: AgentHooksSchema.describe("Claude Code lifecycle hooks (SessionStart, UserPromptSubmit, Stop, etc). " + "Written to settings.json.hooks in Claude Code's native shape."),
11408
11417
  env: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Environment variables exported in start.sh before claude runs"),
11418
+ litellm: LiteLLMConfigSchema.describe("Per-agent LiteLLM routing override. Presence with `enabled: true` opts " + "this agent IN to per-agent virtual-key auto-provisioning + routing env " + "(falls back to the top-level `litellm:` block for base_url/admin_key/" + "team/small_fast_model). Deep-merges one level over defaults/profile; " + "`tags` merge per-key, agent wins. Default OFF."),
11409
11419
  system_prompt_append: exports_external.string().optional().describe("Text passed via claude's --append-system-prompt flag. " + "Appended to the default or CLAUDE.md-derived system prompt."),
11410
11420
  skills: exports_external.array(exports_external.string()).optional().describe("Names of skills from switchroom.skills_dir to symlink into this " + "agent's skills/ directory. Unioned with defaults.skills."),
11411
11421
  bundled_skills: exports_external.record(exports_external.string(), exports_external.boolean()).optional().describe("Per-agent override of switchroom's bundled-default skills " + "(skill-creator, mcp-builder, webapp-testing, pdf, docx, xlsx, " + "pptx, switchroom-cli/status/health). Set a key to `false` to " + "opt out for this agent. Per-agent value wins over defaults.bundled_skills."),
@@ -11531,7 +11541,7 @@ var WebServiceConfigSchema = exports_external.object({
11531
11541
  });
11532
11542
  var HostdConfigSchema = exports_external.object({
11533
11543
  config_edit_enabled: exports_external.boolean().default(false).describe("Opt-in toggle for the `config_propose_edit` hostd verb (RFC " + "admin-agent-config-edit §3). Default false — the verb returns " + "`E_CONFIG_EDIT_DISABLED` until the operator explicitly flips " + "this to true. When true, admin agents can propose unified-diff " + "patches against " + "`/state/config/switchroom.yaml`, gated by an operator approval " + "card in the primary chat. Same trust posture as `update_apply` " + "and `agent_restart`: the human-in-the-loop tap is the security " + "boundary, not the agent's judgement."),
11534
- config_edit_rate_per_hour: exports_external.number().int().min(1).max(20).default(3).describe("Per-requesting-agent rate cap for `config_propose_edit` cards " + "(RFC admin-agent-config-edit §5). Default 3 cards/hour; min 1, " + "max 20. Configurable now, but the rate limiter is not yet enforced " + "(no `E_RATE_LIMITED` is currently raised); the field is reserved so " + "operators can pin the cap ahead of the limiter going live.")
11544
+ config_edit_rate_per_hour: exports_external.number().int().min(1).max(20).default(3).describe("Per-requesting-agent rate cap for `config_propose_edit` cards " + "(RFC admin-agent-config-edit §5). Default 3 cards/hour; min 1, " + "max 20. ENFORCED server-side: a caller exceeding this in a sliding " + "1-hour window is rejected with `E_RATE_LIMITED` (carrying a " + "`retry_after` fix) instead of posting another operator approval " + "card so a looping agent is throttled rather than spamming the chat.")
11535
11545
  });
11536
11546
  var CronEgressSchema = exports_external.object({
11537
11547
  allowed_hosts: exports_external.array(exports_external.string().min(1)).default([]).describe("Hosts a poll may reach (exact, https-only). loopback/private/IP-literal are always rejected."),
@@ -11564,11 +11574,14 @@ var SwitchroomConfigSchema = exports_external.object({
11564
11574
  message: "Consumer name must be a path-safe slug (letters, digits, underscore, hyphen)"
11565
11575
  }).describe("Socket-path identity; binds at /run/switchroom/auth-broker/<name>/sock"),
11566
11576
  account: exports_external.string().min(1).describe("Pinned account label for this consumer. `get-credentials` returns " + "this account's credentials; `mark-exhausted` from this consumer " + "only affects this account."),
11567
- uid: exports_external.number().int().nonnegative().optional().describe("Optional UID to chown the consumer socket to (defaults to 0 = root, " + "suitable for sibling containers running as root).")
11568
- })).optional().describe("Non-agent peers that hold a broker socket (RFC H §4.8). Each gets " + "its own `/run/switchroom/auth-broker/<name>/sock` chowned to its UID. " + "Consumers cannot be admins; a consumer name that collides with an " + "agent (whether that agent has `admin: true` or not) is a config " + "error caught at schema validation.")
11577
+ uid: exports_external.number().int().nonnegative().optional().describe("Optional UID to chown the consumer socket to (defaults to 0 = root, " + "suitable for sibling containers running as root)."),
11578
+ mirror_dir: exports_external.string().optional().describe("Optional host-side directory path. When set, the broker actively " + "writes the consumer's effective-account `.credentials.json` mirror " + "here — in addition to serving creds on demand via `get-credentials`. " + "Use this to eliminate the pull-latency gap: without a mirror the " + "consumer only gets failover creds at its next scheduled re-fetch " + "(up to 30 min). With a mirror the broker pushes failover creds " + "immediately when it detects exhaustion (consumer-quota-sensor tick, " + "or a mark-exhausted RPC on the pinned account). The directory must " + "be accessible to the broker container (bind-mounted from the host) " + "and to the consumer container; the broker writes " + "`<mirror_dir>/.credentials.json` atomically. Chown is attempted to " + "`uid` (default 0) swallowed when CAP_CHOWN is absent.")
11579
+ })).optional().describe("Non-agent peers that hold a broker socket (RFC H §4.8). Each gets " + "its own `/run/switchroom/auth-broker/<name>/sock` chowned to its UID. " + "Consumers cannot be admins; a consumer name that collides with an " + "agent (whether that agent has `admin: true` or not) is a config " + "error caught at schema validation."),
11580
+ allow_overage_accounts: exports_external.array(exports_external.string().min(1)).optional().describe("Opt-in list of account labels (bare strings matching `auth.active` / " + "`auth.fallback_order` entries) that may be served PAST the weekly " + "utilization wall when Anthropic overage billing is available for the " + "account (`overageStatus === 'allowed'`). Overage is REAL MONEY — " + "default is empty (no account gets this). An account in this list is " + "only kept eligible when its fresh quota snapshot reports " + "`overageStatus: 'allowed'` AND `overageDisabledReason` is NOT " + "'out_of_credits' (i.e. the overage credit has not been exhausted). " + "As soon as `overageDisabledReason` becomes 'out_of_credits', the " + "account is blocked immediately regardless of this flag. Overage lifts " + "ONLY the utilization wall — it cannot lift an active exhaustion mark " + "written by a real 429 (`mark-exhausted`).")
11569
11581
  }).optional().describe("Switchroom-auth-broker configuration (RFC H). Fleet-wide active account, " + "fallback order, admin-agent ACL, and ephemeral-consumer surface. " + "Required from the v0.8+ schema onwards; pre-v0.8 fleets are migrated " + "in-place by `switchroom apply` (see src/auth/migrate-schema.ts)."),
11570
11582
  drive: GoogleWorkspaceConfigSchema.describe("RFC D legacy key — use `google_workspace:` instead. Optional Google " + "Workspace onboarding configuration. When set, supplies Google OAuth " + "client credentials, the approver allowlist for `switchroom drive " + "connect`, and the optional tier knob. Env vars " + "(SWITCHROOM_GOOGLE_CLIENT_ID, SWITCHROOM_GOOGLE_CLIENT_SECRET, " + "SWITCHROOM_APPROVER_USER_ID) take precedence over this block when " + "set, preserving back-compat with the env-only flow shipped in #766."),
11571
11583
  google_workspace: GoogleWorkspaceConfigSchema.describe("RFC G canonical key. Top-level Google Workspace configuration — " + "OAuth client credentials, approver allowlist, and tier knob (`core` " + "| `extended` | `complete`, default `core`). Mutually exclusive with " + "`drive:` at the top level (loader fails fast if both are set)."),
11584
+ litellm: LiteLLMConfigSchema.describe("Top-level LiteLLM routing infra — global base_url, admin_key (the " + "LiteLLM master key, supports a `vault:` ref), team alias, and " + "small_fast_model shared by every agent that opts in. Set `enabled: " + "true` here to default the whole fleet on (each agent can still set " + "`litellm.enabled: false` to opt out). Default OFF."),
11572
11585
  microsoft_workspace: MicrosoftWorkspaceConfigSchema.describe("RFC #1873 (Microsoft 365 integration). Top-level Microsoft Workspace " + "configuration — OAuth client credentials (Entra app), authority " + "endpoint (defaults to /common for personal MSA + work), and the " + "org_mode opt-in for Teams/SharePoint surfaces. Block is optional; " + "when omitted the broker does not register the Microsoft provider."),
11573
11586
  notion_workspace: NotionWorkspaceConfigSchema.describe("RFC reference/rfcs/notion-integration.md. Top-level Notion integration " + "config — vault key for the integration token, friendly-name → " + "database UUID map, optional MCP-package version pin, and optional " + "global rate-limit override (default 3 rps, Notion's documented " + "public-API limit). Block is optional; when omitted no agent gets a " + "Notion MCP entry regardless of per-agent config."),
11574
11587
  quota: QuotaConfigSchema.optional().describe("Optional weekly/monthly USD spend budgets rendered in the session " + "greeting. Usage is read from ccusage at runtime; no network calls."),
@@ -11988,6 +12001,24 @@ function mergeAgentConfig(defaultsIn, agentIn) {
11988
12001
  ...merged.env ?? {}
11989
12002
  };
11990
12003
  }
12004
+ if (defaults.litellm || merged.litellm) {
12005
+ const base = defaults.litellm ?? {};
12006
+ const override = merged.litellm ?? {};
12007
+ const combined = { ...base };
12008
+ for (const [k, v] of Object.entries(override)) {
12009
+ if (v === undefined)
12010
+ continue;
12011
+ if (k === "tags" && base.tags && typeof v === "object" && v !== null && !Array.isArray(v)) {
12012
+ combined.tags = {
12013
+ ...base.tags,
12014
+ ...v
12015
+ };
12016
+ } else {
12017
+ combined[k] = v;
12018
+ }
12019
+ }
12020
+ merged.litellm = combined;
12021
+ }
11991
12022
  if (defaults.subagents || merged.subagents) {
11992
12023
  const dSub = defaults.subagents ?? {};
11993
12024
  const mSub = merged.subagents ?? {};
@@ -12357,6 +12388,14 @@ import { dirname as dirname3, join as join4, resolve as resolve7 } from "node:pa
12357
12388
  // src/agents/compose.ts
12358
12389
  import { createHash } from "node:crypto";
12359
12390
 
12391
+ // src/config/timezone.ts
12392
+ var CONTAINER_DEFAULT_UTC_ZONES = new Set([
12393
+ "UTC",
12394
+ "Etc/UTC",
12395
+ "Etc/Universal",
12396
+ "Universal"
12397
+ ]);
12398
+
12360
12399
  // src/vault/broker/peercred.ts
12361
12400
  var RESERVED_AGENT_NAMES = new Set(["operator", "hostd"]);
12362
12401
  function isReservedAgentName(name) {
@@ -12387,6 +12426,9 @@ var BIND_MOUNT_EXACT_SOURCE_DENY = new Set(["/var/run/docker.sock"]);
12387
12426
  var OAUTH_BETA = "oauth-2025-04-20";
12388
12427
  var DEFAULT_USER_AGENT = "claude-cli/1.0.0 (external, cli)";
12389
12428
  var DEFAULT_PROBE_MODEL = "claude-haiku-4-5-20251001";
12429
+ function isProbeThin(q) {
12430
+ return q.fiveHourUtilPresent === false && q.sevenDayUtilPresent === false;
12431
+ }
12390
12432
  function parseFloatHeader(headers, name) {
12391
12433
  const v = headers.get(name);
12392
12434
  if (v == null || v.trim().length === 0)
@@ -12417,6 +12459,8 @@ function parseQuotaHeaders(headers) {
12417
12459
  data: {
12418
12460
  fiveHourUtilizationPct: (fiveHour ?? 0) * 100,
12419
12461
  sevenDayUtilizationPct: (sevenDay ?? 0) * 100,
12462
+ fiveHourUtilPresent: fiveHour != null,
12463
+ sevenDayUtilPresent: sevenDay != null,
12420
12464
  fiveHourResetAt: parseEpochHeader(headers, "anthropic-ratelimit-unified-5h-reset"),
12421
12465
  sevenDayResetAt: parseEpochHeader(headers, "anthropic-ratelimit-unified-7d-reset"),
12422
12466
  representativeClaim: headers.get("anthropic-ratelimit-unified-representative-claim"),
@@ -12470,57 +12514,50 @@ async function fetchQuota(opts) {
12470
12514
  return parsed;
12471
12515
  }
12472
12516
 
12473
- // src/auth/broker/consumer-quota-sensor.ts
12474
- var EXHAUSTION_PCT = 99.5;
12475
- var DEFAULT_CONSUMER_PROBE_INTERVAL_MS = 10 * 60 * 1000;
12476
- function quotaIndicatesExhaustion(result) {
12477
- if (!result.ok)
12478
- return { exhausted: false, until: null };
12479
- const d = result.data;
12480
- const fiveBlocked = d.fiveHourUtilizationPct >= EXHAUSTION_PCT;
12481
- const sevenBlocked = d.sevenDayUtilizationPct >= EXHAUSTION_PCT;
12482
- if (!fiveBlocked && !sevenBlocked)
12483
- return { exhausted: false, until: null };
12484
- const fiveReset = fiveBlocked ? d.fiveHourResetAt?.getTime() ?? null : null;
12485
- const sevenReset = sevenBlocked ? d.sevenDayResetAt?.getTime() ?? null : null;
12486
- const candidates = [fiveReset, sevenReset].filter((x) => x != null);
12487
- const until = candidates.length > 0 ? Math.max(...candidates) : null;
12488
- return { exhausted: true, until };
12489
- }
12490
- function resolveConsumerProbeIntervalMs(env) {
12491
- if (env.SWITCHROOM_DISABLE_CONSUMER_QUOTA_PROBE === "1")
12492
- return 0;
12493
- const raw = env.SWITCHROOM_CONSUMER_QUOTA_PROBE_MS;
12494
- if (raw !== undefined) {
12495
- const n = Number(raw);
12496
- if (Number.isFinite(n) && n >= 0)
12497
- return n;
12498
- }
12499
- return DEFAULT_CONSUMER_PROBE_INTERVAL_MS;
12500
- }
12501
-
12502
12517
  // src/auth/broker/account-eligibility.ts
12503
12518
  var WALL_PCT = 99.5;
12504
12519
  var HEALTHY_CLEAR_PCT = 80;
12505
12520
  var SNAPSHOT_STALE_AGE_MS = 24 * 60 * 60 * 1000;
12521
+ var OVERAGE_EXHAUSTED_REASONS = new Set(["out_of_credits"]);
12506
12522
  function snapshotFresh(s, now, maxAgeMs = SNAPSHOT_STALE_AGE_MS) {
12507
12523
  return !!s && now - s.capturedAt <= maxAgeMs && s.capturedAt <= now + 60000;
12508
12524
  }
12509
12525
  function snapshotWalled(s) {
12510
12526
  return s.fiveHourUtilizationPct >= WALL_PCT || s.sevenDayUtilizationPct >= WALL_PCT;
12511
12527
  }
12528
+ function overageLiftsWall(snapshot, inAllowList) {
12529
+ if (!inAllowList)
12530
+ return false;
12531
+ if (snapshot.overageStatus !== "allowed")
12532
+ return false;
12533
+ const reason = snapshot.overageDisabledReason;
12534
+ if (reason != null && OVERAGE_EXHAUSTED_REASONS.has(reason))
12535
+ return false;
12536
+ return true;
12537
+ }
12512
12538
  function snapshotClearlyHealthy(s) {
12513
12539
  return s.fiveHourUtilizationPct < HEALTHY_CLEAR_PCT && s.sevenDayUtilizationPct < HEALTHY_CLEAR_PCT;
12514
12540
  }
12515
- function isAccountBlocked(opts) {
12516
- const { mark, snapshot, now } = opts;
12541
+ function accountEligibility(opts) {
12542
+ const { mark, snapshot, now, allowOverage = false } = opts;
12517
12543
  if (snapshotFresh(snapshot, now)) {
12518
12544
  const markedAt = mark?.marked_at ?? 0;
12519
12545
  if (snapshot.capturedAt >= markedAt) {
12520
- return snapshotWalled(snapshot);
12546
+ if (snapshotWalled(snapshot)) {
12547
+ if (overageLiftsWall(snapshot, allowOverage)) {
12548
+ return "eligible";
12549
+ }
12550
+ return "blocked";
12551
+ }
12552
+ return "eligible";
12521
12553
  }
12522
12554
  }
12523
- return mark !== undefined && mark.exhausted_until > now;
12555
+ if (mark !== undefined && mark.exhausted_until > now)
12556
+ return "blocked";
12557
+ return "unknown";
12558
+ }
12559
+ function isAccountBlocked(opts) {
12560
+ return accountEligibility(opts) === "blocked";
12524
12561
  }
12525
12562
  function snapshotShouldClearMark(snapshot, mark, now) {
12526
12563
  if (!mark)
@@ -12529,6 +12566,8 @@ function snapshotShouldClearMark(snapshot, mark, now) {
12529
12566
  return false;
12530
12567
  if (snapshot.capturedAt < (mark.marked_at ?? 0))
12531
12568
  return false;
12569
+ if (isProbeThin(snapshot))
12570
+ return false;
12532
12571
  return snapshotClearlyHealthy(snapshot);
12533
12572
  }
12534
12573
  function clampMarkExpiry(opts) {
@@ -12540,6 +12579,46 @@ function clampMarkExpiry(opts) {
12540
12579
  return liveContradictsWeeklyWall ? shortCeil : proposedUntil;
12541
12580
  }
12542
12581
 
12582
+ // src/auth/broker/consumer-quota-sensor.ts
12583
+ var EXHAUSTION_PCT = 99.5;
12584
+ var DEFAULT_CONSUMER_PROBE_INTERVAL_MS = 10 * 60 * 1000;
12585
+ function quotaIndicatesExhaustion(result, allowOverage = false) {
12586
+ if (!result.ok)
12587
+ return { exhausted: false, until: null };
12588
+ const d = result.data;
12589
+ const fiveBlocked = d.fiveHourUtilizationPct >= EXHAUSTION_PCT;
12590
+ const sevenBlocked = d.sevenDayUtilizationPct >= EXHAUSTION_PCT;
12591
+ if (!fiveBlocked && !sevenBlocked)
12592
+ return { exhausted: false, until: null };
12593
+ if (allowOverage) {
12594
+ const snap = {
12595
+ fiveHourUtilizationPct: d.fiveHourUtilizationPct,
12596
+ sevenDayUtilizationPct: d.sevenDayUtilizationPct,
12597
+ capturedAt: Date.now(),
12598
+ overageStatus: d.overageStatus,
12599
+ overageDisabledReason: d.overageDisabledReason
12600
+ };
12601
+ if (overageLiftsWall(snap, true))
12602
+ return { exhausted: false, until: null };
12603
+ }
12604
+ const fiveReset = fiveBlocked ? d.fiveHourResetAt?.getTime() ?? null : null;
12605
+ const sevenReset = sevenBlocked ? d.sevenDayResetAt?.getTime() ?? null : null;
12606
+ const candidates = [fiveReset, sevenReset].filter((x) => x != null);
12607
+ const until = candidates.length > 0 ? Math.max(...candidates) : null;
12608
+ return { exhausted: true, until };
12609
+ }
12610
+ function resolveConsumerProbeIntervalMs(env) {
12611
+ if (env.SWITCHROOM_DISABLE_CONSUMER_QUOTA_PROBE === "1")
12612
+ return 0;
12613
+ const raw = env.SWITCHROOM_CONSUMER_QUOTA_PROBE_MS;
12614
+ if (raw !== undefined) {
12615
+ const n = Number(raw);
12616
+ if (Number.isFinite(n) && n >= 0)
12617
+ return n;
12618
+ }
12619
+ return DEFAULT_CONSUMER_PROBE_INTERVAL_MS;
12620
+ }
12621
+
12543
12622
  // src/util/atomic.ts
12544
12623
  import { randomBytes } from "node:crypto";
12545
12624
  import { closeSync, constants, fsyncSync, openSync, renameSync, rmSync, writeSync } from "node:fs";
@@ -12721,7 +12800,7 @@ function atomicWriteJson(destPath, value, mode = 384) {
12721
12800
  // src/auth/account-refresh.ts
12722
12801
  var REFRESH_THRESHOLD_MS = 60 * 60 * 1000;
12723
12802
  var DEFAULT_TOKEN_URL = process.env.SWITCHROOM_OAUTH_TOKEN_URL ?? "https://console.anthropic.com/v1/oauth/token";
12724
- var DEFAULT_CLIENT_ID = process.env.SWITCHROOM_OAUTH_CLIENT_ID ?? "9d1cd16e-bcb9-40c9-a915-196412f27aa6";
12803
+ var DEFAULT_CLIENT_ID = process.env.SWITCHROOM_OAUTH_CLIENT_ID ?? "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
12725
12804
  var defaultFetcher = async (url, init) => {
12726
12805
  const res = await fetch(url, {
12727
12806
  method: init.method,
@@ -13543,7 +13622,8 @@ var ProbeQuotaRequestSchema = exports_external.object({
13543
13622
  op: exports_external.literal("probe-quota"),
13544
13623
  id: exports_external.string().min(1),
13545
13624
  accounts: exports_external.array(exports_external.string().min(1)).min(1).max(32),
13546
- timeoutMs: exports_external.number().int().positive().max(60000).optional()
13625
+ timeoutMs: exports_external.number().int().positive().max(60000).optional(),
13626
+ forceLive: exports_external.boolean().optional()
13547
13627
  });
13548
13628
  var ClaimNotificationRequestSchema = exports_external.object({
13549
13629
  v: exports_external.literal(PROTOCOL_VERSION),
@@ -13594,7 +13674,8 @@ var ListStateDataSchema = exports_external.object({
13594
13674
  fallback_order: exports_external.array(exports_external.string()),
13595
13675
  accounts: exports_external.array(AccountStateSchema),
13596
13676
  agents: exports_external.array(AgentStateSchema),
13597
- consumers: exports_external.array(ConsumerStateSchema)
13677
+ consumers: exports_external.array(ConsumerStateSchema),
13678
+ active_overage_serving: exports_external.boolean().optional()
13598
13679
  });
13599
13680
  var SetActiveDataSchema = exports_external.object({
13600
13681
  active: exports_external.string(),
@@ -13710,6 +13791,30 @@ var AUDIT_ROTATE_BYTES = 10 * 1024 * 1024;
13710
13791
  var AUDIT_KEEP = 5;
13711
13792
  var AUDIT_LINE_MAX = 4000;
13712
13793
  var NOTIFICATION_CLAIM_MAX_AGE_MS = 86400000;
13794
+ var DEFAULT_QUOTA_PROBE_TTL_MS = 45000;
13795
+ function quotaProbeTtlMs() {
13796
+ const raw = process.env.SWITCHROOM_QUOTA_PROBE_TTL_MS;
13797
+ if (raw == null || raw === "")
13798
+ return DEFAULT_QUOTA_PROBE_TTL_MS;
13799
+ const n = Number(raw);
13800
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_QUOTA_PROBE_TTL_MS;
13801
+ }
13802
+ function cachedSnapshotToResult(s) {
13803
+ return {
13804
+ ok: true,
13805
+ data: {
13806
+ fiveHourUtilizationPct: s.fiveHourUtilizationPct,
13807
+ sevenDayUtilizationPct: s.sevenDayUtilizationPct,
13808
+ fiveHourResetAt: s.fiveHourResetAt ? new Date(s.fiveHourResetAt) : null,
13809
+ sevenDayResetAt: s.sevenDayResetAt ? new Date(s.sevenDayResetAt) : null,
13810
+ representativeClaim: s.representativeClaim,
13811
+ overageStatus: s.overageStatus,
13812
+ overageDisabledReason: s.overageDisabledReason,
13813
+ fiveHourUtilPresent: s.fiveHourUtilPresent,
13814
+ sevenDayUtilPresent: s.sevenDayUtilPresent
13815
+ }
13816
+ };
13817
+ }
13713
13818
  function sha256Hex(content) {
13714
13819
  return createHash2("sha256").update(content).digest("hex");
13715
13820
  }
@@ -13764,6 +13869,7 @@ class AuthBroker {
13764
13869
  providers;
13765
13870
  quota = {};
13766
13871
  lastQuotaCache = {};
13872
+ probeInFlight = new Map;
13767
13873
  shaIndex = {};
13768
13874
  thresholdViolations = {};
13769
13875
  notificationClaims = {};
@@ -14164,7 +14270,7 @@ class AuthBroker {
14164
14270
  await this.opListMicrosoftAccounts(socket, reqId, identity2);
14165
14271
  break;
14166
14272
  case "probe-quota":
14167
- await this.opProbeQuota(socket, reqId, identity2, req.accounts, req.timeoutMs);
14273
+ await this.opProbeQuota(socket, reqId, identity2, req.accounts, req.timeoutMs, req.forceLive);
14168
14274
  break;
14169
14275
  case "claim-notification":
14170
14276
  this.opClaimNotification(socket, reqId, identity2, req.key, req.windowMs);
@@ -14206,12 +14312,78 @@ class AuthBroker {
14206
14312
  return account;
14207
14313
  return this.accountWithFailover(account);
14208
14314
  }
14315
+ isOverageAllowed(account) {
14316
+ return (this.config.auth?.allow_overage_accounts ?? []).includes(account);
14317
+ }
14209
14318
  isAccountExhausted(account) {
14210
14319
  return isAccountBlocked({
14211
14320
  mark: this.quota[account],
14212
14321
  snapshot: this.lastQuotaCache[account],
14213
- now: this.now()
14322
+ now: this.now(),
14323
+ allowOverage: this.isOverageAllowed(account)
14324
+ });
14325
+ }
14326
+ isActiveOverageServing(account) {
14327
+ if (!account)
14328
+ return false;
14329
+ if (!this.isOverageAllowed(account))
14330
+ return false;
14331
+ const snapshot = this.lastQuotaCache[account];
14332
+ const now = this.now();
14333
+ if (!snapshot || !snapshotFresh(snapshot, now))
14334
+ return false;
14335
+ if (!overageLiftsWall(snapshot, true))
14336
+ return false;
14337
+ return accountEligibility({
14338
+ mark: this.quota[account],
14339
+ snapshot,
14340
+ now,
14341
+ allowOverage: true
14342
+ }) === "eligible";
14343
+ }
14344
+ accountEligibilityOf(account) {
14345
+ const snapshot = this.lastQuotaCache[account];
14346
+ const allowOverage = this.isOverageAllowed(account);
14347
+ const verdict = accountEligibility({
14348
+ mark: this.quota[account],
14349
+ snapshot,
14350
+ now: this.now(),
14351
+ allowOverage
14214
14352
  });
14353
+ if (verdict === "eligible" && allowOverage && snapshot && (snapshot.fiveHourUtilizationPct >= WALL_PCT || snapshot.sevenDayUtilizationPct >= WALL_PCT)) {
14354
+ process.stdout.write(`auth-broker: ${account} is past the utilization wall but eligible via allow_overage — Anthropic overage billing active (5h=${snapshot.fiveHourUtilizationPct.toFixed(1)}%, 7d=${snapshot.sevenDayUtilizationPct.toFixed(1)}%)
14355
+ `);
14356
+ }
14357
+ return verdict;
14358
+ }
14359
+ async probeAndCacheOne(account) {
14360
+ try {
14361
+ const creds = readAccountCredentials(account, this.home);
14362
+ const token = creds?.claudeAiOauth?.accessToken;
14363
+ if (!token)
14364
+ return;
14365
+ const result = await this.probeQuotaSingleFlight(account, token);
14366
+ if (result.ok)
14367
+ this.cacheQuotaSnapshot(account, result);
14368
+ } catch {}
14369
+ }
14370
+ async nextHealthyAccountLive(current, order) {
14371
+ const cached = this.nextHealthyAccount(current, order);
14372
+ if (cached && this.accountEligibilityOf(cached) === "eligible")
14373
+ return cached;
14374
+ const start = order.indexOf(current);
14375
+ const ring = start === -1 ? [...order] : order.map((_, i) => order[(start + 1 + i) % order.length]).filter((x) => !!x);
14376
+ const unknowns = ring.filter((cand) => cand && cand !== current && accountExists(cand, this.home) && this.accountEligibilityOf(cand) === "unknown");
14377
+ await Promise.all(unknowns.map((cand) => this.probeAndCacheOne(cand)));
14378
+ const reselected = this.nextHealthyAccount(current, order);
14379
+ if (reselected && this.accountEligibilityOf(reselected) === "eligible")
14380
+ return reselected;
14381
+ for (const cand of ring) {
14382
+ if (cand && cand !== current && accountExists(cand, this.home) && this.accountEligibilityOf(cand) === "unknown") {
14383
+ return cand;
14384
+ }
14385
+ }
14386
+ return null;
14215
14387
  }
14216
14388
  accountWithFailover(account) {
14217
14389
  if (!account || !this.isAccountExhausted(account))
@@ -14222,23 +14394,27 @@ class AuthBroker {
14222
14394
  if (readAccountCredentials(cand, this.home))
14223
14395
  return cand;
14224
14396
  }
14397
+ const active = this.config.auth?.active;
14398
+ if (active && active !== account && !this.isAccountExhausted(active) && readAccountCredentials(active, this.home)) {
14399
+ return active;
14400
+ }
14225
14401
  return account;
14226
14402
  }
14227
14403
  async opGetCredentials(socket, id, identity2) {
14228
14404
  const account = this.servingAccount(identity2);
14229
14405
  if (!account) {
14230
- this.audit({ op: "get-credentials", identity: identity2, ok: false, error: "no-active-account" });
14406
+ this.audit({ op: "get-credentials", identity: identity2, accountKind: "claude", ok: false, error: "no-active-account" });
14231
14407
  socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", "no active account configured"));
14232
14408
  return;
14233
14409
  }
14234
14410
  const creds = readAccountCredentials(account, this.home);
14235
14411
  if (!creds) {
14236
- this.audit({ op: "get-credentials", identity: identity2, account, ok: false, error: "missing-credentials" });
14412
+ this.audit({ op: "get-credentials", identity: identity2, account, accountKind: "claude", ok: false, error: "missing-credentials" });
14237
14413
  socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", `no credentials for account '${account}'`));
14238
14414
  return;
14239
14415
  }
14240
14416
  const expiresAt = creds.claudeAiOauth?.expiresAt;
14241
- this.audit({ op: "get-credentials", identity: identity2, account, ok: true });
14417
+ this.audit({ op: "get-credentials", identity: identity2, account, accountKind: "claude", ok: true });
14242
14418
  socket.write(encodeSuccess(id, { account, credentials: creds, expiresAt }));
14243
14419
  }
14244
14420
  async opListState(socket, id, identity2) {
@@ -14269,13 +14445,15 @@ class AuthBroker {
14269
14445
  account: c.account,
14270
14446
  last_seen_at: this.consumerLastSeen[c.name] ?? null
14271
14447
  }));
14448
+ const active_overage_serving = this.isActiveOverageServing(this.callerAccount(identity2));
14272
14449
  this.audit({ op: "list-state", identity: identity2, ok: true });
14273
14450
  socket.write(encodeSuccess(id, {
14274
14451
  active: auth.active ?? "",
14275
14452
  fallback_order: auth.fallback_order ?? [],
14276
14453
  accounts,
14277
14454
  agents,
14278
- consumers
14455
+ consumers,
14456
+ active_overage_serving
14279
14457
  }));
14280
14458
  }
14281
14459
  async opListGoogleAccounts(socket, id, identity2) {
@@ -14290,35 +14468,60 @@ class AuthBroker {
14290
14468
  clientId: creds.googleOauth.clientId
14291
14469
  };
14292
14470
  }).filter((entry) => entry !== null).sort((a, b) => a.account.localeCompare(b.account));
14293
- this.audit({ op: "list-google-accounts", identity: identity2, ok: true });
14471
+ this.audit({ op: "list-google-accounts", identity: identity2, accountKind: "google", ok: true });
14294
14472
  socket.write(encodeSuccess(id, { accounts }));
14295
14473
  }
14296
- async opProbeQuota(socket, id, identity2, accounts, timeoutMs) {
14474
+ async opProbeQuota(socket, id, identity2, accounts, timeoutMs, forceLive) {
14475
+ const ttlMs = forceLive ? 0 : quotaProbeTtlMs();
14297
14476
  const results = await Promise.all(accounts.map(async (label) => {
14477
+ const cached = this.lastQuotaCache[label];
14478
+ if (ttlMs > 0 && cached && this.now() - cached.capturedAt < ttlMs) {
14479
+ return { label, result: cachedSnapshotToResult(cached), served: "cache", capturedAt: cached.capturedAt };
14480
+ }
14298
14481
  const creds = readAccountCredentials(label, this.home);
14299
14482
  const token = creds?.claudeAiOauth?.accessToken;
14300
14483
  if (!token) {
14484
+ if (cached) {
14485
+ return { label, result: cachedSnapshotToResult(cached), served: "cache", capturedAt: cached.capturedAt };
14486
+ }
14301
14487
  const result2 = {
14302
14488
  ok: false,
14303
14489
  reason: "no credentials for account in broker store"
14304
14490
  };
14305
- this.audit({ op: "probe-quota", identity: identity2, account: label, ok: false, error: "missing-credentials" });
14491
+ this.audit({ op: "probe-quota", identity: identity2, account: label, accountKind: "claude", ok: false, error: "missing-credentials" });
14306
14492
  return { label, result: result2 };
14307
14493
  }
14308
- const result = await this.fetchQuotaImpl({ accessToken: token, timeoutMs });
14494
+ const result = await this.probeQuotaSingleFlight(label, token, timeoutMs);
14309
14495
  this.audit({
14310
14496
  op: "probe-quota",
14311
14497
  identity: identity2,
14312
14498
  account: label,
14499
+ accountKind: "claude",
14313
14500
  ok: result.ok,
14314
14501
  error: result.ok ? undefined : result.reason
14315
14502
  });
14316
- if (result.ok)
14503
+ if (result.ok) {
14317
14504
  this.cacheQuotaSnapshot(label, result);
14318
- return { label, result };
14505
+ return { label, result, served: "live" };
14506
+ }
14507
+ if (cached) {
14508
+ return { label, result: cachedSnapshotToResult(cached), served: "cache", capturedAt: cached.capturedAt };
14509
+ }
14510
+ return { label, result, served: "live" };
14319
14511
  }));
14320
14512
  socket.write(encodeSuccess(id, { results }));
14321
14513
  }
14514
+ probeQuotaSingleFlight(label, token, timeoutMs) {
14515
+ const existing = this.probeInFlight.get(label);
14516
+ if (existing)
14517
+ return existing;
14518
+ const pending = this.fetchQuotaImpl({ accessToken: token, timeoutMs }).finally(() => {
14519
+ if (this.probeInFlight.get(label) === pending)
14520
+ this.probeInFlight.delete(label);
14521
+ });
14522
+ this.probeInFlight.set(label, pending);
14523
+ return pending;
14524
+ }
14322
14525
  cacheQuotaSnapshot(label, result) {
14323
14526
  if (!result.ok)
14324
14527
  return;
@@ -14330,14 +14533,18 @@ class AuthBroker {
14330
14533
  representativeClaim: result.data.representativeClaim,
14331
14534
  overageStatus: result.data.overageStatus,
14332
14535
  overageDisabledReason: result.data.overageDisabledReason,
14333
- capturedAt: this.now()
14536
+ capturedAt: this.now(),
14537
+ fiveHourUtilPresent: result.data.fiveHourUtilPresent,
14538
+ sevenDayUtilPresent: result.data.sevenDayUtilPresent
14334
14539
  };
14335
14540
  this.lastQuotaCache[label] = snapshot;
14541
+ this.persistLastQuotaCache();
14336
14542
  if (snapshotShouldClearMark(snapshot, this.quota[label], this.now())) {
14337
14543
  delete this.quota[label];
14338
14544
  this.persistQuota();
14339
14545
  process.stdout.write(`auth-broker: live probe shows ${label} healthy (5h=${snapshot.fiveHourUtilizationPct}% 7d=${snapshot.sevenDayUtilizationPct}%) — cleared stale exhaustion mark
14340
14546
  `);
14547
+ this.fanoutToAffectedConsumers(label);
14341
14548
  }
14342
14549
  }
14343
14550
  async fleetQuotaProbeTick() {
@@ -14371,9 +14578,15 @@ class AuthBroker {
14371
14578
  continue;
14372
14579
  }
14373
14580
  this.cacheQuotaSnapshot(label, result);
14374
- const decision = quotaIndicatesExhaustion(result);
14375
- if (!decision.exhausted)
14581
+ const allowOverage = this.isOverageAllowed(label);
14582
+ const decision = quotaIndicatesExhaustion(result, allowOverage);
14583
+ if (!decision.exhausted) {
14584
+ if (result.ok && (result.data.fiveHourUtilizationPct >= EXHAUSTION_PCT || result.data.sevenDayUtilizationPct >= EXHAUSTION_PCT) && allowOverage) {
14585
+ process.stdout.write(`auth-broker: consumer-quota-sensor ${label} is wall-walled but serving via overage (allow_overage) — Anthropic overage billing is active
14586
+ `);
14587
+ }
14376
14588
  continue;
14589
+ }
14377
14590
  const now = this.now();
14378
14591
  const exhaustedUntil = clampMarkExpiry({
14379
14592
  proposedUntil: decision.until ?? now + MARK_EXHAUSTED_DEFAULT_MS,
@@ -14386,19 +14599,20 @@ class AuthBroker {
14386
14599
  continue;
14387
14600
  this.quota[label] = { exhausted_until: exhaustedUntil, marked_at: now };
14388
14601
  this.persistQuota();
14389
- this.audit({ op: "mark-exhausted", identity: { kind: "operator" }, account: label, ok: true });
14602
+ this.audit({ op: "mark-exhausted", identity: { kind: "operator" }, account: label, accountKind: "claude", ok: true });
14390
14603
  process.stdout.write(`auth-broker: consumer-quota-sensor marked ${label} exhausted until ${new Date(exhaustedUntil).toISOString()} — consumer(s) fail over
14391
14604
  `);
14605
+ this.fanoutToAffectedConsumers(label);
14392
14606
  }
14393
14607
  }
14394
14608
  async opSetActive(socket, id, identity2, account) {
14395
14609
  if (!this.isAdmin(identity2)) {
14396
- this.audit({ op: "set-active", identity: identity2, account, ok: false, error: "FORBIDDEN" });
14610
+ this.audit({ op: "set-active", identity: identity2, account, accountKind: "claude", ok: false, error: "FORBIDDEN" });
14397
14611
  this.respondForbidden(socket, id, "set-active requires admin");
14398
14612
  return;
14399
14613
  }
14400
14614
  if (!accountExists(account, this.home)) {
14401
- this.audit({ op: "set-active", identity: identity2, account, ok: false, error: "ACCOUNT_NOT_FOUND" });
14615
+ this.audit({ op: "set-active", identity: identity2, account, accountKind: "claude", ok: false, error: "ACCOUNT_NOT_FOUND" });
14402
14616
  socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", `account '${account}' not found`));
14403
14617
  return;
14404
14618
  }
@@ -14408,13 +14622,14 @@ class AuthBroker {
14408
14622
  };
14409
14623
  this.config = cfg;
14410
14624
  const fanned = this.fanoutToAffectedAgents(account);
14411
- this.audit({ op: "set-active", identity: identity2, account, ok: true });
14625
+ this.fanoutAllConsumers();
14626
+ this.audit({ op: "set-active", identity: identity2, account, accountKind: "claude", ok: true });
14412
14627
  socket.write(encodeSuccess(id, { active: account, fanned }));
14413
14628
  }
14414
14629
  async opMarkExhausted(socket, id, identity2, until) {
14415
14630
  const account = this.callerAccount(identity2);
14416
14631
  if (!account) {
14417
- this.audit({ op: "mark-exhausted", identity: identity2, ok: false, error: "no-active-account" });
14632
+ this.audit({ op: "mark-exhausted", identity: identity2, accountKind: "claude", ok: false, error: "no-active-account" });
14418
14633
  socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", "no active account configured"));
14419
14634
  return;
14420
14635
  }
@@ -14427,9 +14642,10 @@ class AuthBroker {
14427
14642
  });
14428
14643
  this.quota[account] = { exhausted_until: exhaustedUntil, marked_at: now };
14429
14644
  this.persistQuota();
14430
- const rolled = this.fanoutFailoverFor(account);
14431
- const rolledTo = this.nextHealthyAccount(account, this.config.auth?.fallback_order ?? []);
14432
- this.audit({ op: "mark-exhausted", identity: identity2, account, ok: true });
14645
+ const rolledTo = await this.nextHealthyAccountLive(account, this.config.auth?.fallback_order ?? []);
14646
+ const rolled = this.fanoutFailoverTo(account, rolledTo);
14647
+ this.fanoutToAffectedConsumers(account);
14648
+ this.audit({ op: "mark-exhausted", identity: identity2, account, accountKind: "claude", ok: true });
14433
14649
  socket.write(encodeSuccess(id, { account, rolled, rolledTo }));
14434
14650
  }
14435
14651
  opClaimNotification(socket, id, identity2, key, windowMs) {
@@ -14449,29 +14665,29 @@ class AuthBroker {
14449
14665
  }
14450
14666
  async opRefreshAccount(socket, id, identity2, account) {
14451
14667
  if (!this.isAdmin(identity2)) {
14452
- this.audit({ op: "refresh-account", identity: identity2, account, ok: false, error: "FORBIDDEN" });
14668
+ this.audit({ op: "refresh-account", identity: identity2, account, accountKind: "claude", ok: false, error: "FORBIDDEN" });
14453
14669
  this.respondForbidden(socket, id, "refresh-account requires admin");
14454
14670
  return;
14455
14671
  }
14456
14672
  if (!accountExists(account, this.home)) {
14457
- this.audit({ op: "refresh-account", identity: identity2, account, ok: false, error: "ACCOUNT_NOT_FOUND" });
14673
+ this.audit({ op: "refresh-account", identity: identity2, account, accountKind: "claude", ok: false, error: "ACCOUNT_NOT_FOUND" });
14458
14674
  socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", `account '${account}' not found`));
14459
14675
  return;
14460
14676
  }
14461
14677
  const result = await this.refreshOneAccount(account, true);
14462
14678
  if (result.kind === "failed") {
14463
- this.audit({ op: "refresh-account", identity: identity2, account, ok: false, error: result.error });
14679
+ this.audit({ op: "refresh-account", identity: identity2, account, accountKind: "claude", ok: false, error: result.error });
14464
14680
  socket.write(encodeError(id, "REFRESH_FAILED", result.error));
14465
14681
  return;
14466
14682
  }
14467
14683
  const creds = readAccountCredentials(account, this.home);
14468
14684
  const expiresAt = creds?.claudeAiOauth?.expiresAt;
14469
- this.audit({ op: "refresh-account", identity: identity2, account, ok: true });
14685
+ this.audit({ op: "refresh-account", identity: identity2, account, accountKind: "claude", ok: true });
14470
14686
  socket.write(encodeSuccess(id, { account, expiresAt }));
14471
14687
  }
14472
14688
  async opAddAccount(socket, id, identity2, label, credentials, replace) {
14473
14689
  if (!this.isAdmin(identity2)) {
14474
- this.audit({ op: "add-account", identity: identity2, account: label, ok: false, error: "FORBIDDEN" });
14690
+ this.audit({ op: "add-account", identity: identity2, account: label, accountKind: "claude", ok: false, error: "FORBIDDEN" });
14475
14691
  this.respondForbidden(socket, id, "add-account requires admin");
14476
14692
  return;
14477
14693
  }
@@ -14482,7 +14698,7 @@ class AuthBroker {
14482
14698
  return;
14483
14699
  }
14484
14700
  if (accountExists(label, this.home) && !replace) {
14485
- this.audit({ op: "add-account", identity: identity2, account: label, ok: false, error: "ACCOUNT_ALREADY_EXISTS" });
14701
+ this.audit({ op: "add-account", identity: identity2, account: label, accountKind: "claude", ok: false, error: "ACCOUNT_ALREADY_EXISTS" });
14486
14702
  socket.write(encodeError(id, "ACCOUNT_ALREADY_EXISTS", `account '${label}' already exists; pass replace:true to overwrite`));
14487
14703
  return;
14488
14704
  }
@@ -14500,12 +14716,12 @@ class AuthBroker {
14500
14716
  this.persistShaIndex();
14501
14717
  this.fanoutToAffectedAgents(label);
14502
14718
  const expiresAt = credentials.claudeAiOauth?.expiresAt;
14503
- this.audit({ op: "add-account", identity: identity2, account: label, ok: true, replace });
14719
+ this.audit({ op: "add-account", identity: identity2, account: label, accountKind: "claude", ok: true, replace });
14504
14720
  socket.write(encodeSuccess(id, { label, expiresAt }));
14505
14721
  }
14506
14722
  async opRmAccount(socket, id, identity2, label) {
14507
14723
  if (!this.isAdmin(identity2)) {
14508
- this.audit({ op: "rm-account", identity: identity2, account: label, ok: false, error: "FORBIDDEN" });
14724
+ this.audit({ op: "rm-account", identity: identity2, account: label, accountKind: "claude", ok: false, error: "FORBIDDEN" });
14509
14725
  this.respondForbidden(socket, id, "rm-account requires admin");
14510
14726
  return;
14511
14727
  }
@@ -14533,10 +14749,12 @@ class AuthBroker {
14533
14749
  delete this.quota[label];
14534
14750
  delete this.thresholdViolations[label];
14535
14751
  this.lastWrittenExpiresAt.delete(label);
14752
+ delete this.lastQuotaCache[label];
14536
14753
  this.persistShaIndex();
14537
14754
  this.persistQuota();
14538
14755
  this.persistThresholdViolations();
14539
- this.audit({ op: "rm-account", identity: identity2, account: label, ok: true });
14756
+ this.persistLastQuotaCache();
14757
+ this.audit({ op: "rm-account", identity: identity2, account: label, accountKind: "claude", ok: true });
14540
14758
  socket.write(encodeSuccess(id, { label }));
14541
14759
  }
14542
14760
  async opGoogleGetCredentials(socket, id, identity2) {
@@ -14548,30 +14766,30 @@ class AuthBroker {
14548
14766
  const agent = (this.config.agents ?? {})[agentName];
14549
14767
  const account = agent?.google_workspace?.account;
14550
14768
  if (!account) {
14551
- this.audit({ op: "get-credentials", identity: identity2, ok: false, error: "no-google-account-configured" });
14769
+ this.audit({ op: "get-credentials", identity: identity2, accountKind: "google", ok: false, error: "no-google-account-configured" });
14552
14770
  socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", `agent '${agentName}' has no google_workspace.account configured in switchroom.yaml`));
14553
14771
  return;
14554
14772
  }
14555
14773
  const ga = this.config.google_accounts;
14556
14774
  const enabledFor = ga?.[account]?.enabled_for ?? [];
14557
14775
  if (!enabledFor.includes(agentName)) {
14558
- this.audit({ op: "get-credentials", identity: identity2, account, ok: false, error: "acl-deny" });
14776
+ this.audit({ op: "get-credentials", identity: identity2, account, accountKind: "google", ok: false, error: "acl-deny" });
14559
14777
  socket.write(encodeError(id, "FORBIDDEN", `agent '${agentName}' not in google_accounts['${account}'].enabled_for[] — operator must run \`switchroom auth google enable ${account} ${agentName}\``));
14560
14778
  return;
14561
14779
  }
14562
14780
  const creds = readGoogleAccountCredentials(this.stateDir, account);
14563
14781
  if (!creds) {
14564
- this.audit({ op: "get-credentials", identity: identity2, account, ok: false, error: "missing-credentials" });
14782
+ this.audit({ op: "get-credentials", identity: identity2, account, accountKind: "google", ok: false, error: "missing-credentials" });
14565
14783
  socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", `no Google credentials for account '${account}' — operator must run \`switchroom auth google account add ${account}\``));
14566
14784
  return;
14567
14785
  }
14568
14786
  const expiresAt = creds.googleOauth?.expiresAt;
14569
- this.audit({ op: "get-credentials", identity: identity2, account, ok: true });
14787
+ this.audit({ op: "get-credentials", identity: identity2, account, accountKind: "google", ok: true });
14570
14788
  socket.write(encodeSuccess(id, { account, credentials: creds, expiresAt }));
14571
14789
  }
14572
14790
  async opGoogleAddAccount(socket, id, identity2, label, credentials, replace) {
14573
14791
  if (!this.isAdmin(identity2)) {
14574
- this.audit({ op: "add-account", identity: identity2, account: label, ok: false, error: "FORBIDDEN" });
14792
+ this.audit({ op: "add-account", identity: identity2, account: label, accountKind: "google", ok: false, error: "FORBIDDEN" });
14575
14793
  this.respondForbidden(socket, id, "add-account requires admin");
14576
14794
  return;
14577
14795
  }
@@ -14582,7 +14800,7 @@ class AuthBroker {
14582
14800
  return;
14583
14801
  }
14584
14802
  if (googleAccountExists(this.stateDir, label) && !replace) {
14585
- this.audit({ op: "add-account", identity: identity2, account: label, ok: false, error: "ACCOUNT_ALREADY_EXISTS" });
14803
+ this.audit({ op: "add-account", identity: identity2, account: label, accountKind: "google", ok: false, error: "ACCOUNT_ALREADY_EXISTS" });
14586
14804
  socket.write(encodeError(id, "ACCOUNT_ALREADY_EXISTS", `google account '${label}' already exists; pass replace:true to overwrite`));
14587
14805
  return;
14588
14806
  }
@@ -14593,12 +14811,12 @@ class AuthBroker {
14593
14811
  return;
14594
14812
  }
14595
14813
  const expiresAt = credentials.googleOauth?.expiresAt;
14596
- this.audit({ op: "add-account", identity: identity2, account: label, ok: true, replace });
14814
+ this.audit({ op: "add-account", identity: identity2, account: label, accountKind: "google", ok: true, replace });
14597
14815
  socket.write(encodeSuccess(id, { label, expiresAt }));
14598
14816
  }
14599
14817
  async opGoogleRmAccount(socket, id, identity2, label) {
14600
14818
  if (!this.isAdmin(identity2)) {
14601
- this.audit({ op: "rm-account", identity: identity2, account: label, ok: false, error: "FORBIDDEN" });
14819
+ this.audit({ op: "rm-account", identity: identity2, account: label, accountKind: "google", ok: false, error: "FORBIDDEN" });
14602
14820
  this.respondForbidden(socket, id, "rm-account requires admin");
14603
14821
  return;
14604
14822
  }
@@ -14624,7 +14842,7 @@ class AuthBroker {
14624
14842
  socket.write(encodeError(id, "INTERNAL", err.message));
14625
14843
  return;
14626
14844
  }
14627
- this.audit({ op: "rm-account", identity: identity2, account: label, ok: true });
14845
+ this.audit({ op: "rm-account", identity: identity2, account: label, accountKind: "google", ok: true });
14628
14846
  socket.write(encodeSuccess(id, { label }));
14629
14847
  }
14630
14848
  async opMicrosoftGetCredentials(socket, id, identity2) {
@@ -14636,30 +14854,30 @@ class AuthBroker {
14636
14854
  const agent = (this.config.agents ?? {})[agentName];
14637
14855
  const account = agent?.microsoft_workspace?.account;
14638
14856
  if (!account) {
14639
- this.audit({ op: "get-credentials", identity: identity2, ok: false, error: "no-microsoft-account-configured" });
14857
+ this.audit({ op: "get-credentials", identity: identity2, accountKind: "microsoft", ok: false, error: "no-microsoft-account-configured" });
14640
14858
  socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", `agent '${agentName}' has no microsoft_workspace.account configured in switchroom.yaml`));
14641
14859
  return;
14642
14860
  }
14643
14861
  const ma = this.config.microsoft_accounts;
14644
14862
  const enabledFor = ma?.[account]?.enabled_for ?? [];
14645
14863
  if (!enabledFor.includes(agentName)) {
14646
- this.audit({ op: "get-credentials", identity: identity2, account, ok: false, error: "acl-deny" });
14864
+ this.audit({ op: "get-credentials", identity: identity2, account, accountKind: "microsoft", ok: false, error: "acl-deny" });
14647
14865
  socket.write(encodeError(id, "FORBIDDEN", `agent '${agentName}' not in microsoft_accounts['${account}'].enabled_for[] — operator must run \`switchroom auth microsoft enable ${account} ${agentName}\``));
14648
14866
  return;
14649
14867
  }
14650
14868
  const creds = readMicrosoftAccountCredentials(this.stateDir, account);
14651
14869
  if (!creds) {
14652
- this.audit({ op: "get-credentials", identity: identity2, account, ok: false, error: "missing-credentials" });
14870
+ this.audit({ op: "get-credentials", identity: identity2, account, accountKind: "microsoft", ok: false, error: "missing-credentials" });
14653
14871
  socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", `no Microsoft credentials for account '${account}' — operator must run \`switchroom auth microsoft account add ${account}\``));
14654
14872
  return;
14655
14873
  }
14656
14874
  const expiresAt = creds.microsoftOauth?.expiresAt;
14657
- this.audit({ op: "get-credentials", identity: identity2, account, ok: true });
14875
+ this.audit({ op: "get-credentials", identity: identity2, account, accountKind: "microsoft", ok: true });
14658
14876
  socket.write(encodeSuccess(id, { account, credentials: creds, expiresAt }));
14659
14877
  }
14660
14878
  async opMicrosoftAddAccount(socket, id, identity2, label, credentials, replace) {
14661
14879
  if (!this.isAdmin(identity2)) {
14662
- this.audit({ op: "add-account", identity: identity2, account: label, ok: false, error: "FORBIDDEN" });
14880
+ this.audit({ op: "add-account", identity: identity2, account: label, accountKind: "microsoft", ok: false, error: "FORBIDDEN" });
14663
14881
  this.respondForbidden(socket, id, "add-account requires admin");
14664
14882
  return;
14665
14883
  }
@@ -14670,7 +14888,7 @@ class AuthBroker {
14670
14888
  return;
14671
14889
  }
14672
14890
  if (microsoftAccountExists(this.stateDir, label) && !replace) {
14673
- this.audit({ op: "add-account", identity: identity2, account: label, ok: false, error: "ACCOUNT_ALREADY_EXISTS" });
14891
+ this.audit({ op: "add-account", identity: identity2, account: label, accountKind: "microsoft", ok: false, error: "ACCOUNT_ALREADY_EXISTS" });
14674
14892
  socket.write(encodeError(id, "ACCOUNT_ALREADY_EXISTS", `microsoft account '${label}' already exists; pass replace:true to overwrite`));
14675
14893
  return;
14676
14894
  }
@@ -14681,12 +14899,12 @@ class AuthBroker {
14681
14899
  return;
14682
14900
  }
14683
14901
  const expiresAt = credentials.microsoftOauth?.expiresAt;
14684
- this.audit({ op: "add-account", identity: identity2, account: label, ok: true, replace });
14902
+ this.audit({ op: "add-account", identity: identity2, account: label, accountKind: "microsoft", ok: true, replace });
14685
14903
  socket.write(encodeSuccess(id, { label, expiresAt }));
14686
14904
  }
14687
14905
  async opMicrosoftRmAccount(socket, id, identity2, label) {
14688
14906
  if (!this.isAdmin(identity2)) {
14689
- this.audit({ op: "rm-account", identity: identity2, account: label, ok: false, error: "FORBIDDEN" });
14907
+ this.audit({ op: "rm-account", identity: identity2, account: label, accountKind: "microsoft", ok: false, error: "FORBIDDEN" });
14690
14908
  this.respondForbidden(socket, id, "rm-account requires admin");
14691
14909
  return;
14692
14910
  }
@@ -14712,7 +14930,7 @@ class AuthBroker {
14712
14930
  socket.write(encodeError(id, "INTERNAL", err.message));
14713
14931
  return;
14714
14932
  }
14715
- this.audit({ op: "rm-account", identity: identity2, account: label, ok: true });
14933
+ this.audit({ op: "rm-account", identity: identity2, account: label, accountKind: "microsoft", ok: true });
14716
14934
  socket.write(encodeSuccess(id, { label }));
14717
14935
  }
14718
14936
  async opListMicrosoftAccounts(socket, id, identity2) {
@@ -14728,12 +14946,12 @@ class AuthBroker {
14728
14946
  accountType: creds.microsoftOauth.accountType
14729
14947
  };
14730
14948
  }).filter((entry) => entry !== null).sort((a, b) => a.account.localeCompare(b.account));
14731
- this.audit({ op: "list-microsoft-accounts", identity: identity2, ok: true });
14949
+ this.audit({ op: "list-microsoft-accounts", identity: identity2, accountKind: "microsoft", ok: true });
14732
14950
  socket.write(encodeSuccess(id, { accounts }));
14733
14951
  }
14734
14952
  async opSetOverride(socket, id, identity2, agentName, account) {
14735
14953
  if (!this.isAdmin(identity2)) {
14736
- this.audit({ op: "set-override", identity: identity2, account: account ?? undefined, ok: false, error: "FORBIDDEN" });
14954
+ this.audit({ op: "set-override", identity: identity2, account: account ?? undefined, accountKind: "claude", ok: false, error: "FORBIDDEN" });
14737
14955
  this.respondForbidden(socket, id, "set-override requires admin");
14738
14956
  return;
14739
14957
  }
@@ -14755,7 +14973,7 @@ class AuthBroker {
14755
14973
  agents[agentName] = { ...cur, auth };
14756
14974
  this.config = { ...this.config, agents };
14757
14975
  this.fanoutForAgent(agentName);
14758
- this.audit({ op: "set-override", identity: identity2, account: account ?? undefined, ok: true });
14976
+ this.audit({ op: "set-override", identity: identity2, account: account ?? undefined, accountKind: "claude", ok: true });
14759
14977
  socket.write(encodeSuccess(id, { agent: agentName, account }));
14760
14978
  }
14761
14979
  async refreshTick() {
@@ -14892,6 +15110,7 @@ class AuthBroker {
14892
15110
  this.shaIndex[label] = sha256Hex(contents);
14893
15111
  this.persistShaIndex();
14894
15112
  this.fanoutToAffectedAgents(label);
15113
+ this.fanoutToAffectedConsumers(label);
14895
15114
  return { kind: "refreshed", newExpiresAt };
14896
15115
  }
14897
15116
  if (outcome.kind === "failed") {
@@ -14913,6 +15132,9 @@ class AuthBroker {
14913
15132
  if (this.fanoutForAgent(name))
14914
15133
  out.push(name);
14915
15134
  }
15135
+ for (const consumerName of this.fanoutAllConsumers()) {
15136
+ out.push(`consumer:${consumerName}`);
15137
+ }
14916
15138
  return out;
14917
15139
  }
14918
15140
  fanoutToAffectedAgents(label) {
@@ -14927,10 +15149,68 @@ class AuthBroker {
14927
15149
  }
14928
15150
  return fanned;
14929
15151
  }
14930
- fanoutFailoverFor(label) {
15152
+ fanoutToAffectedConsumers(label) {
15153
+ const fanned = [];
15154
+ for (const consumer of this.config.auth?.consumers ?? []) {
15155
+ if (!consumer.mirror_dir)
15156
+ continue;
15157
+ const isPinned = consumer.account === label;
15158
+ const effective = this.servingAccountForConsumer(consumer.name);
15159
+ const isEffective = effective === label;
15160
+ if (!isPinned && !isEffective)
15161
+ continue;
15162
+ const toMirror = effective ?? consumer.account;
15163
+ if (this.mirrorAccountToConsumer(toMirror, consumer)) {
15164
+ fanned.push(consumer.name);
15165
+ }
15166
+ }
15167
+ return fanned;
15168
+ }
15169
+ fanoutAllConsumers() {
15170
+ const out = [];
15171
+ for (const consumer of this.config.auth?.consumers ?? []) {
15172
+ if (!consumer.mirror_dir)
15173
+ continue;
15174
+ const effective = this.servingAccountForConsumer(consumer.name);
15175
+ if (!effective)
15176
+ continue;
15177
+ if (this.mirrorAccountToConsumer(effective, consumer))
15178
+ out.push(consumer.name);
15179
+ }
15180
+ return out;
15181
+ }
15182
+ servingAccountForConsumer(name) {
15183
+ const c = (this.config.auth?.consumers ?? []).find((x) => x.name === name);
15184
+ if (!c)
15185
+ return null;
15186
+ return this.accountWithFailover(c.account);
15187
+ }
15188
+ mirrorAccountToConsumer(label, consumer) {
15189
+ const mirrorDir = consumer.mirror_dir;
15190
+ if (!mirrorDir)
15191
+ return false;
15192
+ const targetPath = join4(mirrorDir, ".credentials.json");
15193
+ const credsPath = accountCredentialsPath(label, this.home);
15194
+ if (!existsSync7(credsPath))
15195
+ return false;
15196
+ const mirrorContent = enrichMirrorContent(readFileSync6(credsPath, "utf-8"));
15197
+ try {
15198
+ mkdirSync4(mirrorDir, { recursive: true, mode: 448 });
15199
+ atomicWriteFileSync(targetPath, mirrorContent, 384);
15200
+ try {
15201
+ const uid = consumer.uid ?? 0;
15202
+ chownSync2(targetPath, uid, uid);
15203
+ } catch (err) {
15204
+ this.warnCapChownMissing(err);
15205
+ }
15206
+ return true;
15207
+ } catch (err) {
15208
+ this.logErr(`consumer-mirror ${consumer.name} <- ${label}: ${err.message}`);
15209
+ return false;
15210
+ }
15211
+ }
15212
+ fanoutFailoverTo(label, next) {
14931
15213
  const auth = this.config.auth ?? {};
14932
- const order = auth.fallback_order ?? [];
14933
- const next = this.nextHealthyAccount(label, order);
14934
15214
  if (!next || next === label)
14935
15215
  return [];
14936
15216
  const rolled = [];
@@ -15048,6 +15328,7 @@ class AuthBroker {
15048
15328
  this.shaIndex = this.readJson("sha-index.json") ?? {};
15049
15329
  this.thresholdViolations = this.readJson("threshold-violations.json") ?? {};
15050
15330
  this.notificationClaims = this.readJson("notification-claims.json") ?? {};
15331
+ this.lastQuotaCache = this.readJson("last-quota.json") ?? {};
15051
15332
  }
15052
15333
  readJson(name) {
15053
15334
  const p = join4(this.stateDir, name);
@@ -15062,6 +15343,9 @@ class AuthBroker {
15062
15343
  persistQuota() {
15063
15344
  atomicWriteJsonSync(join4(this.stateDir, "quota.json"), this.quota, 384);
15064
15345
  }
15346
+ persistLastQuotaCache() {
15347
+ atomicWriteJsonSync(join4(this.stateDir, "last-quota.json"), this.lastQuotaCache, 384);
15348
+ }
15065
15349
  persistNotificationClaims() {
15066
15350
  atomicWriteJsonSync(join4(this.stateDir, "notification-claims.json"), this.notificationClaims, 384);
15067
15351
  }
@@ -15098,6 +15382,7 @@ class AuthBroker {
15098
15382
  op: entry.op,
15099
15383
  peer,
15100
15384
  account: entry.account,
15385
+ accountKind: entry.accountKind,
15101
15386
  ok: entry.ok,
15102
15387
  error: entry.error,
15103
15388
  replace: entry.replace
@@ -15188,7 +15473,8 @@ class AuthBroker {
15188
15473
  quota: { ...this.quota },
15189
15474
  shaIndex: { ...this.shaIndex },
15190
15475
  thresholdViolations: { ...this.thresholdViolations },
15191
- listeners: [...this.listeners.keys()]
15476
+ listeners: [...this.listeners.keys()],
15477
+ lastQuotaCache: structuredClone(this.lastQuotaCache)
15192
15478
  };
15193
15479
  }
15194
15480
  _fanoutAll() {