@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.
- package/README.md +61 -106
- package/dist/.plugins-ready +1 -0
- package/dist/cli.js +331 -174
- package/dist/cli.js.map +1 -1
- package/dist/client/apple-touch-icon-dev.png +0 -0
- package/dist/client/assets/PiWebApp-BXSziMEg.js +4921 -0
- package/dist/client/assets/classic-CyM97Akm.css +54 -0
- package/dist/client/assets/external-B9K8duyP.js +2 -0
- package/dist/client/assets/graphite-DquaqFXU.css +54 -0
- package/dist/client/assets/index-cdtUOfOW.js +2 -0
- package/dist/client/assets/paper-CB1oLuRr.css +54 -0
- package/dist/client/assets/pi-web-dark-D_KtbM95.css +54 -0
- package/dist/client/assets/pi-web-light-DSNLMIBI.css +54 -0
- package/dist/client/assets/vendor-editor-core-BfnHL1Dg.js +10 -0
- package/dist/client/assets/vendor-editor-languages-bNquDJP0.js +28 -0
- package/dist/client/favicon-dev.svg +8 -0
- package/dist/client/index.html +7 -51
- package/dist/client/manifest.webmanifest +2 -1
- package/dist/client/pwa-icon-dev-192.png +0 -0
- package/dist/client/pwa-icon-dev-512.png +0 -0
- package/dist/config.js +55 -8
- package/dist/config.js.map +1 -1
- package/dist/docker/piWebDockerCommandPlan.js +13 -4
- package/dist/docker/piWebDockerCommandPlan.js.map +1 -1
- package/dist/environment.js +4 -0
- package/dist/environment.js.map +1 -0
- package/dist/localization.js +24 -0
- package/dist/localization.js.map +1 -0
- package/dist/nativeServices/installedServiceDefinitions.js +617 -0
- package/dist/nativeServices/installedServiceDefinitions.js.map +1 -0
- package/dist/nativeServices/serviceAction.js +78 -20
- package/dist/nativeServices/serviceAction.js.map +1 -1
- package/dist/nativeServices/serviceDoctor.js +418 -114
- package/dist/nativeServices/serviceDoctor.js.map +1 -1
- package/dist/nativeServices/servicePlan.js +2 -2
- package/dist/nativeServices/servicePlan.js.map +1 -1
- package/dist/pi-packages/captains-log/README.md +11 -0
- package/dist/pi-packages/captains-log/dist/browser/channelProtocol.js +29 -0
- package/dist/pi-packages/captains-log/dist/browser/index.js +143 -0
- package/dist/pi-packages/captains-log/dist/browser/markdown.js +68 -0
- package/dist/pi-packages/captains-log/dist/browser/panel.js +148 -0
- package/dist/pi-packages/captains-log/dist/browser/protocol.js +26 -0
- package/dist/pi-packages/captains-log/dist/companion.js +126 -0
- package/dist/pi-packages/captains-log/dist/roundtrip.js +62 -0
- package/dist/pi-packages/captains-log/dist/server.js +210 -0
- package/dist/pi-packages/captains-log/dist/store.js +75 -0
- package/dist/pi-packages/captains-log/docs/usage.md +29 -0
- package/dist/pi-packages/captains-log/i18n.js +7 -0
- package/dist/pi-packages/captains-log/package.json +35 -0
- package/dist/pi-packages/captains-log/tsconfig.json +21 -0
- package/dist/pi-packages/captains-log/vite.config.mjs +28 -0
- package/dist/pi-packages/relays/extensions/index.js +61 -0
- package/dist/pi-packages/relays/i18n.js +7 -0
- package/dist/{pi-web-plugins → pi-packages}/relays/markdownDocument.js +3 -2
- package/dist/pi-packages/relays/package.json +20 -0
- package/dist/pi-packages/relays/pi-web-plugin.js +96 -0
- package/dist/pi-packages/relays/prompts/relay-worktree.md +18 -0
- package/dist/pi-packages/relays/prompts/relay.md +18 -0
- package/dist/{pi-web-plugins → pi-packages}/relays/relayDiscovery.js +2 -2
- package/dist/pi-packages/relays/relayDispatch.js +67 -0
- package/dist/pi-packages/relays/relayIdentity.js +37 -0
- package/dist/{pi-web-plugins → pi-packages}/relays/relaysPanelElement.js +98 -59
- package/dist/pi-packages/relays/skills/relay/SKILL.md +50 -0
- package/dist/pi-packages/relays/skills/relay-runner/SKILL.md +74 -0
- package/dist/pi-packages/workflows/browser/pi-web-plugin.js +42 -0
- package/dist/pi-packages/workflows/browser/worktreeDialog.js +170 -0
- package/dist/pi-packages/workflows/package.json +23 -0
- package/dist/pi-packages/workflows/prompts/worktree.md +21 -0
- package/dist/pi-packages/workflows/server-plugin.js +586 -0
- package/dist/pi-packages/workflows/vite.config.mjs +17 -0
- package/dist/pi-web-plugins/code-highlight/browser/pi-web-plugin.js +22 -0
- package/dist/pi-web-plugins/code-highlight/package.json +13 -0
- package/dist/pi-web-plugins/files/browser/assets/files-icon-DZObYhxb.svg +3 -0
- package/dist/pi-web-plugins/files/browser/pi-web-plugin.js +466 -0
- package/dist/pi-web-plugins/files/package.json +15 -0
- package/dist/pi-web-plugins/git/browser/git-contract.js +22 -11
- package/dist/pi-web-plugins/git/browser/git-panel.js +228 -100
- package/dist/pi-web-plugins/git/browser/i18n.js +7 -0
- package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +28 -4
- package/dist/pi-web-plugins/git/git-backend.js +61 -21
- package/dist/pi-web-plugins/git/gitStatusCache.js +48 -0
- package/dist/pi-web-plugins/git/i18n.js +7 -0
- package/dist/pi-web-plugins/git/package.json +1 -0
- package/dist/pi-web-plugins/git/server-plugin.js +45 -24
- package/dist/pi-web-plugins/git/workspace-discovery-notify.cjs +14 -0
- package/dist/pi-web-plugins/git/workspaceDiscoveryHooks.js +186 -0
- package/dist/pi-web-plugins/info/i18n.js +7 -0
- package/dist/pi-web-plugins/info/infoInternals.js +76 -65
- package/dist/pi-web-plugins/info/package.json +1 -1
- package/dist/pi-web-plugins/info/pi-web-plugin.js +40 -42
- package/dist/pi-web-plugins/mermaid/browser/mermaid-engine.js +4255 -0
- package/dist/pi-web-plugins/mermaid/browser/pi-web-plugin.js +11 -0
- package/dist/pi-web-plugins/mermaid/package.json +13 -0
- package/dist/pi-web-plugins/terminal/browser/pi-web-plugin.js +219 -0
- package/dist/pi-web-plugins/terminal/i18n.js +7 -0
- package/dist/pi-web-plugins/terminal/package.json +16 -0
- package/dist/pi-web-plugins/terminal/server-plugin.js +367 -0
- package/dist/pi-web-plugins/terminal/terminalService.js +436 -0
- package/dist/pi-web-plugins/updates/i18n.js +7 -0
- package/dist/pi-web-plugins/updates/package.json +1 -1
- package/dist/pi-web-plugins/updates/pi-web-plugin.js +143 -140
- package/dist/pi-web-plugins/updates/updatesLogic.js +24 -24
- package/dist/pi-web-plugins/workspace-tasks/config.js +12 -11
- package/dist/pi-web-plugins/workspace-tasks/i18n.js +7 -0
- package/dist/pi-web-plugins/workspace-tasks/package.json +1 -1
- package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +31 -9
- package/dist/pi-web-plugins/workspace-tasks/tasksPanelElement.js +54 -36
- package/dist/pi-web-plugins/workspace-tasks/workspaceTasksClient.js +12 -11
- package/dist/pi-web-plugins/zh-cn/auth.json +9 -0
- package/dist/pi-web-plugins/zh-cn/bundled.json +219 -0
- package/dist/pi-web-plugins/zh-cn/cli.json +230 -0
- package/dist/pi-web-plugins/zh-cn/client.json +225 -0
- package/dist/pi-web-plugins/zh-cn/components.json +1252 -0
- package/dist/pi-web-plugins/zh-cn/config-errors.json +188 -0
- package/dist/pi-web-plugins/zh-cn/files.json +50 -0
- package/dist/pi-web-plugins/zh-cn/machines.json +28 -0
- package/dist/pi-web-plugins/zh-cn/main.json +459 -0
- package/dist/pi-web-plugins/zh-cn/management-errors.json +23 -0
- package/dist/pi-web-plugins/zh-cn/manifest.webmanifest +25 -0
- package/dist/pi-web-plugins/zh-cn/metadata.json +3 -0
- package/dist/pi-web-plugins/zh-cn/package.json +10 -0
- package/dist/pi-web-plugins/zh-cn/pi-web-plugin.js +16 -0
- package/dist/pi-web-plugins/zh-cn/projects.json +6 -0
- package/dist/pi-web-plugins/zh-cn/provider-auth.json +18 -0
- package/dist/pi-web-plugins/zh-cn/routing.json +68 -0
- package/dist/pi-web-plugins/zh-cn/server-main.json +99 -0
- package/dist/pi-web-plugins/zh-cn/server.json +256 -0
- package/dist/piAi.js +4 -0
- package/dist/piAi.js.map +1 -0
- package/dist/piRuntime.js +150 -0
- package/dist/piRuntime.js.map +1 -0
- package/dist/piSdk.js +5 -0
- package/dist/piSdk.js.map +1 -0
- package/dist/piWebUpdate.js +508 -0
- package/dist/piWebUpdate.js.map +1 -0
- package/dist/piWebVersionReport.js +109 -41
- package/dist/piWebVersionReport.js.map +1 -1
- package/dist/plugin-api.d.ts +609 -29
- package/dist/plugin-api.js +1 -0
- package/dist/pluginRecoveryCli.js +27 -25
- package/dist/pluginRecoveryCli.js.map +1 -1
- package/dist/server/activity/workspaceActivityService.js +13 -6
- package/dist/server/activity/workspaceActivityService.js.map +1 -1
- package/dist/server/app.js +155 -77
- package/dist/server/app.js.map +1 -1
- package/dist/server/audit/managementAuditStore.js +6 -1
- package/dist/server/audit/managementAuditStore.js.map +1 -1
- package/dist/server/audit/normalToolAuditCli.js +45 -43
- package/dist/server/audit/normalToolAuditCli.js.map +1 -1
- package/dist/server/browserMessageProjection.js +39 -22
- package/dist/server/browserMessageProjection.js.map +1 -1
- package/dist/server/configRoutes.js +54 -33
- package/dist/server/configRoutes.js.map +1 -1
- package/dist/server/deploymentIdentity.js +67 -0
- package/dist/server/deploymentIdentity.js.map +1 -0
- package/dist/server/deploymentIdentityRoutes.js +22 -0
- package/dist/server/deploymentIdentityRoutes.js.map +1 -0
- package/dist/server/diagnostics/nodePtyNativeModule.js +7 -6
- package/dist/server/diagnostics/nodePtyNativeModule.js.map +1 -1
- package/dist/server/diagnostics/nodePtySpawnHelper.js +21 -20
- package/dist/server/diagnostics/nodePtySpawnHelper.js.map +1 -1
- package/dist/server/index.js +10 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/knownAutoInstallPiPackages.js +36 -0
- package/dist/server/knownAutoInstallPiPackages.js.map +1 -0
- package/dist/server/knownPiPackages.js +12 -0
- package/dist/server/knownPiPackages.js.map +1 -0
- package/dist/server/machines/machineClient.js +23 -19
- package/dist/server/machines/machineClient.js.map +1 -1
- package/dist/server/machines/machinePluginProxyRoutes.js +131 -31
- package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -1
- package/dist/server/machines/machineProxyRoutes.js +138 -47
- package/dist/server/machines/machineProxyRoutes.js.map +1 -1
- package/dist/server/machines/machineRoutes.js +18 -13
- package/dist/server/machines/machineRoutes.js.map +1 -1
- package/dist/server/machines/machineService.js +40 -18
- package/dist/server/machines/machineService.js.map +1 -1
- package/dist/server/managementEmbed.js +149 -30
- package/dist/server/managementEmbed.js.map +1 -1
- package/dist/server/managementPrivileges.js +21 -0
- package/dist/server/managementPrivileges.js.map +1 -0
- package/dist/server/managementTerminalProxyRoutes.js +124 -0
- package/dist/server/managementTerminalProxyRoutes.js.map +1 -0
- package/dist/server/navigationPreferenceRoutes.js +59 -0
- package/dist/server/navigationPreferenceRoutes.js.map +1 -0
- package/dist/server/normalAuth.js +77 -53
- package/dist/server/normalAuth.js.map +1 -1
- package/dist/server/notices/serverNoticeRoutes.js +43 -0
- package/dist/server/notices/serverNoticeRoutes.js.map +1 -0
- package/dist/server/notices/serverNoticeService.js +26 -0
- package/dist/server/notices/serverNoticeService.js.map +1 -0
- package/dist/server/notices/serverNoticeStore.js +122 -0
- package/dist/server/notices/serverNoticeStore.js.map +1 -0
- package/dist/server/passwordVerifier.js +91 -0
- package/dist/server/passwordVerifier.js.map +1 -0
- package/dist/server/piPackageIdentity.js +25 -0
- package/dist/server/piPackageIdentity.js.map +1 -0
- package/dist/server/piPackageService.js +72 -9
- package/dist/server/piPackageService.js.map +1 -1
- package/dist/server/piWebPluginCatalog.js +212 -35
- package/dist/server/piWebPluginCatalog.js.map +1 -1
- package/dist/server/piWebPluginLifecycle.js +29 -3
- package/dist/server/piWebPluginLifecycle.js.map +1 -1
- package/dist/server/piWebPluginService.js +199 -292
- package/dist/server/piWebPluginService.js.map +1 -1
- package/dist/server/piWebStatus.js +57 -100
- package/dist/server/piWebStatus.js.map +1 -1
- package/dist/server/pluginCallbackDrain.js +35 -0
- package/dist/server/pluginCallbackDrain.js.map +1 -0
- package/dist/server/plugins/pluginBackendChannelProxyAdmission.js +84 -0
- package/dist/server/plugins/pluginBackendChannelProxyAdmission.js.map +1 -0
- package/dist/server/plugins/pluginBackendChannelProxyCoordinator.js +234 -0
- package/dist/server/plugins/pluginBackendChannelProxyCoordinator.js.map +1 -0
- package/dist/server/plugins/pluginBackendChannelProxyRoutes.js +61 -0
- package/dist/server/plugins/pluginBackendChannelProxyRoutes.js.map +1 -0
- package/dist/server/plugins/pluginBackendProxyRoutes.js +47 -57
- package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -1
- package/dist/server/plugins/pluginBackendRegistry.js +804 -0
- package/dist/server/plugins/pluginBackendRegistry.js.map +1 -0
- package/dist/server/plugins/projectPluginSettings.js +95 -0
- package/dist/server/plugins/projectPluginSettings.js.map +1 -0
- package/dist/server/plugins/serverPluginCatalogCapability.js +106 -0
- package/dist/server/plugins/serverPluginCatalogCapability.js.map +1 -0
- package/dist/server/plugins/serverPluginPiSessionEventsCapability.js +34 -0
- package/dist/server/plugins/serverPluginPiSessionEventsCapability.js.map +1 -0
- package/dist/server/plugins/serverPluginPiSessionsCapability.js +185 -0
- package/dist/server/plugins/serverPluginPiSessionsCapability.js.map +1 -0
- package/dist/server/plugins/serverPluginRuntime.js +1088 -108
- package/dist/server/plugins/serverPluginRuntime.js.map +1 -1
- package/dist/server/plugins/serverPluginWorkspacesCapability.js +114 -0
- package/dist/server/plugins/serverPluginWorkspacesCapability.js.map +1 -0
- package/dist/server/projectTrustRoutes.js +86 -0
- package/dist/server/projectTrustRoutes.js.map +1 -0
- package/dist/server/projects/projectLifecycleService.js +94 -0
- package/dist/server/projects/projectLifecycleService.js.map +1 -0
- package/dist/server/projects/projectService.js +7 -4
- package/dist/server/projects/projectService.js.map +1 -1
- package/dist/server/realtime/sessionEventHub.js +58 -39
- package/dist/server/realtime/sessionEventHub.js.map +1 -1
- package/dist/server/realtime/sessionEventScope.js +4 -0
- package/dist/server/realtime/sessionEventScope.js.map +1 -1
- package/dist/server/requestCancellation.js +15 -1
- package/dist/server/requestCancellation.js.map +1 -1
- package/dist/server/sessiond/agentHttpDispatcher.js +108 -0
- package/dist/server/sessiond/agentHttpDispatcher.js.map +1 -0
- package/dist/server/sessiond/autoInstallPiPackages.js +63 -0
- package/dist/server/sessiond/autoInstallPiPackages.js.map +1 -0
- package/dist/server/sessiond/pluginBackendChannelRoutes.js +410 -0
- package/dist/server/sessiond/pluginBackendChannelRoutes.js.map +1 -0
- package/dist/server/sessiond/pluginBackendRoutes.js +64 -47
- package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -1
- package/dist/server/sessiond/pluginCatalogRoutes.js +41 -0
- package/dist/server/sessiond/pluginCatalogRoutes.js.map +1 -0
- package/dist/server/sessiond/projectMutationRoutes.js +39 -0
- package/dist/server/sessiond/projectMutationRoutes.js.map +1 -0
- package/dist/server/sessiond/sessionDaemonShutdown.js +36 -0
- package/dist/server/sessiond/sessionDaemonShutdown.js.map +1 -0
- package/dist/server/sessiond/sessionDaemonStartup.js +30 -4
- package/dist/server/sessiond/sessionDaemonStartup.js.map +1 -1
- package/dist/server/sessiond/sessionProxyRoutes.js +263 -91
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
- package/dist/server/sessiond/sessionServiceDependencies.js +5 -0
- package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -1
- package/dist/server/sessiond/sessiondStateOwnership.js +174 -39
- package/dist/server/sessiond/sessiondStateOwnership.js.map +1 -1
- package/dist/server/sessiond/workspaceCatalogRoutes.js +11 -5
- package/dist/server/sessiond/workspaceCatalogRoutes.js.map +1 -1
- package/dist/server/sessiond/workspaceNotificationRoutes.js +55 -0
- package/dist/server/sessiond/workspaceNotificationRoutes.js.map +1 -0
- package/dist/server/sessiond/workspaceRemovalRoutes.js +1 -1
- package/dist/server/sessiond/workspaceRemovalRoutes.js.map +1 -1
- package/dist/server/sessiond.js +313 -69
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/askUserDomain.js +232 -0
- package/dist/server/sessions/askUserDomain.js.map +1 -0
- package/dist/server/sessions/askUserTool.js +18 -21
- package/dist/server/sessions/askUserTool.js.map +1 -1
- package/dist/server/sessions/attachmentService.js +2 -6
- package/dist/server/sessions/attachmentService.js.map +1 -1
- package/dist/server/sessions/authRoutes.js +32 -19
- package/dist/server/sessions/authRoutes.js.map +1 -1
- package/dist/server/sessions/authService.js +31 -11
- package/dist/server/sessions/authService.js.map +1 -1
- package/dist/server/sessions/builtinCommands.js +28 -21
- package/dist/server/sessions/builtinCommands.js.map +1 -1
- package/dist/server/sessions/builtinExtensionFactories.js +36 -0
- package/dist/server/sessions/builtinExtensionFactories.js.map +1 -0
- package/dist/server/sessions/clientSessionPreview.js +14 -0
- package/dist/server/sessions/clientSessionPreview.js.map +1 -0
- package/dist/server/sessions/companionBackend.js +286 -0
- package/dist/server/sessions/companionBackend.js.map +1 -0
- package/dist/server/sessions/dockerEnvironmentFacts.js +98 -10
- package/dist/server/sessions/dockerEnvironmentFacts.js.map +1 -1
- package/dist/server/sessions/extensionSessionDispatch.js +76 -0
- package/dist/server/sessions/extensionSessionDispatch.js.map +1 -0
- package/dist/server/sessions/globalProviderPolicy.js +88 -19
- package/dist/server/sessions/globalProviderPolicy.js.map +1 -1
- package/dist/server/sessions/managementAgentTools.js +116 -6
- package/dist/server/sessions/managementAgentTools.js.map +1 -1
- package/dist/server/sessions/managementPermissionSystem.js +36 -14
- package/dist/server/sessions/managementPermissionSystem.js.map +1 -1
- package/dist/server/sessions/managementSandbox.js +7 -18
- package/dist/server/sessions/managementSandbox.js.map +1 -1
- package/dist/server/sessions/mcpAuthFlowService.js +289 -0
- package/dist/server/sessions/mcpAuthFlowService.js.map +1 -0
- package/dist/server/sessions/mcpAuthRoutes.js +104 -0
- package/dist/server/sessions/mcpAuthRoutes.js.map +1 -0
- package/dist/server/sessions/mcpOAuthCredentialAdapter.js +92 -0
- package/dist/server/sessions/mcpOAuthCredentialAdapter.js.map +1 -0
- package/dist/server/sessions/messageHistoryTarget.js +79 -0
- package/dist/server/sessions/messageHistoryTarget.js.map +1 -0
- package/dist/server/sessions/messagePaging.js +2 -2
- package/dist/server/sessions/messagePaging.js.map +1 -1
- package/dist/server/sessions/modelCatalogRefresher.js +4 -3
- package/dist/server/sessions/modelCatalogRefresher.js.map +1 -1
- package/dist/server/sessions/oauthLoginFlowService.js +19 -20
- package/dist/server/sessions/oauthLoginFlowService.js.map +1 -1
- package/dist/server/sessions/piSessionEventConnections.js +68 -0
- package/dist/server/sessions/piSessionEventConnections.js.map +1 -0
- package/dist/server/sessions/piSessionManagerGateway.js +206 -27
- package/dist/server/sessions/piSessionManagerGateway.js.map +1 -1
- package/dist/server/sessions/piSessionService.js +2508 -991
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/plainTextTheme.js +1 -1
- package/dist/server/sessions/plainTextTheme.js.map +1 -1
- package/dist/server/sessions/projectAuthService.js +35 -6
- package/dist/server/sessions/projectAuthService.js.map +1 -1
- package/dist/server/sessions/projectSettingsScope.js +88 -33
- package/dist/server/sessions/projectSettingsScope.js.map +1 -1
- package/dist/server/sessions/projectTrust.js +119 -0
- package/dist/server/sessions/projectTrust.js.map +1 -0
- package/dist/server/sessions/queuedMessageMutation.js +170 -0
- package/dist/server/sessions/queuedMessageMutation.js.map +1 -0
- package/dist/server/sessions/relaySkill.js +169 -0
- package/dist/server/sessions/relaySkill.js.map +1 -0
- package/dist/server/sessions/sessionActivityMarker.js +105 -0
- package/dist/server/sessions/sessionActivityMarker.js.map +1 -0
- package/dist/server/sessions/sessionArchiveMapping.js +16 -15
- package/dist/server/sessions/sessionArchiveMapping.js.map +1 -1
- package/dist/server/sessions/sessionArchiveStore.js +5 -1
- package/dist/server/sessions/sessionArchiveStore.js.map +1 -1
- package/dist/server/sessions/sessionArchiveTree.js +2 -2
- package/dist/server/sessions/sessionArchiveTree.js.map +1 -1
- package/dist/server/sessions/sessionCleanup.js +4 -3
- package/dist/server/sessions/sessionCleanup.js.map +1 -1
- package/dist/server/sessions/sessionCommandService.js +79 -87
- package/dist/server/sessions/sessionCommandService.js.map +1 -1
- package/dist/server/sessions/sessionEnvironmentFacts.js +5 -4
- package/dist/server/sessions/sessionEnvironmentFacts.js.map +1 -1
- package/dist/server/sessions/sessionFileHeader.js +41 -0
- package/dist/server/sessions/sessionFileHeader.js.map +1 -1
- package/dist/server/sessions/sessionImageDimensions.js +31 -0
- package/dist/server/sessions/sessionImageDimensions.js.map +1 -0
- package/dist/server/sessions/sessionMediaIndex.js +191 -0
- package/dist/server/sessions/sessionMediaIndex.js.map +1 -0
- package/dist/server/sessions/sessionMetadata.js +51 -0
- package/dist/server/sessions/sessionMetadata.js.map +1 -0
- package/dist/server/sessions/sessionModelScope.js +160 -0
- package/dist/server/sessions/sessionModelScope.js.map +1 -0
- package/dist/server/sessions/sessionNameGenerator.js +16 -35
- package/dist/server/sessions/sessionNameGenerator.js.map +1 -1
- package/dist/server/sessions/sessionNotificationStore.js +14 -11
- package/dist/server/sessions/sessionNotificationStore.js.map +1 -1
- package/dist/server/sessions/sessionReadAccess.js +41 -0
- package/dist/server/sessions/sessionReadAccess.js.map +1 -0
- package/dist/server/sessions/sessionRoutes.js +221 -78
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/sessions/sessionSummaryScanner.js +62 -5
- package/dist/server/sessions/sessionSummaryScanner.js.map +1 -1
- package/dist/server/sessions/sessionTreeProjection.js +50 -49
- package/dist/server/sessions/sessionTreeProjection.js.map +1 -1
- package/dist/server/sessions/sessionUiEvents.js +22 -10
- package/dist/server/sessions/sessionUiEvents.js.map +1 -1
- package/dist/server/sessions/sessionUnreadStore.js +53 -3
- package/dist/server/sessions/sessionUnreadStore.js.map +1 -1
- package/dist/server/sessions/spawnSessionTool.js +14 -6
- package/dist/server/sessions/spawnSessionTool.js.map +1 -1
- package/dist/server/sessions/spawnSubsessionTool.js +40 -24
- package/dist/server/sessions/spawnSubsessionTool.js.map +1 -1
- package/dist/server/sessions/spawnTargetResolver.js +1 -1
- package/dist/server/sessions/subsessionCompletionDelivery.js +15 -0
- package/dist/server/sessions/subsessionCompletionDelivery.js.map +1 -0
- package/dist/server/sessions/subsessionCompletionInbox.js +134 -0
- package/dist/server/sessions/subsessionCompletionInbox.js.map +1 -0
- package/dist/server/sessions/subsessionProvenance.js +27 -0
- package/dist/server/sessions/subsessionProvenance.js.map +1 -0
- package/dist/server/sessions/transcriptBranchCache.js +81 -0
- package/dist/server/sessions/transcriptBranchCache.js.map +1 -0
- package/dist/server/sessions/transcriptMarkdown.js +48 -0
- package/dist/server/sessions/transcriptMarkdown.js.map +1 -0
- package/dist/server/sessions/transcriptMessages.js +57 -0
- package/dist/server/sessions/transcriptMessages.js.map +1 -0
- package/dist/server/startupLogger.js +13 -0
- package/dist/server/startupLogger.js.map +1 -0
- package/dist/server/startupUrls.js +27 -0
- package/dist/server/startupUrls.js.map +1 -0
- package/dist/server/status/machineStatusService.js +15 -7
- package/dist/server/status/machineStatusService.js.map +1 -1
- package/dist/server/storage/navigationPreferenceStore.js +138 -0
- package/dist/server/storage/navigationPreferenceStore.js.map +1 -0
- package/dist/server/storage/piPackageDismissalStore.js +78 -0
- package/dist/server/storage/piPackageDismissalStore.js.map +1 -0
- package/dist/server/terminals/{terminalService.js → managementCommandRunService.js} +98 -102
- package/dist/server/terminals/managementCommandRunService.js.map +1 -0
- package/dist/server/terminals/managementTerminalRoutes.js +91 -0
- package/dist/server/terminals/managementTerminalRoutes.js.map +1 -0
- package/dist/server/terminals/requiredTerminalService.js +103 -0
- package/dist/server/terminals/requiredTerminalService.js.map +1 -0
- package/dist/server/webSocketBridge.js +348 -0
- package/dist/server/webSocketBridge.js.map +1 -1
- package/dist/server/workbench/accessStateStore.js +33 -5
- package/dist/server/workbench/accessStateStore.js.map +1 -1
- package/dist/server/workbench/capabilitySearch.js +3 -2
- package/dist/server/workbench/capabilitySearch.js.map +1 -1
- package/dist/server/workbench/gatewayIntegration.js +40 -13
- package/dist/server/workbench/gatewayIntegration.js.map +1 -1
- package/dist/server/workbench/knowledgeSearch.js +100 -0
- package/dist/server/workbench/knowledgeSearch.js.map +1 -0
- package/dist/server/workbench/mcpClient.js +41 -8
- package/dist/server/workbench/mcpClient.js.map +1 -1
- package/dist/server/workbench/skillSync.js +11 -3
- package/dist/server/workbench/skillSync.js.map +1 -1
- package/dist/server/workbench/workbenchClient.js +130 -7
- package/dist/server/workbench/workbenchClient.js.map +1 -1
- package/dist/server/workbench/workbenchTools.js +196 -14
- package/dist/server/workbench/workbenchTools.js.map +1 -1
- package/dist/server/workingDirectory.js +3 -2
- package/dist/server/workingDirectory.js.map +1 -1
- package/dist/server/workspaceExplorerRoutes.js +53 -21
- package/dist/server/workspaceExplorerRoutes.js.map +1 -1
- package/dist/server/workspaces/fileContentService.js +31 -16
- package/dist/server/workspaces/fileContentService.js.map +1 -1
- package/dist/server/workspaces/fileOperationService.js +11 -9
- package/dist/server/workspaces/fileOperationService.js.map +1 -1
- package/dist/server/workspaces/filePreviewService.js +10 -6
- package/dist/server/workspaces/filePreviewService.js.map +1 -1
- package/dist/server/workspaces/fileSuggestions.js +77 -10
- package/dist/server/workspaces/fileSuggestions.js.map +1 -1
- package/dist/server/workspaces/fileTreeService.js +103 -15
- package/dist/server/workspaces/fileTreeService.js.map +1 -1
- package/dist/server/workspaces/normalProjectCwdResolver.js +35 -0
- package/dist/server/workspaces/normalProjectCwdResolver.js.map +1 -0
- package/dist/server/workspaces/pathAccessPolicy.js +24 -18
- package/dist/server/workspaces/pathAccessPolicy.js.map +1 -1
- package/dist/server/workspaces/pathSafety.js +6 -5
- package/dist/server/workspaces/pathSafety.js.map +1 -1
- package/dist/server/workspaces/projectPiWebConfig.js +95 -8
- package/dist/server/workspaces/projectPiWebConfig.js.map +1 -1
- package/dist/server/workspaces/projectWorkspaceCwds.js +0 -5
- package/dist/server/workspaces/projectWorkspaceCwds.js.map +1 -1
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js +37 -22
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js.map +1 -1
- package/dist/server/workspaces/workspaceCatalog.js +6 -4
- package/dist/server/workspaces/workspaceCatalog.js.map +1 -1
- package/dist/server/workspaces/workspaceChangeNotifier.js +12 -0
- package/dist/server/workspaces/workspaceChangeNotifier.js.map +1 -0
- package/dist/server/workspaces/workspaceDeletionRoutes.js +9 -70
- package/dist/server/workspaces/workspaceDeletionRoutes.js.map +1 -1
- package/dist/server/workspaces/workspaceNotificationCommand.js +64 -0
- package/dist/server/workspaces/workspaceNotificationCommand.js.map +1 -0
- package/dist/server/workspaces/workspaceProviderRegistry.js +128 -24
- package/dist/server/workspaces/workspaceProviderRegistry.js.map +1 -1
- package/dist/server/workspaces/workspaceRemovalService.js +41 -7
- package/dist/server/workspaces/workspaceRemovalService.js.map +1 -1
- package/dist/server/workspaces/workspaceRouteContext.js +1 -1
- package/dist/server/workspaces/workspaceRouteContext.js.map +1 -1
- package/dist/server/workspaces/workspaceRouteErrors.js +4 -2
- package/dist/server/workspaces/workspaceRouteErrors.js.map +1 -1
- package/dist/server/workspaces/workspaceUserError.js +18 -0
- package/dist/server/workspaces/workspaceUserError.js.map +1 -0
- package/dist/server-plugin-api.d.ts +349 -49
- package/dist/server-plugin-api.js +344 -2
- package/dist/server-plugin-api.js.map +1 -1
- package/dist/serverPluginRecovery.js +6 -1
- package/dist/serverPluginRecovery.js.map +1 -1
- package/dist/sessiond/activeAgentProfile.js +27 -2
- package/dist/sessiond/activeAgentProfile.js.map +1 -1
- package/dist/sessiond/config.js +4 -3
- package/dist/sessiond/config.js.map +1 -1
- package/dist/sessiond/sessionDaemonClient.js +75 -19
- package/dist/sessiond/sessionDaemonClient.js.map +1 -1
- package/dist/sessiond/sessionDaemonErrors.js +49 -0
- package/dist/sessiond/sessionDaemonErrors.js.map +1 -0
- package/dist/shared/activeAgentProfile.js +2 -34
- package/dist/shared/activeAgentProfile.js.map +1 -1
- package/dist/shared/activity.js +0 -3
- package/dist/shared/activity.js.map +1 -1
- package/dist/shared/apiTypes.js +10 -3
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/capabilities.js +8 -0
- package/dist/shared/capabilities.js.map +1 -1
- package/dist/shared/federatedRoutes.js +77 -11
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/dist/shared/i18n.js +41 -0
- package/dist/shared/i18n.js.map +1 -0
- package/dist/shared/machinePluginIds.js +30 -7
- package/dist/shared/machinePluginIds.js.map +1 -1
- package/dist/shared/machineStatus.js +46 -3
- package/dist/shared/machineStatus.js.map +1 -1
- package/dist/shared/navigationPreferences.js +6 -0
- package/dist/shared/navigationPreferences.js.map +1 -0
- package/dist/shared/piWebConfigParsing.js +118 -49
- package/dist/shared/piWebConfigParsing.js.map +1 -1
- package/dist/shared/piWebStatusParsing.js +33 -0
- package/dist/shared/piWebStatusParsing.js.map +1 -1
- package/dist/shared/pluginApiTypes.d.ts +87 -8
- package/dist/shared/pluginApiTypes.js +2 -0
- package/dist/shared/pluginApiTypes.js.map +1 -0
- package/dist/shared/pluginBackendProtocol.js +218 -2
- package/dist/shared/pluginBackendProtocol.js.map +1 -1
- package/dist/shared/pluginCatalogTypes.d.ts +49 -0
- package/dist/shared/pluginCatalogTypes.js +156 -0
- package/dist/shared/pluginCatalogTypes.js.map +1 -0
- package/dist/shared/pluginIds.js +10 -1
- package/dist/shared/pluginIds.js.map +1 -1
- package/dist/shared/requiredTerminalPlugin.js +6 -0
- package/dist/shared/requiredTerminalPlugin.js.map +1 -0
- package/dist/shared/serverNoticeContract.js +88 -0
- package/dist/shared/serverNoticeContract.js.map +1 -0
- package/dist/shared/sessionDefaults.js +42 -0
- package/dist/shared/sessionDefaults.js.map +1 -0
- package/dist/shared/sessionMedia.js +12 -0
- package/dist/shared/sessionMedia.js.map +1 -0
- package/dist/shared/streamingBehavior.js +8 -0
- package/dist/shared/streamingBehavior.js.map +1 -0
- package/dist/shared/themePreference.js +24 -0
- package/dist/shared/themePreference.js.map +1 -0
- package/dist/shared/thinkingLevels.js +17 -16
- package/dist/shared/thinkingLevels.js.map +1 -1
- package/dist/shared/workspaceFiles.js.map +1 -1
- package/dist/workspaceNotificationCli.js +95 -0
- package/dist/workspaceNotificationCli.js.map +1 -0
- package/docs/README.md +31 -0
- package/docs/config.md +344 -277
- package/docs/plugins.md +204 -930
- package/examples/session-bridge-plugin/README.md +20 -0
- package/examples/session-bridge-plugin/docs/usage.md +54 -0
- package/examples/session-bridge-plugin/package.json +26 -0
- package/examples/session-bridge-plugin/src/browser/index.ts +87 -0
- package/examples/session-bridge-plugin/src/browser/protocol.ts +24 -0
- package/examples/session-bridge-plugin/src/companion.ts +65 -0
- package/examples/session-bridge-plugin/src/reviewRun.ts +39 -0
- package/examples/session-bridge-plugin/src/server.ts +85 -0
- package/examples/session-bridge-plugin/src/store.ts +80 -0
- package/examples/session-bridge-plugin/tsconfig.json +21 -0
- package/examples/workspace-provider-plugin/README.md +39 -0
- package/examples/workspace-provider-plugin/package.json +25 -0
- package/examples/workspace-provider-plugin/src/browser/index.ts +69 -0
- package/examples/workspace-provider-plugin/src/server.ts +68 -0
- package/examples/workspace-provider-plugin/tsconfig.json +21 -0
- package/extensions/pi-web.ts +3 -1
- package/package.json +59 -36
- package/skills/README.md +28 -2
- package/skills/relay/SKILL.md +24 -89
- package/skills/relay-runner/SKILL.md +74 -0
- package/dist/client/assets/CodeViewer-DCl56K0Z.js +0 -4
- package/dist/client/assets/TerminalPanel-DCvoEjUF.js +0 -187
- package/dist/client/assets/index-CMvms5cF.js +0 -4325
- package/dist/client/assets/vendor-editor-core-vUi74DnD.js +0 -12
- package/dist/client/assets/vendor-editor-languages-DoaXUodB.js +0 -46
- package/dist/client/assets/vendor-editor-legacy-CYBnW6ZU.js +0 -1
- package/dist/client/assets/vendor-terminal-BrP-ENHg.css +0 -1
- package/dist/client/assets/vendor-terminal-D8k4UKM2.js +0 -35
- package/dist/pi-web-plugins/relays/package.json +0 -9
- package/dist/pi-web-plugins/relays/pi-web-plugin.js +0 -44
- package/dist/server/git/gitService.js +0 -290
- package/dist/server/git/gitService.js.map +0 -1
- package/dist/server/gitRoutes.js +0 -23
- package/dist/server/gitRoutes.js.map +0 -1
- package/dist/server/sessions/pendingAskStore.js +0 -278
- package/dist/server/sessions/pendingAskStore.js.map +0 -1
- package/dist/server/terminalProxyRoutes.js +0 -178
- package/dist/server/terminalProxyRoutes.js.map +0 -1
- package/dist/server/terminals/terminalRoutes.js +0 -164
- package/dist/server/terminals/terminalRoutes.js.map +0 -1
- package/dist/server/terminals/terminalService.js.map +0 -1
- package/dist/server/terminals/terminalSize.js +0 -17
- package/dist/server/terminals/terminalSize.js.map +0 -1
- package/dist/server/workspaces/gitWorktreeDiscovery.js +0 -43
- package/dist/server/workspaces/gitWorktreeDiscovery.js.map +0 -1
- package/dist/server/workspaces/workspaceService.js +0 -52
- package/dist/server/workspaces/workspaceService.js.map +0 -1
- /package/dist/{pi-web-plugins → pi-packages}/relays/vendor/README.md +0 -0
- /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
|
-
*
|
|
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.
|
|
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
|
-
/**
|
|
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
|
-
|
|
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(
|
|
190
|
+
.sort(requiredTerminalFirst);
|
|
107
191
|
for (const entry of serverEntries)
|
|
108
|
-
await this.
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
142
|
-
|
|
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
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
|
191
|
-
|
|
192
|
-
|
|
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, "
|
|
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
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
|
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
|
|
700
|
+
if (typeof activateValue !== "function") {
|
|
261
701
|
throw new IncompatibleServerPluginError("Server plugin activate must be a function");
|
|
262
702
|
}
|
|
263
|
-
const
|
|
264
|
-
return Object.freeze({
|
|
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
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
744
|
+
dispose: value["dispose"],
|
|
284
745
|
health: value["health"],
|
|
285
746
|
};
|
|
286
|
-
for (const callback of ["start", "
|
|
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
|
|
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
|
-
...(
|
|
300
|
-
...(
|
|
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
|
|
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
|
-
&& (
|
|
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
|
-
&& (
|
|
352
|
-
&& (
|
|
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 (!
|
|
1279
|
+
function cloneJsonObject(value, label, limits = {}) {
|
|
1280
|
+
if (!isPlainRecord(value))
|
|
409
1281
|
throw new IncompatibleServerPluginError(`${label} must be a JSON object`);
|
|
410
|
-
|
|
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
|
-
|
|
418
|
-
|
|
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
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|