@loicngr/kobo 1.10.3 → 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 +25 -0
- package/README.md +15 -9
- 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/search-service.js +3 -1
- package/dist/server/services/sentry-service.js +20 -0
- package/dist/server/services/settings-service.js +24 -0
- 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-OyS0LqjP.js → pwa/assets/ChangelogPage-DLmP9KSb.js} +1 -1
- package/src/client/dist/{spa/assets/ClosePopup-B1wf7Gh3.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-QMan-G3D.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-BKi1Jbys.js → pwa/assets/QBtnToggle-BUJ1g7BR.js} +1 -1
- package/src/client/dist/{spa/assets/QChip-1uTELmCE.js → pwa/assets/QChip-CPiNmK-x.js} +1 -1
- package/src/client/dist/{spa/assets/QSelect-CKc3IVuT.js → pwa/assets/QSelect-D63npmWX.js} +1 -1
- package/src/client/dist/{spa/assets/QTooltip-86biX27D.js → pwa/assets/QTooltip-DOQ5d9M2.js} +1 -1
- 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/{spa/assets/SettingsPage-DGAGBtQ2.css → pwa/assets/SettingsPage-DJEH2ahl.css} +1 -1
- 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-EYoNwghH.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-BAxSTjwb.js → pwa/assets/build-path-tree-B3R74R5X.js} +1 -1
- package/src/client/dist/{spa/assets/cssMode-CG3m7iQl.js → pwa/assets/cssMode-Ca0QWQGQ.js} +1 -1
- package/src/client/dist/{spa/assets/documents-Dz8sHeyR.js → pwa/assets/documents-a-MDhFWk.js} +1 -1
- package/src/client/dist/{spa/assets/editor.api2-DqJsxy06.js → pwa/assets/editor.api2-CQQ3TFHp.js} +1 -1
- package/src/client/dist/{spa/assets/editor.main-r2oDGbHA.js → pwa/assets/editor.main-BmtlNqLW.js} +2 -2
- package/src/client/dist/{spa/assets/expand-template-DP5gf1ro.js → pwa/assets/expand-template-DkLDjInO.js} +1 -1
- package/src/client/dist/{spa/assets/formatters-EQwGVQQS.js → pwa/assets/formatters-COWwqgNl.js} +1 -1
- package/src/client/dist/{spa/assets/freemarker2-C6Dcsusp.js → pwa/assets/freemarker2-Be_VI-2P.js} +1 -1
- package/src/client/dist/{spa/assets/handlebars-BYzVrRyn.js → pwa/assets/handlebars-C6MV-4UG.js} +1 -1
- package/src/client/dist/{spa/assets/html-Yuf2QU9u.js → pwa/assets/html-H42ZDxQV.js} +1 -1
- package/src/client/dist/{spa/assets/htmlMode-BJJsbkSn.js → pwa/assets/htmlMode-COCELcr-.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-BgrVh81z.js +1 -0
- package/src/client/dist/{spa/assets/index-CC1iOXc-.js → pwa/assets/index-Cs6I8vi_.js} +7 -7
- package/src/client/dist/{spa/assets/javascript-Ch3ueiOd.js → pwa/assets/javascript-CiFkTKAE.js} +1 -1
- package/src/client/dist/{spa/assets/jsonMode-xEENY1ms.js → pwa/assets/jsonMode--0Mpy1sQ.js} +1 -1
- package/src/client/dist/{spa/assets/kobo-commands-CbIH5Wgu.js → pwa/assets/kobo-commands-BzxwZ3vQ.js} +1 -1
- package/src/client/dist/{spa/assets/layout-EwsT5w-M.js → pwa/assets/layout-BcUfiaFQ.js} +1 -1
- package/src/client/dist/{spa/assets/liquid-Bh89yiMp.js → pwa/assets/liquid-BlkSsRl8.js} +1 -1
- package/src/client/dist/{spa/assets/lspLanguageFeatures-CNi35cTh.js → pwa/assets/lspLanguageFeatures-DXelvPSE.js} +1 -1
- package/src/client/dist/{spa/assets/mdx-Pk_6m6B2.js → pwa/assets/mdx-BoNBdGqW.js} +1 -1
- package/src/client/dist/{spa/assets/monaco.contribution-DyjS5cak.js → pwa/assets/monaco.contribution-D8uREgcb.js} +2 -2
- package/src/client/dist/{spa/assets/network-auth-B4eYLa1I.js → pwa/assets/network-auth-wzwG23Rr.js} +1 -1
- package/src/client/dist/{spa/assets/QScrollArea-ehpkh2GE.js → pwa/assets/pending-diff-open-DyoLDUlV.js} +1 -1
- package/src/client/dist/{spa/assets/QExpansionItem-BJZyoT79.js → pwa/assets/permissionModes-D7Iv04mQ.js} +1 -1
- package/src/client/dist/{spa/assets/project-color-8ANVbJax.js → pwa/assets/project-color-C3soNzD0.js} +1 -1
- package/src/client/dist/{spa/assets/python-7GmD1T0-.js → pwa/assets/python-DZCdvSIy.js} +1 -1
- package/src/client/dist/{spa/assets/razor-CTW-7qAs.js → pwa/assets/razor-CFwK0FwM.js} +1 -1
- package/src/client/dist/{spa/assets/render-chat-markdown-iOBona09.js → pwa/assets/render-chat-markdown-DCydC_jc.js} +1 -1
- package/src/client/dist/{spa/assets/tsMode-BMCTiDkj.js → pwa/assets/tsMode-D8ZbVZB6.js} +1 -1
- package/src/client/dist/{spa/assets/typescript-CBNlJ2bB.js → pwa/assets/typescript-CjxR71vN.js} +1 -1
- package/src/client/dist/{spa/assets/use-onboarding-C7pg8RV8.js → pwa/assets/use-onboarding-DA5ka-XJ.js} +1 -1
- package/src/client/dist/{spa/assets/use-quasar-by40ecAs.js → pwa/assets/use-quasar-5A_Wy1iR.js} +1 -1
- package/src/client/dist/{spa/assets/workers-CrZaPx7e.js → pwa/assets/workers-Bv1gqcaP.js} +1 -1
- package/src/client/dist/{spa/assets/xml-CkKKuUBJ.js → pwa/assets/xml-vP8L_5ev.js} +1 -1
- package/src/client/dist/{spa/assets/yaml-B6VtPgSa.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/sw.js +1 -0
- package/src/client/dist/pwa/workbox-778a14ff.js +1 -0
- package/src/client/dist/spa/assets/ActivityFeed-Bn4IG1gM.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-CkOwo6Jn.js +0 -2
- package/src/client/dist/spa/assets/DiffViewer-DUy5tfI3.js +0 -8
- package/src/client/dist/spa/assets/MainLayout-7nwhu8UE.css +0 -1
- package/src/client/dist/spa/assets/MainLayout-CkVcmDeg.js +0 -37
- package/src/client/dist/spa/assets/SearchPage-V8OnALqt.js +0 -1
- package/src/client/dist/spa/assets/SearchPage-cVwt0DaQ.css +0 -1
- package/src/client/dist/spa/assets/SettingsPage-CazPsJnl.js +0 -16
- package/src/client/dist/spa/assets/WorkspacePage-BOApCawq.css +0 -1
- package/src/client/dist/spa/assets/WorkspacePage-q6R6SPuh.js +0 -4
- package/src/client/dist/spa/assets/i18n-CPaxvFKM.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/7eme-compagnie-03.mp3 +0 -0
- /package/src/client/dist/{spa → pwa}/sounds/aller-ftg.mp3 +0 -0
- /package/src/client/dist/{spa → pwa}/sounds/arrete-de-mentir.mp3 +0 -0
- /package/src/client/dist/{spa → pwa}/sounds/arretez-les-messages.mp3 +0 -0
- /package/src/client/dist/{spa → pwa}/sounds/bah-alors-on-est-nul.mp3 +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/gta-v-death.mp3 +0 -0
- /package/src/client/dist/{spa → pwa}/sounds/hey.mp3 +0 -0
- /package/src/client/dist/{spa → pwa}/sounds/nan-tu-degages.mp3 +0 -0
- /package/src/client/dist/{spa → pwa}/sounds/nan-wallah-pardon.mp3 +0 -0
- /package/src/client/dist/{spa → pwa}/sounds/ouais-cest-greg.mp3 +0 -0
- /package/src/client/dist/{spa → pwa}/sounds/pas-ca-zinedine.mp3 +0 -0
- /package/src/client/dist/{spa → pwa}/sounds/ta-gueule.mp3 +0 -0
- /package/src/client/dist/{spa → pwa}/sounds/travail_termine.mp3 +0 -0
- /package/src/client/dist/{spa → pwa}/sounds/tu-vas-la-fermer.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);
|
|
@@ -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
|
|
@@ -750,6 +750,24 @@ const settingsMigrations = [
|
|
|
750
750
|
}
|
|
751
751
|
},
|
|
752
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
|
+
},
|
|
753
771
|
];
|
|
754
772
|
/** Current settings schema version — always equals the highest migration version. */
|
|
755
773
|
export const SETTINGS_SCHEMA_VERSION = settingsMigrations.length > 0 ? settingsMigrations[settingsMigrations.length - 1].version : 0;
|
|
@@ -837,6 +855,9 @@ function defaultSettings() {
|
|
|
837
855
|
defaultPermissionModeByEngine: { 'claude-code': 'plan', codex: 'plan' },
|
|
838
856
|
notionMcpKey: '',
|
|
839
857
|
sentryMcpKey: '',
|
|
858
|
+
notionEnabled: true,
|
|
859
|
+
sentryEnabled: true,
|
|
860
|
+
showThinkingBlocks: true,
|
|
840
861
|
tags: [...DEFAULT_WORKSPACE_TAGS],
|
|
841
862
|
branchPrefixes: [...DEFAULT_BRANCH_PREFIXES],
|
|
842
863
|
worktreesPath: WORKTREES_PATH,
|
|
@@ -1219,6 +1240,9 @@ export function updateGlobalSettings(data) {
|
|
|
1219
1240
|
'defaultPermissionModeByEngine',
|
|
1220
1241
|
'notionMcpKey',
|
|
1221
1242
|
'sentryMcpKey',
|
|
1243
|
+
'notionEnabled',
|
|
1244
|
+
'sentryEnabled',
|
|
1245
|
+
'showThinkingBlocks',
|
|
1222
1246
|
'tags',
|
|
1223
1247
|
'branchPrefixes',
|
|
1224
1248
|
'worktreesPath',
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { nanoid } from 'nanoid';
|
|
3
|
+
import { getDb } from '../db/index.js';
|
|
4
|
+
const turnRules = new Map();
|
|
5
|
+
function turnKey(request) {
|
|
6
|
+
return `${request.engine}\u0000${request.toolName}`;
|
|
7
|
+
}
|
|
8
|
+
export function allowPermissionForTurn(workspaceId, request) {
|
|
9
|
+
const rules = turnRules.get(workspaceId) ?? new Set();
|
|
10
|
+
rules.add(turnKey(request));
|
|
11
|
+
turnRules.set(workspaceId, rules);
|
|
12
|
+
}
|
|
13
|
+
export function clearTurnPermissions(workspaceId) {
|
|
14
|
+
turnRules.delete(workspaceId);
|
|
15
|
+
}
|
|
16
|
+
function stable(value) {
|
|
17
|
+
if (value === null || typeof value !== 'object')
|
|
18
|
+
return JSON.stringify(value);
|
|
19
|
+
if (Array.isArray(value))
|
|
20
|
+
return `[${value.map(stable).join(',')}]`;
|
|
21
|
+
const record = value;
|
|
22
|
+
return `{${Object.keys(record)
|
|
23
|
+
.sort()
|
|
24
|
+
.map((key) => `${JSON.stringify(key)}:${stable(record[key])}`)
|
|
25
|
+
.join(',')}}`;
|
|
26
|
+
}
|
|
27
|
+
export function permissionFingerprint(payload) {
|
|
28
|
+
return createHash('sha256').update(stable(payload)).digest('hex');
|
|
29
|
+
}
|
|
30
|
+
function toRule(row) {
|
|
31
|
+
return {
|
|
32
|
+
id: String(row.id),
|
|
33
|
+
workspaceId: String(row.workspace_id),
|
|
34
|
+
engine: row.engine,
|
|
35
|
+
toolName: String(row.tool_name),
|
|
36
|
+
scope: row.scope,
|
|
37
|
+
fingerprint: row.fingerprint,
|
|
38
|
+
displayLabel: String(row.display_label),
|
|
39
|
+
createdAt: String(row.created_at),
|
|
40
|
+
updatedAt: String(row.updated_at),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function listWorkspacePermissionRules(workspaceId) {
|
|
44
|
+
return getDb()
|
|
45
|
+
.prepare('SELECT * FROM workspace_permission_rules WHERE workspace_id=? ORDER BY created_at DESC')
|
|
46
|
+
.all(workspaceId).map(toRule);
|
|
47
|
+
}
|
|
48
|
+
export function createWorkspacePermissionRule(workspaceId, request, scope) {
|
|
49
|
+
const now = new Date().toISOString();
|
|
50
|
+
const id = nanoid();
|
|
51
|
+
const fingerprint = scope === 'operation' ? permissionFingerprint(request.payload) : null;
|
|
52
|
+
const displayLabel = scope === 'tool' ? request.toolName : `${request.toolName} — opération précise`;
|
|
53
|
+
getDb()
|
|
54
|
+
.prepare('INSERT INTO workspace_permission_rules (id,workspace_id,engine,tool_name,scope,fingerprint,display_label,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?)')
|
|
55
|
+
.run(id, workspaceId, request.engine, request.toolName, scope, fingerprint, displayLabel, now, now);
|
|
56
|
+
return {
|
|
57
|
+
id,
|
|
58
|
+
workspaceId,
|
|
59
|
+
engine: request.engine,
|
|
60
|
+
toolName: request.toolName,
|
|
61
|
+
scope,
|
|
62
|
+
fingerprint,
|
|
63
|
+
displayLabel,
|
|
64
|
+
createdAt: now,
|
|
65
|
+
updatedAt: now,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export function removeWorkspacePermissionRule(workspaceId, ruleId) {
|
|
69
|
+
return (getDb().prepare('DELETE FROM workspace_permission_rules WHERE id=? AND workspace_id=?').run(ruleId, workspaceId)
|
|
70
|
+
.changes > 0);
|
|
71
|
+
}
|
|
72
|
+
export function isWorkspacePermissionAllowed(workspaceId, request) {
|
|
73
|
+
if (turnRules.get(workspaceId)?.has(turnKey(request)))
|
|
74
|
+
return true;
|
|
75
|
+
const fingerprint = permissionFingerprint(request.payload);
|
|
76
|
+
try {
|
|
77
|
+
return Boolean(getDb()
|
|
78
|
+
.prepare(`SELECT 1 FROM workspace_permission_rules WHERE workspace_id=? AND tool_name=? AND (engine IS NULL OR engine=?) AND (scope='tool' OR (scope='operation' AND fingerprint=?)) LIMIT 1`)
|
|
79
|
+
.get(workspaceId, request.toolName, request.engine, fingerprint));
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (error instanceof Error && /no such table/i.test(error.message))
|
|
83
|
+
return false;
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -137,10 +137,10 @@ export function getChangelogPath() {
|
|
|
137
137
|
return getPackageAssetPath('CHANGELOG.md');
|
|
138
138
|
}
|
|
139
139
|
/**
|
|
140
|
-
* Absolute path to the built Quasar
|
|
140
|
+
* Absolute path to the built Quasar PWA (src/client/dist/pwa). Returns null
|
|
141
141
|
* if the SPA has not been built yet.
|
|
142
142
|
*/
|
|
143
143
|
export function getClientSpaPath() {
|
|
144
|
-
const
|
|
145
|
-
return fs.existsSync(
|
|
144
|
+
const pwa = getPackageAssetPath('src', 'client', 'dist', 'pwa');
|
|
145
|
+
return fs.existsSync(pwa) ? pwa : null;
|
|
146
146
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loicngr/kobo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "Kōbō — multi-workspace agent manager for Claude Code. Orchestrates isolated git worktrees with dev servers, Notion integration, and MCP tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist/",
|
|
12
|
-
"src/client/dist/
|
|
12
|
+
"src/client/dist/pwa/",
|
|
13
13
|
"src/mcp-server/",
|
|
14
14
|
"AGENTS.md",
|
|
15
15
|
"README.md",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"dev": "KOBO_ENFORCE_LOCAL_HOME=1 KOBO_HOME=./data PORT=3300 tsx watch src/server/index.ts",
|
|
52
52
|
"dev:client": "cd src/client && KOBO_BACKEND_PORT=3300 npx quasar dev",
|
|
53
53
|
"dev:all": "concurrently \"npm run dev\" \"npm run dev:client\"",
|
|
54
|
-
"build:client": "cd src/client && npx quasar build",
|
|
54
|
+
"build:client": "cd src/client && npx quasar build -m pwa",
|
|
55
55
|
"build:server": "npx tsc && chmod +x dist/server/index.js",
|
|
56
56
|
"build": "npm run build:client && npm run build:server",
|
|
57
57
|
"install-all": "npm install && cd src/client && npm install",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.markdown-message[data-v-1b7bd8ca]{color:#e0e0e0;word-break:break-word;overflow-wrap:anywhere;min-width:0;max-width:100%;font-size:13px;line-height:1.55}.markdown-message[data-v-1b7bd8ca] *{max-width:100%}.markdown-message[data-v-1b7bd8ca] p{margin:0 0 .5em}.markdown-message[data-v-1b7bd8ca] p:last-child{margin-bottom:0}.markdown-message[data-v-1b7bd8ca] pre{background:#00000059;border-radius:4px;margin:.5em 0;padding:.5em .75em;overflow-x:auto}.markdown-message[data-v-1b7bd8ca] code{word-break:break-all;background:#0000004d;border-radius:3px;padding:.1em .3em;font-size:.9em}.markdown-message[data-v-1b7bd8ca] pre code{background:0 0;padding:0}.markdown-message[data-v-1b7bd8ca] ul,.markdown-message[data-v-1b7bd8ca] ol{margin:.25em 0 .5em;padding-left:1.5em}.markdown-message[data-v-1b7bd8ca] li{margin:.15em 0}.markdown-message[data-v-1b7bd8ca] a{color:#7986cb;text-decoration:underline}.markdown-message[data-v-1b7bd8ca] .document-link{color:#9fa8da;cursor:pointer;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.markdown-message[data-v-1b7bd8ca] .document-link:hover{color:#c5cae9;-webkit-text-decoration:underline;text-decoration:underline}.markdown-message[data-v-1b7bd8ca] h1,.markdown-message[data-v-1b7bd8ca] h2,.markdown-message[data-v-1b7bd8ca] h3,.markdown-message[data-v-1b7bd8ca] h4,.markdown-message[data-v-1b7bd8ca] h5,.markdown-message[data-v-1b7bd8ca] h6{margin:.5em 0 .3em;font-weight:600;line-height:1.3}.markdown-message[data-v-1b7bd8ca] h1{font-size:1.25em}.markdown-message[data-v-1b7bd8ca] h2{font-size:1.15em}.markdown-message[data-v-1b7bd8ca] h3{font-size:1.08em}.markdown-message[data-v-1b7bd8ca] h4,.markdown-message[data-v-1b7bd8ca] h5,.markdown-message[data-v-1b7bd8ca] h6{font-size:1em}.markdown-message[data-v-1b7bd8ca] blockquote{color:#ffffffb3;border-left:3px solid #fff3;margin:.5em 0;padding-left:.75em}.markdown-message[data-v-1b7bd8ca] table{border-collapse:collapse;margin:.5em 0}.markdown-message[data-v-1b7bd8ca] th,.markdown-message[data-v-1b7bd8ca] td{border:1px solid #ffffff26;padding:.25em .5em}.tool-row[data-v-fc912366]{border-radius:4px;margin:0;font-size:12px}.tool-header[data-v-fc912366]{color:#bbb;cursor:default;align-items:center;gap:10px;min-width:0;padding:5px 10px;display:flex}.tool-row:not(.tool-row-generic) .tool-header[data-v-fc912366],.tool-row--toggleable .tool-header[data-v-fc912366]{cursor:pointer}.tool-row:has(.tool-diff) .tool-header[data-v-fc912366]{cursor:pointer}.tool-row:not(.tool-row-generic) .tool-header[data-v-fc912366]:hover,.tool-row--toggleable .tool-header[data-v-fc912366]:hover{background:#ffffff08}.tool-icon[data-v-fc912366]{color:#9fbce0;flex-shrink:0}.tool-name[data-v-fc912366]{color:#d0d0d0;flex-shrink:0;font-weight:600}.tool-arg[data-v-fc912366],.tool-path[data-v-fc912366]{color:#999;text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:100%;font-family:SF Mono,Menlo,Consolas,monospace;font-size:11.5px;overflow:hidden}.tool-open-diff[data-v-fc912366]{flex-shrink:0}.tool-path[data-v-fc912366],.tool-arg[data-v-fc912366]{flex:1}.tool-stat-add[data-v-fc912366]{color:#66bb6a;flex-shrink:0;font-size:11px;font-weight:600}.tool-stat-del[data-v-fc912366]{color:#ef5350;flex-shrink:0;font-size:11px;font-weight:600}.tool-diff[data-v-fc912366]{background:#0003;border-radius:4px;max-height:400px;margin-top:4px;padding:8px 0;font-family:SF Mono,Menlo,Consolas,monospace;font-size:11px;line-height:1.5;overflow:auto}.diff-line[data-v-fc912366]{white-space:pre;color:#bbb;padding:0 12px}.diff-sign[data-v-fc912366]{color:#555;-webkit-user-select:none;user-select:none;width:14px;display:inline-block}.diff-add[data-v-fc912366]{color:#c8e6c9;background:#66bb6a1a}.diff-add .diff-sign[data-v-fc912366]{color:#66bb6a}.diff-del[data-v-fc912366]{color:#ffcdd2;background:#ef53501a}.diff-del .diff-sign[data-v-fc912366]{color:#ef5350}.tool-output[data-v-fc912366]{color:#aaa;white-space:pre-wrap;background:#00000026;border-radius:4px;max-height:8em;margin-top:4px;padding:6px 10px;font-family:SF Mono,Menlo,Consolas,monospace;font-size:11px;overflow:auto}.markdown-message[data-v-f34be4c5]{color:#e0e0e0;word-break:break-word;overflow-wrap:anywhere;min-width:0;max-width:100%;font-size:13px;line-height:1.55}.markdown-message[data-v-f34be4c5] *{max-width:100%}.markdown-message[data-v-f34be4c5] img{-o-object-fit:contain;object-fit:contain;cursor:zoom-in;background:#0003;border-radius:4px;max-width:180px;max-height:100px;margin:.3em 0;display:block}.markdown-message[data-v-f34be4c5] code{word-break:break-all}.markdown-message[data-v-f34be4c5] p{margin:0 0 .4em}.markdown-message[data-v-f34be4c5] p:last-child{margin-bottom:0}.markdown-message[data-v-f34be4c5] code{background:#00000040;border-radius:3px;padding:.1em .3em}.markdown-message[data-v-f34be4c5] h1,.markdown-message[data-v-f34be4c5] h2,.markdown-message[data-v-f34be4c5] h3,.markdown-message[data-v-f34be4c5] h4,.markdown-message[data-v-f34be4c5] h5,.markdown-message[data-v-f34be4c5] h6{margin:.4em 0 .25em;font-weight:600;line-height:1.3}.markdown-message[data-v-f34be4c5] h1{font-size:1.25em}.markdown-message[data-v-f34be4c5] h2{font-size:1.15em}.markdown-message[data-v-f34be4c5] h3{font-size:1.08em}.markdown-message[data-v-f34be4c5] h4,.markdown-message[data-v-f34be4c5] h5,.markdown-message[data-v-f34be4c5] h6{font-size:1em}.markdown-user-prompt[data-v-f34be4c5]{color:#aaa;font-size:12px;font-style:italic}.markdown-user-prompt[data-v-f34be4c5] p{margin:0 0 .4em}.markdown-user-prompt[data-v-f34be4c5] code{background:#ffffff14;border-radius:3px;padding:.1em .3em;font-style:normal}.image-lightbox-img{-o-object-fit:contain;object-fit:contain;cursor:zoom-out;background:#0000004d;border-radius:4px;max-width:92vw;max-height:92vh;display:block}.turn-card[data-v-6b8d8e3e]{border:1px solid #ffffff14;border-left:3px solid var(--turn-accent);background:#ffffff05;border-radius:6px;min-width:0;max-width:100%;margin:14px 0;overflow:hidden}.turn-card--highlighted[data-v-6b8d8e3e]{animation:1.8s ease-out search-highlight-6b8d8e3e}@keyframes search-highlight-6b8d8e3e{0%,45%{box-shadow:0 0 0 2px #818cf8f2,0 0 22px #818cf873}to{box-shadow:none}}.turn-header[data-v-6b8d8e3e]{color:#888;background:#ffffff08;border-bottom:1px solid #ffffff0d;align-items:center;gap:8px;padding:8px 14px;font-size:11px;display:flex}.turn-badge[data-v-6b8d8e3e]{letter-spacing:.3px;border-radius:3px;padding:2px 8px;font-size:11px;font-weight:700}.turn-badge-user[data-v-6b8d8e3e]{color:#ce93d8;background:#ce93d826}.turn-badge-agent[data-v-6b8d8e3e]{color:#7986cb;background:#7986cb26}.turn-badge-system[data-v-6b8d8e3e]{color:#bdbdbd;background:#75757533;font-style:italic}.turn-badge-session[data-v-6b8d8e3e]{color:#9e9e9e;background:#61616133}.turn-badge-script[data-v-6b8d8e3e]{color:#4db6ac;background:#4db6ac26}.turn-time[data-v-6b8d8e3e]{color:#666;font-family:SF Mono,Menlo,Consolas,monospace;font-size:11px}.turn-time-arrow[data-v-6b8d8e3e]{opacity:.7;margin:0 -2px}.turn-time-updated[data-v-6b8d8e3e]{color:#8891a3}.turn-actions[data-v-6b8d8e3e]{color:#777;font-size:11px}.turn-body[data-v-6b8d8e3e]{flex-direction:column;gap:12px;min-width:0;padding:14px 18px;display:flex}.turn-body[data-v-6b8d8e3e]>*{min-width:0;max-width:100%}.turn-body[data-v-6b8d8e3e] .tool-row+.tool-row{margin-top:-8px}.turn-scroll-top[data-v-6b8d8e3e]{justify-content:flex-start;padding:0 8px 6px;display:flex}.turn-scroll-top-btn[data-v-6b8d8e3e]{opacity:.5;transition:opacity .15s}.turn-scroll-top-btn[data-v-6b8d8e3e]:hover{opacity:1}.activity-feed-wrap[data-v-0cdc51af]{width:100%;height:100%;position:relative}.activity-feed-scroll[data-v-0cdc51af]{width:100%;height:100%}.activity-feed-nav-cluster[data-v-0cdc51af]{z-index:2;align-items:center;gap:8px;display:flex;position:absolute;bottom:14px;right:14px}.activity-feed-compacting[data-v-0cdc51af]{z-index:3;background:var(--kobo-surface);border:1px solid var(--kobo-border);border-radius:6px;align-items:center;gap:8px;padding:6px 14px;display:flex;position:absolute;top:12px;left:50%;transform:translate(-50%);box-shadow:0 4px 16px #00000059}.fade-enter-active[data-v-0cdc51af],.fade-leave-active[data-v-0cdc51af]{transition:opacity .16s}.fade-enter-from[data-v-0cdc51af],.fade-leave-to[data-v-0cdc51af]{opacity:0}.activity-feed-nav-btn[data-v-0cdc51af]{opacity:.8;transition:opacity .12s}.activity-feed-nav-btn[data-v-0cdc51af]:hover{opacity:1}.content-origin-marker[data-v-0cdc51af]{pointer-events:none;width:0;height:0;margin:0;padding:0}.activity-feed-scroll[data-v-0cdc51af] .q-scrollarea__content{max-width:100%;overflow-x:hidden}.activity-feed-switching[data-v-0cdc51af]{justify-content:center;align-items:center;width:100%;height:100%;display:flex}
|