@loicngr/kobo 1.5.7 → 1.6.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 +6 -1
- package/README.md +30 -15
- package/dist/server/db/index.js +17 -0
- package/dist/server/db/migrations.js +10 -0
- package/dist/server/db/schema.js +2 -1
- package/dist/server/index.js +24 -3
- package/dist/server/middleware/migration-guard.js +15 -0
- package/dist/server/routes/dev-server.js +3 -2
- package/dist/server/routes/engines.js +9 -0
- package/dist/server/routes/migration.js +5 -0
- package/dist/server/routes/workspaces.js +138 -10
- package/dist/server/services/agent/engines/claude-code/args-builder.js +22 -0
- package/dist/server/services/agent/engines/claude-code/capabilities.js +17 -0
- package/dist/server/services/agent/engines/claude-code/engine.js +163 -0
- package/dist/server/services/agent/engines/claude-code/mcp-config.js +23 -0
- package/dist/server/services/agent/engines/claude-code/stream-parser.js +224 -0
- package/dist/server/services/agent/engines/registry.js +21 -0
- package/dist/server/services/agent/engines/types.js +18 -0
- package/dist/server/services/agent/event-router.js +4 -0
- package/dist/server/services/agent/orchestrator.js +582 -0
- package/dist/server/services/agent/session-controller.js +79 -0
- package/dist/server/services/content-migration-service.js +155 -0
- package/dist/server/services/db-backup-service.js +15 -0
- package/dist/server/services/websocket-service.js +81 -50
- package/dist/server/services/workspace-service.js +11 -5
- package/dist/server/utils/git-ops.js +78 -9
- package/dist/server/utils/paths.js +1 -1
- package/dist/shared/models.js +50 -0
- package/package.json +1 -1
- package/src/client/dist/spa/assets/ActivityFeed-DYtAK49y.js +7 -0
- package/src/client/dist/spa/assets/ActivityFeed-DiwnrdKX.css +1 -0
- package/src/client/dist/spa/assets/ClosePopup-DqhgFbQo.js +1 -0
- package/src/client/dist/spa/assets/CreatePage-DENfwzPL.js +2 -0
- package/src/client/dist/spa/assets/CreatePage-yu2IH7GW.css +1 -0
- package/src/client/dist/spa/assets/DiffViewer-C6q11kmw.js +2 -0
- package/src/client/dist/spa/assets/HealthPage-Cjc79NaA.js +1 -0
- package/src/client/dist/spa/assets/{MainLayout-rVleAIBi.css → MainLayout-B5poKEy_.css} +1 -1
- package/src/client/dist/spa/assets/MainLayout-CFbMw65L.js +37 -0
- package/src/client/dist/spa/assets/QBadge-BUkmTO0P.js +1 -0
- package/src/client/dist/spa/assets/QBtn-p1aZtrJH.js +1 -0
- package/src/client/dist/spa/assets/QDialog-D42GLa1i.js +1 -0
- package/src/client/dist/spa/assets/QExpansionItem-5ekmpO-2.js +1 -0
- package/src/client/dist/spa/assets/{QSpinner-CliSLjf8.js → QIcon-B0-pH3Qs.js} +1 -1
- package/src/client/dist/spa/assets/QItemLabel-Czw5g0px.js +1 -0
- package/src/client/dist/spa/assets/QItemSection-GlMrLmz3.js +1 -0
- package/src/client/dist/spa/assets/QList-DNzlynsS.js +1 -0
- package/src/client/dist/spa/assets/QMenu-Q69oVX7b.js +1 -0
- package/src/client/dist/spa/assets/QPage-B09NY4Nf.js +1 -0
- package/src/client/dist/spa/assets/QScrollArea-L6wUiA20.js +1 -0
- package/src/client/dist/spa/assets/QSeparator-rkjCbX2M.js +1 -0
- package/src/client/dist/spa/assets/QSpace-PlDK6Fg3.js +1 -0
- package/src/client/dist/spa/assets/QSpinnerDots-By20ptst.js +1 -0
- package/src/client/dist/spa/assets/QTabPanels-Crs-ujNO.js +1 -0
- package/src/client/dist/spa/assets/QTooltip-Cg9E3Dvw.js +1 -0
- package/src/client/dist/spa/assets/SearchPage-Bf-iZnyE.js +1 -0
- package/src/client/dist/spa/assets/SettingsPage-BdcH3BSs.js +1 -0
- package/src/client/dist/spa/assets/TouchPan-DFx22dM3.js +1 -0
- package/src/client/dist/spa/assets/WorkspacePage-DPGiH02q.css +1 -0
- package/src/client/dist/spa/assets/WorkspacePage-UUE0pPCR.js +4 -0
- package/src/client/dist/spa/assets/{cssMode-C9wGTDAD.js → cssMode-BYtqFZtm.js} +1 -1
- package/src/client/dist/spa/assets/{editor.api-K7V5sl05.js → editor.api-D6ZaO4A_.js} +1 -1
- package/src/client/dist/spa/assets/{editor.main-vZ6V2hrP.js → editor.main-Cc_RDKsq.js} +3 -3
- package/src/client/dist/spa/assets/format-uvONOeL4.js +1 -0
- package/src/client/dist/spa/assets/{formatters-BzaS4w0I.js → formatters-DiJ12fKd.js} +1 -1
- package/src/client/dist/spa/assets/{freemarker2-CRk6pTND.js → freemarker2-CBm--bBd.js} +1 -1
- package/src/client/dist/spa/assets/{handlebars-Cs3bFomb.js → handlebars-whX2mkV5.js} +1 -1
- package/src/client/dist/spa/assets/{html-BT4-1gwt.js → html-D7ga_o6c.js} +1 -1
- package/src/client/dist/spa/assets/{htmlMode-DZ9LYDVG.js → htmlMode-BXImjcsv.js} +1 -1
- package/src/client/dist/spa/assets/i18n-BxLBrD1J.js +1 -0
- package/src/client/dist/spa/assets/index-D997aY4Y.js +2 -0
- package/src/client/dist/spa/assets/{javascript-C0nTLIDg.js → javascript-BwmzNMn5.js} +1 -1
- package/src/client/dist/spa/assets/{jsonMode-LIrD4Pxq.js → jsonMode-CN5Z5bK_.js} +1 -1
- package/src/client/dist/spa/assets/{liquid-BbaUnvHA.js → liquid-CzMNAPor.js} +1 -1
- package/src/client/dist/spa/assets/{marked.esm-gIBce057.js → marked.esm-DW0ulF0a.js} +1 -1
- package/src/client/dist/spa/assets/{mdx-B6iNIRi2.js → mdx-DC_P05Da.js} +1 -1
- package/src/client/dist/spa/assets/models-DMQoi09X.js +1 -0
- package/src/client/dist/spa/assets/{monaco.contribution-CZ_PxrB6.js → monaco.contribution-BsBaFOOD.js} +2 -2
- package/src/client/dist/spa/assets/private.use-form-D1RuEt2P.js +1 -0
- package/src/client/dist/spa/assets/{python-C0uk6BYc.js → python-9DTZ8C3K.js} +1 -1
- package/src/client/dist/spa/assets/{razor-Bj__h6OQ.js → razor-B1LfM20o.js} +1 -1
- package/src/client/dist/spa/assets/scroll-Dh2g7BwR.js +1 -0
- package/src/client/dist/spa/assets/touch-D_A29lik.js +1 -0
- package/src/client/dist/spa/assets/{tsMode-ZSZcAFKU.js → tsMode-DI2bWo8r.js} +1 -1
- package/src/client/dist/spa/assets/{typescript-Bgw42jIH.js → typescript-BZ9QJ2_N.js} +1 -1
- package/src/client/dist/spa/assets/use-id-CeduaJbU.js +1 -0
- package/src/client/dist/spa/assets/use-portal-mhLq4Rqk.js +1 -0
- package/src/client/dist/spa/assets/use-quasar-BBrzedjR.js +1 -0
- package/src/client/dist/spa/assets/{xml-B9gVeW9V.js → xml-D6qm6rp0.js} +1 -1
- package/src/client/dist/spa/assets/{yaml-DUqEwwz-.js → yaml-D2dUr_wY.js} +1 -1
- package/src/client/dist/spa/index.html +11 -14
- package/src/mcp-server/README.md +1 -1
- package/dist/server/services/agent-manager.js +0 -621
- package/src/client/dist/spa/assets/ActivityFeed-CfsKExt9.css +0 -1
- package/src/client/dist/spa/assets/ActivityFeed-Dc1oLbwJ.js +0 -10
- package/src/client/dist/spa/assets/ClosePopup-CdSn7HO8.js +0 -1
- package/src/client/dist/spa/assets/CreatePage-BDKfkW-N.js +0 -2
- package/src/client/dist/spa/assets/CreatePage-dMi4xVYN.css +0 -1
- package/src/client/dist/spa/assets/DiffViewer-DZ9h2M2n.js +0 -2
- package/src/client/dist/spa/assets/HealthPage-BkqexlJb.js +0 -1
- package/src/client/dist/spa/assets/MainLayout-VxUBOt-P.js +0 -37
- package/src/client/dist/spa/assets/QBadge-Bvh-hQ8K.js +0 -1
- package/src/client/dist/spa/assets/QBtn-BsD8vrWq.js +0 -1
- package/src/client/dist/spa/assets/QDialog-CkbLS1If.js +0 -1
- package/src/client/dist/spa/assets/QExpansionItem-C735ptO9.js +0 -1
- package/src/client/dist/spa/assets/QItem-DfoP6eYj.js +0 -1
- package/src/client/dist/spa/assets/QList-D80ms7bw.js +0 -1
- package/src/client/dist/spa/assets/QMenu-DU-wiY_A.js +0 -1
- package/src/client/dist/spa/assets/QPage-BKY2-sf-.js +0 -1
- package/src/client/dist/spa/assets/QSpace-C5Ebr0vq.js +0 -1
- package/src/client/dist/spa/assets/QSpinnerDots-Dp12eHrB.js +0 -1
- package/src/client/dist/spa/assets/QTabPanels-DV1b1MQb.js +0 -1
- package/src/client/dist/spa/assets/QToggle-B0HvuNEg.js +0 -1
- package/src/client/dist/spa/assets/QTooltip-kLXuUa_m.js +0 -1
- package/src/client/dist/spa/assets/SearchPage-ZDAo7WgD.js +0 -1
- package/src/client/dist/spa/assets/SettingsPage-D89evCuo.js +0 -1
- package/src/client/dist/spa/assets/TouchPan-CVMnGs0y.js +0 -1
- package/src/client/dist/spa/assets/WorkspacePage-CWRMLYs-.css +0 -1
- package/src/client/dist/spa/assets/WorkspacePage-Ds5Dqxas.js +0 -4
- package/src/client/dist/spa/assets/focus-manager-DYbz9jFW.js +0 -1
- package/src/client/dist/spa/assets/format-Cyg8IgRi.js +0 -1
- package/src/client/dist/spa/assets/i18n-C0RbMxeL.js +0 -1
- package/src/client/dist/spa/assets/i18n-CkN9X6lQ.js +0 -1
- package/src/client/dist/spa/assets/index-Br4eMfSu.js +0 -5
- package/src/client/dist/spa/assets/models-C3h6lSte.js +0 -1
- package/src/client/dist/spa/assets/pinia-C3JsrLkB.js +0 -1
- package/src/client/dist/spa/assets/private.use-form-BhKyDtO7.js +0 -1
- package/src/client/dist/spa/assets/scroll-CLibRGI-.js +0 -1
- package/src/client/dist/spa/assets/settings-B69lIVX0.js +0 -1
- package/src/client/dist/spa/assets/touch-ChrvzrnI.js +0 -1
- package/src/client/dist/spa/assets/use-dark-DnuCB6tC.js +0 -1
- /package/src/client/dist/spa/assets/{_plugin-vue_export-helper-Cxt1D8wE.js → _plugin-vue_export-helper-CEhRWsKN.js} +0 -0
- /package/src/client/dist/spa/assets/{abap-CFuyUYKP.js → abap-DiwvWnMr.js} +0 -0
- /package/src/client/dist/spa/assets/{apex-Ctq_xcrv.js → apex-CmtZjKlf.js} +0 -0
- /package/src/client/dist/spa/assets/{azcli-BBQSVn-C.js → azcli-DL2My_i-.js} +0 -0
- /package/src/client/dist/spa/assets/{bat-DbnqAfvr.js → bat-B-nC98wG.js} +0 -0
- /package/src/client/dist/spa/assets/{bicep-BtDlIXop.js → bicep-Ju5MwOgh.js} +0 -0
- /package/src/client/dist/spa/assets/{cameligo-BLeJgKTj.js → cameligo-8Eu1TyBr.js} +0 -0
- /package/src/client/dist/spa/assets/{clojure-aZUQIUKP.js → clojure-u-RpMkH3.js} +0 -0
- /package/src/client/dist/spa/assets/{coffee-Secadq9U.js → coffee-CdA7bbTe.js} +0 -0
- /package/src/client/dist/spa/assets/{cpp-JicRPTRv.js → cpp-CzNFP8ks.js} +0 -0
- /package/src/client/dist/spa/assets/{csharp-C7NSOZyj.js → csharp-j1LThmcE.js} +0 -0
- /package/src/client/dist/spa/assets/{csp-CIje7830.js → csp-CLRC61y6.js} +0 -0
- /package/src/client/dist/spa/assets/{css-G0bm1q_M.js → css-r6rC_7P2.js} +0 -0
- /package/src/client/dist/spa/assets/{cypher-CldD5D0u.js → cypher-CW08XVUh.js} +0 -0
- /package/src/client/dist/spa/assets/{dart-DIK3l8YT.js → dart-Cs9aL5T_.js} +0 -0
- /package/src/client/dist/spa/assets/{dockerfile-czxaGh2L.js → dockerfile-BWM0M184.js} +0 -0
- /package/src/client/dist/spa/assets/{ecl-BqdYhwmw.js → ecl-MJJuer5P.js} +0 -0
- /package/src/client/dist/spa/assets/{elixir-m52LePTW.js → elixir-D2AIuXqn.js} +0 -0
- /package/src/client/dist/spa/assets/{flow9-B5QJ9GvZ.js → flow9-B2H24giC.js} +0 -0
- /package/src/client/dist/spa/assets/{fsharp-B15czHsH.js → fsharp-CMk2OIJN.js} +0 -0
- /package/src/client/dist/spa/assets/{go-BkoQxDo1.js → go-BrMkuJg0.js} +0 -0
- /package/src/client/dist/spa/assets/{graphql-BnI6uRa_.js → graphql-PSR1UKGv.js} +0 -0
- /package/src/client/dist/spa/assets/{hcl-CAwwENT7.js → hcl-DAQrbDOW.js} +0 -0
- /package/src/client/dist/spa/assets/{ini-BHM5zh1H.js → ini-0TG5BxW0.js} +0 -0
- /package/src/client/dist/spa/assets/{java-B5i95QvQ.js → java-rgorz17v.js} +0 -0
- /package/src/client/dist/spa/assets/{julia-DPDm885q.js → julia-C8VMdHm8.js} +0 -0
- /package/src/client/dist/spa/assets/{kotlin-qoccd5BP.js → kotlin-CllWo3gX.js} +0 -0
- /package/src/client/dist/spa/assets/{less-B6RU166D.js → less-Cgca25AP.js} +0 -0
- /package/src/client/dist/spa/assets/{lexon-YfUeoL1V.js → lexon-D0GHdBaw.js} +0 -0
- /package/src/client/dist/spa/assets/{lua-BIUI5y9b.js → lua-DmRsNG-P.js} +0 -0
- /package/src/client/dist/spa/assets/{m3-D5SAbSdU.js → m3-BgL5dNKT.js} +0 -0
- /package/src/client/dist/spa/assets/{markdown-CVJLwHzJ.js → markdown-BuJfycGS.js} +0 -0
- /package/src/client/dist/spa/assets/{mips-R-FZ3zOR.js → mips-C9m_93PR.js} +0 -0
- /package/src/client/dist/spa/assets/{msdax-Blveyl9r.js → msdax-CpFHC9OI.js} +0 -0
- /package/src/client/dist/spa/assets/{mysql-D4mY1AFx.js → mysql-qFvltsqN.js} +0 -0
- /package/src/client/dist/spa/assets/{objective-c-BmXrLr4h.js → objective-c-Bnmr858J.js} +0 -0
- /package/src/client/dist/spa/assets/{pascal-yxckoyvV.js → pascal-WP0_D5AO.js} +0 -0
- /package/src/client/dist/spa/assets/{pascaligo-Q5JCwXMI.js → pascaligo-Blom4Rij.js} +0 -0
- /package/src/client/dist/spa/assets/{perl-BF1Rrs5h.js → perl-B-vk8g64.js} +0 -0
- /package/src/client/dist/spa/assets/{pgsql-CnYB97wm.js → pgsql-Cgvz6v67.js} +0 -0
- /package/src/client/dist/spa/assets/{php-CdDfQfSg.js → php-8a3Lrw9m.js} +0 -0
- /package/src/client/dist/spa/assets/{pla-whj-d71F.js → pla-DuFqEZ8V.js} +0 -0
- /package/src/client/dist/spa/assets/{postiats-ClfLr4I-.js → postiats-DkLtSgkp.js} +0 -0
- /package/src/client/dist/spa/assets/{powerquery-iRaBhuuk.js → powerquery-BJ1aNepW.js} +0 -0
- /package/src/client/dist/spa/assets/{powershell-DjiEt5xK.js → powershell-rE98k687.js} +0 -0
- /package/src/client/dist/spa/assets/{protobuf-B6dcIEUr.js → protobuf-CUheFacr.js} +0 -0
- /package/src/client/dist/spa/assets/{pug-DtmHnjM9.js → pug-LDcAMD8w.js} +0 -0
- /package/src/client/dist/spa/assets/{qsharp-CELCyd79.js → qsharp-DUKSQoR1.js} +0 -0
- /package/src/client/dist/spa/assets/{r-ZpJXWV-o.js → r-D-QApv87.js} +0 -0
- /package/src/client/dist/spa/assets/{rate-limit-labels-dCPVjS61.js → rate-limit-labels-BvYERsho.js} +0 -0
- /package/src/client/dist/spa/assets/{redis-BiHSNkAl.js → redis-SXdDyWR9.js} +0 -0
- /package/src/client/dist/spa/assets/{redshift-DzuwYCHP.js → redshift-Y6lsCryn.js} +0 -0
- /package/src/client/dist/spa/assets/{restructuredtext-YOT94bbS.js → restructuredtext-edObr9a8.js} +0 -0
- /package/src/client/dist/spa/assets/{ruby-BfiHr6Uu.js → ruby-CNnUfF-8.js} +0 -0
- /package/src/client/dist/spa/assets/{rust-JZ-uOoYM.js → rust-IHUZWzBr.js} +0 -0
- /package/src/client/dist/spa/assets/{sb-CBglP1-t.js → sb-DrUvY44N.js} +0 -0
- /package/src/client/dist/spa/assets/{scala-C9l41paw.js → scala-B4hbXGLM.js} +0 -0
- /package/src/client/dist/spa/assets/{scheme-B-InQ6hy.js → scheme-BGrd12j3.js} +0 -0
- /package/src/client/dist/spa/assets/{scss-v6OmJRN9.js → scss-x5G1ES4U.js} +0 -0
- /package/src/client/dist/spa/assets/{shell-Dyp6iwB6.js → shell-DOehe2Y8.js} +0 -0
- /package/src/client/dist/spa/assets/{solidity-D5epNWue.js → solidity-BeRvcwWV.js} +0 -0
- /package/src/client/dist/spa/assets/{sophia-Eva-79sB.js → sophia-DZbkUNjy.js} +0 -0
- /package/src/client/dist/spa/assets/{sparql-gvALLO1w.js → sparql-B7_oi5-h.js} +0 -0
- /package/src/client/dist/spa/assets/{sql-COdamZYI.js → sql-CTlsFWVE.js} +0 -0
- /package/src/client/dist/spa/assets/{st-eMoImIwE.js → st-DJVEJdPE.js} +0 -0
- /package/src/client/dist/spa/assets/{swift-7R_T9RYH.js → swift-CwhT3fYa.js} +0 -0
- /package/src/client/dist/spa/assets/{symbols-CAg-nBkV.js → symbols-DCYodwb2.js} +0 -0
- /package/src/client/dist/spa/assets/{systemverilog-1pCEfaHU.js → systemverilog-BQN63pkN.js} +0 -0
- /package/src/client/dist/spa/assets/{tcl-B_KgnhfE.js → tcl-DqwfpskA.js} +0 -0
- /package/src/client/dist/spa/assets/{twig-CFZUJxb9.js → twig-BiyenUgc.js} +0 -0
- /package/src/client/dist/spa/assets/{typespec-B1ZgHlud.js → typespec-CWOJribt.js} +0 -0
- /package/src/client/dist/spa/assets/{vb-DKdun5tL.js → vb-Cq5F87m3.js} +0 -0
- /package/src/client/dist/spa/assets/{vue-i18n-eUDnMrPl.js → vue-i18n-CeG0hR0Z.js} +0 -0
- /package/src/client/dist/spa/assets/{wgsl-CzNaxTrn.js → wgsl-BAvW2lVr.js} +0 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid';
|
|
2
|
+
import { createParserState, parseClaudeLine } from './agent/engines/claude-code/stream-parser.js';
|
|
3
|
+
import { createPreMigrationBackup } from './db-backup-service.js';
|
|
4
|
+
import { broadcastAll } from './websocket-service.js';
|
|
5
|
+
const internal = { state: 'idle', total: 0, processed: 0 };
|
|
6
|
+
let isRunning = false;
|
|
7
|
+
function snapshot() {
|
|
8
|
+
switch (internal.state) {
|
|
9
|
+
case 'idle':
|
|
10
|
+
return { state: 'idle' };
|
|
11
|
+
case 'backing-up':
|
|
12
|
+
return { state: 'backing-up', startedAt: internal.startedAt ?? new Date(0).toISOString() };
|
|
13
|
+
case 'running':
|
|
14
|
+
return {
|
|
15
|
+
state: 'running',
|
|
16
|
+
total: internal.total,
|
|
17
|
+
processed: internal.processed,
|
|
18
|
+
startedAt: internal.startedAt ?? new Date(0).toISOString(),
|
|
19
|
+
...(internal.backupPath !== undefined ? { backupPath: internal.backupPath } : {}),
|
|
20
|
+
};
|
|
21
|
+
case 'done':
|
|
22
|
+
return {
|
|
23
|
+
state: 'done',
|
|
24
|
+
total: internal.total,
|
|
25
|
+
processed: internal.processed,
|
|
26
|
+
startedAt: internal.startedAt ?? new Date(0).toISOString(),
|
|
27
|
+
finishedAt: internal.finishedAt ?? new Date(0).toISOString(),
|
|
28
|
+
...(internal.backupPath !== undefined ? { backupPath: internal.backupPath } : {}),
|
|
29
|
+
};
|
|
30
|
+
case 'error':
|
|
31
|
+
return {
|
|
32
|
+
state: 'error',
|
|
33
|
+
errorMessage: internal.errorMessage ?? 'Unknown error',
|
|
34
|
+
...(internal.startedAt !== undefined ? { startedAt: internal.startedAt } : {}),
|
|
35
|
+
...(internal.backupPath !== undefined ? { backupPath: internal.backupPath } : {}),
|
|
36
|
+
...(internal.total > 0 ? { total: internal.total } : {}),
|
|
37
|
+
...(internal.processed > 0 ? { processed: internal.processed } : {}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function getContentMigrationStatus() {
|
|
42
|
+
return snapshot();
|
|
43
|
+
}
|
|
44
|
+
export async function runContentMigrationIfNeeded(db, dbPath) {
|
|
45
|
+
if (isRunning)
|
|
46
|
+
return;
|
|
47
|
+
isRunning = true;
|
|
48
|
+
try {
|
|
49
|
+
const row = db
|
|
50
|
+
.prepare("SELECT COUNT(*) AS c FROM ws_events WHERE type IN ('agent:output', 'agent:stderr', 'agent:status')")
|
|
51
|
+
.get();
|
|
52
|
+
if (row.c === 0) {
|
|
53
|
+
internal.state = 'idle';
|
|
54
|
+
isRunning = false;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
internal.state = 'backing-up';
|
|
58
|
+
internal.startedAt = new Date().toISOString();
|
|
59
|
+
broadcastStatus();
|
|
60
|
+
const backup = await createPreMigrationBackup(db, dbPath, 'v10');
|
|
61
|
+
internal.backupPath = backup.created ?? undefined;
|
|
62
|
+
internal.state = 'running';
|
|
63
|
+
internal.total = row.c;
|
|
64
|
+
internal.processed = 0;
|
|
65
|
+
broadcastStatus();
|
|
66
|
+
await processLoop(db);
|
|
67
|
+
internal.state = 'done';
|
|
68
|
+
internal.finishedAt = new Date().toISOString();
|
|
69
|
+
broadcastStatus();
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
internal.state = 'error';
|
|
73
|
+
internal.errorMessage = err instanceof Error ? err.message : String(err);
|
|
74
|
+
broadcastStatus();
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
isRunning = false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function broadcastStatus() {
|
|
82
|
+
// Content-migration events are global (no workspace context) — use broadcastAll so every
|
|
83
|
+
// connected WS client receives them regardless of their workspace subscriptions.
|
|
84
|
+
broadcastAll(internal.state === 'error' ? 'migration:error' : 'migration:progress', getContentMigrationStatus());
|
|
85
|
+
}
|
|
86
|
+
async function processLoop(db) {
|
|
87
|
+
const batchSize = 500;
|
|
88
|
+
const selectStmt = db.prepare("SELECT id, workspace_id, type, payload, session_id, created_at FROM ws_events WHERE type IN ('agent:output', 'agent:stderr', 'agent:status') ORDER BY created_at ASC LIMIT ?");
|
|
89
|
+
const insertStmt = db.prepare('INSERT INTO ws_events (id, workspace_id, type, payload, session_id, created_at) VALUES (?, ?, ?, ?, ?, ?)');
|
|
90
|
+
const deleteStmt = db.prepare('DELETE FROM ws_events WHERE id = ?');
|
|
91
|
+
while (true) {
|
|
92
|
+
const rows = selectStmt.all(batchSize);
|
|
93
|
+
if (rows.length === 0)
|
|
94
|
+
break;
|
|
95
|
+
db.transaction(() => {
|
|
96
|
+
for (const r of rows) {
|
|
97
|
+
const events = convertRow(r.type, r.payload, { workspaceId: r.workspace_id });
|
|
98
|
+
for (const ev of events) {
|
|
99
|
+
insertStmt.run(nanoid(), r.workspace_id, 'agent:event', JSON.stringify(ev), r.session_id, r.created_at);
|
|
100
|
+
}
|
|
101
|
+
deleteStmt.run(r.id);
|
|
102
|
+
}
|
|
103
|
+
})();
|
|
104
|
+
internal.processed += rows.length;
|
|
105
|
+
broadcastStatus();
|
|
106
|
+
// Yield to the event loop
|
|
107
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export function convertRow(type, payload, context) {
|
|
111
|
+
if (type === 'agent:status')
|
|
112
|
+
return []; // redundant — re-derivable from session events
|
|
113
|
+
if (type === 'agent:stderr') {
|
|
114
|
+
// Drop: the new engine only logs non-quota stderr via console.warn and
|
|
115
|
+
// does not persist it. Converting legacy stderr rows to error events
|
|
116
|
+
// would surface every historical Claude CLI warning ("no stdin data in
|
|
117
|
+
// 3s…", debug lines) as a UI-blocking banner. Quota-bearing stderr is
|
|
118
|
+
// handled live by the engine's backoff path, not via replay.
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
if (type === 'agent:output') {
|
|
122
|
+
try {
|
|
123
|
+
const parsed = JSON.parse(payload);
|
|
124
|
+
// The legacy payload may be either the raw Claude NDJSON already-parsed object,
|
|
125
|
+
// or a wrapper { type: 'raw', content: '...' } for non-JSON output. Handle both.
|
|
126
|
+
if (parsed && typeof parsed === 'object' && parsed.type === 'raw') {
|
|
127
|
+
return [{ kind: 'message:raw', content: String(parsed.content ?? '') }];
|
|
128
|
+
}
|
|
129
|
+
const state = createParserState();
|
|
130
|
+
const { events } = parseClaudeLine(JSON.stringify(parsed), state);
|
|
131
|
+
return events;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Log enough to debug (first 200 chars of the bad payload + the owning
|
|
135
|
+
// workspace id when the caller passed one). We intentionally do not log
|
|
136
|
+
// the full payload to keep the console readable on noisy migrations.
|
|
137
|
+
const preview = payload.length > 200 ? `${payload.slice(0, 200)}…` : payload;
|
|
138
|
+
const ctx = context?.workspaceId ? ` (workspace=${context.workspaceId})` : '';
|
|
139
|
+
console.warn(`[content-migration] Could not parse agent:output payload${ctx}, falling back to message:raw. Preview: ${preview}`);
|
|
140
|
+
return [{ kind: 'message:raw', content: payload }];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
/** Test-only. */
|
|
146
|
+
export function _resetStatusForTest() {
|
|
147
|
+
internal.state = 'idle';
|
|
148
|
+
internal.total = 0;
|
|
149
|
+
internal.processed = 0;
|
|
150
|
+
internal.errorMessage = undefined;
|
|
151
|
+
internal.startedAt = undefined;
|
|
152
|
+
internal.finishedAt = undefined;
|
|
153
|
+
internal.backupPath = undefined;
|
|
154
|
+
isRunning = false;
|
|
155
|
+
}
|
|
@@ -64,3 +64,18 @@ export async function createDailyDbBackupIfNeeded(db, dbPath, keepCount = DEFAUL
|
|
|
64
64
|
}
|
|
65
65
|
return result;
|
|
66
66
|
}
|
|
67
|
+
const PREMIGRATION_PREFIX = 'kobo.db.premigration-';
|
|
68
|
+
export async function createPreMigrationBackup(db, dbPath, tag) {
|
|
69
|
+
const dir = path.dirname(dbPath);
|
|
70
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
71
|
+
backupSequence += 1;
|
|
72
|
+
const backupPath = path.join(dir, `${PREMIGRATION_PREFIX}${tag}-${stamp}-${backupSequence}`);
|
|
73
|
+
try {
|
|
74
|
+
await db.backup(backupPath);
|
|
75
|
+
return { created: backupPath, deleted: [] };
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
console.error('[kobo] Pre-migration backup failed:', err);
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -3,9 +3,6 @@ import { getDb } from '../db/index.js';
|
|
|
3
3
|
// ── State ──────────────────────────────────────────────────────────────────────
|
|
4
4
|
/** Maps each WS client to the set of workspaceIds they are subscribed to */
|
|
5
5
|
const clients = new Map();
|
|
6
|
-
/** Per-workspace emit counter for periodic cleanup. */
|
|
7
|
-
const emitCounters = new Map();
|
|
8
|
-
const EMIT_CLEANUP_THRESHOLD = 2000;
|
|
9
6
|
let messageHandler = null;
|
|
10
7
|
/** Register the handler that processes routed WS messages (e.g. chat:message, workspace:start). */
|
|
11
8
|
export function setMessageHandler(handler) {
|
|
@@ -56,7 +53,7 @@ export function handleConnection(ws) {
|
|
|
56
53
|
handleSyncRequest(ws, lastEventId, workspaceIds);
|
|
57
54
|
break;
|
|
58
55
|
}
|
|
59
|
-
// Routed messages — delegated to
|
|
56
|
+
// Routed messages — delegated to the orchestrator via messageHandler
|
|
60
57
|
case 'chat:message':
|
|
61
58
|
case 'workspace:start':
|
|
62
59
|
case 'workspace:stop':
|
|
@@ -102,15 +99,6 @@ export function emit(workspaceId, type, payload, sessionId) {
|
|
|
102
99
|
try {
|
|
103
100
|
const db = getDb();
|
|
104
101
|
db.prepare('INSERT INTO ws_events (id, workspace_id, type, payload, session_id, created_at) VALUES (?, ?, ?, ?, ?, ?)').run(id, workspaceId, type, JSON.stringify(payload), sessionId ?? null, createdAt);
|
|
105
|
-
// Periodic cleanup — trigger when emit threshold is reached
|
|
106
|
-
const count = (emitCounters.get(workspaceId) ?? 0) + 1;
|
|
107
|
-
if (count >= EMIT_CLEANUP_THRESHOLD) {
|
|
108
|
-
cleanupOldEvents(workspaceId);
|
|
109
|
-
emitCounters.set(workspaceId, 0);
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
emitCounters.set(workspaceId, count);
|
|
113
|
-
}
|
|
114
102
|
}
|
|
115
103
|
catch (err) {
|
|
116
104
|
console.error(`[websocket-service] Failed to persist event (workspace=${workspaceId}, type=${type}):`, err);
|
|
@@ -118,10 +106,20 @@ export function emit(workspaceId, type, payload, sessionId) {
|
|
|
118
106
|
// Build the event object to send
|
|
119
107
|
const event = { id, workspaceId, type, payload, sessionId, createdAt };
|
|
120
108
|
const message = JSON.stringify(event);
|
|
121
|
-
// Broadcast to subscribed clients
|
|
109
|
+
// Broadcast to subscribed clients. Wrap `.send` in try/catch so a dropped
|
|
110
|
+
// client doesn't throw and abort delivery to the remaining subscribers.
|
|
111
|
+
let emitSendErrorLogged = false;
|
|
122
112
|
for (const [ws, subs] of clients) {
|
|
123
113
|
if (subs.has(workspaceId) && ws.readyState === 1 /* WebSocket.OPEN */) {
|
|
124
|
-
|
|
114
|
+
try {
|
|
115
|
+
ws.send(message);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
if (!emitSendErrorLogged) {
|
|
119
|
+
console.warn(`[ws] emit send failed (workspace=${workspaceId}, type=${type}):`, err);
|
|
120
|
+
emitSendErrorLogged = true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
125
123
|
}
|
|
126
124
|
}
|
|
127
125
|
return id;
|
|
@@ -135,9 +133,18 @@ export function emitEphemeral(workspaceId, type, payload) {
|
|
|
135
133
|
const createdAt = new Date().toISOString();
|
|
136
134
|
const event = { id, workspaceId, type, payload, createdAt };
|
|
137
135
|
const message = JSON.stringify(event);
|
|
136
|
+
let sendErrorLogged = false;
|
|
138
137
|
for (const [ws, subs] of clients) {
|
|
139
138
|
if (subs.has(workspaceId) && ws.readyState === 1 /* WebSocket.OPEN */) {
|
|
140
|
-
|
|
139
|
+
try {
|
|
140
|
+
ws.send(message);
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
if (!sendErrorLogged) {
|
|
144
|
+
console.warn(`[ws] emitEphemeral send failed (workspace=${workspaceId}, type=${type}):`, err);
|
|
145
|
+
sendErrorLogged = true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
141
148
|
}
|
|
142
149
|
}
|
|
143
150
|
}
|
|
@@ -161,8 +168,16 @@ export function handleSyncRequest(ws, lastEventId, workspaceIds) {
|
|
|
161
168
|
// Build a query with placeholders for all subscribed workspaces
|
|
162
169
|
const placeholders = resolvedIds.map(() => '?').join(', ');
|
|
163
170
|
let rows;
|
|
171
|
+
// Initial window size: on a fresh connection (no lastEventId), we only
|
|
172
|
+
// replay the most recent slice of history. The client fetches older
|
|
173
|
+
// events on-demand via GET /api/workspaces/:id/events as the user scrolls
|
|
174
|
+
// up. This keeps first-paint fast on long-lived workspaces with tens of
|
|
175
|
+
// thousands of events without ever deleting anything from the DB.
|
|
176
|
+
const INITIAL_WINDOW = 300;
|
|
164
177
|
if (lastEventId) {
|
|
165
|
-
//
|
|
178
|
+
// Resume path: replay every event strictly after the cursor (delta
|
|
179
|
+
// since last seen). If the cursor is stale/unknown, fall back to the
|
|
180
|
+
// recent window rather than streaming the entire history.
|
|
166
181
|
const lastRow = db.prepare('SELECT rowid FROM ws_events WHERE id = ?').get(lastEventId);
|
|
167
182
|
if (lastRow) {
|
|
168
183
|
rows = db
|
|
@@ -170,17 +185,18 @@ export function handleSyncRequest(ws, lastEventId, workspaceIds) {
|
|
|
170
185
|
.all(...resolvedIds, lastRow.rowid);
|
|
171
186
|
}
|
|
172
187
|
else {
|
|
173
|
-
// lastEventId not found — send events capped to avoid unbounded memory usage
|
|
174
188
|
rows = db
|
|
175
|
-
.prepare(`SELECT * FROM ws_events WHERE workspace_id IN (${placeholders}) ORDER BY rowid
|
|
176
|
-
.all(...resolvedIds);
|
|
189
|
+
.prepare(`SELECT * FROM ws_events WHERE workspace_id IN (${placeholders}) ORDER BY rowid DESC LIMIT ?`)
|
|
190
|
+
.all(...resolvedIds, INITIAL_WINDOW);
|
|
191
|
+
rows.reverse();
|
|
177
192
|
}
|
|
178
193
|
}
|
|
179
194
|
else {
|
|
180
|
-
//
|
|
195
|
+
// Fresh connect: most recent INITIAL_WINDOW events, in chronological order.
|
|
181
196
|
rows = db
|
|
182
|
-
.prepare(`SELECT * FROM ws_events WHERE workspace_id IN (${placeholders}) ORDER BY rowid
|
|
183
|
-
.all(...resolvedIds);
|
|
197
|
+
.prepare(`SELECT * FROM ws_events WHERE workspace_id IN (${placeholders}) ORDER BY rowid DESC LIMIT ?`)
|
|
198
|
+
.all(...resolvedIds, INITIAL_WINDOW);
|
|
199
|
+
rows.reverse();
|
|
184
200
|
}
|
|
185
201
|
const events = rows.map((row) => {
|
|
186
202
|
let parsedPayload;
|
|
@@ -188,6 +204,11 @@ export function handleSyncRequest(ws, lastEventId, workspaceIds) {
|
|
|
188
204
|
parsedPayload = JSON.parse(row.payload);
|
|
189
205
|
}
|
|
190
206
|
catch {
|
|
207
|
+
console.error('[ws] corrupt ws_events row, falling back to raw:', {
|
|
208
|
+
id: row.id,
|
|
209
|
+
workspace_id: row.workspace_id,
|
|
210
|
+
type: row.type,
|
|
211
|
+
});
|
|
191
212
|
parsedPayload = { raw: row.payload };
|
|
192
213
|
}
|
|
193
214
|
return {
|
|
@@ -200,30 +221,6 @@ export function handleSyncRequest(ws, lastEventId, workspaceIds) {
|
|
|
200
221
|
};
|
|
201
222
|
});
|
|
202
223
|
ws.send(JSON.stringify({ type: 'sync:response', payload: { events } }));
|
|
203
|
-
// Trigger cleanup when the event count is high to prevent unbounded growth
|
|
204
|
-
const CLEANUP_THRESHOLD = 5000;
|
|
205
|
-
if (rows.length >= CLEANUP_THRESHOLD) {
|
|
206
|
-
for (const wid of resolvedIds) {
|
|
207
|
-
cleanupOldEvents(wid);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
// ── Cleanup ────────────────────────────────────────────────────────────────────
|
|
212
|
-
/**
|
|
213
|
-
* Delete old events keeping only the last N (default 1000) per workspace.
|
|
214
|
-
*/
|
|
215
|
-
export function cleanupOldEvents(workspaceId, keepCount = 1000) {
|
|
216
|
-
const db = getDb();
|
|
217
|
-
db.prepare(`
|
|
218
|
-
DELETE FROM ws_events
|
|
219
|
-
WHERE workspace_id = ?
|
|
220
|
-
AND rowid NOT IN (
|
|
221
|
-
SELECT rowid FROM ws_events
|
|
222
|
-
WHERE workspace_id = ?
|
|
223
|
-
ORDER BY rowid DESC
|
|
224
|
-
LIMIT ?
|
|
225
|
-
)
|
|
226
|
-
`).run(workspaceId, workspaceId, keepCount);
|
|
227
224
|
}
|
|
228
225
|
// ── Utilities ──────────────────────────────────────────────────────────────────
|
|
229
226
|
/**
|
|
@@ -239,10 +236,44 @@ export function getClientCount() {
|
|
|
239
236
|
export function _getClients() {
|
|
240
237
|
return clients;
|
|
241
238
|
}
|
|
239
|
+
// ── Global broadcast ───────────────────────────────────────────────────────────
|
|
240
|
+
/**
|
|
241
|
+
* Broadcast an ephemeral event to every connected WebSocket client,
|
|
242
|
+
* regardless of which workspaces they have subscribed to. Used for
|
|
243
|
+
* global events like migration progress.
|
|
244
|
+
*/
|
|
245
|
+
export function broadcastAll(type, payload) {
|
|
246
|
+
const message = JSON.stringify({ type, payload });
|
|
247
|
+
let sendErrorLogged = false;
|
|
248
|
+
for (const client of clients.keys()) {
|
|
249
|
+
if (client.readyState === 1 /* WebSocket.OPEN */) {
|
|
250
|
+
try {
|
|
251
|
+
client.send(message);
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
// client dropped; next iteration will fail its .readyState check.
|
|
255
|
+
// Log the first occurrence so real regressions surface without
|
|
256
|
+
// flooding the console if many clients die at once.
|
|
257
|
+
if (!sendErrorLogged) {
|
|
258
|
+
console.warn('[ws] broadcastAll send failed:', err);
|
|
259
|
+
sendErrorLogged = true;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
242
265
|
/**
|
|
243
|
-
*
|
|
266
|
+
* Return a mutable set view of connected clients — exposed for testing only.
|
|
267
|
+
* Adding/removing clients via this handle registers/unregisters them with the
|
|
268
|
+
* internal `clients` map so helpers like `broadcastAll` see them.
|
|
244
269
|
* @internal
|
|
245
270
|
*/
|
|
246
|
-
export function
|
|
247
|
-
return
|
|
271
|
+
export function _connectionsForTest() {
|
|
272
|
+
return {
|
|
273
|
+
add: (ws) => {
|
|
274
|
+
if (!clients.has(ws))
|
|
275
|
+
clients.set(ws, new Set());
|
|
276
|
+
},
|
|
277
|
+
delete: (ws) => clients.delete(ws),
|
|
278
|
+
};
|
|
248
279
|
}
|
|
@@ -11,6 +11,11 @@ const VALID_TRANSITIONS = {
|
|
|
11
11
|
error: ['idle', 'executing', 'brainstorming', 'extracting'],
|
|
12
12
|
quota: ['idle', 'executing'],
|
|
13
13
|
};
|
|
14
|
+
// NOTE: `engine` is stored as a plain TEXT column and returned as a `string` on
|
|
15
|
+
// the `Workspace` interface rather than the stricter `EngineId` union. The DB
|
|
16
|
+
// is untyped, so we intentionally do not narrow here — validation against
|
|
17
|
+
// `listEngines()` is expected to happen at workspace creation (see the
|
|
18
|
+
// routes/engines handler) and when resolving an engine at agent-start time.
|
|
14
19
|
function mapWorkspace(row) {
|
|
15
20
|
return {
|
|
16
21
|
id: row.id,
|
|
@@ -29,6 +34,7 @@ function mapWorkspace(row) {
|
|
|
29
34
|
archivedAt: row.archived_at,
|
|
30
35
|
favoritedAt: row.favorited_at,
|
|
31
36
|
tags: parseTags(row.tags),
|
|
37
|
+
engine: row.engine ?? 'claude-code',
|
|
32
38
|
createdAt: row.created_at,
|
|
33
39
|
updatedAt: row.updated_at,
|
|
34
40
|
};
|
|
@@ -65,9 +71,9 @@ export function createWorkspace(data) {
|
|
|
65
71
|
const now = new Date().toISOString();
|
|
66
72
|
const id = nanoid();
|
|
67
73
|
db.prepare(`
|
|
68
|
-
INSERT INTO workspaces (id, name, project_path, source_branch, working_branch, status, notion_url, notion_page_id, model, reasoning_effort, permission_mode, created_at, updated_at)
|
|
69
|
-
VALUES (?, ?, ?, ?, ?, 'created', ?, ?, ?, ?, ?, ?, ?)
|
|
70
|
-
`).run(id, data.name, data.projectPath, data.sourceBranch, data.workingBranch, data.notionUrl ?? null, data.notionPageId ?? null, data.model ?? 'claude-opus-4-7', data.reasoningEffort ?? 'auto', data.permissionMode ?? 'auto-accept', now, now);
|
|
74
|
+
INSERT INTO workspaces (id, name, project_path, source_branch, working_branch, status, notion_url, notion_page_id, model, reasoning_effort, permission_mode, engine, created_at, updated_at)
|
|
75
|
+
VALUES (?, ?, ?, ?, ?, 'created', ?, ?, ?, ?, ?, ?, ?, ?)
|
|
76
|
+
`).run(id, data.name, data.projectPath, data.sourceBranch, data.workingBranch, data.notionUrl ?? null, data.notionPageId ?? null, data.model ?? 'claude-opus-4-7', data.reasoningEffort ?? 'auto', data.permissionMode ?? 'auto-accept', data.engine ?? 'claude-code', now, now);
|
|
71
77
|
return getWorkspace(id);
|
|
72
78
|
}
|
|
73
79
|
/** Fetch a single workspace by ID, or null if not found. */
|
|
@@ -336,7 +342,7 @@ function mapSession(row) {
|
|
|
336
342
|
id: row.id,
|
|
337
343
|
workspaceId: row.workspace_id,
|
|
338
344
|
pid: row.pid,
|
|
339
|
-
|
|
345
|
+
engineSessionId: row.engine_session_id,
|
|
340
346
|
status: row.status,
|
|
341
347
|
startedAt: row.started_at,
|
|
342
348
|
endedAt: row.ended_at,
|
|
@@ -389,7 +395,7 @@ export function createIdleSession(workspaceId) {
|
|
|
389
395
|
id,
|
|
390
396
|
workspaceId,
|
|
391
397
|
pid: null,
|
|
392
|
-
|
|
398
|
+
engineSessionId: null,
|
|
393
399
|
status: 'idle',
|
|
394
400
|
startedAt: now,
|
|
395
401
|
endedAt: null,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFile as execFileCb, execFileSync } from 'node:child_process';
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { promisify } from 'node:util';
|
|
5
5
|
const execFileAsync = promisify(execFileCb);
|
|
@@ -133,7 +133,47 @@ export function pullBranch(repoPath, branchName, remote = 'origin') {
|
|
|
133
133
|
throw new Error(`Failed to pull branch '${branchName}' from '${remote}': ${message}`);
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
|
-
/**
|
|
136
|
+
/** Thrown when a rebase or merge produces conflicts. Leaves the repo in the mid-operation state
|
|
137
|
+
* so the caller can decide between abort and agent-assisted resolution. */
|
|
138
|
+
export class GitConflictError extends Error {
|
|
139
|
+
operation;
|
|
140
|
+
files;
|
|
141
|
+
constructor(operation, files) {
|
|
142
|
+
super(`${operation} produced ${files.length} conflicted file(s)`);
|
|
143
|
+
this.name = 'GitConflictError';
|
|
144
|
+
this.operation = operation;
|
|
145
|
+
this.files = files;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/** List files currently in a conflicted state (unmerged paths). */
|
|
149
|
+
export function getConflictedFiles(repoPath) {
|
|
150
|
+
try {
|
|
151
|
+
const output = git(repoPath, ['diff', '--name-only', '--diff-filter=U']);
|
|
152
|
+
return output
|
|
153
|
+
.split('\n')
|
|
154
|
+
.map((f) => f.trim())
|
|
155
|
+
.filter(Boolean);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/** Detect whether a merge or rebase is currently in progress in the worktree. */
|
|
162
|
+
export function getOngoingGitOperation(repoPath) {
|
|
163
|
+
try {
|
|
164
|
+
const gitDir = git(repoPath, ['rev-parse', '--git-dir']);
|
|
165
|
+
const dir = gitDir.startsWith('/') ? gitDir : join(repoPath, gitDir);
|
|
166
|
+
if (existsSync(join(dir, 'MERGE_HEAD')))
|
|
167
|
+
return 'merge';
|
|
168
|
+
if (existsSync(join(dir, 'rebase-merge')) || existsSync(join(dir, 'rebase-apply')))
|
|
169
|
+
return 'rebase';
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/** Rebase the current branch onto the given base branch. Fetches origin first. Leaves conflicts in place. */
|
|
137
177
|
export function rebaseBranch(repoPath, baseBranch) {
|
|
138
178
|
try {
|
|
139
179
|
git(repoPath, ['fetch', 'origin', baseBranch]);
|
|
@@ -145,17 +185,46 @@ export function rebaseBranch(repoPath, baseBranch) {
|
|
|
145
185
|
git(repoPath, ['rebase', `origin/${baseBranch}`]);
|
|
146
186
|
}
|
|
147
187
|
catch (err) {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
catch {
|
|
154
|
-
/* best-effort */
|
|
188
|
+
const conflicted = getConflictedFiles(repoPath);
|
|
189
|
+
if (conflicted.length > 0 || getOngoingGitOperation(repoPath) === 'rebase') {
|
|
190
|
+
// Leave the rebase in progress so the caller can abort or request agent-assisted resolution.
|
|
191
|
+
throw new GitConflictError('rebase', conflicted);
|
|
155
192
|
}
|
|
193
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
156
194
|
throw new Error(`Rebase onto '${baseBranch}' failed: ${message}`);
|
|
157
195
|
}
|
|
158
196
|
}
|
|
197
|
+
/** Merge `origin/<baseBranch>` into the current branch. Fetches first. Leaves conflicts in place. */
|
|
198
|
+
export function mergeBranch(repoPath, baseBranch) {
|
|
199
|
+
try {
|
|
200
|
+
git(repoPath, ['fetch', 'origin', baseBranch]);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// offline — continue with local ref
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
git(repoPath, ['merge', '--no-ff', '--no-edit', `origin/${baseBranch}`]);
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
const conflicted = getConflictedFiles(repoPath);
|
|
210
|
+
if (conflicted.length > 0 || getOngoingGitOperation(repoPath) === 'merge') {
|
|
211
|
+
throw new GitConflictError('merge', conflicted);
|
|
212
|
+
}
|
|
213
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
214
|
+
throw new Error(`Merge of 'origin/${baseBranch}' failed: ${message}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/** Abort an in-progress merge or rebase. No-op if nothing is in progress. */
|
|
218
|
+
export function abortOngoingGitOperation(repoPath) {
|
|
219
|
+
const op = getOngoingGitOperation(repoPath);
|
|
220
|
+
if (op === 'merge') {
|
|
221
|
+
git(repoPath, ['merge', '--abort']);
|
|
222
|
+
}
|
|
223
|
+
else if (op === 'rebase') {
|
|
224
|
+
git(repoPath, ['rebase', '--abort']);
|
|
225
|
+
}
|
|
226
|
+
return op;
|
|
227
|
+
}
|
|
159
228
|
/** Try a git command with `base`, falling back to `origin/base` if the local ref is missing. */
|
|
160
229
|
function resolveBase(repoPath, base) {
|
|
161
230
|
try {
|
|
@@ -99,7 +99,7 @@ export function getTemplatesPath() {
|
|
|
99
99
|
/**
|
|
100
100
|
* Absolute path to the compiled MCP server entry (shipped in the published
|
|
101
101
|
* package as dist/mcp-server/kobo-tasks-server.js). Returns null if not
|
|
102
|
-
* present — callers (
|
|
102
|
+
* present — callers (orchestrator) then fall back to the TS source for dev.
|
|
103
103
|
*/
|
|
104
104
|
export function getCompiledMcpServerPath() {
|
|
105
105
|
const compiled = getPackageAssetPath('dist', 'mcp-server', 'kobo-tasks-server.js');
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export const CLAUDE_MODELS = [
|
|
2
|
+
{
|
|
3
|
+
id: 'auto',
|
|
4
|
+
label: 'Auto',
|
|
5
|
+
i18nLabelKey: 'model.auto',
|
|
6
|
+
i18nDescriptionKey: 'model.autoDescription',
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
id: 'claude-opus-4-7',
|
|
10
|
+
label: 'Opus 4.7 (Classic)',
|
|
11
|
+
i18nLabelKey: 'model.opus47Classic',
|
|
12
|
+
i18nDescriptionKey: 'model.opus47ClassicDescription',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 'claude-opus-4-7[1m]',
|
|
16
|
+
label: 'Opus 4.7 (1M)',
|
|
17
|
+
i18nLabelKey: 'model.opus471m',
|
|
18
|
+
i18nDescriptionKey: 'model.opus471mDescription',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'claude-opus-4-6',
|
|
22
|
+
label: 'Opus 4.6 (Classic)',
|
|
23
|
+
i18nLabelKey: 'model.opusClassic',
|
|
24
|
+
i18nDescriptionKey: 'model.opusClassicDescription',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'claude-opus-4-6[1m]',
|
|
28
|
+
label: 'Opus 4.6 (1M)',
|
|
29
|
+
i18nLabelKey: 'model.opus1m',
|
|
30
|
+
i18nDescriptionKey: 'model.opus1mDescription',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'claude-sonnet-4-6',
|
|
34
|
+
label: 'Sonnet 4.6 (Classic)',
|
|
35
|
+
i18nLabelKey: 'model.sonnetClassic',
|
|
36
|
+
i18nDescriptionKey: 'model.sonnetClassicDescription',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'claude-sonnet-4-6[1m]',
|
|
40
|
+
label: 'Sonnet 4.6 (1M)',
|
|
41
|
+
i18nLabelKey: 'model.sonnet1m',
|
|
42
|
+
i18nDescriptionKey: 'model.sonnet1mDescription',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'claude-haiku-4-5-20251001',
|
|
46
|
+
label: 'Haiku 4.5',
|
|
47
|
+
i18nLabelKey: 'model.haiku',
|
|
48
|
+
i18nDescriptionKey: 'model.haikuDescription',
|
|
49
|
+
},
|
|
50
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loicngr/kobo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.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",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import{E as e,F as t,H as n,L as r,M as i,Q as a,U as o,_t as s,bt as c,d as l,f as u,g as d,h as f,l as p,p as m,r as h,rt as g,u as _,v,yt as y}from"./runtime-core.esm-bundler-C3IgBgY5.js";import{L as b,l as x,t as S}from"./QIcon-B0-pH3Qs.js";import{t as C}from"./QBtn-p1aZtrJH.js";import{n as w}from"./vue-i18n-CeG0hR0Z.js";import{S as T,v as E,x as D}from"./index-D997aY4Y.js";import{t as O}from"./QSpinnerDots-By20ptst.js";import{t as k}from"./QExpansionItem-5ekmpO-2.js";import{t as ee}from"./QScrollArea-L6wUiA20.js";import{n as A,t as j}from"./marked.esm-DW0ulF0a.js";import{t as M}from"./_plugin-vue_export-helper-CEhRWsKN.js";function N(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:brainstorm-complete`: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 te(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];return r.sort((e,t)=>{let n=e.ts??``,r=t.ts??``;return n===r?0:n?r?n<r?-1:1:-1:1}),r}function P(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:`user`;case`session`:return`session`;default:return`agent`}}function ne(e){let t=[],n=null;for(let r of e){let e=P(r),i=e===`session`||e===`system-prompt`;!n||n.speaker!==e||i?(n={speaker:e,ts:r.ts,items:[r]},t.push(n),i&&(n=null)):n.items.push(r)}return t}var F={class:`text-caption text-grey-6`},I=v({__name:`SessionEventItem`,props:{item:{}},setup(e){let n=e,r=p(()=>{switch(n.item.kind){case`started`:return`session.started`;case`ended`:return`session.ended`;case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,n)=>(t(),m(`span`,F,c(e.$t(r.value)),1))}}),L={class:`markdown-message`},R=[`innerHTML`],z=M(v({__name:`TextMessageItem`,props:{item:{}},setup(e){let n=e,r=p(()=>{let e=j.parse(n.item.text,{async:!1,breaks:!0,gfm:!0});return A.sanitize(e)});return(n,i)=>(t(),m(`div`,L,[_(`div`,{innerHTML:r.value},null,8,R),e.item.streaming?(t(),l(x,{key:0,size:`xs`,class:`q-ml-xs`})):u(``,!0)]))}}),[[`__scopeId`,`data-v-158cbb54`]]),B={key:0,class:`text-caption text-grey-5`,style:{"font-style":`italic`}},V=[`innerHTML`],H={key:1,style:{"white-space":`pre-wrap`}},U=M(v({__name:`ThinkingItem`,props:{item:{}},setup(e){let n=e,r=p(()=>n.item.text.trim().slice(0,100)),i=p(()=>n.item.text.trim().length>0),a=p(()=>n.item.text.trim().length>100),s=p(()=>{let e=j.parse(n.item.text,{async:!1,breaks:!0,gfm:!0});return A.sanitize(e)});return(n,d)=>i.value?(t(),m(`div`,B,[a.value?(t(),l(k,{key:0,dense:``,"dense-toggle":``,label:r.value,"header-class":`text-grey-5 text-caption`,style:{"font-style":`italic`}},{default:o(()=>[_(`div`,{class:`q-py-xs markdown-thinking`,innerHTML:s.value},null,8,V)]),_:1},8,[`label`])):(t(),m(`span`,H,c(e.item.text),1))])):u(``,!0)}}),[[`__scopeId`,`data-v-e9fb9f90`]]);function W(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 G(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??``;return{toolName:`Edit`,filePath:e,oldString:t,newString:r,replaceAll:n.replace_all??!1,additions:r?r.split(`
|
|
4
|
+
`).length:0,deletions:t?t.split(`
|
|
5
|
+
`).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(`
|
|
6
|
+
`).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 K(e,t){if(!e||!t?.projectPath)return e;let n=q(e,`${t.projectPath}/.worktrees/${t.workingBranch}`);return n===e&&(n=q(e,t.projectPath)),n}function q(e,t){if(!t)return e;let n=t.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`);return e.replace(RegExp(`${n}/`,`g`),``).replace(RegExp(`${n}(?=\\s|$|["'\`])`,`g`),`.`)}var J={class:`tool-name`},Y=[`title`],X={key:0,class:`tool-stat-add`},re={key:1,class:`tool-stat-del`},ie={class:`diff-sign`},ae={key:1,class:`tool-row tool-row-generic`},Z={class:`tool-header`},oe={class:`tool-name`},se=[`title`],ce={key:0,class:`tool-output`},le=M(v({__name:`ToolCallItem`,props:{item:{}},setup(e){let n=e,i=a(!1),o=E(),g=p(()=>G(n.item.name,n.item.input)),v=p(()=>g.value?K(g.value.filePath,o.selectedWorkspace):``),y={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`,ToolSearch:`search`,WebFetch:`public`,WebSearch:`travel_explore`,NotebookRead:`book`,NotebookEdit:`edit_note`,SendMessage:`send`,ExitPlanMode:`check_circle_outline`,KillShell:`stop_circle`,BashOutput:`terminal`},x=p(()=>y[n.item.name]??`build`),C=p(()=>{if(g.value)return``;let e=n.item.input,t=w(e);return t?K(t,o.selectedWorkspace):``});function w(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 T=p(()=>{let e=g.value;return e?e.toolName===`Edit`&&e.oldString!==void 0&&e.newString!==void 0?W(e.oldString,e.newString):e.toolName===`Write`&&e.content!==void 0?e.content.split(`
|
|
7
|
+
`).map(e=>({type:`add`,content:e})):e.toolName===`Bash:rm`?[{type:`del`,content:`File deleted`}]:null:null}),D=p(()=>{let e=n.item.result;if(!e)return``;if(typeof e.output==`string`)return e.output;try{return JSON.stringify(e.output)}catch{return String(e.output)}});function O(){i.value=!i.value}return(n,a)=>g.value?(t(),m(`div`,{key:0,class:s([`tool-row`,{"tool-row-expanded":i.value}])},[_(`div`,{class:`tool-header`,onClick:O},[d(S,{name:x.value,size:`14px`,class:`tool-icon`},null,8,[`name`]),_(`span`,J,c(g.value.toolName===`Bash:rm`?`Bash`:g.value.toolName),1),_(`span`,{class:`tool-path`,title:g.value.filePath},c(v.value),9,Y),g.value.additions>0?(t(),m(`span`,X,`+`+c(g.value.additions),1)):u(``,!0),g.value.deletions>0?(t(),m(`span`,re,`-`+c(g.value.deletions),1)):u(``,!0),e.item.result?.isError?(t(),l(S,{key:2,name:`error_outline`,color:`negative`,size:`xs`,class:`q-ml-xs`})):e.item.result?(t(),l(S,{key:3,name:`check`,color:`positive`,size:`xs`,class:`q-ml-xs`})):u(``,!0),d(S,{name:i.value?`expand_less`:`expand_more`,size:`xs`,class:`q-ml-auto text-grey-6`},null,8,[`name`])]),i.value&&T.value?(t(),m(`div`,{key:0,class:`tool-diff`,onClick:a[0]||=b(()=>{},[`stop`])},[(t(!0),m(h,null,r(T.value,(e,n)=>(t(),m(`div`,{key:n,class:s([`diff-line`,{"diff-del":e.type===`del`,"diff-add":e.type===`add`,"diff-context":e.type===`context`}])},[_(`span`,ie,c(e.type===`del`?`-`:e.type===`add`?`+`:` `),1),f(c(e.content),1)],2))),128))])):u(``,!0)],2)):(t(),m(`div`,ae,[_(`div`,Z,[d(S,{name:x.value,size:`14px`,class:`tool-icon`},null,8,[`name`]),_(`span`,oe,c(e.item.name),1),C.value?(t(),m(`span`,{key:0,class:`tool-arg`,title:w(e.item.input)||C.value},c(C.value),9,se)):u(``,!0),e.item.result?.isError?(t(),l(S,{key:1,name:`error_outline`,color:`negative`,size:`xs`,class:`q-ml-auto`})):e.item.result?(t(),l(S,{key:2,name:`check`,color:`positive`,size:`xs`,class:`q-ml-auto`})):u(``,!0)]),e.item.result&&D.value?(t(),m(`div`,ce,c(D.value),1)):u(``,!0)]))}}),[[`__scopeId`,`data-v-440d2c83`]]),ue=[`innerHTML`],de={key:1,class:`markdown-message`},fe=[`innerHTML`],pe=M(v({__name:`UserMessageItem`,props:{item:{}},setup(e){let n=e,r=p(()=>n.item.sender===`system-prompt`),i=p(()=>{let e=j.parse(n.item.content,{async:!1,breaks:!0,gfm:!0});return A.sanitize(e)});return(e,n)=>r.value?(t(),l(k,{key:0,dense:``,"dense-toggle":``,label:e.$t(`chat.systemPrompt`),"header-class":`text-grey-5 text-caption`},{default:o(()=>[_(`div`,{class:`q-py-xs markdown-user-prompt`,innerHTML:i.value},null,8,ue)]),_:1},8,[`label`])):(t(),m(`div`,de,[_(`div`,{innerHTML:i.value},null,8,fe)]))}}),[[`__scopeId`,`data-v-cb8cec0f`]]),me={class:`turn-header`},he={key:0,class:`turn-time`},ge={key:1,class:`turn-actions`},_e={class:`turn-body`},ve=M(v({__name:`TurnCard`,props:{turn:{}},setup(e){let n=e,{t:i}=w(),a=p(()=>{switch(n.turn.speaker){case`user`:return{label:i(`chat.you`),accent:`#ce93d8`,badgeClass:`turn-badge-user`};case`agent`:return{label:i(`chat.agent`),accent:`#7986cb`,badgeClass:`turn-badge-agent`};case`system-prompt`:return{label:i(`chat.systemPrompt`),accent:`#757575`,badgeClass:`turn-badge-system`};case`session`:return{label:i(`chat.session`),accent:`#616161`,badgeClass:`turn-badge-session`}}}),o=p(()=>{if(!n.turn.ts)return``;let e=new Date(n.turn.ts);return Number.isNaN(e.getTime())?``:e.toLocaleTimeString(void 0,{hour:`2-digit`,minute:`2-digit`})}),d=p(()=>n.turn.items.filter(e=>e.type===`tool`).length);return(n,f)=>(t(),m(`div`,{class:s([`turn-card`,{"turn-card--user":e.turn.speaker===`user`}]),style:y({"--turn-accent":a.value.accent})},[_(`div`,me,[_(`span`,{class:s([`turn-badge`,a.value.badgeClass])},c(a.value.label),3),o.value?(t(),m(`span`,he,c(o.value),1)):u(``,!0),d.value>0?(t(),m(`span`,ge,` · `+c(g(i)(`chat.nActions`,{n:d.value})),1)):u(``,!0)]),_(`div`,_e,[(t(!0),m(h,null,r(e.turn.items,(e,n)=>(t(),m(h,{key:n},[e.type===`text`?(t(),l(z,{key:0,item:e},null,8,[`item`])):e.type===`thinking`?(t(),l(U,{key:1,item:e},null,8,[`item`])):e.type===`tool`?(t(),l(le,{key:2,item:e},null,8,[`item`])):e.type===`user`?(t(),l(pe,{key:3,item:e},null,8,[`item`])):e.type===`session`?(t(),l(I,{key:4,item:e},null,8,[`item`])):u(``,!0)],64))),128))])],6))}}),[[`__scopeId`,`data-v-8a9ce6dd`]]),ye={key:0,class:`activity-feed-switching`},be={key:1,class:`activity-feed-wrap`},xe={key:0,class:`text-center q-py-sm text-caption text-grey-6`},Se={class:`q-pa-md`},Ce={key:1,class:`q-px-md q-pb-md`},we=60,Q=200,$=200,Te=400,Ee=200,De=M(v({__name:`ActivityFeed`,props:{workspaceId:{}},setup(s){let g=s,v=T(),y=D(),b=E(),S=p(()=>(b.activityFeeds[g.workspaceId]??[]).filter(e=>e.type===`text`&&typeof e.content==`string`).map(e=>({content:e.content,sender:e.meta?.sender??`user`,ts:e.timestamp,sessionId:e.sessionId}))),w=p(()=>{let e=b.workspaces.find(e=>e.id===g.workspaceId);return e?e.status===`extracting`||e.status===`brainstorming`||e.status===`executing`:!1}),A=p(()=>{let e=te(N(v.eventsFor(g.workspaceId),v.timestampsFor(g.workspaceId),w.value),S.value);return ne(y.showVerboseSystemMessages?e:e.filter(e=>e.type!==`session`))}),j=p(()=>y.showVerboseSystemMessages?v.eventsFor(g.workspaceId).filter(e=>e.kind===`message:raw`).map(e=>e.content):[]),M=a(null),P=!0,F=a(!1),I=!1,L=a(!0);function R(e){P=e.verticalSize-e.verticalPosition-e.verticalContainerSize<=we,I&&e.verticalPosition<=Q&&!F.value&&v.hasMoreOlderFor(g.workspaceId)&&z()}async function z(){let t=g.workspaceId,n=v.oldestIdFor(t);if(!n)return;F.value=!0;let r=Date.now();try{let r=M.value,i=r?.getScroll().verticalSize??0,a=r?.getScroll().verticalPosition??0,o=fetch(`/api/workspaces/${t}/events?before=${encodeURIComponent(n)}&limit=200`),s=new Promise(e=>setTimeout(e,$)),[c]=await Promise.all([o,s]);if(!c.ok){v.prepend(t,[],[],{oldestId:n,hasMoreOlder:!1});return}let l=await c.json(),u=l.events??[],d=u.filter(e=>e.type===`agent:event`&&e.workspaceId===t),f=u.filter(e=>e.type===`user:message`&&e.workspaceId===t),p=d.map(e=>e.payload),m=d.map(e=>e.createdAt),h=d.map(e=>e.sessionId??null),g=u.length>0?u[0].id:n;v.prepend(t,p,m,{oldestId:g,hasMoreOlder:l.hasMore,sessionIds:h});for(let e of f){let n=e.payload;typeof n.content==`string`&&b.addActivityItem(t,{id:e.id,type:`text`,content:n.content,timestamp:e.createdAt,sessionId:e.sessionId??void 0,meta:{sender:n.sender??`user`}})}if(await e(),r){let e=r.getScroll().verticalSize-i,t=Math.max(a+e,Q+50);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,$-e);await new Promise(e=>setTimeout(e,t+Te)),F.value=!1}}async function B(t=0){await e();let n=M.value;if(!n)return;let r=n.getScroll();n.setScrollPosition(`vertical`,r.verticalSize,t)}let V=a([]),H=a(null);function U(){let e=A.value,t=V.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=H.value?.parentElement;if(r){let e=r.querySelectorAll(`.turn-card--user`);for(let t of e)n.push(t)}return n}function W(){let e=M.value;if(!e)return null;let t=H.value;if(!t)return null;let n=e.getScroll().verticalPosition,r=t.getBoundingClientRect().top,i=null;for(let e of U()){let t=e.getBoundingClientRect().top-r;if(t<n-40)i=t;else break}return i}async function G(){let t=M.value;if(!t)return;let n=W();if(n===null)for(let t=0;t<15&&v.hasMoreOlderFor(g.workspaceId);t++){for(;F.value;)await new Promise(e=>setTimeout(e,50));if(await z(),await e(),n=W(),n!==null)break}n!==null&&t.setScrollPosition(`vertical`,Math.max(0,n-12),250)}async function K(){I=!1,await e(),await B(0),requestAnimationFrame(()=>{requestAnimationFrame(()=>{I=!0})})}let q=p(()=>v.eventsFor(g.workspaceId).length);async function J(){L.value=!0;let e=Date.now();await new Promise(e=>setTimeout(e,Ee));let t=e+5e3;for(;q.value===0&&Date.now()<t;)await new Promise(e=>setTimeout(e,50));L.value=!1}n(L,async e=>{!e&&q.value>0&&await K()}),i(()=>{J(),q.value>0&&K()});let Y=!1;return n(q,async(e,t)=>{if(!Y&&e>0){Y=!0,await K();return}e>t&&P&&!F.value&&await B(180)}),n(()=>g.workspaceId,()=>{P=!0,Y=!1,I=!1,J(),q.value>0&&K()}),n(()=>b.selectedSessionId,async()=>{P=!0,I=!1,await K()}),n(p(()=>S.value.filter(e=>e.sender!==`system-prompt`).length),async(e,t)=>{e>t&&(P=!0,await B(180))}),(e,n)=>L.value?(t(),m(`div`,ye,[d(O,{size:`40px`,color:`indigo-4`})])):(t(),m(`div`,be,[d(ee,{ref_key:`scrollRef`,ref:M,class:`activity-feed-scroll`,onScroll:R},{default:o(()=>[_(`div`,{ref_key:`contentOriginRef`,ref:H,class:`content-origin-marker`},null,512),F.value?(t(),m(`div`,xe,[d(x,{size:`sm`}),f(` `+c(e.$t(`activity.loading_older`)),1)])):u(``,!0),_(`div`,Se,[(t(!0),m(h,null,r(A.value,(e,n)=>(t(),l(ve,{key:n,ref_for:!0,ref_key:`turnRefs`,ref:V,turn:e},null,8,[`turn`]))),128))]),j.value.length?(t(),m(`div`,Ce,[d(k,{label:e.$t(`activity.raw_lines`,{n:j.value.length}),dense:``},{default:o(()=>[(t(!0),m(h,null,r(j.value,(e,n)=>(t(),m(`div`,{key:n,class:`text-caption text-grey q-pa-xs`},c(e),1))),128))]),_:1},8,[`label`])])):u(``,!0)]),_:1},512),d(C,{round:``,dense:``,unelevated:``,color:`grey-9`,"text-color":`grey-3`,icon:`arrow_upward`,size:`sm`,class:`activity-feed-prev-btn`,title:e.$t(`activity.prev_user_message`),onClick:G},null,8,[`title`])]))}}),[[`__scopeId`,`data-v-51e2c6eb`]]);export{De as default};
|