@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,387 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import Fastify from "fastify";
|
|
5
|
+
import fastifyWebsocket from "@fastify/websocket";
|
|
6
|
+
import { WorkspaceActivityService } from "./activity/workspaceActivityService.js";
|
|
7
|
+
import { MachineStatusService } from "./status/machineStatusService.js";
|
|
8
|
+
import { registerMachineStatusRoutes } from "./status/machineStatusRoutes.js";
|
|
9
|
+
import { CachedWorkspaceAttribution } from "./status/workspaceAttribution.js";
|
|
10
|
+
import { SessionEventHub } from "./realtime/sessionEventHub.js";
|
|
11
|
+
import { drainActiveWork, resolveDrainTimeoutMs } from "./sessions/shutdownDrain.js";
|
|
12
|
+
import { recordInterruptedRuns, takeInterruptedRuns } from "./sessions/interruptedRunStore.js";
|
|
13
|
+
import { AuthService } from "./sessions/authService.js";
|
|
14
|
+
import { bootstrapAndFreezeGlobalExtensionProviders } from "./sessions/globalProviderPolicy.js";
|
|
15
|
+
import { registerAuthRoutes } from "./sessions/authRoutes.js";
|
|
16
|
+
import { ModelCatalogRefresher } from "./sessions/modelCatalogRefresher.js";
|
|
17
|
+
import { PiSessionService } from "./sessions/piSessionService.js";
|
|
18
|
+
import { createPiSessionManagerGateway } from "./sessions/piSessionManagerGateway.js";
|
|
19
|
+
import { registerSessionRoutes } from "./sessions/sessionRoutes.js";
|
|
20
|
+
import { SessionNotificationStore } from "./sessions/sessionNotificationStore.js";
|
|
21
|
+
import { SessionArchiveStore, defaultSessionArchiveFilePath } from "./sessions/sessionArchiveStore.js";
|
|
22
|
+
import { FileSessionUnreadPersistence, SessionUnreadStore, defaultSessionUnreadFilePath } from "./sessions/sessionUnreadStore.js";
|
|
23
|
+
import { ProjectScopedSpawnTargetResolver } from "./sessions/spawnTargetResolver.js";
|
|
24
|
+
import { ProjectService } from "./projects/projectService.js";
|
|
25
|
+
import { ProjectStore, projectStorePath } from "./storage/projectStore.js";
|
|
26
|
+
import { eligibleWorkspaceProviderContributions, WorkspaceProviderRegistry, } from "./workspaces/workspaceProviderRegistry.js";
|
|
27
|
+
import { sessiondSocketPath } from "../sessiond/config.js";
|
|
28
|
+
import { TerminalService } from "./terminals/terminalService.js";
|
|
29
|
+
import { registerTerminalRoutes } from "./terminals/terminalRoutes.js";
|
|
30
|
+
import { getPiWebRuntimeComponent } from "./piWebStatus.js";
|
|
31
|
+
import { SESSIOND_RUNTIME_CAPABILITIES } from "../shared/capabilities.js";
|
|
32
|
+
import { agentSessionDirEnvOverride, effectivePiWebConfig, maxUploadBytes, offlineModeEnabled, PI_CODING_AGENT_DIR_ENV, PI_CODING_AGENT_SESSION_DIR_ENV } from "../config.js";
|
|
33
|
+
import { createActiveAgentProfileDescriptor } from "../sessiond/activeAgentProfile.js";
|
|
34
|
+
import { loadServerPluginRecoveryConfig } from "../serverPluginRecovery.js";
|
|
35
|
+
import { PiWebPluginCatalog } from "./piWebPluginCatalog.js";
|
|
36
|
+
import { applyAgentHttpIdleTimeout } from "./sessiond/agentHttpDispatcher.js";
|
|
37
|
+
import { scrubNonAgentVisibleEnvKeys } from "./sessiond/agentProcessEnvironment.js";
|
|
38
|
+
import { claimSessiondStateOwnership } from "./sessiond/sessiondStateOwnership.js";
|
|
39
|
+
import { dockerEnvironmentPromptSections } from "./sessions/dockerEnvironmentFacts.js";
|
|
40
|
+
import { PI_WEB_SESSION_ENV, sessionEnvironmentPromptSections } from "./sessions/sessionEnvironmentFacts.js";
|
|
41
|
+
import { createServerPluginExecFile } from "./plugins/serverPluginExec.js";
|
|
42
|
+
import { createServerPluginRuntime } from "./plugins/serverPluginRuntime.js";
|
|
43
|
+
import { runSessionDaemonShutdown } from "./sessiond/sessionDaemonShutdown.js";
|
|
44
|
+
import { sessionServiceDependencies } from "./sessiond/sessionServiceDependencies.js";
|
|
45
|
+
import { registerWorkspaceCatalogRoutes } from "./sessiond/workspaceCatalogRoutes.js";
|
|
46
|
+
import { registerPluginBackendRoutes } from "./sessiond/pluginBackendRoutes.js";
|
|
47
|
+
import { registerWorkspaceRemovalRoutes } from "./sessiond/workspaceRemovalRoutes.js";
|
|
48
|
+
import { createWorkspaceProviderRuntimeSnapshot } from "./workspaces/workspaceCatalog.js";
|
|
49
|
+
import { WorkspaceRemovalService } from "./workspaces/workspaceRemovalService.js";
|
|
50
|
+
const daemonEnvironment = Object.freeze({ ...process.env });
|
|
51
|
+
const serverPluginRecovery = loadServerPluginRecoveryConfig({ env: daemonEnvironment });
|
|
52
|
+
const { config, deprecatedAgentInputs } = effectivePiWebConfig({ env: daemonEnvironment });
|
|
53
|
+
const activeAgentProfile = createActiveAgentProfileDescriptor(config.agent);
|
|
54
|
+
// Normalize the resolved agent state locations into the canonical pi SDK env
|
|
55
|
+
// vars before anything agent-visible can spawn: the embedded SDK's own
|
|
56
|
+
// resolution (getAgentDir(), its session-dir override), terminals, the bash
|
|
57
|
+
// tool, and subsessions then all observe the same values by inheritance. The
|
|
58
|
+
// exported canonical values are fully resolved, so agent processes that also
|
|
59
|
+
// inherit the deprecated PI_WEB_AGENT_* aliases still resolve to the same
|
|
60
|
+
// locations.
|
|
61
|
+
process.env[PI_CODING_AGENT_DIR_ENV] = activeAgentProfile.dir;
|
|
62
|
+
const agentSessionDirOverride = agentSessionDirEnvOverride(daemonEnvironment);
|
|
63
|
+
if (agentSessionDirOverride !== undefined) {
|
|
64
|
+
process.env[PI_CODING_AGENT_SESSION_DIR_ENV] = agentSessionDirOverride;
|
|
65
|
+
}
|
|
66
|
+
// Deliberately agent-visible nesting marker: every process spawned from a
|
|
67
|
+
// session can tell it runs inside this pi-web instance. The session
|
|
68
|
+
// environment facts below explain the precautions that follow from it.
|
|
69
|
+
process.env[PI_WEB_SESSION_ENV] = "1";
|
|
70
|
+
const app = Fastify({ logger: true, bodyLimit: maxUploadBytes(daemonEnvironment, config) });
|
|
71
|
+
if (deprecatedAgentInputs.length > 0) {
|
|
72
|
+
app.log.warn({ deprecatedAgentInputs }, "deprecated agent configuration inputs detected; support will be removed in a future release");
|
|
73
|
+
}
|
|
74
|
+
if (serverPluginRecovery.safeStartDiagnostic !== undefined) {
|
|
75
|
+
app.log.error({ component: "server-plugins", configPath: serverPluginRecovery.path }, serverPluginRecovery.safeStartDiagnostic);
|
|
76
|
+
}
|
|
77
|
+
await app.register(fastifyWebsocket);
|
|
78
|
+
let serverQuiescing = false;
|
|
79
|
+
app.addHook("onRequest", (_request, reply, done) => {
|
|
80
|
+
if (!serverQuiescing) {
|
|
81
|
+
done();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
void reply.code(503).send({ error: "Session daemon is shutting down" });
|
|
85
|
+
});
|
|
86
|
+
const serverPluginCatalog = new PiWebPluginCatalog({
|
|
87
|
+
cwd: process.cwd(),
|
|
88
|
+
agentDir: activeAgentProfile.dir,
|
|
89
|
+
configProvider: () => config,
|
|
90
|
+
warningSink: (message) => { app.log.warn({ component: "server-plugins" }, message); },
|
|
91
|
+
});
|
|
92
|
+
let runtimeShutdown;
|
|
93
|
+
let pendingShutdownSignal;
|
|
94
|
+
let shutdownStarted = false;
|
|
95
|
+
async function requestShutdown(signal) {
|
|
96
|
+
if (shutdownStarted)
|
|
97
|
+
return;
|
|
98
|
+
if (runtimeShutdown === undefined) {
|
|
99
|
+
pendingShutdownSignal = signal;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
shutdownStarted = true;
|
|
103
|
+
app.log.info({ signal }, "shutting down session daemon");
|
|
104
|
+
await runtimeShutdown();
|
|
105
|
+
}
|
|
106
|
+
process.once("SIGINT", (signal) => { void requestShutdown(signal); });
|
|
107
|
+
process.once("SIGTERM", (signal) => { void requestShutdown(signal); });
|
|
108
|
+
// Agent-executed processes (bash tool, terminals, subsessions) are spawned from
|
|
109
|
+
// this process and inherit its environment. The scrub removes only the keys
|
|
110
|
+
// that silently distort the tools agents run; the daemon's PI_WEB_* wiring
|
|
111
|
+
// stays visible on purpose, because a nested instance resolving the same state
|
|
112
|
+
// now fails loudly through the ownership claim below instead of corrupting it
|
|
113
|
+
// silently. The daemon keeps using the captured daemonEnvironment above; its
|
|
114
|
+
// runtime stores resolve their paths from it explicitly below.
|
|
115
|
+
const scrubbedEnvKeys = scrubNonAgentVisibleEnvKeys(process.env);
|
|
116
|
+
app.log.info({ scrubbedEnvKeys }, "daemon-only environment keys hidden from agent processes");
|
|
117
|
+
// Claim ownership of the instance's durable state before any runtime store can
|
|
118
|
+
// touch it: a second daemon resolving the same data directory finds a live
|
|
119
|
+
// owner here and fails with an actionable error. Service and tsx-watch
|
|
120
|
+
// restarts briefly overlap the outgoing daemon, so the claim waits out a short
|
|
121
|
+
// grace for it to release first. The web/API process of this instance shares
|
|
122
|
+
// the data directory but never claims it.
|
|
123
|
+
const stateOwnership = await claimSessiondStateOwnership({ env: daemonEnvironment, logger: app.log });
|
|
124
|
+
const runtime = await createSessionDaemonRuntime();
|
|
125
|
+
try {
|
|
126
|
+
registerSessionDaemonRoutes(runtime);
|
|
127
|
+
await listenSessionDaemon(runtime);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
try {
|
|
131
|
+
await runtime.shutdown();
|
|
132
|
+
}
|
|
133
|
+
catch (disposeError) {
|
|
134
|
+
app.log.error({ err: disposeError }, "session daemon startup failed and runtime disposal was incomplete");
|
|
135
|
+
}
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
async function createSessionDaemonRuntime() {
|
|
139
|
+
// Apply the active agent profile's httpIdleTimeoutMs before any other
|
|
140
|
+
// startup work so even catalog-refresh fetches run under the configured
|
|
141
|
+
// HTTP idle timeouts (issue #113).
|
|
142
|
+
const appliedHttpIdleTimeout = applyAgentHttpIdleTimeout({ agentDir: activeAgentProfile.dir, cwd: process.cwd() });
|
|
143
|
+
if (appliedHttpIdleTimeout.warning !== undefined) {
|
|
144
|
+
app.log.warn({ httpIdleTimeoutMs: appliedHttpIdleTimeout.timeoutMs }, appliedHttpIdleTimeout.warning);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
app.log.info({ httpIdleTimeoutMs: appliedHttpIdleTimeout.timeoutMs }, "applied agent profile HTTP idle timeout to the session daemon HTTP stack");
|
|
148
|
+
}
|
|
149
|
+
const serverPlugins = await createServerPluginRuntime({
|
|
150
|
+
catalog: serverPluginCatalog,
|
|
151
|
+
...(serverPluginRecovery.safeStart === undefined ? {} : { safeStart: serverPluginRecovery.safeStart }),
|
|
152
|
+
logger: app.log,
|
|
153
|
+
execFile: createServerPluginExecFile({ env: daemonEnvironment }),
|
|
154
|
+
});
|
|
155
|
+
try {
|
|
156
|
+
const eventHub = new SessionEventHub();
|
|
157
|
+
const notificationStore = new SessionNotificationStore();
|
|
158
|
+
const unreadStore = new SessionUnreadStore({
|
|
159
|
+
persistence: new FileSessionUnreadPersistence(defaultSessionUnreadFilePath(daemonEnvironment)),
|
|
160
|
+
onPersistenceError(operation, error) {
|
|
161
|
+
app.log.error({ err: error, operation }, "session unread persistence failed");
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
await unreadStore.load();
|
|
165
|
+
// Activity and status are mutually dependent by design: the record notifies
|
|
166
|
+
// the projection, the projection reads the record. The notification runs
|
|
167
|
+
// long after both are constructed.
|
|
168
|
+
const workspaceActivity = new WorkspaceActivityService(() => { machineStatus.notifyChanged(); });
|
|
169
|
+
const auth = await AuthService.create({ agentDir: activeAgentProfile.dir, logger: app.log });
|
|
170
|
+
// Capture providers registered by global extensions while the runtime is
|
|
171
|
+
// still mutable, then freeze every later extension-provider mutation before
|
|
172
|
+
// any real session can load project resources.
|
|
173
|
+
await bootstrapAndFreezeGlobalExtensionProviders(auth.runtime, activeAgentProfile.dir, app.log);
|
|
174
|
+
// The shared model runtime is constructed offline so request paths never
|
|
175
|
+
// wait on provider-catalog fetches; this is the single bounded network
|
|
176
|
+
// refresher, and auth changes (login/logout) ask it for a prompt run. It
|
|
177
|
+
// stays fully inert when the operator asked for offline behavior.
|
|
178
|
+
const catalogRefresher = new ModelCatalogRefresher({
|
|
179
|
+
runtime: auth.runtime,
|
|
180
|
+
logger: app.log,
|
|
181
|
+
offline: offlineModeEnabled(daemonEnvironment),
|
|
182
|
+
});
|
|
183
|
+
catalogRefresher.start();
|
|
184
|
+
auth.subscribe(() => { catalogRefresher.requestRefresh(); });
|
|
185
|
+
const projects = new ProjectService(new ProjectStore(projectStorePath(daemonEnvironment)));
|
|
186
|
+
const providerHealth = await serverPlugins.inspectHealth();
|
|
187
|
+
const workspaceProviders = new WorkspaceProviderRegistry({
|
|
188
|
+
contributions: eligibleWorkspaceProviderContributions(serverPlugins.providerContributions(), providerHealth),
|
|
189
|
+
logger: app.log,
|
|
190
|
+
});
|
|
191
|
+
const workspaceProviderRuntime = createWorkspaceProviderRuntimeSnapshot(serverPlugins.healthRecords(), providerHealth, serverPlugins.safeStartLevel(), serverPlugins.catalogDiagnostics());
|
|
192
|
+
const statusAttribution = new CachedWorkspaceAttribution({
|
|
193
|
+
projects,
|
|
194
|
+
workspaces: workspaceProviders,
|
|
195
|
+
logger: app.log,
|
|
196
|
+
});
|
|
197
|
+
const machineStatus = new MachineStatusService({
|
|
198
|
+
activity: workspaceActivity,
|
|
199
|
+
unread: unreadStore,
|
|
200
|
+
attribution: statusAttribution,
|
|
201
|
+
publisher: { publish: (snapshot) => { eventHub.publishRealtime({ type: "machine.status", status: snapshot }); } },
|
|
202
|
+
logger: app.log,
|
|
203
|
+
});
|
|
204
|
+
// Every global subscriber is handed the current projection on connect, so a
|
|
205
|
+
// browser never has to reconcile a snapshot fetch against live frames.
|
|
206
|
+
eventHub.setGlobalJoinFrame(() => ({ type: "machine.status", status: machineStatus.snapshot() }));
|
|
207
|
+
// Unread state was loaded from disk above, so the projection is computed
|
|
208
|
+
// once at startup instead of waiting for the first change. It is not
|
|
209
|
+
// awaited: resolving it lists workspaces through provider plugins, and
|
|
210
|
+
// daemon startup must not depend on how long that takes.
|
|
211
|
+
machineStatus.notifyChanged();
|
|
212
|
+
const projectWorkspaceDeps = { projects, workspaces: workspaceProviders };
|
|
213
|
+
const spawnTargets = config.spawnSessions ? new ProjectScopedSpawnTargetResolver(projectWorkspaceDeps) : undefined;
|
|
214
|
+
const sessions = new PiSessionService(eventHub, sessionServiceDependencies({
|
|
215
|
+
modelRuntime: auth.runtime,
|
|
216
|
+
agentDir: activeAgentProfile.dir,
|
|
217
|
+
archiveStore: new SessionArchiveStore(defaultSessionArchiveFilePath(daemonEnvironment)),
|
|
218
|
+
workspaceActivity,
|
|
219
|
+
logger: app.log,
|
|
220
|
+
...(spawnTargets === undefined ? {} : { spawnTargets }),
|
|
221
|
+
subsessionsEnabled: config.subsessions,
|
|
222
|
+
askUserEnabled: config.askUser,
|
|
223
|
+
appendSystemPromptSections: [
|
|
224
|
+
// Sessions always run nested in this daemon, so they always get the
|
|
225
|
+
// session environment facts; Docker deployments add their container
|
|
226
|
+
// facts on top. Resolved once here, from the captured daemon
|
|
227
|
+
// environment, because the deployment cannot change while the daemon
|
|
228
|
+
// runs.
|
|
229
|
+
...sessionEnvironmentPromptSections({
|
|
230
|
+
env: daemonEnvironment,
|
|
231
|
+
enabled: config.environmentFacts,
|
|
232
|
+
}),
|
|
233
|
+
...dockerEnvironmentPromptSections({
|
|
234
|
+
env: daemonEnvironment,
|
|
235
|
+
enabled: config.environmentFacts,
|
|
236
|
+
logger: app.log,
|
|
237
|
+
}),
|
|
238
|
+
],
|
|
239
|
+
extensionDialogsTimeoutMs: config.extensionDialogsTimeoutMs,
|
|
240
|
+
notificationStore,
|
|
241
|
+
unreadStore,
|
|
242
|
+
onUnreadChanged: () => { machineStatus.notifyChanged(); },
|
|
243
|
+
catalogRefreshStatus: catalogRefresher,
|
|
244
|
+
sessionManager: createPiSessionManagerGateway({
|
|
245
|
+
agentDir: activeAgentProfile.dir,
|
|
246
|
+
env: daemonEnvironment,
|
|
247
|
+
}),
|
|
248
|
+
}));
|
|
249
|
+
auth.subscribe((change) => { sessions.applyAuthChange(change); });
|
|
250
|
+
const terminals = new TerminalService(eventHub, workspaceActivity);
|
|
251
|
+
const workspaceRemovals = new WorkspaceRemovalService(workspaceProviders, terminals);
|
|
252
|
+
const runtimeComponent = Object.freeze({
|
|
253
|
+
// The deprecated-input report is fixed at startup: it was detected from
|
|
254
|
+
// the captured pre-scrub daemon environment and the config snapshot this
|
|
255
|
+
// daemon actually honors, so it stays accurate for this daemon lifetime
|
|
256
|
+
// and clears only when the inputs are removed and the daemon restarts.
|
|
257
|
+
...getPiWebRuntimeComponent("sessiond", SESSIOND_RUNTIME_CAPABILITIES, deprecatedAgentInputs),
|
|
258
|
+
activeAgentProfile,
|
|
259
|
+
});
|
|
260
|
+
let disposed = false;
|
|
261
|
+
const shutdown = async () => {
|
|
262
|
+
if (disposed)
|
|
263
|
+
return;
|
|
264
|
+
disposed = true;
|
|
265
|
+
await runSessionDaemonShutdown({
|
|
266
|
+
logger: app.log,
|
|
267
|
+
dependencies: {
|
|
268
|
+
quiesceServer: () => { serverQuiescing = true; },
|
|
269
|
+
// Give agent turns already in flight a bounded chance to finish, so
|
|
270
|
+
// updating or restarting does not cut work off mid-run. Disable with
|
|
271
|
+
// PI_WEB_SHUTDOWN_DRAIN_MS=0.
|
|
272
|
+
drainActiveWork: async () => {
|
|
273
|
+
const timeoutMs = resolveDrainTimeoutMs(daemonEnvironment);
|
|
274
|
+
const startedAt = Date.now();
|
|
275
|
+
const decision = await drainActiveWork(timeoutMs, {
|
|
276
|
+
activeSessionIds: () => sessions.activeWorkSessionIds(),
|
|
277
|
+
elapsedMs: () => Date.now() - startedAt,
|
|
278
|
+
wait: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
279
|
+
});
|
|
280
|
+
if (decision.reason === "deadline-reached") {
|
|
281
|
+
// Named, not swallowed: these are the runs the restart cut off.
|
|
282
|
+
app.log.warn({ activeSessionIds: decision.activeSessionIds, timeoutMs }, "shutdown drain deadline reached with work still running");
|
|
283
|
+
// Recorded so the next start can say which conversations were cut
|
|
284
|
+
// off. The drain deadline is deliberately shorter than systemd's
|
|
285
|
+
// TimeoutStopSec, so there is still time to write this before the
|
|
286
|
+
// process is killed outright.
|
|
287
|
+
const interrupted = new Set(decision.activeSessionIds);
|
|
288
|
+
await recordInterruptedRuns(sessions
|
|
289
|
+
.activeWorkSessions()
|
|
290
|
+
.filter(({ sessionId }) => interrupted.has(sessionId))
|
|
291
|
+
.map(({ sessionId, cwd }) => ({ sessionId, cwd, interruptedAt: new Date().toISOString() })));
|
|
292
|
+
}
|
|
293
|
+
else if (decision.reason === "waiting-for-active-work" || decision.reason === "no-active-work") {
|
|
294
|
+
app.log.info({ waitedMs: Date.now() - startedAt }, "shutdown drain finished");
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
serverPlugins,
|
|
298
|
+
terminals,
|
|
299
|
+
catalogRefresher,
|
|
300
|
+
auth,
|
|
301
|
+
sessions,
|
|
302
|
+
unreadStore,
|
|
303
|
+
closeServer: () => app.close(),
|
|
304
|
+
},
|
|
305
|
+
onFailure: () => { process.exitCode = 1; },
|
|
306
|
+
});
|
|
307
|
+
// Release last: the daemon owns the instance state until everything that
|
|
308
|
+
// writes it is down. Best-effort; a leftover marker goes stale and the
|
|
309
|
+
// next start discards it.
|
|
310
|
+
await stateOwnership.release();
|
|
311
|
+
};
|
|
312
|
+
return { eventHub, machineStatus, statusAttribution, auth, sessions, terminals, unreadStore, activeAgentProfile, runtimeComponent, catalogRefresher, serverPlugins, projects, workspaceProviders, workspaceProviderRuntime, workspaceRemovals, shutdown };
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
try {
|
|
316
|
+
await serverPlugins.stop();
|
|
317
|
+
}
|
|
318
|
+
catch (disposeError) {
|
|
319
|
+
app.log.error({ err: disposeError }, "session daemon construction failed and server plugin disposal was incomplete");
|
|
320
|
+
}
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function registerSessionDaemonRoutes({ eventHub, machineStatus, statusAttribution, auth, sessions, terminals, runtimeComponent, projects, workspaceProviders, workspaceProviderRuntime, workspaceRemovals }) {
|
|
325
|
+
registerMachineStatusRoutes(app, machineStatus);
|
|
326
|
+
registerAuthRoutes(app, auth);
|
|
327
|
+
registerSessionRoutes(app, sessions, eventHub);
|
|
328
|
+
registerTerminalRoutes(app, terminals);
|
|
329
|
+
registerWorkspaceCatalogRoutes(app, {
|
|
330
|
+
projects,
|
|
331
|
+
workspaces: workspaceProviders,
|
|
332
|
+
providerRuntime: workspaceProviderRuntime,
|
|
333
|
+
});
|
|
334
|
+
registerPluginBackendRoutes(app, {
|
|
335
|
+
projects,
|
|
336
|
+
backends: workspaceProviders,
|
|
337
|
+
onWorkspacesMutated: () => { statusAttribution.invalidate(); },
|
|
338
|
+
});
|
|
339
|
+
registerWorkspaceRemovalRoutes(app, {
|
|
340
|
+
projects,
|
|
341
|
+
removals: workspaceRemovals,
|
|
342
|
+
onWorkspacesMutated: () => { statusAttribution.invalidate(); },
|
|
343
|
+
});
|
|
344
|
+
app.get("/health", () => ({
|
|
345
|
+
ok: true,
|
|
346
|
+
activeSessions: sessions.activeCount(),
|
|
347
|
+
checkedAt: new Date().toISOString(),
|
|
348
|
+
version: {
|
|
349
|
+
component: runtimeComponent.component,
|
|
350
|
+
label: runtimeComponent.label,
|
|
351
|
+
...(runtimeComponent.runtimeVersion === undefined ? {} : { runtimeVersion: runtimeComponent.runtimeVersion }),
|
|
352
|
+
...(runtimeComponent.piVersion === undefined ? {} : { piVersion: runtimeComponent.piVersion }),
|
|
353
|
+
stale: false,
|
|
354
|
+
available: runtimeComponent.available,
|
|
355
|
+
},
|
|
356
|
+
}));
|
|
357
|
+
app.get("/runtime", () => runtimeComponent);
|
|
358
|
+
}
|
|
359
|
+
async function listenSessionDaemon({ shutdown }) {
|
|
360
|
+
runtimeShutdown = shutdown;
|
|
361
|
+
if (pendingShutdownSignal !== undefined) {
|
|
362
|
+
await requestShutdown(pendingShutdownSignal);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
// Anything still marked in flight belongs to the previous process, which is
|
|
366
|
+
// gone -- so it did not finish. Promoted into the record the API serves, and
|
|
367
|
+
// cleared, so a restart cannot report the same interruption forever.
|
|
368
|
+
const leftOverRuns = await takeInterruptedRuns();
|
|
369
|
+
if (leftOverRuns.length > 0) {
|
|
370
|
+
app.log.warn({ sessionIds: leftOverRuns.map((run) => run.sessionId) }, "runs were in flight when the previous session daemon exited");
|
|
371
|
+
await recordInterruptedRuns(leftOverRuns);
|
|
372
|
+
}
|
|
373
|
+
const portValue = daemonEnvironment["PI_WEB_SESSIOND_PORT"];
|
|
374
|
+
const port = portValue !== undefined && portValue !== "" ? Number(portValue) : undefined;
|
|
375
|
+
const host = daemonEnvironment["PI_WEB_SESSIOND_HOST"] ?? "127.0.0.1";
|
|
376
|
+
if (port !== undefined) {
|
|
377
|
+
await app.listen({ port, host });
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
const path = sessiondSocketPath(daemonEnvironment);
|
|
381
|
+
await mkdir(dirname(path), { recursive: true });
|
|
382
|
+
await rm(path, { force: true });
|
|
383
|
+
await app.listen({ path });
|
|
384
|
+
process.on("exit", () => void rm(path, { force: true }));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
//# sourceMappingURL=sessiond.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessiond.js","sourceRoot":"","sources":["../../src/server/sessiond.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { ASK_USER_ID_MAX_LENGTH, ASK_USER_OPTION_LIMIT, ASK_USER_QUESTION_LIMIT, ASK_USER_TEXT_MAX_LENGTH, } from "../../shared/apiTypes.js";
|
|
4
|
+
import { renderSupersededAskText } from "./pendingAskStore.js";
|
|
5
|
+
const AskUserOptionParams = Type.Object({
|
|
6
|
+
value: Type.String({
|
|
7
|
+
maxLength: ASK_USER_ID_MAX_LENGTH,
|
|
8
|
+
description: "Stable machine value reported back to you when the user picks this option.",
|
|
9
|
+
}),
|
|
10
|
+
label: Type.String({
|
|
11
|
+
maxLength: ASK_USER_TEXT_MAX_LENGTH,
|
|
12
|
+
description: "Short label the user reads, ideally a few words.",
|
|
13
|
+
}),
|
|
14
|
+
detail: Type.Optional(Type.String({
|
|
15
|
+
maxLength: ASK_USER_TEXT_MAX_LENGTH,
|
|
16
|
+
description: "Optional clarification shown under the label.",
|
|
17
|
+
})),
|
|
18
|
+
});
|
|
19
|
+
const AskUserQuestionParams = Type.Object({
|
|
20
|
+
id: Type.String({
|
|
21
|
+
maxLength: ASK_USER_ID_MAX_LENGTH,
|
|
22
|
+
description: "Unique within this call; used as the answer key reported back to you.",
|
|
23
|
+
}),
|
|
24
|
+
question: Type.String({
|
|
25
|
+
maxLength: ASK_USER_TEXT_MAX_LENGTH,
|
|
26
|
+
description: "The question itself, as one plain-text line.",
|
|
27
|
+
}),
|
|
28
|
+
detail: Type.Optional(Type.String({
|
|
29
|
+
maxLength: ASK_USER_TEXT_MAX_LENGTH,
|
|
30
|
+
description: "Optional supporting context shown under the question.",
|
|
31
|
+
})),
|
|
32
|
+
options: Type.Optional(Type.Array(AskUserOptionParams, {
|
|
33
|
+
maxItems: ASK_USER_OPTION_LIMIT,
|
|
34
|
+
description: "Options to choose from. Omit when free text is the whole answer; the browser always adds a Custom choice.",
|
|
35
|
+
})),
|
|
36
|
+
multiple: Type.Optional(Type.Boolean({
|
|
37
|
+
description: "Allow several options at once. Default: one answer per question.",
|
|
38
|
+
})),
|
|
39
|
+
});
|
|
40
|
+
const AskUserParams = Type.Object({
|
|
41
|
+
questions: Type.Array(AskUserQuestionParams, {
|
|
42
|
+
minItems: 1,
|
|
43
|
+
maxItems: ASK_USER_QUESTION_LIMIT,
|
|
44
|
+
description: "The questions to post, in the order the user should read them. Every question may be left unanswered.",
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
/** Shapes one schema question into the domain question; the store owns validation. */
|
|
48
|
+
function toQuestion(param) {
|
|
49
|
+
const { detail, options, multiple } = param;
|
|
50
|
+
return {
|
|
51
|
+
id: param.id,
|
|
52
|
+
question: param.question,
|
|
53
|
+
...(detail === undefined ? {} : { detail }),
|
|
54
|
+
options: (options ?? []).map(toOption),
|
|
55
|
+
...(multiple === undefined ? {} : { multiple }),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function toOption(param) {
|
|
59
|
+
const { detail } = param;
|
|
60
|
+
return { value: param.value, label: param.label, ...(detail === undefined ? {} : { detail }) };
|
|
61
|
+
}
|
|
62
|
+
function postedText(result) {
|
|
63
|
+
const count = result.ask.questions.length;
|
|
64
|
+
const posted = `Posted ${count.toString()} question${count === 1 ? "" : "s"} to the user as ask ${result.ask.askId}. Ending this run; the answers arrive as a follow-up message that wakes you, naming every question the user left unanswered. Do not repost these questions.`;
|
|
65
|
+
return result.superseded === undefined ? posted : `${posted}\n\n${renderSupersededAskText(result.superseded)}`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Custom tool that posts a question set to the user's browser as interactive UI.
|
|
69
|
+
*
|
|
70
|
+
* It deliberately does **not** await the user. Awaiting would pin the agent run
|
|
71
|
+
* for an unbounded human-scale wait, keep the session streaming, and leave a
|
|
72
|
+
* dangling tool call if the runtime were replaced meanwhile. Instead the ask
|
|
73
|
+
* becomes daemon-owned state, the tool terminates the run, and the submitted
|
|
74
|
+
* answers return later as a follow-up message that wakes the session.
|
|
75
|
+
*
|
|
76
|
+
* Rejected question sets throw: the agent loop turns the thrown message into an
|
|
77
|
+
* error tool result, so the model can fix the ask and post it again.
|
|
78
|
+
*/
|
|
79
|
+
export function createAskUserToolDefinition(deps) {
|
|
80
|
+
return defineTool({
|
|
81
|
+
name: "ask_user",
|
|
82
|
+
label: "Ask user",
|
|
83
|
+
description: "Post a set of questions to the user as a browser form and end this run. Answers arrive later as a follow-up message; the user may leave any question unanswered.",
|
|
84
|
+
promptSnippet: "ask_user: post a question set to the user; ends the run, answers return as a follow-up",
|
|
85
|
+
promptGuidelines: [
|
|
86
|
+
"When you need decisions from the user, post them together with ask_user instead of asking in prose one at a time. It ends the run and the answers, including the questions the user left unanswered, come back as a follow-up message that wakes you. Call it alone and last, and do not repost the same questions or poll for answers.",
|
|
87
|
+
],
|
|
88
|
+
parameters: AskUserParams,
|
|
89
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
90
|
+
const result = await deps.open({
|
|
91
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
92
|
+
questions: params.questions.map(toQuestion),
|
|
93
|
+
});
|
|
94
|
+
return {
|
|
95
|
+
content: [{ type: "text", text: postedText(result) }],
|
|
96
|
+
details: result,
|
|
97
|
+
terminate: true,
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=askUserTool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"askUserTool.js","sourceRoot":"","sources":["../../../src/server/sessions/askUserTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAe,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,uBAAuB,EACvB,wBAAwB,GAGzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,uBAAuB,EAA6B,MAAM,sBAAsB,CAAC;AAe1F,MAAM,mBAAmB,GAAG,IAAI,CAAC,MAAM,CAAC;IACtC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC;QACjB,SAAS,EAAE,sBAAsB;QACjC,WAAW,EAAE,4EAA4E;KAC1F,CAAC;IACF,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC;QACjB,SAAS,EAAE,wBAAwB;QACnC,WAAW,EAAE,kDAAkD;KAChE,CAAC;IACF,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;QAChC,SAAS,EAAE,wBAAwB;QACnC,WAAW,EAAE,+CAA+C;KAC7D,CAAC,CAAC;CACJ,CAAC,CAAC;AAEH,MAAM,qBAAqB,GAAG,IAAI,CAAC,MAAM,CAAC;IACxC,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC;QACd,SAAS,EAAE,sBAAsB;QACjC,WAAW,EAAE,uEAAuE;KACrF,CAAC;IACF,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC;QACpB,SAAS,EAAE,wBAAwB;QACnC,WAAW,EAAE,8CAA8C;KAC5D,CAAC;IACF,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;QAChC,SAAS,EAAE,wBAAwB;QACnC,WAAW,EAAE,uDAAuD;KACrE,CAAC,CAAC;IACH,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,mBAAmB,EAAE;QACrD,QAAQ,EAAE,qBAAqB;QAC/B,WAAW,EAAE,2GAA2G;KACzH,CAAC,CAAC;IACH,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC;QACnC,WAAW,EAAE,kEAAkE;KAChF,CAAC,CAAC;CACJ,CAAC,CAAC;AAEH,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,CAAC;IAChC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,qBAAqB,EAAE;QAC3C,QAAQ,EAAE,CAAC;QACX,QAAQ,EAAE,uBAAuB;QACjC,WAAW,EAAE,uGAAuG;KACrH,CAAC;CACH,CAAC,CAAC;AAEH,sFAAsF;AACtF,SAAS,UAAU,CAAC,KAA2C;IAC7D,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAC5C,OAAO;QACL,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;QAC3C,OAAO,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;QACtC,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;KAChD,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,KAAyC;IACzD,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IACzB,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;AACjG,CAAC;AAED,SAAS,UAAU,CAAC,MAA4B;IAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC;IAC1C,MAAM,MAAM,GAAG,UAAU,KAAK,CAAC,QAAQ,EAAE,YAAY,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,uBAAuB,MAAM,CAAC,GAAG,CAAC,KAAK,6JAA6J,CAAC;IAChR,OAAO,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,OAAO,uBAAuB,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;AACjH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,2BAA2B,CAAC,IAAqB;IAC/D,OAAO,UAAU,CAA2C;QAC1D,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,kKAAkK;QAC/K,aAAa,EAAE,wFAAwF;QACvG,gBAAgB,EAAE;YAChB,yUAAyU;SAC1U;QACD,UAAU,EAAE,aAAa;QACzB,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG;YACxD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC;gBAC7B,SAAS,EAAE,GAAG,CAAC,cAAc,CAAC,YAAY,EAAE;gBAC5C,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC;aAC5C,CAAC,CAAC;YACH,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;gBACrD,OAAO,EAAE,MAAM;gBACf,SAAS,EAAE,IAAI;aAChB,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { mkdir, realpath, writeFile } from "node:fs/promises";
|
|
2
|
+
import { basename, extname, join } from "node:path";
|
|
3
|
+
import { formatDimensionNote, resizeImage } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { extensionForImageMimeType } from "../../shared/promptAttachments.js";
|
|
5
|
+
import { ensureInside, isNodeErrorWithCode, resolveParentInsideWorkspace } from "../workspaces/pathSafety.js";
|
|
6
|
+
/**
|
|
7
|
+
* Default workspace-relative folder used when saving pasted/dropped
|
|
8
|
+
* attachments for the agent to read with its own tools.
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_ATTACHMENT_FOLDER = ".pi-web/attachments";
|
|
11
|
+
/**
|
|
12
|
+
* Convert validated attachments into pi-compatible inline image content.
|
|
13
|
+
*
|
|
14
|
+
* Mirrors pi's own CLI/TUI behaviour: each image is run through pi's
|
|
15
|
+
* `resizeImage` so it fits within pi's max dimensions and inline byte budget
|
|
16
|
+
* (2000x2000, ~4.5MB base64). Images that cannot be resized below the limit
|
|
17
|
+
* are dropped, matching pi's `[Image omitted]` behaviour.
|
|
18
|
+
*/
|
|
19
|
+
export async function attachmentsToInlineImages(attachments) {
|
|
20
|
+
const results = [];
|
|
21
|
+
for (const attachment of attachments) {
|
|
22
|
+
const bytes = Buffer.from(attachment.data, "base64");
|
|
23
|
+
const resized = await resizeImage(bytes, attachment.mimeType);
|
|
24
|
+
if (resized === null)
|
|
25
|
+
continue;
|
|
26
|
+
const note = formatDimensionNote(resized);
|
|
27
|
+
results.push({
|
|
28
|
+
image: { type: "image", data: resized.data, mimeType: resized.mimeType },
|
|
29
|
+
...(note === undefined ? {} : { dimensionNote: note }),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return results;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Write attachments into a workspace folder and return their relative paths.
|
|
36
|
+
* Filenames are collision-safe and stay inside the workspace root.
|
|
37
|
+
*/
|
|
38
|
+
export async function saveAttachmentsToWorkspace(cwd, attachments, options = {}) {
|
|
39
|
+
const folder = options.folder ?? DEFAULT_ATTACHMENT_FOLDER;
|
|
40
|
+
const now = options.now ?? (() => new Date());
|
|
41
|
+
const { root, target: requestedFolderTarget, relativePath: normalizedFolder } = await resolveParentInsideWorkspace(cwd, folder);
|
|
42
|
+
await mkdir(requestedFolderTarget, { recursive: true });
|
|
43
|
+
const folderTarget = await realpath(requestedFolderTarget);
|
|
44
|
+
ensureInside(root, folderTarget);
|
|
45
|
+
const stamp = timestamp(now());
|
|
46
|
+
const saved = [];
|
|
47
|
+
for (const [index, attachment] of attachments.entries()) {
|
|
48
|
+
const bytes = Buffer.from(attachment.data, "base64");
|
|
49
|
+
const filename = await writeUniqueAttachmentFile(folderTarget, attachmentFilename(attachment, stamp, index), bytes);
|
|
50
|
+
const relativePath = normalizedFolder === "" ? filename : `${normalizedFolder}/${filename}`;
|
|
51
|
+
saved.push({ path: relativePath, mimeType: attachment.mimeType, size: bytes.byteLength });
|
|
52
|
+
}
|
|
53
|
+
return saved;
|
|
54
|
+
}
|
|
55
|
+
async function writeUniqueAttachmentFile(folderTarget, filename, bytes) {
|
|
56
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
57
|
+
const candidate = attempt === 0 ? filename : addCollisionSuffix(filename, attempt + 1);
|
|
58
|
+
try {
|
|
59
|
+
await writeFile(join(folderTarget, candidate), bytes, { flag: "wx" });
|
|
60
|
+
return candidate;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (!isNodeErrorWithCode(error, "EEXIST"))
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
throw new Error("Unable to choose a unique attachment filename");
|
|
68
|
+
}
|
|
69
|
+
function addCollisionSuffix(filename, suffix) {
|
|
70
|
+
const extension = extname(filename);
|
|
71
|
+
const stem = filename.slice(0, filename.length - extension.length);
|
|
72
|
+
return `${stem}-${String(suffix)}${extension}`;
|
|
73
|
+
}
|
|
74
|
+
function attachmentFilename(attachment, stamp, index) {
|
|
75
|
+
const originalName = sanitizeOriginalFilename(attachment.name) ?? fallbackAttachmentFilename(attachment);
|
|
76
|
+
return `attachment-${stamp}-${String(index + 1)}-${originalName}`;
|
|
77
|
+
}
|
|
78
|
+
function fallbackAttachmentFilename(attachment) {
|
|
79
|
+
if (attachment.kind === "image")
|
|
80
|
+
return `image.${extensionForImageMimeType(attachment.mimeType)}`;
|
|
81
|
+
return "file.bin";
|
|
82
|
+
}
|
|
83
|
+
const MAX_ORIGINAL_FILENAME_LENGTH = 96;
|
|
84
|
+
function sanitizeOriginalFilename(name) {
|
|
85
|
+
const trimmed = name?.trim();
|
|
86
|
+
if (trimmed === undefined || trimmed === "")
|
|
87
|
+
return undefined;
|
|
88
|
+
const leaf = basename(trimmed.replace(/\\/g, "/"));
|
|
89
|
+
const sanitized = stripControlCharacters(leaf)
|
|
90
|
+
.normalize("NFKC")
|
|
91
|
+
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
92
|
+
.replace(/-+/g, "-")
|
|
93
|
+
.replace(/-+\./g, ".")
|
|
94
|
+
.replace(/^\.+/, "")
|
|
95
|
+
.replace(/[.-]+$/, "");
|
|
96
|
+
if (sanitized === "")
|
|
97
|
+
return undefined;
|
|
98
|
+
return truncateFilename(sanitized, MAX_ORIGINAL_FILENAME_LENGTH);
|
|
99
|
+
}
|
|
100
|
+
function stripControlCharacters(value) {
|
|
101
|
+
return Array.from(value).filter((character) => {
|
|
102
|
+
const codePoint = character.codePointAt(0);
|
|
103
|
+
return codePoint !== undefined && codePoint > 0x1f && codePoint !== 0x7f;
|
|
104
|
+
}).join("");
|
|
105
|
+
}
|
|
106
|
+
function truncateFilename(filename, maxLength) {
|
|
107
|
+
if (filename.length <= maxLength)
|
|
108
|
+
return filename;
|
|
109
|
+
const extension = extname(filename);
|
|
110
|
+
if (extension.length >= maxLength)
|
|
111
|
+
return filename.slice(0, maxLength);
|
|
112
|
+
const stem = filename.slice(0, filename.length - extension.length);
|
|
113
|
+
return `${stem.slice(0, maxLength - extension.length)}${extension}`;
|
|
114
|
+
}
|
|
115
|
+
function timestamp(date) {
|
|
116
|
+
const pad = (value, length = 2) => String(value).padStart(length, "0");
|
|
117
|
+
return `${String(date.getFullYear())}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}-${pad(date.getMilliseconds(), 3)}`;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=attachmentService.js.map
|