@hienlh/ppm 0.9.0-beta.9 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/CHANGELOG.md +233 -0
  2. package/bun.lock +17 -0
  3. package/dist/web/assets/{_basePickBy-3Xe18azI.js → _basePickBy-5PGDJbfF.js} +1 -1
  4. package/dist/web/assets/{_baseUniq-Yy35llnn.js → _baseUniq-BT4Ow4Kk.js} +1 -1
  5. package/dist/web/assets/api-settings-BUvk6Saw.js +1 -0
  6. package/dist/web/assets/{arc-B9n1Gvb5.js → arc-BAOivWpI.js} +1 -1
  7. package/dist/web/assets/architecture-PBZL5I3N-DEO2f3VD.js +1 -0
  8. package/dist/web/assets/{architectureDiagram-2XIMDMQ5-DqAZP_F6.js → architectureDiagram-2XIMDMQ5-Z-4eN4za.js} +1 -1
  9. package/dist/web/assets/arrow-up-BYhx9ckd.js +1 -0
  10. package/dist/web/assets/{blockDiagram-WCTKOSBZ-h3cDF2vI.js → blockDiagram-WCTKOSBZ-BCLqzhuZ.js} +1 -1
  11. package/dist/web/assets/browser-tab-CrkhFCaw.js +1 -0
  12. package/dist/web/assets/{c4Diagram-IC4MRINW--pF1r5lr.js → c4Diagram-IC4MRINW-0Vp0Jeas.js} +1 -1
  13. package/dist/web/assets/channel-By7bn0Yq.js +1 -0
  14. package/dist/web/assets/chat-tab-C6jpiwh7.js +8 -0
  15. package/dist/web/assets/chevron-right-5HgK6l7K.js +1 -0
  16. package/dist/web/assets/{chunk-4BX2VUAB-C3aZvW7B.js → chunk-4BX2VUAB-D4tOov49.js} +1 -1
  17. package/dist/web/assets/{chunk-55IACEB6-D5cABeB9.js → chunk-55IACEB6-DJ6BynZ4.js} +1 -1
  18. package/dist/web/assets/{chunk-7E7YKBS2-CkFGv6Zs.js → chunk-7E7YKBS2-CiyUJxNI.js} +1 -1
  19. package/dist/web/assets/{chunk-7R4GIKGN-Dvbyu4Zw.js → chunk-7R4GIKGN-Dv-4cAYn.js} +2 -2
  20. package/dist/web/assets/{chunk-C72U2L5F-CtqKiH4q.js → chunk-C72U2L5F-D21mS_6G.js} +1 -1
  21. package/dist/web/assets/{chunk-EGIJ26TM-Cpr87sBR.js → chunk-EGIJ26TM-DzqmU2Z7.js} +1 -1
  22. package/dist/web/assets/{chunk-FMBD7UC4-D23YVTOU.js → chunk-FMBD7UC4-DXncblvW.js} +1 -1
  23. package/dist/web/assets/{chunk-GEFDOKGD-tDjHsAUs.js → chunk-GEFDOKGD-D-pKjlVd.js} +1 -1
  24. package/dist/web/assets/chunk-GLR3WWYH-DKikpoJM.js +2 -0
  25. package/dist/web/assets/chunk-HHEYEP7N-C7vxA5i9.js +1 -0
  26. package/dist/web/assets/{chunk-JSJVCQXG-BBmymCjA.js → chunk-JSJVCQXG-99JzIdPr.js} +1 -1
  27. package/dist/web/assets/{chunk-KX2RTZJC-DP36BDiU.js → chunk-KX2RTZJC-CRq1OBZv.js} +1 -1
  28. package/dist/web/assets/{chunk-KYZI473N-Djw13C-3.js → chunk-KYZI473N-Bb0MCaIO.js} +1 -1
  29. package/dist/web/assets/{chunk-L3YUKLVL-HG_eMj_C.js → chunk-L3YUKLVL-C7qGJrfV.js} +1 -1
  30. package/dist/web/assets/{chunk-MX3YWQON-C2UEioMs.js → chunk-MX3YWQON-BpS_PtKp.js} +1 -1
  31. package/dist/web/assets/{chunk-NQ4KR5QH-DXUTQ-BL.js → chunk-NQ4KR5QH-z_blpjxi.js} +1 -1
  32. package/dist/web/assets/{chunk-O4XLMI2P-BsUWb9d0.js → chunk-O4XLMI2P-nDhi_cVu.js} +1 -1
  33. package/dist/web/assets/{chunk-OZEHJAEY-rG0P22U9.js → chunk-OZEHJAEY-BXhYx3nO.js} +1 -1
  34. package/dist/web/assets/{chunk-PQ6SQG4A-DX0xW7kO.js → chunk-PQ6SQG4A-TF58UVMU.js} +1 -1
  35. package/dist/web/assets/{chunk-PU5JKC2W-C7Gry6md.js → chunk-PU5JKC2W-ek7k4QVB.js} +1 -1
  36. package/dist/web/assets/chunk-QZHKN3VN-CYaTbeZf.js +1 -0
  37. package/dist/web/assets/{chunk-R5LLSJPH-CMY0PkRK.js → chunk-R5LLSJPH-CFwSJijQ.js} +1 -1
  38. package/dist/web/assets/{chunk-WL4C6EOR-CXuQvlyu.js → chunk-WL4C6EOR-ByUrSRin.js} +1 -1
  39. package/dist/web/assets/{chunk-XIRO2GV7-DRJEb7Zb.js → chunk-XIRO2GV7-Djlmrely.js} +1 -1
  40. package/dist/web/assets/{chunk-XPW4576I-BPEX8KhL.js → chunk-XPW4576I-BPQQBakK.js} +1 -1
  41. package/dist/web/assets/{chunk-XZSTWKYB-Cb0iqycX.js → chunk-XZSTWKYB-DxAOx4hG.js} +1 -1
  42. package/dist/web/assets/{chunk-YBOYWFTD-av5aeHLq.js → chunk-YBOYWFTD-rQG3QH5s.js} +1 -1
  43. package/dist/web/assets/classDiagram-VBA2DB6C-BA8Nj-_C.js +1 -0
  44. package/dist/web/assets/classDiagram-v2-RAHNMMFH-DjYu-6mn.js +1 -0
  45. package/dist/web/assets/clone-LRxlvnMj.js +1 -0
  46. package/dist/web/assets/code-editor-CBIPzlP2.js +2 -0
  47. package/dist/web/assets/columns-2-cEVJHYd7.js +1 -0
  48. package/dist/web/assets/{cose-bilkent-S5V4N54A-qudEiMCT.js → cose-bilkent-S5V4N54A-B_AWZsOP.js} +1 -1
  49. package/dist/web/assets/createLucideIcon-PuMiQgHl.js +1 -0
  50. package/dist/web/assets/{csv-preview-DUbHtTAS.js → csv-preview-ncSOnJSC.js} +2 -2
  51. package/dist/web/assets/{dagre-BFcnKyBF.js → dagre-DHq9bhnd.js} +1 -1
  52. package/dist/web/assets/{dagre-KLK3FWXG-C3O-MTLf.js → dagre-KLK3FWXG-BdJr7Byp.js} +1 -1
  53. package/dist/web/assets/database-viewer-BqOJR_zi.js +1 -0
  54. package/dist/web/assets/{diagram-E7M64L7V-DxPjK7_c.js → diagram-E7M64L7V-_db4pBVA.js} +1 -1
  55. package/dist/web/assets/{diagram-IFDJBPK2-sqTog_XV.js → diagram-IFDJBPK2-xKoeuiJx.js} +1 -1
  56. package/dist/web/assets/{diagram-P4PSJMXO-hzmp0GHK.js → diagram-P4PSJMXO-C8tjJsev.js} +1 -1
  57. package/dist/web/assets/diff-viewer-CcLyp4eY.js +4 -0
  58. package/dist/web/assets/{dist-CALwEtco.js → dist-DIV6WgAG.js} +1 -1
  59. package/dist/web/assets/{dist-DGDPTxs1.js → dist-ovWkrgO-.js} +1 -1
  60. package/dist/web/assets/{erDiagram-INFDFZHY-DLeYhAAT.js → erDiagram-INFDFZHY-BSh2z9Df.js} +1 -1
  61. package/dist/web/assets/extension-webview-NiZ7Ybvv.js +3 -0
  62. package/dist/web/assets/{flowDiagram-PKNHOUZH-CRxlE9Sr.js → flowDiagram-PKNHOUZH-oYaovqyp.js} +1 -1
  63. package/dist/web/assets/{ganttDiagram-A5KZAMGK-BdjmoMLS.js → ganttDiagram-A5KZAMGK-DmL26q2P.js} +1 -1
  64. package/dist/web/assets/git-graph-CoTvMrIo.js +1 -0
  65. package/dist/web/assets/gitGraph-HDMCJU4V-Bwna3and.js +1 -0
  66. package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-BeHSX7kk.js → gitGraphDiagram-K3NZZRJ6-CMoukSrY.js} +1 -1
  67. package/dist/web/assets/{graphlib-Duh_bWLa.js → graphlib-BcsNnGcW.js} +1 -1
  68. package/dist/web/assets/index-C8byznLO.js +37 -0
  69. package/dist/web/assets/index-KwC2YrG4.css +2 -0
  70. package/dist/web/assets/info-3K5VOQVL-_vRxVNUm.js +1 -0
  71. package/dist/web/assets/infoDiagram-LFFYTUFH-DWwumDkq.js +2 -0
  72. package/dist/web/assets/{isEmpty-B9L-Ge-H.js → isEmpty-bnrF3Qbc.js} +1 -1
  73. package/dist/web/assets/{ishikawaDiagram-PHBUUO56-Cu0Rt1Ok.js → ishikawaDiagram-PHBUUO56-D05_LyL7.js} +1 -1
  74. package/dist/web/assets/{journeyDiagram-4ABVD52K-CgDI-UG4.js → journeyDiagram-4ABVD52K-B_L20qMe.js} +1 -1
  75. package/dist/web/assets/jsx-runtime-kMwlnEGE.js +1 -0
  76. package/dist/web/assets/{kanban-definition-K7BYSVSG-h4g10UHL.js → kanban-definition-K7BYSVSG-CZ535BbZ.js} +1 -1
  77. package/dist/web/assets/keybindings-store-DPYzBe_M.js +1 -0
  78. package/dist/web/assets/{line-B75-Rx70.js → line-CVvo3dRu.js} +1 -1
  79. package/dist/web/assets/{linear-Bcjv9FQt.js → linear-DP4mkX3m.js} +1 -1
  80. package/dist/web/assets/{markdown-renderer-VIZB1GXE.js → markdown-renderer-DPLdR9xc.js} +5 -5
  81. package/dist/web/assets/{mermaid-parser.core-8u2leTXI.js → mermaid-parser.core-C7UwoIh6.js} +2 -2
  82. package/dist/web/assets/{mindmap-definition-YRQLILUH-BaOBwb-W.js → mindmap-definition-YRQLILUH-x0MTutJp.js} +1 -1
  83. package/dist/web/assets/{ordinal-LFEjVtwQ.js → ordinal-_K3x1fkz.js} +1 -1
  84. package/dist/web/assets/packet-RMMSAZCW-DY5PNnZU.js +1 -0
  85. package/dist/web/assets/pie-UPGHQEXC-BHncZutv.js +1 -0
  86. package/dist/web/assets/{pieDiagram-SKSYHLDU-At5Kz0KK.js → pieDiagram-SKSYHLDU-C1Gjrtzy.js} +1 -1
  87. package/dist/web/assets/postgres-viewer-BeiK4lCa.js +1 -0
  88. package/dist/web/assets/{quadrantDiagram-337W2JSQ-CdjGIDfw.js → quadrantDiagram-337W2JSQ-C8bzJCjQ.js} +1 -1
  89. package/dist/web/assets/radar-KQ55EAFF-DH0AOkUy.js +1 -0
  90. package/dist/web/assets/{requirementDiagram-Z7DCOOCP-B9F_Cx_p.js → requirementDiagram-Z7DCOOCP-pQyah6WB.js} +1 -1
  91. package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-RolPi8bU.js → sankeyDiagram-WA2Y5GQK-T6RgG-N8.js} +1 -1
  92. package/dist/web/assets/{sequenceDiagram-2WXFIKYE-DM-tMAhx.js → sequenceDiagram-2WXFIKYE-BQDJ4CVs.js} +1 -1
  93. package/dist/web/assets/settings-tab-D3AvU4lu.js +1 -0
  94. package/dist/web/assets/sqlite-viewer-nA2sD4Yv.js +1 -0
  95. package/dist/web/assets/{stateDiagram-RAJIS63D-C4EMl6jf.js → stateDiagram-RAJIS63D-66vhiIuk.js} +1 -1
  96. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-BGVqj_g9.js +1 -0
  97. package/dist/web/assets/tab-store-BOgTrqRr.js +1 -0
  98. package/dist/web/assets/table-DFevCOMd.js +1 -0
  99. package/dist/web/assets/tag-CXMT0QB6.js +1 -0
  100. package/dist/web/assets/{terminal-tab-XhKfb4ei.js → terminal-tab-BBi0pEji.js} +1 -1
  101. package/dist/web/assets/{timeline-definition-YZTLITO2-A4PN_Efm.js → timeline-definition-YZTLITO2-DwZqB3nn.js} +1 -1
  102. package/dist/web/assets/treemap-KZPCXAKY-B2Xkyv-K.js +1 -0
  103. package/dist/web/assets/{use-monaco-theme-0p0-84jJ.js → use-monaco-theme-B5pG2d1w.js} +1 -1
  104. package/dist/web/assets/{vennDiagram-LZ73GAT5-ywK7LMaH.js → vennDiagram-LZ73GAT5-s9Z71fz-.js} +1 -1
  105. package/dist/web/assets/{xychartDiagram-JWTSCODW-DylHYNtJ.js → xychartDiagram-JWTSCODW-DRa_TH4B.js} +1 -1
  106. package/dist/web/index.html +10 -9
  107. package/dist/web/monacoeditorwork/css.worker.bundle.js +122 -122
  108. package/dist/web/monacoeditorwork/editor.worker.bundle.js +78 -78
  109. package/dist/web/monacoeditorwork/html.worker.bundle.js +110 -110
  110. package/dist/web/monacoeditorwork/json.worker.bundle.js +108 -108
  111. package/dist/web/monacoeditorwork/ts.worker.bundle.js +81 -81
  112. package/dist/web/sw.js +1 -1
  113. package/docs/code-standards.md +128 -1
  114. package/docs/codebase-summary.md +79 -12
  115. package/docs/extension-development-guide.md +532 -0
  116. package/docs/project-changelog.md +51 -1
  117. package/docs/project-roadmap.md +9 -3
  118. package/docs/streaming-input-guide.md +267 -0
  119. package/docs/system-architecture.md +432 -3
  120. package/package.json +6 -3
  121. package/packages/ext-database/package.json +41 -0
  122. package/packages/ext-database/src/connection-tree.ts +142 -0
  123. package/packages/ext-database/src/extension.ts +346 -0
  124. package/packages/ext-database/src/query-panel.ts +120 -0
  125. package/packages/ext-database/src/table-viewer-panel.ts +410 -0
  126. package/packages/ext-database/tsconfig.json +8 -0
  127. package/packages/vscode-compat/package.json +16 -0
  128. package/packages/vscode-compat/src/commands.ts +39 -0
  129. package/packages/vscode-compat/src/context.ts +65 -0
  130. package/packages/vscode-compat/src/disposable.ts +21 -0
  131. package/packages/vscode-compat/src/env.ts +20 -0
  132. package/packages/vscode-compat/src/event-emitter.ts +28 -0
  133. package/packages/vscode-compat/src/index.ts +93 -0
  134. package/packages/vscode-compat/src/not-supported.ts +15 -0
  135. package/packages/vscode-compat/src/types.ts +167 -0
  136. package/packages/vscode-compat/src/uri.ts +65 -0
  137. package/packages/vscode-compat/src/window.ts +229 -0
  138. package/packages/vscode-compat/src/workspace.ts +76 -0
  139. package/packages/vscode-compat/tsconfig.json +10 -0
  140. package/snapshot-state.md +1526 -0
  141. package/src/cli/commands/autostart.ts +1 -1
  142. package/src/cli/commands/ext-cmd.ts +121 -0
  143. package/src/cli/commands/restart.ts +9 -1
  144. package/src/cli/commands/status.ts +19 -0
  145. package/src/index.ts +5 -3
  146. package/src/providers/claude-agent-sdk.ts +221 -17
  147. package/src/providers/cli-provider-base.ts +6 -0
  148. package/src/server/index.ts +55 -155
  149. package/src/server/routes/chat.ts +81 -11
  150. package/src/server/routes/extensions.ts +81 -0
  151. package/src/server/routes/project-scoped.ts +2 -0
  152. package/src/server/routes/settings.ts +27 -0
  153. package/src/server/routes/workspace.ts +35 -0
  154. package/src/server/ws/chat.ts +9 -3
  155. package/src/server/ws/extensions.ts +175 -0
  156. package/src/services/account-selector.service.ts +14 -5
  157. package/src/services/account.service.ts +7 -7
  158. package/src/services/claude-usage.service.ts +11 -11
  159. package/src/services/cloud-ws.service.ts +228 -0
  160. package/src/services/cloud.service.ts +1 -0
  161. package/src/services/contribution-registry.ts +110 -0
  162. package/src/services/db.service.ts +181 -4
  163. package/src/services/extension-host-worker.ts +160 -0
  164. package/src/services/extension-installer.ts +112 -0
  165. package/src/services/extension-manifest.ts +65 -0
  166. package/src/services/extension-rpc-handlers.ts +235 -0
  167. package/src/services/extension-rpc.ts +105 -0
  168. package/src/services/extension.service.ts +228 -0
  169. package/src/services/mcp-config.service.ts +15 -6
  170. package/src/services/supervisor.ts +271 -25
  171. package/src/types/api.ts +1 -0
  172. package/src/types/chat.ts +4 -0
  173. package/src/types/extension-messages.ts +64 -0
  174. package/src/types/extension.ts +131 -0
  175. package/src/web/app.tsx +69 -48
  176. package/src/web/components/chat/account-rotation-settings.tsx +163 -0
  177. package/src/web/components/chat/chat-history-bar.tsx +106 -10
  178. package/src/web/components/chat/chat-tab.tsx +15 -10
  179. package/src/web/components/chat/chat-welcome.tsx +148 -0
  180. package/src/web/components/chat/message-list.tsx +19 -6
  181. package/src/web/components/chat/session-picker.tsx +80 -32
  182. package/src/web/components/chat/usage-badge.tsx +68 -8
  183. package/src/web/components/extensions/extension-inputbox.tsx +92 -0
  184. package/src/web/components/extensions/extension-quickpick.tsx +194 -0
  185. package/src/web/components/extensions/extension-tree-view.tsx +240 -0
  186. package/src/web/components/extensions/extension-webview.tsx +83 -0
  187. package/src/web/components/layout/command-palette.tsx +22 -2
  188. package/src/web/components/layout/editor-panel.tsx +163 -18
  189. package/src/web/components/layout/mobile-nav.tsx +2 -1
  190. package/src/web/components/layout/sidebar.tsx +21 -3
  191. package/src/web/components/layout/status-bar.tsx +64 -0
  192. package/src/web/components/layout/tab-bar.tsx +2 -0
  193. package/src/web/components/layout/tab-content.tsx +5 -0
  194. package/src/web/components/layout/upgrade-banner.tsx +15 -5
  195. package/src/web/components/settings/change-password-section.tsx +128 -0
  196. package/src/web/components/settings/extension-manager-section.tsx +214 -0
  197. package/src/web/components/settings/settings-tab.tsx +9 -2
  198. package/src/web/components/shared/connection-lost-overlay.tsx +89 -0
  199. package/src/web/hooks/use-chat.ts +28 -0
  200. package/src/web/hooks/use-extension-ws.ts +181 -0
  201. package/src/web/hooks/use-global-keybindings.ts +18 -2
  202. package/src/web/hooks/use-server-reload.ts +9 -0
  203. package/src/web/hooks/use-url-sync.ts +173 -21
  204. package/src/web/stores/connection-store.ts +39 -0
  205. package/src/web/stores/extension-store.ts +204 -0
  206. package/src/web/stores/panel-store.ts +63 -9
  207. package/src/web/stores/panel-utils.ts +145 -3
  208. package/src/web/stores/settings-store.ts +7 -2
  209. package/src/web/stores/tab-store.ts +2 -1
  210. package/test-session-ops.mjs +444 -0
  211. package/test-tokens.mjs +212 -0
  212. package/tsconfig.json +3 -1
  213. package/dist/web/assets/api-settings-CEMxVMCV.js +0 -1
  214. package/dist/web/assets/architecture-PBZL5I3N-CFzkFKEL.js +0 -1
  215. package/dist/web/assets/arrow-up--LjUXLEt.js +0 -1
  216. package/dist/web/assets/browser-tab-D1Zua62g.js +0 -1
  217. package/dist/web/assets/channel-C2fMafck.js +0 -1
  218. package/dist/web/assets/chat-tab-BnD27Vp9.js +0 -7
  219. package/dist/web/assets/chevron-right-CHnjJt4E.js +0 -1
  220. package/dist/web/assets/chunk-GLR3WWYH-DBdWQ3zy.js +0 -2
  221. package/dist/web/assets/chunk-HHEYEP7N-BBw_z0fW.js +0 -1
  222. package/dist/web/assets/chunk-QZHKN3VN-DFKFM_C1.js +0 -1
  223. package/dist/web/assets/classDiagram-VBA2DB6C-Dp4Kk3Yb.js +0 -1
  224. package/dist/web/assets/classDiagram-v2-RAHNMMFH-D8IvcV_B.js +0 -1
  225. package/dist/web/assets/clone-B2hUek6n.js +0 -1
  226. package/dist/web/assets/code-editor-DGRg8stf.js +0 -2
  227. package/dist/web/assets/columns-2-DbesTfa7.js +0 -1
  228. package/dist/web/assets/database-viewer-DxCXZQcE.js +0 -1
  229. package/dist/web/assets/diff-viewer-C1sDJG35.js +0 -4
  230. package/dist/web/assets/git-graph-BDn-EiGE.js +0 -1
  231. package/dist/web/assets/gitGraph-HDMCJU4V-CNlas3Rz.js +0 -1
  232. package/dist/web/assets/index-Bun94AK3.js +0 -37
  233. package/dist/web/assets/index-Db8uky1a.css +0 -2
  234. package/dist/web/assets/info-3K5VOQVL-BDzTLc11.js +0 -1
  235. package/dist/web/assets/infoDiagram-LFFYTUFH-ZZmpgc6t.js +0 -2
  236. package/dist/web/assets/jsx-runtime-BRW_vwa9.js +0 -1
  237. package/dist/web/assets/keybindings-store-COmK4Dte.js +0 -1
  238. package/dist/web/assets/packet-RMMSAZCW-IVa5F-go.js +0 -1
  239. package/dist/web/assets/pie-UPGHQEXC-CvXHKAzp.js +0 -1
  240. package/dist/web/assets/postgres-viewer-CvQZ8gkh.js +0 -1
  241. package/dist/web/assets/radar-KQ55EAFF-Z-Tr5wtS.js +0 -1
  242. package/dist/web/assets/settings-tab-RCnvZ29H.js +0 -1
  243. package/dist/web/assets/sqlite-viewer-CEEm2W4C.js +0 -1
  244. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-B-UjZch3.js +0 -1
  245. package/dist/web/assets/tab-store-Bjh6bXFP.js +0 -1
  246. package/dist/web/assets/table-CQVQM2SB.js +0 -1
  247. package/dist/web/assets/tag-Q2dZiSPX.js +0 -1
  248. package/dist/web/assets/treemap-KZPCXAKY-C9TYRE0k.js +0 -1
  249. /package/dist/web/assets/{api-client-BKIT_Qeg.js → api-client-BfBM3I7n.js} +0 -0
  250. /package/dist/web/assets/{array-DqLCdDFv.js → array-B9UHiPd-.js} +0 -0
  251. /package/dist/web/assets/{cytoscape.esm-CWPXKqbJ.js → cytoscape.esm-BW-DbntU.js} +0 -0
  252. /package/dist/web/assets/{defaultLocale-CrJzLgRD.js → defaultLocale-5eAKkKJC.js} +0 -0
  253. /package/dist/web/assets/{dist-Cep75xXf.js → dist-CSJdAyA9.js} +0 -0
  254. /package/dist/web/assets/{init-C0r9Gk5G.js → init-DlZdxViB.js} +0 -0
  255. /package/dist/web/assets/{isArrayLikeObject-CGBoxvCD.js → isArrayLikeObject-B_v2FtYn.js} +0 -0
  256. /package/dist/web/assets/{katex-DzXRfQ_m.js → katex-Bqvo_ZG0.js} +0 -0
  257. /package/dist/web/assets/{lib-BeaDXEkP.js → lib-BQ34Db2e.js} +0 -0
  258. /package/dist/web/assets/{math-y9zN1W-N.js → math-069Z4SuC.js} +0 -0
  259. /package/dist/web/assets/{path-DIKpVbHL.js → path-6uRLdFF7.js} +0 -0
  260. /package/dist/web/assets/{rough.esm-nHaDi0Kw.js → rough.esm-JX0wREDd.js} +0 -0
  261. /package/dist/web/assets/{src-Dw4QhedI.js → src-BqX54PbV.js} +0 -0
  262. /package/dist/web/assets/{utils-DMiycH3O.js → utils-BNytJOb1.js} +0 -0
@@ -19,6 +19,7 @@ import { browserPreviewRoutes } from "./routes/browser-preview.ts";
19
19
  import { initAdapters } from "../services/database/init-adapters.ts";
20
20
  import { terminalWebSocket } from "./ws/terminal.ts";
21
21
  import { chatWebSocket } from "./ws/chat.ts";
22
+ import { extensionWebSocket } from "./ws/extensions.ts";
22
23
  import { ok, err } from "../types/api.ts";
23
24
 
24
25
  /** Tee console.log/error to ~/.ppm/ppm.log while preserving terminal output */
@@ -149,6 +150,10 @@ app.route("/api/postgres", postgresRoutes);
149
150
  app.route("/api/db", databaseRoutes);
150
151
  app.route("/api/accounts", accountsRoutes);
151
152
 
153
+ // Extensions management
154
+ import { extensionRoutes } from "./routes/extensions.ts";
155
+ app.route("/api/extensions", extensionRoutes);
156
+
152
157
  // Upgrade routes (check for updates, apply upgrade)
153
158
  import { upgradeRoutes } from "./routes/upgrade.ts";
154
159
  app.route("/api/upgrade", upgradeRoutes);
@@ -162,51 +167,43 @@ app.route("/", staticRoutes);
162
167
 
163
168
  export async function startServer(options: {
164
169
  port?: string;
165
- foreground?: boolean;
166
- daemon?: boolean; // compat, ignored (daemon is now default)
167
170
  share?: boolean;
168
171
  config?: string;
169
172
  profile?: string;
170
173
  }) {
174
+ // Tunnel always enabled — cloudflared shares the server publicly
175
+ options.share = true;
176
+
171
177
  // Load config
172
178
  configService.load(options.config);
173
179
  const port = parseInt(options.port ?? String(configService.get("port")), 10);
174
180
  const host = configService.get("host");
175
181
 
176
- // Setup log file (both foreground and daemon modes)
177
182
  await setupLogFile();
178
183
 
179
184
  // Bootstrap CLI providers (checks binary availability)
180
185
  const { bootstrapProviders } = await import("../providers/registry.ts");
181
186
  await bootstrapProviders();
182
187
 
183
- // Check if port is already in use before starting.
184
- // Skip in hot-reload mode — Bun.serve() replaces the previous server on the same port,
185
- // but a net.createServer() probe would see it as "in use" and exit prematurely.
186
- // globalThis persists across bun --hot reloads, so we use a flag set after first start.
187
- const isHotReload = !!(globalThis as any).__PPM_SERVER_STARTED__;
188
- if (!isHotReload) {
189
- const portInUse = await new Promise<boolean>((resolve) => {
190
- const net = require("node:net") as typeof import("node:net");
191
- const tester = net.createServer()
192
- .once("error", (err: NodeJS.ErrnoException) => {
193
- resolve(err.code === "EADDRINUSE");
194
- })
195
- .once("listening", () => {
196
- tester.close(() => resolve(false));
197
- })
198
- .listen(port, host);
199
- });
200
- if (portInUse) {
201
- console.error(`\n ✗ Port ${port} is already in use.`);
202
- console.error(` Run 'ppm stop' first or use a different port with --port.\n`);
203
- process.exit(1);
204
- }
188
+ // Check if port is already in use before spawning supervisor
189
+ const portInUse = await new Promise<boolean>((resolve) => {
190
+ const net = require("node:net") as typeof import("node:net");
191
+ const tester = net.createServer()
192
+ .once("error", (err: NodeJS.ErrnoException) => {
193
+ resolve(err.code === "EADDRINUSE");
194
+ })
195
+ .once("listening", () => {
196
+ tester.close(() => resolve(false));
197
+ })
198
+ .listen(port, host);
199
+ });
200
+ if (portInUse) {
201
+ console.error(`\n ✗ Port ${port} is already in use.`);
202
+ console.error(` Run 'ppm stop' first or use a different port with --port.\n`);
203
+ process.exit(1);
205
204
  }
206
205
 
207
- const isDaemon = !options.foreground;
208
-
209
- if (isDaemon) {
206
+ {
210
207
  const { resolve } = await import("node:path");
211
208
  const { homedir } = await import("node:os");
212
209
  const { writeFileSync, readFileSync, mkdirSync, existsSync, openSync } = await import("node:fs");
@@ -272,7 +269,6 @@ export async function startServer(options: {
272
269
  if (isNaN(supervisorPid)) {
273
270
  console.error(" ✗ Failed to start supervisor on Windows.");
274
271
  console.error(` ${result.stderr.toString().trim()}`);
275
- console.error(" Try: ppm start -f (foreground mode)");
276
272
  process.exit(1);
277
273
  }
278
274
  } else {
@@ -297,7 +293,6 @@ export async function startServer(options: {
297
293
  try { process.kill(supervisorPid, 0); } catch {
298
294
  console.error(" ✗ Supervisor exited immediately after start.");
299
295
  console.error(" Check logs: ppm logs");
300
- console.error(" Or try: ppm start -f (foreground mode)");
301
296
  process.exit(1);
302
297
  }
303
298
  // Check if server PID appeared in status.json
@@ -353,130 +348,6 @@ export async function startServer(options: {
353
348
 
354
349
  process.exit(0);
355
350
  }
356
-
357
- // Foreground mode — with WebSocket support
358
- const server = Bun.serve({
359
- port,
360
- hostname: host,
361
- fetch(req, server) {
362
- const url = new URL(req.url);
363
-
364
- // WebSocket upgrade: /ws/project/:projectName/terminal/:id
365
- if (url.pathname.startsWith("/ws/project/")) {
366
- const parts = url.pathname.split("/");
367
- const projectName = parts[3] ?? "";
368
- const wsType = parts[4] ?? "";
369
- const id = parts[5] ?? "";
370
-
371
- if (wsType === "terminal") {
372
- const upgraded = server.upgrade(req, {
373
- data: { type: "terminal", id, projectName },
374
- });
375
- if (upgraded) return undefined;
376
- return new Response("WebSocket upgrade failed", { status: 400 });
377
- }
378
-
379
- if (wsType === "chat") {
380
- const sessionId = id;
381
- const upgraded = server.upgrade(req, {
382
- data: { type: "chat", sessionId, projectName },
383
- });
384
- if (upgraded) return undefined;
385
- return new Response("WebSocket upgrade failed", { status: 400 });
386
- }
387
- }
388
-
389
- return app.fetch(req, server);
390
- },
391
- websocket: {
392
- idleTimeout: 960,
393
- sendPong: true,
394
- perMessageDeflate: false, // Disable compression — Cloudflare tunnels can mangle compressed frames
395
- open(ws: any) {
396
- if (ws.data?.type === "health") {
397
- ws.send(JSON.stringify({ type: "health", status: "ok" }));
398
- } else if (ws.data?.type === "chat") chatWebSocket.open(ws);
399
- else terminalWebSocket.open(ws);
400
- },
401
- message(ws: any, msg: any) {
402
- if (ws.data?.type === "health") {
403
- // Respond to ping with pong
404
- ws.send(JSON.stringify({ type: "health", status: "ok" }));
405
- } else if (ws.data?.type === "chat") chatWebSocket.message(ws, msg);
406
- else terminalWebSocket.message(ws, msg);
407
- },
408
- close(ws: any) {
409
- if (ws.data?.type === "health") return;
410
- if (ws.data?.type === "chat") chatWebSocket.close(ws);
411
- else terminalWebSocket.close(ws);
412
- },
413
- } as Parameters<typeof Bun.serve>[0] extends { websocket?: infer W } ? W : never,
414
- });
415
-
416
- // Mark server as started — survives bun --hot reloads (globalThis persists)
417
- (globalThis as any).__PPM_SERVER_STARTED__ = true;
418
-
419
- // Start background usage polling
420
- import("../services/claude-usage.service.ts").then(({ startUsagePolling }) => startUsagePolling()).catch(() => {});
421
-
422
- // Start background account token refresh
423
- import("../services/account.service.ts").then(({ accountService }) => accountService.startAutoRefresh()).catch(() => {});
424
-
425
- console.log(`\n PPM ready\n`);
426
- console.log(` ➜ Local: http://localhost:${server.port}/`);
427
-
428
- const { networkInterfaces } = await import("node:os");
429
- const nets = networkInterfaces();
430
- for (const name of Object.keys(nets)) {
431
- for (const net of nets[name] ?? []) {
432
- if (net.family === "IPv4" && !net.internal) {
433
- console.log(` ➜ Network: http://${net.address}:${server.port}/`);
434
- }
435
- }
436
- }
437
-
438
- // Share tunnel in foreground mode
439
- if (options.share) {
440
- try {
441
- const { tunnelService } = await import("../services/tunnel.service.ts");
442
- console.log("\n Starting share tunnel...");
443
- const shareUrl = await tunnelService.startTunnel(server.port!);
444
- console.log(` ➜ Share: ${shareUrl}`);
445
- if (!configService.get("auth").enabled) {
446
- console.log(`\n ⚠ Warning: auth is disabled — your IDE is publicly accessible!`);
447
- console.log(` Enable auth: run 'ppm config set auth.enabled true' or restart without --share.`);
448
- }
449
- const qr = await import("qrcode-terminal");
450
- console.log();
451
- qr.generate(shareUrl, { small: true });
452
- } catch (err: unknown) {
453
- const msg = err instanceof Error ? err.message : String(err);
454
- console.error(` ✗ Share failed: ${msg}`);
455
- }
456
- }
457
-
458
- console.log(`\n Auth: ${configService.get("auth").enabled ? "enabled" : "disabled"}`);
459
- if (configService.get("auth").enabled) {
460
- console.log(` Token: ${configService.get("auth").token}`);
461
- }
462
- console.log();
463
-
464
- // Graceful shutdown — stop server + tunnel + preview tunnels + DB on exit
465
- const shutdown = () => {
466
- try { server.stop(true); } catch {}
467
- try {
468
- import("../services/tunnel.service.ts").then(({ tunnelService }) => tunnelService.stopTunnel()).catch(() => {});
469
- } catch {}
470
- try {
471
- import("./routes/browser-preview.ts").then(({ stopAllPreviewTunnels }) => stopAllPreviewTunnels()).catch(() => {});
472
- } catch {}
473
- try {
474
- import("../services/db.service.ts").then(({ closeDb }) => closeDb()).catch(() => {});
475
- } catch {}
476
- };
477
- process.on("SIGINT", () => { shutdown(); process.exit(0); });
478
- process.on("SIGTERM", () => { shutdown(); process.exit(0); });
479
- process.on("exit", shutdown);
480
351
  }
481
352
 
482
353
  // Internal entry point for daemon child process
@@ -500,12 +371,16 @@ if (process.argv.includes("__serve__")) {
500
371
 
501
372
  // Sync externally-started tunnel URL + PID into tunnelService
502
373
  // so GET /api/tunnel reflects the correct state and Share button doesn't start a duplicate.
374
+ // Also write server version to status.json so supervisor heartbeat reports the actual running version.
503
375
  try {
504
376
  const { resolve: r } = await import("node:path");
505
377
  const { homedir: h } = await import("node:os");
506
- const { readFileSync: rf } = await import("node:fs");
378
+ const { readFileSync: rf, writeFileSync: wf } = await import("node:fs");
507
379
  const statusFile = r(h(), ".ppm", "status.json");
508
380
  const status = JSON.parse(rf(statusFile, "utf-8"));
381
+ // Write running server version — source of truth for heartbeat
382
+ status.serverVersion = VERSION;
383
+ wf(statusFile, JSON.stringify(status));
509
384
  if (status.shareUrl) {
510
385
  const { tunnelService } = await import("../services/tunnel.service.ts");
511
386
  tunnelService.setExternalUrl(status.shareUrl);
@@ -525,6 +400,20 @@ if (process.argv.includes("__serve__")) {
525
400
  return new Response("WebSocket upgrade failed", { status: 400 });
526
401
  }
527
402
 
403
+ if (url.pathname === "/ws/extensions") {
404
+ // Auth check for extension WS
405
+ const authConfig = configService.get("auth");
406
+ if (authConfig.enabled) {
407
+ const token = url.searchParams.get("token");
408
+ if (token !== authConfig.token) {
409
+ return new Response("Unauthorized", { status: 401 });
410
+ }
411
+ }
412
+ const upgraded = server.upgrade(req, { data: { type: "extensions" } });
413
+ if (upgraded) return undefined;
414
+ return new Response("WebSocket upgrade failed", { status: 400 });
415
+ }
416
+
528
417
  if (url.pathname.startsWith("/ws/project/")) {
529
418
  const parts = url.pathname.split("/");
530
419
  const projectName = parts[3] ?? "";
@@ -557,14 +446,17 @@ if (process.argv.includes("__serve__")) {
557
446
  perMessageDeflate: false,
558
447
  open(ws: any) {
559
448
  if (ws.data?.type === "chat") chatWebSocket.open(ws);
449
+ else if (ws.data?.type === "extensions") extensionWebSocket.open(ws);
560
450
  else terminalWebSocket.open(ws);
561
451
  },
562
452
  message(ws: any, msg: any) {
563
453
  if (ws.data?.type === "chat") chatWebSocket.message(ws, msg);
454
+ else if (ws.data?.type === "extensions") extensionWebSocket.message(ws, msg);
564
455
  else terminalWebSocket.message(ws, msg);
565
456
  },
566
457
  close(ws: any) {
567
458
  if (ws.data?.type === "chat") chatWebSocket.close(ws);
459
+ else if (ws.data?.type === "extensions") extensionWebSocket.close(ws);
568
460
  else terminalWebSocket.close(ws);
569
461
  },
570
462
  } as Parameters<typeof Bun.serve>[0] extends { websocket?: infer W } ? W : never,
@@ -573,5 +465,13 @@ if (process.argv.includes("__serve__")) {
573
465
  // Start background account token refresh in daemon child
574
466
  import("../services/account.service.ts").then(({ accountService }) => accountService.startAutoRefresh()).catch(() => {});
575
467
 
468
+ // Start background usage limit polling (every 5 min)
469
+ import("../services/claude-usage.service.ts").then(({ startUsagePolling }) => startUsagePolling()).catch(() => {});
470
+
471
+ // Discover + activate enabled extensions
472
+ import("../services/extension.service.ts").then(({ extensionService }) => extensionService.startup()).catch((e) => {
473
+ console.error("[ExtService] Startup error:", e);
474
+ });
475
+
576
476
  console.log(`Server child ready on port ${port}`);
577
477
  }
@@ -8,7 +8,7 @@ import { renameSession as sdkRenameSession } from "@anthropic-ai/claude-agent-sd
8
8
  import { listSlashItems } from "../../services/slash-items.service.ts";
9
9
  import { getCachedUsage, refreshUsageNow } from "../../services/claude-usage.service.ts";
10
10
  import { getSessionLog } from "../../services/session-log.service.ts";
11
- import { getSessionMapping, setSessionTitle } from "../../services/db.service.ts";
11
+ import { getSessionMapping, getSessionProjectPath, setSessionMapping, setSessionTitle, getPinnedSessionIds, pinSession, unpinSession, deleteSessionMapping, deleteSessionTitle } from "../../services/db.service.ts";
12
12
  import { ok, err } from "../../types/api.ts";
13
13
 
14
14
  type Env = { Variables: { projectPath: string; projectName: string } };
@@ -76,7 +76,16 @@ chatRoutes.get("/sessions", async (c) => {
76
76
  const projectPath = c.get("projectPath");
77
77
  const providerId = c.req.query("providerId");
78
78
  const sessions = await chatService.listSessions(providerId, projectPath);
79
- return c.json(ok(sessions));
79
+ // Enrich with pin status
80
+ const pinnedIds = getPinnedSessionIds();
81
+ const enriched = sessions.map((s) => ({ ...s, pinned: pinnedIds.has(s.id) }));
82
+ // Sort: pinned first (by pinned_at implicit via Set order), then unpinned by createdAt
83
+ enriched.sort((a, b) => {
84
+ if (a.pinned && !b.pinned) return -1;
85
+ if (!a.pinned && b.pinned) return 1;
86
+ return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
87
+ });
88
+ return c.json(ok(enriched));
80
89
  } catch (e) {
81
90
  return c.json(err((e as Error).message), 500);
82
91
  }
@@ -116,7 +125,13 @@ chatRoutes.delete("/sessions/:id", async (c) => {
116
125
  try {
117
126
  const id = c.req.param("id");
118
127
  const providerId = c.req.query("providerId") ?? "claude";
128
+ const sdkId = getSessionMapping(id) ?? id;
129
+ // Provider-specific cleanup (JSONL, process, etc.)
119
130
  await chatService.deleteSession(providerId, id);
131
+ // Shared DB cleanup
132
+ deleteSessionMapping(id);
133
+ deleteSessionTitle(sdkId);
134
+ unpinSession(sdkId);
120
135
  return c.json(ok({ deleted: id }));
121
136
  } catch (e) {
122
137
  return c.json(err((e as Error).message), 404);
@@ -146,6 +161,28 @@ chatRoutes.patch("/sessions/:id", async (c) => {
146
161
  }
147
162
  });
148
163
 
164
+ /** PUT /chat/sessions/:id/pin — pin a session */
165
+ chatRoutes.put("/sessions/:id/pin", (c) => {
166
+ try {
167
+ const id = c.req.param("id");
168
+ pinSession(id);
169
+ return c.json(ok({ id, pinned: true }));
170
+ } catch (e) {
171
+ return c.json(err((e as Error).message), 500);
172
+ }
173
+ });
174
+
175
+ /** DELETE /chat/sessions/:id/pin — unpin a session */
176
+ chatRoutes.delete("/sessions/:id/pin", (c) => {
177
+ try {
178
+ const id = c.req.param("id");
179
+ unpinSession(id);
180
+ return c.json(ok({ id, pinned: false }));
181
+ } catch (e) {
182
+ return c.json(err((e as Error).message), 500);
183
+ }
184
+ });
185
+
149
186
  /** POST /chat/sessions/:id/fork — fork session into a new one (for rewind/branch) */
150
187
  chatRoutes.post("/sessions/:id/fork", async (c) => {
151
188
  try {
@@ -153,16 +190,31 @@ chatRoutes.post("/sessions/:id/fork", async (c) => {
153
190
  const projectName = c.get("projectName");
154
191
  const projectPath = c.get("projectPath");
155
192
  const providerId = c.req.query("providerId") ?? "claude";
156
- // Create a new PPM session that will fork from sourceId on first message
157
- const session = await chatService.createSession(providerId, {
158
- projectName,
159
- projectPath,
160
- title: "Forked Chat",
161
- });
162
- // Store fork source so WS handler knows to use forkSession on first message
193
+ const body = await c.req.json<{ messageId?: string }>().catch(() => ({} as { messageId?: string }));
163
194
  const provider = providerRegistry.get(providerId);
164
- provider?.setForkSource?.(session.id, sourceId);
165
- return c.json(ok({ ...session, forkedFrom: sourceId }), 201);
195
+ if (!provider) return c.json(err("Provider not found"), 404);
196
+
197
+ if (body.messageId) {
198
+ // Mid-fork at a specific message
199
+ if (!provider.forkAtMessage) {
200
+ return c.json(err("Provider does not support forking"), 400);
201
+ }
202
+ const result = await provider.forkAtMessage(sourceId, body.messageId, {
203
+ title: "Forked Chat", dir: projectPath,
204
+ });
205
+ const session = await chatService.createSession(providerId, {
206
+ projectName, projectPath, title: "Forked Chat",
207
+ });
208
+ setSessionMapping(session.id, result.sessionId);
209
+ provider.markAsResumed?.(session.id);
210
+ return c.json(ok({ ...session, forkedFrom: sourceId }), 201);
211
+ } else {
212
+ // No messageId (fork at first message) — create a fresh empty session
213
+ const session = await chatService.createSession(providerId, {
214
+ projectName, projectPath, title: "Forked Chat",
215
+ });
216
+ return c.json(ok({ ...session, forkedFrom: sourceId }), 201);
217
+ }
166
218
  } catch (e) {
167
219
  return c.json(err((e as Error).message), 500);
168
220
  }
@@ -180,6 +232,24 @@ chatRoutes.get("/sessions/:id/logs", (c) => {
180
232
  }
181
233
  });
182
234
 
235
+ /** GET /chat/sessions/:id/debug — session debug info (IDs, JSONL path) */
236
+ chatRoutes.get("/sessions/:id/debug", (c) => {
237
+ const ppmId = c.req.param("id");
238
+ const sdkId = getSessionMapping(ppmId) ?? ppmId;
239
+ // Resolve JSONL path: ~/.claude/projects/<encoded-cwd>/<sdkId>.jsonl
240
+ const homedir = process.env.HOME ?? process.env.USERPROFILE ?? "";
241
+ const provider = providerRegistry.get("claude") as any;
242
+ // Try in-memory first, fall back to DB-persisted project_path
243
+ const projectPath = provider?.activeSessions?.get(ppmId)?.projectPath
244
+ ?? getSessionProjectPath(ppmId)
245
+ ?? "";
246
+ const encodedCwd = projectPath ? projectPath.replace(/\//g, "-") : "";
247
+ const jsonlDir = encodedCwd ? resolve(homedir, ".claude", "projects", encodedCwd) : "";
248
+ const jsonlPath = jsonlDir ? resolve(jsonlDir, `${sdkId}.jsonl`) : "";
249
+ const jsonlExists = jsonlPath ? existsSync(jsonlPath) : false;
250
+ return c.json(ok({ ppmSessionId: ppmId, sdkSessionId: sdkId, jsonlPath: jsonlExists ? jsonlPath : null, jsonlDir, projectPath }));
251
+ });
252
+
183
253
  /** POST /chat/upload — upload files for chat attachments, returns server-side paths */
184
254
  chatRoutes.post("/upload", async (c) => {
185
255
  try {
@@ -0,0 +1,81 @@
1
+ import { Hono } from "hono";
2
+ import { extensionService } from "../../services/extension.service.ts";
3
+ import { contributionRegistry } from "../../services/contribution-registry.ts";
4
+ import { ok, err } from "../../types/api.ts";
5
+
6
+ export const extensionRoutes = new Hono();
7
+
8
+ // GET /api/extensions — list all extensions
9
+ extensionRoutes.get("/", (c) => {
10
+ const extensions = extensionService.list();
11
+ return c.json(ok(extensions));
12
+ });
13
+
14
+ // GET /api/extensions/contributions — all contribution points (for UI)
15
+ extensionRoutes.get("/contributions", (c) => {
16
+ return c.json(ok(contributionRegistry.getAll()));
17
+ });
18
+
19
+ // GET /api/extensions/:id — get single extension info
20
+ extensionRoutes.get("/:id{.+}", (c) => {
21
+ const id = c.req.param("id");
22
+ const ext = extensionService.get(id);
23
+ if (!ext) return c.json(err("Extension not found"), 404);
24
+ return c.json(ok(ext));
25
+ });
26
+
27
+ // POST /api/extensions/install — install extension
28
+ extensionRoutes.post("/install", async (c) => {
29
+ const body = await c.req.json<{ name?: string }>().catch(() => ({}) as { name?: string });
30
+ if (!body.name) return c.json(err("Missing 'name' field"), 400);
31
+
32
+ try {
33
+ const manifest = await extensionService.install(body.name);
34
+ return c.json(ok(manifest), 201);
35
+ } catch (e) {
36
+ const msg = e instanceof Error ? e.message : String(e);
37
+ return c.json(err(msg), 500);
38
+ }
39
+ });
40
+
41
+ // POST /api/extensions/dev-link — symlink local extension for development
42
+ extensionRoutes.post("/dev-link", async (c) => {
43
+ const body = await c.req.json<{ path?: string }>().catch(() => ({}) as { path?: string });
44
+ if (!body.path) return c.json(err("Missing 'path' field"), 400);
45
+
46
+ try {
47
+ const manifest = await extensionService.devLink(body.path);
48
+ return c.json(ok(manifest), 201);
49
+ } catch (e) {
50
+ const msg = e instanceof Error ? e.message : String(e);
51
+ return c.json(err(msg), 500);
52
+ }
53
+ });
54
+
55
+ // DELETE /api/extensions/:id — remove extension
56
+ extensionRoutes.delete("/:id{.+}", async (c) => {
57
+ const id = c.req.param("id");
58
+ try {
59
+ await extensionService.remove(id);
60
+ return c.json(ok({ removed: id }));
61
+ } catch (e) {
62
+ const msg = e instanceof Error ? e.message : String(e);
63
+ return c.json(err(msg), 500);
64
+ }
65
+ });
66
+
67
+ // PATCH /api/extensions/:id — enable/disable
68
+ extensionRoutes.patch("/:id{.+}", async (c) => {
69
+ const id = c.req.param("id");
70
+ const body = await c.req.json<{ enabled?: boolean }>().catch(() => ({}) as { enabled?: boolean });
71
+ if (body.enabled === undefined) return c.json(err("Missing 'enabled' field"), 400);
72
+
73
+ try {
74
+ await extensionService.setEnabled(id, body.enabled);
75
+ const ext = extensionService.get(id);
76
+ return c.json(ok(ext));
77
+ } catch (e) {
78
+ const msg = e instanceof Error ? e.message : String(e);
79
+ return c.json(err(msg), 500);
80
+ }
81
+ });
@@ -4,6 +4,7 @@ import { chatRoutes } from "./chat.ts";
4
4
  import { gitRoutes } from "./git.ts";
5
5
  import { fileRoutes } from "./files.ts";
6
6
  import { sqliteRoutes } from "./sqlite.ts";
7
+ import { workspaceRoutes } from "./workspace.ts";
7
8
 
8
9
  type Env = { Variables: { projectPath: string; projectName: string } };
9
10
 
@@ -27,3 +28,4 @@ projectScopedRouter.route("/chat", chatRoutes);
27
28
  projectScopedRouter.route("/git", gitRoutes);
28
29
  projectScopedRouter.route("/files", fileRoutes);
29
30
  projectScopedRouter.route("/sqlite", sqliteRoutes);
31
+ projectScopedRouter.route("/workspace", workspaceRoutes);
@@ -252,6 +252,33 @@ settingsRoutes.post("/telegram/test", async (c) => {
252
252
  }
253
253
  });
254
254
 
255
+ // ── Auth / Password ──────────────────────────────────────────────────
256
+
257
+ /** PUT /settings/auth/password — change the access password (token) */
258
+ settingsRoutes.put("/auth/password", async (c) => {
259
+ try {
260
+ const { password, confirm } = await c.req.json<{ password: string; confirm: string }>();
261
+ if (typeof password !== "string" || !password.trim()) {
262
+ return c.json(err("Password is required"), 400);
263
+ }
264
+ if (password !== confirm) {
265
+ return c.json(err("Passwords do not match"), 400);
266
+ }
267
+ const trimmed = password.trim();
268
+ if (trimmed.length < 4) {
269
+ return c.json(err("Password must be at least 4 characters"), 400);
270
+ }
271
+
272
+ const auth = configService.get("auth");
273
+ configService.set("auth", { ...auth, token: trimmed });
274
+ configService.save();
275
+
276
+ return c.json(ok({ token: trimmed }));
277
+ } catch (e) {
278
+ return c.json(err((e as Error).message), 400);
279
+ }
280
+ });
281
+
255
282
  // ── Proxy ────────────────────────────────────────────────────────────
256
283
 
257
284
  /** GET /settings/proxy — proxy status */
@@ -0,0 +1,35 @@
1
+ import { Hono } from "hono";
2
+ import { getWorkspace, setWorkspace } from "../../services/db.service.ts";
3
+ import { ok, err } from "../../types/api.ts";
4
+
5
+ type Env = { Variables: { projectPath: string; projectName: string } };
6
+
7
+ export const workspaceRoutes = new Hono<Env>();
8
+
9
+ /** GET /workspace — load saved workspace layout */
10
+ workspaceRoutes.get("/", (c) => {
11
+ try {
12
+ const projectName = c.get("projectName");
13
+ const row = getWorkspace(projectName);
14
+ if (!row) return c.json(ok(null));
15
+ return c.json(ok({
16
+ layout: JSON.parse(row.layout_json),
17
+ updatedAt: row.updated_at,
18
+ }));
19
+ } catch (e) {
20
+ return c.json(err((e as Error).message), 500);
21
+ }
22
+ });
23
+
24
+ /** PUT /workspace — save workspace layout */
25
+ workspaceRoutes.put("/", async (c) => {
26
+ try {
27
+ const projectName = c.get("projectName");
28
+ const body = await c.req.json<{ layout: unknown }>();
29
+ if (!body.layout) return c.json(err("Missing layout"), 400);
30
+ const updatedAt = setWorkspace(projectName, JSON.stringify(body.layout));
31
+ return c.json(ok({ updatedAt }));
32
+ } catch (e) {
33
+ return c.json(err((e as Error).message), 500);
34
+ }
35
+ });
@@ -11,7 +11,7 @@ const CLEANUP_TIMEOUT_MS = 5 * 60_000; // 5min after Claude done + no FE
11
11
  const MAX_TURN_EVENTS = 10_000; // memory safety cap
12
12
  const BUFFERABLE_TYPES = new Set([
13
13
  "text", "thinking", "tool_use", "tool_result",
14
- "approval_request", "error", "done", "account_info",
14
+ "approval_request", "error", "done", "account_info", "account_retry",
15
15
  ]);
16
16
 
17
17
  type ChatWsSocket = {
@@ -218,8 +218,14 @@ async function startSessionConsumer(sessionId: string, providerId: string, conte
218
218
  continue;
219
219
  }
220
220
 
221
- // System events → transition connecting → thinking
221
+ // System events → transition connecting → thinking, forward compact events
222
222
  if (evType === "system") {
223
+ const sub = (ev as any).subtype;
224
+ if (sub === "compacting") {
225
+ broadcast(sessionId, { type: "compact_status", status: "compacting" });
226
+ } else if (sub === "compact_done") {
227
+ broadcast(sessionId, { type: "compact_status", status: "done" });
228
+ }
223
229
  if (!firstEventReceived) {
224
230
  if (heartbeat) clearInterval(heartbeat);
225
231
  setPhase(sessionId, "thinking");
@@ -228,7 +234,7 @@ async function startSessionConsumer(sessionId: string, providerId: string, conte
228
234
  }
229
235
 
230
236
  // First content event — stop heartbeat, transition phase
231
- const isMetadataEvent = evType === "account_info" || evType === "streaming_status";
237
+ const isMetadataEvent = evType === "account_info" || evType === "account_retry" || evType === "streaming_status";
232
238
  if (!firstEventReceived && !isMetadataEvent) {
233
239
  firstEventReceived = true;
234
240
  const waitMs = Date.now() - startTime;