coding-tool-x 3.8.4 → 3.8.5

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 (434) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +86 -15
  3. package/dist/web/assets/AgentsDrawer-BG6P3DX-.js +1 -0
  4. package/dist/web/assets/AgentsDrawer-Pw2k-ZAa.css +1 -0
  5. package/dist/web/assets/Analytics--z_FmL5P.js +1 -0
  6. package/dist/web/assets/Analytics-CyHhR126.css +1 -0
  7. package/dist/web/assets/AssetPathField-CrJtRKHy.js +1 -0
  8. package/dist/web/assets/ChatHistoryDrawer-B2qBQs9L.css +1 -0
  9. package/dist/web/assets/ChatHistoryDrawer-BiuEryJj.js +1 -0
  10. package/dist/web/assets/CollapseItem-BAYEsL_Q.js +1 -0
  11. package/dist/web/assets/CommandsDrawer-BZJxlPvV.css +1 -0
  12. package/dist/web/assets/CommandsDrawer-YJ-U7BXN.js +1 -0
  13. package/dist/web/assets/ConfigExportDrawer-Ce_hAknW.js +1 -0
  14. package/dist/web/assets/ConfigExportDrawer-dwruRKWr.css +1 -0
  15. package/dist/web/assets/ConfigTemplates-Bvr1NN6K.js +1 -0
  16. package/dist/web/assets/ConfigTemplatesDrawer-BfDi8yO3.css +1 -0
  17. package/dist/web/assets/ConfigTemplatesDrawer-BjIeHfF-.js +1 -0
  18. package/dist/web/assets/ConfigTemplatesPanel-AV00tm_D.js +1 -0
  19. package/dist/web/assets/ConfigTemplatesPanel-CWyPtk67.css +1 -0
  20. package/dist/web/assets/Divider-D28g1gwj.js +1 -0
  21. package/dist/web/assets/DrawerContent-ChXgZ1Xm.js +1 -0
  22. package/dist/web/assets/FavoritesDrawer-CG9eI9Ay.css +1 -0
  23. package/dist/web/assets/FavoritesDrawer-Cz95o4CD.js +1 -0
  24. package/dist/web/assets/GatewayConvertDrawer-CuNlf7AC.css +1 -0
  25. package/dist/web/assets/GatewayConvertDrawer-DLlbMz27.js +1 -0
  26. package/dist/web/assets/Home-CSuFuJ9Y.css +1 -0
  27. package/dist/web/assets/Home-D7CCOFQC.js +1 -0
  28. package/dist/web/assets/MarkdownEditor-qfVgSre_.css +1 -0
  29. package/dist/web/assets/MarkdownEditor-rEZ-c72L.js +1 -0
  30. package/dist/web/assets/McpDrawer-C0dnsZhQ.css +1 -0
  31. package/dist/web/assets/McpDrawer-DfoCyxU2.js +1 -0
  32. package/dist/web/assets/McpFormDrawer-BRbDcawn.css +1 -0
  33. package/dist/web/assets/McpFormDrawer-SKu8HNUR.js +1 -0
  34. package/dist/web/assets/PluginManager-BbeK-3rV.js +1 -0
  35. package/dist/web/assets/PluginsDrawer-CWVJ7yjS.js +1 -0
  36. package/dist/web/assets/PluginsDrawer-DM3QjprE.css +1 -0
  37. package/dist/web/assets/PluginsPanel-Cm2N3StG.js +1 -0
  38. package/dist/web/assets/PluginsPanel-D6ZqITcR.css +1 -0
  39. package/dist/web/assets/ProjectList-677RLZ8O.css +1 -0
  40. package/dist/web/assets/ProjectList-CsjnsNPv.js +1 -0
  41. package/dist/web/assets/PromptsDrawer-BIgiYCEQ.css +1 -0
  42. package/dist/web/assets/PromptsDrawer-EGd3djFa.js +1 -0
  43. package/dist/web/assets/RecentSessionsDrawer-CKFsOYDM.css +1 -0
  44. package/dist/web/assets/RecentSessionsDrawer-D1vtoLAs.js +1 -0
  45. package/dist/web/assets/SessionCard-BgLg49U3.js +1 -0
  46. package/dist/web/assets/SessionCard-aM2NHu6i.css +1 -0
  47. package/dist/web/assets/SessionList-BpYRz1cX.css +1 -0
  48. package/dist/web/assets/SessionList-TRiTklWk.js +1 -0
  49. package/dist/web/assets/SettingsDrawer-d5liXjHq.css +1 -0
  50. package/dist/web/assets/SettingsDrawer-rz2WejGY.js +1 -0
  51. package/dist/web/assets/SkillManager-BTjTUbHq.js +1 -0
  52. package/dist/web/assets/SkillManager-C0GCT2Kb.css +1 -0
  53. package/dist/web/assets/SkillsDrawer-BCNme-aJ.css +1 -0
  54. package/dist/web/assets/SkillsDrawer-pN0hbkK3.js +1 -0
  55. package/dist/web/assets/SkillsPanel-B3Nm5j32.css +1 -0
  56. package/dist/web/assets/SkillsPanel-gmBWEoiF.js +1 -0
  57. package/dist/web/assets/SpeedTestDrawer-Dc0WToJ_.css +1 -0
  58. package/dist/web/assets/SpeedTestDrawer-DcNzuVJk.js +1 -0
  59. package/dist/web/assets/Upload-v5rV0qTT.js +1 -0
  60. package/dist/web/assets/WorkspaceDrawer-CGnMu9p1.css +1 -0
  61. package/dist/web/assets/WorkspaceDrawer-DxmicM4U.js +1 -0
  62. package/dist/web/assets/WorkspaceManager-CJHCjARN.js +1 -0
  63. package/dist/web/assets/WorkspacePanel-D0-zHTHP.css +1 -0
  64. package/dist/web/assets/WorkspacePanel-D8_Zq-du.js +1 -0
  65. package/dist/web/assets/_plugin-vue_export-helper-BeLZJZij.js +1 -0
  66. package/dist/web/assets/client-UDwXBP9i.js +1 -0
  67. package/dist/web/assets/clipboard-B46yyoz_.js +1 -0
  68. package/dist/web/assets/headers-cf-QapUq.js +1 -0
  69. package/dist/web/assets/icons-B2Vg-IOC.js +1 -0
  70. package/dist/web/assets/index-C0EHRym0.css +1 -0
  71. package/dist/web/assets/index-DnEocV0M.js +2 -0
  72. package/dist/web/assets/light-Cp0t7v32.js +1 -0
  73. package/dist/web/assets/markdown-u082bbtj.js +1 -0
  74. package/dist/web/assets/platforms-CBkB45JP.js +1 -0
  75. package/dist/web/assets/platforms-FTpjxAT6.js +1 -0
  76. package/dist/web/assets/project-config-B2baPzYL.js +1 -0
  77. package/dist/web/assets/sessions-Cmy31slD.js +1 -0
  78. package/dist/web/assets/sessions-yC_oVVh8.js +1 -0
  79. package/dist/web/assets/skill-repo-input-B_fkGlUK.js +1 -0
  80. package/dist/web/assets/use-lock-html-scroll-DXIqmUW4.js +1 -0
  81. package/dist/web/assets/use-message-DoLM8C1C.js +1 -0
  82. package/dist/web/assets/useResponsiveDrawer-B3uojyvV.js +1 -0
  83. package/dist/web/assets/vendors-D0c_0KLP.js +1 -0
  84. package/dist/web/assets/vue-vendor-BqZbbXHA.js +1 -0
  85. package/dist/web/assets/workspaces-mjyPVGS8.js +1 -0
  86. package/dist/web/assets/xml-CgY3RMlX.js +1 -0
  87. package/dist/web/index.html +12 -8
  88. package/docs/native-log-memory-2026-09-16.md +43 -0
  89. package/docs/superpowers/plans/2026-08-26-configuration-driven-cli-platform.md +1578 -0
  90. package/docs/superpowers/plans/2026-08-30-project-scoped-config-control.md +1260 -0
  91. package/docs/superpowers/plans/2026-09-01-omp-skill-refresh-boundary.md +213 -0
  92. package/docs/superpowers/plans/2026-09-01-realtime-logs-panel.md +125 -0
  93. package/docs/superpowers/plans/2026-09-02-config-driven-platform-boundary.md +86 -0
  94. package/docs/superpowers/plans/2026-09-02-platform-implementation-drivers.md +508 -0
  95. package/docs/superpowers/plans/2026-09-03-cli-platform-correctness-boundary.md +760 -0
  96. package/docs/superpowers/plans/2026-09-03-dependency-security-remediation.md +133 -0
  97. package/docs/superpowers/plans/2026-09-04-model-catalog-pricing-sync.md +1078 -0
  98. package/docs/superpowers/plans/2026-09-05-codex-project-scan-500.md +128 -0
  99. package/docs/superpowers/plans/2026-09-06-project-skill-path-fix.md +97 -0
  100. package/docs/superpowers/specs/2026-08-26-configuration-driven-cli-platform-design.md +408 -0
  101. package/docs/superpowers/specs/2026-08-30-project-scoped-config-control-design.md +467 -0
  102. package/docs/superpowers/specs/2026-09-01-claude-session-content-repair-design.md +111 -0
  103. package/docs/superpowers/specs/2026-09-01-omp-skill-refresh-boundary-design.md +83 -0
  104. package/docs/superpowers/specs/2026-09-01-realtime-logs-panel-design.md +27 -0
  105. package/docs/superpowers/specs/2026-09-02-config-driven-platform-boundary-design.md +64 -0
  106. package/docs/superpowers/specs/2026-09-02-platform-implementation-driver-boundary-design.md +123 -0
  107. package/docs/superpowers/specs/2026-09-03-cli-platform-correctness-boundary-design.md +249 -0
  108. package/docs/superpowers/specs/2026-09-03-dependency-security-remediation-design.md +87 -0
  109. package/docs/superpowers/specs/2026-09-04-model-catalog-pricing-sync-design.md +301 -0
  110. package/docs/superpowers/specs/2026-09-05-codex-project-scan-500-design.md +24 -0
  111. package/package.json +8 -4
  112. package/src/commands/channels.js +88 -191
  113. package/src/commands/cli-type.js +19 -11
  114. package/src/commands/daemon.js +94 -32
  115. package/src/commands/doctor.js +16 -9
  116. package/src/commands/export-config.js +23 -5
  117. package/src/commands/list.js +130 -52
  118. package/src/commands/logs.js +44 -27
  119. package/src/commands/platform-command-registry.js +78 -0
  120. package/src/commands/plugin.js +11 -5
  121. package/src/commands/port-config.js +34 -52
  122. package/src/commands/proxy-control.js +207 -145
  123. package/src/commands/proxy.js +2 -2
  124. package/src/commands/resume.js +87 -91
  125. package/src/commands/search.js +137 -67
  126. package/src/commands/stats.js +30 -22
  127. package/src/commands/switch.js +1 -1
  128. package/src/commands/toggle-proxy.js +94 -111
  129. package/src/commands/ui.js +67 -6
  130. package/src/commands/workspace.js +90 -8
  131. package/src/config/default.js +6 -53
  132. package/src/config/loader.js +61 -24
  133. package/src/config/model-metadata.js +49 -59
  134. package/src/config/model-metadata.json +4095 -99
  135. package/src/config/paths.js +181 -8
  136. package/src/index.js +134 -71
  137. package/src/platforms/access.js +196 -0
  138. package/src/{server/services → platforms}/agents-service.js +354 -197
  139. package/src/platforms/channel-auth-registry.js +162 -0
  140. package/src/platforms/channel-auth-service.js +82 -0
  141. package/src/{server/services → platforms}/commands-service.js +195 -115
  142. package/src/platforms/driver-registry.js +59 -0
  143. package/src/platforms/drivers/claude/api-operations.js +62 -0
  144. package/src/{server/services/channels.js → platforms/drivers/claude/channels-implementation.js} +131 -33
  145. package/src/platforms/drivers/claude/channels.js +39 -0
  146. package/src/{server/services/config-sync-service.js → platforms/drivers/claude/config-sync.js} +29 -5
  147. package/src/{server/services → platforms/drivers/claude}/health-check.js +4 -4
  148. package/src/platforms/drivers/claude/health.js +22 -0
  149. package/src/platforms/drivers/claude/mcp.js +9 -0
  150. package/src/{server/services/settings-manager.js → platforms/drivers/claude/native-config-implementation.js} +13 -4
  151. package/src/platforms/drivers/claude/native-config.js +46 -0
  152. package/src/platforms/drivers/claude/native-logs.js +72 -0
  153. package/src/{server/services/claude-openai-gateway.js → platforms/drivers/claude/openai-gateway.js} +8 -65
  154. package/src/platforms/drivers/claude/project-config.js +21 -0
  155. package/src/platforms/drivers/claude/projects.js +62 -0
  156. package/src/platforms/drivers/claude/prompts.js +9 -0
  157. package/src/{server/proxy-server.js → platforms/drivers/claude/proxy-implementation.js} +41 -278
  158. package/src/platforms/drivers/claude/proxy.js +16 -0
  159. package/src/platforms/drivers/claude/resource-sync.js +9 -0
  160. package/src/platforms/drivers/claude/session-history-adapter.js +313 -0
  161. package/src/{server/services/sessions.js → platforms/drivers/claude/sessions-implementation.js} +145 -35
  162. package/src/platforms/drivers/claude/sessions.js +76 -0
  163. package/src/{server/services/claude-statistics-service.js → platforms/drivers/claude/statistics-implementation.js} +2 -2
  164. package/src/platforms/drivers/claude/statistics.js +16 -0
  165. package/src/{server/services/claude-wire.js → platforms/drivers/claude/wire.js} +1 -1
  166. package/src/platforms/drivers/codex/api-operations.js +9 -0
  167. package/src/{server/services/codex-channels.js → platforms/drivers/codex/channels-implementation.js} +149 -14
  168. package/src/platforms/drivers/codex/channels.js +45 -0
  169. package/src/{server/services/codex-config.js → platforms/drivers/codex/config.js} +12 -2
  170. package/src/{server/services/codex-env-manager.js → platforms/drivers/codex/env-manager.js} +1 -1
  171. package/src/platforms/drivers/codex/mcp.js +9 -0
  172. package/src/{server/services/codex-settings-manager.js → platforms/drivers/codex/native-config-implementation.js} +15 -6
  173. package/src/platforms/drivers/codex/native-config.js +47 -0
  174. package/src/platforms/drivers/codex/native-logs.js +166 -0
  175. package/src/platforms/drivers/codex/project-config.js +21 -0
  176. package/src/platforms/drivers/codex/projects.js +26 -0
  177. package/src/platforms/drivers/codex/prompts.js +9 -0
  178. package/src/{server/codex-proxy-server.js → platforms/drivers/codex/proxy-implementation.js} +30 -233
  179. package/src/platforms/drivers/codex/proxy.js +16 -0
  180. package/src/platforms/drivers/codex/resource-sync.js +9 -0
  181. package/src/{server/services/session-history-adapters/codex.js → platforms/drivers/codex/session-history-adapter.js} +37 -23
  182. package/src/{server/services/codex-sessions.js → platforms/drivers/codex/sessions-implementation.js} +81 -75
  183. package/src/platforms/drivers/codex/sessions.js +36 -0
  184. package/src/{server/services/codex-statistics-service.js → platforms/drivers/codex/statistics-implementation.js} +2 -2
  185. package/src/platforms/drivers/codex/statistics.js +16 -0
  186. package/src/platforms/drivers/gemini/api-operations.js +9 -0
  187. package/src/{server/services/gemini-channels.js → platforms/drivers/gemini/channels-implementation.js} +202 -37
  188. package/src/platforms/drivers/gemini/channels.js +39 -0
  189. package/src/{server/services/gemini-config.js → platforms/drivers/gemini/config.js} +12 -2
  190. package/src/platforms/drivers/gemini/mcp.js +9 -0
  191. package/src/{server/services/gemini-settings-manager.js → platforms/drivers/gemini/native-config-implementation.js} +14 -5
  192. package/src/platforms/drivers/gemini/native-config.js +39 -0
  193. package/src/platforms/drivers/gemini/native-logs.js +180 -0
  194. package/src/platforms/drivers/gemini/project-config.js +21 -0
  195. package/src/platforms/drivers/gemini/projects.js +26 -0
  196. package/src/platforms/drivers/gemini/prompts.js +9 -0
  197. package/src/{server/gemini-proxy-server.js → platforms/drivers/gemini/proxy-implementation.js} +36 -267
  198. package/src/platforms/drivers/gemini/proxy.js +16 -0
  199. package/src/platforms/drivers/gemini/resource-sync.js +9 -0
  200. package/src/{server/services/session-history-adapters/gemini.js → platforms/drivers/gemini/session-history-adapter.js} +70 -18
  201. package/src/{server/services/gemini-sessions.js → platforms/drivers/gemini/sessions-implementation.js} +53 -37
  202. package/src/platforms/drivers/gemini/sessions.js +47 -0
  203. package/src/{server/services/gemini-statistics-service.js → platforms/drivers/gemini/statistics-implementation.js} +2 -2
  204. package/src/platforms/drivers/gemini/statistics.js +16 -0
  205. package/src/{server/services/gemini-wire.js → platforms/drivers/gemini/wire.js} +1 -1
  206. package/src/platforms/drivers/generic-filesystem.js +166 -0
  207. package/src/platforms/drivers/generic-jsonl.js +141 -0
  208. package/src/platforms/drivers/generic-mcp.js +44 -0
  209. package/src/platforms/drivers/generic-openai-compatible.js +45 -0
  210. package/src/platforms/drivers/generic-prompt.js +41 -0
  211. package/src/platforms/drivers/legacy.js +677 -0
  212. package/src/platforms/drivers/native-config-snapshot.js +125 -0
  213. package/src/platforms/drivers/native-log-utils.js +1292 -0
  214. package/src/platforms/drivers/notification-hooks.js +578 -0
  215. package/src/{server/api/omp-config.js → platforms/drivers/omp/api-operations.js} +52 -64
  216. package/src/{server/services/omp-auth-provider-worker.js → platforms/drivers/omp/auth-provider-worker.js} +1 -1
  217. package/src/{server/services/omp-auth-providers.js → platforms/drivers/omp/auth-providers.js} +1 -1
  218. package/src/{server/services/omp-channels.js → platforms/drivers/omp/channels-implementation.js} +166 -39
  219. package/src/platforms/drivers/omp/channels.js +58 -0
  220. package/src/{server/services/omp-config.js → platforms/drivers/omp/config.js} +53 -13
  221. package/src/{server/services/omp-gateway-routing.js → platforms/drivers/omp/gateway-routing.js} +14 -7
  222. package/src/{server/services/omp-gateway.js → platforms/drivers/omp/gateway.js} +4 -262
  223. package/src/platforms/drivers/omp/mcp.js +9 -0
  224. package/src/{server/services/omp-settings-manager.js → platforms/drivers/omp/native-config-implementation.js} +75 -10
  225. package/src/platforms/drivers/omp/native-config.js +46 -0
  226. package/src/platforms/drivers/omp/native-logs.js +52 -0
  227. package/src/{server/services/omp-native-plugin-adapter.js → platforms/drivers/omp/native-plugin-adapter.js} +91 -4
  228. package/src/platforms/drivers/omp/project-config.js +29 -0
  229. package/src/platforms/drivers/omp/projects.js +20 -0
  230. package/src/platforms/drivers/omp/prompts.js +9 -0
  231. package/src/{server/omp-proxy-server.js → platforms/drivers/omp/proxy-implementation.js} +6 -15
  232. package/src/platforms/drivers/omp/proxy.js +16 -0
  233. package/src/platforms/drivers/omp/resource-sync.js +9 -0
  234. package/src/{server/services/session-history-adapters/omp.js → platforms/drivers/omp/session-history-adapter.js} +42 -18
  235. package/src/{server/services/omp-sessions.js → platforms/drivers/omp/sessions-implementation.js} +130 -94
  236. package/src/platforms/drivers/omp/sessions.js +36 -0
  237. package/src/{server/services/omp-skill-discovery.js → platforms/drivers/omp/skill-discovery.js} +159 -32
  238. package/src/{server/services/omp-skill-settings-service.js → platforms/drivers/omp/skill-settings.js} +1 -1
  239. package/src/{server/services/omp-statistics-service.js → platforms/drivers/omp/statistics-implementation.js} +3 -3
  240. package/src/platforms/drivers/omp/statistics.js +16 -0
  241. package/src/platforms/drivers/opencode/api-operations.js +9 -0
  242. package/src/{server/services/opencode-channels.js → platforms/drivers/opencode/channels-implementation.js} +28 -9
  243. package/src/platforms/drivers/opencode/channels.js +40 -0
  244. package/src/platforms/drivers/opencode/conversion.js +41 -0
  245. package/src/{server/services/opencode-gateway-converter.js → platforms/drivers/opencode/gateway-converter.js} +1 -1
  246. package/src/platforms/drivers/opencode/mcp.js +9 -0
  247. package/src/{server/services/opencode-settings-manager.js → platforms/drivers/opencode/native-config-implementation.js} +17 -6
  248. package/src/platforms/drivers/opencode/native-config.js +26 -0
  249. package/src/platforms/drivers/opencode/native-logs.js +133 -0
  250. package/src/platforms/drivers/opencode/project-config.js +98 -0
  251. package/src/platforms/drivers/opencode/projects.js +20 -0
  252. package/src/platforms/drivers/opencode/prompts.js +9 -0
  253. package/src/{server/opencode-proxy-server.js → platforms/drivers/opencode/proxy-implementation.js} +27 -362
  254. package/src/platforms/drivers/opencode/proxy.js +16 -0
  255. package/src/platforms/drivers/opencode/resource-sync.js +9 -0
  256. package/src/{server/services/opencode-sessions.js → platforms/drivers/opencode/sessions-implementation.js} +211 -64
  257. package/src/platforms/drivers/opencode/sessions.js +48 -0
  258. package/src/{server/services/opencode-statistics-service.js → platforms/drivers/opencode/statistics-implementation.js} +2 -2
  259. package/src/platforms/drivers/opencode/statistics.js +16 -0
  260. package/src/platforms/drivers/secure-file-driver.js +272 -0
  261. package/src/platforms/drivers/unsupported.js +7 -0
  262. package/src/platforms/manifest-schema.js +398 -0
  263. package/src/platforms/manifests/claude.json +489 -0
  264. package/src/platforms/manifests/codex.json +448 -0
  265. package/src/platforms/manifests/gemini.json +447 -0
  266. package/src/platforms/manifests/omp.json +496 -0
  267. package/src/platforms/manifests/opencode.json +422 -0
  268. package/src/{server/services → platforms}/native-oauth-adapters.js +87 -417
  269. package/src/{server/services → platforms}/notification-hooks.js +140 -1023
  270. package/src/platforms/oauth-credentials-service.js +506 -0
  271. package/src/platforms/path-resolver.js +179 -0
  272. package/src/platforms/platform-path-context.js +305 -0
  273. package/src/platforms/registry.js +248 -0
  274. package/src/platforms/remote-notification-providers.js +678 -0
  275. package/src/platforms/runtime.js +259 -0
  276. package/src/{server/services/session-history-adapters/index.js → platforms/session-history-adapters.js} +8 -1
  277. package/src/plugins/plugin-loader.js +2 -2
  278. package/src/plugins/plugin-manager.js +2 -2
  279. package/src/plugins/registry.js +27 -10
  280. package/src/reset-config.js +21 -5
  281. package/src/server/api/agents.js +46 -72
  282. package/src/server/api/commands.js +16 -6
  283. package/src/server/api/config-registry.js +206 -59
  284. package/src/server/api/config-sync.js +3 -2
  285. package/src/server/api/config-templates.js +20 -4
  286. package/src/server/api/config.js +111 -102
  287. package/src/server/api/convert.js +54 -178
  288. package/src/server/api/dashboard.js +168 -102
  289. package/src/server/api/hooks.js +1 -1
  290. package/src/server/api/mcp.js +224 -84
  291. package/src/server/api/platform-api-config.js +102 -0
  292. package/src/server/api/platform-route-factory.js +215 -0
  293. package/src/server/api/platform-routes.js +26 -0
  294. package/src/server/api/platforms.js +110 -0
  295. package/src/server/api/plugins.js +57 -57
  296. package/src/server/api/project-config.js +168 -0
  297. package/src/server/api/prompts.js +58 -45
  298. package/src/server/api/settings.js +55 -8
  299. package/src/server/api/skills.js +262 -133
  300. package/src/server/api/ui-config.js +2 -2
  301. package/src/server/api/validation-errors.js +18 -4
  302. package/src/server/api/workspaces.js +47 -12
  303. package/src/server/index.js +164 -213
  304. package/src/server/platform-context.js +81 -0
  305. package/src/server/services/channel-balance.js +11 -20
  306. package/src/server/services/channel-health.js +21 -6
  307. package/src/server/services/channel-scheduler.js +29 -39
  308. package/src/server/services/channel-sync-utils.js +23 -13
  309. package/src/server/services/config-export-service.js +350 -319
  310. package/src/server/services/config-registry-service.js +280 -54
  311. package/src/server/services/config-sync-manager.js +172 -944
  312. package/src/server/services/config-templates-service.js +419 -353
  313. package/src/server/services/control-manifest-store.js +206 -0
  314. package/src/server/services/dashboard-snapshot-worker.js +656 -100
  315. package/src/server/services/effective-control-service.js +557 -0
  316. package/src/server/services/local-resource-index.js +314 -0
  317. package/src/server/services/mcp-service.js +607 -977
  318. package/src/server/services/model-detector.js +15 -10
  319. package/src/server/services/native-log-channel-resolver.js +214 -0
  320. package/src/server/services/native-log-observer.js +291 -0
  321. package/src/server/services/network-access.js +1 -1
  322. package/src/server/services/platform-catalog.js +82 -0
  323. package/src/server/services/platform-resolution.js +32 -29
  324. package/src/server/services/plugins-service.js +376 -122
  325. package/src/server/services/project-config-service.js +550 -0
  326. package/src/server/services/project-path-validation.js +31 -0
  327. package/src/server/services/prompts-service.js +267 -233
  328. package/src/server/services/proxy-runtime.js +7 -4
  329. package/src/server/services/remote-credential-cache.js +43 -0
  330. package/src/server/services/repo-scanner-base.js +182 -75
  331. package/src/server/services/request-logger.js +7 -4
  332. package/src/server/services/session-history-index.js +780 -213
  333. package/src/server/services/session-history-worker.js +228 -33
  334. package/src/server/services/session-snapshot-worker.js +124 -34
  335. package/src/server/services/session-snapshots.js +4 -2
  336. package/src/server/services/skill-artifact-store.js +355 -0
  337. package/src/server/services/skill-format-adapters.js +65 -0
  338. package/src/server/services/skill-projection-service.js +323 -0
  339. package/src/server/services/skill-refresh-task-service.js +275 -0
  340. package/src/server/services/skill-service.js +2020 -677
  341. package/src/server/services/snapshot-cache.js +30 -8
  342. package/src/server/services/speed-test.js +8 -8
  343. package/src/server/services/sqlite-connection.js +19 -3
  344. package/src/server/services/statistics-service.js +240 -57
  345. package/src/server/services/ui-config.js +107 -76
  346. package/src/server/services/usage-log-utils.js +98 -0
  347. package/src/server/services/web-build.js +1 -1
  348. package/src/server/services/workspace-service.js +59 -29
  349. package/src/server/utils/pricing.js +10 -19
  350. package/src/server/websocket-server.js +153 -28
  351. package/src/{server/services/base → shared}/base-channel-service.js +156 -28
  352. package/src/shared/capability-driver.js +104 -0
  353. package/src/shared/channel-auth-policy.js +27 -0
  354. package/src/shared/channel-driver.js +161 -0
  355. package/src/shared/driver-factories/api.js +180 -0
  356. package/src/shared/driver-factories/mcp.js +244 -0
  357. package/src/shared/driver-factories/projects.js +121 -0
  358. package/src/shared/driver-factories/prompts.js +58 -0
  359. package/src/shared/driver-factories/resource-sync.js +375 -0
  360. package/src/shared/driver-result.js +78 -0
  361. package/src/shared/mcp-format.js +189 -0
  362. package/src/shared/platforms.js +104 -167
  363. package/src/shared/project-config.js +512 -0
  364. package/src/shared/proxy-driver.js +57 -0
  365. package/src/{server/services/base → shared}/response-usage-parser.js +8 -4
  366. package/src/ui/menu.js +91 -67
  367. package/src/utils/port-helper.js +35 -19
  368. package/src/utils/session.js +1 -1
  369. package/dist/web/assets/Analytics-COEUXmd4.js +0 -25
  370. package/dist/web/assets/Analytics-RNn1BUbG.css +0 -1
  371. package/dist/web/assets/ConfigTemplates-DLLkS53H.js +0 -1
  372. package/dist/web/assets/Home-DCEyLzwX.js +0 -1
  373. package/dist/web/assets/Home-DYVy4cGZ.css +0 -1
  374. package/dist/web/assets/PluginManager-Oe1ZHoMO.js +0 -1
  375. package/dist/web/assets/ProjectList-CWjuAFof.js +0 -1
  376. package/dist/web/assets/ProjectList-VeMNXfgO.css +0 -1
  377. package/dist/web/assets/SessionList-C4chCPxK.css +0 -1
  378. package/dist/web/assets/SessionList-v3ehGg9U.js +0 -1
  379. package/dist/web/assets/SkillManager-BsX8Qpj0.css +0 -1
  380. package/dist/web/assets/SkillManager-DUF_qP5S.js +0 -1
  381. package/dist/web/assets/WorkspaceManager-CjAWBosO.js +0 -1
  382. package/dist/web/assets/icons-nGdSKQn2.js +0 -1
  383. package/dist/web/assets/index-D5hyHPj7.js +0 -2
  384. package/dist/web/assets/index-bJRG3RXH.css +0 -1
  385. package/dist/web/assets/markdown-BNJfEgCA.js +0 -1
  386. package/dist/web/assets/markdown-BfC0goYb.css +0 -10
  387. package/dist/web/assets/naive-ui-BALVpMgd.js +0 -1
  388. package/dist/web/assets/vendors-DQA4Hw9v.js +0 -7
  389. package/dist/web/assets/vue-vendor-B5RqjEjQ.js +0 -45
  390. package/src/config/model-pricing.js +0 -35
  391. package/src/server/api/channels.js +0 -439
  392. package/src/server/api/claude-hooks.js +0 -56
  393. package/src/server/api/claude-statistics.js +0 -57
  394. package/src/server/api/codex-channels.js +0 -491
  395. package/src/server/api/codex-projects.js +0 -117
  396. package/src/server/api/codex-proxy.js +0 -293
  397. package/src/server/api/codex-sessions.js +0 -652
  398. package/src/server/api/codex-statistics.js +0 -57
  399. package/src/server/api/gemini-channels.js +0 -392
  400. package/src/server/api/gemini-projects.js +0 -103
  401. package/src/server/api/gemini-proxy.js +0 -231
  402. package/src/server/api/gemini-sessions.js +0 -550
  403. package/src/server/api/gemini-statistics.js +0 -57
  404. package/src/server/api/health-check.js +0 -31
  405. package/src/server/api/oauth-credentials.js +0 -198
  406. package/src/server/api/omp-channels.js +0 -575
  407. package/src/server/api/omp-projects.js +0 -88
  408. package/src/server/api/omp-proxy.js +0 -125
  409. package/src/server/api/omp-sessions.js +0 -386
  410. package/src/server/api/omp-statistics.js +0 -40
  411. package/src/server/api/opencode-channels.js +0 -471
  412. package/src/server/api/opencode-projects.js +0 -110
  413. package/src/server/api/opencode-proxy.js +0 -354
  414. package/src/server/api/opencode-sessions.js +0 -466
  415. package/src/server/api/opencode-statistics.js +0 -57
  416. package/src/server/api/projects.js +0 -120
  417. package/src/server/api/proxy.js +0 -330
  418. package/src/server/api/sessions.js +0 -810
  419. package/src/server/services/oauth-credentials-service.js +0 -945
  420. package/src/server/services/omp-session-log-observer.js +0 -138
  421. package/src/server/services/opencode-gateway-adapters.js +0 -43
  422. package/src/server/services/project-snapshots.js +0 -59
  423. package/src/server/services/proxy-log-helper.js +0 -298
  424. package/src/server/services/session-cache.js +0 -127
  425. package/src/server/services/session-converter.js +0 -582
  426. package/src/server/services/session-history-adapters/claude.js +0 -235
  427. /package/src/{server/services/claude-token-recovery.js → platforms/drivers/claude/token-recovery.js} +0 -0
  428. /package/src/{server/services/codex-parser.js → platforms/drivers/codex/parser.js} +0 -0
  429. /package/src/{server/services/codex-speed-test-template.json → platforms/drivers/codex/speed-test-template.json} +0 -0
  430. /package/src/{server/services/codex-wire.js → platforms/drivers/codex/wire.js} +0 -0
  431. /package/src/{server/services/omp-auth-gateway-client.js → platforms/drivers/omp/auth-gateway-client.js} +0 -0
  432. /package/src/{server/services/opencode-normalization.js → platforms/drivers/opencode/normalization.js} +0 -0
  433. /package/src/{server/services → shared}/config-artifact-paths.js +0 -0
  434. /package/src/{server/services/base → shared}/proxy-utils.js +0 -0
@@ -0,0 +1,1292 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { StringDecoder } = require('string_decoder');
6
+
7
+ const JSONL_READ_CHUNK_BYTES = 64 * 1024;
8
+ const CURSOR_ANCHOR_BYTES = 256;
9
+ const RECENT_EVENT_LIMIT = 2048;
10
+ const JSONL_LONG_LINE_THRESHOLD_BYTES = 256 * 1024;
11
+ const JSONL_MAX_PARSE_DEPTH = 128;
12
+ const JSONL_MAX_SELECTED_STRING_BYTES = 1024 * 1024;
13
+
14
+ const USAGE_FIELD_NAMES = [
15
+ 'input', 'input_tokens', 'inputTokens', 'prompt_tokens', 'promptTokens', 'promptTokenCount',
16
+ 'output', 'output_tokens', 'outputTokens', 'completion_tokens', 'completionTokens', 'candidatesTokenCount',
17
+ 'reasoning', 'reasoning_tokens', 'reasoningTokens', 'reasoning_output_tokens', 'thoughtsTokenCount',
18
+ 'cacheCreation', 'cache_creation', 'cacheWrite', 'cache_write',
19
+ 'cache_creation_input_tokens', 'cacheCreationInputTokens', 'cacheWriteInputTokens',
20
+ 'cacheRead', 'cache_read', 'cache_read_input_tokens', 'cacheReadInputTokens',
21
+ 'cached_input_tokens', 'cachedInputTokens', 'cachedContentTokenCount',
22
+ 'cached', 'cached_tokens', 'cachedTokens', 'total', 'total_tokens', 'totalTokens', 'totalTokenCount',
23
+ 'cost'
24
+ ];
25
+
26
+ function usageFieldPaths(prefix) {
27
+ const paths = USAGE_FIELD_NAMES.map(name => `${prefix}.${name}`);
28
+ [
29
+ 'usageMetadata',
30
+ 'input_tokens_details', 'inputTokensDetails', 'prompt_tokens_details', 'promptTokensDetails',
31
+ 'completion_tokens_details', 'completionTokensDetails', 'output_tokens_details', 'outputTokensDetails',
32
+ 'cache'
33
+ ].forEach(container => {
34
+ USAGE_FIELD_NAMES.forEach(name => paths.push(`${prefix}.${container}.${name}`));
35
+ });
36
+ ['total', 'usd', 'amount'].forEach(name => paths.push(`${prefix}.cost.${name}`));
37
+ return paths;
38
+ }
39
+
40
+ function createSelectionTree(paths = []) {
41
+ const root = { children: new Map() };
42
+ for (const rawPath of paths) {
43
+ const parts = Array.isArray(rawPath)
44
+ ? rawPath.map(String).filter(Boolean)
45
+ : String(rawPath || '').split('.').filter(Boolean);
46
+ if (parts.length === 0) continue;
47
+ let node = root;
48
+ for (const part of parts) {
49
+ if (!node.children.has(part)) node.children.set(part, { children: new Map() });
50
+ node = node.children.get(part);
51
+ }
52
+ }
53
+ return root;
54
+ }
55
+
56
+ function selectiveJsonError(code, message) {
57
+ const error = new Error(message);
58
+ error.code = code;
59
+ return error;
60
+ }
61
+
62
+ /**
63
+ * Parse a JSON value while retaining only an explicit set of fields.
64
+ * Unselected strings and objects are scanned but never materialized, which
65
+ * keeps large message/tool-output fields out of the heap.
66
+ */
67
+ function createSelectiveJsonLineParser(paths, {
68
+ maxDepth = JSONL_MAX_PARSE_DEPTH,
69
+ maxSelectedStringBytes = JSONL_MAX_SELECTED_STRING_BYTES
70
+ } = {}) {
71
+ const selection = createSelectionTree(paths);
72
+ let mode = 'struct';
73
+ let rootValue;
74
+ let rootDone = false;
75
+ let stringState = null;
76
+ let numberState = null;
77
+ let literalState = null;
78
+ const stack = [];
79
+
80
+ const fail = (code, message) => {
81
+ throw selectiveJsonError(code, message);
82
+ };
83
+
84
+ const assignValue = (sink, value) => {
85
+ if (!sink) {
86
+ rootValue = value;
87
+ rootDone = true;
88
+ return;
89
+ }
90
+ const parent = sink.parent;
91
+ parent.phase = 'commaOrEnd';
92
+ if (!parent.selection || value === undefined) return;
93
+ if (parent.kind === 'object') parent.value[sink.key] = value;
94
+ else parent.value.push(value);
95
+ };
96
+
97
+ const closeContainer = (closing) => {
98
+ const context = stack[stack.length - 1];
99
+ if (!context || context.close !== closing) fail('ERR_JSON_STRUCTURE', 'Mismatched JSON container');
100
+ stack.pop();
101
+ assignValue(context.sink, context.selection ? context.value : undefined);
102
+ };
103
+
104
+ const startValue = (selectionNode, sink, firstChar) => {
105
+ if (firstChar === '{' || firstChar === '[') {
106
+ if (stack.length + 1 > maxDepth) fail('ERR_JSON_MAX_DEPTH', `JSON nesting exceeds ${maxDepth} levels`);
107
+ const kind = firstChar === '{' ? 'object' : 'array';
108
+ stack.push({
109
+ kind,
110
+ close: kind === 'object' ? '}' : ']',
111
+ selection: selectionNode,
112
+ value: selectionNode ? (kind === 'object' ? {} : []) : null,
113
+ sink,
114
+ phase: kind === 'object' ? 'keyOrEnd' : 'valueOrEnd',
115
+ pendingKey: null
116
+ });
117
+ return;
118
+ }
119
+ if (firstChar === '"') {
120
+ stringState = {
121
+ purpose: 'value',
122
+ sink,
123
+ selected: Boolean(selectionNode),
124
+ value: '',
125
+ bytes: 0,
126
+ escaping: false,
127
+ unicodeDigits: 0,
128
+ unicodeValue: 0
129
+ };
130
+ mode = 'string';
131
+ return;
132
+ }
133
+ if (firstChar === '-' || /[0-9]/.test(firstChar)) {
134
+ numberState = { sink, selected: Boolean(selectionNode), text: firstChar, length: 1 };
135
+ mode = 'number';
136
+ return;
137
+ }
138
+ if (firstChar === 't' || firstChar === 'f' || firstChar === 'n') {
139
+ const expected = firstChar === 't' ? 'true' : firstChar === 'f' ? 'false' : 'null';
140
+ literalState = { sink, selected: Boolean(selectionNode), expected, index: 1 };
141
+ mode = 'literal';
142
+ return;
143
+ }
144
+ fail('ERR_JSON_VALUE', `Unexpected JSON value character: ${firstChar}`);
145
+ };
146
+
147
+ const completeString = () => {
148
+ const state = stringState;
149
+ stringState = null;
150
+ mode = 'struct';
151
+ if (state.purpose === 'key') {
152
+ const context = stack[stack.length - 1];
153
+ if (!context || context.kind !== 'object') fail('ERR_JSON_STRUCTURE', 'JSON key outside object');
154
+ context.pendingKey = state.value;
155
+ context.phase = 'colon';
156
+ return;
157
+ }
158
+ assignValue(state.sink, state.selected ? state.value : undefined);
159
+ };
160
+
161
+ const appendStringChar = (char) => {
162
+ const state = stringState;
163
+ if (!state.selected && state.purpose !== 'key') return;
164
+ const nextBytes = state.bytes + Buffer.byteLength(char, 'utf8');
165
+ const maxBytes = state.purpose === 'key' ? 64 * 1024 : maxSelectedStringBytes;
166
+ if (nextBytes > maxBytes) {
167
+ if (state.purpose === 'key') {
168
+ state.selected = false;
169
+ state.value = '';
170
+ return;
171
+ }
172
+ fail('ERR_JSON_SELECTED_STRING_LIMIT', `Selected JSON string exceeds ${maxBytes} bytes`);
173
+ }
174
+ state.bytes = nextBytes;
175
+ state.value += char;
176
+ };
177
+
178
+ const consumeStringChar = (char) => {
179
+ const state = stringState;
180
+ if (state.unicodeDigits > 0) {
181
+ const digit = Number.parseInt(char, 16);
182
+ if (!Number.isInteger(digit)) fail('ERR_JSON_ESCAPE', 'Invalid Unicode escape');
183
+ state.unicodeValue = state.unicodeValue * 16 + digit;
184
+ state.unicodeDigits -= 1;
185
+ if (state.unicodeDigits === 0) appendStringChar(String.fromCharCode(state.unicodeValue));
186
+ return;
187
+ }
188
+ if (state.escaping) {
189
+ state.escaping = false;
190
+ if (char === 'u') {
191
+ state.unicodeDigits = 4;
192
+ state.unicodeValue = 0;
193
+ return;
194
+ }
195
+ const escaped = { '"': '"', '\\': '\\', '/': '/', b: '\b', f: '\f', n: '\n', r: '\r', t: '\t' }[char];
196
+ if (escaped === undefined) fail('ERR_JSON_ESCAPE', `Invalid JSON escape: \\${char}`);
197
+ appendStringChar(escaped);
198
+ return;
199
+ }
200
+ if (char === '\\') {
201
+ state.escaping = true;
202
+ return;
203
+ }
204
+ if (char === '"') {
205
+ completeString();
206
+ return;
207
+ }
208
+ if (char.charCodeAt(0) <= 0x1f) fail('ERR_JSON_STRING', 'Unescaped control character in JSON string');
209
+ appendStringChar(char);
210
+ };
211
+
212
+ const finishNumber = () => {
213
+ const state = numberState;
214
+ numberState = null;
215
+ mode = 'struct';
216
+ if (!/^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$/.test(state.text)) {
217
+ fail('ERR_JSON_NUMBER', 'Invalid JSON number');
218
+ }
219
+ assignValue(state.sink, state.selected ? Number(state.text) : undefined);
220
+ };
221
+
222
+ const finishLiteral = () => {
223
+ const state = literalState;
224
+ if (state.index !== state.expected.length) fail('ERR_JSON_LITERAL', 'Incomplete JSON literal');
225
+ literalState = null;
226
+ mode = 'struct';
227
+ const value = state.expected === 'true' ? true : state.expected === 'false' ? false : null;
228
+ assignValue(state.sink, state.selected ? value : undefined);
229
+ };
230
+
231
+ const childSelection = (context) => {
232
+ if (!context?.selection) return null;
233
+ if (context.kind === 'object') return context.selection.children.get(context.pendingKey) || null;
234
+ return context.selection.children.get('*') || null;
235
+ };
236
+
237
+ const current = () => stack[stack.length - 1] || null;
238
+
239
+ const write = (text) => {
240
+ const source = String(text || '');
241
+ for (let index = 0; index < source.length;) {
242
+ const char = source[index];
243
+ if (mode === 'string') {
244
+ consumeStringChar(char);
245
+ index += 1;
246
+ continue;
247
+ }
248
+ if (mode === 'number') {
249
+ if (/[0-9eE+\-.]/.test(char)) {
250
+ numberState.length += 1;
251
+ if (numberState.length > 256) fail('ERR_JSON_NUMBER_LIMIT', 'JSON number exceeds 256 characters');
252
+ numberState.text += char;
253
+ index += 1;
254
+ } else {
255
+ finishNumber();
256
+ }
257
+ continue;
258
+ }
259
+ if (mode === 'literal') {
260
+ if (literalState.index < literalState.expected.length) {
261
+ if (char !== literalState.expected[literalState.index]) fail('ERR_JSON_LITERAL', 'Invalid JSON literal');
262
+ literalState.index += 1;
263
+ index += 1;
264
+ if (literalState.index === literalState.expected.length) finishLiteral();
265
+ } else {
266
+ finishLiteral();
267
+ }
268
+ continue;
269
+ }
270
+
271
+ if (/\s/.test(char)) {
272
+ index += 1;
273
+ continue;
274
+ }
275
+ if (rootDone && stack.length === 0) fail('ERR_JSON_TRAILING', 'Trailing data after JSON value');
276
+
277
+ const context = current();
278
+ if (!context) {
279
+ startValue(selection, null, char);
280
+ index += 1;
281
+ continue;
282
+ }
283
+
284
+ if (context.kind === 'object') {
285
+ if (context.phase === 'keyOrEnd' || context.phase === 'keyAfterComma') {
286
+ if (char === '}') {
287
+ if (context.phase === 'keyAfterComma') fail('ERR_JSON_OBJECT_KEY', 'Trailing comma in JSON object');
288
+ closeContainer('}');
289
+ index += 1;
290
+ } else if (char === '"') {
291
+ stringState = {
292
+ purpose: 'key', sink: null, selected: true, value: '', bytes: 0,
293
+ escaping: false, unicodeDigits: 0, unicodeValue: 0
294
+ };
295
+ mode = 'string';
296
+ index += 1;
297
+ } else {
298
+ fail('ERR_JSON_OBJECT_KEY', 'Expected JSON object key');
299
+ }
300
+ continue;
301
+ }
302
+ if (context.phase === 'colon') {
303
+ if (char !== ':') fail('ERR_JSON_STRUCTURE', 'Expected colon after JSON object key');
304
+ context.phase = 'valueOrEnd';
305
+ index += 1;
306
+ continue;
307
+ }
308
+ if (context.phase === 'valueOrEnd') {
309
+ startValue(childSelection(context), { parent: context, key: context.pendingKey }, char);
310
+ index += 1;
311
+ continue;
312
+ }
313
+ if (char === ',') {
314
+ context.phase = 'keyAfterComma';
315
+ context.pendingKey = null;
316
+ index += 1;
317
+ } else if (char === '}') {
318
+ closeContainer('}');
319
+ index += 1;
320
+ } else {
321
+ fail('ERR_JSON_STRUCTURE', 'Expected comma or object end');
322
+ }
323
+ continue;
324
+ }
325
+
326
+ if (context.phase === 'valueOrEnd') {
327
+ if (char === ']') {
328
+ closeContainer(']');
329
+ index += 1;
330
+ } else {
331
+ startValue(childSelection(context), { parent: context }, char);
332
+ index += 1;
333
+ }
334
+ continue;
335
+ }
336
+ if (context.phase === 'valueAfterComma') {
337
+ if (char === ']') fail('ERR_JSON_VALUE', 'Trailing comma in JSON array');
338
+ startValue(childSelection(context), { parent: context }, char);
339
+ index += 1;
340
+ continue;
341
+ }
342
+ if (char === ',') {
343
+ context.phase = 'valueAfterComma';
344
+ index += 1;
345
+ } else if (char === ']') {
346
+ closeContainer(']');
347
+ index += 1;
348
+ } else {
349
+ fail('ERR_JSON_STRUCTURE', 'Expected comma or array end');
350
+ }
351
+ }
352
+ return parser;
353
+ };
354
+
355
+ const finish = () => {
356
+ if (mode === 'number') finishNumber();
357
+ else if (mode === 'literal') finishLiteral();
358
+ else if (mode === 'string') fail('ERR_JSON_STRING', 'Unterminated JSON string');
359
+ if (stack.length > 0 || !rootDone) fail('ERR_JSON_INCOMPLETE', 'Incomplete JSON value');
360
+ return rootValue;
361
+ };
362
+
363
+ const parser = { write, finish };
364
+ return parser;
365
+ }
366
+
367
+ function number(value) {
368
+ const result = Number(value);
369
+ return Number.isFinite(result) && result >= 0 ? result : 0;
370
+ }
371
+
372
+ function readNumber(source, keys = []) {
373
+ for (const key of keys) {
374
+ if (source?.[key] === undefined || source?.[key] === null) continue;
375
+ return number(source[key]);
376
+ }
377
+ return 0;
378
+ }
379
+
380
+ function hasAny(source, keys = []) {
381
+ return keys.some(key => source?.[key] !== undefined && source?.[key] !== null);
382
+ }
383
+
384
+ function readNestedNumber(sources = [], keys = []) {
385
+ for (const source of sources) {
386
+ const value = readNumber(source, keys);
387
+ if (value > 0 || hasAny(source, keys)) return value;
388
+ }
389
+ return 0;
390
+ }
391
+
392
+ function normalizeCost(value) {
393
+ if (value && typeof value === 'object') {
394
+ return number(value.total ?? value.usd ?? value.amount);
395
+ }
396
+ return number(value);
397
+ }
398
+
399
+ /**
400
+ * Normalize native provider usage into the shared billing shape.
401
+ * `input` is the uncached billable input; cache reads are kept separately.
402
+ */
403
+ function normalizeUsage(usage = {}) {
404
+ const metadata = usage?.usageMetadata && typeof usage.usageMetadata === 'object'
405
+ ? usage.usageMetadata
406
+ : {};
407
+ const source = { ...metadata, ...(usage || {}) };
408
+ const inputKeys = ['input', 'input_tokens', 'inputTokens', 'prompt_tokens', 'promptTokens', 'promptTokenCount'];
409
+ const outputKeys = ['output', 'output_tokens', 'outputTokens', 'completion_tokens', 'completionTokens', 'candidatesTokenCount'];
410
+ const reasoningKeys = ['reasoning', 'reasoning_tokens', 'reasoningTokens', 'reasoning_output_tokens', 'thoughtsTokenCount'];
411
+ const cacheCreationKeys = [
412
+ 'cacheCreation', 'cache_creation', 'cacheWrite', 'cache_write',
413
+ 'cache_creation_input_tokens', 'cacheCreationInputTokens', 'cacheWriteInputTokens'
414
+ ];
415
+ const cacheReadKeys = [
416
+ 'cacheRead', 'cache_read', 'cache_read_input_tokens', 'cacheReadInputTokens',
417
+ 'cached_input_tokens', 'cachedInputTokens', 'cachedContentTokenCount'
418
+ ];
419
+ const cachedKeys = ['cached', 'cached_tokens', 'cachedTokens', 'cached_input_tokens', 'cachedInputTokens', 'cachedContentTokenCount'];
420
+ const totalKeys = ['total', 'total_tokens', 'totalTokens', 'totalTokenCount'];
421
+ const hasAnthropicCacheFields = hasAny(source, [
422
+ 'cache_creation_input_tokens', 'cacheCreationInputTokens',
423
+ 'cache_read_input_tokens', 'cacheReadInputTokens'
424
+ ]);
425
+ const hasGeminiUsageFields = hasAny(source, [
426
+ 'promptTokenCount', 'candidatesTokenCount', 'totalTokenCount',
427
+ 'cachedContentTokenCount', 'thoughtsTokenCount'
428
+ ]);
429
+ const hasOpenAiCachedInput = hasAny(source, [
430
+ 'cached_input_tokens', 'cachedInputTokens', 'cached_tokens', 'cachedTokens'
431
+ ]) || Boolean(source.input_tokens_details || source.inputTokensDetails || source.prompt_tokens_details || source.promptTokensDetails
432
+ || source.cache?.read || source.cache?.readTokens);
433
+
434
+ const rawInput = readNumber(source, inputKeys);
435
+ const output = readNumber(source, outputKeys);
436
+ const reasoning = readNumber(source, reasoningKeys)
437
+ || readNestedNumber([
438
+ source.completion_tokens_details,
439
+ source.completionTokensDetails,
440
+ source.output_tokens_details,
441
+ source.outputTokensDetails
442
+ ], ['reasoning_tokens', 'reasoningTokens']);
443
+ const cacheCreation = readNumber(source, cacheCreationKeys)
444
+ || readNestedNumber([
445
+ source.prompt_tokens_details,
446
+ source.promptTokensDetails
447
+ ], ['cache_creation_input_tokens', 'cacheCreationInputTokens'])
448
+ || readNumber(source.cache, ['write', 'writeTokens']);
449
+ const cacheRead = readNumber(source, cacheReadKeys)
450
+ || readNestedNumber([
451
+ source.input_tokens_details,
452
+ source.inputTokensDetails,
453
+ source.prompt_tokens_details,
454
+ source.promptTokensDetails
455
+ ], ['cached_tokens', 'cachedTokens'])
456
+ || readNumber(source.cache, ['read', 'readTokens']);
457
+ const cached = readNumber(source, cachedKeys) || cacheRead;
458
+ const inputIncludesCache = hasGeminiUsageFields || hasOpenAiCachedInput;
459
+ const input = Math.max(rawInput - (inputIncludesCache ? cacheRead : 0), 0);
460
+ const reportedTotal = readNumber(source, totalKeys);
461
+ const total = reportedTotal || (
462
+ hasAnthropicCacheFields
463
+ ? rawInput + output + reasoning + cacheCreation + cacheRead
464
+ : hasGeminiUsageFields
465
+ ? rawInput + output + reasoning
466
+ : rawInput + output
467
+ );
468
+ return { input, output, reasoning, cached, cacheCreation, cacheRead, total };
469
+ }
470
+
471
+ function subtractUsage(current, previous = {}) {
472
+ const next = normalizeUsage(current);
473
+ const before = normalizeUsage(previous);
474
+ const delta = Object.fromEntries(Object.keys(next).map(key => [key, Math.max(0, next[key] - before[key])]));
475
+ delta.total = delta.total || Object.entries(delta)
476
+ .filter(([key]) => key !== 'total')
477
+ .reduce((sum, [, value]) => sum + value, 0);
478
+ return delta;
479
+ }
480
+
481
+ function hasUsage(usage = {}) {
482
+ return normalizeUsage(usage).total > 0;
483
+ }
484
+
485
+ function readJsonLines(filePath, fsImpl = fs) {
486
+ const records = [];
487
+ visitJsonLinesForward(filePath, record => {
488
+ records.push(record);
489
+ return true;
490
+ }, { fsImpl, includeTrailingLine: true });
491
+ return records;
492
+ }
493
+
494
+ function walkFiles(rootDir, predicate, fsImpl = fs) {
495
+ const files = [];
496
+ const walk = directory => {
497
+ let entries;
498
+ try { entries = fsImpl.readdirSync(directory, { withFileTypes: true }); } catch (_) { return; }
499
+ for (const entry of entries) {
500
+ const filePath = path.join(directory, entry.name);
501
+ if (entry.isDirectory()) walk(filePath);
502
+ else if (entry.isFile() && predicate(entry.name, filePath)) files.push(filePath);
503
+ }
504
+ };
505
+ walk(rootDir);
506
+ return files;
507
+ }
508
+
509
+ function readFileRange(filePath, offset, length, fsImpl = fs) {
510
+ if (length <= 0) return { bytesRead: 0, buffer: Buffer.alloc(0) };
511
+
512
+ const fd = fsImpl.openSync(filePath, 'r');
513
+ const buffer = Buffer.allocUnsafe(length);
514
+ try {
515
+ const bytesRead = readInto(fd, buffer, length, offset, fsImpl);
516
+ return { bytesRead, buffer: buffer.subarray(0, bytesRead) };
517
+ } finally {
518
+ fsImpl.closeSync(fd);
519
+ }
520
+ }
521
+
522
+ function readInto(fd, buffer, length, offset, fsImpl = fs) {
523
+ let bytesRead = 0;
524
+ while (bytesRead < length) {
525
+ const count = fsImpl.readSync(fd, buffer, bytesRead, length - bytesRead, offset + bytesRead);
526
+ if (!count) break;
527
+ bytesRead += count;
528
+ }
529
+ return bytesRead;
530
+ }
531
+
532
+ function visitJsonLinesReverse(filePath, visitor, {
533
+ fsImpl = fs,
534
+ chunkSize = JSONL_READ_CHUNK_BYTES,
535
+ endOffset = null,
536
+ includeTrailingLine = true,
537
+ createLongLineParser = null,
538
+ longLineThresholdBytes = JSONL_LONG_LINE_THRESHOLD_BYTES,
539
+ stats = null
540
+ } = {}) {
541
+ let size;
542
+ try { size = endOffset === null ? fsImpl.statSync(filePath).size : Math.max(0, Number(endOffset)); } catch (_) { return; }
543
+ const fd = fsImpl.openSync(filePath, 'r');
544
+ let position = size;
545
+ let stopped = false;
546
+ let lineEnd = size;
547
+ let sawLineBoundary = false;
548
+ let trailingCandidate = true;
549
+ let hasFinalNewline = false;
550
+ let pendingRanges = [];
551
+ let activeChunkStart = 0;
552
+ let activeChunkEnd = 0;
553
+ const scratch = Buffer.allocUnsafe(Math.max(1, chunkSize));
554
+
555
+ const visitRange = (start, end) => {
556
+ const length = end - start;
557
+ if (length <= 0) return true;
558
+ if (stats) stats.maxLineLength = Math.max(stats.maxLineLength || 0, length);
559
+ let value;
560
+ try {
561
+ if (length > longLineThresholdBytes && typeof createLongLineParser === 'function') {
562
+ const parser = createLongLineParser();
563
+ const decoder = new StringDecoder('utf8');
564
+ let offset = start;
565
+ while (offset < end) {
566
+ const requested = Math.min(scratch.length, end - offset);
567
+ const count = readInto(fd, scratch, requested, offset, fsImpl);
568
+ if (stats) stats.bytesRead = (stats.bytesRead || 0) + count;
569
+ if (count !== requested) {
570
+ if (stats) stats.readErrors = (stats.readErrors || 0) + 1;
571
+ return true;
572
+ }
573
+ parser.write(decoder.write(scratch.subarray(0, count)));
574
+ offset += count;
575
+ }
576
+ const trailing = decoder.end();
577
+ if (trailing) parser.write(trailing);
578
+ if (offset !== end) return true;
579
+ value = parser.finish();
580
+ } else {
581
+ let text;
582
+ if (start >= activeChunkStart && end <= activeChunkEnd) {
583
+ text = scratch.subarray(start - activeChunkStart, end - activeChunkStart).toString('utf8');
584
+ } else {
585
+ const decoder = new StringDecoder('utf8');
586
+ text = '';
587
+ let offset = start;
588
+ while (offset < end) {
589
+ const requested = Math.min(scratch.length, end - offset);
590
+ const bytesRead = readInto(fd, scratch, requested, offset, fsImpl);
591
+ if (stats) stats.bytesRead = (stats.bytesRead || 0) + bytesRead;
592
+ if (bytesRead !== requested) {
593
+ if (stats) stats.readErrors = (stats.readErrors || 0) + 1;
594
+ return true;
595
+ }
596
+ text += decoder.write(scratch.subarray(0, bytesRead));
597
+ offset += bytesRead;
598
+ }
599
+ text += decoder.end();
600
+ activeChunkStart = -1;
601
+ activeChunkEnd = -1;
602
+ }
603
+ text = text.replace(/\r$/, '');
604
+ if (!text.trim()) return true;
605
+ value = JSON.parse(text);
606
+ }
607
+ } catch (_) {
608
+ if (stats) stats.parseErrors = (stats.parseErrors || 0) + 1;
609
+ activeChunkStart = -1;
610
+ activeChunkEnd = -1;
611
+ return true;
612
+ }
613
+ activeChunkStart = -1;
614
+ activeChunkEnd = -1;
615
+ if (stats) stats.parsedRecords = (stats.parsedRecords || 0) + 1;
616
+ return visitor(value) !== false;
617
+ };
618
+
619
+ try {
620
+ while (position > 0 && !stopped) {
621
+ const start = Math.max(0, position - chunkSize);
622
+ const length = position - start;
623
+ const bytesRead = readInto(fd, scratch, length, start, fsImpl);
624
+ if (stats) stats.bytesRead = (stats.bytesRead || 0) + bytesRead;
625
+ if (bytesRead !== length) {
626
+ if (stats) stats.readErrors = (stats.readErrors || 0) + 1;
627
+ break;
628
+ }
629
+ activeChunkStart = start;
630
+ activeChunkEnd = start + bytesRead;
631
+ for (let index = bytesRead - 1; index >= 0; index -= 1) {
632
+ if (scratch[index] !== 0x0a) continue;
633
+ if (start + index === size - 1) hasFinalNewline = true;
634
+ const lineStart = start + index + 1;
635
+ if (lineEnd > lineStart) {
636
+ const skipUnterminatedTail = !includeTrailingLine && trailingCandidate && !hasFinalNewline;
637
+ if (!skipUnterminatedTail) pendingRanges.push([lineStart, lineEnd]);
638
+ trailingCandidate = false;
639
+ }
640
+ sawLineBoundary = true;
641
+ lineEnd = start + index;
642
+ }
643
+ if (stopped) break;
644
+ for (const [rangeStart, rangeEnd] of pendingRanges) {
645
+ if (!visitRange(rangeStart, rangeEnd)) {
646
+ stopped = true;
647
+ break;
648
+ }
649
+ }
650
+ pendingRanges = [];
651
+ if (stopped) break;
652
+ position = start;
653
+ }
654
+ if (!stopped && lineEnd > 0 && (includeTrailingLine || sawLineBoundary)) {
655
+ visitRange(0, lineEnd);
656
+ }
657
+ } finally {
658
+ fsImpl.closeSync(fd);
659
+ }
660
+ }
661
+
662
+ /**
663
+ * Visit complete JSONL records in forward order up to a fixed byte watermark.
664
+ * The final unterminated record is deliberately ignored.
665
+ */
666
+ function visitJsonLinesForward(filePath, visitor, {
667
+ fsImpl = fs,
668
+ chunkSize = JSONL_READ_CHUNK_BYTES,
669
+ endOffset = null,
670
+ includeTrailingLine = false,
671
+ createLongLineParser = null,
672
+ longLineThresholdBytes = JSONL_LONG_LINE_THRESHOLD_BYTES,
673
+ stats = null
674
+ } = {}) {
675
+ let size;
676
+ try { size = endOffset === null ? fsImpl.statSync(filePath).size : Math.max(0, Number(endOffset)); } catch (_) { return; }
677
+ const fd = fsImpl.openSync(filePath, 'r');
678
+ const scratch = Buffer.allocUnsafe(Math.max(1, chunkSize));
679
+ const decoder = new StringDecoder('utf8');
680
+ let position = 0;
681
+ let stopped = false;
682
+ let lineText = '';
683
+ let lineParser = null;
684
+ let lineBytes = 0;
685
+ let lineDiscarded = false;
686
+
687
+ const submitLine = () => {
688
+ const length = lineBytes;
689
+ if (stats) stats.maxLineLength = Math.max(stats.maxLineLength || 0, length);
690
+ if (lineDiscarded) {
691
+ lineParser = null;
692
+ lineText = '';
693
+ lineBytes = 0;
694
+ lineDiscarded = false;
695
+ return true;
696
+ }
697
+ let value;
698
+ try {
699
+ if (lineParser) {
700
+ value = lineParser.finish();
701
+ } else {
702
+ const text = lineText.replace(/\r$/, '');
703
+ lineText = '';
704
+ if (!text.trim()) {
705
+ lineBytes = 0;
706
+ return true;
707
+ }
708
+ value = JSON.parse(text);
709
+ }
710
+ } catch (_) {
711
+ if (stats) stats.parseErrors = (stats.parseErrors || 0) + 1;
712
+ lineParser = null;
713
+ lineText = '';
714
+ lineBytes = 0;
715
+ return true;
716
+ }
717
+ lineParser = null;
718
+ lineText = '';
719
+ lineBytes = 0;
720
+ if (stats) stats.parsedRecords = (stats.parsedRecords || 0) + 1;
721
+ return visitor(value) !== false;
722
+ };
723
+
724
+ const consumeText = text => {
725
+ const appendSegment = segment => {
726
+ if (!segment) return;
727
+ lineBytes += Buffer.byteLength(segment, 'utf8');
728
+ if (lineDiscarded) return;
729
+ try {
730
+ if (lineParser) {
731
+ lineParser.write(segment);
732
+ } else if (lineBytes > longLineThresholdBytes && typeof createLongLineParser === 'function') {
733
+ lineParser = createLongLineParser();
734
+ lineParser.write(lineText);
735
+ lineParser.write(segment);
736
+ lineText = '';
737
+ } else {
738
+ lineText += segment;
739
+ }
740
+ } catch (_) {
741
+ if (stats) stats.parseErrors = (stats.parseErrors || 0) + 1;
742
+ lineText = '';
743
+ lineParser = null;
744
+ lineDiscarded = true;
745
+ }
746
+ };
747
+ let segmentStart = 0;
748
+ for (let index = 0; index < text.length; index += 1) {
749
+ if (text[index] !== '\n') continue;
750
+ appendSegment(text.slice(segmentStart, index));
751
+ if (!submitLine()) {
752
+ stopped = true;
753
+ return;
754
+ }
755
+ segmentStart = index + 1;
756
+ }
757
+ appendSegment(text.slice(segmentStart));
758
+ };
759
+
760
+ try {
761
+ while (position < size && !stopped) {
762
+ const length = Math.min(scratch.length, size - position);
763
+ const bytesRead = readInto(fd, scratch, length, position, fsImpl);
764
+ if (!bytesRead) {
765
+ if (stats) stats.readErrors = (stats.readErrors || 0) + 1;
766
+ break;
767
+ }
768
+ if (bytesRead !== length) {
769
+ if (stats) stats.readErrors = (stats.readErrors || 0) + 1;
770
+ break;
771
+ }
772
+ if (stats) stats.bytesRead = (stats.bytesRead || 0) + bytesRead;
773
+ consumeText(decoder.write(scratch.subarray(0, bytesRead)));
774
+ position += bytesRead;
775
+ }
776
+ if (!stopped && includeTrailingLine && lineBytes > 0) submitLine();
777
+ } finally {
778
+ fsImpl.closeSync(fd);
779
+ }
780
+ }
781
+
782
+ function findTrailingLineOffset(filePath, size, fsImpl = fs, stats = null) {
783
+ if (size <= 0) return 0;
784
+ const fd = fsImpl.openSync(filePath, 'r');
785
+ let position = size;
786
+ const scratch = Buffer.allocUnsafe(JSONL_READ_CHUNK_BYTES);
787
+ try {
788
+ while (position > 0) {
789
+ const start = Math.max(0, position - scratch.length);
790
+ const requested = position - start;
791
+ const bytesRead = readInto(fd, scratch, requested, start, fsImpl);
792
+ if (stats) stats.bytesRead = (stats.bytesRead || 0) + bytesRead;
793
+ if (bytesRead !== requested) {
794
+ if (stats) stats.readErrors = (stats.readErrors || 0) + 1;
795
+ break;
796
+ }
797
+ for (let index = bytesRead - 1; index >= 0; index -= 1) {
798
+ if (scratch[index] !== 0x0a) continue;
799
+ const absolute = start + index + 1;
800
+ return absolute === size ? size : absolute;
801
+ }
802
+ position = start;
803
+ }
804
+ } finally {
805
+ fsImpl.closeSync(fd);
806
+ }
807
+ return 0;
808
+ }
809
+
810
+ function createIncrementalJsonlCursor({
811
+ scanFiles,
812
+ parseLine,
813
+ createFileState = () => ({}),
814
+ bootstrapFile,
815
+ afterRead,
816
+ normalizeEvent = event => event,
817
+ acceptEvent = () => true,
818
+ fsImpl = fs,
819
+ skipInitialParse = false,
820
+ createLongLineParser = null,
821
+ longLineThresholdBytes = JSONL_LONG_LINE_THRESHOLD_BYTES,
822
+ onDiagnostic = () => {},
823
+ onError = () => {}
824
+ }) {
825
+ let fileStates = new Map();
826
+ let recentEventIds = new Map();
827
+ let initialized = false;
828
+
829
+ const rememberEvent = (eventId) => {
830
+ if (!eventId) return true;
831
+ if (recentEventIds.has(eventId)) {
832
+ // Refresh insertion order so the bounded map behaves as an LRU.
833
+ recentEventIds.delete(eventId);
834
+ recentEventIds.set(eventId, true);
835
+ return false;
836
+ }
837
+ recentEventIds.set(eventId, true);
838
+ if (recentEventIds.size > RECENT_EVENT_LIMIT) {
839
+ recentEventIds.delete(recentEventIds.keys().next().value);
840
+ }
841
+ return true;
842
+ };
843
+
844
+ const makeState = (filePath, stat, previousState = null, replaced = false) => {
845
+ const customState = createFileState(filePath, stat, previousState, replaced) || {};
846
+ return {
847
+ ...customState,
848
+ dev: stat.dev,
849
+ ino: stat.ino,
850
+ size: 0,
851
+ offset: 0,
852
+ mtimeMs: stat.mtimeMs,
853
+ ctimeMs: stat.ctimeMs,
854
+ lineText: '',
855
+ lineParser: null,
856
+ lineBytes: 0,
857
+ lineDiscarded: false,
858
+ decoder: new StringDecoder('utf8'),
859
+ prefixBytes: Buffer.alloc(0),
860
+ tailBytes: Buffer.alloc(0),
861
+ baselineReady: false,
862
+ entryIndex: Number.isInteger(customState.entryIndex) ? customState.entryIndex : 0
863
+ };
864
+ };
865
+
866
+ const setAnchors = (filePath, state, offset, stats = null) => {
867
+ const prefix = readFileRange(
868
+ filePath,
869
+ 0,
870
+ Math.min(offset, CURSOR_ANCHOR_BYTES),
871
+ fsImpl
872
+ );
873
+ const tailLength = Math.min(offset, CURSOR_ANCHOR_BYTES);
874
+ const tail = readFileRange(filePath, Math.max(0, offset - tailLength), tailLength, fsImpl);
875
+ if (prefix.bytesRead !== Math.min(offset, CURSOR_ANCHOR_BYTES) || tail.bytesRead !== tailLength) {
876
+ throw new Error('Native log anchor read was incomplete');
877
+ }
878
+ if (stats) stats.bytesRead = (stats.bytesRead || 0) + prefix.bytesRead + tail.bytesRead;
879
+ state.prefixBytes = Buffer.from(prefix.buffer);
880
+ state.tailBytes = Buffer.from(tail.buffer);
881
+ };
882
+
883
+ const readAnchorSnapshot = (filePath, endOffset, stats = null) => {
884
+ const prefixLength = Math.min(endOffset, CURSOR_ANCHOR_BYTES);
885
+ const tailLength = Math.min(endOffset, CURSOR_ANCHOR_BYTES);
886
+ const prefix = readFileRange(filePath, 0, prefixLength, fsImpl);
887
+ const tail = readFileRange(filePath, Math.max(0, endOffset - tailLength), tailLength, fsImpl);
888
+ if (stats) stats.bytesRead = (stats.bytesRead || 0) + prefix.bytesRead + tail.bytesRead;
889
+ if (prefix.bytesRead !== prefixLength || tail.bytesRead !== tailLength) {
890
+ throw new Error('Native log anchor snapshot was incomplete');
891
+ }
892
+ return { prefix: Buffer.from(prefix.buffer), tail: Buffer.from(tail.buffer) };
893
+ };
894
+
895
+ const updateAnchors = (state, buffer) => {
896
+ if (!buffer.length) return;
897
+ if (state.prefixBytes.length < CURSOR_ANCHOR_BYTES) {
898
+ const needed = CURSOR_ANCHOR_BYTES - state.prefixBytes.length;
899
+ const copied = Math.min(needed, buffer.length);
900
+ const nextPrefix = Buffer.allocUnsafe(state.prefixBytes.length + copied);
901
+ state.prefixBytes.copy(nextPrefix);
902
+ buffer.copy(nextPrefix, state.prefixBytes.length, 0, copied);
903
+ state.prefixBytes = nextPrefix;
904
+ }
905
+ const tailLength = Math.min(CURSOR_ANCHOR_BYTES, state.tailBytes.length + buffer.length);
906
+ const nextTail = Buffer.allocUnsafe(tailLength);
907
+ const fromOld = Math.max(0, tailLength - buffer.length);
908
+ const oldStart = Math.max(0, state.tailBytes.length - fromOld);
909
+ const oldLength = Math.min(fromOld, state.tailBytes.length - oldStart);
910
+ if (oldLength) state.tailBytes.copy(nextTail, 0, oldStart, oldStart + oldLength);
911
+ buffer.copy(nextTail, oldLength, Math.max(0, buffer.length - (tailLength - oldLength)));
912
+ state.tailBytes = nextTail;
913
+ };
914
+
915
+ const isReplaced = (filePath, state, stat) => {
916
+ if (state.dev !== stat.dev || state.ino !== stat.ino || stat.size < state.offset) return true;
917
+ const metadataChanged = state.mtimeMs !== stat.mtimeMs || state.ctimeMs !== stat.ctimeMs;
918
+ if (stat.size === state.size && metadataChanged) return true;
919
+ if (!metadataChanged || stat.size <= state.offset) return false;
920
+ try {
921
+ if (state.prefixBytes.length > 0
922
+ && !readFileRange(filePath, 0, state.prefixBytes.length, fsImpl).buffer.equals(state.prefixBytes)) {
923
+ return true;
924
+ }
925
+ if (state.tailBytes.length > 0
926
+ && !readFileRange(
927
+ filePath,
928
+ state.offset - state.tailBytes.length,
929
+ state.tailBytes.length,
930
+ fsImpl
931
+ ).buffer.equals(state.tailBytes)) {
932
+ return true;
933
+ }
934
+ } catch (_) {
935
+ return true;
936
+ }
937
+ return false;
938
+ };
939
+
940
+ const collectEvent = (rawEvent, filePath, events, shouldEmit) => {
941
+ if (!rawEvent) return;
942
+ const event = normalizeEvent(rawEvent, filePath);
943
+ if (!event || !event.id || !acceptEvent(event)) return;
944
+ if (!rememberEvent(event.id)) return;
945
+ if (shouldEmit) events.push(event);
946
+ };
947
+
948
+ const appendLineText = (state, text) => {
949
+ if (!text) return;
950
+ state.lineBytes += Buffer.byteLength(text, 'utf8');
951
+ if (state.lineDiscarded) return;
952
+ if (state.lineParser) {
953
+ state.lineParser.write(text);
954
+ return;
955
+ }
956
+ if (state.lineBytes > longLineThresholdBytes && typeof createLongLineParser === 'function') {
957
+ state.lineParser = createLongLineParser();
958
+ if (state.lineText) state.lineParser.write(state.lineText);
959
+ state.lineParser.write(text);
960
+ state.lineText = '';
961
+ return;
962
+ }
963
+ state.lineText += text;
964
+ };
965
+
966
+ const submitLine = (filePath, state, events, shouldEmit, stats) => {
967
+ if (state.lineBytes === 0) return;
968
+ stats.maxLineLength = Math.max(stats.maxLineLength, state.lineBytes);
969
+ if (state.lineDiscarded) {
970
+ state.lineText = '';
971
+ state.lineParser = null;
972
+ state.lineBytes = 0;
973
+ state.lineDiscarded = false;
974
+ state.entryIndex += 1;
975
+ return;
976
+ }
977
+ let record;
978
+ try {
979
+ if (state.lineParser) {
980
+ record = state.lineParser.finish();
981
+ } else {
982
+ const line = state.lineText.replace(/\r$/, '');
983
+ if (!line.trim()) {
984
+ state.lineText = '';
985
+ state.lineBytes = 0;
986
+ return;
987
+ }
988
+ record = JSON.parse(line);
989
+ }
990
+ stats.parsedRecords += 1;
991
+ } catch (error) {
992
+ stats.parseErrors += 1;
993
+ onError(error, filePath);
994
+ state.lineText = '';
995
+ state.lineParser = null;
996
+ state.lineBytes = 0;
997
+ state.lineDiscarded = false;
998
+ state.entryIndex += 1;
999
+ return;
1000
+ }
1001
+ try {
1002
+ const parsed = parseLine(filePath, record, state, state.entryIndex);
1003
+ const parsedEvents = Array.isArray(parsed) ? parsed : [parsed];
1004
+ parsedEvents.forEach(event => collectEvent(event, filePath, events, shouldEmit));
1005
+ } catch (error) {
1006
+ stats.parseErrors += 1;
1007
+ onError(error, filePath);
1008
+ }
1009
+ state.lineText = '';
1010
+ state.lineParser = null;
1011
+ state.lineBytes = 0;
1012
+ state.lineDiscarded = false;
1013
+ state.entryIndex += 1;
1014
+ };
1015
+
1016
+ const consumeText = (filePath, state, text, events, shouldEmit, stats) => {
1017
+ const appendSegment = segment => {
1018
+ if (!segment) return;
1019
+ try {
1020
+ appendLineText(state, segment);
1021
+ } catch (error) {
1022
+ stats.parseErrors += 1;
1023
+ onError(error, filePath);
1024
+ state.lineText = '';
1025
+ state.lineParser = null;
1026
+ state.lineDiscarded = true;
1027
+ }
1028
+ };
1029
+ let segmentStart = 0;
1030
+ for (let index = 0; index < text.length; index += 1) {
1031
+ if (text[index] !== '\n') continue;
1032
+ appendSegment(text.slice(segmentStart, index));
1033
+ submitLine(filePath, state, events, shouldEmit, stats);
1034
+ segmentStart = index + 1;
1035
+ }
1036
+ appendSegment(text.slice(segmentStart));
1037
+ };
1038
+
1039
+ const readChangedBytes = (filePath, state, stat, events, shouldEmit, stats) => {
1040
+ if (stat.size <= state.offset) return true;
1041
+ const fd = fsImpl.openSync(filePath, 'r');
1042
+ const scratch = Buffer.allocUnsafe(JSONL_READ_CHUNK_BYTES);
1043
+ let complete = true;
1044
+ try {
1045
+ while (state.offset < stat.size) {
1046
+ const length = Math.min(scratch.length, stat.size - state.offset);
1047
+ const bytesRead = fsImpl.readSync(fd, scratch, 0, length, state.offset);
1048
+ if (!bytesRead) {
1049
+ complete = false;
1050
+ stats.readErrors += 1;
1051
+ break;
1052
+ }
1053
+ if (bytesRead !== length) {
1054
+ complete = false;
1055
+ stats.readErrors += 1;
1056
+ }
1057
+ const chunk = scratch.subarray(0, bytesRead);
1058
+ stats.bytesRead += bytesRead;
1059
+ consumeText(filePath, state, state.decoder.write(chunk), events, shouldEmit, stats);
1060
+ state.offset += bytesRead;
1061
+ updateAnchors(state, chunk);
1062
+ if (!complete) break;
1063
+ }
1064
+ } finally {
1065
+ fsImpl.closeSync(fd);
1066
+ }
1067
+ return complete && state.offset >= stat.size;
1068
+ };
1069
+
1070
+ const read = () => {
1071
+ const events = [];
1072
+ const files = scanFiles();
1073
+ const currentFiles = new Set(files);
1074
+ const isInitialRead = !initialized;
1075
+ const stats = {
1076
+ files: files.length,
1077
+ bytesRead: 0,
1078
+ parsedRecords: 0,
1079
+ maxLineLength: 0,
1080
+ parseErrors: 0,
1081
+ readErrors: 0
1082
+ };
1083
+ let baselineReady = true;
1084
+ for (const filePath of fileStates.keys()) {
1085
+ if (!currentFiles.has(filePath)) fileStates.delete(filePath);
1086
+ }
1087
+
1088
+ for (const filePath of files) {
1089
+ let stat;
1090
+ try { stat = fsImpl.statSync(filePath); } catch (_) {
1091
+ stats.readErrors += 1;
1092
+ if (skipInitialParse && isInitialRead) baselineReady = false;
1093
+ continue;
1094
+ }
1095
+ let state = fileStates.get(filePath);
1096
+ const replaced = state && isReplaced(filePath, state, stat);
1097
+ if (!state || replaced) {
1098
+ state = makeState(filePath, stat, state, Boolean(replaced));
1099
+ fileStates.set(filePath, state);
1100
+ }
1101
+
1102
+ if (skipInitialParse && !state.baselineReady) {
1103
+ const baselineSize = stat.size;
1104
+ const readErrorsBefore = stats.readErrors;
1105
+ let bootstrapResult = true;
1106
+ let baselineAnchors;
1107
+ try {
1108
+ baselineAnchors = readAnchorSnapshot(filePath, baselineSize, stats);
1109
+ bootstrapResult = bootstrapFile ? bootstrapFile(filePath, state, stat, stats) : true;
1110
+ if (bootstrapResult && typeof bootstrapResult === 'object') bootstrapResult = bootstrapResult.ok !== false;
1111
+ } catch (error) {
1112
+ stats.parseErrors += 1;
1113
+ onError(error, filePath);
1114
+ bootstrapResult = false;
1115
+ }
1116
+ let currentStat;
1117
+ let currentAnchors;
1118
+ try { currentStat = fsImpl.statSync(filePath); } catch (_) { currentStat = null; }
1119
+ try { currentAnchors = readAnchorSnapshot(filePath, baselineSize, stats); } catch (_) { currentAnchors = null; }
1120
+ if (!bootstrapResult || !currentStat
1121
+ || stats.readErrors > readErrorsBefore
1122
+ || currentStat.dev !== stat.dev || currentStat.ino !== stat.ino || currentStat.size < baselineSize
1123
+ || !currentAnchors
1124
+ || !currentAnchors.prefix.equals(baselineAnchors?.prefix || Buffer.alloc(0))
1125
+ || !currentAnchors.tail.equals(baselineAnchors?.tail || Buffer.alloc(0))) {
1126
+ baselineReady = false;
1127
+ stats.parseErrors += 1;
1128
+ onError(new Error('Native log baseline did not reach a stable file watermark'), filePath);
1129
+ continue;
1130
+ }
1131
+ state.offset = findTrailingLineOffset(filePath, baselineSize, fsImpl, stats);
1132
+ state.size = stat.size;
1133
+ state.dev = stat.dev;
1134
+ state.ino = stat.ino;
1135
+ state.mtimeMs = stat.mtimeMs;
1136
+ state.ctimeMs = stat.ctimeMs;
1137
+ setAnchors(filePath, state, state.offset, stats);
1138
+ state.baselineReady = true;
1139
+ continue;
1140
+ }
1141
+
1142
+ const unchanged = !replaced
1143
+ && state.size === stat.size
1144
+ && state.mtimeMs === stat.mtimeMs
1145
+ && state.ctimeMs === stat.ctimeMs;
1146
+ if (unchanged) continue;
1147
+
1148
+ const shouldEmit = initialized;
1149
+ let readCompleted = false;
1150
+ try {
1151
+ if (!readChangedBytes(filePath, state, stat, events, shouldEmit, stats)) {
1152
+ throw new Error('Native log read ended before the requested file range');
1153
+ }
1154
+ const trailing = afterRead?.(filePath, state, { initialized, replaced: Boolean(replaced) });
1155
+ const trailingEvents = Array.isArray(trailing) ? trailing : [trailing];
1156
+ trailingEvents.forEach(event => collectEvent(event, filePath, events, shouldEmit));
1157
+ readCompleted = true;
1158
+ } catch (error) {
1159
+ onError(error, filePath);
1160
+ }
1161
+ if (!readCompleted) continue;
1162
+ state.size = stat.size;
1163
+ state.dev = stat.dev;
1164
+ state.ino = stat.ino;
1165
+ state.mtimeMs = stat.mtimeMs;
1166
+ state.ctimeMs = stat.ctimeMs;
1167
+ state.baselineReady = true;
1168
+ }
1169
+ for (const state of fileStates.values()) {
1170
+ if (skipInitialParse && !state.baselineReady) baselineReady = false;
1171
+ }
1172
+ initialized = baselineReady;
1173
+ try { onDiagnostic({ ...stats, initialized }); } catch (_) {}
1174
+ return events;
1175
+ };
1176
+
1177
+ return {
1178
+ initialize() { read(); },
1179
+ readNewEvents: read,
1180
+ read,
1181
+ reset() { fileStates = new Map(); recentEventIds = new Map(); initialized = false; },
1182
+ close() { fileStates.clear(); recentEventIds.clear(); initialized = false; }
1183
+ };
1184
+ }
1185
+
1186
+ function createScannedFileCursor({
1187
+ scanFiles,
1188
+ parseFile,
1189
+ normalizeEvent,
1190
+ fsImpl = fs,
1191
+ skipInitialParse = false,
1192
+ shouldParseFile = () => true
1193
+ }) {
1194
+ let seenByFile = new Map();
1195
+ let fileStates = new Map();
1196
+ let initialized = false;
1197
+
1198
+ const read = () => {
1199
+ const events = [];
1200
+ const files = scanFiles();
1201
+ const isInitialRead = !initialized;
1202
+ const currentFiles = new Set(files);
1203
+ for (const filePath of fileStates.keys()) {
1204
+ if (!currentFiles.has(filePath)) {
1205
+ fileStates.delete(filePath);
1206
+ seenByFile.delete(filePath);
1207
+ }
1208
+ }
1209
+
1210
+ for (const filePath of files) {
1211
+ let stat;
1212
+ try { stat = fsImpl.statSync(filePath); } catch (_) { continue; }
1213
+ const previousState = fileStates.get(filePath);
1214
+ const replaced = previousState
1215
+ && (previousState.dev !== stat.dev
1216
+ || previousState.ino !== stat.ino
1217
+ || stat.size < previousState.size);
1218
+ const changed = !previousState
1219
+ || replaced
1220
+ || previousState.size !== stat.size
1221
+ || previousState.mtimeMs !== stat.mtimeMs
1222
+ || previousState.ctimeMs !== stat.ctimeMs;
1223
+ const unchanged = previousState && !changed;
1224
+ if (!previousState || replaced) {
1225
+ seenByFile.set(filePath, new Set());
1226
+ }
1227
+ if (unchanged) continue;
1228
+ if (isInitialRead && skipInitialParse) {
1229
+ try {
1230
+ shouldParseFile(filePath, stat, { isInitialRead, previousState, skipInitialParse: true });
1231
+ } catch (_) {
1232
+ // Baseline setup must remain non-blocking; the next file change will retry.
1233
+ }
1234
+ fileStates.set(filePath, {
1235
+ dev: stat.dev,
1236
+ ino: stat.ino,
1237
+ size: stat.size,
1238
+ mtimeMs: stat.mtimeMs,
1239
+ ctimeMs: stat.ctimeMs
1240
+ });
1241
+ continue;
1242
+ }
1243
+ if (!shouldParseFile(filePath, stat, { isInitialRead, previousState, skipInitialParse: false })) continue;
1244
+
1245
+ let parsed;
1246
+ try { parsed = parseFile(filePath) || []; } catch (_) { continue; }
1247
+ fileStates.set(filePath, {
1248
+ dev: stat.dev,
1249
+ ino: stat.ino,
1250
+ size: stat.size,
1251
+ mtimeMs: stat.mtimeMs,
1252
+ ctimeMs: stat.ctimeMs
1253
+ });
1254
+ const seen = seenByFile.get(filePath) || new Set();
1255
+ seenByFile.set(filePath, seen);
1256
+ for (const raw of parsed) {
1257
+ const event = normalizeEvent(raw, filePath);
1258
+ if (!event || !event.id || !hasUsage(event.tokens)) continue;
1259
+ if (seen.has(event.id)) continue;
1260
+ seen.add(event.id);
1261
+ if (initialized) events.push(event);
1262
+ }
1263
+ }
1264
+ initialized = true;
1265
+ return events;
1266
+ };
1267
+
1268
+ return {
1269
+ initialize() { read(); },
1270
+ readNewEvents: read,
1271
+ read,
1272
+ reset() { seenByFile = new Map(); fileStates = new Map(); initialized = false; },
1273
+ close() { seenByFile.clear(); fileStates.clear(); initialized = false; }
1274
+ };
1275
+ }
1276
+
1277
+ module.exports = {
1278
+ hasUsage,
1279
+ normalizeCost,
1280
+ normalizeUsage,
1281
+ number,
1282
+ readJsonLines,
1283
+ readFileRange,
1284
+ createSelectiveJsonLineParser,
1285
+ usageFieldPaths,
1286
+ visitJsonLinesForward,
1287
+ visitJsonLinesReverse,
1288
+ subtractUsage,
1289
+ walkFiles,
1290
+ createIncrementalJsonlCursor,
1291
+ createScannedFileCursor
1292
+ };