@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.
- package/AGENTS.md +5 -4
- package/CHANGELOG.md +29 -0
- package/README.md +16 -10
- package/dist/server/db/migrations.js +21 -0
- package/dist/server/db/schema.js +14 -0
- package/dist/server/routes/health.js +2 -2
- package/dist/server/routes/notion.js +18 -1
- package/dist/server/routes/sentry.js +15 -1
- package/dist/server/routes/workspaces.js +140 -6
- package/dist/server/services/agent/engines/claude-code/engine.js +17 -1
- package/dist/server/services/agent/engines/claude-code/event-mapper.js +31 -2
- package/dist/server/services/agent/engines/claude-code/options-builder.js +3 -0
- package/dist/server/services/agent/engines/codex/engine.js +70 -8
- package/dist/server/services/agent/engines/codex/server-requests.js +13 -3
- package/dist/server/services/agent/engines/codex/turn-liveness.js +53 -0
- package/dist/server/services/agent/event-router.js +4 -0
- package/dist/server/services/agent/orchestrator.js +17 -0
- package/dist/server/services/agent/streaming-batcher.js +34 -0
- package/dist/server/services/notion-service.js +19 -0
- package/dist/server/services/pr-watcher-service.js +22 -17
- package/dist/server/services/search-service.js +3 -1
- package/dist/server/services/sentry-service.js +20 -0
- package/dist/server/services/settings-service.js +130 -1
- package/dist/server/services/workspace-permission-policy-service.js +86 -0
- package/dist/server/utils/paths.js +3 -3
- package/package.json +3 -3
- package/src/client/dist/pwa/assets/ActivityFeed-D9EfbRqv.css +1 -0
- package/src/client/dist/pwa/assets/ActivityFeed-ujsfk0sG.js +8 -0
- package/src/client/dist/{spa/assets/ChangelogPage-EA4LPGWB.js → pwa/assets/ChangelogPage-DLmP9KSb.js} +1 -1
- package/src/client/dist/{spa/assets/ClosePopup-BxYvEnmx.js → pwa/assets/ClosePopup-Cc7aUs96.js} +1 -1
- package/src/client/dist/pwa/assets/CreatePage-DfMhzgbv.js +2 -0
- package/src/client/dist/pwa/assets/CreatePage-qMgdzdb-.css +1 -0
- package/src/client/dist/pwa/assets/DiffViewer-B-TSPjtj.js +8 -0
- package/src/client/dist/{spa/assets/DiffViewer-BuYD1EDP.css → pwa/assets/DiffViewer-CRrDdYBa.css} +1 -1
- package/src/client/dist/{spa/assets/HealthPage-5F4m82-8.js → pwa/assets/HealthPage-13Ugeexp.js} +1 -1
- package/src/client/dist/pwa/assets/MainLayout-BzOcdtNd.js +37 -0
- package/src/client/dist/pwa/assets/MainLayout-CgHzEM37.css +1 -0
- package/src/client/dist/{spa/assets/QBtnToggle-Br3smB6b.js → pwa/assets/QBtnToggle-BUJ1g7BR.js} +1 -1
- package/src/client/dist/{spa/assets/QChip-Bc7L8LKe.js → pwa/assets/QChip-CPiNmK-x.js} +1 -1
- package/src/client/dist/pwa/assets/QSelect-D63npmWX.js +36 -0
- package/src/client/dist/pwa/assets/QTooltip-DOQ5d9M2.js +1 -0
- package/src/client/dist/pwa/assets/SearchPage-B08_UXDs.css +1 -0
- package/src/client/dist/pwa/assets/SearchPage-C7ulmpmI.js +1 -0
- package/src/client/dist/pwa/assets/SettingsPage-DJEH2ahl.css +1 -0
- package/src/client/dist/pwa/assets/SettingsPage-DY1g-LTs.js +16 -0
- package/src/client/dist/pwa/assets/ThinkingItem-BVoamjnK.css +1 -0
- package/src/client/dist/pwa/assets/ThinkingItem-M7hXIRih.js +1 -0
- package/src/client/dist/{spa/assets/TouchPan-IMQRiAGn.js → pwa/assets/TouchPan-g-ZGD0pd.js} +1 -1
- package/src/client/dist/pwa/assets/WorkspacePage-BU_uwlKN.js +4 -0
- package/src/client/dist/pwa/assets/WorkspacePage-CnRbNBAh.css +1 -0
- package/src/client/dist/{spa/assets/build-path-tree-Cgbihluh.js → pwa/assets/build-path-tree-B3R74R5X.js} +1 -1
- package/src/client/dist/{spa/assets/cssMode-57i-GAo-.js → pwa/assets/cssMode-Ca0QWQGQ.js} +1 -1
- package/src/client/dist/pwa/assets/documents-a-MDhFWk.js +1 -0
- package/src/client/dist/{spa/assets/editor.api2-CNFQDxW4.js → pwa/assets/editor.api2-CQQ3TFHp.js} +1 -1
- package/src/client/dist/{spa/assets/editor.main-CJJlXa_x.js → pwa/assets/editor.main-BmtlNqLW.js} +2 -2
- package/src/client/dist/{spa/assets/expand-template-CN8wfDhv.js → pwa/assets/expand-template-DkLDjInO.js} +1 -1
- package/src/client/dist/{spa/assets/formatters-9eR6rvkm.js → pwa/assets/formatters-COWwqgNl.js} +1 -1
- package/src/client/dist/{spa/assets/freemarker2-qbKTPHxB.js → pwa/assets/freemarker2-Be_VI-2P.js} +1 -1
- package/src/client/dist/{spa/assets/handlebars-DV81bryH.js → pwa/assets/handlebars-C6MV-4UG.js} +1 -1
- package/src/client/dist/{spa/assets/html-i5i_Ua1s.js → pwa/assets/html-H42ZDxQV.js} +1 -1
- package/src/client/dist/{spa/assets/htmlMode-Du1z3aXL.js → pwa/assets/htmlMode-COCELcr-.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-BgrVh81z.js +1 -0
- package/src/client/dist/pwa/assets/index-Cs6I8vi_.js +84 -0
- package/src/client/dist/{spa/assets/javascript-DgQZF_pf.js → pwa/assets/javascript-CiFkTKAE.js} +1 -1
- package/src/client/dist/{spa/assets/jsonMode-6XLEI_p0.js → pwa/assets/jsonMode--0Mpy1sQ.js} +1 -1
- package/src/client/dist/{spa/assets/kobo-commands-CplA4Y0q.js → pwa/assets/kobo-commands-BzxwZ3vQ.js} +1 -1
- package/src/client/dist/{spa/assets/layout-BbHOhk37.js → pwa/assets/layout-BcUfiaFQ.js} +1 -1
- package/src/client/dist/{spa/assets/liquid-54MmW5iG.js → pwa/assets/liquid-BlkSsRl8.js} +1 -1
- package/src/client/dist/{spa/assets/lspLanguageFeatures-xU1LF4fY.js → pwa/assets/lspLanguageFeatures-DXelvPSE.js} +1 -1
- package/src/client/dist/{spa/assets/mdx-BbX1cCc8.js → pwa/assets/mdx-BoNBdGqW.js} +1 -1
- package/src/client/dist/{spa/assets/monaco.contribution-CNHTTcDF.js → pwa/assets/monaco.contribution-D8uREgcb.js} +2 -2
- package/src/client/dist/pwa/assets/network-auth-wzwG23Rr.js +1 -0
- package/src/client/dist/pwa/assets/pending-diff-open-DyoLDUlV.js +1 -0
- package/src/client/dist/pwa/assets/permissionModes-D7Iv04mQ.js +1 -0
- package/src/client/dist/{spa/assets/project-color-PpNx-yz4.js → pwa/assets/project-color-C3soNzD0.js} +1 -1
- package/src/client/dist/{spa/assets/python-BL4EBjGI.js → pwa/assets/python-DZCdvSIy.js} +1 -1
- package/src/client/dist/{spa/assets/razor-BdIRjBrX.js → pwa/assets/razor-CFwK0FwM.js} +1 -1
- package/src/client/dist/{spa/assets/render-chat-markdown-CYeqc8cI.js → pwa/assets/render-chat-markdown-DCydC_jc.js} +1 -1
- package/src/client/dist/{spa/assets/tsMode-BoQuq_mo.js → pwa/assets/tsMode-D8ZbVZB6.js} +1 -1
- package/src/client/dist/{spa/assets/typescript-TXE1oBSg.js → pwa/assets/typescript-CjxR71vN.js} +1 -1
- package/src/client/dist/{spa/assets/use-onboarding-Ch0swQ7p.js → pwa/assets/use-onboarding-DA5ka-XJ.js} +1 -1
- package/src/client/dist/{spa/assets/use-quasar--cg_cUkd.js → pwa/assets/use-quasar-5A_Wy1iR.js} +1 -1
- package/src/client/dist/{spa/assets/workers-Clrb2kWw.js → pwa/assets/workers-Bv1gqcaP.js} +1 -1
- package/src/client/dist/{spa/assets/xml-36_1aaai.js → pwa/assets/xml-vP8L_5ev.js} +1 -1
- package/src/client/dist/{spa/assets/yaml-CTVfk9Oc.js → pwa/assets/yaml-YN_D0NXJ.js} +1 -1
- package/src/client/dist/{spa → pwa}/index.html +2 -2
- package/src/client/dist/pwa/manifest.json +1 -0
- package/src/client/dist/pwa/sounds/7eme-compagnie-03.mp3 +0 -0
- package/src/client/dist/pwa/sounds/aller-ftg.mp3 +0 -0
- package/src/client/dist/pwa/sounds/arrete-de-mentir.mp3 +0 -0
- package/src/client/dist/pwa/sounds/arretez-les-messages.mp3 +0 -0
- package/src/client/dist/pwa/sounds/bah-alors-on-est-nul.mp3 +0 -0
- package/src/client/dist/pwa/sounds/gta-v-death.mp3 +0 -0
- package/src/client/dist/pwa/sounds/nan-tu-degages.mp3 +0 -0
- package/src/client/dist/pwa/sounds/nan-wallah-pardon.mp3 +0 -0
- package/src/client/dist/pwa/sounds/ouais-cest-greg.mp3 +0 -0
- package/src/client/dist/pwa/sounds/pas-ca-zinedine.mp3 +0 -0
- package/src/client/dist/pwa/sounds/ta-gueule.mp3 +0 -0
- package/src/client/dist/pwa/sounds/tu-vas-la-fermer.mp3 +0 -0
- package/src/client/dist/pwa/sw.js +1 -0
- package/src/client/dist/pwa/workbox-778a14ff.js +1 -0
- package/src/client/dist/spa/assets/ActivityFeed-98LfDiwH.js +0 -8
- package/src/client/dist/spa/assets/ActivityFeed-HbMZOClq.css +0 -1
- package/src/client/dist/spa/assets/CreatePage-C1yAhjsO.css +0 -1
- package/src/client/dist/spa/assets/CreatePage-Cg0BbQwp.js +0 -2
- package/src/client/dist/spa/assets/DiffViewer-CLp2N6Zh.js +0 -8
- package/src/client/dist/spa/assets/MainLayout-7nwhu8UE.css +0 -1
- package/src/client/dist/spa/assets/MainLayout-Dfc_DsIt.js +0 -37
- package/src/client/dist/spa/assets/QExpansionItem-BHyDyRL4.js +0 -1
- package/src/client/dist/spa/assets/QScrollArea-CncsSfK0.js +0 -1
- package/src/client/dist/spa/assets/QSelect-DXXOwpf6.js +0 -36
- package/src/client/dist/spa/assets/QTooltip-C6Vpr1qf.js +0 -1
- package/src/client/dist/spa/assets/SearchPage-BYM38aZA.js +0 -1
- package/src/client/dist/spa/assets/SearchPage-cVwt0DaQ.css +0 -1
- package/src/client/dist/spa/assets/SettingsPage-DendQ8y4.js +0 -16
- package/src/client/dist/spa/assets/SettingsPage-NDsw0Jvo.css +0 -1
- package/src/client/dist/spa/assets/WorkspacePage-BOApCawq.css +0 -1
- package/src/client/dist/spa/assets/WorkspacePage-BghKaAab.js +0 -4
- package/src/client/dist/spa/assets/documents-CdYfTEAV.js +0 -1
- package/src/client/dist/spa/assets/i18n-DA8dnYBE.js +0 -1
- package/src/client/dist/spa/assets/index-B7ivRaq9.js +0 -84
- package/src/client/dist/spa/assets/network-auth-CdzNfMI-.js +0 -1
- package/src/client/dist/spa/assets/permissionModes-6jhegAm-.js +0 -1
- /package/src/client/dist/{spa → pwa}/assets/ChangelogPage-CQ33An2f.css +0 -0
- /package/src/client/dist/{spa → pwa}/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuYjalmUiAw-BCOjigVR.woff +0 -0
- /package/src/client/dist/{spa → pwa}/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuZtalmUiAw-Ocp2tNig.woff +0 -0
- /package/src/client/dist/{spa → pwa}/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWuaabVmUiAw-6jEn9xEl.woff +0 -0
- /package/src/client/dist/{spa → pwa}/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWub2bVmUiAw-CCs8I9zJ.woff +0 -0
- /package/src/client/dist/{spa → pwa}/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWubEbFmUiAw-BLaboPi0.woff +0 -0
- /package/src/client/dist/{spa → pwa}/assets/KFOMCnqEu92Fr1ME7kSn66aGLdTylUAMQXC89YmC2DPNWubEbVmUiAw-DHuK6FBl.woff +0 -0
- /package/src/client/dist/{spa → pwa}/assets/QBadge-DV5kn4mL.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/QBanner-CmfaeizG.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/QBtnGroup-C442-yBb.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/QIcon-C6E-oEjI.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/QInput-BF8x4RTv.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/QList-D5EKdFPn.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/QPage-D3rjwA1l.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/QSeparator-DWTCQxsa.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/QSpace-ezNrZkuj.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/QSpinnerDots-DIV68-cV.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/_plugin-vue_export-helper-BDNMzG2s.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/abap-08VXUWAP.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/apex-BWPQTe0t.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/azcli-Bc_sGQ0U.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/bat-i0X4ZdIN.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/bicep-B5-_aFwp.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/cameligo-DMUM7wLl.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/chunk-QTnfLwEv.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/clojure-Cm7r79vr.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/codicon-ngg6Pgfi.ttf +0 -0
- /package/src/client/dist/{spa → pwa}/assets/coffee-Ba7i2nA0.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/cpp-C7h46wYY.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/csharp-BKxtCVv1.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/csp-bTuwJoIa.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/css-DIMkf-bt.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/css.worker-CBlhdqTa.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/cypher-CVaqCwHa.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/dart-onAF5SnQ.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/dockerfile-DZFCIeNp.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/ecl-D05T4iGw.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/editor-BFebQ78e.css +0 -0
- /package/src/client/dist/{spa → pwa}/assets/editor.worker-Cu3tR8iJ.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/elixir-6RTg0lbw.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/expand-template-D2yUa54D.css +0 -0
- /package/src/client/dist/{spa → pwa}/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNa-Dr0goTwe.woff +0 -0
- /package/src/client/dist/{spa → pwa}/assets/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ-D-x-0Q06.woff2 +0 -0
- /package/src/client/dist/{spa → pwa}/assets/flow9-C5_-GSwl.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/fsharp-C8Ef5oNN.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/go-C-y9NEjX.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/graphql-fmXr3nnJ.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/hcl-CpzslTdj.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/html.worker-BKBrNna1.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/index-CwYR3iHt.css +0 -0
- /package/src/client/dist/{spa → pwa}/assets/ini-sBoK_t0W.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/java-BEtHBSE6.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/json.worker-BaLYt9Ss.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/julia-Bri6UV-V.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/kotlin-BOotOW0E.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/less-B9JPFI3C.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/lexon-CfSJPG6W.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/lua-CsQS60Ue.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/m3-D-oSqn_W.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/markdown-Cimd5fb3.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/mips-CIPQ_RoX.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/msdax-DauUninz.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/mysql-SOo6toE5.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/objective-c-FvmIjYaQ.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/pascal-DrH0SRf2.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/pascaligo-D-ptJ9y-.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/perl-oz_6vUea.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/pgsql-DTj74zXo.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/php-nr791fC2.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/pla-CopQ2nXW.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/postiats-43DmfD33.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/powerquery-D3hlyOfw.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/powershell-DmHpPYUd.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/protobuf-C531GsRP.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/pug-Z5eAx3Zn.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/qsharp-DkqhCAOL.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/r-BwWrilGY.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/redis-ClamHrr6.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/redshift-DT7zqm-g.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/restructuredtext-BYgofb2h.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/ruby-DezsRK8O.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/rust-DdL9SqIa.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/sb-CcwsVR0C.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/scala-DHpiXF5c.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/scheme-BeGwcela.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/scss-gp-XZpBa.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/shell-CC2rA5mh.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/solidity-BEEn4gHE.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/sophia-CRfGWb83.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/sparql-D_Lu-MrJ.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/sql-NEE52Syq.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/st-DbInun42.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/swift-Bxkupp3x.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/systemverilog-Bz4Y3fRF.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/tcl-DISqw1ZD.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/ts.worker-PmaSgaZk.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/twig-De2hgUGE.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/typespec-B8J7ngcE.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/use-checkbox-E9SV7iH5.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/use-onboarding-B3l7mx48.css +0 -0
- /package/src/client/dist/{spa → pwa}/assets/vb-DV3o63ZY.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/vue.runtime.esm-bundler-JZnIeD9D.js +0 -0
- /package/src/client/dist/{spa → pwa}/assets/wgsl-DpFanUEy.js +0 -0
- /package/src/client/dist/{spa → pwa}/favicon.svg +0 -0
- /package/src/client/dist/{spa → pwa}/sounds/ca_va_peter.mp3 +0 -0
- /package/src/client/dist/{spa → pwa}/sounds/dry-fart.mp3 +0 -0
- /package/src/client/dist/{spa → pwa}/sounds/faaah.mp3 +0 -0
- /package/src/client/dist/{spa → pwa}/sounds/for-shure.mp3 +0 -0
- /package/src/client/dist/{spa → pwa}/sounds/hey.mp3 +0 -0
- /package/src/client/dist/{spa → pwa}/sounds/travail_termine.mp3 +0 -0
- /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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
217
|
-
// silent).
|
|
218
|
-
//
|
|
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
|
-
|
|
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',
|
|
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 [
|
|
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;
|