@loicngr/kobo 1.10.2 → 1.11.0

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 (234) hide show
  1. package/AGENTS.md +5 -4
  2. package/CHANGELOG.md +29 -0
  3. package/README.md +16 -10
  4. package/dist/server/db/migrations.js +21 -0
  5. package/dist/server/db/schema.js +14 -0
  6. package/dist/server/routes/health.js +2 -2
  7. package/dist/server/routes/notion.js +18 -1
  8. package/dist/server/routes/sentry.js +15 -1
  9. package/dist/server/routes/workspaces.js +140 -6
  10. package/dist/server/services/agent/engines/claude-code/engine.js +17 -1
  11. package/dist/server/services/agent/engines/claude-code/event-mapper.js +31 -2
  12. package/dist/server/services/agent/engines/claude-code/options-builder.js +3 -0
  13. package/dist/server/services/agent/engines/codex/engine.js +70 -8
  14. package/dist/server/services/agent/engines/codex/server-requests.js +13 -3
  15. package/dist/server/services/agent/engines/codex/turn-liveness.js +53 -0
  16. package/dist/server/services/agent/event-router.js +4 -0
  17. package/dist/server/services/agent/orchestrator.js +17 -0
  18. package/dist/server/services/agent/streaming-batcher.js +34 -0
  19. package/dist/server/services/notion-service.js +19 -0
  20. package/dist/server/services/pr-watcher-service.js +22 -17
  21. package/dist/server/services/search-service.js +3 -1
  22. package/dist/server/services/sentry-service.js +20 -0
  23. package/dist/server/services/settings-service.js +130 -1
  24. package/dist/server/services/workspace-permission-policy-service.js +86 -0
  25. package/dist/server/utils/paths.js +3 -3
  26. package/package.json +3 -3
  27. package/src/client/dist/pwa/assets/ActivityFeed-D9EfbRqv.css +1 -0
  28. package/src/client/dist/pwa/assets/ActivityFeed-ujsfk0sG.js +8 -0
  29. package/src/client/dist/{spa/assets/ChangelogPage-EA4LPGWB.js → pwa/assets/ChangelogPage-DLmP9KSb.js} +1 -1
  30. package/src/client/dist/{spa/assets/ClosePopup-BxYvEnmx.js → pwa/assets/ClosePopup-Cc7aUs96.js} +1 -1
  31. package/src/client/dist/pwa/assets/CreatePage-DfMhzgbv.js +2 -0
  32. package/src/client/dist/pwa/assets/CreatePage-qMgdzdb-.css +1 -0
  33. package/src/client/dist/pwa/assets/DiffViewer-B-TSPjtj.js +8 -0
  34. package/src/client/dist/{spa/assets/DiffViewer-BuYD1EDP.css → pwa/assets/DiffViewer-CRrDdYBa.css} +1 -1
  35. package/src/client/dist/{spa/assets/HealthPage-5F4m82-8.js → pwa/assets/HealthPage-13Ugeexp.js} +1 -1
  36. package/src/client/dist/pwa/assets/MainLayout-BzOcdtNd.js +37 -0
  37. package/src/client/dist/pwa/assets/MainLayout-CgHzEM37.css +1 -0
  38. package/src/client/dist/{spa/assets/QBtnToggle-Br3smB6b.js → pwa/assets/QBtnToggle-BUJ1g7BR.js} +1 -1
  39. package/src/client/dist/{spa/assets/QChip-Bc7L8LKe.js → pwa/assets/QChip-CPiNmK-x.js} +1 -1
  40. package/src/client/dist/pwa/assets/QSelect-D63npmWX.js +36 -0
  41. package/src/client/dist/pwa/assets/QTooltip-DOQ5d9M2.js +1 -0
  42. package/src/client/dist/pwa/assets/SearchPage-B08_UXDs.css +1 -0
  43. package/src/client/dist/pwa/assets/SearchPage-C7ulmpmI.js +1 -0
  44. package/src/client/dist/pwa/assets/SettingsPage-DJEH2ahl.css +1 -0
  45. package/src/client/dist/pwa/assets/SettingsPage-DY1g-LTs.js +16 -0
  46. package/src/client/dist/pwa/assets/ThinkingItem-BVoamjnK.css +1 -0
  47. package/src/client/dist/pwa/assets/ThinkingItem-M7hXIRih.js +1 -0
  48. package/src/client/dist/{spa/assets/TouchPan-IMQRiAGn.js → pwa/assets/TouchPan-g-ZGD0pd.js} +1 -1
  49. package/src/client/dist/pwa/assets/WorkspacePage-BU_uwlKN.js +4 -0
  50. package/src/client/dist/pwa/assets/WorkspacePage-CnRbNBAh.css +1 -0
  51. package/src/client/dist/{spa/assets/build-path-tree-Cgbihluh.js → pwa/assets/build-path-tree-B3R74R5X.js} +1 -1
  52. package/src/client/dist/{spa/assets/cssMode-57i-GAo-.js → pwa/assets/cssMode-Ca0QWQGQ.js} +1 -1
  53. package/src/client/dist/pwa/assets/documents-a-MDhFWk.js +1 -0
  54. package/src/client/dist/{spa/assets/editor.api2-CNFQDxW4.js → pwa/assets/editor.api2-CQQ3TFHp.js} +1 -1
  55. package/src/client/dist/{spa/assets/editor.main-CJJlXa_x.js → pwa/assets/editor.main-BmtlNqLW.js} +2 -2
  56. package/src/client/dist/{spa/assets/expand-template-CN8wfDhv.js → pwa/assets/expand-template-DkLDjInO.js} +1 -1
  57. package/src/client/dist/{spa/assets/formatters-9eR6rvkm.js → pwa/assets/formatters-COWwqgNl.js} +1 -1
  58. package/src/client/dist/{spa/assets/freemarker2-qbKTPHxB.js → pwa/assets/freemarker2-Be_VI-2P.js} +1 -1
  59. package/src/client/dist/{spa/assets/handlebars-DV81bryH.js → pwa/assets/handlebars-C6MV-4UG.js} +1 -1
  60. package/src/client/dist/{spa/assets/html-i5i_Ua1s.js → pwa/assets/html-H42ZDxQV.js} +1 -1
  61. package/src/client/dist/{spa/assets/htmlMode-Du1z3aXL.js → pwa/assets/htmlMode-COCELcr-.js} +1 -1
  62. package/src/client/dist/pwa/assets/i18n-BgrVh81z.js +1 -0
  63. package/src/client/dist/pwa/assets/index-Cs6I8vi_.js +84 -0
  64. package/src/client/dist/{spa/assets/javascript-DgQZF_pf.js → pwa/assets/javascript-CiFkTKAE.js} +1 -1
  65. package/src/client/dist/{spa/assets/jsonMode-6XLEI_p0.js → pwa/assets/jsonMode--0Mpy1sQ.js} +1 -1
  66. package/src/client/dist/{spa/assets/kobo-commands-CplA4Y0q.js → pwa/assets/kobo-commands-BzxwZ3vQ.js} +1 -1
  67. package/src/client/dist/{spa/assets/layout-BbHOhk37.js → pwa/assets/layout-BcUfiaFQ.js} +1 -1
  68. package/src/client/dist/{spa/assets/liquid-54MmW5iG.js → pwa/assets/liquid-BlkSsRl8.js} +1 -1
  69. package/src/client/dist/{spa/assets/lspLanguageFeatures-xU1LF4fY.js → pwa/assets/lspLanguageFeatures-DXelvPSE.js} +1 -1
  70. package/src/client/dist/{spa/assets/mdx-BbX1cCc8.js → pwa/assets/mdx-BoNBdGqW.js} +1 -1
  71. package/src/client/dist/{spa/assets/monaco.contribution-CNHTTcDF.js → pwa/assets/monaco.contribution-D8uREgcb.js} +2 -2
  72. package/src/client/dist/pwa/assets/network-auth-wzwG23Rr.js +1 -0
  73. package/src/client/dist/pwa/assets/pending-diff-open-DyoLDUlV.js +1 -0
  74. package/src/client/dist/pwa/assets/permissionModes-D7Iv04mQ.js +1 -0
  75. package/src/client/dist/{spa/assets/project-color-PpNx-yz4.js → pwa/assets/project-color-C3soNzD0.js} +1 -1
  76. package/src/client/dist/{spa/assets/python-BL4EBjGI.js → pwa/assets/python-DZCdvSIy.js} +1 -1
  77. package/src/client/dist/{spa/assets/razor-BdIRjBrX.js → pwa/assets/razor-CFwK0FwM.js} +1 -1
  78. package/src/client/dist/{spa/assets/render-chat-markdown-CYeqc8cI.js → pwa/assets/render-chat-markdown-DCydC_jc.js} +1 -1
  79. package/src/client/dist/{spa/assets/tsMode-BoQuq_mo.js → pwa/assets/tsMode-D8ZbVZB6.js} +1 -1
  80. package/src/client/dist/{spa/assets/typescript-TXE1oBSg.js → pwa/assets/typescript-CjxR71vN.js} +1 -1
  81. package/src/client/dist/{spa/assets/use-onboarding-Ch0swQ7p.js → pwa/assets/use-onboarding-DA5ka-XJ.js} +1 -1
  82. package/src/client/dist/{spa/assets/use-quasar--cg_cUkd.js → pwa/assets/use-quasar-5A_Wy1iR.js} +1 -1
  83. package/src/client/dist/{spa/assets/workers-Clrb2kWw.js → pwa/assets/workers-Bv1gqcaP.js} +1 -1
  84. package/src/client/dist/{spa/assets/xml-36_1aaai.js → pwa/assets/xml-vP8L_5ev.js} +1 -1
  85. package/src/client/dist/{spa/assets/yaml-CTVfk9Oc.js → pwa/assets/yaml-YN_D0NXJ.js} +1 -1
  86. package/src/client/dist/{spa → pwa}/index.html +2 -2
  87. package/src/client/dist/pwa/manifest.json +1 -0
  88. package/src/client/dist/pwa/sounds/7eme-compagnie-03.mp3 +0 -0
  89. package/src/client/dist/pwa/sounds/aller-ftg.mp3 +0 -0
  90. package/src/client/dist/pwa/sounds/arrete-de-mentir.mp3 +0 -0
  91. package/src/client/dist/pwa/sounds/arretez-les-messages.mp3 +0 -0
  92. package/src/client/dist/pwa/sounds/bah-alors-on-est-nul.mp3 +0 -0
  93. package/src/client/dist/pwa/sounds/gta-v-death.mp3 +0 -0
  94. package/src/client/dist/pwa/sounds/nan-tu-degages.mp3 +0 -0
  95. package/src/client/dist/pwa/sounds/nan-wallah-pardon.mp3 +0 -0
  96. package/src/client/dist/pwa/sounds/ouais-cest-greg.mp3 +0 -0
  97. package/src/client/dist/pwa/sounds/pas-ca-zinedine.mp3 +0 -0
  98. package/src/client/dist/pwa/sounds/ta-gueule.mp3 +0 -0
  99. package/src/client/dist/pwa/sounds/tu-vas-la-fermer.mp3 +0 -0
  100. package/src/client/dist/pwa/sw.js +1 -0
  101. package/src/client/dist/pwa/workbox-778a14ff.js +1 -0
  102. package/src/client/dist/spa/assets/ActivityFeed-98LfDiwH.js +0 -8
  103. package/src/client/dist/spa/assets/ActivityFeed-HbMZOClq.css +0 -1
  104. package/src/client/dist/spa/assets/CreatePage-C1yAhjsO.css +0 -1
  105. package/src/client/dist/spa/assets/CreatePage-Cg0BbQwp.js +0 -2
  106. package/src/client/dist/spa/assets/DiffViewer-CLp2N6Zh.js +0 -8
  107. package/src/client/dist/spa/assets/MainLayout-7nwhu8UE.css +0 -1
  108. package/src/client/dist/spa/assets/MainLayout-Dfc_DsIt.js +0 -37
  109. package/src/client/dist/spa/assets/QExpansionItem-BHyDyRL4.js +0 -1
  110. package/src/client/dist/spa/assets/QScrollArea-CncsSfK0.js +0 -1
  111. package/src/client/dist/spa/assets/QSelect-DXXOwpf6.js +0 -36
  112. package/src/client/dist/spa/assets/QTooltip-C6Vpr1qf.js +0 -1
  113. package/src/client/dist/spa/assets/SearchPage-BYM38aZA.js +0 -1
  114. package/src/client/dist/spa/assets/SearchPage-cVwt0DaQ.css +0 -1
  115. package/src/client/dist/spa/assets/SettingsPage-DendQ8y4.js +0 -16
  116. package/src/client/dist/spa/assets/SettingsPage-NDsw0Jvo.css +0 -1
  117. package/src/client/dist/spa/assets/WorkspacePage-BOApCawq.css +0 -1
  118. package/src/client/dist/spa/assets/WorkspacePage-BghKaAab.js +0 -4
  119. package/src/client/dist/spa/assets/documents-CdYfTEAV.js +0 -1
  120. package/src/client/dist/spa/assets/i18n-DA8dnYBE.js +0 -1
  121. package/src/client/dist/spa/assets/index-B7ivRaq9.js +0 -84
  122. package/src/client/dist/spa/assets/network-auth-CdzNfMI-.js +0 -1
  123. package/src/client/dist/spa/assets/permissionModes-6jhegAm-.js +0 -1
  124. /package/src/client/dist/{spa → pwa}/assets/ChangelogPage-CQ33An2f.css +0 -0
  125. /package/src/client/dist/{spa → pwa}/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuYjalmUiAw-BCOjigVR.woff +0 -0
  126. /package/src/client/dist/{spa → pwa}/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuZtalmUiAw-Ocp2tNig.woff +0 -0
  127. /package/src/client/dist/{spa → pwa}/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuaabVmUiAw-6jEn9xEl.woff +0 -0
  128. /package/src/client/dist/{spa → pwa}/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWub2bVmUiAw-CCs8I9zJ.woff +0 -0
  129. /package/src/client/dist/{spa → pwa}/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWubEbFmUiAw-BLaboPi0.woff +0 -0
  130. /package/src/client/dist/{spa → pwa}/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWubEbVmUiAw-DHuK6FBl.woff +0 -0
  131. /package/src/client/dist/{spa → pwa}/assets/QBadge-DV5kn4mL.js +0 -0
  132. /package/src/client/dist/{spa → pwa}/assets/QBanner-CmfaeizG.js +0 -0
  133. /package/src/client/dist/{spa → pwa}/assets/QBtnGroup-C442-yBb.js +0 -0
  134. /package/src/client/dist/{spa → pwa}/assets/QIcon-C6E-oEjI.js +0 -0
  135. /package/src/client/dist/{spa → pwa}/assets/QInput-BF8x4RTv.js +0 -0
  136. /package/src/client/dist/{spa → pwa}/assets/QList-D5EKdFPn.js +0 -0
  137. /package/src/client/dist/{spa → pwa}/assets/QPage-D3rjwA1l.js +0 -0
  138. /package/src/client/dist/{spa → pwa}/assets/QSeparator-DWTCQxsa.js +0 -0
  139. /package/src/client/dist/{spa → pwa}/assets/QSpace-ezNrZkuj.js +0 -0
  140. /package/src/client/dist/{spa → pwa}/assets/QSpinnerDots-DIV68-cV.js +0 -0
  141. /package/src/client/dist/{spa → pwa}/assets/_plugin-vue_export-helper-BDNMzG2s.js +0 -0
  142. /package/src/client/dist/{spa → pwa}/assets/abap-08VXUWAP.js +0 -0
  143. /package/src/client/dist/{spa → pwa}/assets/apex-BWPQTe0t.js +0 -0
  144. /package/src/client/dist/{spa → pwa}/assets/azcli-Bc_sGQ0U.js +0 -0
  145. /package/src/client/dist/{spa → pwa}/assets/bat-i0X4ZdIN.js +0 -0
  146. /package/src/client/dist/{spa → pwa}/assets/bicep-B5-_aFwp.js +0 -0
  147. /package/src/client/dist/{spa → pwa}/assets/cameligo-DMUM7wLl.js +0 -0
  148. /package/src/client/dist/{spa → pwa}/assets/chunk-QTnfLwEv.js +0 -0
  149. /package/src/client/dist/{spa → pwa}/assets/clojure-Cm7r79vr.js +0 -0
  150. /package/src/client/dist/{spa → pwa}/assets/codicon-ngg6Pgfi.ttf +0 -0
  151. /package/src/client/dist/{spa → pwa}/assets/coffee-Ba7i2nA0.js +0 -0
  152. /package/src/client/dist/{spa → pwa}/assets/cpp-C7h46wYY.js +0 -0
  153. /package/src/client/dist/{spa → pwa}/assets/csharp-BKxtCVv1.js +0 -0
  154. /package/src/client/dist/{spa → pwa}/assets/csp-bTuwJoIa.js +0 -0
  155. /package/src/client/dist/{spa → pwa}/assets/css-DIMkf-bt.js +0 -0
  156. /package/src/client/dist/{spa → pwa}/assets/css.worker-CBlhdqTa.js +0 -0
  157. /package/src/client/dist/{spa → pwa}/assets/cypher-CVaqCwHa.js +0 -0
  158. /package/src/client/dist/{spa → pwa}/assets/dart-onAF5SnQ.js +0 -0
  159. /package/src/client/dist/{spa → pwa}/assets/dockerfile-DZFCIeNp.js +0 -0
  160. /package/src/client/dist/{spa → pwa}/assets/ecl-D05T4iGw.js +0 -0
  161. /package/src/client/dist/{spa → pwa}/assets/editor-BFebQ78e.css +0 -0
  162. /package/src/client/dist/{spa → pwa}/assets/editor.worker-Cu3tR8iJ.js +0 -0
  163. /package/src/client/dist/{spa → pwa}/assets/elixir-6RTg0lbw.js +0 -0
  164. /package/src/client/dist/{spa → pwa}/assets/expand-template-D2yUa54D.css +0 -0
  165. /package/src/client/dist/{spa → pwa}/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-Dr0goTwe.woff +0 -0
  166. /package/src/client/dist/{spa → pwa}/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ-D-x-0Q06.woff2 +0 -0
  167. /package/src/client/dist/{spa → pwa}/assets/flow9-C5_-GSwl.js +0 -0
  168. /package/src/client/dist/{spa → pwa}/assets/fsharp-C8Ef5oNN.js +0 -0
  169. /package/src/client/dist/{spa → pwa}/assets/go-C-y9NEjX.js +0 -0
  170. /package/src/client/dist/{spa → pwa}/assets/graphql-fmXr3nnJ.js +0 -0
  171. /package/src/client/dist/{spa → pwa}/assets/hcl-CpzslTdj.js +0 -0
  172. /package/src/client/dist/{spa → pwa}/assets/html.worker-BKBrNna1.js +0 -0
  173. /package/src/client/dist/{spa → pwa}/assets/index-CwYR3iHt.css +0 -0
  174. /package/src/client/dist/{spa → pwa}/assets/ini-sBoK_t0W.js +0 -0
  175. /package/src/client/dist/{spa → pwa}/assets/java-BEtHBSE6.js +0 -0
  176. /package/src/client/dist/{spa → pwa}/assets/json.worker-BaLYt9Ss.js +0 -0
  177. /package/src/client/dist/{spa → pwa}/assets/julia-Bri6UV-V.js +0 -0
  178. /package/src/client/dist/{spa → pwa}/assets/kotlin-BOotOW0E.js +0 -0
  179. /package/src/client/dist/{spa → pwa}/assets/less-B9JPFI3C.js +0 -0
  180. /package/src/client/dist/{spa → pwa}/assets/lexon-CfSJPG6W.js +0 -0
  181. /package/src/client/dist/{spa → pwa}/assets/lua-CsQS60Ue.js +0 -0
  182. /package/src/client/dist/{spa → pwa}/assets/m3-D-oSqn_W.js +0 -0
  183. /package/src/client/dist/{spa → pwa}/assets/markdown-Cimd5fb3.js +0 -0
  184. /package/src/client/dist/{spa → pwa}/assets/mips-CIPQ_RoX.js +0 -0
  185. /package/src/client/dist/{spa → pwa}/assets/msdax-DauUninz.js +0 -0
  186. /package/src/client/dist/{spa → pwa}/assets/mysql-SOo6toE5.js +0 -0
  187. /package/src/client/dist/{spa → pwa}/assets/objective-c-FvmIjYaQ.js +0 -0
  188. /package/src/client/dist/{spa → pwa}/assets/pascal-DrH0SRf2.js +0 -0
  189. /package/src/client/dist/{spa → pwa}/assets/pascaligo-D-ptJ9y-.js +0 -0
  190. /package/src/client/dist/{spa → pwa}/assets/perl-oz_6vUea.js +0 -0
  191. /package/src/client/dist/{spa → pwa}/assets/pgsql-DTj74zXo.js +0 -0
  192. /package/src/client/dist/{spa → pwa}/assets/php-nr791fC2.js +0 -0
  193. /package/src/client/dist/{spa → pwa}/assets/pla-CopQ2nXW.js +0 -0
  194. /package/src/client/dist/{spa → pwa}/assets/postiats-43DmfD33.js +0 -0
  195. /package/src/client/dist/{spa → pwa}/assets/powerquery-D3hlyOfw.js +0 -0
  196. /package/src/client/dist/{spa → pwa}/assets/powershell-DmHpPYUd.js +0 -0
  197. /package/src/client/dist/{spa → pwa}/assets/protobuf-C531GsRP.js +0 -0
  198. /package/src/client/dist/{spa → pwa}/assets/pug-Z5eAx3Zn.js +0 -0
  199. /package/src/client/dist/{spa → pwa}/assets/qsharp-DkqhCAOL.js +0 -0
  200. /package/src/client/dist/{spa → pwa}/assets/r-BwWrilGY.js +0 -0
  201. /package/src/client/dist/{spa → pwa}/assets/redis-ClamHrr6.js +0 -0
  202. /package/src/client/dist/{spa → pwa}/assets/redshift-DT7zqm-g.js +0 -0
  203. /package/src/client/dist/{spa → pwa}/assets/restructuredtext-BYgofb2h.js +0 -0
  204. /package/src/client/dist/{spa → pwa}/assets/ruby-DezsRK8O.js +0 -0
  205. /package/src/client/dist/{spa → pwa}/assets/rust-DdL9SqIa.js +0 -0
  206. /package/src/client/dist/{spa → pwa}/assets/sb-CcwsVR0C.js +0 -0
  207. /package/src/client/dist/{spa → pwa}/assets/scala-DHpiXF5c.js +0 -0
  208. /package/src/client/dist/{spa → pwa}/assets/scheme-BeGwcela.js +0 -0
  209. /package/src/client/dist/{spa → pwa}/assets/scss-gp-XZpBa.js +0 -0
  210. /package/src/client/dist/{spa → pwa}/assets/shell-CC2rA5mh.js +0 -0
  211. /package/src/client/dist/{spa → pwa}/assets/solidity-BEEn4gHE.js +0 -0
  212. /package/src/client/dist/{spa → pwa}/assets/sophia-CRfGWb83.js +0 -0
  213. /package/src/client/dist/{spa → pwa}/assets/sparql-D_Lu-MrJ.js +0 -0
  214. /package/src/client/dist/{spa → pwa}/assets/sql-NEE52Syq.js +0 -0
  215. /package/src/client/dist/{spa → pwa}/assets/st-DbInun42.js +0 -0
  216. /package/src/client/dist/{spa → pwa}/assets/swift-Bxkupp3x.js +0 -0
  217. /package/src/client/dist/{spa → pwa}/assets/systemverilog-Bz4Y3fRF.js +0 -0
  218. /package/src/client/dist/{spa → pwa}/assets/tcl-DISqw1ZD.js +0 -0
  219. /package/src/client/dist/{spa → pwa}/assets/ts.worker-PmaSgaZk.js +0 -0
  220. /package/src/client/dist/{spa → pwa}/assets/twig-De2hgUGE.js +0 -0
  221. /package/src/client/dist/{spa → pwa}/assets/typespec-B8J7ngcE.js +0 -0
  222. /package/src/client/dist/{spa → pwa}/assets/use-checkbox-E9SV7iH5.js +0 -0
  223. /package/src/client/dist/{spa → pwa}/assets/use-onboarding-B3l7mx48.css +0 -0
  224. /package/src/client/dist/{spa → pwa}/assets/vb-DV3o63ZY.js +0 -0
  225. /package/src/client/dist/{spa → pwa}/assets/vue.runtime.esm-bundler-JZnIeD9D.js +0 -0
  226. /package/src/client/dist/{spa → pwa}/assets/wgsl-DpFanUEy.js +0 -0
  227. /package/src/client/dist/{spa → pwa}/favicon.svg +0 -0
  228. /package/src/client/dist/{spa → pwa}/sounds/ca_va_peter.mp3 +0 -0
  229. /package/src/client/dist/{spa → pwa}/sounds/dry-fart.mp3 +0 -0
  230. /package/src/client/dist/{spa → pwa}/sounds/faaah.mp3 +0 -0
  231. /package/src/client/dist/{spa → pwa}/sounds/for-shure.mp3 +0 -0
  232. /package/src/client/dist/{spa → pwa}/sounds/hey.mp3 +0 -0
  233. /package/src/client/dist/{spa → pwa}/sounds/travail_termine.mp3 +0 -0
  234. /package/src/client/dist/{spa → pwa}/sounds/warcraft-3-humain-travail.mp3 +0 -0
@@ -1,10 +1,25 @@
1
1
  import { getPackageVersion } from '../../../../utils/paths.js';
2
+ import { isWorkspacePermissionAllowed } from '../../../workspace-permission-policy-service.js';
3
+ import { createStreamingBatcher } from '../../streaming-batcher.js';
2
4
  import { CODEX_CAPABILITIES } from './capabilities.js';
3
5
  import { createAppServerClient } from './client.js';
4
6
  import { createMapperState, emitSessionStarted, handleAgentMessageDelta, handleItemCompleted, handleItemStarted, handleRateLimitsUpdated, handleTurnCompleted, QUOTA_PATTERN, tryEmitQuota, } from './event-mapper.js';
5
7
  import { buildCodexOptions } from './options-builder.js';
6
8
  import { buildResponseForResolve, handleServerRequest } from './server-requests.js';
7
9
  import { spawnAppServer } from './spawn.js';
10
+ import { createTurnLiveness } from './turn-liveness.js';
11
+ /** Long enough for normal tool work, short enough to recover a lost turn event. */
12
+ export const CODEX_TURN_IDLE_TIMEOUT_MS = 120_000;
13
+ export const CODEX_GRACEFUL_INTERRUPT_TIMEOUT_MS = 3_000;
14
+ function wait(ms) {
15
+ return new Promise((resolve) => setTimeout(resolve, ms));
16
+ }
17
+ class CodexTurnTimeoutError extends Error {
18
+ constructor() {
19
+ super('Codex stopped reporting activity for this turn');
20
+ this.name = 'CodexTurnTimeoutError';
21
+ }
22
+ }
8
23
  /**
9
24
  * Heuristic for detecting a stale/expired thread id on `thread/resume`.
10
25
  * Canonical wording isn't captured yet — when matched, the engine emits
@@ -34,7 +49,8 @@ export function createCodexEngine() {
34
49
  let discoveredSessionId = options.resumeFromEngineSessionId;
35
50
  let activeTurnId;
36
51
  let steerChain = Promise.resolve();
37
- const safeEmit = (ev) => {
52
+ let gracefulInterruptPromise;
53
+ const emitDirect = (ev) => {
38
54
  try {
39
55
  onEvent(ev);
40
56
  }
@@ -42,6 +58,8 @@ export function createCodexEngine() {
42
58
  console.error('[codex-engine] onEvent handler threw:', err);
43
59
  }
44
60
  };
61
+ const streamingBatcher = createStreamingBatcher(emitDirect);
62
+ const safeEmit = (ev) => streamingBatcher.push(ev);
45
63
  const child = spawnAppServer({ cwd: options.workingDir, signal: abortController.signal });
46
64
  if (child.stderr) {
47
65
  child.stderr.setEncoding('utf8');
@@ -61,6 +79,14 @@ export function createCodexEngine() {
61
79
  resolveTurnDone = resolve;
62
80
  rejectTurnDone = reject;
63
81
  });
82
+ const turnLiveness = createTurnLiveness({
83
+ timeoutMs: CODEX_TURN_IDLE_TIMEOUT_MS,
84
+ onTimeout() {
85
+ mapperState.sawErrorResult = true;
86
+ safeEmit({ kind: 'error', category: 'other', message: 'Codex stopped reporting activity for this turn' });
87
+ rejectTurnDone(new CodexTurnTimeoutError());
88
+ },
89
+ });
64
90
  abortController.signal.addEventListener('abort', () => {
65
91
  const err = new Error('AbortError');
66
92
  err.name = 'AbortError';
@@ -71,9 +97,23 @@ export function createCodexEngine() {
71
97
  stdout: child.stdout,
72
98
  clientInfo: { name: 'kobo', version: getPackageVersion() },
73
99
  onNotification(method, params) {
100
+ turnLiveness.activity();
74
101
  // Ignored notifications — harmless bookkeeping by the server
75
- if (method === 'mcpServer/startupStatus/updated' ||
76
- method === 'thread/started' ||
102
+ if (method === 'mcpServer/startupStatus/updated') {
103
+ const status = (params ?? {});
104
+ const rawStatus = String(status.status ?? status.state ?? 'starting').toLowerCase();
105
+ const normalized = rawStatus.includes('error') || rawStatus.includes('fail')
106
+ ? 'error'
107
+ : rawStatus.includes('ready') || rawStatus.includes('running')
108
+ ? 'ready'
109
+ : 'starting';
110
+ const serverName = String(status.serverName ?? status.name ?? status.id ?? 'MCP');
111
+ const rawMessage = typeof status.message === 'string' ? status.message : undefined;
112
+ const message = rawMessage?.replace(/(token|api[_-]?key|secret)\s*[:=]\s*\S+/gi, '$1=[redacted]');
113
+ safeEmit({ kind: 'mcp:status', serverName, status: normalized, message });
114
+ return;
115
+ }
116
+ if (method === 'thread/started' ||
77
117
  method === 'thread/status/changed' ||
78
118
  method === 'remoteControl/status/changed' ||
79
119
  method === 'turn/started') {
@@ -144,8 +184,11 @@ export function createCodexEngine() {
144
184
  emit: safeEmit,
145
185
  register(callId, pending) {
146
186
  pendingByCallId.set(callId, pending);
187
+ turnLiveness.pause();
147
188
  },
148
189
  respondError: (reqId, code, message) => client.peer.respondError(reqId, code, message),
190
+ respond: (reqId, result) => client.peer.respond(reqId, result),
191
+ autoApprove: (toolName, payload) => isWorkspacePermissionAllowed(options.workspaceId, { engine: 'codex', toolName, payload }),
149
192
  });
150
193
  },
151
194
  onError(err) {
@@ -185,7 +228,9 @@ export function createCodexEngine() {
185
228
  collaborationMode,
186
229
  });
187
230
  activeTurnId = initialTurn.turnId;
231
+ turnLiveness.start();
188
232
  await turnDonePromise;
233
+ turnLiveness.stop();
189
234
  const reason = mapperState.sawErrorResult
190
235
  ? 'error'
191
236
  : mapperState.sawTurnInterrupted
@@ -198,6 +243,7 @@ export function createCodexEngine() {
198
243
  });
199
244
  }
200
245
  catch (err) {
246
+ turnLiveness.stop();
201
247
  const error = err;
202
248
  const message = error.message ?? String(err);
203
249
  const isAbort = userInterrupted || error.name === 'AbortError' || abortController.signal.aborted;
@@ -205,6 +251,9 @@ export function createCodexEngine() {
205
251
  if (isAbort) {
206
252
  safeEmit({ kind: 'session:ended', reason: 'killed', exitCode: null });
207
253
  }
254
+ else if (error.name === 'CodexTurnTimeoutError') {
255
+ safeEmit({ kind: 'session:ended', reason: 'error', exitCode: null });
256
+ }
208
257
  else if (QUOTA_PATTERN.test(message)) {
209
258
  tryEmitQuota(mapperState, safeEmit, message);
210
259
  safeEmit({ kind: 'session:ended', reason: 'error', exitCode: null });
@@ -219,6 +268,8 @@ export function createCodexEngine() {
219
268
  }
220
269
  }
221
270
  finally {
271
+ turnLiveness.stop();
272
+ streamingBatcher.close();
222
273
  iteratorRunning = false;
223
274
  client.close();
224
275
  try {
@@ -257,13 +308,23 @@ export function createCodexEngine() {
257
308
  },
258
309
  interrupt() {
259
310
  userInterrupted = true;
260
- abortController.abort();
261
- if (discoveredSessionId) {
262
- client.interruptTurn({ threadId: discoveredSessionId }).catch(() => { });
263
- }
311
+ if (gracefulInterruptPromise)
312
+ return;
313
+ gracefulInterruptPromise = (async () => {
314
+ if (discoveredSessionId) {
315
+ await Promise.race([
316
+ client.interruptTurn({ threadId: discoveredSessionId }).catch(() => { }),
317
+ wait(CODEX_GRACEFUL_INTERRUPT_TIMEOUT_MS),
318
+ ]);
319
+ }
320
+ await Promise.race([turnDonePromise.catch(() => { }), wait(CODEX_GRACEFUL_INTERRUPT_TIMEOUT_MS)]);
321
+ if (iteratorRunning)
322
+ abortController.abort();
323
+ })();
264
324
  },
265
325
  async stop() {
266
- abortController.abort();
326
+ engineProcess.interrupt();
327
+ await gracefulInterruptPromise;
267
328
  try {
268
329
  await iteratorPromise;
269
330
  }
@@ -282,6 +343,7 @@ export function createCodexEngine() {
282
343
  if (!pending)
283
344
  return false;
284
345
  pendingByCallId.delete(callId);
346
+ turnLiveness.resume();
285
347
  const result = buildResponseForResolve(pending, response);
286
348
  client.peer.respond(pending.requestId, result);
287
349
  return true;
@@ -1,5 +1,5 @@
1
1
  export function handleServerRequest(args) {
2
- const { method, params, requestId, emit, register, respondError } = args;
2
+ const { method, params, requestId, emit, register, respondError, respond, autoApprove } = args;
3
3
  const p = (params ?? {});
4
4
  const callId = typeof p.callId === 'string' ? p.callId : `srv_${requestId}`;
5
5
  if (method === 'mcpServer/elicitation/request') {
@@ -13,24 +13,34 @@ export function handleServerRequest(args) {
13
13
  // (`execCommandApproval`, `applyPatchApproval`) are kept for compat with
14
14
  // older Codex CLI builds that haven't transitioned to the v2 namespace.
15
15
  if (method === 'item/commandExecution/requestApproval' || method === 'execCommandApproval') {
16
+ const payload = { command: p.command, cwd: p.cwd, reason: p.reason };
17
+ if (autoApprove?.('Bash', payload)) {
18
+ respond?.(requestId, { decision: 'accept' });
19
+ return true;
20
+ }
16
21
  register(callId, { requestId, kind: 'command', payload: p });
17
22
  emit({
18
23
  kind: 'session:user-input-requested',
19
24
  requestKind: 'permission',
20
25
  toolCallId: callId,
21
26
  toolName: 'Bash',
22
- payload: { command: p.command, cwd: p.cwd, reason: p.reason },
27
+ payload,
23
28
  });
24
29
  return true;
25
30
  }
26
31
  if (method === 'item/fileChange/requestApproval' || method === 'applyPatchApproval') {
32
+ const payload = { changes: p.changes, reason: p.reason };
33
+ if (autoApprove?.('Edit', payload)) {
34
+ respond?.(requestId, { decision: 'accept' });
35
+ return true;
36
+ }
27
37
  register(callId, { requestId, kind: 'file_change', payload: p });
28
38
  emit({
29
39
  kind: 'session:user-input-requested',
30
40
  requestKind: 'permission',
31
41
  toolCallId: callId,
32
42
  toolName: 'Edit',
33
- payload: { changes: p.changes, reason: p.reason },
43
+ payload,
34
44
  });
35
45
  return true;
36
46
  }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Tracks a single active Codex turn. A pending user decision deliberately
3
+ * pauses the deadline: the agent is waiting on a human, not stalled.
4
+ */
5
+ export function createTurnLiveness(input) {
6
+ let timer;
7
+ let active = false;
8
+ let paused = false;
9
+ let timedOut = false;
10
+ const arm = () => {
11
+ if (!active || paused || timedOut)
12
+ return;
13
+ if (timer)
14
+ clearTimeout(timer);
15
+ timer = setTimeout(() => {
16
+ timer = undefined;
17
+ if (!active || paused || timedOut)
18
+ return;
19
+ timedOut = true;
20
+ input.onTimeout();
21
+ }, input.timeoutMs);
22
+ };
23
+ return {
24
+ start() {
25
+ active = true;
26
+ paused = false;
27
+ timedOut = false;
28
+ arm();
29
+ },
30
+ activity() {
31
+ arm();
32
+ },
33
+ pause() {
34
+ paused = true;
35
+ if (timer)
36
+ clearTimeout(timer);
37
+ timer = undefined;
38
+ },
39
+ resume() {
40
+ if (!active || !paused)
41
+ return;
42
+ paused = false;
43
+ arm();
44
+ },
45
+ stop() {
46
+ active = false;
47
+ paused = false;
48
+ if (timer)
49
+ clearTimeout(timer);
50
+ timer = undefined;
51
+ },
52
+ };
53
+ }
@@ -6,5 +6,9 @@ export function routeEvent(workspaceId, agentSessionId, event) {
6
6
  emitEphemeral(workspaceId, 'agent:event', event);
7
7
  return;
8
8
  }
9
+ if (event.kind === 'mcp:status' && event.status !== 'error') {
10
+ emitEphemeral(workspaceId, 'agent:event', event);
11
+ return;
12
+ }
9
13
  emit(workspaceId, 'agent:event', event, agentSessionId);
10
14
  }
@@ -11,6 +11,7 @@ import { getEffectiveSettings } from '../settings-service.js';
11
11
  import { refreshNow } from '../usage/poller.js';
12
12
  import * as wakeupService from '../wakeup-service.js';
13
13
  import { emit, emitEphemeral } from '../websocket-service.js';
14
+ import * as permissionPolicyService from '../workspace-permission-policy-service.js';
14
15
  import { getWorkspace as getWs, markWorkspaceUnread, updateWorkspaceStatus, } from '../workspace-service.js';
15
16
  import { resolveEngine } from './engines/registry.js';
16
17
  import { routeEvent } from './event-router.js';
@@ -554,6 +555,10 @@ function handleEvent(workspaceId, agentSessionId, ev) {
554
555
  tasksDoneSnapshot.delete(workspaceId);
555
556
  taskStateSnapshot.delete(workspaceId);
556
557
  clearPendingForSession(workspaceId, agentSessionId);
558
+ // A completed/failed SDK session cannot resolve canUseTool anymore. Drop
559
+ // its persisted prompts too, otherwise a reconnect resurrects a panel
560
+ // whose backend callback has already been discarded.
561
+ purgeAllPersistedUserInputRequests(workspaceId, agentSessionId);
557
562
  // Must run BEFORE autoLoopService.onSessionEnded → spawnNextIteration →
558
563
  // startAgent, otherwise startAgent throws "Agent already running" because
559
564
  // the just-ended controller is still in the map.
@@ -698,6 +703,8 @@ function onSessionEnded(workspaceId, agentSessionId, exitCode, reason, resumeFai
698
703
  * query the controller via `_getControllers()` for tests.
699
704
  */
700
705
  export function startAgent(workspaceId, workingDir, prompt, model, resume = false, agentPermissionMode, existingSessionId, reasoningEffort) {
706
+ // "For this turn" never survives a new agent session.
707
+ permissionPolicyService.clearTurnPermissions(workspaceId);
701
708
  // Zombie detection: an SDK iterator hung on a never-resolved canUseTool
702
709
  // callback can leave its controller in the map after the workspace is
703
710
  // logically idle. Evict it instead of refusing the new session.
@@ -1019,6 +1026,16 @@ export async function answerPendingPermission(workspaceId, decision) {
1019
1026
  const response = decision.decision === 'allow'
1020
1027
  ? { kind: 'permission-allow' }
1021
1028
  : { kind: 'permission-deny', reason: decision.reason };
1029
+ if (decision.decision === 'allow' && (decision.scope === 'operation' || decision.scope === 'tool')) {
1030
+ permissionPolicyService.createWorkspacePermissionRule(workspaceId, { engine: ws.engine ?? 'claude-code', toolName: head.toolName, payload: head.toolInput }, decision.scope);
1031
+ }
1032
+ if (decision.decision === 'allow' && decision.scope === 'turn') {
1033
+ permissionPolicyService.allowPermissionForTurn(workspaceId, {
1034
+ engine: ws.engine ?? 'claude-code',
1035
+ toolName: head.toolName,
1036
+ payload: head.toolInput,
1037
+ });
1038
+ }
1022
1039
  const resolved = engineProcess.resolvePendingUserInput(decision.toolCallId, response);
1023
1040
  if (!resolved) {
1024
1041
  throw new Error(`No pending callback for toolCallId '${decision.toolCallId}'`);
@@ -0,0 +1,34 @@
1
+ /** Coalesces token deltas before they reach WebSocket persistence. */
2
+ export function createStreamingBatcher(emit, options = {}) {
3
+ const windowMs = options.windowMs ?? 40;
4
+ const pending = new Map();
5
+ const flush = (messageId) => {
6
+ const entry = pending.get(messageId);
7
+ if (!entry)
8
+ return;
9
+ clearTimeout(entry.timer);
10
+ pending.delete(messageId);
11
+ emit({ kind: 'message:text', messageId, text: entry.text, streaming: true });
12
+ };
13
+ return {
14
+ push(event) {
15
+ if (event.kind === 'message:text' && event.streaming) {
16
+ const entry = pending.get(event.messageId);
17
+ if (entry) {
18
+ entry.text += event.text;
19
+ return;
20
+ }
21
+ const timer = setTimeout(() => flush(event.messageId), windowMs);
22
+ pending.set(event.messageId, { text: event.text, timer });
23
+ return;
24
+ }
25
+ if (event.kind === 'message:end')
26
+ flush(event.messageId);
27
+ emit(event);
28
+ },
29
+ close() {
30
+ for (const messageId of [...pending.keys()])
31
+ flush(messageId);
32
+ },
33
+ };
34
+ }
@@ -269,6 +269,25 @@ export async function extractNotionPage(notionUrl) {
269
269
  mcpProcess.kill();
270
270
  }
271
271
  }
272
+ /** Lightweight MCP/auth smoke test; does not read a user page or mutate Notion. */
273
+ export async function testNotionConnection() {
274
+ const startedAt = Date.now();
275
+ const global = getGlobalSettings();
276
+ const mcpProcess = spawnNotionMcp(global.notionMcpKey);
277
+ try {
278
+ await initializeMcp(mcpProcess);
279
+ await callMcpTool(mcpProcess, 'API-get-users', { page_size: 1 });
280
+ return {
281
+ ok: true,
282
+ durationMs: Date.now() - startedAt,
283
+ detail: 'MCP connection and Notion authentication succeeded',
284
+ };
285
+ }
286
+ finally {
287
+ mcpProcess.stdin?.end();
288
+ mcpProcess.kill();
289
+ }
290
+ }
272
291
  export async function listNotionUsers() {
273
292
  const global = getGlobalSettings();
274
293
  const mcpProcess = spawnNotionMcp(global.notionMcpKey);
@@ -168,6 +168,12 @@ export async function checkPrStatuses() {
168
168
  // Archive on a transition FROM OPEN to CLOSED/MERGED. Skips the
169
169
  // base-change detection below — archiving wins.
170
170
  if (prev?.state === 'OPEN' && (pr.state === 'MERGED' || pr.state === 'CLOSED')) {
171
+ if (pr.state === 'MERGED') {
172
+ emitEphemeral(ws.id, 'pr:merged', {
173
+ prNumber: pr.number,
174
+ prUrl: pr.url,
175
+ });
176
+ }
171
177
  if (['extracting', 'brainstorming', 'executing'].includes(ws.status)) {
172
178
  // Agent is working — update the cache but skip auto-archive.
173
179
  // (The defensive base preservation from the no-base branch doesn't apply here
@@ -213,33 +219,32 @@ export async function checkPrStatuses() {
213
219
  }
214
220
  continue; // do not run base-change detection on a workspace we just archived
215
221
  }
216
- // Review-decision and CI transitions (only on OPEN PRs; first-sight is
217
- // silent). Reuses the baseline rule from base-change detection: act only
218
- // on an actual transition between two known states. Each notable
219
- // transition (changes-requested newly raised, CI newly failing) flips
220
- // `hasUnread` so the workspace card stands out as "something new to
221
- // look at" in the drawer — the unread bit persists until the user opens
222
- // the workspace, matching the existing read/unread UX.
222
+ // Review, CI, mergeability, and readiness transitions (only on OPEN PRs;
223
+ // first-sight is silent). Each check is independent so simultaneous
224
+ // state changes emit every applicable event.
223
225
  if (pr.state === 'OPEN' && prev) {
226
+ const payload = { prNumber: pr.number, prUrl: pr.url };
224
227
  if (prev.reviewDecision !== 'CHANGES_REQUESTED' && pr.reviewDecision === 'CHANGES_REQUESTED') {
225
- emitEphemeral(ws.id, 'pr:changes-requested', {
226
- prNumber: pr.number,
227
- prUrl: pr.url,
228
- });
228
+ emitEphemeral(ws.id, 'pr:changes-requested', payload);
229
229
  markUnread(ws.id);
230
230
  }
231
- else if (prev.reviewDecision === 'CHANGES_REQUESTED' && pr.reviewDecision === 'APPROVED') {
232
- emitEphemeral(ws.id, 'pr:approved', {
233
- prNumber: pr.number,
234
- prUrl: pr.url,
235
- });
231
+ if (prev.reviewDecision !== 'APPROVED' && pr.reviewDecision === 'APPROVED') {
232
+ emitEphemeral(ws.id, 'pr:approved', payload);
236
233
  }
237
234
  if (prev.ci.rollup !== 'FAILURE' && pr.ci.rollup === 'FAILURE') {
235
+ emitEphemeral(ws.id, 'pr:ci-failed', payload);
236
+ markUnread(ws.id);
237
+ }
238
+ if (prev.ci.rollup === 'FAILURE' && pr.ci.rollup === 'SUCCESS') {
239
+ emitEphemeral(ws.id, 'pr:ci-recovered', payload);
240
+ }
241
+ if (prev.mergeable !== 'CONFLICTING' && pr.mergeable === 'CONFLICTING') {
242
+ emitEphemeral(ws.id, 'pr:merge-conflict', payload);
238
243
  markUnread(ws.id);
239
244
  }
240
245
  const notBusy = !['extracting', 'brainstorming', 'executing'].includes(ws.status);
241
246
  if (notBusy && !prev.readyToMerge && pr.readyToMerge) {
242
- emitEphemeral(ws.id, 'pr:ready-to-merge', { prNumber: pr.number, prUrl: pr.url });
247
+ emitEphemeral(ws.id, 'pr:ready-to-merge', payload);
243
248
  markUnread(ws.id);
244
249
  }
245
250
  }
@@ -61,7 +61,7 @@ export function searchEvents(query, options = {}) {
61
61
  // realistic datasets, without scanning the entire table.
62
62
  const dbLimit = Math.max(limit * 3, 100);
63
63
  const rows = db
64
- .prepare(`SELECT e.workspace_id, e.type, e.created_at, e.payload,
64
+ .prepare(`SELECT e.id, e.workspace_id, e.session_id, e.type, e.created_at, e.payload,
65
65
  w.name AS workspace_name, w.archived_at
66
66
  FROM ws_events e
67
67
  JOIN workspaces w ON e.workspace_id = w.id
@@ -88,6 +88,8 @@ export function searchEvents(query, options = {}) {
88
88
  if (idx < 0)
89
89
  continue;
90
90
  results.push({
91
+ eventId: row.id,
92
+ sessionId: row.session_id,
91
93
  workspaceId: row.workspace_id,
92
94
  workspaceName: row.workspace_name,
93
95
  archived: row.archived_at !== null,
@@ -13,6 +13,26 @@ export function parseSentryUrl(url) {
13
13
  }
14
14
  return match[1];
15
15
  }
16
+ /** Lightweight MCP/auth smoke test; does not access or mutate an issue. */
17
+ export async function testSentryConnection() {
18
+ const startedAt = Date.now();
19
+ const global = getGlobalSettings();
20
+ const config = readSentryMcpConfig(global.sentryMcpKey);
21
+ const mcpProcess = spawnMcpProcess(config.command, config.args, config.env);
22
+ try {
23
+ await initializeMcp(mcpProcess);
24
+ await callMcpTool(mcpProcess, 'whoami', {});
25
+ return {
26
+ ok: true,
27
+ durationMs: Date.now() - startedAt,
28
+ detail: 'MCP connection and Sentry authentication succeeded',
29
+ };
30
+ }
31
+ finally {
32
+ mcpProcess.stdin?.end();
33
+ mcpProcess.kill();
34
+ }
35
+ }
16
36
  const SENTRY_CONFIG_ERROR = "Sentry MCP server not configured in ~/.claude.json — add an enabled 'sentry' entry under mcpServers";
17
37
  /**
18
38
  * Return the first enabled (`disabled !== true`) MCP server entry from
@@ -698,6 +698,76 @@ const settingsMigrations = [
698
698
  }
699
699
  },
700
700
  },
701
+ {
702
+ version: 44,
703
+ name: 'add-pr-notification-sounds',
704
+ migrate: ({ global }) => {
705
+ const keys = [
706
+ 'audioPrCiFailedSound',
707
+ 'audioPrCiRecoveredSound',
708
+ 'audioPrChangesRequestedSound',
709
+ 'audioPrApprovedSound',
710
+ 'audioPrMergeConflictSound',
711
+ 'audioPrReadyToMergeSound',
712
+ 'audioPrMergedSound',
713
+ ];
714
+ for (const key of keys) {
715
+ if (typeof global[key] !== 'string' || global[key].length === 0) {
716
+ global[key] = 'inherit';
717
+ }
718
+ }
719
+ },
720
+ },
721
+ {
722
+ version: 45,
723
+ name: 'add-pr-notification-audio-controls',
724
+ migrate: ({ global }) => {
725
+ const legacyVolume = global.audioNotificationVolume;
726
+ const volume = typeof legacyVolume === 'number' && Number.isFinite(legacyVolume) && legacyVolume >= 0 && legacyVolume <= 1
727
+ ? legacyVolume
728
+ : 1;
729
+ const events = [
730
+ 'CiFailed',
731
+ 'CiRecovered',
732
+ 'ChangesRequested',
733
+ 'Approved',
734
+ 'MergeConflict',
735
+ 'ReadyToMerge',
736
+ 'Merged',
737
+ ];
738
+ for (const event of events) {
739
+ const soundKey = `audioPr${event}Sound`;
740
+ const enabledKey = `audioPr${event}Enabled`;
741
+ const volumeKey = `audioPr${event}Volume`;
742
+ const muted = global[soundKey] === 'none';
743
+ if (muted)
744
+ global[soundKey] = 'inherit';
745
+ if (typeof global[enabledKey] !== 'boolean')
746
+ global[enabledKey] = !muted;
747
+ if (typeof global[volumeKey] !== 'number' || !Number.isFinite(global[volumeKey])) {
748
+ global[volumeKey] = volume;
749
+ }
750
+ }
751
+ },
752
+ },
753
+ {
754
+ version: 46,
755
+ name: 'add-integration-enabled-switches',
756
+ migrate: ({ global }) => {
757
+ if (typeof global.notionEnabled !== 'boolean')
758
+ global.notionEnabled = true;
759
+ if (typeof global.sentryEnabled !== 'boolean')
760
+ global.sentryEnabled = true;
761
+ },
762
+ },
763
+ {
764
+ version: 47,
765
+ name: 'add-thinking-block-visibility',
766
+ migrate: ({ global }) => {
767
+ if (typeof global.showThinkingBlocks !== 'boolean')
768
+ global.showThinkingBlocks = true;
769
+ },
770
+ },
701
771
  ];
702
772
  /** Current settings schema version — always equals the highest migration version. */
703
773
  export const SETTINGS_SCHEMA_VERSION = settingsMigrations.length > 0 ? settingsMigrations[settingsMigrations.length - 1].version : 0;
@@ -754,6 +824,27 @@ function defaultSettings() {
754
824
  audioNotificationSound: 'hey.mp3',
755
825
  audioQuestionSound: 'hey.mp3',
756
826
  audioWorkspaceCreatedSound: 'warcraft-3-humain-travail.mp3',
827
+ audioPrCiFailedSound: 'inherit',
828
+ audioPrCiFailedEnabled: true,
829
+ audioPrCiFailedVolume: 1,
830
+ audioPrCiRecoveredSound: 'inherit',
831
+ audioPrCiRecoveredEnabled: true,
832
+ audioPrCiRecoveredVolume: 1,
833
+ audioPrChangesRequestedSound: 'inherit',
834
+ audioPrChangesRequestedEnabled: true,
835
+ audioPrChangesRequestedVolume: 1,
836
+ audioPrApprovedSound: 'inherit',
837
+ audioPrApprovedEnabled: true,
838
+ audioPrApprovedVolume: 1,
839
+ audioPrMergeConflictSound: 'inherit',
840
+ audioPrMergeConflictEnabled: true,
841
+ audioPrMergeConflictVolume: 1,
842
+ audioPrReadyToMergeSound: 'inherit',
843
+ audioPrReadyToMergeEnabled: true,
844
+ audioPrReadyToMergeVolume: 1,
845
+ audioPrMergedSound: 'inherit',
846
+ audioPrMergedEnabled: true,
847
+ audioPrMergedVolume: 1,
757
848
  audioNotificationVolume: 1,
758
849
  audioQuestionVolume: 1,
759
850
  audioWorkspaceCreatedVolume: 1,
@@ -764,6 +855,9 @@ function defaultSettings() {
764
855
  defaultPermissionModeByEngine: { 'claude-code': 'plan', codex: 'plan' },
765
856
  notionMcpKey: '',
766
857
  sentryMcpKey: '',
858
+ notionEnabled: true,
859
+ sentryEnabled: true,
860
+ showThinkingBlocks: true,
767
861
  tags: [...DEFAULT_WORKSPACE_TAGS],
768
862
  branchPrefixes: [...DEFAULT_BRANCH_PREFIXES],
769
863
  worktreesPath: WORKTREES_PATH,
@@ -1115,6 +1209,27 @@ export function updateGlobalSettings(data) {
1115
1209
  'audioNotificationSound',
1116
1210
  'audioQuestionSound',
1117
1211
  'audioWorkspaceCreatedSound',
1212
+ 'audioPrCiFailedSound',
1213
+ 'audioPrCiFailedEnabled',
1214
+ 'audioPrCiFailedVolume',
1215
+ 'audioPrCiRecoveredSound',
1216
+ 'audioPrCiRecoveredEnabled',
1217
+ 'audioPrCiRecoveredVolume',
1218
+ 'audioPrChangesRequestedSound',
1219
+ 'audioPrChangesRequestedEnabled',
1220
+ 'audioPrChangesRequestedVolume',
1221
+ 'audioPrApprovedSound',
1222
+ 'audioPrApprovedEnabled',
1223
+ 'audioPrApprovedVolume',
1224
+ 'audioPrMergeConflictSound',
1225
+ 'audioPrMergeConflictEnabled',
1226
+ 'audioPrMergeConflictVolume',
1227
+ 'audioPrReadyToMergeSound',
1228
+ 'audioPrReadyToMergeEnabled',
1229
+ 'audioPrReadyToMergeVolume',
1230
+ 'audioPrMergedSound',
1231
+ 'audioPrMergedEnabled',
1232
+ 'audioPrMergedVolume',
1118
1233
  'audioNotificationVolume',
1119
1234
  'audioQuestionVolume',
1120
1235
  'audioWorkspaceCreatedVolume',
@@ -1125,6 +1240,9 @@ export function updateGlobalSettings(data) {
1125
1240
  'defaultPermissionModeByEngine',
1126
1241
  'notionMcpKey',
1127
1242
  'sentryMcpKey',
1243
+ 'notionEnabled',
1244
+ 'sentryEnabled',
1245
+ 'showThinkingBlocks',
1128
1246
  'tags',
1129
1247
  'branchPrefixes',
1130
1248
  'worktreesPath',
@@ -1161,7 +1279,18 @@ export function updateGlobalSettings(data) {
1161
1279
  const sanitized = sanitizeBranchPrefixes(filtered.branchPrefixes);
1162
1280
  filtered.branchPrefixes = sanitized.length > 0 ? sanitized : settings.global.branchPrefixes;
1163
1281
  }
1164
- for (const key of ['audioNotificationVolume', 'audioQuestionVolume', 'audioWorkspaceCreatedVolume']) {
1282
+ for (const key of [
1283
+ 'audioNotificationVolume',
1284
+ 'audioQuestionVolume',
1285
+ 'audioWorkspaceCreatedVolume',
1286
+ 'audioPrCiFailedVolume',
1287
+ 'audioPrCiRecoveredVolume',
1288
+ 'audioPrChangesRequestedVolume',
1289
+ 'audioPrApprovedVolume',
1290
+ 'audioPrMergeConflictVolume',
1291
+ 'audioPrReadyToMergeVolume',
1292
+ 'audioPrMergedVolume',
1293
+ ]) {
1165
1294
  if (filtered[key] !== undefined) {
1166
1295
  const v = Number(filtered[key]);
1167
1296
  filtered[key] = Number.isFinite(v) ? Math.max(0, Math.min(1, v)) : 1;