@loicngr/kobo 1.8.0 → 1.8.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 (199) hide show
  1. package/AGENTS.md +53 -52
  2. package/CHANGELOG.md +6 -1
  3. package/README.md +59 -37
  4. package/dist/server/index.js +39 -1
  5. package/dist/server/middleware/network-auth-middleware.js +27 -0
  6. package/dist/server/routes/changelog.js +1 -1
  7. package/dist/server/routes/settings.js +49 -0
  8. package/dist/server/routes/workspaces.js +17 -0
  9. package/dist/server/services/agent/orchestrator.js +4 -0
  10. package/dist/server/services/network-access-service.js +67 -0
  11. package/dist/server/services/settings-service.js +50 -1
  12. package/dist/server/services/templates-service.js +80 -11
  13. package/dist/server/services/worktree-purge-service.js +2 -2
  14. package/dist/server/services/worktree-service.js +64 -2
  15. package/package.json +7 -7
  16. package/src/client/dist/spa/assets/ActivityFeed-qE7kgNNI.js +8 -0
  17. package/src/client/dist/spa/assets/ChangelogPage-P-cSkc52.js +1 -0
  18. package/src/client/dist/spa/assets/ClosePopup-BWi4AXm0.js +1 -0
  19. package/src/client/dist/spa/assets/CreatePage-CdeIZxPs.js +2 -0
  20. package/src/client/dist/spa/assets/DiffViewer-aEMR55x8.js +8 -0
  21. package/src/client/dist/spa/assets/HealthPage-Oq6n1qu0.js +1 -0
  22. package/src/client/dist/spa/assets/{MainLayout-KmjhECRT.css → MainLayout-DXz5Vnxf.css} +1 -1
  23. package/src/client/dist/spa/assets/MainLayout-y1VMkOwI.js +37 -0
  24. package/src/client/dist/spa/assets/{QBadge-CIC5n8w7.js → QBadge-DoPfOZ_x.js} +1 -1
  25. package/src/client/dist/spa/assets/{QBanner-BxBEdhfp.js → QBanner-C5hABcB2.js} +1 -1
  26. package/src/client/dist/spa/assets/QChip-xeL4Nxy_.js +1 -0
  27. package/src/client/dist/spa/assets/QExpansionItem-QIQTvw7U.js +1 -0
  28. package/src/client/dist/spa/assets/{QIcon-C6C3QeM4.js → QIcon-CfOEsLsF.js} +1 -1
  29. package/src/client/dist/spa/assets/QInput-7G0OXYP-.js +1 -0
  30. package/src/client/dist/spa/assets/{QList-DRW_oyZ4.js → QList-kGlJAb-p.js} +1 -1
  31. package/src/client/dist/spa/assets/{QPage-C6xc9fOe.js → QPage-B-ixPPKx.js} +1 -1
  32. package/src/client/dist/spa/assets/QScrollArea-cWkysQ9Q.js +1 -0
  33. package/src/client/dist/spa/assets/QSelect-PP69fnCX.js +36 -0
  34. package/src/client/dist/spa/assets/{use-id-By86THzm.js → QSeparator-Vt0kN1I8.js} +1 -1
  35. package/src/client/dist/spa/assets/QSpace-e68xsYE1.js +1 -0
  36. package/src/client/dist/spa/assets/{QSpinnerDots-atHe_AUn.js → QSpinnerDots-BOryc_9y.js} +1 -1
  37. package/src/client/dist/spa/assets/QTooltip-Brh5sChu.js +1 -0
  38. package/src/client/dist/spa/assets/SearchPage-BcXygFyE.js +1 -0
  39. package/src/client/dist/spa/assets/SettingsPage-Co97HPUU.js +16 -0
  40. package/src/client/dist/spa/assets/SettingsPage-DfYhuzv0.css +1 -0
  41. package/src/client/dist/spa/assets/TouchPan-BfqX_pZK.js +1 -0
  42. package/src/client/dist/spa/assets/{WorkspacePage-36QGRRCt.css → WorkspacePage-Bo6aKwl_.css} +1 -1
  43. package/src/client/dist/spa/assets/WorkspacePage-e-ktRS-M.js +4 -0
  44. package/src/client/dist/spa/assets/build-path-tree-D7rqJH7g.js +1 -0
  45. package/src/client/dist/spa/assets/chunk-QTnfLwEv.js +1 -0
  46. package/src/client/dist/spa/assets/{css.worker-BtW9exzf.js → css.worker-CBlhdqTa.js} +31 -31
  47. package/src/client/dist/spa/assets/{cssMode-DfDzxSXu.js → cssMode-DwJTukMi.js} +1 -1
  48. package/src/client/dist/spa/assets/documents-eaoEREpp.js +1 -0
  49. package/src/client/dist/spa/assets/{editor.api2-Byu6kw7p.js → editor.api2-CCQ74UFa.js} +163 -163
  50. package/src/client/dist/spa/assets/{editor.main-VujCgCtA.js → editor.main-sdLzVGjZ.js} +2 -2
  51. package/src/client/dist/spa/assets/editor.worker-Cu3tR8iJ.js +26 -0
  52. package/src/client/dist/spa/assets/expand-template-DTSWVfFm.js +1 -0
  53. package/src/client/dist/spa/assets/formatters-DnqeH9c3.js +1 -0
  54. package/src/client/dist/spa/assets/{freemarker2-Ce85qzU_.js → freemarker2-DnJdVbFY.js} +1 -1
  55. package/src/client/dist/spa/assets/{handlebars-BLTKFzzh.js → handlebars-CQ9FsB3q.js} +1 -1
  56. package/src/client/dist/spa/assets/{html-Brx-Keq8.js → html-DbwSidb2.js} +1 -1
  57. package/src/client/dist/spa/assets/{html.worker-Dg1SpGQ4.js → html.worker-BKBrNna1.js} +24 -24
  58. package/src/client/dist/spa/assets/{htmlMode-Cn-qtgzd.js → htmlMode-TsbX8xv5.js} +1 -1
  59. package/src/client/dist/spa/assets/i18n-CrwtYRcs.js +1 -0
  60. package/src/client/dist/spa/assets/index-UPQqj74q.js +84 -0
  61. package/src/client/dist/spa/assets/{javascript-ywvG3uUF.js → javascript-CdAxNn6v.js} +1 -1
  62. package/src/client/dist/spa/assets/json.worker-BaLYt9Ss.js +58 -0
  63. package/src/client/dist/spa/assets/{jsonMode-DjYQF9SH.js → jsonMode-DsX17jzq.js} +1 -1
  64. package/src/client/dist/spa/assets/kobo-commands-Cu30N0Ht.js +9 -0
  65. package/src/client/dist/spa/assets/layout-DOF1L6Vf.js +1 -0
  66. package/src/client/dist/spa/assets/{liquid-CZjmNn4a.js → liquid-Bc7-UBG5.js} +1 -1
  67. package/src/client/dist/spa/assets/{lspLanguageFeatures-DJiFF8Wc.js → lspLanguageFeatures-yZgUujPG.js} +1 -1
  68. package/src/client/dist/spa/assets/{mdx-DPvLr_Xj.js → mdx-BvDiub4z.js} +1 -1
  69. package/src/client/dist/spa/assets/{monaco.contribution-rZ1avSXN.js → monaco.contribution-CFTqxJMP.js} +2 -2
  70. package/src/client/dist/spa/assets/network-auth-DtdN0iy_.js +1 -0
  71. package/src/client/dist/spa/assets/{permissionModes-CJN6Olox.js → permissionModes-CUZkcBev.js} +1 -1
  72. package/src/client/dist/spa/assets/{python-agZmzvad.js → python-DZZeQhwZ.js} +1 -1
  73. package/src/client/dist/spa/assets/{razor-D-6K3O2-.js → razor-RaxEa4MG.js} +1 -1
  74. package/src/client/dist/spa/assets/render-chat-markdown-D1XyjSW1.js +66 -0
  75. package/src/client/dist/spa/assets/{ts.worker-DI5g4t5j.js → ts.worker-PmaSgaZk.js} +185 -170
  76. package/src/client/dist/spa/assets/{tsMode-BxYiDnB8.js → tsMode-C6gZAb22.js} +1 -1
  77. package/src/client/dist/spa/assets/{typescript-Cv7_rzaO.js → typescript-BCAqEI1V.js} +1 -1
  78. package/src/client/dist/spa/assets/{use-checkbox-BnkSQgTJ.js → use-checkbox-DE50asz4.js} +1 -1
  79. package/src/client/dist/spa/assets/{use-onboarding-Dy-3eSVP.js → use-onboarding-gBmXW7wm.js} +2 -2
  80. package/src/client/dist/spa/assets/use-quasar-Dyujo9Ue.js +1 -0
  81. package/src/client/dist/spa/assets/{vue.runtime.esm-bundler-BAtKyT0Y.js → vue.runtime.esm-bundler-JZnIeD9D.js} +2 -2
  82. package/src/client/dist/spa/assets/{workers-JYdpxrSy.js → workers-okv2EabB.js} +1 -1
  83. package/src/client/dist/spa/assets/{xml-CshFLvoE.js → xml-iWTTgx9j.js} +1 -1
  84. package/src/client/dist/spa/assets/{yaml-DL8TzVD_.js → yaml-am8-T4BQ.js} +1 -1
  85. package/src/client/dist/spa/index.html +7 -13
  86. package/src/client/dist/spa/assets/ActivityFeed-Y1RkIPW1.js +0 -8
  87. package/src/client/dist/spa/assets/ChangelogPage-DcPmypaS.js +0 -1
  88. package/src/client/dist/spa/assets/ClosePopup-DD10nToj.js +0 -1
  89. package/src/client/dist/spa/assets/CreatePage-DO_pMGS3.js +0 -2
  90. package/src/client/dist/spa/assets/DiffViewer-DRvbmHwK.js +0 -8
  91. package/src/client/dist/spa/assets/HealthPage-BvOR0B7B.js +0 -1
  92. package/src/client/dist/spa/assets/MainLayout-M5TW3GL1.js +0 -37
  93. package/src/client/dist/spa/assets/QBtn-DwemGTZv.js +0 -1
  94. package/src/client/dist/spa/assets/QCheckbox-o3UHW596.js +0 -1
  95. package/src/client/dist/spa/assets/QChip-BpS8c1sW.js +0 -1
  96. package/src/client/dist/spa/assets/QExpansionItem-C9vmJqEO.js +0 -1
  97. package/src/client/dist/spa/assets/QInput-CLZtb8E0.js +0 -1
  98. package/src/client/dist/spa/assets/QItemLabel-BYSjzk-t.js +0 -1
  99. package/src/client/dist/spa/assets/QItemSection-O9WBXftL.js +0 -1
  100. package/src/client/dist/spa/assets/QMenu-D_9kEp2i.js +0 -1
  101. package/src/client/dist/spa/assets/QRadio-B_TurTzx.js +0 -1
  102. package/src/client/dist/spa/assets/QScrollArea-B5jf9S4y.js +0 -1
  103. package/src/client/dist/spa/assets/QScrollObserver-Cxj52Zfg.js +0 -1
  104. package/src/client/dist/spa/assets/QSelect-DgGpVy88.js +0 -36
  105. package/src/client/dist/spa/assets/QSpace-CrVsndpV.js +0 -1
  106. package/src/client/dist/spa/assets/QToggle-Dwr3hSLw.js +0 -1
  107. package/src/client/dist/spa/assets/QTooltip-CyRLTG6i.js +0 -1
  108. package/src/client/dist/spa/assets/SearchPage-Qc2SXUjf.js +0 -1
  109. package/src/client/dist/spa/assets/SettingsPage-B7H6sD7r.css +0 -1
  110. package/src/client/dist/spa/assets/SettingsPage-G4xXwNwc.js +0 -9
  111. package/src/client/dist/spa/assets/TouchPan-BmfIMD00.js +0 -1
  112. package/src/client/dist/spa/assets/WorkspacePage-DeGm-XQp.js +0 -4
  113. package/src/client/dist/spa/assets/build-path-tree-D4_LR3mz.js +0 -1
  114. package/src/client/dist/spa/assets/chunk-DtRyYLXJ.js +0 -1
  115. package/src/client/dist/spa/assets/documents-N8PwB_Gh.js +0 -1
  116. package/src/client/dist/spa/assets/editor.worker-DWlYVeeX.js +0 -26
  117. package/src/client/dist/spa/assets/engineFeatures-6A3KLQRO.js +0 -1
  118. package/src/client/dist/spa/assets/expand-template-CRHCd-I8.js +0 -1
  119. package/src/client/dist/spa/assets/formatters-wq5wP2If.js +0 -1
  120. package/src/client/dist/spa/assets/i18n-Cl6Gt8Eh.js +0 -1
  121. package/src/client/dist/spa/assets/index-CyseQlfR.js +0 -82
  122. package/src/client/dist/spa/assets/json.worker-CCzEOxDx.js +0 -58
  123. package/src/client/dist/spa/assets/kobo-commands-BSkMs22T.js +0 -9
  124. package/src/client/dist/spa/assets/notifications-C4MxuXC7.js +0 -1
  125. package/src/client/dist/spa/assets/render-chat-markdown-8LO2n7Of.js +0 -66
  126. package/src/client/dist/spa/assets/touch-yfnu5R3D.js +0 -1
  127. package/src/client/dist/spa/assets/use-quasar-DcJRs0ay.js +0 -1
  128. package/src/client/dist/spa/assets/vue-i18n-C5Tx4bGk.js +0 -3
  129. /package/src/client/dist/spa/assets/{_plugin-vue_export-helper-BzmG9fMN.js → _plugin-vue_export-helper-BDNMzG2s.js} +0 -0
  130. /package/src/client/dist/spa/assets/{abap-DiwvWnMr.js → abap-08VXUWAP.js} +0 -0
  131. /package/src/client/dist/spa/assets/{apex-CmtZjKlf.js → apex-BWPQTe0t.js} +0 -0
  132. /package/src/client/dist/spa/assets/{azcli-DL2My_i-.js → azcli-Bc_sGQ0U.js} +0 -0
  133. /package/src/client/dist/spa/assets/{bat-B-nC98wG.js → bat-i0X4ZdIN.js} +0 -0
  134. /package/src/client/dist/spa/assets/{bicep-Ju5MwOgh.js → bicep-B5-_aFwp.js} +0 -0
  135. /package/src/client/dist/spa/assets/{cameligo-8Eu1TyBr.js → cameligo-DMUM7wLl.js} +0 -0
  136. /package/src/client/dist/spa/assets/{clojure-u-RpMkH3.js → clojure-Cm7r79vr.js} +0 -0
  137. /package/src/client/dist/spa/assets/{coffee-CdA7bbTe.js → coffee-Ba7i2nA0.js} +0 -0
  138. /package/src/client/dist/spa/assets/{cpp-CzNFP8ks.js → cpp-C7h46wYY.js} +0 -0
  139. /package/src/client/dist/spa/assets/{csharp-j1LThmcE.js → csharp-BKxtCVv1.js} +0 -0
  140. /package/src/client/dist/spa/assets/{csp-CLRC61y6.js → csp-bTuwJoIa.js} +0 -0
  141. /package/src/client/dist/spa/assets/{css-r6rC_7P2.js → css-DIMkf-bt.js} +0 -0
  142. /package/src/client/dist/spa/assets/{cypher-CW08XVUh.js → cypher-CVaqCwHa.js} +0 -0
  143. /package/src/client/dist/spa/assets/{dart-Cs9aL5T_.js → dart-onAF5SnQ.js} +0 -0
  144. /package/src/client/dist/spa/assets/{dockerfile-BWM0M184.js → dockerfile-DZFCIeNp.js} +0 -0
  145. /package/src/client/dist/spa/assets/{ecl-MJJuer5P.js → ecl-D05T4iGw.js} +0 -0
  146. /package/src/client/dist/spa/assets/{elixir-D2AIuXqn.js → elixir-6RTg0lbw.js} +0 -0
  147. /package/src/client/dist/spa/assets/{flow9-B2H24giC.js → flow9-C5_-GSwl.js} +0 -0
  148. /package/src/client/dist/spa/assets/{fsharp-CFNadkg7.js → fsharp-C8Ef5oNN.js} +0 -0
  149. /package/src/client/dist/spa/assets/{go-dSur1iB2.js → go-C-y9NEjX.js} +0 -0
  150. /package/src/client/dist/spa/assets/{graphql-qyhAo11d.js → graphql-fmXr3nnJ.js} +0 -0
  151. /package/src/client/dist/spa/assets/{hcl-DFzjMyzm.js → hcl-CpzslTdj.js} +0 -0
  152. /package/src/client/dist/spa/assets/{ini-TdzA8TIl.js → ini-sBoK_t0W.js} +0 -0
  153. /package/src/client/dist/spa/assets/{java-CSGA9pkE.js → java-BEtHBSE6.js} +0 -0
  154. /package/src/client/dist/spa/assets/{julia-9izz5OsY.js → julia-Bri6UV-V.js} +0 -0
  155. /package/src/client/dist/spa/assets/{kotlin-DIUPrqKg.js → kotlin-BOotOW0E.js} +0 -0
  156. /package/src/client/dist/spa/assets/{less-B8d93iCg.js → less-B9JPFI3C.js} +0 -0
  157. /package/src/client/dist/spa/assets/{lexon-DWtEIyu7.js → lexon-CfSJPG6W.js} +0 -0
  158. /package/src/client/dist/spa/assets/{lua-Ciq0OGgt.js → lua-CsQS60Ue.js} +0 -0
  159. /package/src/client/dist/spa/assets/{m3-Cki6JWj_.js → m3-D-oSqn_W.js} +0 -0
  160. /package/src/client/dist/spa/assets/{markdown-Cu47xwU0.js → markdown-Cimd5fb3.js} +0 -0
  161. /package/src/client/dist/spa/assets/{mips-BM8ui995.js → mips-CIPQ_RoX.js} +0 -0
  162. /package/src/client/dist/spa/assets/{msdax-DqLio0_c.js → msdax-DauUninz.js} +0 -0
  163. /package/src/client/dist/spa/assets/{mysql-v1wbjJOq.js → mysql-SOo6toE5.js} +0 -0
  164. /package/src/client/dist/spa/assets/{objective-c-CQl3PGSB.js → objective-c-FvmIjYaQ.js} +0 -0
  165. /package/src/client/dist/spa/assets/{pascal-D4iW0ZtD.js → pascal-DrH0SRf2.js} +0 -0
  166. /package/src/client/dist/spa/assets/{pascaligo-BdC9CZdj.js → pascaligo-D-ptJ9y-.js} +0 -0
  167. /package/src/client/dist/spa/assets/{perl-BL10m4XD.js → perl-oz_6vUea.js} +0 -0
  168. /package/src/client/dist/spa/assets/{pgsql-Be_oqVo3.js → pgsql-DTj74zXo.js} +0 -0
  169. /package/src/client/dist/spa/assets/{php-BtvXSFRI.js → php-nr791fC2.js} +0 -0
  170. /package/src/client/dist/spa/assets/{pla-B2vUy15C.js → pla-CopQ2nXW.js} +0 -0
  171. /package/src/client/dist/spa/assets/{postiats-CbmTTfXr.js → postiats-43DmfD33.js} +0 -0
  172. /package/src/client/dist/spa/assets/{powerquery-DszLhJGx.js → powerquery-D3hlyOfw.js} +0 -0
  173. /package/src/client/dist/spa/assets/{powershell-B0dYktF6.js → powershell-DmHpPYUd.js} +0 -0
  174. /package/src/client/dist/spa/assets/{protobuf-CZvaj1VX.js → protobuf-C531GsRP.js} +0 -0
  175. /package/src/client/dist/spa/assets/{pug-CPDx1B3S.js → pug-Z5eAx3Zn.js} +0 -0
  176. /package/src/client/dist/spa/assets/{qsharp-CDP9TFLl.js → qsharp-DkqhCAOL.js} +0 -0
  177. /package/src/client/dist/spa/assets/{r-8DbbFX2l.js → r-BwWrilGY.js} +0 -0
  178. /package/src/client/dist/spa/assets/{redis-DRWj9MtJ.js → redis-ClamHrr6.js} +0 -0
  179. /package/src/client/dist/spa/assets/{redshift-C6cElE_5.js → redshift-DT7zqm-g.js} +0 -0
  180. /package/src/client/dist/spa/assets/{restructuredtext-W9pS9n3m.js → restructuredtext-BYgofb2h.js} +0 -0
  181. /package/src/client/dist/spa/assets/{ruby-BKnzWnk-.js → ruby-DezsRK8O.js} +0 -0
  182. /package/src/client/dist/spa/assets/{rust-YPCclWwe.js → rust-DdL9SqIa.js} +0 -0
  183. /package/src/client/dist/spa/assets/{sb-BgM4DTFb.js → sb-CcwsVR0C.js} +0 -0
  184. /package/src/client/dist/spa/assets/{scala-fz1OPLMl.js → scala-DHpiXF5c.js} +0 -0
  185. /package/src/client/dist/spa/assets/{scheme-8Uz1RIbu.js → scheme-BeGwcela.js} +0 -0
  186. /package/src/client/dist/spa/assets/{scss-Djo3IYXr.js → scss-gp-XZpBa.js} +0 -0
  187. /package/src/client/dist/spa/assets/{shell-CINF5Tx_.js → shell-CC2rA5mh.js} +0 -0
  188. /package/src/client/dist/spa/assets/{solidity-GgiNEuUm.js → solidity-BEEn4gHE.js} +0 -0
  189. /package/src/client/dist/spa/assets/{sophia-Culj97P9.js → sophia-CRfGWb83.js} +0 -0
  190. /package/src/client/dist/spa/assets/{sparql-C2ZlpxOY.js → sparql-D_Lu-MrJ.js} +0 -0
  191. /package/src/client/dist/spa/assets/{sql-BEf5Pg7Y.js → sql-NEE52Syq.js} +0 -0
  192. /package/src/client/dist/spa/assets/{st-CT6UUoeH.js → st-DbInun42.js} +0 -0
  193. /package/src/client/dist/spa/assets/{swift-B5g0xTG3.js → swift-Bxkupp3x.js} +0 -0
  194. /package/src/client/dist/spa/assets/{systemverilog-CEgQz9DR.js → systemverilog-Bz4Y3fRF.js} +0 -0
  195. /package/src/client/dist/spa/assets/{tcl-D0qL2L0I.js → tcl-DISqw1ZD.js} +0 -0
  196. /package/src/client/dist/spa/assets/{twig-BFUAVf1E.js → twig-De2hgUGE.js} +0 -0
  197. /package/src/client/dist/spa/assets/{typespec-CjVVcNKm.js → typespec-B8J7ngcE.js} +0 -0
  198. /package/src/client/dist/spa/assets/{vb-CZJr-DQz.js → vb-DV3o63ZY.js} +0 -0
  199. /package/src/client/dist/spa/assets/{wgsl-ivoXUo2e.js → wgsl-DpFanUEy.js} +0 -0
@@ -1,5 +1,7 @@
1
1
  import { Hono } from 'hono';
2
+ import { getBackendPort } from '../services/agent/orchestrator.js';
2
3
  import { DEFAULT_NOTION_INITIAL_PROMPT, DEFAULT_SENTRY_INITIAL_PROMPT, } from '../services/initial-prompt-template-service.js';
4
+ import { generateToken, getLanUrls } from '../services/network-access-service.js';
3
5
  import { DEFAULT_REVIEW_PROMPT_TEMPLATE } from '../services/review-template-service.js';
4
6
  import { DEFAULT_CHANGE_SOURCE_BRANCH_SCRIPT } from '../services/settings-defaults.js';
5
7
  import * as settingsService from '../services/settings-service.js';
@@ -45,6 +47,53 @@ app.get('/defaults', (c) => {
45
47
  changeSourceBranchScript: DEFAULT_CHANGE_SOURCE_BRANCH_SCRIPT,
46
48
  });
47
49
  });
50
+ // GET /api/settings/network — network access state + LAN URLs
51
+ app.get('/network', (c) => {
52
+ try {
53
+ const global = settingsService.getGlobalSettings();
54
+ return c.json({
55
+ enabled: global.networkAccessEnabled,
56
+ token: global.networkAccessToken,
57
+ urls: getLanUrls(getBackendPort()),
58
+ });
59
+ }
60
+ catch (err) {
61
+ const message = err instanceof Error ? err.message : String(err);
62
+ return c.json({ error: message }, 500);
63
+ }
64
+ });
65
+ // GET /api/settings/network/ping — token validation probe (behind the gate)
66
+ app.get('/network/ping', (c) => c.json({ ok: true }));
67
+ // POST /api/settings/network — toggle enabled / regenerate token
68
+ app.post('/network', async (c) => {
69
+ try {
70
+ const body = await c.req.json();
71
+ const current = settingsService.getGlobalSettings();
72
+ const patch = {};
73
+ let restartRequired = false;
74
+ if (typeof body.enabled === 'boolean' && body.enabled !== current.networkAccessEnabled) {
75
+ patch.networkAccessEnabled = body.enabled;
76
+ restartRequired = true;
77
+ if (body.enabled && !current.networkAccessToken) {
78
+ patch.networkAccessToken = generateToken();
79
+ }
80
+ }
81
+ if (body.regenerate) {
82
+ patch.networkAccessToken = generateToken();
83
+ }
84
+ const updated = settingsService.updateNetworkAccessSettings(patch);
85
+ return c.json({
86
+ enabled: updated.networkAccessEnabled,
87
+ token: updated.networkAccessToken,
88
+ urls: getLanUrls(getBackendPort()),
89
+ restartRequired,
90
+ });
91
+ }
92
+ catch (err) {
93
+ const message = err instanceof Error ? err.message : String(err);
94
+ return c.json({ error: message }, 500);
95
+ }
96
+ });
48
97
  // GET /api/settings/mcp-servers — list active MCP servers from Claude config
49
98
  app.get('/mcp-servers', (c) => {
50
99
  try {
@@ -2660,6 +2660,23 @@ app.post('/:id/push', async (c) => {
2660
2660
  return c.json({ error: message }, 500);
2661
2661
  }
2662
2662
  });
2663
+ // POST /api/workspaces/:id/fetch — git fetch the workspace repo (all branches of origin).
2664
+ // Read-only: updates remote-tracking refs, never touches the working tree.
2665
+ app.post('/:id/fetch', (c) => {
2666
+ try {
2667
+ const id = c.req.param('id');
2668
+ const workspace = workspaceService.getWorkspace(id);
2669
+ if (!workspace) {
2670
+ return c.json({ error: `Workspace '${id}' not found` }, 404);
2671
+ }
2672
+ gitOps.fetchAllBranches(workspace.worktreePath);
2673
+ return c.json({ ok: true });
2674
+ }
2675
+ catch (err) {
2676
+ const message = err instanceof Error ? err.message : String(err);
2677
+ return c.json({ error: message }, 500);
2678
+ }
2679
+ });
2663
2680
  // POST /api/workspaces/:id/pull — pull working branch from origin (fast-forward only)
2664
2681
  app.post('/:id/pull', (c) => {
2665
2682
  try {
@@ -22,6 +22,10 @@ let backendPort = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : 300
22
22
  export function setBackendPort(port) {
23
23
  backendPort = port;
24
24
  }
25
+ /** Current bound port of the running backend. */
26
+ export function getBackendPort() {
27
+ return backendPort;
28
+ }
25
29
  /** workspaceId -> SessionController */
26
30
  const controllers = new Map();
27
31
  /** workspaceId -> last engine session ID (for resume) */
@@ -0,0 +1,67 @@
1
+ import crypto from 'node:crypto';
2
+ import os from 'node:os';
3
+ const LOOPBACK_ADDRESSES = new Set(['127.0.0.1', '::1', '::ffff:127.0.0.1']);
4
+ /** True for loopback remote addresses. Undefined → false (deny-safe). */
5
+ export function isLoopbackAddress(address) {
6
+ if (!address)
7
+ return false;
8
+ return LOOPBACK_ADDRESSES.has(address);
9
+ }
10
+ /** Bind host for `serve()`: localhost-only when disabled, all interfaces when enabled. */
11
+ export function resolveBindHost(enabled) {
12
+ return enabled ? undefined : '127.0.0.1';
13
+ }
14
+ /** Non-internal IPv4 URLs for the running server, for display + QR. */
15
+ export function getLanUrls(port) {
16
+ const urls = [];
17
+ for (const infos of Object.values(os.networkInterfaces())) {
18
+ if (!infos)
19
+ continue;
20
+ for (const info of infos) {
21
+ if (info.family === 'IPv4' && !info.internal) {
22
+ urls.push(`http://${info.address}:${port}`);
23
+ }
24
+ }
25
+ }
26
+ return urls;
27
+ }
28
+ /** ~32-char url-safe random token. */
29
+ export function generateToken() {
30
+ return crypto.randomBytes(24).toString('base64url');
31
+ }
32
+ /** Constant-time token comparison; false on empty/length mismatch (never throws). */
33
+ export function tokenMatches(provided, expected) {
34
+ if (!provided || !expected)
35
+ return false;
36
+ const a = Buffer.from(provided);
37
+ const b = Buffer.from(expected);
38
+ if (a.length !== b.length)
39
+ return false;
40
+ return crypto.timingSafeEqual(a, b);
41
+ }
42
+ /** Core gate decision shared by the HTTP middleware and the WS upgrade guard. */
43
+ export function evaluateNetworkAccess(params) {
44
+ if (isLoopbackAddress(params.address))
45
+ return { allow: true, status: 200 };
46
+ if (!params.enabled)
47
+ return { allow: false, status: 403 };
48
+ if (tokenMatches(params.providedToken, params.expectedToken))
49
+ return { allow: true, status: 200 };
50
+ return { allow: false, status: 401 };
51
+ }
52
+ /** WS upgrade authorization: parses `?token=` from the raw URL. */
53
+ export function authorizeWsUpgrade(params) {
54
+ let providedToken;
55
+ try {
56
+ providedToken = new URL(params.rawUrl ?? '/', 'http://localhost').searchParams.get('token') ?? undefined;
57
+ }
58
+ catch {
59
+ providedToken = undefined;
60
+ }
61
+ return evaluateNetworkAccess({
62
+ address: params.address,
63
+ enabled: params.enabled,
64
+ expectedToken: params.expectedToken,
65
+ providedToken,
66
+ }).allow;
67
+ }
@@ -629,6 +629,31 @@ const settingsMigrations = [
629
629
  }
630
630
  },
631
631
  },
632
+ {
633
+ version: 39,
634
+ name: 'add-network-access',
635
+ migrate: ({ global }) => {
636
+ // Opt-in LAN access with token auth. Disabled by default (localhost-only
637
+ // bind). Token is generated lazily on first enable via POST /network.
638
+ if (typeof global.networkAccessEnabled !== 'boolean') {
639
+ global.networkAccessEnabled = false;
640
+ }
641
+ if (typeof global.networkAccessToken !== 'string') {
642
+ global.networkAccessToken = '';
643
+ }
644
+ },
645
+ },
646
+ {
647
+ version: 40,
648
+ name: 'add-question-notification-sound',
649
+ migrate: ({ global }) => {
650
+ // Distinct sound played when the agent asks a question. Defaults to 'hey.mp3'
651
+ // so questions are audibly distinct from a customised task-done sound.
652
+ if (typeof global.audioQuestionSound !== 'string' || global.audioQuestionSound.length === 0) {
653
+ global.audioQuestionSound = 'hey.mp3';
654
+ }
655
+ },
656
+ },
632
657
  ];
633
658
  /** Current settings schema version — always equals the highest migration version. */
634
659
  export const SETTINGS_SCHEMA_VERSION = settingsMigrations.length > 0 ? settingsMigrations[settingsMigrations.length - 1].version : 0;
@@ -675,9 +700,12 @@ function defaultSettings() {
675
700
  fileManagerCommand: '',
676
701
  terminalCommand: '',
677
702
  autoPurgeOnPrMerged: false,
703
+ networkAccessEnabled: false,
704
+ networkAccessToken: '',
678
705
  browserNotifications: true,
679
706
  audioNotifications: true,
680
707
  audioNotificationSound: 'hey.mp3',
708
+ audioQuestionSound: 'hey.mp3',
681
709
  audioNotificationVolume: 1,
682
710
  notionStatusProperty: '',
683
711
  notionInProgressStatus: '',
@@ -867,7 +895,7 @@ export function getSettings() {
867
895
  return readSettings();
868
896
  }
869
897
  /** Keys stripped from exports — secrets that should stay on the machine. */
870
- const SECRET_GLOBAL_KEYS = ['notionMcpKey', 'sentryMcpKey'];
898
+ const SECRET_GLOBAL_KEYS = ['notionMcpKey', 'sentryMcpKey', 'networkAccessToken'];
871
899
  /** Build an export bundle with settings + templates. MCP keys are stripped. */
872
900
  export function exportConfigBundle(templates) {
873
901
  const settings = readSettings();
@@ -1033,6 +1061,7 @@ export function updateGlobalSettings(data) {
1033
1061
  'browserNotifications',
1034
1062
  'audioNotifications',
1035
1063
  'audioNotificationSound',
1064
+ 'audioQuestionSound',
1036
1065
  'audioNotificationVolume',
1037
1066
  'notionStatusProperty',
1038
1067
  'notionInProgressStatus',
@@ -1093,6 +1122,26 @@ export function updateGlobalSettings(data) {
1093
1122
  writeSettings(settings, { backup: true });
1094
1123
  return settings.global;
1095
1124
  }
1125
+ /**
1126
+ * Persist network-access settings (enabled flag and/or token) directly.
1127
+ *
1128
+ * Network access is managed exclusively through this path (POST /api/settings/network),
1129
+ * never the generic `updateGlobalSettings` allowlist: `networkAccessToken` is a secret
1130
+ * (kept out of the allowlist so a config import can't inject one) and `networkAccessEnabled`
1131
+ * is kept out too so it can't be flipped on via PUT /global without an accompanying token
1132
+ * (which would leave the server bound wide but unauthenticatable after a restart).
1133
+ */
1134
+ export function updateNetworkAccessSettings(patch) {
1135
+ const settings = readSettings();
1136
+ if (typeof patch.networkAccessEnabled === 'boolean') {
1137
+ settings.global.networkAccessEnabled = patch.networkAccessEnabled;
1138
+ }
1139
+ if (typeof patch.networkAccessToken === 'string') {
1140
+ settings.global.networkAccessToken = patch.networkAccessToken;
1141
+ }
1142
+ writeSettings(settings, { backup: true });
1143
+ return settings.global;
1144
+ }
1096
1145
  function ensureGlobalWorktreesRootExists(worktreesPath) {
1097
1146
  const root = resolveGlobalWorktreesRoot(worktreesPath);
1098
1147
  if (!root || isNonNativeWindowsPath(root))
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { getTemplatesPath } from '../utils/paths.js';
4
- const CURRENT_FILE_VERSION = 1;
4
+ const CURRENT_FILE_VERSION = 2;
5
5
  const SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
6
6
  const MAX_CONTENT_LENGTH = 4096;
7
7
  const MAX_DESCRIPTION_LENGTH = 120;
@@ -117,10 +117,23 @@ function validateTemplateInput(input) {
117
117
  throw new Error(`Invalid content: must be 1..${MAX_CONTENT_LENGTH} chars`);
118
118
  }
119
119
  }
120
- function writeTemplates(templates) {
120
+ function readSeededSlugs() {
121
+ const filePath = getTemplatesPath();
122
+ if (!existsSync(filePath))
123
+ return undefined;
124
+ try {
125
+ const parsed = JSON.parse(readFileSync(filePath, 'utf-8'));
126
+ return Array.isArray(parsed.seededDefaultSlugs) ? parsed.seededDefaultSlugs : undefined;
127
+ }
128
+ catch {
129
+ return undefined;
130
+ }
131
+ }
132
+ function writeTemplates(templates, seededDefaultSlugs) {
121
133
  const filePath = getTemplatesPath();
122
134
  mkdirSync(path.dirname(filePath), { recursive: true });
123
- const file = { version: CURRENT_FILE_VERSION, templates };
135
+ const seeded = seededDefaultSlugs ?? readSeededSlugs() ?? [...LEGACY_DEFAULT_SLUGS];
136
+ const file = { version: CURRENT_FILE_VERSION, templates, seededDefaultSlugs: seeded };
124
137
  writeFileSync(filePath, JSON.stringify(file, null, 2), 'utf-8');
125
138
  }
126
139
  /**
@@ -154,6 +167,24 @@ export function replaceAllTemplates(templates) {
154
167
  }
155
168
  writeTemplates(validated);
156
169
  }
170
+ /**
171
+ * The default slugs that existed BEFORE the seededDefaultSlugs watermark shipped.
172
+ * Bootstraps the watermark for pre-watermark installs so a default the user deleted
173
+ * earlier is not re-added on the first boot. Frozen historical snapshot; never change it.
174
+ */
175
+ const LEGACY_DEFAULT_SLUGS = [
176
+ 'kobo-context',
177
+ 'review-quality',
178
+ 'add-tests',
179
+ 'explain',
180
+ 'refactor',
181
+ 'plan-tasks',
182
+ 'show-tasks',
183
+ 'mark-done',
184
+ 'sync-tasks',
185
+ 'pr-review-comments',
186
+ 'ci-status',
187
+ ];
157
188
  export const DEFAULT_TEMPLATES = [
158
189
  {
159
190
  slug: 'kobo-context',
@@ -192,7 +223,9 @@ export const DEFAULT_TEMPLATES = [
192
223
  `# Boundaries\n` +
193
224
  `- The user owns the \`description\` field of the workspace — never write it; you only own \`agent_description\`\n` +
194
225
  `- The user can interrupt you at any time via the chat; treat their messages as authoritative redirections\n` +
195
- `- Auto-loop is automatically disabled if the user sends a chat message during a loop — they'll re-enable it manually after\n`,
226
+ `- Auto-loop is automatically disabled if the user sends a chat message during a loop — they'll re-enable it manually after\n` +
227
+ `\n# Decision support\n` +
228
+ `For a high-stakes engineering decision with no obvious answer (architecture choice, risky refactor, a real tradeoff), run a "council": the \`/council\` template spawns 5 sub-agent advisors with distinct lenses, peer-reviews them, and synthesises a verdict into \`.ai/thoughts/\`. It costs ~11 sub-agent calls, so use it sparingly.\n`,
196
229
  },
197
230
  {
198
231
  slug: 'review-quality',
@@ -244,28 +277,64 @@ export const DEFAULT_TEMPLATES = [
244
277
  description: 'Check GitHub Actions status on PR',
245
278
  content: 'Check the CI/CD status for the pull request on branch {working_branch}.\n\nIf a PR exists (PR {pr_url}):\n1. Use the GitHub MCP tools to list the check runs / status checks on the latest commit of the PR\n2. For each check, report:\n - Check name\n - Status (queued, in_progress, completed)\n - Conclusion (success, failure, neutral, skipped, etc.)\n - Duration if available\n3. If any checks failed, fetch the logs or annotations and summarize what went wrong\n4. Give an overall summary: all green, some failing, or still running\n\nIf no PR exists, say so and suggest creating one first.',
246
279
  },
280
+ {
281
+ slug: 'council',
282
+ description: 'Pressure-test a high-stakes decision with 5 sub-agent advisors',
283
+ content: `Run a "council" to pressure-test a high-stakes decision for this workspace ("{workspace_name}").\n\n` +
284
+ `The decision: take what the user described in this message. If they gave none, ask for it in one line and stop.\n\n` +
285
+ `You orchestrate this with your own sub-agents:\n\n` +
286
+ `1. Spawn 5 sub-agents IN PARALLEL, one per lens. Each answers the decision independently in 150-300 words, fully in its lens, no hedging, no false balance:\n` +
287
+ ` - Contrarian: hunt the fatal flaw; what fails, what's missing.\n` +
288
+ ` - First Principles: ignore the surface question; what are we really solving? Is this even the right question?\n` +
289
+ ` - Expansionist: the upside everyone misses; what if it works better than expected?\n` +
290
+ ` - Outsider: zero context; react only to what's stated; catch the curse of knowledge.\n` +
291
+ ` - Executor: can it be done, and what's the fastest first step Monday morning?\n\n` +
292
+ `2. Anonymise the 5 answers as A-E (randomised). Spawn 5 reviewer sub-agents; each names the strongest answer, the biggest blind spot, and what ALL of them missed. Under 200 words each.\n\n` +
293
+ `3. Synthesise the verdict yourself:\n` +
294
+ ` - Where the council agrees (high-confidence signals).\n` +
295
+ ` - Where it clashes (present both sides honestly).\n` +
296
+ ` - Blind spots the review caught.\n` +
297
+ ` - One clear recommendation, not "it depends".\n` +
298
+ ` - The one thing to do first.\n\n` +
299
+ `4. Post the verdict in chat, and save the full transcript (decision, 5 answers, 5 reviews, verdict) to \`.ai/thoughts/council-<short-topic-slug>.md\`.\n\n` +
300
+ `Cost: this runs ~11 sub-agent calls. Use it only for decisions that are expensive to get wrong, not routine choices.\n`,
301
+ },
247
302
  ];
248
303
  function seedTemplates() {
249
304
  const now = new Date().toISOString();
250
305
  const seed = DEFAULT_TEMPLATES.map((t) => ({ ...t, createdAt: now, updatedAt: now }));
251
- writeTemplates(seed);
306
+ writeTemplates(seed, DEFAULT_TEMPLATES.map((t) => t.slug));
252
307
  }
308
+ /**
309
+ * Add any default whose slug was never seeded into this install AND is not already
310
+ * present. Each default seeds at most once (tracked in `seededDefaultSlugs`), so a
311
+ * default the user deleted does not come back. Never overwrites an existing template.
312
+ */
253
313
  export function reloadDefaultTemplates() {
254
314
  const existing = listTemplates();
255
- const existingBySlug = new Map(existing.map((t) => [t.slug, t]));
315
+ const existingBySlug = new Set(existing.map((t) => t.slug));
316
+ const rawSeeded = readSeededSlugs();
317
+ const seeded = new Set(rawSeeded ?? LEGACY_DEFAULT_SLUGS);
256
318
  const now = new Date().toISOString();
257
319
  const added = [];
258
320
  const kept = [];
259
321
  const next = [...existing];
260
322
  for (const def of DEFAULT_TEMPLATES) {
261
- if (existingBySlug.has(def.slug)) {
323
+ if (seeded.has(def.slug)) {
262
324
  kept.push(def.slug);
263
325
  continue;
264
326
  }
265
- next.push({ ...def, createdAt: now, updatedAt: now });
266
- added.push(def.slug);
327
+ if (existingBySlug.has(def.slug)) {
328
+ kept.push(def.slug);
329
+ }
330
+ else {
331
+ next.push({ ...def, createdAt: now, updatedAt: now });
332
+ added.push(def.slug);
333
+ }
334
+ seeded.add(def.slug);
335
+ }
336
+ if (added.length > 0 || rawSeeded === undefined) {
337
+ writeTemplates(next, [...seeded]);
267
338
  }
268
- if (added.length > 0)
269
- writeTemplates(next);
270
339
  return { added, kept };
271
340
  }
@@ -6,7 +6,7 @@ import { resolveForge } from './forge/resolve.js';
6
6
  import { destroyTerminal } from './terminal-service.js';
7
7
  import { emitEphemeral } from './websocket-service.js';
8
8
  import { archiveWorkspace, getWorkspace, markWorktreePurged, } from './workspace-service.js';
9
- import { removeWorktree } from './worktree-service.js';
9
+ import { isPermissionError, removeWorktree } from './worktree-service.js';
10
10
  export async function purgeWorktree(workspaceId) {
11
11
  const workspace = getWorkspace(workspaceId);
12
12
  if (!workspace)
@@ -71,7 +71,7 @@ export async function purgeWorktree(workspaceId) {
71
71
  return { outcome: 'purged', warnings };
72
72
  }
73
73
  function buildRemovalFailureMessage(worktreePath, projectPath, errMsg) {
74
- const isPermission = /EACCES|EPERM|permission denied|operation not permitted/i.test(errMsg);
74
+ const isPermission = isPermissionError(errMsg);
75
75
  const baseLine = `Failed to remove worktree '${worktreePath}'.`;
76
76
  const recovery = ['Recovery:', ` sudo rm -rf '${worktreePath}'`, ` cd '${projectPath}' && git worktree prune`].join('\n');
77
77
  if (isPermission) {
@@ -4,7 +4,41 @@ import path from 'node:path';
4
4
  import { isGitBranchExistsError } from '../utils/git-ops.js';
5
5
  import { resolveWorkspaceWorktreePath, resolveWorktreesRoot } from '../utils/worktree-paths.js';
6
6
  function git(repoPath, args) {
7
- return execFileSync('git', args, { cwd: repoPath, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
7
+ return execFileSync('git', args, {
8
+ cwd: repoPath,
9
+ encoding: 'utf-8',
10
+ stdio: ['pipe', 'pipe', 'pipe'],
11
+ // Force the C locale so git (and libc strerror) emit English error messages.
12
+ // Without this, a French host reports "Permission non accordée" instead of
13
+ // "Permission denied", and permission-failure detection silently misses it.
14
+ env: { ...process.env, LC_ALL: 'C', LANG: 'C' },
15
+ }).trim();
16
+ }
17
+ /** True when an exec/git error message indicates a filesystem permission failure.
18
+ * git runs under LC_ALL=C (English), but we also match common localized phrasings
19
+ * (e.g. French) as a safety net in case the locale override doesn't take effect. */
20
+ export function isPermissionError(message) {
21
+ return /EACCES|EPERM|permission denied|operation not permitted|permission non accordée|opération non permise/i.test(message);
22
+ }
23
+ /** argv for `docker run` that chowns a bind-mounted worktree back to the host user. */
24
+ export function buildDockerChownArgs(worktreePath, uid, gid, image) {
25
+ return ['run', '--rm', '-v', `${worktreePath}:/w`, image, 'chown', '-R', `${uid}:${gid}`, '/w'];
26
+ }
27
+ const DEFAULT_CLEANUP_IMAGE = 'alpine';
28
+ function isDockerAvailable() {
29
+ try {
30
+ execFileSync('docker', ['version'], { stdio: ['ignore', 'ignore', 'ignore'] });
31
+ return true;
32
+ }
33
+ catch {
34
+ return false;
35
+ }
36
+ }
37
+ function reclaimWorktreeOwnershipViaDocker(worktreePath, uid, gid, image) {
38
+ execFileSync('docker', buildDockerChownArgs(worktreePath, uid, gid, image), {
39
+ encoding: 'utf-8',
40
+ stdio: ['pipe', 'pipe', 'pipe'],
41
+ });
8
42
  }
9
43
  function getExcludeFilePath(projectPath) {
10
44
  return path.join(projectPath, '.git', 'info', 'exclude');
@@ -73,13 +107,41 @@ export function createWorktree(projectPath, branchName, sourceBranch, worktreesP
73
107
  addToExclude(projectPath, worktreePath);
74
108
  return worktreePath;
75
109
  }
76
- /** Remove a git worktree and clean up the .git/info/exclude entry. */
110
+ /** Remove a git worktree and clean up the .git/info/exclude entry.
111
+ *
112
+ * If `git worktree remove` fails on a permission error (Docker dev servers leave
113
+ * root-owned files in node_modules / vendor), and Docker is available, reclaim
114
+ * ownership with a throwaway container (`chown -R <uid>:<gid>`) and retry once.
115
+ * Otherwise rethrow so the caller's recovery toast (sudo rm -rf …) fires. */
77
116
  export function removeWorktree(projectPath, worktreePath) {
78
117
  try {
79
118
  git(projectPath, ['worktree', 'remove', worktreePath, '--force']);
80
119
  }
81
120
  catch (err) {
82
121
  const message = err instanceof Error ? err.message : String(err);
122
+ const uid = typeof process.getuid === 'function' ? process.getuid() : null;
123
+ const gid = typeof process.getgid === 'function' ? process.getgid() : null;
124
+ if (isPermissionError(message) && uid != null && gid != null && isDockerAvailable()) {
125
+ const image = process.env.KOBO_WORKTREE_CLEANUP_IMAGE || DEFAULT_CLEANUP_IMAGE;
126
+ console.warn(`[worktree] '${worktreePath}' has root-owned files (permission denied); reclaiming ownership via Docker (${image})…`);
127
+ try {
128
+ reclaimWorktreeOwnershipViaDocker(worktreePath, uid, gid, image);
129
+ // The first `git worktree remove` already de-registered this worktree (it
130
+ // drops the admin entry even when the directory rm fails on permission), so
131
+ // retrying it errors with "is not a working tree". Now that we own the files,
132
+ // delete the directory directly and prune any dangling worktree metadata.
133
+ fs.rmSync(worktreePath, { recursive: true, force: true });
134
+ git(projectPath, ['worktree', 'prune']);
135
+ console.log(`[worktree] Docker cleanup succeeded; removed '${worktreePath}'`);
136
+ removeFromExclude(projectPath, worktreePath);
137
+ return;
138
+ }
139
+ catch (retryErr) {
140
+ const retryMessage = retryErr instanceof Error ? retryErr.message : String(retryErr);
141
+ console.error(`[worktree] Docker cleanup failed for '${worktreePath}': ${retryMessage}`);
142
+ throw new Error(`Failed to remove worktree '${worktreePath}': ${retryMessage}`);
143
+ }
144
+ }
83
145
  throw new Error(`Failed to remove worktree '${worktreePath}': ${message}`);
84
146
  }
85
147
  removeFromExclude(projectPath, worktreePath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loicngr/kobo",
3
- "version": "1.8.0",
3
+ "version": "1.8.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",
@@ -67,13 +67,13 @@
67
67
  "prepublishOnly": "npm run build"
68
68
  },
69
69
  "dependencies": {
70
- "@anthropic-ai/claude-agent-sdk": "^0.3.175",
71
- "@emnapi/core": "^1.11.0",
72
- "@emnapi/runtime": "^1.11.0",
70
+ "@anthropic-ai/claude-agent-sdk": "^0.3.177",
71
+ "@emnapi/core": "^1.11.1",
72
+ "@emnapi/runtime": "^1.11.1",
73
73
  "@hono/node-server": "^2.0.4",
74
74
  "@modelcontextprotocol/sdk": "^1.29.0",
75
75
  "@openai/codex": "^0.139.0",
76
- "better-sqlite3": "^12.10.0",
76
+ "better-sqlite3": "^12.10.1",
77
77
  "cron-parser": "^5.5.0",
78
78
  "hono": "^4.12.25",
79
79
  "nanoid": "^5.1.11",
@@ -85,10 +85,10 @@
85
85
  "@types/better-sqlite3": "^7.6.13",
86
86
  "@types/node": "^25.9.3",
87
87
  "@types/ws": "^8.18.1",
88
- "@vitest/runner": "^4.1.8",
88
+ "@vitest/runner": "^4.1.9",
89
89
  "concurrently": "^10.0.3",
90
90
  "tsx": "^4.22.4",
91
91
  "typescript": "^6.0.3",
92
- "vitest": "^4.1.8"
92
+ "vitest": "^4.1.9"
93
93
  }
94
94
  }
@@ -0,0 +1,8 @@
1
+ import{$ as e,B as t,F as n,I as r,R as i,S as a,U as o,W as s,_ as c,at as l,b as u,c as d,ct as f,g as p,h as m,it as h,k as g,m as _,n as v,p as y,s as b,st as x,y as S}from"./vue.runtime.esm-bundler-JZnIeD9D.js";import{l as C,t as w}from"./QIcon-CfOEsLsF.js";import{B as T,C as E,N as D,S as O,d as k,p as A,x as j,z as ee}from"./index-UPQqj74q.js";import{t as te}from"./QSpinnerDots-BOryc_9y.js";import{t as M}from"./QTooltip-Brh5sChu.js";import{t as N}from"./QExpansionItem-QIQTvw7U.js";import{t as ne}from"./QScrollArea-cWkysQ9Q.js";import{t as re}from"./documents-eaoEREpp.js";import{i as ie,n as ae,t as P}from"./render-chat-markdown-D1XyjSW1.js";import{t as F}from"./_plugin-vue_export-helper-BDNMzG2s.js";function oe(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:compacting`:case`session:brainstorm-complete`:case`session:user-input-requested`: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 se(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];r.sort((e,t)=>{let n=e.ts??``,r=t.ts??``;return n===r?0:n?r?n<r?-1:1:-1:1});let i;for(let e of r)e.type===`user`&&e.sender!==`system-prompt`&&e.ts&&(!i||e.ts>i)&&(i=e.ts);if(i)for(let e of r)e.type===`text`&&e.streaming&&(!e.ts||e.ts<i)&&(e.streaming=!1);return r}var I=new Set([`setup`,`cleanup`,`archive`]);function L(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:I.has(e.sender)?`script`:`user`;case`session`:return`session`;default:return`agent`}}function ce(e){let t=[],n=null,r=null;for(let i of e){let e=L(i),a=e===`session`||e===`system-prompt`,o=e===`script`&&i.type===`user`?`script:${i.sender}`:e;!n||r!==o||a?(n={speaker:e,ts:i.ts,items:[i]},r=o,t.push(n),a&&(n=null)):n.items.push(i)}return t}var R={class:`text-caption text-grey-6`},z=a({__name:`SessionEventItem`,props:{item:{}},setup(e){let t=e,n=y(()=>{switch(t.item.kind){case`started`:return`session.started`;case`ended`:return`session.ended`;case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,t)=>(i(),c(`span`,R,f(e.$t(n.value)),1))}});function le(e,t){if(t.length===0||e.length===0)return e;let n=[...t].sort((e,t)=>t.length-e.length),r=new DOMParser().parseFromString(`<div>${e}</div>`,`text/html`),i=r.body.firstChild;if(!i)return e;function a(e){if(e.nodeType===Node.TEXT_NODE){ue(e,n,r);return}if(e.nodeName===`A`)return;let t=Array.from(e.childNodes);for(let e of t)a(e)}return a(i),i.innerHTML}function ue(e,t,n){let r=e.textContent??``;if(!t.some(e=>r.includes(e)))return;let i=n.createDocumentFragment(),a=0;for(;a<r.length;){let e=B(r,a,t);if(!e){i.appendChild(n.createTextNode(r.slice(a)));break}e.index>a&&i.appendChild(n.createTextNode(r.slice(a,e.index)));let o=n.createElement(`a`);o.className=`document-link`,o.setAttribute(`data-document-path`,e.path),o.setAttribute(`href`,`#`),o.textContent=e.path,i.appendChild(o),a=e.index+e.path.length}e.parentNode?.replaceChild(i,e)}function B(e,t,n){let r=null;for(let i of n){let n=e.indexOf(i,t);n<0||(!r||n<r.index||n===r.index&&i.length>r.path.length)&&(r={index:n,path:i})}return r}var V=[`innerHTML`],H=F(a({__name:`TextMessageItem`,props:{item:{}},setup(e){let t=e,n=re(),r=k(),a=y(()=>{let e=r.selectedWorkspaceId;return e?n.documentsFor(e).map(e=>e.path):[]}),o=y(()=>ae(le(ie.parse(t.item.text,{async:!1,breaks:!0,gfm:!0}),a.value),{addAttr:[`data-document-path`]}));function s(e){let t=e.target?.closest(`.document-link`);if(!t)return;e.preventDefault();let i=t.getAttribute(`data-document-path`),a=r.selectedWorkspaceId;!i||!a||n.openDocumentByPath(a,i)}return(t,n)=>(i(),c(`div`,{class:`markdown-message`,onClick:s},[_(`div`,{innerHTML:o.value},null,8,V),e.item.streaming?(i(),m(C,{key:0,size:`xs`,class:`q-ml-xs`})):p(``,!0)]))}}),[[`__scopeId`,`data-v-1b7bd8ca`]]),de={key:0,class:`text-caption text-grey-5`,style:{"font-style":`italic`}},U=[`innerHTML`],W={key:1,style:{"white-space":`pre-wrap`}},G=F(a({__name:`ThinkingItem`,props:{item:{}},setup(e){let t=e,n=y(()=>t.item.text.trim().slice(0,100)),r=y(()=>t.item.text.trim().length>0),a=y(()=>t.item.text.trim().length>100),o=y(()=>P(t.item.text));return(t,l)=>r.value?(i(),c(`div`,de,[a.value?(i(),m(N,{key:0,dense:``,"dense-toggle":``,label:n.value,"header-class":`text-grey-5 text-caption`,style:{"font-style":`italic`}},{default:s(()=>[_(`div`,{class:`q-py-xs markdown-thinking`,innerHTML:o.value},null,8,U)]),_:1},8,[`label`])):(i(),c(`span`,W,f(e.item.text),1))])):p(``,!0)}}),[[`__scopeId`,`data-v-7f45ed94`]]);function K(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 fe(e){let t=e.split(`
4
+ `),n=[];for(let e of t)e.startsWith(`@@`)||e.startsWith(`+++`)||e.startsWith(`---`)||(e.startsWith(`+`)?n.push({type:`add`,content:e.slice(1)}):e.startsWith(`-`)?n.push({type:`del`,content:e.slice(1)}):e.startsWith(` `)?n.push({type:`context`,content:e.slice(1)}):e.length>0&&n.push({type:`context`,content:e}));return n}function pe(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??``,i=typeof n.diff==`string`?n.diff:``;if(!t&&!r&&i.length>0){let t=fe(i);return{toolName:`Edit`,filePath:e,additions:t.filter(e=>e.type===`add`).length,deletions:t.filter(e=>e.type===`del`).length,diffLines:t}}return{toolName:`Edit`,filePath:e,oldString:t,newString:r,replaceAll:n.replace_all??!1,additions:r?r.split(`
5
+ `).length:0,deletions:t?t.split(`
6
+ `).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(`
7
+ `).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 q(e,t){if(!e||!t?.projectPath)return e;let n=t.worktreePath;if(n){let t=J(e,n);if(t!==e)return t}let r=J(e,`${t.projectPath}/${O}/${t.workingBranch}`);return r===e?J(e,t.projectPath):r}function J(e,t){if(!t)return e;let n=me(t);return n?e.replace(RegExp(`${n}[\\\\/]+`,`g`),``).replace(RegExp(`${n}(?=\\s|$|["'\`])`,`g`),`.`):e}function me(e){let t=e.replace(/[\\/]+$/,``);return t?`${/^[\\/]+/.test(t)?`[\\\\/]+`:``}${t.split(/[\\/]+/).filter(Boolean).map(he).join(`[\\\\/]+`)}`:``}function he(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}var ge={class:`tool-name`},Y=[`title`],X={key:0,class:`tool-stat-add`},_e={key:1,class:`tool-stat-del`},ve={class:`diff-sign`},Z={class:`tool-name`},Q=[`title`],$=F(a({__name:`ToolCallItem`,props:{item:{}},setup(n){let r=n,a=e(!1),s=k(),h=y(()=>pe(r.item.name,r.item.input)),g=y(()=>h.value?q(h.value.filePath,s.selectedWorkspace):``),v={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`,TaskCreate:`add_task`,TaskUpdate:`checklist`,TaskGet:`checklist`,TaskList:`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=y(()=>v[r.item.name]??`build`),C=y(()=>{if(h.value)return``;let e=r.item.input,t=T(e);return t?q(t,s.selectedWorkspace):``});function T(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 E=y(()=>{let e=h.value;return e?e.diffLines?e.diffLines:e.toolName===`Edit`&&e.oldString!==void 0&&e.newString!==void 0?K(e.oldString,e.newString):e.toolName===`Write`&&e.content!==void 0?e.content.split(`
8
+ `).map(e=>({type:`add`,content:e})):e.toolName===`Bash:rm`?[{type:`del`,content:`File deleted`}]:null:null}),D=y(()=>{let e=r.item.result;if(!e)return``;if(typeof e.output==`string`)return e.output;try{return JSON.stringify(e.output)}catch{return String(e.output)}}),O=new Set([`Read`]),A=y(()=>!!(r.item.result&&D.value)&&(!O.has(r.item.name)||r.item.result?.isError===!0));function j(){a.value=!a.value}return o(()=>r.item.result?.isError===!0,e=>{e&&(a.value=!0)},{immediate:!0}),(e,r)=>h.value?(i(),c(`div`,{key:0,class:l([`tool-row`,{"tool-row-expanded":a.value}])},[_(`div`,{class:`tool-header`,onClick:j},[u(w,{name:x.value,size:`14px`,class:`tool-icon`},null,8,[`name`]),_(`span`,ge,f(h.value.toolName===`Bash:rm`?`Bash`:h.value.toolName),1),_(`span`,{class:`tool-path`,title:h.value.filePath},f(g.value),9,Y),h.value.additions>0?(i(),c(`span`,X,`+`+f(h.value.additions),1)):p(``,!0),h.value.deletions>0?(i(),c(`span`,_e,`-`+f(h.value.deletions),1)):p(``,!0),n.item.result?.isError?(i(),m(w,{key:2,name:`error_outline`,color:`negative`,size:`xs`,class:`q-ml-xs`})):n.item.result?(i(),m(w,{key:3,name:`check`,color:`positive`,size:`xs`,class:`q-ml-xs`})):p(``,!0),u(w,{name:a.value?`expand_less`:`expand_more`,size:`xs`,class:`q-ml-auto text-grey-6`},null,8,[`name`])]),a.value&&E.value?(i(),c(`div`,{key:0,class:`tool-diff`,onClick:r[0]||=b(()=>{},[`stop`])},[(i(!0),c(d,null,t(E.value,(e,t)=>(i(),c(`div`,{key:t,class:l([`diff-line`,{"diff-del":e.type===`del`,"diff-add":e.type===`add`,"diff-context":e.type===`context`}])},[_(`span`,ve,f(e.type===`del`?`-`:e.type===`add`?`+`:` `),1),S(f(e.content),1)],2))),128))])):p(``,!0)],2)):(i(),c(`div`,{key:1,class:l([`tool-row tool-row-generic`,{"tool-row-expanded":a.value,"tool-row--toggleable":A.value}])},[_(`div`,{class:`tool-header`,onClick:r[1]||=e=>A.value&&j()},[u(w,{name:x.value,size:`14px`,class:`tool-icon`},null,8,[`name`]),_(`span`,Z,f(n.item.name),1),C.value?(i(),c(`span`,{key:0,class:`tool-arg`,title:T(n.item.input)||C.value},f(C.value),9,Q)):p(``,!0),n.item.result?.isError?(i(),m(w,{key:1,name:`error_outline`,color:`negative`,size:`xs`,class:`q-ml-auto`})):n.item.result?(i(),m(w,{key:2,name:`check`,color:`positive`,size:`xs`,class:`q-ml-auto`})):p(``,!0),A.value?(i(),m(w,{key:3,name:a.value?`expand_less`:`expand_more`,size:`xs`,class:`q-ml-xs text-grey-6`},null,8,[`name`])):p(``,!0)]),a.value&&A.value?(i(),c(`div`,{key:0,class:`tool-output`,onClick:r[2]||=b(()=>{},[`stop`])},f(D.value),1)):p(``,!0)],2))}}),[[`__scopeId`,`data-v-e7bdb021`]]);function ye(e,t){return t?e.replace(/\[image:\s+([^\]]+)\]/g,(e,n)=>{let r=String(n).trim();return/^(\.ai\/images\/|images\/)/.test(r)?`![${r}](${`/api/workspaces/${encodeURIComponent(t)}/images/file?path=${encodeURIComponent(r)}`})`:e}):e}var be=[`innerHTML`],xe=[`innerHTML`],Se=[`src`],Ce=F(a({__name:`UserMessageItem`,props:{item:{}},setup(t){let n=t,r=k(),a=y(()=>n.item.sender===`system-prompt`),o=y(()=>P(ye(n.item.content,r.selectedWorkspaceId??``))),l=e(null),f=e(!1);function h(e){let t=e.target;if(t?.tagName!==`IMG`)return;let n=t;n.src&&(l.value=n.src,f.value=!0)}return(e,t)=>(i(),c(d,null,[a.value?(i(),m(N,{key:0,dense:``,"dense-toggle":``,label:e.$t(`chat.systemPrompt`),"header-class":`text-grey-5 text-caption`},{default:s(()=>[_(`div`,{class:`q-py-xs markdown-user-prompt`,innerHTML:o.value},null,8,be)]),_:1},8,[`label`])):(i(),c(`div`,{key:1,class:`markdown-message`,onClick:h},[_(`div`,{innerHTML:o.value},null,8,xe)])),u(T,{modelValue:f.value,"onUpdate:modelValue":t[1]||=e=>f.value=e},{default:s(()=>[l.value?(i(),c(`img`,{key:0,src:l.value,alt:``,class:`image-lightbox-img`,onClick:t[0]||=e=>f.value=!1},null,8,Se)):p(``,!0)]),_:1},8,[`modelValue`])],64))}}),[[`__scopeId`,`data-v-f34be4c5`]]),we={class:`turn-header`},Te={key:0,class:`turn-time`},Ee={class:`turn-time turn-time-updated`},De={key:2,class:`turn-actions`},Oe={class:`turn-body`},ke={key:0,class:`turn-scroll-top`},Ae=F(a({__name:`TurnCard`,props:{turn:{}},emits:[`scrollTo`],setup(n,{emit:r}){let a=n,o=r,{t:g}=ee(),v=e(null);function b(){let e=v.value;if(!e)return;let t=e.closest(`.q-scrollarea`)?.querySelector(`.q-scrollarea__content`);if(!t){e.scrollIntoView({behavior:`smooth`,block:`start`});return}let n=e.getBoundingClientRect().top-t.getBoundingClientRect().top;o(`scrollTo`,Math.max(0,n-8))}let C=y(()=>{switch(a.turn.speaker){case`user`:return{label:g(`chat.you`),accent:`#ce93d8`,badgeClass:`turn-badge-user`};case`agent`:return{label:g(`chat.agent`),accent:`#7986cb`,badgeClass:`turn-badge-agent`};case`system-prompt`:return{label:g(`chat.systemPrompt`),accent:`#757575`,badgeClass:`turn-badge-system`};case`session`:return{label:g(`chat.session`),accent:`#616161`,badgeClass:`turn-badge-session`};case`script`:{let e=a.turn.items[0],t=e?.type===`user`?e.sender:``;return{label:g(t===`archive`?`chat.archiveScript`:t===`setup`?`chat.setupScript`:`chat.cleanupScript`),accent:`#4db6ac`,badgeClass:`turn-badge-script`}}}});function T(e,t=!1){if(!e)return``;let n=new Date(e);return Number.isNaN(n.getTime())?``:n.toLocaleTimeString(void 0,t?{hour:`2-digit`,minute:`2-digit`,second:`2-digit`}:{hour:`2-digit`,minute:`2-digit`})}let E=y(()=>T(a.turn.ts)),O=y(()=>{let e=a.turn.items;if(e.length===0)return null;for(let t=e.length-1;t>=0;t--){let n=e[t].ts;if(n)return n}return null}),k=y(()=>{let e=a.turn.ts,t=O.value;if(!t||!e||t===e)return``;let n=new Date(e).getTime(),r=new Date(t).getTime();return Number.isNaN(n)||Number.isNaN(r)||r<=n?``:T(t,r-n<6e4)}),A=y(()=>k.value!==``),j=y(()=>a.turn.items.filter(e=>e.type===`tool`).length);return(e,r)=>(i(),c(`div`,{ref_key:`cardEl`,ref:v,class:l([`turn-card`,{"turn-card--user":n.turn.speaker===`user`}]),style:x({"--turn-accent":C.value.accent})},[_(`div`,we,[_(`span`,{class:l([`turn-badge`,C.value.badgeClass])},f(C.value.label),3),E.value?(i(),c(`span`,Te,f(E.value),1)):p(``,!0),A.value?(i(),c(d,{key:1},[u(w,{name:`arrow_forward`,size:`10px`,color:`grey-7`,class:`turn-time-arrow`}),_(`span`,Ee,[S(f(k.value)+` `,1),u(M,null,{default:s(()=>[S(f(h(g)(`chat.lastUpdatedAt`,{time:k.value})),1)]),_:1})])],64)):p(``,!0),j.value>0?(i(),c(`span`,De,` · `+f(h(g)(`chat.nActions`,{n:j.value})),1)):p(``,!0)]),_(`div`,Oe,[(i(!0),c(d,null,t(n.turn.items,(e,t)=>(i(),c(d,{key:t},[e.type===`text`?(i(),m(H,{key:0,item:e},null,8,[`item`])):e.type===`thinking`?(i(),m(G,{key:1,item:e},null,8,[`item`])):e.type===`tool`?(i(),m($,{key:2,item:e},null,8,[`item`])):e.type===`user`?(i(),m(Ce,{key:3,item:e},null,8,[`item`])):e.type===`session`?(i(),m(z,{key:4,item:e},null,8,[`item`])):p(``,!0)],64))),128))]),n.turn.items.length>4?(i(),c(`div`,ke,[u(D,{flat:``,round:``,dense:``,size:`xs`,icon:`arrow_upward`,color:`grey-6`,class:`turn-scroll-top-btn`,onClick:b},{default:s(()=>[u(M,null,{default:s(()=>[S(f(h(g)(`chat.scrollToTurnTop`)),1)]),_:1})]),_:1})])):p(``,!0)],6))}}),[[`__scopeId`,`data-v-ce5d132b`]]),je={key:0,class:`activity-feed-switching`},Me={key:1,class:`activity-feed-wrap`},Ne={key:0,class:`activity-feed-compacting`},Pe={class:`text-caption text-grey-4`},Fe={key:0,class:`text-center q-py-sm text-caption text-grey-6`},Ie={class:`q-pa-md`},Le={key:1,class:`q-px-md q-pb-md`},Re={class:`activity-feed-nav-cluster`},ze=60,Be=200,Ve=200,He=400,Ue=200,We=180,Ge=F(a({__name:`ActivityFeed`,props:{workspaceId:{}},setup(a){let l=a,h=E(),b=j(),x=k(),w=y(()=>h.isCompacting(l.workspaceId)),T=y(()=>x.selectedSessionId),O=y(()=>x.sessions.find(e=>e.id===T.value)?.engineSessionId??null),ee=y(()=>{let e=x.sessions;return e.length===0?!1:T.value===e[e.length-1].id});function M(e){return T.value?e?e===T.value||e===O.value:ee.value:!0}let re=y(()=>(x.activityFeeds[l.workspaceId]??[]).filter(e=>e.type===`text`&&typeof e.content==`string`&&M(e.sessionId)).map(e=>({content:e.content,sender:e.meta?.sender??`user`,ts:e.timestamp,sessionId:e.sessionId}))),ie=y(()=>A(x.workspaces.find(e=>e.id===l.workspaceId)?.status)),ae=y(()=>{let e=h.eventsFor(l.workspaceId),t=h.timestampsFor(l.workspaceId),n=h.sessionIdsFor(l.workspaceId),r=[],i=[];for(let a=0;a<e.length;a++)M(n[a])&&(r.push(e[a]),i.push(t[a]));let a=se(oe(r,i,ie.value),re.value);return ce(b.showVerboseSystemMessages?a:a.filter(e=>e.type!==`session`))}),P=y(()=>b.showVerboseSystemMessages?h.eventsFor(l.workspaceId).filter(e=>e.kind===`message:raw`).map(e=>e.content):[]),F=e(null),I=e(!0),L=e(!1),R=!1,z=e(!0),le=e(new Map);function ue(e){I.value=e.verticalSize-e.verticalPosition-e.verticalContainerSize<=ze,R&&e.verticalPosition<=Be&&!L.value&&V()&&U()}function B(e,t){return`${e}:${t}`}function V(){let e=T.value;return e?le.value.get(B(l.workspaceId,e))??!0:h.hasMoreOlderFor(l.workspaceId)}function H(e,t,n){le.value.set(B(e,t),n)}function de(e){if(!T.value)return h.oldestIdFor(e);let t=h.eventIdsFor(e),n=h.sessionIdsFor(e);for(let e=0;e<t.length;e++){if(!M(n[e]))continue;let r=t[e];if(r)return r}}async function U(){let e=l.workspaceId,t=T.value,n=de(e);if(!n)return;L.value=!0;let r=Date.now();try{let r=F.value;await g();let i=r?.getScroll().verticalSize??0,a=r?.getScroll().verticalPosition??0,o=new URLSearchParams({before:n,limit:`200`});t&&o.set(`session`,t);let s=fetch(`/api/workspaces/${e}/events?${o.toString()}`),c=new Promise(e=>setTimeout(e,Ve)),[l]=await Promise.all([s,c]);if(!l.ok){t?H(e,t,!1):h.prepend(e,[],[],{oldestId:n,hasMoreOlder:!1});return}let u=await l.json(),d=u.events??[],f=d.filter(t=>t.type===`agent:event`&&t.workspaceId===e),p=d.filter(t=>t.type===`user:message`&&t.workspaceId===e),m=f.map(e=>e.payload),_=f.map(e=>e.createdAt),v=f.map(e=>e.sessionId??null),y=f.map(e=>e.id),b=d.length>0?d[0].id:n;t&&H(e,t,u.hasMore),h.prepend(e,m,_,{oldestId:b,hasMoreOlder:t?h.hasMoreOlderFor(e):u.hasMore,sessionIds:v,eventIds:y});for(let t of p){let n=t.payload;typeof n.content==`string`&&x.addActivityItem(e,{id:t.id,type:`text`,content:n.content,timestamp:t.createdAt,sessionId:t.sessionId??void 0,meta:{sender:n.sender??`user`}})}if(await g(),r){let e=r.getScroll().verticalSize-i;if(e>0){let t=Math.max(a+e,250);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,Ve-e);await new Promise(e=>setTimeout(e,t+He)),L.value=!1}}async function W(e=0){await g();let t=F.value;if(!t)return;let n=t.getScroll();t.setScrollPosition(`vertical`,n.verticalSize,e)}let G=null,K=0;function fe(){if(G!=null)return;let e=performance.now()-K<We;G=requestAnimationFrame(()=>{G=null,K=performance.now(),W(e?0:180)})}function pe(e){let t=F.value;t&&t.setScrollPosition(`vertical`,Math.max(0,e),250)}let q=e([]),J=e(null);function me(){let e=ae.value,t=q.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=J.value?.parentElement;if(r){let e=r.querySelectorAll(`.turn-card--user`);for(let t of e)n.push(t)}return n}function he(){let e=F.value;if(!e)return null;let t=J.value;if(!t)return null;let n=e.getScroll().verticalPosition,r=t.getBoundingClientRect().top,i=null;for(let e of me()){let t=e.getBoundingClientRect().top-r;if(t<n-40)i=t;else break}return i}async function ge(){let e=F.value;if(!e)return;let t=he();if(t===null)for(let e=0;e<15&&V();e++){for(;L.value;)await new Promise(e=>setTimeout(e,50));if(await U(),await g(),t=he(),t!==null)break}t!==null&&e.setScrollPosition(`vertical`,Math.max(0,t-12),250)}async function Y(){R=!1,await g(),await W(0),requestAnimationFrame(()=>{requestAnimationFrame(()=>{R=!0})})}let X=y(()=>{let e=h.sessionIdsFor(l.workspaceId);if(!T.value)return e.length;let t=0;for(let n of e)M(n)&&t++;return t}),_e=y(()=>h.eventsFor(l.workspaceId).length);async function ve(){z.value=!0;let e=Date.now();await new Promise(e=>setTimeout(e,Ue));let t=e+5e3;for(;_e.value===0&&Date.now()<t;)await new Promise(e=>setTimeout(e,50));z.value=!1}o(z,async e=>{!e&&X.value>0&&await Y(),!e&&X.value===0&&T.value&&$()}),n(()=>{ve(),X.value>0&&Y(),T.value&&$()});let Z=!1;o(X,async(e,t)=>{if(!Z&&e>0){Z=!0,await Y();return}e>t&&I.value&&!L.value&&fe()}),r(()=>{G!=null&&(cancelAnimationFrame(G),G=null)}),o(()=>l.workspaceId,()=>{I.value=!0,Z=!1,R=!1,ve(),X.value>0&&Y()}),o(()=>x.selectedSessionId,async()=>{I.value=!0,R=!1,await Y(),$()});let Q=new Set;async function $(){let e=T.value;if(!e||X.value>0)return;let t=B(l.workspaceId,e);if(!Q.has(t)){Q.add(t);try{let t=await fetch(`/api/workspaces/${l.workspaceId}/events?session=${encodeURIComponent(e)}&limit=500`);if(!t.ok)return;let n=await t.json(),r=n.events??[];if(r.length===0)return;let i=r.filter(e=>e.type===`agent:event`&&e.workspaceId===l.workspaceId),a=r.filter(e=>e.type===`user:message`&&e.workspaceId===l.workspaceId),o=i.map(e=>e.payload),s=i.map(e=>e.createdAt),c=i.map(e=>e.sessionId??null),u=i.map(e=>e.id);H(l.workspaceId,e,n.hasMore),o.length>0&&h.prepend(l.workspaceId,o,s,{oldestId:r[0].id,hasMoreOlder:h.hasMoreOlderFor(l.workspaceId),sessionIds:c,eventIds:u});for(let e of a){let t=e.payload;typeof t.content==`string`&&x.addActivityItem(l.workspaceId,{id:e.id,type:`text`,content:t.content,timestamp:e.createdAt,sessionId:e.sessionId??void 0,meta:{sender:t.sender??`user`}})}await g(),await W(0)}catch(e){console.error(`[ActivityFeed] fetchSessionIfMissing failed:`,e),Q.delete(t)}}}o(y(()=>re.value.filter(e=>e.sender!==`system-prompt`).length),async(e,t)=>{e>t&&(I.value=!0,await W(180))});async function ye(){I.value=!0,await W(250)}return(e,n)=>z.value?(i(),c(`div`,je,[u(te,{size:`40px`,color:`indigo-4`})])):(i(),c(`div`,Me,[u(v,{name:`fade`},{default:s(()=>[w.value?(i(),c(`div`,Ne,[u(te,{size:`18px`,color:`indigo-4`}),_(`span`,Pe,f(e.$t(`activity.compacting`)),1)])):p(``,!0)]),_:1}),u(ne,{ref_key:`scrollRef`,ref:F,class:`activity-feed-scroll`,onScroll:ue},{default:s(()=>[_(`div`,{ref_key:`contentOriginRef`,ref:J,class:`content-origin-marker`},null,512),L.value?(i(),c(`div`,Fe,[u(C,{size:`sm`}),S(` `+f(e.$t(`activity.loading_older`)),1)])):p(``,!0),_(`div`,Ie,[(i(!0),c(d,null,t(ae.value,(e,t)=>(i(),m(Ae,{key:t,ref_for:!0,ref_key:`turnRefs`,ref:q,turn:e,onScrollTo:pe},null,8,[`turn`]))),128))]),P.value.length?(i(),c(`div`,Le,[u(N,{label:e.$t(`activity.raw_lines`,{n:P.value.length}),dense:``},{default:s(()=>[(i(!0),c(d,null,t(P.value,(e,t)=>(i(),c(`div`,{key:t,class:`text-caption text-grey q-pa-xs`},f(e),1))),128))]),_:1},8,[`label`])])):p(``,!0)]),_:1},512),_(`div`,Re,[I.value?p(``,!0):(i(),m(D,{key:0,round:``,dense:``,unelevated:``,color:`grey-9`,"text-color":`grey-3`,icon:`arrow_downward`,size:`sm`,class:`activity-feed-nav-btn`,title:e.$t(`activity.scroll_to_bottom`),onClick:ye},null,8,[`title`])),u(D,{round:``,dense:``,unelevated:``,color:`grey-9`,"text-color":`grey-3`,icon:`arrow_upward`,size:`sm`,class:`activity-feed-nav-btn`,title:e.$t(`activity.prev_user_message`),onClick:ge},null,8,[`title`])])]))}}),[[`__scopeId`,`data-v-d16ab9e7`]]);export{Ge as default};
@@ -0,0 +1 @@
1
+ import{$ as e,B as t,F as n,R as r,S as i,W as a,_ as o,b as s,c,ct as l,g as u,h as d,it as f,m as p}from"./vue.runtime.esm-bundler-JZnIeD9D.js";import{N as m,gt as h,mt as g,s as _}from"./index-UPQqj74q.js";import{t as v}from"./render-chat-markdown-D1XyjSW1.js";import{t as y}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{t as b}from"./QSpace-e68xsYE1.js";import{t as x}from"./QChip-xeL4Nxy_.js";import{t as S}from"./QPage-B-ixPPKx.js";var C={class:`row items-center q-mb-md`},w={class:`text-h6 q-ml-sm`},T={key:0,class:`text-grey-6 text-center q-pa-lg`},E={key:1,class:`text-negative text-center q-pa-lg`},D={key:2,class:`text-grey-6 text-center q-pa-lg`},O={key:3,class:`column q-gutter-md`},k={key:0,class:`text-caption text-grey-6`},A={class:`row items-center q-mb-sm`},j={class:`text-subtitle1 text-indigo-3`,style:{"font-family":`var(--kobo-font-mono, monospace)`}},M=[`innerHTML`],N=y(i({__name:`ChangelogPage`,setup(i){let y=_(),N=e([]),P=e(``),F=e(!1),I=e(null);function L(e){return v(e)}async function R(){F.value=!0,I.value=null;try{let e=await fetch(`/api/changelog`);if(!e.ok)throw Error(`HTTP ${e.status}`);let t=await e.json();P.value=t.currentVersion??``,N.value=t.versions??[]}catch(e){I.value=e instanceof Error?e.message:String(e)}finally{F.value=!1}}return n(R),(e,n)=>(r(),d(S,{class:`q-pa-md`,style:{"max-width":`900px`,margin:`0 auto`}},{default:a(()=>[p(`div`,C,[s(m,{flat:``,dense:``,round:``,icon:`arrow_back`,onClick:n[0]||=e=>f(y).back()}),p(`div`,w,l(e.$t(`changelog.title`)),1),s(b),s(m,{flat:``,dense:``,icon:`refresh`,loading:F.value,label:e.$t(`common.refresh`),onClick:R},null,8,[`loading`,`label`])]),F.value&&N.value.length===0?(r(),o(`div`,T,l(e.$t(`common.loading`)),1)):I.value?(r(),o(`div`,E,l(I.value),1)):N.value.length===0?(r(),o(`div`,D,l(e.$t(`changelog.empty`)),1)):(r(),o(`div`,O,[P.value?(r(),o(`div`,k,l(e.$t(`changelog.currentVersion`,{version:P.value})),1)):u(``,!0),(r(!0),o(c,null,t(N.value,t=>(r(),d(g,{key:t.version,dark:``,flat:``,bordered:``},{default:a(()=>[s(h,null,{default:a(()=>[p(`div`,A,[p(`div`,j,` v`+l(t.version),1),t.version===P.value?(r(),d(x,{key:0,dense:``,size:`sm`,color:`indigo-7`,"text-color":`grey-2`,label:e.$t(`changelog.current`),class:`q-ml-sm`},null,8,[`label`])):u(``,!0)]),p(`div`,{class:`changelog-notes`,innerHTML:L(t.notes)},null,8,M)]),_:2},1024)]),_:2},1024))),128))]))]),_:1}))}}),[[`__scopeId`,`data-v-ed73d661`]]);export{N as default};
@@ -0,0 +1 @@
1
+ import{F as e,b as t}from"./QIcon-CfOEsLsF.js";import{nt as n,rt as r}from"./index-UPQqj74q.js";var i={left:!0,right:!0,up:!0,down:!0,horizontal:!0,vertical:!0},a=Object.keys(i);i.all=!0;function o(e){let t={};for(let n of a)e[n]&&(t[n]=!0);return Object.keys(t).length===0?i:(t.horizontal?t.left=t.right=!0:t.left&&t.right&&(t.horizontal=!0),t.vertical?t.up=t.down=!0:t.up&&t.down&&(t.vertical=!0),t.horizontal&&t.vertical&&(t.all=!0),t)}var s=[`INPUT`,`TEXTAREA`];function c(e,t){return t.event===void 0&&e.target!==void 0&&!e.target.draggable&&typeof t.handler==`function`&&!s.includes(e.target.nodeName.toUpperCase())&&(e.qClonedBy===void 0||!e.qClonedBy.includes(t.uid))}function l(e){return e===!1?0:e===!0||e===void 0?1:Number.parseInt(e,10)||0}var u=e({name:`close-popup`,beforeMount(e,{value:i}){let a={depth:l(i),handler(t){a.depth!==0&&setTimeout(()=>{let i=r(e);i!==void 0&&n(i,t,a.depth)})},handlerKey(e){t(e,13)&&a.handler(e)}};e.__qclosepopup=a,e.addEventListener(`click`,a.handler),e.addEventListener(`keyup`,a.handlerKey)},updated(e,{value:t,oldValue:n}){t!==n&&(e.__qclosepopup.depth=l(t))},beforeUnmount(e){let t=e.__qclosepopup;e.removeEventListener(`click`,t.handler),e.removeEventListener(`keyup`,t.handlerKey),delete e.__qclosepopup}});export{o as n,c as r,u as t};