@jsonstudio/rcc 0.89.2202 → 0.90.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/README.md +27 -0
  2. package/dist/build-info.js +2 -2
  3. package/dist/build-info.js.map +1 -1
  4. package/dist/cli/commands/claude.js +1 -0
  5. package/dist/cli/commands/claude.js.map +1 -1
  6. package/dist/cli/commands/codex.js +1 -0
  7. package/dist/cli/commands/codex.js.map +1 -1
  8. package/dist/cli/commands/guardian-daemon.d.ts +2 -0
  9. package/dist/cli/commands/guardian-daemon.js +299 -0
  10. package/dist/cli/commands/guardian-daemon.js.map +1 -0
  11. package/dist/cli/commands/init/camoufox.js +1 -1
  12. package/dist/cli/commands/init/camoufox.js.map +1 -1
  13. package/dist/cli/commands/init.js +15 -1
  14. package/dist/cli/commands/init.js.map +1 -1
  15. package/dist/cli/commands/launcher/types.d.ts +6 -0
  16. package/dist/cli/commands/launcher-kernel.js +456 -109
  17. package/dist/cli/commands/launcher-kernel.js.map +1 -1
  18. package/dist/cli/commands/port.js +28 -8
  19. package/dist/cli/commands/port.js.map +1 -1
  20. package/dist/cli/commands/restart.d.ts +4 -0
  21. package/dist/cli/commands/restart.js +91 -42
  22. package/dist/cli/commands/restart.js.map +1 -1
  23. package/dist/cli/commands/start-types.d.ts +4 -0
  24. package/dist/cli/commands/start.js +112 -68
  25. package/dist/cli/commands/start.js.map +1 -1
  26. package/dist/cli/commands/stop.d.ts +3 -0
  27. package/dist/cli/commands/stop.js +30 -63
  28. package/dist/cli/commands/stop.js.map +1 -1
  29. package/dist/cli/config/init-config.js +15 -1
  30. package/dist/cli/config/init-config.js.map +1 -1
  31. package/dist/cli/config/init-provider-catalog.js +13 -5
  32. package/dist/cli/config/init-provider-catalog.js.map +1 -1
  33. package/dist/cli/guardian/client.d.ts +38 -0
  34. package/dist/cli/guardian/client.js +237 -0
  35. package/dist/cli/guardian/client.js.map +1 -0
  36. package/dist/cli/guardian/paths.d.ts +7 -0
  37. package/dist/cli/guardian/paths.js +13 -0
  38. package/dist/cli/guardian/paths.js.map +1 -0
  39. package/dist/cli/guardian/types.d.ts +30 -0
  40. package/dist/cli/guardian/types.js +2 -0
  41. package/dist/cli/guardian/types.js.map +1 -0
  42. package/dist/cli/register/guardian-daemon-command.d.ts +2 -0
  43. package/dist/cli/register/guardian-daemon-command.js +5 -0
  44. package/dist/cli/register/guardian-daemon-command.js.map +1 -0
  45. package/dist/cli/server/port-utils.js +57 -1
  46. package/dist/cli/server/port-utils.js.map +1 -1
  47. package/dist/cli.js +48 -0
  48. package/dist/cli.js.map +1 -1
  49. package/dist/commands/oauth.js +6 -6
  50. package/dist/commands/oauth.js.map +1 -1
  51. package/dist/commands/provider-update.js +12 -0
  52. package/dist/commands/provider-update.js.map +1 -1
  53. package/dist/config/routecodex-config-loader.js +66 -1
  54. package/dist/config/routecodex-config-loader.js.map +1 -1
  55. package/dist/config/virtual-router-builder.js +18 -0
  56. package/dist/config/virtual-router-builder.js.map +1 -1
  57. package/dist/config/virtual-router-types.js +20 -5
  58. package/dist/config/virtual-router-types.js.map +1 -1
  59. package/dist/daemon-admin-ui/assets/index-C8vP_c5E.js +15 -0
  60. package/dist/daemon-admin-ui/assets/index-DjIoHmNv.css +1 -0
  61. package/dist/daemon-admin-ui/index.html +13 -0
  62. package/dist/docs/daemon-admin-ui.html +328 -57
  63. package/dist/index.d.ts +9 -0
  64. package/dist/index.js +268 -10
  65. package/dist/index.js.map +1 -1
  66. package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.d.ts +1 -0
  67. package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.js +36 -0
  68. package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.js.map +1 -1
  69. package/dist/manager/modules/quota/provider-quota-daemon.events.js +50 -1
  70. package/dist/manager/modules/quota/provider-quota-daemon.events.js.map +1 -1
  71. package/dist/providers/auth/antigravity-user-agent.js +78 -31
  72. package/dist/providers/auth/antigravity-user-agent.js.map +1 -1
  73. package/dist/providers/auth/gemini-cli-userinfo-helper.js +94 -63
  74. package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -1
  75. package/dist/providers/auth/iflow-userinfo-helper.js +1 -1
  76. package/dist/providers/auth/iflow-userinfo-helper.js.map +1 -1
  77. package/dist/providers/auth/oauth-error-message.d.ts +1 -0
  78. package/dist/providers/auth/oauth-error-message.js +44 -0
  79. package/dist/providers/auth/oauth-error-message.js.map +1 -0
  80. package/dist/providers/auth/oauth-lifecycle/error-detection.js +42 -8
  81. package/dist/providers/auth/oauth-lifecycle/error-detection.js.map +1 -1
  82. package/dist/providers/auth/oauth-lifecycle/token-io.d.ts +1 -0
  83. package/dist/providers/auth/oauth-lifecycle/token-io.js +12 -0
  84. package/dist/providers/auth/oauth-lifecycle/token-io.js.map +1 -1
  85. package/dist/providers/auth/oauth-lifecycle.js +502 -87
  86. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  87. package/dist/providers/auth/oauth-repair-cooldown.js +2 -7
  88. package/dist/providers/auth/oauth-repair-cooldown.js.map +1 -1
  89. package/dist/providers/auth/oauth-repair-env.js +3 -5
  90. package/dist/providers/auth/oauth-repair-env.js.map +1 -1
  91. package/dist/providers/auth/oauth-utils/error-extraction.js +42 -8
  92. package/dist/providers/auth/oauth-utils/error-extraction.js.map +1 -1
  93. package/dist/providers/auth/qwen-userinfo-helper.js +18 -3
  94. package/dist/providers/auth/qwen-userinfo-helper.js.map +1 -1
  95. package/dist/providers/auth/tokenfile-auth.d.ts +1 -0
  96. package/dist/providers/auth/tokenfile-auth.js +15 -9
  97. package/dist/providers/auth/tokenfile-auth.js.map +1 -1
  98. package/dist/providers/core/config/camoufox-actions.d.ts +31 -0
  99. package/dist/providers/core/config/camoufox-actions.js +461 -0
  100. package/dist/providers/core/config/camoufox-actions.js.map +1 -0
  101. package/dist/providers/core/config/camoufox-launcher.d.ts +3 -0
  102. package/dist/providers/core/config/camoufox-launcher.js +518 -160
  103. package/dist/providers/core/config/camoufox-launcher.js.map +1 -1
  104. package/dist/providers/core/config/oauth-flows.js +6 -44
  105. package/dist/providers/core/config/oauth-flows.js.map +1 -1
  106. package/dist/providers/core/config/provider-oauth-configs.js +51 -7
  107. package/dist/providers/core/config/provider-oauth-configs.js.map +1 -1
  108. package/dist/providers/core/config/service-profiles.js +13 -4
  109. package/dist/providers/core/config/service-profiles.js.map +1 -1
  110. package/dist/providers/core/runtime/http-transport-provider.d.ts +1 -0
  111. package/dist/providers/core/runtime/http-transport-provider.js +60 -1
  112. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  113. package/dist/providers/core/runtime/provider-error-classifier.js +32 -15
  114. package/dist/providers/core/runtime/provider-error-classifier.js.map +1 -1
  115. package/dist/providers/core/runtime/provider-family-profile-utils.js +1 -1
  116. package/dist/providers/core/runtime/provider-family-profile-utils.js.map +1 -1
  117. package/dist/providers/core/runtime/provider-response-postprocessor.js +61 -14
  118. package/dist/providers/core/runtime/provider-response-postprocessor.js.map +1 -1
  119. package/dist/providers/core/strategies/oauth-auth-code-flow.d.ts +1 -0
  120. package/dist/providers/core/strategies/oauth-auth-code-flow.js +124 -19
  121. package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
  122. package/dist/providers/core/strategies/oauth-device-flow.js +6 -3
  123. package/dist/providers/core/strategies/oauth-device-flow.js.map +1 -1
  124. package/dist/providers/profile/families/iflow-profile.js +83 -10
  125. package/dist/providers/profile/families/iflow-profile.js.map +1 -1
  126. package/dist/providers/profile/families/qwen-profile.js +203 -0
  127. package/dist/providers/profile/families/qwen-profile.js.map +1 -1
  128. package/dist/scripts/camoufox/launch-auth.mjs +112 -5
  129. package/dist/server/handlers/config-admin-handler.js +9 -2
  130. package/dist/server/handlers/config-admin-handler.js.map +1 -1
  131. package/dist/server/handlers/handler-utils.js +3 -14
  132. package/dist/server/handlers/handler-utils.js.map +1 -1
  133. package/dist/server/handlers/logging.js +3 -4
  134. package/dist/server/handlers/logging.js.map +1 -1
  135. package/dist/server/runtime/http-server/clock-client-reaper.d.ts +1 -0
  136. package/dist/server/runtime/http-server/clock-client-reaper.js +21 -15
  137. package/dist/server/runtime/http-server/clock-client-reaper.js.map +1 -1
  138. package/dist/server/runtime/http-server/clock-client-registry-utils.d.ts +4 -0
  139. package/dist/server/runtime/http-server/clock-client-registry-utils.js +74 -16
  140. package/dist/server/runtime/http-server/clock-client-registry-utils.js.map +1 -1
  141. package/dist/server/runtime/http-server/clock-client-registry.d.ts +15 -0
  142. package/dist/server/runtime/http-server/clock-client-registry.js +300 -6
  143. package/dist/server/runtime/http-server/clock-client-registry.js.map +1 -1
  144. package/dist/server/runtime/http-server/clock-client-routes.js +49 -19
  145. package/dist/server/runtime/http-server/clock-client-routes.js.map +1 -1
  146. package/dist/server/runtime/http-server/clock-daemon-log-throttle.d.ts +16 -0
  147. package/dist/server/runtime/http-server/clock-daemon-log-throttle.js +49 -0
  148. package/dist/server/runtime/http-server/clock-daemon-log-throttle.js.map +1 -1
  149. package/dist/server/runtime/http-server/clock-scope-resolution.d.ts +14 -0
  150. package/dist/server/runtime/http-server/clock-scope-resolution.js +212 -0
  151. package/dist/server/runtime/http-server/clock-scope-resolution.js.map +1 -0
  152. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js +5 -3
  153. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js.map +1 -1
  154. package/dist/server/runtime/http-server/daemon-admin/control-handler.js +104 -15
  155. package/dist/server/runtime/http-server/daemon-admin/control-handler.js.map +1 -1
  156. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +2 -2
  157. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js.map +1 -1
  158. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.d.ts +24 -0
  159. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.js +316 -70
  160. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.js.map +1 -1
  161. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +190 -1
  162. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -1
  163. package/dist/server/runtime/http-server/daemon-admin/routing-policy.js +18 -29
  164. package/dist/server/runtime/http-server/daemon-admin/routing-policy.js.map +1 -1
  165. package/dist/server/runtime/http-server/daemon-admin/stats-handler.js +2 -0
  166. package/dist/server/runtime/http-server/daemon-admin/stats-handler.js.map +1 -1
  167. package/dist/server/runtime/http-server/daemon-admin-routes.d.ts +8 -1
  168. package/dist/server/runtime/http-server/daemon-admin-routes.js +30 -0
  169. package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -1
  170. package/dist/server/runtime/http-server/executor/client-injection-flow.d.ts +14 -0
  171. package/dist/server/runtime/http-server/executor/client-injection-flow.js +287 -0
  172. package/dist/server/runtime/http-server/executor/client-injection-flow.js.map +1 -0
  173. package/dist/server/runtime/http-server/executor/index.d.ts +1 -1
  174. package/dist/server/runtime/http-server/executor/index.js +1 -1
  175. package/dist/server/runtime/http-server/executor/index.js.map +1 -1
  176. package/dist/server/runtime/http-server/executor/provider-response-converter.js +236 -62
  177. package/dist/server/runtime/http-server/executor/provider-response-converter.js.map +1 -1
  178. package/dist/server/runtime/http-server/executor/provider-response-utils.js +5 -0
  179. package/dist/server/runtime/http-server/executor/provider-response-utils.js.map +1 -1
  180. package/dist/server/runtime/http-server/executor/request-executor-core-utils.d.ts +2 -0
  181. package/dist/server/runtime/http-server/executor/request-executor-core-utils.js +60 -0
  182. package/dist/server/runtime/http-server/executor/request-executor-core-utils.js.map +1 -1
  183. package/dist/server/runtime/http-server/executor/request-retry-helpers.js +20 -8
  184. package/dist/server/runtime/http-server/executor/request-retry-helpers.js.map +1 -1
  185. package/dist/server/runtime/http-server/executor/sse-error-handler.d.ts +1 -0
  186. package/dist/server/runtime/http-server/executor/sse-error-handler.js +13 -2
  187. package/dist/server/runtime/http-server/executor/sse-error-handler.js.map +1 -1
  188. package/dist/server/runtime/http-server/executor/usage-aggregator.d.ts +0 -12
  189. package/dist/server/runtime/http-server/executor/usage-aggregator.js +84 -88
  190. package/dist/server/runtime/http-server/executor/usage-aggregator.js.map +1 -1
  191. package/dist/server/runtime/http-server/executor-metadata.js +328 -7
  192. package/dist/server/runtime/http-server/executor-metadata.js.map +1 -1
  193. package/dist/server/runtime/http-server/executor-response.d.ts +1 -0
  194. package/dist/server/runtime/http-server/executor-response.js +52 -50
  195. package/dist/server/runtime/http-server/executor-response.js.map +1 -1
  196. package/dist/server/runtime/http-server/http-server-bootstrap.js +55 -6
  197. package/dist/server/runtime/http-server/http-server-bootstrap.js.map +1 -1
  198. package/dist/server/runtime/http-server/http-server-clock-daemon.d.ts +1 -0
  199. package/dist/server/runtime/http-server/http-server-clock-daemon.js +199 -44
  200. package/dist/server/runtime/http-server/http-server-clock-daemon.js.map +1 -1
  201. package/dist/server/runtime/http-server/http-server-lifecycle.js +4 -4
  202. package/dist/server/runtime/http-server/http-server-lifecycle.js.map +1 -1
  203. package/dist/server/runtime/http-server/hub-shadow-compare.js +1 -1
  204. package/dist/server/runtime/http-server/hub-shadow-compare.js.map +1 -1
  205. package/dist/server/runtime/http-server/index.d.ts +1 -0
  206. package/dist/server/runtime/http-server/index.js +1 -0
  207. package/dist/server/runtime/http-server/index.js.map +1 -1
  208. package/dist/server/runtime/http-server/middleware.js +82 -4
  209. package/dist/server/runtime/http-server/middleware.js.map +1 -1
  210. package/dist/server/runtime/http-server/request-executor.js +26 -7
  211. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  212. package/dist/server/runtime/http-server/routes.d.ts +2 -1
  213. package/dist/server/runtime/http-server/routes.js +4 -2
  214. package/dist/server/runtime/http-server/routes.js.map +1 -1
  215. package/dist/server/runtime/http-server/session-dir.js +12 -1
  216. package/dist/server/runtime/http-server/session-dir.js.map +1 -1
  217. package/dist/server/runtime/http-server/stats-manager.d.ts +35 -0
  218. package/dist/server/runtime/http-server/stats-manager.js +269 -21
  219. package/dist/server/runtime/http-server/stats-manager.js.map +1 -1
  220. package/dist/server/runtime/http-server/stopmessage-scope-rebind.d.ts +13 -0
  221. package/dist/server/runtime/http-server/stopmessage-scope-rebind.js +168 -0
  222. package/dist/server/runtime/http-server/stopmessage-scope-rebind.js.map +1 -0
  223. package/dist/server/runtime/http-server/tmux-session-probe.d.ts +10 -0
  224. package/dist/server/runtime/http-server/tmux-session-probe.js +97 -0
  225. package/dist/server/runtime/http-server/tmux-session-probe.js.map +1 -1
  226. package/dist/server-lifecycle/port-utils.d.ts +2 -1
  227. package/dist/server-lifecycle/port-utils.js +84 -4
  228. package/dist/server-lifecycle/port-utils.js.map +1 -1
  229. package/dist/token-daemon/index.d.ts +1 -0
  230. package/dist/token-daemon/index.js +17 -12
  231. package/dist/token-daemon/index.js.map +1 -1
  232. package/dist/utils/clock-client-token.d.ts +2 -1
  233. package/dist/utils/clock-client-token.js +52 -8
  234. package/dist/utils/clock-client-token.js.map +1 -1
  235. package/dist/utils/clock-scope-trace.d.ts +11 -0
  236. package/dist/utils/clock-scope-trace.js +41 -0
  237. package/dist/utils/clock-scope-trace.js.map +1 -0
  238. package/dist/utils/llms-engine-shadow.js +1 -1
  239. package/dist/utils/llms-engine-shadow.js.map +1 -1
  240. package/docs/DAEMON_CONTROL_PLANE.md +1 -0
  241. package/docs/ROUTING_POLICY_SCHEMA.md +4 -2
  242. package/docs/daemon-admin-ui.html +328 -57
  243. package/docs/design/servertool-stopmessage-lifecycle.md +109 -0
  244. package/docs/exec-command-guard-policy.example.v1.json +7 -1
  245. package/docs/providers/antigravity-gemini-provider-compat.md +2 -2
  246. package/package.json +23 -6
  247. package/scripts/build-core.mjs +12 -0
  248. package/scripts/camoufox/launch-auth.mjs +112 -5
  249. package/scripts/ci/repo-sanity.mjs +1 -0
  250. package/scripts/cleanup-stale-server-pids.mjs +142 -0
  251. package/scripts/install-global.sh +8 -0
  252. package/scripts/install-verify.mjs +33 -16
  253. package/scripts/run-bg.sh +226 -43
  254. package/scripts/run-fg-gtimeout.sh +158 -14
  255. package/scripts/tests/blackbox-rcc-vs-routecodex-antigravity.mjs +3 -3
  256. package/scripts/tests/ci-jest.mjs +9 -1
  257. package/scripts/triage-errorsamples.mjs +216 -0
  258. package/scripts/verify-codex-error-samples.mjs +92 -15
  259. package/scripts/verify-e2e-toolcall.mjs +12 -1
  260. package/scripts/verify-install-e2e.mjs +69 -28
@@ -1489,6 +1489,14 @@
1489
1489
  <select id="routingSourceSelect" style="width: 420px;"></select>
1490
1490
  <button id="refreshRoutingSourcesBtn">Refresh sources</button>
1491
1491
  </div>
1492
+ <div class="row" style="margin-bottom: 10px;">
1493
+ <label for="routingGroupSelect">group</label>
1494
+ <select id="routingGroupSelect" style="width: 240px;"></select>
1495
+ <input id="routingGroupNameInput" type="text" placeholder="new group id" style="width: 200px;" />
1496
+ <button id="createRoutingGroupBtn">Create/Copy group</button>
1497
+ <button id="deleteRoutingGroupBtn" class="danger">Delete group</button>
1498
+ <span id="routingActiveGroupHint" class="mono muted" style="margin-left:auto;"></span>
1499
+ </div>
1492
1500
  <div class="row" style="margin-bottom: 10px;">
1493
1501
  <label for="routingQuotaModeSelect">mode</label>
1494
1502
  <select id="routingQuotaModeSelect" style="width: 160px;">
@@ -1509,9 +1517,9 @@
1509
1517
  </div>
1510
1518
  <div class="row" style="margin-bottom: 10px;">
1511
1519
  <button id="loadRoutingBtn" class="primary">Load</button>
1512
- <button id="saveRoutingBtn" class="primary">Save</button>
1513
- <button id="saveRoutingRestartLocalBtn">Save + Restart local</button>
1514
- <button id="saveRoutingRestartAllBtn">Save + Restart all</button>
1520
+ <button id="saveRoutingBtn" class="primary">Save group</button>
1521
+ <button id="saveRoutingRestartLocalBtn">Activate group</button>
1522
+ <button id="saveRoutingRestartAllBtn">Activate + Restart all</button>
1515
1523
  <button id="refreshRoutingPoolBtn">Refresh pool status</button>
1516
1524
  <button id="routingRegExpandBtn">Expand all</button>
1517
1525
  <button id="routingRegCollapseBtn">Collapse all</button>
@@ -1551,6 +1559,9 @@
1551
1559
  routingTargetsUpdatedAt: 0,
1552
1560
  routingSources: [],
1553
1561
  routingSourcesUpdatedAt: 0,
1562
+ routingGroups: {},
1563
+ routingActiveGroupId: "",
1564
+ routingSelectedGroupId: "",
1554
1565
  routingLocation: "virtualrouter.routing",
1555
1566
  routingHasLoadBalancing: false,
1556
1567
  credentials: [],
@@ -4245,9 +4256,13 @@
4245
4256
  }
4246
4257
 
4247
4258
  function buildRoutingPolicyEditorValue(snapshot) {
4248
- const routingObj = toPlainObjectOrNull(snapshot && snapshot.routing) || {};
4249
- const hasLoadBalancing = Boolean(snapshot && snapshot.hasLoadBalancing === true);
4250
- const loadBalancingObj = toPlainObjectOrNull(snapshot && snapshot.loadBalancing) || {};
4259
+ const source = toPlainObjectOrNull(snapshot) || {};
4260
+ const routingObj = toPlainObjectOrNull(source.routing) || {};
4261
+ const loadBalancingObj = toPlainObjectOrNull(source.loadBalancing) || {};
4262
+ const hasLoadBalancing =
4263
+ typeof source.hasLoadBalancing === "boolean"
4264
+ ? Boolean(source.hasLoadBalancing)
4265
+ : Boolean(loadBalancingObj && Object.keys(loadBalancingObj).length > 0);
4251
4266
  UI.routingHasLoadBalancing = hasLoadBalancing;
4252
4267
  return {
4253
4268
  routing: routingObj,
@@ -4276,30 +4291,114 @@
4276
4291
  };
4277
4292
  }
4278
4293
 
4279
- async function loadRouting() {
4280
- setLog("routingOpLog", "");
4281
- const auth = UI.adminAuth ? UI.adminAuth : await refreshAdminAuthStatus();
4282
- if (!auth || !auth.authenticated) {
4283
- notifyUnauthorizedOnce("routing");
4284
- return;
4285
- }
4286
- try {
4287
- const selectedPath = textOf($("routingSourceSelect").value || "").trim();
4288
- const query = selectedPath ? `?path=${encodeURIComponent(selectedPath)}` : "";
4289
- const out = await apiFetch(`/config/routing${query}`);
4290
- UI.routingLocation = out.location || "virtualrouter.routing";
4291
- routingEditorSetValue(buildRoutingPolicyEditorValue(out));
4292
- setLog(
4293
- "routingOpLog",
4294
- `Loaded. Path: ${out.path || "—"}\nLocation: ${UI.routingLocation}\nloadBalancing: ${UI.routingHasLoadBalancing ? "present" : "absent"}`
4295
- );
4296
- toast("Routing policy loaded.", "ok");
4297
- } catch (e) {
4298
- if (e && e.status === 401) notifyUnauthorizedOnce("routing");
4299
- setLog("routingOpLog", `Load failed: ${e.message}`);
4300
- toast(`Load failed: ${e.message}`);
4301
- }
4302
- }
4294
+ function normalizeRoutingGroupsMap(value) {
4295
+ const out = {};
4296
+ if (!value || typeof value !== "object" || Array.isArray(value)) return out;
4297
+ for (const [groupId, groupPolicy] of Object.entries(value)) {
4298
+ const policyObj = toPlainObjectOrNull(groupPolicy);
4299
+ if (!groupId || !policyObj) continue;
4300
+ out[groupId] = policyObj;
4301
+ }
4302
+ return out;
4303
+ }
4304
+
4305
+ function currentRoutingSourcePath() {
4306
+ return textOf($("routingSourceSelect").value || "").trim();
4307
+ }
4308
+
4309
+ function currentRoutingSourceQuery() {
4310
+ const selectedPath = currentRoutingSourcePath();
4311
+ return selectedPath ? `?path=${encodeURIComponent(selectedPath)}` : "";
4312
+ }
4313
+
4314
+ function updateRoutingGroupHint() {
4315
+ const hint = $("routingActiveGroupHint");
4316
+ if (!hint) return;
4317
+ const selected = textOf(UI.routingSelectedGroupId || "").trim();
4318
+ const active = textOf(UI.routingActiveGroupId || "").trim();
4319
+ if (!selected && !active) {
4320
+ hint.textContent = "group: —";
4321
+ return;
4322
+ }
4323
+ hint.textContent = `selected=${selected || "—"} · active=${active || "—"}`;
4324
+ }
4325
+
4326
+ function renderRoutingGroupSelect(preferredGroupId) {
4327
+ const select = $("routingGroupSelect");
4328
+ if (!select) return "";
4329
+ const groups = normalizeRoutingGroupsMap(UI.routingGroups);
4330
+ let ids = Object.keys(groups).sort((a, b) => a.localeCompare(b));
4331
+ if (!ids.length) {
4332
+ groups.default = { routing: {} };
4333
+ ids = ["default"];
4334
+ }
4335
+ UI.routingGroups = groups;
4336
+
4337
+ const prev = textOf(select.value || "").trim();
4338
+ select.replaceChildren();
4339
+ for (const id of ids) {
4340
+ const opt = document.createElement("option");
4341
+ opt.value = id;
4342
+ opt.textContent = id === UI.routingActiveGroupId ? `${id} (active)` : id;
4343
+ select.appendChild(opt);
4344
+ }
4345
+ const nextSelected =
4346
+ (preferredGroupId && groups[preferredGroupId] ? preferredGroupId : "")
4347
+ || (prev && groups[prev] ? prev : "")
4348
+ || (UI.routingSelectedGroupId && groups[UI.routingSelectedGroupId] ? UI.routingSelectedGroupId : "")
4349
+ || (UI.routingActiveGroupId && groups[UI.routingActiveGroupId] ? UI.routingActiveGroupId : "")
4350
+ || ids[0];
4351
+ select.value = nextSelected;
4352
+ UI.routingSelectedGroupId = nextSelected;
4353
+ updateRoutingGroupHint();
4354
+ return nextSelected;
4355
+ }
4356
+
4357
+ function getSelectedRoutingGroupId() {
4358
+ const select = $("routingGroupSelect");
4359
+ const selected = textOf((select && select.value) || UI.routingSelectedGroupId || "").trim();
4360
+ if (selected) UI.routingSelectedGroupId = selected;
4361
+ return selected;
4362
+ }
4363
+
4364
+ function loadSelectedRoutingGroupIntoEditor() {
4365
+ const selectedGroupId = getSelectedRoutingGroupId();
4366
+ const groups = normalizeRoutingGroupsMap(UI.routingGroups);
4367
+ const selectedPolicy = toPlainObjectOrNull(groups[selectedGroupId]) || { routing: {} };
4368
+ routingEditorSetValue(buildRoutingPolicyEditorValue(selectedPolicy));
4369
+ updateRoutingGroupHint();
4370
+ }
4371
+
4372
+ async function loadRouting(preferredGroupId) {
4373
+ setLog("routingOpLog", "");
4374
+ const auth = UI.adminAuth ? UI.adminAuth : await refreshAdminAuthStatus();
4375
+ if (!auth || !auth.authenticated) {
4376
+ notifyUnauthorizedOnce("routing");
4377
+ return;
4378
+ }
4379
+ try {
4380
+ const selectedPath = currentRoutingSourcePath();
4381
+ const query = currentRoutingSourceQuery();
4382
+ const out = await apiFetch(`/config/routing/groups${query}`);
4383
+ UI.routingLocation = out.location || "virtualrouter.routing";
4384
+ UI.routingGroups = normalizeRoutingGroupsMap(out.groups);
4385
+ UI.routingActiveGroupId = textOf(out.activeGroupId || "").trim();
4386
+ const selectedGroupId = renderRoutingGroupSelect(preferredGroupId);
4387
+ if (!UI.routingActiveGroupId && selectedGroupId) {
4388
+ UI.routingActiveGroupId = selectedGroupId;
4389
+ }
4390
+ loadSelectedRoutingGroupIntoEditor();
4391
+ setLog(
4392
+ "routingOpLog",
4393
+ `Loaded groups. Path: ${out.path || selectedPath || "—"}\nLocation: ${UI.routingLocation}\nselected: ${UI.routingSelectedGroupId || "—"}\nactive: ${UI.routingActiveGroupId || "—"}\nloadBalancing: ${UI.routingHasLoadBalancing ? "present" : "absent"}`
4394
+ );
4395
+ toast("Routing groups loaded.", "ok");
4396
+ } catch (e) {
4397
+ if (e && e.status === 401) notifyUnauthorizedOnce("routing");
4398
+ setLog("routingOpLog", `Load failed: ${e.message}`);
4399
+ toast(`Load failed: ${e.message}`);
4400
+ }
4401
+ }
4303
4402
 
4304
4403
  async function saveRouting() {
4305
4404
  setLog("routingOpLog", "");
@@ -4309,30 +4408,41 @@
4309
4408
  return null;
4310
4409
  }
4311
4410
  try {
4312
- const selectedPath = textOf($("routingSourceSelect").value || "").trim();
4313
- const query = selectedPath ? `?path=${encodeURIComponent(selectedPath)}` : "";
4411
+ const selectedPath = currentRoutingSourcePath();
4412
+ const query = currentRoutingSourceQuery();
4413
+ const selectedGroupId = getSelectedRoutingGroupId();
4414
+ if (!selectedGroupId) {
4415
+ throw new Error("No routing group selected");
4416
+ }
4314
4417
  const policy = parseRoutingPolicyEditorValue(routingEditorGetValue() || {});
4315
- const payload = {
4316
- routing: policy.routing,
4317
- location: UI.routingLocation,
4318
- path: selectedPath || undefined
4418
+ const basePolicy = toPlainObjectOrNull((UI.routingGroups || {})[selectedGroupId]) || {};
4419
+ const nextPolicy = {
4420
+ ...basePolicy,
4421
+ routing: policy.routing
4319
4422
  };
4320
4423
  if (policy.shouldWriteLoadBalancing) {
4321
- payload.loadBalancing = policy.loadBalancing || {};
4424
+ nextPolicy.loadBalancing = policy.loadBalancing || {};
4425
+ } else {
4426
+ delete nextPolicy.loadBalancing;
4322
4427
  }
4323
- const out = await apiFetch(`/config/routing${query}`, {
4428
+ const out = await apiFetch(`/config/routing/groups/${encodeURIComponent(selectedGroupId)}${query}`, {
4324
4429
  method: "PUT",
4325
- body: JSON.stringify(payload)
4430
+ body: JSON.stringify({
4431
+ policy: nextPolicy,
4432
+ location: UI.routingLocation,
4433
+ path: selectedPath || undefined
4434
+ })
4326
4435
  });
4327
- UI.routingLocation = out.location || UI.routingLocation;
4328
- if (typeof out.hasLoadBalancing === "boolean") {
4329
- UI.routingHasLoadBalancing = out.hasLoadBalancing;
4330
- }
4436
+ UI.routingLocation = out.location || UI.routingLocation;
4437
+ UI.routingGroups = normalizeRoutingGroupsMap(out.groups);
4438
+ UI.routingActiveGroupId = textOf(out.activeGroupId || UI.routingActiveGroupId || "").trim();
4439
+ renderRoutingGroupSelect(selectedGroupId);
4440
+ loadSelectedRoutingGroupIntoEditor();
4331
4441
  setLog(
4332
4442
  "routingOpLog",
4333
- `Saved. Path: ${out.path || "—"}\nLocation: ${UI.routingLocation}\nloadBalancing: ${UI.routingHasLoadBalancing ? "present" : "absent"}\nRestart required to apply.`
4443
+ `Saved group. Path: ${out.path || selectedPath || "—"}\nLocation: ${UI.routingLocation}\nselected: ${UI.routingSelectedGroupId || ""}\nactive: ${UI.routingActiveGroupId || ""}`
4334
4444
  );
4335
- toast("Routing policy saved.", "ok");
4445
+ toast("Routing group saved.", "ok");
4336
4446
  routingEditorSetDirty(false);
4337
4447
  return out;
4338
4448
  } catch (e) {
@@ -4344,17 +4454,39 @@
4344
4454
  }
4345
4455
 
4346
4456
  async function saveRoutingAndRestartLocal() {
4347
- const saved = await saveRouting();
4348
- if (!saved) return;
4349
- if (!confirm("Restart this RouteCodex runtime now to apply routing policy changes?")) return;
4457
+ const selectedGroupId = getSelectedRoutingGroupId();
4458
+ if (!selectedGroupId) {
4459
+ toast("No routing group selected.");
4460
+ return;
4461
+ }
4462
+ if (routingEditorState.dirty) {
4463
+ const shouldSave = confirm("Current group has unsaved changes. Save now before activation?");
4464
+ if (!shouldSave) return;
4465
+ const saved = await saveRouting();
4466
+ if (!saved) return;
4467
+ }
4468
+ if (!confirm(`Activate routing group \"${selectedGroupId}\" and reload local runtime now?`)) return;
4350
4469
  try {
4351
- const out = await apiFetch("/daemon/restart", { method: "POST" });
4352
- const warnings = Array.isArray(out.warnings) && out.warnings.length ? `\nWarnings:\n- ${out.warnings.join("\n- ")}` : "";
4470
+ const selectedPath = currentRoutingSourcePath();
4471
+ const query = currentRoutingSourceQuery();
4472
+ const out = await apiFetch(`/config/routing/groups/activate${query}`, {
4473
+ method: "POST",
4474
+ body: JSON.stringify({
4475
+ groupId: selectedGroupId,
4476
+ location: UI.routingLocation,
4477
+ path: selectedPath || undefined
4478
+ })
4479
+ });
4480
+ UI.routingLocation = out.location || UI.routingLocation;
4481
+ UI.routingGroups = normalizeRoutingGroupsMap(out.groups);
4482
+ UI.routingActiveGroupId = textOf(out.activeGroupId || selectedGroupId).trim();
4483
+ renderRoutingGroupSelect(selectedGroupId);
4484
+ loadSelectedRoutingGroupIntoEditor();
4353
4485
  setLog(
4354
4486
  "routingOpLog",
4355
- `Saved + restarted local.\nconfigPath: ${out.configPath || "—"}\nreloadedAt: ${out.reloadedAt || "—"}${warnings}`
4487
+ `Activated local.\nPath: ${out.path || selectedPath || "—"}\nselected: ${UI.routingSelectedGroupId || "—"}\nactive: ${UI.routingActiveGroupId || "—"}\nselfReload: ${JSON.stringify(out.selfReload ?? null, null, 2)}`
4356
4488
  );
4357
- toast("Routing saved and local runtime restarted.", "ok");
4489
+ toast("Routing group activated (local).", "ok");
4358
4490
  await refreshStatus();
4359
4491
  await refreshProviders();
4360
4492
  await refreshCredentials();
@@ -4369,13 +4501,37 @@
4369
4501
  }
4370
4502
 
4371
4503
  async function saveRoutingAndRestartAll() {
4372
- const saved = await saveRouting();
4373
- if (!saved) return;
4504
+ const selectedGroupId = getSelectedRoutingGroupId();
4505
+ if (!selectedGroupId) {
4506
+ toast("No routing group selected.");
4507
+ return;
4508
+ }
4509
+ if (routingEditorState.dirty) {
4510
+ const shouldSave = confirm("Current group has unsaved changes. Save now before activation?");
4511
+ if (!shouldSave) return;
4512
+ const saved = await saveRouting();
4513
+ if (!saved) return;
4514
+ }
4374
4515
  if (!confirm("Broadcast restart all local RouteCodex servers now?")) return;
4375
4516
  try {
4517
+ const selectedPath = currentRoutingSourcePath();
4518
+ const query = currentRoutingSourceQuery();
4519
+ const activated = await apiFetch(`/config/routing/groups/activate${query}`, {
4520
+ method: "POST",
4521
+ body: JSON.stringify({
4522
+ groupId: selectedGroupId,
4523
+ location: UI.routingLocation,
4524
+ path: selectedPath || undefined
4525
+ })
4526
+ });
4527
+ UI.routingLocation = activated.location || UI.routingLocation;
4528
+ UI.routingGroups = normalizeRoutingGroupsMap(activated.groups);
4529
+ UI.routingActiveGroupId = textOf(activated.activeGroupId || selectedGroupId).trim();
4530
+ renderRoutingGroupSelect(selectedGroupId);
4531
+ loadSelectedRoutingGroupIntoEditor();
4376
4532
  const out = await controlMutate("servers.restart", {});
4377
- setLog("routingOpLog", `Saved + restart-all requested.\n${JSON.stringify(out, null, 2)}`);
4378
- toast("Routing saved and restart-all requested.", "ok");
4533
+ setLog("routingOpLog", `Activated + restart-all requested.\n${JSON.stringify(out, null, 2)}`);
4534
+ toast("Routing group activated and restart-all requested.", "ok");
4379
4535
  await refreshControl();
4380
4536
  await refreshStatus();
4381
4537
  } catch (e) {
@@ -4384,6 +4540,106 @@
4384
4540
  }
4385
4541
  }
4386
4542
 
4543
+ function buildRoutingTemplatePolicyForNewGroup() {
4544
+ const selectedGroupId = getSelectedRoutingGroupId();
4545
+ const groups = normalizeRoutingGroupsMap(UI.routingGroups);
4546
+ const base = toPlainObjectOrNull(groups[selectedGroupId]) || { routing: {} };
4547
+ if (!routingEditorState.dirty) {
4548
+ return { ...base };
4549
+ }
4550
+ const parsed = parseRoutingPolicyEditorValue(routingEditorGetValue() || {});
4551
+ const next = {
4552
+ ...base,
4553
+ routing: parsed.routing
4554
+ };
4555
+ if (parsed.shouldWriteLoadBalancing) {
4556
+ next.loadBalancing = parsed.loadBalancing || {};
4557
+ } else {
4558
+ delete next.loadBalancing;
4559
+ }
4560
+ return next;
4561
+ }
4562
+
4563
+ async function createRoutingGroup() {
4564
+ setLog("routingOpLog", "");
4565
+ const auth = UI.adminAuth ? UI.adminAuth : await refreshAdminAuthStatus();
4566
+ if (!auth || !auth.authenticated) {
4567
+ notifyUnauthorizedOnce("routing groups");
4568
+ return;
4569
+ }
4570
+ const input = $("routingGroupNameInput");
4571
+ const groupId = textOf((input && input.value) || "").trim();
4572
+ if (!groupId) {
4573
+ toast("Group id is required.");
4574
+ return;
4575
+ }
4576
+ const selectedPath = currentRoutingSourcePath();
4577
+ const query = currentRoutingSourceQuery();
4578
+ try {
4579
+ const policy = buildRoutingTemplatePolicyForNewGroup();
4580
+ const out = await apiFetch(`/config/routing/groups/${encodeURIComponent(groupId)}${query}`, {
4581
+ method: "PUT",
4582
+ body: JSON.stringify({
4583
+ policy,
4584
+ location: UI.routingLocation,
4585
+ path: selectedPath || undefined
4586
+ })
4587
+ });
4588
+ UI.routingLocation = out.location || UI.routingLocation;
4589
+ UI.routingGroups = normalizeRoutingGroupsMap(out.groups);
4590
+ UI.routingActiveGroupId = textOf(out.activeGroupId || UI.routingActiveGroupId || "").trim();
4591
+ renderRoutingGroupSelect(groupId);
4592
+ loadSelectedRoutingGroupIntoEditor();
4593
+ if (input) input.value = "";
4594
+ setLog(
4595
+ "routingOpLog",
4596
+ `Created/updated group.\nPath: ${out.path || selectedPath || "—"}\nselected: ${UI.routingSelectedGroupId || "—"}\nactive: ${UI.routingActiveGroupId || "—"}`
4597
+ );
4598
+ toast("Routing group created/updated.", "ok");
4599
+ } catch (e) {
4600
+ if (e && e.status === 401) notifyUnauthorizedOnce("routing groups");
4601
+ setLog("routingOpLog", `Create group failed: ${e && e.message ? e.message : String(e)}`);
4602
+ toast(`Create group failed: ${e && e.message ? e.message : String(e)}`);
4603
+ }
4604
+ }
4605
+
4606
+ async function deleteRoutingGroup() {
4607
+ setLog("routingOpLog", "");
4608
+ const auth = UI.adminAuth ? UI.adminAuth : await refreshAdminAuthStatus();
4609
+ if (!auth || !auth.authenticated) {
4610
+ notifyUnauthorizedOnce("routing groups");
4611
+ return;
4612
+ }
4613
+ const groupId = getSelectedRoutingGroupId();
4614
+ if (!groupId) {
4615
+ toast("No routing group selected.");
4616
+ return;
4617
+ }
4618
+ if (!confirm(`Delete routing group \"${groupId}\"?`)) return;
4619
+ try {
4620
+ const queryBase = currentRoutingSourceQuery();
4621
+ const locationPart = UI.routingLocation ? `${queryBase ? "&" : "?"}location=${encodeURIComponent(UI.routingLocation)}` : "";
4622
+ const query = `${queryBase}${locationPart}`;
4623
+ const out = await apiFetch(`/config/routing/groups/${encodeURIComponent(groupId)}${query}`, {
4624
+ method: "DELETE"
4625
+ });
4626
+ UI.routingLocation = out.location || UI.routingLocation;
4627
+ UI.routingGroups = normalizeRoutingGroupsMap(out.groups);
4628
+ UI.routingActiveGroupId = textOf(out.activeGroupId || UI.routingActiveGroupId || "").trim();
4629
+ renderRoutingGroupSelect();
4630
+ loadSelectedRoutingGroupIntoEditor();
4631
+ setLog(
4632
+ "routingOpLog",
4633
+ `Deleted group \"${groupId}\".\nPath: ${out.path || currentRoutingSourcePath() || "—"}\nselected: ${UI.routingSelectedGroupId || "—"}\nactive: ${UI.routingActiveGroupId || "—"}`
4634
+ );
4635
+ toast("Routing group deleted.", "ok");
4636
+ } catch (e) {
4637
+ if (e && e.status === 401) notifyUnauthorizedOnce("routing groups");
4638
+ setLog("routingOpLog", `Delete group failed: ${e && e.message ? e.message : String(e)}`);
4639
+ toast(`Delete group failed: ${e && e.message ? e.message : String(e)}`);
4640
+ }
4641
+ }
4642
+
4387
4643
  async function refreshRoutingSources() {
4388
4644
  const select = $("routingSourceSelect");
4389
4645
  if (!select) return;
@@ -5642,8 +5898,23 @@
5642
5898
  $("saveRoutingBtn").addEventListener("click", saveRouting);
5643
5899
  $("saveRoutingRestartLocalBtn").addEventListener("click", () => void saveRoutingAndRestartLocal());
5644
5900
  $("saveRoutingRestartAllBtn").addEventListener("click", () => void saveRoutingAndRestartAll());
5901
+ $("createRoutingGroupBtn").addEventListener("click", () => void createRoutingGroup());
5902
+ $("deleteRoutingGroupBtn").addEventListener("click", () => void deleteRoutingGroup());
5645
5903
  $("refreshRoutingSourcesBtn").addEventListener("click", refreshRoutingSources);
5646
- $("routingSourceSelect").addEventListener("change", loadRouting);
5904
+ $("routingSourceSelect").addEventListener("change", async () => {
5905
+ if (routingEditorState.dirty && !confirm("Discard unsaved routing editor changes and switch source?")) {
5906
+ return;
5907
+ }
5908
+ await loadRouting();
5909
+ });
5910
+ $("routingGroupSelect").addEventListener("change", () => {
5911
+ if (routingEditorState.dirty && !confirm("Discard unsaved routing editor changes and switch group?")) {
5912
+ renderRoutingGroupSelect(UI.routingSelectedGroupId || UI.routingActiveGroupId || "");
5913
+ return;
5914
+ }
5915
+ UI.routingSelectedGroupId = textOf($("routingGroupSelect").value || "").trim();
5916
+ loadSelectedRoutingGroupIntoEditor();
5917
+ });
5647
5918
  $("routingRegExpandBtn").addEventListener("click", () => setAllDetailsOpen($("routingKvEditor"), true));
5648
5919
  $("routingRegCollapseBtn").addEventListener("click", () => setAllDetailsOpen($("routingKvEditor"), false));
5649
5920
  $("refreshRoutingPoolBtn").addEventListener("click", async () => {
@@ -0,0 +1,109 @@
1
+ # ServerTool / StopMessage Lifecycle (tmux-only)
2
+
3
+ ## 1. Scope
4
+
5
+ This document defines the only valid lifecycle for:
6
+
7
+ - `stopMessage`
8
+ - `clock` client injection
9
+ - `continue_execution` client injection
10
+
11
+ Design goals:
12
+
13
+ 1. Trigger logic stays in Chat Process response orchestration.
14
+ 2. Execution path is tmux stdin injection only.
15
+ 3. `stopMessage` does not use nested reenter model requests.
16
+ 4. State is scoped by `tmux:<sessionId>` only.
17
+
18
+ ## 2. Current Code Entry Points
19
+
20
+ - Request metadata resolution: `src/server/runtime/http-server/executor-metadata.ts`
21
+ - Scope resolution: `src/server/runtime/http-server/clock-scope-resolution.ts`
22
+ - Injection implementation: `src/server/runtime/http-server/executor/client-injection-flow.ts`
23
+ - ServerTool orchestration (response side): `sharedmodule/llmswitch-core/src/servertool/engine.ts`
24
+ - StopMessage handler: `sharedmodule/llmswitch-core/src/servertool/handlers/stop-message-auto.ts`
25
+ - Routing state store (tmux scope selection): `sharedmodule/llmswitch-core/src/router/virtual-router/engine/routing-state/store.ts`
26
+
27
+ ## 3. Lifecycle (Approved Target)
28
+
29
+ 1. Client starts via `routecodex codex` / `routecodex claude` inside tmux.
30
+ 2. Client request carries tmux metadata (`tmuxSessionId` family fields / headers).
31
+ 3. Server resolves tmux session and marks `clientInjectReady=true`.
32
+ 4. User sends `<**stopMessage,...**>` instruction.
33
+ 5. Chat-process parser saves instruction state under `tmux:<sessionId>`.
34
+ 6. Model response reaches response orchestration.
35
+ 7. StopMessage matcher runs in chat-process servertool stage.
36
+ 8. If matched, build reviewer followup text.
37
+ 9. Dispatch to client injection (tmux stdin) directly.
38
+ 10. Injection success: increment `used`, keep/update state.
39
+ 11. Injection failure: clear stopMessage state for that tmux scope, skip followup.
40
+ 12. Main request must still complete (no request-level hard failure only because stopMessage inject failed).
41
+
42
+ ## 4. Hard Rules
43
+
44
+ 1. Split dispatch:
45
+ - Normal servertools (e.g. search/vision) may use `reenterPipeline`.
46
+ - `stopMessage/clock/continue_execution` must use client injection dispatcher only.
47
+
48
+ 2. No fallback:
49
+ - No old session-based fallback compare.
50
+ - No daemon-only fallback for stopMessage matching.
51
+
52
+ 3. Scope:
53
+ - All stopMessage state read/write keys are `tmux:<sessionId>`.
54
+
55
+ 4. Set behavior:
56
+ - If tmux scope missing when setting stopMessage: drop set instruction, keep request normal.
57
+
58
+ 5. Trigger behavior:
59
+ - If tmux not ready at trigger time: clear stale state and skip followup (no loop).
60
+
61
+ ## 5. Client Restart Rebinding (New Requirement)
62
+
63
+ When tmux client restarts and re-registers:
64
+
65
+ 1. Registration updates daemon->tmux mapping immediately.
66
+ 2. If previous stopMessage state exists under old tmux scope and the same daemon/client identity is re-registered with a new tmux session, migrate stopMessage binding to the new `tmux:<newSessionId>` scope.
67
+ 3. Migration is atomic:
68
+ - copy state to new tmux scope
69
+ - delete old tmux scope state
70
+ 4. If old tmux session is already gone and no valid rebind target exists, clean old state.
71
+
72
+ This prevents:
73
+
74
+ - trigger using stale scope
75
+ - inject lookup miss loops after client restart
76
+
77
+ ## 6. Observability Requirements
78
+
79
+ Required logs:
80
+
81
+ 1. stopMessage set parse:
82
+ - parse success/fail
83
+ - resolved tmux scope
84
+
85
+ 2. stopMessage match:
86
+ - matched/miss
87
+ - reason
88
+ - scope
89
+
90
+ 3. client injection:
91
+ - selected tmux session
92
+ - submit key result
93
+ - success/failure reason
94
+
95
+ 4. state mutation:
96
+ - set/override
97
+ - trigger used counter
98
+ - clear on failure
99
+ - rebind migration (old scope -> new scope)
100
+
101
+ ## 7. Validation Checklist
102
+
103
+ 1. Set stopMessage in tmux session A -> state key is `tmux:A`.
104
+ 2. Trigger matched response -> tmux A receives injected text + enter.
105
+ 3. Counter decrements and persists.
106
+ 4. Restart client with new tmux session B (same client identity) -> state migrates to `tmux:B`.
107
+ 5. Next trigger injects into B, not A.
108
+ 6. Injection failure clears active state and does not create reenter loop.
109
+ 7. Non-stop servertools still execute through normal reenter path.
@@ -36,7 +36,13 @@
36
36
  "pattern": "\\b(xargs\\s+rm\\b|for\\s+[^;\\n]+;\\s*do\\s+rm\\b|while\\s+[^;\\n]+;\\s*do\\s+rm\\b)",
37
37
  "flags": "i",
38
38
  "reason": "Loop/batch deletion is not allowed"
39
+ },
40
+ {
41
+ "id": "deny-mass-kill-commands",
42
+ "type": "regex",
43
+ "pattern": "\\bpkill\\b|\\bkillall\\b|\\btaskkill\\b|\\bxargs\\b[^\\n]*\\bkill\\b|\\blsof\\b[^\\n]*\\|[^\\n]*\\bxargs\\b[^\\n]*\\bkill\\b",
44
+ "flags": "i",
45
+ "reason": "mass kill command is not allowed"
39
46
  }
40
47
  ]
41
48
  }
42
-
@@ -171,9 +171,9 @@ antigravity/<version> <os>/<arch>
171
171
  版本解析顺序(从高到低):
172
172
  1. 显式 UA:`ROUTECODEX_ANTIGRAVITY_USER_AGENT` / `RCC_ANTIGRAVITY_USER_AGENT`(完全覆盖)
173
173
  2. 显式版本号:`ROUTECODEX_ANTIGRAVITY_UA_VERSION` / `RCC_ANTIGRAVITY_UA_VERSION`
174
- 3. 远程拉取:`VERSION_URL`(可用 `ROUTECODEX_ANTIGRAVITY_UA_DISABLE_REMOTE=1` 禁用)
174
+ 3. 远程拉取:`VERSION_URL`,失败时回退 `CHANGELOG_URL`(可用 `ROUTECODEX_ANTIGRAVITY_UA_DISABLE_REMOTE=1` 禁用)
175
175
  4. 本地磁盘 cache:`~/.routecodex/state/antigravity-ua-version.json`
176
- 5. 最后兜底:`LEGACY_PINNED_VERSION`(只保证“有值”,不保证不过期)
176
+ 5. 最后兜底:`KNOWN_STABLE_VERSION`(当前 `4.1.24`,对齐 Antigravity-Manager)
177
177
 
178
178
  ### 4.3 `<os>/<arch>` suffix 如何从指纹推断(并且为何禁 linux)
179
179