@hienlh/ppm 0.17.16 → 0.17.18

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 (464) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/assets/skills/ppm/SKILL.md +1 -1
  3. package/assets/skills/ppm/references/cli-reference.md +4 -4
  4. package/assets/skills/ppm/references/http-api.md +5 -1
  5. package/dist/web/assets/{ai-resource-editor-CsossqDO.js → ai-resource-editor-BgWg-ty8.js} +1 -1
  6. package/dist/web/assets/{angular-html-CkxY2iBQ.js → angular-html-2g8YGiJY.js} +1 -1
  7. package/dist/web/assets/{angular-ts-BePeGkkC.js → angular-ts-BwSmoLfh.js} +1 -1
  8. package/dist/web/assets/{api-settings-DAk7D-NP.js → api-settings-BJTjIG4U.js} +1 -1
  9. package/dist/web/assets/{api-themes-84MT-XqR.js → api-themes-DuLpbQFV.js} +1 -1
  10. package/dist/web/assets/{apl-DBS7PWvB.js → apl-B44SMKHp.js} +1 -1
  11. package/dist/web/assets/architecture-PBZL5I3N-gLlhsjM9.js +1 -0
  12. package/dist/web/assets/{astro-Clbk1k4h.js → astro-E5620wek.js} +1 -1
  13. package/dist/web/assets/{audio-preview-DIkokvPM.js → audio-preview-CLn1LTxc.js} +1 -1
  14. package/dist/web/assets/{blade-BeBsBuGS.js → blade-D-VQWAHB.js} +1 -1
  15. package/dist/web/assets/{bsl-DYigT3JZ.js → bsl-Bq5qFzpU.js} +1 -1
  16. package/dist/web/assets/{cairo-BjpO78wI.js → cairo-Cn9-ZKRa.js} +1 -1
  17. package/dist/web/assets/chat-tab-tTUVo4G3.js +13 -0
  18. package/dist/web/assets/{cobol-CpuUx69s.js → cobol-IM3E3iCH.js} +1 -1
  19. package/dist/web/assets/code-editor-DMfUjdk5.js +10 -0
  20. package/dist/web/assets/{coffee-FoFd2Cxo.js → coffee-24wG7ekn.js} +1 -1
  21. package/dist/web/assets/{conflict-editor-Dqm1bkyL.js → conflict-editor-BosR4PW3.js} +3 -3
  22. package/dist/web/assets/{cpp-CO_nPfdr.js → cpp-DH9Cqt4g.js} +1 -1
  23. package/dist/web/assets/{crystal-D-UygAAF.js → crystal-4nZYHhdi.js} +1 -1
  24. package/dist/web/assets/{csv-preview-CvP4xzug.js → csv-preview-BqszurHp.js} +1 -1
  25. package/dist/web/assets/data-grid-overlay-editor-DxdOKRAY.js +1 -0
  26. package/dist/web/assets/data-grid-types-CzEQU9PJ.js +1 -0
  27. package/dist/web/assets/database-viewer-CiomoOU6.js +1 -0
  28. package/dist/web/assets/dialog-BpkAWOy8.js +1 -0
  29. package/dist/web/assets/{diff-viewer-BfWixe_z.js → diff-viewer-aOrOaGWx.js} +1 -1
  30. package/dist/web/assets/dist-Bn3E6I4R.js +11 -0
  31. package/dist/web/assets/{docx-preview-CguUoTKe.js → docx-preview-BAzpJdua.js} +1 -1
  32. package/dist/web/assets/{edge-BSzxzX11.js → edge-DTI9d2Qi.js} +1 -1
  33. package/dist/web/assets/{elixir-DH1-Mrep.js → elixir-Ys0wioho.js} +1 -1
  34. package/dist/web/assets/{elm-B0ln5J70.js → elm-Cihc9TAz.js} +1 -1
  35. package/dist/web/assets/{erb-GIz6NnlZ.js → erb-BwYBWK2-.js} +1 -1
  36. package/dist/web/assets/{erlang-B8MgYhib.js → erlang-CzlD_Joo.js} +1 -1
  37. package/dist/web/assets/esm-JLkfN0k-.js +2 -0
  38. package/dist/web/assets/{extension-webview-DTpFt1eL.js → extension-webview-CKUUvgOr.js} +1 -1
  39. package/dist/web/assets/{fortran-fixed-form-D_-TrK6U.js → fortran-fixed-form-CT90EtSp.js} +1 -1
  40. package/dist/web/assets/{fsharp-C9cLCk2b.js → fsharp-LasxezlU.js} +1 -1
  41. package/dist/web/assets/{gdresource-CsZ3lXwY.js → gdresource-XqgPdfiE.js} +1 -1
  42. package/dist/web/assets/{git-commit-xmq0KqrA.js → git-commit-CZBgPolw.js} +1 -1
  43. package/dist/web/assets/{git-log-panel-DUX7N5tN.js → git-log-panel-B6GFWhj4.js} +1 -1
  44. package/dist/web/assets/{git-rebase-D3O-jsSX.js → git-rebase-C2VzKDB1.js} +1 -1
  45. package/dist/web/assets/gitGraph-HDMCJU4V-BKGq--Jw.js +1 -0
  46. package/dist/web/assets/glide-data-grid-z9FPvRBG.js +138 -0
  47. package/dist/web/assets/{glimmer-js-g74gESrC.js → glimmer-js-DfjWcOLD.js} +1 -1
  48. package/dist/web/assets/{glimmer-ts-DF4aDWHT.js → glimmer-ts-DBldQATc.js} +1 -1
  49. package/dist/web/assets/{glsl-D5LmfW7l.js → glsl-CzNv1NPP.js} +1 -1
  50. package/dist/web/assets/{graphql-_KI4RuXL.js → graphql-DDyLtQb4.js} +1 -1
  51. package/dist/web/assets/group-chat-tab-Bu31mwmF.js +1 -0
  52. package/dist/web/assets/{hack-B1NaTC39.js → hack-CtoYNYkC.js} +1 -1
  53. package/dist/web/assets/{haml-DDFXadZ4.js → haml-CnBVap7G.js} +1 -1
  54. package/dist/web/assets/{handlebars-Bp-Jq3K9.js → handlebars-Dg51esUH.js} +1 -1
  55. package/dist/web/assets/{html-DyB9_5Tj.js → html-Ec2_evSy.js} +1 -1
  56. package/dist/web/assets/{html-derivative-CC60wi-3.js → html-derivative-Cy-F7ZqL.js} +1 -1
  57. package/dist/web/assets/{http-C-9qngH4.js → http-C236v8Tg.js} +1 -1
  58. package/dist/web/assets/{hurl-BtxgpNDs.js → hurl-0rMD2gPU.js} +1 -1
  59. package/dist/web/assets/{hxml-DOoOS9op.js → hxml-e5uS5jTD.js} +1 -1
  60. package/dist/web/assets/{image-preview-CZg6FQ3w.js → image-preview-CaX4gpN6.js} +1 -1
  61. package/dist/web/assets/index-4M24PSHW.js +50 -0
  62. package/dist/web/assets/index-BYFXiFsa.css +2 -0
  63. package/dist/web/assets/info-3K5VOQVL-BOhGriBF.js +1 -0
  64. package/dist/web/assets/{jinja-cN-150ao.js → jinja-DybFmhfq.js} +1 -1
  65. package/dist/web/assets/{jison-CqjsqPq5.js → jison-hkHbixdZ.js} +1 -1
  66. package/dist/web/assets/{julia-C-I5m8W4.js → julia-QnxY6sRE.js} +1 -1
  67. package/dist/web/assets/{just-CgsSW9zs.js → just-1Rs-nm8g.js} +1 -1
  68. package/dist/web/assets/keybindings-store-Cv-U85V6.js +1 -0
  69. package/dist/web/assets/{latex-BFzR9N2d.js → latex-jEBQqA7u.js} +1 -1
  70. package/dist/web/assets/{liquid-BWfr-_iC.js → liquid-DfJaZpxG.js} +1 -1
  71. package/dist/web/assets/{lua-DsXVun6g.js → lua-ti0Jr6-C.js} +1 -1
  72. package/dist/web/assets/{markdown-renderer-CiQkvWP_.js → markdown-renderer-BNXJygz9.js} +3 -3
  73. package/dist/web/assets/markdown-renderer-DCsEEdo7.js +1 -0
  74. package/dist/web/assets/{marko-DWBbo09n.js → marko-DNj4AhPg.js} +1 -1
  75. package/dist/web/assets/{mdc-CUMe9LOG.js → mdc-DPFwQFe4.js} +1 -1
  76. package/dist/web/assets/mobile-bottom-sheet--5_YEewV.js +1 -0
  77. package/dist/web/assets/{nextflow-CoYbADOm.js → nextflow-BUrFV5aB.js} +1 -1
  78. package/dist/web/assets/{nginx-z-JRZREw.js → nginx-BrW9NdOb.js} +1 -1
  79. package/dist/web/assets/{nim-Ci4LHyBm.js → nim-DS_6PF2p.js} +1 -1
  80. package/dist/web/assets/notification-store-DADt1gJH.js +1 -0
  81. package/dist/web/assets/{number-overlay-editor-lnnuUpPI.js → number-overlay-editor-CReXWiQ1.js} +1 -1
  82. package/dist/web/assets/packet-RMMSAZCW-CdLFnL8a.js +1 -0
  83. package/dist/web/assets/{panel-store-D6RV_ztx.js → panel-store-DnbT1c8L.js} +1 -1
  84. package/dist/web/assets/{pdf-preview-B6ZqANpw.js → pdf-preview-DZzbm9A4.js} +1 -1
  85. package/dist/web/assets/{perl-pbDimKOO.js → perl-ytm9vKO_.js} +1 -1
  86. package/dist/web/assets/{php-B003E5v7.js → php-DWTD1Usw.js} +1 -1
  87. package/dist/web/assets/pie-UPGHQEXC-COC-h0zz.js +1 -0
  88. package/dist/web/assets/{trash-2-CbbD5QF1.js → plus-DcuJuDC3.js} +1 -1
  89. package/dist/web/assets/postgres-viewer-BxpeiVkt.js +3 -0
  90. package/dist/web/assets/{project-store-Cekcjcom.js → project-store-Sbnqc41N.js} +1 -1
  91. package/dist/web/assets/{pug-AoueAWJo.js → pug-Bt7KL3_r.js} +1 -1
  92. package/dist/web/assets/{qml-CepxioQA.js → qml-Dexdon5J.js} +1 -1
  93. package/dist/web/assets/radar-KQ55EAFF-CiCfdJNL.js +1 -0
  94. package/dist/web/assets/{razor-lIvU-Gu-.js → razor-DnYCSl0G.js} +1 -1
  95. package/dist/web/assets/{rst-Dn-dwElE.js → rst-BEFYzwF8.js} +1 -1
  96. package/dist/web/assets/{ruby-DmsD1Iz5.js → ruby-BPaXFEjN.js} +1 -1
  97. package/dist/web/assets/{sas-6IM2P4YP.js → sas-CywiNBp8.js} +1 -1
  98. package/dist/web/assets/{scss-RgYEZQqh.js → scss-CGk0L6hN.js} +1 -1
  99. package/dist/web/assets/{settings-store-BkSf4SPQ.js → settings-store-DkRZhj-g.js} +2 -2
  100. package/dist/web/assets/settings-tab-DJOhpW09.js +1 -0
  101. package/dist/web/assets/{shaderlab-B0MNEwt4.js → shaderlab-Byjk0kH7.js} +1 -1
  102. package/dist/web/assets/{shellsession-F8S4LrQI.js → shellsession-DbA70QB6.js} +1 -1
  103. package/dist/web/assets/{soy-DDlMbX7u.js → soy-Bm97kWiA.js} +1 -1
  104. package/dist/web/assets/sparkles-B3DmG8PJ.js +1 -0
  105. package/dist/web/assets/{sparql-kthmGAAe.js → sparql-CawLnniT.js} +1 -1
  106. package/dist/web/assets/{sql-query-editor-BYNM0RRl.js → sql-query-editor-9TwIPvRj.js} +1 -1
  107. package/dist/web/assets/sqlite-viewer-D-3i_x28.js +1 -0
  108. package/dist/web/assets/{stata-Tfbew8La.js → stata-RifEDtNO.js} +1 -1
  109. package/dist/web/assets/{surrealql-C0LIGqRx.js → surrealql-DGRQa5Lm.js} +1 -1
  110. package/dist/web/assets/{svelte-CfTy8CGF.js → svelte-w9Hb12aM.js} +1 -1
  111. package/dist/web/assets/system-monitor-tab-CuVVp2wt.js +1 -0
  112. package/dist/web/assets/{tab-store-CcLQfu8H.js → tab-store-vn0viMOP.js} +1 -1
  113. package/dist/web/assets/{templ-V5H1Sbfy.js → templ-BpUwjJXV.js} +1 -1
  114. package/dist/web/assets/{terminal-tab-CK3lX-0Y.js → terminal-tab-DrSqSrdX.js} +2 -2
  115. package/dist/web/assets/{tex-C3H3uMZf.js → tex-DJFSsuTD.js} +1 -1
  116. package/dist/web/assets/tool-cards-CtE_JnF-.js +7 -0
  117. package/dist/web/assets/treemap-KZPCXAKY-CZwuSCtA.js +1 -0
  118. package/dist/web/assets/{ts-tags-7AXgG5YS.js → ts-tags-vp9Qvx2O.js} +1 -1
  119. package/dist/web/assets/{twig-BSyS38T3.js → twig-CoazxjRB.js} +1 -1
  120. package/dist/web/assets/{use-blob-url-CpyRr7Cx.js → use-blob-url-DdXgVqfz.js} +1 -1
  121. package/dist/web/assets/use-is-mobile-CE6AamG6.js +1 -0
  122. package/dist/web/assets/{use-monaco-theme-Jj2OxkAE.js → use-monaco-theme-BvzqffqR.js} +1 -1
  123. package/dist/web/assets/{vendor-mermaid-DGOXKrJC.js → vendor-mermaid-DPpYkrMf.js} +193 -193
  124. package/dist/web/assets/{video-preview-BOHxkwKG.js → video-preview-DxdyVATR.js} +1 -1
  125. package/dist/web/assets/{vue-C4d_br9u.js → vue-CnshsiXd.js} +1 -1
  126. package/dist/web/assets/{vue-html-CGdoz4vW.js → vue-html-CZshynFp.js} +1 -1
  127. package/dist/web/assets/{vue-vine-CqNolQlN.js → vue-vine-C5gtp_T1.js} +1 -1
  128. package/dist/web/assets/{xml-UmCmu-Od.js → xml-BEGJasgj.js} +1 -1
  129. package/dist/web/assets/{xsl-DJG6USGh.js → xsl-DXpRLjYk.js} +1 -1
  130. package/dist/web/index.html +22 -20
  131. package/dist/web/monacoeditorwork/css.worker.bundle.js +122 -122
  132. package/dist/web/monacoeditorwork/editor.worker.bundle.js +78 -78
  133. package/dist/web/monacoeditorwork/html.worker.bundle.js +110 -110
  134. package/dist/web/monacoeditorwork/json.worker.bundle.js +108 -108
  135. package/dist/web/monacoeditorwork/ts.worker.bundle.js +81 -81
  136. package/dist/web/sw.js +1 -1
  137. package/docs/journal/binary-self-upgrade-260724.md +66 -0
  138. package/nul/docs/claude-agent-sdk-reference.md +780 -0
  139. package/nul/docs/code-standards.md +1080 -0
  140. package/nul/docs/codebase-summary.md +916 -0
  141. package/nul/docs/deployment-guide.md +702 -0
  142. package/nul/docs/design-guidelines.md +767 -0
  143. package/nul/docs/extension-development-guide.md +629 -0
  144. package/nul/docs/journals/260414-1400-ext-git-graph-port-complete.md +147 -0
  145. package/nul/docs/journals/260414-1452-git-graph-faithful-port.md +144 -0
  146. package/nul/docs/journals/260414-1810-git-graph-ui-improvements-complete.md +261 -0
  147. package/nul/docs/journals/260414-2001-bundled-extensions.md +219 -0
  148. package/nul/docs/journals/260415-frontend-memory-optimization.md +73 -0
  149. package/nul/docs/lessons-learned.md +70 -0
  150. package/nul/docs/project-changelog.md +574 -0
  151. package/nul/docs/project-overview-pdr.md +205 -0
  152. package/nul/docs/project-roadmap.md +222 -0
  153. package/nul/docs/system-architecture.md +1961 -0
  154. package/package.json +1 -1
  155. package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
  156. package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
  157. package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
  158. package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
  159. package/spike-group-channel.mjs +156 -0
  160. package/spike-group-chat.mjs +181 -0
  161. package/src/server/routes/group-chat.ts +118 -9
  162. package/src/services/group-chat/agent-runner.ts +15 -2
  163. package/src/services/group-chat/context-window.ts +15 -0
  164. package/src/services/group-chat/group-chat.service.ts +178 -79
  165. package/src/services/group-chat/group-chat.store.ts +30 -0
  166. package/src/services/group-chat/responder-router.ts +110 -0
  167. package/src/services/group-chat/transcript-archive.ts +18 -0
  168. package/src/services/group-chat/turn-engine.ts +183 -105
  169. package/src/services/jsonl-transcript-parser.ts +36 -0
  170. package/src/types/group-chat-ws.ts +2 -2
  171. package/src/types/group-chat.ts +33 -4
  172. package/src/web/components/group-chat/group-chat-tab.tsx +109 -14
  173. package/src/web/components/group-chat/group-create-dialog.tsx +10 -3
  174. package/src/web/components/group-chat/group-full-transcript-view.tsx +9 -8
  175. package/src/web/components/group-chat/group-member-edit-dialog.tsx +108 -0
  176. package/src/web/components/group-chat/group-member-roster.tsx +51 -23
  177. package/src/web/components/group-chat/group-message-item.tsx +45 -26
  178. package/src/web/components/group-chat/group-transcript-messages.tsx +126 -0
  179. package/src/web/components/layout/cloud-share-popover.tsx +10 -2
  180. package/src/web/components/layout/mobile-drawer-tab-bar.tsx +10 -0
  181. package/src/web/components/layout/mobile-drawer.tsx +26 -9
  182. package/src/web/components/layout/nav-section-rail.tsx +8 -2
  183. package/src/web/components/layout/upgrade-button.tsx +33 -11
  184. package/src/web/hooks/use-group-chat.ts +8 -1
  185. package/src/web/lib/api-group-chat.ts +47 -4
  186. package/src/web/lib/sidebar-tabs/tab-registry.ts +3 -1
  187. package/dist/web/assets/architecture-PBZL5I3N-NAS99xy0.js +0 -1
  188. package/dist/web/assets/chat-tab-Bh6l253v.js +0 -18
  189. package/dist/web/assets/click-outside-container-DXZVmhaX.js +0 -1
  190. package/dist/web/assets/code-editor-B7F-d4DK.js +0 -10
  191. package/dist/web/assets/data-grid-overlay-editor-CUlFsD-D.js +0 -1
  192. package/dist/web/assets/database-viewer-5oNTPgjF.js +0 -1
  193. package/dist/web/assets/dist-BsziT-tR.js +0 -11
  194. package/dist/web/assets/esm-CjVeOTNQ.js +0 -2
  195. package/dist/web/assets/gitGraph-HDMCJU4V-B4I3L4Sf.js +0 -1
  196. package/dist/web/assets/glide-data-grid-7vB1SNne.js +0 -138
  197. package/dist/web/assets/group-chat-tab-CNECY-zg.js +0 -1
  198. package/dist/web/assets/index-D2G6kmTs.js +0 -50
  199. package/dist/web/assets/index-DCmrZnq0.css +0 -2
  200. package/dist/web/assets/info-3K5VOQVL-CBbbOe3C.js +0 -1
  201. package/dist/web/assets/input-24Nf_edC.js +0 -1
  202. package/dist/web/assets/keybindings-store-D8Cppv4w.js +0 -1
  203. package/dist/web/assets/notification-store-DOGxkWih.js +0 -1
  204. package/dist/web/assets/packet-RMMSAZCW-fr8-QSTF.js +0 -1
  205. package/dist/web/assets/pie-UPGHQEXC-Biu7_Pbb.js +0 -1
  206. package/dist/web/assets/postgres-viewer-B23xTCSs.js +0 -3
  207. package/dist/web/assets/radar-KQ55EAFF-CMtNRpK6.js +0 -1
  208. package/dist/web/assets/settings-tab-D5hAA1bY.js +0 -1
  209. package/dist/web/assets/sparkles-2ii4lR7b.js +0 -1
  210. package/dist/web/assets/sqlite-viewer-DW9f2Hqj.js +0 -1
  211. package/dist/web/assets/system-monitor-tab-B5f4pgdX.js +0 -1
  212. package/dist/web/assets/treemap-KZPCXAKY-ZYVFjoaN.js +0 -1
  213. package/dist/web/assets/use-websocket-DrDBgIWU.js +0 -1
  214. /package/dist/web/assets/{abap-BZ3J5hF3.js → abap-wT6fgqwv.js} +0 -0
  215. /package/dist/web/assets/{actionscript-3-CkyGfT-8.js → actionscript-3-CqcHpPYj.js} +0 -0
  216. /package/dist/web/assets/{ada-CxNLBoe7.js → ada-DWE3Hn4E.js} +0 -0
  217. /package/dist/web/assets/{andromeeda-BPN5rsDz.js → andromeeda-DpQ-y-Jt.js} +0 -0
  218. /package/dist/web/assets/{apache-C1F5wHL6.js → apache-LRTUP4r3.js} +0 -0
  219. /package/dist/web/assets/{apex-BaNixdO-.js → apex-DwVeH5_h.js} +0 -0
  220. /package/dist/web/assets/{api-client-Dvzcc_EO.js → api-client-BK4NPNoY.js} +0 -0
  221. /package/dist/web/assets/{applescript-Cl_pdcch.js → applescript-DyQiO9JU.js} +0 -0
  222. /package/dist/web/assets/{apply-theme-ORrEdHKT.js → apply-theme-DxKsrDLA.js} +0 -0
  223. /package/dist/web/assets/{ara-B6UAE7M2.js → ara-Cf-OWd2S.js} +0 -0
  224. /package/dist/web/assets/{asciidoc-D9c9Twki.js → asciidoc-gocbKcVK.js} +0 -0
  225. /package/dist/web/assets/{asm-CNmtv7Y9.js → asm-C3meb5HY.js} +0 -0
  226. /package/dist/web/assets/{aurora-x-C9z-NyzR.js → aurora-x-BYcXaCIg.js} +0 -0
  227. /package/dist/web/assets/{awk-BfLHEnKa.js → awk-BcT6nIdN.js} +0 -0
  228. /package/dist/web/assets/{ayu-dark-Bn7trkt_.js → ayu-dark-CLZPoccg.js} +0 -0
  229. /package/dist/web/assets/{ayu-light-Dpya9hPf.js → ayu-light-CTyRQdS2.js} +0 -0
  230. /package/dist/web/assets/{ayu-mirage-Cc0jfABD.js → ayu-mirage-DjqfqZdo.js} +0 -0
  231. /package/dist/web/assets/{ballerina-BtiCmN7T.js → ballerina-BCARR-zK.js} +0 -0
  232. /package/dist/web/assets/{bat-tmFM76zR.js → bat-_yaRjWOb.js} +0 -0
  233. /package/dist/web/assets/{beancount-BjPqRrcH.js → beancount-BPESBMS9.js} +0 -0
  234. /package/dist/web/assets/{berry-D0SQBxN2.js → berry-BSLE-Sf8.js} +0 -0
  235. /package/dist/web/assets/{bibtex-CkjRWQKr.js → bibtex-CvUWWCVx.js} +0 -0
  236. /package/dist/web/assets/{bicep-Bhq28Cxc.js → bicep--vcqqALZ.js} +0 -0
  237. /package/dist/web/assets/{bird2-DFGInYb3.js → bird2-Clclqgh6.js} +0 -0
  238. /package/dist/web/assets/{c-CP8cB4k_.js → c-CSC2LwTp.js} +0 -0
  239. /package/dist/web/assets/{c3-oRytyeWo.js → c3-Cl1_jUnm.js} +0 -0
  240. /package/dist/web/assets/{cadence-Inf1rGb7.js → cadence-BwAD7vSC.js} +0 -0
  241. /package/dist/web/assets/{catppuccin-frappe-DAzribQo.js → catppuccin-frappe-hr0HKoFX.js} +0 -0
  242. /package/dist/web/assets/{catppuccin-latte-B6Y_mCCQ.js → catppuccin-latte-DXEIYlFO.js} +0 -0
  243. /package/dist/web/assets/{catppuccin-macchiato-COLbYqVL.js → catppuccin-macchiato-B2zST7sA.js} +0 -0
  244. /package/dist/web/assets/{catppuccin-mocha-DKWaML1B.js → catppuccin-mocha-CvrdnYv9.js} +0 -0
  245. /package/dist/web/assets/{chevron-right-BBc6ATtm.js → chevron-right-DeUue2aK.js} +0 -0
  246. /package/dist/web/assets/{circle-question-mark-CveWaCux.js → circle-question-mark-PaJgx-3d.js} +0 -0
  247. /package/dist/web/assets/{clarity-_ZLLrrsX.js → clarity-BeQd5TQD.js} +0 -0
  248. /package/dist/web/assets/{clojure-DWGVCg_P.js → clojure-Csdo0vb_.js} +0 -0
  249. /package/dist/web/assets/{cmake-Cwh3mug7.js → cmake-Cf0q7ddg.js} +0 -0
  250. /package/dist/web/assets/{code-7ZAlTvsm.js → code-TekEnDFM.js} +0 -0
  251. /package/dist/web/assets/{codeowners-BdV6CFYi.js → codeowners-CUEzlC7R.js} +0 -0
  252. /package/dist/web/assets/{codeql-NvupDIDf.js → codeql-DxHyogQt.js} +0 -0
  253. /package/dist/web/assets/{common-lisp-B-vtBh5k.js → common-lisp-WLb-tAda.js} +0 -0
  254. /package/dist/web/assets/{coq-M8-FTfiZ.js → coq-DFtMD6Wn.js} +0 -0
  255. /package/dist/web/assets/{csharp-B7Ugdtd-.js → csharp-DEnvXVjs.js} +0 -0
  256. /package/dist/web/assets/{css-86k3Z-YA.js → css-DMPpCfkZ.js} +0 -0
  257. /package/dist/web/assets/{csv-BX5XYhfH.js → csv-DTSgNSaP.js} +0 -0
  258. /package/dist/web/assets/{csv-parser-d56Fzpwc.js → csv-parser-B_xNn07y.js} +0 -0
  259. /package/dist/web/assets/{cue-B7099gQa.js → cue-BEmVIZmd.js} +0 -0
  260. /package/dist/web/assets/{cypher-D99mWVIu.js → cypher-CncQVhvU.js} +0 -0
  261. /package/dist/web/assets/{d-CFE8oQwh.js → d-BzUSBVex.js} +0 -0
  262. /package/dist/web/assets/{dark-plus-BEeeDwdg.js → dark-plus-mysFNRq2.js} +0 -0
  263. /package/dist/web/assets/{dart-HVSWGnI5.js → dart-BBu3nkcE.js} +0 -0
  264. /package/dist/web/assets/{database-BLht7BVM.js → database-COYAMZaw.js} +0 -0
  265. /package/dist/web/assets/{dax-DjDgSJkh.js → dax-DcLVz9_9.js} +0 -0
  266. /package/dist/web/assets/{desktop-B5A3i7J1.js → desktop-DS2PdvoU.js} +0 -0
  267. /package/dist/web/assets/{diff-CpTrlaE9.js → diff-aUYk5Qys.js} +0 -0
  268. /package/dist/web/assets/{dist-BSCgJs-V.js → dist-Dxl3dqhC.js} +0 -0
  269. /package/dist/web/assets/{docker-xBhmJeHj.js → docker-12Lj1QUD.js} +0 -0
  270. /package/dist/web/assets/{dotenv-DKKhLEv5.js → dotenv-D-P4bzvl.js} +0 -0
  271. /package/dist/web/assets/{dracula-CVwQuzra.js → dracula-C-eRNn4o.js} +0 -0
  272. /package/dist/web/assets/{dracula-soft-CG6mB-lf.js → dracula-soft-CBB1BI7O.js} +0 -0
  273. /package/dist/web/assets/{dream-maker-SC-IdYY3.js → dream-maker-C_j5A0r7.js} +0 -0
  274. /package/dist/web/assets/{edit-diff-preview-C50i1H63.js → edit-diff-preview-CRMu-6r1.js} +0 -0
  275. /package/dist/web/assets/{emacs-lisp-CIBFq6W8.js → emacs-lisp-W54LT7rh.js} +0 -0
  276. /package/dist/web/assets/{everforest-dark-odRT_lec.js → everforest-dark-B_ISUDeh.js} +0 -0
  277. /package/dist/web/assets/{everforest-light-or1Voqjl.js → everforest-light-CNUYa354.js} +0 -0
  278. /package/dist/web/assets/{fennel-tQd1z3iu.js → fennel-DK82-oso.js} +0 -0
  279. /package/dist/web/assets/{file-exclamation-point-C-lL0g_E.js → file-exclamation-point-DBEC-Kst.js} +0 -0
  280. /package/dist/web/assets/{fish-DSuZQG0k.js → fish-CbJVJzQj.js} +0 -0
  281. /package/dist/web/assets/{fluent-D7xI8a8e.js → fluent-Cug5cOL1.js} +0 -0
  282. /package/dist/web/assets/{fortran-free-form-BlAtcPd3.js → fortran-free-form-amHz32Zw.js} +0 -0
  283. /package/dist/web/assets/{gdscript-VsEQXBPm.js → gdscript-t_G6VYpv.js} +0 -0
  284. /package/dist/web/assets/{gdshader-iqHMOdwW.js → gdshader-DqZaHh4E.js} +0 -0
  285. /package/dist/web/assets/{genie-C4XU4g1a.js → genie-CIJtFlZK.js} +0 -0
  286. /package/dist/web/assets/{gherkin-C4nfQzAB.js → gherkin-CalidqLY.js} +0 -0
  287. /package/dist/web/assets/{github-dark-B_pbG3CC.js → github-dark-BG2xX0wa.js} +0 -0
  288. /package/dist/web/assets/{github-dark-default-kFbx7C95.js → github-dark-default-tDp_EOBs.js} +0 -0
  289. /package/dist/web/assets/{github-dark-dimmed-CpVfQEqs.js → github-dark-dimmed-DlSfPwW2.js} +0 -0
  290. /package/dist/web/assets/{github-dark-high-contrast-DYKqQwZh.js → github-dark-high-contrast-qZM7j5KB.js} +0 -0
  291. /package/dist/web/assets/{github-light-gEYBmKaj.js → github-light-CnvWTiyb.js} +0 -0
  292. /package/dist/web/assets/{github-light-default-DVHfMPQ2.js → github-light-default-Dy6OAt2Q.js} +0 -0
  293. /package/dist/web/assets/{github-light-high-contrast-ClVJDu-q.js → github-light-high-contrast-1BHQCOPl.js} +0 -0
  294. /package/dist/web/assets/{gleam-BLaPu7ft.js → gleam-D-2cpXVX.js} +0 -0
  295. /package/dist/web/assets/{gn-BKgerUyI.js → gn-C1cC4xLc.js} +0 -0
  296. /package/dist/web/assets/{gnuplot-cqJuEFb-.js → gnuplot-2Gp459mV.js} +0 -0
  297. /package/dist/web/assets/{go-B2EHdyD8.js → go-BA1q0q8h.js} +0 -0
  298. /package/dist/web/assets/{groovy-DdImoj8s.js → groovy-BlIa-VfI.js} +0 -0
  299. /package/dist/web/assets/{gruvbox-dark-hard-DlwstAds.js → gruvbox-dark-hard-BMIwVwaJ.js} +0 -0
  300. /package/dist/web/assets/{gruvbox-dark-medium-CiYEd1vd.js → gruvbox-dark-medium-DxfuBW3n.js} +0 -0
  301. /package/dist/web/assets/{gruvbox-dark-soft-C-wEa4h4.js → gruvbox-dark-soft-_NSslDDi.js} +0 -0
  302. /package/dist/web/assets/{gruvbox-light-hard-BqYmfna-.js → gruvbox-light-hard-CP5MIsL9.js} +0 -0
  303. /package/dist/web/assets/{gruvbox-light-medium-CxmfalT2.js → gruvbox-light-medium-BTMursH0.js} +0 -0
  304. /package/dist/web/assets/{gruvbox-light-soft-DGWXXMCB.js → gruvbox-light-soft-Bv6zFoVX.js} +0 -0
  305. /package/dist/web/assets/{haskell-H_C_IuVb.js → haskell-Da8UeSv-.js} +0 -0
  306. /package/dist/web/assets/{haxe-CGNSMpeZ.js → haxe-mcKlcxBa.js} +0 -0
  307. /package/dist/web/assets/{hcl-B7bSTSQf.js → hcl-BdZcx5JX.js} +0 -0
  308. /package/dist/web/assets/{hjson-DlJ-glQZ.js → hjson-pFXYbN4t.js} +0 -0
  309. /package/dist/web/assets/{hlsl-CehW5SJN.js → hlsl-Z0II0KR8.js} +0 -0
  310. /package/dist/web/assets/{horizon-Beabcz2d.js → horizon-CufN8DyP.js} +0 -0
  311. /package/dist/web/assets/{horizon-bright-BC87RXPa.js → horizon-bright-5qoQVyIY.js} +0 -0
  312. /package/dist/web/assets/{houston-Da7RFwUy.js → houston-DOFkQwep.js} +0 -0
  313. /package/dist/web/assets/{hy-m1H70w19.js → hy-BVbDMDcG.js} +0 -0
  314. /package/dist/web/assets/{imba-MExFsdAA.js → imba-DRM6LLS5.js} +0 -0
  315. /package/dist/web/assets/{ini-CJy_zmwy.js → ini-2KCnNvmz.js} +0 -0
  316. /package/dist/web/assets/{java-DqDFvJBH.js → java-BmvH-Ou1.js} +0 -0
  317. /package/dist/web/assets/{javascript-C3Wyv22X.js → javascript-B7VhU1Yc.js} +0 -0
  318. /package/dist/web/assets/{json-DnGBw_DZ.js → json-X1pNk0n7.js} +0 -0
  319. /package/dist/web/assets/{json5-CRN-zoN4.js → json5-CJeEJPTg.js} +0 -0
  320. /package/dist/web/assets/{jsonc-CKRUZNRo.js → jsonc-C1haTBsf.js} +0 -0
  321. /package/dist/web/assets/{jsonl-CGfD6FrH.js → jsonl-wQ8-AJgs.js} +0 -0
  322. /package/dist/web/assets/{jsonnet-nbFuDj0o.js → jsonnet-BrEjY3Xh.js} +0 -0
  323. /package/dist/web/assets/{jssm-D0ShzIqD.js → jssm-C6LgYbOs.js} +0 -0
  324. /package/dist/web/assets/{jsx-D27hbRbs.js → jsx-B2cfD_Fb.js} +0 -0
  325. /package/dist/web/assets/{kanagawa-dragon-Ov3f8K0T.js → kanagawa-dragon-6UCcJpLw.js} +0 -0
  326. /package/dist/web/assets/{kanagawa-lotus-CLyOqh-g.js → kanagawa-lotus-DOmfpsRg.js} +0 -0
  327. /package/dist/web/assets/{kanagawa-wave-Kj3dzabH.js → kanagawa-wave-CpxmPE7N.js} +0 -0
  328. /package/dist/web/assets/{kdl-DFF58Gu4.js → kdl-BrLEivmb.js} +0 -0
  329. /package/dist/web/assets/{kotlin-BM4PKaB4.js → kotlin-BO2FUnDB.js} +0 -0
  330. /package/dist/web/assets/{kusto-DrvHLCv5.js → kusto-Cev1yiq9.js} +0 -0
  331. /package/dist/web/assets/{laserwave-Db6i6ZkO.js → laserwave-BlEkf-eH.js} +0 -0
  332. /package/dist/web/assets/{lean-D0hEjzwk.js → lean-BLS3Xk2w.js} +0 -0
  333. /package/dist/web/assets/{less-BEj4npGG.js → less-BXmcsqcq.js} +0 -0
  334. /package/dist/web/assets/{lib-CBdlQ75W.js → lib-CxSBdrd7.js} +0 -0
  335. /package/dist/web/assets/{light-plus-DLIIbDea.js → light-plus-DS6zriYl.js} +0 -0
  336. /package/dist/web/assets/{llvm-DMt6umsO.js → llvm-C_wLnJgX.js} +0 -0
  337. /package/dist/web/assets/{log-BopvdtPW.js → log-Cg8pvmWi.js} +0 -0
  338. /package/dist/web/assets/{logo-CYSUFPFR.js → logo-C9-kEvPW.js} +0 -0
  339. /package/dist/web/assets/{luau-XC611HNO.js → luau-NwaGugfZ.js} +0 -0
  340. /package/dist/web/assets/{make-BMYZIfBj.js → make-DBpJE-Z8.js} +0 -0
  341. /package/dist/web/assets/{markdown-BLza5nvF.js → markdown-B7ZTAu7B.js} +0 -0
  342. /package/dist/web/assets/{material-theme-Uw27UfJW.js → material-theme-BsXH-Mss.js} +0 -0
  343. /package/dist/web/assets/{material-theme-darker-dfqt8XBY.js → material-theme-darker-CW9OlIeY.js} +0 -0
  344. /package/dist/web/assets/{material-theme-lighter-CPtBTblV.js → material-theme-lighter-D2PAh0Kw.js} +0 -0
  345. /package/dist/web/assets/{material-theme-ocean-BfZgVqC-.js → material-theme-ocean-Vz6DBVi3.js} +0 -0
  346. /package/dist/web/assets/{material-theme-palenight-DAZoVYg8.js → material-theme-palenight-D-GleP3G.js} +0 -0
  347. /package/dist/web/assets/{matlab-cSzmquiW.js → matlab-C7MYlMSn.js} +0 -0
  348. /package/dist/web/assets/{mdx-Dq0kW5VM.js → mdx-FkOOX3Dq.js} +0 -0
  349. /package/dist/web/assets/{mermaid-DoIKF0yS.js → mermaid-C_9qw5it.js} +0 -0
  350. /package/dist/web/assets/{min-dark-DyraimDU.js → min-dark-BowNvxk2.js} +0 -0
  351. /package/dist/web/assets/{min-light-Co9cbcUv.js → min-light-x8oWUmbL.js} +0 -0
  352. /package/dist/web/assets/{mipsasm-ph6cKgHn.js → mipsasm-Dmi1AZOT.js} +0 -0
  353. /package/dist/web/assets/{mojo-BBT0XwmY.js → mojo-C6z7mry2.js} +0 -0
  354. /package/dist/web/assets/{monokai-IP0T5Rtg.js → monokai-BQ4e_Hn0.js} +0 -0
  355. /package/dist/web/assets/{moonbit-B8cogNXl.js → moonbit-CEeNIoB4.js} +0 -0
  356. /package/dist/web/assets/{move-DkY26NtM.js → move-bALfKUa7.js} +0 -0
  357. /package/dist/web/assets/{narrat-rBs3byG0.js → narrat-DX4JlAWT.js} +0 -0
  358. /package/dist/web/assets/{nextflow-groovy-DkO9wMLP.js → nextflow-groovy-CKQWtRkO.js} +0 -0
  359. /package/dist/web/assets/{night-owl-jVR4lFji.js → night-owl-D4PQd726.js} +0 -0
  360. /package/dist/web/assets/{night-owl-light-fBYJ7s3U.js → night-owl-light-BQo_YHll.js} +0 -0
  361. /package/dist/web/assets/{nix-WX9oavu6.js → nix-B1jThkGj.js} +0 -0
  362. /package/dist/web/assets/{nord-Cf7G21Pu.js → nord-35quHUKo.js} +0 -0
  363. /package/dist/web/assets/{nushell-DQdzcJ1H.js → nushell-L6DbJTuL.js} +0 -0
  364. /package/dist/web/assets/{objective-c-JSO2uvZ-.js → objective-c-CTZ4bR2U.js} +0 -0
  365. /package/dist/web/assets/{objective-cpp-CZMGJ9Fb.js → objective-cpp-Dm1YDIdA.js} +0 -0
  366. /package/dist/web/assets/{ocaml-C4S4NSDo.js → ocaml-B3n__LGy.js} +0 -0
  367. /package/dist/web/assets/{odin-Y2jXCgVq.js → odin-DmqZ8n3i.js} +0 -0
  368. /package/dist/web/assets/{one-dark-pro-dVIxuoJY.js → one-dark-pro-16_UaUig.js} +0 -0
  369. /package/dist/web/assets/{one-light-BQ1IKfD1.js → one-light-B-GGjWn5.js} +0 -0
  370. /package/dist/web/assets/{openscad-Bddw4I3z.js → openscad-DTFB9Sj6.js} +0 -0
  371. /package/dist/web/assets/{pascal-BKVXrzC1.js → pascal-RcT1dQYN.js} +0 -0
  372. /package/dist/web/assets/{pkl-BTd-YRsO.js → pkl-Dr4yLQ_o.js} +0 -0
  373. /package/dist/web/assets/{plastic-eiGvBIxV.js → plastic-hp2Nry0R.js} +0 -0
  374. /package/dist/web/assets/{plsql-C290tieP.js → plsql-pFcO4WZ0.js} +0 -0
  375. /package/dist/web/assets/{po-DyDwh0tx.js → po-BkZ0_HvY.js} +0 -0
  376. /package/dist/web/assets/{poimandres-B87zc_g-.js → poimandres-BcTrxj7f.js} +0 -0
  377. /package/dist/web/assets/{polar-Cdub1YMT.js → polar-DHK3q8FF.js} +0 -0
  378. /package/dist/web/assets/{postcss-BcI_3K0v.js → postcss-Bv7WzijA.js} +0 -0
  379. /package/dist/web/assets/{powerquery-OdMrkJSe.js → powerquery-DpaU_hW9.js} +0 -0
  380. /package/dist/web/assets/{powershell-C8auT6xg.js → powershell-_Rqc0MGP.js} +0 -0
  381. /package/dist/web/assets/{prisma-DIbVv8PA.js → prisma-wyegE8us.js} +0 -0
  382. /package/dist/web/assets/{prolog-DbTOH3Ld.js → prolog-DspHCURv.js} +0 -0
  383. /package/dist/web/assets/{proto-C4Cq2dCD.js → proto-BOBiNGf5.js} +0 -0
  384. /package/dist/web/assets/{puppet-DJp3uqum.js → puppet-DfLvV8h2.js} +0 -0
  385. /package/dist/web/assets/{purescript-CLsV4rPU.js → purescript-BY5ZSOZy.js} +0 -0
  386. /package/dist/web/assets/{python-2gBtEza2.js → python-B758mMeg.js} +0 -0
  387. /package/dist/web/assets/{qmldir-DfhULcua.js → qmldir-C75bpHvy.js} +0 -0
  388. /package/dist/web/assets/{qss-DK--VTTV.js → qss-DcLTlbLd.js} +0 -0
  389. /package/dist/web/assets/{r-BArBO2Sa.js → r-D0G_Y1P5.js} +0 -0
  390. /package/dist/web/assets/{racket-5mijHsVy.js → racket-DTye_5qn.js} +0 -0
  391. /package/dist/web/assets/{raku-ir0suElk.js → raku-uJXBXkV0.js} +0 -0
  392. /package/dist/web/assets/{react-J9g6e3Rq.js → react-CorNPdie.js} +0 -0
  393. /package/dist/web/assets/{red-DSecpIOT.js → red-rs3HE3re.js} +0 -0
  394. /package/dist/web/assets/{reg-Dp30FusS.js → reg-DkTIaX7a.js} +0 -0
  395. /package/dist/web/assets/{regexp-DXJnEM_6.js → regexp-4TLSs4_m.js} +0 -0
  396. /package/dist/web/assets/{rel-Dr483ozj.js → rel-B0jumCjt.js} +0 -0
  397. /package/dist/web/assets/{resolve-theme-DJORYzKu.js → resolve-theme-CCoakF2E.js} +0 -0
  398. /package/dist/web/assets/{riscv-ZfBy5IA1.js → riscv-DtaWMlo0.js} +0 -0
  399. /package/dist/web/assets/{ron-ZJ_CljGP.js → ron-BCPxwJD6.js} +0 -0
  400. /package/dist/web/assets/{rose-pine-CcW_eJ6G.js → rose-pine-CNryoqGi.js} +0 -0
  401. /package/dist/web/assets/{rose-pine-dawn-v2l_UF-j.js → rose-pine-dawn-C-gJOQVP.js} +0 -0
  402. /package/dist/web/assets/{rose-pine-moon-CNXkwf3D.js → rose-pine-moon-BdXSXpZh.js} +0 -0
  403. /package/dist/web/assets/{rosmsg-BMYMVW84.js → rosmsg-CPG8X-D5.js} +0 -0
  404. /package/dist/web/assets/{rust-BR-zJh5q.js → rust-CxfW3ePx.js} +0 -0
  405. /package/dist/web/assets/{sass-BukPOBFz.js → sass-D-7bxo_L.js} +0 -0
  406. /package/dist/web/assets/{save-DO5xjW95.js → save-BM4p9_9n.js} +0 -0
  407. /package/dist/web/assets/{scala-DUFY7D6D.js → scala-DyUsfHNc.js} +0 -0
  408. /package/dist/web/assets/{scheme-Dl-dNNxQ.js → scheme-ROgrwiO-.js} +0 -0
  409. /package/dist/web/assets/{sdbl-DdmlQhia.js → sdbl-B1EAQWpp.js} +0 -0
  410. /package/dist/web/assets/{shellscript-UyhrvGcd.js → shellscript-HhmB_Ufy.js} +0 -0
  411. /package/dist/web/assets/{shield-check-m2QzZTuZ.js → shield-check-C3qYSU8a.js} +0 -0
  412. /package/dist/web/assets/{shield-off-DNn7VzJe.js → shield-off-D5tU7gQT.js} +0 -0
  413. /package/dist/web/assets/{slack-dark-Cv6Szr-q.js → slack-dark-DnmSY72G.js} +0 -0
  414. /package/dist/web/assets/{slack-ochin-V6Ii3lAm.js → slack-ochin-q97gHjcd.js} +0 -0
  415. /package/dist/web/assets/{smalltalk-BUCv5G91.js → smalltalk-B-1waXvA.js} +0 -0
  416. /package/dist/web/assets/{snazzy-light-B67a78qi.js → snazzy-light-C8yWDN2g.js} +0 -0
  417. /package/dist/web/assets/{solarized-dark-ccWnfYyA.js → solarized-dark-ClVDEMJZ.js} +0 -0
  418. /package/dist/web/assets/{solarized-light-C3WyRO5I.js → solarized-light-Bts6CUIP.js} +0 -0
  419. /package/dist/web/assets/{solidity-BFbRPNi_.js → solidity-DYxwp0om.js} +0 -0
  420. /package/dist/web/assets/{splunk-zdpwCJLE.js → splunk-jK6-Sr06.js} +0 -0
  421. /package/dist/web/assets/{sql-B2IPUsfz.js → sql-DPpd8aUS.js} +0 -0
  422. /package/dist/web/assets/{ssh-config-Co4cXifq.js → ssh-config-Bimb9LVr.js} +0 -0
  423. /package/dist/web/assets/{stylus-CmI75XY8.js → stylus-B2DgJRJw.js} +0 -0
  424. /package/dist/web/assets/{swift-Dfks9YQI.js → swift-ChEH2nFL.js} +0 -0
  425. /package/dist/web/assets/{synthwave-84-B3LERzNS.js → synthwave-84-DwSLbgAm.js} +0 -0
  426. /package/dist/web/assets/{system-verilog-BFvldiwl.js → system-verilog-DoVBMhJ8.js} +0 -0
  427. /package/dist/web/assets/{systemd-Cu4Vvgbu.js → systemd--Ux4VAYD.js} +0 -0
  428. /package/dist/web/assets/{table-CAVmgQMT.js → table-Di8pv2Y_.js} +0 -0
  429. /package/dist/web/assets/{talonscript-Bnq32pUB.js → talonscript-DQNx9nak.js} +0 -0
  430. /package/dist/web/assets/{tasl-Nwe2K3J1.js → tasl-Bwy_Y9uD.js} +0 -0
  431. /package/dist/web/assets/{tcl-zOnpywrl.js → tcl-Drea-2jA.js} +0 -0
  432. /package/dist/web/assets/{terraform-Bc9Vj-n3.js → terraform-4rS5HoFk.js} +0 -0
  433. /package/dist/web/assets/{tokyo-night-9_KyM8M2.js → tokyo-night-C9MqaxIn.js} +0 -0
  434. /package/dist/web/assets/{toml-ByH-fxOY.js → toml-tSgOSv42.js} +0 -0
  435. /package/dist/web/assets/{tsv-DrCnDClP.js → tsv-BmL4O1Zx.js} +0 -0
  436. /package/dist/web/assets/{tsx-B8LafSYr.js → tsx-BwzeA880.js} +0 -0
  437. /package/dist/web/assets/{turtle-DuyP_QCJ.js → turtle-2DYvssFL.js} +0 -0
  438. /package/dist/web/assets/{typescript-BTBJb5RD.js → typescript-ZC-vonSh.js} +0 -0
  439. /package/dist/web/assets/{typespec-DdOFo7M5.js → typespec-DkCEO45F.js} +0 -0
  440. /package/dist/web/assets/{typst-LYvnBWQu.js → typst-DAZdb2n6.js} +0 -0
  441. /package/dist/web/assets/{utils-CTg5uAYR.js → utils-CSCvNZxE.js} +0 -0
  442. /package/dist/web/assets/{v-BuBYA1_x.js → v-B_wsb4C1.js} +0 -0
  443. /package/dist/web/assets/{vala-vRIBYYe3.js → vala-BDBNS784.js} +0 -0
  444. /package/dist/web/assets/{vb-DwGi-IYM.js → vb-CuOoJSSa.js} +0 -0
  445. /package/dist/web/assets/{vendor-xterm-i6SHFTfU.js → vendor-xterm-CImj6i6Z.js} +0 -0
  446. /package/dist/web/assets/{verilog-6B1X4G2-.js → verilog-BCcOvOcc.js} +0 -0
  447. /package/dist/web/assets/{vesper-Dw1y_Ae7.js → vesper-vN8CREK5.js} +0 -0
  448. /package/dist/web/assets/{vhdl-DK8JwBzJ.js → vhdl-CI8PoqeM.js} +0 -0
  449. /package/dist/web/assets/{viml-BTKIEIqm.js → viml-B66dNP1B.js} +0 -0
  450. /package/dist/web/assets/{vitesse-black-BqqolhEg.js → vitesse-black-0bGIaCfD.js} +0 -0
  451. /package/dist/web/assets/{vitesse-dark-B9JL5WQA.js → vitesse-dark-Dk-RSXq-.js} +0 -0
  452. /package/dist/web/assets/{vitesse-light-DYJKj2se.js → vitesse-light-BUsA5RhG.js} +0 -0
  453. /package/dist/web/assets/{vyper-D-jDI3WH.js → vyper-dNodzEa8.js} +0 -0
  454. /package/dist/web/assets/{wasm-1-GlNdpk.js → wasm-BDxaNPCj.js} +0 -0
  455. /package/dist/web/assets/{wasm-BBW4WmT2.js → wasm-d6SE2_qm.js} +0 -0
  456. /package/dist/web/assets/{wenyan-B6cM7sDO.js → wenyan-D0h8Uz55.js} +0 -0
  457. /package/dist/web/assets/{wgsl-FaHGQgko.js → wgsl-vKs_vsvQ.js} +0 -0
  458. /package/dist/web/assets/{wikitext-DtIyPUrk.js → wikitext-BIWsPJit.js} +0 -0
  459. /package/dist/web/assets/{wit-BMVRif5Y.js → wit-6vmKi2H3.js} +0 -0
  460. /package/dist/web/assets/{wolfram-DQsCGohn.js → wolfram-zoNg6F1a.js} +0 -0
  461. /package/dist/web/assets/{x-C2VSAp7O.js → x-BsFLg-T8.js} +0 -0
  462. /package/dist/web/assets/{yaml-C-Va56Gt.js → yaml-W95qP6-B.js} +0 -0
  463. /package/dist/web/assets/{zenscript-DdobKGI-.js → zenscript-BxircxDx.js} +0 -0
  464. /package/dist/web/assets/{zig-vqDFwj6q.js → zig-DD79WQtz.js} +0 -0
@@ -0,0 +1,1961 @@
1
+ # PPM System Architecture
2
+
3
+ ## High-Level Architecture
4
+
5
+ ```
6
+ ┌──────────────────────────────────────────────────────────────────────┐
7
+ │ User Devices │
8
+ │ ┌─────────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │
9
+ │ │ Desktop/Tab │ │ Mobile/iPad │ │ Terminal (CLI mode) │ │
10
+ │ │ Web Browser │ │ Web Browser │ │ STDIN → ppm chat │ │
11
+ │ └────────┬────────┘ └──────┬───────┘ └────────────┬─────────────┘ │
12
+ │ │ │ │ │
13
+ │ └───────────────────┼────────────────────────┘ │
14
+ │ │ HTTP/WebSocket │
15
+ ├──────────────────────────────┼────────────────────────────────────────┤
16
+ │ PPM Server (Bun) │
17
+ │ ┌────────────────────────────────────────────────────────────────┐ │
18
+ │ │ Hono HTTP Framework (Port 8080) │ │
19
+ │ ├────────────────────────────────────────────────────────────────┤ │
20
+ │ │ Routes (src/server/routes/) │ │
21
+ │ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐ │ │
22
+ │ │ │ /api/projects │ │ /api/project/:n/ │ │ /api/db/* │ │ │
23
+ │ │ │ (CRUD projects) │ │ (scoped routes) │ │ (connections)│ │ │
24
+ │ │ └──────────────────┘ └──────────────────┘ └──────────────┘ │ │
25
+ │ ├────────────────────────────────────────────────────────────────┤ │
26
+ │ │ Services (src/services/) │ │
27
+ │ │ ┌───────────────────────────────────────────────────────────┐│ │
28
+ │ │ │ ChatService │ GitService │ FileService │ TerminalService ││ │
29
+ │ │ │ (streaming │ (simple- │ (read/write │ (PTY/shell) ││ │
30
+ │ │ │ messages) │ git) │ files) │ (Bun.spawn) ││ │
31
+ │ │ │ TableCache │ DbService │ DatabaseAdapterRegistry ││ │
32
+ │ │ │ (metadata) │ (SQLite) │ (SQLite, PostgreSQL adapters) ││ │
33
+ │ │ └───────────────────────────────────────────────────────────┘│ │
34
+ │ ├────────────────────────────────────────────────────────────────┤ │
35
+ │ │ Providers (src/providers/) │ │
36
+ │ │ ┌──────────────────────────────────────────────────────────┐ │ │
37
+ │ │ │ ProviderRegistry (routes to active AI provider) │ │ │
38
+ │ │ │ ┌───────────────────────┬──────────────────────────┐ │ │ │
39
+ │ │ │ │ claude-agent-sdk │ mock-provider (test) │ │ │ │
40
+ │ │ │ │ @anthropic/SDK (prim) │ Returns canned resp. │ │ │ │
41
+ │ │ │ └───────────────────────┴──────────────────────────┘ │ │ │
42
+ │ │ └──────────────────────────────────────────────────────────┘ │ │
43
+ │ └────────────────────────────────────────────────────────────────┘ │
44
+ │ │
45
+ │ Config & State (src/services/) │
46
+ │ ┌──────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │
47
+ │ │ SQLite DB │ │ Git Repos │ │ Session Storage │ │
48
+ │ │ (config, projs) │ │ (local disk) │ │ (SQLite + SDK) │ │
49
+ │ │ (session map) │ │ │ │ (session_map, │ │
50
+ │ │ (push subs, │ │ Connections: │ │ session_logs, │ │
51
+ │ │ usage, logs) │ │ • SQLite files │ │ usage_history) │ │
52
+ │ │ (connections) │ │ • PostgreSQL svr │ │ (connections) │ │
53
+ │ │ (table metadata) │ │ via connStr │ │ │ │
54
+ │ └──────────────────┘ └──────────────────┘ └─────────────────┘ │
55
+ └──────────────────────────────────────────────────────────────────────┘
56
+ ↓↑
57
+ ┌────────────────────────────────────────────────┐
58
+ │ Filesystem Access (Local Only) │
59
+ │ • Project directories (git repos) │
60
+ │ • File read/write operations │
61
+ │ • SQLite database (~/.ppm/ppm.db) │
62
+ │ • Config database (~/.ppm/ppm.db) │
63
+ └────────────────────────────────────────────────┘
64
+ ```
65
+
66
+ ## Layer Descriptions
67
+
68
+ ### Presentation Layer (Browser/CLI)
69
+ **Components:** React frontend + CLI commands
70
+
71
+ **Responsibilities:**
72
+ - Render UI for file explorer, editor, terminal, chat
73
+ - Project switching with visual indicators (avatars, colors, keep-alive workspaces)
74
+ - Capture user input (text, file uploads, terminal commands)
75
+ - Display streaming responses, terminal output
76
+ - Handle authentication (token in localStorage)
77
+
78
+ **Key Files:**
79
+ - `src/web/app.tsx` — Root React component
80
+ - `src/web/components/layout/project-bar.tsx` — Narrow left sidebar with project avatars (52px width)
81
+ - `src/web/components/layout/project-bottom-sheet.tsx` — Mobile project switcher (bottom sheet)
82
+ - `src/web/components/layout/sidebar.tsx` — Main sidebar with Explorer/Git/History tabs
83
+ - `src/web/components/chat/chat-history-panel.tsx` — History tab content (chat sessions)
84
+ - `src/web/components/` — UI components
85
+ - `src/cli/commands/` — CLI command handlers
86
+
87
+ ---
88
+
89
+ ### HTTP API Layer (Hono)
90
+ **Component:** Hono framework, request routing
91
+
92
+ **Responsibilities:**
93
+ - Parse HTTP requests, validate tokens
94
+ - Route to correct handler (projects, chat, git, files)
95
+ - Format responses in `ApiResponse` envelope
96
+ - Handle WebSocket upgrades
97
+
98
+ **Key Files:**
99
+ - `src/server/index.ts` — Server setup, middleware chain
100
+ - `src/server/routes/projects.ts` — Project CRUD
101
+ - `src/server/routes/project-scoped.ts` — Mount per-project routes
102
+ - `src/server/middleware/auth.ts` — Token validation
103
+
104
+ **Routes:**
105
+ ```
106
+ GET /api/health → Health check
107
+ GET /api/auth/check → Verify auth token
108
+ GET /api/settings/ai → Get AI provider settings
109
+ PUT /api/settings/ai → Update AI provider settings
110
+ GET /api/accounts → List all accounts (sanitized)
111
+ POST /api/accounts → Create account (encrypt & store token)
112
+ GET /api/accounts/:id → Get account (sanitized, no token)
113
+ PUT /api/accounts/:id → Update account (name, priority)
114
+ DELETE /api/accounts/:id → Delete account
115
+ POST /api/accounts/:id/activate → Set as active account
116
+ POST /api/projects → Create project
117
+ GET /api/projects → List projects
118
+ DELETE /api/projects/:name → Delete project
119
+ PATCH /api/projects/reorder → Reorder projects by name order
120
+ PATCH /api/projects/:name/color → Set project color (hex string)
121
+ GET /api/project/:name/chat/sessions → List sessions
122
+ POST /api/project/:name/chat/sessions → Create session
123
+ GET /api/project/:name/chat/sessions/:id/messages → Get history
124
+ DELETE /api/project/:name/chat/sessions/:id → Delete session
125
+ GET /api/project/:name/git/status → Git status
126
+ GET /api/project/:name/git/diff → Diff
127
+ POST /api/project/:name/git/stage → Stage file
128
+ POST /api/project/:name/git/commit → Commit
129
+ GET /api/project/:name/files/tree → Directory tree
130
+ GET /api/project/:name/files/raw → File content
131
+ PUT /api/project/:name/files/write → Write file
132
+ GET /api/db/connections → List all connections
133
+ POST /api/db/connections → Create connection (SQLite/PostgreSQL)
134
+ GET /api/db/connections/:id → Get connection (sanitized)
135
+ PUT /api/db/connections/:id → Update connection (toggle readonly, UI-only)
136
+ DELETE /api/db/connections/:id → Delete connection
137
+ GET /api/db/connections/:id/tables → List tables (with sync)
138
+ GET /api/db/connections/:id/tables/:table → Get table schema + data
139
+ POST /api/db/connections/:id/query → Execute query (readonly checked)
140
+ PATCH /api/db/connections/:id/cell → Update cell value (single)
141
+ GET /api/upgrade/status → Get current + available versions, install method
142
+ POST /api/upgrade/apply → Install new version, trigger supervisor self-replace
143
+ GET /api/project/:name/workspace → Get saved workspace layout + metadata
144
+ PUT /api/project/:name/workspace → Save workspace layout (layout JSON)
145
+ GET /api/project/:name/chat/slash-items → List slash commands/skills (optional ?q=<query> for fuzzy search)
146
+ WS /ws/project/:name/chat/:sessionId → Chat streaming
147
+ WS /ws/project/:name/terminal/:id → Terminal I/O
148
+ ```
149
+
150
+ **URL Format (Deterministic Tabs, v0.8.77+):**
151
+ ```
152
+ /project/{name} → Project root (project switcher)
153
+ /project/{name}/editor/{filePath} → Open editor tab (e.g., src/index.ts)
154
+ /project/{name}/conflict-editor/{filePath} → Open conflict resolution editor (during merge/rebase)
155
+ /project/{name}/chat/{provider}/{sessionId} → Open chat tab
156
+ /project/{name}/terminal/{index} → Open terminal tab
157
+ /project/{name}/database/{connId}/{table} → Open database browser
158
+ /project/{name}/git-graph → Git history graph (singleton)
159
+ /project/{name}/settings → Settings panel (singleton)
160
+ ```
161
+ Tab IDs are deterministic: `{type}:{identifier}` (e.g., `editor:src/index.ts`, `conflict-editor:src/file.ts`, `chat:claude/abc123`). Deep links auto-create missing tabs.
162
+
163
+ ---
164
+
165
+ ### Service Layer (Business Logic)
166
+ **Components:** Singleton service modules
167
+
168
+ **Responsibilities:**
169
+ - Implement core business logic (chat, git, files, terminal)
170
+ - Manage dependencies (file paths, command execution)
171
+ - Coordinate between providers and data sources
172
+ - Validate input and propagate errors
173
+
174
+ **Services:**
175
+
176
+ | Service | Purpose | Key Methods |
177
+ |---------|---------|-------------|
178
+ | **ChatService** | Session management, message streaming | createSession, streamMessage, getHistory |
179
+ | **ConfigService** | Config loading (YAML→SQLite migration) | load, save, getToken |
180
+ | **DbService** | SQLite persistence (10 tables, WAL, connections/accounts/workspace CRUD) | getDb, openTestDb, getWorkspace, setWorkspace, getConnections, insertConnection, deleteConnection, getTableCache |
181
+ | **TableCacheService** | Cache table metadata, search tables | syncTables, searchTables, invalidateCache |
182
+ | **GitService** | Git command execution | status, diff, commit, stage, branch |
183
+ | **FileService** | File operations with validation | read, write, tree, delete, mkdir |
184
+ | **TerminalService** | PTY lifecycle, shell spawning | spawn, write, kill |
185
+ | **ProjectService** | Project CRUD, scanning | add, remove, get, list, scan |
186
+ | **ClaudeUsageService** | Token tracking, cost calculation | trackUsage, getUsage |
187
+ | **PushNotificationService** | Web push subscriptions | subscribe, unsubscribe, notify |
188
+ | **SessionLogService** | Audit logs with redaction | logSession, getLog |
189
+ | **ProviderRegistry** | AI provider routing | getDefault, send (delegates) |
190
+ | **CloudflaredService** | Download cloudflared binary | ensureCloudflared, getCloudflaredPath |
191
+ | **TunnelService** | Cloudflare Quick Tunnel lifecycle | startTunnel, stopTunnel, getTunnelUrl |
192
+ | **DatabaseAdapterRegistry** | Register/retrieve DB adapters (extensible) | registerAdapter, getAdapter |
193
+ | **SQLiteAdapter** | SQLite connection, query execution, readonly checks | testConnection, getTables, getTableSchema, getTableData, executeQuery, updateCell |
194
+ | **PostgresAdapter** | PostgreSQL connection, query execution, readonly checks | testConnection, getTables, getTableSchema, getTableData, executeQuery, updateCell |
195
+ | **AccountService** | Account CRUD, token encryption/decryption | getAccounts, createAccount, updateAccount, deleteAccount |
196
+ | **AccountSelectorService** | Select active account based on config + pre-flight retry loop | next(excludeIds?), peek(), onPreflightFail(), onRateLimit(), onAuthError(), onSuccess() |
197
+ | **UpgradeService** | Version checking, installation, self-replace signaling | checkForUpdate, applyUpgrade, getInstallMethod, compareSemver |
198
+ | **SlashDiscoveryService** | Modular command discovery (skills, builtin commands) | discoverSkillRoots, loadSkills, searchSkills, resolveOverrides, fuzzySearch |
199
+ | **PPMBotService** | Coordinator orchestrator (team leader, delegation mgmt) | start, stop, handleUpdate, checkPendingTasks |
200
+ | **PPMBotSessionManager** | Coordinator session per chat, project resolver | getCoordinatorSession, rotateCoordinatorSession, resolveProject |
201
+ | **PPMBotTelegramService** | Telegram long-polling, message ops | getUpdates, sendMessage, editMessage, setTyping, handleCommands |
202
+ | **PPMBotMemoryService** | SQLite project memory persistence | saveMemory, recallMemories, searchByProject |
203
+ | **executeDelegation()** | Task execution in isolated session, result capture | (async function, manages ChatService + result storage) |
204
+ | **PPMBotFormatterService** | Markdown → Telegram HTML + chunking | formatMarkdown, chunkMessage |
205
+ | **PPMBotStreamerService** | ChatEvent → progressive Telegram edits | streamMessageEdits |
206
+ | **ClawBotService** | LEGACY Telegram bot (deprecated v0.9.11) | (direct-chat model, replaced by coordinator) |
207
+ | **ClawBotTelegramService** | LEGACY Telegram API | (deprecated v0.9.11) |
208
+ | **ClawBotSessionService** | LEGACY chatID mapping | (deprecated v0.9.11) |
209
+ | **ClawBotMemoryService** | LEGACY FTS5 memory | (deprecated v0.9.11) |
210
+ | **ClawBotFormatterService** | LEGACY formatter | (deprecated v0.9.11) |
211
+ | **ClawBotStreamerService** | LEGACY streamer | (deprecated v0.9.11) |
212
+
213
+ **Key Files:** `src/services/*.service.ts`, `src/services/ppmbot/*.ts`, `src/cli/commands/bot-cmd.ts`
214
+
215
+ ---
216
+
217
+ ### Provider Layer (AI Adapters)
218
+ **Component:** Provider interface + implementations
219
+
220
+ **Responsibilities:**
221
+ - Abstract AI model differences behind common interface
222
+ - Stream responses as async generators
223
+ - Handle tool use and approval flows
224
+ - Track token usage
225
+
226
+ **Interface (src/providers/provider.interface.ts):**
227
+ ```typescript
228
+ interface AIProvider {
229
+ createSession(): Promise<Session>;
230
+ sendMessage(sessionId: string, message: string, context?: FileContext[]): AsyncIterable<ChatEvent>;
231
+ onToolApproval(sessionId: string, requestId: string, approved: boolean, data?: unknown): Promise<void>;
232
+ }
233
+ ```
234
+
235
+ **Implementations:**
236
+ - **claude-agent-sdk** (Primary) — @anthropic-ai/claude-agent-sdk, streaming, tool use. Reads model/effort/maxTurns/budget/thinking from config. Settings refreshed per query. Windows CLI fallback for Bun subprocess pipe issues. .env poisoning mitigation. **Multi-account support:** Injects account API token from AccountService instead of relying on ANTHROPIC_API_KEY env var when accounts configured.
237
+ - **mock-provider** (Testing) — Returns canned responses
238
+ - **cursor-cli** (CLI-based) — Spawns `cursor-agent` CLI binary with NDJSON streaming. Extends `CliProvider` base class.
239
+ - **codex/gemini** (Planned) — Pluggable via `CliProvider` extension (~100-150 lines each)
240
+
241
+ #### Multi-Provider Architecture (v0.8.61+)
242
+
243
+ PPM supports multiple AI providers through a generic `AIProvider` interface and extensible base classes:
244
+
245
+ **Provider Types:**
246
+ 1. **SDK-based** (claude-agent-sdk) — Uses Anthropic SDK for rich features (approvals, thinking blocks)
247
+ 2. **CLI-based** (cursor-cli, codex, gemini) — Spawns external binary with NDJSON streaming
248
+
249
+ **Base Classes:**
250
+ - `AIProvider` interface — Defines required methods (createSession, sendMessage) + optional capabilities (abortQuery, getMessages, listSessionsByDir, ensureProjectPath)
251
+ - `CliProvider` abstract class — Shared spawn/parse/abort logic for all CLI-spawning providers
252
+ - Provider-specific subclasses implement: `buildArgs()`, `mapEvent()`, `extractSessionId()`, `isAvailable()`
253
+
254
+ **Streaming Infrastructure:**
255
+ - `parseNdjsonLines()` utility — Async generator that buffers partial TCP packets, yields complete JSON lines
256
+ - `ChatEvent` union type — Normalized event format across all providers (text, tool_use, thinking, approval_request, system, done, error)
257
+ - Event mappers translate provider-specific JSON → ChatEvent (e.g., Cursor's `reasoning` type → `thinking` event)
258
+
259
+ **Provider Registration & Bootstrap:**
260
+ - `ProviderRegistry` maintains active provider instances
261
+ - `bootstrapProviders()` async function checks `isAvailable()` on CLI providers before registering
262
+ - Graceful fallback: if Cursor binary not found, provider skips registration (no crash, logged as info)
263
+ - Config type `AIProviderConfig.type` union: `"agent-sdk" | "cli" | "mock"`
264
+
265
+ **CLI-Provider Features:**
266
+ - **Session capture** — Extract session ID from provider's init event, re-key process tracking
267
+ - **Workspace trust auto-retry** — Detect trust prompts in stderr, retry once with `--trust` flag
268
+ - **Process lifecycle** — Track active processes per session, escalate SIGTERM → SIGKILL on abort
269
+ - **History loading** — Override `listSessions()` to read native provider history (e.g., Cursor SQLite DAG)
270
+ - **Graceful degradation** — Missing binary → provider skipped, not fatal
271
+
272
+ **New Files (v0.8.61):**
273
+ - `src/utils/ndjson-line-parser.ts` — NDJSON streaming parser
274
+ - `src/providers/cli-provider-base.ts` — Abstract base class for CLI providers
275
+ - `src/providers/cursor-cli/cursor-provider.ts` — CursorCliProvider implementation
276
+ - `src/providers/cursor-cli/cursor-event-mapper.ts` — NDJSON → ChatEvent mapping
277
+ - `src/providers/cursor-cli/cursor-history.ts` — SQLite DAG reader for Cursor history
278
+ - `src/web/components/chat/provider-selector.tsx` — UI component for provider selection
279
+
280
+ ---
281
+
282
+ ### PPMBot Coordinator Service Layer (Telegram-based Team Leader)
283
+ **Component:** PPMBot coordinator orchestrator + delegation executor
284
+
285
+ **Responsibilities:**
286
+ - Manage single persistent coordinator session per Telegram chat in `~/.ppm/bot/` workspace
287
+ - Route incoming Telegram messages to coordinator (ask/answer) or delegation tracking
288
+ - Decide when to answer directly vs. delegate to subagents (based on project context)
289
+ - Execute delegated tasks in isolated project sessions
290
+ - Track task status and report results back to Telegram
291
+ - Format responses as Telegram HTML with progressive message editing
292
+
293
+ **Architecture:**
294
+ ```
295
+ Telegram → PPMBotTelegramService (polling) → PPMBotService (orchestrator)
296
+
297
+ PPMBotSessionManager (coordinator session per chat)
298
+ coordinatorSession.id → chatService.sendMessage()
299
+ Task Poller (5s interval)
300
+
301
+ executeDelegation(taskId, telegram, providerId)
302
+ ├─ getBotTask(taskId) → prompt
303
+ ├─ chatService.createSession(providerId, projectPath)
304
+ ├─ run async generator (abort, 900s timeout)
305
+ └─ updateBotTaskStatus(taskId, "completed", {result})
306
+
307
+ telegram.sendMessage(chatId, result summary)
308
+ ```
309
+
310
+ **Services (src/services/ppmbot/):**
311
+ - **PPMBotService** — Lifecycle (start/stop), message queue, Telegram polling loop, task poller loop
312
+ - **PPMBotSessionManager** — Coordinator session cache per chatID, project resolver (case-insensitive, prefix match)
313
+ - **PPMBotTelegramService** — Telegram Bot API (getUpdates polling, sendMessage, editMessage, setTyping)
314
+ - **PPMBotMemoryService** — SQLite project memories, contextual recall
315
+ - **executeDelegation()** — Task execution in isolated session, result capture, timeout/abort handling
316
+ - **PPMBotFormatterService** — Markdown → Telegram HTML, 4096-char chunking
317
+ - **PPMBotStreamerService** — ChatEvent → progressive Telegram message edits (1s throttle)
318
+
319
+ **Coordinator Identity (Persistent Cross-Provider):**
320
+ - Location: `~/.ppm/bot/coordinator.md` (loaded on startup, cached in `coordinatorIdentity`)
321
+ - Role definition: Team leader, project coordinator, decision-maker
322
+ - Decision framework: Answer directly (no project context) vs. Delegate (file access needed)
323
+ - Coordination tools: Bash-safe CLI commands (`ppm bot delegate`, `ppm bot task-status`, etc.)
324
+ - Cross-provider: Identity text injected as XML context block, works with Claude SDK + CLI providers
325
+
326
+ **Delegation Flow:**
327
+ 1. User asks task in Telegram
328
+ 2. Coordinator decides: delegate? → yes
329
+ 3. Coordinator calls bash: `ppm bot delegate --chat <chatId> --project <name> --prompt "<enriched>"`
330
+ 4. CLI creates `bot_tasks` row, returns taskId
331
+ 5. Service tells user: "Working on it, I'll notify you when done"
332
+ 6. Background poller (5s) detects pending task
333
+ 7. Executes: `chatService.createSession()` in target project
334
+ 8. Streams response, captures summary + full output
335
+ 9. Updates task status → "completed"
336
+ 10. Sends Telegram notification with result
337
+
338
+ **Task Execution (Isolation & Safety):**
339
+ - Each task = fresh isolated session (no shared context)
340
+ - Timeout: 900s default (configurable per task)
341
+ - Abort: AbortController on timeout, can be canceled mid-execution
342
+ - Result capture: Both summary (for notification) and full text (for detailed review)
343
+ - Error handling: Task status → "failed", error message stored, user notified
344
+
345
+ **Database Schema (v14):**
346
+ - `bot_tasks` — id (UUID), chatId, projectName, projectPath, prompt, status, resultSummary, resultFull, sessionId, error, reported, timeoutMs, createdAt, startedAt, completedAt
347
+ - Indexes: `idx_bot_tasks_status` (fast poller lookup), `idx_bot_tasks_chat` (history queries)
348
+
349
+ **Key Design Decisions:**
350
+ 1. **Single coordinator session** — Per chat, persistent, one identity (vs. per-task sessions in ClawBot)
351
+ 2. **Delegation via CLI** — Coordinator calls bash commands (safer than direct DB writes, auditable)
352
+ 3. **Isolated task execution** — Each delegated task spawns fresh session (no context bleed)
353
+ 4. **Background polling** — Task execution decoupled from message handler (non-blocking)
354
+ 5. **Result summary + full** — Notification shows short summary; user can fetch full output via CLI
355
+ 6. **Cross-provider identity** — Single `coordinator.md` works with any AI provider
356
+ 7. **Bash-safe tools only** — Coordinator restricted to Bash, Read, Write, Edit, Glob, Grep (safe delegation)
357
+
358
+ **CLI Expansion (ppm bot commands):**
359
+ ```
360
+ ppm bot delegate --chat <id> --project <name> --prompt "<text>" # Create task
361
+ ppm bot task-status <id> # Check status
362
+ ppm bot task-result <id> # Get full output
363
+ ppm bot tasks [--chat <id>] # List recent
364
+ ppm bot project list # Available projects
365
+ ppm bot project current # Active project
366
+ ppm bot project switch <name> # Switch project
367
+ ppm bot session new <title> # Create session
368
+ ppm bot session list # List sessions
369
+ ppm bot session resume <id> # Resume session
370
+ ppm bot session stop <id> # Stop session
371
+ ppm bot status # Bot health
372
+ ppm bot version # PPM version
373
+ ppm bot restart # Restart service
374
+ ppm bot help # Help
375
+ ```
376
+
377
+ **Settings UI (ppmbot-settings-section.tsx):**
378
+ - Enable/disable PPMBot
379
+ - Paired Telegram chats (approval management)
380
+ - Default project selection
381
+ - System prompt customization
382
+ - Task auto-refresh (poll interval, max history)
383
+ - Delegated tasks panel (status, result preview, delete)
384
+
385
+ ---
386
+
387
+ ### ClawBot Service Layer (Telegram Bot Integration) — LEGACY (v0.9.10)
388
+ **Component:** Telegram bot service + subsidiary services
389
+
390
+ **Responsibilities:**
391
+ - Receive Telegram messages via long-polling (no webhooks needed)
392
+ - Route Telegram user (chatID) to PPM session with pairing-based security
393
+ - Persist session state + conversation memory in SQLite (FTS5)
394
+ - Stream AI responses back to Telegram with progressive message editing
395
+ - Format responses as Telegram HTML with proper chunking (4096 char limit)
396
+
397
+ **Architecture:**
398
+ ```
399
+ Telegram → ClawBotTelegramService (polling) → ClawBotService (orchestrator)
400
+
401
+ ClawBotSessionService (chatID→sessionID)
402
+ ClawBotMemoryService (FTS5 recall)
403
+ ChatService + ProviderRegistry
404
+ ClawBotStreamerService (ChatEvent→edits)
405
+ ClawBotFormatterService (Markdown→HTML)
406
+ ```
407
+
408
+ **Services (src/services/clawbot/):**
409
+ - **ClawBotService** — Lifecycle management (start/stop), message queue, routing logic
410
+ - **ClawBotTelegramService** — Telegram Bot API wrapper (getUpdates long-polling, sendMessage, editMessage, setTyping, command handlers)
411
+ - **ClawBotSessionService** — chatID ↔ PPM sessionID bidirectional mapping, session state tracking
412
+ - **ClawBotMemoryService** — FTS5 persistent memory (save, recall with relevance, decay factor, supersede logic, cross-project search by name mention)
413
+ - **ClawBotFormatterService** — Markdown → Telegram HTML conversion, message chunking (respects 4096 char limit), code block formatting
414
+ - **ClawBotStreamerService** — ChatEvent async generator → progressive Telegram message edits (1s throttle for rate limiting)
415
+
416
+ **Security Model:**
417
+ - **Pairing System** — Replace allowlists with code-based pairing: User requests pairing → receives code → owner approves in web UI → chatID registered in `clawbot_paired_chats`
418
+ - **Per-User Sessions** — Each Telegram chatID maps to isolated PPM session (no cross-user interference)
419
+ - **bypassPermissions** — ClawBot bot runs headless, auto-approves tools (no manual approval flow)
420
+
421
+ **Database Schema (v13):**
422
+ - `clawbot_sessions` — chatID (PK), sessionID (FK chat_sessions), pairedAt, lastUsed
423
+ - `clawbot_memories` — id (PK), sessionID (FK), content (text), role (user|assistant), created, decay_factor (FTS5 full-text index)
424
+ - `clawbot_paired_chats` — chatID (PK), pairingCode (unique, 6 chars), approvedAt, approvedBy (user ID)
425
+
426
+ **Key Design Decisions:**
427
+ 1. **Long-polling** — No webhooks = no public URL required, simpler for self-hosted
428
+ 2. **Message queue** — Concurrent Telegram messages queued FIFO, prevents race conditions
429
+ 3. **Progressive edits** — Edit same message for long responses, reduce Telegram API calls, better UX
430
+ 4. **Memory system** — Hybrid extraction (AI primary + regex fallback), supports cross-project search by project name mention
431
+ 5. **Config reuse** — Shares existing Telegram bot_token with notifications, separate ClawBotConfig section
432
+ 6. **Session tagging** — [Claw] prefix visible in web UI without schema changes, robot icon for identification
433
+
434
+ **Settings Endpoints:**
435
+ - `GET /api/settings/clawbot` — Fetch config (enabled, bot token, default project, system prompt, debounce, display toggles)
436
+ - `PUT /api/settings/clawbot` — Update config
437
+ - `GET /api/clawbot/paired-chats` — List paired Telegram chatIDs
438
+ - `POST /api/clawbot/pairing` — Request pairing code (returns code)
439
+ - `POST /api/clawbot/pairing/:code/approve` — Approve pairing code (owner only)
440
+ - `DELETE /api/clawbot/paired-chats/:chatId` — Revoke pairing
441
+
442
+ ---
443
+
444
+ ### Data Access Layer (SQLite + Filesystem + Git)
445
+ **Components:** SQLite via bun:sqlite, direct filesystem access, simple-git wrapper
446
+
447
+ **Responsibilities:**
448
+ - Persist config, projects, session maps, usage, logs in SQLite
449
+ - Read/write project files with path validation
450
+ - Execute git commands via simple-git
451
+ - Cache directory listings
452
+ - Enforce security (no parent directory access)
453
+
454
+ **Key Patterns:**
455
+ - SQLite: WAL mode, foreign keys, lazy init, schema v13 (13 tables: config, connections, accounts, usage_history, session_logs, push_subscriptions, session_map, table_metadata, workspace_state, extension_storage, mcp_servers, clawbot_sessions, clawbot_memories, clawbot_paired_chats)
456
+ - Path validation: `projectPath/relativePath` only, reject `..`
457
+ - Caching: Directory trees cached with TTL
458
+ - Error handling: Descriptive messages (file not found, permission denied)
459
+ - Migration: Automatic YAML→SQLite migration on first run with new db.service; schema auto-upgrade on version bump
460
+
461
+ ---
462
+
463
+ ### State Management (Frontend)
464
+ **Component:** Zustand stores in browser
465
+
466
+ **Stores:**
467
+ - **projectStore** — Active project, project list, localStorage persistence
468
+ - **tabStore** — Tab facade, delegates to panelStore
469
+ - **panelStore** — Grid layout (rows/columns), panel creation/movement, keep-alive snapshots
470
+ - **fileStore** — File cache
471
+ - **settingsStore** — Theme, sidebar state, git view mode, device name
472
+
473
+ **Pattern:** Selectors for subscriptions (only re-render affected components)
474
+
475
+ ```typescript
476
+ const messages = chatStore((s) => s.messages); // Subscribe to messages only
477
+ ```
478
+
479
+ #### Workspace Sync (v0.8.77+)
480
+
481
+ **Deterministic Tab IDs & URL Routing:**
482
+ - Tab IDs derived from type + metadata: `deriveTabId(type, metadata) → {type}:{identifier}`
483
+ - Examples: `editor:src/index.ts`, `chat:claude/abc123`, `terminal:1`, `git-graph`
484
+ - URLs rebuilt from active tab: `/project/{name}/{type}/{identifier}`
485
+ - Deep linking: URL → `parseUrlState()` → auto-create tabs if missing
486
+
487
+ **Workspace Persistence:**
488
+ 1. **Client**: PanelStore layout (grid, panels, tabs) cached in localStorage per project
489
+ 2. **Server**: Workspace JSON persisted in `workspace_state` SQLite table
490
+ 3. **Sync Flow:**
491
+ - User loads project → fetch workspace from server (GET `/api/project/:name/workspace`)
492
+ - Latest-wins: server `updated_at` vs client localStorage timestamp
493
+ - Panel layout changes debounced (1.5s) → POST to server
494
+ - On reconnect: server layout restored, client edits queued
495
+ 4. **Cross-Device:** Any device can load workspace, browser restores exact grid + active tabs
496
+
497
+ ---
498
+
499
+ ## Communication Protocols
500
+
501
+ ### REST API (Request/Response)
502
+ **Protocol:** HTTP/1.1 with JSON
503
+
504
+ **Pattern:**
505
+ 1. Client sends request with auth token header
506
+ 2. Server validates token (middleware)
507
+ 3. Service processes request
508
+ 4. Response formatted as `ApiResponse<T>` envelope
509
+ 5. HTTP status set (200, 400, 404, 500)
510
+
511
+ **Example:**
512
+ ```
513
+ POST /api/project/my-project/chat/sessions/abc/messages HTTP/1.1
514
+ Authorization: Bearer <token>
515
+ Content-Type: application/json
516
+
517
+ { "content": "What does this code do?" }
518
+
519
+ HTTP/1.1 200 OK
520
+ {
521
+ "ok": true,
522
+ "data": {
523
+ "messageId": "msg-123",
524
+ "sessionId": "abc"
525
+ }
526
+ }
527
+ ```
528
+
529
+ ---
530
+
531
+ ### WebSocket (Streaming)
532
+ **Protocol:** WebSocket over HTTP/1.1
533
+
534
+ **Chat Streaming Flow:**
535
+ 1. Client connects: `WS /ws/project/:name/chat/:sessionId`
536
+ 2. Client sends: `{ type: "message", content: "..." }`
537
+ 3. Server streams messages:
538
+ - `{ type: "text", content: "..." }` (incremental)
539
+ - `{ type: "tool_use", tool: "file_read", input: {...} }`
540
+ - `{ type: "approval_request", requestId, tool, input }`
541
+ - `{ type: "done", sessionId }`
542
+ 4. Client approves tool: `{ type: "approval_response", requestId, approved: true }`
543
+
544
+ **Terminal I/O Flow:**
545
+ 1. Client connects: `WS /ws/project/:name/terminal/:id`
546
+ 2. Client sends: `{ type: "input", data: "ls\n" }`
547
+ 3. Server sends: `{ type: "output", data: "file1 file2\n" }`
548
+ 4. Client sends: `{ type: "resize", cols: 80, rows: 24 }`
549
+
550
+ ---
551
+
552
+ ## Project Workspace Management
553
+
554
+ ### Keep-Alive Pattern (v2.0+)
555
+ When switching projects, workspaces are preserved instead of destroyed:
556
+ 1. **Workspace Mount State**: Each project's UI (tabs, terminal xterm DOM, file selections) remains mounted in the DOM
557
+ 2. **Visibility Toggle**: CSS `display: none/block` hides/shows workspaces instead of React unmounting
558
+ 3. **Terminal DOM Persistence**: xterm.js terminal instances retain their DOM structure across switches (prevents re-render flicker)
559
+ 4. **Cache Efficiency**: Zustand stores persist open tabs, selections, and scroll positions per project
560
+
561
+ **Benefits:**
562
+ - Instant project switching (no DOM reconstruction)
563
+ - Terminal history preserved across switches
564
+ - Smooth UX without flashing/re-rendering
565
+ - Reduced network requests (cached UI state)
566
+
567
+ ### Project Color & Ordering (v2.0+)
568
+ **Storage**: Colors stored as optional `color` field in `Project` interface (hex string or undefined)
569
+
570
+ **Endpoints:**
571
+ - `PATCH /api/projects/:name/color` — Update project color
572
+ - `PATCH /api/projects/reorder` — Reorder projects array in config
573
+
574
+ **UI Components:**
575
+ - `ProjectBar` (52px sidebar) — Shows project avatars with color backgrounds, context menu for reorder/rename/delete/color-picker
576
+ - `ProjectBottomSheet` (mobile) — Bottom sheet switcher with scrollable project list
577
+ - `ProjectAvatar` utility — Generates smart initials with collision resolution (prefer 1-char, fallback to 2-char or index)
578
+ - `PROJECT_PALETTE` — 12-color palette for default colors when not customized
579
+
580
+ ---
581
+
582
+ ## Code Editor Migration (v2.0+)
583
+
584
+ **Migration**: CodeMirror 6 → Monaco Editor (@monaco-editor/react)
585
+
586
+ **Reasons:**
587
+ - Better syntax highlighting for complex languages
588
+ - Superior IntelliSense and code completion
589
+ - Performance improvements on large files
590
+ - More polished diff viewer experience
591
+
592
+ **Components Updated:**
593
+ - `src/web/components/editor/code-editor.tsx` — Monaco Editor with language detection
594
+ - `src/web/components/editor/diff-viewer.tsx` — Monaco diff viewer for git diffs
595
+
596
+ **Features:**
597
+ - Alt+Z toggle for word wrap
598
+ - Automatic language detection from file extension
599
+ - Theme sync with app dark/light mode
600
+ - Responsive layout with proper scrolling
601
+
602
+ ---
603
+
604
+ ## Authentication Flow
605
+
606
+ ```
607
+ User opens http://localhost:8080
608
+
609
+ App checks localStorage for auth token
610
+
611
+ If no token:
612
+ → LoginScreen shown (prompt for token)
613
+ → GET /api/auth/check to validate token
614
+
615
+ If valid token:
616
+ → Store in localStorage
617
+ → Load projects: GET /api/projects
618
+ → Main UI rendered
619
+
620
+ For each API request:
621
+ → Include "Authorization: Bearer <token>" header
622
+ → Middleware validates token
623
+ → If invalid → 401 Unauthorized
624
+ ```
625
+
626
+ **Token Management:**
627
+ - Generated on `ppm init` → stored in `ppm.yaml`
628
+ - Sent from CLI via `-c <config>` flag
629
+ - Stored in browser localStorage for session persistence
630
+ - No expiry (single-user, local environment)
631
+
632
+ ---
633
+
634
+ ## AI Provider Configuration
635
+
636
+ PPM exposes AI settings as global configuration (not per-session) via REST API and Settings UI. Configuration is stored in `ppm.yaml` and read fresh per query.
637
+
638
+ ### Configuration Shape
639
+ ```yaml
640
+ ai:
641
+ default_provider: claude
642
+ providers:
643
+ claude:
644
+ type: agent-sdk
645
+ api_key_env: ANTHROPIC_API_KEY
646
+ model: claude-sonnet-4-6
647
+ effort: high
648
+ max_turns: 100
649
+ max_budget_usd: 2.00
650
+ thinking_budget_tokens: 10000
651
+ ```
652
+
653
+ **Fields:**
654
+ - `default_provider`: Active provider name (e.g., `claude`)
655
+ - `type`: Provider type (`agent-sdk` or `mock`)
656
+ - `api_key_env`: Environment variable containing API key
657
+ - `model`: Model ID (e.g., `claude-sonnet-4-6`, `claude-opus-4-6`)
658
+ - `effort`: Processing level (`low`, `medium`, `high`, `max`)
659
+ - `max_turns`: Maximum interaction turns (1-500, default 100)
660
+ - `max_budget_usd`: Spending limit in USD (optional)
661
+ - `thinking_budget_tokens`: Extended thinking budget in tokens (optional, 0=disabled)
662
+
663
+ ### API Endpoints
664
+
665
+ **GET /api/settings/ai** — Fetch current AI config
666
+ ```json
667
+ {
668
+ "ok": true,
669
+ "data": {
670
+ "default_provider": "claude",
671
+ "providers": { "claude": {...} }
672
+ }
673
+ }
674
+ ```
675
+
676
+ **PUT /api/settings/ai** — Update AI config (shallow merge per provider)
677
+ ```json
678
+ {
679
+ "providers": {
680
+ "claude": {
681
+ "model": "claude-opus-4-6",
682
+ "max_turns": 50
683
+ }
684
+ }
685
+ }
686
+ ```
687
+ Returns full updated config. Validates ranges/enums before writing.
688
+
689
+ ### How Provider Uses Settings
690
+
691
+ 1. **SDK Provider (`sendMessage`)**
692
+ - Calls `getProviderConfig()` to read fresh config from `configService`
693
+ - Maps snake_case config to camelCase SDK options
694
+ - Passes `model`, `effort`, `maxTurns`, `maxBudgetUsd`, `thinkingBudgetTokens` to `query()`
695
+ - Falls back to defaults if fields not set
696
+
697
+ 2. **Mock Provider**
698
+ - Ignores AI settings (always returns canned responses for testing)
699
+
700
+ 3. **Changes Take Effect**
701
+ - Immediately on next query (config read fresh each time)
702
+ - No active queries affected (config mid-flight not re-evaluated)
703
+
704
+ ---
705
+
706
+ ## Chat Streaming Flow (Persistent AsyncGenerator Sessions)
707
+
708
+ ### Architecture Overview (v0.8.55+)
709
+
710
+ PPM uses a **persistent streaming session** model instead of per-message query execution:
711
+
712
+ **Key Changes:**
713
+ - Provider maintains **long-lived AsyncGenerator streaming input** per chat session (not per message)
714
+ - Follow-up messages **push into the existing generator** instead of abort-and-replace
715
+ - **Single streaming loop** per session decoupled from WebSocket message handler
716
+ - Message priority support: `now` (interrupt current), `next` (queue first), `later` (queue at end)
717
+ - Supports image attachments in messages
718
+
719
+ **Design Benefits:**
720
+ - Continuous context preservation — multi-turn conversations flow naturally
721
+ - No SDK subprocess restarts between messages (faster)
722
+ - Clean separation: BE owns Claude connection, FE disconnect doesn't abort
723
+ - Message buffering on reconnect — clients that lose WS connection sync turn events
724
+ - Tool approvals don't restart the query — integrated into streaming loop
725
+
726
+ ### Message Flow
727
+
728
+ ```
729
+ User types: "Debug this function"
730
+
731
+ MessageInput.tsx calls useChat.sendMessage()
732
+
733
+ useChat opens WebSocket: WS /ws/project/:name/chat/:sessionId
734
+
735
+ Sends: { type: "message", content: "Debug...", priority?: "now"|"next"|"later" }
736
+
737
+ WS handler in chat.ts receives message
738
+
739
+ If already streaming with different content → abort previous + wait cleanup
740
+ If streaming, new message priority determines queue behavior:
741
+ • priority: "now" → abort current, restart with new content
742
+ • priority: "next" → push into pending queue (higher priority)
743
+ • priority: "later" → push to end of queue (FIFO)
744
+
745
+ runStreamLoop() executes in detached async context
746
+
747
+ ChatService calls provider.sendMessage() (async generator)
748
+
749
+ Provider (Claude SDK) yields events:
750
+ 1. { type: "text", content: "Here's what..." }
751
+ 2. { type: "text", content: " happens..." }
752
+ 3. { type: "tool_use", tool: "read_file", input: {...} }
753
+
754
+ Stream loop buffers + broadcasts to all connected clients:
755
+ { type: "text", content: "Here's what..." }
756
+ { type: "text", content: " happens..." }
757
+ { type: "tool_use", tool: "read_file", input: {...} }
758
+ { type: "approval_request", requestId, tool, input }
759
+
760
+ Client receives, displays message incrementally
761
+
762
+ User sees tool approval prompt, clicks "Approve"
763
+
764
+ Client sends: { type: "approval_response", requestId, approved: true }
765
+
766
+ Provider continues streaming with tool result (no restart)
767
+
768
+ If multiple messages queued, next message processes after done event
769
+
770
+ Final response streamed, then: { type: "done", sessionId }
771
+
772
+ Phase transitions to idle, clients can send new message
773
+
774
+ useChat saves message to store, displays in chat history
775
+ ```
776
+
777
+ ### Session State Management
778
+
779
+ **Session Entry** (BE-owned, persists across FE disconnections):
780
+ ```typescript
781
+ interface SessionEntry {
782
+ providerId: string; // Which AI provider (e.g., "claude")
783
+ clients: Set<ChatWsSocket>; // Connected FE clients (may be empty)
784
+ abort?: AbortController; // Current stream abort handle
785
+ projectPath?: string; // Project context
786
+ projectName?: string;
787
+ pingIntervals: Map<...>; // Per-client keepalive
788
+ phase: SessionPhase; // "initializing" | "connecting" | "thinking" | "streaming" | "idle"
789
+ cleanupTimer?: ReturnType<...>; // Auto-cleanup if no FE reconnects (5min)
790
+ pendingApprovalEvent?: {...}; // Current tool approval waiting
791
+ turnEvents: unknown[]; // Buffered events (for reconnect sync)
792
+ streamPromise?: Promise<void>; // Track ongoing runStreamLoop
793
+ permissionMode?: string; // Sticky permission mode for session
794
+ }
795
+ ```
796
+
797
+ **Client Connection States:**
798
+ - **Active streaming + FE connected** → Events broadcast to all clients in real-time
799
+ - **Active streaming + FE disconnected** → Events buffered in turnEvents array, BE stream continues
800
+ - **FE reconnects** → Receive session_state + buffered turnEvents, resync with stream
801
+ - **Idle (no query running)** → Phase is "idle", ready for next message
802
+ - **Idle + no FE for 5min** → Cleanup timer removes session from memory
803
+
804
+ ### Follow-up Messages
805
+
806
+ **Abort-and-Replace Pattern:**
807
+ ```typescript
808
+ if (entry.phase !== "idle" && entry.abort) {
809
+ console.log(`[chat] aborting current query for new message`);
810
+ entry.abort.abort();
811
+ await entry.streamPromise; // Wait for cleanup
812
+ // Re-fetch entry — may have been mutated during cleanup
813
+ entry = activeSessions.get(sessionId)!;
814
+ }
815
+ ```
816
+
817
+ **Multiple Message Queueing:**
818
+ - First message: immediately starts runStreamLoop
819
+ - Second message (while streaming): abort current, wait, start new runStreamLoop
820
+ - Priority modes (future): could queue messages for intelligent interleaving
821
+
822
+ ### WebSocket Reconnection Sync
823
+
824
+ ```
825
+ FE WebSocket closes (network issue, tab closes)
826
+
827
+ BE keeps session alive, streaming continues
828
+
829
+ FE reconnects: WS /ws/project/:name/chat/:sessionId
830
+
831
+ open() handler checks activeSessions.get(sessionId)
832
+
833
+ If exists (entry found):
834
+ 1. Clear cleanup timer (FE is back)
835
+ 2. Send session_state with current phase + pendingApproval
836
+ 3. If phase !== "idle", send buffered turnEvents
837
+ 4. Add WS to clients Set
838
+
839
+ FE processes session_state, renders current phase
840
+
841
+ FE applies buffered events to rebuild turn state
842
+
843
+ FE displays: "reconnected, current phase: streaming" etc.
844
+ ```
845
+
846
+ ### Phase Transitions
847
+
848
+ ```
849
+ idle → initializing → connecting → thinking/streaming ↔ thinking/streaming → idle
850
+ ^ ↑ ↓
851
+ └──────────────────────────────────────────────────────────────────────────┘
852
+ ```
853
+
854
+ **Phase Descriptions:**
855
+ - **idle** — No query running, ready to accept new message
856
+ - **initializing** — Preparing (permission checks, session resume)
857
+ - **connecting** — Waiting for first SDK event (heartbeat: "connecting" with elapsed time every 5s)
858
+ - **thinking** — Receiving thinking content (extended thinking)
859
+ - **streaming** — Receiving text/tool_use content (dynamic switch between thinking/streaming)
860
+
861
+ ### Image Attachment Support
862
+
863
+ Messages can now include images:
864
+ ```typescript
865
+ type ChatWsClientMessage =
866
+ | { type: "message"; content: string; images?: { id: string; data: string }[]; priority?: string }
867
+ | ...
868
+ ```
869
+
870
+ Images are passed to provider's message context and included in tool input/output.
871
+
872
+ ---
873
+
874
+ ## Terminal Flow
875
+
876
+ ```
877
+ User clicks Terminal tab
878
+
879
+ TerminalTab.tsx mounts
880
+
881
+ useTerminal hook opens WebSocket: WS /ws/project/:name/terminal/:id
882
+
883
+ TerminalService.spawn() creates PTY (Bun.spawn)
884
+
885
+ xterm.js renders terminal emulator
886
+
887
+ User types: "npm test"
888
+
889
+ xterm.js captures key event
890
+
891
+ Sends via WebSocket: { type: "input", data: "npm test\n" }
892
+
893
+ TerminalService.write(pty, "npm test\n")
894
+
895
+ npm process spawned inside PTY
896
+
897
+ Output captured: "PASS: all tests\n"
898
+
899
+ TerminalService sends: { type: "output", data: "PASS: all tests\n" }
900
+
901
+ xterm.js renders output
902
+
903
+ User resizes window → xterm.js resizes terminal
904
+
905
+ Sends: { type: "resize", cols: 120, rows: 40 }
906
+
907
+ TerminalService calls pty.resize()
908
+
909
+ Shell (bash/zsh) receives SIGWINCH signal
910
+
911
+ Terminal state updated
912
+ ```
913
+
914
+ ---
915
+
916
+ ## Git Integration Flow
917
+
918
+ ```
919
+ User right-clicks file in FileTree
920
+
921
+ Context menu shows "Stage" option
922
+
923
+ User clicks "Stage"
924
+
925
+ FileActions.tsx calls POST /api/project/:name/git/stage
926
+
927
+ Sends: { path: "src/index.ts" }
928
+
929
+ GitService.stage(projectPath, "src/index.ts")
930
+
931
+ Executes: git add src/index.ts (via simple-git)
932
+
933
+ Returns: { ok: true }
934
+
935
+ GitStatusPanel.tsx refreshes: GET /api/project/:name/git/status
936
+
937
+ GitService.status() returns:
938
+ {
939
+ current: "main",
940
+ staged: ["src/index.ts"],
941
+ unstaged: ["README.md"],
942
+ untracked: ["temp.log"]
943
+ }
944
+
945
+ UI updates: "src/index.ts" moves from "Unstaged" to "Staged"
946
+ ```
947
+
948
+ ---
949
+
950
+ ## Database Management (v2.0+)
951
+
952
+ ### Architecture Overview
953
+
954
+ PPM now supports managing external databases (SQLite & PostgreSQL) through a unified adapter pattern:
955
+
956
+ ```
957
+ ┌─────────────────────────────────────────────────────────────────┐
958
+ │ Web UI (React) │
959
+ │ ┌──────────────────────────────────────────────────────────┐ │
960
+ │ │ Database Sidebar │ │
961
+ │ │ • Connection List (with color badges) │ │
962
+ │ │ • Create/Edit Connection Form │ │
963
+ │ │ • Color Picker (WCAG contrast-aware) │ │
964
+ │ │ • Query Execution UI │ │
965
+ │ └──────────────────────────────────────────────────────────┘ │
966
+ └─────────────────┬───────────────────────────────────────────────┘
967
+ │ HTTP REST / WebSocket
968
+ ┌─────────────────┴───────────────────────────────────────────────┐
969
+ │ PPM Server (Hono) │
970
+ │ ┌──────────────────────────────────────────────────────────┐ │
971
+ │ │ /api/db Routes │ │
972
+ │ │ • GET /connections → List all connections │ │
973
+ │ │ • POST /connections → Create connection │ │
974
+ │ │ • GET /connections/:id → Get connection (sanitized) │ │
975
+ │ │ • PUT /connections/:id → Update (readonly toggle) │ │
976
+ │ │ • DELETE /connections/:id → Remove connection │ │
977
+ │ │ • GET /connections/:id/tables → List + sync tables │ │
978
+ │ │ • GET /connections/:id/tables/:tbl → Schema + data │ │
979
+ │ │ • POST /connections/:id/query → Execute query │ │
980
+ │ │ • PATCH /connections/:id/cell → Update cell │ │
981
+ │ └──────────────────────────────────────────────────────────┘ │
982
+ │ ┌──────────────────────────────────────────────────────────┐ │
983
+ │ │ Service Layer │ │
984
+ │ │ • DbService (connection CRUD, caching) │ │
985
+ │ │ • TableCacheService (metadata cache, search) │ │
986
+ │ │ • DatabaseAdapterRegistry (extensible) │ │
987
+ │ └──────────────────────────────────────────────────────────┘ │
988
+ │ ┌──────────────────────────────────────────────────────────┐ │
989
+ │ │ Adapters (Pluggable Pattern) │ │
990
+ │ │ • SQLiteAdapter → Uses `bun:sqlite` for local files │ │
991
+ │ │ • PostgresAdapter → Uses postgres driver for servers │ │
992
+ │ │ • isReadOnlyQuery() → Safety check (CTE-safe regex) │ │
993
+ │ │ • readonly=1 by default (safe-by-default) │ │
994
+ │ └──────────────────────────────────────────────────────────┘ │
995
+ └──────────────────────────────────────────────────────────────────┘
996
+ ↓↑
997
+ ┌────────────────────────────────────────────┐
998
+ │ External Databases │
999
+ │ • SQLite files (path: /path/to/db.db) │
1000
+ │ • PostgreSQL servers (connStr: postgres://)│
1001
+ └────────────────────────────────────────────┘
1002
+ ```
1003
+
1004
+ ### DatabaseAdapter Pattern (Extensible)
1005
+
1006
+ **Interface** (`src/types/database.ts`):
1007
+ ```typescript
1008
+ interface DatabaseAdapter {
1009
+ testConnection(config: DbConnectionConfig): Promise<{ ok: boolean; error?: string }>;
1010
+ getTables(config: DbConnectionConfig): Promise<DbTableInfo[]>;
1011
+ getTableSchema(config: DbConnectionConfig, table: string, schema?: string): Promise<DbColumnInfo[]>;
1012
+ getTableData(config: DbConnectionConfig, table: string, opts: {...}): Promise<DbPagedData>;
1013
+ executeQuery(config: DbConnectionConfig, sql: string): Promise<DbQueryResult>;
1014
+ updateCell(config: DbConnectionConfig, table: string, opts: {...}): Promise<void>;
1015
+ }
1016
+ ```
1017
+
1018
+ **Implementations:**
1019
+ 1. **SQLiteAdapter** — Local file-based SQLite via `bun:sqlite`
1020
+ - testConnection: Opens file, runs pragma check
1021
+ - Supports: SELECT, INSERT, UPDATE, DELETE (if writable), CREATE TABLE
1022
+
1023
+ 2. **PostgresAdapter** — Remote PostgreSQL servers via postgres driver
1024
+ - testConnection: Attempts connection with credentials
1025
+ - Supports: Full SQL except DDL on readonly connections
1026
+
1027
+ **Registry Pattern** (`src/services/database/adapter-registry.ts`):
1028
+ ```typescript
1029
+ registerAdapter("sqlite", new SQLiteAdapter());
1030
+ registerAdapter("postgres", new PostgresAdapter());
1031
+ // Can be extended: registerAdapter("mysql", new MysqlAdapter());
1032
+ ```
1033
+
1034
+ ### Security Design
1035
+
1036
+ **Readonly by Default:**
1037
+ - All connections created with `readonly = true` in database
1038
+ - Default: read-only query execution (safe-by-default)
1039
+ - Web UI toggle: Switch to writable (admin decision only)
1040
+ - CLI: Cannot disable readonly via command-line (browser only)
1041
+
1042
+ **Readonly Query Detection:**
1043
+ ```typescript
1044
+ // isReadOnlyQuery() in src/services/database/readonly-check.ts
1045
+ // Checks for: SELECT, PRAGMA, EXPLAIN, WITH (CTE)
1046
+ // Rejects: INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, etc.
1047
+ // CTE-safe: Handles "WITH AS SELECT" (wraps CTE result check)
1048
+ ```
1049
+
1050
+ **Credential Handling:**
1051
+ - Connection credentials stored in SQLite `connections` table as `connection_config` JSON
1052
+ - **NEVER** returned in API responses (stripped by `sanitizeConn()` in routes)
1053
+ - Only used internally by adapters when executing queries
1054
+ - Frontend never sees passwords/connection strings
1055
+
1056
+ **API Security:**
1057
+ - All `/api/db` requests require valid auth token (middleware checked)
1058
+ - Connection IDs are numeric (no enumeration risk)
1059
+ - Connection color is user-specific (cosmetic only, not sensitive)
1060
+
1061
+ ### Data Flow: Query Execution
1062
+
1063
+ ```
1064
+ User opens Database tab
1065
+
1066
+ DatabaseSidebar fetches: GET /api/db/connections
1067
+
1068
+ ConnectionList displays (sanitized, no credentials)
1069
+
1070
+ User clicks connection → GET /api/db/connections/:id/tables
1071
+
1072
+ DbService.getConnections() reads from SQLite
1073
+
1074
+ TableCacheService.syncTables() calls adapter.getTables()
1075
+
1076
+ SQLiteAdapter/PostgresAdapter queries database
1077
+
1078
+ Results cached in table_metadata table
1079
+
1080
+ UI displays table list + schema
1081
+
1082
+ User selects table → GET /api/db/connections/:id/tables/:table
1083
+
1084
+ Adapter.getTableData() executes paginated query
1085
+
1086
+ Results returned: { columns, rows, total, page, limit }
1087
+
1088
+ UI renders table grid with pagination
1089
+
1090
+ User executes custom query → POST /api/db/connections/:id/query
1091
+
1092
+ isReadOnlyQuery() checks SQL (rejects writes if readonly=true)
1093
+
1094
+ Adapter.executeQuery() runs SQL
1095
+
1096
+ Results returned: { columns, rows, rowsAffected, changeType }
1097
+
1098
+ UI displays results (read-only highlight if mutation was blocked)
1099
+ ```
1100
+
1101
+ ### Connection Storage
1102
+
1103
+ **SQLite Schema** (in `~/.ppm/ppm.db`):
1104
+ ```sql
1105
+ CREATE TABLE accounts (
1106
+ id TEXT PRIMARY KEY,
1107
+ account_name TEXT NOT NULL,
1108
+ encrypted_api_key TEXT NOT NULL,
1109
+ priority INTEGER DEFAULT 0,
1110
+ is_active INTEGER DEFAULT 0, -- 1 = active, 0 = inactive
1111
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
1112
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP
1113
+ );
1114
+
1115
+ CREATE TABLE connections (
1116
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1117
+ type TEXT NOT NULL, -- 'sqlite' | 'postgres'
1118
+ name TEXT NOT NULL,
1119
+ connection_config TEXT NOT NULL, -- JSON: { path, connectionString, ... }
1120
+ readonly INTEGER DEFAULT 1, -- 1 = readonly, 0 = writable (UI-only toggle)
1121
+ group_name TEXT,
1122
+ color TEXT, -- Optional hex color (#3b82f6)
1123
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
1124
+ updated_at TEXT DEFAULT CURRENT_TIMESTAMP
1125
+ );
1126
+
1127
+ CREATE TABLE table_metadata (
1128
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1129
+ connection_id INTEGER NOT NULL REFERENCES connections(id) ON DELETE CASCADE,
1130
+ table_name TEXT NOT NULL,
1131
+ schema_name TEXT DEFAULT 'public',
1132
+ row_count INTEGER,
1133
+ last_synced TEXT,
1134
+ UNIQUE(connection_id, table_name, schema_name)
1135
+ );
1136
+ ```
1137
+
1138
+ ### CLI Support (ppm db)
1139
+
1140
+ **Commands** (`src/cli/commands/db-cmd.ts`):
1141
+ ```bash
1142
+ ppm db connections # List all connections
1143
+ ppm db connect # Add new connection (interactive)
1144
+ ppm db remove <name> # Delete connection
1145
+ ppm db query <name> <sql> # Execute query (respects readonly)
1146
+ ppm db tables <name> # List tables
1147
+ ppm db schema <name> <table> # Show table schema
1148
+ ppm db data <name> <table> # Show table data (paginated)
1149
+ ```
1150
+
1151
+ **CLI Safety:**
1152
+ - Always respects readonly flag (cannot override via CLI)
1153
+ - Uses same adapter/validation as web UI
1154
+ - Table formatting for terminal output
1155
+
1156
+ ---
1157
+
1158
+ ## MCP Server Management
1159
+
1160
+ ### Overview
1161
+ MCP (Model Context Protocol) servers extend Claude with custom tools and resources. PPM manages MCP server configurations via Settings UI, storing them in SQLite and passing them to the Claude Agent SDK.
1162
+
1163
+ **Features:**
1164
+ - **Add/Edit/Delete** MCP servers via Settings UI
1165
+ - **Auto-import** from `~/.claude.json` on first access (convenience, no forced import)
1166
+ - **Three transport types:** stdio, HTTP, SSE
1167
+ - **Validation** on name and config before storage
1168
+ - **SDK integration:** Servers passed to `query()` as `mcpServers` object, tools auto-allowed via `mcp__*` wildcard
1169
+
1170
+ ### Storage Schema
1171
+
1172
+ ```sql
1173
+ CREATE TABLE mcp_servers (
1174
+ name TEXT PRIMARY KEY,
1175
+ transport TEXT NOT NULL DEFAULT 'stdio', -- 'stdio' | 'http' | 'sse'
1176
+ config TEXT NOT NULL, -- JSON: McpServerConfig
1177
+ created_at TEXT DEFAULT (datetime('now')),
1178
+ updated_at TEXT DEFAULT (datetime('now'))
1179
+ );
1180
+ ```
1181
+
1182
+ **Config Format (JSON):**
1183
+ ```json
1184
+ {
1185
+ "type": "stdio",
1186
+ "command": "path/to/server",
1187
+ "args": ["--flag"],
1188
+ "env": { "VAR": "value" }
1189
+ }
1190
+ ```
1191
+
1192
+ Or HTTP/SSE:
1193
+ ```json
1194
+ {
1195
+ "type": "http",
1196
+ "url": "http://localhost:3000",
1197
+ "headers": { "Authorization": "Bearer token" }
1198
+ }
1199
+ ```
1200
+
1201
+ ### REST API
1202
+
1203
+ **Endpoints** (`src/server/routes/mcp.ts`):
1204
+
1205
+ | Method | Endpoint | Description |
1206
+ |--------|----------|-------------|
1207
+ | **GET** | `/api/settings/mcp` | List all servers; auto-import on first access |
1208
+ | **GET** | `/api/settings/mcp/:name` | Get single server config |
1209
+ | **POST** | `/api/settings/mcp` | Add new server (validates name + config) |
1210
+ | **PUT** | `/api/settings/mcp/:name` | Update existing server |
1211
+ | **DELETE** | `/api/settings/mcp/:name` | Remove server |
1212
+ | **GET** | `/api/settings/mcp/import/preview` | Preview servers in `~/.claude.json` |
1213
+ | **POST** | `/api/settings/mcp/import` | Bulk import from `~/.claude.json` |
1214
+
1215
+ **Add Server Example:**
1216
+ ```bash
1217
+ POST /api/settings/mcp
1218
+ Content-Type: application/json
1219
+
1220
+ {
1221
+ "name": "file-server",
1222
+ "config": {
1223
+ "type": "stdio",
1224
+ "command": "/usr/local/bin/file-server",
1225
+ "args": ["--port", "8000"]
1226
+ }
1227
+ }
1228
+ ```
1229
+
1230
+ ### Service Layer
1231
+
1232
+ **McpConfigService** (`src/services/mcp-config.service.ts`):
1233
+ - `list()` — Record<name, McpServerConfig> (SDK-compatible format)
1234
+ - `listWithMeta()` — Array with metadata (for UI)
1235
+ - `get(name)` — Single server config
1236
+ - `set(name, config)` — Add or update (upsert)
1237
+ - `remove(name)` — Delete server
1238
+ - `exists(name)` — Check if name exists
1239
+ - `bulkImport(servers)` — Transactional import from `~/.claude.json`, skips existing/invalid
1240
+
1241
+ **Validation:**
1242
+ - `validateMcpName(name)` — alphanumeric + hyphens/underscores, max 50 chars
1243
+ - `validateMcpConfig(config)` — type-specific checks (command for stdio, url for http/sse)
1244
+
1245
+ ### Frontend Integration
1246
+
1247
+ **UI Components:**
1248
+ - `MCP Settings Section` (`src/web/components/settings/mcp-settings-section.tsx`) — Tab in Settings UI
1249
+ - `MCP Server Dialog` (`src/web/components/settings/mcp-server-dialog.tsx`) — Add/Edit modal
1250
+ - `API client` (`src/web/lib/api-mcp.ts`) — Fetch/mutate operations
1251
+
1252
+ **Workflow:**
1253
+ 1. User opens Settings → MCP tab
1254
+ 2. **GET** `/api/settings/mcp` (auto-imports on first access)
1255
+ 3. Display list with transport badge + actions (edit, delete)
1256
+ 4. Click "Add" → Dialog with name + transport selector + config fields
1257
+ 5. **POST** to `/api/settings/mcp` or **PUT** to update
1258
+ 6. On success, list refreshes
1259
+
1260
+ ### SDK Integration
1261
+
1262
+ **Claude Agent SDK Provider** (`src/providers/claude-agent-sdk.ts`):
1263
+ ```typescript
1264
+ // Line ~574
1265
+ const mcpServers = mcpConfigService.list();
1266
+ const hasMcp = Object.keys(mcpServers).length > 0;
1267
+
1268
+ // Line ~589: Pass to query() if servers exist
1269
+ const mcpTools = ["mcp__*"];
1270
+ const queryConfig = {
1271
+ // ... other options
1272
+ ...(hasMcp && { mcpServers }),
1273
+ allowedTools: [...otherTools, ...mcpTools],
1274
+ };
1275
+
1276
+ const query = new Query(messages, queryConfig);
1277
+ ```
1278
+
1279
+ ---
1280
+
1281
+ ## Extension System (v0.9.0+)
1282
+
1283
+ ### Overview
1284
+
1285
+ PPM Extension System enables VSCode-compatible, npm-installable extensions that run in isolated Bun Worker threads. Crash-safe, permission-based, with RPC messaging between main process and worker, and WebSocket bridge for real-time UI updates.
1286
+
1287
+ **Architecture (3-tier):**
1288
+ ```
1289
+ Extension Code (Bun Worker) ← @ppm/vscode-compat API
1290
+ │ RPC (postMessage)
1291
+
1292
+ Main Process (Hono/Bun) ← extension-rpc-handlers.ts
1293
+ │ WebSocket (/ws/extensions)
1294
+
1295
+ Browser (React) ← Zustand store + React components
1296
+ ```
1297
+
1298
+ **Key components:**
1299
+ - **Package Format:** npm packages (`@ppm/ext-database`, `@ppm/ext-git-graph`, `@ppm/ext-docker`, etc.)
1300
+ - **Installation:** `~/.ppm/extensions/node_modules/{id}/`
1301
+ - **Lifecycle:** Install → Enable → Activate → Deactivate → Remove
1302
+ - **Worker Isolation:** Each activated extension runs in a Bun Worker (crash-safe, 10s activation timeout)
1303
+ - **Communication:** RPC (Worker↔Main) + WebSocket (Main↔Browser)
1304
+ - **API Shim:** `@ppm/vscode-compat` — VSCode-compatible API (commands, window, workspace)
1305
+ - **Subprocess Access:** RPC `process:spawn` handler for extensions needing CLI commands (git, docker, npm, python, etc.)
1306
+ - **State Storage:** globalState + workspaceState in SQLite via Memento
1307
+ - **UI Bridge:** StatusBar, TreeView, WebviewPanel, QuickPick, InputBox, Notifications
1308
+ - **Contributions:** Commands, views, configuration contributed via manifest
1309
+
1310
+ **Official Extensions:**
1311
+ - `@ppm/ext-database` — Database browser with SQLite/PostgreSQL support (tree view + query panel)
1312
+ - `@ppm/ext-git-graph` — Git commit graph visualization (faithful vscode-git-graph SVG algorithm with Bézier curves, uses process:spawn for git CLI across registered projects)
1313
+
1314
+ ### Manifest Format
1315
+
1316
+ Extension metadata defined in `package.json` under `ppm` key:
1317
+
1318
+ ```json
1319
+ {
1320
+ "name": "@ppm/ext-database",
1321
+ "version": "1.0.0",
1322
+ "main": "dist/extension.js",
1323
+ "ppm": {
1324
+ "displayName": "Database Browser",
1325
+ "description": "Browse and query databases",
1326
+ "icon": "database.svg",
1327
+ "engines": { "ppm": ">=0.9.0" },
1328
+ "activationEvents": ["onView:databases"],
1329
+ "contributes": {
1330
+ "commands": [
1331
+ {
1332
+ "command": "ppm.database.openConnection",
1333
+ "title": "Open Database Connection",
1334
+ "category": "Database"
1335
+ }
1336
+ ],
1337
+ "views": {
1338
+ "explorer": [
1339
+ {
1340
+ "id": "databases",
1341
+ "name": "Databases",
1342
+ "type": "tree"
1343
+ }
1344
+ ]
1345
+ },
1346
+ "configuration": {
1347
+ "properties": {
1348
+ "ppm.database.maxRows": {
1349
+ "type": "number",
1350
+ "default": 1000,
1351
+ "description": "Max rows to fetch per query"
1352
+ }
1353
+ }
1354
+ }
1355
+ }
1356
+ }
1357
+ }
1358
+ ```
1359
+
1360
+ **Fields:**
1361
+ - `engines.ppm` — PPM version requirement
1362
+ - `activationEvents` — When extension activates (e.g., `onView:databases`, `onCommand:ext.activate`)
1363
+ - `contributes` — UI elements + commands contributed by extension
1364
+
1365
+ ### Installation & Lifecycle
1366
+
1367
+ **Installation** (`ppm ext install @ppm/ext-database`):
1368
+ 1. Fetch package from npm
1369
+ 2. Extract to `~/.ppm/extensions/node_modules/{id}/`
1370
+ 3. Parse manifest from `package.json`
1371
+ 4. Store in SQLite `extensions` table (enabled=1)
1372
+ 5. Discover contributions
1373
+
1374
+ **Activation** (`ppm ext enable @ppm/ext-database` or automatic):
1375
+ 1. Load manifest + entry point from disk
1376
+ 2. Spawn Bun Worker (process isolation)
1377
+ 3. Create scoped `@ppm/vscode-compat` API instance (RPC-backed)
1378
+ 4. Call `activate(context, vscodeApi)` with 10s timeout
1379
+ 5. Register contributions in `contributionRegistry`
1380
+ 6. Broadcast `contributions:update` via WS to all connected browsers
1381
+ 7. Mark as activated
1382
+
1383
+ **Deactivation:**
1384
+ 1. Unregister contributions
1385
+ 2. Terminate worker
1386
+ 3. Clear persisted state if needed
1387
+
1388
+ **Removal** (`ppm ext remove @ppm/ext-database`):
1389
+ 1. Deactivate if active
1390
+ 2. Delete from `~/.ppm/extensions/`
1391
+ 3. Remove from SQLite
1392
+ 4. Unregister contributions
1393
+
1394
+ ### RPC Protocol (Extension ↔ Main Process)
1395
+
1396
+ **Message Types:**
1397
+
1398
+ 1. **Request** (extension → main)
1399
+ ```json
1400
+ {
1401
+ "type": "request",
1402
+ "id": 1,
1403
+ "method": "storage:get",
1404
+ "params": ["extId", "global", "key"]
1405
+ }
1406
+ ```
1407
+
1408
+ 2. **Response** (main → extension)
1409
+ ```json
1410
+ {
1411
+ "type": "response",
1412
+ "id": 1,
1413
+ "result": "value"
1414
+ }
1415
+ ```
1416
+
1417
+ 3. **Event** (both directions)
1418
+ ```json
1419
+ {
1420
+ "type": "event",
1421
+ "event": "file:changed",
1422
+ "data": { "path": "/path/to/file" }
1423
+ }
1424
+ ```
1425
+
1426
+ **Built-in Methods (vscode-compat API):**
1427
+ - `commands:execute(command, ...args)` — Execute command
1428
+ - `commands:list(filterInternal)` — List available commands
1429
+ - `window:showMessage(level, message, items[])` — Show dialog with buttons
1430
+ - `window:showQuickPick(items[], options)` — Quick pick menu
1431
+ - `window:showInputBox(options)` — Text input dialog
1432
+ - `window:webview:create(panelId, extensionId, viewType, title)` — Create webview panel
1433
+ - `window:webview:html(panelId, html)` — Set webview content
1434
+ - `window:webview:postMessage(panelId, message)` — Send message to webview
1435
+ - `window:tree:update(viewId, items[])` — Update tree view items
1436
+ - `window:tree:refresh(viewId)` — Refresh tree view
1437
+ - `window:statusbar:update(item)` — Update/create status bar item
1438
+ - `window:statusbar:remove(itemId)` — Remove status bar item
1439
+ - `workspace:config:get(key)` — Read config value
1440
+ - `workspace:config:update(key, value, target)` — Write config value
1441
+ - `workspace:fs:readFile(filePath)` — Read file (base64 encoded)
1442
+ - `workspace:fs:writeFile(filePath, base64Content)` — Write file
1443
+ - `workspace:fs:stat(filePath)` — Get file metadata
1444
+ - `workspace:fs:readDirectory(dirPath)` — List directory contents
1445
+
1446
+ **Subprocess Execution (extensions needing CLI access):**
1447
+ - `process:spawn(command, args[], options)` — Execute external command
1448
+ - **Allowed commands:** git, node, bun, npm, yarn, pnpm, docker, psql, sqlite3, python3, python
1449
+ - **Options:** `{ cwd?: string, timeout?: number }` (default: 30s timeout, CWD must be within registered project paths, ~/.ppm/extensions/, or current process directory)
1450
+ - **Returns:** `{ code: number, stdout: string, stderr: string, error?: string }`
1451
+ - **Example:** See ext-git-graph for real-world usage (runs `git log --all` across any registered project via path-based CWD)
1452
+
1453
+ - Extension can define custom RPC methods via `rpc.onRequest(method, handler)`
1454
+
1455
+ ### State Storage
1456
+
1457
+ **Database Schema:**
1458
+
1459
+ ```sql
1460
+ CREATE TABLE extension_storage (
1461
+ ext_id TEXT NOT NULL,
1462
+ scope TEXT NOT NULL, -- 'global' | 'workspace'
1463
+ key TEXT NOT NULL,
1464
+ value TEXT, -- JSON-serialized
1465
+ PRIMARY KEY (ext_id, scope, key)
1466
+ );
1467
+ ```
1468
+
1469
+ **Scopes:**
1470
+ - **globalState** — Persists across all projects (e.g., user settings, cache)
1471
+ - **workspaceState** — Project-specific state (e.g., open panel state)
1472
+
1473
+ **API** (inside extension):
1474
+ ```typescript
1475
+ // In activate(context: ExtensionContext)
1476
+ const globalVal = context.globalState.get("lastConnection", "default");
1477
+ await context.globalState.update("lastConnection", "my-db");
1478
+
1479
+ const wsVal = context.workspaceState.get("selectedTable");
1480
+ await context.workspaceState.update("selectedTable", "users");
1481
+ ```
1482
+
1483
+ ### WebSocket Bridge (Extension ↔ Browser)
1484
+
1485
+ Extensions interact with the browser UI via a dedicated WebSocket at `/ws/extensions`. The main process translates between Worker RPC and WS messages.
1486
+
1487
+ **Server → Client (ExtServerMsg):** `tree:update`, `tree:refresh`, `statusbar:update/remove`, `notification`, `quickpick:show`, `inputbox:show`, `webview:create/html/dispose/postMessage`, `contributions:update`
1488
+
1489
+ **Client → Server (ExtClientMsg):** `ready`, `command:execute`, `tree:expand/click`, `webview:message`, `quickpick:resolve`, `inputbox:resolve`, `notification:action`
1490
+
1491
+ **Message routing:**
1492
+ - Extension calls `vscode.window.showInformationMessage()` → RPC → `extension-rpc-handlers.ts` → `broadcastExtMsg()` → WS → `use-extension-ws` hook → toast notification
1493
+ - Browser user clicks tree item → WS `tree:click` → `extensions.ts` → Worker RPC `ext:command:execute` → CommandService → extension handler
1494
+ - Webview iframe postMessage → parent → CustomEvent → WS `webview:message` → Worker RPC `ext:webview:message` → EventEmitter → extension's `onDidReceiveMessage` handler
1495
+
1496
+ **Request/response pattern:** QuickPick, InputBox, and notification actions use `requestFromBrowser(msg, trackingId, 30s timeout)` — sends WS message and awaits browser response via pending Promise map.
1497
+
1498
+ ### UI Components
1499
+
1500
+ Extension UI state lives in Zustand (`extension-store.ts`) and renders via React:
1501
+ - **StatusBar** — Fixed bottom bar with left/right aligned items
1502
+ - **TreeView** — Recursive tree with expand/collapse, renders in sidebar for `ext:*` tabs
1503
+ - **WebviewPanel** — Sandboxed iframe (`allow-scripts` only), `acquireVsCodeApi()` shim auto-injected
1504
+ - **QuickPick** — Filterable picker with keyboard nav, bottom-sheet on mobile
1505
+ - **InputBox** — Text input dialog with password mode support
1506
+ - **Command Palette** — Extension commands merged with built-in commands
1507
+
1508
+ ### Contribution Registry
1509
+
1510
+ **Purpose:** Central registry of all extension contributions (commands, views, etc.)
1511
+
1512
+ **Storage:** In-memory map during runtime
1513
+
1514
+ **Endpoints:**
1515
+ - `GET /api/extensions/contributions` — List all active contributions
1516
+
1517
+ **Contribution Types:**
1518
+ 1. **Commands** — Callable actions (e.g., `ppm.database.openConnection`)
1519
+ - Registered: `registry.registerCommand(extId, command)`
1520
+ - Invoked: `POST /api/extensions/{extId}/commands/{command}`
1521
+
1522
+ 2. **Views** — Sidebar panels or tree views
1523
+ - Registered: `registry.registerView(extId, view)`
1524
+ - Rendered in UI based on `type` (tree, webview)
1525
+
1526
+ 3. **Configuration** — Settings schema
1527
+ - Registered: `registry.registerConfig(extId, schema)`
1528
+ - Merged with global settings
1529
+
1530
+ ### CLI Commands
1531
+
1532
+ ```bash
1533
+ ppm ext list # List installed extensions
1534
+ ppm ext install @ppm/ext-database # Install from npm
1535
+ ppm ext remove @ppm/ext-database # Uninstall
1536
+ ppm ext enable @ppm/ext-database # Enable extension
1537
+ ppm ext disable @ppm/ext-database # Disable extension
1538
+ ppm ext dev /path/to/ext-src # Symlink local extension for dev
1539
+ ppm ext config <ext-id> <key> <value> # Set config value
1540
+ ```
1541
+
1542
+ **Dev Mode** (`ppm ext dev /path/to/src`):
1543
+ - Symlinks local extension to `~/.ppm/extensions/node_modules/`
1544
+ - Auto-reloads on file change
1545
+ - Extension runs from source (TypeScript not compiled)
1546
+
1547
+ ### REST API
1548
+
1549
+ **Endpoints** (`src/server/routes/extensions.ts`):
1550
+
1551
+ | Method | Endpoint | Description |
1552
+ |--------|----------|-------------|
1553
+ | **GET** | `/api/extensions` | List installed extensions |
1554
+ | **POST** | `/api/extensions` | Install extension (body: {name, version?}) |
1555
+ | **GET** | `/api/extensions/:id` | Get extension info (manifest, status) |
1556
+ | **DELETE** | `/api/extensions/:id` | Remove extension |
1557
+ | **PATCH** | `/api/extensions/:id` | Update extension (body: {enabled}) |
1558
+ | **GET** | `/api/extensions/contributions` | List all contributions (commands, views, config) |
1559
+ | **POST** | `/api/extensions/:id/commands/:cmd` | Invoke extension command |
1560
+
1561
+ **Example: Install Extension**
1562
+ ```bash
1563
+ POST /api/extensions
1564
+ Content-Type: application/json
1565
+
1566
+ { "name": "@ppm/ext-database", "version": "1.0.0" }
1567
+
1568
+ # Response
1569
+ {
1570
+ "ok": true,
1571
+ "data": {
1572
+ "id": "@ppm/ext-database",
1573
+ "version": "1.0.0",
1574
+ "displayName": "Database Browser",
1575
+ "enabled": true,
1576
+ "activated": false
1577
+ }
1578
+ }
1579
+ ```
1580
+
1581
+ ### Service Layer
1582
+
1583
+ **ExtensionService** (`src/services/extension.service.ts`):
1584
+ - `discover()` — Scan `~/.ppm/extensions/` for installed packages
1585
+ - `install(name)` — Fetch from npm, install locally
1586
+ - `remove(id)` — Uninstall extension
1587
+ - `activate(id)` — Load + run extension in worker
1588
+ - `deactivate(id)` — Terminate worker, cleanup
1589
+ - `parseManifest(pkg)` — Extract manifest from package.json
1590
+ - `setExtensionState(extId, scope, key, value)` — Persist state
1591
+
1592
+ **ExtensionInstaller** (`src/services/extension-installer.ts`):
1593
+ - `installExtension(name, dir)` — npm install + verify
1594
+ - `removeExtension(id, dir)` — rm -rf extension directory
1595
+ - `devLinkExtension(localPath)` — Symlink for local dev
1596
+
1597
+ **ExtensionManifest** (`src/services/extension-manifest.ts`):
1598
+ - `parseManifest(pkg)` — Validate + parse ppm section
1599
+ - `discoverManifests(dir)` — Scan all installed extensions
1600
+
1601
+ **RpcChannel** (`src/services/extension-rpc.ts`):
1602
+ - Bidirectional RPC messaging
1603
+ - Request/response matching by ID
1604
+ - Event broadcasting
1605
+ - Timeout handling
1606
+
1607
+ ### Worker Integration
1608
+
1609
+ **ExtensionHostWorker** (`src/services/extension-host-worker.ts`):
1610
+ - Worker-side code that loads + activates extension
1611
+ - Loads extension code into worker context
1612
+ - Exposes ExtensionContext API (globalState, workspaceState, subscriptions)
1613
+ - Handles incoming RPC messages
1614
+ - Communicates back to main process
1615
+
1616
+ **Design:**
1617
+ ```
1618
+ Main Process Worker
1619
+ ↓ ↓
1620
+ ExtensionService ExtensionHostWorker
1621
+ ↓ ↓
1622
+ RpcChannel ←────────────→ RpcChannel
1623
+ ↓ ↓
1624
+ Sends: { Extension Code
1625
+ type: "request", (User's ext.ts)
1626
+ method: "..." ↓
1627
+ } activate(context)
1628
+ ↓ ↓
1629
+ Handlers respond context.storage.get()
1630
+ ↑ ↑
1631
+ └─────────────────┘
1632
+ ```
1633
+
1634
+ ### Dev Workflow
1635
+
1636
+ **Creating an Extension:**
1637
+
1638
+ 1. Create npm package:
1639
+ ```bash
1640
+ npm init -y @ppm/ext-my-feature
1641
+ npm install @ppm/extension-api
1642
+ ```
1643
+
1644
+ 2. Write `src/extension.ts`:
1645
+ ```typescript
1646
+ import type { ExtensionContext } from "@ppm/extension-api";
1647
+
1648
+ export async function activate(context: ExtensionContext) {
1649
+ console.log(`Extension ${context.extensionId} activated!`);
1650
+
1651
+ const val = context.globalState.get("count", 0);
1652
+ await context.globalState.update("count", val + 1);
1653
+ }
1654
+
1655
+ export function deactivate() {
1656
+ console.log("Extension deactivated");
1657
+ }
1658
+ ```
1659
+
1660
+ 3. Add to `package.json`:
1661
+ ```json
1662
+ {
1663
+ "ppm": {
1664
+ "displayName": "My Feature",
1665
+ "main": "dist/extension.js",
1666
+ "contributes": {
1667
+ "commands": [...]
1668
+ }
1669
+ }
1670
+ }
1671
+ ```
1672
+
1673
+ 4. Install locally for dev:
1674
+ ```bash
1675
+ ppm ext dev /path/to/ext-my-feature
1676
+ ```
1677
+
1678
+ 5. Extension auto-activates based on `activationEvents`, state persists
1679
+
1680
+ ### Error Handling & Debugging
1681
+
1682
+ **Activation Error Tracking:**
1683
+ - `ExtensionService.activationErrors` Map tracks `extId → error message` for all failed activations
1684
+ - Errors set during `activate()` if worker response indicates failure (`!result.ok`)
1685
+ - Errors cleared on successful activation or worker termination
1686
+ - Errors included in `contributions:update` message sent via WS to browser on client connect
1687
+
1688
+ **User Feedback (UI):**
1689
+ - **Command Errors:** When extension command fails, toast shows "Extension command failed: {error}" with error details
1690
+ - **Timeout Handling:** If webview panel doesn't load within 10s, fallback UI displays activation error (if available) + "Retry" button
1691
+ - **Retry Button:** User can click to re-trigger the command without page reload (re-dispatches `ext:command:execute`)
1692
+
1693
+ **Breadcrumb Logging (Console):**
1694
+ - **`[ExtService]`** — Main process lifecycle: activation start/success, worker lifecycle, contributions broadcast
1695
+ - **`[ExtHost]`** — Worker-side execution: command routing, handler invocation, error context
1696
+ - **`[ExtWS]`** — WebSocket bridge: client connect, message handling, error responses
1697
+ - **Extension-specific tags** — e.g., `[ext-git-graph]` for extension-specific log context
1698
+
1699
+ **Example Log Flow (normal):**
1700
+ ```
1701
+ [ExtService] startup: activating ext-git-graph...
1702
+ [ExtWS] Client connected (1 total)
1703
+ [ExtHost] activating ext-git-graph from dist/extension.js
1704
+ [ExtHost] activated ext-git-graph (1 total)
1705
+ [ExtService] activated ext-git-graph successfully
1706
+ [ExtWS] command:execute "git-graph.view"
1707
+ [ExtHost] command:execute "git-graph.view" (1 extensions active)
1708
+ [ExtHost] routing "git-graph.view" → ext-git-graph
1709
+ ```
1710
+
1711
+ **Example Log Flow (error):**
1712
+ ```
1713
+ [ExtService] startup: activating ext-git-graph...
1714
+ [ExtHost] activating ext-git-graph from dist/extension.ts
1715
+ [ExtHost] ERROR: Cannot find module 'missing-dep'
1716
+ [ExtService] Failed to activate ext-git-graph on startup: Cannot find module 'missing-dep'
1717
+ → activationErrors["ext-git-graph"] = "Cannot find module 'missing-dep'"
1718
+ → browser receives { type: "contributions:update", activationErrors: {"ext-git-graph": "..."} }
1719
+ → user sees toast: "Extension "ext-git-graph" failed to activate: Cannot find module..."
1720
+ ```
1721
+
1722
+ ### Crash Safety
1723
+
1724
+ **Worker Isolation:**
1725
+ - Each extension in isolated Bun Worker thread
1726
+ - Worker crash doesn't crash main process
1727
+ - Error events logged, extension marked as failed
1728
+ - Main process continues operating
1729
+
1730
+ **Cleanup:**
1731
+ - Worker terminates → cleanup timer expires after 5min
1732
+ - Persisted state preserved in SQLite (not lost on crash)
1733
+ - Next activation reloads from disk, state auto-restored
1734
+
1735
+ ### Future Enhancements (Phase 2+)
1736
+
1737
+ - **UI Webview Support** — Extensions define HTML/React UI panels
1738
+ - **Extension Settings UI** — Auto-generate UI from `contributes.configuration`
1739
+ - **Hot Reload** — Auto-reload extension on file change during dev
1740
+ - **Marketplace** — Browse, rate, publish extensions (v1.0+)
1741
+ - **Permissions** — User prompt for sensitive operations
1742
+ - **Inter-Extension API** — Extensions can call each other via RPC
1743
+
1744
+ ---
1745
+
1746
+ **Tool Allow List:**
1747
+ - All MCP tools automatically allowed via wildcard `mcp__*`
1748
+ - MCP server connection failures don't block chat (logged as warning)
1749
+
1750
+ ### Import Flow
1751
+
1752
+ **Auto-import on first access:**
1753
+ 1. GET `/api/settings/mcp` called
1754
+ 2. If table is empty, read `~/.claude.json`
1755
+ 3. If `mcpServers` key exists, bulk import (validate + skip duplicates)
1756
+ 4. Return populated list
1757
+
1758
+ **Manual import:**
1759
+ 1. GET `/api/settings/mcp/import/preview` — show what's available
1760
+ 2. POST `/api/settings/mcp/import` — import validated servers
1761
+ 3. Returns `{ imported: N, skipped: M }`
1762
+
1763
+ ### Error Handling
1764
+
1765
+ | Scenario | Response |
1766
+ |----------|----------|
1767
+ | Invalid name (non-alphanumeric) | 400 Bad Request |
1768
+ | Invalid config (missing required fields) | 400 Bad Request |
1769
+ | Duplicate name | 409 Conflict |
1770
+ | Server not found (GET/:name, PUT/:name, DELETE/:name) | 404 Not Found |
1771
+ | `~/.claude.json` not found (import) | 404 Not Found |
1772
+ | Corrupt config JSON (recovery) | Log warning, skip entry, continue |
1773
+
1774
+ ---
1775
+
1776
+ ## Deployment Architecture
1777
+
1778
+ ### Single-Machine Deployment (Current)
1779
+ ```
1780
+ Linux/macOS Host
1781
+ ├── ppm (compiled binary)
1782
+ │ └── Embeds: server code, frontend assets
1783
+ ├── ppm.yaml (config, auto-generated)
1784
+ └── ~/.ppm/ (optional: session cache, logs)
1785
+ ```
1786
+
1787
+ ### Daemon Mode (Default)
1788
+ ```
1789
+ $ ppm start
1790
+ → Background process (background by default)
1791
+ → Supervisor spawns server + tunnel, monitors health
1792
+ → Status saved to ~/.ppm/status.json (with PID, port, host, shareUrl, supervisorPid, availableVersion)
1793
+ → Fallback compat: ppm.pid read/written for backward compatibility
1794
+ → Supervisor checks npm registry every 15min for updates, writes availableVersion to status.json
1795
+
1796
+ $ ppm start --foreground
1797
+ → Runs in foreground (debugging, CI/CD)
1798
+ → WebSocket and all features fully functional
1799
+ → Tunnel (--share) works in foreground mode
1800
+
1801
+ $ ppm start --share
1802
+ → Daemon mode + Cloudflare Quick Tunnel
1803
+ → Downloads cloudflared to ~/.ppm/bin/ (if missing, shows progress)
1804
+ → Spawns tunnel process, extracts public URL from stderr
1805
+ → URL saved to status.json for parent process
1806
+ → Auth warning if auth.enabled is false
1807
+
1808
+ $ ppm upgrade
1809
+ → CLI command to check and install updates
1810
+ → Fetches latest version from npm registry
1811
+ → Installs via bun or npm based on install method
1812
+ → Signals supervisor to self-replace (spawn new → wait healthy → exit old)
1813
+ → Works in headless environments (no OS autostart dependency)
1814
+
1815
+ $ ppm stop
1816
+ → SOFT STOP: kills server only, supervisor stays alive with Cloud WS + tunnel
1817
+ → Supervisor transitions to "stopped" state
1818
+ → Minimal HTML page served on port (503 status on /api/health)
1819
+ → Tunnel and Cloud connectivity remain active
1820
+ → `ppm start` resumes without restarting supervisor process
1821
+
1822
+ $ ppm stop --kill OR ppm down
1823
+ → FULL SHUTDOWN: kills everything (supervisor + server + tunnel)
1824
+ → Supervisor transitions to "upgrading" then terminates
1825
+ → Cleans up status.json and ppm.pid
1826
+ → Graceful cleanup (close WS, cleanup PTY, stop tunnel)
1827
+ ```
1828
+
1829
+ ### Supervisor Architecture (v0.9.11+)
1830
+
1831
+ The supervisor is a long-lived parent process that manages server + tunnel children with resilience and state management.
1832
+
1833
+ **Architecture:**
1834
+ ```
1835
+ Supervisor Process (parent)
1836
+ ├── Server Child (Hono HTTP server)
1837
+ │ ├── Health checks every 30s (/api/health)
1838
+ │ ├── Auto-restart on crash (exponential backoff, max 10 restarts)
1839
+ │ └── If in "stopped" state, serves minimal 503 page instead of restarting
1840
+
1841
+ ├── Tunnel Child (Cloudflare Quick Tunnel, if --share)
1842
+ │ ├── URL probe every 2min
1843
+ │ ├── Auto-reconnect on failure
1844
+ │ └── URL persisted to status.json
1845
+
1846
+ ├── State Machine: "running" | "paused" | "stopped" | "upgrading"
1847
+ │ ├── running — Server spawned, tunnel optional, serving requests
1848
+ │ ├── paused — Supervisor paused (resume via signal)
1849
+ │ ├── stopped — Server stopped (soft stop), tunnel alive, Cloud WS active
1850
+ │ └── upgrading — Self-replace in progress
1851
+
1852
+ ├── Upgrade Check (every 15min)
1853
+ │ └── npm registry poll → availableVersion written to status.json
1854
+
1855
+ ├── Stopped Page Server
1856
+ │ ├── Lightweight HTTP handler on same port as server
1857
+ │ ├── Returns 503 on /api/health
1858
+ │ └── Tunnels Cloud WS calls through to PPM Cloud
1859
+
1860
+ └── Error Resilience
1861
+ ├── uncaughtException → log + exit gracefully
1862
+ ├── unhandledRejection → log + continue
1863
+ └── Signal handlers: SIGTERM (full shutdown), SIGUSR1 (self-replace), SIGUSR2 (restart skip backoff)
1864
+ ```
1865
+
1866
+ **Soft Stop vs Full Shutdown:**
1867
+ | Command | Server | Supervisor | Tunnel | Use Case |
1868
+ |---------|--------|------------|--------|----------|
1869
+ | `ppm stop` | Killed | Stays alive | Stays alive | Restart later with `ppm start` |
1870
+ | `ppm stop --kill` | Killed | Killed | Killed | Full cleanup, exit |
1871
+ | `ppm down` | Killed | Killed | Killed | Full cleanup, exit |
1872
+
1873
+ **State Persistence:**
1874
+ - Status file: `~/.ppm/status.json` — PID, port, host, shareUrl, supervisorPid, availableVersion, state
1875
+ - Lock file: `~/.ppm/.start-lock` — Prevent concurrent starts
1876
+ - Command file: `~/.ppm/.supervisor-cmd` — IPC for soft_stop, resume, self_replace
1877
+
1878
+ **Stopped Page Implementation:**
1879
+ - Minimal HTTP server on same port as main server
1880
+ - Serves `503 Service Unavailable` on /api/health
1881
+ - Proxies Cloud WS calls to PPM Cloud (if tunnel configured)
1882
+ - Allows `ppm start` to resume without supervisor restart
1883
+
1884
+ **Files (Modular Design):**
1885
+ - `src/services/supervisor.ts` — Main orchestrator (spawn, health checks, upgrade checks)
1886
+ - `src/services/supervisor-state.ts` — State machine, IPC command handling, signal routing
1887
+ - `src/services/supervisor-stopped-page.ts` — Minimal 503 page + Cloud WS proxy
1888
+
1889
+ ---
1890
+
1891
+ ### Future: Multi-Machine (Not in v2)
1892
+ Would require:
1893
+ - Central state server (Redis/Postgres)
1894
+ - Session sharing across servers
1895
+ - Shared filesystem or file sync protocol
1896
+ - Load balancer
1897
+
1898
+ ---
1899
+
1900
+ ## Frontend Performance Optimization (v0.9.86+)
1901
+
1902
+ ### Memory & Re-Render Reduction
1903
+
1904
+ **1. useShallow Pattern (Zustand)**
1905
+ - All destructured store selectors wrapped in `useShallow()` (36 sites)
1906
+ - Prevents unnecessary re-renders when object properties mutate
1907
+ - Example: `const { messages, addMessage } = chatStore(useShallow(...))`
1908
+
1909
+ **2. Component Memoization (React.memo)**
1910
+ - 10 heavy components wrapped (CodeEditor, MessageBubble, ProjectBar, ProjectAvatar, TerminalTab, PanelLayout, Sidebar, StatusBar, StatusBarEntry, TabBar, TreeNode)
1911
+ - Memoization skips re-renders if props unchanged
1912
+ - Paired with `useCallback` to maintain stable references
1913
+
1914
+ **3. Lazy Loading**
1915
+ - MarkdownRenderer lazy-loaded from 3 sites (reduces initial bundle)
1916
+ - CodeMirror on-demand in postgres-viewer
1917
+ - Mermaid diagram support loaded dynamically only when diagram syntax detected
1918
+
1919
+ **4. Code Splitting (vite.config.ts)**
1920
+ - 5 vendor chunks: `vendor-monaco`, `vendor-mermaid`, `vendor-xterm`, `vendor-markdown`, `vendor-ui`
1921
+ - Heavy libraries (>500KB) in separate chunks for better browser caching
1922
+ - Each chunk independently cacheable and updated
1923
+
1924
+ **5. Chat Pagination & Message Caps**
1925
+ - Chat history loads 50 messages per page with load-more button (prevents DOM bloat)
1926
+ - Team activity capped at 500 messages (prevents unbounded growth)
1927
+
1928
+ ### Benefits
1929
+ - Faster page load (lazy chunks load on-demand)
1930
+ - Reduced re-render cycles (useShallow + memo)
1931
+ - Lower memory footprint (capped message buffers)
1932
+ - Better caching (vendor chunk stability across versions)
1933
+
1934
+ ---
1935
+
1936
+ ## Error Handling Strategy
1937
+
1938
+ | Layer | Error Type | Handling |
1939
+ |-------|-----------|----------|
1940
+ | **Presentation** | Network error | Retry, show toast |
1941
+ | **API** | Invalid input | 400 Bad Request, error message |
1942
+ | **Service** | File not found | Throw Error, API returns 404 |
1943
+ | **Service** | Git failed | Throw Error with git output |
1944
+ | **Provider** | Token invalid | Return error event |
1945
+ | **Filesystem** | Permission denied | Throw Error with context |
1946
+
1947
+ **Pattern:** Bottom-up exception bubbling with context addition at each layer.
1948
+
1949
+ ---
1950
+
1951
+ ## Security Architecture
1952
+
1953
+ | Component | Security Measure | Implementation |
1954
+ |-----------|-----------------|-----------------|
1955
+ | **Auth** | Token validation | Middleware checks header token vs config |
1956
+ | **Path Traversal** | Path validation | FileService rejects paths with `..` |
1957
+ | **WebSocket** | Token in URL query | WS connects with `?token=...` or via session |
1958
+ | **CLI** | Config file permissions | 0600 (user read/write only) |
1959
+ | **API** | No sensitive data in logs | Token masked in debug output |
1960
+ | **CORS** | Same-origin only | WS on same host as HTTP API |
1961
+