@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
@@ -0,0 +1,175 @@
1
+ /**
2
+ * WebSocket handler for extension UI bridge.
3
+ * Routes messages between browser clients and the extension host.
4
+ */
5
+ import { contributionRegistry } from "../../services/contribution-registry.ts";
6
+ import type { ExtServerMsg, ExtClientMsg } from "../../types/extension-messages.ts";
7
+
8
+ type ExtWsSocket = {
9
+ data: { type: string };
10
+ send: (data: string) => void;
11
+ };
12
+
13
+ /** All connected extension WS clients */
14
+ const clients = new Set<ExtWsSocket>();
15
+
16
+ /** Pending request resolvers for quickpick/inputbox responses from browser */
17
+ const pendingRequests = new Map<string, (value: unknown) => void>();
18
+
19
+ // --- Public API for extension service to push UI updates ---
20
+
21
+ /** Broadcast a message to all connected extension WS clients */
22
+ export function broadcastExtMsg(msg: ExtServerMsg): void {
23
+ const data = JSON.stringify(msg);
24
+ for (const ws of clients) {
25
+ try { ws.send(data); } catch {}
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Send a request to browser and wait for response (quickpick, inputbox, notification).
31
+ * The `trackingId` is the key used to match the response.
32
+ * Returns the resolved value or undefined on timeout.
33
+ */
34
+ export function requestFromBrowser<T = unknown>(
35
+ msg: ExtServerMsg,
36
+ trackingId: string,
37
+ timeoutMs = 30_000,
38
+ ): Promise<T | undefined> {
39
+ broadcastExtMsg(msg);
40
+ return new Promise<T | undefined>((resolve) => {
41
+ const timer = setTimeout(() => {
42
+ pendingRequests.delete(trackingId);
43
+ resolve(undefined);
44
+ }, timeoutMs);
45
+ pendingRequests.set(trackingId, (value) => {
46
+ clearTimeout(timer);
47
+ resolve(value as T);
48
+ });
49
+ });
50
+ }
51
+
52
+ /** Get the number of connected extension WS clients */
53
+ export function getExtClientCount(): number {
54
+ return clients.size;
55
+ }
56
+
57
+ // --- WS lifecycle handlers ---
58
+
59
+ function handleOpen(ws: ExtWsSocket): void {
60
+ clients.add(ws);
61
+ console.log(`[ExtWS] Client connected (${clients.size} total)`);
62
+ }
63
+
64
+ async function handleMessage(ws: ExtWsSocket, raw: string | Buffer): Promise<void> {
65
+ let msg: ExtClientMsg;
66
+ try {
67
+ msg = JSON.parse(typeof raw === "string" ? raw : raw.toString()) as ExtClientMsg;
68
+ } catch {
69
+ return;
70
+ }
71
+
72
+ switch (msg.type) {
73
+ case "ready": {
74
+ // Send current contributions on connect
75
+ const contributions = contributionRegistry.getAll();
76
+ ws.send(JSON.stringify({ type: "contributions:update", contributions } satisfies ExtServerMsg));
77
+ break;
78
+ }
79
+
80
+ case "command:execute": {
81
+ try {
82
+ const { extensionService } = await import("../../services/extension.service.ts");
83
+ // Forward to extension host worker via RPC
84
+ if (extensionService["rpc"]) {
85
+ await extensionService["rpc"].sendRequest("ext:command:execute", msg.command, ...(msg.args ?? []));
86
+ }
87
+ } catch (e) {
88
+ console.error(`[ExtWS] command:execute error:`, e);
89
+ }
90
+ break;
91
+ }
92
+
93
+ case "tree:click": {
94
+ if (msg.command) {
95
+ try {
96
+ const { extensionService } = await import("../../services/extension.service.ts");
97
+ if (extensionService["rpc"]) {
98
+ await extensionService["rpc"].sendRequest("ext:command:execute", msg.command);
99
+ }
100
+ } catch (e) {
101
+ console.error(`[ExtWS] tree:click command error:`, e);
102
+ }
103
+ }
104
+ break;
105
+ }
106
+
107
+ case "quickpick:resolve": {
108
+ const resolver = pendingRequests.get(msg.requestId);
109
+ if (resolver) {
110
+ pendingRequests.delete(msg.requestId);
111
+ resolver(msg.selected);
112
+ }
113
+ break;
114
+ }
115
+
116
+ case "inputbox:resolve": {
117
+ const resolver = pendingRequests.get(msg.requestId);
118
+ if (resolver) {
119
+ pendingRequests.delete(msg.requestId);
120
+ resolver(msg.value);
121
+ }
122
+ break;
123
+ }
124
+
125
+ case "notification:action": {
126
+ const resolver = pendingRequests.get(msg.id);
127
+ if (resolver) {
128
+ pendingRequests.delete(msg.id);
129
+ resolver(msg.action);
130
+ }
131
+ break;
132
+ }
133
+
134
+ case "webview:message": {
135
+ try {
136
+ const { extensionService } = await import("../../services/extension.service.ts");
137
+ if (extensionService["rpc"]) {
138
+ await extensionService["rpc"].sendRequest("ext:webview:message", msg.panelId, msg.message);
139
+ }
140
+ } catch (e) {
141
+ console.error(`[ExtWS] webview:message error:`, e);
142
+ }
143
+ break;
144
+ }
145
+
146
+ case "tree:expand": {
147
+ try {
148
+ const { extensionService } = await import("../../services/extension.service.ts");
149
+ if (extensionService["rpc"]) {
150
+ const result = await extensionService["rpc"].sendRequest<{ ok: boolean; items?: unknown[] }>(
151
+ "ext:tree:expand", msg.viewId, msg.itemId,
152
+ );
153
+ if (result?.ok && result.items) {
154
+ // Send children back to the requesting client (parentId distinguishes child updates from root updates)
155
+ ws.send(JSON.stringify({ type: "tree:update", viewId: msg.viewId, items: result.items as import("../../types/extension-messages.ts").TreeItemMsg[], parentId: msg.itemId } satisfies ExtServerMsg));
156
+ }
157
+ }
158
+ } catch (e) {
159
+ console.error(`[ExtWS] tree:expand error:`, e);
160
+ }
161
+ break;
162
+ }
163
+ }
164
+ }
165
+
166
+ function handleClose(ws: ExtWsSocket): void {
167
+ clients.delete(ws);
168
+ console.log(`[ExtWS] Client disconnected (${clients.size} remaining)`);
169
+ }
170
+
171
+ export const extensionWebSocket = {
172
+ open: handleOpen,
173
+ message: handleMessage,
174
+ close: handleClose,
175
+ };
@@ -9,6 +9,8 @@ const MAX_RETRY_CONFIG_KEY = "account_max_retry";
9
9
  const BACKOFF_BASE_MS = 1_000;
10
10
  const BACKOFF_MAX_MS = 30 * 60_000;
11
11
  const AUTH_BACKOFF_BASE_MS = 5 * 60_000; // 5min base for auth errors (longer than rate limits)
12
+ /** Skip accounts whose 5-hour utilization >= this threshold (proactive avoidance) */
13
+ const FIVE_HOUR_SKIP_THRESHOLD = 0.95;
12
14
 
13
15
  class AccountSelectorService {
14
16
  private cursor = 0;
@@ -74,18 +76,25 @@ class AccountSelectorService {
74
76
  return null;
75
77
  }
76
78
 
79
+ // Proactive: skip accounts whose 5-hour utilization >= 95%
80
+ const usable = active.filter((a) => {
81
+ const snap = getLatestSnapshotForAccount(a.id);
82
+ return !snap || (snap.five_hour_util ?? 0) < FIVE_HOUR_SKIP_THRESHOLD;
83
+ });
84
+ const candidates = usable.length > 0 ? usable : active; // fallback to all if every account is near limit
85
+
77
86
  let pickedId: string;
78
87
  const strategy = this.getStrategy();
79
88
  if (strategy === "lowest-usage") {
80
- pickedId = this.pickLowestUsage(active);
89
+ pickedId = this.pickLowestUsage(candidates);
81
90
  } else if (strategy === "fill-first") {
82
- const sorted = [...active].sort((a, b) => b.priority - a.priority || a.createdAt - b.createdAt);
91
+ const sorted = [...candidates].sort((a, b) => b.priority - a.priority || a.createdAt - b.createdAt);
83
92
  pickedId = sorted[0]!.id;
84
93
  } else {
85
94
  // Round-robin
86
- this.cursor = this.cursor % active.length;
87
- pickedId = active[this.cursor]!.id;
88
- this.cursor = (this.cursor + 1) % active.length;
95
+ this.cursor = this.cursor % candidates.length;
96
+ pickedId = candidates[this.cursor]!.id;
97
+ this.cursor = (this.cursor + 1) % candidates.length;
89
98
  }
90
99
  this._lastPickedId = pickedId;
91
100
  const result = accountService.getWithTokens(pickedId);
@@ -139,7 +139,7 @@ class AccountService {
139
139
  await this.refreshAccessToken(id, false);
140
140
  return this.getWithTokens(id);
141
141
  } catch (e) {
142
- console.error(`[accounts] Pre-flight refresh failed for ${id}:`, e);
142
+ console.error(`[accounts] Pre-flight refresh failed for ${id}: ${(e as Error).message ?? e}`);
143
143
  return null;
144
144
  }
145
145
  }
@@ -623,13 +623,13 @@ class AccountService {
623
623
  if (!row.id || !row.access_token) continue;
624
624
  const hasRefresh = !!row.refresh_token && row.refresh_token !== "";
625
625
 
626
- // Duplicate handling: if existing account has no refresh token but import does, upgrade it
626
+ // Duplicate handling: update existing account tokens from import
627
627
  const existingById = getAccountById(row.id);
628
628
  const existingByEmail = row.email ? this.list().find((a) => a.email === row.email) : null;
629
629
  const existing = existingById ?? (existingByEmail ? getAccountById(existingByEmail.id) : null);
630
630
  if (existing) {
631
- if (hasRefresh && !this.hasRefreshToken(existing.id)) {
632
- // Upgrade: import has refresh token, existing doesn't update tokens
631
+ if (hasRefresh) {
632
+ // Always update tokens when import has refresh token (handles expired/invalid tokens too)
633
633
  let accessToken = row.access_token;
634
634
  if (!looksEncrypted(accessToken)) accessToken = encrypt(accessToken);
635
635
  const refreshToken = looksEncrypted(row.refresh_token) ? row.refresh_token : encrypt(row.refresh_token);
@@ -641,9 +641,9 @@ class AccountService {
641
641
  });
642
642
  imported++;
643
643
  fullTransferIds.push(existing.id);
644
- console.log(`[accounts] Upgraded ${row.email ?? existing.id} with refresh token from import`);
644
+ console.log(`[accounts] Updated ${row.email ?? existing.id} tokens from import`);
645
645
  }
646
- continue; // skip if existing already has refresh token or import doesn't
646
+ continue; // skip if import doesn't have refresh token
647
647
  }
648
648
 
649
649
  // New account — insert
@@ -709,7 +709,7 @@ class AccountService {
709
709
  try {
710
710
  await this.refreshAccessToken(acc.id, false);
711
711
  } catch (e) {
712
- console.error(`[accounts] Auto-refresh failed for ${acc.id}:`, e);
712
+ console.error(`[accounts] Auto-refresh failed for ${acc.id}: ${(e as Error).message ?? e}`);
713
713
  }
714
714
  }
715
715
  };
@@ -273,28 +273,28 @@ export function getUsageForAccount(accountId: string): ClaudeUsage {
273
273
  return row ? snapshotToUsage(row) : {};
274
274
  }
275
275
 
276
- /** Get usage for all accounts (excludes expired temporary accounts) */
276
+ /** Get usage for all accounts */
277
277
  export function getAllAccountUsages(): AccountUsageEntry[] {
278
- const nowS = Math.floor(Date.now() / 1000);
279
- const accounts = accountService.list().filter(acc => {
280
- // Exclude expired accounts without refresh token (temporary/invalid)
281
- if (!accountService.hasRefreshToken(acc.id) && acc.expiresAt && acc.expiresAt < nowS) return false;
282
- return true;
283
- });
278
+ const accounts = accountService.list();
284
279
  const snapshots = getAllLatestSnapshots();
285
280
  const snapshotMap = new Map(snapshots.map(s => [s.account_id, s]));
286
- return accounts.map(acc => {
281
+ const nowS = Math.floor(Date.now() / 1000);
282
+ const result: AccountUsageEntry[] = [];
283
+ for (const acc of accounts) {
287
284
  const withTokens = accountService.getWithTokens(acc.id);
285
+ // Skip expired accounts without refresh token (temporary/disposable)
286
+ if (acc.expiresAt && acc.expiresAt < nowS && !withTokens?.refreshToken) continue;
288
287
  const isOAuth = withTokens?.accessToken.startsWith("sk-ant-oat") ?? false;
289
288
  const row = snapshotMap.get(acc.id);
290
- return {
289
+ result.push({
291
290
  accountId: acc.id,
292
291
  accountLabel: acc.label,
293
292
  accountStatus: acc.status,
294
293
  isOAuth,
295
294
  usage: row ? snapshotToUsage(row) : {},
296
- };
297
- });
295
+ });
296
+ }
297
+ return result;
298
298
  }
299
299
 
300
300
  /** Get cached usage for active account (used by chat header) */
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Cloud WebSocket client — persistent connection from supervisor to PPM Cloud.
3
+ * Auto-reconnects with exponential backoff + jitter. Queues messages when disconnected.
4
+ */
5
+ import { appendFileSync } from "node:fs";
6
+ import { resolve } from "node:path";
7
+ import { homedir } from "node:os";
8
+
9
+ // ─── Types (must match Cloud's ws-types.ts) ─────────
10
+ interface WsMessage {
11
+ type: string;
12
+ id?: string;
13
+ timestamp: string;
14
+ }
15
+
16
+ interface HeartbeatMsg extends WsMessage {
17
+ type: "heartbeat";
18
+ tunnelUrl: string | null;
19
+ state: string;
20
+ appVersion: string;
21
+ availableVersion: string | null;
22
+ serverPid: number | null;
23
+ uptime: number;
24
+ deviceName?: string;
25
+ }
26
+
27
+ interface StateChangeMsg extends WsMessage {
28
+ type: "state_change";
29
+ from: string;
30
+ to: string;
31
+ reason: string;
32
+ }
33
+
34
+ interface CommandAckMsg extends WsMessage {
35
+ type: "command_ack";
36
+ id: string;
37
+ }
38
+
39
+ interface CommandResultMsg extends WsMessage {
40
+ type: "command_result";
41
+ id: string;
42
+ success: boolean;
43
+ error?: string;
44
+ data?: Record<string, unknown>;
45
+ }
46
+
47
+ type OutboundMsg = HeartbeatMsg | StateChangeMsg | CommandAckMsg | CommandResultMsg;
48
+
49
+ interface CommandMsg extends WsMessage {
50
+ type: "command";
51
+ id: string;
52
+ action: string;
53
+ params?: Record<string, unknown>;
54
+ }
55
+
56
+ type CommandHandler = (cmd: CommandMsg) => void;
57
+
58
+ // ─── Constants ──────────────────────────────────────
59
+ const BACKOFF_STEPS = [1000, 2000, 4000, 8000, 15000, 30000, 60000];
60
+ const MAX_QUEUE_SIZE = 50;
61
+ const HEARTBEAT_INTERVAL_MS = 60_000; // 60s via WS
62
+
63
+ // ─── State ──────────────────────────────────────────
64
+ let ws: WebSocket | null = null;
65
+ let connected = false;
66
+ let reconnecting = false;
67
+ let reconnectAttempt = 0;
68
+ let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
69
+ let heartbeatTimer: ReturnType<typeof setInterval> | null = null;
70
+ let commandHandler: CommandHandler | null = null;
71
+ let outboundQueue: OutboundMsg[] = [];
72
+ let wsUrl = "";
73
+ let shouldConnect = false;
74
+
75
+ // Credentials for first-message auth
76
+ let deviceId = "";
77
+ let secretKey = "";
78
+
79
+ // For heartbeat payload
80
+ let getHeartbeatData: (() => HeartbeatMsg) | null = null;
81
+
82
+ // ─── Public API ─────────────────────────────────────
83
+
84
+ export function connect(opts: {
85
+ cloudUrl: string;
86
+ deviceId: string;
87
+ secretKey: string;
88
+ heartbeatFn: () => HeartbeatMsg;
89
+ }): void {
90
+ // No secret_key in URL — auth via first message after connect
91
+ wsUrl = `${opts.cloudUrl.replace(/^http/, "ws")}/ws/device`;
92
+ deviceId = opts.deviceId;
93
+ secretKey = opts.secretKey;
94
+ getHeartbeatData = opts.heartbeatFn;
95
+ shouldConnect = true;
96
+ reconnectAttempt = 0;
97
+ doConnect();
98
+ }
99
+
100
+ export function disconnect(): void {
101
+ shouldConnect = false;
102
+ if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
103
+ if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; }
104
+ if (ws) {
105
+ try { ws.close(1000, "shutdown"); } catch {}
106
+ ws = null;
107
+ }
108
+ connected = false;
109
+ outboundQueue = [];
110
+ }
111
+
112
+ export function send(msg: OutboundMsg): void {
113
+ if (connected && ws?.readyState === WebSocket.OPEN) {
114
+ ws.send(JSON.stringify(msg));
115
+ } else {
116
+ outboundQueue.push(msg);
117
+ if (outboundQueue.length > MAX_QUEUE_SIZE) outboundQueue.shift();
118
+ }
119
+ }
120
+
121
+ export function onCommand(handler: CommandHandler): void {
122
+ commandHandler = handler;
123
+ }
124
+
125
+ export function isConnected(): boolean {
126
+ return connected;
127
+ }
128
+
129
+ // ─── Internal ───────────────────────────────────────
130
+
131
+ function doConnect(): void {
132
+ if (!shouldConnect || reconnecting) return;
133
+ reconnecting = true;
134
+
135
+ // Capture local ref — if a reconnect replaces `ws` before this socket's
136
+ // handlers fire, stale handlers must not reset module-level state.
137
+ let sock: WebSocket;
138
+ try {
139
+ sock = new WebSocket(wsUrl);
140
+ ws = sock;
141
+ } catch {
142
+ reconnecting = false;
143
+ scheduleReconnect("constructor");
144
+ return;
145
+ }
146
+
147
+ sock.onopen = () => {
148
+ if (ws !== sock) return; // stale — newer connection replaced us
149
+ reconnecting = false;
150
+ reconnectAttempt = 0;
151
+ log("INFO", "Cloud WS connected, sending auth");
152
+
153
+ // Send auth as first message — server must process this before any other msg
154
+ sock.send(JSON.stringify({
155
+ type: "auth",
156
+ deviceId,
157
+ secretKey,
158
+ timestamp: new Date().toISOString(),
159
+ version: 1,
160
+ }));
161
+
162
+ // Delay setting connected + sending heartbeat to let server process auth.
163
+ // Server's authenticateDevice() is async (DB lookup), so messages sent
164
+ // immediately after auth arrive before authenticated=true → 4002 reject.
165
+ setTimeout(() => {
166
+ if (ws !== sock) return; // replaced during delay
167
+ connected = true;
168
+
169
+ // Flush queued messages
170
+ while (outboundQueue.length > 0 && connected) {
171
+ const msg = outboundQueue.shift()!;
172
+ sock.send(JSON.stringify(msg));
173
+ }
174
+
175
+ // Send immediate heartbeat
176
+ if (getHeartbeatData) send(getHeartbeatData());
177
+
178
+ // Start periodic heartbeat
179
+ if (heartbeatTimer) clearInterval(heartbeatTimer);
180
+ heartbeatTimer = setInterval(() => {
181
+ if (getHeartbeatData && connected) send(getHeartbeatData());
182
+ }, HEARTBEAT_INTERVAL_MS);
183
+ }, 500); // 500ms for DB auth round-trip
184
+ };
185
+
186
+ sock.onmessage = (event) => {
187
+ try {
188
+ const msg = JSON.parse(String(event.data)) as CommandMsg;
189
+ if (msg.type === "command" && commandHandler) {
190
+ commandHandler(msg);
191
+ }
192
+ } catch {} // ignore malformed
193
+ };
194
+
195
+ sock.onclose = (event) => {
196
+ if (ws !== sock) return; // stale — ignore close from replaced connection
197
+ log("WARN", `Cloud WS closed: code=${event.code} reason=${event.reason || ""}`);
198
+ connected = false;
199
+ reconnecting = false;
200
+ ws = null;
201
+ if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; }
202
+ if (shouldConnect) scheduleReconnect("onclose");
203
+ };
204
+
205
+ sock.onerror = (event) => {
206
+ log("ERROR", `Cloud WS error: ${String(event)}`);
207
+ };
208
+ }
209
+
210
+ function scheduleReconnect(source = "unknown"): void {
211
+ if (!shouldConnect || reconnectTimer) return;
212
+ const base = BACKOFF_STEPS[Math.min(reconnectAttempt, BACKOFF_STEPS.length - 1)]!;
213
+ // Add ±30% jitter to prevent thundering herd after Cloud deploy
214
+ const jitter = base * (0.7 + Math.random() * 0.6);
215
+ const delay = Math.round(jitter);
216
+ reconnectAttempt++;
217
+ log("WARN", `Cloud WS reconnect in ${delay}ms (attempt #${reconnectAttempt}) src=${source}`);
218
+ reconnectTimer = setTimeout(() => {
219
+ reconnectTimer = null;
220
+ doConnect();
221
+ }, delay);
222
+ }
223
+
224
+ function log(level: string, msg: string): void {
225
+ const ts = new Date().toISOString();
226
+ const logFile = resolve(process.env.PPM_HOME || resolve(homedir(), ".ppm"), "ppm.log");
227
+ try { appendFileSync(logFile, `[${ts}] [${level}] [cloud-ws] ${msg}\n`); } catch {}
228
+ }
@@ -354,6 +354,7 @@ export async function sendHeartbeat(tunnelUrl: string): Promise<boolean> {
354
354
  secret_key: device.secret_key,
355
355
  tunnel_url: tunnelUrl,
356
356
  status: "online",
357
+ name: device.name,
357
358
  }),
358
359
  });
359
360
  return res.ok;
@@ -0,0 +1,110 @@
1
+ import type { ExtensionContributes, ContributedCommand, ContributedView, ContributedMenu } from "../types/extension.ts";
2
+
3
+ /**
4
+ * In-memory registry of all contribution points from enabled extensions.
5
+ * Populated when extensions activate, cleared when they deactivate.
6
+ */
7
+ class ContributionRegistry {
8
+ private commands = new Map<string, ContributedCommand & { extId: string }>();
9
+ private views = new Map<string, Map<string, ContributedView & { extId: string }>>();
10
+ private configs = new Map<string, Record<string, unknown>>();
11
+ private menus = new Map<string, Array<ContributedMenu & { extId: string }>>();
12
+
13
+ register(extId: string, contributes: ExtensionContributes): void {
14
+ if (contributes.commands) {
15
+ for (const cmd of contributes.commands) {
16
+ this.commands.set(cmd.command, { ...cmd, extId });
17
+ }
18
+ }
19
+ if (contributes.views) {
20
+ for (const [location, views] of Object.entries(contributes.views)) {
21
+ if (!this.views.has(location)) this.views.set(location, new Map());
22
+ const locationMap = this.views.get(location)!;
23
+ for (const view of views) {
24
+ locationMap.set(view.id, { ...view, extId });
25
+ }
26
+ }
27
+ }
28
+ if (contributes.configuration?.properties) {
29
+ this.configs.set(extId, contributes.configuration.properties);
30
+ }
31
+ if (contributes.menus) {
32
+ for (const [location, items] of Object.entries(contributes.menus)) {
33
+ if (!this.menus.has(location)) this.menus.set(location, []);
34
+ const list = this.menus.get(location)!;
35
+ for (const item of items) {
36
+ list.push({ ...item, extId });
37
+ }
38
+ }
39
+ }
40
+ }
41
+
42
+ unregister(extId: string): void {
43
+ for (const [key, cmd] of this.commands) {
44
+ if (cmd.extId === extId) this.commands.delete(key);
45
+ }
46
+ for (const [, locationMap] of this.views) {
47
+ for (const [key, view] of locationMap) {
48
+ if (view.extId === extId) locationMap.delete(key);
49
+ }
50
+ }
51
+ for (const [location, items] of this.menus) {
52
+ this.menus.set(location, items.filter((m) => m.extId !== extId));
53
+ }
54
+ this.configs.delete(extId);
55
+ }
56
+
57
+ getCommands(): Array<ContributedCommand & { extId: string }> {
58
+ return [...this.commands.values()];
59
+ }
60
+
61
+ getViews(location?: string): Array<ContributedView & { extId: string }> {
62
+ if (location) {
63
+ return [...(this.views.get(location)?.values() ?? [])];
64
+ }
65
+ const all: Array<ContributedView & { extId: string }> = [];
66
+ for (const locationMap of this.views.values()) {
67
+ all.push(...locationMap.values());
68
+ }
69
+ return all;
70
+ }
71
+
72
+ getViewLocations(): string[] {
73
+ return [...this.views.keys()];
74
+ }
75
+
76
+ getConfiguration(extId?: string): Record<string, Record<string, unknown>> {
77
+ if (extId) {
78
+ const cfg = this.configs.get(extId);
79
+ return cfg ? { [extId]: cfg } : {};
80
+ }
81
+ return Object.fromEntries(this.configs);
82
+ }
83
+
84
+ /** Get all contributions as a single object (for API responses) */
85
+ getAll() {
86
+ const viewsByLocation: Record<string, Array<ContributedView & { extId: string }>> = {};
87
+ for (const location of this.views.keys()) {
88
+ viewsByLocation[location] = this.getViews(location);
89
+ }
90
+ const menusByLocation: Record<string, Array<ContributedMenu & { extId: string }>> = {};
91
+ for (const [location, items] of this.menus) {
92
+ menusByLocation[location] = items;
93
+ }
94
+ return {
95
+ commands: this.getCommands(),
96
+ views: viewsByLocation,
97
+ menus: menusByLocation,
98
+ configuration: this.getConfiguration(),
99
+ };
100
+ }
101
+
102
+ clear(): void {
103
+ this.commands.clear();
104
+ this.views.clear();
105
+ this.configs.clear();
106
+ this.menus.clear();
107
+ }
108
+ }
109
+
110
+ export const contributionRegistry = new ContributionRegistry();