@hienlh/ppm 0.9.85 → 0.9.87

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 (243) hide show
  1. package/260415-0932-git-graph-stash-rebase-conflicts/reports/code-reviewer-260415-1020-stash-rebase-conflicts.md +288 -0
  2. package/260415-0932-git-graph-stash-rebase-conflicts/reports/tester-260415-1020-build-check.md +117 -0
  3. package/260415-1150-ext-silent-failure-debugging/reports/code-reviewer-260415-1159-ext-error-reporting-review.md +205 -0
  4. package/260415-1150-ext-silent-failure-debugging/reports/docs-manager-260415-1206-ext-error-reporting.md +99 -0
  5. package/260415-1150-ext-silent-failure-debugging/reports/tester-260415-1159-extension-error-reporting.md +174 -0
  6. package/CHANGELOG.md +24 -0
  7. package/dist/web/assets/{_basePickBy-D-bUmjma.js → _basePickBy-Bj0dI1ei.js} +1 -1
  8. package/dist/web/assets/{_baseUniq-BnXXIfRB.js → _baseUniq-CyzdZeQH.js} +1 -1
  9. package/dist/web/assets/ai-settings-section-Bo9lCaTd.js +1 -0
  10. package/dist/web/assets/{api-settings-Qi2xRiHa.js → api-settings-CUxg9RE5.js} +1 -1
  11. package/dist/web/assets/{arc-DB9vXGzd.js → arc-CxgHJ7Z4.js} +1 -1
  12. package/dist/web/assets/architecture-PBZL5I3N-DDFO_NKq.js +1 -0
  13. package/dist/web/assets/{architectureDiagram-2XIMDMQ5-BBV25747.js → architectureDiagram-2XIMDMQ5-D16OotsC.js} +1 -1
  14. package/dist/web/assets/arrow-up-I9-21gkR.js +1 -0
  15. package/dist/web/assets/{blockDiagram-WCTKOSBZ-BOTnY2Lq.js → blockDiagram-WCTKOSBZ-Ct57Wtfk.js} +1 -1
  16. package/dist/web/assets/{c4Diagram-IC4MRINW-D7QAUdHD.js → c4Diagram-IC4MRINW-BIymcNsg.js} +1 -1
  17. package/dist/web/assets/channel-wumTB1if.js +1 -0
  18. package/dist/web/assets/chat-tab-R4gKsnxD.js +10 -0
  19. package/dist/web/assets/chevron-right-DY_wImxB.js +1 -0
  20. package/dist/web/assets/{chunk-4BX2VUAB-BnOVw77D.js → chunk-4BX2VUAB-CENmY7Kw.js} +1 -1
  21. package/dist/web/assets/{chunk-55IACEB6-BftA8DxR.js → chunk-55IACEB6-DhZGI1l3.js} +1 -1
  22. package/dist/web/assets/{chunk-7E7YKBS2-B0vnP8v3.js → chunk-7E7YKBS2-DZcnC7Ow.js} +1 -1
  23. package/dist/web/assets/{chunk-7R4GIKGN-Czlaj26D.js → chunk-7R4GIKGN-y8bfHEy-.js} +2 -2
  24. package/dist/web/assets/{chunk-C72U2L5F-DpEbDtMo.js → chunk-C72U2L5F-BHPkfQj2.js} +1 -1
  25. package/dist/web/assets/{chunk-EGIJ26TM-BWXe6lkx.js → chunk-EGIJ26TM-nant2LXl.js} +1 -1
  26. package/dist/web/assets/{chunk-FMBD7UC4-DspqhPfk.js → chunk-FMBD7UC4-Bog4cpN-.js} +1 -1
  27. package/dist/web/assets/{chunk-GEFDOKGD-D6HHRbYk.js → chunk-GEFDOKGD-86LFbsAC.js} +1 -1
  28. package/dist/web/assets/chunk-GLR3WWYH-Re-5eSlQ.js +2 -0
  29. package/dist/web/assets/chunk-HHEYEP7N-C45i5G_3.js +1 -0
  30. package/dist/web/assets/{chunk-JSJVCQXG-BC8wnMwf.js → chunk-JSJVCQXG-23eG9mgt.js} +1 -1
  31. package/dist/web/assets/{chunk-KX2RTZJC-D3VDtyvX.js → chunk-KX2RTZJC-CHj8TnTB.js} +1 -1
  32. package/dist/web/assets/{chunk-KYZI473N-Z-NBw_HS.js → chunk-KYZI473N-gqRLpJ4w.js} +1 -1
  33. package/dist/web/assets/{chunk-L3YUKLVL--RGkEh__.js → chunk-L3YUKLVL-DnSMmNFC.js} +1 -1
  34. package/dist/web/assets/{chunk-MX3YWQON-2B76t_Kx.js → chunk-MX3YWQON-B6g1ZH9X.js} +1 -1
  35. package/dist/web/assets/{chunk-NQ4KR5QH-BekY3tEi.js → chunk-NQ4KR5QH-DX32345Y.js} +1 -1
  36. package/dist/web/assets/{chunk-O4XLMI2P-2CJLfx_1.js → chunk-O4XLMI2P-Vp_V4P-b.js} +1 -1
  37. package/dist/web/assets/{chunk-OZEHJAEY-sug_L09P.js → chunk-OZEHJAEY-lKq2SWjA.js} +1 -1
  38. package/dist/web/assets/{chunk-PQ6SQG4A-_fwPRLQy.js → chunk-PQ6SQG4A-Bik13fTV.js} +1 -1
  39. package/dist/web/assets/{chunk-PU5JKC2W-BUaTFJVQ.js → chunk-PU5JKC2W-DD95Rx35.js} +1 -1
  40. package/dist/web/assets/chunk-QZHKN3VN-N3VXx1VH.js +1 -0
  41. package/dist/web/assets/{chunk-R5LLSJPH-C37xW0vj.js → chunk-R5LLSJPH-dRhXRnrb.js} +1 -1
  42. package/dist/web/assets/{chunk-WL4C6EOR-CCkt_MT6.js → chunk-WL4C6EOR-B1iIvLOG.js} +1 -1
  43. package/dist/web/assets/{chunk-XIRO2GV7-Dz2LBq7Y.js → chunk-XIRO2GV7-DZBoNl1_.js} +1 -1
  44. package/dist/web/assets/{chunk-XPW4576I-DenTbBuj.js → chunk-XPW4576I-CgLyyW03.js} +1 -1
  45. package/dist/web/assets/{chunk-XZSTWKYB-Dbp1nUSQ.js → chunk-XZSTWKYB-DjV8xl5A.js} +1 -1
  46. package/dist/web/assets/{chunk-YBOYWFTD-3OTKowjE.js → chunk-YBOYWFTD-D_ILLe6_.js} +1 -1
  47. package/dist/web/assets/classDiagram-VBA2DB6C-mr-Cb1me.js +1 -0
  48. package/dist/web/assets/classDiagram-v2-RAHNMMFH-BKe8_uda.js +1 -0
  49. package/dist/web/assets/clone--z5KLAuR.js +1 -0
  50. package/dist/web/assets/code-editor-Br0vzTOy.js +8 -0
  51. package/dist/web/assets/columns-2-IeETSfON.js +1 -0
  52. package/dist/web/assets/conflict-editor-BPgCjnNz.js +19 -0
  53. package/dist/web/assets/{cose-bilkent-S5V4N54A-MbmGZnt0.js → cose-bilkent-S5V4N54A-BGNPFv3x.js} +1 -1
  54. package/dist/web/assets/{csv-preview-uZ_7b8I7.js → csv-preview-BZRICDP0.js} +2 -2
  55. package/dist/web/assets/{dagre-CPhI6v-K.js → dagre-CkhlMHnx.js} +1 -1
  56. package/dist/web/assets/{dagre-KLK3FWXG-CmSE-oNj.js → dagre-KLK3FWXG-Cnp996VG.js} +1 -1
  57. package/dist/web/assets/database-CgTomMxt.js +1 -0
  58. package/dist/web/assets/{database-viewer-5xljX0JI.js → database-viewer-DaUoQ-oR.js} +2 -2
  59. package/dist/web/assets/{diagram-E7M64L7V-B5XG3ZT7.js → diagram-E7M64L7V-BZF0tSOr.js} +1 -1
  60. package/dist/web/assets/{diagram-IFDJBPK2-BsP248aX.js → diagram-IFDJBPK2-nUcO8sN8.js} +1 -1
  61. package/dist/web/assets/{diagram-P4PSJMXO-Cna3408N.js → diagram-P4PSJMXO-CW0eCkwC.js} +1 -1
  62. package/dist/web/assets/diff-viewer-BzvK3gAE.js +4 -0
  63. package/dist/web/assets/dist-CM0oD8tQ.js +1 -0
  64. package/dist/web/assets/{erDiagram-INFDFZHY-B7SgktiR.js → erDiagram-INFDFZHY-DSkriYZ9.js} +1 -1
  65. package/dist/web/assets/extension-webview-CGepEw-b.js +3 -0
  66. package/dist/web/assets/{flowDiagram-PKNHOUZH-FOYZZ1OB.js → flowDiagram-PKNHOUZH-CFYAfZBx.js} +1 -1
  67. package/dist/web/assets/{ganttDiagram-A5KZAMGK-CnHVYh9v.js → ganttDiagram-A5KZAMGK-KSn4XAU4.js} +1 -1
  68. package/dist/web/assets/gitGraph-HDMCJU4V-OkvBPi6H.js +1 -0
  69. package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-0G9XxZay.js → gitGraphDiagram-K3NZZRJ6-BMgjjVys.js} +1 -1
  70. package/dist/web/assets/{graphlib-CNiBwlg_.js → graphlib-BWe1iK_s.js} +1 -1
  71. package/dist/web/assets/index-CKsEzQ4f.js +26 -0
  72. package/dist/web/assets/index-Chf0otez.css +2 -0
  73. package/dist/web/assets/info-3K5VOQVL-BDU2_bYD.js +1 -0
  74. package/dist/web/assets/infoDiagram-LFFYTUFH-Diq4Cyc3.js +2 -0
  75. package/dist/web/assets/input-BHj0veau.js +45 -0
  76. package/dist/web/assets/{isEmpty-CcCb5n2-.js → isEmpty-BfLnxq-B.js} +1 -1
  77. package/dist/web/assets/{ishikawaDiagram-PHBUUO56-D4QCzh5J.js → ishikawaDiagram-PHBUUO56-CiVEvp8o.js} +1 -1
  78. package/dist/web/assets/{journeyDiagram-4ABVD52K-CnHYNfKW.js → journeyDiagram-4ABVD52K-CG_v5Aho.js} +1 -1
  79. package/dist/web/assets/jsx-runtime-BRW_vwa9.js +1 -0
  80. package/dist/web/assets/{kanban-definition-K7BYSVSG-Bh_g3EVu.js → kanban-definition-K7BYSVSG-miB0-_Zq.js} +1 -1
  81. package/dist/web/assets/keybindings-store-D5zgHod8.js +1 -0
  82. package/dist/web/assets/{line-6d3eBADm.js → line-CSuSrJ9J.js} +1 -1
  83. package/dist/web/assets/{linear-cA_2lQy7.js → linear-DFN_MPsw.js} +1 -1
  84. package/dist/web/assets/{markdown-renderer-CZ07F7T6.js → markdown-renderer-DSYnGywb.js} +6 -6
  85. package/dist/web/assets/{mermaid-parser.core-C3kd7JXM.js → mermaid-parser.core-CFdP1Z5_.js} +2 -2
  86. package/dist/web/assets/{mindmap-definition-YRQLILUH-CYiUwhr_.js → mindmap-definition-YRQLILUH-pYPWwASE.js} +1 -1
  87. package/dist/web/assets/{ordinal-XHK5vIzZ.js → ordinal-DpFn432U.js} +1 -1
  88. package/dist/web/assets/packet-RMMSAZCW-BwpIpYB3.js +1 -0
  89. package/dist/web/assets/pie-UPGHQEXC-BPgAfmes.js +1 -0
  90. package/dist/web/assets/{pieDiagram-SKSYHLDU-D0S7jeZA.js → pieDiagram-SKSYHLDU-Dovdlvhu.js} +1 -1
  91. package/dist/web/assets/plus-DQGIb4mQ.js +1 -0
  92. package/dist/web/assets/port-forwarding-tab-vmqDKmk2.js +1 -0
  93. package/dist/web/assets/{postgres-viewer-RldlAO_m.js → postgres-viewer-0lIAosrr.js} +3 -3
  94. package/dist/web/assets/{quadrantDiagram-337W2JSQ-0hNP63hW.js → quadrantDiagram-337W2JSQ-TXe6cU_F.js} +1 -1
  95. package/dist/web/assets/radar-KQ55EAFF-TqxBkWx-.js +1 -0
  96. package/dist/web/assets/refresh-cw-Clk8fdUD.js +1 -0
  97. package/dist/web/assets/{requirementDiagram-Z7DCOOCP-BVnmqFbL.js → requirementDiagram-Z7DCOOCP-CuiiuGS9.js} +1 -1
  98. package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-DVkYdCJb.js → sankeyDiagram-WA2Y5GQK-BbRmhv0t.js} +1 -1
  99. package/dist/web/assets/scroll-area-BpXCNme3.js +1 -0
  100. package/dist/web/assets/{sequenceDiagram-2WXFIKYE-B80s7sOg.js → sequenceDiagram-2WXFIKYE-B2D8IQDb.js} +1 -1
  101. package/dist/web/assets/settings-tab-CMnv1fce.js +1 -0
  102. package/dist/web/assets/{sql-query-editor-CjZ7Z6XL.js → sql-query-editor-Bc2hAwqT.js} +1 -1
  103. package/dist/web/assets/sqlite-viewer-B60MS2Dy.js +1 -0
  104. package/dist/web/assets/square-vBdqj0bF.js +1 -0
  105. package/dist/web/assets/{stateDiagram-RAJIS63D-BPLXgXRR.js → stateDiagram-RAJIS63D-ylr4HxPu.js} +1 -1
  106. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-D6zvxf3M.js +1 -0
  107. package/dist/web/assets/table-Bi27fEaN.js +1 -0
  108. package/dist/web/assets/{terminal-tab-DjzD8GLn.js → terminal-tab-CCJoLstH.js} +2 -2
  109. package/dist/web/assets/text-wrap-D_OmSzhp.js +1 -0
  110. package/dist/web/assets/{timeline-definition-YZTLITO2-fa_51u1X.js → timeline-definition-YZTLITO2-pMv1grvM.js} +1 -1
  111. package/dist/web/assets/trash-2-CNuB-htI.js +1 -0
  112. package/dist/web/assets/treemap-KZPCXAKY-Kck06FKU.js +1 -0
  113. package/dist/web/assets/{use-monaco-theme-D9XFxQuU.js → use-monaco-theme-BJK48EmK.js} +1 -1
  114. package/dist/web/assets/{vennDiagram-LZ73GAT5-kX4jJn6W.js → vennDiagram-LZ73GAT5-C-rkIUbo.js} +1 -1
  115. package/dist/web/assets/x-Dw3TjeY_.js +1 -0
  116. package/dist/web/assets/{xychartDiagram-JWTSCODW-Bzm5lZBs.js → xychartDiagram-JWTSCODW-CtpjAakO.js} +1 -1
  117. package/dist/web/index.html +18 -22
  118. package/dist/web/sw.js +1 -1
  119. package/docs/codebase-summary.md +169 -13
  120. package/docs/extension-development-guide.md +98 -1
  121. package/docs/journals/260414-1400-ext-git-graph-port-complete.md +147 -0
  122. package/docs/journals/260414-1452-git-graph-faithful-port.md +144 -0
  123. package/docs/journals/260414-1810-git-graph-ui-improvements-complete.md +261 -0
  124. package/docs/journals/260414-2001-bundled-extensions.md +219 -0
  125. package/docs/project-changelog.md +123 -21
  126. package/docs/project-roadmap.md +4 -2
  127. package/docs/system-architecture.md +77 -6
  128. package/package.json +1 -1
  129. package/packages/ext-git-graph/package.json +30 -0
  130. package/packages/ext-git-graph/src/extension-integration.test.ts +230 -0
  131. package/packages/ext-git-graph/src/extension-parsers.test.ts +193 -0
  132. package/packages/ext-git-graph/src/extension.ts +921 -0
  133. package/packages/ext-git-graph/src/git-log-parser.test.ts +271 -0
  134. package/packages/ext-git-graph/src/git-log-parser.ts +38 -0
  135. package/packages/ext-git-graph/src/types.ts +192 -0
  136. package/packages/ext-git-graph/src/webview-html.test.ts +142 -0
  137. package/packages/ext-git-graph/src/webview-html.ts +2417 -0
  138. package/packages/vscode-compat/src/index.ts +4 -0
  139. package/packages/vscode-compat/src/process.ts +25 -0
  140. package/packages/vscode-compat/src/window.ts +10 -0
  141. package/src/cli/commands/ext-cmd.ts +3 -1
  142. package/src/server/ws/extensions.ts +34 -4
  143. package/src/services/contribution-registry.ts +14 -1
  144. package/src/services/extension-host-worker.ts +12 -3
  145. package/src/services/extension-manifest.ts +18 -1
  146. package/src/services/extension-rpc-handlers.ts +68 -2
  147. package/src/services/extension.service.ts +63 -9
  148. package/src/types/extension-messages.ts +3 -1
  149. package/src/types/extension.ts +8 -0
  150. package/src/web/components/editor/code-editor.tsx +16 -4
  151. package/src/web/components/editor/conflict-editor.tsx +368 -0
  152. package/src/web/components/extensions/extension-webview.tsx +153 -12
  153. package/src/web/components/layout/command-palette.tsx +41 -17
  154. package/src/web/components/layout/editor-panel.tsx +16 -4
  155. package/src/web/components/layout/mobile-nav.tsx +6 -5
  156. package/src/web/components/layout/tab-bar.tsx +3 -3
  157. package/src/web/components/layout/tab-content.tsx +17 -5
  158. package/src/web/components/settings/keyboard-shortcuts-section.tsx +46 -1
  159. package/src/web/hooks/use-extension-ws.ts +30 -4
  160. package/src/web/hooks/use-global-keybindings.ts +24 -2
  161. package/src/web/hooks/use-url-sync.ts +8 -3
  162. package/src/web/stores/extension-store.ts +8 -0
  163. package/src/web/stores/keybindings-store.ts +2 -3
  164. package/src/web/stores/panel-store.ts +2 -2
  165. package/src/web/stores/panel-utils.ts +6 -2
  166. package/src/web/stores/tab-store.ts +3 -2
  167. package/dist/web/assets/ai-settings-section-D6d-RmR6.js +0 -1
  168. package/dist/web/assets/architecture-PBZL5I3N-DpVzOETR.js +0 -1
  169. package/dist/web/assets/arrow-up-BigIMx-e.js +0 -1
  170. package/dist/web/assets/channel-Cgy1thYT.js +0 -1
  171. package/dist/web/assets/chat-tab-DXBb9Y3U.js +0 -10
  172. package/dist/web/assets/check-ePA3ZvK4.js +0 -1
  173. package/dist/web/assets/chevron-down-EQA06nR-.js +0 -1
  174. package/dist/web/assets/chevron-right-CXzzT44u.js +0 -1
  175. package/dist/web/assets/chunk-GLR3WWYH-CxUl1sdz.js +0 -2
  176. package/dist/web/assets/chunk-HHEYEP7N-DN7ebS2Y.js +0 -1
  177. package/dist/web/assets/chunk-QZHKN3VN-C4La7oLj.js +0 -1
  178. package/dist/web/assets/classDiagram-VBA2DB6C-C3IyfqG-.js +0 -1
  179. package/dist/web/assets/classDiagram-v2-RAHNMMFH-Dcvhz2pb.js +0 -1
  180. package/dist/web/assets/clone--C7Tby8z.js +0 -1
  181. package/dist/web/assets/code-editor-Cr7JrBKC.js +0 -8
  182. package/dist/web/assets/columns-2-BZ9uqssV.js +0 -1
  183. package/dist/web/assets/createLucideIcon-PuMiQgHl.js +0 -1
  184. package/dist/web/assets/database-D1ToEV9d.js +0 -1
  185. package/dist/web/assets/diff-viewer-BBr6e_gb.js +0 -4
  186. package/dist/web/assets/dist-KUoHa6tg.js +0 -1
  187. package/dist/web/assets/extension-webview-B0klBip8.js +0 -3
  188. package/dist/web/assets/eye-CNcBU6Tx.js +0 -1
  189. package/dist/web/assets/git-graph-CDiwGa0g.js +0 -1
  190. package/dist/web/assets/gitGraph-HDMCJU4V-DcPyMEIJ.js +0 -1
  191. package/dist/web/assets/index-CkaCzNgO.css +0 -2
  192. package/dist/web/assets/index-Ic5uTu20.js +0 -26
  193. package/dist/web/assets/info-3K5VOQVL-Dw4O15cw.js +0 -1
  194. package/dist/web/assets/infoDiagram-LFFYTUFH-DFhmsucr.js +0 -2
  195. package/dist/web/assets/input-CcbTF6ih.js +0 -45
  196. package/dist/web/assets/jsx-runtime-R_NjdZtX.js +0 -1
  197. package/dist/web/assets/keybindings-store-CxE6BlG2.js +0 -1
  198. package/dist/web/assets/packet-RMMSAZCW-o3LmdL8H.js +0 -1
  199. package/dist/web/assets/pie-UPGHQEXC-BjNP0M3B.js +0 -1
  200. package/dist/web/assets/plus-Iso5r9vD.js +0 -1
  201. package/dist/web/assets/port-forwarding-tab-BPuSc6pI.js +0 -1
  202. package/dist/web/assets/radar-KQ55EAFF-gDgOiaME.js +0 -1
  203. package/dist/web/assets/refresh-cw-BgQzFNaG.js +0 -1
  204. package/dist/web/assets/scroll-area-i4EZlOl_.js +0 -1
  205. package/dist/web/assets/settings-tab-BzSSN2BQ.js +0 -1
  206. package/dist/web/assets/sqlite-viewer-CoyZOM_Y.js +0 -1
  207. package/dist/web/assets/square-pfn_LYYy.js +0 -1
  208. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-DksQJ7es.js +0 -1
  209. package/dist/web/assets/table-CHv2x_qg.js +0 -1
  210. package/dist/web/assets/tag-Bb_UFXt0.js +0 -1
  211. package/dist/web/assets/text-wrap-D8BbQYTx.js +0 -1
  212. package/dist/web/assets/trash-2-DYCa06CV.js +0 -1
  213. package/dist/web/assets/treemap-KZPCXAKY-DwFqAvnj.js +0 -1
  214. package/dist/web/assets/x-BXecj-16.js +0 -1
  215. package/src/web/components/git/git-graph-branch-label.tsx +0 -124
  216. package/src/web/components/git/git-graph-constants.ts +0 -185
  217. package/src/web/components/git/git-graph-detail.tsx +0 -107
  218. package/src/web/components/git/git-graph-dialog.tsx +0 -72
  219. package/src/web/components/git/git-graph-row.tsx +0 -167
  220. package/src/web/components/git/git-graph-settings-dialog.tsx +0 -104
  221. package/src/web/components/git/git-graph-svg.tsx +0 -54
  222. package/src/web/components/git/git-graph-toolbar.tsx +0 -195
  223. package/src/web/components/git/git-graph.tsx +0 -193
  224. package/src/web/components/git/use-column-resize.ts +0 -33
  225. package/src/web/components/git/use-git-graph.ts +0 -201
  226. /package/dist/web/assets/{api-client-wQbeUyeh.js → api-client-BvxmRZUi.js} +0 -0
  227. /package/dist/web/assets/{array-X0JlPOfd.js → array-BFDiaBgf.js} +0 -0
  228. /package/dist/web/assets/{csv-parser-CElqio6o.js → csv-parser-i7fjqP2H.js} +0 -0
  229. /package/dist/web/assets/{cytoscape.esm-BfIOPvwt.js → cytoscape.esm-C8i2jUzT.js} +0 -0
  230. /package/dist/web/assets/{defaultLocale-B6RGN4id.js → defaultLocale-ZeknFqNe.js} +0 -0
  231. /package/dist/web/assets/{dist-CK1enexV.js → dist-DZmJeHOA.js} +0 -0
  232. /package/dist/web/assets/{init-BmUWJJHz.js → init-0VJVrkRJ.js} +0 -0
  233. /package/dist/web/assets/{isArrayLikeObject-BrCM-iA1.js → isArrayLikeObject-ClzWCpcm.js} +0 -0
  234. /package/dist/web/assets/{katex-xQS_6bNb.js → katex-DR0kdMDv.js} +0 -0
  235. /package/dist/web/assets/{lib-CfWBrYll.js → lib-DSLzfeW0.js} +0 -0
  236. /package/dist/web/assets/{math-CpLFzrfV.js → math-CRc16Nj6.js} +0 -0
  237. /package/dist/web/assets/{path-CoPyR7c2.js → path-INs8XTPH.js} +0 -0
  238. /package/dist/web/assets/{preload-helper-CH6UZRzu.js → preload-helper-mr3rCizq.js} +0 -0
  239. /package/dist/web/assets/{react-j5zqhEum.js → react-0tkk-ztn.js} +0 -0
  240. /package/dist/web/assets/{rough.esm-D5NinLFK.js → rough.esm-eLccZ4OJ.js} +0 -0
  241. /package/dist/web/assets/{sql-completion-provider-D0xutVaK.js → sql-completion-provider-B8uUWWej.js} +0 -0
  242. /package/dist/web/assets/{src-j04igtQ5.js → src-CqyWLlNZ.js} +0 -0
  243. /package/dist/web/assets/{utils-CSCvNZxE.js → utils-DX8jb5qv.js} +0 -0
@@ -21,11 +21,13 @@ export {
21
21
  type RpcClient,
22
22
  } from "./types.ts";
23
23
  export { type ExtensionContext, Memento } from "./context.ts";
24
+ export { ProcessService, type SpawnResult, type SpawnOptions } from "./process.ts";
24
25
 
25
26
  // Service imports
26
27
  import { CommandService } from "./commands.ts";
27
28
  import { WindowService } from "./window.ts";
28
29
  import { WorkspaceService } from "./workspace.ts";
30
+ import { ProcessService } from "./process.ts";
29
31
  import { createExtensionContext, type ExtensionContext } from "./context.ts";
30
32
  import { createEnvNamespace } from "./env.ts";
31
33
  import { createNotSupported } from "./not-supported.ts";
@@ -55,12 +57,14 @@ export function createVscodeCompat(options: CreateVscodeCompatOptions) {
55
57
  const commands = new CommandService(rpc, extensionId);
56
58
  const window = new WindowService(rpc, extensionId);
57
59
  const workspace = new WorkspaceService(rpc, extensionId);
60
+ const process = new ProcessService(rpc);
58
61
 
59
62
  return {
60
63
  // Active API namespaces
61
64
  commands,
62
65
  window,
63
66
  workspace,
67
+ process,
64
68
  env: createEnvNamespace(options.appName ?? "PPM", options.machineId ?? "ppm-local"),
65
69
 
66
70
  // Classes & utilities
@@ -0,0 +1,25 @@
1
+ import type { RpcClient } from "./types.ts";
2
+
3
+ export interface SpawnResult {
4
+ stdout: string;
5
+ stderr: string;
6
+ exitCode: number;
7
+ }
8
+
9
+ export interface SpawnOptions {
10
+ timeout?: number;
11
+ env?: Record<string, string>;
12
+ }
13
+
14
+ /** Process namespace — spawn subprocesses via RPC to main process */
15
+ export class ProcessService {
16
+ private rpc: RpcClient;
17
+
18
+ constructor(rpc: RpcClient) {
19
+ this.rpc = rpc;
20
+ }
21
+
22
+ async spawn(cmd: string, args: string[], cwd: string, options?: SpawnOptions): Promise<SpawnResult> {
23
+ return this.rpc.request<SpawnResult>("process:spawn", cmd, args, cwd, options);
24
+ }
25
+ }
@@ -184,6 +184,16 @@ export class WindowService {
184
184
  });
185
185
  }
186
186
 
187
+ // --- Open PPM Tab ---
188
+
189
+ async openTab(tabType: string, title: string, projectId: string | null, metadata?: Record<string, unknown>): Promise<void> {
190
+ await this.rpc.request("window:openTab", tabType, title, projectId, metadata);
191
+ }
192
+
193
+ async switchProject(projectName: string): Promise<void> {
194
+ await this.rpc.request("window:switchProject", projectName);
195
+ }
196
+
187
197
  // --- Webview Panel ---
188
198
 
189
199
  createWebviewPanel(viewType: string, title: string, showOptions: ViewColumn): unknown {
@@ -63,6 +63,7 @@ export function registerExtCommands(program: Command): void {
63
63
  .description("List installed extensions")
64
64
  .action(async () => {
65
65
  const { extensionService } = await import("../../services/extension.service.ts");
66
+ await extensionService.discover(); // populate bundledIds for source column
66
67
  const extensions = extensionService.list();
67
68
  if (extensions.length === 0) {
68
69
  console.log(`${C.dim}No extensions installed.${C.reset}`);
@@ -71,10 +72,11 @@ export function registerExtCommands(program: Command): void {
71
72
  const rows = extensions.map((e) => [
72
73
  e.id,
73
74
  e.version,
75
+ extensionService.isBundled(e.id) ? `${C.cyan}bundled${C.reset}` : "user",
74
76
  e.enabled ? `${C.green}enabled${C.reset}` : `${C.dim}disabled${C.reset}`,
75
77
  e.activated ? `${C.green}active${C.reset}` : `${C.dim}inactive${C.reset}`,
76
78
  ]);
77
- printTable(["ID", "Version", "Enabled", "Status"], rows);
79
+ printTable(["ID", "Version", "Source", "Enabled", "Status"], rows);
78
80
  });
79
81
 
80
82
  ext
@@ -71,21 +71,51 @@ async function handleMessage(ws: ExtWsSocket, raw: string | Buffer): Promise<voi
71
71
 
72
72
  switch (msg.type) {
73
73
  case "ready": {
74
- // Send current contributions on connect
74
+ // Send current contributions + any activation errors on connect
75
75
  const contributions = contributionRegistry.getAll();
76
- ws.send(JSON.stringify({ type: "contributions:update", contributions } satisfies ExtServerMsg));
76
+ const { extensionService } = await import("../../services/extension.service.ts");
77
+ const activationErrors = Object.fromEntries(extensionService.getActivationErrors());
78
+ const readyMsg: ExtServerMsg = Object.keys(activationErrors).length > 0
79
+ ? { type: "contributions:update", contributions, activationErrors }
80
+ : { type: "contributions:update", contributions };
81
+ ws.send(JSON.stringify(readyMsg));
77
82
  break;
78
83
  }
79
84
 
80
85
  case "command:execute": {
81
86
  try {
82
87
  const { extensionService } = await import("../../services/extension.service.ts");
83
- // Forward to extension host worker via RPC
84
88
  if (extensionService["rpc"]) {
85
- await extensionService["rpc"].sendRequest("ext:command:execute", msg.command, ...(msg.args ?? []));
89
+ console.log(`[ExtWS] command:execute "${msg.command}"`);
90
+ const result = await extensionService["rpc"].sendRequest<{ ok: boolean; error?: string }>(
91
+ "ext:command:execute", msg.command, ...(msg.args ?? []),
92
+ );
93
+ if (!result?.ok) {
94
+ console.error(`[ExtWS] command:execute failed: ${result?.error ?? "unknown"}`);
95
+ broadcastExtMsg({
96
+ type: "notification",
97
+ id: `cmd-error-${Date.now()}`,
98
+ level: "error",
99
+ message: `Extension command failed: ${result?.error ?? "unknown error"}`,
100
+ });
101
+ }
102
+ } else {
103
+ console.error(`[ExtWS] command:execute: extension host not ready`);
104
+ broadcastExtMsg({
105
+ type: "notification",
106
+ id: `cmd-error-${Date.now()}`,
107
+ level: "error",
108
+ message: `Extension host not ready. Try reloading the page.`,
109
+ });
86
110
  }
87
111
  } catch (e) {
88
112
  console.error(`[ExtWS] command:execute error:`, e);
113
+ broadcastExtMsg({
114
+ type: "notification",
115
+ id: `cmd-error-${Date.now()}`,
116
+ level: "error",
117
+ message: `Extension command error: ${e instanceof Error ? e.message : String(e)}`,
118
+ });
89
119
  }
90
120
  break;
91
121
  }
@@ -1,4 +1,4 @@
1
- import type { ExtensionContributes, ContributedCommand, ContributedView, ContributedMenu } from "../types/extension.ts";
1
+ import type { ExtensionContributes, ContributedCommand, ContributedView, ContributedMenu, ContributedKeybinding } from "../types/extension.ts";
2
2
 
3
3
  /**
4
4
  * In-memory registry of all contribution points from enabled extensions.
@@ -9,6 +9,7 @@ class ContributionRegistry {
9
9
  private views = new Map<string, Map<string, ContributedView & { extId: string }>>();
10
10
  private configs = new Map<string, Record<string, unknown>>();
11
11
  private menus = new Map<string, Array<ContributedMenu & { extId: string }>>();
12
+ private keybindings: Array<ContributedKeybinding & { extId: string }> = [];
12
13
 
13
14
  register(extId: string, contributes: ExtensionContributes): void {
14
15
  if (contributes.commands) {
@@ -37,6 +38,11 @@ class ContributionRegistry {
37
38
  }
38
39
  }
39
40
  }
41
+ if (contributes.keybindings) {
42
+ for (const kb of contributes.keybindings) {
43
+ this.keybindings.push({ ...kb, extId });
44
+ }
45
+ }
40
46
  }
41
47
 
42
48
  unregister(extId: string): void {
@@ -51,6 +57,7 @@ class ContributionRegistry {
51
57
  for (const [location, items] of this.menus) {
52
58
  this.menus.set(location, items.filter((m) => m.extId !== extId));
53
59
  }
60
+ this.keybindings = this.keybindings.filter((kb) => kb.extId !== extId);
54
61
  this.configs.delete(extId);
55
62
  }
56
63
 
@@ -73,6 +80,10 @@ class ContributionRegistry {
73
80
  return [...this.views.keys()];
74
81
  }
75
82
 
83
+ getKeybindings(): Array<ContributedKeybinding & { extId: string }> {
84
+ return [...this.keybindings];
85
+ }
86
+
76
87
  getConfiguration(extId?: string): Record<string, Record<string, unknown>> {
77
88
  if (extId) {
78
89
  const cfg = this.configs.get(extId);
@@ -95,6 +106,7 @@ class ContributionRegistry {
95
106
  commands: this.getCommands(),
96
107
  views: viewsByLocation,
97
108
  menus: menusByLocation,
109
+ keybindings: this.getKeybindings(),
98
110
  configuration: this.getConfiguration(),
99
111
  };
100
112
  }
@@ -104,6 +116,7 @@ class ContributionRegistry {
104
116
  this.views.clear();
105
117
  this.configs.clear();
106
118
  this.menus.clear();
119
+ this.keybindings = [];
107
120
  }
108
121
  }
109
122
 
@@ -29,6 +29,7 @@ self.addEventListener("message", (event: MessageEvent<RpcMessage>) => {
29
29
 
30
30
  rpc.onRequest("ext:activate", async (params) => {
31
31
  const [extId, entryPath, extensionPath, storedState, baseUrl] = params as [string, string, string, Record<string, Record<string, string | null>>?, string?];
32
+ console.log(`[ExtHost] activating ${extId} from ${entryPath}`);
32
33
  if (activeExtensions.has(extId)) return { ok: true, already: true };
33
34
 
34
35
  // Expose server base URL so extensions can use fetch() with absolute URLs
@@ -68,6 +69,7 @@ rpc.onRequest("ext:activate", async (params) => {
68
69
  window: api.window as WindowService,
69
70
  commands: api.commands as CommandService,
70
71
  });
72
+ console.log(`[ExtHost] activated ${extId} (${activeExtensions.size} total)`);
71
73
  return { ok: true };
72
74
  } catch (e) {
73
75
  const msg = e instanceof Error ? e.message : String(e);
@@ -101,16 +103,23 @@ rpc.onRequest("ext:deactivate", async (params) => {
101
103
 
102
104
  rpc.onRequest("ext:command:execute", async (params) => {
103
105
  const [command, ...args] = params as [string, ...unknown[]];
104
- for (const [, ext] of activeExtensions) {
106
+ console.log(`[ExtHost] command:execute "${command}" (${activeExtensions.size} extensions active)`);
107
+ for (const [extId, ext] of activeExtensions) {
105
108
  if (ext.commands) {
109
+ const hasLocal = (ext.commands as any).localHandlers?.has(command);
110
+ if (!hasLocal) continue;
111
+ console.log(`[ExtHost] routing "${command}" → ${extId}`);
106
112
  try {
107
113
  const result = await (ext.commands as any).executeCommand(command, ...args);
108
114
  return { ok: true, result };
109
- } catch {
110
- // Command not found in this extension, try next
115
+ } catch (e) {
116
+ const msg = e instanceof Error ? e.message : String(e);
117
+ console.error(`[ExtHost] command "${command}" in ${extId} threw:`, msg);
118
+ return { ok: false, error: msg };
111
119
  }
112
120
  }
113
121
  }
122
+ console.warn(`[ExtHost] command not found: "${command}"`);
114
123
  return { ok: false, error: `Command not found: ${command}` };
115
124
  });
116
125
 
@@ -38,7 +38,7 @@ export function readManifestAt(dir: string): ExtensionManifest | null {
38
38
  }
39
39
  }
40
40
 
41
- /** Scan extensions directory for all valid manifests */
41
+ /** Scan extensions directory (node_modules) for all valid manifests */
42
42
  export async function discoverManifests(extensionsDir: string): Promise<ExtensionManifest[]> {
43
43
  const manifests: ExtensionManifest[] = [];
44
44
  if (!existsSync(extensionsDir)) return manifests;
@@ -63,3 +63,20 @@ export async function discoverManifests(extensionsDir: string): Promise<Extensio
63
63
  }
64
64
  return manifests;
65
65
  }
66
+
67
+ export type BundledManifest = ExtensionManifest & { _dir: string };
68
+
69
+ /** Scan packages directory for bundled extensions (ext-* dirs) */
70
+ export async function discoverBundledManifests(packagesDir: string): Promise<BundledManifest[]> {
71
+ const manifests: BundledManifest[] = [];
72
+ if (!existsSync(packagesDir)) return manifests;
73
+
74
+ const entries = await readdir(packagesDir, { withFileTypes: true });
75
+ for (const entry of entries) {
76
+ if (!entry.isDirectory() || !entry.name.startsWith("ext-")) continue;
77
+ const dir = resolve(packagesDir, entry.name);
78
+ const manifest = readManifestAt(dir);
79
+ if (manifest) manifests.push({ ...manifest, _dir: dir });
80
+ }
81
+ return manifests;
82
+ }
@@ -112,6 +112,22 @@ export function registerVscodeCompatHandlers(rpc: RpcChannel): void {
112
112
  return { ok: true };
113
113
  });
114
114
 
115
+ // --- open PPM tab (generic, any extension can use) ---
116
+ rpc.onRequest("window:openTab", async (params) => {
117
+ const [tabType, title, projectId, metadata] = params as [
118
+ string, string, string | null, Record<string, unknown> | undefined,
119
+ ];
120
+ broadcastExtMsg({ type: "tab:open", tabType, title, projectId, closable: true, metadata });
121
+ return { ok: true };
122
+ });
123
+
124
+ // --- switch PPM project ---
125
+ rpc.onRequest("window:switchProject", async (params) => {
126
+ const [projectName] = params as [string];
127
+ broadcastExtMsg({ type: "project:switch", projectName });
128
+ return { ok: true };
129
+ });
130
+
115
131
  // --- tree views (forwarded to browser via WS bridge) ---
116
132
  rpc.onRequest("window:tree:update", async (params) => {
117
133
  const [viewId, items] = params as [string, unknown[]];
@@ -155,9 +171,11 @@ export function registerVscodeCompatHandlers(rpc: RpcChannel): void {
155
171
  async function assertSafePath(filePath: string): Promise<string> {
156
172
  const { resolve, relative } = await import("node:path");
157
173
  const resolved = resolve(filePath);
158
- // Allow: CWD (project root) and ~/.ppm/extensions/ (extension storage)
174
+ // Allow: CWD, ~/.ppm/extensions/, and all registered project paths
159
175
  const { getPpmDir } = await import("./ppm-dir.ts");
160
- const allowedRoots = [resolve(process.cwd()), resolve(getPpmDir(), "extensions")];
176
+ const { configService } = await import("./config.service.ts");
177
+ const projectPaths = configService.get("projects").map((p: { path: string }) => resolve(p.path));
178
+ const allowedRoots = [resolve(process.cwd()), resolve(getPpmDir(), "extensions"), ...projectPaths];
161
179
  const isSafe = allowedRoots.some((root) => {
162
180
  const rel = relative(root, resolved);
163
181
  return !rel.startsWith("..") && !rel.startsWith("/");
@@ -221,6 +239,54 @@ export function registerVscodeCompatHandlers(rpc: RpcChannel): void {
221
239
  }
222
240
  return results;
223
241
  });
242
+
243
+ // --- process spawn (for extensions needing subprocess access) ---
244
+
245
+ const ALLOWED_SPAWN_COMMANDS = new Set(["git", "node", "bun", "npx", "sqlite3"]);
246
+ const BLOCKED_ENV_KEYS = new Set(["PATH", "HOME", "LD_PRELOAD", "DYLD_INSERT_LIBRARIES", "LD_LIBRARY_PATH"]);
247
+
248
+ rpc.onRequest("process:spawn", async (params) => {
249
+ const [cmd, args, cwd, options] = params as [string, string[], string, { timeout?: number; env?: Record<string, string> }?];
250
+
251
+ // Security: command allowlist
252
+ const baseName = cmd.split("/").pop() || cmd;
253
+ if (!ALLOWED_SPAWN_COMMANDS.has(baseName)) {
254
+ throw new Error(`process:spawn: command "${cmd}" not allowed. Allowed: ${[...ALLOWED_SPAWN_COMMANDS].join(", ")}`);
255
+ }
256
+
257
+ // Security: CWD must be within allowed roots
258
+ const safeCwd = await assertSafePath(cwd);
259
+
260
+ // Security: block dangerous env overrides
261
+ const safeEnv = { ...process.env };
262
+ if (options?.env) {
263
+ for (const [key, val] of Object.entries(options.env)) {
264
+ if (!BLOCKED_ENV_KEYS.has(key)) safeEnv[key] = val;
265
+ }
266
+ }
267
+
268
+ const timeout = options?.timeout ?? 30_000;
269
+ const proc = Bun.spawn([cmd, ...args], {
270
+ cwd: safeCwd,
271
+ stdout: "pipe",
272
+ stderr: "pipe",
273
+ env: safeEnv,
274
+ });
275
+
276
+ const timer = setTimeout(() => { try { proc.kill(); } catch {} }, timeout);
277
+ try {
278
+ const [stdout, stderr] = await Promise.all([
279
+ new Response(proc.stdout).text(),
280
+ new Response(proc.stderr).text(),
281
+ ]);
282
+ const exitCode = await proc.exited;
283
+ clearTimeout(timer);
284
+ return { stdout, stderr, exitCode };
285
+ } catch (e) {
286
+ clearTimeout(timer);
287
+ throw new Error(`process:spawn failed: ${e instanceof Error ? e.message : String(e)}`);
288
+ }
289
+ });
224
290
  }
225
291
 
226
292
  /** Get a nested value from an object by dot-separated key */
@@ -1,10 +1,10 @@
1
1
  import { resolve } from "node:path";
2
2
  import { existsSync } from "node:fs";
3
3
  import type { ExtensionManifest, ExtensionInfo, RpcMessage } from "../types/extension.ts";
4
- import { getExtensions, getExtensionById, insertExtension, getExtensionStorage, setExtensionStorageValue } from "./db.service.ts";
4
+ import { getExtensions, getExtensionById, insertExtension, updateExtension, getExtensionStorage, setExtensionStorageValue } from "./db.service.ts";
5
5
  import { contributionRegistry } from "./contribution-registry.ts";
6
6
  import { RpcChannel } from "./extension-rpc.ts";
7
- import { parseManifest, discoverManifests } from "./extension-manifest.ts";
7
+ import { parseManifest, discoverManifests, discoverBundledManifests } from "./extension-manifest.ts";
8
8
  import { installExtension, removeExtension, devLinkExtension, ensureExtensionsDir } from "./extension-installer.ts";
9
9
  import { registerVscodeCompatHandlers } from "./extension-rpc-handlers.ts";
10
10
  import { getPpmDir } from "./ppm-dir.ts";
@@ -13,8 +13,11 @@ class ExtensionService {
13
13
  private worker: Worker | null = null;
14
14
  private rpc: RpcChannel | null = null;
15
15
  private activatedIds = new Set<string>();
16
+ private activationErrors = new Map<string, string>();
16
17
  private workerReady = false;
17
18
  private installing = new Set<string>();
19
+ private extensionPaths = new Map<string, string>();
20
+ private bundledIds = new Set<string>();
18
21
 
19
22
  // --- Worker lifecycle ---
20
23
 
@@ -54,6 +57,9 @@ class ExtensionService {
54
57
  if (this.worker) { this.worker.terminate(); this.worker = null; }
55
58
  this.workerReady = false;
56
59
  this.activatedIds.clear();
60
+ this.activationErrors.clear();
61
+ this.extensionPaths.clear();
62
+ this.bundledIds.clear();
57
63
  contributionRegistry.clear();
58
64
  }
59
65
 
@@ -65,7 +71,29 @@ class ExtensionService {
65
71
 
66
72
  async discover(): Promise<ExtensionManifest[]> {
67
73
  ensureExtensionsDir(resolve(getPpmDir(), "extensions"));
68
- return discoverManifests(resolve(getPpmDir(), "extensions"));
74
+
75
+ // Discover bundled extensions from packages/ext-*
76
+ const bundledDir = resolve(import.meta.dir, "../../packages");
77
+ const bundled = await discoverBundledManifests(bundledDir);
78
+ for (const m of bundled) {
79
+ this.extensionPaths.set(m.id, m._dir);
80
+ this.bundledIds.add(m.id);
81
+ }
82
+
83
+ // Discover user-installed extensions
84
+ const userExtDir = resolve(getPpmDir(), "extensions");
85
+ const userManifests = await discoverManifests(userExtDir);
86
+ for (const m of userManifests) {
87
+ this.extensionPaths.set(m.id, resolve(userExtDir, "node_modules", m.id));
88
+ }
89
+
90
+ // Merge: user overrides bundled if same id (strip _dir to avoid leaking paths)
91
+ const byId = new Map(bundled.map((m) => {
92
+ const { _dir, ...manifest } = m;
93
+ return [m.id, manifest as ExtensionManifest];
94
+ }));
95
+ for (const m of userManifests) byId.set(m.id, m);
96
+ return [...byId.values()];
69
97
  }
70
98
 
71
99
  async install(name: string): Promise<ExtensionManifest> {
@@ -79,6 +107,9 @@ class ExtensionService {
79
107
  }
80
108
 
81
109
  async remove(id: string): Promise<void> {
110
+ if (this.bundledIds.has(id)) {
111
+ throw new Error(`Cannot remove bundled extension "${id}". Use 'ppm ext disable ${id}' instead.`);
112
+ }
82
113
  if (this.activatedIds.has(id)) await this.deactivate(id);
83
114
  await removeExtension(id, resolve(getPpmDir(), "extensions"));
84
115
  contributionRegistry.unregister(id);
@@ -92,7 +123,8 @@ class ExtensionService {
92
123
  if (!row.enabled) throw new Error(`Extension ${id} is disabled`);
93
124
 
94
125
  const manifest: ExtensionManifest = JSON.parse(row.manifest);
95
- const extDir = resolve(resolve(getPpmDir(), "extensions"), "node_modules", id);
126
+ const extDir = this.extensionPaths.get(id)
127
+ ?? resolve(resolve(getPpmDir(), "extensions"), "node_modules", id);
96
128
  const entryPath = resolve(extDir, manifest.main);
97
129
  if (!existsSync(entryPath)) throw new Error(`Entry point not found: ${entryPath}`);
98
130
 
@@ -111,15 +143,20 @@ class ExtensionService {
111
143
  const port = cfg.get("port") ?? 8080;
112
144
  const baseUrl = `http://localhost:${port}`;
113
145
 
146
+ console.log(`[ExtService] activating ${id} (entry: ${entryPath})`);
114
147
  const result = await rpc.sendRequest<{ ok: boolean; error?: string }>(
115
148
  "ext:activate", id, entryPath, extDir, storedState, baseUrl,
116
149
  );
117
- if (!result.ok) throw new Error(`Failed to activate ${id}: ${result.error}`);
150
+ if (!result.ok) {
151
+ this.activationErrors.set(id, result.error ?? "Unknown activation error");
152
+ throw new Error(`Failed to activate ${id}: ${result.error}`);
153
+ }
118
154
 
155
+ this.activationErrors.delete(id);
119
156
  this.activatedIds.add(id);
120
157
  if (manifest.contributes) contributionRegistry.register(id, manifest.contributes);
121
158
  this.broadcastContributions();
122
- console.log(`[ExtService] Activated ${id}`);
159
+ console.log(`[ExtService] activated ${id} successfully`);
123
160
  }
124
161
 
125
162
  async deactivate(id: string): Promise<void> {
@@ -166,7 +203,6 @@ class ExtensionService {
166
203
  async setEnabled(id: string, enabled: boolean): Promise<void> {
167
204
  const row = getExtensionById(id);
168
205
  if (!row) throw new Error(`Extension ${id} not found`);
169
- const { updateExtension } = await import("./db.service.ts");
170
206
  updateExtension(id, { enabled: enabled ? 1 : 0 });
171
207
  if (enabled && !this.activatedIds.has(id)) await this.activate(id);
172
208
  else if (!enabled && this.activatedIds.has(id)) await this.deactivate(id);
@@ -187,17 +223,29 @@ class ExtensionService {
187
223
  ensureExtensionsDir(resolve(getPpmDir(), "extensions"));
188
224
  const manifests = await this.discover();
189
225
  for (const m of manifests) {
190
- if (!getExtensionById(m.id)) {
226
+ const existing = getExtensionById(m.id);
227
+ if (!existing) {
191
228
  insertExtension({
192
229
  id: m.id, version: m.version,
193
230
  display_name: m.displayName ?? null, description: m.description ?? null,
194
231
  icon: m.icon ?? null, enabled: 1, manifest: JSON.stringify(m),
195
232
  });
233
+ } else {
234
+ // Always sync manifest from disk so new contributes (keybindings, etc.) are picked up
235
+ updateExtension(m.id, {
236
+ version: m.version,
237
+ display_name: m.displayName ?? null,
238
+ description: m.description ?? null,
239
+ icon: m.icon ?? null,
240
+ manifest: JSON.stringify(m),
241
+ });
196
242
  }
197
243
  }
198
244
  for (const row of getExtensions()) {
199
245
  if (row.enabled !== 1) continue;
246
+ console.log(`[ExtService] startup: activating ${row.id}...`);
200
247
  try { await this.activate(row.id); } catch (e) {
248
+ this.activationErrors.set(row.id, e instanceof Error ? e.message : String(e));
201
249
  console.error(`[ExtService] Failed to activate ${row.id} on startup:`, e);
202
250
  }
203
251
  }
@@ -211,13 +259,19 @@ class ExtensionService {
211
259
  }
212
260
 
213
261
  isActivated(id: string): boolean { return this.activatedIds.has(id); }
262
+ isBundled(id: string): boolean { return this.bundledIds.has(id); }
214
263
  getExtensionsDir(): string { return resolve(getPpmDir(), "extensions"); }
264
+ getActivationErrors(): Map<string, string> { return new Map(this.activationErrors); }
215
265
 
216
266
  /** Push current contributions to all connected browser clients */
217
267
  private broadcastContributions(): void {
218
268
  try {
219
269
  const { broadcastExtMsg } = require("../server/ws/extensions.ts");
220
- broadcastExtMsg({ type: "contributions:update", contributions: contributionRegistry.getAll() });
270
+ const contributions = contributionRegistry.getAll();
271
+ broadcastExtMsg(this.activationErrors.size > 0
272
+ ? { type: "contributions:update", contributions, activationErrors: Object.fromEntries(this.activationErrors) }
273
+ : { type: "contributions:update", contributions },
274
+ );
221
275
  } catch {}
222
276
  }
223
277
  }
@@ -49,7 +49,9 @@ export type ExtServerMsg =
49
49
  | { type: "webview:html"; panelId: string; html: string }
50
50
  | { type: "webview:dispose"; panelId: string }
51
51
  | { type: "webview:postMessage"; panelId: string; message: unknown }
52
- | { type: "contributions:update"; contributions: ExtensionContributes };
52
+ | { type: "tab:open"; tabType: string; title: string; projectId: string | null; closable?: boolean; metadata?: Record<string, unknown> }
53
+ | { type: "project:switch"; projectName: string }
54
+ | { type: "contributions:update"; contributions: ExtensionContributes; activationErrors?: Record<string, string> };
53
55
 
54
56
  // --- Client → Server messages ---
55
57
 
@@ -23,6 +23,7 @@ export interface ExtensionContributes {
23
23
  views?: Record<string, ContributedView[]>;
24
24
  configuration?: { properties?: Record<string, ConfigProperty> };
25
25
  menus?: Record<string, ContributedMenu[]>;
26
+ keybindings?: ContributedKeybinding[];
26
27
  }
27
28
 
28
29
  export interface ContributedCommand {
@@ -52,6 +53,13 @@ export interface ContributedMenu {
52
53
  group?: string;
53
54
  }
54
55
 
56
+ export interface ContributedKeybinding {
57
+ command: string;
58
+ key: string;
59
+ mac?: string;
60
+ when?: string;
61
+ }
62
+
55
63
  /** Runtime extension info returned by API */
56
64
  export interface ExtensionInfo {
57
65
  id: string;
@@ -340,22 +340,28 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
340
340
  );
341
341
  }
342
342
 
343
- // Register CodeLens for inline Run buttons on .sql files
343
+ // Register CodeLens for inline Run buttons on .sql files (scoped to this editor's model)
344
344
  if (isSql) {
345
345
  codeLensDisposable.current.forEach((d) => d.dispose());
346
346
  codeLensDisposable.current = [];
347
347
 
348
+ const thisModel = editor.getModel();
348
349
  const cmdId = editor.addCommand(0, (_accessor: unknown, sql: string) => {
349
350
  if (sql) runSqlRef.current(sql);
350
351
  });
351
352
 
352
- if (cmdId) {
353
+ if (cmdId && thisModel) {
353
354
  const provider = monaco.languages.registerCodeLensProvider("sql", {
354
355
  provideCodeLenses: (model: MonacoType.editor.ITextModel) => {
356
+ // Only provide lenses for THIS editor's model, not all SQL models
357
+ if (model !== thisModel) return { lenses: [], dispose: () => {} };
358
+
355
359
  const lenses: MonacoType.languages.CodeLens[] = [];
356
- const lines = model.getValue().split("\n");
360
+ const text = model.getValue();
361
+ const lines = text.split("\n");
357
362
  let stmtStartLine = -1;
358
363
  let stmtLines: string[] = [];
364
+ let dollarBlock = false; // Track DO $$ ... $$ blocks
359
365
 
360
366
  const addLens = (line: number, stmt: string) => {
361
367
  const trimmed = stmt.trim();
@@ -374,7 +380,13 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
374
380
  stmtLines = [];
375
381
  }
376
382
  stmtLines.push(lines[i]!);
377
- if (trimmed.endsWith(";")) {
383
+
384
+ // Detect $$ dollar-quoted block start/end
385
+ const dollarMatches = (trimmed.match(/\$\$/g) || []).length;
386
+ if (dollarMatches % 2 === 1) dollarBlock = !dollarBlock;
387
+
388
+ // Only split on ; when NOT inside a $$ block
389
+ if (!dollarBlock && trimmed.endsWith(";")) {
378
390
  addLens(stmtStartLine, stmtLines.join("\n"));
379
391
  stmtStartLine = -1;
380
392
  stmtLines = [];