@loicngr/kobo 1.6.0 → 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.
Files changed (201) hide show
  1. package/AGENTS.md +6 -1
  2. package/README.md +29 -16
  3. package/dist/server/db/index.js +17 -0
  4. package/dist/server/db/migrations.js +10 -0
  5. package/dist/server/db/schema.js +2 -1
  6. package/dist/server/index.js +24 -3
  7. package/dist/server/middleware/migration-guard.js +15 -0
  8. package/dist/server/routes/dev-server.js +3 -2
  9. package/dist/server/routes/engines.js +9 -0
  10. package/dist/server/routes/migration.js +5 -0
  11. package/dist/server/routes/workspaces.js +35 -11
  12. package/dist/server/services/agent/engines/claude-code/args-builder.js +22 -0
  13. package/dist/server/services/agent/engines/claude-code/capabilities.js +17 -0
  14. package/dist/server/services/agent/engines/claude-code/engine.js +163 -0
  15. package/dist/server/services/agent/engines/claude-code/mcp-config.js +23 -0
  16. package/dist/server/services/agent/engines/claude-code/stream-parser.js +224 -0
  17. package/dist/server/services/agent/engines/registry.js +21 -0
  18. package/dist/server/services/agent/engines/types.js +18 -0
  19. package/dist/server/services/agent/event-router.js +4 -0
  20. package/dist/server/services/agent/orchestrator.js +582 -0
  21. package/dist/server/services/agent/session-controller.js +79 -0
  22. package/dist/server/services/content-migration-service.js +155 -0
  23. package/dist/server/services/db-backup-service.js +15 -0
  24. package/dist/server/services/websocket-service.js +81 -50
  25. package/dist/server/services/workspace-service.js +11 -5
  26. package/dist/server/utils/paths.js +1 -1
  27. package/dist/shared/models.js +50 -0
  28. package/package.json +1 -1
  29. package/src/client/dist/spa/assets/ActivityFeed-DYtAK49y.js +7 -0
  30. package/src/client/dist/spa/assets/ActivityFeed-DiwnrdKX.css +1 -0
  31. package/src/client/dist/spa/assets/ClosePopup-DqhgFbQo.js +1 -0
  32. package/src/client/dist/spa/assets/CreatePage-DENfwzPL.js +2 -0
  33. package/src/client/dist/spa/assets/CreatePage-yu2IH7GW.css +1 -0
  34. package/src/client/dist/spa/assets/DiffViewer-C6q11kmw.js +2 -0
  35. package/src/client/dist/spa/assets/HealthPage-Cjc79NaA.js +1 -0
  36. package/src/client/dist/spa/assets/{MainLayout-_oPM07ln.js → MainLayout-CFbMw65L.js} +17 -17
  37. package/src/client/dist/spa/assets/QBadge-BUkmTO0P.js +1 -0
  38. package/src/client/dist/spa/assets/QBtn-p1aZtrJH.js +1 -0
  39. package/src/client/dist/spa/assets/QDialog-D42GLa1i.js +1 -0
  40. package/src/client/dist/spa/assets/QExpansionItem-5ekmpO-2.js +1 -0
  41. package/src/client/dist/spa/assets/{QSpinner-CliSLjf8.js → QIcon-B0-pH3Qs.js} +1 -1
  42. package/src/client/dist/spa/assets/QItemLabel-Czw5g0px.js +1 -0
  43. package/src/client/dist/spa/assets/QItemSection-GlMrLmz3.js +1 -0
  44. package/src/client/dist/spa/assets/QList-DNzlynsS.js +1 -0
  45. package/src/client/dist/spa/assets/QMenu-Q69oVX7b.js +1 -0
  46. package/src/client/dist/spa/assets/QPage-B09NY4Nf.js +1 -0
  47. package/src/client/dist/spa/assets/QScrollArea-L6wUiA20.js +1 -0
  48. package/src/client/dist/spa/assets/QSeparator-rkjCbX2M.js +1 -0
  49. package/src/client/dist/spa/assets/QSpace-PlDK6Fg3.js +1 -0
  50. package/src/client/dist/spa/assets/QSpinnerDots-By20ptst.js +1 -0
  51. package/src/client/dist/spa/assets/QTabPanels-Crs-ujNO.js +1 -0
  52. package/src/client/dist/spa/assets/QTooltip-Cg9E3Dvw.js +1 -0
  53. package/src/client/dist/spa/assets/SearchPage-Bf-iZnyE.js +1 -0
  54. package/src/client/dist/spa/assets/SettingsPage-BdcH3BSs.js +1 -0
  55. package/src/client/dist/spa/assets/TouchPan-DFx22dM3.js +1 -0
  56. package/src/client/dist/spa/assets/WorkspacePage-DPGiH02q.css +1 -0
  57. package/src/client/dist/spa/assets/WorkspacePage-UUE0pPCR.js +4 -0
  58. package/src/client/dist/spa/assets/{cssMode-DMX8jq8u.js → cssMode-BYtqFZtm.js} +1 -1
  59. package/src/client/dist/spa/assets/{editor.api-DirOkGGg.js → editor.api-D6ZaO4A_.js} +1 -1
  60. package/src/client/dist/spa/assets/{editor.main-DC4ezIu0.js → editor.main-Cc_RDKsq.js} +3 -3
  61. package/src/client/dist/spa/assets/format-uvONOeL4.js +1 -0
  62. package/src/client/dist/spa/assets/{formatters-BzaS4w0I.js → formatters-DiJ12fKd.js} +1 -1
  63. package/src/client/dist/spa/assets/{freemarker2-DI9xJfj0.js → freemarker2-CBm--bBd.js} +1 -1
  64. package/src/client/dist/spa/assets/{handlebars-B9F-pScn.js → handlebars-whX2mkV5.js} +1 -1
  65. package/src/client/dist/spa/assets/{html-DTe2v8Q8.js → html-D7ga_o6c.js} +1 -1
  66. package/src/client/dist/spa/assets/{htmlMode-F_XLjWfJ.js → htmlMode-BXImjcsv.js} +1 -1
  67. package/src/client/dist/spa/assets/i18n-BxLBrD1J.js +1 -0
  68. package/src/client/dist/spa/assets/index-D997aY4Y.js +2 -0
  69. package/src/client/dist/spa/assets/{javascript-B9xJRPC6.js → javascript-BwmzNMn5.js} +1 -1
  70. package/src/client/dist/spa/assets/{jsonMode-DTZ6j6UO.js → jsonMode-CN5Z5bK_.js} +1 -1
  71. package/src/client/dist/spa/assets/{liquid-BjU5MtW6.js → liquid-CzMNAPor.js} +1 -1
  72. package/src/client/dist/spa/assets/{marked.esm-DCmk6NO8.js → marked.esm-DW0ulF0a.js} +1 -1
  73. package/src/client/dist/spa/assets/{mdx-BMUpG7Be.js → mdx-DC_P05Da.js} +1 -1
  74. package/src/client/dist/spa/assets/models-DMQoi09X.js +1 -0
  75. package/src/client/dist/spa/assets/{monaco.contribution-D7JUf8DP.js → monaco.contribution-BsBaFOOD.js} +2 -2
  76. package/src/client/dist/spa/assets/private.use-form-D1RuEt2P.js +1 -0
  77. package/src/client/dist/spa/assets/{python-Dz0D4uSk.js → python-9DTZ8C3K.js} +1 -1
  78. package/src/client/dist/spa/assets/{razor-D7CFxuwR.js → razor-B1LfM20o.js} +1 -1
  79. package/src/client/dist/spa/assets/scroll-Dh2g7BwR.js +1 -0
  80. package/src/client/dist/spa/assets/touch-D_A29lik.js +1 -0
  81. package/src/client/dist/spa/assets/{tsMode-DjscaxpS.js → tsMode-DI2bWo8r.js} +1 -1
  82. package/src/client/dist/spa/assets/{typescript-DozCWZl2.js → typescript-BZ9QJ2_N.js} +1 -1
  83. package/src/client/dist/spa/assets/use-id-CeduaJbU.js +1 -0
  84. package/src/client/dist/spa/assets/use-portal-mhLq4Rqk.js +1 -0
  85. package/src/client/dist/spa/assets/use-quasar-BBrzedjR.js +1 -0
  86. package/src/client/dist/spa/assets/{xml-DFOJMT39.js → xml-D6qm6rp0.js} +1 -1
  87. package/src/client/dist/spa/assets/{yaml-yEefnsXm.js → yaml-D2dUr_wY.js} +1 -1
  88. package/src/client/dist/spa/index.html +11 -14
  89. package/src/mcp-server/README.md +1 -1
  90. package/dist/server/services/agent-manager.js +0 -621
  91. package/src/client/dist/spa/assets/ActivityFeed-0GR1zPoc.js +0 -10
  92. package/src/client/dist/spa/assets/ActivityFeed-CfsKExt9.css +0 -1
  93. package/src/client/dist/spa/assets/ClosePopup-CdSn7HO8.js +0 -1
  94. package/src/client/dist/spa/assets/CreatePage-dMi4xVYN.css +0 -1
  95. package/src/client/dist/spa/assets/CreatePage-je_7dC5I.js +0 -2
  96. package/src/client/dist/spa/assets/DiffViewer-DREYX-8k.js +0 -2
  97. package/src/client/dist/spa/assets/HealthPage-Do8QZdxw.js +0 -1
  98. package/src/client/dist/spa/assets/QBadge-Bvh-hQ8K.js +0 -1
  99. package/src/client/dist/spa/assets/QBtn-BsD8vrWq.js +0 -1
  100. package/src/client/dist/spa/assets/QDialog-CkbLS1If.js +0 -1
  101. package/src/client/dist/spa/assets/QExpansionItem-UgkE560c.js +0 -1
  102. package/src/client/dist/spa/assets/QList-D80ms7bw.js +0 -1
  103. package/src/client/dist/spa/assets/QMenu-DU-wiY_A.js +0 -1
  104. package/src/client/dist/spa/assets/QPage-BKY2-sf-.js +0 -1
  105. package/src/client/dist/spa/assets/QSpace-C5Ebr0vq.js +0 -1
  106. package/src/client/dist/spa/assets/QSpinnerDots-Dp12eHrB.js +0 -1
  107. package/src/client/dist/spa/assets/QTabPanels-C7lWp1yU.js +0 -1
  108. package/src/client/dist/spa/assets/QToggle-B0HvuNEg.js +0 -1
  109. package/src/client/dist/spa/assets/QTooltip-kLXuUa_m.js +0 -1
  110. package/src/client/dist/spa/assets/SearchPage-CCfyqBKh.js +0 -1
  111. package/src/client/dist/spa/assets/SettingsPage-CmyIsV-S.js +0 -1
  112. package/src/client/dist/spa/assets/TouchPan-CVMnGs0y.js +0 -1
  113. package/src/client/dist/spa/assets/WorkspacePage-CWRMLYs-.css +0 -1
  114. package/src/client/dist/spa/assets/WorkspacePage-Cl7YrG51.js +0 -4
  115. package/src/client/dist/spa/assets/focus-manager-DYbz9jFW.js +0 -1
  116. package/src/client/dist/spa/assets/format-Cyg8IgRi.js +0 -1
  117. package/src/client/dist/spa/assets/i18n-B13zBh1H.js +0 -1
  118. package/src/client/dist/spa/assets/i18n-CCWLBc0p.js +0 -1
  119. package/src/client/dist/spa/assets/index-DoNZ_5QK.js +0 -5
  120. package/src/client/dist/spa/assets/models-B8fzv7K4.js +0 -1
  121. package/src/client/dist/spa/assets/pinia-C3JsrLkB.js +0 -1
  122. package/src/client/dist/spa/assets/private.use-form-BhKyDtO7.js +0 -1
  123. package/src/client/dist/spa/assets/scroll-CLibRGI-.js +0 -1
  124. package/src/client/dist/spa/assets/settings-B69lIVX0.js +0 -1
  125. package/src/client/dist/spa/assets/touch-ChrvzrnI.js +0 -1
  126. package/src/client/dist/spa/assets/use-dark-DnuCB6tC.js +0 -1
  127. package/src/client/dist/spa/assets/use-quasar-DBoizHBW.js +0 -1
  128. /package/src/client/dist/spa/assets/{_plugin-vue_export-helper-Cxt1D8wE.js → _plugin-vue_export-helper-CEhRWsKN.js} +0 -0
  129. /package/src/client/dist/spa/assets/{abap-CFuyUYKP.js → abap-DiwvWnMr.js} +0 -0
  130. /package/src/client/dist/spa/assets/{apex-Ctq_xcrv.js → apex-CmtZjKlf.js} +0 -0
  131. /package/src/client/dist/spa/assets/{azcli-BBQSVn-C.js → azcli-DL2My_i-.js} +0 -0
  132. /package/src/client/dist/spa/assets/{bat-DbnqAfvr.js → bat-B-nC98wG.js} +0 -0
  133. /package/src/client/dist/spa/assets/{bicep-BtDlIXop.js → bicep-Ju5MwOgh.js} +0 -0
  134. /package/src/client/dist/spa/assets/{cameligo-BLeJgKTj.js → cameligo-8Eu1TyBr.js} +0 -0
  135. /package/src/client/dist/spa/assets/{clojure-aZUQIUKP.js → clojure-u-RpMkH3.js} +0 -0
  136. /package/src/client/dist/spa/assets/{coffee-Secadq9U.js → coffee-CdA7bbTe.js} +0 -0
  137. /package/src/client/dist/spa/assets/{cpp-JicRPTRv.js → cpp-CzNFP8ks.js} +0 -0
  138. /package/src/client/dist/spa/assets/{csharp-C7NSOZyj.js → csharp-j1LThmcE.js} +0 -0
  139. /package/src/client/dist/spa/assets/{csp-CIje7830.js → csp-CLRC61y6.js} +0 -0
  140. /package/src/client/dist/spa/assets/{css-G0bm1q_M.js → css-r6rC_7P2.js} +0 -0
  141. /package/src/client/dist/spa/assets/{cypher-CldD5D0u.js → cypher-CW08XVUh.js} +0 -0
  142. /package/src/client/dist/spa/assets/{dart-DIK3l8YT.js → dart-Cs9aL5T_.js} +0 -0
  143. /package/src/client/dist/spa/assets/{dockerfile-czxaGh2L.js → dockerfile-BWM0M184.js} +0 -0
  144. /package/src/client/dist/spa/assets/{ecl-BqdYhwmw.js → ecl-MJJuer5P.js} +0 -0
  145. /package/src/client/dist/spa/assets/{elixir-m52LePTW.js → elixir-D2AIuXqn.js} +0 -0
  146. /package/src/client/dist/spa/assets/{flow9-B5QJ9GvZ.js → flow9-B2H24giC.js} +0 -0
  147. /package/src/client/dist/spa/assets/{fsharp-B15czHsH.js → fsharp-CMk2OIJN.js} +0 -0
  148. /package/src/client/dist/spa/assets/{go-BkoQxDo1.js → go-BrMkuJg0.js} +0 -0
  149. /package/src/client/dist/spa/assets/{graphql-BnI6uRa_.js → graphql-PSR1UKGv.js} +0 -0
  150. /package/src/client/dist/spa/assets/{hcl-CAwwENT7.js → hcl-DAQrbDOW.js} +0 -0
  151. /package/src/client/dist/spa/assets/{ini-BHM5zh1H.js → ini-0TG5BxW0.js} +0 -0
  152. /package/src/client/dist/spa/assets/{java-B5i95QvQ.js → java-rgorz17v.js} +0 -0
  153. /package/src/client/dist/spa/assets/{julia-DPDm885q.js → julia-C8VMdHm8.js} +0 -0
  154. /package/src/client/dist/spa/assets/{kotlin-qoccd5BP.js → kotlin-CllWo3gX.js} +0 -0
  155. /package/src/client/dist/spa/assets/{less-B6RU166D.js → less-Cgca25AP.js} +0 -0
  156. /package/src/client/dist/spa/assets/{lexon-YfUeoL1V.js → lexon-D0GHdBaw.js} +0 -0
  157. /package/src/client/dist/spa/assets/{lua-BIUI5y9b.js → lua-DmRsNG-P.js} +0 -0
  158. /package/src/client/dist/spa/assets/{m3-D5SAbSdU.js → m3-BgL5dNKT.js} +0 -0
  159. /package/src/client/dist/spa/assets/{markdown-CVJLwHzJ.js → markdown-BuJfycGS.js} +0 -0
  160. /package/src/client/dist/spa/assets/{mips-R-FZ3zOR.js → mips-C9m_93PR.js} +0 -0
  161. /package/src/client/dist/spa/assets/{msdax-Blveyl9r.js → msdax-CpFHC9OI.js} +0 -0
  162. /package/src/client/dist/spa/assets/{mysql-D4mY1AFx.js → mysql-qFvltsqN.js} +0 -0
  163. /package/src/client/dist/spa/assets/{objective-c-BmXrLr4h.js → objective-c-Bnmr858J.js} +0 -0
  164. /package/src/client/dist/spa/assets/{pascal-yxckoyvV.js → pascal-WP0_D5AO.js} +0 -0
  165. /package/src/client/dist/spa/assets/{pascaligo-Q5JCwXMI.js → pascaligo-Blom4Rij.js} +0 -0
  166. /package/src/client/dist/spa/assets/{perl-BF1Rrs5h.js → perl-B-vk8g64.js} +0 -0
  167. /package/src/client/dist/spa/assets/{pgsql-CnYB97wm.js → pgsql-Cgvz6v67.js} +0 -0
  168. /package/src/client/dist/spa/assets/{php-CdDfQfSg.js → php-8a3Lrw9m.js} +0 -0
  169. /package/src/client/dist/spa/assets/{pla-whj-d71F.js → pla-DuFqEZ8V.js} +0 -0
  170. /package/src/client/dist/spa/assets/{postiats-ClfLr4I-.js → postiats-DkLtSgkp.js} +0 -0
  171. /package/src/client/dist/spa/assets/{powerquery-iRaBhuuk.js → powerquery-BJ1aNepW.js} +0 -0
  172. /package/src/client/dist/spa/assets/{powershell-DjiEt5xK.js → powershell-rE98k687.js} +0 -0
  173. /package/src/client/dist/spa/assets/{protobuf-B6dcIEUr.js → protobuf-CUheFacr.js} +0 -0
  174. /package/src/client/dist/spa/assets/{pug-DtmHnjM9.js → pug-LDcAMD8w.js} +0 -0
  175. /package/src/client/dist/spa/assets/{qsharp-CELCyd79.js → qsharp-DUKSQoR1.js} +0 -0
  176. /package/src/client/dist/spa/assets/{r-ZpJXWV-o.js → r-D-QApv87.js} +0 -0
  177. /package/src/client/dist/spa/assets/{rate-limit-labels-dCPVjS61.js → rate-limit-labels-BvYERsho.js} +0 -0
  178. /package/src/client/dist/spa/assets/{redis-BiHSNkAl.js → redis-SXdDyWR9.js} +0 -0
  179. /package/src/client/dist/spa/assets/{redshift-DzuwYCHP.js → redshift-Y6lsCryn.js} +0 -0
  180. /package/src/client/dist/spa/assets/{restructuredtext-YOT94bbS.js → restructuredtext-edObr9a8.js} +0 -0
  181. /package/src/client/dist/spa/assets/{ruby-BfiHr6Uu.js → ruby-CNnUfF-8.js} +0 -0
  182. /package/src/client/dist/spa/assets/{rust-JZ-uOoYM.js → rust-IHUZWzBr.js} +0 -0
  183. /package/src/client/dist/spa/assets/{sb-CBglP1-t.js → sb-DrUvY44N.js} +0 -0
  184. /package/src/client/dist/spa/assets/{scala-C9l41paw.js → scala-B4hbXGLM.js} +0 -0
  185. /package/src/client/dist/spa/assets/{scheme-B-InQ6hy.js → scheme-BGrd12j3.js} +0 -0
  186. /package/src/client/dist/spa/assets/{scss-v6OmJRN9.js → scss-x5G1ES4U.js} +0 -0
  187. /package/src/client/dist/spa/assets/{shell-Dyp6iwB6.js → shell-DOehe2Y8.js} +0 -0
  188. /package/src/client/dist/spa/assets/{solidity-D5epNWue.js → solidity-BeRvcwWV.js} +0 -0
  189. /package/src/client/dist/spa/assets/{sophia-Eva-79sB.js → sophia-DZbkUNjy.js} +0 -0
  190. /package/src/client/dist/spa/assets/{sparql-gvALLO1w.js → sparql-B7_oi5-h.js} +0 -0
  191. /package/src/client/dist/spa/assets/{sql-COdamZYI.js → sql-CTlsFWVE.js} +0 -0
  192. /package/src/client/dist/spa/assets/{st-eMoImIwE.js → st-DJVEJdPE.js} +0 -0
  193. /package/src/client/dist/spa/assets/{swift-7R_T9RYH.js → swift-CwhT3fYa.js} +0 -0
  194. /package/src/client/dist/spa/assets/{symbols-CAg-nBkV.js → symbols-DCYodwb2.js} +0 -0
  195. /package/src/client/dist/spa/assets/{systemverilog-1pCEfaHU.js → systemverilog-BQN63pkN.js} +0 -0
  196. /package/src/client/dist/spa/assets/{tcl-B_KgnhfE.js → tcl-DqwfpskA.js} +0 -0
  197. /package/src/client/dist/spa/assets/{twig-CFZUJxb9.js → twig-BiyenUgc.js} +0 -0
  198. /package/src/client/dist/spa/assets/{typespec-B1ZgHlud.js → typespec-CWOJribt.js} +0 -0
  199. /package/src/client/dist/spa/assets/{vb-DKdun5tL.js → vb-Cq5F87m3.js} +0 -0
  200. /package/src/client/dist/spa/assets/{vue-i18n-eUDnMrPl.js → vue-i18n-CeG0hR0Z.js} +0 -0
  201. /package/src/client/dist/spa/assets/{wgsl-CzNaxTrn.js → wgsl-BAvW2lVr.js} +0 -0
@@ -1,621 +0,0 @@
1
- import { spawn } from 'node:child_process';
2
- import fs, { readFileSync, writeFileSync } from 'node:fs';
3
- import path from 'node:path';
4
- import readline from 'node:readline';
5
- import { nanoid } from 'nanoid';
6
- import { getDb } from '../db/index.js';
7
- import { ensureKoboHome, getCompiledMcpServerPath, getDbPath, getMcpServerSourcePath, getSettingsPath, getSkillsPath, } from '../utils/paths.js';
8
- import { registerProcess, unregisterProcess } from '../utils/process-tracker.js';
9
- import { getEffectiveSettings } from './settings-service.js';
10
- import { emit, emitEphemeral } from './websocket-service.js';
11
- import { getWorkspace as getWs, listTasks, markWorkspaceUnread, updateWorkspaceStatus } from './workspace-service.js';
12
- // ── State ──────────────────────────────────────────────────────────────────────
13
- /** Actual bound port of the running backend — set at startup via setBackendPort() */
14
- let backendPort = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
15
- /** Called from index.ts once the HTTP server is listening so MCP children can reach it. */
16
- export function setBackendPort(port) {
17
- backendPort = port;
18
- }
19
- /** workspaceId -> agent instance */
20
- const agents = new Map();
21
- /** workspaceId -> last Claude session ID (for --resume) */
22
- const sessionIds = new Map();
23
- /** Cached list of available slash commands — persisted to <KOBO_HOME>/skills.json */
24
- let availableSkills = (() => {
25
- try {
26
- const data = JSON.parse(readFileSync(getSkillsPath(), 'utf-8'));
27
- return Array.isArray(data) ? data : [];
28
- }
29
- catch {
30
- return [];
31
- }
32
- })();
33
- /** workspaceId -> retry count (for quota backoff) */
34
- const retryCounts = new Map();
35
- /** workspaceId -> backoff timer */
36
- const backoffTimers = new Map();
37
- /** workspaceId -> pending SIGKILL timer */
38
- const killTimers = new Map();
39
- // ── Watchdog ──────────────────────────────────────────────────────────────────
40
- // Periodically checks that tracked agent processes are still alive.
41
- // If a process died without triggering the 'exit' handler (crash, OOM kill,
42
- // etc.), the watchdog cleans up and updates the workspace status.
43
- const WATCHDOG_INTERVAL_MS = 30_000;
44
- let watchdogTimer = null;
45
- function isProcessAlive(pid) {
46
- try {
47
- process.kill(pid, 0); // signal 0 = existence check, doesn't actually kill
48
- return true;
49
- }
50
- catch {
51
- return false;
52
- }
53
- }
54
- function runWatchdog() {
55
- for (const [workspaceId, agent] of agents) {
56
- const pid = agent.process.pid;
57
- if (pid === undefined || isProcessAlive(pid))
58
- continue;
59
- console.error(`[watchdog] Agent process for workspace '${workspaceId}' (PID ${pid}) is dead — cleaning up`);
60
- // Close readline to release the stream
61
- try {
62
- agent.rl.close();
63
- }
64
- catch {
65
- // Ignore
66
- }
67
- unregisterProcess(workspaceId);
68
- agents.delete(workspaceId);
69
- retryCounts.delete(workspaceId);
70
- // Update DB session
71
- try {
72
- const db = getDb();
73
- db.prepare('UPDATE agent_sessions SET status = ?, ended_at = ? WHERE id = ?').run('error', new Date().toISOString(), agent.agentSessionId);
74
- }
75
- catch (err) {
76
- console.error('[watchdog] Failed to update agent_sessions:', err);
77
- }
78
- // Update workspace status
79
- try {
80
- updateWorkspaceStatus(workspaceId, 'error');
81
- }
82
- catch {
83
- // Transition may not be valid — ignore
84
- }
85
- try {
86
- markWorkspaceUnread(workspaceId);
87
- emitEphemeral(workspaceId, 'workspace:unread', { hasUnread: true });
88
- }
89
- catch {
90
- // best-effort
91
- }
92
- emit(workspaceId, 'agent:status', { status: 'error', message: 'Agent process died unexpectedly' }, agent.agentSessionId);
93
- }
94
- }
95
- /** Start the watchdog (called once from server bootstrap). */
96
- export function startWatchdog() {
97
- if (watchdogTimer)
98
- return;
99
- watchdogTimer = setInterval(runWatchdog, WATCHDOG_INTERVAL_MS);
100
- watchdogTimer.unref?.();
101
- }
102
- /** Stop the watchdog (for clean shutdown / tests). */
103
- export function stopWatchdog() {
104
- if (watchdogTimer) {
105
- clearInterval(watchdogTimer);
106
- watchdogTimer = null;
107
- }
108
- }
109
- // ── Start agent ────────────────────────────────────────────────────────────────
110
- /** Spawn a Claude Code CLI process for a workspace and wire up stdout/stderr/exit handling. */
111
- export function startAgent(workspaceId, workingDir, prompt, model, resume = false, permissionMode = 'auto-accept', existingSessionId, reasoningEffort) {
112
- // Check if agent already running for this workspace
113
- if (agents.has(workspaceId)) {
114
- throw new Error(`Agent already running for workspace '${workspaceId}'`);
115
- }
116
- const db = getDb();
117
- let agentSessionId;
118
- let resumedClaudeSessionId;
119
- // Build CLI args — read dangerouslySkipPermissions from effective settings
120
- const ws = getWs(workspaceId);
121
- const effectiveSettings = ws ? getEffectiveSettings(ws.projectPath) : null;
122
- const skipPermissions = effectiveSettings?.dangerouslySkipPermissions ?? true;
123
- const args = ['--output-format', 'stream-json', '--verbose'];
124
- if (skipPermissions) {
125
- args.push('--dangerously-skip-permissions');
126
- }
127
- if (permissionMode === 'plan') {
128
- // In plan mode, prepend read-only instructions to the prompt
129
- prompt = `[PLAN MODE] You are in PLAN/READ-ONLY mode. You MUST NOT create, edit, write, or delete any files. Only use read-only tools (Read, Grep, Glob, LS, Bash for read-only commands). Analyze the codebase, plan your approach, and present your findings — but do NOT execute any changes.\n\n${prompt}`;
130
- }
131
- if (model && model !== 'auto') {
132
- args.push('--model', model);
133
- }
134
- if (reasoningEffort && reasoningEffort !== 'auto') {
135
- args.push('--effort', reasoningEffort);
136
- }
137
- if (resume) {
138
- // Prefer resuming the specific session requested by the caller (existingSessionId).
139
- // Otherwise fall back to the most recent session for the workspace.
140
- let lastSession;
141
- if (existingSessionId) {
142
- lastSession = db
143
- .prepare('SELECT id, claude_session_id FROM agent_sessions WHERE id = ? AND workspace_id = ? AND claude_session_id IS NOT NULL LIMIT 1')
144
- .get(existingSessionId, workspaceId);
145
- // If the caller explicitly asked to resume a specific session, fail loudly
146
- // when it cannot be resumed (no claude_session_id, wrong workspace, or
147
- // missing row). Silently creating a new session would orphan the target.
148
- if (!lastSession) {
149
- throw new Error(`Cannot resume session '${existingSessionId}' for workspace '${workspaceId}': ` +
150
- 'session not found or has no associated Claude conversation');
151
- }
152
- }
153
- else {
154
- lastSession = db
155
- .prepare('SELECT id, claude_session_id FROM agent_sessions WHERE workspace_id = ? AND claude_session_id IS NOT NULL ORDER BY started_at DESC LIMIT 1')
156
- .get(workspaceId);
157
- }
158
- // The in-memory cache is workspace-scoped, only useful when no specific session was requested.
159
- const claudeSessionId = lastSession?.claude_session_id ?? (existingSessionId ? undefined : sessionIds.get(workspaceId));
160
- if (claudeSessionId) {
161
- resumedClaudeSessionId = claudeSessionId;
162
- args.push('--resume', claudeSessionId, '-p', prompt);
163
- // Always reuse existing session — find by claude_session_id if lastSession didn't match
164
- const existingId = lastSession?.id ??
165
- db
166
- .prepare('SELECT id FROM agent_sessions WHERE claude_session_id = ? ORDER BY started_at DESC LIMIT 1')
167
- .get(claudeSessionId)?.id;
168
- agentSessionId = existingId ?? nanoid();
169
- if (existingId) {
170
- db.prepare('UPDATE agent_sessions SET status = ?, ended_at = NULL WHERE id = ?').run('running', agentSessionId);
171
- }
172
- else {
173
- db.prepare('INSERT INTO agent_sessions (id, workspace_id, pid, status, claude_session_id, started_at) VALUES (?, ?, ?, ?, ?, ?)').run(agentSessionId, workspaceId, null, 'running', claudeSessionId, new Date().toISOString());
174
- }
175
- }
176
- else {
177
- args.push('-p', prompt);
178
- agentSessionId = nanoid();
179
- db.prepare('INSERT INTO agent_sessions (id, workspace_id, pid, status, started_at) VALUES (?, ?, ?, ?, ?)').run(agentSessionId, workspaceId, null, 'running', new Date().toISOString());
180
- }
181
- }
182
- else {
183
- args.push('-p', prompt);
184
- if (existingSessionId) {
185
- const result = db
186
- .prepare('UPDATE agent_sessions SET status = ?, started_at = ?, ended_at = NULL WHERE id = ? AND workspace_id = ?')
187
- .run('running', new Date().toISOString(), existingSessionId, workspaceId);
188
- if (result.changes === 0) {
189
- throw new Error(`Agent session '${existingSessionId}' not found for workspace '${workspaceId}'`);
190
- }
191
- agentSessionId = existingSessionId;
192
- }
193
- else {
194
- agentSessionId = nanoid();
195
- db.prepare('INSERT INTO agent_sessions (id, workspace_id, pid, status, started_at) VALUES (?, ?, ?, ?, ?)').run(agentSessionId, workspaceId, null, 'running', new Date().toISOString());
196
- }
197
- }
198
- // Write .mcp.json to workingDir so claude picks up the kobo-tasks MCP server
199
- const mcpConfigPath = path.join(workingDir, '.mcp.json');
200
- try {
201
- const mcpServerCompiled = getCompiledMcpServerPath();
202
- const mcpServerSource = getMcpServerSourcePath();
203
- const mcpConfig = {
204
- mcpServers: {
205
- 'kobo-tasks': {
206
- command: mcpServerCompiled ? 'node' : 'npx',
207
- args: mcpServerCompiled ? [mcpServerCompiled] : ['tsx', mcpServerSource],
208
- env: {
209
- KOBO_WORKSPACE_ID: workspaceId,
210
- KOBO_DB_PATH: getDbPath(),
211
- KOBO_SETTINGS_PATH: getSettingsPath(),
212
- KOBO_BACKEND_URL: `http://127.0.0.1:${backendPort}`,
213
- },
214
- },
215
- },
216
- };
217
- fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
218
- args.push('--mcp-config', mcpConfigPath);
219
- }
220
- catch (err) {
221
- console.error('[agent-manager] Failed to write .mcp.json, continuing without kobo-tasks MCP:', err instanceof Error ? err.message : err);
222
- }
223
- // Spawn Claude Code process
224
- const proc = spawn('claude', args, {
225
- cwd: workingDir,
226
- stdio: ['pipe', 'pipe', 'pipe'],
227
- });
228
- // Create readline interface for NDJSON parsing from stdout
229
- const rl = readline.createInterface({
230
- input: proc.stdout,
231
- crlfDelay: Infinity,
232
- });
233
- // Update PID in DB session
234
- db.prepare('UPDATE agent_sessions SET pid = ? WHERE id = ?').run(proc.pid ?? null, agentSessionId);
235
- // Register with process tracker
236
- registerProcess(workspaceId, proc);
237
- const agent = {
238
- workspaceId,
239
- process: proc,
240
- rl,
241
- status: 'running',
242
- agentSessionId,
243
- claudeSessionId: resumedClaudeSessionId,
244
- };
245
- // ── stdout line-by-line (NDJSON) ──
246
- rl.on('line', (line) => {
247
- if (!line.trim())
248
- return;
249
- let parsed;
250
- try {
251
- parsed = JSON.parse(line);
252
- }
253
- catch {
254
- // Parsing failed — emit raw line
255
- emit(workspaceId, 'agent:output', { type: 'raw', content: line }, agent.agentSessionId);
256
- // Check for BRAINSTORM_COMPLETE marker in raw lines
257
- if (line.includes('[BRAINSTORM_COMPLETE]')) {
258
- try {
259
- updateWorkspaceStatus(workspaceId, 'executing');
260
- emit(workspaceId, 'agent:status', { status: 'executing' }, agent.agentSessionId);
261
- }
262
- catch (err) {
263
- console.error('[agent] Failed to transition to executing:', err);
264
- }
265
- }
266
- return;
267
- }
268
- const p = parsed;
269
- const msgType = p.type;
270
- // Capture available skills from init message
271
- if (msgType === 'system' &&
272
- p.subtype === 'init' &&
273
- Array.isArray(p.slash_commands) &&
274
- p.slash_commands.length > 0) {
275
- availableSkills = p.slash_commands;
276
- try {
277
- ensureKoboHome();
278
- writeFileSync(getSkillsPath(), JSON.stringify(availableSkills));
279
- }
280
- catch (err) {
281
- console.error('[agent] Failed to persist skills:', err);
282
- }
283
- }
284
- // Capture session_id for --resume support
285
- if (typeof p.session_id === 'string' && p.session_id) {
286
- sessionIds.set(workspaceId, p.session_id);
287
- if (!agent.claudeSessionId) {
288
- agent.claudeSessionId = p.session_id;
289
- const db = getDb();
290
- db.prepare('UPDATE agent_sessions SET claude_session_id = ? WHERE id = ?').run(agent.claudeSessionId, agent.agentSessionId);
291
- }
292
- }
293
- // After compact, reinject criteria so the agent doesn't lose track
294
- if (msgType === 'system' && (p.subtype === 'compact' || p.subtype === 'compact_boundary')) {
295
- try {
296
- const ws = getWs(workspaceId);
297
- const tasks = listTasks(workspaceId);
298
- const criteria = tasks.filter((t) => t.isAcceptanceCriterion);
299
- const todos = tasks.filter((t) => !t.isAcceptanceCriterion);
300
- if (criteria.length > 0 || todos.length > 0) {
301
- let reminder = `\n--- Context reminder after compaction ---\n`;
302
- reminder += `Task: ${ws?.name ?? workspaceId}\n`;
303
- if (todos.length > 0) {
304
- reminder += `\nTasks:\n${todos.map((t) => `- [${t.status === 'done' ? 'x' : ' '}] ${t.title}`).join('\n')}\n`;
305
- }
306
- if (criteria.length > 0) {
307
- reminder += `\nAcceptance criteria:\n${criteria.map((t) => `- [${t.status === 'done' ? 'x' : ' '}] ${t.title}`).join('\n')}\n`;
308
- reminder += `\nWhen you complete a criterion, tell me which one so I can mark it as done.\n`;
309
- }
310
- reminder += `--- End of reminder ---\n`;
311
- if (agent.process.stdin?.writable) {
312
- agent.process.stdin.write(`${reminder}\n`);
313
- }
314
- }
315
- }
316
- catch (err) {
317
- console.error('[agent] Failed to inject post-compact reminder:', err);
318
- }
319
- }
320
- // Filter out user messages (tool results) — they create noise in the feed
321
- if (msgType === 'user') {
322
- return;
323
- }
324
- emit(workspaceId, 'agent:output', parsed, agent.agentSessionId);
325
- // Detect brainstorming completion from parsed output
326
- if (msgType === 'assistant' && Array.isArray(p.content)) {
327
- const hasMarker = p.content.some((block) => {
328
- const b = block;
329
- return b.type === 'text' && typeof b.text === 'string' && b.text.includes('[BRAINSTORM_COMPLETE]');
330
- });
331
- if (hasMarker) {
332
- try {
333
- updateWorkspaceStatus(workspaceId, 'executing');
334
- emit(workspaceId, 'agent:status', { status: 'executing' }, agent.agentSessionId);
335
- }
336
- catch (err) {
337
- console.error('[agent] Failed to transition to executing:', err);
338
- }
339
- }
340
- }
341
- });
342
- // ── stderr — detect quota / rate limit errors ──
343
- proc.stderr?.on('data', (data) => {
344
- const currentAgent = agents.get(workspaceId);
345
- if (!currentAgent || currentAgent.status === 'stopping')
346
- return;
347
- const text = data.toString();
348
- const lowerText = text.toLowerCase();
349
- if (lowerText.includes('rate limit') || lowerText.includes('quota') || lowerText.includes('limit exceeded')) {
350
- handleQuota(workspaceId, agent.agentSessionId);
351
- }
352
- // Also emit stderr for visibility
353
- emit(workspaceId, 'agent:stderr', { content: text }, agent.agentSessionId);
354
- });
355
- // ── process exit ──
356
- proc.on('exit', (code) => {
357
- // Clean up the .mcp.json file written before spawn
358
- try {
359
- fs.unlinkSync(mcpConfigPath);
360
- }
361
- catch {
362
- // File may not exist (spawn failed) — ignore
363
- }
364
- agent.rl.close();
365
- unregisterProcess(workspaceId);
366
- // Only remove from the map if this exact agent instance is still current.
367
- // stopAgent() eagerly removes the entry so startAgent() can proceed
368
- // immediately; if a new agent was started in the meantime, we must not
369
- // remove it.
370
- if (agents.get(workspaceId) === agent) {
371
- agents.delete(workspaceId);
372
- }
373
- // Clean up retry state and inactivity timer
374
- retryCounts.delete(workspaceId);
375
- // Clear the kill timer if it's still pending (process exited naturally before SIGKILL)
376
- const pendingKillTimer = killTimers.get(workspaceId);
377
- if (pendingKillTimer) {
378
- clearTimeout(pendingKillTimer);
379
- killTimers.delete(workspaceId);
380
- }
381
- // Update agent_sessions row
382
- {
383
- const db = getDb();
384
- db.prepare('UPDATE agent_sessions SET status = ?, ended_at = ? WHERE id = ?').run(code === 0 ? 'completed' : 'error', new Date().toISOString(), agent.agentSessionId);
385
- }
386
- if (agent.status === 'stopping') {
387
- // Clean stop requested
388
- emit(workspaceId, 'agent:status', { status: 'stopped' }, agent.agentSessionId);
389
- return;
390
- }
391
- // Also clear backoff timers on non-stopping exit
392
- const pendingBackoff = backoffTimers.get(workspaceId);
393
- if (pendingBackoff) {
394
- clearTimeout(pendingBackoff);
395
- backoffTimers.delete(workspaceId);
396
- }
397
- if (code !== null && code !== 0) {
398
- try {
399
- updateWorkspaceStatus(workspaceId, 'error');
400
- }
401
- catch (err) {
402
- console.error('[agent] Failed to update workspace status on exit:', err);
403
- }
404
- try {
405
- markWorkspaceUnread(workspaceId);
406
- emitEphemeral(workspaceId, 'workspace:unread', { hasUnread: true });
407
- }
408
- catch {
409
- // best-effort
410
- }
411
- emit(workspaceId, 'agent:status', { status: 'error', exitCode: code }, agent.agentSessionId);
412
- }
413
- else {
414
- try {
415
- updateWorkspaceStatus(workspaceId, 'completed');
416
- }
417
- catch (err) {
418
- console.error('[agent] Failed to update workspace status on exit:', err);
419
- }
420
- try {
421
- markWorkspaceUnread(workspaceId);
422
- emitEphemeral(workspaceId, 'workspace:unread', { hasUnread: true });
423
- }
424
- catch {
425
- // best-effort
426
- }
427
- emit(workspaceId, 'agent:status', { status: 'completed' }, agent.agentSessionId);
428
- }
429
- });
430
- // Store in agents map
431
- agents.set(workspaceId, agent);
432
- // Notify frontend that agent is now running
433
- emit(workspaceId, 'agent:status', { status: 'executing' }, agent.agentSessionId);
434
- return agent;
435
- }
436
- // ── Interrupt agent ────────────────────────────────────────────────────────────
437
- /**
438
- * Soft-interrupt the running agent by sending SIGINT. This mirrors pressing
439
- * Escape in Claude Code CLI: the current tool call is aborted but the process
440
- * stays alive and waits for the next user message. The agent session remains
441
- * in 'running' status.
442
- */
443
- export function interruptAgent(workspaceId) {
444
- const agent = agents.get(workspaceId);
445
- if (!agent) {
446
- throw new Error(`No agent running for workspace '${workspaceId}'`);
447
- }
448
- const pid = agent.process.pid;
449
- if (pid === undefined) {
450
- throw new Error(`Agent process has no PID for workspace '${workspaceId}'`);
451
- }
452
- try {
453
- process.kill(pid, 'SIGINT');
454
- }
455
- catch (err) {
456
- const message = err instanceof Error ? err.message : String(err);
457
- throw new Error(`Failed to interrupt agent for workspace '${workspaceId}': ${message}`);
458
- }
459
- }
460
- // ── Stop agent ─────────────────────────────────────────────────────────────────
461
- /** Gracefully stop an agent (SIGTERM, then SIGKILL after 5s). */
462
- export function stopAgent(workspaceId) {
463
- const agent = agents.get(workspaceId);
464
- if (!agent) {
465
- throw new Error(`No agent running for workspace '${workspaceId}'`);
466
- }
467
- agent.status = 'stopping';
468
- // Remove from the map immediately so startAgent() can be called right after
469
- // without hitting "Agent already running". The exit handler checks identity
470
- // before removing, so a new agent started in the meantime won't be affected.
471
- agents.delete(workspaceId);
472
- // Cancel any pending backoff timer
473
- const timer = backoffTimers.get(workspaceId);
474
- if (timer) {
475
- clearTimeout(timer);
476
- backoffTimers.delete(workspaceId);
477
- }
478
- try {
479
- agent.rl.close();
480
- }
481
- catch {
482
- // Ignore
483
- }
484
- // Send SIGTERM
485
- try {
486
- agent.process.kill('SIGTERM');
487
- }
488
- catch {
489
- // Process may already be dead
490
- }
491
- // After 5s timeout, send SIGKILL if still running
492
- const killTimer = setTimeout(() => {
493
- // If a new agent has been started for this workspace in the meantime,
494
- // don't kill the old process — it's handled by the new lifecycle.
495
- const currentAgent = agents.get(workspaceId);
496
- if (currentAgent && currentAgent !== agent) {
497
- killTimers.delete(workspaceId);
498
- return;
499
- }
500
- try {
501
- if (!agent.process.killed) {
502
- agent.process.kill('SIGKILL');
503
- }
504
- }
505
- catch {
506
- // Ignore
507
- }
508
- killTimers.delete(workspaceId);
509
- }, 5000);
510
- // Don't keep the process alive for this timer
511
- killTimer.unref?.();
512
- killTimers.set(workspaceId, killTimer);
513
- }
514
- // ── Send message to agent stdin ────────────────────────────────────────────────
515
- /** Write a user message to the running agent's stdin. */
516
- export function sendMessage(workspaceId, content) {
517
- const agent = agents.get(workspaceId);
518
- if (!agent) {
519
- throw new Error(`No agent running for workspace '${workspaceId}'`);
520
- }
521
- if (!agent.process.stdin?.writable) {
522
- throw new Error(`Agent stdin not writable for workspace '${workspaceId}'`);
523
- }
524
- agent.process.stdin.write(`${content}\n`);
525
- }
526
- // ── Status queries ─────────────────────────────────────────────────────────────
527
- /** Get the in-memory status of the agent for a workspace, or null if not running. */
528
- export function getAgentStatus(workspaceId) {
529
- const agent = agents.get(workspaceId);
530
- return agent?.status ?? null;
531
- }
532
- /** Return the number of currently running agents. */
533
- export function getRunningCount() {
534
- return agents.size;
535
- }
536
- /** Kobo built-in slash commands injected into the skill list (without leading /). */
537
- const KOBO_COMMANDS = ['kobo-check-progress'];
538
- /** Return the cached list of slash commands discovered from the last agent init, plus Kobo built-in commands. */
539
- export function getAvailableSkills() {
540
- return [...KOBO_COMMANDS, ...availableSkills];
541
- }
542
- // ── Quota handling ─────────────────────────────────────────────────────────────
543
- function handleQuota(workspaceId, agentSessionId) {
544
- // Update workspace status
545
- try {
546
- updateWorkspaceStatus(workspaceId, 'quota');
547
- }
548
- catch {
549
- // May fail if transition is not valid
550
- }
551
- // Emit status event
552
- emit(workspaceId, 'agent:status', { status: 'quota' }, agentSessionId);
553
- // Calculate backoff: 15min first, then 30min, then 60min cap
554
- const retryCount = retryCounts.get(workspaceId) ?? 0;
555
- const backoffMinutes = Math.min(15 * 2 ** retryCount, 60);
556
- const backoffMs = backoffMinutes * 60 * 1000;
557
- retryCounts.set(workspaceId, retryCount + 1);
558
- emit(workspaceId, 'agent:status', {
559
- status: 'quota:backoff',
560
- retryCount: retryCount + 1,
561
- backoffMinutes,
562
- }, agentSessionId);
563
- // Set timer to restart agent
564
- const timer = setTimeout(() => {
565
- backoffTimers.delete(workspaceId);
566
- // Only restart if not already running or stopped
567
- if (!agents.has(workspaceId)) {
568
- // Re-read workspace from DB — it may have been deleted or archived during backoff
569
- const freshWs = getWs(workspaceId);
570
- if (!freshWs || freshWs.archivedAt !== null || freshWs.status !== 'quota') {
571
- return;
572
- }
573
- try {
574
- const freshWorkingDir = `${freshWs.projectPath}/.worktrees/${freshWs.workingBranch}`;
575
- startAgent(workspaceId, freshWorkingDir, 'Continue the previous task where you left off.', undefined, true);
576
- }
577
- catch {
578
- // Agent restart failed
579
- emit(workspaceId, 'agent:status', { status: 'error', message: 'Quota retry failed' });
580
- }
581
- }
582
- }, backoffMs);
583
- timer.unref?.();
584
- backoffTimers.set(workspaceId, timer);
585
- }
586
- // ── Testing utilities ──────────────────────────────────────────────────────────
587
- /**
588
- * Get the internal agents map — exposed for testing only.
589
- * @internal
590
- */
591
- export function _getAgents() {
592
- return agents;
593
- }
594
- /**
595
- * Get retry counts — exposed for testing only.
596
- * @internal
597
- */
598
- export function _getRetryCounts() {
599
- return retryCounts;
600
- }
601
- /**
602
- * Get backoff timers — exposed for testing only.
603
- * @internal
604
- */
605
- export function _getBackoffTimers() {
606
- return backoffTimers;
607
- }
608
- /**
609
- * Get kill timers — exposed for testing only.
610
- * @internal
611
- */
612
- export function _getKillTimers() {
613
- return killTimers;
614
- }
615
- /**
616
- * Get session IDs map — exposed for testing only.
617
- * @internal
618
- */
619
- export function _getSessionIds() {
620
- return sessionIds;
621
- }
@@ -1,10 +0,0 @@
1
- import{E as e,F as t,H as n,L as r,M as i,N as a,Q as o,U as s,_t as c,bt as l,d as u,f as d,g as f,h as p,l as m,p as h,r as g,rt as ee,u as _,v as te}from"./runtime-core.esm-bundler-C3IgBgY5.js";import{F as ne,i as v}from"./QSpinner-CliSLjf8.js";import{n as re}from"./vue-i18n-eUDnMrPl.js";import{t as y}from"./QBtn-BsD8vrWq.js";import{x as ie,y as ae}from"./index-DoNZ_5QK.js";import{t as b}from"./QSpinnerDots-Dp12eHrB.js";import{t as x}from"./QSpace-C5Ebr0vq.js";import{t as S}from"./QTooltip-kLXuUa_m.js";import{n as oe,t as se}from"./marked.esm-DCmk6NO8.js";import{t as C}from"./_plugin-vue_export-helper-Cxt1D8wE.js";var ce={key:0,class:`af-empty column items-center justify-center text-center q-pa-xl`},le={class:`text-grey-6 q-mt-md text-body2`},ue={class:`text-grey-8 text-caption q-mt-xs`},de={key:1,class:`row justify-center q-my-sm`},fe=[`data-item-id`],pe={class:`af-tool row items-center q-gutter-xs`},me={class:`af-tool-label text-indigo-4`},he={class:`af-time`},ge={key:0,class:`text-grey-4 q-mb-xs`},_e={class:`af-ask-options-list q-mb-sm`},ve={class:`text-weight-bold text-grey-3`},ye={key:0},be={key:1,class:`af-ask-buttons q-gutter-xs`},xe={key:0,class:`q-mt-sm row justify-end`},Se=[`onClick`],Ce={class:`af-file-header row items-center no-wrap q-gutter-xs`},we={class:`af-file-path text-grey-4 ellipsis`},Te={class:`af-diff-stats`},Ee={key:0,class:`text-green-5`},De={key:1,class:`text-red-5 q-ml-xs`},Oe={class:`af-time`},ke={class:`af-diff-sign`},Ae={key:1,class:`af-diff-line af-diff-del`},je={key:0,class:`af-diff-line text-grey-7 text-italic`},Me=[`onClick`],Ne={class:`af-tool-label text-grey-7`},Pe={key:0,class:`af-tool-desc text-grey-8`},Fe={class:`af-time`},Ie={key:0,class:`af-tool-args q-mt-xs rounded-borders`},Le={class:`af-args-pre`},Re={class:`af-text-header row items-center q-mb-xs`},ze={class:`af-time`},Be=[`innerHTML`],Ve=[`onClick`],He={class:`af-system-content text-caption text-amber-6`},Ue={class:`af-time`},We={key:0,class:`af-system-details q-mt-xs rounded-borders`},Ge={class:`af-args-pre`},Ke={key:5,class:`row items-center`},qe={class:`af-error-content text-red-5`},Je={class:`af-time`},Ye={key:6,class:`row items-center`},Xe={class:`af-raw-content text-grey-7`},Ze={class:`af-time`},Qe={class:`scroll-buttons`},w=50,$e=50,T=C(te({__name:`ActivityFeed`,setup(te){function C(e){let t=se.parse(e,{async:!1,breaks:!0,gfm:!0});return oe.sanitize(t)}let{t:T}=re(),E=ie(),et=ae(),D=o(null),O=o(!1),k=o(new Set),A=new Map;function j(e,t){return A.has(e)||A.set(e,Y(t)),A.get(e)}let M=new Map;function tt(e,t){return M.has(e)||M.set(e,C(t)),M.get(e)}let N=m(()=>{let e=E.activityFeed;for(let t=e.length-1;t>=0;t--){let n=e[t];if(n.meta?.sender===`user`)return null;if(n.type===`tool_use`&&n.content===`AskUserQuestion`)return n.id}return null}),P=o(new Map);function nt(e,t,n){P.value.has(e)||P.value.set(e,new Map);let r=P.value.get(e);r.get(t)===n?r.delete(t):r.set(t,n)}function F(e,t,n){return P.value.get(e)?.get(t)===n}function rt(e){let t=P.value.get(e);return!!t&&t.size>0}function it(e,t){let n=E.selectedWorkspaceId;if(!n)return;let r=P.value.get(e);if(!r||r.size===0)return;let i=[];for(let[e,n]of r.entries()){let r=t[e];if(!r)continue;let a=r.options[n];a&&i.push(`${r.question}: ${a.label}`)}i.length>0&&et.sendChatMessage(n,i.join(`
2
- `))}let I=o(-1);function at(){let t=E.activityFeed,n=t.map((e,t)=>({item:e,idx:t})).filter(({item:e})=>e.meta?.sender===`user`);if(n.length===0)return;I.value<=0?I.value=n.length-1:I.value--;let r=n[I.value].idx,i=n[I.value].item.id,a=t.length-r;a>L.value&&(L.value=Math.min(a+10,t.length)),e(()=>{let e=D.value?.querySelector(`[data-item-id="${i}"]`);e&&e.scrollIntoView({behavior:`smooth`,block:`center`})})}n(()=>E.selectedWorkspaceId,()=>{I.value=-1,A.clear(),P.value.clear(),M.clear(),W.clear(),B=0,O.value=!1});let L=o(w),R=m(()=>{let e=E.activityFeed;return e.length<=L.value?e:e.slice(-L.value)});n(()=>E.selectedWorkspaceId,()=>{L.value=w}),n(R,e=>{let t=new Set(e.map(e=>e.id));for(let e of A.keys())t.has(e)||A.delete(e);for(let e of P.value.keys())t.has(e)||P.value.delete(e);for(let e of M.keys())t.has(e)||M.delete(e);for(let e of W.keys())t.has(e)||W.delete(e)},{flush:`post`});let z=o(!1),B=0;function V(){let t=D.value;if(t&&(t.scrollHeight-t.scrollTop-t.clientHeight<50?z.value=!1:t.scrollTop<B&&(z.value=!0),B=t.scrollTop,t.scrollTop<200&&!O.value)){let n=E.activityFeed.length;if(L.value<n){O.value=!0;let r=t.scrollHeight;L.value=Math.min(L.value+$e,n),e(()=>{D.value&&(D.value.scrollTop+=D.value.scrollHeight-r),O.value=!1})}else if(E.selectedWorkspaceId&&E.hasMoreEvents[E.selectedWorkspaceId]!==!1){O.value=!0;let n=t.scrollHeight;E.fetchOlderEvents(E.selectedWorkspaceId).then(t=>{t?(L.value=E.activityFeed.length,e(()=>{D.value&&(D.value.scrollTop+=D.value.scrollHeight-n),O.value=!1})):O.value=!1})}}}function H(){if(z.value)return;let e=D.value;e&&(e.scrollTop=e.scrollHeight)}function ot(){z.value=!1,L.value=w,e(()=>{let e=D.value;e&&(e.scrollTop=e.scrollHeight)})}n(()=>E.activityFeed.length,()=>{e(H)}),i(()=>{D.value?.addEventListener(`scroll`,V),e(H)}),a(()=>{D.value?.removeEventListener(`scroll`,V)});function U(e){return new Date(e).toLocaleTimeString(void 0,{hour:`2-digit`,minute:`2-digit`,second:`2-digit`})}function st(e,t){let n=e.split(`
3
- `),r=t.split(`
4
- `),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}let W=new Map;function ct(e,t,n){return W.has(e)||W.set(e,st(t,n)),W.get(e)}function G(e){if(e.type!==`tool_use`)return null;let t=e.meta?.input;if(e.content===`Edit`){if(!t?.file_path)return null;let e=t.file_path,n=t.old_string??``,r=t.new_string??``,i=n.split(`
5
- `),a=r.split(`
6
- `);return{toolName:`Edit`,filePath:e,oldString:n,newString:r,replaceAll:t.replace_all??!1,additions:a.length,deletions:i.length}}if(e.content===`Write`){if(!t?.file_path)return null;let e=t.file_path,n=t.content??``;return{toolName:`Write`,filePath:e,content:n,additions:n.split(`
7
- `).length,deletions:0}}if(e.content===`Bash`){let e=(t?.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){let t=E.selectedWorkspace;if(t){let n=`${t.projectPath}/.worktrees/${t.workingBranch}/`;if(e.startsWith(n))return e.slice(n.length);if(e.startsWith(`${t.projectPath}/`))return e.slice(t.projectPath.length+1)}return e.startsWith(`/`)&&e.split(`/`).length>4?`…/${e.split(`/`).slice(-3).join(`/`)}`:e}function lt(e){return e.split(`/`).pop()??e}function q(e){let t=lt(e),n=t.lastIndexOf(`.`);return n>=0?t.substring(n+1):``}function ut(e){return{ts:`JS`,tsx:`TS`,js:`JS`,jsx:`JS`,vue:`VU`,py:`PY`,rs:`RS`,go:`GO`,java:`JA`,php:`PH`,css:`CS`,scss:`SC`,html:`HT`,md:`MD`,json:`JS`,sql:`SQ`,sh:`SH`,yaml:`YA`,yml:`YA`,toml:`TM`}[e.toLowerCase()]??e.substring(0,2).toUpperCase()}function dt(e){return{ts:`blue-5`,tsx:`blue-5`,js:`yellow-8`,jsx:`yellow-8`,vue:`green-5`,py:`blue-4`,rs:`orange-5`,go:`cyan-5`,java:`red-5`,php:`indigo-4`,css:`purple-4`,scss:`pink-4`,html:`orange-4`,md:`grey-5`,json:`yellow-6`}[e.toLowerCase()]??`grey-5`}function ft(e){let t=e.toLowerCase();return t.includes(`read`)||t.includes(`grep`)||t.includes(`glob`)?`search`:t.includes(`write`)||t.includes(`edit`)?`edit`:t.includes(`bash`)||t.includes(`terminal`)?`terminal`:t.includes(`agent`)||t.includes(`task`)?`smart_toy`:`build`}function pt(e){switch(e.type){case`text`:return e.meta?.sender===`system-prompt`?`af-item--prompt`:e.meta?.sender===`user`?`af-item--user`:`af-item--text`;case`system`:return`af-item--system`;case`error`:return`af-item--error`;case`tool_use`:return`af-item--tool`;case`raw`:return`af-item--raw`;default:return``}}function mt(e){switch(e.meta?.sender){case`system-prompt`:return T(`activityFeed.initialPrompt`);case`user`:return T(`activityFeed.you`);default:return T(`activityFeed.agent`)}}function ht(e){switch(e.meta?.sender){case`system-prompt`:return`text-indigo-4`;case`user`:return`text-green-4`;default:return`text-blue-4`}}function gt(e){if(e.content===`Skill`&&e.meta){let t=e.meta.input;if(t&&typeof t.skill==`string`)return`Skill — ${t.skill}`}return e.content}function J(e){if(!e.meta)return``;let t=e.meta.input;if(!t)return``;if(typeof t.description==`string`)return t.description;if(typeof t.file_path==`string`)return K(t.file_path);if(typeof t.pattern==`string`)return`${typeof t.path==`string`?`${K(t.path)}/`:``}${t.pattern}`;if(typeof t.path==`string`)return K(t.path);if(typeof t.command==`string`){let e=t.command;return e.length>80?`${e.slice(0,80)}…`:e}return``}function Y(e){if(e.type!==`tool_use`||e.content!==`AskUserQuestion`)return null;let t=e.meta?.input;return!t?.questions||!Array.isArray(t.questions)?null:t.questions.filter(e=>Array.isArray(e.options)&&e.options.length>0).map(e=>({question:e.question??``,options:e.options.map(e=>({label:e.label??``,description:e.description??``}))}))}function X(e){if(!e.meta)return!1;let t=e.meta;return t.input!==void 0&&t.input!==null}function Z(e){k.value.has(e)?k.value.delete(e):k.value.add(e)}function Q(e){return k.value.has(e)}function _t(e){if(!e.meta)return``;let t=e.meta;if(!t.input)return``;try{return JSON.stringify(t.input,null,2)}catch{return String(t.input)}}let vt=m(()=>E.activityFeed.some(e=>e.meta?.sender===`user`));function $(e){return e.type!==`system`||!e.meta?!1:Object.keys(e.meta).length>0}function yt(e){if(!e.meta)return``;try{return JSON.stringify(e.meta,null,2)}catch{return``}}return(e,n)=>(t(),h(`div`,{ref_key:`feedContainer`,ref:D,class:`activity-feed q-pa-sm`},[ee(E).activityFeed.length===0?(t(),h(`div`,ce,[f(v,{name:`forum`,size:`48px`,color:`grey-8`}),_(`div`,le,l(e.$t(`activityFeed.empty`)),1),_(`div`,ue,l(e.$t(`activityFeed.emptyHint`)),1)])):d(``,!0),O.value?(t(),h(`div`,de,[f(b,{size:`24px`,color:`grey-6`})])):d(``,!0),(t(!0),h(g,null,r(R.value,i=>(t(),h(`div`,{key:i.id,"data-item-id":i.id,class:c([`af-item text-caption rounded-borders`,pt(i)])},[i.type===`tool_use`&&j(i.id,i)?(t(),h(g,{key:0},[_(`div`,pe,[f(v,{name:`help_outline`,size:`14px`,color:`indigo-4`}),_(`span`,me,l(e.$t(`activityFeed.question`)),1),f(x),_(`span`,he,l(U(i.timestamp)),1)]),(t(!0),h(g,null,r(j(i.id,i),(e,n)=>(t(),h(`div`,{key:n,class:`q-mt-sm`},[e.question?(t(),h(`div`,ge,l(e.question),1)):d(``,!0),_(`div`,_e,[(t(!0),h(g,null,r(e.options,(e,n)=>(t(),h(`div`,{key:n,class:`af-ask-option-item text-caption text-grey-5`},[_(`span`,ve,l(n+1)+`. `+l(e.label),1),e.description?(t(),h(`span`,ye,` — `+l(e.description),1)):d(``,!0)]))),128))]),i.id===N.value?(t(),h(`div`,be,[(t(!0),h(g,null,r(e.options,(e,r)=>(t(),u(y,{key:e.label,"no-caps":``,dense:``,outline:!F(i.id,n,r),unelevated:F(i.id,n,r),color:F(i.id,n,r)?`indigo-6`:`indigo-4`,"text-color":F(i.id,n,r)?`white`:void 0,class:`af-option-btn`,onClick:e=>nt(i.id,n,r)},{default:s(()=>[p(l(e.label),1)]),_:2},1032,[`outline`,`unelevated`,`color`,`text-color`,`onClick`]))),128))])):d(``,!0)]))),128)),i.id===N.value?(t(),h(`div`,xe,[f(y,{"no-caps":``,unelevated:``,dense:``,color:`indigo-6`,label:e.$t(`activityFeed.sendAnswers`),icon:`send`,disable:!rt(i.id),onClick:e=>it(i.id,j(i.id,i))},null,8,[`label`,`disable`,`onClick`])])):d(``,!0)],64)):i.type===`tool_use`&&G(i)?(t(),h(`div`,{key:1,class:`af-file-change cursor-pointer`,onClick:e=>Z(i.id)},[_(`div`,Ce,[_(`span`,{class:c([`af-lang-badge`,`text-${dt(q(G(i).filePath))}`])},l(ut(q(G(i).filePath))),3),_(`span`,we,l(K(G(i).filePath)),1),_(`span`,Te,[G(i).additions?(t(),h(`span`,Ee,`+`+l(G(i).additions),1)):d(``,!0),G(i).deletions?(t(),h(`span`,De,`-`+l(G(i).deletions),1)):d(``,!0)]),f(v,{name:Q(i.id)?`expand_less`:`expand_more`,size:`14px`,color:`grey-6`},null,8,[`name`]),f(x),_(`span`,Oe,l(U(i.timestamp)),1)]),Q(i.id)?(t(),h(`div`,{key:0,class:`af-diff-body q-mt-xs`,onClick:n[0]||=ne(()=>{},[`stop`])},[G(i).toolName===`Edit`?(t(!0),h(g,{key:0},r(ct(i.id,G(i).oldString??``,G(i).newString??``),(e,n)=>(t(),h(`div`,{key:n,class:c([`af-diff-line`,{"af-diff-del":e.type===`del`,"af-diff-add":e.type===`add`,"af-diff-context":e.type===`context`}])},[_(`span`,ke,l(e.type===`del`?`-`:e.type===`add`?`+`:` `),1),p(l(e.content),1)],2))),128)):G(i).toolName===`Bash:rm`?(t(),h(`div`,Ae,[...n[1]||=[_(`span`,{class:`af-diff-sign`},`-`,-1),p(`File deleted`,-1)]])):(t(),h(g,{key:2},[(t(!0),h(g,null,r((G(i).content??``).split(`
8
- `).slice(0,30),(e,r)=>(t(),h(`div`,{key:`w-${r}`,class:`af-diff-line af-diff-add`},[n[2]||=_(`span`,{class:`af-diff-sign`},`+`,-1),p(l(e),1)]))),128)),(G(i).content??``).split(`
9
- `).length>30?(t(),h(`div`,je,`… `+l((G(i).content??``).split(`
10
- `).length-30)+` more lines`,1)):d(``,!0)],64))])):d(``,!0)],8,Se)):i.type===`tool_use`?(t(),h(g,{key:2},[_(`div`,{class:c([`af-tool row items-center q-gutter-xs`,{"cursor-pointer":X(i)}]),onClick:e=>X(i)&&Z(i.id)},[f(v,{name:ft(i.content),size:`14px`,color:`grey-6`},null,8,[`name`]),_(`span`,Ne,l(gt(i)),1),J(i)?(t(),h(`span`,Pe,`— `+l(J(i)),1)):d(``,!0),X(i)?(t(),u(v,{key:1,name:Q(i.id)?`expand_less`:`expand_more`,size:`14px`,color:`grey-7`},null,8,[`name`])):d(``,!0),f(x),_(`span`,Fe,l(U(i.timestamp)),1)],10,Me),Q(i.id)?(t(),h(`div`,Ie,[_(`pre`,Le,l(_t(i)),1)])):d(``,!0)],64)):i.type===`text`?(t(),h(g,{key:3},[_(`div`,Re,[_(`span`,{class:c([`text-caption text-weight-bold`,ht(i)])},l(mt(i)),3),i.meta?.pending?(t(),u(b,{key:0,size:`14px`,color:`grey-5`,class:`q-ml-sm`})):d(``,!0),f(x),_(`span`,ze,l(U(i.timestamp)),1)]),_(`div`,{class:`af-text-content af-markdown`,innerHTML:tt(i.id,i.content)},null,8,Be)],64)):i.type===`system`?(t(),h(g,{key:4},[_(`div`,{class:c([`row items-center`,{"cursor-pointer":$(i)}]),onClick:e=>$(i)&&Z(i.id)},[f(v,{name:`info`,size:`14px`,color:`amber-6`,class:`q-mr-xs`}),_(`span`,He,l(i.content),1),$(i)?(t(),u(v,{key:0,name:Q(i.id)?`expand_less`:`expand_more`,size:`14px`,color:`amber-8`,class:`q-ml-xs`},null,8,[`name`])):d(``,!0),f(x),_(`span`,Ue,l(U(i.timestamp)),1)],10,Ve),Q(i.id)&&$(i)?(t(),h(`div`,We,[_(`pre`,Ge,l(yt(i)),1)])):d(``,!0)],64)):i.type===`error`?(t(),h(`div`,Ke,[f(v,{name:`error`,size:`14px`,color:`red-5`,class:`q-mr-xs`}),_(`span`,qe,l(i.content),1),f(x),_(`span`,Je,l(U(i.timestamp)),1)])):(t(),h(`div`,Ye,[_(`span`,Xe,l(i.content),1),f(x),_(`span`,Ze,l(U(i.timestamp)),1)]))],10,fe))),128)),_(`div`,Qe,[vt.value?(t(),u(y,{key:0,round:``,dense:``,size:`sm`,icon:`person_search`,color:`indigo-8`,class:`scroll-btn`,onClick:at},{default:s(()=>[f(S,null,{default:s(()=>[p(l(e.$t(`activityFeed.goToPrevious`)),1)]),_:1})]),_:1})):d(``,!0),z.value?(t(),u(y,{key:1,round:``,dense:``,size:`sm`,icon:`keyboard_double_arrow_down`,color:`indigo-8`,class:`scroll-btn`,onClick:ot},{default:s(()=>[f(S,null,{default:s(()=>[p(l(e.$t(`activityFeed.scrollToBottom`)),1)]),_:1})]),_:1})):d(``,!0)])],512))}}),[[`__scopeId`,`data-v-c5b7e063`]]);export{T as default};
@@ -1 +0,0 @@
1
- .activity-feed[data-v-c5b7e063]{flex-direction:column;gap:4px;display:flex;position:relative;overflow:hidden auto}.af-item[data-v-c5b7e063]{word-break:break-word;overflow-wrap:break-word;flex-shrink:0;padding:6px 10px;overflow-x:hidden}.af-time[data-v-c5b7e063]{color:#555;flex-shrink:0;font-size:10px}.af-tool-label[data-v-c5b7e063]{font-family:Roboto Mono,monospace;font-size:11px}.af-tool-desc[data-v-c5b7e063]{text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.af-ask-option-item[data-v-c5b7e063]{padding:2px 0}.af-tool-args[data-v-c5b7e063]{background-color:#ffffff0a;padding:6px 8px;overflow-x:auto}.af-args-pre[data-v-c5b7e063]{color:#888;white-space:pre-wrap;word-break:break-word;margin:0;font-family:Roboto Mono,monospace;font-size:10px}.af-file-change[data-v-c5b7e063]{background:#ffffff08;border:1px solid #ffffff0f;border-radius:6px;padding:6px 8px}.af-file-header[data-v-c5b7e063]{font-family:Roboto Mono,monospace;font-size:11px}.af-lang-badge[data-v-c5b7e063]{text-align:center;background:#ffffff0f;border-radius:3px;min-width:20px;padding:1px 4px;font-family:Roboto Mono,monospace;font-size:9px;font-weight:700}.af-file-path[data-v-c5b7e063]{max-width:70%;font-size:11px}.af-diff-stats[data-v-c5b7e063]{white-space:nowrap;font-family:Roboto Mono,monospace;font-size:10px}.af-diff-body[data-v-c5b7e063]{background:#0003;border-radius:4px;max-height:300px;padding:4px 0;overflow:auto}.af-diff-line[data-v-c5b7e063]{white-space:pre;min-width:-moz-fit-content;min-width:fit-content;padding:0 8px;font-family:Roboto Mono,monospace;font-size:10px;line-height:1.5}.af-diff-sign[data-v-c5b7e063]{-webkit-user-select:none;user-select:none;width:12px;display:inline-block}.af-diff-del[data-v-c5b7e063]{color:#f85149;background:#f851491a}.af-diff-add[data-v-c5b7e063]{color:#3fb950;background:#3fb9501a}.af-diff-context[data-v-c5b7e063]{color:#8b949e}.af-item--text[data-v-c5b7e063]{background-color:#1a2a3a;border-left:3px solid #3b82f6}.af-item--user[data-v-c5b7e063]{background-color:#1a2a1a;border-left:3px solid #22c55e}.af-item--prompt[data-v-c5b7e063]{background-color:#1a1a2e;border-left:3px solid #6c63ff}.af-text-content[data-v-c5b7e063]{color:#d0d0d0;word-break:break-word;line-height:1.5}.af-markdown[data-v-c5b7e063] p{margin:0 0 8px}.af-markdown[data-v-c5b7e063] p:last-child{margin-bottom:0}.af-markdown[data-v-c5b7e063] h1,.af-markdown[data-v-c5b7e063] h2,.af-markdown[data-v-c5b7e063] h3{color:#e0e0e0;margin:12px 0 6px}.af-markdown[data-v-c5b7e063] h1{font-size:16px}.af-markdown[data-v-c5b7e063] h2{font-size:14px}.af-markdown[data-v-c5b7e063] h3{font-size:13px}.af-markdown[data-v-c5b7e063] ul,.af-markdown[data-v-c5b7e063] ol{margin:4px 0;padding-left:20px}.af-markdown[data-v-c5b7e063] li{margin:2px 0}.af-markdown[data-v-c5b7e063] code{background-color:#ffffff14;border-radius:3px;padding:1px 4px;font-family:Roboto Mono,monospace;font-size:11px}.af-markdown[data-v-c5b7e063] pre{background-color:#0000004d;border-radius:4px;margin:6px 0;padding:8px;overflow-x:auto}.af-markdown[data-v-c5b7e063] pre code{background:0 0;padding:0}.af-markdown[data-v-c5b7e063] strong{color:#fff}.af-markdown[data-v-c5b7e063] a{color:#6c63ff}.af-markdown[data-v-c5b7e063] blockquote{color:#aaa;border-left:3px solid #6c63ff;margin:6px 0;padding:4px 12px}.af-markdown[data-v-c5b7e063] table{border-collapse:collapse;margin:6px 0;font-size:11px}.af-markdown[data-v-c5b7e063] table th,.af-markdown[data-v-c5b7e063] table td{border:1px solid #2a2a4a;padding:4px 8px}.af-markdown[data-v-c5b7e063] table th{background-color:#ffffff0d}.af-item--system[data-v-c5b7e063]{background-color:#2a2a1a;border-left:3px solid #f59e0b}.af-system-details[data-v-c5b7e063]{background-color:#ffffff0a;padding:6px 8px;overflow-x:auto}.af-system-content[data-v-c5b7e063]{white-space:pre-wrap}.af-item--error[data-v-c5b7e063]{background-color:#2a1a1a;border-left:3px solid #ef4444}.af-item--raw[data-v-c5b7e063]{white-space:pre-wrap;font-family:Roboto Mono,monospace}.scroll-buttons[data-v-c5b7e063]{align-self:flex-end;gap:6px;margin-right:8px;display:flex;position:sticky;bottom:8px}.scroll-btn[data-v-c5b7e063]{opacity:.7;transition:opacity .15s}.scroll-btn[data-v-c5b7e063]:hover{opacity:1}