@vincenthanxiaodu/pi-web 1.202608.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/dist/cli.js +1149 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/apple-touch-icon.png +0 -0
- package/dist/client/assets/CodeViewer-D_Q6ljLV.js +4 -0
- package/dist/client/assets/TerminalPanel-CTGixJIz.js +187 -0
- package/dist/client/assets/index-C2phil_t.js +5637 -0
- package/dist/client/assets/vendor-editor-core-CXO8gGab.js +12 -0
- package/dist/client/assets/vendor-editor-languages-CpW4sJsX.js +46 -0
- package/dist/client/assets/vendor-editor-legacy-CYBnW6ZU.js +1 -0
- package/dist/client/assets/vendor-terminal-BrP-ENHg.css +1 -0
- package/dist/client/assets/vendor-terminal-D8k4UKM2.js +35 -0
- package/dist/client/favicon.svg +11 -0
- package/dist/client/index.html +124 -0
- package/dist/client/manifest.webmanifest +24 -0
- package/dist/client/pwa-icon-192.png +0 -0
- package/dist/client/pwa-icon-512.png +0 -0
- package/dist/config.js +505 -0
- package/dist/config.js.map +1 -0
- package/dist/docker/piWebDockerCommandPlan.js +212 -0
- package/dist/docker/piWebDockerCommandPlan.js.map +1 -0
- package/dist/environment.js +4 -0
- package/dist/environment.js.map +1 -0
- package/dist/nativeServices/installedServiceDefinitions.js +602 -0
- package/dist/nativeServices/installedServiceDefinitions.js.map +1 -0
- package/dist/nativeServices/serviceAction.js +174 -0
- package/dist/nativeServices/serviceAction.js.map +1 -0
- package/dist/nativeServices/serviceDoctor.js +783 -0
- package/dist/nativeServices/serviceDoctor.js.map +1 -0
- package/dist/nativeServices/serviceInstall.js +35 -0
- package/dist/nativeServices/serviceInstall.js.map +1 -0
- package/dist/nativeServices/servicePlan.js +358 -0
- package/dist/nativeServices/servicePlan.js.map +1 -0
- package/dist/nativeServices/serviceProbe.js +447 -0
- package/dist/nativeServices/serviceProbe.js.map +1 -0
- package/dist/nativeServices/serviceRendering.js +132 -0
- package/dist/nativeServices/serviceRendering.js.map +1 -0
- package/dist/pi-web-plugins/git/browser/git-contract.js +108 -0
- package/dist/pi-web-plugins/git/browser/git-panel.js +609 -0
- package/dist/pi-web-plugins/git/browser/gitFileList.js +43 -0
- package/dist/pi-web-plugins/git/browser/gitFileShared.js +16 -0
- package/dist/pi-web-plugins/git/browser/gitFileTree.js +81 -0
- package/dist/pi-web-plugins/git/browser/gitFileViewPreference.js +35 -0
- package/dist/pi-web-plugins/git/browser/gitRoute.js +42 -0
- package/dist/pi-web-plugins/git/browser/pi-web-plugin.js +10 -0
- package/dist/pi-web-plugins/git/browser/unifiedDiff.js +301 -0
- package/dist/pi-web-plugins/git/git-backend.js +439 -0
- package/dist/pi-web-plugins/git/package.json +16 -0
- package/dist/pi-web-plugins/git/server-plugin.js +176 -0
- package/dist/pi-web-plugins/info/infoInternals.js +222 -0
- package/dist/pi-web-plugins/info/package.json +9 -0
- package/dist/pi-web-plugins/info/pi-web-plugin.js +48 -0
- package/dist/pi-web-plugins/relays/markdownDocument.js +81 -0
- package/dist/pi-web-plugins/relays/package.json +9 -0
- package/dist/pi-web-plugins/relays/pi-web-plugin.js +44 -0
- package/dist/pi-web-plugins/relays/relayDiscovery.js +228 -0
- package/dist/pi-web-plugins/relays/relaysPanelElement.js +550 -0
- package/dist/pi-web-plugins/relays/vendor/README.md +24 -0
- package/dist/pi-web-plugins/relays/vendor/marked.esm.js +76 -0
- package/dist/pi-web-plugins/updates/package.json +9 -0
- package/dist/pi-web-plugins/updates/pi-web-plugin.js +185 -0
- package/dist/pi-web-plugins/updates/updatesLogic.js +97 -0
- package/dist/pi-web-plugins/workspace-tasks/config.js +91 -0
- package/dist/pi-web-plugins/workspace-tasks/package.json +9 -0
- package/dist/pi-web-plugins/workspace-tasks/pi-web-plugin.js +48 -0
- package/dist/pi-web-plugins/workspace-tasks/taskRunner.js +12 -0
- package/dist/pi-web-plugins/workspace-tasks/tasksPanelElement.js +288 -0
- package/dist/pi-web-plugins/workspace-tasks/workspaceTasksClient.js +47 -0
- package/dist/piWebVersionReport.js +278 -0
- package/dist/piWebVersionReport.js.map +1 -0
- package/dist/plugin-api.d.ts +222 -0
- package/dist/pluginRecoveryCli.js +172 -0
- package/dist/pluginRecoveryCli.js.map +1 -0
- package/dist/server/activeAgentProfileProvider.js +25 -0
- package/dist/server/activeAgentProfileProvider.js.map +1 -0
- package/dist/server/activity/workspaceActivityService.js +106 -0
- package/dist/server/activity/workspaceActivityService.js.map +1 -0
- package/dist/server/app.js +218 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/browserMessageProjection.js +54 -0
- package/dist/server/browserMessageProjection.js.map +1 -0
- package/dist/server/configRoutes.js +293 -0
- package/dist/server/configRoutes.js.map +1 -0
- package/dist/server/diagnostics/nodePtyNativeModule.js +36 -0
- package/dist/server/diagnostics/nodePtyNativeModule.js.map +1 -0
- package/dist/server/diagnostics/nodePtySpawnHelper.js +135 -0
- package/dist/server/diagnostics/nodePtySpawnHelper.js.map +1 -0
- package/dist/server/git/gitEnv.js +15 -0
- package/dist/server/git/gitEnv.js.map +1 -0
- package/dist/server/goals/goalArchive.js +184 -0
- package/dist/server/goals/goalArchive.js.map +1 -0
- package/dist/server/goals/goalFile.js +200 -0
- package/dist/server/goals/goalFile.js.map +1 -0
- package/dist/server/goals/goalStore.js +65 -0
- package/dist/server/goals/goalStore.js.map +1 -0
- package/dist/server/index.js +7 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/machines/machineClient.js +191 -0
- package/dist/server/machines/machineClient.js.map +1 -0
- package/dist/server/machines/machinePluginProxyRoutes.js +247 -0
- package/dist/server/machines/machinePluginProxyRoutes.js.map +1 -0
- package/dist/server/machines/machineProxyRoutes.js +318 -0
- package/dist/server/machines/machineProxyRoutes.js.map +1 -0
- package/dist/server/machines/machineRoutes.js +56 -0
- package/dist/server/machines/machineRoutes.js.map +1 -0
- package/dist/server/machines/machineService.js +274 -0
- package/dist/server/machines/machineService.js.map +1 -0
- package/dist/server/machines/machineStore.js +143 -0
- package/dist/server/machines/machineStore.js.map +1 -0
- package/dist/server/piPackageRoutes.js +91 -0
- package/dist/server/piPackageRoutes.js.map +1 -0
- package/dist/server/piPackageService.js +101 -0
- package/dist/server/piPackageService.js.map +1 -0
- package/dist/server/piWebPluginCatalog.js +584 -0
- package/dist/server/piWebPluginCatalog.js.map +1 -0
- package/dist/server/piWebPluginLifecycle.js +162 -0
- package/dist/server/piWebPluginLifecycle.js.map +1 -0
- package/dist/server/piWebPluginService.js +224 -0
- package/dist/server/piWebPluginService.js.map +1 -0
- package/dist/server/piWebReleaseLookupCache.js +37 -0
- package/dist/server/piWebReleaseLookupCache.js.map +1 -0
- package/dist/server/piWebStatus.js +624 -0
- package/dist/server/piWebStatus.js.map +1 -0
- package/dist/server/piWebStatusCache.js +45 -0
- package/dist/server/piWebStatusCache.js.map +1 -0
- package/dist/server/plugins/pluginBackendProxyRoutes.js +69 -0
- package/dist/server/plugins/pluginBackendProxyRoutes.js.map +1 -0
- package/dist/server/plugins/serverPluginExec.js +184 -0
- package/dist/server/plugins/serverPluginExec.js.map +1 -0
- package/dist/server/plugins/serverPluginRuntime.js +480 -0
- package/dist/server/plugins/serverPluginRuntime.js.map +1 -0
- package/dist/server/projectTrustRoutes.js +80 -0
- package/dist/server/projectTrustRoutes.js.map +1 -0
- package/dist/server/projects/directorySuggestions.js +207 -0
- package/dist/server/projects/directorySuggestions.js.map +1 -0
- package/dist/server/projects/projectService.js +33 -0
- package/dist/server/projects/projectService.js.map +1 -0
- package/dist/server/realtime/sessionEventHub.js +93 -0
- package/dist/server/realtime/sessionEventHub.js.map +1 -0
- package/dist/server/requestCancellation.js +32 -0
- package/dist/server/requestCancellation.js.map +1 -0
- package/dist/server/sessiond/agentHttpDispatcher.js +108 -0
- package/dist/server/sessiond/agentHttpDispatcher.js.map +1 -0
- package/dist/server/sessiond/agentProcessEnvironment.js +61 -0
- package/dist/server/sessiond/agentProcessEnvironment.js.map +1 -0
- package/dist/server/sessiond/pluginBackendRoutes.js +64 -0
- package/dist/server/sessiond/pluginBackendRoutes.js.map +1 -0
- package/dist/server/sessiond/sessionDaemonShutdown.js +31 -0
- package/dist/server/sessiond/sessionDaemonShutdown.js.map +1 -0
- package/dist/server/sessiond/sessionProxyRoutes.js +61 -0
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -0
- package/dist/server/sessiond/sessionServiceDependencies.js +34 -0
- package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -0
- package/dist/server/sessiond/sessiondStateOwnership.js +235 -0
- package/dist/server/sessiond/sessiondStateOwnership.js.map +1 -0
- package/dist/server/sessiond/workspaceCatalogRoutes.js +31 -0
- package/dist/server/sessiond/workspaceCatalogRoutes.js.map +1 -0
- package/dist/server/sessiond/workspaceRemovalRoutes.js +41 -0
- package/dist/server/sessiond/workspaceRemovalRoutes.js.map +1 -0
- package/dist/server/sessiond.js +387 -0
- package/dist/server/sessiond.js.map +1 -0
- package/dist/server/sessions/askUserTool.js +102 -0
- package/dist/server/sessions/askUserTool.js.map +1 -0
- package/dist/server/sessions/attachmentService.js +119 -0
- package/dist/server/sessions/attachmentService.js.map +1 -0
- package/dist/server/sessions/authProviderOptions.js +50 -0
- package/dist/server/sessions/authProviderOptions.js.map +1 -0
- package/dist/server/sessions/authRoutes.js +59 -0
- package/dist/server/sessions/authRoutes.js.map +1 -0
- package/dist/server/sessions/authService.js +122 -0
- package/dist/server/sessions/authService.js.map +1 -0
- package/dist/server/sessions/builtinCommands.js +27 -0
- package/dist/server/sessions/builtinCommands.js.map +1 -0
- package/dist/server/sessions/dockerEnvironmentFacts.js +254 -0
- package/dist/server/sessions/dockerEnvironmentFacts.js.map +1 -0
- package/dist/server/sessions/editPreview.js +196 -0
- package/dist/server/sessions/editPreview.js.map +1 -0
- package/dist/server/sessions/extensionDialogWaiters.js +76 -0
- package/dist/server/sessions/extensionDialogWaiters.js.map +1 -0
- package/dist/server/sessions/globalProviderPolicy.js +167 -0
- package/dist/server/sessions/globalProviderPolicy.js.map +1 -0
- package/dist/server/sessions/interruptedRunStore.js +147 -0
- package/dist/server/sessions/interruptedRunStore.js.map +1 -0
- package/dist/server/sessions/messagePaging.js +43 -0
- package/dist/server/sessions/messagePaging.js.map +1 -0
- package/dist/server/sessions/modelCatalogRefresher.js +193 -0
- package/dist/server/sessions/modelCatalogRefresher.js.map +1 -0
- package/dist/server/sessions/oauthLoginFlowService.js +346 -0
- package/dist/server/sessions/oauthLoginFlowService.js.map +1 -0
- package/dist/server/sessions/pendingAskStore.js +275 -0
- package/dist/server/sessions/pendingAskStore.js.map +1 -0
- package/dist/server/sessions/pendingExtensionDialogStore.js +196 -0
- package/dist/server/sessions/pendingExtensionDialogStore.js.map +1 -0
- package/dist/server/sessions/piSessionManagerGateway.js +246 -0
- package/dist/server/sessions/piSessionManagerGateway.js.map +1 -0
- package/dist/server/sessions/piSessionService.js +4059 -0
- package/dist/server/sessions/piSessionService.js.map +1 -0
- package/dist/server/sessions/plainTextTheme.js +112 -0
- package/dist/server/sessions/plainTextTheme.js.map +1 -0
- package/dist/server/sessions/sessionArchiveStore.js +273 -0
- package/dist/server/sessions/sessionArchiveStore.js.map +1 -0
- package/dist/server/sessions/sessionArchiveTree.js +35 -0
- package/dist/server/sessions/sessionArchiveTree.js.map +1 -0
- package/dist/server/sessions/sessionCleanup.js +157 -0
- package/dist/server/sessions/sessionCleanup.js.map +1 -0
- package/dist/server/sessions/sessionCommandService.js +336 -0
- package/dist/server/sessions/sessionCommandService.js.map +1 -0
- package/dist/server/sessions/sessionEnvironmentFacts.js +48 -0
- package/dist/server/sessions/sessionEnvironmentFacts.js.map +1 -0
- package/dist/server/sessions/sessionFileFormat.js +27 -0
- package/dist/server/sessions/sessionFileFormat.js.map +1 -0
- package/dist/server/sessions/sessionFileHeader.js +104 -0
- package/dist/server/sessions/sessionFileHeader.js.map +1 -0
- package/dist/server/sessions/sessionModelScope.js +141 -0
- package/dist/server/sessions/sessionModelScope.js.map +1 -0
- package/dist/server/sessions/sessionNameGenerator.js +90 -0
- package/dist/server/sessions/sessionNameGenerator.js.map +1 -0
- package/dist/server/sessions/sessionNotificationStore.js +484 -0
- package/dist/server/sessions/sessionNotificationStore.js.map +1 -0
- package/dist/server/sessions/sessionRoutes.js +744 -0
- package/dist/server/sessions/sessionRoutes.js.map +1 -0
- package/dist/server/sessions/sessionRuntimeStore.js +2 -0
- package/dist/server/sessions/sessionRuntimeStore.js.map +1 -0
- package/dist/server/sessions/sessionService.js +2 -0
- package/dist/server/sessions/sessionService.js.map +1 -0
- package/dist/server/sessions/sessionSummaryScanner.js +491 -0
- package/dist/server/sessions/sessionSummaryScanner.js.map +1 -0
- package/dist/server/sessions/sessionTreeProjection.js +250 -0
- package/dist/server/sessions/sessionTreeProjection.js.map +1 -0
- package/dist/server/sessions/sessionUnreadStore.js +520 -0
- package/dist/server/sessions/sessionUnreadStore.js.map +1 -0
- package/dist/server/sessions/shutdownDrain.js +73 -0
- package/dist/server/sessions/shutdownDrain.js.map +1 -0
- package/dist/server/sessions/spawnSessionTool.js +48 -0
- package/dist/server/sessions/spawnSessionTool.js.map +1 -0
- package/dist/server/sessions/spawnSubsessionTool.js +216 -0
- package/dist/server/sessions/spawnSubsessionTool.js.map +1 -0
- package/dist/server/sessions/spawnTargetResolver.js +23 -0
- package/dist/server/sessions/spawnTargetResolver.js.map +1 -0
- package/dist/server/sessions/subsessionTranscript.js +235 -0
- package/dist/server/sessions/subsessionTranscript.js.map +1 -0
- package/dist/server/status/machineStatusRoutes.js +8 -0
- package/dist/server/status/machineStatusRoutes.js.map +1 -0
- package/dist/server/status/machineStatusService.js +181 -0
- package/dist/server/status/machineStatusService.js.map +1 -0
- package/dist/server/status/workspaceAttribution.js +87 -0
- package/dist/server/status/workspaceAttribution.js.map +1 -0
- package/dist/server/storage/projectStore.js +88 -0
- package/dist/server/storage/projectStore.js.map +1 -0
- package/dist/server/terminalProxyRoutes.js +141 -0
- package/dist/server/terminalProxyRoutes.js.map +1 -0
- package/dist/server/terminals/terminalRoutes.js +155 -0
- package/dist/server/terminals/terminalRoutes.js.map +1 -0
- package/dist/server/terminals/terminalService.js +305 -0
- package/dist/server/terminals/terminalService.js.map +1 -0
- package/dist/server/terminals/terminalSize.js +17 -0
- package/dist/server/terminals/terminalSize.js.map +1 -0
- package/dist/server/types.js +2 -0
- package/dist/server/types.js.map +1 -0
- package/dist/server/updates/fleetRoutes.js +111 -0
- package/dist/server/updates/fleetRoutes.js.map +1 -0
- package/dist/server/updates/restartRoutes.js +48 -0
- package/dist/server/updates/restartRoutes.js.map +1 -0
- package/dist/server/updates/selfUpdateRoutes.js +145 -0
- package/dist/server/updates/selfUpdateRoutes.js.map +1 -0
- package/dist/server/webSocketBridge.js +32 -0
- package/dist/server/webSocketBridge.js.map +1 -0
- package/dist/server/workingDirectory.js +44 -0
- package/dist/server/workingDirectory.js.map +1 -0
- package/dist/server/workspaceExplorerRoutes.js +147 -0
- package/dist/server/workspaceExplorerRoutes.js.map +1 -0
- package/dist/server/workspaces/effectivePathAccess.js +8 -0
- package/dist/server/workspaces/effectivePathAccess.js.map +1 -0
- package/dist/server/workspaces/fileContentService.js +190 -0
- package/dist/server/workspaces/fileContentService.js.map +1 -0
- package/dist/server/workspaces/filePreviewResponseHeaders.js +18 -0
- package/dist/server/workspaces/filePreviewResponseHeaders.js.map +1 -0
- package/dist/server/workspaces/filePreviewResponsePolicy.js +55 -0
- package/dist/server/workspaces/filePreviewResponsePolicy.js.map +1 -0
- package/dist/server/workspaces/filePreviewService.js +81 -0
- package/dist/server/workspaces/filePreviewService.js.map +1 -0
- package/dist/server/workspaces/fileSuggestions.js +520 -0
- package/dist/server/workspaces/fileSuggestions.js.map +1 -0
- package/dist/server/workspaces/fileTreeService.js +35 -0
- package/dist/server/workspaces/fileTreeService.js.map +1 -0
- package/dist/server/workspaces/pathAccessPolicy.js +90 -0
- package/dist/server/workspaces/pathAccessPolicy.js.map +1 -0
- package/dist/server/workspaces/pathSafety.js +45 -0
- package/dist/server/workspaces/pathSafety.js.map +1 -0
- package/dist/server/workspaces/projectPiWebConfig.js +61 -0
- package/dist/server/workspaces/projectPiWebConfig.js.map +1 -0
- package/dist/server/workspaces/projectWorkspaceCwds.js +17 -0
- package/dist/server/workspaces/projectWorkspaceCwds.js.map +1 -0
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js +402 -0
- package/dist/server/workspaces/sessionDaemonWorkspaceCatalog.js.map +1 -0
- package/dist/server/workspaces/workspaceCatalog.js +47 -0
- package/dist/server/workspaces/workspaceCatalog.js.map +1 -0
- package/dist/server/workspaces/workspaceContext.js +6 -0
- package/dist/server/workspaces/workspaceContext.js.map +1 -0
- package/dist/server/workspaces/workspaceDeletionRoutes.js +49 -0
- package/dist/server/workspaces/workspaceDeletionRoutes.js.map +1 -0
- package/dist/server/workspaces/workspaceProviderRegistry.js +651 -0
- package/dist/server/workspaces/workspaceProviderRegistry.js.map +1 -0
- package/dist/server/workspaces/workspaceRemovalService.js +256 -0
- package/dist/server/workspaces/workspaceRemovalService.js.map +1 -0
- package/dist/server/workspaces/workspaceRouteErrors.js +7 -0
- package/dist/server/workspaces/workspaceRouteErrors.js.map +1 -0
- package/dist/server/workspaces/worktreePreRemoveHook.js +39 -0
- package/dist/server/workspaces/worktreePreRemoveHook.js.map +1 -0
- package/dist/server-plugin-api.d.ts +140 -0
- package/dist/server-plugin-api.js +2 -0
- package/dist/server-plugin-api.js.map +1 -0
- package/dist/serverPluginRecovery.js +138 -0
- package/dist/serverPluginRecovery.js.map +1 -0
- package/dist/sessiond/activeAgentProfile.js +12 -0
- package/dist/sessiond/activeAgentProfile.js.map +1 -0
- package/dist/sessiond/config.js +20 -0
- package/dist/sessiond/config.js.map +1 -0
- package/dist/sessiond/sessionDaemonClient.js +105 -0
- package/dist/sessiond/sessionDaemonClient.js.map +1 -0
- package/dist/shared/activeAgentProfile.js +37 -0
- package/dist/shared/activeAgentProfile.js.map +1 -0
- package/dist/shared/activity.js +16 -0
- package/dist/shared/activity.js.map +1 -0
- package/dist/shared/apiTypes.js +41 -0
- package/dist/shared/apiTypes.js.map +1 -0
- package/dist/shared/capabilities.js +37 -0
- package/dist/shared/capabilities.js.map +1 -0
- package/dist/shared/federatedRoutes.js +127 -0
- package/dist/shared/federatedRoutes.js.map +1 -0
- package/dist/shared/machinePluginIds.js +43 -0
- package/dist/shared/machinePluginIds.js.map +1 -0
- package/dist/shared/machineStatus.js +94 -0
- package/dist/shared/machineStatus.js.map +1 -0
- package/dist/shared/piWebStatusParsing.js +141 -0
- package/dist/shared/piWebStatusParsing.js.map +1 -0
- package/dist/shared/pluginApiTypes.d.ts +150 -0
- package/dist/shared/pluginApiTypes.js +3 -0
- package/dist/shared/pluginApiTypes.js.map +1 -0
- package/dist/shared/pluginBackendProtocol.js +110 -0
- package/dist/shared/pluginBackendProtocol.js.map +1 -0
- package/dist/shared/pluginIds.js +10 -0
- package/dist/shared/pluginIds.js.map +1 -0
- package/dist/shared/pluginRecoveryCommands.js +14 -0
- package/dist/shared/pluginRecoveryCommands.js.map +1 -0
- package/dist/shared/promptAttachments.js +92 -0
- package/dist/shared/promptAttachments.js.map +1 -0
- package/dist/shared/selfUpdate.js +3 -0
- package/dist/shared/selfUpdate.js.map +1 -0
- package/dist/shared/thinkingLevels.js +31 -0
- package/dist/shared/thinkingLevels.js.map +1 -0
- package/dist/shared/workspaceDeletion.js +12 -0
- package/dist/shared/workspaceDeletion.js.map +1 -0
- package/dist/shared/workspaceFiles.js +42 -0
- package/dist/shared/workspaceFiles.js.map +1 -0
- package/dist/shared/workspaceRemovalProtocol.js +24 -0
- package/dist/shared/workspaceRemovalProtocol.js.map +1 -0
- package/docs/assets/favicon.svg +11 -0
- package/docs/assets/pi-web-banner.png +0 -0
- package/docs/assets/pi-web-desktop.png +0 -0
- package/docs/assets/pi-web-mobile.png +0 -0
- package/docs/assets/pi-web-tablet.png +0 -0
- package/docs/config.md +444 -0
- package/docs/plugins.md +1305 -0
- package/examples/workspace-provider-plugin/README.md +52 -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 +64 -0
- package/examples/workspace-provider-plugin/tsconfig.json +21 -0
- package/extensions/README-pi-web-restart-migration.md +45 -0
- package/extensions/pi-web.ts +324 -0
- package/install.sh +5 -0
- package/package.json +173 -0
- package/plugin-api.d.ts +1 -0
- package/server-plugin-api.d.ts +1 -0
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
3
|
+
import { isAbsolute, resolve } from "node:path";
|
|
4
|
+
import { isPiWebPluginId } from "../../shared/pluginIds.js";
|
|
5
|
+
import { cloneBoundedPluginBackendJson, PLUGIN_BACKEND_DISPATCH_TIMEOUT_MS, PLUGIN_BACKEND_REQUEST_TIMEOUT_MS, PLUGIN_BACKEND_RESPONSE_JSON_MAX_BYTES, requirePluginBackendOperation, requirePluginBackendRevision, } from "../../shared/pluginBackendProtocol.js";
|
|
6
|
+
const DEFAULT_PROVIDER_TIMEOUT_MS = PLUGIN_BACKEND_REQUEST_TIMEOUT_MS;
|
|
7
|
+
export class WorkspaceProviderRemovalError extends Error {
|
|
8
|
+
constructor(code, statusCode, message, options = {}) {
|
|
9
|
+
super(message, options);
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.statusCode = statusCode;
|
|
12
|
+
this.name = "WorkspaceProviderRemovalError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class WorkspaceProviderRequestError extends Error {
|
|
16
|
+
constructor(code, statusCode, message, options = {}) {
|
|
17
|
+
super(message, options);
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.statusCode = statusCode;
|
|
20
|
+
this.name = "WorkspaceProviderRequestError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** Keep only active providers whose bounded startup health inspection is not unhealthy. */
|
|
24
|
+
export function eligibleWorkspaceProviderContributions(contributions, inspections) {
|
|
25
|
+
const healthByPluginId = new Map(inspections.map(({ pluginId, health }) => [pluginId, health.status]));
|
|
26
|
+
return Object.freeze(contributions.filter(({ pluginId }) => {
|
|
27
|
+
const status = healthByPluginId.get(pluginId);
|
|
28
|
+
return status === "healthy" || status === "degraded";
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolves one exclusive workspace owner from the active server-plugin snapshot.
|
|
33
|
+
* Probe failures are local to one resolution; a claimant that later fails to
|
|
34
|
+
* list never falls through to a lower-priority provider.
|
|
35
|
+
*/
|
|
36
|
+
export class WorkspaceProviderRegistry {
|
|
37
|
+
constructor(options) {
|
|
38
|
+
this.options = options;
|
|
39
|
+
this.pendingResolutions = new Map();
|
|
40
|
+
this.contributions = Object.freeze([...options.contributions]
|
|
41
|
+
.sort((left, right) => left.pluginId.localeCompare(right.pluginId)));
|
|
42
|
+
this.providerTimeoutMs = positiveInteger(options.providerTimeoutMs, DEFAULT_PROVIDER_TIMEOUT_MS, "providerTimeoutMs");
|
|
43
|
+
this.requestTimeoutMs = positiveInteger(options.requestTimeoutMs, PLUGIN_BACKEND_DISPATCH_TIMEOUT_MS, "requestTimeoutMs");
|
|
44
|
+
this.pathInspector = options.pathInspector ?? pathIsDirectory;
|
|
45
|
+
}
|
|
46
|
+
/** Workspace-lister adapter used by spawned-session target validation. */
|
|
47
|
+
async list(project) {
|
|
48
|
+
const resolution = await this.resolve(project);
|
|
49
|
+
return [...resolution.workspaces];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolve workspaces together with attributable diagnostics for host consumers.
|
|
53
|
+
* Only identical work already in flight is shared; the entry is removed before
|
|
54
|
+
* callers observe completion so ownership and topology are never cached.
|
|
55
|
+
*/
|
|
56
|
+
async resolve(project) {
|
|
57
|
+
const input = snapshotProject(project);
|
|
58
|
+
const key = workspaceResolutionKey(input);
|
|
59
|
+
const existing = this.pendingResolutions.get(key);
|
|
60
|
+
if (existing !== undefined)
|
|
61
|
+
return existing;
|
|
62
|
+
const pending = this.resolveSnapshot(input);
|
|
63
|
+
this.pendingResolutions.set(key, pending);
|
|
64
|
+
try {
|
|
65
|
+
return await pending;
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
if (this.pendingResolutions.get(key) === pending)
|
|
69
|
+
this.pendingResolutions.delete(key);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async resolveSnapshot(input) {
|
|
73
|
+
const diagnostics = [];
|
|
74
|
+
for (const tier of ["primary", "fallback"]) {
|
|
75
|
+
const selection = await this.selectInTier(input, tier, diagnostics);
|
|
76
|
+
if (selection.kind === "none")
|
|
77
|
+
continue;
|
|
78
|
+
if (selection.kind === "conflict") {
|
|
79
|
+
const message = `Workspace provider conflict in ${tier} tier: ${selection.pluginIds.join(", ")}`;
|
|
80
|
+
const diagnostic = freezeDiagnostic({
|
|
81
|
+
code: "claim-conflict",
|
|
82
|
+
message,
|
|
83
|
+
tier,
|
|
84
|
+
pluginIds: selection.pluginIds,
|
|
85
|
+
});
|
|
86
|
+
diagnostics.push(diagnostic);
|
|
87
|
+
this.options.logger.warn({ projectId: input.id, tier, pluginIds: selection.pluginIds }, "workspace provider claim conflict");
|
|
88
|
+
return degradedResolution(input, diagnostics);
|
|
89
|
+
}
|
|
90
|
+
return await this.resolveWinner(input, tier, selection.contribution, diagnostics);
|
|
91
|
+
}
|
|
92
|
+
return Object.freeze({
|
|
93
|
+
status: "folder",
|
|
94
|
+
projectId: input.id,
|
|
95
|
+
workspaces: Object.freeze([folderWorkspace(input)]),
|
|
96
|
+
diagnostics: Object.freeze([...diagnostics]),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Re-resolve the current owner and its private workspace snapshot before
|
|
101
|
+
* invoking one bounded provider operation. Callers never supply owner data.
|
|
102
|
+
*/
|
|
103
|
+
async request(request) {
|
|
104
|
+
try {
|
|
105
|
+
return await runBoundedProviderOperation(request.pluginId, "request", this.requestTimeoutMs, (signal) => this.dispatchRequest(request, signal));
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
if (error instanceof WorkspaceProviderTimeoutError) {
|
|
109
|
+
throw providerRequestError("request-timeout", 504, boundedErrorMessage(error), error);
|
|
110
|
+
}
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Re-resolve one live owner/target before host safety checks and provider planning. */
|
|
115
|
+
async resolveRemoval(project, workspaceId, signal) {
|
|
116
|
+
const input = snapshotProject(project);
|
|
117
|
+
if (workspaceId === "")
|
|
118
|
+
throw providerRemovalError("workspace-not-found", 404, "Workspace not found");
|
|
119
|
+
const diagnostics = [];
|
|
120
|
+
for (const tier of ["primary", "fallback"]) {
|
|
121
|
+
const selection = await this.selectInTier(input, tier, diagnostics, signal);
|
|
122
|
+
if (selection.kind === "none")
|
|
123
|
+
continue;
|
|
124
|
+
if (selection.kind === "conflict") {
|
|
125
|
+
throw providerRemovalError("owner-conflict", 409, `Workspace owner conflict prevents removal: ${selection.pluginIds.join(", ")}`);
|
|
126
|
+
}
|
|
127
|
+
const contribution = selection.contribution;
|
|
128
|
+
let validated;
|
|
129
|
+
try {
|
|
130
|
+
const listed = await runBoundedProviderOperation(contribution.pluginId, "list", this.providerTimeoutMs, (operationSignal) => contribution.provider.list(input, operationSignal), signal);
|
|
131
|
+
validated = await validateProviderWorkspaces(input, contribution, listed, this.pathInspector, signal);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (signal?.aborted === true)
|
|
135
|
+
throw abortError(signal);
|
|
136
|
+
if (error instanceof WorkspaceProviderTimeoutError) {
|
|
137
|
+
throw providerRemovalError("resolution-timeout", 504, boundedErrorMessage(error), error);
|
|
138
|
+
}
|
|
139
|
+
throw providerRemovalError("resolution-failed", 502, `Server plugin ${contribution.pluginId} could not resolve workspaces for removal: ${boundedErrorMessage(error)}`, error);
|
|
140
|
+
}
|
|
141
|
+
const current = validated.find(({ workspace }) => workspace.id === workspaceId);
|
|
142
|
+
if (current === undefined) {
|
|
143
|
+
throw providerRemovalError("workspace-not-found", 404, `Workspace ${workspaceId} is stale or unavailable for removal`);
|
|
144
|
+
}
|
|
145
|
+
const callback = contribution.provider.prepareRemove?.bind(contribution.provider);
|
|
146
|
+
if (callback === undefined || current.workspace.removal === undefined) {
|
|
147
|
+
throw providerRemovalError("removal-unavailable", 409, `Server plugin ${contribution.pluginId} does not advertise removal for workspace ${workspaceId}`);
|
|
148
|
+
}
|
|
149
|
+
const workspaces = Object.freeze(validated.map(({ workspace }) => workspace));
|
|
150
|
+
return Object.freeze({
|
|
151
|
+
ownerPluginId: contribution.pluginId,
|
|
152
|
+
target: current.workspace,
|
|
153
|
+
workspaces,
|
|
154
|
+
prepare: async () => {
|
|
155
|
+
let value;
|
|
156
|
+
try {
|
|
157
|
+
value = await runBoundedProviderOperation(contribution.pluginId, "prepareRemove", this.providerTimeoutMs, (operationSignal) => callback(Object.freeze({
|
|
158
|
+
project: input,
|
|
159
|
+
workspace: current.providerWorkspace,
|
|
160
|
+
signal: operationSignal,
|
|
161
|
+
})), signal);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (signal?.aborted === true)
|
|
165
|
+
throw abortError(signal);
|
|
166
|
+
if (error instanceof WorkspaceProviderTimeoutError) {
|
|
167
|
+
throw providerRemovalError("preparation-timeout", 504, boundedErrorMessage(error), error);
|
|
168
|
+
}
|
|
169
|
+
throw providerRemovalError("preparation-failed", 409, `Server plugin ${contribution.pluginId} rejected workspace removal: ${boundedErrorMessage(error)}`, error);
|
|
170
|
+
}
|
|
171
|
+
return parseWorkspaceRemovePlan(value, contribution.pluginId);
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
const failedProbe = diagnostics.find(({ code }) => code === "probe-failed");
|
|
176
|
+
if (failedProbe !== undefined) {
|
|
177
|
+
throw providerRemovalError("resolution-failed", 502, `Workspace owner resolution failed before removal: ${boundedErrorMessage(failedProbe.message)}`);
|
|
178
|
+
}
|
|
179
|
+
throw providerRemovalError("owner-unavailable", 409, `No workspace provider currently owns project ${input.id}`);
|
|
180
|
+
}
|
|
181
|
+
async dispatchRequest(request, dispatchSignal) {
|
|
182
|
+
const pluginId = request.pluginId;
|
|
183
|
+
if (!isPiWebPluginId(pluginId)) {
|
|
184
|
+
throw providerRequestError("inactive-plugin", 409, `Server plugin is not active: ${pluginId}`);
|
|
185
|
+
}
|
|
186
|
+
const operation = parseRequestOperation(request.operation);
|
|
187
|
+
const moduleRevision = parseRequestRevision(request.moduleRevision, operation);
|
|
188
|
+
const activeContribution = this.contributions.find((contribution) => contribution.pluginId === pluginId);
|
|
189
|
+
if (activeContribution === undefined) {
|
|
190
|
+
throw providerRequestError("inactive-plugin", 409, `Server plugin ${pluginId} is not active for workspace backend operation ${operation}`);
|
|
191
|
+
}
|
|
192
|
+
if (activeContribution.moduleRevision !== moduleRevision) {
|
|
193
|
+
throw providerRequestError("stale-plugin-revision", 409, `Server plugin ${pluginId} backend revision is stale for operation ${operation}; reload after the session daemon restarts`);
|
|
194
|
+
}
|
|
195
|
+
if (request.workspaceId === "") {
|
|
196
|
+
throw providerRequestError("workspace-not-found", 404, `Workspace not found for server plugin ${pluginId} operation ${operation}`);
|
|
197
|
+
}
|
|
198
|
+
let input;
|
|
199
|
+
try {
|
|
200
|
+
input = cloneBoundedPluginBackendJson(request.input, `Server plugin ${pluginId} operation ${operation} input`);
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
throw providerRequestError("invalid-input", 400, boundedErrorMessage(error), error);
|
|
204
|
+
}
|
|
205
|
+
const project = snapshotProject(request.project);
|
|
206
|
+
const diagnostics = [];
|
|
207
|
+
for (const tier of ["primary", "fallback"]) {
|
|
208
|
+
const selection = await this.selectInTier(project, tier, diagnostics, dispatchSignal);
|
|
209
|
+
if (selection.kind === "none")
|
|
210
|
+
continue;
|
|
211
|
+
if (selection.kind === "conflict") {
|
|
212
|
+
throw providerRequestError("owner-conflict", 409, `Workspace owner conflict prevents server plugin ${pluginId} operation ${operation}: ${selection.pluginIds.join(", ")}`);
|
|
213
|
+
}
|
|
214
|
+
if (selection.contribution.pluginId !== pluginId) {
|
|
215
|
+
throw providerRequestError("owner-mismatch", 409, `Server plugin ${pluginId} does not own project ${project.id}; current owner is ${selection.contribution.pluginId}`);
|
|
216
|
+
}
|
|
217
|
+
const validated = await this.listRequestWorkspaces(project, selection.contribution, operation, dispatchSignal);
|
|
218
|
+
const target = validated.find(({ workspace }) => workspace.id === request.workspaceId);
|
|
219
|
+
if (target === undefined) {
|
|
220
|
+
throw providerRequestError("workspace-not-found", 404, `Workspace ${request.workspaceId} is stale or unavailable for server plugin ${pluginId} operation ${operation}`);
|
|
221
|
+
}
|
|
222
|
+
const callback = selection.contribution.provider.request?.bind(selection.contribution.provider);
|
|
223
|
+
if (callback === undefined) {
|
|
224
|
+
throw providerRequestError("operation-unavailable", 501, `Server plugin ${pluginId} does not provide workspace backend operations`);
|
|
225
|
+
}
|
|
226
|
+
let result;
|
|
227
|
+
try {
|
|
228
|
+
result = await runBoundedProviderOperation(pluginId, "request", this.providerTimeoutMs, (signal) => callback(Object.freeze({
|
|
229
|
+
project,
|
|
230
|
+
workspace: target.providerWorkspace,
|
|
231
|
+
operation,
|
|
232
|
+
input,
|
|
233
|
+
signal,
|
|
234
|
+
})), dispatchSignal);
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
if (error instanceof WorkspaceProviderTimeoutError) {
|
|
238
|
+
throw providerRequestError("request-timeout", 504, boundedErrorMessage(error), error);
|
|
239
|
+
}
|
|
240
|
+
throw providerRequestError("request-failed", 502, `Server plugin ${pluginId} operation ${operation} failed: ${boundedErrorMessage(error)}`, error);
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
return cloneBoundedPluginBackendJson(result, `Server plugin ${pluginId} operation ${operation} result`, PLUGIN_BACKEND_RESPONSE_JSON_MAX_BYTES);
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
throw providerRequestError("invalid-result", 502, boundedErrorMessage(error), error);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const failedProbe = diagnostics.find((diagnostic) => diagnostic.pluginId === pluginId && diagnostic.code === "probe-failed");
|
|
250
|
+
if (failedProbe !== undefined) {
|
|
251
|
+
throw providerRequestError("resolution-failed", 502, `Server plugin ${pluginId} owner resolution failed for operation ${operation}: ${boundedErrorMessage(failedProbe.message)}`);
|
|
252
|
+
}
|
|
253
|
+
throw providerRequestError("owner-mismatch", 409, `Server plugin ${pluginId} does not own project ${project.id}`);
|
|
254
|
+
}
|
|
255
|
+
async listRequestWorkspaces(project, contribution, operation, dispatchSignal) {
|
|
256
|
+
try {
|
|
257
|
+
const listed = await runBoundedProviderOperation(contribution.pluginId, "list", this.providerTimeoutMs, (signal) => contribution.provider.list(project, signal), dispatchSignal);
|
|
258
|
+
return await validateProviderWorkspaces(project, contribution, listed, this.pathInspector, dispatchSignal);
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
if (dispatchSignal.aborted)
|
|
262
|
+
throw abortError(dispatchSignal);
|
|
263
|
+
if (error instanceof WorkspaceProviderTimeoutError) {
|
|
264
|
+
throw providerRequestError("resolution-timeout", 504, boundedErrorMessage(error), error);
|
|
265
|
+
}
|
|
266
|
+
throw providerRequestError("resolution-failed", 502, `Server plugin ${contribution.pluginId} could not resolve workspaces for operation ${operation}: ${boundedErrorMessage(error)}`, error);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async selectInTier(project, tier, diagnostics, dispatchSignal) {
|
|
270
|
+
const candidates = this.contributions.filter(({ provider }) => (provider.fallback === true) === (tier === "fallback"));
|
|
271
|
+
const claimants = [];
|
|
272
|
+
for (const contribution of candidates) {
|
|
273
|
+
try {
|
|
274
|
+
const claim = await runBoundedProviderOperation(contribution.pluginId, "probe", this.providerTimeoutMs, (signal) => contribution.provider.probe(project, signal), dispatchSignal);
|
|
275
|
+
if (!isProviderClaim(claim)) {
|
|
276
|
+
throw new WorkspaceProviderContractError(`Workspace provider ${contribution.pluginId} returned an invalid probe result`);
|
|
277
|
+
}
|
|
278
|
+
if (claim === "claim")
|
|
279
|
+
claimants.push(contribution);
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
if (dispatchSignal?.aborted === true)
|
|
283
|
+
throw abortError(dispatchSignal);
|
|
284
|
+
const message = errorMessage(error);
|
|
285
|
+
diagnostics.push(freezeDiagnostic({
|
|
286
|
+
code: "probe-failed",
|
|
287
|
+
message,
|
|
288
|
+
tier,
|
|
289
|
+
pluginId: contribution.pluginId,
|
|
290
|
+
}));
|
|
291
|
+
this.options.logger.warn({ err: error, projectId: project.id, pluginId: contribution.pluginId, tier, operation: "probe" }, "workspace provider probe failed");
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (claimants.length === 0)
|
|
295
|
+
return { kind: "none" };
|
|
296
|
+
if (claimants.length === 1) {
|
|
297
|
+
const contribution = claimants[0];
|
|
298
|
+
if (contribution === undefined)
|
|
299
|
+
throw new Error("Workspace provider claimant disappeared");
|
|
300
|
+
return { kind: "winner", contribution };
|
|
301
|
+
}
|
|
302
|
+
return Object.freeze({
|
|
303
|
+
kind: "conflict",
|
|
304
|
+
pluginIds: Object.freeze(claimants.map(({ pluginId }) => pluginId)),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
async resolveWinner(project, tier, contribution, diagnostics) {
|
|
308
|
+
try {
|
|
309
|
+
const listed = await runBoundedProviderOperation(contribution.pluginId, "list", this.providerTimeoutMs, (signal) => contribution.provider.list(project, signal));
|
|
310
|
+
const validated = await validateProviderWorkspaces(project, contribution, listed, this.pathInspector);
|
|
311
|
+
return Object.freeze({
|
|
312
|
+
status: "provider",
|
|
313
|
+
projectId: project.id,
|
|
314
|
+
ownerPluginId: contribution.pluginId,
|
|
315
|
+
workspaces: Object.freeze(validated.map(({ workspace }) => workspace)),
|
|
316
|
+
diagnostics: Object.freeze([...diagnostics]),
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
const message = errorMessage(error);
|
|
321
|
+
diagnostics.push(freezeDiagnostic({
|
|
322
|
+
code: "list-failed",
|
|
323
|
+
message,
|
|
324
|
+
tier,
|
|
325
|
+
pluginId: contribution.pluginId,
|
|
326
|
+
}));
|
|
327
|
+
this.options.logger.warn({ err: error, projectId: project.id, pluginId: contribution.pluginId, tier, operation: "list" }, "workspace provider listing failed after claim");
|
|
328
|
+
return degradedResolution(project, diagnostics, contribution.pluginId);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async function validateProviderWorkspaces(project, contribution, value, pathInspector, signal) {
|
|
333
|
+
if (!Array.isArray(value)) {
|
|
334
|
+
throw new WorkspaceProviderContractError(`Workspace provider ${contribution.pluginId} list result must be an array`);
|
|
335
|
+
}
|
|
336
|
+
const keys = new Set();
|
|
337
|
+
const paths = new Set();
|
|
338
|
+
const workspaces = [];
|
|
339
|
+
let mainCount = 0;
|
|
340
|
+
for (const [index, rawWorkspace] of value.entries()) {
|
|
341
|
+
throwIfAborted(signal);
|
|
342
|
+
const label = `Workspace provider ${contribution.pluginId} result ${String(index + 1)}`;
|
|
343
|
+
const candidate = parseProviderWorkspace(rawWorkspace, label);
|
|
344
|
+
if (keys.has(candidate.key))
|
|
345
|
+
throw new WorkspaceProviderContractError(`${label} has duplicate key: ${candidate.key}`);
|
|
346
|
+
keys.add(candidate.key);
|
|
347
|
+
const path = normalizeAbsolutePath(candidate.path, `${label} path`);
|
|
348
|
+
if (paths.has(path))
|
|
349
|
+
throw new WorkspaceProviderContractError(`${label} has duplicate path: ${path}`);
|
|
350
|
+
paths.add(path);
|
|
351
|
+
if (!(await pathInspector(path)))
|
|
352
|
+
throw new WorkspaceProviderContractError(`${label} path is not an accessible directory: ${path}`);
|
|
353
|
+
throwIfAborted(signal);
|
|
354
|
+
if (candidate.isMain)
|
|
355
|
+
mainCount += 1;
|
|
356
|
+
const metadata = candidate.publicMetadata === undefined
|
|
357
|
+
? undefined
|
|
358
|
+
: cloneJsonObject(candidate.publicMetadata, `${label} publicMetadata`);
|
|
359
|
+
const data = candidate.data === undefined
|
|
360
|
+
? undefined
|
|
361
|
+
: cloneJsonValue(candidate.data, new Set(), `${label} data`);
|
|
362
|
+
const removal = candidate.removal === undefined ? undefined : parseRemoval(candidate.removal, `${label} removal`);
|
|
363
|
+
if (removal !== undefined && contribution.provider.prepareRemove === undefined) {
|
|
364
|
+
throw new WorkspaceProviderContractError(`${label} advertises removal without a prepareRemove capability`);
|
|
365
|
+
}
|
|
366
|
+
const publicRemoval = removal === undefined
|
|
367
|
+
? undefined
|
|
368
|
+
: hostRemovalPresentation(project, contribution, candidate.key, path, removal);
|
|
369
|
+
const provider = Object.freeze({
|
|
370
|
+
pluginId: contribution.pluginId,
|
|
371
|
+
capabilities: Object.freeze({
|
|
372
|
+
request: contribution.provider.request !== undefined,
|
|
373
|
+
remove: removal !== undefined,
|
|
374
|
+
}),
|
|
375
|
+
...(metadata === undefined ? {} : { metadata }),
|
|
376
|
+
});
|
|
377
|
+
const workspace = {
|
|
378
|
+
id: workspaceId(project.id, candidate.key),
|
|
379
|
+
projectId: project.id,
|
|
380
|
+
path,
|
|
381
|
+
label: candidate.label,
|
|
382
|
+
isMain: candidate.isMain,
|
|
383
|
+
provider,
|
|
384
|
+
...(publicRemoval === undefined ? {} : { removal: publicRemoval }),
|
|
385
|
+
};
|
|
386
|
+
const providerWorkspace = Object.freeze({
|
|
387
|
+
key: candidate.key,
|
|
388
|
+
path,
|
|
389
|
+
label: candidate.label,
|
|
390
|
+
isMain: candidate.isMain,
|
|
391
|
+
...(data === undefined ? {} : { data }),
|
|
392
|
+
...(metadata === undefined ? {} : { publicMetadata: metadata }),
|
|
393
|
+
...(removal === undefined ? {} : { removal }),
|
|
394
|
+
});
|
|
395
|
+
workspaces.push(Object.freeze({ workspace: Object.freeze(workspace), providerWorkspace }));
|
|
396
|
+
}
|
|
397
|
+
if (mainCount !== 1) {
|
|
398
|
+
throw new WorkspaceProviderContractError(`Workspace provider ${contribution.pluginId} must return exactly one main workspace`);
|
|
399
|
+
}
|
|
400
|
+
return workspaces;
|
|
401
|
+
}
|
|
402
|
+
function parseProviderWorkspace(value, label) {
|
|
403
|
+
if (!isRecord(value))
|
|
404
|
+
throw new WorkspaceProviderContractError(`${label} must be an object`);
|
|
405
|
+
const key = value["key"];
|
|
406
|
+
const path = value["path"];
|
|
407
|
+
const workspaceLabel = value["label"];
|
|
408
|
+
const isMain = value["isMain"];
|
|
409
|
+
if (typeof key !== "string" || key === "")
|
|
410
|
+
throw new WorkspaceProviderContractError(`${label} key must be a non-empty string`);
|
|
411
|
+
if (typeof path !== "string" || path === "")
|
|
412
|
+
throw new WorkspaceProviderContractError(`${label} path must be a non-empty string`);
|
|
413
|
+
if (typeof workspaceLabel !== "string" || workspaceLabel === "")
|
|
414
|
+
throw new WorkspaceProviderContractError(`${label} label must be a non-empty string`);
|
|
415
|
+
if (typeof isMain !== "boolean")
|
|
416
|
+
throw new WorkspaceProviderContractError(`${label} isMain must be a boolean`);
|
|
417
|
+
return {
|
|
418
|
+
key,
|
|
419
|
+
path,
|
|
420
|
+
label: workspaceLabel,
|
|
421
|
+
isMain,
|
|
422
|
+
...(value["data"] === undefined ? {} : { data: value["data"] }),
|
|
423
|
+
...(value["publicMetadata"] === undefined ? {} : { publicMetadata: value["publicMetadata"] }),
|
|
424
|
+
...(value["removal"] === undefined ? {} : { removal: value["removal"] }),
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
function parseRemoval(value, label) {
|
|
428
|
+
if (!isRecord(value))
|
|
429
|
+
throw new WorkspaceProviderContractError(`${label} must be an object`);
|
|
430
|
+
const actionLabel = value["actionLabel"];
|
|
431
|
+
const confirmation = value["confirmation"];
|
|
432
|
+
if (typeof actionLabel !== "string" || actionLabel === "")
|
|
433
|
+
throw new WorkspaceProviderContractError(`${label} actionLabel must be a non-empty string`);
|
|
434
|
+
if (typeof confirmation !== "string" || confirmation === "")
|
|
435
|
+
throw new WorkspaceProviderContractError(`${label} confirmation must be a non-empty string`);
|
|
436
|
+
return Object.freeze({ actionLabel, confirmation });
|
|
437
|
+
}
|
|
438
|
+
function hostRemovalPresentation(project, contribution, providerKey, path, removal) {
|
|
439
|
+
const digest = createHash("sha256").update(JSON.stringify([
|
|
440
|
+
contribution.pluginId,
|
|
441
|
+
contribution.moduleRevision,
|
|
442
|
+
project.id,
|
|
443
|
+
providerKey,
|
|
444
|
+
path,
|
|
445
|
+
removal.actionLabel,
|
|
446
|
+
removal.confirmation,
|
|
447
|
+
])).digest("base64url");
|
|
448
|
+
return Object.freeze({ ...removal, precondition: `v1.${digest}` });
|
|
449
|
+
}
|
|
450
|
+
function workspaceResolutionKey(project) {
|
|
451
|
+
return JSON.stringify([project.id, project.name, project.path]);
|
|
452
|
+
}
|
|
453
|
+
function snapshotProject(project) {
|
|
454
|
+
if (typeof project.id !== "string" || project.id === "")
|
|
455
|
+
throw new Error("Project id must be a non-empty string");
|
|
456
|
+
if (typeof project.name !== "string" || project.name === "")
|
|
457
|
+
throw new Error("Project name must be a non-empty string");
|
|
458
|
+
return Object.freeze({
|
|
459
|
+
id: project.id,
|
|
460
|
+
name: project.name,
|
|
461
|
+
path: normalizeAbsolutePath(project.path, "Project path"),
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
function degradedResolution(project, diagnostics, ownerPluginId) {
|
|
465
|
+
return Object.freeze({
|
|
466
|
+
status: "degraded",
|
|
467
|
+
projectId: project.id,
|
|
468
|
+
...(ownerPluginId === undefined ? {} : { ownerPluginId }),
|
|
469
|
+
workspaces: Object.freeze([folderWorkspace(project)]),
|
|
470
|
+
diagnostics: Object.freeze([...diagnostics]),
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
function folderWorkspace(project) {
|
|
474
|
+
return Object.freeze({
|
|
475
|
+
id: workspaceId(project.id, project.path),
|
|
476
|
+
projectId: project.id,
|
|
477
|
+
path: project.path,
|
|
478
|
+
label: project.name,
|
|
479
|
+
isMain: true,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
function workspaceId(projectId, providerKey) {
|
|
483
|
+
return createHash("sha1").update(`${projectId}:${providerKey}`).digest("hex").slice(0, 12);
|
|
484
|
+
}
|
|
485
|
+
function normalizeAbsolutePath(path, label) {
|
|
486
|
+
if (!isAbsolute(path))
|
|
487
|
+
throw new WorkspaceProviderContractError(`${label} must be absolute`);
|
|
488
|
+
return resolve(path);
|
|
489
|
+
}
|
|
490
|
+
async function pathIsDirectory(path) {
|
|
491
|
+
const value = await stat(path).catch(() => undefined);
|
|
492
|
+
return value?.isDirectory() === true;
|
|
493
|
+
}
|
|
494
|
+
async function runBoundedProviderOperation(pluginId, operation, timeoutMs, callback, parentSignal) {
|
|
495
|
+
const controller = new AbortController();
|
|
496
|
+
const abortFromParent = () => {
|
|
497
|
+
if (parentSignal !== undefined)
|
|
498
|
+
controller.abort(abortError(parentSignal));
|
|
499
|
+
};
|
|
500
|
+
if (parentSignal?.aborted === true)
|
|
501
|
+
abortFromParent();
|
|
502
|
+
else
|
|
503
|
+
parentSignal?.addEventListener("abort", abortFromParent, { once: true });
|
|
504
|
+
const timeoutError = new WorkspaceProviderTimeoutError(`Workspace provider ${pluginId} ${operation} timed out after ${String(timeoutMs)}ms`);
|
|
505
|
+
const timeout = setTimeout(() => { controller.abort(timeoutError); }, timeoutMs);
|
|
506
|
+
timeout.unref();
|
|
507
|
+
const deadline = controller.signal.aborted
|
|
508
|
+
? Promise.reject(abortError(controller.signal))
|
|
509
|
+
: new Promise((_resolve, rejectPromise) => {
|
|
510
|
+
controller.signal.addEventListener("abort", () => { rejectPromise(abortError(controller.signal)); }, { once: true });
|
|
511
|
+
});
|
|
512
|
+
const result = controller.signal.aborted
|
|
513
|
+
? new Promise(() => { })
|
|
514
|
+
: Promise.resolve().then(() => callback(controller.signal));
|
|
515
|
+
try {
|
|
516
|
+
return await Promise.race([result, deadline]);
|
|
517
|
+
}
|
|
518
|
+
finally {
|
|
519
|
+
clearTimeout(timeout);
|
|
520
|
+
parentSignal?.removeEventListener("abort", abortFromParent);
|
|
521
|
+
if (!controller.signal.aborted)
|
|
522
|
+
controller.abort(new DOMException("Workspace provider operation completed", "AbortError"));
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
function freezeDiagnostic(diagnostic) {
|
|
526
|
+
const pluginIds = diagnostic.pluginIds === undefined ? undefined : Object.freeze([...diagnostic.pluginIds]);
|
|
527
|
+
return Object.freeze({
|
|
528
|
+
...diagnostic,
|
|
529
|
+
...(pluginIds === undefined ? {} : { pluginIds }),
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
function cloneJsonObject(value, label) {
|
|
533
|
+
if (!isRecord(value))
|
|
534
|
+
throw new WorkspaceProviderContractError(`${label} must be a JSON object`);
|
|
535
|
+
const cloned = cloneJsonRecord(value, new Set(), label);
|
|
536
|
+
return cloned;
|
|
537
|
+
}
|
|
538
|
+
function cloneJsonRecord(value, ancestors, label) {
|
|
539
|
+
if (ancestors.has(value))
|
|
540
|
+
throw new WorkspaceProviderContractError(`${label} must not contain cycles`);
|
|
541
|
+
ancestors.add(value);
|
|
542
|
+
const output = {};
|
|
543
|
+
for (const [key, child] of Object.entries(value)) {
|
|
544
|
+
Object.defineProperty(output, key, {
|
|
545
|
+
value: cloneJsonValue(child, ancestors, label),
|
|
546
|
+
enumerable: true,
|
|
547
|
+
configurable: true,
|
|
548
|
+
writable: true,
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
ancestors.delete(value);
|
|
552
|
+
Object.freeze(output);
|
|
553
|
+
return output;
|
|
554
|
+
}
|
|
555
|
+
function cloneJsonValue(value, ancestors, label) {
|
|
556
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
557
|
+
return value;
|
|
558
|
+
if (typeof value === "number") {
|
|
559
|
+
if (!Number.isFinite(value))
|
|
560
|
+
throw new WorkspaceProviderContractError(`${label} must contain only finite JSON numbers`);
|
|
561
|
+
return value;
|
|
562
|
+
}
|
|
563
|
+
if (Array.isArray(value)) {
|
|
564
|
+
if (ancestors.has(value))
|
|
565
|
+
throw new WorkspaceProviderContractError(`${label} must not contain cycles`);
|
|
566
|
+
ancestors.add(value);
|
|
567
|
+
const output = value.map((child) => cloneJsonValue(child, ancestors, label));
|
|
568
|
+
ancestors.delete(value);
|
|
569
|
+
Object.freeze(output);
|
|
570
|
+
return output;
|
|
571
|
+
}
|
|
572
|
+
if (isRecord(value))
|
|
573
|
+
return cloneJsonRecord(value, ancestors, label);
|
|
574
|
+
throw new WorkspaceProviderContractError(`${label} must contain only JSON values`);
|
|
575
|
+
}
|
|
576
|
+
function parseRequestOperation(value) {
|
|
577
|
+
try {
|
|
578
|
+
return requirePluginBackendOperation(value);
|
|
579
|
+
}
|
|
580
|
+
catch (error) {
|
|
581
|
+
throw providerRequestError("invalid-operation", 400, boundedErrorMessage(error), error);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
function parseRequestRevision(value, operation) {
|
|
585
|
+
try {
|
|
586
|
+
return requirePluginBackendRevision(value);
|
|
587
|
+
}
|
|
588
|
+
catch (error) {
|
|
589
|
+
throw providerRequestError("stale-plugin-revision", 409, `Plugin backend revision is unavailable for operation ${operation}: ${boundedErrorMessage(error)}`, error);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function providerRequestError(code, statusCode, message, cause) {
|
|
593
|
+
return new WorkspaceProviderRequestError(code, statusCode, message, cause === undefined ? {} : { cause });
|
|
594
|
+
}
|
|
595
|
+
function providerRemovalError(code, statusCode, message, cause) {
|
|
596
|
+
return new WorkspaceProviderRemovalError(code, statusCode, message, cause === undefined ? {} : { cause });
|
|
597
|
+
}
|
|
598
|
+
function parseWorkspaceRemovePlan(value, pluginId) {
|
|
599
|
+
if (!isRecord(value)) {
|
|
600
|
+
throw providerRemovalError("invalid-plan", 502, `Server plugin ${pluginId} returned an invalid workspace removal plan`);
|
|
601
|
+
}
|
|
602
|
+
const title = value["title"];
|
|
603
|
+
const command = value["command"];
|
|
604
|
+
if (typeof title !== "string" || title.trim() === "") {
|
|
605
|
+
throw providerRemovalError("invalid-plan", 502, `Server plugin ${pluginId} removal plan title must be a non-empty string`);
|
|
606
|
+
}
|
|
607
|
+
if (typeof command !== "string" || command.trim() === "") {
|
|
608
|
+
throw providerRemovalError("invalid-plan", 502, `Server plugin ${pluginId} removal plan command must be a non-empty string`);
|
|
609
|
+
}
|
|
610
|
+
return Object.freeze({ title, command });
|
|
611
|
+
}
|
|
612
|
+
function boundedErrorMessage(error) {
|
|
613
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
614
|
+
return message.length <= 2_048 ? message : `${message.slice(0, 2_045)}...`;
|
|
615
|
+
}
|
|
616
|
+
function isProviderClaim(value) {
|
|
617
|
+
return value === "claim" || value === "pass";
|
|
618
|
+
}
|
|
619
|
+
function abortError(signal) {
|
|
620
|
+
const reason = signal.reason;
|
|
621
|
+
return reason instanceof Error ? reason : new Error("Workspace provider operation aborted", { cause: reason });
|
|
622
|
+
}
|
|
623
|
+
function throwIfAborted(signal) {
|
|
624
|
+
if (signal?.aborted === true)
|
|
625
|
+
throw abortError(signal);
|
|
626
|
+
}
|
|
627
|
+
function positiveInteger(value, fallback, key) {
|
|
628
|
+
const resolved = value ?? fallback;
|
|
629
|
+
if (!Number.isInteger(resolved) || resolved <= 0)
|
|
630
|
+
throw new Error(`${key} must be a positive integer`);
|
|
631
|
+
return resolved;
|
|
632
|
+
}
|
|
633
|
+
function errorMessage(error) {
|
|
634
|
+
return error instanceof Error ? error.message : String(error);
|
|
635
|
+
}
|
|
636
|
+
function isRecord(value) {
|
|
637
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
638
|
+
}
|
|
639
|
+
class WorkspaceProviderContractError extends Error {
|
|
640
|
+
constructor() {
|
|
641
|
+
super(...arguments);
|
|
642
|
+
this.name = "WorkspaceProviderContractError";
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
class WorkspaceProviderTimeoutError extends Error {
|
|
646
|
+
constructor() {
|
|
647
|
+
super(...arguments);
|
|
648
|
+
this.name = "TimeoutError";
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
//# sourceMappingURL=workspaceProviderRegistry.js.map
|