@loicngr/kobo 1.6.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/AGENTS.md +6 -1
  2. package/README.md +29 -16
  3. package/dist/server/db/index.js +17 -0
  4. package/dist/server/db/migrations.js +10 -0
  5. package/dist/server/db/schema.js +2 -1
  6. package/dist/server/index.js +24 -3
  7. package/dist/server/middleware/migration-guard.js +15 -0
  8. package/dist/server/routes/dev-server.js +3 -2
  9. package/dist/server/routes/engines.js +9 -0
  10. package/dist/server/routes/migration.js +5 -0
  11. package/dist/server/routes/workspaces.js +35 -11
  12. package/dist/server/services/agent/engines/claude-code/args-builder.js +22 -0
  13. package/dist/server/services/agent/engines/claude-code/capabilities.js +17 -0
  14. package/dist/server/services/agent/engines/claude-code/engine.js +163 -0
  15. package/dist/server/services/agent/engines/claude-code/mcp-config.js +23 -0
  16. package/dist/server/services/agent/engines/claude-code/stream-parser.js +224 -0
  17. package/dist/server/services/agent/engines/registry.js +21 -0
  18. package/dist/server/services/agent/engines/types.js +18 -0
  19. package/dist/server/services/agent/event-router.js +4 -0
  20. package/dist/server/services/agent/orchestrator.js +582 -0
  21. package/dist/server/services/agent/session-controller.js +79 -0
  22. package/dist/server/services/content-migration-service.js +155 -0
  23. package/dist/server/services/db-backup-service.js +15 -0
  24. package/dist/server/services/websocket-service.js +81 -50
  25. package/dist/server/services/workspace-service.js +11 -5
  26. package/dist/server/utils/paths.js +1 -1
  27. package/dist/shared/models.js +50 -0
  28. package/package.json +1 -1
  29. package/src/client/dist/spa/assets/ActivityFeed-DYtAK49y.js +7 -0
  30. package/src/client/dist/spa/assets/ActivityFeed-DiwnrdKX.css +1 -0
  31. package/src/client/dist/spa/assets/ClosePopup-DqhgFbQo.js +1 -0
  32. package/src/client/dist/spa/assets/CreatePage-DENfwzPL.js +2 -0
  33. package/src/client/dist/spa/assets/CreatePage-yu2IH7GW.css +1 -0
  34. package/src/client/dist/spa/assets/DiffViewer-C6q11kmw.js +2 -0
  35. package/src/client/dist/spa/assets/HealthPage-Cjc79NaA.js +1 -0
  36. package/src/client/dist/spa/assets/{MainLayout-_oPM07ln.js → MainLayout-CFbMw65L.js} +17 -17
  37. package/src/client/dist/spa/assets/QBadge-BUkmTO0P.js +1 -0
  38. package/src/client/dist/spa/assets/QBtn-p1aZtrJH.js +1 -0
  39. package/src/client/dist/spa/assets/QDialog-D42GLa1i.js +1 -0
  40. package/src/client/dist/spa/assets/QExpansionItem-5ekmpO-2.js +1 -0
  41. package/src/client/dist/spa/assets/{QSpinner-CliSLjf8.js → QIcon-B0-pH3Qs.js} +1 -1
  42. package/src/client/dist/spa/assets/QItemLabel-Czw5g0px.js +1 -0
  43. package/src/client/dist/spa/assets/QItemSection-GlMrLmz3.js +1 -0
  44. package/src/client/dist/spa/assets/QList-DNzlynsS.js +1 -0
  45. package/src/client/dist/spa/assets/QMenu-Q69oVX7b.js +1 -0
  46. package/src/client/dist/spa/assets/QPage-B09NY4Nf.js +1 -0
  47. package/src/client/dist/spa/assets/QScrollArea-L6wUiA20.js +1 -0
  48. package/src/client/dist/spa/assets/QSeparator-rkjCbX2M.js +1 -0
  49. package/src/client/dist/spa/assets/QSpace-PlDK6Fg3.js +1 -0
  50. package/src/client/dist/spa/assets/QSpinnerDots-By20ptst.js +1 -0
  51. package/src/client/dist/spa/assets/QTabPanels-Crs-ujNO.js +1 -0
  52. package/src/client/dist/spa/assets/QTooltip-Cg9E3Dvw.js +1 -0
  53. package/src/client/dist/spa/assets/SearchPage-Bf-iZnyE.js +1 -0
  54. package/src/client/dist/spa/assets/SettingsPage-BdcH3BSs.js +1 -0
  55. package/src/client/dist/spa/assets/TouchPan-DFx22dM3.js +1 -0
  56. package/src/client/dist/spa/assets/WorkspacePage-DPGiH02q.css +1 -0
  57. package/src/client/dist/spa/assets/WorkspacePage-UUE0pPCR.js +4 -0
  58. package/src/client/dist/spa/assets/{cssMode-DMX8jq8u.js → cssMode-BYtqFZtm.js} +1 -1
  59. package/src/client/dist/spa/assets/{editor.api-DirOkGGg.js → editor.api-D6ZaO4A_.js} +1 -1
  60. package/src/client/dist/spa/assets/{editor.main-DC4ezIu0.js → editor.main-Cc_RDKsq.js} +3 -3
  61. package/src/client/dist/spa/assets/format-uvONOeL4.js +1 -0
  62. package/src/client/dist/spa/assets/{formatters-BzaS4w0I.js → formatters-DiJ12fKd.js} +1 -1
  63. package/src/client/dist/spa/assets/{freemarker2-DI9xJfj0.js → freemarker2-CBm--bBd.js} +1 -1
  64. package/src/client/dist/spa/assets/{handlebars-B9F-pScn.js → handlebars-whX2mkV5.js} +1 -1
  65. package/src/client/dist/spa/assets/{html-DTe2v8Q8.js → html-D7ga_o6c.js} +1 -1
  66. package/src/client/dist/spa/assets/{htmlMode-F_XLjWfJ.js → htmlMode-BXImjcsv.js} +1 -1
  67. package/src/client/dist/spa/assets/i18n-BxLBrD1J.js +1 -0
  68. package/src/client/dist/spa/assets/index-D997aY4Y.js +2 -0
  69. package/src/client/dist/spa/assets/{javascript-B9xJRPC6.js → javascript-BwmzNMn5.js} +1 -1
  70. package/src/client/dist/spa/assets/{jsonMode-DTZ6j6UO.js → jsonMode-CN5Z5bK_.js} +1 -1
  71. package/src/client/dist/spa/assets/{liquid-BjU5MtW6.js → liquid-CzMNAPor.js} +1 -1
  72. package/src/client/dist/spa/assets/{marked.esm-DCmk6NO8.js → marked.esm-DW0ulF0a.js} +1 -1
  73. package/src/client/dist/spa/assets/{mdx-BMUpG7Be.js → mdx-DC_P05Da.js} +1 -1
  74. package/src/client/dist/spa/assets/models-DMQoi09X.js +1 -0
  75. package/src/client/dist/spa/assets/{monaco.contribution-D7JUf8DP.js → monaco.contribution-BsBaFOOD.js} +2 -2
  76. package/src/client/dist/spa/assets/private.use-form-D1RuEt2P.js +1 -0
  77. package/src/client/dist/spa/assets/{python-Dz0D4uSk.js → python-9DTZ8C3K.js} +1 -1
  78. package/src/client/dist/spa/assets/{razor-D7CFxuwR.js → razor-B1LfM20o.js} +1 -1
  79. package/src/client/dist/spa/assets/scroll-Dh2g7BwR.js +1 -0
  80. package/src/client/dist/spa/assets/touch-D_A29lik.js +1 -0
  81. package/src/client/dist/spa/assets/{tsMode-DjscaxpS.js → tsMode-DI2bWo8r.js} +1 -1
  82. package/src/client/dist/spa/assets/{typescript-DozCWZl2.js → typescript-BZ9QJ2_N.js} +1 -1
  83. package/src/client/dist/spa/assets/use-id-CeduaJbU.js +1 -0
  84. package/src/client/dist/spa/assets/use-portal-mhLq4Rqk.js +1 -0
  85. package/src/client/dist/spa/assets/use-quasar-BBrzedjR.js +1 -0
  86. package/src/client/dist/spa/assets/{xml-DFOJMT39.js → xml-D6qm6rp0.js} +1 -1
  87. package/src/client/dist/spa/assets/{yaml-yEefnsXm.js → yaml-D2dUr_wY.js} +1 -1
  88. package/src/client/dist/spa/index.html +11 -14
  89. package/src/mcp-server/README.md +1 -1
  90. package/dist/server/services/agent-manager.js +0 -621
  91. package/src/client/dist/spa/assets/ActivityFeed-0GR1zPoc.js +0 -10
  92. package/src/client/dist/spa/assets/ActivityFeed-CfsKExt9.css +0 -1
  93. package/src/client/dist/spa/assets/ClosePopup-CdSn7HO8.js +0 -1
  94. package/src/client/dist/spa/assets/CreatePage-dMi4xVYN.css +0 -1
  95. package/src/client/dist/spa/assets/CreatePage-je_7dC5I.js +0 -2
  96. package/src/client/dist/spa/assets/DiffViewer-DREYX-8k.js +0 -2
  97. package/src/client/dist/spa/assets/HealthPage-Do8QZdxw.js +0 -1
  98. package/src/client/dist/spa/assets/QBadge-Bvh-hQ8K.js +0 -1
  99. package/src/client/dist/spa/assets/QBtn-BsD8vrWq.js +0 -1
  100. package/src/client/dist/spa/assets/QDialog-CkbLS1If.js +0 -1
  101. package/src/client/dist/spa/assets/QExpansionItem-UgkE560c.js +0 -1
  102. package/src/client/dist/spa/assets/QList-D80ms7bw.js +0 -1
  103. package/src/client/dist/spa/assets/QMenu-DU-wiY_A.js +0 -1
  104. package/src/client/dist/spa/assets/QPage-BKY2-sf-.js +0 -1
  105. package/src/client/dist/spa/assets/QSpace-C5Ebr0vq.js +0 -1
  106. package/src/client/dist/spa/assets/QSpinnerDots-Dp12eHrB.js +0 -1
  107. package/src/client/dist/spa/assets/QTabPanels-C7lWp1yU.js +0 -1
  108. package/src/client/dist/spa/assets/QToggle-B0HvuNEg.js +0 -1
  109. package/src/client/dist/spa/assets/QTooltip-kLXuUa_m.js +0 -1
  110. package/src/client/dist/spa/assets/SearchPage-CCfyqBKh.js +0 -1
  111. package/src/client/dist/spa/assets/SettingsPage-CmyIsV-S.js +0 -1
  112. package/src/client/dist/spa/assets/TouchPan-CVMnGs0y.js +0 -1
  113. package/src/client/dist/spa/assets/WorkspacePage-CWRMLYs-.css +0 -1
  114. package/src/client/dist/spa/assets/WorkspacePage-Cl7YrG51.js +0 -4
  115. package/src/client/dist/spa/assets/focus-manager-DYbz9jFW.js +0 -1
  116. package/src/client/dist/spa/assets/format-Cyg8IgRi.js +0 -1
  117. package/src/client/dist/spa/assets/i18n-B13zBh1H.js +0 -1
  118. package/src/client/dist/spa/assets/i18n-CCWLBc0p.js +0 -1
  119. package/src/client/dist/spa/assets/index-DoNZ_5QK.js +0 -5
  120. package/src/client/dist/spa/assets/models-B8fzv7K4.js +0 -1
  121. package/src/client/dist/spa/assets/pinia-C3JsrLkB.js +0 -1
  122. package/src/client/dist/spa/assets/private.use-form-BhKyDtO7.js +0 -1
  123. package/src/client/dist/spa/assets/scroll-CLibRGI-.js +0 -1
  124. package/src/client/dist/spa/assets/settings-B69lIVX0.js +0 -1
  125. package/src/client/dist/spa/assets/touch-ChrvzrnI.js +0 -1
  126. package/src/client/dist/spa/assets/use-dark-DnuCB6tC.js +0 -1
  127. package/src/client/dist/spa/assets/use-quasar-DBoizHBW.js +0 -1
  128. /package/src/client/dist/spa/assets/{_plugin-vue_export-helper-Cxt1D8wE.js → _plugin-vue_export-helper-CEhRWsKN.js} +0 -0
  129. /package/src/client/dist/spa/assets/{abap-CFuyUYKP.js → abap-DiwvWnMr.js} +0 -0
  130. /package/src/client/dist/spa/assets/{apex-Ctq_xcrv.js → apex-CmtZjKlf.js} +0 -0
  131. /package/src/client/dist/spa/assets/{azcli-BBQSVn-C.js → azcli-DL2My_i-.js} +0 -0
  132. /package/src/client/dist/spa/assets/{bat-DbnqAfvr.js → bat-B-nC98wG.js} +0 -0
  133. /package/src/client/dist/spa/assets/{bicep-BtDlIXop.js → bicep-Ju5MwOgh.js} +0 -0
  134. /package/src/client/dist/spa/assets/{cameligo-BLeJgKTj.js → cameligo-8Eu1TyBr.js} +0 -0
  135. /package/src/client/dist/spa/assets/{clojure-aZUQIUKP.js → clojure-u-RpMkH3.js} +0 -0
  136. /package/src/client/dist/spa/assets/{coffee-Secadq9U.js → coffee-CdA7bbTe.js} +0 -0
  137. /package/src/client/dist/spa/assets/{cpp-JicRPTRv.js → cpp-CzNFP8ks.js} +0 -0
  138. /package/src/client/dist/spa/assets/{csharp-C7NSOZyj.js → csharp-j1LThmcE.js} +0 -0
  139. /package/src/client/dist/spa/assets/{csp-CIje7830.js → csp-CLRC61y6.js} +0 -0
  140. /package/src/client/dist/spa/assets/{css-G0bm1q_M.js → css-r6rC_7P2.js} +0 -0
  141. /package/src/client/dist/spa/assets/{cypher-CldD5D0u.js → cypher-CW08XVUh.js} +0 -0
  142. /package/src/client/dist/spa/assets/{dart-DIK3l8YT.js → dart-Cs9aL5T_.js} +0 -0
  143. /package/src/client/dist/spa/assets/{dockerfile-czxaGh2L.js → dockerfile-BWM0M184.js} +0 -0
  144. /package/src/client/dist/spa/assets/{ecl-BqdYhwmw.js → ecl-MJJuer5P.js} +0 -0
  145. /package/src/client/dist/spa/assets/{elixir-m52LePTW.js → elixir-D2AIuXqn.js} +0 -0
  146. /package/src/client/dist/spa/assets/{flow9-B5QJ9GvZ.js → flow9-B2H24giC.js} +0 -0
  147. /package/src/client/dist/spa/assets/{fsharp-B15czHsH.js → fsharp-CMk2OIJN.js} +0 -0
  148. /package/src/client/dist/spa/assets/{go-BkoQxDo1.js → go-BrMkuJg0.js} +0 -0
  149. /package/src/client/dist/spa/assets/{graphql-BnI6uRa_.js → graphql-PSR1UKGv.js} +0 -0
  150. /package/src/client/dist/spa/assets/{hcl-CAwwENT7.js → hcl-DAQrbDOW.js} +0 -0
  151. /package/src/client/dist/spa/assets/{ini-BHM5zh1H.js → ini-0TG5BxW0.js} +0 -0
  152. /package/src/client/dist/spa/assets/{java-B5i95QvQ.js → java-rgorz17v.js} +0 -0
  153. /package/src/client/dist/spa/assets/{julia-DPDm885q.js → julia-C8VMdHm8.js} +0 -0
  154. /package/src/client/dist/spa/assets/{kotlin-qoccd5BP.js → kotlin-CllWo3gX.js} +0 -0
  155. /package/src/client/dist/spa/assets/{less-B6RU166D.js → less-Cgca25AP.js} +0 -0
  156. /package/src/client/dist/spa/assets/{lexon-YfUeoL1V.js → lexon-D0GHdBaw.js} +0 -0
  157. /package/src/client/dist/spa/assets/{lua-BIUI5y9b.js → lua-DmRsNG-P.js} +0 -0
  158. /package/src/client/dist/spa/assets/{m3-D5SAbSdU.js → m3-BgL5dNKT.js} +0 -0
  159. /package/src/client/dist/spa/assets/{markdown-CVJLwHzJ.js → markdown-BuJfycGS.js} +0 -0
  160. /package/src/client/dist/spa/assets/{mips-R-FZ3zOR.js → mips-C9m_93PR.js} +0 -0
  161. /package/src/client/dist/spa/assets/{msdax-Blveyl9r.js → msdax-CpFHC9OI.js} +0 -0
  162. /package/src/client/dist/spa/assets/{mysql-D4mY1AFx.js → mysql-qFvltsqN.js} +0 -0
  163. /package/src/client/dist/spa/assets/{objective-c-BmXrLr4h.js → objective-c-Bnmr858J.js} +0 -0
  164. /package/src/client/dist/spa/assets/{pascal-yxckoyvV.js → pascal-WP0_D5AO.js} +0 -0
  165. /package/src/client/dist/spa/assets/{pascaligo-Q5JCwXMI.js → pascaligo-Blom4Rij.js} +0 -0
  166. /package/src/client/dist/spa/assets/{perl-BF1Rrs5h.js → perl-B-vk8g64.js} +0 -0
  167. /package/src/client/dist/spa/assets/{pgsql-CnYB97wm.js → pgsql-Cgvz6v67.js} +0 -0
  168. /package/src/client/dist/spa/assets/{php-CdDfQfSg.js → php-8a3Lrw9m.js} +0 -0
  169. /package/src/client/dist/spa/assets/{pla-whj-d71F.js → pla-DuFqEZ8V.js} +0 -0
  170. /package/src/client/dist/spa/assets/{postiats-ClfLr4I-.js → postiats-DkLtSgkp.js} +0 -0
  171. /package/src/client/dist/spa/assets/{powerquery-iRaBhuuk.js → powerquery-BJ1aNepW.js} +0 -0
  172. /package/src/client/dist/spa/assets/{powershell-DjiEt5xK.js → powershell-rE98k687.js} +0 -0
  173. /package/src/client/dist/spa/assets/{protobuf-B6dcIEUr.js → protobuf-CUheFacr.js} +0 -0
  174. /package/src/client/dist/spa/assets/{pug-DtmHnjM9.js → pug-LDcAMD8w.js} +0 -0
  175. /package/src/client/dist/spa/assets/{qsharp-CELCyd79.js → qsharp-DUKSQoR1.js} +0 -0
  176. /package/src/client/dist/spa/assets/{r-ZpJXWV-o.js → r-D-QApv87.js} +0 -0
  177. /package/src/client/dist/spa/assets/{rate-limit-labels-dCPVjS61.js → rate-limit-labels-BvYERsho.js} +0 -0
  178. /package/src/client/dist/spa/assets/{redis-BiHSNkAl.js → redis-SXdDyWR9.js} +0 -0
  179. /package/src/client/dist/spa/assets/{redshift-DzuwYCHP.js → redshift-Y6lsCryn.js} +0 -0
  180. /package/src/client/dist/spa/assets/{restructuredtext-YOT94bbS.js → restructuredtext-edObr9a8.js} +0 -0
  181. /package/src/client/dist/spa/assets/{ruby-BfiHr6Uu.js → ruby-CNnUfF-8.js} +0 -0
  182. /package/src/client/dist/spa/assets/{rust-JZ-uOoYM.js → rust-IHUZWzBr.js} +0 -0
  183. /package/src/client/dist/spa/assets/{sb-CBglP1-t.js → sb-DrUvY44N.js} +0 -0
  184. /package/src/client/dist/spa/assets/{scala-C9l41paw.js → scala-B4hbXGLM.js} +0 -0
  185. /package/src/client/dist/spa/assets/{scheme-B-InQ6hy.js → scheme-BGrd12j3.js} +0 -0
  186. /package/src/client/dist/spa/assets/{scss-v6OmJRN9.js → scss-x5G1ES4U.js} +0 -0
  187. /package/src/client/dist/spa/assets/{shell-Dyp6iwB6.js → shell-DOehe2Y8.js} +0 -0
  188. /package/src/client/dist/spa/assets/{solidity-D5epNWue.js → solidity-BeRvcwWV.js} +0 -0
  189. /package/src/client/dist/spa/assets/{sophia-Eva-79sB.js → sophia-DZbkUNjy.js} +0 -0
  190. /package/src/client/dist/spa/assets/{sparql-gvALLO1w.js → sparql-B7_oi5-h.js} +0 -0
  191. /package/src/client/dist/spa/assets/{sql-COdamZYI.js → sql-CTlsFWVE.js} +0 -0
  192. /package/src/client/dist/spa/assets/{st-eMoImIwE.js → st-DJVEJdPE.js} +0 -0
  193. /package/src/client/dist/spa/assets/{swift-7R_T9RYH.js → swift-CwhT3fYa.js} +0 -0
  194. /package/src/client/dist/spa/assets/{symbols-CAg-nBkV.js → symbols-DCYodwb2.js} +0 -0
  195. /package/src/client/dist/spa/assets/{systemverilog-1pCEfaHU.js → systemverilog-BQN63pkN.js} +0 -0
  196. /package/src/client/dist/spa/assets/{tcl-B_KgnhfE.js → tcl-DqwfpskA.js} +0 -0
  197. /package/src/client/dist/spa/assets/{twig-CFZUJxb9.js → twig-BiyenUgc.js} +0 -0
  198. /package/src/client/dist/spa/assets/{typespec-B1ZgHlud.js → typespec-CWOJribt.js} +0 -0
  199. /package/src/client/dist/spa/assets/{vb-DKdun5tL.js → vb-Cq5F87m3.js} +0 -0
  200. /package/src/client/dist/spa/assets/{vue-i18n-eUDnMrPl.js → vue-i18n-CeG0hR0Z.js} +0 -0
  201. /package/src/client/dist/spa/assets/{wgsl-CzNaxTrn.js → wgsl-BAvW2lVr.js} +0 -0
@@ -0,0 +1,23 @@
1
+ import { existsSync, unlinkSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ const MCP_FILENAME = '.mcp.json';
4
+ export function writeMcpConfig(workingDir, servers) {
5
+ const filePath = join(workingDir, MCP_FILENAME);
6
+ const mcpServers = {};
7
+ for (const s of servers) {
8
+ mcpServers[s.name] = { command: s.command, args: s.args, env: s.env };
9
+ }
10
+ writeFileSync(filePath, JSON.stringify({ mcpServers }, null, 2));
11
+ return filePath;
12
+ }
13
+ export function cleanupMcpConfig(workingDir) {
14
+ const filePath = join(workingDir, MCP_FILENAME);
15
+ if (existsSync(filePath)) {
16
+ try {
17
+ unlinkSync(filePath);
18
+ }
19
+ catch {
20
+ // Best-effort cleanup
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,224 @@
1
+ function normalizeResetsAt(raw) {
2
+ if (typeof raw === 'string' && raw.length > 0)
3
+ return raw;
4
+ if (typeof raw === 'number' && Number.isFinite(raw))
5
+ return new Date(raw * 1000).toISOString();
6
+ return undefined;
7
+ }
8
+ function extractUsedPct(source) {
9
+ const raw = (source.utilization ?? source.used_percent ?? source.percent_used ?? source.usedPct);
10
+ if (typeof raw === 'number' && Number.isFinite(raw))
11
+ return raw <= 1 ? raw * 100 : raw;
12
+ const used = source.used ?? source.current ?? source.spent;
13
+ const limit = source.limit ?? source.max ?? source.allowed;
14
+ if (typeof used === 'number' && typeof limit === 'number' && limit > 0)
15
+ return (used / limit) * 100;
16
+ return null;
17
+ }
18
+ function makeBucket(id, source) {
19
+ const usedPct = extractUsedPct(source) ?? source.__fallbackPct ?? null;
20
+ if (usedPct === null)
21
+ return null;
22
+ const resetsAt = normalizeResetsAt(source.resets_at ?? source.reset_at ?? source.resetsAt ?? source.resetAt);
23
+ const label = (typeof source.label === 'string' && source.label) || undefined;
24
+ const used = source.used ?? source.current ?? source.spent;
25
+ const limit = source.limit ?? source.max ?? source.allowed;
26
+ const details = used !== undefined && limit !== undefined ? `${String(used)} / ${String(limit)}` : undefined;
27
+ return { id, label, usedPct: Math.max(0, Math.min(100, usedPct)), resetsAt, details };
28
+ }
29
+ function normalizeRateLimitInfo(info) {
30
+ const buckets = [];
31
+ if (typeof info.rateLimitType === 'string') {
32
+ const b = makeBucket(info.rateLimitType, { ...info, __fallbackPct: 0 });
33
+ if (b)
34
+ buckets.push(b);
35
+ }
36
+ if (Array.isArray(info.buckets)) {
37
+ for (const entry of info.buckets) {
38
+ if (!entry || typeof entry !== 'object')
39
+ continue;
40
+ const obj = entry;
41
+ const id = (typeof obj.id === 'string' && obj.id) ||
42
+ (typeof obj.name === 'string' && obj.name) ||
43
+ (typeof obj.rateLimitType === 'string' && obj.rateLimitType) ||
44
+ 'unknown';
45
+ const b = makeBucket(id, obj);
46
+ if (b)
47
+ buckets.push(b);
48
+ }
49
+ }
50
+ return { buckets };
51
+ }
52
+ export function createParserState() {
53
+ return { sessionStartedEmitted: false, openMessages: new Map() };
54
+ }
55
+ export function parseClaudeLine(line, state) {
56
+ const trimmed = line.trim();
57
+ if (!trimmed)
58
+ return { events: [], state };
59
+ // The marker can appear as a raw stdout line OR inside an assistant text block.
60
+ // We detect it in the raw line first so even unparseable lines that contain it
61
+ // still emit the signal. The assistant-branch handling below catches the
62
+ // structured case.
63
+ const markerDetected = trimmed.includes('[BRAINSTORM_COMPLETE]');
64
+ let parsed;
65
+ try {
66
+ parsed = JSON.parse(trimmed);
67
+ }
68
+ catch {
69
+ const events = [{ kind: 'message:raw', content: line }];
70
+ if (markerDetected)
71
+ events.push({ kind: 'session:brainstorm-complete' });
72
+ return { events, state };
73
+ }
74
+ const events = [];
75
+ const type = parsed.type;
76
+ const subtype = parsed.subtype;
77
+ const sessionId = typeof parsed.session_id === 'string' ? parsed.session_id : undefined;
78
+ if (type === 'system') {
79
+ if (subtype === 'compact' || subtype === 'compact_boundary') {
80
+ events.push({ kind: 'session:compacted' });
81
+ return { events, state };
82
+ }
83
+ if (subtype === 'rate_limit_event') {
84
+ const info = parsed.rate_limit_info;
85
+ if (info && typeof info === 'object') {
86
+ events.push({ kind: 'rate_limit', info: normalizeRateLimitInfo(info) });
87
+ }
88
+ return { events, state };
89
+ }
90
+ if (subtype === 'task_started' || subtype === 'task_progress' || subtype === 'task_notification') {
91
+ const toolCallId = typeof parsed.tool_use_id === 'string' ? parsed.tool_use_id : undefined;
92
+ if (toolCallId) {
93
+ const usage = parsed.usage;
94
+ const taskStatus = typeof parsed.status === 'string' ? parsed.status : undefined;
95
+ const isDone = subtype === 'task_notification' &&
96
+ taskStatus !== undefined &&
97
+ ['completed', 'stopped', 'failed', 'cancelled'].includes(taskStatus);
98
+ events.push({
99
+ kind: 'subagent:progress',
100
+ toolCallId,
101
+ status: isDone ? 'done' : 'running',
102
+ description: typeof parsed.description === 'string' ? parsed.description : undefined,
103
+ taskType: typeof parsed.task_type === 'string' ? parsed.task_type : undefined,
104
+ lastToolName: typeof parsed.last_tool_name === 'string' ? parsed.last_tool_name : undefined,
105
+ totalTokens: typeof usage?.total_tokens === 'number' ? usage.total_tokens : undefined,
106
+ toolUses: typeof usage?.tool_uses === 'number' ? usage.tool_uses : undefined,
107
+ durationMs: typeof usage?.duration_ms === 'number' ? usage.duration_ms : undefined,
108
+ });
109
+ }
110
+ return { events, state };
111
+ }
112
+ }
113
+ if (type === 'system' && subtype === 'init') {
114
+ if (sessionId && (!state.sessionStartedEmitted || state.sessionId !== sessionId)) {
115
+ events.push({
116
+ kind: 'session:started',
117
+ engineSessionId: sessionId,
118
+ model: typeof parsed.model === 'string' ? parsed.model : undefined,
119
+ });
120
+ state.sessionStartedEmitted = true;
121
+ state.sessionId = sessionId;
122
+ }
123
+ if (Array.isArray(parsed.slash_commands) && parsed.slash_commands.length > 0) {
124
+ events.push({ kind: 'skills:discovered', skills: parsed.slash_commands });
125
+ }
126
+ return { events, state };
127
+ }
128
+ if (type === 'assistant') {
129
+ const message = parsed.message;
130
+ const messageId = typeof message?.id === 'string' ? message.id : 'unknown';
131
+ const content = Array.isArray(message?.content) ? message?.content : [];
132
+ // A new messageId arriving means any previously-open message is done.
133
+ // Claude CLI's stream-json output doesn't always carry an explicit
134
+ // `stop_reason` or `message_stop` on the last chunk; some runs finish
135
+ // implicitly when the next turn begins. Close stale openMessages here
136
+ // so the UI's streaming spinner doesn't hang forever.
137
+ for (const openId of Array.from(state.openMessages.keys())) {
138
+ if (openId !== messageId) {
139
+ events.push({ kind: 'message:end', messageId: openId });
140
+ state.openMessages.delete(openId);
141
+ }
142
+ }
143
+ if (!state.openMessages.has(messageId)) {
144
+ state.openMessages.set(messageId, { sawText: false });
145
+ }
146
+ const msgState = state.openMessages.get(messageId);
147
+ for (const block of content) {
148
+ const blockType = block.type;
149
+ if (blockType === 'text' && typeof block.text === 'string') {
150
+ events.push({ kind: 'message:text', messageId, text: block.text, streaming: true });
151
+ msgState.sawText = true;
152
+ }
153
+ if (blockType === 'tool_use') {
154
+ events.push({
155
+ kind: 'tool:call',
156
+ messageId,
157
+ toolCallId: typeof block.id === 'string' ? block.id : 'unknown',
158
+ name: typeof block.name === 'string' ? block.name : 'unknown',
159
+ input: block.input ?? {},
160
+ });
161
+ }
162
+ if (blockType === 'thinking') {
163
+ events.push({
164
+ kind: 'message:thinking',
165
+ messageId,
166
+ text: typeof block.thinking === 'string' ? block.thinking : '',
167
+ });
168
+ }
169
+ if (blockType === 'text' &&
170
+ typeof block.text === 'string' &&
171
+ block.text.includes('[BRAINSTORM_COMPLETE]')) {
172
+ events.push({ kind: 'session:brainstorm-complete' });
173
+ }
174
+ }
175
+ // Claude CLI sends many intermediate deltas for the same message; most of
176
+ // them carry `stop_reason: null`. Only a truly terminal event has either
177
+ // `message_stop: true` at the root, or a non-null `stop_reason`. Checking
178
+ // `!== undefined` would spuriously emit message:end on every delta.
179
+ const stopReason = message?.stop_reason;
180
+ const isStop = parsed.message_stop === true || (stopReason !== undefined && stopReason !== null);
181
+ if (isStop) {
182
+ events.push({ kind: 'message:end', messageId });
183
+ state.openMessages.delete(messageId);
184
+ }
185
+ return { events, state };
186
+ }
187
+ if (type === 'user') {
188
+ const message = parsed.message;
189
+ const content = Array.isArray(message?.content) ? message?.content : [];
190
+ for (const block of content) {
191
+ if (block.type === 'tool_result') {
192
+ events.push({
193
+ kind: 'tool:result',
194
+ toolCallId: typeof block.tool_use_id === 'string' ? block.tool_use_id : 'unknown',
195
+ output: block.content ?? null,
196
+ isError: block.is_error === true,
197
+ });
198
+ }
199
+ }
200
+ return { events, state };
201
+ }
202
+ if (type === 'result') {
203
+ // Terminal event — close any message still considered "streaming".
204
+ for (const openId of Array.from(state.openMessages.keys())) {
205
+ events.push({ kind: 'message:end', messageId: openId });
206
+ state.openMessages.delete(openId);
207
+ }
208
+ const usage = parsed.usage;
209
+ if (usage) {
210
+ events.push({
211
+ kind: 'usage',
212
+ inputTokens: Number(usage.input_tokens ?? 0),
213
+ outputTokens: Number(usage.output_tokens ?? 0),
214
+ cacheRead: typeof usage.cache_read_input_tokens === 'number' ? usage.cache_read_input_tokens : undefined,
215
+ cacheWrite: typeof usage.cache_creation_input_tokens === 'number'
216
+ ? usage.cache_creation_input_tokens
217
+ : undefined,
218
+ costUsd: typeof parsed.cost_usd === 'number' ? parsed.cost_usd : undefined,
219
+ });
220
+ }
221
+ return { events, state };
222
+ }
223
+ return { events, state };
224
+ }
@@ -0,0 +1,21 @@
1
+ import { createClaudeCodeEngine } from './claude-code/engine.js';
2
+ const ENGINES = {
3
+ 'claude-code': createClaudeCodeEngine(),
4
+ };
5
+ export function listEngines() {
6
+ return Object.values(ENGINES);
7
+ }
8
+ export function resolveEngine(id) {
9
+ const engine = ENGINES[id];
10
+ if (!engine)
11
+ throw new Error(`Unknown agent engine '${id}'`);
12
+ return engine;
13
+ }
14
+ /**
15
+ * Test-only seam. Replaces or adds an engine at runtime. Do not use in
16
+ * production — the static `ENGINES` map is the source of truth; this helper
17
+ * exists only so unit tests can inject fakes without wiring a DI container.
18
+ */
19
+ export function _registerEngineForTest(engine) {
20
+ ENGINES[engine.id] = engine;
21
+ }
@@ -0,0 +1,18 @@
1
+ /** Every AgentEvent kind, as a const for exhaustive iteration in tests. */
2
+ export const ALL_AGENT_EVENT_KINDS = [
3
+ 'session:started',
4
+ 'session:ended',
5
+ 'session:compacted',
6
+ 'session:brainstorm-complete',
7
+ 'message:text',
8
+ 'message:thinking',
9
+ 'message:end',
10
+ 'message:raw',
11
+ 'tool:call',
12
+ 'tool:result',
13
+ 'subagent:progress',
14
+ 'skills:discovered',
15
+ 'usage',
16
+ 'rate_limit',
17
+ 'error',
18
+ ];
@@ -0,0 +1,4 @@
1
+ import { emit } from '../websocket-service.js';
2
+ export function routeEvent(workspaceId, agentSessionId, event) {
3
+ emit(workspaceId, 'agent:event', event, agentSessionId);
4
+ }