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