@jsonstudio/rcc 0.89.2239 → 0.90.89

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 (338) 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 +4 -8
  5. package/dist/cli/commands/claude.js.map +1 -1
  6. package/dist/cli/commands/codex.js +6 -3
  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/launcher/index.d.ts +1 -1
  14. package/dist/cli/commands/launcher/types.d.ts +7 -1
  15. package/dist/cli/commands/launcher/utils.d.ts +4 -1
  16. package/dist/cli/commands/launcher/utils.js +18 -8
  17. package/dist/cli/commands/launcher/utils.js.map +1 -1
  18. package/dist/cli/commands/launcher-kernel.d.ts +1 -1
  19. package/dist/cli/commands/launcher-kernel.js +608 -249
  20. package/dist/cli/commands/launcher-kernel.js.map +1 -1
  21. package/dist/cli/commands/port.js +28 -8
  22. package/dist/cli/commands/port.js.map +1 -1
  23. package/dist/cli/commands/restart.d.ts +4 -0
  24. package/dist/cli/commands/restart.js +91 -42
  25. package/dist/cli/commands/restart.js.map +1 -1
  26. package/dist/cli/commands/{clock-admin.d.ts → session-admin.d.ts} +2 -2
  27. package/dist/cli/commands/{clock-admin.js → session-admin.js} +17 -17
  28. package/dist/cli/commands/session-admin.js.map +1 -0
  29. package/dist/cli/commands/{tmux-inject.d.ts → session-inject.d.ts} +2 -2
  30. package/dist/cli/commands/{tmux-inject.js → session-inject.js} +12 -12
  31. package/dist/cli/commands/session-inject.js.map +1 -0
  32. package/dist/cli/commands/start-types.d.ts +4 -0
  33. package/dist/cli/commands/start-utils.d.ts +1 -0
  34. package/dist/cli/commands/start-utils.js +3 -0
  35. package/dist/cli/commands/start-utils.js.map +1 -1
  36. package/dist/cli/commands/start.js +122 -72
  37. package/dist/cli/commands/start.js.map +1 -1
  38. package/dist/cli/commands/stop.d.ts +3 -0
  39. package/dist/cli/commands/stop.js +30 -63
  40. package/dist/cli/commands/stop.js.map +1 -1
  41. package/dist/cli/config/init-provider-catalog.js +8 -3
  42. package/dist/cli/config/init-provider-catalog.js.map +1 -1
  43. package/dist/cli/guardian/client.d.ts +38 -0
  44. package/dist/cli/guardian/client.js +237 -0
  45. package/dist/cli/guardian/client.js.map +1 -0
  46. package/dist/cli/guardian/paths.d.ts +7 -0
  47. package/dist/cli/guardian/paths.js +13 -0
  48. package/dist/cli/guardian/paths.js.map +1 -0
  49. package/dist/cli/guardian/types.d.ts +30 -0
  50. package/dist/cli/guardian/types.js +2 -0
  51. package/dist/cli/guardian/types.js.map +1 -0
  52. package/dist/cli/register/guardian-daemon-command.d.ts +2 -0
  53. package/dist/cli/register/guardian-daemon-command.js +5 -0
  54. package/dist/cli/register/guardian-daemon-command.js.map +1 -0
  55. package/dist/cli/register/session-admin-command.d.ts +3 -0
  56. package/dist/cli/register/session-admin-command.js +5 -0
  57. package/dist/cli/register/session-admin-command.js.map +1 -0
  58. package/dist/cli/register/session-inject-command.d.ts +3 -0
  59. package/dist/cli/register/session-inject-command.js +5 -0
  60. package/dist/cli/register/session-inject-command.js.map +1 -0
  61. package/dist/cli/server/port-utils.js +57 -1
  62. package/dist/cli/server/port-utils.js.map +1 -1
  63. package/dist/cli.js +52 -4
  64. package/dist/cli.js.map +1 -1
  65. package/dist/commands/oauth.js +6 -6
  66. package/dist/commands/oauth.js.map +1 -1
  67. package/dist/config/provider-v2-loader.js +18 -3
  68. package/dist/config/provider-v2-loader.js.map +1 -1
  69. package/dist/config/routecodex-config-loader.js +184 -9
  70. package/dist/config/routecodex-config-loader.js.map +1 -1
  71. package/dist/config/unified-config-paths.js +22 -0
  72. package/dist/config/unified-config-paths.js.map +1 -1
  73. package/dist/config/virtual-router-builder.js +18 -5
  74. package/dist/config/virtual-router-builder.js.map +1 -1
  75. package/dist/config/virtual-router-types.js +20 -5
  76. package/dist/config/virtual-router-types.js.map +1 -1
  77. package/dist/daemon-admin-ui/assets/index-C8vP_c5E.js +15 -0
  78. package/dist/daemon-admin-ui/assets/index-DjIoHmNv.css +1 -0
  79. package/dist/daemon-admin-ui/index.html +13 -0
  80. package/dist/docs/daemon-admin-ui.html +334 -63
  81. package/dist/index.d.ts +9 -0
  82. package/dist/index.js +268 -10
  83. package/dist/index.js.map +1 -1
  84. package/dist/manager/modules/quota/provider-key-normalization.js +1 -10
  85. package/dist/manager/modules/quota/provider-key-normalization.js.map +1 -1
  86. package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.d.ts +1 -0
  87. package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.js +36 -0
  88. package/dist/manager/modules/quota/provider-quota-daemon.error-helpers.js.map +1 -1
  89. package/dist/manager/modules/quota/provider-quota-daemon.events.js +89 -49
  90. package/dist/manager/modules/quota/provider-quota-daemon.events.js.map +1 -1
  91. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js +2 -16
  92. package/dist/manager/modules/quota/provider-quota-daemon.model-backoff.js.map +1 -1
  93. package/dist/manager/modules/token/index.d.ts +1 -0
  94. package/dist/manager/modules/token/index.js +6 -1
  95. package/dist/manager/modules/token/index.js.map +1 -1
  96. package/dist/manager/types.d.ts +1 -0
  97. package/dist/modules/llmswitch/bridge/state-integrations.js +1 -1
  98. package/dist/modules/llmswitch/bridge/state-integrations.js.map +1 -1
  99. package/dist/providers/auth/antigravity-user-agent.js +78 -31
  100. package/dist/providers/auth/antigravity-user-agent.js.map +1 -1
  101. package/dist/providers/auth/gemini-cli-userinfo-helper.js +94 -63
  102. package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -1
  103. package/dist/providers/auth/iflow-userinfo-helper.js +1 -1
  104. package/dist/providers/auth/iflow-userinfo-helper.js.map +1 -1
  105. package/dist/providers/auth/oauth-error-message.d.ts +1 -0
  106. package/dist/providers/auth/oauth-error-message.js +44 -0
  107. package/dist/providers/auth/oauth-error-message.js.map +1 -0
  108. package/dist/providers/auth/oauth-lifecycle/error-detection.js +42 -8
  109. package/dist/providers/auth/oauth-lifecycle/error-detection.js.map +1 -1
  110. package/dist/providers/auth/oauth-lifecycle/token-io.d.ts +1 -0
  111. package/dist/providers/auth/oauth-lifecycle/token-io.js +12 -0
  112. package/dist/providers/auth/oauth-lifecycle/token-io.js.map +1 -1
  113. package/dist/providers/auth/oauth-lifecycle.js +502 -87
  114. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  115. package/dist/providers/auth/oauth-repair-env.js +3 -5
  116. package/dist/providers/auth/oauth-repair-env.js.map +1 -1
  117. package/dist/providers/auth/oauth-utils/error-extraction.js +42 -8
  118. package/dist/providers/auth/oauth-utils/error-extraction.js.map +1 -1
  119. package/dist/providers/core/config/camoufox-actions.d.ts +31 -0
  120. package/dist/providers/core/config/camoufox-actions.js +470 -0
  121. package/dist/providers/core/config/camoufox-actions.js.map +1 -0
  122. package/dist/providers/core/config/camoufox-launcher.d.ts +3 -0
  123. package/dist/providers/core/config/camoufox-launcher.js +553 -159
  124. package/dist/providers/core/config/camoufox-launcher.js.map +1 -1
  125. package/dist/providers/core/config/oauth-flows.js +6 -44
  126. package/dist/providers/core/config/oauth-flows.js.map +1 -1
  127. package/dist/providers/core/config/provider-oauth-configs.js +51 -7
  128. package/dist/providers/core/config/provider-oauth-configs.js.map +1 -1
  129. package/dist/providers/core/config/service-profiles.js +2 -2
  130. package/dist/providers/core/config/service-profiles.js.map +1 -1
  131. package/dist/providers/core/runtime/base-provider-runtime-helpers.js +15 -2
  132. package/dist/providers/core/runtime/base-provider-runtime-helpers.js.map +1 -1
  133. package/dist/providers/core/runtime/provider-error-classifier.js +32 -15
  134. package/dist/providers/core/runtime/provider-error-classifier.js.map +1 -1
  135. package/dist/providers/core/runtime/provider-family-profile-utils.js +1 -1
  136. package/dist/providers/core/runtime/provider-family-profile-utils.js.map +1 -1
  137. package/dist/providers/core/runtime/provider-response-postprocessor.js +61 -14
  138. package/dist/providers/core/runtime/provider-response-postprocessor.js.map +1 -1
  139. package/dist/providers/core/strategies/oauth-auth-code-flow.d.ts +1 -0
  140. package/dist/providers/core/strategies/oauth-auth-code-flow.js +124 -19
  141. package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
  142. package/dist/providers/core/strategies/oauth-device-flow.js +32 -6
  143. package/dist/providers/core/strategies/oauth-device-flow.js.map +1 -1
  144. package/dist/providers/core/utils/provider-error-reporter.js +51 -0
  145. package/dist/providers/core/utils/provider-error-reporter.js.map +1 -1
  146. package/dist/providers/profile/families/iflow-profile.js +83 -10
  147. package/dist/providers/profile/families/iflow-profile.js.map +1 -1
  148. package/dist/scripts/camoufox/launch-auth.mjs +112 -5
  149. package/dist/server/handlers/config-admin-handler.js +9 -2
  150. package/dist/server/handlers/config-admin-handler.js.map +1 -1
  151. package/dist/server/handlers/handler-response-utils.js +3 -6
  152. package/dist/server/handlers/handler-response-utils.js.map +1 -1
  153. package/dist/server/handlers/handler-utils.js +14 -17
  154. package/dist/server/handlers/handler-utils.js.map +1 -1
  155. package/dist/server/handlers/logging.js +3 -4
  156. package/dist/server/handlers/logging.js.map +1 -1
  157. package/dist/server/handlers/responses-handler.js +5 -3
  158. package/dist/server/handlers/responses-handler.js.map +1 -1
  159. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js +5 -3
  160. package/dist/server/runtime/http-server/daemon-admin/auth-handler.js.map +1 -1
  161. package/dist/server/runtime/http-server/daemon-admin/control-handler.js +104 -15
  162. package/dist/server/runtime/http-server/daemon-admin/control-handler.js.map +1 -1
  163. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js +2 -2
  164. package/dist/server/runtime/http-server/daemon-admin/credentials-handler.js.map +1 -1
  165. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.d.ts +24 -0
  166. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.js +316 -70
  167. package/dist/server/runtime/http-server/daemon-admin/providers-handler-routing-utils.js.map +1 -1
  168. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js +190 -1
  169. package/dist/server/runtime/http-server/daemon-admin/providers-handler.js.map +1 -1
  170. package/dist/server/runtime/http-server/daemon-admin/routing-policy.d.ts +1 -1
  171. package/dist/server/runtime/http-server/daemon-admin/routing-policy.js +21 -32
  172. package/dist/server/runtime/http-server/daemon-admin/routing-policy.js.map +1 -1
  173. package/dist/server/runtime/http-server/daemon-admin/stats-handler.js +2 -0
  174. package/dist/server/runtime/http-server/daemon-admin/stats-handler.js.map +1 -1
  175. package/dist/server/runtime/http-server/daemon-admin-routes.d.ts +8 -1
  176. package/dist/server/runtime/http-server/daemon-admin-routes.js +30 -0
  177. package/dist/server/runtime/http-server/daemon-admin-routes.js.map +1 -1
  178. package/dist/server/runtime/http-server/executor/client-injection-flow.d.ts +14 -0
  179. package/dist/server/runtime/http-server/executor/client-injection-flow.js +297 -0
  180. package/dist/server/runtime/http-server/executor/client-injection-flow.js.map +1 -0
  181. package/dist/server/runtime/http-server/executor/index.d.ts +1 -1
  182. package/dist/server/runtime/http-server/executor/index.js +1 -1
  183. package/dist/server/runtime/http-server/executor/index.js.map +1 -1
  184. package/dist/server/runtime/http-server/executor/provider-response-converter.js +281 -70
  185. package/dist/server/runtime/http-server/executor/provider-response-converter.js.map +1 -1
  186. package/dist/server/runtime/http-server/executor/provider-runtime-resolver.js +8 -6
  187. package/dist/server/runtime/http-server/executor/provider-runtime-resolver.js.map +1 -1
  188. package/dist/server/runtime/http-server/executor/request-executor-core-utils.d.ts +1 -0
  189. package/dist/server/runtime/http-server/executor/request-executor-core-utils.js +12 -0
  190. package/dist/server/runtime/http-server/executor/request-executor-core-utils.js.map +1 -1
  191. package/dist/server/runtime/http-server/executor/request-retry-helpers.d.ts +1 -1
  192. package/dist/server/runtime/http-server/executor/request-retry-helpers.js +23 -19
  193. package/dist/server/runtime/http-server/executor/request-retry-helpers.js.map +1 -1
  194. package/dist/server/runtime/http-server/executor/retry-engine.d.ts +2 -2
  195. package/dist/server/runtime/http-server/executor/retry-engine.js +2 -2
  196. package/dist/server/runtime/http-server/executor/retry-engine.js.map +1 -1
  197. package/dist/server/runtime/http-server/executor/sse-error-handler.d.ts +1 -0
  198. package/dist/server/runtime/http-server/executor/sse-error-handler.js +13 -2
  199. package/dist/server/runtime/http-server/executor/sse-error-handler.js.map +1 -1
  200. package/dist/server/runtime/http-server/executor/usage-aggregator.d.ts +0 -12
  201. package/dist/server/runtime/http-server/executor/usage-aggregator.js +89 -90
  202. package/dist/server/runtime/http-server/executor/usage-aggregator.js.map +1 -1
  203. package/dist/server/runtime/http-server/executor-metadata.js +318 -17
  204. package/dist/server/runtime/http-server/executor-metadata.js.map +1 -1
  205. package/dist/server/runtime/http-server/executor-provider.d.ts +1 -0
  206. package/dist/server/runtime/http-server/executor-provider.js +5 -1
  207. package/dist/server/runtime/http-server/executor-provider.js.map +1 -1
  208. package/dist/server/runtime/http-server/executor-response.d.ts +1 -0
  209. package/dist/server/runtime/http-server/executor-response.js +52 -58
  210. package/dist/server/runtime/http-server/executor-response.js.map +1 -1
  211. package/dist/server/runtime/http-server/http-server-bootstrap.js +50 -6
  212. package/dist/server/runtime/http-server/http-server-bootstrap.js.map +1 -1
  213. package/dist/server/runtime/http-server/http-server-lifecycle.js +6 -5
  214. package/dist/server/runtime/http-server/http-server-lifecycle.js.map +1 -1
  215. package/dist/server/runtime/http-server/http-server-runtime-setup.js +1 -1
  216. package/dist/server/runtime/http-server/http-server-runtime-setup.js.map +1 -1
  217. package/dist/server/runtime/http-server/http-server-session-daemon.d.ts +6 -0
  218. package/dist/server/runtime/http-server/http-server-session-daemon.js +404 -0
  219. package/dist/server/runtime/http-server/http-server-session-daemon.js.map +1 -0
  220. package/dist/server/runtime/http-server/hub-shadow-compare.js +1 -1
  221. package/dist/server/runtime/http-server/hub-shadow-compare.js.map +1 -1
  222. package/dist/server/runtime/http-server/index.d.ts +11 -10
  223. package/dist/server/runtime/http-server/index.js +21 -20
  224. package/dist/server/runtime/http-server/index.js.map +1 -1
  225. package/dist/server/runtime/http-server/managed-process-probe.js +1 -1
  226. package/dist/server/runtime/http-server/managed-process-probe.js.map +1 -1
  227. package/dist/server/runtime/http-server/middleware.js +91 -5
  228. package/dist/server/runtime/http-server/middleware.js.map +1 -1
  229. package/dist/server/runtime/http-server/request-executor.js +19 -9
  230. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  231. package/dist/server/runtime/http-server/routes.d.ts +2 -1
  232. package/dist/server/runtime/http-server/routes.js +7 -5
  233. package/dist/server/runtime/http-server/routes.js.map +1 -1
  234. package/dist/server/runtime/http-server/{clock-client-reaper.d.ts → session-client-reaper.d.ts} +6 -6
  235. package/dist/server/runtime/http-server/{clock-client-reaper.js → session-client-reaper.js} +26 -49
  236. package/dist/server/runtime/http-server/session-client-reaper.js.map +1 -0
  237. package/dist/server/runtime/http-server/{clock-client-registry-utils.d.ts → session-client-registry-utils.d.ts} +14 -10
  238. package/dist/server/runtime/http-server/{clock-client-registry-utils.js → session-client-registry-utils.js} +77 -19
  239. package/dist/server/runtime/http-server/session-client-registry-utils.js.map +1 -0
  240. package/dist/server/runtime/http-server/{clock-client-registry.d.ts → session-client-registry.d.ts} +26 -11
  241. package/dist/server/runtime/http-server/{clock-client-registry.js → session-client-registry.js} +305 -11
  242. package/dist/server/runtime/http-server/session-client-registry.js.map +1 -0
  243. package/dist/server/runtime/http-server/{clock-client-route-utils.d.ts → session-client-route-utils.d.ts} +1 -1
  244. package/dist/server/runtime/http-server/{clock-client-route-utils.js → session-client-route-utils.js} +4 -4
  245. package/dist/server/runtime/http-server/session-client-route-utils.js.map +1 -0
  246. package/dist/server/runtime/http-server/session-client-routes.d.ts +2 -0
  247. package/dist/server/runtime/http-server/{clock-client-routes.js → session-client-routes.js} +107 -59
  248. package/dist/server/runtime/http-server/session-client-routes.js.map +1 -0
  249. package/dist/server/runtime/http-server/session-daemon-inject-config.d.ts +1 -0
  250. package/dist/server/runtime/http-server/{clock-daemon-inject-config.js → session-daemon-inject-config.js} +2 -2
  251. package/dist/server/runtime/http-server/session-daemon-inject-config.js.map +1 -0
  252. package/dist/server/runtime/http-server/session-daemon-log-throttle.d.ts +28 -0
  253. package/dist/server/runtime/http-server/session-daemon-log-throttle.js +105 -0
  254. package/dist/server/runtime/http-server/session-daemon-log-throttle.js.map +1 -0
  255. package/dist/server/runtime/http-server/session-dir.js +12 -1
  256. package/dist/server/runtime/http-server/session-dir.js.map +1 -1
  257. package/dist/server/runtime/http-server/session-scope-resolution.d.ts +14 -0
  258. package/dist/server/runtime/http-server/session-scope-resolution.js +208 -0
  259. package/dist/server/runtime/http-server/session-scope-resolution.js.map +1 -0
  260. package/dist/server/runtime/http-server/stats-manager.d.ts +35 -0
  261. package/dist/server/runtime/http-server/stats-manager.js +269 -21
  262. package/dist/server/runtime/http-server/stats-manager.js.map +1 -1
  263. package/dist/server/runtime/http-server/stopmessage-scope-rebind.d.ts +21 -0
  264. package/dist/server/runtime/http-server/stopmessage-scope-rebind.js +197 -0
  265. package/dist/server/runtime/http-server/stopmessage-scope-rebind.js.map +1 -0
  266. package/dist/server/runtime/http-server/tmux-session-probe.d.ts +10 -0
  267. package/dist/server/runtime/http-server/tmux-session-probe.js +98 -1
  268. package/dist/server/runtime/http-server/tmux-session-probe.js.map +1 -1
  269. package/dist/server-lifecycle/port-utils.d.ts +2 -1
  270. package/dist/server-lifecycle/port-utils.js +84 -4
  271. package/dist/server-lifecycle/port-utils.js.map +1 -1
  272. package/dist/token-daemon/index.d.ts +1 -0
  273. package/dist/token-daemon/index.js +17 -12
  274. package/dist/token-daemon/index.js.map +1 -1
  275. package/dist/token-daemon/token-daemon.d.ts +2 -0
  276. package/dist/token-daemon/token-daemon.js +18 -10
  277. package/dist/token-daemon/token-daemon.js.map +1 -1
  278. package/dist/utils/llms-engine-shadow.js +1 -1
  279. package/dist/utils/llms-engine-shadow.js.map +1 -1
  280. package/dist/utils/log-helpers.js +46 -0
  281. package/dist/utils/log-helpers.js.map +1 -1
  282. package/dist/utils/session-client-token.d.ts +4 -0
  283. package/dist/utils/session-client-token.js +93 -0
  284. package/dist/utils/session-client-token.js.map +1 -0
  285. package/dist/utils/session-scope-trace.d.ts +11 -0
  286. package/dist/utils/session-scope-trace.js +41 -0
  287. package/dist/utils/session-scope-trace.js.map +1 -0
  288. package/docs/CLOCK.md +0 -1
  289. package/docs/DAEMON_CONTROL_PLANE.md +1 -0
  290. package/docs/PORTS.md +2 -2
  291. package/docs/ROUTING_POLICY_SCHEMA.md +5 -3
  292. package/docs/antigravity-routing-contract.md +2 -2
  293. package/docs/daemon-admin-ui.html +334 -63
  294. package/docs/design/servertool-stopmessage-lifecycle.md +109 -0
  295. package/docs/exec-command-guard-policy.example.v1.json +7 -1
  296. package/docs/providers/antigravity-gemini-provider-compat.md +2 -2
  297. package/docs/{clock-client-daemon-design.md → session-client-daemon-design.md} +34 -34
  298. package/package.json +23 -7
  299. package/scripts/build-core.mjs +12 -0
  300. package/scripts/camoufox/launch-auth.mjs +112 -5
  301. package/scripts/ci/repo-sanity.mjs +1 -0
  302. package/scripts/compare-responses-sse.mjs +267 -0
  303. package/scripts/install-global.sh +6 -0
  304. package/scripts/install-verify.mjs +33 -16
  305. package/scripts/replay-codex-sample.mjs +52 -6
  306. package/scripts/run-bg.sh +226 -43
  307. package/scripts/run-fg-gtimeout.sh +158 -14
  308. package/scripts/tests/blackbox-rcc-vs-routecodex-antigravity.mjs +3 -3
  309. package/scripts/tests/ci-jest.mjs +9 -1
  310. package/scripts/triage-errorsamples.mjs +216 -0
  311. package/scripts/verify-codex-error-samples.mjs +92 -15
  312. package/scripts/verify-install-e2e.mjs +57 -27
  313. package/scripts/virtual-router-dryrun.mjs +7 -1
  314. package/dist/cli/commands/clock-admin.js.map +0 -1
  315. package/dist/cli/commands/tmux-inject.js.map +0 -1
  316. package/dist/cli/register/clock-admin-command.d.ts +0 -3
  317. package/dist/cli/register/clock-admin-command.js +0 -5
  318. package/dist/cli/register/clock-admin-command.js.map +0 -1
  319. package/dist/cli/register/tmux-inject-command.d.ts +0 -3
  320. package/dist/cli/register/tmux-inject-command.js +0 -5
  321. package/dist/cli/register/tmux-inject-command.js.map +0 -1
  322. package/dist/server/runtime/http-server/clock-client-reaper.js.map +0 -1
  323. package/dist/server/runtime/http-server/clock-client-registry-utils.js.map +0 -1
  324. package/dist/server/runtime/http-server/clock-client-registry.js.map +0 -1
  325. package/dist/server/runtime/http-server/clock-client-route-utils.js.map +0 -1
  326. package/dist/server/runtime/http-server/clock-client-routes.d.ts +0 -2
  327. package/dist/server/runtime/http-server/clock-client-routes.js.map +0 -1
  328. package/dist/server/runtime/http-server/clock-daemon-inject-config.d.ts +0 -1
  329. package/dist/server/runtime/http-server/clock-daemon-inject-config.js.map +0 -1
  330. package/dist/server/runtime/http-server/clock-daemon-log-throttle.d.ts +0 -12
  331. package/dist/server/runtime/http-server/clock-daemon-log-throttle.js +0 -56
  332. package/dist/server/runtime/http-server/clock-daemon-log-throttle.js.map +0 -1
  333. package/dist/server/runtime/http-server/http-server-clock-daemon.d.ts +0 -5
  334. package/dist/server/runtime/http-server/http-server-clock-daemon.js +0 -255
  335. package/dist/server/runtime/http-server/http-server-clock-daemon.js.map +0 -1
  336. package/dist/utils/clock-client-token.d.ts +0 -3
  337. package/dist/utils/clock-client-token.js +0 -54
  338. package/dist/utils/clock-client-token.js.map +0 -1
@@ -0,0 +1,208 @@
1
+ import { extractSessionClientScopeIdFromApiKey } from '../../../utils/session-client-token.js';
2
+ function readToken(value) {
3
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
4
+ }
5
+ function extractHeaderValue(headers, name) {
6
+ if (!headers) {
7
+ return undefined;
8
+ }
9
+ const target = name.toLowerCase();
10
+ for (const [key, value] of Object.entries(headers)) {
11
+ if (key.toLowerCase() !== target) {
12
+ continue;
13
+ }
14
+ if (typeof value === 'string') {
15
+ return value.trim() || undefined;
16
+ }
17
+ if (Array.isArray(value) && value.length > 0) {
18
+ return String(value[0]).trim() || undefined;
19
+ }
20
+ return undefined;
21
+ }
22
+ return undefined;
23
+ }
24
+ function extractTmuxSessionIdFromTurnMetadata(rawValue) {
25
+ if (!rawValue) {
26
+ return undefined;
27
+ }
28
+ const normalizeTmuxToken = (value) => {
29
+ const token = readToken(value);
30
+ if (!token) {
31
+ return undefined;
32
+ }
33
+ if (!/^[a-zA-Z0-9._:-]+$/.test(token)) {
34
+ return undefined;
35
+ }
36
+ return token;
37
+ };
38
+ const parseFromObject = (root) => {
39
+ if (!root || typeof root !== 'object') {
40
+ return undefined;
41
+ }
42
+ const queue = [root];
43
+ const visited = new Set();
44
+ while (queue.length > 0) {
45
+ const current = queue.shift();
46
+ if (!current || typeof current !== 'object') {
47
+ continue;
48
+ }
49
+ if (visited.has(current)) {
50
+ continue;
51
+ }
52
+ visited.add(current);
53
+ if (Array.isArray(current)) {
54
+ for (const value of current) {
55
+ if (value && typeof value === 'object') {
56
+ queue.push(value);
57
+ }
58
+ }
59
+ continue;
60
+ }
61
+ const record = current;
62
+ const directTmux = normalizeTmuxToken(record.tmuxSessionId)
63
+ || normalizeTmuxToken(record.tmux_session_id)
64
+ || normalizeTmuxToken(record.tmuxSession)
65
+ || normalizeTmuxToken(record.tmux_session)
66
+ || normalizeTmuxToken(record.rccTmuxSessionId)
67
+ || normalizeTmuxToken(record.rcc_tmux_session_id)
68
+ || normalizeTmuxToken(record.clientTmuxSessionId)
69
+ || normalizeTmuxToken(record.client_tmux_session_id)
70
+ || normalizeTmuxToken(record.rccSessionClientTmuxSessionId)
71
+ || normalizeTmuxToken(record.rcc_session_client_tmux_session_id);
72
+ if (directTmux) {
73
+ return directTmux;
74
+ }
75
+ for (const [key, value] of Object.entries(record)) {
76
+ if (value && typeof value === 'object') {
77
+ queue.push(value);
78
+ continue;
79
+ }
80
+ if (typeof value !== 'string') {
81
+ continue;
82
+ }
83
+ const normalizedKey = key.trim().toLowerCase();
84
+ if (normalizedKey.includes('tmux')
85
+ || normalizedKey === 'rcc_session_client_tmux_session_id'
86
+ || normalizedKey === 'rccsessionclienttmuxsessionid') {
87
+ const token = normalizeTmuxToken(value);
88
+ if (token) {
89
+ return token;
90
+ }
91
+ }
92
+ }
93
+ }
94
+ return undefined;
95
+ };
96
+ const candidates = [rawValue];
97
+ try {
98
+ candidates.push(decodeURIComponent(rawValue));
99
+ }
100
+ catch {
101
+ // ignore URI decoding errors
102
+ }
103
+ for (const candidate of [...candidates]) {
104
+ const normalized = candidate.trim();
105
+ if (!normalized) {
106
+ continue;
107
+ }
108
+ if (!/^[A-Za-z0-9+/=_-]+$/.test(normalized) || normalized.length < 12) {
109
+ continue;
110
+ }
111
+ try {
112
+ const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=');
113
+ const decoded = Buffer.from(padded, 'base64').toString('utf8').trim();
114
+ if (decoded) {
115
+ candidates.push(decoded);
116
+ }
117
+ }
118
+ catch {
119
+ // ignore base64 decoding errors
120
+ }
121
+ }
122
+ for (const candidate of candidates) {
123
+ try {
124
+ const parsed = JSON.parse(candidate);
125
+ const tmux = parseFromObject(parsed);
126
+ if (tmux) {
127
+ return tmux;
128
+ }
129
+ }
130
+ catch {
131
+ // continue to URLSearchParams fallback
132
+ }
133
+ try {
134
+ const params = new URLSearchParams(candidate);
135
+ const fromParams = normalizeTmuxToken(params.get('tmuxSessionId'))
136
+ || normalizeTmuxToken(params.get('tmux_session_id'))
137
+ || normalizeTmuxToken(params.get('tmuxSession'))
138
+ || normalizeTmuxToken(params.get('tmux_session'))
139
+ || normalizeTmuxToken(params.get('rccTmuxSessionId'))
140
+ || normalizeTmuxToken(params.get('rcc_tmux_session_id'))
141
+ || normalizeTmuxToken(params.get('clientTmuxSessionId'))
142
+ || normalizeTmuxToken(params.get('client_tmux_session_id'))
143
+ || normalizeTmuxToken(params.get('rcc_session_client_tmux_session_id'));
144
+ if (fromParams) {
145
+ return fromParams;
146
+ }
147
+ }
148
+ catch {
149
+ // ignore non-URLSearchParams text
150
+ }
151
+ }
152
+ return undefined;
153
+ }
154
+ function readTmuxFromHeaderSource(source) {
155
+ if (!source) {
156
+ return undefined;
157
+ }
158
+ const fromTurnMeta = extractTmuxSessionIdFromTurnMetadata(extractHeaderValue(source, 'x-codex-turn-metadata'));
159
+ if (fromTurnMeta) {
160
+ return fromTurnMeta;
161
+ }
162
+ const fromHeader = extractHeaderValue(source, 'x-routecodex-client-tmux-session-id')
163
+ || extractHeaderValue(source, 'x-rcc-client-tmux-session-id')
164
+ || extractHeaderValue(source, 'x-routecodex-client-tmuxsession-id')
165
+ || extractHeaderValue(source, 'x-rcc-client-tmuxsession-id')
166
+ || extractHeaderValue(source, 'x-routecodex-clienttmuxsessionid')
167
+ || extractHeaderValue(source, 'x-routecodex-tmux-session-id')
168
+ || extractHeaderValue(source, 'x-rcc-tmux-session-id')
169
+ || extractHeaderValue(source, 'x-tmux-session-id');
170
+ if (fromHeader) {
171
+ return fromHeader;
172
+ }
173
+ const fromApiKeyHeader = extractHeaderValue(source, 'x-routecodex-api-key')
174
+ || extractHeaderValue(source, 'x-api-key')
175
+ || extractHeaderValue(source, 'x-routecodex-apikey')
176
+ || extractHeaderValue(source, 'api-key')
177
+ || extractHeaderValue(source, 'apikey');
178
+ const fromApiKey = extractSessionClientScopeIdFromApiKey(fromApiKeyHeader);
179
+ if (fromApiKey) {
180
+ return fromApiKey;
181
+ }
182
+ const authorization = extractHeaderValue(source, 'authorization');
183
+ if (authorization) {
184
+ const match = authorization.match(/^(?:Bearer|ApiKey)\s+(.+)$/i);
185
+ const fromAuth = extractSessionClientScopeIdFromApiKey(match ? String(match[1]) : authorization);
186
+ if (fromAuth) {
187
+ return fromAuth;
188
+ }
189
+ }
190
+ return undefined;
191
+ }
192
+ export function resolveTmuxSessionIdAndSource(args) {
193
+ const tmuxFromHeaders = readTmuxFromHeaderSource(args.headers)
194
+ || readTmuxFromHeaderSource(args.clientHeaders);
195
+ if (tmuxFromHeaders) {
196
+ return { tmuxSessionId: tmuxFromHeaders, source: 'headers_or_api_key' };
197
+ }
198
+ const tmuxFromMeta = readToken(args.userMeta.tmuxSessionId) || readToken(args.userMeta.tmux_session_id);
199
+ if (tmuxFromMeta) {
200
+ return { tmuxSessionId: tmuxFromMeta, source: 'metadata' };
201
+ }
202
+ const tmuxFromBody = readToken(args.bodyMeta.tmuxSessionId) || readToken(args.bodyMeta.tmux_session_id);
203
+ if (tmuxFromBody) {
204
+ return { tmuxSessionId: tmuxFromBody, source: 'body_metadata' };
205
+ }
206
+ return { source: 'none' };
207
+ }
208
+ //# sourceMappingURL=session-scope-resolution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-scope-resolution.js","sourceRoot":"","sources":["../../../../src/server/runtime/http-server/session-scope-resolution.ts"],"names":[],"mappings":"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"}
@@ -53,6 +53,20 @@ export interface HistoricalStatsSnapshot {
53
53
  sampleCount: number;
54
54
  totals: ProviderStatsView[];
55
55
  }
56
+ export interface HistoricalPeriodBucket {
57
+ period: string;
58
+ requestCount: number;
59
+ errorCount: number;
60
+ totalPromptTokens: number;
61
+ totalCompletionTokens: number;
62
+ totalOutputTokens: number;
63
+ }
64
+ export interface HistoricalPeriodsSnapshot {
65
+ generatedAt: number;
66
+ daily: HistoricalPeriodBucket[];
67
+ weekly: HistoricalPeriodBucket[];
68
+ monthly: HistoricalPeriodBucket[];
69
+ }
56
70
  export type StatsPersistOptions = {
57
71
  logPath?: string;
58
72
  reason?: string;
@@ -74,6 +88,17 @@ export declare class StatsManager {
74
88
  private historicalSnapshotCount;
75
89
  private historicalSampleCount;
76
90
  private historicalLoaded;
91
+ private readonly dailyPeriods;
92
+ private readonly weeklyPeriods;
93
+ private readonly monthlyPeriods;
94
+ private readonly persistIntervalMs;
95
+ private readonly maxDailyPeriods;
96
+ private readonly maxWeeklyPeriods;
97
+ private readonly maxMonthlyPeriods;
98
+ private periodicPersistTimer;
99
+ private persistSeq;
100
+ private lastPeriodicSignature;
101
+ private readonly persistSessionId;
77
102
  constructor();
78
103
  recordRequestStart(requestId: string): void;
79
104
  bindProvider(requestId: string, meta: {
@@ -91,12 +116,22 @@ export declare class StatsManager {
91
116
  }, payload: unknown): void;
92
117
  snapshot(uptimeMs: number): StatsSnapshot;
93
118
  logSummary(uptimeMs: number): StatsSnapshot;
119
+ logFinalSummary(uptimeMs: number): {
120
+ session: StatsSnapshot;
121
+ historical: HistoricalStatsSnapshot;
122
+ };
94
123
  persistSnapshot(snapshot: StatsSnapshot, options?: StatsPersistOptions): Promise<void>;
95
124
  logHistoricalSummary(options?: {
96
125
  logPath?: string;
97
126
  }): Promise<void>;
98
127
  snapshotHistorical(): HistoricalStatsSnapshot;
128
+ snapshotHistoricalPeriods(): HistoricalPeriodsSnapshot;
99
129
  private ensureHistoricalLoaded;
130
+ private startPeriodicPersistence;
131
+ private persistPeriodicSnapshot;
132
+ private buildSnapshotSignature;
133
+ private mergeSnapshotIntoPeriods;
134
+ private mergeSnapshotIntoHistorical;
100
135
  private loadHistoricalFromDisk;
101
136
  private normalizeLogPath;
102
137
  private resolveLogPath;
@@ -7,6 +7,10 @@ import { buildHistoricalProviderRow, buildSessionProviderRow, composeBucketKey,
7
7
  const DEFAULT_STATS_LOG_PATH = path.join(os.homedir(), '.routecodex', 'logs', 'provider-stats.jsonl');
8
8
  const DEFAULT_HISTORY_MAX_TAIL_BYTES = 8 * 1024 * 1024;
9
9
  const DEFAULT_HISTORY_MAX_LINES = 20000;
10
+ const DEFAULT_PERSIST_INTERVAL_MS = 30000;
11
+ const DEFAULT_DAILY_PERIODS = 90;
12
+ const DEFAULT_WEEKLY_PERIODS = 104;
13
+ const DEFAULT_MONTHLY_PERIODS = 36;
10
14
  function resolveBoolFromEnv(value, fallback) {
11
15
  if (!value) {
12
16
  return fallback;
@@ -36,6 +40,84 @@ function resolvePositiveIntEnv(primary, secondary, fallback) {
36
40
  }
37
41
  return fallback;
38
42
  }
43
+ function toUtcDayKey(ts) {
44
+ const date = new Date(ts);
45
+ const y = date.getUTCFullYear();
46
+ const m = String(date.getUTCMonth() + 1).padStart(2, '0');
47
+ const d = String(date.getUTCDate()).padStart(2, '0');
48
+ return `${y}-${m}-${d}`;
49
+ }
50
+ function toUtcMonthKey(ts) {
51
+ const date = new Date(ts);
52
+ const y = date.getUTCFullYear();
53
+ const m = String(date.getUTCMonth() + 1).padStart(2, '0');
54
+ return `${y}-${m}`;
55
+ }
56
+ function toUtcIsoWeekKey(ts) {
57
+ const date = new Date(ts);
58
+ const day = date.getUTCDay() || 7;
59
+ date.setUTCDate(date.getUTCDate() + 4 - day);
60
+ const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
61
+ const week = Math.ceil((((date.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
62
+ return `${date.getUTCFullYear()}-W${String(week).padStart(2, '0')}`;
63
+ }
64
+ function sumSnapshotTotals(rows) {
65
+ const totals = {
66
+ requestCount: 0,
67
+ errorCount: 0,
68
+ totalPromptTokens: 0,
69
+ totalCompletionTokens: 0,
70
+ totalOutputTokens: 0
71
+ };
72
+ for (const row of rows) {
73
+ totals.requestCount += row.requestCount ?? 0;
74
+ totals.errorCount += row.errorCount ?? 0;
75
+ totals.totalPromptTokens += row.totalPromptTokens ?? 0;
76
+ totals.totalCompletionTokens += row.totalCompletionTokens ?? 0;
77
+ totals.totalOutputTokens += row.totalOutputTokens ?? 0;
78
+ }
79
+ return totals;
80
+ }
81
+ function sortSummaryRows(rows) {
82
+ return rows
83
+ .slice()
84
+ .sort((a, b) => {
85
+ const requestDelta = (b.requestCount ?? 0) - (a.requestCount ?? 0);
86
+ if (requestDelta !== 0) {
87
+ return requestDelta;
88
+ }
89
+ const keyA = composeBucketKey(a.providerKey, a.model);
90
+ const keyB = composeBucketKey(b.providerKey, b.model);
91
+ return keyA.localeCompare(keyB);
92
+ });
93
+ }
94
+ function upsertPeriodBucket(map, period, delta) {
95
+ const current = map.get(period) ?? {
96
+ requestCount: 0,
97
+ errorCount: 0,
98
+ totalPromptTokens: 0,
99
+ totalCompletionTokens: 0,
100
+ totalOutputTokens: 0
101
+ };
102
+ current.requestCount += delta.requestCount;
103
+ current.errorCount += delta.errorCount;
104
+ current.totalPromptTokens += delta.totalPromptTokens;
105
+ current.totalCompletionTokens += delta.totalCompletionTokens;
106
+ current.totalOutputTokens += delta.totalOutputTokens;
107
+ map.set(period, current);
108
+ }
109
+ function trimPeriodMap(map, maxEntries) {
110
+ if (map.size <= maxEntries) {
111
+ return;
112
+ }
113
+ const keys = Array.from(map.keys()).sort((a, b) => a.localeCompare(b));
114
+ while (map.size > maxEntries && keys.length) {
115
+ const key = keys.shift();
116
+ if (key) {
117
+ map.delete(key);
118
+ }
119
+ }
120
+ }
39
121
  function readTailLines(filePath, maxTailBytes, maxLines) {
40
122
  let text = '';
41
123
  const stat = fsSync.statSync(filePath);
@@ -84,12 +166,30 @@ export class StatsManager {
84
166
  historicalSnapshotCount = 0;
85
167
  historicalSampleCount = 0;
86
168
  historicalLoaded = false;
169
+ dailyPeriods = new Map();
170
+ weeklyPeriods = new Map();
171
+ monthlyPeriods = new Map();
172
+ persistIntervalMs;
173
+ maxDailyPeriods;
174
+ maxWeeklyPeriods;
175
+ maxMonthlyPeriods;
176
+ periodicPersistTimer = null;
177
+ persistSeq = 0;
178
+ lastPeriodicSignature = '';
179
+ persistSessionId = `${process.pid}-${Date.now().toString(36)}-${Math.random()
180
+ .toString(36)
181
+ .slice(2, 8)}`;
87
182
  constructor() {
88
183
  this.enabled = isStatsEnabledByDefault();
89
184
  this.verboseLogging = isStatsVerboseEnabled(this.enabled);
90
185
  this.statsLogPath = this.resolveLogPath();
186
+ this.persistIntervalMs = resolvePositiveIntEnv(process.env.ROUTECODEX_STATS_PERSIST_INTERVAL_MS, process.env.RCC_STATS_PERSIST_INTERVAL_MS, DEFAULT_PERSIST_INTERVAL_MS);
187
+ this.maxDailyPeriods = resolvePositiveIntEnv(process.env.ROUTECODEX_STATS_DAILY_MAX_PERIODS, process.env.RCC_STATS_DAILY_MAX_PERIODS, DEFAULT_DAILY_PERIODS);
188
+ this.maxWeeklyPeriods = resolvePositiveIntEnv(process.env.ROUTECODEX_STATS_WEEKLY_MAX_PERIODS, process.env.RCC_STATS_WEEKLY_MAX_PERIODS, DEFAULT_WEEKLY_PERIODS);
189
+ this.maxMonthlyPeriods = resolvePositiveIntEnv(process.env.ROUTECODEX_STATS_MONTHLY_MAX_PERIODS, process.env.RCC_STATS_MONTHLY_MAX_PERIODS, DEFAULT_MONTHLY_PERIODS);
91
190
  if (this.enabled) {
92
191
  this.loadHistoricalFromDisk(this.statsLogPath, true);
192
+ this.startPeriodicPersistence();
93
193
  }
94
194
  }
95
195
  recordRequestStart(requestId) {
@@ -247,16 +347,7 @@ export class StatsManager {
247
347
  return snapshot;
248
348
  }
249
349
  this.ensureHistoricalLoaded();
250
- const merged = mergeSnapshotIntoHistorical({
251
- snapshot,
252
- historicalBuckets: this.historicalBuckets,
253
- historicalToolAggregate: this.historicalToolAggregate,
254
- historicalToolByProvider: this.historicalToolByProvider,
255
- historicalSnapshotCount: this.historicalSnapshotCount,
256
- historicalSampleCount: this.historicalSampleCount
257
- });
258
- this.historicalSnapshotCount = merged.historicalSnapshotCount;
259
- this.historicalSampleCount = merged.historicalSampleCount;
350
+ this.mergeSnapshotIntoHistorical(snapshot);
260
351
  if (!this.verboseLogging) {
261
352
  return snapshot;
262
353
  }
@@ -269,6 +360,42 @@ export class StatsManager {
269
360
  logToolSummary(snapshot.tools, formatProviderLabel);
270
361
  return snapshot;
271
362
  }
363
+ logFinalSummary(uptimeMs) {
364
+ const session = this.snapshot(uptimeMs);
365
+ if (!this.enabled) {
366
+ return { session, historical: this.snapshotHistorical() };
367
+ }
368
+ this.ensureHistoricalLoaded();
369
+ this.mergeSnapshotIntoHistorical(session);
370
+ const historical = this.snapshotHistorical();
371
+ const sessionTotals = sumSnapshotTotals(session.totals);
372
+ const historicalTotals = sumSnapshotTotals(historical.totals);
373
+ const sessionRows = sortSummaryRows(session.totals);
374
+ const historicalRows = sortSummaryRows(historical.totals);
375
+ console.log('\n[Stats][final][session] calls=%d errors=%d tokens(prompt/completion/total)=%d/%d/%d uptimeMs=%d', sessionTotals.requestCount, sessionTotals.errorCount, sessionTotals.totalPromptTokens, sessionTotals.totalCompletionTokens, sessionTotals.totalOutputTokens, Math.round(session.uptimeMs));
376
+ if (!sessionRows.length) {
377
+ console.log('[Stats][final][session] providers: none');
378
+ }
379
+ else {
380
+ console.log('[Stats][final][session] providers:');
381
+ for (const row of sessionRows) {
382
+ const providerLabel = formatProviderLabel(row.providerKey, row.model);
383
+ console.log(' - %s calls=%d tokens=%d/%d/%d', providerLabel, row.requestCount, row.totalPromptTokens, row.totalCompletionTokens, row.totalOutputTokens);
384
+ }
385
+ }
386
+ console.log('\n[Stats][final][historical] calls=%d errors=%d tokens(prompt/completion/total)=%d/%d/%d snapshots=%d samples=%d', historicalTotals.requestCount, historicalTotals.errorCount, historicalTotals.totalPromptTokens, historicalTotals.totalCompletionTokens, historicalTotals.totalOutputTokens, historical.snapshotCount, historical.sampleCount);
387
+ if (!historicalRows.length) {
388
+ console.log('[Stats][final][historical] providers: none');
389
+ }
390
+ else {
391
+ console.log('[Stats][final][historical] providers:');
392
+ for (const row of historicalRows) {
393
+ const providerLabel = formatProviderLabel(row.providerKey, row.model);
394
+ console.log(' - %s calls=%d tokens=%d/%d/%d', providerLabel, row.requestCount, row.totalPromptTokens, row.totalCompletionTokens, row.totalOutputTokens);
395
+ }
396
+ }
397
+ return { session, historical };
398
+ }
272
399
  async persistSnapshot(snapshot, options) {
273
400
  if (!this.enabled) {
274
401
  return;
@@ -279,7 +406,10 @@ export class StatsManager {
279
406
  const record = {
280
407
  ...snapshot,
281
408
  reason: options?.reason ?? 'shutdown',
282
- pid: process.pid
409
+ pid: process.pid,
410
+ sessionId: this.persistSessionId,
411
+ snapshotSeq: ++this.persistSeq,
412
+ persistedAt: Date.now()
283
413
  };
284
414
  await fs.appendFile(logPath, `${JSON.stringify(record)}\n`, 'utf-8');
285
415
  }
@@ -318,12 +448,99 @@ export class StatsManager {
318
448
  totals
319
449
  };
320
450
  }
451
+ snapshotHistoricalPeriods() {
452
+ if (!this.enabled) {
453
+ return { generatedAt: Date.now(), daily: [], weekly: [], monthly: [] };
454
+ }
455
+ this.ensureHistoricalLoaded();
456
+ const toRows = (map) => Array.from(map.entries())
457
+ .sort((a, b) => b[0].localeCompare(a[0]))
458
+ .map(([period, bucket]) => ({ period, ...bucket }));
459
+ return {
460
+ generatedAt: Date.now(),
461
+ daily: toRows(this.dailyPeriods),
462
+ weekly: toRows(this.weeklyPeriods),
463
+ monthly: toRows(this.monthlyPeriods)
464
+ };
465
+ }
321
466
  ensureHistoricalLoaded() {
322
467
  if (!this.enabled || this.historicalLoaded) {
323
468
  return;
324
469
  }
325
470
  this.loadHistoricalFromDisk(this.statsLogPath, true);
326
471
  }
472
+ startPeriodicPersistence() {
473
+ if (!this.enabled || this.persistIntervalMs <= 0) {
474
+ return;
475
+ }
476
+ this.periodicPersistTimer = setInterval(() => {
477
+ void this.persistPeriodicSnapshot();
478
+ }, this.persistIntervalMs);
479
+ this.periodicPersistTimer.unref?.();
480
+ }
481
+ async persistPeriodicSnapshot() {
482
+ try {
483
+ const snapshot = this.snapshot(Math.round(process.uptime() * 1000));
484
+ if (!snapshot.totals.length) {
485
+ return;
486
+ }
487
+ const signature = this.buildSnapshotSignature(snapshot);
488
+ if (signature === this.lastPeriodicSignature) {
489
+ return;
490
+ }
491
+ await this.persistSnapshot(snapshot, { reason: 'periodic' });
492
+ this.lastPeriodicSignature = signature;
493
+ }
494
+ catch {
495
+ // persistence must be best-effort
496
+ }
497
+ }
498
+ buildSnapshotSignature(snapshot) {
499
+ const totals = sumSnapshotTotals(snapshot.totals || []);
500
+ const toolCalls = typeof snapshot.tools?.totalCalls === 'number' ? snapshot.tools.totalCalls : 0;
501
+ const toolResponses = typeof snapshot.tools?.totalResponses === 'number' ? snapshot.tools.totalResponses : 0;
502
+ return [
503
+ totals.requestCount,
504
+ totals.errorCount,
505
+ totals.totalPromptTokens,
506
+ totals.totalCompletionTokens,
507
+ totals.totalOutputTokens,
508
+ toolCalls,
509
+ toolResponses
510
+ ].join(':');
511
+ }
512
+ mergeSnapshotIntoPeriods(snapshot) {
513
+ const generatedAt = typeof snapshot.generatedAt === 'number' && Number.isFinite(snapshot.generatedAt)
514
+ ? snapshot.generatedAt
515
+ : Date.now();
516
+ const totals = Array.isArray(snapshot.totals) ? snapshot.totals : [];
517
+ if (!totals.length) {
518
+ return;
519
+ }
520
+ const delta = sumSnapshotTotals(totals);
521
+ const day = toUtcDayKey(generatedAt);
522
+ const week = toUtcIsoWeekKey(generatedAt);
523
+ const month = toUtcMonthKey(generatedAt);
524
+ upsertPeriodBucket(this.dailyPeriods, day, delta);
525
+ upsertPeriodBucket(this.weeklyPeriods, week, delta);
526
+ upsertPeriodBucket(this.monthlyPeriods, month, delta);
527
+ trimPeriodMap(this.dailyPeriods, this.maxDailyPeriods);
528
+ trimPeriodMap(this.weeklyPeriods, this.maxWeeklyPeriods);
529
+ trimPeriodMap(this.monthlyPeriods, this.maxMonthlyPeriods);
530
+ }
531
+ mergeSnapshotIntoHistorical(snapshot) {
532
+ const merged = mergeSnapshotIntoHistorical({
533
+ snapshot,
534
+ historicalBuckets: this.historicalBuckets,
535
+ historicalToolAggregate: this.historicalToolAggregate,
536
+ historicalToolByProvider: this.historicalToolByProvider,
537
+ historicalSnapshotCount: this.historicalSnapshotCount,
538
+ historicalSampleCount: this.historicalSampleCount
539
+ });
540
+ this.historicalSnapshotCount = merged.historicalSnapshotCount;
541
+ this.historicalSampleCount = merged.historicalSampleCount;
542
+ this.mergeSnapshotIntoPeriods(snapshot);
543
+ }
327
544
  loadHistoricalFromDisk(logPath, reset) {
328
545
  if (reset) {
329
546
  this.historicalBuckets.clear();
@@ -331,29 +548,60 @@ export class StatsManager {
331
548
  this.historicalToolByProvider.clear();
332
549
  this.historicalSnapshotCount = 0;
333
550
  this.historicalSampleCount = 0;
551
+ this.dailyPeriods.clear();
552
+ this.weeklyPeriods.clear();
553
+ this.monthlyPeriods.clear();
334
554
  }
335
555
  try {
336
556
  const maxTailBytes = resolvePositiveIntEnv(process.env.ROUTECODEX_STATS_HISTORY_MAX_TAIL_BYTES, process.env.RCC_STATS_HISTORY_MAX_TAIL_BYTES, DEFAULT_HISTORY_MAX_TAIL_BYTES);
337
557
  const maxLines = resolvePositiveIntEnv(process.env.ROUTECODEX_STATS_HISTORY_MAX_LINES, process.env.RCC_STATS_HISTORY_MAX_LINES, DEFAULT_HISTORY_MAX_LINES);
338
558
  const lines = readTailLines(logPath, maxTailBytes, maxLines);
559
+ const latestBySession = new Map();
560
+ const legacyRecords = [];
339
561
  for (const line of lines) {
340
562
  try {
341
563
  const record = JSON.parse(line);
342
- const merged = mergeSnapshotIntoHistorical({
343
- snapshot: record,
344
- historicalBuckets: this.historicalBuckets,
345
- historicalToolAggregate: this.historicalToolAggregate,
346
- historicalToolByProvider: this.historicalToolByProvider,
347
- historicalSnapshotCount: this.historicalSnapshotCount,
348
- historicalSampleCount: this.historicalSampleCount
349
- });
350
- this.historicalSnapshotCount = merged.historicalSnapshotCount;
351
- this.historicalSampleCount = merged.historicalSampleCount;
564
+ if (typeof record?.sessionId === 'string' &&
565
+ record.sessionId.trim() &&
566
+ typeof record?.snapshotSeq === 'number' &&
567
+ Number.isFinite(record.snapshotSeq)) {
568
+ const key = record.sessionId.trim();
569
+ const seq = Math.floor(record.snapshotSeq);
570
+ const existing = latestBySession.get(key);
571
+ if (!existing ||
572
+ seq > existing.snapshotSeq ||
573
+ (seq === existing.snapshotSeq &&
574
+ (typeof record.generatedAt === 'number' ? record.generatedAt : 0) >
575
+ (typeof existing.generatedAt === 'number' ? existing.generatedAt : 0))) {
576
+ latestBySession.set(key, { ...record, snapshotSeq: seq });
577
+ }
578
+ }
579
+ else {
580
+ legacyRecords.push(record);
581
+ }
352
582
  }
353
583
  catch {
354
584
  continue;
355
585
  }
356
586
  }
587
+ const dedupedRecords = [
588
+ ...legacyRecords,
589
+ ...Array.from(latestBySession.values())
590
+ ].sort((a, b) => (typeof a.generatedAt === 'number' ? a.generatedAt : 0) -
591
+ (typeof b.generatedAt === 'number' ? b.generatedAt : 0));
592
+ for (const record of dedupedRecords) {
593
+ const merged = mergeSnapshotIntoHistorical({
594
+ snapshot: record,
595
+ historicalBuckets: this.historicalBuckets,
596
+ historicalToolAggregate: this.historicalToolAggregate,
597
+ historicalToolByProvider: this.historicalToolByProvider,
598
+ historicalSnapshotCount: this.historicalSnapshotCount,
599
+ historicalSampleCount: this.historicalSampleCount
600
+ });
601
+ this.historicalSnapshotCount = merged.historicalSnapshotCount;
602
+ this.historicalSampleCount = merged.historicalSampleCount;
603
+ this.mergeSnapshotIntoPeriods(record);
604
+ }
357
605
  }
358
606
  catch {
359
607
  // File missing or unreadable -> treat as empty history.