@vantran-se/openchamber-web 1.23.0 → 1.24.3

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 (362) hide show
  1. package/bin/cli.test.js +29 -6
  2. package/bin/lib/cli-http.js +5 -1
  3. package/bin/lib/commands-status.js +5 -0
  4. package/dist/assets/{CodeMirrorCommentWidgets-DFg2wnpo.js → CodeMirrorCommentWidgets-BjJHrzmU.js} +1 -1
  5. package/dist/assets/DiffView-COVQhWGd.js +4 -0
  6. package/dist/assets/FilesView-B1V5JxFX.js +13 -0
  7. package/dist/assets/GitView-CDOJy5Ay.js +6 -0
  8. package/dist/assets/GuestAttachDialog-D3S0Rjic.js +1 -0
  9. package/dist/assets/{JsonTreeView-BKdsjg5B.js → JsonTreeView-D0C9nU7K.js} +1 -1
  10. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  11. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  12. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  13. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  14. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  15. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  16. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  17. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  18. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  19. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  20. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  21. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  22. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  23. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  24. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  25. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  26. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  27. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  28. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  29. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  30. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  31. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  32. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  33. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  34. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  35. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  36. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  37. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  38. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  39. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  40. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  41. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  42. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  43. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  44. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  45. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  46. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  47. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  48. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  49. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  50. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  51. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  52. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  53. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  54. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  55. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  56. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  57. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  58. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  59. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  60. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  61. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  62. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  63. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  64. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  65. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  66. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  67. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  68. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  69. package/dist/assets/LinearIssuesView-D7FKg1XJ.js +1 -0
  70. package/dist/assets/{MarkdownImageGallery-_5gEVr0X.js → MarkdownImageGallery-BrFjAVEf.js} +1 -1
  71. package/dist/assets/MarkdownRendererImpl-DWepTafh.js +12 -0
  72. package/dist/assets/{OnboardingScreen-DdP4_nuc.js → OnboardingScreen-Dyxxiqdt.js} +1 -1
  73. package/dist/assets/{PatchDiff-am3SAss2.js → PatchDiff-DnEhjXcC.js} +1 -1
  74. package/dist/assets/PierreDiffViewer-DyxZVVVR.js +210 -0
  75. package/dist/assets/{PlanView-BoJxdRZe.js → PlanView-LW2z_EAP.js} +4 -4
  76. package/dist/assets/PluginPane-B4tKno2h.js +3 -0
  77. package/dist/assets/SettingsView-DEaMulN9.js +29 -0
  78. package/dist/assets/{SettingsWindow-DVTdDSol.js → SettingsWindow-FLDJXOgQ.js} +1 -1
  79. package/dist/assets/SubmoduleDiffSummary-CvVwD-Vi.js +1 -0
  80. package/dist/assets/ToolOutputDialog-BiP0x2Rw.js +15 -0
  81. package/dist/assets/{ToolPartDiffPreview-C7WLk5i9.js → ToolPartDiffPreview-CkR4msKP.js} +2 -2
  82. package/dist/assets/{VirtualizedFileDiff-DcdgkH-x.js → VirtualizedFileDiff-0pyr_mHY.js} +4 -4
  83. package/dist/assets/WalkthroughView-yNj4nOP5.js +4 -0
  84. package/dist/assets/appThemeRegistry-BuUKhbYQ.js +1 -0
  85. package/dist/assets/commitAndPush-DvJKa3Ks.js +1 -0
  86. package/dist/assets/{computeVirtualFileMetrics-C8byMnRh.js → computeVirtualFileMetrics-DxTBMuu-.js} +2 -2
  87. package/dist/assets/de-DgxgFyGG.js +10 -0
  88. package/dist/assets/es-BFsZj5F_.js +16 -0
  89. package/dist/assets/fr-jdZ5KKQi.js +14 -0
  90. package/dist/assets/{index-BB7ryz8y.js → index-CKHjPKbq.js} +1 -1
  91. package/dist/assets/index-Cbci4i1W.js +40 -0
  92. package/dist/assets/index-DqaMWNrC.css +1 -0
  93. package/dist/assets/{isDiffPlainText-45ujMPGb.js → isDiffPlainText-D7FkBMP6.js} +1 -1
  94. package/dist/assets/{isFilePlainText-Cc4KFSYi.js → isFilePlainText-CL2vsrIZ.js} +1 -1
  95. package/dist/assets/ja-C5kKrhO4.js +16 -0
  96. package/dist/assets/ko-DXDTT_0O.js +16 -0
  97. package/dist/assets/main-CZALtVek.js +896 -0
  98. package/dist/assets/main-CgXMBNvG.js +2 -0
  99. package/dist/assets/{markdown-shiki.worker-D5fBU-lw.js → markdown-shiki.worker-CahLvHIq.js} +5 -5
  100. package/dist/assets/markdownCore-Dh7WEsdM.js +5 -0
  101. package/dist/assets/miniChat-CwTmDixi.js +2 -0
  102. package/dist/assets/mobile-CLf2vCtO.js +2 -0
  103. package/dist/assets/patchFileDiff-Bsba8KFp.js +13 -0
  104. package/dist/assets/pl-D-7x4Y8Q.js +16 -0
  105. package/dist/assets/pt-BR-CSBZrHa8.js +16 -0
  106. package/dist/assets/renderElectronMiniChatApp-C5gAFBk0.js +2 -0
  107. package/dist/assets/renderMobileApp-V4hYd32H.js +25 -0
  108. package/dist/assets/runtimeEndpointReset-DhlHCn-2.js +25 -0
  109. package/dist/assets/scheduledTasksApi-DzjbLd8P.js +1 -0
  110. package/dist/assets/shikiHighlight-8_3csmr6.js +1 -0
  111. package/dist/assets/surface-B6n7wJtt.js +1 -0
  112. package/dist/assets/textMateThemeFromAppTheme-CHo2fCgW.js +1 -0
  113. package/dist/assets/tr-Dr-oJ22L.js +7 -0
  114. package/dist/assets/uk-9Nx83UcI.js +16 -0
  115. package/dist/assets/useAppFontEffects-khZXpFL_.js +7333 -0
  116. package/dist/assets/useInlineCommentController-DkQ_gqpn.js +2 -0
  117. package/dist/assets/useRootScrollLock-DdvHf9Pt.js +2 -0
  118. package/dist/assets/{vendor-base-ui-DdV8vhHn.js → vendor-base-ui-CsXYnR5c.js} +1 -1
  119. package/dist/assets/{vendor-codemirror-language-data-DzlSIu_W.js → vendor-codemirror-language-data-CdzUmUyZ.js} +2 -2
  120. package/dist/assets/vendor-framer-motion-Bq3iTZ6r.js +1 -0
  121. package/dist/assets/vendor-katex-Cp579UoJ.css +1 -0
  122. package/dist/assets/{vendor-motion-dom-D_o3BTI3.js → vendor-motion-dom-DcCgeG69.js} +1 -1
  123. package/dist/assets/{vendor-remixicon-react-Dil0zKKT.js → vendor-remixicon-react-BDmGmFCA.js} +1 -1
  124. package/dist/assets/vendor-strip-json-comments-5dJlOkP1.js +4 -0
  125. package/dist/assets/{vendor-tanstack-react-virtual-g05Cx3z9.js → vendor-tanstack-react-virtual-ClYN8CzB.js} +1 -1
  126. package/dist/assets/{vendor-tanstack-virtual-core-Dubg10g5.js → vendor-tanstack-virtual-core-BewVQryB.js} +1 -1
  127. package/dist/assets/vendor-zod-CgAdXWZO.js +39 -0
  128. package/dist/assets/{web-components-CdGfR2XJ.js → web-components-B8IcVQAA.js} +2 -2
  129. package/dist/assets/zh-CN-Bxy_Hj3r.js +16 -0
  130. package/dist/assets/zh-TW-0BB4gpHM.js +16 -0
  131. package/dist/index.html +6 -4
  132. package/dist/mini-chat.html +6 -4
  133. package/dist/mobile.html +6 -4
  134. package/package.json +12 -9
  135. package/server/built-in-extensions/registry.json +4 -0
  136. package/server/index.d.ts +2 -0
  137. package/server/index.js +47 -23
  138. package/server/lib/agent-memory/runtime.js +7 -2
  139. package/server/lib/agent-memory/runtime.test.js +22 -0
  140. package/server/lib/agent-tool/DOCUMENTATION.md +17 -4
  141. package/server/lib/agent-tool/runtime.js +62 -15
  142. package/server/lib/agent-tool/runtime.test.js +88 -2
  143. package/server/lib/changelog/update-notes.js +2 -1
  144. package/server/lib/changelog/update-notes.test.js +19 -0
  145. package/server/lib/event-stream/DOCUMENTATION.md +26 -0
  146. package/server/lib/event-stream/delta-coalescer.js +0 -0
  147. package/server/lib/event-stream/delta-coalescer.test.js +292 -0
  148. package/server/lib/event-stream/global-hub.js +68 -13
  149. package/server/lib/event-stream/global-hub.test.js +182 -0
  150. package/server/lib/event-stream/global-ws-bridge.js +16 -18
  151. package/server/lib/event-stream/index.js +4 -0
  152. package/server/lib/event-stream/protocol.js +21 -7
  153. package/server/lib/event-stream/runtime.js +9 -6
  154. package/server/lib/event-stream/runtime.test.js +60 -0
  155. package/server/lib/fs/DOCUMENTATION.md +10 -1
  156. package/server/lib/fs/git-process.test.js +77 -0
  157. package/server/lib/fs/routes.js +58 -5
  158. package/server/lib/fs/routes.test.js +116 -0
  159. package/server/lib/fs/search.js +2 -1
  160. package/server/lib/git/DOCUMENTATION.md +30 -6
  161. package/server/lib/git/routes.js +37 -11
  162. package/server/lib/git/routes.test.js +78 -0
  163. package/server/lib/git/serial-refresh.js +93 -0
  164. package/server/lib/git/serial-refresh.test.js +112 -0
  165. package/server/lib/git/service.js +588 -74
  166. package/server/lib/git/service.test.js +492 -0
  167. package/server/lib/github/DOCUMENTATION.md +5 -0
  168. package/server/lib/github/pr-status.js +5 -5
  169. package/server/lib/github/pr-status.test.js +1 -1
  170. package/server/lib/github/routes.js +6 -6
  171. package/server/lib/guests/DOCUMENTATION.md +68 -0
  172. package/server/lib/guests/auth-store.js +184 -0
  173. package/server/lib/guests/auth-store.test.js +112 -0
  174. package/server/lib/guests/background.test.js +83 -0
  175. package/server/lib/guests/builtins.js +34 -0
  176. package/server/lib/guests/builtins.test.js +174 -0
  177. package/server/lib/guests/catalog.js +477 -0
  178. package/server/lib/guests/catalog.test.js +390 -0
  179. package/server/lib/guests/clone.js +297 -0
  180. package/server/lib/guests/extract-zip.js +136 -0
  181. package/server/lib/guests/extract-zip.test.js +109 -0
  182. package/server/lib/guests/files.js +308 -0
  183. package/server/lib/guests/files.test.js +262 -0
  184. package/server/lib/guests/grant-scope.js +81 -0
  185. package/server/lib/guests/grant-scope.test.js +75 -0
  186. package/server/lib/guests/host-session.js +73 -0
  187. package/server/lib/guests/host-session.test.js +30 -0
  188. package/server/lib/guests/html-styles.js +6 -0
  189. package/server/lib/guests/html-styles.test.js +45 -0
  190. package/server/lib/guests/html-tokens.js +73 -0
  191. package/server/lib/guests/html-tokens.test.js +53 -0
  192. package/server/lib/guests/install.js +406 -0
  193. package/server/lib/guests/install.test.js +338 -0
  194. package/server/lib/guests/oauth.js +501 -0
  195. package/server/lib/guests/oauth.test.js +481 -0
  196. package/server/lib/guests/persist.js +336 -0
  197. package/server/lib/guests/persist.test.js +288 -0
  198. package/server/lib/guests/request.js +112 -0
  199. package/server/lib/guests/request.test.js +206 -0
  200. package/server/lib/guests/routes.js +838 -0
  201. package/server/lib/guests/service.js +538 -0
  202. package/server/lib/guests/service.test.js +277 -0
  203. package/server/lib/guests/sockets.js +129 -0
  204. package/server/lib/guests/sockets.test.js +110 -0
  205. package/server/lib/guests/ssh-install.test.js +188 -0
  206. package/server/lib/guests/storage.js +68 -0
  207. package/server/lib/guests/storage.test.js +59 -0
  208. package/server/lib/guests/updates.js +294 -0
  209. package/server/lib/guests/updates.test.js +334 -0
  210. package/server/lib/guests/upload.js +50 -0
  211. package/server/lib/guests/upload.test.js +131 -0
  212. package/server/lib/linear/DOCUMENTATION.md +1 -1
  213. package/server/lib/message-queue/DOCUMENTATION.md +17 -1
  214. package/server/lib/message-queue/runtime.js +34 -1
  215. package/server/lib/message-queue/runtime.test.js +83 -6
  216. package/server/lib/notifications/DOCUMENTATION.md +1 -1
  217. package/server/lib/notifications/emitter-runtime.js +4 -3
  218. package/server/lib/notifications/routes.js +5 -0
  219. package/server/lib/openchamber-control/DOCUMENTATION.md +6 -0
  220. package/server/lib/openchamber-sessions/routes.js +62 -29
  221. package/server/lib/openchamber-sessions/routes.test.js +96 -0
  222. package/server/lib/opencode/DOCUMENTATION.md +73 -5
  223. package/server/lib/opencode/bootstrap-runtime.js +1 -0
  224. package/server/lib/opencode/core-routes.js +39 -1
  225. package/server/lib/opencode/env-runtime.js +5 -1
  226. package/server/lib/opencode/env-runtime.test.js +72 -0
  227. package/server/lib/opencode/feature-routes-runtime.js +30 -1
  228. package/server/lib/opencode/lifecycle-process.test.js +84 -0
  229. package/server/lib/opencode/lifecycle.js +58 -70
  230. package/server/lib/opencode/lifecycle.test.js +29 -0
  231. package/server/lib/opencode/managed-plugin-config.js +1 -1
  232. package/server/lib/opencode/openchamber-routes.js +72 -14
  233. package/server/lib/opencode/openchamber-routes.test.js +71 -3
  234. package/server/lib/opencode/proxy.js +33 -0
  235. package/server/lib/opencode/session-runtime.js +68 -0
  236. package/server/lib/opencode/session-runtime.test.js +43 -0
  237. package/server/lib/opencode/settings-helpers.js +11 -0
  238. package/server/lib/opencode/settings-helpers.test.js +24 -2
  239. package/server/lib/opencode/settings-normalization-runtime.js +2 -0
  240. package/server/lib/opencode/settings-normalization-runtime.test.js +13 -0
  241. package/server/lib/opencode/settings-registry.json +18 -0
  242. package/server/lib/opencode/settings-runtime.js +36 -9
  243. package/server/lib/opencode/settings-runtime.test.js +158 -2
  244. package/server/lib/opencode/shutdown-runtime.js +3 -0
  245. package/server/lib/opencode/shutdown-runtime.test.js +24 -0
  246. package/server/lib/opencode/theme-archive.js +53 -0
  247. package/server/lib/opencode/theme-catalog.js +171 -0
  248. package/server/lib/opencode/theme-catalog.test.js +125 -0
  249. package/server/lib/opencode/theme-import.test.js +144 -0
  250. package/server/lib/opencode/theme-runtime.js +100 -28
  251. package/server/lib/opencode/theme-runtime.test.js +15 -0
  252. package/server/lib/permission-auto-accept/DOCUMENTATION.md +8 -0
  253. package/server/lib/permission-auto-accept/runtime.js +13 -0
  254. package/server/lib/permission-auto-accept/runtime.test.js +37 -1
  255. package/server/lib/project-context/DOCUMENTATION.md +13 -3
  256. package/server/lib/project-context/runtime.js +8 -2
  257. package/server/lib/project-context/runtime.test.js +54 -0
  258. package/server/lib/projects/DOCUMENTATION.md +2 -1
  259. package/server/lib/projects/project-config.js +40 -11
  260. package/server/lib/projects/project-config.test.js +97 -0
  261. package/server/lib/projects/project-id.js +26 -0
  262. package/server/lib/quota/DOCUMENTATION.md +0 -1
  263. package/server/lib/quota/providers/index.js +0 -7
  264. package/server/lib/realtime-proxy-terminal.test.js +150 -0
  265. package/server/lib/realtime-proxy.js +2 -0
  266. package/server/lib/relay/DOCUMENTATION.md +20 -0
  267. package/server/lib/relay/host-client.js +7 -0
  268. package/server/lib/relay/host-client.test.js +14 -0
  269. package/server/lib/routing/DOCUMENTATION.md +83 -0
  270. package/server/lib/routing/defaults.js +98 -0
  271. package/server/lib/routing/feature-flag.js +11 -0
  272. package/server/lib/routing/history.js +34 -0
  273. package/server/lib/routing/jev.js +98 -0
  274. package/server/lib/routing/routes.http.test.js +100 -0
  275. package/server/lib/routing/routes.js +81 -0
  276. package/server/lib/routing/runtime.js +217 -0
  277. package/server/lib/routing/runtime.test.js +188 -0
  278. package/server/lib/routing/store.js +249 -0
  279. package/server/lib/routing/store.test.js +96 -0
  280. package/server/lib/scheduled-tasks/DOCUMENTATION.md +9 -1
  281. package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +28 -0
  282. package/server/lib/scheduled-tasks/runtime.js +14 -4
  283. package/server/lib/scheduled-tasks/runtime.test.js +67 -0
  284. package/server/lib/security/request-security.js +9 -5
  285. package/server/lib/security/request-security.test.js +37 -0
  286. package/server/lib/session-assist/DOCUMENTATION.md +131 -66
  287. package/server/lib/session-assist/context.js +127 -0
  288. package/server/lib/session-assist/context.test.js +132 -0
  289. package/server/lib/session-assist/prompt.js +54 -0
  290. package/server/lib/session-assist/runtime.js +111 -257
  291. package/server/lib/session-assist/runtime.test.js +203 -0
  292. package/server/lib/small-model/DOCUMENTATION.md +14 -2
  293. package/server/lib/small-model/call.js +37 -6
  294. package/server/lib/small-model/call.test.js +182 -0
  295. package/server/lib/terminal/DOCUMENTATION.md +23 -2
  296. package/server/lib/terminal/runtime.js +27 -5
  297. package/server/lib/terminal/runtime.test.js +67 -0
  298. package/server/lib/terminal/shutdown.js +106 -0
  299. package/server/lib/terminal/shutdown.test.js +84 -0
  300. package/server/lib/tts/service.test.js +78 -0
  301. package/server/lib/ui-auth/DOCUMENTATION.md +8 -0
  302. package/server/lib/ui-auth/session-cookie.js +51 -0
  303. package/server/lib/ui-auth/session-cookie.test.js +46 -0
  304. package/server/lib/ui-auth/ui-auth.js +56 -17
  305. package/server/lib/ui-auth/ui-auth.test.js +96 -0
  306. package/server/lib/walkthrough/DOCUMENTATION.md +15 -5
  307. package/server/lib/walkthrough/pr-source.test.js +21 -0
  308. package/server/lib/walkthrough/pull-request.js +7 -4
  309. package/server/lib/walkthrough/pull-request.test.js +16 -0
  310. package/server/lib/walkthrough/routes.js +19 -0
  311. package/server/lib/walkthrough/routes.test.js +26 -0
  312. package/server/lib/walkthrough/sources.js +13 -2
  313. package/server/opencode-worktree-readiness.test.js +130 -0
  314. package/dist/assets/DiffView-3Dn4QHbu.js +0 -4
  315. package/dist/assets/DirtyBranchSwitchDialog-CwOerqVi.js +0 -1
  316. package/dist/assets/FilesView-BdTOpew9.js +0 -17
  317. package/dist/assets/GitView-Dqf9vHJh.js +0 -6
  318. package/dist/assets/LinearIssuesView-CmbeL7I6.js +0 -1
  319. package/dist/assets/MarkdownRendererImpl-wVZlx4dd.js +0 -12
  320. package/dist/assets/PierreDiffViewer-DCsydlKs.js +0 -161
  321. package/dist/assets/SettingsView-CcM8pF5O.js +0 -28
  322. package/dist/assets/ToolOutputDialog-BgfTbDZ4.js +0 -15
  323. package/dist/assets/WalkthroughView-BEVUDxuw.js +0 -4
  324. package/dist/assets/appThemeRegistry-BK0dHKa6.js +0 -1
  325. package/dist/assets/de-C8jV7J9y.js +0 -10
  326. package/dist/assets/es-BI6J1M5R.js +0 -16
  327. package/dist/assets/fr-D6oDFt7E.js +0 -14
  328. package/dist/assets/index-BqhiCmYS.js +0 -39
  329. package/dist/assets/index-FqSOBz0J.css +0 -1
  330. package/dist/assets/ja-C_4i5EQu.js +0 -16
  331. package/dist/assets/ko-BFbOTpbq.js +0 -16
  332. package/dist/assets/main-C9ETLKFc.js +0 -2
  333. package/dist/assets/main-CJmb2dF8.js +0 -930
  334. package/dist/assets/markdownCore-DVuZpnli.js +0 -5
  335. package/dist/assets/miniChat-B4Ytoq2n.js +0 -2
  336. package/dist/assets/mobile-BUT6-lo1.js +0 -2
  337. package/dist/assets/patchFileDiff-8j1apICU.js +0 -13
  338. package/dist/assets/pl-COFvT56l.js +0 -16
  339. package/dist/assets/pt-BR-DtVOO2Lj.js +0 -16
  340. package/dist/assets/registerCustomTheme-BBXUCRnG.js +0 -1
  341. package/dist/assets/renderElectronMiniChatApp-B6vUG-vF.js +0 -2
  342. package/dist/assets/renderMobileApp-BNgHraq6.js +0 -25
  343. package/dist/assets/runtimeEndpointReset-B8LYpWvr.js +0 -17
  344. package/dist/assets/scheduledTasksApi-GC4epIYA.js +0 -1
  345. package/dist/assets/shikiHighlight-BJISeZsQ.js +0 -1
  346. package/dist/assets/surface-BvRzS58U.js +0 -1
  347. package/dist/assets/tr-B8Q0Go2l.js +0 -7
  348. package/dist/assets/uk-Dk4a-Fn-.js +0 -16
  349. package/dist/assets/useAppFontEffects-DpFNefUA.js +0 -7284
  350. package/dist/assets/useGitComparison-Dfxaqd0s.js +0 -1
  351. package/dist/assets/useInlineCommentController-CscsE4t-.js +0 -2
  352. package/dist/assets/useRootScrollLock-BaCvuRlT.js +0 -1
  353. package/dist/assets/vendor-framer-motion-livSKyyV.js +0 -1
  354. package/dist/assets/vendor-zod-BPAANqvH.js +0 -39
  355. package/dist/assets/zh-CN-B_BXxG1Q.js +0 -16
  356. package/dist/assets/zh-TW-DLrl1SPZ.js +0 -16
  357. package/server/lib/mcp-reconnect/DOCUMENTATION.md +0 -55
  358. package/server/lib/mcp-reconnect/runtime.js +0 -122
  359. package/server/lib/mcp-reconnect/runtime.test.js +0 -140
  360. package/server/lib/quota/providers/crof.js +0 -96
  361. package/server/lib/quota/providers/crof.test.js +0 -98
  362. /package/dist/assets/{vendor-katex-k3Ppq1Ha.js → vendor-katex-CHD9BdDO.js} +0 -0
@@ -0,0 +1,174 @@
1
+ import { afterEach, describe, expect, spyOn, test } from 'bun:test';
2
+ import fs from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import express from 'express';
6
+ import request from 'supertest';
7
+
8
+ import { registerBuiltInGuests, listInstalledGuests, invalidateGuestCatalog, toPublicGuest } from './catalog.js';
9
+ import { registerGuestRoutes } from './routes.js';
10
+ import { readExtensionStore, writeExtensionStore } from './persist.js';
11
+ import { getServiceStatus, stopAllGuestServices } from './service.js';
12
+
13
+ const roots = [];
14
+ const releases = [];
15
+ const id = 'openchamber-builtin-fixture';
16
+ afterEach(async () => {
17
+ await stopAllGuestServices();
18
+ for (const release of releases.splice(0)) release();
19
+ await Promise.all(roots.splice(0).map((root) => fs.rm(root, { recursive: true, force: true })));
20
+ });
21
+
22
+ const manifest = (panelId = id) => ({
23
+ name: 'fixture', version: '1.0.0', type: 'module',
24
+ openchamber: { apiVersion: 1, contributes: {
25
+ panel: { id: panelId, name: 'Fixture', icon: 'puzzle', entry: 'panel/index.html' },
26
+ capabilities: ['files', 'sessions'],
27
+ integration: { name: 'Fixture', description: 'GitHub', token: { apiOrigin: 'https://api.github.com', scheme: 'bearer', account: { path: '/user', name: 'login' } } },
28
+ } },
29
+ });
30
+ const writePackage = async (root, definition) => {
31
+ await fs.mkdir(path.join(root, 'panel'), { recursive: true });
32
+ await fs.writeFile(path.join(root, 'package.json'), JSON.stringify(definition));
33
+ await fs.writeFile(path.join(root, 'panel/index.html'), '<script src="main.js"></script>');
34
+ await fs.writeFile(path.join(root, 'panel/main.js'), 'console.log("fixture");');
35
+ };
36
+ const fixture = async () => {
37
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-builtins-'));
38
+ roots.push(root);
39
+ const bundle = path.join(root, 'app-bundle');
40
+ const packageRoot = path.join(bundle, 'fixture');
41
+ const data = path.join(root, 'data');
42
+ const persistPath = path.join(data, 'extensions.json');
43
+ const project = path.join(root, 'project');
44
+ await fs.mkdir(project);
45
+ await fs.writeFile(path.join(project, 'README.md'), 'Fixture project');
46
+ await writePackage(packageRoot, manifest());
47
+ await fs.writeFile(path.join(bundle, 'registry.json'), JSON.stringify({ version: 1, extensions: [{ id, directory: 'fixture' }] }));
48
+ releases.push(await registerBuiltInGuests({ persistPath, root: bundle }));
49
+ const app = express();
50
+ registerGuestRoutes(app, { openchamberDataDir: data, openchamberVersion: '1.23.2', resolveGitBinaryForSpawn: () => 'git', resolveOptionalProjectDirectory: () => ({ directory: project }) });
51
+ return { root, bundle, packageRoot, persistPath, api: request(app) };
52
+ };
53
+
54
+ describe('app-owned extensions', () => {
55
+ test('each instance owns its disabled state independently', async () => {
56
+ const first = await fixture();
57
+ const second = await fixture();
58
+ await first.api.put(`/api/guests/${id}/enabled`).send({ enabled: false }).expect(200);
59
+ expect((await first.api.get('/api/guests').expect(200)).body.guests[0].enabled).toBe(false);
60
+ expect((await second.api.get('/api/guests').expect(200)).body.guests[0].enabled).toBe(true);
61
+ });
62
+ test('ships enabled with its exact declared grants and no persisted approval', async () => {
63
+ const { api, persistPath } = await fixture();
64
+ const response = await api.get('/api/guests').expect(200);
65
+ const guest = response.body.guests[0];
66
+ expect(guest.source).toBe('bundled');
67
+ expect(guest.enabled).toBe(true);
68
+ expect(guest.path).toBeNull();
69
+ expect([...guest.capabilities.granted].sort()).toEqual([...guest.capabilities.requested].sort());
70
+ expect(guest.capabilities.granted).toContain('files');
71
+ expect(guest.capabilities.granted).toContain('network');
72
+ expect((await readExtensionStore(persistPath)).capabilityGrants).toEqual({});
73
+ await api.post(`/api/guests/${id}/files`).send({ op: 'read', path: 'README.md' }).expect(200);
74
+ const deniedModel = await api.post(`/api/guests/${id}/generate`).send({ prompt: 'not declared' }).expect(400);
75
+ expect(deniedModel.body.error).toBe('NOT_GRANTED');
76
+ await api.put(`/api/guests/${id}/capabilities`).send({ granted: [] }).expect(400, { error: 'bundled' });
77
+ await api.delete(`/api/guests/${id}`).expect(400, { error: 'bundled' });
78
+ await api.post(`/api/guests/${id}/update`).expect(400, { error: 'not-git' });
79
+ });
80
+
81
+ test('disable survives a new application version and preserves storage and credentials', async () => {
82
+ const { api, persistPath, packageRoot, bundle } = await fixture();
83
+ await api.post(`/api/guests/${id}/storage`).send({ op: 'set', key: 'note', value: 'keep this' }).expect(200);
84
+ const fetch = spyOn(globalThis, 'fetch').mockResolvedValue(Response.json({ login: 'fixture' }));
85
+ try {
86
+ await api.put(`/api/guests/${id}/token`).send({ token: 'fixture-token' }).expect(200);
87
+ } finally { fetch.mockRestore(); }
88
+ await api.put(`/api/guests/${id}/enabled`).send({ enabled: false }).expect(200);
89
+ const deniedFile = await api.post(`/api/guests/${id}/files`).send({ op: 'read', path: 'README.md' }).expect(400);
90
+ expect(deniedFile.body.error).toBe('DISABLED');
91
+ const blockedFetch = spyOn(globalThis, 'fetch').mockRejectedValue(new Error('Disabled extension reached the provider'));
92
+ try {
93
+ const deniedRequest = await api.post(`/api/guests/${id}/request`).send({ method: 'GET', path: '/user' }).expect(400);
94
+ expect(deniedRequest.body.error).toBe('DISABLED');
95
+ expect(blockedFetch.mock.calls).toHaveLength(0);
96
+ } finally { blockedFetch.mockRestore(); }
97
+ const next = manifest();
98
+ next.version = '2.0.0';
99
+ next.openchamber.contributes.capabilities.push('model');
100
+ await writePackage(packageRoot, next);
101
+ releases.push(await registerBuiltInGuests({ persistPath, root: bundle }));
102
+ const updated = (await api.get('/api/guests').expect(200)).body.guests[0];
103
+ expect(updated.enabled).toBe(false);
104
+ expect(updated.version).toBe('2.0.0');
105
+ expect(updated.capabilities.granted).toContain('model');
106
+ expect((await readExtensionStore(persistPath)).disabledGuests[id]).toBe(true);
107
+ await api.put(`/api/guests/${id}/enabled`).send({ enabled: true }).expect(200);
108
+ const saved = await api.post(`/api/guests/${id}/storage`).send({ op: 'get', key: 'note' }).expect(200);
109
+ expect(saved.body.value).toBe('keep this');
110
+ expect((await api.get(`/api/guests/${id}/oauth/status`).expect(200)).body.connected).toBe(true);
111
+ });
112
+
113
+ test('a package cannot claim built-in trust or replace a reserved ID', async () => {
114
+ const { api, root, persistPath } = await fixture();
115
+ const forged = path.join(root, 'forged');
116
+ const definition = manifest('third-party');
117
+ definition.builtin = true;
118
+ definition.openchamber.builtin = true;
119
+ await writePackage(forged, definition);
120
+ const installed = await api.post('/api/guests').send({ path: forged, source: 'bundled' }).expect(201);
121
+ expect(installed.body.guest.source).toBe('path');
122
+ expect(installed.body.guest.capabilities.granted).toEqual([]);
123
+ await writePackage(forged, manifest());
124
+ await api.post('/api/guests').send({ path: forged, replace: true }).expect(400, { error: 'reserved-id' });
125
+ await writeExtensionStore(persistPath, { paths: [forged], sources: {}, capabilityGrants: { [id]: ['files', 'network'] } });
126
+ const guests = await listInstalledGuests({ persistPath });
127
+ expect(guests).toHaveLength(1);
128
+ expect(guests[0].source).toBe('bundled');
129
+ expect(guests[0].packageRoot).not.toBe(forged);
130
+ });
131
+
132
+ test('automatic grants never move a saved token to a changed provider origin', async () => {
133
+ const { api, packageRoot, persistPath } = await fixture();
134
+ const fetch = spyOn(globalThis, 'fetch').mockResolvedValue(Response.json({ login: 'fixture' }));
135
+ try { await api.put(`/api/guests/${id}/token`).send({ token: 'fixture-token' }).expect(200); }
136
+ finally { fetch.mockRestore(); }
137
+ const definition = manifest();
138
+ definition.openchamber.contributes.integration.token.apiOrigin = 'https://api.example.com';
139
+ await writePackage(packageRoot, definition);
140
+ invalidateGuestCatalog(persistPath);
141
+ expect((await api.get('/api/guests').expect(200)).body.guests[0].capabilities.granted).toContain('network');
142
+ expect((await api.get(`/api/guests/${id}/oauth/status`).expect(200)).body.connected).toBe(false);
143
+ });
144
+
145
+ test('rejects invalid registries and does not elevate symlinked packages outside the bundle', async () => {
146
+ const { root, bundle, persistPath } = await fixture();
147
+ const outside = path.join(root, 'outside');
148
+ await writePackage(outside, manifest());
149
+ const broken = path.join(bundle, 'linked');
150
+ await fs.symlink(outside, broken, 'dir');
151
+ await fs.writeFile(path.join(bundle, 'registry.json'), JSON.stringify({ version: 1, extensions: [{ id, directory: '../outside' }] }));
152
+ await expect(registerBuiltInGuests({ persistPath, root: bundle })).rejects.toThrow();
153
+ const otherId = 'openchamber-builtin-linked';
154
+ await fs.writeFile(path.join(bundle, 'registry.json'), JSON.stringify({ version: 1, extensions: [{ id, directory: 'fixture' }, { id: otherId, directory: 'linked' }] }));
155
+ releases.push(await registerBuiltInGuests({ persistPath, root: bundle }));
156
+ const guests = await listInstalledGuests({ persistPath });
157
+ expect(guests.map((guest) => guest.id)).toEqual([id]);
158
+ expect(toPublicGuest(guests[0]).capabilities.granted).toContain('network');
159
+ });
160
+
161
+ test('disable stops an automatically approved built-in service', async () => {
162
+ const { api, packageRoot, persistPath } = await fixture();
163
+ const definition = manifest();
164
+ definition.openchamber.contributes.service = { runtime: 'host', entry: 'service.mjs' };
165
+ await fs.writeFile(path.join(packageRoot, 'service.mjs'), `import http from 'node:http';
166
+ http.createServer((req,res)=>{ if(req.headers.authorization!=='Bearer '+process.env.OPENCHAMBER_SERVICE_TOKEN){res.writeHead(401);res.end();return;}res.end('ok'); }).listen(Number(process.env.OPENCHAMBER_SERVICE_PORT),'127.0.0.1');`);
167
+ await fs.writeFile(path.join(packageRoot, 'package.json'), JSON.stringify(definition));
168
+ invalidateGuestCatalog(persistPath);
169
+ await api.post(`/api/guests/${id}/service/request`).send({ method: 'GET', path: '/test' }).expect(200);
170
+ expect(getServiceStatus(id)).toBe('ready');
171
+ await api.put(`/api/guests/${id}/enabled`).send({ enabled: false }).expect(200);
172
+ expect(getServiceStatus(id)).toBe('stopped');
173
+ });
174
+ });
@@ -0,0 +1,477 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ import { hasGuestPage, requestedGuestCapabilities, resolveAttachEntry, resolveAttachMode, resolvePageEntry, toPublicService, toPublicIntegration, hostMeetsOpenChamberEngine, openChamberEngineMinimum } from '@openchamber/sdk';
5
+ import { parseManifestJson } from '@openchamber/sdk/schemas';
6
+
7
+ import { listRelativeGuestScriptHrefs, resolveGuestHtmlRelativePath } from './html-tokens.js';
8
+ import { effectiveGrants, guestGrantScope } from './grant-scope.js';
9
+ import { onExtensionStoreWrite, readExtensionStore } from './persist.js';
10
+ import { buildPublicSocketBindings } from './sockets.js';
11
+ import { isReservedBuiltInId, readBuiltInRegistry } from './builtins.js';
12
+
13
+ const builtInsByStore = new Map();
14
+
15
+ /** Registers the app-shipped catalog for one server instance, never a user install directory. */
16
+ export const registerBuiltInGuests = async ({ persistPath, root }) => {
17
+ const registry = await readBuiltInRegistry(root);
18
+ builtInsByStore.set(persistPath, registry);
19
+ invalidateGuestCatalog(persistPath);
20
+ return () => {
21
+ if (builtInsByStore.get(persistPath) !== registry) return;
22
+ builtInsByStore.delete(persistPath);
23
+ invalidateGuestCatalog(persistPath);
24
+ };
25
+ };
26
+
27
+ const PANEL_ID = /^[a-z][a-z0-9-]*$/;
28
+
29
+ const MIME_BY_EXT = {
30
+ '.html': 'text/html; charset=utf-8',
31
+ '.js': 'text/javascript; charset=utf-8',
32
+ '.css': 'text/css; charset=utf-8',
33
+ '.svg': 'image/svg+xml',
34
+ '.json': 'application/json',
35
+ '.png': 'image/png',
36
+ };
37
+
38
+ export const isGuestPanelId = (value) => typeof value === 'string' && PANEL_ID.test(value);
39
+
40
+ export const resolveGuestAssetPath = async (packageRoot, relativePath) => {
41
+ if (typeof relativePath !== 'string' || relativePath.includes('\0') || relativePath.includes('\\')) {
42
+ return null;
43
+ }
44
+ const segments = relativePath.split('/');
45
+ if (segments.some((segment) => segment === '' || segment === '.' || segment === '..')) {
46
+ return null;
47
+ }
48
+ if (relativePath.startsWith('/') || relativePath.includes('://')) {
49
+ return null;
50
+ }
51
+
52
+ const rootReal = await fs.realpath(packageRoot);
53
+ const candidate = path.resolve(rootReal, ...segments);
54
+ if (candidate !== rootReal && !candidate.startsWith(rootReal + path.sep)) {
55
+ return null;
56
+ }
57
+
58
+ try {
59
+ const resolved = await fs.realpath(candidate);
60
+ if (resolved !== rootReal && !resolved.startsWith(rootReal + path.sep)) {
61
+ return null;
62
+ }
63
+ const stat = await fs.stat(resolved);
64
+ if (!stat.isFile()) {
65
+ return null;
66
+ }
67
+ return resolved;
68
+ } catch {
69
+ return null;
70
+ }
71
+ };
72
+
73
+ export const guestAssetContentType = (filePath) => {
74
+ return MIME_BY_EXT[path.extname(filePath).toLowerCase()] ?? null;
75
+ };
76
+
77
+ /** Documents and scripts are served only to extensions with an execution entry. */
78
+ const isGuestFrameContentType = (contentType) => (
79
+ contentType.startsWith('text/html') || contentType.startsWith('text/javascript')
80
+ );
81
+
82
+ /**
83
+ * A `.js` URL can be served from a sibling `.ts` that the host compiles.
84
+ * `hasRuntime` means the guest declared panel.entry or background.entry.
85
+ * Tools-only packages can serve assets, never HTML or JS.
86
+ */
87
+ export const resolveGuestServedFile = async (packageRoot, relativePath, { hasRuntime = true } = {}) => {
88
+ const filePath = await resolveGuestAssetPath(packageRoot, relativePath);
89
+ const contentType = filePath ? guestAssetContentType(filePath) : null;
90
+ if (contentType && !hasRuntime && isGuestFrameContentType(contentType)) {
91
+ return null;
92
+ }
93
+ if (filePath && contentType) {
94
+ return { filePath, contentType };
95
+ }
96
+ if (!hasRuntime || !relativePath.endsWith('.js')) {
97
+ return null;
98
+ }
99
+ const tsPath = await resolveGuestAssetPath(packageRoot, `${relativePath.slice(0, -3)}.ts`);
100
+ if (!tsPath) {
101
+ return null;
102
+ }
103
+ return {
104
+ filePath: `${tsPath.slice(0, -3)}.js`,
105
+ contentType: MIME_BY_EXT['.js'],
106
+ };
107
+ };
108
+
109
+ export const resolveGuestPackageRoot = async (rawPath) => {
110
+ if (rawPath === '' || rawPath == null) {
111
+ return null;
112
+ }
113
+ const text = `${rawPath}`;
114
+ if (!path.isAbsolute(text) || text.includes('\0')) {
115
+ return null;
116
+ }
117
+ try {
118
+ const real = await fs.realpath(text);
119
+ const stat = await fs.stat(real);
120
+ if (!stat.isDirectory()) {
121
+ return null;
122
+ }
123
+ return real;
124
+ } catch {
125
+ return null;
126
+ }
127
+ };
128
+
129
+ /** Every relative `script src` on the entry page must be a real `.js` file. TypeScript is not enough. */
130
+ const guestBuiltScriptsReady = async (packageRoot, entry) => {
131
+ const entryPath = await resolveGuestAssetPath(packageRoot, entry);
132
+ if (!entryPath) {
133
+ return false;
134
+ }
135
+ const html = await fs.readFile(entryPath, 'utf8');
136
+ for (const href of listRelativeGuestScriptHrefs(html)) {
137
+ const relativePath = resolveGuestHtmlRelativePath(entry, href);
138
+ if (!relativePath || !relativePath.endsWith('.js')) {
139
+ return false;
140
+ }
141
+ const filePath = await resolveGuestAssetPath(packageRoot, relativePath);
142
+ if (!filePath) {
143
+ return false;
144
+ }
145
+ }
146
+ return true;
147
+ };
148
+
149
+ export const inspectGuestPackage = async (packageRoot, { openchamberVersion, skipEngineCheck } = {}) => {
150
+ let raw;
151
+ try {
152
+ raw = await fs.readFile(path.join(packageRoot, 'package.json'), 'utf8');
153
+ } catch {
154
+ return { ok: false, code: 'invalid-manifest' };
155
+ }
156
+ const parsed = parseManifestJson(raw);
157
+ if (!parsed.ok) {
158
+ return { ok: false, code: 'invalid-manifest' };
159
+ }
160
+ if (!skipEngineCheck && !parsed.version) {
161
+ return { ok: false, code: 'invalid-manifest' };
162
+ }
163
+ const engine = parsed.manifest.engines?.openchamber;
164
+ if (engine && !skipEngineCheck) {
165
+ const hostVersion = typeof openchamberVersion === 'string' ? openchamberVersion : '';
166
+ if (!hostMeetsOpenChamberEngine(hostVersion, engine)) {
167
+ return {
168
+ ok: false,
169
+ code: 'host-too-old',
170
+ required: openChamberEngineMinimum(engine) ?? engine,
171
+ };
172
+ }
173
+ }
174
+ const panel = parsed.manifest.contributes.panel;
175
+ // The visible panel and background runtime are optional independent entries.
176
+ if (hasGuestPage(parsed.manifest.contributes)) {
177
+ const entryPath = await resolveGuestAssetPath(packageRoot, panel.entry);
178
+ if (!entryPath) {
179
+ return { ok: false, code: 'invalid-manifest' };
180
+ }
181
+ }
182
+ if (panel.icon.toLowerCase().endsWith('.svg')) {
183
+ const iconPath = await resolveGuestAssetPath(packageRoot, panel.icon);
184
+ if (!iconPath) {
185
+ return { ok: false, code: 'invalid-manifest' };
186
+ }
187
+ }
188
+ if (panel.entry && !await guestBuiltScriptsReady(packageRoot, panel.entry)) {
189
+ return { ok: false, code: 'missing-build' };
190
+ }
191
+ const guest = {
192
+ id: panel.id,
193
+ name: panel.name,
194
+ icon: panel.icon,
195
+ packageRoot,
196
+ };
197
+ if (panel.entry) {
198
+ guest.entry = panel.entry;
199
+ }
200
+ const backgroundEntry = parsed.manifest.contributes.background?.entry;
201
+ if (backgroundEntry) {
202
+ if (!await resolveGuestAssetPath(packageRoot, backgroundEntry)) {
203
+ return { ok: false, code: 'invalid-manifest' };
204
+ }
205
+ if (!await guestBuiltScriptsReady(packageRoot, backgroundEntry)) {
206
+ return { ok: false, code: 'missing-build' };
207
+ }
208
+ guest.backgroundEntry = backgroundEntry;
209
+ }
210
+ if (parsed.version) {
211
+ guest.version = parsed.version;
212
+ }
213
+ if (parsed.manifest.engines) {
214
+ guest.engines = parsed.manifest.engines;
215
+ }
216
+ const attach = resolveAttachMode(parsed.manifest.contributes.attach);
217
+ if (attach) {
218
+ guest.attach = attach;
219
+ }
220
+ // A dialog page is checked like panel.entry: the HTML must exist and every
221
+ // relative script it loads must already be built.
222
+ const attachEntry = resolveAttachEntry(parsed.manifest.contributes);
223
+ if (attachEntry) {
224
+ if (!await resolveGuestAssetPath(packageRoot, attachEntry)) {
225
+ return { ok: false, code: 'invalid-manifest' };
226
+ }
227
+ if (!await guestBuiltScriptsReady(packageRoot, attachEntry)) {
228
+ return { ok: false, code: 'missing-build' };
229
+ }
230
+ guest.attachEntry = attachEntry;
231
+ }
232
+ const pageEntry = resolvePageEntry(parsed.manifest.contributes);
233
+ if (pageEntry) {
234
+ if (!await resolveGuestAssetPath(packageRoot, pageEntry)) {
235
+ return { ok: false, code: 'invalid-manifest' };
236
+ }
237
+ if (!await guestBuiltScriptsReady(packageRoot, pageEntry)) {
238
+ return { ok: false, code: 'missing-build' };
239
+ }
240
+ guest.pageEntry = pageEntry;
241
+ const page = parsed.manifest.contributes.page;
242
+ if (page !== true && page?.title) guest.pageTitle = page.title;
243
+ }
244
+ if (parsed.manifest.contributes.capabilities?.length) {
245
+ guest.capabilities = [...parsed.manifest.contributes.capabilities];
246
+ }
247
+ if (parsed.manifest.contributes.integration) {
248
+ guest.integration = parsed.manifest.contributes.integration;
249
+ }
250
+ if (parsed.manifest.contributes.filesystem?.length) {
251
+ guest.filesystem = [...parsed.manifest.contributes.filesystem];
252
+ }
253
+ if (parsed.manifest.contributes.actions?.length) {
254
+ guest.actions = parsed.manifest.contributes.actions.map((action) => ({ ...action }));
255
+ }
256
+ if (parsed.manifest.contributes.commands?.length) {
257
+ guest.commands = parsed.manifest.contributes.commands.map((command) => ({ ...command }));
258
+ }
259
+ if (parsed.manifest.contributes.tools?.length) {
260
+ guest.tools = parsed.manifest.contributes.tools.map((tool) => ({ ...tool }));
261
+ }
262
+ if (parsed.manifest.contributes.service) {
263
+ const serviceEntry = await resolveGuestAssetPath(packageRoot, parsed.manifest.contributes.service.entry);
264
+ if (!serviceEntry) {
265
+ return { ok: false, code: 'missing-build' };
266
+ }
267
+ guest.service = parsed.manifest.contributes.service;
268
+ }
269
+ return { ok: true, guest };
270
+ };
271
+
272
+ const loadGuestFromPackageRoot = async (packageRoot, options) => {
273
+ const result = await inspectGuestPackage(packageRoot, options);
274
+ return result.ok ? result.guest : null;
275
+ };
276
+
277
+ const withSource = (guest, source, displayPath) => ({
278
+ ...guest,
279
+ source,
280
+ path: displayPath,
281
+ });
282
+
283
+ /** Catalog JSON. Drops packageRoot. Keeps attach only when true. `entry` is absent for a page-less guest. */
284
+ export const toPublicGuest = (guest) => {
285
+ const row = {
286
+ id: guest.id,
287
+ name: guest.name,
288
+ icon: guest.icon,
289
+ source: guest.source,
290
+ path: guest.path ?? null,
291
+ enabled: guest.enabled !== false,
292
+ };
293
+ if (guest.entry) {
294
+ row.entry = guest.entry;
295
+ }
296
+ if (guest.backgroundEntry) {
297
+ row.backgroundEntry = guest.backgroundEntry;
298
+ }
299
+ if (typeof guest.version === 'string' && guest.version) {
300
+ row.version = guest.version;
301
+ }
302
+ // Git installs remember where they came from so the UI can offer updates.
303
+ // The URL is the one the user typed at install; it carries no credentials.
304
+ if (guest.source === 'git' && guest.gitOrigin && typeof guest.gitOrigin.url === 'string') {
305
+ row.origin = guest.gitOrigin.ref
306
+ ? { url: guest.gitOrigin.url, ref: guest.gitOrigin.ref }
307
+ : { url: guest.gitOrigin.url };
308
+ }
309
+ if (guest.update && typeof guest.update.version === 'string' && guest.update.version) {
310
+ row.update = { version: guest.update.version };
311
+ }
312
+ const attach = resolveAttachMode(guest.attach);
313
+ if (guest.pageEntry) row.pageEntry = guest.pageEntry;
314
+ if (guest.pageTitle) row.pageTitle = guest.pageTitle;
315
+ if (attach) {
316
+ row.attach = attach;
317
+ }
318
+ if (attach === 'dialog' && typeof guest.attachEntry === 'string' && guest.attachEntry) {
319
+ row.attachEntry = guest.attachEntry;
320
+ }
321
+ if (guest.integration) {
322
+ row.integration = toPublicIntegration(guest.integration);
323
+ }
324
+ if (Array.isArray(guest.filesystem) && guest.filesystem.length > 0) {
325
+ row.filesystem = [...guest.filesystem];
326
+ }
327
+ // Actions, commands, and tools are the parsed manifest entries as they
328
+ // are: the UI decides which ones to apply from the grant and the enabled flag.
329
+ if (Array.isArray(guest.actions) && guest.actions.length > 0) {
330
+ row.actions = guest.actions.map((action) => ({ ...action }));
331
+ }
332
+ if (Array.isArray(guest.commands) && guest.commands.length > 0) {
333
+ row.commands = guest.commands.map((command) => ({ ...command }));
334
+ }
335
+ if (Array.isArray(guest.tools) && guest.tools.length > 0) {
336
+ row.tools = guest.tools.map((tool) => ({ ...tool }));
337
+ }
338
+ const granted = Array.isArray(guest.capabilityGrants) ? guest.capabilityGrants : [];
339
+ row.capabilities = {
340
+ requested: requestedGuestCapabilities(guest),
341
+ granted,
342
+ };
343
+ const service = toPublicService(
344
+ guest.service,
345
+ granted.includes('service'),
346
+ guest.socketBindings,
347
+ );
348
+ if (service) {
349
+ row.service = service;
350
+ }
351
+ return row;
352
+ };
353
+
354
+ /**
355
+ * The rail asks for every panel file through `findInstalledGuest`, and each
356
+ * listing re-reads every package's manifest and HTML. A short-lived cache per
357
+ * store file absorbs that burst; any store write drops it, and the TTL bounds
358
+ * how long an on-disk edit to a folder-installed package goes unnoticed.
359
+ */
360
+ const CATALOG_CACHE_TTL_MS = 5_000;
361
+ /** @type {Map<string, { expiresAt: number, guests: Awaited<ReturnType<typeof listInstalledGuestsUncached>> }>} */
362
+ const catalogCache = new Map();
363
+ /**
364
+ * Bumped on every invalidation. A listing remembers the version it started
365
+ * at and only caches its result if nothing changed meanwhile, so a read that
366
+ * straddled a store write (old file, new grants) never sticks for the TTL.
367
+ * @type {Map<string, number>}
368
+ */
369
+ const catalogVersions = new Map();
370
+
371
+ const catalogVersionOf = (persistPath) => catalogVersions.get(persistPath) ?? 0;
372
+
373
+ export const invalidateGuestCatalog = (persistPath) => {
374
+ if (persistPath) {
375
+ catalogCache.delete(persistPath);
376
+ catalogVersions.set(persistPath, catalogVersionOf(persistPath) + 1);
377
+ } else {
378
+ for (const key of catalogCache.keys()) {
379
+ catalogVersions.set(key, catalogVersionOf(key) + 1);
380
+ }
381
+ catalogCache.clear();
382
+ }
383
+ };
384
+ onExtensionStoreWrite(invalidateGuestCatalog);
385
+
386
+ export const listInstalledGuests = async ({ persistPath } = {}) => {
387
+ const cached = persistPath ? catalogCache.get(persistPath) : undefined;
388
+ if (cached && cached.expiresAt > Date.now()) {
389
+ return cached.guests;
390
+ }
391
+ const version = persistPath ? catalogVersionOf(persistPath) : 0;
392
+ const guests = await listInstalledGuestsUncached({ persistPath });
393
+ if (persistPath && catalogVersionOf(persistPath) === version) {
394
+ catalogCache.set(persistPath, { guests, expiresAt: Date.now() + CATALOG_CACHE_TTL_MS });
395
+ }
396
+ return guests;
397
+ };
398
+
399
+ const listInstalledGuestsUncached = async ({ persistPath } = {}) => {
400
+ const guests = [];
401
+ const seen = new Set();
402
+
403
+ const stored = await readExtensionStore(persistPath);
404
+ const builtIns = builtInsByStore.get(persistPath);
405
+ if (builtIns) {
406
+ for (const entry of builtIns.extensions) {
407
+ // Reserve the ID even when this individual package is broken.
408
+ seen.add(entry.id);
409
+ const root = await fs.realpath(path.join(builtIns.root, entry.directory)).catch(() => null);
410
+ if (!root || !root.startsWith(builtIns.root + path.sep)) {
411
+ console.warn(`Built-in extension is unavailable: ${entry.id}`);
412
+ continue;
413
+ }
414
+ const guest = await loadGuestFromPackageRoot(root, { skipEngineCheck: true }).catch(() => null);
415
+ if (!guest || guest.id !== entry.id) {
416
+ console.warn(`Built-in extension is invalid: ${entry.id}`);
417
+ continue;
418
+ }
419
+ const socketBindings = guest.service?.permissions?.sockets?.length
420
+ ? await buildPublicSocketBindings(guest.service.permissions.sockets, stored.serviceSocketOverrides?.[guest.id] ?? {})
421
+ : undefined;
422
+ guests.push({
423
+ ...withSource(guest, 'bundled', null),
424
+ capabilityGrants: requestedGuestCapabilities(guest),
425
+ enabled: !stored.disabledGuests?.[guest.id],
426
+ socketBindings,
427
+ });
428
+ }
429
+ }
430
+ for (const storedPath of stored.paths) {
431
+ const root = await resolveGuestPackageRoot(storedPath);
432
+ if (!root) {
433
+ continue;
434
+ }
435
+ // Already-installed packages stay listed even if engines.openchamber is newer
436
+ // than this host. Install is the gate.
437
+ const guest = await loadGuestFromPackageRoot(root, { skipEngineCheck: true });
438
+ if (!guest || seen.has(guest.id) || isReservedBuiltInId(guest.id)) {
439
+ continue;
440
+ }
441
+ seen.add(guest.id);
442
+ const source = stored.sources[root] ?? stored.sources[storedPath] ?? 'path';
443
+ const socketBindings = guest.service?.permissions?.sockets?.length
444
+ ? await buildPublicSocketBindings(
445
+ guest.service.permissions.sockets,
446
+ stored.serviceSocketOverrides?.[guest.id] ?? {},
447
+ )
448
+ : undefined;
449
+ const gitOrigin = source === 'git'
450
+ ? stored.gitOrigins[root] ?? stored.gitOrigins[storedPath]
451
+ : undefined;
452
+ guests.push({
453
+ ...withSource(guest, source, root),
454
+ gitOrigin,
455
+ // Grants are narrowed to what the user actually approved for this
456
+ // version: a widened filesystem list, a new API origin, or new service
457
+ // permissions drop that capability until the dialog runs again.
458
+ capabilityGrants: effectiveGrants(
459
+ stored.capabilityGrants?.[guest.id] ?? [],
460
+ stored.capabilityScopes?.[guest.id],
461
+ guestGrantScope(guest),
462
+ ),
463
+ enabled: !stored.disabledGuests?.[guest.id],
464
+ socketBindings,
465
+ });
466
+ }
467
+
468
+ return guests;
469
+ };
470
+
471
+ export const findInstalledGuest = async (id, persistPath) => {
472
+ if (!isGuestPanelId(id)) {
473
+ return null;
474
+ }
475
+ const guests = await listInstalledGuests({ persistPath });
476
+ return guests.find((guest) => guest.id === id) ?? null;
477
+ };