@chainingintention/pi-web-cn 2.0.0 → 2.202610.0

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 (584) hide show
  1. package/README.md +61 -106
  2. package/dist/.plugins-ready +1 -0
  3. package/dist/cli.js +331 -174
  4. package/dist/cli.js.map +1 -1
  5. package/dist/client/apple-touch-icon-dev.png +0 -0
  6. package/dist/client/assets/PiWebApp-BXSziMEg.js +4921 -0
  7. package/dist/client/assets/classic-CyM97Akm.css +54 -0
  8. package/dist/client/assets/external-B9K8duyP.js +2 -0
  9. package/dist/client/assets/graphite-DquaqFXU.css +54 -0
  10. package/dist/client/assets/index-cdtUOfOW.js +2 -0
  11. package/dist/client/assets/paper-CB1oLuRr.css +54 -0
  12. package/dist/client/assets/pi-web-dark-D_KtbM95.css +54 -0
  13. package/dist/client/assets/pi-web-light-DSNLMIBI.css +54 -0
  14. package/dist/client/assets/vendor-editor-core-BfnHL1Dg.js +10 -0
  15. package/dist/client/assets/vendor-editor-languages-bNquDJP0.js +28 -0
  16. package/dist/client/favicon-dev.svg +8 -0
  17. package/dist/client/index.html +7 -51
  18. package/dist/client/manifest.webmanifest +2 -1
  19. package/dist/client/pwa-icon-dev-192.png +0 -0
  20. package/dist/client/pwa-icon-dev-512.png +0 -0
  21. package/dist/config.js +55 -8
  22. package/dist/config.js.map +1 -1
  23. package/dist/docker/piWebDockerCommandPlan.js +13 -4
  24. package/dist/docker/piWebDockerCommandPlan.js.map +1 -1
  25. package/dist/environment.js +4 -0
  26. package/dist/environment.js.map +1 -0
  27. package/dist/localization.js +24 -0
  28. package/dist/localization.js.map +1 -0
  29. package/dist/nativeServices/installedServiceDefinitions.js +617 -0
  30. package/dist/nativeServices/installedServiceDefinitions.js.map +1 -0
  31. package/dist/nativeServices/serviceAction.js +78 -20
  32. package/dist/nativeServices/serviceAction.js.map +1 -1
  33. package/dist/nativeServices/serviceDoctor.js +418 -114
  34. package/dist/nativeServices/serviceDoctor.js.map +1 -1
  35. package/dist/nativeServices/servicePlan.js +2 -2
  36. package/dist/nativeServices/servicePlan.js.map +1 -1
  37. package/dist/pi-packages/captains-log/README.md +11 -0
  38. package/dist/pi-packages/captains-log/dist/browser/channelProtocol.js +29 -0
  39. package/dist/pi-packages/captains-log/dist/browser/index.js +143 -0
  40. package/dist/pi-packages/captains-log/dist/browser/markdown.js +68 -0
  41. package/dist/pi-packages/captains-log/dist/browser/panel.js +148 -0
  42. package/dist/pi-packages/captains-log/dist/browser/protocol.js +26 -0
  43. package/dist/pi-packages/captains-log/dist/companion.js +126 -0
  44. package/dist/pi-packages/captains-log/dist/roundtrip.js +62 -0
  45. package/dist/pi-packages/captains-log/dist/server.js +210 -0
  46. package/dist/pi-packages/captains-log/dist/store.js +75 -0
  47. package/dist/pi-packages/captains-log/docs/usage.md +29 -0
  48. package/dist/pi-packages/captains-log/i18n.js +7 -0
  49. package/dist/pi-packages/captains-log/package.json +35 -0
  50. package/dist/pi-packages/captains-log/tsconfig.json +21 -0
  51. package/dist/pi-packages/captains-log/vite.config.mjs +28 -0
  52. package/dist/pi-packages/relays/extensions/index.js +61 -0
  53. package/dist/pi-packages/relays/i18n.js +7 -0
  54. package/dist/{pi-web-plugins → pi-packages}/relays/markdownDocument.js +3 -2
  55. package/dist/pi-packages/relays/package.json +20 -0
  56. package/dist/pi-packages/relays/pi-web-plugin.js +96 -0
  57. package/dist/pi-packages/relays/prompts/relay-worktree.md +18 -0
  58. package/dist/pi-packages/relays/prompts/relay.md +18 -0
  59. package/dist/{pi-web-plugins → pi-packages}/relays/relayDiscovery.js +2 -2
  60. package/dist/pi-packages/relays/relayDispatch.js +67 -0
  61. package/dist/pi-packages/relays/relayIdentity.js +37 -0
  62. package/dist/{pi-web-plugins → pi-packages}/relays/relaysPanelElement.js +98 -59
  63. package/dist/pi-packages/relays/skills/relay/SKILL.md +50 -0
  64. package/dist/pi-packages/relays/skills/relay-runner/SKILL.md +74 -0
  65. package/dist/pi-packages/workflows/browser/pi-web-plugin.js +42 -0
  66. package/dist/pi-packages/workflows/browser/worktreeDialog.js +170 -0
  67. package/dist/pi-packages/workflows/package.json +23 -0
  68. package/dist/pi-packages/workflows/prompts/worktree.md +21 -0
  69. package/dist/pi-packages/workflows/server-plugin.js +586 -0
  70. package/dist/pi-packages/workflows/vite.config.mjs +17 -0
  71. package/dist/pi-web-plugins/code-highlight/browser/pi-web-plugin.js +22 -0
  72. package/dist/pi-web-plugins/code-highlight/package.json +13 -0
  73. package/dist/pi-web-plugins/files/browser/assets/files-icon-DZObYhxb.svg +3 -0
  74. package/dist/pi-web-plugins/files/browser/pi-web-plugin.js +466 -0
  75. package/dist/pi-web-plugins/files/package.json +15 -0
  76. package/dist/pi-web-plugins/git/browser/git-contract.js +22 -11
  77. package/dist/pi-web-plugins/git/browser/git-panel.js +228 -100
  78. package/dist/pi-web-plugins/git/browser/i18n.js +7 -0
  79. package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +28 -4
  80. package/dist/pi-web-plugins/git/git-backend.js +61 -21
  81. package/dist/pi-web-plugins/git/gitStatusCache.js +48 -0
  82. package/dist/pi-web-plugins/git/i18n.js +7 -0
  83. package/dist/pi-web-plugins/git/package.json +1 -0
  84. package/dist/pi-web-plugins/git/server-plugin.js +45 -24
  85. package/dist/pi-web-plugins/git/workspace-discovery-notify.cjs +14 -0
  86. package/dist/pi-web-plugins/git/workspaceDiscoveryHooks.js +186 -0
  87. package/dist/pi-web-plugins/info/i18n.js +7 -0
  88. package/dist/pi-web-plugins/info/infoInternals.js +76 -65
  89. package/dist/pi-web-plugins/info/package.json +1 -1
  90. package/dist/pi-web-plugins/info/pi-web-plugin.js +40 -42
  91. package/dist/pi-web-plugins/mermaid/browser/mermaid-engine.js +4255 -0
  92. package/dist/pi-web-plugins/mermaid/browser/pi-web-plugin.js +11 -0
  93. package/dist/pi-web-plugins/mermaid/package.json +13 -0
  94. package/dist/pi-web-plugins/terminal/browser/pi-web-plugin.js +219 -0
  95. package/dist/pi-web-plugins/terminal/i18n.js +7 -0
  96. package/dist/pi-web-plugins/terminal/package.json +16 -0
  97. package/dist/pi-web-plugins/terminal/server-plugin.js +367 -0
  98. package/dist/pi-web-plugins/terminal/terminalService.js +436 -0
  99. package/dist/pi-web-plugins/updates/i18n.js +7 -0
  100. package/dist/pi-web-plugins/updates/package.json +1 -1
  101. package/dist/pi-web-plugins/updates/pi-web-plugin.js +143 -140
  102. package/dist/pi-web-plugins/updates/updatesLogic.js +24 -24
  103. package/dist/pi-web-plugins/workspace-tasks/config.js +12 -11
  104. package/dist/pi-web-plugins/workspace-tasks/i18n.js +7 -0
  105. package/dist/pi-web-plugins/workspace-tasks/package.json +1 -1
  106. package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +31 -9
  107. package/dist/pi-web-plugins/workspace-tasks/tasksPanelElement.js +54 -36
  108. package/dist/pi-web-plugins/workspace-tasks/workspaceTasksClient.js +12 -11
  109. package/dist/pi-web-plugins/zh-cn/auth.json +9 -0
  110. package/dist/pi-web-plugins/zh-cn/bundled.json +219 -0
  111. package/dist/pi-web-plugins/zh-cn/cli.json +230 -0
  112. package/dist/pi-web-plugins/zh-cn/client.json +225 -0
  113. package/dist/pi-web-plugins/zh-cn/components.json +1252 -0
  114. package/dist/pi-web-plugins/zh-cn/config-errors.json +188 -0
  115. package/dist/pi-web-plugins/zh-cn/files.json +50 -0
  116. package/dist/pi-web-plugins/zh-cn/machines.json +28 -0
  117. package/dist/pi-web-plugins/zh-cn/main.json +459 -0
  118. package/dist/pi-web-plugins/zh-cn/management-errors.json +23 -0
  119. package/dist/pi-web-plugins/zh-cn/manifest.webmanifest +25 -0
  120. package/dist/pi-web-plugins/zh-cn/metadata.json +3 -0
  121. package/dist/pi-web-plugins/zh-cn/package.json +10 -0
  122. package/dist/pi-web-plugins/zh-cn/pi-web-plugin.js +16 -0
  123. package/dist/pi-web-plugins/zh-cn/projects.json +6 -0
  124. package/dist/pi-web-plugins/zh-cn/provider-auth.json +18 -0
  125. package/dist/pi-web-plugins/zh-cn/routing.json +68 -0
  126. package/dist/pi-web-plugins/zh-cn/server-main.json +99 -0
  127. package/dist/pi-web-plugins/zh-cn/server.json +256 -0
  128. package/dist/piAi.js +4 -0
  129. package/dist/piAi.js.map +1 -0
  130. package/dist/piRuntime.js +150 -0
  131. package/dist/piRuntime.js.map +1 -0
  132. package/dist/piSdk.js +5 -0
  133. package/dist/piSdk.js.map +1 -0
  134. package/dist/piWebUpdate.js +508 -0
  135. package/dist/piWebUpdate.js.map +1 -0
  136. package/dist/piWebVersionReport.js +109 -41
  137. package/dist/piWebVersionReport.js.map +1 -1
  138. package/dist/plugin-api.d.ts +609 -29
  139. package/dist/plugin-api.js +1 -0
  140. package/dist/pluginRecoveryCli.js +27 -25
  141. package/dist/pluginRecoveryCli.js.map +1 -1
  142. package/dist/server/activity/workspaceActivityService.js +13 -6
  143. package/dist/server/activity/workspaceActivityService.js.map +1 -1
  144. package/dist/server/app.js +155 -77
  145. package/dist/server/app.js.map +1 -1
  146. package/dist/server/audit/managementAuditStore.js +6 -1
  147. package/dist/server/audit/managementAuditStore.js.map +1 -1
  148. package/dist/server/audit/normalToolAuditCli.js +45 -43
  149. package/dist/server/audit/normalToolAuditCli.js.map +1 -1
  150. package/dist/server/browserMessageProjection.js +39 -22
  151. package/dist/server/browserMessageProjection.js.map +1 -1
  152. package/dist/server/configRoutes.js +54 -33
  153. package/dist/server/configRoutes.js.map +1 -1
  154. package/dist/server/deploymentIdentity.js +67 -0
  155. package/dist/server/deploymentIdentity.js.map +1 -0
  156. package/dist/server/deploymentIdentityRoutes.js +22 -0
  157. package/dist/server/deploymentIdentityRoutes.js.map +1 -0
  158. package/dist/server/diagnostics/nodePtyNativeModule.js +7 -6
  159. package/dist/server/diagnostics/nodePtyNativeModule.js.map +1 -1
  160. package/dist/server/diagnostics/nodePtySpawnHelper.js +21 -20
  161. package/dist/server/diagnostics/nodePtySpawnHelper.js.map +1 -1
  162. package/dist/server/index.js +10 -1
  163. package/dist/server/index.js.map +1 -1
  164. package/dist/server/knownAutoInstallPiPackages.js +36 -0
  165. package/dist/server/knownAutoInstallPiPackages.js.map +1 -0
  166. package/dist/server/knownPiPackages.js +12 -0
  167. package/dist/server/knownPiPackages.js.map +1 -0
  168. package/dist/server/machines/machineClient.js +23 -19
  169. package/dist/server/machines/machineClient.js.map +1 -1
  170. package/dist/server/machines/machinePluginProxyRoutes.js +131 -31
  171. package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -1
  172. package/dist/server/machines/machineProxyRoutes.js +138 -47
  173. package/dist/server/machines/machineProxyRoutes.js.map +1 -1
  174. package/dist/server/machines/machineRoutes.js +18 -13
  175. package/dist/server/machines/machineRoutes.js.map +1 -1
  176. package/dist/server/machines/machineService.js +40 -18
  177. package/dist/server/machines/machineService.js.map +1 -1
  178. package/dist/server/managementEmbed.js +149 -30
  179. package/dist/server/managementEmbed.js.map +1 -1
  180. package/dist/server/managementPrivileges.js +21 -0
  181. package/dist/server/managementPrivileges.js.map +1 -0
  182. package/dist/server/managementTerminalProxyRoutes.js +124 -0
  183. package/dist/server/managementTerminalProxyRoutes.js.map +1 -0
  184. package/dist/server/navigationPreferenceRoutes.js +59 -0
  185. package/dist/server/navigationPreferenceRoutes.js.map +1 -0
  186. package/dist/server/normalAuth.js +77 -53
  187. package/dist/server/normalAuth.js.map +1 -1
  188. package/dist/server/notices/serverNoticeRoutes.js +43 -0
  189. package/dist/server/notices/serverNoticeRoutes.js.map +1 -0
  190. package/dist/server/notices/serverNoticeService.js +26 -0
  191. package/dist/server/notices/serverNoticeService.js.map +1 -0
  192. package/dist/server/notices/serverNoticeStore.js +122 -0
  193. package/dist/server/notices/serverNoticeStore.js.map +1 -0
  194. package/dist/server/passwordVerifier.js +91 -0
  195. package/dist/server/passwordVerifier.js.map +1 -0
  196. package/dist/server/piPackageIdentity.js +25 -0
  197. package/dist/server/piPackageIdentity.js.map +1 -0
  198. package/dist/server/piPackageService.js +72 -9
  199. package/dist/server/piPackageService.js.map +1 -1
  200. package/dist/server/piWebPluginCatalog.js +212 -35
  201. package/dist/server/piWebPluginCatalog.js.map +1 -1
  202. package/dist/server/piWebPluginLifecycle.js +29 -3
  203. package/dist/server/piWebPluginLifecycle.js.map +1 -1
  204. package/dist/server/piWebPluginService.js +199 -292
  205. package/dist/server/piWebPluginService.js.map +1 -1
  206. package/dist/server/piWebStatus.js +57 -100
  207. package/dist/server/piWebStatus.js.map +1 -1
  208. package/dist/server/pluginCallbackDrain.js +35 -0
  209. package/dist/server/pluginCallbackDrain.js.map +1 -0
  210. package/dist/server/plugins/pluginBackendChannelProxyAdmission.js +84 -0
  211. package/dist/server/plugins/pluginBackendChannelProxyAdmission.js.map +1 -0
  212. package/dist/server/plugins/pluginBackendChannelProxyCoordinator.js +234 -0
  213. package/dist/server/plugins/pluginBackendChannelProxyCoordinator.js.map +1 -0
  214. package/dist/server/plugins/pluginBackendChannelProxyRoutes.js +61 -0
  215. package/dist/server/plugins/pluginBackendChannelProxyRoutes.js.map +1 -0
  216. package/dist/server/plugins/pluginBackendProxyRoutes.js +47 -57
  217. package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -1
  218. package/dist/server/plugins/pluginBackendRegistry.js +804 -0
  219. package/dist/server/plugins/pluginBackendRegistry.js.map +1 -0
  220. package/dist/server/plugins/projectPluginSettings.js +95 -0
  221. package/dist/server/plugins/projectPluginSettings.js.map +1 -0
  222. package/dist/server/plugins/serverPluginCatalogCapability.js +106 -0
  223. package/dist/server/plugins/serverPluginCatalogCapability.js.map +1 -0
  224. package/dist/server/plugins/serverPluginPiSessionEventsCapability.js +34 -0
  225. package/dist/server/plugins/serverPluginPiSessionEventsCapability.js.map +1 -0
  226. package/dist/server/plugins/serverPluginPiSessionsCapability.js +185 -0
  227. package/dist/server/plugins/serverPluginPiSessionsCapability.js.map +1 -0
  228. package/dist/server/plugins/serverPluginRuntime.js +1088 -108
  229. package/dist/server/plugins/serverPluginRuntime.js.map +1 -1
  230. package/dist/server/plugins/serverPluginWorkspacesCapability.js +114 -0
  231. package/dist/server/plugins/serverPluginWorkspacesCapability.js.map +1 -0
  232. package/dist/server/projectTrustRoutes.js +86 -0
  233. package/dist/server/projectTrustRoutes.js.map +1 -0
  234. package/dist/server/projects/projectLifecycleService.js +94 -0
  235. package/dist/server/projects/projectLifecycleService.js.map +1 -0
  236. package/dist/server/projects/projectService.js +7 -4
  237. package/dist/server/projects/projectService.js.map +1 -1
  238. package/dist/server/realtime/sessionEventHub.js +58 -39
  239. package/dist/server/realtime/sessionEventHub.js.map +1 -1
  240. package/dist/server/realtime/sessionEventScope.js +4 -0
  241. package/dist/server/realtime/sessionEventScope.js.map +1 -1
  242. package/dist/server/requestCancellation.js +15 -1
  243. package/dist/server/requestCancellation.js.map +1 -1
  244. package/dist/server/sessiond/agentHttpDispatcher.js +108 -0
  245. package/dist/server/sessiond/agentHttpDispatcher.js.map +1 -0
  246. package/dist/server/sessiond/autoInstallPiPackages.js +63 -0
  247. package/dist/server/sessiond/autoInstallPiPackages.js.map +1 -0
  248. package/dist/server/sessiond/pluginBackendChannelRoutes.js +410 -0
  249. package/dist/server/sessiond/pluginBackendChannelRoutes.js.map +1 -0
  250. package/dist/server/sessiond/pluginBackendRoutes.js +64 -47
  251. package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -1
  252. package/dist/server/sessiond/pluginCatalogRoutes.js +41 -0
  253. package/dist/server/sessiond/pluginCatalogRoutes.js.map +1 -0
  254. package/dist/server/sessiond/projectMutationRoutes.js +39 -0
  255. package/dist/server/sessiond/projectMutationRoutes.js.map +1 -0
  256. package/dist/server/sessiond/sessionDaemonShutdown.js +36 -0
  257. package/dist/server/sessiond/sessionDaemonShutdown.js.map +1 -0
  258. package/dist/server/sessiond/sessionDaemonStartup.js +30 -4
  259. package/dist/server/sessiond/sessionDaemonStartup.js.map +1 -1
  260. package/dist/server/sessiond/sessionProxyRoutes.js +263 -91
  261. package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
  262. package/dist/server/sessiond/sessionServiceDependencies.js +5 -0
  263. package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -1
  264. package/dist/server/sessiond/sessiondStateOwnership.js +174 -39
  265. package/dist/server/sessiond/sessiondStateOwnership.js.map +1 -1
  266. package/dist/server/sessiond/workspaceCatalogRoutes.js +11 -5
  267. package/dist/server/sessiond/workspaceCatalogRoutes.js.map +1 -1
  268. package/dist/server/sessiond/workspaceNotificationRoutes.js +55 -0
  269. package/dist/server/sessiond/workspaceNotificationRoutes.js.map +1 -0
  270. package/dist/server/sessiond/workspaceRemovalRoutes.js +1 -1
  271. package/dist/server/sessiond/workspaceRemovalRoutes.js.map +1 -1
  272. package/dist/server/sessiond.js +313 -69
  273. package/dist/server/sessiond.js.map +1 -1
  274. package/dist/server/sessions/askUserDomain.js +232 -0
  275. package/dist/server/sessions/askUserDomain.js.map +1 -0
  276. package/dist/server/sessions/askUserTool.js +18 -21
  277. package/dist/server/sessions/askUserTool.js.map +1 -1
  278. package/dist/server/sessions/attachmentService.js +2 -6
  279. package/dist/server/sessions/attachmentService.js.map +1 -1
  280. package/dist/server/sessions/authRoutes.js +32 -19
  281. package/dist/server/sessions/authRoutes.js.map +1 -1
  282. package/dist/server/sessions/authService.js +31 -11
  283. package/dist/server/sessions/authService.js.map +1 -1
  284. package/dist/server/sessions/builtinCommands.js +28 -21
  285. package/dist/server/sessions/builtinCommands.js.map +1 -1
  286. package/dist/server/sessions/builtinExtensionFactories.js +36 -0
  287. package/dist/server/sessions/builtinExtensionFactories.js.map +1 -0
  288. package/dist/server/sessions/clientSessionPreview.js +14 -0
  289. package/dist/server/sessions/clientSessionPreview.js.map +1 -0
  290. package/dist/server/sessions/companionBackend.js +286 -0
  291. package/dist/server/sessions/companionBackend.js.map +1 -0
  292. package/dist/server/sessions/dockerEnvironmentFacts.js +98 -10
  293. package/dist/server/sessions/dockerEnvironmentFacts.js.map +1 -1
  294. package/dist/server/sessions/extensionSessionDispatch.js +76 -0
  295. package/dist/server/sessions/extensionSessionDispatch.js.map +1 -0
  296. package/dist/server/sessions/globalProviderPolicy.js +88 -19
  297. package/dist/server/sessions/globalProviderPolicy.js.map +1 -1
  298. package/dist/server/sessions/managementAgentTools.js +116 -6
  299. package/dist/server/sessions/managementAgentTools.js.map +1 -1
  300. package/dist/server/sessions/managementPermissionSystem.js +36 -14
  301. package/dist/server/sessions/managementPermissionSystem.js.map +1 -1
  302. package/dist/server/sessions/managementSandbox.js +7 -18
  303. package/dist/server/sessions/managementSandbox.js.map +1 -1
  304. package/dist/server/sessions/mcpAuthFlowService.js +289 -0
  305. package/dist/server/sessions/mcpAuthFlowService.js.map +1 -0
  306. package/dist/server/sessions/mcpAuthRoutes.js +104 -0
  307. package/dist/server/sessions/mcpAuthRoutes.js.map +1 -0
  308. package/dist/server/sessions/mcpOAuthCredentialAdapter.js +92 -0
  309. package/dist/server/sessions/mcpOAuthCredentialAdapter.js.map +1 -0
  310. package/dist/server/sessions/messageHistoryTarget.js +79 -0
  311. package/dist/server/sessions/messageHistoryTarget.js.map +1 -0
  312. package/dist/server/sessions/messagePaging.js +2 -2
  313. package/dist/server/sessions/messagePaging.js.map +1 -1
  314. package/dist/server/sessions/modelCatalogRefresher.js +4 -3
  315. package/dist/server/sessions/modelCatalogRefresher.js.map +1 -1
  316. package/dist/server/sessions/oauthLoginFlowService.js +19 -20
  317. package/dist/server/sessions/oauthLoginFlowService.js.map +1 -1
  318. package/dist/server/sessions/piSessionEventConnections.js +68 -0
  319. package/dist/server/sessions/piSessionEventConnections.js.map +1 -0
  320. package/dist/server/sessions/piSessionManagerGateway.js +206 -27
  321. package/dist/server/sessions/piSessionManagerGateway.js.map +1 -1
  322. package/dist/server/sessions/piSessionService.js +2508 -991
  323. package/dist/server/sessions/piSessionService.js.map +1 -1
  324. package/dist/server/sessions/plainTextTheme.js +1 -1
  325. package/dist/server/sessions/plainTextTheme.js.map +1 -1
  326. package/dist/server/sessions/projectAuthService.js +35 -6
  327. package/dist/server/sessions/projectAuthService.js.map +1 -1
  328. package/dist/server/sessions/projectSettingsScope.js +88 -33
  329. package/dist/server/sessions/projectSettingsScope.js.map +1 -1
  330. package/dist/server/sessions/projectTrust.js +119 -0
  331. package/dist/server/sessions/projectTrust.js.map +1 -0
  332. package/dist/server/sessions/queuedMessageMutation.js +170 -0
  333. package/dist/server/sessions/queuedMessageMutation.js.map +1 -0
  334. package/dist/server/sessions/relaySkill.js +169 -0
  335. package/dist/server/sessions/relaySkill.js.map +1 -0
  336. package/dist/server/sessions/sessionActivityMarker.js +105 -0
  337. package/dist/server/sessions/sessionActivityMarker.js.map +1 -0
  338. package/dist/server/sessions/sessionArchiveMapping.js +16 -15
  339. package/dist/server/sessions/sessionArchiveMapping.js.map +1 -1
  340. package/dist/server/sessions/sessionArchiveStore.js +5 -1
  341. package/dist/server/sessions/sessionArchiveStore.js.map +1 -1
  342. package/dist/server/sessions/sessionArchiveTree.js +2 -2
  343. package/dist/server/sessions/sessionArchiveTree.js.map +1 -1
  344. package/dist/server/sessions/sessionCleanup.js +4 -3
  345. package/dist/server/sessions/sessionCleanup.js.map +1 -1
  346. package/dist/server/sessions/sessionCommandService.js +79 -87
  347. package/dist/server/sessions/sessionCommandService.js.map +1 -1
  348. package/dist/server/sessions/sessionEnvironmentFacts.js +5 -4
  349. package/dist/server/sessions/sessionEnvironmentFacts.js.map +1 -1
  350. package/dist/server/sessions/sessionFileHeader.js +41 -0
  351. package/dist/server/sessions/sessionFileHeader.js.map +1 -1
  352. package/dist/server/sessions/sessionImageDimensions.js +31 -0
  353. package/dist/server/sessions/sessionImageDimensions.js.map +1 -0
  354. package/dist/server/sessions/sessionMediaIndex.js +191 -0
  355. package/dist/server/sessions/sessionMediaIndex.js.map +1 -0
  356. package/dist/server/sessions/sessionMetadata.js +51 -0
  357. package/dist/server/sessions/sessionMetadata.js.map +1 -0
  358. package/dist/server/sessions/sessionModelScope.js +160 -0
  359. package/dist/server/sessions/sessionModelScope.js.map +1 -0
  360. package/dist/server/sessions/sessionNameGenerator.js +16 -35
  361. package/dist/server/sessions/sessionNameGenerator.js.map +1 -1
  362. package/dist/server/sessions/sessionNotificationStore.js +14 -11
  363. package/dist/server/sessions/sessionNotificationStore.js.map +1 -1
  364. package/dist/server/sessions/sessionReadAccess.js +41 -0
  365. package/dist/server/sessions/sessionReadAccess.js.map +1 -0
  366. package/dist/server/sessions/sessionRoutes.js +221 -78
  367. package/dist/server/sessions/sessionRoutes.js.map +1 -1
  368. package/dist/server/sessions/sessionSummaryScanner.js +62 -5
  369. package/dist/server/sessions/sessionSummaryScanner.js.map +1 -1
  370. package/dist/server/sessions/sessionTreeProjection.js +50 -49
  371. package/dist/server/sessions/sessionTreeProjection.js.map +1 -1
  372. package/dist/server/sessions/sessionUiEvents.js +22 -10
  373. package/dist/server/sessions/sessionUiEvents.js.map +1 -1
  374. package/dist/server/sessions/sessionUnreadStore.js +53 -3
  375. package/dist/server/sessions/sessionUnreadStore.js.map +1 -1
  376. package/dist/server/sessions/spawnSessionTool.js +14 -6
  377. package/dist/server/sessions/spawnSessionTool.js.map +1 -1
  378. package/dist/server/sessions/spawnSubsessionTool.js +40 -24
  379. package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
  380. package/dist/server/sessions/spawnTargetResolver.js +1 -1
  381. package/dist/server/sessions/subsessionCompletionDelivery.js +15 -0
  382. package/dist/server/sessions/subsessionCompletionDelivery.js.map +1 -0
  383. package/dist/server/sessions/subsessionCompletionInbox.js +134 -0
  384. package/dist/server/sessions/subsessionCompletionInbox.js.map +1 -0
  385. package/dist/server/sessions/subsessionProvenance.js +27 -0
  386. package/dist/server/sessions/subsessionProvenance.js.map +1 -0
  387. package/dist/server/sessions/transcriptBranchCache.js +81 -0
  388. package/dist/server/sessions/transcriptBranchCache.js.map +1 -0
  389. package/dist/server/sessions/transcriptMarkdown.js +48 -0
  390. package/dist/server/sessions/transcriptMarkdown.js.map +1 -0
  391. package/dist/server/sessions/transcriptMessages.js +57 -0
  392. package/dist/server/sessions/transcriptMessages.js.map +1 -0
  393. package/dist/server/startupLogger.js +13 -0
  394. package/dist/server/startupLogger.js.map +1 -0
  395. package/dist/server/startupUrls.js +27 -0
  396. package/dist/server/startupUrls.js.map +1 -0
  397. package/dist/server/status/machineStatusService.js +15 -7
  398. package/dist/server/status/machineStatusService.js.map +1 -1
  399. package/dist/server/storage/navigationPreferenceStore.js +138 -0
  400. package/dist/server/storage/navigationPreferenceStore.js.map +1 -0
  401. package/dist/server/storage/piPackageDismissalStore.js +78 -0
  402. package/dist/server/storage/piPackageDismissalStore.js.map +1 -0
  403. package/dist/server/terminals/{terminalService.js → managementCommandRunService.js} +98 -102
  404. package/dist/server/terminals/managementCommandRunService.js.map +1 -0
  405. package/dist/server/terminals/managementTerminalRoutes.js +91 -0
  406. package/dist/server/terminals/managementTerminalRoutes.js.map +1 -0
  407. package/dist/server/terminals/requiredTerminalService.js +103 -0
  408. package/dist/server/terminals/requiredTerminalService.js.map +1 -0
  409. package/dist/server/webSocketBridge.js +348 -0
  410. package/dist/server/webSocketBridge.js.map +1 -1
  411. package/dist/server/workbench/accessStateStore.js +33 -5
  412. package/dist/server/workbench/accessStateStore.js.map +1 -1
  413. package/dist/server/workbench/capabilitySearch.js +3 -2
  414. package/dist/server/workbench/capabilitySearch.js.map +1 -1
  415. package/dist/server/workbench/gatewayIntegration.js +40 -13
  416. package/dist/server/workbench/gatewayIntegration.js.map +1 -1
  417. package/dist/server/workbench/knowledgeSearch.js +100 -0
  418. package/dist/server/workbench/knowledgeSearch.js.map +1 -0
  419. package/dist/server/workbench/mcpClient.js +41 -8
  420. package/dist/server/workbench/mcpClient.js.map +1 -1
  421. package/dist/server/workbench/skillSync.js +11 -3
  422. package/dist/server/workbench/skillSync.js.map +1 -1
  423. package/dist/server/workbench/workbenchClient.js +130 -7
  424. package/dist/server/workbench/workbenchClient.js.map +1 -1
  425. package/dist/server/workbench/workbenchTools.js +196 -14
  426. package/dist/server/workbench/workbenchTools.js.map +1 -1
  427. package/dist/server/workingDirectory.js +3 -2
  428. package/dist/server/workingDirectory.js.map +1 -1
  429. package/dist/server/workspaceExplorerRoutes.js +53 -21
  430. package/dist/server/workspaceExplorerRoutes.js.map +1 -1
  431. package/dist/server/workspaces/fileContentService.js +31 -16
  432. package/dist/server/workspaces/fileContentService.js.map +1 -1
  433. package/dist/server/workspaces/fileOperationService.js +11 -9
  434. package/dist/server/workspaces/fileOperationService.js.map +1 -1
  435. package/dist/server/workspaces/filePreviewService.js +10 -6
  436. package/dist/server/workspaces/filePreviewService.js.map +1 -1
  437. package/dist/server/workspaces/fileSuggestions.js +77 -10
  438. package/dist/server/workspaces/fileSuggestions.js.map +1 -1
  439. package/dist/server/workspaces/fileTreeService.js +103 -15
  440. package/dist/server/workspaces/fileTreeService.js.map +1 -1
  441. package/dist/server/workspaces/normalProjectCwdResolver.js +35 -0
  442. package/dist/server/workspaces/normalProjectCwdResolver.js.map +1 -0
  443. package/dist/server/workspaces/pathAccessPolicy.js +24 -18
  444. package/dist/server/workspaces/pathAccessPolicy.js.map +1 -1
  445. package/dist/server/workspaces/pathSafety.js +6 -5
  446. package/dist/server/workspaces/pathSafety.js.map +1 -1
  447. package/dist/server/workspaces/projectPiWebConfig.js +95 -8
  448. package/dist/server/workspaces/projectPiWebConfig.js.map +1 -1
  449. package/dist/server/workspaces/projectWorkspaceCwds.js +0 -5
  450. package/dist/server/workspaces/projectWorkspaceCwds.js.map +1 -1
  451. package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js +37 -22
  452. package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js.map +1 -1
  453. package/dist/server/workspaces/workspaceCatalog.js +6 -4
  454. package/dist/server/workspaces/workspaceCatalog.js.map +1 -1
  455. package/dist/server/workspaces/workspaceChangeNotifier.js +12 -0
  456. package/dist/server/workspaces/workspaceChangeNotifier.js.map +1 -0
  457. package/dist/server/workspaces/workspaceDeletionRoutes.js +9 -70
  458. package/dist/server/workspaces/workspaceDeletionRoutes.js.map +1 -1
  459. package/dist/server/workspaces/workspaceNotificationCommand.js +64 -0
  460. package/dist/server/workspaces/workspaceNotificationCommand.js.map +1 -0
  461. package/dist/server/workspaces/workspaceProviderRegistry.js +128 -24
  462. package/dist/server/workspaces/workspaceProviderRegistry.js.map +1 -1
  463. package/dist/server/workspaces/workspaceRemovalService.js +41 -7
  464. package/dist/server/workspaces/workspaceRemovalService.js.map +1 -1
  465. package/dist/server/workspaces/workspaceRouteContext.js +1 -1
  466. package/dist/server/workspaces/workspaceRouteContext.js.map +1 -1
  467. package/dist/server/workspaces/workspaceRouteErrors.js +4 -2
  468. package/dist/server/workspaces/workspaceRouteErrors.js.map +1 -1
  469. package/dist/server/workspaces/workspaceUserError.js +18 -0
  470. package/dist/server/workspaces/workspaceUserError.js.map +1 -0
  471. package/dist/server-plugin-api.d.ts +349 -49
  472. package/dist/server-plugin-api.js +344 -2
  473. package/dist/server-plugin-api.js.map +1 -1
  474. package/dist/serverPluginRecovery.js +6 -1
  475. package/dist/serverPluginRecovery.js.map +1 -1
  476. package/dist/sessiond/activeAgentProfile.js +27 -2
  477. package/dist/sessiond/activeAgentProfile.js.map +1 -1
  478. package/dist/sessiond/config.js +4 -3
  479. package/dist/sessiond/config.js.map +1 -1
  480. package/dist/sessiond/sessionDaemonClient.js +75 -19
  481. package/dist/sessiond/sessionDaemonClient.js.map +1 -1
  482. package/dist/sessiond/sessionDaemonErrors.js +49 -0
  483. package/dist/sessiond/sessionDaemonErrors.js.map +1 -0
  484. package/dist/shared/activeAgentProfile.js +2 -34
  485. package/dist/shared/activeAgentProfile.js.map +1 -1
  486. package/dist/shared/activity.js +0 -3
  487. package/dist/shared/activity.js.map +1 -1
  488. package/dist/shared/apiTypes.js +10 -3
  489. package/dist/shared/apiTypes.js.map +1 -1
  490. package/dist/shared/capabilities.js +8 -0
  491. package/dist/shared/capabilities.js.map +1 -1
  492. package/dist/shared/federatedRoutes.js +77 -11
  493. package/dist/shared/federatedRoutes.js.map +1 -1
  494. package/dist/shared/i18n.js +41 -0
  495. package/dist/shared/i18n.js.map +1 -0
  496. package/dist/shared/machinePluginIds.js +30 -7
  497. package/dist/shared/machinePluginIds.js.map +1 -1
  498. package/dist/shared/machineStatus.js +46 -3
  499. package/dist/shared/machineStatus.js.map +1 -1
  500. package/dist/shared/navigationPreferences.js +6 -0
  501. package/dist/shared/navigationPreferences.js.map +1 -0
  502. package/dist/shared/piWebConfigParsing.js +118 -49
  503. package/dist/shared/piWebConfigParsing.js.map +1 -1
  504. package/dist/shared/piWebStatusParsing.js +33 -0
  505. package/dist/shared/piWebStatusParsing.js.map +1 -1
  506. package/dist/shared/pluginApiTypes.d.ts +87 -8
  507. package/dist/shared/pluginApiTypes.js +2 -0
  508. package/dist/shared/pluginApiTypes.js.map +1 -0
  509. package/dist/shared/pluginBackendProtocol.js +218 -2
  510. package/dist/shared/pluginBackendProtocol.js.map +1 -1
  511. package/dist/shared/pluginCatalogTypes.d.ts +49 -0
  512. package/dist/shared/pluginCatalogTypes.js +156 -0
  513. package/dist/shared/pluginCatalogTypes.js.map +1 -0
  514. package/dist/shared/pluginIds.js +10 -1
  515. package/dist/shared/pluginIds.js.map +1 -1
  516. package/dist/shared/requiredTerminalPlugin.js +6 -0
  517. package/dist/shared/requiredTerminalPlugin.js.map +1 -0
  518. package/dist/shared/serverNoticeContract.js +88 -0
  519. package/dist/shared/serverNoticeContract.js.map +1 -0
  520. package/dist/shared/sessionDefaults.js +42 -0
  521. package/dist/shared/sessionDefaults.js.map +1 -0
  522. package/dist/shared/sessionMedia.js +12 -0
  523. package/dist/shared/sessionMedia.js.map +1 -0
  524. package/dist/shared/streamingBehavior.js +8 -0
  525. package/dist/shared/streamingBehavior.js.map +1 -0
  526. package/dist/shared/themePreference.js +24 -0
  527. package/dist/shared/themePreference.js.map +1 -0
  528. package/dist/shared/thinkingLevels.js +17 -16
  529. package/dist/shared/thinkingLevels.js.map +1 -1
  530. package/dist/shared/workspaceFiles.js.map +1 -1
  531. package/dist/workspaceNotificationCli.js +95 -0
  532. package/dist/workspaceNotificationCli.js.map +1 -0
  533. package/docs/README.md +31 -0
  534. package/docs/config.md +344 -277
  535. package/docs/plugins.md +204 -930
  536. package/examples/session-bridge-plugin/README.md +20 -0
  537. package/examples/session-bridge-plugin/docs/usage.md +54 -0
  538. package/examples/session-bridge-plugin/package.json +26 -0
  539. package/examples/session-bridge-plugin/src/browser/index.ts +87 -0
  540. package/examples/session-bridge-plugin/src/browser/protocol.ts +24 -0
  541. package/examples/session-bridge-plugin/src/companion.ts +65 -0
  542. package/examples/session-bridge-plugin/src/reviewRun.ts +39 -0
  543. package/examples/session-bridge-plugin/src/server.ts +85 -0
  544. package/examples/session-bridge-plugin/src/store.ts +80 -0
  545. package/examples/session-bridge-plugin/tsconfig.json +21 -0
  546. package/examples/workspace-provider-plugin/README.md +39 -0
  547. package/examples/workspace-provider-plugin/package.json +25 -0
  548. package/examples/workspace-provider-plugin/src/browser/index.ts +69 -0
  549. package/examples/workspace-provider-plugin/src/server.ts +68 -0
  550. package/examples/workspace-provider-plugin/tsconfig.json +21 -0
  551. package/extensions/pi-web.ts +3 -1
  552. package/package.json +59 -36
  553. package/skills/README.md +28 -2
  554. package/skills/relay/SKILL.md +24 -89
  555. package/skills/relay-runner/SKILL.md +74 -0
  556. package/dist/client/assets/CodeViewer-DCl56K0Z.js +0 -4
  557. package/dist/client/assets/TerminalPanel-DCvoEjUF.js +0 -187
  558. package/dist/client/assets/index-CMvms5cF.js +0 -4325
  559. package/dist/client/assets/vendor-editor-core-vUi74DnD.js +0 -12
  560. package/dist/client/assets/vendor-editor-languages-DoaXUodB.js +0 -46
  561. package/dist/client/assets/vendor-editor-legacy-CYBnW6ZU.js +0 -1
  562. package/dist/client/assets/vendor-terminal-BrP-ENHg.css +0 -1
  563. package/dist/client/assets/vendor-terminal-D8k4UKM2.js +0 -35
  564. package/dist/pi-web-plugins/relays/package.json +0 -9
  565. package/dist/pi-web-plugins/relays/pi-web-plugin.js +0 -44
  566. package/dist/server/git/gitService.js +0 -290
  567. package/dist/server/git/gitService.js.map +0 -1
  568. package/dist/server/gitRoutes.js +0 -23
  569. package/dist/server/gitRoutes.js.map +0 -1
  570. package/dist/server/sessions/pendingAskStore.js +0 -278
  571. package/dist/server/sessions/pendingAskStore.js.map +0 -1
  572. package/dist/server/terminalProxyRoutes.js +0 -178
  573. package/dist/server/terminalProxyRoutes.js.map +0 -1
  574. package/dist/server/terminals/terminalRoutes.js +0 -164
  575. package/dist/server/terminals/terminalRoutes.js.map +0 -1
  576. package/dist/server/terminals/terminalService.js.map +0 -1
  577. package/dist/server/terminals/terminalSize.js +0 -17
  578. package/dist/server/terminals/terminalSize.js.map +0 -1
  579. package/dist/server/workspaces/gitWorktreeDiscovery.js +0 -43
  580. package/dist/server/workspaces/gitWorktreeDiscovery.js.map +0 -1
  581. package/dist/server/workspaces/workspaceService.js +0 -52
  582. package/dist/server/workspaces/workspaceService.js.map +0 -1
  583. /package/dist/{pi-web-plugins → pi-packages}/relays/vendor/README.md +0 -0
  584. /package/dist/{pi-web-plugins → pi-packages}/relays/vendor/marked.esm.js +0 -0
@@ -1,32 +1,59 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import { isPiWebPluginId } from "../../shared/pluginIds.js";
1
4
  import { pathToFileURL } from "node:url";
5
+ import { parseServerNoticeScope, SERVER_PLUGIN_NOTICE_CONTEXT_MAX_BYTES, SERVER_PLUGIN_NOTICE_CONTEXT_MAX_DEPTH, SERVER_PLUGIN_NOTICE_MESSAGE_MAX_BYTES, SERVER_PLUGIN_NOTICE_SOURCE_PREFIX, serverNoticeStringExceedsUtf8ByteLimit, } from "../../shared/serverNoticeContract.js";
6
+ import { REQUIRED_TERMINAL_PLUGIN_ID, REQUIRED_TERMINAL_RECOVERY_GUIDANCE, } from "../../shared/requiredTerminalPlugin.js";
7
+ import { REQUIRED_TERMINAL_SERVICE_CAPABILITY } from "../terminals/requiredTerminalService.js";
2
8
  import { createServerPluginExecFile } from "./serverPluginExec.js";
3
9
  const DEFAULT_LIFECYCLE_TIMEOUT_MS = 10_000;
4
10
  /**
5
11
  * Resolves exactly one desired catalog snapshot and activates its server
6
12
  * entries. The resulting runtime is immutable except for explicit shutdown;
7
- * v1 intentionally has no hot reload or unload path.
13
+ * this lifecycle intentionally has no hot reload or unload path.
8
14
  */
9
15
  export async function createServerPluginRuntime(options) {
10
16
  if (options.safeStart === "none") {
11
17
  return await ServerPluginRuntime.activate({ plugins: [], diagnostics: [] }, options);
12
18
  }
13
19
  const snapshot = await options.catalog.snapshot(options.safeStart === "bundled-only" ? { scope: "bundled" } : undefined);
20
+ if (options.enforceRequiredTerminal !== false)
21
+ requireTerminalCatalogEntry(snapshot);
14
22
  return await ServerPluginRuntime.activate(snapshot, options);
15
23
  }
16
24
  export class ServerPluginRuntime {
17
- constructor(safeStart, diagnostics, logger, importer, execFile, lifecycleTimeoutMs) {
25
+ constructor(dataDir, safeStart, diagnostics, logger, importer, execFile, projectSettingsResolver, workspaceNotificationsFactory, lifecycleTimeoutMs, noticeSink, hostCapabilities, hostCapabilityFactories, lateHostCapabilities, enforceRequiredTerminal, translator) {
26
+ this.dataDir = dataDir;
18
27
  this.safeStart = safeStart;
19
28
  this.diagnostics = diagnostics;
20
29
  this.logger = logger;
21
30
  this.importer = importer;
22
31
  this.execFile = execFile;
32
+ this.projectSettingsResolver = projectSettingsResolver;
33
+ this.workspaceNotificationsFactory = workspaceNotificationsFactory;
23
34
  this.lifecycleTimeoutMs = lifecycleTimeoutMs;
35
+ this.noticeSink = noticeSink;
36
+ this.enforceRequiredTerminal = enforceRequiredTerminal;
37
+ this.translator = translator;
24
38
  this.recordsById = new Map();
39
+ this.declaredCapabilitiesByKey = new Map();
40
+ this.activeCapabilitiesByKey = new Map();
25
41
  this.activePlugins = [];
42
+ this.stagedPlugins = [];
43
+ this.lifetimesAborted = false;
44
+ this.lateHostCapabilitiesResumed = false;
26
45
  this.stopped = false;
46
+ for (const provision of hostCapabilities) {
47
+ this.registerHostCapability(provision);
48
+ this.activeCapabilitiesByKey.set(provision.key, provision);
49
+ }
50
+ for (const factory of hostCapabilityFactories)
51
+ this.registerHostCapability(factory);
52
+ for (const capability of lateHostCapabilities)
53
+ this.registerHostCapability(capability);
27
54
  }
28
55
  static async activate(snapshot, options) {
29
- const runtime = new ServerPluginRuntime(options.safeStart, Object.freeze(snapshot.diagnostics.map((diagnostic) => Object.freeze({ ...diagnostic }))), options.logger, options.importer ?? importServerPluginModule, options.execFile ?? createServerPluginExecFile(), positiveInteger(options.lifecycleTimeoutMs, DEFAULT_LIFECYCLE_TIMEOUT_MS, "lifecycleTimeoutMs"));
56
+ const runtime = new ServerPluginRuntime(resolve(options.dataDir), options.safeStart, Object.freeze(snapshot.diagnostics.map((diagnostic) => Object.freeze({ ...diagnostic }))), options.logger, options.importer ?? importServerPluginModule, options.execFile ?? createServerPluginExecFile(), options.projectSettingsResolver, options.workspaceNotificationsFactory, positiveInteger(options.lifecycleTimeoutMs, DEFAULT_LIFECYCLE_TIMEOUT_MS, "lifecycleTimeoutMs"), options.noticeSink, snapshotHostCapabilities(options.hostCapabilities), snapshotHostCapabilityFactories(options.hostCapabilityFactories), snapshotLateHostCapabilities(options.lateHostCapabilities), options.enforceRequiredTerminal !== false && options.safeStart !== "none", options.translator);
30
57
  try {
31
58
  await runtime.start(snapshot.plugins);
32
59
  return runtime;
@@ -36,6 +63,12 @@ export class ServerPluginRuntime {
36
63
  throw error;
37
64
  }
38
65
  }
66
+ registerHostCapability(declaration) {
67
+ if (this.declaredCapabilitiesByKey.has(declaration.key)) {
68
+ throw new Error(`Host capability ${formatCapability(declaration.capability)} is configured more than once`);
69
+ }
70
+ this.declaredCapabilitiesByKey.set(declaration.key, declaration);
71
+ }
39
72
  safeStartLevel() {
40
73
  return this.safeStart;
41
74
  }
@@ -48,11 +81,67 @@ export class ServerPluginRuntime {
48
81
  .map((record) => Object.freeze({ ...record })));
49
82
  }
50
83
  providerContributions() {
51
- return Object.freeze(this.activePlugins.flatMap((active) => active.contribution === undefined ? [] : [active.contribution]));
84
+ return Object.freeze(this.activePlugins.flatMap((active) => active.providerContribution === undefined ? [] : [active.providerContribution]));
85
+ }
86
+ pairedBackendContributions() {
87
+ return Object.freeze(this.activePlugins.flatMap((active) => active.pairedBackendContribution === undefined ? [] : [active.pairedBackendContribution]));
88
+ }
89
+ /** Resolves one exact active capability for a core host consumer. */
90
+ resolve(capability) {
91
+ const parsed = snapshotTypedCapability(capability, "Host capability request");
92
+ const provision = this.activeCapabilitiesByKey.get(capabilityKey(parsed));
93
+ if (provision === undefined) {
94
+ throw new Error(`Server capability ${formatCapability(parsed)} is not active`);
95
+ }
96
+ return parseCapabilityValue(parsed, provision.value, `Server capability ${formatCapability(parsed)}`);
97
+ }
98
+ /**
99
+ * Registers every declared late host factory atomically, then resumes the
100
+ * plugins staged on those capabilities. This boundary is intentionally one-shot.
101
+ */
102
+ async resumeWithHostCapabilityFactories(factories) {
103
+ if (this.stopped || this.lifetimesAborted) {
104
+ throw new Error("Server plugin runtime cannot resume after shutdown begins");
105
+ }
106
+ if (this.lateHostCapabilitiesResumed) {
107
+ throw new Error("Server plugin late host capabilities have already resumed");
108
+ }
109
+ const snapshottedFactories = snapshotHostCapabilityFactories(factories);
110
+ const lateDeclarations = [...this.declaredCapabilitiesByKey.values()]
111
+ .filter((declaration) => declaration.source === "late-host");
112
+ const factoriesByKey = new Map(snapshottedFactories.map((factory) => [factory.key, factory]));
113
+ for (const factory of snapshottedFactories) {
114
+ if (!lateDeclarations.some(({ key }) => key === factory.key)) {
115
+ throw new Error(`Host capability ${formatCapability(factory.capability)} was not declared for late registration`);
116
+ }
117
+ }
118
+ for (const declaration of lateDeclarations) {
119
+ if (!factoriesByKey.has(declaration.key)) {
120
+ throw new Error(`Late host capability ${formatCapability(declaration.capability)} was not registered`);
121
+ }
122
+ }
123
+ this.lateHostCapabilitiesResumed = true;
124
+ for (const factory of snapshottedFactories)
125
+ this.declaredCapabilitiesByKey.set(factory.key, factory);
126
+ await this.startStagedPlugins(false);
127
+ }
128
+ /** Cancels plugin lifetimes while retaining publications for consumer cleanup. */
129
+ beginShutdown() {
130
+ if (this.lifetimesAborted)
131
+ return;
132
+ this.lifetimesAborted = true;
133
+ const reason = new DOMException("Server plugin host is shutting down", "AbortError");
134
+ for (const plugin of [...this.activePlugins, ...this.stagedPlugins]) {
135
+ if (!plugin.lifetimeController.signal.aborted)
136
+ plugin.lifetimeController.abort(reason);
137
+ }
52
138
  }
53
- async inspectHealth() {
139
+ async inspectHealth(pluginIds) {
140
+ const selectedPluginIds = pluginIds === undefined ? undefined : new Set(pluginIds);
54
141
  const inspections = [];
55
142
  for (const active of this.activePlugins) {
143
+ if (selectedPluginIds !== undefined && !selectedPluginIds.has(active.entry.id))
144
+ continue;
56
145
  const callback = active.activation.health?.bind(active.activation);
57
146
  if (callback === undefined) {
58
147
  inspections.push(Object.freeze({ pluginId: active.entry.id, health: Object.freeze({ status: "healthy" }) }));
@@ -75,39 +164,43 @@ export class ServerPluginRuntime {
75
164
  }
76
165
  return Object.freeze(inspections);
77
166
  }
78
- /** Stops every successfully published plugin in reverse activation order. */
167
+ /** Disposes published plugins in reverse dependency/start order. */
79
168
  async stop() {
80
169
  if (this.stopped)
81
170
  return;
171
+ this.beginShutdown();
82
172
  this.stopped = true;
173
+ // During failed construction, activated entries may not have reached start.
174
+ // Dispose those first while any started providers remain published.
175
+ const stagedPlugins = [...this.stagedPlugins].reverse();
176
+ this.stagedPlugins = [];
177
+ for (const staged of stagedPlugins) {
178
+ await this.disposeForShutdown(staged);
179
+ }
83
180
  const activePlugins = [...this.activePlugins].reverse();
84
181
  this.activePlugins = [];
182
+ this.activeCapabilitiesByKey.clear();
85
183
  for (const active of activePlugins) {
86
- const stop = active.activation.stop?.bind(active.activation);
87
- if (stop === undefined)
88
- continue;
89
- try {
90
- await runBounded(active.entry.id, "stop", this.lifecycleTimeoutMs, (signal) => stop(signal));
91
- }
92
- catch (error) {
93
- this.recordsById.set(active.entry.id, recordFor(active.entry, {
94
- state: "failed",
95
- name: active.plugin.name,
96
- phase: "stop",
97
- message: errorMessage(error),
98
- }));
99
- this.logger.error({ err: error, pluginId: active.entry.id, phase: "stop" }, "server plugin stop failed");
100
- }
184
+ await this.disposeForShutdown(active);
101
185
  }
102
186
  }
103
187
  async start(entries) {
104
188
  const serverEntries = entries
105
189
  .filter((entry) => entry.serverModule !== undefined)
106
- .sort((left, right) => left.id.localeCompare(right.id));
190
+ .sort(requiredTerminalFirst);
107
191
  for (const entry of serverEntries)
108
- await this.activateEntry(entry);
192
+ await this.stageEntry(entry);
193
+ await this.startStagedPlugins(true);
194
+ if (this.enforceRequiredTerminal) {
195
+ try {
196
+ this.resolve(REQUIRED_TERMINAL_SERVICE_CAPABILITY);
197
+ }
198
+ catch (error) {
199
+ throw requiredTerminalError("Required Terminal service capability is not active", error);
200
+ }
201
+ }
109
202
  }
110
- async activateEntry(entry) {
203
+ async stageEntry(entry) {
111
204
  const disabledMessage = disabledReason(entry, this.safeStart);
112
205
  if (disabledMessage !== undefined) {
113
206
  this.recordsById.set(entry.id, recordFor(entry, { state: "disabled", message: disabledMessage }));
@@ -116,7 +209,9 @@ export class ServerPluginRuntime {
116
209
  }
117
210
  let phase = "validate";
118
211
  let plugin;
119
- let activation;
212
+ let rollbackDispose;
213
+ let noticeReporter;
214
+ const lifetimeController = new AbortController();
120
215
  try {
121
216
  const settings = cloneJsonObject(entry.settings, `settings for server plugin ${entry.id}`);
122
217
  phase = "import";
@@ -126,51 +221,64 @@ export class ServerPluginRuntime {
126
221
  const loadedPlugin = parsePluginExport(imported);
127
222
  plugin = loadedPlugin;
128
223
  phase = "activate";
224
+ if (!isPiWebPluginId(entry.id))
225
+ throw new Error(`Invalid PI WEB plugin id: ${entry.id}`);
226
+ const dataDirectory = resolve(this.dataDir, "plugin-data", entry.id);
227
+ await mkdir(dataDirectory, { recursive: true });
129
228
  const scopedLogger = createScopedLogger(entry.id, this.logger);
229
+ if (loadedPlugin.apiVersion === 3) {
230
+ this.logger.warn({ pluginId: entry.id, apiVersion: 3 }, "Server plugin API v3 is deprecated; migrate to API v5");
231
+ }
232
+ noticeReporter = createScopedNoticeReporter(entry.id, this.noticeSink);
233
+ const resolveProjectSettings = this.projectSettingsResolver?.bind(undefined, entry.id, settings);
234
+ const workspaceNotifications = this.workspaceNotificationsFactory?.(entry.id);
130
235
  const activationValue = await runBounded(entry.id, phase, this.lifecycleTimeoutMs, (signal) => loadedPlugin.activate(Object.freeze({
131
- apiVersion: 1,
236
+ apiVersion: loadedPlugin.apiVersion,
132
237
  pluginId: entry.id,
133
238
  packageRoot: entry.packageRoot,
239
+ dataDirectory,
134
240
  logger: scopedLogger,
135
241
  settings,
242
+ ...(resolveProjectSettings === undefined ? {} : { resolveProjectSettings }),
243
+ ...(workspaceNotifications === undefined ? {} : { workspaceNotifications }),
244
+ ...(noticeReporter === undefined ? {} : { notices: noticeReporter.reporter }),
136
245
  execFile: this.execFile,
137
246
  signal,
247
+ lifetimeSignal: lifetimeController.signal,
248
+ ...(this.translator === undefined ? {} : { translate: this.translator }),
138
249
  })));
250
+ rollbackDispose = activationDisposeForRollback(activationValue);
139
251
  phase = "validate";
140
- const loadedActivation = parseActivation(activationValue);
141
- activation = loadedActivation;
142
- phase = "start";
143
- const start = loadedActivation.start?.bind(loadedActivation);
144
- if (start !== undefined) {
145
- await runBounded(entry.id, phase, this.lifecycleTimeoutMs, (signal) => start(signal));
252
+ const loadedActivation = parseActivation(activationValue, entry.id, loadedPlugin.apiVersion);
253
+ if (this.enforceRequiredTerminal && entry.id === REQUIRED_TERMINAL_PLUGIN_ID) {
254
+ requireTerminalActivation(loadedActivation);
146
255
  }
147
- const contribution = loadedActivation.workspaceProvider === undefined
148
- ? undefined
149
- : Object.freeze({
150
- pluginId: entry.id,
151
- pluginName: loadedPlugin.name,
152
- packageRoot: entry.packageRoot,
153
- source: entry.source,
154
- scope: entry.scope,
155
- moduleRevision: requireServerModule(entry).revision,
156
- provider: loadedActivation.workspaceProvider,
157
- });
158
- this.activePlugins.push(Object.freeze({
256
+ const provisions = internalProvisions(loadedActivation, "plugin");
257
+ for (const provision of provisions) {
258
+ if (this.declaredCapabilitiesByKey.has(provision.key)) {
259
+ throw new IncompatibleServerPluginError(`Server capability ${formatCapability(provision.capability)} is provided more than once`);
260
+ }
261
+ }
262
+ const staged = Object.freeze({
159
263
  entry,
160
264
  plugin: loadedPlugin,
161
265
  activation: loadedActivation,
162
- ...(contribution === undefined ? {} : { contribution }),
163
- }));
164
- this.recordsById.set(entry.id, recordFor(entry, { state: "active", name: loadedPlugin.name }));
165
- this.logger.info({ pluginId: entry.id, pluginName: loadedPlugin.name }, "server plugin activated");
266
+ lifetimeController,
267
+ ...(noticeReporter === undefined ? {} : { noticeReporter }),
268
+ provisions,
269
+ hostCapabilityInstances: [],
270
+ });
271
+ this.stagedPlugins.push(staged);
272
+ for (const provision of provisions)
273
+ this.declaredCapabilitiesByKey.set(provision.key, provision);
166
274
  }
167
275
  catch (error) {
168
- const rollbackError = phase === "start" && activation?.stop !== undefined
169
- ? await this.rollbackStart(entry.id, activation)
170
- : undefined;
171
- const message = rollbackError === undefined
172
- ? errorMessage(error)
173
- : `${errorMessage(error)}; startup rollback failed: ${errorMessage(rollbackError)}`;
276
+ abortPluginLifetime(lifetimeController, entry.id, "activation failed");
277
+ noticeReporter?.revoke();
278
+ const rollbackError = rollbackDispose === undefined
279
+ ? undefined
280
+ : await this.runDispose(entry.id, rollbackDispose);
281
+ const message = withRollbackError(errorMessage(error), rollbackError);
174
282
  const state = error instanceof IncompatibleServerPluginError ? "incompatible" : "failed";
175
283
  this.recordsById.set(entry.id, recordFor(entry, {
176
284
  state,
@@ -179,20 +287,312 @@ export class ServerPluginRuntime {
179
287
  message,
180
288
  }));
181
289
  const details = { err: error, pluginId: entry.id, phase, ...(rollbackError === undefined ? {} : { rollbackError }) };
182
- if (state === "incompatible") {
290
+ if (state === "incompatible")
183
291
  this.logger.warn(details, "server plugin is incompatible");
292
+ else
293
+ this.logger.error(details, "server plugin activation failed");
294
+ if (this.enforceRequiredTerminal && entry.id === REQUIRED_TERMINAL_PLUGIN_ID) {
295
+ throw requiredTerminalError(`Required Terminal server entry failed during ${phase}: ${message}`, error);
296
+ }
297
+ }
298
+ }
299
+ async startStagedPlugins(allowLateHostWait) {
300
+ const pending = new Map(this.stagedPlugins.map((plugin) => [plugin.entry.id, plugin]));
301
+ while (pending.size > 0) {
302
+ let progressed = false;
303
+ const candidates = [...pending.values()].sort((left, right) => requiredTerminalFirst(left.entry, right.entry));
304
+ for (const staged of candidates) {
305
+ const dependencyFailure = this.dependencyFailure(staged, pending);
306
+ if (dependencyFailure !== undefined) {
307
+ pending.delete(staged.entry.id);
308
+ const message = await this.failBeforeStart(staged, dependencyFailure);
309
+ progressed = true;
310
+ if (this.enforceRequiredTerminal && staged.entry.id === REQUIRED_TERMINAL_PLUGIN_ID) {
311
+ throw requiredTerminalError(`Required Terminal server entry failed during start: ${message}`, dependencyFailure);
312
+ }
313
+ continue;
314
+ }
315
+ if (this.waitingForDependencies(staged))
316
+ continue;
317
+ pending.delete(staged.entry.id);
318
+ const startError = await this.startStagedPlugin(staged);
319
+ progressed = true;
320
+ if (startError !== undefined && this.enforceRequiredTerminal && staged.entry.id === REQUIRED_TERMINAL_PLUGIN_ID) {
321
+ const record = this.recordsById.get(staged.entry.id);
322
+ throw requiredTerminalError(`Required Terminal server entry failed during start: ${record?.message ?? errorMessage(startError)}`, startError);
323
+ }
324
+ }
325
+ if (progressed)
326
+ continue;
327
+ const cycleIds = findCapabilityCycle(pending, this.declaredCapabilitiesByKey);
328
+ if (cycleIds.length > 0) {
329
+ for (const pluginId of cycleIds) {
330
+ const staged = pending.get(pluginId);
331
+ if (staged === undefined)
332
+ continue;
333
+ pending.delete(pluginId);
334
+ const cyclicRequirement = (staged.plugin.requires ?? []).find((requirement) => cycleIds.includes(requirement.pluginId));
335
+ const error = new Error(cyclicRequirement === undefined
336
+ ? `Server plugin ${pluginId} has an unresolved capability dependency cycle`
337
+ : `Server plugin ${pluginId} requires ${formatCapability(cyclicRequirement)} in a capability dependency cycle`);
338
+ const message = await this.failBeforeStart(staged, error);
339
+ if (this.enforceRequiredTerminal && pluginId === REQUIRED_TERMINAL_PLUGIN_ID) {
340
+ throw requiredTerminalError(`Required Terminal server entry failed during start: ${message}`, error);
341
+ }
342
+ }
343
+ continue;
344
+ }
345
+ if (allowLateHostWait) {
346
+ const providerCycles = candidates.flatMap((staged) => {
347
+ if (staged.activation.workspaceProvider === undefined)
348
+ return [];
349
+ const lateCapability = findLateHostDependency(staged, pending, this.declaredCapabilitiesByKey);
350
+ return lateCapability === undefined ? [] : [{ staged, lateCapability }];
351
+ });
352
+ if (providerCycles.length > 0) {
353
+ for (const { staged, lateCapability } of providerCycles) {
354
+ pending.delete(staged.entry.id);
355
+ const error = new Error(`Server plugin ${staged.entry.id} contributes a workspace provider that depends on late host capability ${formatCapability(lateCapability)} in a capability dependency cycle`);
356
+ const message = await this.failBeforeStart(staged, error);
357
+ if (this.enforceRequiredTerminal && staged.entry.id === REQUIRED_TERMINAL_PLUGIN_ID) {
358
+ throw requiredTerminalError(`Required Terminal server entry failed during start: ${message}`, error);
359
+ }
360
+ }
361
+ continue;
362
+ }
363
+ if (candidates.every((staged) => (findLateHostDependency(staged, pending, this.declaredCapabilitiesByKey) !== undefined)))
364
+ return;
365
+ }
366
+ const unresolved = candidates[0];
367
+ if (unresolved === undefined)
368
+ return;
369
+ pending.delete(unresolved.entry.id);
370
+ const error = new Error(`Server plugin ${unresolved.entry.id} has an unresolved capability dependency cycle`);
371
+ const message = await this.failBeforeStart(unresolved, error);
372
+ if (this.enforceRequiredTerminal && unresolved.entry.id === REQUIRED_TERMINAL_PLUGIN_ID) {
373
+ throw requiredTerminalError(`Required Terminal server entry failed during start: ${message}`, error);
374
+ }
375
+ }
376
+ }
377
+ dependencyFailure(staged, pending) {
378
+ for (const requirement of staged.plugin.requires ?? []) {
379
+ const key = capabilityKey(requirement);
380
+ const provision = this.declaredCapabilitiesByKey.get(key);
381
+ if (provision === undefined) {
382
+ return new Error(`Server plugin ${staged.entry.id} requires unavailable capability ${formatCapability(requirement)}`);
383
+ }
384
+ if (this.activeCapabilitiesByKey.has(key)
385
+ || provision.source === "host"
386
+ || provision.source === "host-factory"
387
+ || provision.source === "late-host")
388
+ continue;
389
+ if (!pending.has(requirement.pluginId)) {
390
+ return new Error(`Server plugin ${staged.entry.id} requires ${formatCapability(requirement)}, but provider plugin ${requirement.pluginId} did not start`);
391
+ }
392
+ }
393
+ return undefined;
394
+ }
395
+ waitingForDependencies(staged) {
396
+ return (staged.plugin.requires ?? []).some((requirement) => {
397
+ const key = capabilityKey(requirement);
398
+ return this.declaredCapabilitiesByKey.get(key)?.source !== "host-factory"
399
+ && !this.activeCapabilitiesByKey.has(key);
400
+ });
401
+ }
402
+ async startStagedPlugin(staged) {
403
+ try {
404
+ const capabilities = this.capabilityResolver(staged);
405
+ const start = staged.activation.start?.bind(staged.activation);
406
+ if (start !== undefined) {
407
+ await runBounded(staged.entry.id, "start", this.lifecycleTimeoutMs, (signal) => start(Object.freeze({
408
+ capabilities,
409
+ signal,
410
+ })));
411
+ }
412
+ if (this.enforceRequiredTerminal && staged.entry.id === REQUIRED_TERMINAL_PLUGIN_ID) {
413
+ await this.requireHealthyTerminal(staged);
414
+ }
415
+ this.publish(staged);
416
+ return undefined;
417
+ }
418
+ catch (error) {
419
+ await this.failBeforeStart(staged, error);
420
+ return error;
421
+ }
422
+ }
423
+ capabilityResolver(staged) {
424
+ const resolved = new Map();
425
+ for (const requirement of staged.plugin.requires ?? []) {
426
+ const key = capabilityKey(requirement);
427
+ const activeProvision = this.activeCapabilitiesByKey.get(key);
428
+ const declaration = this.declaredCapabilitiesByKey.get(key);
429
+ let provisionValue;
430
+ if (activeProvision !== undefined) {
431
+ provisionValue = activeProvision.value;
432
+ }
433
+ else if (declaration?.source === "host-factory") {
434
+ provisionValue = this.createHostCapabilityValue(staged, declaration);
184
435
  }
185
436
  else {
186
- this.logger.error(details, "server plugin activation failed");
437
+ throw new Error(`Server plugin ${staged.entry.id} requires inactive capability ${formatCapability(requirement)}`);
187
438
  }
439
+ // Validate the declared requirement before start, but retain the provider value:
440
+ // resolve() may supply a different parser for this same capability key.
441
+ parseCapabilityValue(requirement, provisionValue, `Required capability ${formatCapability(requirement)} for server plugin ${staged.entry.id}`);
442
+ resolved.set(key, { provisionValue });
188
443
  }
444
+ return createCapabilityResolver(staged.entry.id, resolved);
445
+ }
446
+ createHostCapabilityValue(staged, factory) {
447
+ const context = Object.freeze({
448
+ pluginId: staged.entry.id,
449
+ packageRoot: staged.entry.packageRoot,
450
+ lifetimeSignal: staged.lifetimeController.signal,
451
+ });
452
+ const instance = factory.create(context);
453
+ staged.hostCapabilityInstances.push(instance);
454
+ return parseCapabilityValue(factory.capability, instance.value, `Host capability ${formatCapability(factory.capability)} for server plugin ${staged.entry.id}`);
455
+ }
456
+ async requireHealthyTerminal(staged) {
457
+ const health = staged.activation.health?.bind(staged.activation);
458
+ if (health === undefined)
459
+ return;
460
+ const result = await runBounded(staged.entry.id, "health", this.lifecycleTimeoutMs, (signal) => health(signal));
461
+ const parsed = parseHealth(result);
462
+ if (parsed.status === "unhealthy") {
463
+ throw new Error(`Required Terminal server entry is unhealthy: ${parsed.message ?? "health check failed"}`);
464
+ }
465
+ }
466
+ publish(staged) {
467
+ const providerContribution = staged.activation.workspaceProvider === undefined
468
+ ? undefined
469
+ : Object.freeze({
470
+ pluginId: staged.entry.id,
471
+ pluginName: staged.plugin.name,
472
+ packageRoot: staged.entry.packageRoot,
473
+ source: staged.entry.source,
474
+ scope: staged.entry.scope,
475
+ moduleRevision: requireServerModule(staged.entry).revision,
476
+ provider: staged.activation.workspaceProvider,
477
+ });
478
+ const pairedBackendContribution = staged.activation.peer === undefined
479
+ ? undefined
480
+ : Object.freeze({
481
+ pluginId: staged.entry.id,
482
+ pluginName: staged.plugin.name,
483
+ packageRoot: staged.entry.packageRoot,
484
+ source: staged.entry.source,
485
+ scope: staged.entry.scope,
486
+ moduleRevision: requireServerModule(staged.entry).revision,
487
+ apiVersion: staged.plugin.apiVersion,
488
+ backend: staged.activation.peer,
489
+ requires: this.resolvedCapabilityRequirements(staged),
490
+ });
491
+ const active = Object.freeze({
492
+ ...staged,
493
+ ...(providerContribution === undefined ? {} : { providerContribution }),
494
+ ...(pairedBackendContribution === undefined ? {} : { pairedBackendContribution }),
495
+ });
496
+ this.removeStaged(staged.entry.id);
497
+ this.activePlugins.push(active);
498
+ for (const provision of staged.provisions)
499
+ this.activeCapabilitiesByKey.set(provision.key, provision);
500
+ this.recordsById.set(staged.entry.id, recordFor(staged.entry, {
501
+ state: "active",
502
+ name: staged.plugin.name,
503
+ ...(pairedBackendContribution?.backend.request === undefined ? {} : { pairedRequestVersion: 1 }),
504
+ ...(pairedBackendContribution?.backend.openChannel === undefined ? {} : { pairedChannelVersion: 1 }),
505
+ }));
506
+ this.logger.info({ pluginId: staged.entry.id, pluginName: staged.plugin.name }, "server plugin activated");
507
+ }
508
+ resolvedCapabilityRequirements(staged) {
509
+ const requirements = new Map();
510
+ const visited = new Set();
511
+ const visit = (plugin) => {
512
+ if (visited.has(plugin.entry.id))
513
+ return;
514
+ visited.add(plugin.entry.id);
515
+ for (const requirement of plugin.plugin.requires ?? []) {
516
+ const key = capabilityKey(requirement);
517
+ requirements.set(key, requirement);
518
+ if (this.declaredCapabilitiesByKey.get(key)?.source !== "plugin")
519
+ continue;
520
+ const provider = this.activePlugins.find(({ entry }) => entry.id === requirement.pluginId);
521
+ if (provider !== undefined)
522
+ visit(provider);
523
+ }
524
+ };
525
+ visit(staged);
526
+ return Object.freeze([...requirements.values()]);
189
527
  }
190
- async rollbackStart(pluginId, activation) {
191
- const stop = activation.stop?.bind(activation);
192
- if (stop === undefined)
528
+ async failBeforeStart(staged, error) {
529
+ abortPluginLifetime(staged.lifetimeController, staged.entry.id, "startup failed");
530
+ staged.noticeReporter?.revoke();
531
+ this.removeStaged(staged.entry.id);
532
+ const dispose = staged.activation.dispose?.bind(staged.activation);
533
+ const rollbackError = dispose === undefined ? undefined : await this.runDispose(staged.entry.id, dispose);
534
+ const hostCleanupError = await this.disposeHostCapabilityInstances(staged);
535
+ const message = withHostCleanupError(withRollbackError(errorMessage(error), rollbackError), hostCleanupError);
536
+ this.recordsById.set(staged.entry.id, recordFor(staged.entry, {
537
+ state: "failed",
538
+ name: staged.plugin.name,
539
+ phase: "start",
540
+ message,
541
+ }));
542
+ this.logger.error({
543
+ err: error,
544
+ pluginId: staged.entry.id,
545
+ phase: "start",
546
+ ...(rollbackError === undefined ? {} : { rollbackError }),
547
+ ...(hostCleanupError === undefined ? {} : { hostCleanupError }),
548
+ }, "server plugin start failed");
549
+ return message;
550
+ }
551
+ removeStaged(pluginId) {
552
+ this.stagedPlugins = this.stagedPlugins.filter(({ entry }) => entry.id !== pluginId);
553
+ }
554
+ async disposeForShutdown(plugin) {
555
+ plugin.noticeReporter?.revoke();
556
+ const dispose = plugin.activation.dispose?.bind(plugin.activation);
557
+ const pluginDisposeError = dispose === undefined ? undefined : await this.runDispose(plugin.entry.id, dispose);
558
+ const hostCleanupError = await this.disposeHostCapabilityInstances(plugin);
559
+ const error = combinedDisposalError(pluginDisposeError, hostCleanupError);
560
+ if (error === undefined)
561
+ return;
562
+ this.recordsById.set(plugin.entry.id, recordFor(plugin.entry, {
563
+ state: "failed",
564
+ name: plugin.plugin.name,
565
+ phase: "dispose",
566
+ message: errorMessage(error),
567
+ ...(plugin.activation.peer?.request === undefined ? {} : { pairedRequestVersion: 1 }),
568
+ ...(plugin.activation.peer?.openChannel === undefined ? {} : { pairedChannelVersion: 1 }),
569
+ }));
570
+ this.logger.error({
571
+ err: error,
572
+ pluginId: plugin.entry.id,
573
+ phase: "dispose",
574
+ ...(hostCleanupError === undefined ? {} : { hostCleanupError }),
575
+ }, "server plugin disposal failed");
576
+ }
577
+ async disposeHostCapabilityInstances(plugin) {
578
+ const instances = plugin.hostCapabilityInstances.splice(0).reverse();
579
+ const errors = [];
580
+ for (const instance of instances) {
581
+ if (instance.dispose === undefined)
582
+ continue;
583
+ const error = await this.runDispose(plugin.entry.id, instance.dispose);
584
+ if (error !== undefined)
585
+ errors.push(error);
586
+ }
587
+ if (errors.length === 0)
193
588
  return undefined;
589
+ if (errors.length === 1)
590
+ return errors[0];
591
+ return new AggregateError(errors, "Multiple host capability cleanup operations failed");
592
+ }
593
+ async runDispose(pluginId, dispose) {
194
594
  try {
195
- await runBounded(pluginId, "stop", this.lifecycleTimeoutMs, (signal) => stop(signal));
595
+ await runBounded(pluginId, "dispose", this.lifecycleTimeoutMs, (signal) => dispose(signal));
196
596
  return undefined;
197
597
  }
198
598
  catch (error) {
@@ -200,6 +600,46 @@ export class ServerPluginRuntime {
200
600
  }
201
601
  }
202
602
  }
603
+ function requireTerminalCatalogEntry(snapshot) {
604
+ const entry = snapshot.plugins.find(({ id }) => id === REQUIRED_TERMINAL_PLUGIN_ID);
605
+ if (entry === undefined)
606
+ throw requiredTerminalError("Required bundled Terminal package is missing");
607
+ if (entry.scope !== "bundled" || entry.source !== "bundled") {
608
+ throw requiredTerminalError("Required Terminal package must come from the bundled plugin scope");
609
+ }
610
+ if (entry.browserModule === undefined || entry.serverModule === undefined || !entry.machineSpecific) {
611
+ throw requiredTerminalError("Required Terminal package must provide machine-specific browser and server entries");
612
+ }
613
+ if (!entry.enabled)
614
+ throw requiredTerminalError("Required Terminal package cannot be disabled in normal startup");
615
+ return entry;
616
+ }
617
+ function requireTerminalActivation(activation) {
618
+ if (activation.peer?.request === undefined || activation.peer.openChannel === undefined) {
619
+ throw new IncompatibleServerPluginError("Required Terminal server entry must expose peer request and channel capabilities");
620
+ }
621
+ const requiredKey = capabilityKey(REQUIRED_TERMINAL_SERVICE_CAPABILITY);
622
+ const provision = activation.provides?.find(({ capability }) => capabilityKey(capability) === requiredKey);
623
+ if (provision === undefined) {
624
+ throw new IncompatibleServerPluginError(`Required Terminal server entry must provide ${formatCapability(REQUIRED_TERMINAL_SERVICE_CAPABILITY)}`);
625
+ }
626
+ try {
627
+ REQUIRED_TERMINAL_SERVICE_CAPABILITY.parse(provision.value);
628
+ }
629
+ catch (error) {
630
+ throw new IncompatibleServerPluginError(errorMessage(error), { cause: error });
631
+ }
632
+ }
633
+ function requiredTerminalFirst(left, right) {
634
+ if (left.id === REQUIRED_TERMINAL_PLUGIN_ID)
635
+ return right.id === REQUIRED_TERMINAL_PLUGIN_ID ? 0 : -1;
636
+ if (right.id === REQUIRED_TERMINAL_PLUGIN_ID)
637
+ return 1;
638
+ return left.id.localeCompare(right.id);
639
+ }
640
+ function requiredTerminalError(message, cause) {
641
+ return new RequiredTerminalPluginError(`${message}. ${REQUIRED_TERMINAL_RECOVERY_GUIDANCE}`, cause === undefined ? {} : { cause });
642
+ }
203
643
  function disabledReason(entry, safeStart) {
204
644
  if (!entry.enabled)
205
645
  return "disabled in PI WEB config";
@@ -218,6 +658,8 @@ function recordFor(entry, status) {
218
658
  ...(entry.browserModule === undefined ? {} : { browserRevision: entry.browserModule.revision }),
219
659
  settingsRevision: entry.settingsRevision,
220
660
  machineSpecific: entry.machineSpecific,
661
+ ...(status.pairedRequestVersion === undefined ? {} : { pairedRequestVersion: status.pairedRequestVersion }),
662
+ ...(status.pairedChannelVersion === undefined ? {} : { pairedChannelVersion: status.pairedChannelVersion }),
221
663
  state: status.state,
222
664
  ...(status.name === undefined ? {} : { name: status.name }),
223
665
  ...(status.phase === undefined ? {} : { phase: status.phase }),
@@ -246,44 +688,63 @@ function parsePluginExport(imported) {
246
688
  const plugin = imported["default"];
247
689
  if (!isRecord(plugin))
248
690
  throw new IncompatibleServerPluginError("Server plugin module must export a default plugin object");
249
- const candidate = {
250
- apiVersion: plugin["apiVersion"],
251
- name: plugin["name"],
252
- activate: plugin["activate"],
253
- };
254
- if (candidate.apiVersion !== 1) {
255
- throw new IncompatibleServerPluginError(`Unsupported server plugin API version: ${formatUnknown(candidate.apiVersion)}`);
691
+ const apiVersion = plugin["apiVersion"];
692
+ const name = plugin["name"];
693
+ const activateValue = plugin["activate"];
694
+ if (apiVersion !== 3 && apiVersion !== 5) {
695
+ throw new IncompatibleServerPluginError(`Unsupported server plugin API version: ${formatUnknown(apiVersion)}`);
256
696
  }
257
- if (typeof candidate.name !== "string" || candidate.name === "") {
697
+ if (typeof name !== "string" || name === "") {
258
698
  throw new IncompatibleServerPluginError("Server plugin name must be a non-empty string");
259
699
  }
260
- if (typeof candidate.activate !== "function" || !isPiWebServerPlugin(candidate)) {
700
+ if (typeof activateValue !== "function") {
261
701
  throw new IncompatibleServerPluginError("Server plugin activate must be a function");
262
702
  }
263
- const activate = candidate.activate.bind(plugin);
264
- return Object.freeze({ apiVersion: 1, name: candidate.name, activate: (context) => activate(context) });
703
+ const requires = snapshotCapabilityRequirements(plugin["requires"], `Server plugin ${name} requirements`);
704
+ return Object.freeze({
705
+ apiVersion,
706
+ name,
707
+ ...(requires.length === 0 ? {} : { requires }),
708
+ activate(context) {
709
+ const result = Reflect.apply(activateValue, plugin, [context]);
710
+ return result;
711
+ },
712
+ });
265
713
  }
266
- function isPiWebServerPlugin(value) {
267
- return isRecord(value)
268
- && value["apiVersion"] === 1
269
- && typeof value["name"] === "string"
270
- && value["name"] !== ""
271
- && typeof value["activate"] === "function";
714
+ function activationDisposeForRollback(value) {
715
+ if (!isRecord(value))
716
+ return undefined;
717
+ const dispose = value["dispose"];
718
+ if (typeof dispose !== "function")
719
+ return undefined;
720
+ return async (signal) => {
721
+ await Reflect.apply(dispose, value, [signal]);
722
+ };
272
723
  }
273
- function parseActivation(value) {
724
+ function parseActivation(value, pluginId, apiVersion) {
274
725
  if (!isRecord(value))
275
726
  throw new IncompatibleServerPluginError("Server plugin activation must be an object");
276
727
  if (value["workspaceProviders"] !== undefined) {
277
728
  throw new IncompatibleServerPluginError("Server plugins may contribute only one workspaceProvider");
278
729
  }
730
+ if (value["requiredTerminalService"] !== undefined) {
731
+ throw new IncompatibleServerPluginError("requiredTerminalService was removed in server plugin API v3; use a typed capability provision");
732
+ }
733
+ if (value["stop"] !== undefined) {
734
+ throw new IncompatibleServerPluginError("Server plugin stop was removed in API v3; use dispose");
735
+ }
279
736
  const workspaceProviderValue = value["workspaceProvider"];
737
+ const peerValue = value["peer"];
738
+ const provides = snapshotCapabilityProvisions(value["provides"], pluginId, `Server plugin ${pluginId} provisions`);
280
739
  const candidate = {
281
740
  workspaceProvider: workspaceProviderValue === undefined ? undefined : snapshotWorkspaceProvider(workspaceProviderValue),
741
+ peer: peerValue === undefined ? undefined : snapshotPluginPeer(peerValue, apiVersion),
742
+ provides,
282
743
  start: value["start"],
283
- stop: value["stop"],
744
+ dispose: value["dispose"],
284
745
  health: value["health"],
285
746
  };
286
- for (const callback of ["start", "stop", "health"]) {
747
+ for (const callback of ["start", "dispose", "health"]) {
287
748
  const callbackValue = candidate[callback];
288
749
  if (callbackValue !== undefined && typeof callbackValue !== "function") {
289
750
  throw new IncompatibleServerPluginError(`Server plugin ${callback} must be a function`);
@@ -292,12 +753,14 @@ function parseActivation(value) {
292
753
  if (!isServerPluginActivation(candidate))
293
754
  throw new IncompatibleServerPluginError("Server plugin activation is invalid");
294
755
  const start = candidate.start?.bind(value);
295
- const stop = candidate.stop?.bind(value);
756
+ const dispose = candidate.dispose?.bind(value);
296
757
  const health = candidate.health?.bind(value);
297
758
  return Object.freeze({
298
759
  ...(candidate.workspaceProvider === undefined ? {} : { workspaceProvider: candidate.workspaceProvider }),
299
- ...(start === undefined ? {} : { start: (signal) => start(signal) }),
300
- ...(stop === undefined ? {} : { stop: (signal) => stop(signal) }),
760
+ ...(candidate.peer === undefined ? {} : { peer: candidate.peer }),
761
+ ...(provides.length === 0 ? {} : { provides }),
762
+ ...(start === undefined ? {} : { start: (context) => start(context) }),
763
+ ...(dispose === undefined ? {} : { dispose: (signal) => dispose(signal) }),
301
764
  ...(health === undefined ? {} : { health: (signal) => health(signal) }),
302
765
  });
303
766
  }
@@ -305,14 +768,350 @@ function isServerPluginActivation(value) {
305
768
  if (!isRecord(value))
306
769
  return false;
307
770
  const workspaceProvider = value["workspaceProvider"];
771
+ const peer = value["peer"];
772
+ const provides = value["provides"];
308
773
  const start = value["start"];
309
- const stop = value["stop"];
774
+ const dispose = value["dispose"];
310
775
  const health = value["health"];
311
776
  return (workspaceProvider === undefined || isWorkspaceProvider(workspaceProvider))
777
+ && (peer === undefined || isPluginPeer(peer))
778
+ && (provides === undefined || Array.isArray(provides))
312
779
  && (start === undefined || typeof start === "function")
313
- && (stop === undefined || typeof stop === "function")
780
+ && (dispose === undefined || typeof dispose === "function")
314
781
  && (health === undefined || typeof health === "function");
315
782
  }
783
+ function snapshotCapabilityRequirements(value, label) {
784
+ if (value === undefined)
785
+ return Object.freeze([]);
786
+ if (!Array.isArray(value))
787
+ throw new IncompatibleServerPluginError(`${label} must be an array`);
788
+ const seen = new Set();
789
+ const requirements = [];
790
+ for (let index = 0; index < value.length; index += 1) {
791
+ if (!Object.hasOwn(value, index))
792
+ throw new IncompatibleServerPluginError(`${label} must not be sparse`);
793
+ const capability = snapshotUnknownCapability(value[index], `${label}[${String(index)}]`);
794
+ const key = capabilityKey(capability);
795
+ if (seen.has(key))
796
+ throw new IncompatibleServerPluginError(`${label} declares ${formatCapability(capability)} more than once`);
797
+ seen.add(key);
798
+ requirements.push(capability);
799
+ }
800
+ return Object.freeze(requirements);
801
+ }
802
+ function snapshotCapabilityProvisions(value, ownerPluginId, label) {
803
+ if (value === undefined)
804
+ return Object.freeze([]);
805
+ if (!Array.isArray(value))
806
+ throw new IncompatibleServerPluginError(`${label} must be an array`);
807
+ const seen = new Set();
808
+ const provisions = [];
809
+ for (let index = 0; index < value.length; index += 1) {
810
+ if (!Object.hasOwn(value, index))
811
+ throw new IncompatibleServerPluginError(`${label} must not be sparse`);
812
+ const candidate = value[index];
813
+ if (!isRecord(candidate))
814
+ throw new IncompatibleServerPluginError(`${label}[${String(index)}] must be an object`);
815
+ const capability = snapshotUnknownCapability(candidate["capability"], `${label}[${String(index)}].capability`);
816
+ if (ownerPluginId !== undefined && capability.pluginId !== ownerPluginId) {
817
+ throw new IncompatibleServerPluginError(`Server plugin ${ownerPluginId} cannot provide capability owned by ${capability.pluginId}`);
818
+ }
819
+ const key = capabilityKey(capability);
820
+ if (seen.has(key))
821
+ throw new IncompatibleServerPluginError(`${label} publishes ${formatCapability(capability)} more than once`);
822
+ seen.add(key);
823
+ const parsedValue = parseCapabilityValue(capability, candidate["value"], `Provided capability ${formatCapability(capability)}`);
824
+ provisions.push(Object.freeze({ capability, value: parsedValue }));
825
+ }
826
+ return Object.freeze(provisions);
827
+ }
828
+ function snapshotHostCapabilities(value) {
829
+ const provisions = snapshotCapabilityProvisions(value, undefined, "Host capability provisions");
830
+ return internalProvisions({ provides: provisions }, "host");
831
+ }
832
+ function snapshotLateHostCapabilities(value) {
833
+ if (value === undefined)
834
+ return Object.freeze([]);
835
+ if (!Array.isArray(value))
836
+ throw new Error("Late host capabilities must be an array");
837
+ const seen = new Set();
838
+ const capabilities = [];
839
+ for (let index = 0; index < value.length; index += 1) {
840
+ if (!Object.hasOwn(value, index))
841
+ throw new Error("Late host capabilities must not be sparse");
842
+ const capability = snapshotUnknownCapability(value[index], `Late host capabilities[${String(index)}]`);
843
+ const key = capabilityKey(capability);
844
+ if (seen.has(key))
845
+ throw new Error(`Late host capability ${formatCapability(capability)} is configured more than once`);
846
+ seen.add(key);
847
+ capabilities.push(Object.freeze({ capability, key, source: "late-host" }));
848
+ }
849
+ return Object.freeze(capabilities);
850
+ }
851
+ function snapshotHostCapabilityFactories(value) {
852
+ if (value === undefined)
853
+ return Object.freeze([]);
854
+ if (!Array.isArray(value))
855
+ throw new Error("Host capability factories must be an array");
856
+ const seen = new Set();
857
+ const factories = [];
858
+ for (let index = 0; index < value.length; index += 1) {
859
+ if (!Object.hasOwn(value, index))
860
+ throw new Error("Host capability factories must not be sparse");
861
+ const candidate = value[index];
862
+ if (!isRecord(candidate))
863
+ throw new Error(`Host capability factories[${String(index)}] must be an object`);
864
+ const capability = snapshotUnknownCapability(candidate["capability"], `Host capability factories[${String(index)}].capability`);
865
+ const key = capabilityKey(capability);
866
+ if (seen.has(key))
867
+ throw new Error(`Host capability factory for ${formatCapability(capability)} is configured more than once`);
868
+ const create = candidate["create"];
869
+ if (typeof create !== "function")
870
+ throw new Error(`Host capability factory for ${formatCapability(capability)} must expose create`);
871
+ seen.add(key);
872
+ factories.push(Object.freeze({
873
+ capability,
874
+ key,
875
+ source: "host-factory",
876
+ create(context) {
877
+ const result = Reflect.apply(create, candidate, [context]);
878
+ return snapshotHostCapabilityInstance(result, `Host capability ${formatCapability(capability)}`);
879
+ },
880
+ }));
881
+ }
882
+ return Object.freeze(factories);
883
+ }
884
+ function snapshotHostCapabilityInstance(value, label) {
885
+ if (!isRecord(value) || !Object.hasOwn(value, "value")) {
886
+ throw new Error(`${label} factory must return an object containing value`);
887
+ }
888
+ const dispose = value["dispose"];
889
+ if (dispose !== undefined && typeof dispose !== "function") {
890
+ throw new Error(`${label} factory dispose must be a function`);
891
+ }
892
+ return Object.freeze({
893
+ value: value["value"],
894
+ ...(dispose === undefined
895
+ ? {}
896
+ : { dispose: async (signal) => { await Reflect.apply(dispose, value, [signal]); } }),
897
+ });
898
+ }
899
+ function internalProvisions(activation, source) {
900
+ return Object.freeze((activation.provides ?? []).map((provision) => Object.freeze({
901
+ capability: provision.capability,
902
+ key: capabilityKey(provision.capability),
903
+ value: provision.value,
904
+ source,
905
+ })));
906
+ }
907
+ function snapshotUnknownCapability(value, label) {
908
+ if (!isRecord(value))
909
+ throw new IncompatibleServerPluginError(`${label} must be an object`);
910
+ const pluginId = value["pluginId"];
911
+ const id = value["id"];
912
+ const version = value["version"];
913
+ const parse = value["parse"];
914
+ const validated = validateCapabilityFields(pluginId, id, version, parse, label);
915
+ return Object.freeze({
916
+ pluginId: validated.pluginId,
917
+ id: validated.id,
918
+ version: validated.version,
919
+ parse(input) {
920
+ const result = Reflect.apply(validated.parse, value, [input]);
921
+ return result;
922
+ },
923
+ });
924
+ }
925
+ function snapshotTypedCapability(value, label) {
926
+ const validated = validateCapabilityFields(value.pluginId, value.id, value.version, value.parse, label);
927
+ const parse = value.parse.bind(value);
928
+ return Object.freeze({
929
+ pluginId: validated.pluginId,
930
+ id: validated.id,
931
+ version: validated.version,
932
+ parse: (input) => parse(input),
933
+ });
934
+ }
935
+ function validateCapabilityFields(pluginId, id, version, parse, label) {
936
+ if (typeof pluginId !== "string" || pluginId.trim() === "") {
937
+ throw new IncompatibleServerPluginError(`${label} pluginId must be a non-empty string`);
938
+ }
939
+ if (typeof id !== "string" || id.trim() === "") {
940
+ throw new IncompatibleServerPluginError(`${label} id must be a non-empty string`);
941
+ }
942
+ if (typeof version !== "number" || !Number.isInteger(version) || version <= 0) {
943
+ throw new IncompatibleServerPluginError(`${label} version must be a positive integer`);
944
+ }
945
+ if (!isCapabilityParser(parse))
946
+ throw new IncompatibleServerPluginError(`${label} parse must be a function`);
947
+ return { pluginId, id, version, parse };
948
+ }
949
+ function isCapabilityParser(value) {
950
+ return typeof value === "function";
951
+ }
952
+ function parseCapabilityValue(capability, value, label) {
953
+ try {
954
+ return capability.parse(value);
955
+ }
956
+ catch (error) {
957
+ throw new IncompatibleServerPluginError(`${label} is invalid: ${errorMessage(error)}`, { cause: error });
958
+ }
959
+ }
960
+ function capabilityKey(capability) {
961
+ return JSON.stringify([capability.pluginId, capability.id, capability.version]);
962
+ }
963
+ function formatCapability(capability) {
964
+ return `${capability.pluginId}/${capability.id} v${String(capability.version)}`;
965
+ }
966
+ function createCapabilityResolver(pluginId, resolved) {
967
+ return Object.freeze({
968
+ resolve(value) {
969
+ const capability = snapshotTypedCapability(value, `Capability request from server plugin ${pluginId}`);
970
+ const requirement = resolved.get(capabilityKey(capability));
971
+ if (requirement === undefined) {
972
+ throw new Error(`Server plugin ${pluginId} did not declare required capability ${formatCapability(capability)}`);
973
+ }
974
+ return parseCapabilityValue(capability, requirement.provisionValue, `Required capability ${formatCapability(capability)} for server plugin ${pluginId}`);
975
+ },
976
+ });
977
+ }
978
+ function findLateHostDependency(staged, pending, declared, visiting = new Set()) {
979
+ if (visiting.has(staged.entry.id))
980
+ return undefined;
981
+ const nextVisiting = new Set(visiting);
982
+ nextVisiting.add(staged.entry.id);
983
+ for (const requirement of staged.plugin.requires ?? []) {
984
+ const declaration = declared.get(capabilityKey(requirement));
985
+ if (declaration?.source === "late-host")
986
+ return declaration.capability;
987
+ if (declaration?.source !== "plugin")
988
+ continue;
989
+ const provider = pending.get(requirement.pluginId);
990
+ if (provider === undefined)
991
+ continue;
992
+ const lateCapability = findLateHostDependency(provider, pending, declared, nextVisiting);
993
+ if (lateCapability !== undefined)
994
+ return lateCapability;
995
+ }
996
+ return undefined;
997
+ }
998
+ function findCapabilityCycle(pending, declared) {
999
+ const state = new Map();
1000
+ const stack = [];
1001
+ const visit = (pluginId) => {
1002
+ state.set(pluginId, "visiting");
1003
+ stack.push(pluginId);
1004
+ const plugin = pending.get(pluginId);
1005
+ const providers = (plugin?.plugin.requires ?? [])
1006
+ .filter((requirement) => declared.get(capabilityKey(requirement))?.source === "plugin" && pending.has(requirement.pluginId))
1007
+ .map((requirement) => requirement.pluginId)
1008
+ .sort((left, right) => left.localeCompare(right));
1009
+ for (const providerId of providers) {
1010
+ if (state.get(providerId) === "visiting") {
1011
+ const start = stack.indexOf(providerId);
1012
+ return stack.slice(start);
1013
+ }
1014
+ if (state.get(providerId) !== "visited") {
1015
+ const cycle = visit(providerId);
1016
+ if (cycle !== undefined)
1017
+ return cycle;
1018
+ }
1019
+ }
1020
+ stack.pop();
1021
+ state.set(pluginId, "visited");
1022
+ return undefined;
1023
+ };
1024
+ for (const pluginId of [...pending.keys()].sort((left, right) => left.localeCompare(right))) {
1025
+ if (state.has(pluginId))
1026
+ continue;
1027
+ const cycle = visit(pluginId);
1028
+ if (cycle !== undefined)
1029
+ return cycle;
1030
+ }
1031
+ return [];
1032
+ }
1033
+ function abortPluginLifetime(controller, pluginId, outcome) {
1034
+ if (!controller.signal.aborted) {
1035
+ controller.abort(new DOMException(`Server plugin ${pluginId} ${outcome}`, "AbortError"));
1036
+ }
1037
+ }
1038
+ function withRollbackError(message, rollbackError) {
1039
+ return rollbackError === undefined
1040
+ ? message
1041
+ : `${message}; startup rollback failed: ${errorMessage(rollbackError)}`;
1042
+ }
1043
+ function withHostCleanupError(message, cleanupError) {
1044
+ return cleanupError === undefined
1045
+ ? message
1046
+ : `${message}; host capability cleanup failed: ${errorMessage(cleanupError)}`;
1047
+ }
1048
+ function combinedDisposalError(pluginError, hostCleanupError) {
1049
+ if (pluginError === undefined)
1050
+ return hostCleanupError;
1051
+ if (hostCleanupError === undefined)
1052
+ return pluginError;
1053
+ return new AggregateError([pluginError, hostCleanupError], `${errorMessage(pluginError)}; host capability cleanup failed: ${errorMessage(hostCleanupError)}`);
1054
+ }
1055
+ function snapshotPluginPeer(value, apiVersion) {
1056
+ if (!isPluginPeer(value)) {
1057
+ throw new IncompatibleServerPluginError("Server plugin peer must include a request or channel handler");
1058
+ }
1059
+ const request = value.request?.bind(value);
1060
+ const openChannel = value.openChannel?.bind(value);
1061
+ const snapshotRequest = request === undefined
1062
+ ? undefined
1063
+ : (context) => request(peerContextForVersion(context, apiVersion));
1064
+ const snapshotOpenChannel = openChannel === undefined
1065
+ ? undefined
1066
+ : async (context) => (snapshotPluginPeerChannel(await openChannel(peerContextForVersion(context, apiVersion))));
1067
+ if (snapshotRequest !== undefined && snapshotOpenChannel !== undefined) {
1068
+ return Object.freeze({ request: snapshotRequest, openChannel: snapshotOpenChannel });
1069
+ }
1070
+ if (snapshotRequest !== undefined)
1071
+ return Object.freeze({ request: snapshotRequest });
1072
+ if (snapshotOpenChannel !== undefined)
1073
+ return Object.freeze({ openChannel: snapshotOpenChannel });
1074
+ throw new IncompatibleServerPluginError("Server plugin peer must include a request or channel handler");
1075
+ }
1076
+ function peerContextForVersion(context, apiVersion) {
1077
+ if (apiVersion === 5)
1078
+ return context;
1079
+ if (context.scope.kind !== "workspace")
1080
+ throw new Error("Server plugin API v3 requires workspace scope");
1081
+ const { scope, ...invocation } = context;
1082
+ return Object.freeze({ ...invocation, project: scope.project, workspace: scope.workspace });
1083
+ }
1084
+ function isPluginPeer(value) {
1085
+ if (!isRecord(value))
1086
+ return false;
1087
+ const request = value["request"];
1088
+ const openChannel = value["openChannel"];
1089
+ return (typeof request === "function" || typeof openChannel === "function")
1090
+ && (request === undefined || typeof request === "function")
1091
+ && (openChannel === undefined || typeof openChannel === "function");
1092
+ }
1093
+ function snapshotPluginPeerChannel(value) {
1094
+ if (!isPluginPeerChannel(value)) {
1095
+ throw new Error("Server plugin openChannel must return a channel with receive, optional completion, and optional close callbacks");
1096
+ }
1097
+ const receive = value.receive.bind(value);
1098
+ const closed = value.closed === undefined ? undefined : Promise.resolve(value.closed);
1099
+ const close = value.close?.bind(value);
1100
+ return Object.freeze({
1101
+ receive: (data, signal) => receive(data, signal),
1102
+ ...(closed === undefined ? {} : { closed }),
1103
+ ...(close === undefined ? {} : { close: (context) => close(context) }),
1104
+ });
1105
+ }
1106
+ function isPluginPeerChannel(value) {
1107
+ return isRecord(value)
1108
+ && typeof value["receive"] === "function"
1109
+ && (value["closed"] === undefined || isPromiseLike(value["closed"]))
1110
+ && (value["close"] === undefined || typeof value["close"] === "function");
1111
+ }
1112
+ function isPromiseLike(value) {
1113
+ return isRecord(value) && typeof value["then"] === "function";
1114
+ }
316
1115
  function snapshotWorkspaceProvider(value) {
317
1116
  if (!isRecord(value))
318
1117
  throw new IncompatibleServerPluginError("Server plugin workspaceProvider is invalid");
@@ -320,21 +1119,21 @@ function snapshotWorkspaceProvider(value) {
320
1119
  fallback: value["fallback"],
321
1120
  probe: value["probe"],
322
1121
  list: value["list"],
323
- request: value["request"],
324
1122
  prepareRemove: value["prepareRemove"],
1123
+ integration: value["integration"],
325
1124
  };
326
1125
  if (!isWorkspaceProvider(candidate))
327
1126
  throw new IncompatibleServerPluginError("Server plugin workspaceProvider is invalid");
328
1127
  const probe = candidate.probe.bind(value);
329
1128
  const list = candidate.list.bind(value);
330
- const request = candidate.request?.bind(value);
331
1129
  const prepareRemove = candidate.prepareRemove?.bind(value);
1130
+ const integration = snapshotWorkspaceProviderIntegration(candidate.integration);
332
1131
  return Object.freeze({
333
1132
  ...(candidate.fallback === undefined ? {} : { fallback: candidate.fallback }),
334
1133
  probe: (project, signal) => probe(project, signal),
335
1134
  list: (project, signal) => list(project, signal),
336
- ...(request === undefined ? {} : { request: (context) => request(context) }),
337
1135
  ...(prepareRemove === undefined ? {} : { prepareRemove: (context) => prepareRemove(context) }),
1136
+ ...(integration === undefined ? {} : { integration }),
338
1137
  });
339
1138
  }
340
1139
  function isWorkspaceProvider(value) {
@@ -343,13 +1142,25 @@ function isWorkspaceProvider(value) {
343
1142
  const fallback = value["fallback"];
344
1143
  const probe = value["probe"];
345
1144
  const list = value["list"];
346
- const request = value["request"];
347
1145
  const prepareRemove = value["prepareRemove"];
348
1146
  return (fallback === undefined || typeof fallback === "boolean")
349
1147
  && typeof probe === "function"
350
1148
  && typeof list === "function"
351
- && (request === undefined || typeof request === "function")
352
- && (prepareRemove === undefined || typeof prepareRemove === "function");
1149
+ && (prepareRemove === undefined || typeof prepareRemove === "function")
1150
+ && (value["integration"] === undefined || isWorkspaceProviderIntegration(value["integration"]));
1151
+ }
1152
+ function isWorkspaceProviderIntegration(value) {
1153
+ return isRecord(value) && typeof value["reconcile"] === "function" && typeof value["acceptNotification"] === "function";
1154
+ }
1155
+ function snapshotWorkspaceProviderIntegration(value) {
1156
+ if (value === undefined)
1157
+ return undefined;
1158
+ const reconcile = value.reconcile.bind(value);
1159
+ const acceptNotification = value.acceptNotification.bind(value);
1160
+ return Object.freeze({
1161
+ reconcile: (project, signal) => reconcile(project, signal),
1162
+ acceptNotification: (project, signal) => acceptNotification(project, signal),
1163
+ });
353
1164
  }
354
1165
  function parseHealth(value) {
355
1166
  if (!isRecord(value))
@@ -370,6 +1181,67 @@ function parseHealth(value) {
370
1181
  ...(clonedDetails === undefined ? {} : { details: clonedDetails }),
371
1182
  });
372
1183
  }
1184
+ function createScopedNoticeReporter(pluginId, sink) {
1185
+ if (sink === undefined)
1186
+ return undefined;
1187
+ const source = `${SERVER_PLUGIN_NOTICE_SOURCE_PREFIX}${pluginId}`;
1188
+ let active = true;
1189
+ const reporter = Object.freeze({
1190
+ version: 1,
1191
+ record(input) {
1192
+ if (!active)
1193
+ throw new Error(`Server plugin notice reporter for ${pluginId} is no longer active`);
1194
+ sink(source, parseServerPluginNoticeInput(input));
1195
+ },
1196
+ });
1197
+ return Object.freeze({
1198
+ reporter,
1199
+ revoke() { active = false; },
1200
+ });
1201
+ }
1202
+ function parseServerPluginNoticeInput(value) {
1203
+ if (!isPlainRecord(value))
1204
+ throw new Error("Server plugin notice input must be an object");
1205
+ if ("source" in value)
1206
+ throw new Error("Server plugin notices cannot set their source");
1207
+ for (const key in value) {
1208
+ if (!Object.hasOwn(value, key))
1209
+ continue;
1210
+ if (key !== "severity" && key !== "message" && key !== "scope" && key !== "context") {
1211
+ throw new Error(`Unsupported server plugin notice field: ${key}`);
1212
+ }
1213
+ }
1214
+ const severity = value["severity"];
1215
+ if (severity !== "info" && severity !== "warning" && severity !== "error") {
1216
+ throw new Error("Server plugin notice severity must be info, warning, or error");
1217
+ }
1218
+ const message = value["message"];
1219
+ if (typeof message !== "string") {
1220
+ throw new Error("Server plugin notice message must be a non-empty string");
1221
+ }
1222
+ if (serverNoticeStringExceedsUtf8ByteLimit(message, SERVER_PLUGIN_NOTICE_MESSAGE_MAX_BYTES)) {
1223
+ throw new Error(`Server plugin notice message exceeds the ${String(SERVER_PLUGIN_NOTICE_MESSAGE_MAX_BYTES)} byte limit`);
1224
+ }
1225
+ // This scan and allocation are bounded because the UTF-8 limit passed first.
1226
+ if (message.trim() === "") {
1227
+ throw new Error("Server plugin notice message must be a non-empty string");
1228
+ }
1229
+ const scope = value["scope"] === undefined
1230
+ ? undefined
1231
+ : parseServerNoticeScope(value["scope"], "Server plugin notice scope");
1232
+ const context = value["context"] === undefined
1233
+ ? undefined
1234
+ : cloneJsonObject(value["context"], "server plugin notice context", {
1235
+ maxDepth: SERVER_PLUGIN_NOTICE_CONTEXT_MAX_DEPTH,
1236
+ maxBytes: SERVER_PLUGIN_NOTICE_CONTEXT_MAX_BYTES,
1237
+ });
1238
+ return Object.freeze({
1239
+ severity,
1240
+ message,
1241
+ ...(scope === undefined ? {} : { scope }),
1242
+ ...(context === undefined ? {} : { context }),
1243
+ });
1244
+ }
373
1245
  function createScopedLogger(pluginId, logger) {
374
1246
  return Object.freeze({
375
1247
  debug(message, details) {
@@ -404,40 +1276,136 @@ async function runBounded(pluginId, phase, timeoutMs, operation) {
404
1276
  controller.abort(new DOMException("Server plugin operation completed", "AbortError"));
405
1277
  }
406
1278
  }
407
- function cloneJsonObject(value, label) {
408
- if (!isRecord(value))
1279
+ function cloneJsonObject(value, label, limits = {}) {
1280
+ if (!isPlainRecord(value))
409
1281
  throw new IncompatibleServerPluginError(`${label} must be a JSON object`);
410
- return cloneJsonRecord(value, new Set(), label);
1282
+ const byteBudget = limits.maxBytes === undefined
1283
+ ? undefined
1284
+ : { label, maxBytes: limits.maxBytes, remaining: limits.maxBytes };
1285
+ return cloneJsonRecord(value, new Set(), label, 0, limits.maxDepth, byteBudget);
411
1286
  }
412
- function cloneJsonRecord(value, ancestors, label) {
1287
+ function cloneJsonRecord(value, ancestors, label, depth, maxDepth, byteBudget) {
1288
+ requireJsonDepth(depth, maxDepth, label);
413
1289
  if (ancestors.has(value))
414
1290
  throw new IncompatibleServerPluginError(`${label} must not contain cycles`);
1291
+ consumeJsonBytes(byteBudget, 2); // Opening and closing braces.
415
1292
  ancestors.add(value);
416
1293
  const output = {};
417
- for (const [key, child] of Object.entries(value))
418
- output[key] = cloneJsonValue(child, ancestors, label);
1294
+ let propertyCount = 0;
1295
+ for (const key in value) {
1296
+ if (!Object.hasOwn(value, key))
1297
+ continue;
1298
+ if (propertyCount > 0)
1299
+ consumeJsonBytes(byteBudget, 1); // Comma.
1300
+ consumeJsonStringBytes(byteBudget, key);
1301
+ consumeJsonBytes(byteBudget, 1); // Colon.
1302
+ defineJsonProperty(output, key, cloneJsonValue(value[key], ancestors, label, depth + 1, maxDepth, byteBudget));
1303
+ propertyCount += 1;
1304
+ }
419
1305
  ancestors.delete(value);
420
1306
  return Object.freeze(output);
421
1307
  }
422
- function cloneJsonValue(value, ancestors, label) {
423
- if (value === null || typeof value === "string" || typeof value === "boolean")
1308
+ function cloneJsonValue(value, ancestors, label, depth, maxDepth, byteBudget) {
1309
+ requireJsonDepth(depth, maxDepth, label);
1310
+ if (value === null) {
1311
+ consumeJsonBytes(byteBudget, 4);
1312
+ return value;
1313
+ }
1314
+ if (typeof value === "string") {
1315
+ consumeJsonStringBytes(byteBudget, value);
1316
+ return value;
1317
+ }
1318
+ if (typeof value === "boolean") {
1319
+ consumeJsonBytes(byteBudget, value ? 4 : 5);
424
1320
  return value;
1321
+ }
425
1322
  if (typeof value === "number") {
426
1323
  if (!Number.isFinite(value))
427
1324
  throw new IncompatibleServerPluginError(`${label} must contain only finite JSON numbers`);
1325
+ consumeJsonBytes(byteBudget, String(value).length);
428
1326
  return value;
429
1327
  }
430
- if (Array.isArray(value)) {
431
- if (ancestors.has(value))
432
- throw new IncompatibleServerPluginError(`${label} must not contain cycles`);
433
- ancestors.add(value);
434
- const output = value.map((child) => cloneJsonValue(child, ancestors, label));
435
- ancestors.delete(value);
1328
+ if (Array.isArray(value))
1329
+ return cloneJsonArray(value, ancestors, label, depth, maxDepth, byteBudget);
1330
+ if (isPlainRecord(value))
1331
+ return cloneJsonRecord(value, ancestors, label, depth, maxDepth, byteBudget);
1332
+ throw new IncompatibleServerPluginError(`${label} must contain only JSON values`);
1333
+ }
1334
+ function cloneJsonArray(value, ancestors, label, depth, maxDepth, byteBudget) {
1335
+ requireJsonDepth(depth, maxDepth, label);
1336
+ if (ancestors.has(value))
1337
+ throw new IncompatibleServerPluginError(`${label} must not contain cycles`);
1338
+ consumeJsonBytes(byteBudget, 2); // Opening and closing brackets.
1339
+ ancestors.add(value);
1340
+ try {
1341
+ const output = [];
1342
+ const length = value.length;
1343
+ // Plugin-owned arrays may override iteration helpers; inspect each dense element directly.
1344
+ for (let index = 0; index < length; index += 1) {
1345
+ if (!Object.hasOwn(value, index)) {
1346
+ throw new IncompatibleServerPluginError(`${label} must not contain sparse arrays`);
1347
+ }
1348
+ if (index > 0)
1349
+ consumeJsonBytes(byteBudget, 1); // Comma.
1350
+ output[index] = cloneJsonValue(value[index], ancestors, label, depth + 1, maxDepth, byteBudget);
1351
+ }
436
1352
  return Object.freeze(output);
437
1353
  }
438
- if (isRecord(value))
439
- return cloneJsonRecord(value, ancestors, label);
440
- throw new IncompatibleServerPluginError(`${label} must contain only JSON values`);
1354
+ finally {
1355
+ ancestors.delete(value);
1356
+ }
1357
+ }
1358
+ function consumeJsonStringBytes(byteBudget, value) {
1359
+ if (byteBudget === undefined)
1360
+ return;
1361
+ consumeJsonBytes(byteBudget, 2); // Opening and closing quotes.
1362
+ for (let index = 0; index < value.length; index += 1) {
1363
+ const codeUnit = value.charCodeAt(index);
1364
+ if (codeUnit === 0x22 || codeUnit === 0x5c
1365
+ || codeUnit === 0x08 || codeUnit === 0x09 || codeUnit === 0x0a
1366
+ || codeUnit === 0x0c || codeUnit === 0x0d) {
1367
+ consumeJsonBytes(byteBudget, 2);
1368
+ }
1369
+ else if (codeUnit <= 0x1f) {
1370
+ consumeJsonBytes(byteBudget, 6);
1371
+ }
1372
+ else if (codeUnit <= 0x7f) {
1373
+ consumeJsonBytes(byteBudget, 1);
1374
+ }
1375
+ else if (codeUnit <= 0x7ff) {
1376
+ consumeJsonBytes(byteBudget, 2);
1377
+ }
1378
+ else if (codeUnit >= 0xd800 && codeUnit <= 0xdbff
1379
+ && index + 1 < value.length
1380
+ && value.charCodeAt(index + 1) >= 0xdc00
1381
+ && value.charCodeAt(index + 1) <= 0xdfff) {
1382
+ consumeJsonBytes(byteBudget, 4);
1383
+ index += 1;
1384
+ }
1385
+ else if (codeUnit >= 0xd800 && codeUnit <= 0xdfff) {
1386
+ // Well-formed JSON.stringify escapes isolated UTF-16 surrogates as \uXXXX.
1387
+ consumeJsonBytes(byteBudget, 6);
1388
+ }
1389
+ else {
1390
+ consumeJsonBytes(byteBudget, 3);
1391
+ }
1392
+ }
1393
+ }
1394
+ function consumeJsonBytes(byteBudget, count) {
1395
+ if (byteBudget === undefined)
1396
+ return;
1397
+ byteBudget.remaining -= count;
1398
+ if (byteBudget.remaining < 0) {
1399
+ throw new Error(`${byteBudget.label} exceeds the ${String(byteBudget.maxBytes)} byte limit`);
1400
+ }
1401
+ }
1402
+ function requireJsonDepth(depth, maxDepth, label) {
1403
+ if (maxDepth !== undefined && depth > maxDepth) {
1404
+ throw new IncompatibleServerPluginError(`${label} exceeds the maximum JSON depth of ${String(maxDepth)}`);
1405
+ }
1406
+ }
1407
+ function defineJsonProperty(record, key, value) {
1408
+ Object.defineProperty(record, key, { value, enumerable: true, configurable: true, writable: true });
441
1409
  }
442
1410
  function abortError(signal) {
443
1411
  const reason = signal.reason;
@@ -449,6 +1417,12 @@ class IncompatibleServerPluginError extends Error {
449
1417
  this.name = "IncompatibleServerPluginError";
450
1418
  }
451
1419
  }
1420
+ export class RequiredTerminalPluginError extends Error {
1421
+ constructor() {
1422
+ super(...arguments);
1423
+ this.name = "RequiredTerminalPluginError";
1424
+ }
1425
+ }
452
1426
  class ServerPluginTimeoutError extends Error {
453
1427
  constructor() {
454
1428
  super(...arguments);
@@ -477,4 +1451,10 @@ function formatUnknown(value) {
477
1451
  function isRecord(value) {
478
1452
  return typeof value === "object" && value !== null && !Array.isArray(value);
479
1453
  }
1454
+ function isPlainRecord(value) {
1455
+ if (!isRecord(value))
1456
+ return false;
1457
+ const prototype = Object.getPrototypeOf(value);
1458
+ return prototype === Object.prototype || prototype === null;
1459
+ }
480
1460
  //# sourceMappingURL=serverPluginRuntime.js.map