@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
package/AGENTS.md
CHANGED
|
@@ -12,13 +12,13 @@ Single-user dev tool, local by default. The server binds `127.0.0.1` unless the
|
|
|
12
12
|
|
|
13
13
|
**Backend**: Node.js ≥ 20, Hono (HTTP), `ws` (WebSocket), better-sqlite3 (WAL mode), nanoid, `@modelcontextprotocol/sdk`. TypeScript throughout, `tsx` for dev, `tsc` for production build.
|
|
14
14
|
|
|
15
|
-
**Frontend**: Vue 3, Quasar 2, Pinia, vue-router, marked + dompurify for markdown rendering. Vite via `@quasar/app-vite
|
|
15
|
+
**Frontend**: Vue 3, Quasar 2, Pinia, vue-router, marked + dompurify for markdown rendering. Vite via `@quasar/app-vite`, built and served as a Progressive Web App in production.
|
|
16
16
|
|
|
17
17
|
**Database**: a single SQLite file under the **Kōbō home directory** (`~/.config/kobo/kobo.db` by default, overridable via `KOBO_HOME`). Fresh-install schema lives in `src/server/db/schema.ts` (`initSchema`); incremental migrations live in `src/server/db/migrations.ts`. **The project is in production**, so every schema change MUST ship as a migration that preserves data, never as a breaking change to `initSchema` alone. See [Database migrations](#database-migrations) below.
|
|
18
18
|
|
|
19
19
|
**Kōbō home directory**: `KOBO_HOME` env var overrides everything. Otherwise `$XDG_CONFIG_HOME/kobo/`, else `~/.config/kobo/`. Contains `kobo.db`, `settings.json`, `skills.json`, `templates.json`. **Development uses `./data/`** via the `KOBO_HOME=./data` prefix in the `dev` npm script, so local dev never touches your real `~/.config/kobo/` and can run in parallel with a production-installed Kōbō (`npx @loicngr/kobo`). See `src/server/utils/paths.ts`.
|
|
20
20
|
|
|
21
|
-
**Tests**:
|
|
21
|
+
**Tests**: Vitest covers backend services/routes and frontend stores/pure utilities. Vue components are not unit-tested; type-checking and manual smoke tests cover their behavior. Run `npm test` and `(cd src/client && npm test)` for the complete suites.
|
|
22
22
|
|
|
23
23
|
## Commands
|
|
24
24
|
|
|
@@ -83,7 +83,7 @@ src/
|
|
|
83
83
|
│ ├── utils/
|
|
84
84
|
│ │ ├── git-ops.ts # pushBranch, pullBranch, getCommitsBetween, delete{Local,Remote}Branch…
|
|
85
85
|
│ │ └── process-tracker.ts # per-workspace spawned-process map
|
|
86
|
-
├── client/ # Vue 3 + Quasar
|
|
86
|
+
├── client/ # Vue 3 + Quasar PWA
|
|
87
87
|
│ └── src/
|
|
88
88
|
│ ├── stores/ # pinia: workspace, websocket, settings, dev-server, templates
|
|
89
89
|
│ ├── components/ # WorkspaceList, NotionPanel, AcceptancePanel, ChatInput, GitPanel, PlansPanel, WorkspaceAttentionLabels…
|
|
@@ -102,10 +102,11 @@ src/
|
|
|
102
102
|
|---|---|
|
|
103
103
|
| `workspaces` | the unit of work: id, name, project_path, source_branch, working_branch, status, notion_url, model, `brainstorm_model`, dev_server_status, `archived_at`, `worktree_purged_at`, `worktree_purge_restore_data` (JSON), `auto_loop`, `auto_loop_ready`, `auto_loop_session_mode`, `no_progress_streak`, timestamps |
|
|
104
104
|
| `tasks` | workspace sub-items: title, status, `is_acceptance_criterion`, sort_order; CASCADE DELETE on workspace |
|
|
105
|
-
| `agent_sessions` |
|
|
105
|
+
| `agent_sessions` | agent-engine sessions: pid where applicable, engine session id, status, timestamps, model, and name |
|
|
106
106
|
| `ws_events` | persisted WebSocket events for replay on reconnect: type, payload, session_id, created_at |
|
|
107
107
|
| `pending_wakeups` | one-row-per-workspace scheduler for the `ScheduleWakeup` tool: target_at (ISO UTC), prompt, reason; CASCADE DELETE on workspace |
|
|
108
108
|
| `workspace_chat_history` | chat-input history per workspace: message text + `created_at`, ordered by autoincrement id, capped at 200 entries by the service; CASCADE DELETE on workspace |
|
|
109
|
+
| `workspace_permission_rules` | remembered per-workspace tool approvals, scoped to an exact operation or every invocation of a tool; CASCADE DELETE on workspace |
|
|
109
110
|
|
|
110
111
|
`status` enum: `created | extracting | brainstorming | executing | completed | idle | error | quota`. Transitions are validated in `updateWorkspaceStatus` against `VALID_TRANSITIONS`.
|
|
111
112
|
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ All notable changes to Kōbō are documented here. The format is based on
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/). Each release is an `## <version>`
|
|
5
5
|
section — the in-app "What's new" dialog reads this file.
|
|
6
6
|
|
|
7
|
+
## 1.11.0
|
|
8
|
+
|
|
9
|
+
- feat(pwa): ship the production client as an installable Progressive Web App.
|
|
10
|
+
- feat(chat): add `Ctrl+J` for a new line and `Ctrl+Enter` to force delivery of
|
|
11
|
+
a queued message while an agent is active.
|
|
12
|
+
- feat(chat): move the latest Claude/Codex reasoning into one live panel above
|
|
13
|
+
the chat input, with a global visibility preference.
|
|
14
|
+
- feat(integrations): independently enable or disable Notion and Sentry from
|
|
15
|
+
Settings while preserving their configuration.
|
|
16
|
+
- feat(integrations): add **Test connection** checks for the configured Notion
|
|
17
|
+
and Sentry MCP servers, with actionable status and elapsed time.
|
|
18
|
+
- feat(chat): add `Ctrl+F` workspace-history search with readable results;
|
|
19
|
+
opening a result loads the matching session, scrolls to its message, and
|
|
20
|
+
highlights it. Global search now deep-links to the same location.
|
|
21
|
+
- feat(git): let Edit and Write cards open their exact worktree-relative file
|
|
22
|
+
in the Git diff viewer.
|
|
23
|
+
- feat(observability): add a session timeline panel and downloadable redacted
|
|
24
|
+
workspace diagnostic JSON.
|
|
25
|
+
- feat(pwa): show offline and update-ready states, with explicit update reload
|
|
26
|
+
instead of interrupting an active page.
|
|
27
|
+
- fix(chat): consume one-shot search deep-link parameters after use so a page
|
|
28
|
+
refresh does not jump back to an old result.
|
|
29
|
+
- feat(permissions): remember workspace-scoped tool or exact-operation
|
|
30
|
+
approvals across sessions.
|
|
31
|
+
|
|
7
32
|
## 1.10.3
|
|
8
33
|
|
|
9
34
|
- feat(notifications): add configurable PR state sounds (#22)
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](./LICENSE)
|
|
7
7
|
[](https://nodejs.org/)
|
|
8
8
|
|
|
9
|
-
Kōbō runs multiple coding agents in parallel, each isolated in its own git worktree, branch, and dev server.
|
|
9
|
+
Kōbō runs multiple coding agents in parallel, each isolated in its own git worktree, branch, and dev server. Its installable Vue/Quasar dashboard streams output, tasks, git state, and quota usage across every workspace.
|
|
10
10
|
|
|
11
11
|

|
|
12
12
|
|
|
@@ -17,11 +17,11 @@ Kōbō runs multiple coding agents in parallel, each isolated in its own git wor
|
|
|
17
17
|
|
|
18
18
|
- **Isolated worktrees**: each workspace is a dedicated git worktree on its own branch, so parallel sessions never collide.
|
|
19
19
|
- **Two agent engines**: Claude Code (via `@anthropic-ai/claude-agent-sdk`) and OpenAI Codex (via `codex app-server`), chosen per workspace.
|
|
20
|
-
- **Live chat**: streaming text,
|
|
20
|
+
- **Live chat**: streaming text, inline Edit/Write diffs, per-turn cards, a compaction-in-progress indicator, infinite scrollback, and the latest Claude or Codex reasoning in a panel immediately above the message input. `/` autocompletes skills and commands, `@` fuzzy-autocompletes worktree file paths, and `Ctrl+F` searches readable messages in the current workspace: selecting a result loads its session and scrolls to it. The global search page deep-links to the same location. Messages queued while an agent is busy stay scoped to their workspace and session. **Send now** delivers a queued message to an active Claude Code stream or steers the active Codex turn; it remains queued until the engine confirms acceptance. Use `Enter` to send, `Ctrl+Enter` to force delivery while an agent is busy, and `Shift+Enter` or `Ctrl+J` for a new line.
|
|
21
21
|
- **Full MCP toolset (`kobo-tasks`)**: a per-workspace MCP server the agent uses for far more than tasks — task/acceptance-criteria CRUD, starting/stopping the dev server and reading its logs, a unified `get_ticket` (Notion or Sentry), searching past conversations across every workspace, per-session token/cost usage, and a `.ai/thoughts` decision log. Native Claude Code Task tools complement it for lightweight sub-agent coordination. See [`AGENTS.md`](./AGENTS.md) for the full tool list.
|
|
22
22
|
|
|
23
23
|

|
|
24
|
-
- **Git panel**: a Monaco-based diff viewer with **inline file editing** (edit the right-hand panel directly, save with `Ctrl/Cmd+S`, conflict-guarded via sha precondition), inline conflict resolution, and `Sync` / `Push` / `Open PR` / `Change PR base` / `Change source branch`. Multi-forge: GitHub (`gh`), GitLab (`glab`), or no forge, auto-detected from the remote and overridable per project.
|
|
24
|
+
- **Git panel**: a Monaco-based diff viewer with **inline file editing** (edit the right-hand panel directly, save with `Ctrl/Cmd+S`, conflict-guarded via sha precondition), inline conflict resolution, and `Sync` / `Push` / `Open PR` / `Change PR base` / `Change source branch`. Edit/Write cards in chat open their exact changed file in the diff viewer. Multi-forge: GitHub (`gh`), GitLab (`glab`), or no forge, auto-detected from the remote and overridable per project.
|
|
25
25
|
|
|
26
26
|

|
|
27
27
|
- **Dev server panel**: start, stop, and tail logs for a workspace's dev server (Docker or npm) straight from the Tools panel — no need to leave the UI.
|
|
@@ -35,7 +35,9 @@ Kōbō runs multiple coding agents in parallel, each isolated in its own git wor
|
|
|
35
35
|
- **Cron schedules**: recurring per-workspace triggers the agent registers through MCP tools (`cron_create` / `cron_delete` / `cron_list`). Each tick resumes the workspace session (skipped if already active), and schedules are re-armed at boot with skip-missed semantics.
|
|
36
36
|
- **Lifecycle scripts**: shell scripts run automatically at key moments — **setup** (worktree created), **cleanup** (session ended), **archive** (workspace archived). Configure them globally or per project, with their output streamed into the chat.
|
|
37
37
|
- **Disk-space purge**: free a merged workspace's disk space without losing its chat history — see [below](#disk-space-purge).
|
|
38
|
-
- **
|
|
38
|
+
- **Observability**: a compact session timeline in the right panel shows session duration and status; download a redacted workspace diagnostic JSON for troubleshooting.
|
|
39
|
+
- **Optional integrations**: independently enable or disable Notion (import missions) and Sentry (fix from issue URL) in Settings, with a **Test connection** action for each; local voice transcription uses whisper.cpp.
|
|
40
|
+
- **Installable PWA**: production builds work as a Progressive Web App. Install Kōbō from your browser on desktop or mobile for an app-like window; it signals offline state and offers an explicit reload when an update is ready. Live agent activity still requires a connection to the local Kōbō server.
|
|
39
41
|
|
|
40
42
|
## Quick start
|
|
41
43
|
|
|
@@ -53,6 +55,10 @@ SERVER_PORT=9997 PORT=9998 npx @loicngr/kobo@latest
|
|
|
53
55
|
|
|
54
56
|
Open <http://localhost:3000> (or whichever port you picked). Data is persisted under `~/.config/kobo/` (override via `KOBO_HOME`).
|
|
55
57
|
|
|
58
|
+
### Install as an app
|
|
59
|
+
|
|
60
|
+
Kōbō's production build is a PWA. When opening a production instance, use your browser's **Install app** / **Add to Home Screen** action to install it. It shows an offline notice and lets you explicitly reload when an update is ready, so an active session is never interrupted automatically. The development client (`npm run dev:client`) intentionally runs as a normal Vite application, so it does not expose an install prompt.
|
|
61
|
+
|
|
56
62
|
Want to run from source or contribute? See [`CONTRIBUTING.md`](./CONTRIBUTING.md).
|
|
57
63
|
|
|
58
64
|
### Docker
|
|
@@ -78,7 +84,7 @@ The most common knobs:
|
|
|
78
84
|
| `ANTHROPIC_API_KEY` | none | Claude Code engine credential (alternative to `claude /login`) |
|
|
79
85
|
| `OPENAI_API_KEY` | none | Codex engine credential (alternative to `codex login`) |
|
|
80
86
|
|
|
81
|
-
Global and per-project settings (worktree path, dev server commands, E2E framework, prompt templates, git conventions, branch prefixes, lifecycle scripts, task prompt) are edited in **Settings** at runtime. Per-project values inherit from the global ones when left empty.
|
|
87
|
+
Global and per-project settings (worktree path, dev server commands, E2E framework, prompt templates, git conventions, branch prefixes, lifecycle scripts, task prompt) are edited in **Settings** at runtime. Notion and Sentry are enabled independently in their respective tabs; disabling one keeps its configuration and existing workspace links, but removes its import and automation UI. In **Settings → General → Activity feed**, you can hide agent reasoning panels. Per-project values inherit from the global ones when left empty.
|
|
82
88
|
|
|
83
89
|
The full reference (every env var, every setting key, MCP server registration, Notion / Sentry / Voice setup) is in [`CONFIGURATION.md`](./CONFIGURATION.md).
|
|
84
90
|
|
|
@@ -102,8 +108,8 @@ A merged workspace is automatically archived, but its worktree folder usually ca
|
|
|
102
108
|
|
|
103
109
|
Kōbō ships first-class support for three external systems. All are opt-in and reuse credentials you may already have configured for Claude Code.
|
|
104
110
|
|
|
105
|
-
- **Notion**: import missions, tasks, and acceptance criteria from a Notion page.
|
|
106
|
-
- **Sentry**: paste an issue URL to spawn a fix workspace with the stacktrace, tags, and a TDD workflow.
|
|
111
|
+
- **Notion**: import missions, tasks, and acceptance criteria from a Notion page. Enable it in **Settings → Notion** when you want its creation/import flow, then use **Test connection** to validate its MCP credentials.
|
|
112
|
+
- **Sentry**: paste an issue URL to spawn a fix workspace with the stacktrace, tags, and a TDD workflow. Enable it independently in **Settings → Sentry**; **Test connection** validates its MCP credentials and authenticated identity.
|
|
107
113
|
- **Voice transcription**: local push-to-talk via [`whisper.cpp`](https://github.com/ggml-org/whisper.cpp).
|
|
108
114
|
|
|
109
115
|
See [`CONFIGURATION.md`](./CONFIGURATION.md) for the setup of each.
|
|
@@ -145,14 +151,14 @@ Full install instructions and the prompt-suite differences are in [`CONFIGURATIO
|
|
|
145
151
|
|
|
146
152
|
## Architecture
|
|
147
153
|
|
|
148
|
-
Hono backend, Vue 3 + Quasar
|
|
154
|
+
Hono backend, Vue 3 + Quasar PWA, SQLite (WAL) for persistence, WebSocket for live updates. Each workspace spawns its own agent engine and a dedicated MCP server (`kobo-tasks`) that the agent uses to query and mutate workspace state.
|
|
149
155
|
|
|
150
156
|
```
|
|
151
157
|
src/
|
|
152
158
|
├── server/ # Hono backend (routes, services, db, agent orchestrator)
|
|
153
159
|
│ ├── services/agent/engines/ # claude-code/ + codex/ engines
|
|
154
160
|
│ └── ...
|
|
155
|
-
├── client/ # Vue 3 + Quasar
|
|
161
|
+
├── client/ # Vue 3 + Quasar PWA
|
|
156
162
|
├── mcp-server/ # kobo-tasks MCP server, spawned per workspace
|
|
157
163
|
├── shared/ # types shared backend ↔ frontend
|
|
158
164
|
└── __tests__/ # Vitest suite, backend + client, thousands of tests
|
|
@@ -420,6 +420,27 @@ export const migrations = [
|
|
|
420
420
|
}
|
|
421
421
|
},
|
|
422
422
|
},
|
|
423
|
+
{
|
|
424
|
+
version: 32,
|
|
425
|
+
name: 'add-workspace-permission-rules',
|
|
426
|
+
migrate: (db) => {
|
|
427
|
+
db.exec(`
|
|
428
|
+
CREATE TABLE IF NOT EXISTS workspace_permission_rules (
|
|
429
|
+
id TEXT PRIMARY KEY,
|
|
430
|
+
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
431
|
+
engine TEXT,
|
|
432
|
+
tool_name TEXT NOT NULL,
|
|
433
|
+
scope TEXT NOT NULL CHECK(scope IN ('operation', 'tool')),
|
|
434
|
+
fingerprint TEXT,
|
|
435
|
+
display_label TEXT NOT NULL,
|
|
436
|
+
created_at TEXT NOT NULL,
|
|
437
|
+
updated_at TEXT NOT NULL
|
|
438
|
+
);
|
|
439
|
+
CREATE INDEX IF NOT EXISTS idx_workspace_permission_rules_lookup
|
|
440
|
+
ON workspace_permission_rules(workspace_id, engine, tool_name, scope, fingerprint);
|
|
441
|
+
`);
|
|
442
|
+
},
|
|
443
|
+
},
|
|
423
444
|
];
|
|
424
445
|
/** Current schema version — always equals the highest migration version. */
|
|
425
446
|
export const SCHEMA_VERSION = migrations.length > 0 ? migrations[migrations.length - 1].version : 1;
|
package/dist/server/db/schema.js
CHANGED
|
@@ -73,6 +73,20 @@ export function initSchema(db) {
|
|
|
73
73
|
created_at TEXT NOT NULL
|
|
74
74
|
);
|
|
75
75
|
|
|
76
|
+
CREATE TABLE IF NOT EXISTS workspace_permission_rules (
|
|
77
|
+
id TEXT PRIMARY KEY,
|
|
78
|
+
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
79
|
+
engine TEXT,
|
|
80
|
+
tool_name TEXT NOT NULL,
|
|
81
|
+
scope TEXT NOT NULL CHECK(scope IN ('operation', 'tool')),
|
|
82
|
+
fingerprint TEXT,
|
|
83
|
+
display_label TEXT NOT NULL,
|
|
84
|
+
created_at TEXT NOT NULL,
|
|
85
|
+
updated_at TEXT NOT NULL
|
|
86
|
+
);
|
|
87
|
+
CREATE INDEX IF NOT EXISTS idx_workspace_permission_rules_lookup
|
|
88
|
+
ON workspace_permission_rules(workspace_id, engine, tool_name, scope, fingerprint);
|
|
89
|
+
|
|
76
90
|
CREATE TABLE IF NOT EXISTS pending_wakeups (
|
|
77
91
|
workspace_id TEXT PRIMARY KEY REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
78
92
|
target_at TEXT NOT NULL,
|
|
@@ -164,8 +164,8 @@ app.get('/report', (c) => {
|
|
|
164
164
|
},
|
|
165
165
|
agentSessions: { orphaned },
|
|
166
166
|
integrations: {
|
|
167
|
-
notion: { configured: Boolean(healthGlobalSettings.notionMcpKey) },
|
|
168
|
-
sentry: { configured: Boolean(healthGlobalSettings.sentryMcpKey) },
|
|
167
|
+
notion: { configured: Boolean(healthGlobalSettings.notionMcpKey), enabled: healthGlobalSettings.notionEnabled },
|
|
168
|
+
sentry: { configured: Boolean(healthGlobalSettings.sentryMcpKey), enabled: healthGlobalSettings.sentryEnabled },
|
|
169
169
|
editor: { configured: Boolean(healthGlobalSettings.editorCommand) },
|
|
170
170
|
},
|
|
171
171
|
active: {
|
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
import { extractNotionPage, listNotionUsers } from '../services/notion-service.js';
|
|
2
|
+
import { extractNotionPage, listNotionUsers, testNotionConnection } from '../services/notion-service.js';
|
|
3
|
+
import { getGlobalSettings } from '../services/settings-service.js';
|
|
3
4
|
/** Hono sub-router for Notion page extraction. */
|
|
4
5
|
const app = new Hono();
|
|
6
|
+
app.post('/test', async (c) => {
|
|
7
|
+
try {
|
|
8
|
+
if (!getGlobalSettings().notionEnabled)
|
|
9
|
+
return c.json({ error: 'Notion integration is disabled in Settings' }, 403);
|
|
10
|
+
return c.json(await testNotionConnection());
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
5
16
|
// GET /api/notion/users — list workspace users (humans only, for the settings dropdown)
|
|
6
17
|
app.get('/users', async (c) => {
|
|
7
18
|
try {
|
|
19
|
+
if (!getGlobalSettings().notionEnabled) {
|
|
20
|
+
return c.json({ error: 'Notion integration is disabled in Settings' }, 403);
|
|
21
|
+
}
|
|
8
22
|
const users = await listNotionUsers();
|
|
9
23
|
return c.json({ users });
|
|
10
24
|
}
|
|
@@ -16,6 +30,9 @@ app.get('/users', async (c) => {
|
|
|
16
30
|
// POST /api/notion/extract — extract a Notion page
|
|
17
31
|
app.post('/extract', async (c) => {
|
|
18
32
|
try {
|
|
33
|
+
if (!getGlobalSettings().notionEnabled) {
|
|
34
|
+
return c.json({ error: 'Notion integration is disabled in Settings' }, 403);
|
|
35
|
+
}
|
|
19
36
|
const body = await c.req.json();
|
|
20
37
|
if (!body.url) {
|
|
21
38
|
return c.json({ error: 'Missing required field: url' }, 400);
|
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
import { extractSentryIssue } from '../services/sentry-service.js';
|
|
2
|
+
import { extractSentryIssue, testSentryConnection } from '../services/sentry-service.js';
|
|
3
|
+
import { getGlobalSettings } from '../services/settings-service.js';
|
|
3
4
|
/** Hono sub-router for Sentry issue extraction (preflight). */
|
|
4
5
|
const app = new Hono();
|
|
6
|
+
app.post('/test', async (c) => {
|
|
7
|
+
try {
|
|
8
|
+
if (!getGlobalSettings().sentryEnabled)
|
|
9
|
+
return c.json({ error: 'Sentry integration is disabled in Settings' }, 403);
|
|
10
|
+
return c.json(await testSentryConnection());
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
5
16
|
// POST /api/sentry/extract — extract a Sentry issue by URL
|
|
6
17
|
app.post('/extract', async (c) => {
|
|
7
18
|
try {
|
|
19
|
+
if (!getGlobalSettings().sentryEnabled) {
|
|
20
|
+
return c.json({ error: 'Sentry integration is disabled in Settings' }, 403);
|
|
21
|
+
}
|
|
8
22
|
const body = await c.req.json();
|
|
9
23
|
if (!body.url) {
|
|
10
24
|
return c.json({ error: 'Missing required field: url' }, 400);
|
|
@@ -34,6 +34,7 @@ import { getSuitePrompts } from '../services/skill-suite-prompts.js';
|
|
|
34
34
|
import * as terminalService from '../services/terminal-service.js';
|
|
35
35
|
import * as wakeupService from '../services/wakeup-service.js';
|
|
36
36
|
import * as wsService from '../services/websocket-service.js';
|
|
37
|
+
import * as permissionPolicyService from '../services/workspace-permission-policy-service.js';
|
|
37
38
|
import * as workspaceService from '../services/workspace-service.js';
|
|
38
39
|
import * as purgeWorktreeService from '../services/worktree-purge-service.js';
|
|
39
40
|
import * as worktreeService from '../services/worktree-service.js';
|
|
@@ -181,6 +182,9 @@ app.post('/', migrationGuard, async (c) => {
|
|
|
181
182
|
// before createWorkspace and surface failures as 422.
|
|
182
183
|
let notionContent = null;
|
|
183
184
|
if (body.notionUrl) {
|
|
185
|
+
if (!settingsService.getGlobalSettings().notionEnabled) {
|
|
186
|
+
return c.json({ error: 'Notion integration is disabled in Settings' }, 403);
|
|
187
|
+
}
|
|
184
188
|
try {
|
|
185
189
|
notionContent = await notionService.extractNotionPage(body.notionUrl);
|
|
186
190
|
}
|
|
@@ -203,6 +207,9 @@ app.post('/', migrationGuard, async (c) => {
|
|
|
203
207
|
}
|
|
204
208
|
let sentryContent = null;
|
|
205
209
|
if (body.sentryUrl) {
|
|
210
|
+
if (!settingsService.getGlobalSettings().sentryEnabled) {
|
|
211
|
+
return c.json({ error: 'Sentry integration is disabled in Settings' }, 403);
|
|
212
|
+
}
|
|
206
213
|
try {
|
|
207
214
|
sentryContent = await sentryService.extractSentryIssue(body.sentryUrl);
|
|
208
215
|
}
|
|
@@ -376,11 +383,9 @@ app.post('/', migrationGuard, async (c) => {
|
|
|
376
383
|
}
|
|
377
384
|
// Update workspace name with Notion page title only if user didn't
|
|
378
385
|
// provide a custom name. Prefix with the Notion unique-id (e.g.
|
|
379
|
-
//
|
|
380
|
-
// immediately scannable in the sidebar.
|
|
386
|
+
// Use the Notion page title when the user left the generic placeholder.
|
|
381
387
|
if (notionContent.title && workspace.name === 'workspace') {
|
|
382
|
-
|
|
383
|
-
workspace = workspaceService.updateWorkspaceName(workspace.id, `${prefix}${notionContent.title}`);
|
|
388
|
+
workspace = workspaceService.updateWorkspaceName(workspace.id, notionContent.title);
|
|
384
389
|
}
|
|
385
390
|
}
|
|
386
391
|
// Create manual tasks/criteria if no Notion content was extracted
|
|
@@ -1275,10 +1280,14 @@ app.post('/:id/deferred-permission/decision', async (c) => {
|
|
|
1275
1280
|
if (body.decision !== 'allow' && body.decision !== 'deny') {
|
|
1276
1281
|
return c.json({ error: "decision must be 'allow' or 'deny'" }, 400);
|
|
1277
1282
|
}
|
|
1283
|
+
if (body.scope && !['once', 'turn', 'operation', 'tool'].includes(body.scope)) {
|
|
1284
|
+
return c.json({ error: "scope must be 'once', 'turn', 'operation' or 'tool'" }, 400);
|
|
1285
|
+
}
|
|
1278
1286
|
await agentManager.answerPendingPermission(id, {
|
|
1279
1287
|
toolCallId: body.toolCallId,
|
|
1280
1288
|
decision: body.decision,
|
|
1281
1289
|
reason: typeof body.reason === 'string' ? body.reason : undefined,
|
|
1290
|
+
scope: body.scope,
|
|
1282
1291
|
});
|
|
1283
1292
|
return c.json({ ok: true });
|
|
1284
1293
|
}
|
|
@@ -1288,6 +1297,20 @@ app.post('/:id/deferred-permission/decision', async (c) => {
|
|
|
1288
1297
|
return c.json({ error: message }, status);
|
|
1289
1298
|
}
|
|
1290
1299
|
});
|
|
1300
|
+
app.get('/:id/permission-rules', (c) => {
|
|
1301
|
+
const id = c.req.param('id');
|
|
1302
|
+
if (!workspaceService.getWorkspace(id))
|
|
1303
|
+
return c.json({ error: `Workspace '${id}' not found` }, 404);
|
|
1304
|
+
return c.json({ rules: permissionPolicyService.listWorkspacePermissionRules(id) });
|
|
1305
|
+
});
|
|
1306
|
+
app.delete('/:id/permission-rules/:ruleId', (c) => {
|
|
1307
|
+
const id = c.req.param('id');
|
|
1308
|
+
if (!workspaceService.getWorkspace(id))
|
|
1309
|
+
return c.json({ error: `Workspace '${id}' not found` }, 404);
|
|
1310
|
+
if (!permissionPolicyService.removeWorkspacePermissionRule(id, c.req.param('ruleId')))
|
|
1311
|
+
return c.json({ error: 'Permission rule not found' }, 404);
|
|
1312
|
+
return c.json({ ok: true });
|
|
1313
|
+
});
|
|
1291
1314
|
// DELETE /api/workspaces/:id/events/:eventId — permanently dismiss a single
|
|
1292
1315
|
// persisted ws_events row (used today by the agent error banner so a closed
|
|
1293
1316
|
// error doesn't replay on F5 / reconnect). Defensive: only deletes if the row
|
|
@@ -1465,6 +1488,103 @@ app.post('/:id/tasks/:taskId/notify-done', (c) => {
|
|
|
1465
1488
|
return c.json({ error: message }, 500);
|
|
1466
1489
|
}
|
|
1467
1490
|
});
|
|
1491
|
+
// GET /api/workspaces/:id/history-search — full-workspace persisted conversation search.
|
|
1492
|
+
app.get('/:id/history-search', (c) => {
|
|
1493
|
+
try {
|
|
1494
|
+
const id = c.req.param('id');
|
|
1495
|
+
if (!workspaceService.getWorkspace(id))
|
|
1496
|
+
return c.json({ error: `Workspace '${id}' not found` }, 404);
|
|
1497
|
+
const query = (c.req.query('q') ?? '').trim();
|
|
1498
|
+
if (query.length < 2)
|
|
1499
|
+
return c.json({ results: [] });
|
|
1500
|
+
const limit = Math.min(parseInt(c.req.query('limit') ?? '30', 10) || 30, 100);
|
|
1501
|
+
const needle = `%${query.replace(/[%_]/g, '\\$&')}%`;
|
|
1502
|
+
const rows = getDb()
|
|
1503
|
+
.prepare(`SELECT id, session_id, type, payload, created_at FROM ws_events
|
|
1504
|
+
WHERE workspace_id = ? AND payload LIKE ? ESCAPE '\\'
|
|
1505
|
+
ORDER BY rowid DESC LIMIT ?`)
|
|
1506
|
+
.all(id, needle, limit * 4);
|
|
1507
|
+
const normalized = rows.flatMap((row) => {
|
|
1508
|
+
const result = readableHistorySearchResult(row.type, row.payload, query);
|
|
1509
|
+
if (!result)
|
|
1510
|
+
return [];
|
|
1511
|
+
return [{ eventId: row.id, sessionId: row.session_id, createdAt: row.created_at, ...result }];
|
|
1512
|
+
});
|
|
1513
|
+
return c.json({ results: normalized.slice(0, limit) });
|
|
1514
|
+
}
|
|
1515
|
+
catch (err) {
|
|
1516
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
function readableHistorySearchResult(type, rawPayload, query) {
|
|
1520
|
+
let payload;
|
|
1521
|
+
try {
|
|
1522
|
+
payload = JSON.parse(rawPayload);
|
|
1523
|
+
}
|
|
1524
|
+
catch {
|
|
1525
|
+
return null;
|
|
1526
|
+
}
|
|
1527
|
+
let text = null;
|
|
1528
|
+
let kind = 'agent';
|
|
1529
|
+
if (type === 'user:message') {
|
|
1530
|
+
text = typeof payload.content === 'string' ? payload.content : null;
|
|
1531
|
+
kind = 'user';
|
|
1532
|
+
}
|
|
1533
|
+
else if (type === 'agent:event') {
|
|
1534
|
+
if (payload.kind === 'message:text' && typeof payload.text === 'string')
|
|
1535
|
+
text = payload.text;
|
|
1536
|
+
}
|
|
1537
|
+
else if (type === 'agent:output') {
|
|
1538
|
+
const message = payload.message;
|
|
1539
|
+
if (Array.isArray(message?.content)) {
|
|
1540
|
+
text = message.content
|
|
1541
|
+
.flatMap((block) => {
|
|
1542
|
+
if (!block || typeof block !== 'object')
|
|
1543
|
+
return [];
|
|
1544
|
+
const candidate = block;
|
|
1545
|
+
return candidate.type === 'text' && typeof candidate.text === 'string' ? [candidate.text] : [];
|
|
1546
|
+
})
|
|
1547
|
+
.join('\n');
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
if (!text)
|
|
1551
|
+
return null;
|
|
1552
|
+
const matchIndex = text.toLowerCase().indexOf(query.toLowerCase());
|
|
1553
|
+
if (matchIndex < 0)
|
|
1554
|
+
return null;
|
|
1555
|
+
const start = Math.max(0, matchIndex - 90);
|
|
1556
|
+
const end = Math.min(text.length, matchIndex + query.length + 180);
|
|
1557
|
+
return {
|
|
1558
|
+
kind,
|
|
1559
|
+
snippet: `${start > 0 ? '…' : ''}${text.slice(start, end).replace(/\s+/g, ' ').trim()}${end < text.length ? '…' : ''}`,
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
// GET /api/workspaces/:id/diagnostic.json — portable, redacted-by-design event diagnostic.
|
|
1563
|
+
app.get('/:id/diagnostic.json', (c) => {
|
|
1564
|
+
try {
|
|
1565
|
+
const id = c.req.param('id');
|
|
1566
|
+
const workspace = workspaceService.getWorkspace(id);
|
|
1567
|
+
if (!workspace)
|
|
1568
|
+
return c.json({ error: `Workspace '${id}' not found` }, 404);
|
|
1569
|
+
const db = getDb();
|
|
1570
|
+
const sessions = db
|
|
1571
|
+
.prepare('SELECT id, engine_session_id, status, started_at, ended_at, model, name FROM agent_sessions WHERE workspace_id = ? ORDER BY started_at ASC')
|
|
1572
|
+
.all(id);
|
|
1573
|
+
const events = db
|
|
1574
|
+
.prepare('SELECT id, session_id, type, created_at FROM ws_events WHERE workspace_id = ? ORDER BY rowid ASC')
|
|
1575
|
+
.all(id);
|
|
1576
|
+
c.header('Content-Disposition', `attachment; filename="${workspace.name.replace(/[^a-z0-9]+/gi, '-').toLowerCase() || id}-diagnostic.json"`);
|
|
1577
|
+
return c.json({
|
|
1578
|
+
generatedAt: new Date().toISOString(),
|
|
1579
|
+
workspace: { id, name: workspace.name, engine: workspace.engine },
|
|
1580
|
+
sessions,
|
|
1581
|
+
events,
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
catch (err) {
|
|
1585
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1586
|
+
}
|
|
1587
|
+
});
|
|
1468
1588
|
// GET /api/workspaces/:id/events — paginated event history (must be before GET /:id for route ordering)
|
|
1469
1589
|
app.get('/:id/events', (c) => {
|
|
1470
1590
|
try {
|
|
@@ -1474,13 +1594,27 @@ app.get('/:id/events', (c) => {
|
|
|
1474
1594
|
return c.json({ error: `Workspace '${id}' not found` }, 404);
|
|
1475
1595
|
}
|
|
1476
1596
|
const before = c.req.query('before'); // event ID cursor
|
|
1597
|
+
const around = c.req.query('around'); // event ID to include in a focused history window
|
|
1477
1598
|
// optional: scope to a session view. Session views also include
|
|
1478
1599
|
// workspace-level rows where session_id IS NULL (legacy/pre-session items).
|
|
1479
1600
|
const session = c.req.query('session');
|
|
1480
1601
|
const limit = Math.min(parseInt(c.req.query('limit') ?? '100', 10) || 100, 500);
|
|
1481
1602
|
const db = getDb();
|
|
1482
1603
|
let rows;
|
|
1483
|
-
if (
|
|
1604
|
+
if (around) {
|
|
1605
|
+
const targetRow = db.prepare('SELECT rowid FROM ws_events WHERE id = ? AND workspace_id = ?').get(around, id);
|
|
1606
|
+
if (!targetRow)
|
|
1607
|
+
return c.json({ events: [], hasMore: false });
|
|
1608
|
+
const halfWindow = Math.floor(limit / 2);
|
|
1609
|
+
rows = session
|
|
1610
|
+
? db
|
|
1611
|
+
.prepare('SELECT * FROM ws_events WHERE workspace_id = ? AND (session_id = ? OR session_id IS NULL) AND rowid BETWEEN ? AND ? ORDER BY rowid ASC')
|
|
1612
|
+
.all(id, session, targetRow.rowid - halfWindow, targetRow.rowid + halfWindow)
|
|
1613
|
+
: db
|
|
1614
|
+
.prepare('SELECT * FROM ws_events WHERE workspace_id = ? AND rowid BETWEEN ? AND ? ORDER BY rowid ASC')
|
|
1615
|
+
.all(id, targetRow.rowid - halfWindow, targetRow.rowid + halfWindow);
|
|
1616
|
+
}
|
|
1617
|
+
else if (before) {
|
|
1484
1618
|
// Get the rowid of the cursor event
|
|
1485
1619
|
const cursorRow = db.prepare('SELECT rowid FROM ws_events WHERE id = ?').get(before);
|
|
1486
1620
|
if (!cursorRow) {
|
|
@@ -1508,7 +1642,7 @@ app.get('/:id/events', (c) => {
|
|
|
1508
1642
|
}
|
|
1509
1643
|
// Reverse to chronological order (we queried DESC for "before" pagination,
|
|
1510
1644
|
// or for the "session + no cursor" case where we fetched the newest first).
|
|
1511
|
-
if (before || session)
|
|
1645
|
+
if (before || (session && !around))
|
|
1512
1646
|
rows.reverse();
|
|
1513
1647
|
const events = rows.map((row) => {
|
|
1514
1648
|
let parsedPayload;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { query, } from '@anthropic-ai/claude-agent-sdk';
|
|
2
2
|
import { nanoid } from 'nanoid';
|
|
3
|
+
import { isWorkspacePermissionAllowed } from '../../../workspace-permission-policy-service.js';
|
|
4
|
+
import { createStreamingBatcher } from '../../streaming-batcher.js';
|
|
3
5
|
import { CLAUDE_CODE_CAPABILITIES } from './capabilities.js';
|
|
4
6
|
import { createMapperState, mapSdkMessage, QUOTA_PATTERN, tryEmitQuota } from './event-mapper.js';
|
|
5
7
|
import { buildClaudeOptions } from './options-builder.js';
|
|
@@ -100,10 +102,17 @@ export function createClaudeCodeEngine() {
|
|
|
100
102
|
if (toolName !== 'AskUserQuestion' && !isInteractive) {
|
|
101
103
|
return Promise.resolve({ behavior: 'allow', updatedInput: input });
|
|
102
104
|
}
|
|
105
|
+
if (toolName !== 'AskUserQuestion' &&
|
|
106
|
+
isWorkspacePermissionAllowed(options.workspaceId, { engine: 'claude-code', toolName, payload: input })) {
|
|
107
|
+
return Promise.resolve({ behavior: 'allow', updatedInput: input });
|
|
108
|
+
}
|
|
103
109
|
const requestKind = toolName === 'AskUserQuestion' ? 'question' : 'permission';
|
|
104
110
|
return new Promise((resolve, reject) => {
|
|
105
111
|
const resolver = { resolve, input, requestKind };
|
|
106
112
|
pendingResolvers.set(toolCallId, resolver);
|
|
113
|
+
// A user decision is intentional inactivity. The text-only watchdog
|
|
114
|
+
// must never end this session while the permission card is visible.
|
|
115
|
+
clearTextIdleWatchdog();
|
|
107
116
|
const onAbort = () => {
|
|
108
117
|
if (pendingResolvers.get(toolCallId) === resolver) {
|
|
109
118
|
pendingResolvers.delete(toolCallId);
|
|
@@ -202,7 +211,7 @@ export function createClaudeCodeEngine() {
|
|
|
202
211
|
let discoveredSessionId;
|
|
203
212
|
// A throwing onEvent handler (e.g. DB query against a closed connection
|
|
204
213
|
// during async test teardown) must not escape as an unhandled rejection.
|
|
205
|
-
const
|
|
214
|
+
const emitDirect = (ev) => {
|
|
206
215
|
try {
|
|
207
216
|
onEvent(ev);
|
|
208
217
|
}
|
|
@@ -210,6 +219,8 @@ export function createClaudeCodeEngine() {
|
|
|
210
219
|
console.error('[claude-engine] onEvent handler threw:', err);
|
|
211
220
|
}
|
|
212
221
|
};
|
|
222
|
+
const streamingBatcher = createStreamingBatcher(emitDirect);
|
|
223
|
+
const safeEmit = (ev) => streamingBatcher.push(ev);
|
|
213
224
|
let iteratorRunning = false;
|
|
214
225
|
let userInterrupted = false;
|
|
215
226
|
let completedResponses = 0;
|
|
@@ -240,6 +251,10 @@ export function createClaudeCodeEngine() {
|
|
|
240
251
|
const armTextIdleWatchdog = () => {
|
|
241
252
|
clearTextIdleWatchdog();
|
|
242
253
|
textIdleTimer = setTimeout(() => {
|
|
254
|
+
if (pendingResolvers.size > 0) {
|
|
255
|
+
textIdleTimer = undefined;
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
243
258
|
console.warn(`[claude-engine] SDK stream inactive ${TEXT_IDLE_TIMEOUT_MS}ms after a text-only response — forcing session:ended`);
|
|
244
259
|
const reason = userInterrupted ? 'killed' : mapperState.sawErrorResult ? 'error' : 'completed';
|
|
245
260
|
emitSessionEnded(reason, reason === 'completed' ? 0 : null);
|
|
@@ -322,6 +337,7 @@ export function createClaudeCodeEngine() {
|
|
|
322
337
|
}
|
|
323
338
|
}
|
|
324
339
|
finally {
|
|
340
|
+
streamingBatcher.close();
|
|
325
341
|
// The post-result drain watchdog (if armed) is moot once the
|
|
326
342
|
// iterator has exited — clear it so a healthy run never triggers a
|
|
327
343
|
// stray abort after it already ended.
|
|
@@ -151,6 +151,33 @@ export function mapSdkMessage(msg, state) {
|
|
|
151
151
|
}
|
|
152
152
|
return events;
|
|
153
153
|
}
|
|
154
|
+
if (type === 'stream_event') {
|
|
155
|
+
const event = parsed.event;
|
|
156
|
+
if (!event)
|
|
157
|
+
return events;
|
|
158
|
+
if (event.type === 'message_start') {
|
|
159
|
+
const streamedMessage = event.message;
|
|
160
|
+
const messageId = typeof streamedMessage?.id === 'string' ? streamedMessage.id : undefined;
|
|
161
|
+
if (messageId) {
|
|
162
|
+
state.streamingMessageId = messageId;
|
|
163
|
+
if (!state.openMessages.has(messageId))
|
|
164
|
+
state.openMessages.set(messageId, { sawText: false });
|
|
165
|
+
}
|
|
166
|
+
return events;
|
|
167
|
+
}
|
|
168
|
+
if (event.type === 'content_block_delta') {
|
|
169
|
+
const delta = event.delta;
|
|
170
|
+
const text = delta?.type === 'text_delta' && typeof delta.text === 'string' ? delta.text : undefined;
|
|
171
|
+
if (text && state.streamingMessageId) {
|
|
172
|
+
const messageId = state.streamingMessageId;
|
|
173
|
+
const messageState = state.openMessages.get(messageId);
|
|
174
|
+
if (messageState)
|
|
175
|
+
messageState.sawText = true;
|
|
176
|
+
events.push({ kind: 'message:text', messageId, text, streaming: true });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return events;
|
|
180
|
+
}
|
|
154
181
|
if (type === 'system') {
|
|
155
182
|
if (subtype === 'compact' || subtype === 'compact_boundary') {
|
|
156
183
|
events.push({ kind: 'session:compacted' });
|
|
@@ -233,8 +260,10 @@ export function mapSdkMessage(msg, state) {
|
|
|
233
260
|
const blockType = block.type;
|
|
234
261
|
if (blockType === 'text' && typeof block.text === 'string') {
|
|
235
262
|
const text = block.text;
|
|
236
|
-
|
|
237
|
-
|
|
263
|
+
if (!msgState.sawText) {
|
|
264
|
+
events.push({ kind: 'message:text', messageId, text, streaming: true });
|
|
265
|
+
msgState.sawText = true;
|
|
266
|
+
}
|
|
238
267
|
if (text.includes('[BRAINSTORM_COMPLETE]')) {
|
|
239
268
|
events.push({ kind: 'session:brainstorm-complete' });
|
|
240
269
|
}
|
|
@@ -42,6 +42,9 @@ export function buildClaudeOptions(input) {
|
|
|
42
42
|
// `~/.claude/settings.json` (where `skipDangerousModePermissionPrompt`
|
|
43
43
|
// lives), breaking bypass for MCP tools.
|
|
44
44
|
settingSources: ['user', 'project', 'local'],
|
|
45
|
+
// Relay the Anthropic API's raw message stream so Kōbō can display true
|
|
46
|
+
// text deltas instead of waiting for complete assistant content blocks.
|
|
47
|
+
includePartialMessages: true,
|
|
45
48
|
};
|
|
46
49
|
switch (input.agentPermissionMode) {
|
|
47
50
|
case 'plan':
|