@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,222 @@
|
|
|
1
|
+
// Generated from pi-web-plugins/info/infoInternals.ts. Do not edit directly.
|
|
2
|
+
// Implementation details of the bundled Info plugin.
|
|
3
|
+
//
|
|
4
|
+
// This file is NOT part of the plugin skeleton. If you copied the Info plugin
|
|
5
|
+
// as a starting point for your own plugin, replace everything here with your
|
|
6
|
+
// own content — the plugin contract (metadata and contribution definitions)
|
|
7
|
+
// lives in pi-web-plugin.ts.
|
|
8
|
+
export function componentHealth(component) {
|
|
9
|
+
if (!component.available)
|
|
10
|
+
return "unavailable";
|
|
11
|
+
if (component.stale)
|
|
12
|
+
return "restart needed";
|
|
13
|
+
return "current";
|
|
14
|
+
}
|
|
15
|
+
export function formatVersion(version) {
|
|
16
|
+
return version === undefined || version === "" ? "unknown" : version;
|
|
17
|
+
}
|
|
18
|
+
export function installationLabel(installation) {
|
|
19
|
+
if (installation === undefined)
|
|
20
|
+
return "installation unknown";
|
|
21
|
+
if (installation.kind === "pi-package") {
|
|
22
|
+
const scope = installation.scope === undefined ? "" : ` · ${installation.scope}`;
|
|
23
|
+
const source = installation.source ?? "Pi package";
|
|
24
|
+
return `${source}${scope}`;
|
|
25
|
+
}
|
|
26
|
+
if (installation.kind === "npm-global")
|
|
27
|
+
return "global npm package";
|
|
28
|
+
if (installation.kind === "local")
|
|
29
|
+
return "local checkout";
|
|
30
|
+
if (installation.kind === "docker")
|
|
31
|
+
return installation.dockerMode === "dev" ? "Docker development runtime" : "Docker runtime";
|
|
32
|
+
return "installation unknown";
|
|
33
|
+
}
|
|
34
|
+
export function machineKindLabel(kind) {
|
|
35
|
+
return kind === "local" ? "local machine" : "remote machine";
|
|
36
|
+
}
|
|
37
|
+
export function releaseSummary(release) {
|
|
38
|
+
if (release.updateAvailable) {
|
|
39
|
+
return release.latestVersion === undefined || release.latestVersion === ""
|
|
40
|
+
? "Update available"
|
|
41
|
+
: `Update available: ${release.latestVersion}`;
|
|
42
|
+
}
|
|
43
|
+
if (release.error !== undefined && release.error !== "")
|
|
44
|
+
return `Update check failed: ${release.error}`;
|
|
45
|
+
if (release.skipped === true)
|
|
46
|
+
return "Update check skipped";
|
|
47
|
+
return "Up to date";
|
|
48
|
+
}
|
|
49
|
+
/** One-line component summary used by the panel rows and the clipboard diagnostics. */
|
|
50
|
+
export function componentDetails(component) {
|
|
51
|
+
const parts = [
|
|
52
|
+
`running ${formatVersion(component.runtimeVersion)}`,
|
|
53
|
+
`installed ${formatVersion(component.installedVersion)}`,
|
|
54
|
+
`pi ${formatVersion(component.piVersion)}`,
|
|
55
|
+
componentHealth(component),
|
|
56
|
+
installationLabel(component.installation),
|
|
57
|
+
];
|
|
58
|
+
if (component.installation?.path !== undefined && component.installation.path !== "")
|
|
59
|
+
parts.push(component.installation.path);
|
|
60
|
+
if (component.error !== undefined && component.error !== "")
|
|
61
|
+
parts.push(`error: ${component.error}`);
|
|
62
|
+
return parts.join(" · ");
|
|
63
|
+
}
|
|
64
|
+
/** Note shown when the session daemon runs a different Pi version than the web process. */
|
|
65
|
+
export function piVersionDriftNote(web, sessiond) {
|
|
66
|
+
if (!sessiond.available)
|
|
67
|
+
return undefined;
|
|
68
|
+
if (web.piVersion === undefined || sessiond.piVersion === undefined)
|
|
69
|
+
return undefined;
|
|
70
|
+
return web.piVersion === sessiond.piVersion ? undefined : `session daemon running ${formatVersion(sessiond.piVersion)}`;
|
|
71
|
+
}
|
|
72
|
+
export function workspaceFlags(workspace) {
|
|
73
|
+
return [
|
|
74
|
+
workspace.provider === undefined ? "folder workspace" : `provider: ${workspace.provider.pluginId}`,
|
|
75
|
+
workspace.isMain ? "main workspace" : undefined,
|
|
76
|
+
].filter((flag) => flag !== undefined);
|
|
77
|
+
}
|
|
78
|
+
/** Plain-text status block suitable for pasting into a bug report. */
|
|
79
|
+
export function diagnosticsSummary({ status, machine, workspace }) {
|
|
80
|
+
const lines = ["PI WEB diagnostics"];
|
|
81
|
+
if (status === undefined) {
|
|
82
|
+
lines.push("Status: unavailable");
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
lines.push(`Package: ${status.packageName}`);
|
|
86
|
+
lines.push(`${status.components.web.label}: ${componentDetails(status.components.web)}`);
|
|
87
|
+
lines.push(`${status.components.sessiond.label}: ${componentDetails(status.components.sessiond)}`);
|
|
88
|
+
const checked = status.release.checkedAt === undefined || status.release.skipped === true ? "" : ` (checked ${status.release.checkedAt})`;
|
|
89
|
+
lines.push(`Release: ${releaseSummary(status.release)}${checked}`);
|
|
90
|
+
lines.push(`Status generated: ${status.generatedAt}`);
|
|
91
|
+
}
|
|
92
|
+
if (machine !== undefined)
|
|
93
|
+
lines.push(`Machine: ${machine.name} (${machineKindLabel(machine.kind)})`);
|
|
94
|
+
if (workspace === undefined) {
|
|
95
|
+
lines.push("Workspace: none selected");
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
lines.push(`Workspace: ${workspace.label} — ${workspace.path} (${workspaceFlags(workspace).join(", ")})`);
|
|
99
|
+
}
|
|
100
|
+
return lines.join("\n");
|
|
101
|
+
}
|
|
102
|
+
/** Action body: copy the diagnostics summary for the current runtime context. */
|
|
103
|
+
export async function copyDiagnostics(context) {
|
|
104
|
+
const summary = diagnosticsSummary({
|
|
105
|
+
status: context.state.piWebStatus,
|
|
106
|
+
machine: context.state.selectedMachine,
|
|
107
|
+
workspace: context.state.selectedWorkspace,
|
|
108
|
+
});
|
|
109
|
+
await navigator.clipboard.writeText(summary);
|
|
110
|
+
}
|
|
111
|
+
function renderComponent(html, component) {
|
|
112
|
+
const health = componentHealth(component);
|
|
113
|
+
return html `
|
|
114
|
+
<div class="info-component">
|
|
115
|
+
<strong>${component.label}</strong>
|
|
116
|
+
<span class=${health === "current" ? "info-health-ok" : "info-health-attention"}>${health}</span>
|
|
117
|
+
<small>${componentDetails(component)}</small>
|
|
118
|
+
</div>
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
function renderStatusSection(html, status) {
|
|
122
|
+
if (status === undefined) {
|
|
123
|
+
return html `
|
|
124
|
+
<section>
|
|
125
|
+
<strong>PI WEB</strong>
|
|
126
|
+
<p class="muted">PI WEB status is not available yet. It refreshes automatically in the background.</p>
|
|
127
|
+
</section>
|
|
128
|
+
`;
|
|
129
|
+
}
|
|
130
|
+
const web = status.components.web;
|
|
131
|
+
const driftNote = piVersionDriftNote(web, status.components.sessiond);
|
|
132
|
+
const messageCount = status.messages.length;
|
|
133
|
+
return html `
|
|
134
|
+
<section>
|
|
135
|
+
<strong>PI WEB</strong>
|
|
136
|
+
<div class="info-row">
|
|
137
|
+
<span>Version</span>
|
|
138
|
+
<span>${formatVersion(web.runtimeVersion)}</span>
|
|
139
|
+
${web.installedVersion === undefined || web.installedVersion === web.runtimeVersion ? null : html `<small>installed ${formatVersion(web.installedVersion)}</small>`}
|
|
140
|
+
</div>
|
|
141
|
+
<div class="info-row">
|
|
142
|
+
<span>Pi</span>
|
|
143
|
+
<span>${formatVersion(web.piVersion)}</span>
|
|
144
|
+
${driftNote === undefined ? null : html `<small>${driftNote}</small>`}
|
|
145
|
+
</div>
|
|
146
|
+
<div class="info-row">
|
|
147
|
+
<span>Package</span>
|
|
148
|
+
<span>${status.packageName}</span>
|
|
149
|
+
</div>
|
|
150
|
+
<div class="info-row">
|
|
151
|
+
<span>Installation</span>
|
|
152
|
+
<span>${installationLabel(web.installation)}</span>
|
|
153
|
+
${web.installation?.path === undefined || web.installation.path === "" ? null : html `<small>${web.installation.path}</small>`}
|
|
154
|
+
</div>
|
|
155
|
+
<div class="info-row">
|
|
156
|
+
<span>Release</span>
|
|
157
|
+
<span>${releaseSummary(status.release)}</span>
|
|
158
|
+
${status.release.checkedAt === undefined || status.release.skipped === true ? null : html `<small>checked ${status.release.checkedAt}</small>`}
|
|
159
|
+
</div>
|
|
160
|
+
${messageCount === 0 ? null : html `<p class="muted">${String(messageCount)} status ${messageCount === 1 ? "message" : "messages"} — open the Updates tab for details.</p>`}
|
|
161
|
+
<p class="muted">Status generated ${status.generatedAt}</p>
|
|
162
|
+
</section>
|
|
163
|
+
<section>
|
|
164
|
+
<strong>Services</strong>
|
|
165
|
+
${renderComponent(html, status.components.web)}
|
|
166
|
+
${renderComponent(html, status.components.sessiond)}
|
|
167
|
+
</section>
|
|
168
|
+
`;
|
|
169
|
+
}
|
|
170
|
+
function renderMachineSection(html, machine) {
|
|
171
|
+
return html `
|
|
172
|
+
<section>
|
|
173
|
+
<strong>Machine</strong>
|
|
174
|
+
<div class="info-row">
|
|
175
|
+
<span>Name</span>
|
|
176
|
+
<span>${machine.name}</span>
|
|
177
|
+
</div>
|
|
178
|
+
<div class="info-row">
|
|
179
|
+
<span>Type</span>
|
|
180
|
+
<span>${machineKindLabel(machine.kind)}</span>
|
|
181
|
+
</div>
|
|
182
|
+
</section>
|
|
183
|
+
`;
|
|
184
|
+
}
|
|
185
|
+
function renderWorkspaceSection(html, workspace) {
|
|
186
|
+
return html `
|
|
187
|
+
<section>
|
|
188
|
+
<strong>Workspace</strong>
|
|
189
|
+
<div class="info-row">
|
|
190
|
+
<span>Name</span>
|
|
191
|
+
<span>${workspace.label}</span>
|
|
192
|
+
</div>
|
|
193
|
+
<div class="info-row">
|
|
194
|
+
<span>Path</span>
|
|
195
|
+
<span class="info-path">${workspace.path}</span>
|
|
196
|
+
${workspaceFlags(workspace).length === 0 ? null : html `<small>${workspaceFlags(workspace).join(" · ")}</small>`}
|
|
197
|
+
</div>
|
|
198
|
+
</section>
|
|
199
|
+
`;
|
|
200
|
+
}
|
|
201
|
+
/** Panel body: render the Info tab for the current workspace panel context. */
|
|
202
|
+
export function renderInfoPanel(html, context) {
|
|
203
|
+
return html `
|
|
204
|
+
<style>
|
|
205
|
+
.viewer.info-status { flex: 1 1 auto; min-height: 0; box-sizing: border-box; display: flex; flex-direction: column; gap: 14px; padding: 12px; overflow-y: auto; overflow-x: hidden; }
|
|
206
|
+
.viewer.info-status section { flex: 0 0 auto; min-width: 0; display: grid; gap: 8px; align-content: start; }
|
|
207
|
+
.viewer.info-status p { margin: 0; }
|
|
208
|
+
.info-row { display: grid; grid-template-columns: minmax(90px, auto) minmax(0, 1fr); gap: 3px 10px; border-bottom: 1px solid var(--pi-border-muted); padding: 6px 0; overflow-wrap: anywhere; }
|
|
209
|
+
.info-row small { grid-column: 1 / -1; color: var(--pi-muted); }
|
|
210
|
+
.info-component { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 3px 10px; border-bottom: 1px solid var(--pi-border-muted); padding: 6px 0; }
|
|
211
|
+
.info-component small { grid-column: 1 / -1; color: var(--pi-muted); overflow-wrap: anywhere; }
|
|
212
|
+
.info-health-ok { color: var(--pi-success); }
|
|
213
|
+
.info-health-attention { color: var(--pi-warning); }
|
|
214
|
+
</style>
|
|
215
|
+
<section class="toolbar"><strong>Info</strong></section>
|
|
216
|
+
<section class="viewer info-status">
|
|
217
|
+
${renderStatusSection(html, context.state?.piWebStatus)}
|
|
218
|
+
${renderMachineSection(html, context.machine)}
|
|
219
|
+
${renderWorkspaceSection(html, context.workspace)}
|
|
220
|
+
</section>
|
|
221
|
+
`;
|
|
222
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Generated from pi-web-plugins/info/pi-web-plugin.ts. Do not edit directly.
|
|
2
|
+
// Skeleton of a PI WEB plugin: metadata plus contribution definitions.
|
|
3
|
+
//
|
|
4
|
+
// Everything the bundled Info panel and action actually render lives in
|
|
5
|
+
// infoInternals.ts. That file is replaceable implementation detail — when
|
|
6
|
+
// copying this plugin as a starting point, keep this file's shape and swap
|
|
7
|
+
// the internals for your own.
|
|
8
|
+
import { copyDiagnostics, renderInfoPanel } from "./infoInternals.js";
|
|
9
|
+
const plugin = {
|
|
10
|
+
apiVersion: 2,
|
|
11
|
+
name: "Info Plugin",
|
|
12
|
+
activate: ({ html, svg }) => ({
|
|
13
|
+
contributions: {
|
|
14
|
+
actions: [
|
|
15
|
+
{
|
|
16
|
+
id: "copy-diagnostics",
|
|
17
|
+
title: "Copy PI WEB Diagnostics",
|
|
18
|
+
description: "Copy version, installation, and status details for this machine, ready to paste into a bug report",
|
|
19
|
+
group: "Info",
|
|
20
|
+
run: (context) => copyDiagnostics(context),
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
workspaceLabels: [
|
|
24
|
+
{
|
|
25
|
+
id: "workspace.kind-label",
|
|
26
|
+
order: 100,
|
|
27
|
+
items: (context) => [{ type: "text", text: context.workspace.provider?.pluginId ?? "folder", title: context.workspace.path }],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
workspacePanels: [
|
|
31
|
+
{
|
|
32
|
+
id: "workspace.info",
|
|
33
|
+
title: "Info",
|
|
34
|
+
icon: svg `
|
|
35
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
36
|
+
<circle cx="12" cy="12" r="9"></circle>
|
|
37
|
+
<path d="M12 11v5"></path>
|
|
38
|
+
<path d="M12 8h.01"></path>
|
|
39
|
+
</svg>
|
|
40
|
+
`,
|
|
41
|
+
order: 1000,
|
|
42
|
+
render: (context) => renderInfoPanel(html, context),
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
};
|
|
48
|
+
export default plugin;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Generated from pi-web-plugins/relays/markdownDocument.ts. Do not edit directly.
|
|
2
|
+
import { marked } from "./vendor/marked.esm.js";
|
|
3
|
+
// Raw HTML inside relay documents is escaped before sanitizing, so the
|
|
4
|
+
// sanitizer only ever sees marked-generated markup. This mirrors the safety
|
|
5
|
+
// rules of the app's renderer in src/client/src/formatting/markdown.ts; the
|
|
6
|
+
// plugin cannot import that module, so the rules are duplicated here.
|
|
7
|
+
const renderer = new marked.Renderer();
|
|
8
|
+
renderer.html = ({ text }) => escapeHtml(text);
|
|
9
|
+
const MAX_MARKDOWN_CACHE_ENTRIES = 300;
|
|
10
|
+
const markdownHtmlCache = new Map();
|
|
11
|
+
/** Relay documents ending in .md render as markdown; everything else stays preformatted text. */
|
|
12
|
+
export function isMarkdownDocumentPath(path) {
|
|
13
|
+
return path.toLowerCase().endsWith(".md");
|
|
14
|
+
}
|
|
15
|
+
/** Render one relay markdown document into sanitized HTML safe to interpolate into innerHTML. */
|
|
16
|
+
export function renderRelayDocumentHtml(source) {
|
|
17
|
+
const cached = markdownHtmlCache.get(source);
|
|
18
|
+
if (cached !== undefined)
|
|
19
|
+
return cached;
|
|
20
|
+
const html = marked.parse(source, { async: false, breaks: true, gfm: true, renderer });
|
|
21
|
+
const safeHtml = sanitizeHtml(html);
|
|
22
|
+
markdownHtmlCache.set(source, safeHtml);
|
|
23
|
+
if (markdownHtmlCache.size > MAX_MARKDOWN_CACHE_ENTRIES) {
|
|
24
|
+
const oldest = markdownHtmlCache.keys().next().value;
|
|
25
|
+
if (oldest !== undefined)
|
|
26
|
+
markdownHtmlCache.delete(oldest);
|
|
27
|
+
}
|
|
28
|
+
return safeHtml;
|
|
29
|
+
}
|
|
30
|
+
function escapeHtml(text) {
|
|
31
|
+
return text
|
|
32
|
+
.replaceAll("&", "&")
|
|
33
|
+
.replaceAll("<", "<")
|
|
34
|
+
.replaceAll(">", ">");
|
|
35
|
+
}
|
|
36
|
+
const TABLE_SCROLL_CLASS = "table-scroll";
|
|
37
|
+
function sanitizeHtml(html) {
|
|
38
|
+
const template = document.createElement("template");
|
|
39
|
+
template.innerHTML = html;
|
|
40
|
+
template.content.querySelectorAll("script, style, iframe, object, embed").forEach((node) => { node.remove(); });
|
|
41
|
+
template.content.querySelectorAll("*").forEach((element) => {
|
|
42
|
+
for (const attribute of [...element.attributes]) {
|
|
43
|
+
const name = attribute.name.toLowerCase();
|
|
44
|
+
if (name.startsWith("on"))
|
|
45
|
+
element.removeAttribute(attribute.name);
|
|
46
|
+
if ((name === "href" || name === "src") && !isSafeUrl(attribute.value))
|
|
47
|
+
element.removeAttribute(attribute.name);
|
|
48
|
+
}
|
|
49
|
+
if (element.tagName === "A") {
|
|
50
|
+
element.setAttribute("target", "_blank");
|
|
51
|
+
element.setAttribute("rel", "noreferrer noopener");
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
wrapTablesInScrollRegions(template.content);
|
|
55
|
+
return template.innerHTML;
|
|
56
|
+
}
|
|
57
|
+
// Tables keep their natural width and scroll horizontally instead of being
|
|
58
|
+
// squeezed into the panel, which is unreadable on narrow screens.
|
|
59
|
+
function wrapTablesInScrollRegions(root) {
|
|
60
|
+
root.querySelectorAll("table").forEach((table) => {
|
|
61
|
+
if (table.parentElement?.classList.contains(TABLE_SCROLL_CLASS) === true)
|
|
62
|
+
return;
|
|
63
|
+
const wrapper = document.createElement("div");
|
|
64
|
+
wrapper.className = TABLE_SCROLL_CLASS;
|
|
65
|
+
wrapper.setAttribute("role", "region");
|
|
66
|
+
wrapper.setAttribute("aria-label", "Table");
|
|
67
|
+
wrapper.setAttribute("tabindex", "0");
|
|
68
|
+
table.before(wrapper);
|
|
69
|
+
wrapper.append(table);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function isSafeUrl(url) {
|
|
73
|
+
if (url.startsWith("#") || url.startsWith("/"))
|
|
74
|
+
return true;
|
|
75
|
+
try {
|
|
76
|
+
return ["http:", "https:", "mailto:"].includes(new URL(url).protocol);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Generated from pi-web-plugins/relays/pi-web-plugin.ts. Do not edit directly.
|
|
2
|
+
import { RELAYS_ROOT } from "./relayDiscovery.js";
|
|
3
|
+
import { defineRelaysPanelElement } from "./relaysPanelElement.js";
|
|
4
|
+
const plugin = {
|
|
5
|
+
apiVersion: 2,
|
|
6
|
+
name: "Relays",
|
|
7
|
+
activate: ({ runtimePluginId, html, svg }) => {
|
|
8
|
+
defineRelaysPanelElement();
|
|
9
|
+
return {
|
|
10
|
+
contributions: {
|
|
11
|
+
actions: [
|
|
12
|
+
{
|
|
13
|
+
id: "workspace.open-relays",
|
|
14
|
+
title: "Open Workspace Relays",
|
|
15
|
+
description: `Open the workspace Relays tab. Relays live in ${RELAYS_ROOT}.`,
|
|
16
|
+
group: "Workspace",
|
|
17
|
+
enabled: (context) => context.state.selectedWorkspace !== undefined,
|
|
18
|
+
run: (context) => {
|
|
19
|
+
if (context.state.selectedWorkspace === undefined)
|
|
20
|
+
return;
|
|
21
|
+
context.selectWorkspaceTool(`${runtimePluginId}:workspace.relays`);
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
workspacePanels: [
|
|
26
|
+
{
|
|
27
|
+
id: "workspace.relays",
|
|
28
|
+
title: "Relays",
|
|
29
|
+
icon: svg `
|
|
30
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
31
|
+
<path d="M9 17H7A5 5 0 0 1 7 7h2"></path>
|
|
32
|
+
<path d="M15 7h2a5 5 0 1 1 0 10h-2"></path>
|
|
33
|
+
<line x1="8" y1="12" x2="16" y2="12"></line>
|
|
34
|
+
</svg>
|
|
35
|
+
`,
|
|
36
|
+
order: 50,
|
|
37
|
+
render: (context) => html `<pi-web-relays-panel .context=${context}></pi-web-relays-panel>`,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
export default plugin;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// Generated from pi-web-plugins/relays/relayDiscovery.ts. Do not edit directly.
|
|
2
|
+
export const RELAYS_ROOT = ".pi-web/relays";
|
|
3
|
+
/** Documents that anchor a relay packet, in display order. Any other files follow alphabetically. */
|
|
4
|
+
export const RELAY_ANCHOR_DOCUMENTS = ["status.md", "charter.md", "log.md"];
|
|
5
|
+
/**
|
|
6
|
+
* Deepest node depth listed below the relay root; direct children of the relay
|
|
7
|
+
* root are depth 0. Children of a directory at this depth are skipped and the
|
|
8
|
+
* listing is flagged partial instead of walking arbitrarily deep trees.
|
|
9
|
+
*/
|
|
10
|
+
export const MAX_RELAY_TREE_DEPTH = 5;
|
|
11
|
+
/** Most documents listed across one relay; extras are skipped and the listing is flagged partial. */
|
|
12
|
+
export const MAX_RELAY_DOCUMENTS = 200;
|
|
13
|
+
/** Most directories walked in one relay; extras stay listed but unexplored and the listing is flagged partial. */
|
|
14
|
+
export const MAX_RELAY_DIRECTORIES = 50;
|
|
15
|
+
// The workspace file API rejects with these messages when a path is absent or
|
|
16
|
+
// is not a directory. For the relays root both mean "zero relays", not a failure.
|
|
17
|
+
const missingListingErrorMessages = new Set(["Path does not exist", "Path is not a directory"]);
|
|
18
|
+
/** List the workspace's relays, most recently modified first. Never rejects. */
|
|
19
|
+
export async function listWorkspaceRelays(files) {
|
|
20
|
+
let listing;
|
|
21
|
+
try {
|
|
22
|
+
listing = await files.listFiles(RELAYS_ROOT);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
return fileAccessFailure(error);
|
|
26
|
+
}
|
|
27
|
+
const relays = sortRelaysByRecency(listing.entries
|
|
28
|
+
.filter((entry) => entry.type === "directory")
|
|
29
|
+
.map((entry) => toRelaySummary(entry)));
|
|
30
|
+
return { kind: "loaded", relays };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* List one relay's document tree. At the relay root the anchor documents come
|
|
34
|
+
* first (status.md, charter.md, log.md); at every level files sort
|
|
35
|
+
* alphabetically before directories. Symlinks are never followed. Never
|
|
36
|
+
* rejects.
|
|
37
|
+
*/
|
|
38
|
+
export async function listRelayDocumentTree(files, relayPath) {
|
|
39
|
+
let rootListing;
|
|
40
|
+
try {
|
|
41
|
+
rootListing = await files.listFiles(relayPath);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
return fileAccessFailure(error);
|
|
45
|
+
}
|
|
46
|
+
const state = { documentCount: 0, directoryCount: 0, partial: rootListing.truncated };
|
|
47
|
+
const tree = await walkListing(files, rootListing, relayPath, 0, state);
|
|
48
|
+
return { kind: "loaded", tree, documentCount: state.documentCount, partial: state.partial };
|
|
49
|
+
}
|
|
50
|
+
/** Read one relay document. Never rejects. */
|
|
51
|
+
export async function readRelayDocument(files, documentPath) {
|
|
52
|
+
try {
|
|
53
|
+
const file = await files.readFile(documentPath);
|
|
54
|
+
return { kind: "loaded", content: file.content, truncated: file.truncated, binary: file.binary };
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
return fileAccessFailure(error);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Newest first; relays without a usable modifiedAt sort last, alphabetically. */
|
|
61
|
+
export function sortRelaysByRecency(relays) {
|
|
62
|
+
return [...relays].sort(compareRelaysByRecency);
|
|
63
|
+
}
|
|
64
|
+
/** The document a relay opens on: the depth-first first file (root anchors sort first, so status.md wins when present). */
|
|
65
|
+
export function defaultRelayDocument(tree) {
|
|
66
|
+
for (const node of tree) {
|
|
67
|
+
if (node.kind === "file")
|
|
68
|
+
return node;
|
|
69
|
+
const nested = defaultRelayDocument(node.children);
|
|
70
|
+
if (nested !== undefined)
|
|
71
|
+
return nested;
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
/** All file nodes in tree order, depth-first. */
|
|
76
|
+
export function flattenRelayTree(tree) {
|
|
77
|
+
const files = [];
|
|
78
|
+
const visit = (nodes) => {
|
|
79
|
+
for (const node of nodes) {
|
|
80
|
+
if (node.kind === "file")
|
|
81
|
+
files.push(node);
|
|
82
|
+
else
|
|
83
|
+
visit(node.children);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
visit(tree);
|
|
87
|
+
return files;
|
|
88
|
+
}
|
|
89
|
+
/** Directory paths of every directory in the tree; used to prune stale expansion state. */
|
|
90
|
+
export function collectDirectoryPaths(tree) {
|
|
91
|
+
const paths = new Set();
|
|
92
|
+
const visit = (nodes) => {
|
|
93
|
+
for (const node of nodes) {
|
|
94
|
+
if (node.kind !== "directory")
|
|
95
|
+
continue;
|
|
96
|
+
paths.add(node.path);
|
|
97
|
+
visit(node.children);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
visit(tree);
|
|
101
|
+
return paths;
|
|
102
|
+
}
|
|
103
|
+
/** Directory paths containing the given file, from the relay root down; empty when the file is not in the tree. */
|
|
104
|
+
export function ancestorDirectoryPaths(tree, filePath) {
|
|
105
|
+
const chain = [];
|
|
106
|
+
const visit = (nodes) => {
|
|
107
|
+
for (const node of nodes) {
|
|
108
|
+
if (node.kind === "file") {
|
|
109
|
+
if (node.path === filePath)
|
|
110
|
+
return true;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
chain.push(node.path);
|
|
114
|
+
if (visit(node.children))
|
|
115
|
+
return true;
|
|
116
|
+
chain.pop();
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
};
|
|
120
|
+
return visit(tree) ? chain : [];
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Build the ordered children of one already-fetched directory listing.
|
|
124
|
+
* Subdirectories are walked in parallel; a subdirectory whose own listing
|
|
125
|
+
* fails contributes an empty node and flags the tree partial rather than
|
|
126
|
+
* failing the whole relay.
|
|
127
|
+
*/
|
|
128
|
+
async function walkListing(files, listing, relayPath, depth, state) {
|
|
129
|
+
// Symlink entries are intentionally never followed.
|
|
130
|
+
const fileEntries = listing.entries.filter((entry) => entry.type === "file");
|
|
131
|
+
const directoryEntries = listing.entries.filter((entry) => entry.type === "directory");
|
|
132
|
+
let fileNodes = orderFileEntries(fileEntries, depth === 0).map((entry) => toFileNode(entry, relayPath, depth));
|
|
133
|
+
const remaining = MAX_RELAY_DOCUMENTS - state.documentCount;
|
|
134
|
+
if (fileNodes.length > remaining) {
|
|
135
|
+
fileNodes = fileNodes.slice(0, Math.max(0, remaining));
|
|
136
|
+
state.partial = true;
|
|
137
|
+
}
|
|
138
|
+
state.documentCount += fileNodes.length;
|
|
139
|
+
const directories = await Promise.all([...directoryEntries].sort(compareByName).map(async (entry) => {
|
|
140
|
+
const node = {
|
|
141
|
+
kind: "directory",
|
|
142
|
+
name: entry.name,
|
|
143
|
+
path: entry.path,
|
|
144
|
+
relativePath: relativePathOf(entry.path, relayPath),
|
|
145
|
+
depth,
|
|
146
|
+
children: [],
|
|
147
|
+
};
|
|
148
|
+
if (depth >= MAX_RELAY_TREE_DEPTH) {
|
|
149
|
+
state.partial = true;
|
|
150
|
+
return node;
|
|
151
|
+
}
|
|
152
|
+
// The count is bumped synchronously before any await, so the parallel
|
|
153
|
+
// walk below stays deterministic in sorted order.
|
|
154
|
+
if (state.directoryCount >= MAX_RELAY_DIRECTORIES) {
|
|
155
|
+
state.partial = true;
|
|
156
|
+
return node;
|
|
157
|
+
}
|
|
158
|
+
state.directoryCount += 1;
|
|
159
|
+
try {
|
|
160
|
+
const subListing = await files.listFiles(entry.path);
|
|
161
|
+
if (subListing.truncated)
|
|
162
|
+
state.partial = true;
|
|
163
|
+
node.children = await walkListing(files, subListing, relayPath, depth + 1, state);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
state.partial = true;
|
|
167
|
+
}
|
|
168
|
+
return node;
|
|
169
|
+
}));
|
|
170
|
+
return [...fileNodes, ...directories];
|
|
171
|
+
}
|
|
172
|
+
/** At the relay root the anchor documents lead; deeper levels are simply alphabetical. */
|
|
173
|
+
function orderFileEntries(entries, isRelayRoot) {
|
|
174
|
+
return [...entries].sort(isRelayRoot ? compareRootFiles : compareByName);
|
|
175
|
+
}
|
|
176
|
+
function compareRootFiles(left, right) {
|
|
177
|
+
const anchorOrder = anchorIndexOf(left.name) - anchorIndexOf(right.name);
|
|
178
|
+
if (anchorOrder !== 0)
|
|
179
|
+
return anchorOrder;
|
|
180
|
+
return left.name.localeCompare(right.name);
|
|
181
|
+
}
|
|
182
|
+
function anchorIndexOf(name) {
|
|
183
|
+
const index = RELAY_ANCHOR_DOCUMENTS.indexOf(name);
|
|
184
|
+
return index === -1 ? RELAY_ANCHOR_DOCUMENTS.length : index;
|
|
185
|
+
}
|
|
186
|
+
function compareByName(left, right) {
|
|
187
|
+
return left.name.localeCompare(right.name);
|
|
188
|
+
}
|
|
189
|
+
function toRelaySummary(entry) {
|
|
190
|
+
return { name: entry.name, path: entry.path, modifiedAt: entry.modifiedAt };
|
|
191
|
+
}
|
|
192
|
+
function toFileNode(entry, relayPath, depth) {
|
|
193
|
+
return {
|
|
194
|
+
kind: "file",
|
|
195
|
+
name: entry.name,
|
|
196
|
+
path: entry.path,
|
|
197
|
+
relativePath: relativePathOf(entry.path, relayPath),
|
|
198
|
+
depth,
|
|
199
|
+
modifiedAt: entry.modifiedAt,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/** Entry paths from the file API should sit under the relay root; fall back to the file name if not. */
|
|
203
|
+
function relativePathOf(path, relayPath) {
|
|
204
|
+
const prefix = `${relayPath}/`;
|
|
205
|
+
return path.startsWith(prefix) ? path.slice(prefix.length) : path.slice(path.lastIndexOf("/") + 1);
|
|
206
|
+
}
|
|
207
|
+
function compareRelaysByRecency(left, right) {
|
|
208
|
+
const leftTime = timestampOf(left.modifiedAt);
|
|
209
|
+
const rightTime = timestampOf(right.modifiedAt);
|
|
210
|
+
if (leftTime !== undefined && rightTime !== undefined && leftTime !== rightTime)
|
|
211
|
+
return rightTime - leftTime;
|
|
212
|
+
if (leftTime !== undefined)
|
|
213
|
+
return -1;
|
|
214
|
+
if (rightTime !== undefined)
|
|
215
|
+
return 1;
|
|
216
|
+
return left.name.localeCompare(right.name);
|
|
217
|
+
}
|
|
218
|
+
function timestampOf(modifiedAt) {
|
|
219
|
+
if (modifiedAt === undefined)
|
|
220
|
+
return undefined;
|
|
221
|
+
const time = Date.parse(modifiedAt);
|
|
222
|
+
return Number.isNaN(time) ? undefined : time;
|
|
223
|
+
}
|
|
224
|
+
function fileAccessFailure(error) {
|
|
225
|
+
if (error instanceof Error && missingListingErrorMessages.has(error.message))
|
|
226
|
+
return { kind: "missing" };
|
|
227
|
+
return { kind: "unavailable", detail: error instanceof Error ? error.message : String(error) };
|
|
228
|
+
}
|