@loicngr/kobo 1.5.7 → 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 (204) hide show
  1. package/AGENTS.md +6 -1
  2. package/README.md +30 -15
  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 +138 -10
  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/git-ops.js +78 -9
  27. package/dist/server/utils/paths.js +1 -1
  28. package/dist/shared/models.js +50 -0
  29. package/package.json +1 -1
  30. package/src/client/dist/spa/assets/ActivityFeed-DYtAK49y.js +7 -0
  31. package/src/client/dist/spa/assets/ActivityFeed-DiwnrdKX.css +1 -0
  32. package/src/client/dist/spa/assets/ClosePopup-DqhgFbQo.js +1 -0
  33. package/src/client/dist/spa/assets/CreatePage-DENfwzPL.js +2 -0
  34. package/src/client/dist/spa/assets/CreatePage-yu2IH7GW.css +1 -0
  35. package/src/client/dist/spa/assets/DiffViewer-C6q11kmw.js +2 -0
  36. package/src/client/dist/spa/assets/HealthPage-Cjc79NaA.js +1 -0
  37. package/src/client/dist/spa/assets/{MainLayout-rVleAIBi.css → MainLayout-B5poKEy_.css} +1 -1
  38. package/src/client/dist/spa/assets/MainLayout-CFbMw65L.js +37 -0
  39. package/src/client/dist/spa/assets/QBadge-BUkmTO0P.js +1 -0
  40. package/src/client/dist/spa/assets/QBtn-p1aZtrJH.js +1 -0
  41. package/src/client/dist/spa/assets/QDialog-D42GLa1i.js +1 -0
  42. package/src/client/dist/spa/assets/QExpansionItem-5ekmpO-2.js +1 -0
  43. package/src/client/dist/spa/assets/{QSpinner-CliSLjf8.js → QIcon-B0-pH3Qs.js} +1 -1
  44. package/src/client/dist/spa/assets/QItemLabel-Czw5g0px.js +1 -0
  45. package/src/client/dist/spa/assets/QItemSection-GlMrLmz3.js +1 -0
  46. package/src/client/dist/spa/assets/QList-DNzlynsS.js +1 -0
  47. package/src/client/dist/spa/assets/QMenu-Q69oVX7b.js +1 -0
  48. package/src/client/dist/spa/assets/QPage-B09NY4Nf.js +1 -0
  49. package/src/client/dist/spa/assets/QScrollArea-L6wUiA20.js +1 -0
  50. package/src/client/dist/spa/assets/QSeparator-rkjCbX2M.js +1 -0
  51. package/src/client/dist/spa/assets/QSpace-PlDK6Fg3.js +1 -0
  52. package/src/client/dist/spa/assets/QSpinnerDots-By20ptst.js +1 -0
  53. package/src/client/dist/spa/assets/QTabPanels-Crs-ujNO.js +1 -0
  54. package/src/client/dist/spa/assets/QTooltip-Cg9E3Dvw.js +1 -0
  55. package/src/client/dist/spa/assets/SearchPage-Bf-iZnyE.js +1 -0
  56. package/src/client/dist/spa/assets/SettingsPage-BdcH3BSs.js +1 -0
  57. package/src/client/dist/spa/assets/TouchPan-DFx22dM3.js +1 -0
  58. package/src/client/dist/spa/assets/WorkspacePage-DPGiH02q.css +1 -0
  59. package/src/client/dist/spa/assets/WorkspacePage-UUE0pPCR.js +4 -0
  60. package/src/client/dist/spa/assets/{cssMode-C9wGTDAD.js → cssMode-BYtqFZtm.js} +1 -1
  61. package/src/client/dist/spa/assets/{editor.api-K7V5sl05.js → editor.api-D6ZaO4A_.js} +1 -1
  62. package/src/client/dist/spa/assets/{editor.main-vZ6V2hrP.js → editor.main-Cc_RDKsq.js} +3 -3
  63. package/src/client/dist/spa/assets/format-uvONOeL4.js +1 -0
  64. package/src/client/dist/spa/assets/{formatters-BzaS4w0I.js → formatters-DiJ12fKd.js} +1 -1
  65. package/src/client/dist/spa/assets/{freemarker2-CRk6pTND.js → freemarker2-CBm--bBd.js} +1 -1
  66. package/src/client/dist/spa/assets/{handlebars-Cs3bFomb.js → handlebars-whX2mkV5.js} +1 -1
  67. package/src/client/dist/spa/assets/{html-BT4-1gwt.js → html-D7ga_o6c.js} +1 -1
  68. package/src/client/dist/spa/assets/{htmlMode-DZ9LYDVG.js → htmlMode-BXImjcsv.js} +1 -1
  69. package/src/client/dist/spa/assets/i18n-BxLBrD1J.js +1 -0
  70. package/src/client/dist/spa/assets/index-D997aY4Y.js +2 -0
  71. package/src/client/dist/spa/assets/{javascript-C0nTLIDg.js → javascript-BwmzNMn5.js} +1 -1
  72. package/src/client/dist/spa/assets/{jsonMode-LIrD4Pxq.js → jsonMode-CN5Z5bK_.js} +1 -1
  73. package/src/client/dist/spa/assets/{liquid-BbaUnvHA.js → liquid-CzMNAPor.js} +1 -1
  74. package/src/client/dist/spa/assets/{marked.esm-gIBce057.js → marked.esm-DW0ulF0a.js} +1 -1
  75. package/src/client/dist/spa/assets/{mdx-B6iNIRi2.js → mdx-DC_P05Da.js} +1 -1
  76. package/src/client/dist/spa/assets/models-DMQoi09X.js +1 -0
  77. package/src/client/dist/spa/assets/{monaco.contribution-CZ_PxrB6.js → monaco.contribution-BsBaFOOD.js} +2 -2
  78. package/src/client/dist/spa/assets/private.use-form-D1RuEt2P.js +1 -0
  79. package/src/client/dist/spa/assets/{python-C0uk6BYc.js → python-9DTZ8C3K.js} +1 -1
  80. package/src/client/dist/spa/assets/{razor-Bj__h6OQ.js → razor-B1LfM20o.js} +1 -1
  81. package/src/client/dist/spa/assets/scroll-Dh2g7BwR.js +1 -0
  82. package/src/client/dist/spa/assets/touch-D_A29lik.js +1 -0
  83. package/src/client/dist/spa/assets/{tsMode-ZSZcAFKU.js → tsMode-DI2bWo8r.js} +1 -1
  84. package/src/client/dist/spa/assets/{typescript-Bgw42jIH.js → typescript-BZ9QJ2_N.js} +1 -1
  85. package/src/client/dist/spa/assets/use-id-CeduaJbU.js +1 -0
  86. package/src/client/dist/spa/assets/use-portal-mhLq4Rqk.js +1 -0
  87. package/src/client/dist/spa/assets/use-quasar-BBrzedjR.js +1 -0
  88. package/src/client/dist/spa/assets/{xml-B9gVeW9V.js → xml-D6qm6rp0.js} +1 -1
  89. package/src/client/dist/spa/assets/{yaml-DUqEwwz-.js → yaml-D2dUr_wY.js} +1 -1
  90. package/src/client/dist/spa/index.html +11 -14
  91. package/src/mcp-server/README.md +1 -1
  92. package/dist/server/services/agent-manager.js +0 -621
  93. package/src/client/dist/spa/assets/ActivityFeed-CfsKExt9.css +0 -1
  94. package/src/client/dist/spa/assets/ActivityFeed-Dc1oLbwJ.js +0 -10
  95. package/src/client/dist/spa/assets/ClosePopup-CdSn7HO8.js +0 -1
  96. package/src/client/dist/spa/assets/CreatePage-BDKfkW-N.js +0 -2
  97. package/src/client/dist/spa/assets/CreatePage-dMi4xVYN.css +0 -1
  98. package/src/client/dist/spa/assets/DiffViewer-DZ9h2M2n.js +0 -2
  99. package/src/client/dist/spa/assets/HealthPage-BkqexlJb.js +0 -1
  100. package/src/client/dist/spa/assets/MainLayout-VxUBOt-P.js +0 -37
  101. package/src/client/dist/spa/assets/QBadge-Bvh-hQ8K.js +0 -1
  102. package/src/client/dist/spa/assets/QBtn-BsD8vrWq.js +0 -1
  103. package/src/client/dist/spa/assets/QDialog-CkbLS1If.js +0 -1
  104. package/src/client/dist/spa/assets/QExpansionItem-C735ptO9.js +0 -1
  105. package/src/client/dist/spa/assets/QItem-DfoP6eYj.js +0 -1
  106. package/src/client/dist/spa/assets/QList-D80ms7bw.js +0 -1
  107. package/src/client/dist/spa/assets/QMenu-DU-wiY_A.js +0 -1
  108. package/src/client/dist/spa/assets/QPage-BKY2-sf-.js +0 -1
  109. package/src/client/dist/spa/assets/QSpace-C5Ebr0vq.js +0 -1
  110. package/src/client/dist/spa/assets/QSpinnerDots-Dp12eHrB.js +0 -1
  111. package/src/client/dist/spa/assets/QTabPanels-DV1b1MQb.js +0 -1
  112. package/src/client/dist/spa/assets/QToggle-B0HvuNEg.js +0 -1
  113. package/src/client/dist/spa/assets/QTooltip-kLXuUa_m.js +0 -1
  114. package/src/client/dist/spa/assets/SearchPage-ZDAo7WgD.js +0 -1
  115. package/src/client/dist/spa/assets/SettingsPage-D89evCuo.js +0 -1
  116. package/src/client/dist/spa/assets/TouchPan-CVMnGs0y.js +0 -1
  117. package/src/client/dist/spa/assets/WorkspacePage-CWRMLYs-.css +0 -1
  118. package/src/client/dist/spa/assets/WorkspacePage-Ds5Dqxas.js +0 -4
  119. package/src/client/dist/spa/assets/focus-manager-DYbz9jFW.js +0 -1
  120. package/src/client/dist/spa/assets/format-Cyg8IgRi.js +0 -1
  121. package/src/client/dist/spa/assets/i18n-C0RbMxeL.js +0 -1
  122. package/src/client/dist/spa/assets/i18n-CkN9X6lQ.js +0 -1
  123. package/src/client/dist/spa/assets/index-Br4eMfSu.js +0 -5
  124. package/src/client/dist/spa/assets/models-C3h6lSte.js +0 -1
  125. package/src/client/dist/spa/assets/pinia-C3JsrLkB.js +0 -1
  126. package/src/client/dist/spa/assets/private.use-form-BhKyDtO7.js +0 -1
  127. package/src/client/dist/spa/assets/scroll-CLibRGI-.js +0 -1
  128. package/src/client/dist/spa/assets/settings-B69lIVX0.js +0 -1
  129. package/src/client/dist/spa/assets/touch-ChrvzrnI.js +0 -1
  130. package/src/client/dist/spa/assets/use-dark-DnuCB6tC.js +0 -1
  131. /package/src/client/dist/spa/assets/{_plugin-vue_export-helper-Cxt1D8wE.js → _plugin-vue_export-helper-CEhRWsKN.js} +0 -0
  132. /package/src/client/dist/spa/assets/{abap-CFuyUYKP.js → abap-DiwvWnMr.js} +0 -0
  133. /package/src/client/dist/spa/assets/{apex-Ctq_xcrv.js → apex-CmtZjKlf.js} +0 -0
  134. /package/src/client/dist/spa/assets/{azcli-BBQSVn-C.js → azcli-DL2My_i-.js} +0 -0
  135. /package/src/client/dist/spa/assets/{bat-DbnqAfvr.js → bat-B-nC98wG.js} +0 -0
  136. /package/src/client/dist/spa/assets/{bicep-BtDlIXop.js → bicep-Ju5MwOgh.js} +0 -0
  137. /package/src/client/dist/spa/assets/{cameligo-BLeJgKTj.js → cameligo-8Eu1TyBr.js} +0 -0
  138. /package/src/client/dist/spa/assets/{clojure-aZUQIUKP.js → clojure-u-RpMkH3.js} +0 -0
  139. /package/src/client/dist/spa/assets/{coffee-Secadq9U.js → coffee-CdA7bbTe.js} +0 -0
  140. /package/src/client/dist/spa/assets/{cpp-JicRPTRv.js → cpp-CzNFP8ks.js} +0 -0
  141. /package/src/client/dist/spa/assets/{csharp-C7NSOZyj.js → csharp-j1LThmcE.js} +0 -0
  142. /package/src/client/dist/spa/assets/{csp-CIje7830.js → csp-CLRC61y6.js} +0 -0
  143. /package/src/client/dist/spa/assets/{css-G0bm1q_M.js → css-r6rC_7P2.js} +0 -0
  144. /package/src/client/dist/spa/assets/{cypher-CldD5D0u.js → cypher-CW08XVUh.js} +0 -0
  145. /package/src/client/dist/spa/assets/{dart-DIK3l8YT.js → dart-Cs9aL5T_.js} +0 -0
  146. /package/src/client/dist/spa/assets/{dockerfile-czxaGh2L.js → dockerfile-BWM0M184.js} +0 -0
  147. /package/src/client/dist/spa/assets/{ecl-BqdYhwmw.js → ecl-MJJuer5P.js} +0 -0
  148. /package/src/client/dist/spa/assets/{elixir-m52LePTW.js → elixir-D2AIuXqn.js} +0 -0
  149. /package/src/client/dist/spa/assets/{flow9-B5QJ9GvZ.js → flow9-B2H24giC.js} +0 -0
  150. /package/src/client/dist/spa/assets/{fsharp-B15czHsH.js → fsharp-CMk2OIJN.js} +0 -0
  151. /package/src/client/dist/spa/assets/{go-BkoQxDo1.js → go-BrMkuJg0.js} +0 -0
  152. /package/src/client/dist/spa/assets/{graphql-BnI6uRa_.js → graphql-PSR1UKGv.js} +0 -0
  153. /package/src/client/dist/spa/assets/{hcl-CAwwENT7.js → hcl-DAQrbDOW.js} +0 -0
  154. /package/src/client/dist/spa/assets/{ini-BHM5zh1H.js → ini-0TG5BxW0.js} +0 -0
  155. /package/src/client/dist/spa/assets/{java-B5i95QvQ.js → java-rgorz17v.js} +0 -0
  156. /package/src/client/dist/spa/assets/{julia-DPDm885q.js → julia-C8VMdHm8.js} +0 -0
  157. /package/src/client/dist/spa/assets/{kotlin-qoccd5BP.js → kotlin-CllWo3gX.js} +0 -0
  158. /package/src/client/dist/spa/assets/{less-B6RU166D.js → less-Cgca25AP.js} +0 -0
  159. /package/src/client/dist/spa/assets/{lexon-YfUeoL1V.js → lexon-D0GHdBaw.js} +0 -0
  160. /package/src/client/dist/spa/assets/{lua-BIUI5y9b.js → lua-DmRsNG-P.js} +0 -0
  161. /package/src/client/dist/spa/assets/{m3-D5SAbSdU.js → m3-BgL5dNKT.js} +0 -0
  162. /package/src/client/dist/spa/assets/{markdown-CVJLwHzJ.js → markdown-BuJfycGS.js} +0 -0
  163. /package/src/client/dist/spa/assets/{mips-R-FZ3zOR.js → mips-C9m_93PR.js} +0 -0
  164. /package/src/client/dist/spa/assets/{msdax-Blveyl9r.js → msdax-CpFHC9OI.js} +0 -0
  165. /package/src/client/dist/spa/assets/{mysql-D4mY1AFx.js → mysql-qFvltsqN.js} +0 -0
  166. /package/src/client/dist/spa/assets/{objective-c-BmXrLr4h.js → objective-c-Bnmr858J.js} +0 -0
  167. /package/src/client/dist/spa/assets/{pascal-yxckoyvV.js → pascal-WP0_D5AO.js} +0 -0
  168. /package/src/client/dist/spa/assets/{pascaligo-Q5JCwXMI.js → pascaligo-Blom4Rij.js} +0 -0
  169. /package/src/client/dist/spa/assets/{perl-BF1Rrs5h.js → perl-B-vk8g64.js} +0 -0
  170. /package/src/client/dist/spa/assets/{pgsql-CnYB97wm.js → pgsql-Cgvz6v67.js} +0 -0
  171. /package/src/client/dist/spa/assets/{php-CdDfQfSg.js → php-8a3Lrw9m.js} +0 -0
  172. /package/src/client/dist/spa/assets/{pla-whj-d71F.js → pla-DuFqEZ8V.js} +0 -0
  173. /package/src/client/dist/spa/assets/{postiats-ClfLr4I-.js → postiats-DkLtSgkp.js} +0 -0
  174. /package/src/client/dist/spa/assets/{powerquery-iRaBhuuk.js → powerquery-BJ1aNepW.js} +0 -0
  175. /package/src/client/dist/spa/assets/{powershell-DjiEt5xK.js → powershell-rE98k687.js} +0 -0
  176. /package/src/client/dist/spa/assets/{protobuf-B6dcIEUr.js → protobuf-CUheFacr.js} +0 -0
  177. /package/src/client/dist/spa/assets/{pug-DtmHnjM9.js → pug-LDcAMD8w.js} +0 -0
  178. /package/src/client/dist/spa/assets/{qsharp-CELCyd79.js → qsharp-DUKSQoR1.js} +0 -0
  179. /package/src/client/dist/spa/assets/{r-ZpJXWV-o.js → r-D-QApv87.js} +0 -0
  180. /package/src/client/dist/spa/assets/{rate-limit-labels-dCPVjS61.js → rate-limit-labels-BvYERsho.js} +0 -0
  181. /package/src/client/dist/spa/assets/{redis-BiHSNkAl.js → redis-SXdDyWR9.js} +0 -0
  182. /package/src/client/dist/spa/assets/{redshift-DzuwYCHP.js → redshift-Y6lsCryn.js} +0 -0
  183. /package/src/client/dist/spa/assets/{restructuredtext-YOT94bbS.js → restructuredtext-edObr9a8.js} +0 -0
  184. /package/src/client/dist/spa/assets/{ruby-BfiHr6Uu.js → ruby-CNnUfF-8.js} +0 -0
  185. /package/src/client/dist/spa/assets/{rust-JZ-uOoYM.js → rust-IHUZWzBr.js} +0 -0
  186. /package/src/client/dist/spa/assets/{sb-CBglP1-t.js → sb-DrUvY44N.js} +0 -0
  187. /package/src/client/dist/spa/assets/{scala-C9l41paw.js → scala-B4hbXGLM.js} +0 -0
  188. /package/src/client/dist/spa/assets/{scheme-B-InQ6hy.js → scheme-BGrd12j3.js} +0 -0
  189. /package/src/client/dist/spa/assets/{scss-v6OmJRN9.js → scss-x5G1ES4U.js} +0 -0
  190. /package/src/client/dist/spa/assets/{shell-Dyp6iwB6.js → shell-DOehe2Y8.js} +0 -0
  191. /package/src/client/dist/spa/assets/{solidity-D5epNWue.js → solidity-BeRvcwWV.js} +0 -0
  192. /package/src/client/dist/spa/assets/{sophia-Eva-79sB.js → sophia-DZbkUNjy.js} +0 -0
  193. /package/src/client/dist/spa/assets/{sparql-gvALLO1w.js → sparql-B7_oi5-h.js} +0 -0
  194. /package/src/client/dist/spa/assets/{sql-COdamZYI.js → sql-CTlsFWVE.js} +0 -0
  195. /package/src/client/dist/spa/assets/{st-eMoImIwE.js → st-DJVEJdPE.js} +0 -0
  196. /package/src/client/dist/spa/assets/{swift-7R_T9RYH.js → swift-CwhT3fYa.js} +0 -0
  197. /package/src/client/dist/spa/assets/{symbols-CAg-nBkV.js → symbols-DCYodwb2.js} +0 -0
  198. /package/src/client/dist/spa/assets/{systemverilog-1pCEfaHU.js → systemverilog-BQN63pkN.js} +0 -0
  199. /package/src/client/dist/spa/assets/{tcl-B_KgnhfE.js → tcl-DqwfpskA.js} +0 -0
  200. /package/src/client/dist/spa/assets/{twig-CFZUJxb9.js → twig-BiyenUgc.js} +0 -0
  201. /package/src/client/dist/spa/assets/{typespec-B1ZgHlud.js → typespec-CWOJribt.js} +0 -0
  202. /package/src/client/dist/spa/assets/{vb-DKdun5tL.js → vb-Cq5F87m3.js} +0 -0
  203. /package/src/client/dist/spa/assets/{vue-i18n-eUDnMrPl.js → vue-i18n-CeG0hR0Z.js} +0 -0
  204. /package/src/client/dist/spa/assets/{wgsl-CzNaxTrn.js → wgsl-BAvW2lVr.js} +0 -0
@@ -0,0 +1,155 @@
1
+ import { nanoid } from 'nanoid';
2
+ import { createParserState, parseClaudeLine } from './agent/engines/claude-code/stream-parser.js';
3
+ import { createPreMigrationBackup } from './db-backup-service.js';
4
+ import { broadcastAll } from './websocket-service.js';
5
+ const internal = { state: 'idle', total: 0, processed: 0 };
6
+ let isRunning = false;
7
+ function snapshot() {
8
+ switch (internal.state) {
9
+ case 'idle':
10
+ return { state: 'idle' };
11
+ case 'backing-up':
12
+ return { state: 'backing-up', startedAt: internal.startedAt ?? new Date(0).toISOString() };
13
+ case 'running':
14
+ return {
15
+ state: 'running',
16
+ total: internal.total,
17
+ processed: internal.processed,
18
+ startedAt: internal.startedAt ?? new Date(0).toISOString(),
19
+ ...(internal.backupPath !== undefined ? { backupPath: internal.backupPath } : {}),
20
+ };
21
+ case 'done':
22
+ return {
23
+ state: 'done',
24
+ total: internal.total,
25
+ processed: internal.processed,
26
+ startedAt: internal.startedAt ?? new Date(0).toISOString(),
27
+ finishedAt: internal.finishedAt ?? new Date(0).toISOString(),
28
+ ...(internal.backupPath !== undefined ? { backupPath: internal.backupPath } : {}),
29
+ };
30
+ case 'error':
31
+ return {
32
+ state: 'error',
33
+ errorMessage: internal.errorMessage ?? 'Unknown error',
34
+ ...(internal.startedAt !== undefined ? { startedAt: internal.startedAt } : {}),
35
+ ...(internal.backupPath !== undefined ? { backupPath: internal.backupPath } : {}),
36
+ ...(internal.total > 0 ? { total: internal.total } : {}),
37
+ ...(internal.processed > 0 ? { processed: internal.processed } : {}),
38
+ };
39
+ }
40
+ }
41
+ export function getContentMigrationStatus() {
42
+ return snapshot();
43
+ }
44
+ export async function runContentMigrationIfNeeded(db, dbPath) {
45
+ if (isRunning)
46
+ return;
47
+ isRunning = true;
48
+ try {
49
+ const row = db
50
+ .prepare("SELECT COUNT(*) AS c FROM ws_events WHERE type IN ('agent:output', 'agent:stderr', 'agent:status')")
51
+ .get();
52
+ if (row.c === 0) {
53
+ internal.state = 'idle';
54
+ isRunning = false;
55
+ return;
56
+ }
57
+ internal.state = 'backing-up';
58
+ internal.startedAt = new Date().toISOString();
59
+ broadcastStatus();
60
+ const backup = await createPreMigrationBackup(db, dbPath, 'v10');
61
+ internal.backupPath = backup.created ?? undefined;
62
+ internal.state = 'running';
63
+ internal.total = row.c;
64
+ internal.processed = 0;
65
+ broadcastStatus();
66
+ await processLoop(db);
67
+ internal.state = 'done';
68
+ internal.finishedAt = new Date().toISOString();
69
+ broadcastStatus();
70
+ }
71
+ catch (err) {
72
+ internal.state = 'error';
73
+ internal.errorMessage = err instanceof Error ? err.message : String(err);
74
+ broadcastStatus();
75
+ throw err;
76
+ }
77
+ finally {
78
+ isRunning = false;
79
+ }
80
+ }
81
+ function broadcastStatus() {
82
+ // Content-migration events are global (no workspace context) — use broadcastAll so every
83
+ // connected WS client receives them regardless of their workspace subscriptions.
84
+ broadcastAll(internal.state === 'error' ? 'migration:error' : 'migration:progress', getContentMigrationStatus());
85
+ }
86
+ async function processLoop(db) {
87
+ const batchSize = 500;
88
+ const selectStmt = db.prepare("SELECT id, workspace_id, type, payload, session_id, created_at FROM ws_events WHERE type IN ('agent:output', 'agent:stderr', 'agent:status') ORDER BY created_at ASC LIMIT ?");
89
+ const insertStmt = db.prepare('INSERT INTO ws_events (id, workspace_id, type, payload, session_id, created_at) VALUES (?, ?, ?, ?, ?, ?)');
90
+ const deleteStmt = db.prepare('DELETE FROM ws_events WHERE id = ?');
91
+ while (true) {
92
+ const rows = selectStmt.all(batchSize);
93
+ if (rows.length === 0)
94
+ break;
95
+ db.transaction(() => {
96
+ for (const r of rows) {
97
+ const events = convertRow(r.type, r.payload, { workspaceId: r.workspace_id });
98
+ for (const ev of events) {
99
+ insertStmt.run(nanoid(), r.workspace_id, 'agent:event', JSON.stringify(ev), r.session_id, r.created_at);
100
+ }
101
+ deleteStmt.run(r.id);
102
+ }
103
+ })();
104
+ internal.processed += rows.length;
105
+ broadcastStatus();
106
+ // Yield to the event loop
107
+ await new Promise((resolve) => setImmediate(resolve));
108
+ }
109
+ }
110
+ export function convertRow(type, payload, context) {
111
+ if (type === 'agent:status')
112
+ return []; // redundant — re-derivable from session events
113
+ if (type === 'agent:stderr') {
114
+ // Drop: the new engine only logs non-quota stderr via console.warn and
115
+ // does not persist it. Converting legacy stderr rows to error events
116
+ // would surface every historical Claude CLI warning ("no stdin data in
117
+ // 3s…", debug lines) as a UI-blocking banner. Quota-bearing stderr is
118
+ // handled live by the engine's backoff path, not via replay.
119
+ return [];
120
+ }
121
+ if (type === 'agent:output') {
122
+ try {
123
+ const parsed = JSON.parse(payload);
124
+ // The legacy payload may be either the raw Claude NDJSON already-parsed object,
125
+ // or a wrapper { type: 'raw', content: '...' } for non-JSON output. Handle both.
126
+ if (parsed && typeof parsed === 'object' && parsed.type === 'raw') {
127
+ return [{ kind: 'message:raw', content: String(parsed.content ?? '') }];
128
+ }
129
+ const state = createParserState();
130
+ const { events } = parseClaudeLine(JSON.stringify(parsed), state);
131
+ return events;
132
+ }
133
+ catch {
134
+ // Log enough to debug (first 200 chars of the bad payload + the owning
135
+ // workspace id when the caller passed one). We intentionally do not log
136
+ // the full payload to keep the console readable on noisy migrations.
137
+ const preview = payload.length > 200 ? `${payload.slice(0, 200)}…` : payload;
138
+ const ctx = context?.workspaceId ? ` (workspace=${context.workspaceId})` : '';
139
+ console.warn(`[content-migration] Could not parse agent:output payload${ctx}, falling back to message:raw. Preview: ${preview}`);
140
+ return [{ kind: 'message:raw', content: payload }];
141
+ }
142
+ }
143
+ return [];
144
+ }
145
+ /** Test-only. */
146
+ export function _resetStatusForTest() {
147
+ internal.state = 'idle';
148
+ internal.total = 0;
149
+ internal.processed = 0;
150
+ internal.errorMessage = undefined;
151
+ internal.startedAt = undefined;
152
+ internal.finishedAt = undefined;
153
+ internal.backupPath = undefined;
154
+ isRunning = false;
155
+ }
@@ -64,3 +64,18 @@ export async function createDailyDbBackupIfNeeded(db, dbPath, keepCount = DEFAUL
64
64
  }
65
65
  return result;
66
66
  }
67
+ const PREMIGRATION_PREFIX = 'kobo.db.premigration-';
68
+ export async function createPreMigrationBackup(db, dbPath, tag) {
69
+ const dir = path.dirname(dbPath);
70
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
71
+ backupSequence += 1;
72
+ const backupPath = path.join(dir, `${PREMIGRATION_PREFIX}${tag}-${stamp}-${backupSequence}`);
73
+ try {
74
+ await db.backup(backupPath);
75
+ return { created: backupPath, deleted: [] };
76
+ }
77
+ catch (err) {
78
+ console.error('[kobo] Pre-migration backup failed:', err);
79
+ throw err;
80
+ }
81
+ }
@@ -3,9 +3,6 @@ import { getDb } from '../db/index.js';
3
3
  // ── State ──────────────────────────────────────────────────────────────────────
4
4
  /** Maps each WS client to the set of workspaceIds they are subscribed to */
5
5
  const clients = new Map();
6
- /** Per-workspace emit counter for periodic cleanup. */
7
- const emitCounters = new Map();
8
- const EMIT_CLEANUP_THRESHOLD = 2000;
9
6
  let messageHandler = null;
10
7
  /** Register the handler that processes routed WS messages (e.g. chat:message, workspace:start). */
11
8
  export function setMessageHandler(handler) {
@@ -56,7 +53,7 @@ export function handleConnection(ws) {
56
53
  handleSyncRequest(ws, lastEventId, workspaceIds);
57
54
  break;
58
55
  }
59
- // Routed messages — delegated to agent-manager via messageHandler
56
+ // Routed messages — delegated to the orchestrator via messageHandler
60
57
  case 'chat:message':
61
58
  case 'workspace:start':
62
59
  case 'workspace:stop':
@@ -102,15 +99,6 @@ export function emit(workspaceId, type, payload, sessionId) {
102
99
  try {
103
100
  const db = getDb();
104
101
  db.prepare('INSERT INTO ws_events (id, workspace_id, type, payload, session_id, created_at) VALUES (?, ?, ?, ?, ?, ?)').run(id, workspaceId, type, JSON.stringify(payload), sessionId ?? null, createdAt);
105
- // Periodic cleanup — trigger when emit threshold is reached
106
- const count = (emitCounters.get(workspaceId) ?? 0) + 1;
107
- if (count >= EMIT_CLEANUP_THRESHOLD) {
108
- cleanupOldEvents(workspaceId);
109
- emitCounters.set(workspaceId, 0);
110
- }
111
- else {
112
- emitCounters.set(workspaceId, count);
113
- }
114
102
  }
115
103
  catch (err) {
116
104
  console.error(`[websocket-service] Failed to persist event (workspace=${workspaceId}, type=${type}):`, err);
@@ -118,10 +106,20 @@ export function emit(workspaceId, type, payload, sessionId) {
118
106
  // Build the event object to send
119
107
  const event = { id, workspaceId, type, payload, sessionId, createdAt };
120
108
  const message = JSON.stringify(event);
121
- // Broadcast to subscribed clients
109
+ // Broadcast to subscribed clients. Wrap `.send` in try/catch so a dropped
110
+ // client doesn't throw and abort delivery to the remaining subscribers.
111
+ let emitSendErrorLogged = false;
122
112
  for (const [ws, subs] of clients) {
123
113
  if (subs.has(workspaceId) && ws.readyState === 1 /* WebSocket.OPEN */) {
124
- ws.send(message);
114
+ try {
115
+ ws.send(message);
116
+ }
117
+ catch (err) {
118
+ if (!emitSendErrorLogged) {
119
+ console.warn(`[ws] emit send failed (workspace=${workspaceId}, type=${type}):`, err);
120
+ emitSendErrorLogged = true;
121
+ }
122
+ }
125
123
  }
126
124
  }
127
125
  return id;
@@ -135,9 +133,18 @@ export function emitEphemeral(workspaceId, type, payload) {
135
133
  const createdAt = new Date().toISOString();
136
134
  const event = { id, workspaceId, type, payload, createdAt };
137
135
  const message = JSON.stringify(event);
136
+ let sendErrorLogged = false;
138
137
  for (const [ws, subs] of clients) {
139
138
  if (subs.has(workspaceId) && ws.readyState === 1 /* WebSocket.OPEN */) {
140
- ws.send(message);
139
+ try {
140
+ ws.send(message);
141
+ }
142
+ catch (err) {
143
+ if (!sendErrorLogged) {
144
+ console.warn(`[ws] emitEphemeral send failed (workspace=${workspaceId}, type=${type}):`, err);
145
+ sendErrorLogged = true;
146
+ }
147
+ }
141
148
  }
142
149
  }
143
150
  }
@@ -161,8 +168,16 @@ export function handleSyncRequest(ws, lastEventId, workspaceIds) {
161
168
  // Build a query with placeholders for all subscribed workspaces
162
169
  const placeholders = resolvedIds.map(() => '?').join(', ');
163
170
  let rows;
171
+ // Initial window size: on a fresh connection (no lastEventId), we only
172
+ // replay the most recent slice of history. The client fetches older
173
+ // events on-demand via GET /api/workspaces/:id/events as the user scrolls
174
+ // up. This keeps first-paint fast on long-lived workspaces with tens of
175
+ // thousands of events without ever deleting anything from the DB.
176
+ const INITIAL_WINDOW = 300;
164
177
  if (lastEventId) {
165
- // Get the rowid of the last event to compare ordering
178
+ // Resume path: replay every event strictly after the cursor (delta
179
+ // since last seen). If the cursor is stale/unknown, fall back to the
180
+ // recent window rather than streaming the entire history.
166
181
  const lastRow = db.prepare('SELECT rowid FROM ws_events WHERE id = ?').get(lastEventId);
167
182
  if (lastRow) {
168
183
  rows = db
@@ -170,17 +185,18 @@ export function handleSyncRequest(ws, lastEventId, workspaceIds) {
170
185
  .all(...resolvedIds, lastRow.rowid);
171
186
  }
172
187
  else {
173
- // lastEventId not found — send events capped to avoid unbounded memory usage
174
188
  rows = db
175
- .prepare(`SELECT * FROM ws_events WHERE workspace_id IN (${placeholders}) ORDER BY rowid ASC LIMIT 10000`)
176
- .all(...resolvedIds);
189
+ .prepare(`SELECT * FROM ws_events WHERE workspace_id IN (${placeholders}) ORDER BY rowid DESC LIMIT ?`)
190
+ .all(...resolvedIds, INITIAL_WINDOW);
191
+ rows.reverse();
177
192
  }
178
193
  }
179
194
  else {
180
- // No lastEventId send all events (capped to avoid unbounded memory usage)
195
+ // Fresh connect: most recent INITIAL_WINDOW events, in chronological order.
181
196
  rows = db
182
- .prepare(`SELECT * FROM ws_events WHERE workspace_id IN (${placeholders}) ORDER BY rowid ASC LIMIT 10000`)
183
- .all(...resolvedIds);
197
+ .prepare(`SELECT * FROM ws_events WHERE workspace_id IN (${placeholders}) ORDER BY rowid DESC LIMIT ?`)
198
+ .all(...resolvedIds, INITIAL_WINDOW);
199
+ rows.reverse();
184
200
  }
185
201
  const events = rows.map((row) => {
186
202
  let parsedPayload;
@@ -188,6 +204,11 @@ export function handleSyncRequest(ws, lastEventId, workspaceIds) {
188
204
  parsedPayload = JSON.parse(row.payload);
189
205
  }
190
206
  catch {
207
+ console.error('[ws] corrupt ws_events row, falling back to raw:', {
208
+ id: row.id,
209
+ workspace_id: row.workspace_id,
210
+ type: row.type,
211
+ });
191
212
  parsedPayload = { raw: row.payload };
192
213
  }
193
214
  return {
@@ -200,30 +221,6 @@ export function handleSyncRequest(ws, lastEventId, workspaceIds) {
200
221
  };
201
222
  });
202
223
  ws.send(JSON.stringify({ type: 'sync:response', payload: { events } }));
203
- // Trigger cleanup when the event count is high to prevent unbounded growth
204
- const CLEANUP_THRESHOLD = 5000;
205
- if (rows.length >= CLEANUP_THRESHOLD) {
206
- for (const wid of resolvedIds) {
207
- cleanupOldEvents(wid);
208
- }
209
- }
210
- }
211
- // ── Cleanup ────────────────────────────────────────────────────────────────────
212
- /**
213
- * Delete old events keeping only the last N (default 1000) per workspace.
214
- */
215
- export function cleanupOldEvents(workspaceId, keepCount = 1000) {
216
- const db = getDb();
217
- db.prepare(`
218
- DELETE FROM ws_events
219
- WHERE workspace_id = ?
220
- AND rowid NOT IN (
221
- SELECT rowid FROM ws_events
222
- WHERE workspace_id = ?
223
- ORDER BY rowid DESC
224
- LIMIT ?
225
- )
226
- `).run(workspaceId, workspaceId, keepCount);
227
224
  }
228
225
  // ── Utilities ──────────────────────────────────────────────────────────────────
229
226
  /**
@@ -239,10 +236,44 @@ export function getClientCount() {
239
236
  export function _getClients() {
240
237
  return clients;
241
238
  }
239
+ // ── Global broadcast ───────────────────────────────────────────────────────────
240
+ /**
241
+ * Broadcast an ephemeral event to every connected WebSocket client,
242
+ * regardless of which workspaces they have subscribed to. Used for
243
+ * global events like migration progress.
244
+ */
245
+ export function broadcastAll(type, payload) {
246
+ const message = JSON.stringify({ type, payload });
247
+ let sendErrorLogged = false;
248
+ for (const client of clients.keys()) {
249
+ if (client.readyState === 1 /* WebSocket.OPEN */) {
250
+ try {
251
+ client.send(message);
252
+ }
253
+ catch (err) {
254
+ // client dropped; next iteration will fail its .readyState check.
255
+ // Log the first occurrence so real regressions surface without
256
+ // flooding the console if many clients die at once.
257
+ if (!sendErrorLogged) {
258
+ console.warn('[ws] broadcastAll send failed:', err);
259
+ sendErrorLogged = true;
260
+ }
261
+ }
262
+ }
263
+ }
264
+ }
242
265
  /**
243
- * Get the internal emit counters map — exposed for testing only.
266
+ * Return a mutable set view of connected clients — exposed for testing only.
267
+ * Adding/removing clients via this handle registers/unregisters them with the
268
+ * internal `clients` map so helpers like `broadcastAll` see them.
244
269
  * @internal
245
270
  */
246
- export function _getEmitCounters() {
247
- return emitCounters;
271
+ export function _connectionsForTest() {
272
+ return {
273
+ add: (ws) => {
274
+ if (!clients.has(ws))
275
+ clients.set(ws, new Set());
276
+ },
277
+ delete: (ws) => clients.delete(ws),
278
+ };
248
279
  }
@@ -11,6 +11,11 @@ const VALID_TRANSITIONS = {
11
11
  error: ['idle', 'executing', 'brainstorming', 'extracting'],
12
12
  quota: ['idle', 'executing'],
13
13
  };
14
+ // NOTE: `engine` is stored as a plain TEXT column and returned as a `string` on
15
+ // the `Workspace` interface rather than the stricter `EngineId` union. The DB
16
+ // is untyped, so we intentionally do not narrow here — validation against
17
+ // `listEngines()` is expected to happen at workspace creation (see the
18
+ // routes/engines handler) and when resolving an engine at agent-start time.
14
19
  function mapWorkspace(row) {
15
20
  return {
16
21
  id: row.id,
@@ -29,6 +34,7 @@ function mapWorkspace(row) {
29
34
  archivedAt: row.archived_at,
30
35
  favoritedAt: row.favorited_at,
31
36
  tags: parseTags(row.tags),
37
+ engine: row.engine ?? 'claude-code',
32
38
  createdAt: row.created_at,
33
39
  updatedAt: row.updated_at,
34
40
  };
@@ -65,9 +71,9 @@ export function createWorkspace(data) {
65
71
  const now = new Date().toISOString();
66
72
  const id = nanoid();
67
73
  db.prepare(`
68
- INSERT INTO workspaces (id, name, project_path, source_branch, working_branch, status, notion_url, notion_page_id, model, reasoning_effort, permission_mode, created_at, updated_at)
69
- VALUES (?, ?, ?, ?, ?, 'created', ?, ?, ?, ?, ?, ?, ?)
70
- `).run(id, data.name, data.projectPath, data.sourceBranch, data.workingBranch, data.notionUrl ?? null, data.notionPageId ?? null, data.model ?? 'claude-opus-4-7', data.reasoningEffort ?? 'auto', data.permissionMode ?? 'auto-accept', now, now);
74
+ INSERT INTO workspaces (id, name, project_path, source_branch, working_branch, status, notion_url, notion_page_id, model, reasoning_effort, permission_mode, engine, created_at, updated_at)
75
+ VALUES (?, ?, ?, ?, ?, 'created', ?, ?, ?, ?, ?, ?, ?, ?)
76
+ `).run(id, data.name, data.projectPath, data.sourceBranch, data.workingBranch, data.notionUrl ?? null, data.notionPageId ?? null, data.model ?? 'claude-opus-4-7', data.reasoningEffort ?? 'auto', data.permissionMode ?? 'auto-accept', data.engine ?? 'claude-code', now, now);
71
77
  return getWorkspace(id);
72
78
  }
73
79
  /** Fetch a single workspace by ID, or null if not found. */
@@ -336,7 +342,7 @@ function mapSession(row) {
336
342
  id: row.id,
337
343
  workspaceId: row.workspace_id,
338
344
  pid: row.pid,
339
- claudeSessionId: row.claude_session_id,
345
+ engineSessionId: row.engine_session_id,
340
346
  status: row.status,
341
347
  startedAt: row.started_at,
342
348
  endedAt: row.ended_at,
@@ -389,7 +395,7 @@ export function createIdleSession(workspaceId) {
389
395
  id,
390
396
  workspaceId,
391
397
  pid: null,
392
- claudeSessionId: null,
398
+ engineSessionId: null,
393
399
  status: 'idle',
394
400
  startedAt: now,
395
401
  endedAt: null,
@@ -1,5 +1,5 @@
1
1
  import { execFile as execFileCb, execFileSync } from 'node:child_process';
2
- import { readFileSync } from 'node:fs';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { promisify } from 'node:util';
5
5
  const execFileAsync = promisify(execFileCb);
@@ -133,7 +133,47 @@ export function pullBranch(repoPath, branchName, remote = 'origin') {
133
133
  throw new Error(`Failed to pull branch '${branchName}' from '${remote}': ${message}`);
134
134
  }
135
135
  }
136
- /** Rebase the current branch onto the given base branch. Fetches origin first to get latest changes. */
136
+ /** Thrown when a rebase or merge produces conflicts. Leaves the repo in the mid-operation state
137
+ * so the caller can decide between abort and agent-assisted resolution. */
138
+ export class GitConflictError extends Error {
139
+ operation;
140
+ files;
141
+ constructor(operation, files) {
142
+ super(`${operation} produced ${files.length} conflicted file(s)`);
143
+ this.name = 'GitConflictError';
144
+ this.operation = operation;
145
+ this.files = files;
146
+ }
147
+ }
148
+ /** List files currently in a conflicted state (unmerged paths). */
149
+ export function getConflictedFiles(repoPath) {
150
+ try {
151
+ const output = git(repoPath, ['diff', '--name-only', '--diff-filter=U']);
152
+ return output
153
+ .split('\n')
154
+ .map((f) => f.trim())
155
+ .filter(Boolean);
156
+ }
157
+ catch {
158
+ return [];
159
+ }
160
+ }
161
+ /** Detect whether a merge or rebase is currently in progress in the worktree. */
162
+ export function getOngoingGitOperation(repoPath) {
163
+ try {
164
+ const gitDir = git(repoPath, ['rev-parse', '--git-dir']);
165
+ const dir = gitDir.startsWith('/') ? gitDir : join(repoPath, gitDir);
166
+ if (existsSync(join(dir, 'MERGE_HEAD')))
167
+ return 'merge';
168
+ if (existsSync(join(dir, 'rebase-merge')) || existsSync(join(dir, 'rebase-apply')))
169
+ return 'rebase';
170
+ return null;
171
+ }
172
+ catch {
173
+ return null;
174
+ }
175
+ }
176
+ /** Rebase the current branch onto the given base branch. Fetches origin first. Leaves conflicts in place. */
137
177
  export function rebaseBranch(repoPath, baseBranch) {
138
178
  try {
139
179
  git(repoPath, ['fetch', 'origin', baseBranch]);
@@ -145,17 +185,46 @@ export function rebaseBranch(repoPath, baseBranch) {
145
185
  git(repoPath, ['rebase', `origin/${baseBranch}`]);
146
186
  }
147
187
  catch (err) {
148
- const message = err instanceof Error ? err.message : String(err);
149
- // Abort the rebase if it fails (conflicts etc.)
150
- try {
151
- git(repoPath, ['rebase', '--abort']);
152
- }
153
- catch {
154
- /* best-effort */
188
+ const conflicted = getConflictedFiles(repoPath);
189
+ if (conflicted.length > 0 || getOngoingGitOperation(repoPath) === 'rebase') {
190
+ // Leave the rebase in progress so the caller can abort or request agent-assisted resolution.
191
+ throw new GitConflictError('rebase', conflicted);
155
192
  }
193
+ const message = err instanceof Error ? err.message : String(err);
156
194
  throw new Error(`Rebase onto '${baseBranch}' failed: ${message}`);
157
195
  }
158
196
  }
197
+ /** Merge `origin/<baseBranch>` into the current branch. Fetches first. Leaves conflicts in place. */
198
+ export function mergeBranch(repoPath, baseBranch) {
199
+ try {
200
+ git(repoPath, ['fetch', 'origin', baseBranch]);
201
+ }
202
+ catch {
203
+ // offline — continue with local ref
204
+ }
205
+ try {
206
+ git(repoPath, ['merge', '--no-ff', '--no-edit', `origin/${baseBranch}`]);
207
+ }
208
+ catch (err) {
209
+ const conflicted = getConflictedFiles(repoPath);
210
+ if (conflicted.length > 0 || getOngoingGitOperation(repoPath) === 'merge') {
211
+ throw new GitConflictError('merge', conflicted);
212
+ }
213
+ const message = err instanceof Error ? err.message : String(err);
214
+ throw new Error(`Merge of 'origin/${baseBranch}' failed: ${message}`);
215
+ }
216
+ }
217
+ /** Abort an in-progress merge or rebase. No-op if nothing is in progress. */
218
+ export function abortOngoingGitOperation(repoPath) {
219
+ const op = getOngoingGitOperation(repoPath);
220
+ if (op === 'merge') {
221
+ git(repoPath, ['merge', '--abort']);
222
+ }
223
+ else if (op === 'rebase') {
224
+ git(repoPath, ['rebase', '--abort']);
225
+ }
226
+ return op;
227
+ }
159
228
  /** Try a git command with `base`, falling back to `origin/base` if the local ref is missing. */
160
229
  function resolveBase(repoPath, base) {
161
230
  try {
@@ -99,7 +99,7 @@ export function getTemplatesPath() {
99
99
  /**
100
100
  * Absolute path to the compiled MCP server entry (shipped in the published
101
101
  * package as dist/mcp-server/kobo-tasks-server.js). Returns null if not
102
- * present — callers (agent-manager) then fall back to the TS source for dev.
102
+ * present — callers (orchestrator) then fall back to the TS source for dev.
103
103
  */
104
104
  export function getCompiledMcpServerPath() {
105
105
  const compiled = getPackageAssetPath('dist', 'mcp-server', 'kobo-tasks-server.js');
@@ -0,0 +1,50 @@
1
+ export const CLAUDE_MODELS = [
2
+ {
3
+ id: 'auto',
4
+ label: 'Auto',
5
+ i18nLabelKey: 'model.auto',
6
+ i18nDescriptionKey: 'model.autoDescription',
7
+ },
8
+ {
9
+ id: 'claude-opus-4-7',
10
+ label: 'Opus 4.7 (Classic)',
11
+ i18nLabelKey: 'model.opus47Classic',
12
+ i18nDescriptionKey: 'model.opus47ClassicDescription',
13
+ },
14
+ {
15
+ id: 'claude-opus-4-7[1m]',
16
+ label: 'Opus 4.7 (1M)',
17
+ i18nLabelKey: 'model.opus471m',
18
+ i18nDescriptionKey: 'model.opus471mDescription',
19
+ },
20
+ {
21
+ id: 'claude-opus-4-6',
22
+ label: 'Opus 4.6 (Classic)',
23
+ i18nLabelKey: 'model.opusClassic',
24
+ i18nDescriptionKey: 'model.opusClassicDescription',
25
+ },
26
+ {
27
+ id: 'claude-opus-4-6[1m]',
28
+ label: 'Opus 4.6 (1M)',
29
+ i18nLabelKey: 'model.opus1m',
30
+ i18nDescriptionKey: 'model.opus1mDescription',
31
+ },
32
+ {
33
+ id: 'claude-sonnet-4-6',
34
+ label: 'Sonnet 4.6 (Classic)',
35
+ i18nLabelKey: 'model.sonnetClassic',
36
+ i18nDescriptionKey: 'model.sonnetClassicDescription',
37
+ },
38
+ {
39
+ id: 'claude-sonnet-4-6[1m]',
40
+ label: 'Sonnet 4.6 (1M)',
41
+ i18nLabelKey: 'model.sonnet1m',
42
+ i18nDescriptionKey: 'model.sonnet1mDescription',
43
+ },
44
+ {
45
+ id: 'claude-haiku-4-5-20251001',
46
+ label: 'Haiku 4.5',
47
+ i18nLabelKey: 'model.haiku',
48
+ i18nDescriptionKey: 'model.haikuDescription',
49
+ },
50
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loicngr/kobo",
3
- "version": "1.5.7",
3
+ "version": "1.6.1",
4
4
  "description": "Kōbō — multi-workspace agent manager for Claude Code. Orchestrates isolated git worktrees with dev servers, Notion integration, and MCP tools.",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-or-later",
@@ -0,0 +1,7 @@
1
+ import{E as e,F as t,H as n,L as r,M as i,Q as a,U as o,_t as s,bt as c,d as l,f as u,g as d,h as f,l as p,p as m,r as h,rt as g,u as _,v,yt as y}from"./runtime-core.esm-bundler-C3IgBgY5.js";import{L as b,l as x,t as S}from"./QIcon-B0-pH3Qs.js";import{t as C}from"./QBtn-p1aZtrJH.js";import{n as w}from"./vue-i18n-CeG0hR0Z.js";import{S as T,v as E,x as D}from"./index-D997aY4Y.js";import{t as O}from"./QSpinnerDots-By20ptst.js";import{t as k}from"./QExpansionItem-5ekmpO-2.js";import{t as ee}from"./QScrollArea-L6wUiA20.js";import{n as A,t as j}from"./marked.esm-DW0ulF0a.js";import{t as M}from"./_plugin-vue_export-helper-CEhRWsKN.js";function N(e,t,n=!0){let r=[],i=new Map,a=new Map;for(let n=0;n<e.length;n++){let o=e[n],s=t?.[n];switch(o.kind){case`message:text`:{let e=i.get(o.messageId);if(e)e.text+=o.text,e.streaming=o.streaming;else{let e={type:`text`,messageId:o.messageId,text:o.text,streaming:o.streaming,ts:s};i.set(o.messageId,e),r.push(e)}break}case`message:end`:{let e=i.get(o.messageId);e&&(e.streaming=!1);break}case`message:thinking`:r.push({type:`thinking`,messageId:o.messageId,text:o.text,ts:s});break;case`tool:call`:{let e={type:`tool`,toolCallId:o.toolCallId,name:o.name,input:o.input,ts:s};a.set(o.toolCallId,e),r.push(e);break}case`tool:result`:{let e=a.get(o.toolCallId);e&&(e.result={output:o.output,isError:o.isError});break}case`session:started`:r.push({type:`session`,kind:`started`,detail:{engineSessionId:o.engineSessionId,model:o.model},ts:s});break;case`session:ended`:r.push({type:`session`,kind:`ended`,detail:{reason:o.reason,exitCode:o.exitCode},ts:s});break;case`session:compacted`:r.push({type:`session`,kind:`compacted`,ts:s});break;case`session:brainstorm-complete`:case`message:raw`:case`skills:discovered`:case`usage`:case`rate_limit`:case`subagent:progress`:case`error`:break;default:}}let o=null;for(let e of r)e.type===`text`&&e.streaming&&(o&&(o.streaming=!1),o=e);return o&&!n&&(o.streaming=!1),r}function te(e,t){if(t.length===0)return e;let n=t.map(e=>({type:`user`,content:e.content,sender:e.sender,ts:e.ts})),r=[...e,...n];return r.sort((e,t)=>{let n=e.ts??``,r=t.ts??``;return n===r?0:n?r?n<r?-1:1:-1:1}),r}function P(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:`user`;case`session`:return`session`;default:return`agent`}}function ne(e){let t=[],n=null;for(let r of e){let e=P(r),i=e===`session`||e===`system-prompt`;!n||n.speaker!==e||i?(n={speaker:e,ts:r.ts,items:[r]},t.push(n),i&&(n=null)):n.items.push(r)}return t}var F={class:`text-caption text-grey-6`},I=v({__name:`SessionEventItem`,props:{item:{}},setup(e){let n=e,r=p(()=>{switch(n.item.kind){case`started`:return`session.started`;case`ended`:return`session.ended`;case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,n)=>(t(),m(`span`,F,c(e.$t(r.value)),1))}}),L={class:`markdown-message`},R=[`innerHTML`],z=M(v({__name:`TextMessageItem`,props:{item:{}},setup(e){let n=e,r=p(()=>{let e=j.parse(n.item.text,{async:!1,breaks:!0,gfm:!0});return A.sanitize(e)});return(n,i)=>(t(),m(`div`,L,[_(`div`,{innerHTML:r.value},null,8,R),e.item.streaming?(t(),l(x,{key:0,size:`xs`,class:`q-ml-xs`})):u(``,!0)]))}}),[[`__scopeId`,`data-v-158cbb54`]]),B={key:0,class:`text-caption text-grey-5`,style:{"font-style":`italic`}},V=[`innerHTML`],H={key:1,style:{"white-space":`pre-wrap`}},U=M(v({__name:`ThinkingItem`,props:{item:{}},setup(e){let n=e,r=p(()=>n.item.text.trim().slice(0,100)),i=p(()=>n.item.text.trim().length>0),a=p(()=>n.item.text.trim().length>100),s=p(()=>{let e=j.parse(n.item.text,{async:!1,breaks:!0,gfm:!0});return A.sanitize(e)});return(n,d)=>i.value?(t(),m(`div`,B,[a.value?(t(),l(k,{key:0,dense:``,"dense-toggle":``,label:r.value,"header-class":`text-grey-5 text-caption`,style:{"font-style":`italic`}},{default:o(()=>[_(`div`,{class:`q-py-xs markdown-thinking`,innerHTML:s.value},null,8,V)]),_:1},8,[`label`])):(t(),m(`span`,H,c(e.item.text),1))])):u(``,!0)}}),[[`__scopeId`,`data-v-e9fb9f90`]]);function W(e,t){let n=e.split(`
2
+ `),r=t.split(`
3
+ `),i=n.length,a=r.length,o=Array.from({length:i+1},()=>Array(a+1).fill(0));for(let e=i-1;e>=0;e--)for(let t=a-1;t>=0;t--)n[e]===r[t]?o[e][t]=o[e+1][t+1]+1:o[e][t]=Math.max(o[e+1][t],o[e][t+1]);let s=[],c=0,l=0;for(;c<i&&l<a;)n[c]===r[l]?(s.push({type:`context`,content:n[c]}),c++,l++):o[c+1][l]>=o[c][l+1]?(s.push({type:`del`,content:n[c]}),c++):(s.push({type:`add`,content:r[l]}),l++);for(;c<i;)s.push({type:`del`,content:n[c++]});for(;l<a;)s.push({type:`add`,content:r[l++]});return s}function G(e,t){if(!t||typeof t!=`object`)return null;let n=t;if(e===`Edit`){let e=n.file_path;if(!e)return null;let t=n.old_string??``,r=n.new_string??``;return{toolName:`Edit`,filePath:e,oldString:t,newString:r,replaceAll:n.replace_all??!1,additions:r?r.split(`
4
+ `).length:0,deletions:t?t.split(`
5
+ `).length:0}}if(e===`Write`){let e=n.file_path;if(!e)return null;let t=n.content??``;return{toolName:`Write`,filePath:e,content:t,additions:t?t.split(`
6
+ `).length:0,deletions:0}}if(e===`Bash`){let e=(n.command??``).match(/^\s*rm\s+(?:-[a-zA-Z]*\s+)*(.+)/);if(e)return{toolName:`Bash:rm`,filePath:e[1].trim().replace(/["']/g,``),additions:0,deletions:1}}return null}function K(e,t){if(!e||!t?.projectPath)return e;let n=q(e,`${t.projectPath}/.worktrees/${t.workingBranch}`);return n===e&&(n=q(e,t.projectPath)),n}function q(e,t){if(!t)return e;let n=t.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`);return e.replace(RegExp(`${n}/`,`g`),``).replace(RegExp(`${n}(?=\\s|$|["'\`])`,`g`),`.`)}var J={class:`tool-name`},Y=[`title`],X={key:0,class:`tool-stat-add`},re={key:1,class:`tool-stat-del`},ie={class:`diff-sign`},ae={key:1,class:`tool-row tool-row-generic`},Z={class:`tool-header`},oe={class:`tool-name`},se=[`title`],ce={key:0,class:`tool-output`},le=M(v({__name:`ToolCallItem`,props:{item:{}},setup(e){let n=e,i=a(!1),o=E(),g=p(()=>G(n.item.name,n.item.input)),v=p(()=>g.value?K(g.value.filePath,o.selectedWorkspace):``),y={Bash:`terminal`,Read:`description`,Edit:`edit`,Write:`edit_note`,MultiEdit:`edit`,Glob:`folder_open`,Grep:`manage_search`,LS:`list`,Skill:`auto_awesome`,Task:`hub`,Agent:`hub`,TodoWrite:`checklist`,TodoRead:`checklist`,ToolSearch:`search`,WebFetch:`public`,WebSearch:`travel_explore`,NotebookRead:`book`,NotebookEdit:`edit_note`,SendMessage:`send`,ExitPlanMode:`check_circle_outline`,KillShell:`stop_circle`,BashOutput:`terminal`},x=p(()=>y[n.item.name]??`build`),C=p(()=>{if(g.value)return``;let e=n.item.input,t=w(e);return t?K(t,o.selectedWorkspace):``});function w(e){if(!e||typeof e!=`object`)return typeof e==`string`?e:``;let t=e;for(let e of[`file_path`,`path`,`command`,`pattern`,`query`,`url`,`skill`,`description`,`subject`,`prompt`]){let n=t[e];if(typeof n==`string`&&n.length>0)return n}for(let e of Object.values(t))if(typeof e==`string`&&e.length>0)return e;return``}let T=p(()=>{let e=g.value;return e?e.toolName===`Edit`&&e.oldString!==void 0&&e.newString!==void 0?W(e.oldString,e.newString):e.toolName===`Write`&&e.content!==void 0?e.content.split(`
7
+ `).map(e=>({type:`add`,content:e})):e.toolName===`Bash:rm`?[{type:`del`,content:`File deleted`}]:null:null}),D=p(()=>{let e=n.item.result;if(!e)return``;if(typeof e.output==`string`)return e.output;try{return JSON.stringify(e.output)}catch{return String(e.output)}});function O(){i.value=!i.value}return(n,a)=>g.value?(t(),m(`div`,{key:0,class:s([`tool-row`,{"tool-row-expanded":i.value}])},[_(`div`,{class:`tool-header`,onClick:O},[d(S,{name:x.value,size:`14px`,class:`tool-icon`},null,8,[`name`]),_(`span`,J,c(g.value.toolName===`Bash:rm`?`Bash`:g.value.toolName),1),_(`span`,{class:`tool-path`,title:g.value.filePath},c(v.value),9,Y),g.value.additions>0?(t(),m(`span`,X,`+`+c(g.value.additions),1)):u(``,!0),g.value.deletions>0?(t(),m(`span`,re,`-`+c(g.value.deletions),1)):u(``,!0),e.item.result?.isError?(t(),l(S,{key:2,name:`error_outline`,color:`negative`,size:`xs`,class:`q-ml-xs`})):e.item.result?(t(),l(S,{key:3,name:`check`,color:`positive`,size:`xs`,class:`q-ml-xs`})):u(``,!0),d(S,{name:i.value?`expand_less`:`expand_more`,size:`xs`,class:`q-ml-auto text-grey-6`},null,8,[`name`])]),i.value&&T.value?(t(),m(`div`,{key:0,class:`tool-diff`,onClick:a[0]||=b(()=>{},[`stop`])},[(t(!0),m(h,null,r(T.value,(e,n)=>(t(),m(`div`,{key:n,class:s([`diff-line`,{"diff-del":e.type===`del`,"diff-add":e.type===`add`,"diff-context":e.type===`context`}])},[_(`span`,ie,c(e.type===`del`?`-`:e.type===`add`?`+`:` `),1),f(c(e.content),1)],2))),128))])):u(``,!0)],2)):(t(),m(`div`,ae,[_(`div`,Z,[d(S,{name:x.value,size:`14px`,class:`tool-icon`},null,8,[`name`]),_(`span`,oe,c(e.item.name),1),C.value?(t(),m(`span`,{key:0,class:`tool-arg`,title:w(e.item.input)||C.value},c(C.value),9,se)):u(``,!0),e.item.result?.isError?(t(),l(S,{key:1,name:`error_outline`,color:`negative`,size:`xs`,class:`q-ml-auto`})):e.item.result?(t(),l(S,{key:2,name:`check`,color:`positive`,size:`xs`,class:`q-ml-auto`})):u(``,!0)]),e.item.result&&D.value?(t(),m(`div`,ce,c(D.value),1)):u(``,!0)]))}}),[[`__scopeId`,`data-v-440d2c83`]]),ue=[`innerHTML`],de={key:1,class:`markdown-message`},fe=[`innerHTML`],pe=M(v({__name:`UserMessageItem`,props:{item:{}},setup(e){let n=e,r=p(()=>n.item.sender===`system-prompt`),i=p(()=>{let e=j.parse(n.item.content,{async:!1,breaks:!0,gfm:!0});return A.sanitize(e)});return(e,n)=>r.value?(t(),l(k,{key:0,dense:``,"dense-toggle":``,label:e.$t(`chat.systemPrompt`),"header-class":`text-grey-5 text-caption`},{default:o(()=>[_(`div`,{class:`q-py-xs markdown-user-prompt`,innerHTML:i.value},null,8,ue)]),_:1},8,[`label`])):(t(),m(`div`,de,[_(`div`,{innerHTML:i.value},null,8,fe)]))}}),[[`__scopeId`,`data-v-cb8cec0f`]]),me={class:`turn-header`},he={key:0,class:`turn-time`},ge={key:1,class:`turn-actions`},_e={class:`turn-body`},ve=M(v({__name:`TurnCard`,props:{turn:{}},setup(e){let n=e,{t:i}=w(),a=p(()=>{switch(n.turn.speaker){case`user`:return{label:i(`chat.you`),accent:`#ce93d8`,badgeClass:`turn-badge-user`};case`agent`:return{label:i(`chat.agent`),accent:`#7986cb`,badgeClass:`turn-badge-agent`};case`system-prompt`:return{label:i(`chat.systemPrompt`),accent:`#757575`,badgeClass:`turn-badge-system`};case`session`:return{label:i(`chat.session`),accent:`#616161`,badgeClass:`turn-badge-session`}}}),o=p(()=>{if(!n.turn.ts)return``;let e=new Date(n.turn.ts);return Number.isNaN(e.getTime())?``:e.toLocaleTimeString(void 0,{hour:`2-digit`,minute:`2-digit`})}),d=p(()=>n.turn.items.filter(e=>e.type===`tool`).length);return(n,f)=>(t(),m(`div`,{class:s([`turn-card`,{"turn-card--user":e.turn.speaker===`user`}]),style:y({"--turn-accent":a.value.accent})},[_(`div`,me,[_(`span`,{class:s([`turn-badge`,a.value.badgeClass])},c(a.value.label),3),o.value?(t(),m(`span`,he,c(o.value),1)):u(``,!0),d.value>0?(t(),m(`span`,ge,` · `+c(g(i)(`chat.nActions`,{n:d.value})),1)):u(``,!0)]),_(`div`,_e,[(t(!0),m(h,null,r(e.turn.items,(e,n)=>(t(),m(h,{key:n},[e.type===`text`?(t(),l(z,{key:0,item:e},null,8,[`item`])):e.type===`thinking`?(t(),l(U,{key:1,item:e},null,8,[`item`])):e.type===`tool`?(t(),l(le,{key:2,item:e},null,8,[`item`])):e.type===`user`?(t(),l(pe,{key:3,item:e},null,8,[`item`])):e.type===`session`?(t(),l(I,{key:4,item:e},null,8,[`item`])):u(``,!0)],64))),128))])],6))}}),[[`__scopeId`,`data-v-8a9ce6dd`]]),ye={key:0,class:`activity-feed-switching`},be={key:1,class:`activity-feed-wrap`},xe={key:0,class:`text-center q-py-sm text-caption text-grey-6`},Se={class:`q-pa-md`},Ce={key:1,class:`q-px-md q-pb-md`},we=60,Q=200,$=200,Te=400,Ee=200,De=M(v({__name:`ActivityFeed`,props:{workspaceId:{}},setup(s){let g=s,v=T(),y=D(),b=E(),S=p(()=>(b.activityFeeds[g.workspaceId]??[]).filter(e=>e.type===`text`&&typeof e.content==`string`).map(e=>({content:e.content,sender:e.meta?.sender??`user`,ts:e.timestamp,sessionId:e.sessionId}))),w=p(()=>{let e=b.workspaces.find(e=>e.id===g.workspaceId);return e?e.status===`extracting`||e.status===`brainstorming`||e.status===`executing`:!1}),A=p(()=>{let e=te(N(v.eventsFor(g.workspaceId),v.timestampsFor(g.workspaceId),w.value),S.value);return ne(y.showVerboseSystemMessages?e:e.filter(e=>e.type!==`session`))}),j=p(()=>y.showVerboseSystemMessages?v.eventsFor(g.workspaceId).filter(e=>e.kind===`message:raw`).map(e=>e.content):[]),M=a(null),P=!0,F=a(!1),I=!1,L=a(!0);function R(e){P=e.verticalSize-e.verticalPosition-e.verticalContainerSize<=we,I&&e.verticalPosition<=Q&&!F.value&&v.hasMoreOlderFor(g.workspaceId)&&z()}async function z(){let t=g.workspaceId,n=v.oldestIdFor(t);if(!n)return;F.value=!0;let r=Date.now();try{let r=M.value,i=r?.getScroll().verticalSize??0,a=r?.getScroll().verticalPosition??0,o=fetch(`/api/workspaces/${t}/events?before=${encodeURIComponent(n)}&limit=200`),s=new Promise(e=>setTimeout(e,$)),[c]=await Promise.all([o,s]);if(!c.ok){v.prepend(t,[],[],{oldestId:n,hasMoreOlder:!1});return}let l=await c.json(),u=l.events??[],d=u.filter(e=>e.type===`agent:event`&&e.workspaceId===t),f=u.filter(e=>e.type===`user:message`&&e.workspaceId===t),p=d.map(e=>e.payload),m=d.map(e=>e.createdAt),h=d.map(e=>e.sessionId??null),g=u.length>0?u[0].id:n;v.prepend(t,p,m,{oldestId:g,hasMoreOlder:l.hasMore,sessionIds:h});for(let e of f){let n=e.payload;typeof n.content==`string`&&b.addActivityItem(t,{id:e.id,type:`text`,content:n.content,timestamp:e.createdAt,sessionId:e.sessionId??void 0,meta:{sender:n.sender??`user`}})}if(await e(),r){let e=r.getScroll().verticalSize-i,t=Math.max(a+e,Q+50);r.setScrollPosition(`vertical`,t,0)}}catch(e){console.error(`[ActivityFeed] failed to load older events:`,e)}finally{let e=Date.now()-r,t=Math.max(0,$-e);await new Promise(e=>setTimeout(e,t+Te)),F.value=!1}}async function B(t=0){await e();let n=M.value;if(!n)return;let r=n.getScroll();n.setScrollPosition(`vertical`,r.verticalSize,t)}let V=a([]),H=a(null);function U(){let e=A.value,t=V.value,n=[];if(t.length===e.length){for(let r=0;r<e.length;r++){if(e[r].speaker!==`user`)continue;let i=t[r]?.$el;i&&n.push(i)}if(n.length>0)return n}let r=H.value?.parentElement;if(r){let e=r.querySelectorAll(`.turn-card--user`);for(let t of e)n.push(t)}return n}function W(){let e=M.value;if(!e)return null;let t=H.value;if(!t)return null;let n=e.getScroll().verticalPosition,r=t.getBoundingClientRect().top,i=null;for(let e of U()){let t=e.getBoundingClientRect().top-r;if(t<n-40)i=t;else break}return i}async function G(){let t=M.value;if(!t)return;let n=W();if(n===null)for(let t=0;t<15&&v.hasMoreOlderFor(g.workspaceId);t++){for(;F.value;)await new Promise(e=>setTimeout(e,50));if(await z(),await e(),n=W(),n!==null)break}n!==null&&t.setScrollPosition(`vertical`,Math.max(0,n-12),250)}async function K(){I=!1,await e(),await B(0),requestAnimationFrame(()=>{requestAnimationFrame(()=>{I=!0})})}let q=p(()=>v.eventsFor(g.workspaceId).length);async function J(){L.value=!0;let e=Date.now();await new Promise(e=>setTimeout(e,Ee));let t=e+5e3;for(;q.value===0&&Date.now()<t;)await new Promise(e=>setTimeout(e,50));L.value=!1}n(L,async e=>{!e&&q.value>0&&await K()}),i(()=>{J(),q.value>0&&K()});let Y=!1;return n(q,async(e,t)=>{if(!Y&&e>0){Y=!0,await K();return}e>t&&P&&!F.value&&await B(180)}),n(()=>g.workspaceId,()=>{P=!0,Y=!1,I=!1,J(),q.value>0&&K()}),n(()=>b.selectedSessionId,async()=>{P=!0,I=!1,await K()}),n(p(()=>S.value.filter(e=>e.sender!==`system-prompt`).length),async(e,t)=>{e>t&&(P=!0,await B(180))}),(e,n)=>L.value?(t(),m(`div`,ye,[d(O,{size:`40px`,color:`indigo-4`})])):(t(),m(`div`,be,[d(ee,{ref_key:`scrollRef`,ref:M,class:`activity-feed-scroll`,onScroll:R},{default:o(()=>[_(`div`,{ref_key:`contentOriginRef`,ref:H,class:`content-origin-marker`},null,512),F.value?(t(),m(`div`,xe,[d(x,{size:`sm`}),f(` `+c(e.$t(`activity.loading_older`)),1)])):u(``,!0),_(`div`,Se,[(t(!0),m(h,null,r(A.value,(e,n)=>(t(),l(ve,{key:n,ref_for:!0,ref_key:`turnRefs`,ref:V,turn:e},null,8,[`turn`]))),128))]),j.value.length?(t(),m(`div`,Ce,[d(k,{label:e.$t(`activity.raw_lines`,{n:j.value.length}),dense:``},{default:o(()=>[(t(!0),m(h,null,r(j.value,(e,n)=>(t(),m(`div`,{key:n,class:`text-caption text-grey q-pa-xs`},c(e),1))),128))]),_:1},8,[`label`])])):u(``,!0)]),_:1},512),d(C,{round:``,dense:``,unelevated:``,color:`grey-9`,"text-color":`grey-3`,icon:`arrow_upward`,size:`sm`,class:`activity-feed-prev-btn`,title:e.$t(`activity.prev_user_message`),onClick:G},null,8,[`title`])]))}}),[[`__scopeId`,`data-v-51e2c6eb`]]);export{De as default};