@agimon-ai/po 0.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +755 -0
- package/assets/banner.png +0 -0
- package/assets/banner.svg +25 -0
- package/assets/favicon.svg +15 -0
- package/assets/logo-light.png +0 -0
- package/assets/logo-light.svg +25 -0
- package/assets/logo.png +0 -0
- package/assets/logo.svg +25 -0
- package/assets/mascot.png +0 -0
- package/assets/mascot.svg +25 -0
- package/dist/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/antigravity.cjs +5 -0
- package/dist/antigravity.d.mts +2 -0
- package/dist/antigravity.mjs +2 -0
- package/dist/apiContracts.cjs +11 -0
- package/dist/apiContracts.cjs.map +1 -0
- package/dist/apiContracts.d.mts +52 -0
- package/dist/apiContracts.d.mts.map +1 -0
- package/dist/apiContracts.mjs +7 -0
- package/dist/apiContracts.mjs.map +1 -0
- package/dist/baseCommand.cjs +3 -0
- package/dist/baseCommand.d.mts +2 -0
- package/dist/baseCommand.mjs +2 -0
- package/dist/bin/cli.cjs +24 -0
- package/dist/bin/cli.cjs.map +1 -0
- package/dist/bin/cli.d.mts +1 -0
- package/dist/bin/cli.mjs +25 -0
- package/dist/bin/cli.mjs.map +1 -0
- package/dist/bin/doomRunner.cjs +33 -0
- package/dist/bin/doomRunner.cjs.map +1 -0
- package/dist/bin/doomRunner.d.mts +4 -0
- package/dist/bin/doomRunner.d.mts.map +1 -0
- package/dist/bin/doomRunner.mjs +32 -0
- package/dist/bin/doomRunner.mjs.map +1 -0
- package/dist/bin/serve.cjs +30 -0
- package/dist/bin/serve.cjs.map +1 -0
- package/dist/bin/serve.d.mts +1 -0
- package/dist/bin/serve.mjs +31 -0
- package/dist/bin/serve.mjs.map +1 -0
- package/dist/builders/cli/extensionAssembler/index.cjs +342 -0
- package/dist/builders/cli/extensionAssembler/index.cjs.map +1 -0
- package/dist/builders/cli/extensionAssembler/index.d.mts +85 -0
- package/dist/builders/cli/extensionAssembler/index.d.mts.map +1 -0
- package/dist/builders/cli/extensionAssembler/index.mjs +335 -0
- package/dist/builders/cli/extensionAssembler/index.mjs.map +1 -0
- package/dist/catalog/index.json +430 -0
- package/dist/cli.cjs +28 -0
- package/dist/cli.d.mts +11 -0
- package/dist/cli.mjs +11 -0
- package/dist/cliApp.cjs +6 -0
- package/dist/cliApp.d.mts +2 -0
- package/dist/cliApp.mjs +2 -0
- package/dist/codex.cjs +8 -0
- package/dist/codex.d.mts +2 -0
- package/dist/codex.mjs +2 -0
- package/dist/commands.cjs +18 -0
- package/dist/commands.d.mts +9 -0
- package/dist/commands.mjs +9 -0
- package/dist/compatibility.cjs +27 -0
- package/dist/compatibility.d.mts +6 -0
- package/dist/compatibility.mjs +6 -0
- package/dist/compatibilityCommand.cjs +4 -0
- package/dist/compatibilityCommand.d.mts +2 -0
- package/dist/compatibilityCommand.mjs +2 -0
- package/dist/compatibilityContext.cjs +3 -0
- package/dist/compatibilityContext.d.mts +2 -0
- package/dist/compatibilityContext.mjs +2 -0
- package/dist/compatibilityOptions.cjs +5 -0
- package/dist/compatibilityOptions.d.mts +2 -0
- package/dist/compatibilityOptions.mjs +2 -0
- package/dist/config.cjs +163 -0
- package/dist/config.d.mts +7 -0
- package/dist/config.mjs +7 -0
- package/dist/doctorCommand.cjs +5 -0
- package/dist/doctorCommand.d.mts +2 -0
- package/dist/doctorCommand.mjs +2 -0
- package/dist/emitMcpCommand.cjs +4 -0
- package/dist/emitMcpCommand.d.mts +2 -0
- package/dist/emitMcpCommand.mjs +2 -0
- package/dist/explainCommand.cjs +5 -0
- package/dist/explainCommand.d.mts +2 -0
- package/dist/explainCommand.mjs +2 -0
- package/dist/extensions/server.cjs +39 -0
- package/dist/extensions/server.cjs.map +1 -0
- package/dist/extensions/server.d.mts +5 -0
- package/dist/extensions/server.d.mts.map +1 -0
- package/dist/extensions/server.mjs +34 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/harnessContext.cjs +5 -0
- package/dist/harnessContext.d.mts +2 -0
- package/dist/harnessContext.mjs +2 -0
- package/dist/harnessState.cjs +19 -0
- package/dist/harnessState.d.mts +2 -0
- package/dist/harnessState.mjs +2 -0
- package/dist/help.cjs +12 -0
- package/dist/help.d.mts +6 -0
- package/dist/help.mjs +6 -0
- package/dist/index.cjs +306 -0
- package/dist/index.d.mts +40 -0
- package/dist/index.mjs +37 -0
- package/dist/initCommand.cjs +4 -0
- package/dist/initCommand.d.mts +2 -0
- package/dist/initCommand.mjs +2 -0
- package/dist/matrixOptions.cjs +28 -0
- package/dist/matrixOptions.d.mts +2 -0
- package/dist/matrixOptions.mjs +2 -0
- package/dist/matrixSwitcher.cjs +11 -0
- package/dist/matrixSwitcher.d.mts +3 -0
- package/dist/matrixSwitcher.mjs +3 -0
- package/dist/mcpFilter.cjs +9 -0
- package/dist/mcpFilter.d.mts +1 -0
- package/dist/mcpFilter.mjs +2 -0
- package/dist/options.cjs +3 -0
- package/dist/options.d.mts +2 -0
- package/dist/options.mjs +2 -0
- package/dist/process.cjs +12 -0
- package/dist/process.d.mts +2 -0
- package/dist/process.mjs +2 -0
- package/dist/projectTrust.cjs +6 -0
- package/dist/projectTrust.d.mts +2 -0
- package/dist/projectTrust.mjs +2 -0
- package/dist/repository.cjs +5 -0
- package/dist/repository.d.mts +2 -0
- package/dist/repository.mjs +2 -0
- package/dist/resourceCollector.cjs +9 -0
- package/dist/resourceCollector.d.mts +1 -0
- package/dist/resourceCollector.mjs +2 -0
- package/dist/router.cjs +6 -0
- package/dist/router.d.mts +2 -0
- package/dist/router.mjs +2 -0
- package/dist/services.cjs +97 -0
- package/dist/services.d.mts +12 -0
- package/dist/services.mjs +12 -0
- package/dist/shared.cjs +18 -0
- package/dist/shared.d.mts +2 -0
- package/dist/shared.mjs +2 -0
- package/dist/src/bin/agentDirectory.cjs +27 -0
- package/dist/src/bin/agentDirectory.cjs.map +1 -0
- package/dist/src/bin/agentDirectory.mjs +26 -0
- package/dist/src/bin/agentDirectory.mjs.map +1 -0
- package/dist/src/builders/apiContractExport/index.cjs +126 -0
- package/dist/src/builders/apiContractExport/index.cjs.map +1 -0
- package/dist/src/builders/apiContractExport/index.mjs +123 -0
- package/dist/src/builders/apiContractExport/index.mjs.map +1 -0
- package/dist/src/builders/cli/entryResolution.cjs +39 -0
- package/dist/src/builders/cli/entryResolution.cjs.map +1 -0
- package/dist/src/builders/cli/entryResolution.mjs +35 -0
- package/dist/src/builders/cli/entryResolution.mjs.map +1 -0
- package/dist/src/builders/cli/harnessContext.cjs +175 -0
- package/dist/src/builders/cli/harnessContext.cjs.map +1 -0
- package/dist/src/builders/cli/harnessContext.d.mts +48 -0
- package/dist/src/builders/cli/harnessContext.d.mts.map +1 -0
- package/dist/src/builders/cli/harnessContext.mjs +167 -0
- package/dist/src/builders/cli/harnessContext.mjs.map +1 -0
- package/dist/src/builders/cli/sessionComposition.cjs +27 -0
- package/dist/src/builders/cli/sessionComposition.cjs.map +1 -0
- package/dist/src/builders/cli/sessionComposition.mjs +26 -0
- package/dist/src/builders/cli/sessionComposition.mjs.map +1 -0
- package/dist/src/builders/hooks/index.cjs +238 -0
- package/dist/src/builders/hooks/index.cjs.map +1 -0
- package/dist/src/builders/hooks/index.mjs +231 -0
- package/dist/src/builders/hooks/index.mjs.map +1 -0
- package/dist/src/builders/native/index.cjs +176 -0
- package/dist/src/builders/native/index.cjs.map +1 -0
- package/dist/src/builders/native/index.d.mts +36 -0
- package/dist/src/builders/native/index.d.mts.map +1 -0
- package/dist/src/builders/native/index.mjs +170 -0
- package/dist/src/builders/native/index.mjs.map +1 -0
- package/dist/src/builders/server/backgroundSync.cjs +333 -0
- package/dist/src/builders/server/backgroundSync.cjs.map +1 -0
- package/dist/src/builders/server/backgroundSync.mjs +320 -0
- package/dist/src/builders/server/backgroundSync.mjs.map +1 -0
- package/dist/src/builders/server/computerUseBinding.cjs +273 -0
- package/dist/src/builders/server/computerUseBinding.cjs.map +1 -0
- package/dist/src/builders/server/computerUseBinding.mjs +269 -0
- package/dist/src/builders/server/computerUseBinding.mjs.map +1 -0
- package/dist/src/builders/server/computerUseRecordings.cjs +188 -0
- package/dist/src/builders/server/computerUseRecordings.cjs.map +1 -0
- package/dist/src/builders/server/computerUseRecordings.mjs +184 -0
- package/dist/src/builders/server/computerUseRecordings.mjs.map +1 -0
- package/dist/src/builders/server/defaultWorkspace.cjs +38 -0
- package/dist/src/builders/server/defaultWorkspace.cjs.map +1 -0
- package/dist/src/builders/server/defaultWorkspace.mjs +35 -0
- package/dist/src/builders/server/defaultWorkspace.mjs.map +1 -0
- package/dist/src/builders/server/idleRestart.cjs +149 -0
- package/dist/src/builders/server/idleRestart.cjs.map +1 -0
- package/dist/src/builders/server/idleRestart.mjs +136 -0
- package/dist/src/builders/server/idleRestart.mjs.map +1 -0
- package/dist/src/builders/server/index.cjs +63 -0
- package/dist/src/builders/server/index.cjs.map +1 -0
- package/dist/src/builders/server/index.mjs +60 -0
- package/dist/src/builders/server/index.mjs.map +1 -0
- package/dist/src/builders/server/logSink.cjs +57 -0
- package/dist/src/builders/server/logSink.cjs.map +1 -0
- package/dist/src/builders/server/logSink.mjs +57 -0
- package/dist/src/builders/server/logSink.mjs.map +1 -0
- package/dist/src/builders/server/mcpApp.cjs +152 -0
- package/dist/src/builders/server/mcpApp.cjs.map +1 -0
- package/dist/src/builders/server/mcpApp.d.mts +18 -0
- package/dist/src/builders/server/mcpApp.d.mts.map +1 -0
- package/dist/src/builders/server/mcpApp.mjs +147 -0
- package/dist/src/builders/server/mcpApp.mjs.map +1 -0
- package/dist/src/builders/server/mcpAppBuild.cjs +111 -0
- package/dist/src/builders/server/mcpAppBuild.cjs.map +1 -0
- package/dist/src/builders/server/mcpAppBuild.d.mts +24 -0
- package/dist/src/builders/server/mcpAppBuild.d.mts.map +1 -0
- package/dist/src/builders/server/mcpAppBuild.mjs +105 -0
- package/dist/src/builders/server/mcpAppBuild.mjs.map +1 -0
- package/dist/src/builders/server/mcpBundle.cjs +87 -0
- package/dist/src/builders/server/mcpBundle.cjs.map +1 -0
- package/dist/src/builders/server/mcpBundle.d.mts +23 -0
- package/dist/src/builders/server/mcpBundle.d.mts.map +1 -0
- package/dist/src/builders/server/mcpBundle.mjs +84 -0
- package/dist/src/builders/server/mcpBundle.mjs.map +1 -0
- package/dist/src/builders/server/packageReloadCheck.cjs +62 -0
- package/dist/src/builders/server/packageReloadCheck.cjs.map +1 -0
- package/dist/src/builders/server/packageReloadCheck.mjs +61 -0
- package/dist/src/builders/server/packageReloadCheck.mjs.map +1 -0
- package/dist/src/builders/server/packageUrl.cjs +210 -0
- package/dist/src/builders/server/packageUrl.cjs.map +1 -0
- package/dist/src/builders/server/packageUrl.mjs +203 -0
- package/dist/src/builders/server/packageUrl.mjs.map +1 -0
- package/dist/src/builders/server/runtime.cjs +1428 -0
- package/dist/src/builders/server/runtime.cjs.map +1 -0
- package/dist/src/builders/server/runtime.mjs +1420 -0
- package/dist/src/builders/server/runtime.mjs.map +1 -0
- package/dist/src/builders/server/runtimeResources.cjs +72 -0
- package/dist/src/builders/server/runtimeResources.cjs.map +1 -0
- package/dist/src/builders/server/runtimeResources.mjs +71 -0
- package/dist/src/builders/server/runtimeResources.mjs.map +1 -0
- package/dist/src/builders/server/selectionStatus.cjs +12 -0
- package/dist/src/builders/server/selectionStatus.cjs.map +1 -0
- package/dist/src/builders/server/selectionStatus.mjs +12 -0
- package/dist/src/builders/server/selectionStatus.mjs.map +1 -0
- package/dist/src/builders/server/sessionArguments.cjs +63 -0
- package/dist/src/builders/server/sessionArguments.cjs.map +1 -0
- package/dist/src/builders/server/sessionArguments.mjs +62 -0
- package/dist/src/builders/server/sessionArguments.mjs.map +1 -0
- package/dist/src/builders/server/shutdown.cjs +16 -0
- package/dist/src/builders/server/shutdown.cjs.map +1 -0
- package/dist/src/builders/server/shutdown.mjs +16 -0
- package/dist/src/builders/server/shutdown.mjs.map +1 -0
- package/dist/src/builders/server/webTemplateDecision.cjs +34 -0
- package/dist/src/builders/server/webTemplateDecision.cjs.map +1 -0
- package/dist/src/builders/server/webTemplateDecision.mjs +32 -0
- package/dist/src/builders/server/webTemplateDecision.mjs.map +1 -0
- package/dist/src/builders/server/workspaceCheckout.cjs +31 -0
- package/dist/src/builders/server/workspaceCheckout.cjs.map +1 -0
- package/dist/src/builders/server/workspaceCheckout.mjs +29 -0
- package/dist/src/builders/server/workspaceCheckout.mjs.map +1 -0
- package/dist/src/builders/web/bundle.cjs +225 -0
- package/dist/src/builders/web/bundle.cjs.map +1 -0
- package/dist/src/builders/web/bundle.d.mts +65 -0
- package/dist/src/builders/web/bundle.d.mts.map +1 -0
- package/dist/src/builders/web/bundle.mjs +221 -0
- package/dist/src/builders/web/bundle.mjs.map +1 -0
- package/dist/src/builders/web/bundleAssetPolicy.cjs +96 -0
- package/dist/src/builders/web/bundleAssetPolicy.cjs.map +1 -0
- package/dist/src/builders/web/bundleAssetPolicy.d.mts +22 -0
- package/dist/src/builders/web/bundleAssetPolicy.d.mts.map +1 -0
- package/dist/src/builders/web/bundleAssetPolicy.mjs +95 -0
- package/dist/src/builders/web/bundleAssetPolicy.mjs.map +1 -0
- package/dist/src/builders/web/index.cjs +361 -0
- package/dist/src/builders/web/index.cjs.map +1 -0
- package/dist/src/builders/web/index.d.mts +109 -0
- package/dist/src/builders/web/index.d.mts.map +1 -0
- package/dist/src/builders/web/index.mjs +342 -0
- package/dist/src/builders/web/index.mjs.map +1 -0
- package/dist/src/builders/web/webDevRoots.cjs +27 -0
- package/dist/src/builders/web/webDevRoots.cjs.map +1 -0
- package/dist/src/builders/web/webDevRoots.d.mts +22 -0
- package/dist/src/builders/web/webDevRoots.d.mts.map +1 -0
- package/dist/src/builders/web/webDevRoots.mjs +25 -0
- package/dist/src/builders/web/webDevRoots.mjs.map +1 -0
- package/dist/src/builders/web/webPluginGenerate.cjs +188 -0
- package/dist/src/builders/web/webPluginGenerate.cjs.map +1 -0
- package/dist/src/builders/web/webPluginGenerate.d.mts +56 -0
- package/dist/src/builders/web/webPluginGenerate.d.mts.map +1 -0
- package/dist/src/builders/web/webPluginGenerate.mjs +178 -0
- package/dist/src/builders/web/webPluginGenerate.mjs.map +1 -0
- package/dist/src/builders/web/webPluginManifest.cjs +128 -0
- package/dist/src/builders/web/webPluginManifest.cjs.map +1 -0
- package/dist/src/builders/web/webPluginManifest.d.mts +64 -0
- package/dist/src/builders/web/webPluginManifest.d.mts.map +1 -0
- package/dist/src/builders/web/webPluginManifest.mjs +118 -0
- package/dist/src/builders/web/webPluginManifest.mjs.map +1 -0
- package/dist/src/builders/web/webPluginScan.cjs +57 -0
- package/dist/src/builders/web/webPluginScan.cjs.map +1 -0
- package/dist/src/builders/web/webPluginScan.d.mts +19 -0
- package/dist/src/builders/web/webPluginScan.d.mts.map +1 -0
- package/dist/src/builders/web/webPluginScan.mjs +54 -0
- package/dist/src/builders/web/webPluginScan.mjs.map +1 -0
- package/dist/src/builders/web/webPluginVite.cjs +255 -0
- package/dist/src/builders/web/webPluginVite.cjs.map +1 -0
- package/dist/src/builders/web/webPluginVite.d.mts +64 -0
- package/dist/src/builders/web/webPluginVite.d.mts.map +1 -0
- package/dist/src/builders/web/webPluginVite.mjs +242 -0
- package/dist/src/builders/web/webPluginVite.mjs.map +1 -0
- package/dist/src/cli/cliApp.cjs +187 -0
- package/dist/src/cli/cliApp.cjs.map +1 -0
- package/dist/src/cli/cliApp.d.mts +32 -0
- package/dist/src/cli/cliApp.d.mts.map +1 -0
- package/dist/src/cli/cliApp.mjs +184 -0
- package/dist/src/cli/cliApp.mjs.map +1 -0
- package/dist/src/cli/commands/api-export/index.cjs +66 -0
- package/dist/src/cli/commands/api-export/index.cjs.map +1 -0
- package/dist/src/cli/commands/api-export/index.mjs +62 -0
- package/dist/src/cli/commands/api-export/index.mjs.map +1 -0
- package/dist/src/cli/commands/baseCommand.cjs +16 -0
- package/dist/src/cli/commands/baseCommand.cjs.map +1 -0
- package/dist/src/cli/commands/baseCommand.d.mts +24 -0
- package/dist/src/cli/commands/baseCommand.d.mts.map +1 -0
- package/dist/src/cli/commands/baseCommand.mjs +16 -0
- package/dist/src/cli/commands/baseCommand.mjs.map +1 -0
- package/dist/src/cli/commands/compat/context.cjs +115 -0
- package/dist/src/cli/commands/compat/context.cjs.map +1 -0
- package/dist/src/cli/commands/compat/context.d.mts +22 -0
- package/dist/src/cli/commands/compat/context.d.mts.map +1 -0
- package/dist/src/cli/commands/compat/context.mjs +110 -0
- package/dist/src/cli/commands/compat/context.mjs.map +1 -0
- package/dist/src/cli/commands/compat/help.cjs +26 -0
- package/dist/src/cli/commands/compat/help.cjs.map +1 -0
- package/dist/src/cli/commands/compat/help.d.mts +4 -0
- package/dist/src/cli/commands/compat/help.d.mts.map +1 -0
- package/dist/src/cli/commands/compat/help.mjs +25 -0
- package/dist/src/cli/commands/compat/help.mjs.map +1 -0
- package/dist/src/cli/commands/compat/index.cjs +73 -0
- package/dist/src/cli/commands/compat/index.cjs.map +1 -0
- package/dist/src/cli/commands/compat/index.d.mts +14 -0
- package/dist/src/cli/commands/compat/index.d.mts.map +1 -0
- package/dist/src/cli/commands/compat/index.mjs +70 -0
- package/dist/src/cli/commands/compat/index.mjs.map +1 -0
- package/dist/src/cli/commands/compat/options.cjs +88 -0
- package/dist/src/cli/commands/compat/options.cjs.map +1 -0
- package/dist/src/cli/commands/compat/options.d.mts +15 -0
- package/dist/src/cli/commands/compat/options.d.mts.map +1 -0
- package/dist/src/cli/commands/compat/options.mjs +86 -0
- package/dist/src/cli/commands/compat/options.mjs.map +1 -0
- package/dist/src/cli/commands/compat/providers/antigravity.cjs +294 -0
- package/dist/src/cli/commands/compat/providers/antigravity.cjs.map +1 -0
- package/dist/src/cli/commands/compat/providers/antigravity.d.mts +9 -0
- package/dist/src/cli/commands/compat/providers/antigravity.d.mts.map +1 -0
- package/dist/src/cli/commands/compat/providers/antigravity.mjs +289 -0
- package/dist/src/cli/commands/compat/providers/antigravity.mjs.map +1 -0
- package/dist/src/cli/commands/compat/providers/codex.cjs +178 -0
- package/dist/src/cli/commands/compat/providers/codex.cjs.map +1 -0
- package/dist/src/cli/commands/compat/providers/codex.d.mts +17 -0
- package/dist/src/cli/commands/compat/providers/codex.d.mts.map +1 -0
- package/dist/src/cli/commands/compat/providers/codex.mjs +170 -0
- package/dist/src/cli/commands/compat/providers/codex.mjs.map +1 -0
- package/dist/src/cli/commands/compat/providers/index.cjs +65 -0
- package/dist/src/cli/commands/compat/providers/index.cjs.map +1 -0
- package/dist/src/cli/commands/compat/providers/index.d.mts +15 -0
- package/dist/src/cli/commands/compat/providers/index.d.mts.map +1 -0
- package/dist/src/cli/commands/compat/providers/index.mjs +45 -0
- package/dist/src/cli/commands/compat/providers/index.mjs.map +1 -0
- package/dist/src/cli/commands/compat/providers/process.cjs +172 -0
- package/dist/src/cli/commands/compat/providers/process.cjs.map +1 -0
- package/dist/src/cli/commands/compat/providers/process.d.mts +33 -0
- package/dist/src/cli/commands/compat/providers/process.d.mts.map +1 -0
- package/dist/src/cli/commands/compat/providers/process.mjs +159 -0
- package/dist/src/cli/commands/compat/providers/process.mjs.map +1 -0
- package/dist/src/cli/commands/compat/providers/shared.cjs +76 -0
- package/dist/src/cli/commands/compat/providers/shared.cjs.map +1 -0
- package/dist/src/cli/commands/compat/providers/shared.d.mts +40 -0
- package/dist/src/cli/commands/compat/providers/shared.d.mts.map +1 -0
- package/dist/src/cli/commands/compat/providers/shared.mjs +61 -0
- package/dist/src/cli/commands/compat/providers/shared.mjs.map +1 -0
- package/dist/src/cli/commands/compat/types.d.mts +31 -0
- package/dist/src/cli/commands/compat/types.d.mts.map +1 -0
- package/dist/src/cli/commands/doctor/help.cjs +19 -0
- package/dist/src/cli/commands/doctor/help.cjs.map +1 -0
- package/dist/src/cli/commands/doctor/help.d.mts +4 -0
- package/dist/src/cli/commands/doctor/help.d.mts.map +1 -0
- package/dist/src/cli/commands/doctor/help.mjs +19 -0
- package/dist/src/cli/commands/doctor/help.mjs.map +1 -0
- package/dist/src/cli/commands/doctor/index.cjs +118 -0
- package/dist/src/cli/commands/doctor/index.cjs.map +1 -0
- package/dist/src/cli/commands/doctor/index.d.mts +33 -0
- package/dist/src/cli/commands/doctor/index.d.mts.map +1 -0
- package/dist/src/cli/commands/doctor/index.mjs +113 -0
- package/dist/src/cli/commands/doctor/index.mjs.map +1 -0
- package/dist/src/cli/commands/emit-mcp/index.cjs +32 -0
- package/dist/src/cli/commands/emit-mcp/index.cjs.map +1 -0
- package/dist/src/cli/commands/emit-mcp/index.d.mts +19 -0
- package/dist/src/cli/commands/emit-mcp/index.d.mts.map +1 -0
- package/dist/src/cli/commands/emit-mcp/index.mjs +31 -0
- package/dist/src/cli/commands/emit-mcp/index.mjs.map +1 -0
- package/dist/src/cli/commands/explain/index.cjs +155 -0
- package/dist/src/cli/commands/explain/index.cjs.map +1 -0
- package/dist/src/cli/commands/explain/index.d.mts +57 -0
- package/dist/src/cli/commands/explain/index.d.mts.map +1 -0
- package/dist/src/cli/commands/explain/index.mjs +150 -0
- package/dist/src/cli/commands/explain/index.mjs.map +1 -0
- package/dist/src/cli/commands/init/constants.cjs +11 -0
- package/dist/src/cli/commands/init/constants.cjs.map +1 -0
- package/dist/src/cli/commands/init/constants.mjs +11 -0
- package/dist/src/cli/commands/init/constants.mjs.map +1 -0
- package/dist/src/cli/commands/init/help.cjs +17 -0
- package/dist/src/cli/commands/init/help.cjs.map +1 -0
- package/dist/src/cli/commands/init/help.d.mts +4 -0
- package/dist/src/cli/commands/init/help.d.mts.map +1 -0
- package/dist/src/cli/commands/init/help.mjs +17 -0
- package/dist/src/cli/commands/init/help.mjs.map +1 -0
- package/dist/src/cli/commands/init/index.cjs +67 -0
- package/dist/src/cli/commands/init/index.cjs.map +1 -0
- package/dist/src/cli/commands/init/index.d.mts +12 -0
- package/dist/src/cli/commands/init/index.d.mts.map +1 -0
- package/dist/src/cli/commands/init/index.mjs +63 -0
- package/dist/src/cli/commands/init/index.mjs.map +1 -0
- package/dist/src/cli/commands/init/presenter.cjs +140 -0
- package/dist/src/cli/commands/init/presenter.cjs.map +1 -0
- package/dist/src/cli/commands/init/presenter.d.mts +8 -0
- package/dist/src/cli/commands/init/presenter.d.mts.map +1 -0
- package/dist/src/cli/commands/init/presenter.mjs +140 -0
- package/dist/src/cli/commands/init/presenter.mjs.map +1 -0
- package/dist/src/cli/commands/sync/help.cjs +34 -0
- package/dist/src/cli/commands/sync/help.cjs.map +1 -0
- package/dist/src/cli/commands/sync/help.d.mts +4 -0
- package/dist/src/cli/commands/sync/help.d.mts.map +1 -0
- package/dist/src/cli/commands/sync/help.mjs +34 -0
- package/dist/src/cli/commands/sync/help.mjs.map +1 -0
- package/dist/src/cli/commands/sync/index.cjs +215 -0
- package/dist/src/cli/commands/sync/index.cjs.map +1 -0
- package/dist/src/cli/commands/sync/index.d.mts +20 -0
- package/dist/src/cli/commands/sync/index.d.mts.map +1 -0
- package/dist/src/cli/commands/sync/index.mjs +206 -0
- package/dist/src/cli/commands/sync/index.mjs.map +1 -0
- package/dist/src/cli/commands/sync/presenter.cjs +58 -0
- package/dist/src/cli/commands/sync/presenter.cjs.map +1 -0
- package/dist/src/cli/commands/sync/presenter.d.mts +16 -0
- package/dist/src/cli/commands/sync/presenter.d.mts.map +1 -0
- package/dist/src/cli/commands/sync/presenter.mjs +57 -0
- package/dist/src/cli/commands/sync/presenter.mjs.map +1 -0
- package/dist/src/cli/commands/sync/workflow.cjs +16 -0
- package/dist/src/cli/commands/sync/workflow.cjs.map +1 -0
- package/dist/src/cli/commands/sync/workflow.mjs +16 -0
- package/dist/src/cli/commands/sync/workflow.mjs.map +1 -0
- package/dist/src/cli/commands/web/index.cjs +88 -0
- package/dist/src/cli/commands/web/index.cjs.map +1 -0
- package/dist/src/cli/commands/web/index.mjs +85 -0
- package/dist/src/cli/commands/web/index.mjs.map +1 -0
- package/dist/src/cli/harnessOptions.cjs +70 -0
- package/dist/src/cli/harnessOptions.cjs.map +1 -0
- package/dist/src/cli/harnessOptions.d.mts +26 -0
- package/dist/src/cli/harnessOptions.d.mts.map +1 -0
- package/dist/src/cli/harnessOptions.mjs +66 -0
- package/dist/src/cli/harnessOptions.mjs.map +1 -0
- package/dist/src/cli/help.cjs +82 -0
- package/dist/src/cli/help.cjs.map +1 -0
- package/dist/src/cli/help.d.mts +10 -0
- package/dist/src/cli/help.d.mts.map +1 -0
- package/dist/src/cli/help.mjs +78 -0
- package/dist/src/cli/help.mjs.map +1 -0
- package/dist/src/cli/matrixOptions.cjs +178 -0
- package/dist/src/cli/matrixOptions.cjs.map +1 -0
- package/dist/src/cli/matrixOptions.d.mts +90 -0
- package/dist/src/cli/matrixOptions.d.mts.map +1 -0
- package/dist/src/cli/matrixOptions.mjs +151 -0
- package/dist/src/cli/matrixOptions.mjs.map +1 -0
- package/dist/src/cli/options.cjs +281 -0
- package/dist/src/cli/options.cjs.map +1 -0
- package/dist/src/cli/options.d.mts +5 -0
- package/dist/src/cli/options.d.mts.map +1 -0
- package/dist/src/cli/options.mjs +279 -0
- package/dist/src/cli/options.mjs.map +1 -0
- package/dist/src/cli/router.cjs +57 -0
- package/dist/src/cli/router.cjs.map +1 -0
- package/dist/src/cli/router.d.mts +32 -0
- package/dist/src/cli/router.d.mts.map +1 -0
- package/dist/src/cli/router.mjs +54 -0
- package/dist/src/cli/router.mjs.map +1 -0
- package/dist/src/cli/server/options.cjs +71 -0
- package/dist/src/cli/server/options.cjs.map +1 -0
- package/dist/src/cli/server/options.mjs +70 -0
- package/dist/src/cli/server/options.mjs.map +1 -0
- package/dist/src/cli/server/processGuards.cjs +83 -0
- package/dist/src/cli/server/processGuards.cjs.map +1 -0
- package/dist/src/cli/server/processGuards.mjs +79 -0
- package/dist/src/cli/server/processGuards.mjs.map +1 -0
- package/dist/src/cli/server/run.cjs +69 -0
- package/dist/src/cli/server/run.cjs.map +1 -0
- package/dist/src/cli/server/run.mjs +66 -0
- package/dist/src/cli/server/run.mjs.map +1 -0
- package/dist/src/composition/harnessState/index.cjs +48 -0
- package/dist/src/composition/harnessState/index.cjs.map +1 -0
- package/dist/src/composition/harnessState/index.d.mts +19 -0
- package/dist/src/composition/harnessState/index.d.mts.map +1 -0
- package/dist/src/composition/harnessState/index.mjs +34 -0
- package/dist/src/composition/harnessState/index.mjs.map +1 -0
- package/dist/src/composition/layerPackageInstaller/index.cjs +651 -0
- package/dist/src/composition/layerPackageInstaller/index.cjs.map +1 -0
- package/dist/src/composition/layerPackageInstaller/index.d.mts +74 -0
- package/dist/src/composition/layerPackageInstaller/index.d.mts.map +1 -0
- package/dist/src/composition/layerPackageInstaller/index.mjs +637 -0
- package/dist/src/composition/layerPackageInstaller/index.mjs.map +1 -0
- package/dist/src/composition/poHomeMigration/index.cjs +234 -0
- package/dist/src/composition/poHomeMigration/index.cjs.map +1 -0
- package/dist/src/composition/poHomeMigration/index.d.mts +12 -0
- package/dist/src/composition/poHomeMigration/index.d.mts.map +1 -0
- package/dist/src/composition/poHomeMigration/index.mjs +226 -0
- package/dist/src/composition/poHomeMigration/index.mjs.map +1 -0
- package/dist/src/composition/projectTrust/index.cjs +51 -0
- package/dist/src/composition/projectTrust/index.cjs.map +1 -0
- package/dist/src/composition/projectTrust/index.d.mts +22 -0
- package/dist/src/composition/projectTrust/index.d.mts.map +1 -0
- package/dist/src/composition/projectTrust/index.mjs +48 -0
- package/dist/src/composition/projectTrust/index.mjs.map +1 -0
- package/dist/src/composition/repository/index.cjs +68 -0
- package/dist/src/composition/repository/index.cjs.map +1 -0
- package/dist/src/composition/repository/index.d.mts +22 -0
- package/dist/src/composition/repository/index.d.mts.map +1 -0
- package/dist/src/composition/repository/index.mjs +63 -0
- package/dist/src/composition/repository/index.mjs.map +1 -0
- package/dist/src/composition/syncPrune/index.cjs +85 -0
- package/dist/src/composition/syncPrune/index.cjs.map +1 -0
- package/dist/src/composition/syncPrune/index.d.mts +33 -0
- package/dist/src/composition/syncPrune/index.d.mts.map +1 -0
- package/dist/src/composition/syncPrune/index.mjs +82 -0
- package/dist/src/composition/syncPrune/index.mjs.map +1 -0
- package/dist/src/composition/syncService/index.cjs +808 -0
- package/dist/src/composition/syncService/index.cjs.map +1 -0
- package/dist/src/composition/syncService/index.d.mts +65 -0
- package/dist/src/composition/syncService/index.d.mts.map +1 -0
- package/dist/src/composition/syncService/index.mjs +792 -0
- package/dist/src/composition/syncService/index.mjs.map +1 -0
- package/dist/src/composition/syncService/localBuilds.cjs +367 -0
- package/dist/src/composition/syncService/localBuilds.cjs.map +1 -0
- package/dist/src/composition/syncService/localBuilds.mjs +358 -0
- package/dist/src/composition/syncService/localBuilds.mjs.map +1 -0
- package/dist/src/composition/transitionLayers.cjs +17 -0
- package/dist/src/composition/transitionLayers.cjs.map +1 -0
- package/dist/src/composition/transitionLayers.d.mts +6 -0
- package/dist/src/composition/transitionLayers.d.mts.map +1 -0
- package/dist/src/composition/transitionLayers.mjs +16 -0
- package/dist/src/composition/transitionLayers.mjs.map +1 -0
- package/dist/src/composition/types/harness.d.mts +52 -0
- package/dist/src/composition/types/harness.d.mts.map +1 -0
- package/dist/src/extensions/setupDiagnostics.cjs +65 -0
- package/dist/src/extensions/setupDiagnostics.cjs.map +1 -0
- package/dist/src/extensions/setupDiagnostics.mjs +64 -0
- package/dist/src/extensions/setupDiagnostics.mjs.map +1 -0
- package/dist/utils.cjs +4 -0
- package/dist/utils.d.mts +2 -0
- package/dist/utils.mjs +2 -0
- package/dist/webBuilder.cjs +59 -0
- package/dist/webBuilder.d.mts +9 -0
- package/dist/webBuilder.mjs +9 -0
- package/docs/server/api-export.md +86 -0
- package/docs/server/api.md +144 -0
- package/docs/server/getting-started.md +114 -0
- package/docs/server/index.md +67 -0
- package/docs/server/ipc.md +97 -0
- package/docs/server/lifecycle.md +108 -0
- package/docs/server/security.md +101 -0
- package/llms.txt +12 -0
- package/package.json +295 -0
- package/src/prompts/po-author-extension/SKILL.md +35 -0
- package/src/prompts/po-author-extension/references/extension-contract.md +134 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import fs$1 from "node:fs/promises";
|
|
6
|
+
//#region src/builders/server/computerUseRecordings.ts
|
|
7
|
+
const MAX_RECORDING_BYTES = 134217728;
|
|
8
|
+
const MAX_STORE_BYTES = 536870912;
|
|
9
|
+
const MAX_RECORDINGS = 8;
|
|
10
|
+
const RETENTION_MS = 864e5;
|
|
11
|
+
const CHUNK_BYTES = 1048576;
|
|
12
|
+
const SAFE_ID = /^[a-zA-Z0-9-]{1,128}$/u;
|
|
13
|
+
/** Imports only grant-bound native IPC receipts, never browser-provided paths. */
|
|
14
|
+
var ComputerUseRecordingStore = class {
|
|
15
|
+
directory;
|
|
16
|
+
stagingRoot;
|
|
17
|
+
recordings = /* @__PURE__ */ new Map();
|
|
18
|
+
imports = /* @__PURE__ */ new Map();
|
|
19
|
+
closed = false;
|
|
20
|
+
incarnations = /* @__PURE__ */ new Map();
|
|
21
|
+
constructor(directory, stagingRoot = path.join(os.tmpdir(), "po-computer-use")) {
|
|
22
|
+
this.directory = directory;
|
|
23
|
+
this.stagingRoot = stagingRoot;
|
|
24
|
+
}
|
|
25
|
+
async prune() {
|
|
26
|
+
let bytes = [...this.recordings.values()].reduce((total, item) => total + item.sizeBytes, 0);
|
|
27
|
+
for (const [id, item] of this.recordings) {
|
|
28
|
+
if (Date.now() - Date.parse(item.completedAt) < RETENTION_MS && this.recordings.size <= MAX_RECORDINGS && bytes <= MAX_STORE_BYTES) break;
|
|
29
|
+
this.recordings.delete(id);
|
|
30
|
+
this.imports.delete(`${item.sessionId}\0${item.grantId}`);
|
|
31
|
+
bytes -= item.sizeBytes;
|
|
32
|
+
await fs$1.rm(item.file, { force: true });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async importStop(sessionId, grantId, value) {
|
|
36
|
+
if (this.closed) throw new Error("Recording storage is closed.");
|
|
37
|
+
const key = `${sessionId}\0${grantId}`;
|
|
38
|
+
const previous = this.imports.get(key);
|
|
39
|
+
if (previous) return previous;
|
|
40
|
+
const pending = this.importReceipt(sessionId, grantId, value);
|
|
41
|
+
this.imports.set(key, pending);
|
|
42
|
+
try {
|
|
43
|
+
const result = await pending;
|
|
44
|
+
if (!result?.artifact) this.imports.delete(key);
|
|
45
|
+
return result;
|
|
46
|
+
} catch {
|
|
47
|
+
this.imports.delete(key);
|
|
48
|
+
throw new Error("Native recording import failed.");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async importReceipt(sessionId, grantId, value) {
|
|
52
|
+
const receipt = value;
|
|
53
|
+
if (receipt?.stopped !== true || receipt.artifact === void 0) return { stopped: receipt?.stopped === true };
|
|
54
|
+
const incarnation = this.incarnations.get(sessionId) ?? 0;
|
|
55
|
+
const artifact = receipt.artifact;
|
|
56
|
+
if (!SAFE_ID.test(grantId) || artifact.kind !== "screen_recording" || artifact.contentType !== "video/mp4" || artifact.audioScope !== "target_application") throw new Error("Invalid native recording receipt.");
|
|
57
|
+
const grantDirectory = path.join(this.stagingRoot, grantId);
|
|
58
|
+
const source = path.join(grantDirectory, "recording.mp4");
|
|
59
|
+
if (artifact.path !== source) throw new Error("Invalid native recording location.");
|
|
60
|
+
for (const directory of [this.stagingRoot, grantDirectory]) {
|
|
61
|
+
const stat = await fs$1.lstat(directory);
|
|
62
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || (stat.mode & 63) !== 0) throw new Error("Recording staging is not private.");
|
|
63
|
+
}
|
|
64
|
+
const input = await fs$1.open(source, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
65
|
+
const artifactId = randomUUID();
|
|
66
|
+
const destination = path.join(this.directory, `${artifactId}.mp4`);
|
|
67
|
+
const temporary = `${destination}.tmp`;
|
|
68
|
+
let output;
|
|
69
|
+
try {
|
|
70
|
+
const stat = await input.stat();
|
|
71
|
+
if (!stat.isFile() || stat.size < 1 || stat.size > MAX_RECORDING_BYTES) throw new Error("Recording exceeds the permitted size.");
|
|
72
|
+
await fs$1.mkdir(this.directory, {
|
|
73
|
+
recursive: true,
|
|
74
|
+
mode: 448
|
|
75
|
+
});
|
|
76
|
+
const root = await fs$1.lstat(this.directory);
|
|
77
|
+
if (!root.isDirectory() || root.isSymbolicLink() || (root.mode & 63) !== 0) throw new Error("Recording storage is not private.");
|
|
78
|
+
output = await fs$1.open(temporary, "wx", 384);
|
|
79
|
+
const buffer = Buffer.alloc(CHUNK_BYTES);
|
|
80
|
+
let copied = 0;
|
|
81
|
+
while (true) {
|
|
82
|
+
const { bytesRead } = await input.read(buffer, 0, buffer.length, null);
|
|
83
|
+
if (bytesRead === 0) break;
|
|
84
|
+
copied += bytesRead;
|
|
85
|
+
if (copied > MAX_RECORDING_BYTES || copied > stat.size) throw new Error("Recording changed during import.");
|
|
86
|
+
await output.writeFile(buffer.subarray(0, bytesRead));
|
|
87
|
+
}
|
|
88
|
+
if (copied !== stat.size) throw new Error("Recording changed during import.");
|
|
89
|
+
await output.sync();
|
|
90
|
+
await output.close();
|
|
91
|
+
output = void 0;
|
|
92
|
+
if (this.closed || (this.incarnations.get(sessionId) ?? 0) !== incarnation) throw new Error("Recording storage is closed.");
|
|
93
|
+
await fs$1.rename(temporary, destination);
|
|
94
|
+
if (this.closed || (this.incarnations.get(sessionId) ?? 0) !== incarnation) {
|
|
95
|
+
await fs$1.rm(destination, { force: true });
|
|
96
|
+
throw new Error("Recording storage is closed.");
|
|
97
|
+
}
|
|
98
|
+
const item = {
|
|
99
|
+
sessionId,
|
|
100
|
+
grantId,
|
|
101
|
+
artifactId,
|
|
102
|
+
sizeBytes: copied,
|
|
103
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
104
|
+
file: destination
|
|
105
|
+
};
|
|
106
|
+
this.recordings.set(artifactId, item);
|
|
107
|
+
await this.prune();
|
|
108
|
+
await fs$1.unlink(source);
|
|
109
|
+
await fs$1.rmdir(grantDirectory);
|
|
110
|
+
return {
|
|
111
|
+
stopped: true,
|
|
112
|
+
artifact: {
|
|
113
|
+
artifactId,
|
|
114
|
+
status: "ready",
|
|
115
|
+
sizeBytes: copied,
|
|
116
|
+
completedAt: item.completedAt
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
} catch {
|
|
120
|
+
this.recordings.delete(artifactId);
|
|
121
|
+
await fs$1.rm(temporary, { force: true });
|
|
122
|
+
await fs$1.rm(destination, { force: true });
|
|
123
|
+
throw new Error("Native recording import failed.");
|
|
124
|
+
} finally {
|
|
125
|
+
await output?.close();
|
|
126
|
+
await input.close();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async forgetSession(sessionId) {
|
|
130
|
+
this.incarnations.set(sessionId, (this.incarnations.get(sessionId) ?? 0) + 1);
|
|
131
|
+
const files = [];
|
|
132
|
+
for (const [id, item] of this.recordings) {
|
|
133
|
+
if (item.sessionId !== sessionId) continue;
|
|
134
|
+
this.recordings.delete(id);
|
|
135
|
+
files.push(item.file);
|
|
136
|
+
}
|
|
137
|
+
for (const key of this.imports.keys()) if (key.startsWith(`${sessionId}\0`)) this.imports.delete(key);
|
|
138
|
+
await Promise.all(files.map((file) => fs$1.rm(file, { force: true })));
|
|
139
|
+
}
|
|
140
|
+
async close() {
|
|
141
|
+
this.closed = true;
|
|
142
|
+
await Promise.allSettled(this.imports.values());
|
|
143
|
+
this.imports.clear();
|
|
144
|
+
this.recordings.clear();
|
|
145
|
+
await fs$1.rm(this.directory, {
|
|
146
|
+
recursive: true,
|
|
147
|
+
force: true
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async read(sessionId, artifactId, range) {
|
|
151
|
+
if (this.closed) return new Response(null, { status: 404 });
|
|
152
|
+
await this.prune();
|
|
153
|
+
const item = this.recordings.get(artifactId);
|
|
154
|
+
if (!item || item.sessionId !== sessionId) return new Response(null, { status: 404 });
|
|
155
|
+
const match = /^bytes=(\d+)-(\d+)$/u.exec(range ?? "");
|
|
156
|
+
const start = Number(match?.[1]);
|
|
157
|
+
const requestedEnd = Number(match?.[2]);
|
|
158
|
+
if (!match || !Number.isSafeInteger(start) || !Number.isSafeInteger(requestedEnd) || start < 0 || start >= item.sizeBytes || requestedEnd < start || requestedEnd - start >= CHUNK_BYTES) return new Response(null, { status: 416 });
|
|
159
|
+
const end = Math.min(requestedEnd, item.sizeBytes - 1);
|
|
160
|
+
const input = await fs$1.open(item.file, constants.O_RDONLY | constants.O_NOFOLLOW).catch(() => void 0);
|
|
161
|
+
if (input === void 0) return new Response(null, { status: 410 });
|
|
162
|
+
try {
|
|
163
|
+
const bytes = Buffer.alloc(end - start + 1);
|
|
164
|
+
const { bytesRead } = await input.read(bytes, 0, bytes.length, start);
|
|
165
|
+
if (bytesRead !== bytes.length) return new Response(null, { status: 410 });
|
|
166
|
+
return new Response(bytes, {
|
|
167
|
+
status: 206,
|
|
168
|
+
headers: {
|
|
169
|
+
"content-type": "video/mp4",
|
|
170
|
+
"content-range": `bytes ${start}-${end}/${item.sizeBytes}`,
|
|
171
|
+
"accept-ranges": "bytes",
|
|
172
|
+
"cache-control": "no-store",
|
|
173
|
+
"content-length": String(bytes.length)
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
} finally {
|
|
177
|
+
await input.close();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
//#endregion
|
|
182
|
+
export { ComputerUseRecordingStore };
|
|
183
|
+
|
|
184
|
+
//# sourceMappingURL=computerUseRecordings.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"computerUseRecordings.mjs","names":["fs"],"sources":["../../../../src/builders/server/computerUseRecordings.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { constants } from 'node:fs';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\n\nconst MAX_RECORDING_BYTES = 128 * 1024 * 1024;\nconst MAX_STORE_BYTES = 512 * 1024 * 1024;\nconst MAX_RECORDINGS = 8;\nconst RETENTION_MS = 24 * 60 * 60 * 1000;\nconst CHUNK_BYTES = 1024 * 1024;\nconst SAFE_ID = /^[a-zA-Z0-9-]{1,128}$/u;\n\ntype Recording = {\n sessionId: string;\n grantId: string;\n artifactId: string;\n sizeBytes: number;\n completedAt: string;\n file: string;\n};\n\n/** Imports only grant-bound native IPC receipts, never browser-provided paths. */\nexport class ComputerUseRecordingStore {\n private readonly recordings = new Map<string, Recording>();\n private readonly imports = new Map<string, Promise<unknown>>();\n private closed = false;\n private readonly incarnations = new Map<string, number>();\n constructor(\n private readonly directory: string,\n private readonly stagingRoot = path.join(os.tmpdir(), 'po-computer-use'),\n ) {}\n\n private async prune(): Promise<void> {\n let bytes = [...this.recordings.values()].reduce((total, item) => total + item.sizeBytes, 0);\n for (const [id, item] of this.recordings) {\n if (\n Date.now() - Date.parse(item.completedAt) < RETENTION_MS &&\n this.recordings.size <= MAX_RECORDINGS &&\n bytes <= MAX_STORE_BYTES\n )\n break;\n this.recordings.delete(id);\n this.imports.delete(`${item.sessionId}\\0${item.grantId}`);\n bytes -= item.sizeBytes;\n await fs.rm(item.file, { force: true });\n }\n }\n\n async importStop(sessionId: string, grantId: string, value: unknown): Promise<unknown> {\n if (this.closed) throw new Error('Recording storage is closed.');\n const key = `${sessionId}\\0${grantId}`;\n const previous = this.imports.get(key);\n if (previous) return previous;\n const pending = this.importReceipt(sessionId, grantId, value);\n this.imports.set(key, pending);\n try {\n const result = await pending;\n if (!(result as { artifact?: unknown } | undefined)?.artifact) this.imports.delete(key);\n return result;\n } catch {\n this.imports.delete(key);\n throw new Error('Native recording import failed.');\n }\n }\n\n private async importReceipt(sessionId: string, grantId: string, value: unknown): Promise<unknown> {\n const receipt = value as {\n stopped?: unknown;\n artifact?: { kind?: unknown; path?: unknown; contentType?: unknown; audioScope?: unknown };\n } | null;\n if (receipt?.stopped !== true || receipt.artifact === undefined) return { stopped: receipt?.stopped === true };\n const incarnation = this.incarnations.get(sessionId) ?? 0;\n const artifact = receipt.artifact;\n if (\n !SAFE_ID.test(grantId) ||\n artifact.kind !== 'screen_recording' ||\n artifact.contentType !== 'video/mp4' ||\n artifact.audioScope !== 'target_application'\n )\n throw new Error('Invalid native recording receipt.');\n const grantDirectory = path.join(this.stagingRoot, grantId);\n const source = path.join(grantDirectory, 'recording.mp4');\n if (artifact.path !== source) throw new Error('Invalid native recording location.');\n for (const directory of [this.stagingRoot, grantDirectory]) {\n const stat = await fs.lstat(directory);\n if (!stat.isDirectory() || stat.isSymbolicLink() || (stat.mode & 0o077) !== 0)\n throw new Error('Recording staging is not private.');\n }\n const input = await fs.open(source, constants.O_RDONLY | constants.O_NOFOLLOW);\n const artifactId = randomUUID();\n const destination = path.join(this.directory, `${artifactId}.mp4`);\n const temporary = `${destination}.tmp`;\n let output: Awaited<ReturnType<typeof fs.open>> | undefined;\n try {\n const stat = await input.stat();\n if (!stat.isFile() || stat.size < 1 || stat.size > MAX_RECORDING_BYTES)\n throw new Error('Recording exceeds the permitted size.');\n await fs.mkdir(this.directory, { recursive: true, mode: 0o700 });\n const root = await fs.lstat(this.directory);\n if (!root.isDirectory() || root.isSymbolicLink() || (root.mode & 0o077) !== 0)\n throw new Error('Recording storage is not private.');\n output = await fs.open(temporary, 'wx', 0o600);\n const buffer = Buffer.alloc(CHUNK_BYTES);\n let copied = 0;\n while (true) {\n const { bytesRead } = await input.read(buffer, 0, buffer.length, null);\n if (bytesRead === 0) break;\n copied += bytesRead;\n if (copied > MAX_RECORDING_BYTES || copied > stat.size) throw new Error('Recording changed during import.');\n await output.writeFile(buffer.subarray(0, bytesRead));\n }\n if (copied !== stat.size) throw new Error('Recording changed during import.');\n await output.sync();\n await output.close();\n output = undefined;\n if (this.closed || (this.incarnations.get(sessionId) ?? 0) !== incarnation)\n throw new Error('Recording storage is closed.');\n await fs.rename(temporary, destination);\n if (this.closed || (this.incarnations.get(sessionId) ?? 0) !== incarnation) {\n await fs.rm(destination, { force: true });\n throw new Error('Recording storage is closed.');\n }\n const item = {\n sessionId,\n grantId,\n artifactId,\n sizeBytes: copied,\n completedAt: new Date().toISOString(),\n file: destination,\n };\n this.recordings.set(artifactId, item);\n await this.prune();\n await fs.unlink(source);\n await fs.rmdir(grantDirectory);\n return {\n stopped: true,\n artifact: { artifactId, status: 'ready', sizeBytes: copied, completedAt: item.completedAt },\n };\n } catch {\n this.recordings.delete(artifactId);\n await fs.rm(temporary, { force: true });\n await fs.rm(destination, { force: true });\n throw new Error('Native recording import failed.');\n } finally {\n await output?.close();\n await input.close();\n }\n }\n\n async forgetSession(sessionId: string): Promise<void> {\n this.incarnations.set(sessionId, (this.incarnations.get(sessionId) ?? 0) + 1);\n const files: string[] = [];\n for (const [id, item] of this.recordings) {\n if (item.sessionId !== sessionId) continue;\n this.recordings.delete(id);\n files.push(item.file);\n }\n for (const key of this.imports.keys()) if (key.startsWith(`${sessionId}\\0`)) this.imports.delete(key);\n await Promise.all(files.map((file) => fs.rm(file, { force: true })));\n }\n\n async close(): Promise<void> {\n this.closed = true;\n await Promise.allSettled(this.imports.values());\n this.imports.clear();\n this.recordings.clear();\n await fs.rm(this.directory, { recursive: true, force: true });\n }\n\n async read(sessionId: string, artifactId: string, range: string | undefined): Promise<Response> {\n if (this.closed) return new Response(null, { status: 404 });\n await this.prune();\n const item = this.recordings.get(artifactId);\n if (!item || item.sessionId !== sessionId) return new Response(null, { status: 404 });\n const match = /^bytes=(\\d+)-(\\d+)$/u.exec(range ?? '');\n const start = Number(match?.[1]);\n const requestedEnd = Number(match?.[2]);\n if (\n !match ||\n !Number.isSafeInteger(start) ||\n !Number.isSafeInteger(requestedEnd) ||\n start < 0 ||\n start >= item.sizeBytes ||\n requestedEnd < start ||\n requestedEnd - start >= CHUNK_BYTES\n )\n return new Response(null, { status: 416 });\n const end = Math.min(requestedEnd, item.sizeBytes - 1);\n const input = await fs.open(item.file, constants.O_RDONLY | constants.O_NOFOLLOW).catch(() => undefined);\n if (input === undefined) return new Response(null, { status: 410 });\n try {\n const bytes = Buffer.alloc(end - start + 1);\n const { bytesRead } = await input.read(bytes, 0, bytes.length, start);\n if (bytesRead !== bytes.length) return new Response(null, { status: 410 });\n return new Response(bytes, {\n status: 206,\n headers: {\n 'content-type': 'video/mp4',\n 'content-range': `bytes ${start}-${end}/${item.sizeBytes}`,\n 'accept-ranges': 'bytes',\n 'cache-control': 'no-store',\n 'content-length': String(bytes.length),\n },\n });\n } finally {\n await input.close();\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const require_runtime = require("../../../_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
let node_fs = require("node:fs");
|
|
3
|
+
node_fs = require_runtime.__toESM(node_fs, 1);
|
|
4
|
+
let node_path = require("node:path");
|
|
5
|
+
node_path = require_runtime.__toESM(node_path, 1);
|
|
6
|
+
let node_child_process = require("node:child_process");
|
|
7
|
+
let _agimon_ai_po_config_config = require("@agimon-ai/po-config/config");
|
|
8
|
+
let node_util = require("node:util");
|
|
9
|
+
//#region src/builders/server/defaultWorkspace.ts
|
|
10
|
+
const WORKSPACES_FOLDER = "workspace";
|
|
11
|
+
const DIRECTORY_MODE = 448;
|
|
12
|
+
const GIT_TIMEOUT_MS = 15e3;
|
|
13
|
+
const run = (0, node_util.promisify)(node_child_process.execFile);
|
|
14
|
+
/**
|
|
15
|
+
* Creates `~/.po/workspace/<name>` for a workspace added by name alone.
|
|
16
|
+
*
|
|
17
|
+
* The folder is made a git repository because workspace admission resolves a
|
|
18
|
+
* path to its nearest repository root; an empty folder would otherwise resolve
|
|
19
|
+
* to an ancestor such as home, which a `.po` directory marks as a root.
|
|
20
|
+
*/
|
|
21
|
+
async function createDefaultWorkspaceFolder(name, homeDirectory) {
|
|
22
|
+
if (name === "" || node_path.default.basename(name) !== name || name === "." || name === "..") throw new Error(`"${name}" cannot be used as a workspace folder name.`);
|
|
23
|
+
const folder = node_path.default.join((0, _agimon_ai_po_config_config.globalDoomConfigDirectory)(homeDirectory), WORKSPACES_FOLDER, name);
|
|
24
|
+
await node_fs.default.promises.mkdir(folder, {
|
|
25
|
+
recursive: true,
|
|
26
|
+
mode: DIRECTORY_MODE
|
|
27
|
+
});
|
|
28
|
+
if (!node_fs.default.existsSync(node_path.default.join(folder, ".git"))) await run("git", ["init", "--quiet"], {
|
|
29
|
+
cwd: folder,
|
|
30
|
+
timeout: GIT_TIMEOUT_MS,
|
|
31
|
+
windowsHide: true
|
|
32
|
+
});
|
|
33
|
+
return folder;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
exports.createDefaultWorkspaceFolder = createDefaultWorkspaceFolder;
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=defaultWorkspace.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaultWorkspace.cjs","names":["promisify","execFile","path","globalDoomConfigDirectory","fs"],"sources":["../../../../src/builders/server/defaultWorkspace.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/po-config/config';\n\nconst WORKSPACES_FOLDER = 'workspace';\nconst DIRECTORY_MODE = 0o700;\nconst GIT_TIMEOUT_MS = 15_000;\n\nconst run = promisify(execFile);\n\n/**\n * Creates `~/.po/workspace/<name>` for a workspace added by name alone.\n *\n * The folder is made a git repository because workspace admission resolves a\n * path to its nearest repository root; an empty folder would otherwise resolve\n * to an ancestor such as home, which a `.po` directory marks as a root.\n */\nexport async function createDefaultWorkspaceFolder(name: string, homeDirectory: string): Promise<string> {\n if (name === '' || path.basename(name) !== name || name === '.' || name === '..')\n throw new Error(`\"${name}\" cannot be used as a workspace folder name.`);\n const folder = path.join(globalDoomConfigDirectory(homeDirectory), WORKSPACES_FOLDER, name);\n await fs.promises.mkdir(folder, { recursive: true, mode: DIRECTORY_MODE });\n if (!fs.existsSync(path.join(folder, '.git'))) {\n await run('git', ['init', '--quiet'], { cwd: folder, timeout: GIT_TIMEOUT_MS, windowsHide: true });\n }\n return folder;\n}\n"],"mappings":";;;;;;;;;AAOA,MAAM,oBAAoB;AAC1B,MAAM,iBAAiB;AACvB,MAAM,iBAAiB;AAEvB,MAAM,OAAA,GAAMA,UAAAA,UAAAA,CAAUC,mBAAAA,QAAQ;;;;;;;;AAS9B,eAAsB,6BAA6B,MAAc,eAAwC;CACvG,IAAI,SAAS,MAAMC,UAAAA,QAAK,SAAS,IAAI,MAAM,QAAQ,SAAS,OAAO,SAAS,MAC1E,MAAM,IAAI,MAAM,IAAI,KAAK,6CAA6C;CACxE,MAAM,SAASA,UAAAA,QAAK,MAAA,GAAKC,4BAAAA,0BAAAA,CAA0B,aAAa,GAAG,mBAAmB,IAAI;CAC1F,MAAMC,QAAAA,QAAG,SAAS,MAAM,QAAQ;EAAE,WAAW;EAAM,MAAM;CAAe,CAAC;CACzE,IAAI,CAACA,QAAAA,QAAG,WAAWF,UAAAA,QAAK,KAAK,QAAQ,MAAM,CAAC,GAC1C,MAAM,IAAI,OAAO,CAAC,QAAQ,SAAS,GAAG;EAAE,KAAK;EAAQ,SAAS;EAAgB,aAAa;CAAK,CAAC;CAEnG,OAAO;AACT"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { globalDoomConfigDirectory } from "@agimon-ai/po-config/config";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
//#region src/builders/server/defaultWorkspace.ts
|
|
7
|
+
const WORKSPACES_FOLDER = "workspace";
|
|
8
|
+
const DIRECTORY_MODE = 448;
|
|
9
|
+
const GIT_TIMEOUT_MS = 15e3;
|
|
10
|
+
const run = promisify(execFile);
|
|
11
|
+
/**
|
|
12
|
+
* Creates `~/.po/workspace/<name>` for a workspace added by name alone.
|
|
13
|
+
*
|
|
14
|
+
* The folder is made a git repository because workspace admission resolves a
|
|
15
|
+
* path to its nearest repository root; an empty folder would otherwise resolve
|
|
16
|
+
* to an ancestor such as home, which a `.po` directory marks as a root.
|
|
17
|
+
*/
|
|
18
|
+
async function createDefaultWorkspaceFolder(name, homeDirectory) {
|
|
19
|
+
if (name === "" || path.basename(name) !== name || name === "." || name === "..") throw new Error(`"${name}" cannot be used as a workspace folder name.`);
|
|
20
|
+
const folder = path.join(globalDoomConfigDirectory(homeDirectory), WORKSPACES_FOLDER, name);
|
|
21
|
+
await fs.promises.mkdir(folder, {
|
|
22
|
+
recursive: true,
|
|
23
|
+
mode: DIRECTORY_MODE
|
|
24
|
+
});
|
|
25
|
+
if (!fs.existsSync(path.join(folder, ".git"))) await run("git", ["init", "--quiet"], {
|
|
26
|
+
cwd: folder,
|
|
27
|
+
timeout: GIT_TIMEOUT_MS,
|
|
28
|
+
windowsHide: true
|
|
29
|
+
});
|
|
30
|
+
return folder;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { createDefaultWorkspaceFolder };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=defaultWorkspace.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaultWorkspace.mjs","names":[],"sources":["../../../../src/builders/server/defaultWorkspace.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { globalDoomConfigDirectory } from '@agimon-ai/po-config/config';\n\nconst WORKSPACES_FOLDER = 'workspace';\nconst DIRECTORY_MODE = 0o700;\nconst GIT_TIMEOUT_MS = 15_000;\n\nconst run = promisify(execFile);\n\n/**\n * Creates `~/.po/workspace/<name>` for a workspace added by name alone.\n *\n * The folder is made a git repository because workspace admission resolves a\n * path to its nearest repository root; an empty folder would otherwise resolve\n * to an ancestor such as home, which a `.po` directory marks as a root.\n */\nexport async function createDefaultWorkspaceFolder(name: string, homeDirectory: string): Promise<string> {\n if (name === '' || path.basename(name) !== name || name === '.' || name === '..')\n throw new Error(`\"${name}\" cannot be used as a workspace folder name.`);\n const folder = path.join(globalDoomConfigDirectory(homeDirectory), WORKSPACES_FOLDER, name);\n await fs.promises.mkdir(folder, { recursive: true, mode: DIRECTORY_MODE });\n if (!fs.existsSync(path.join(folder, '.git'))) {\n await run('git', ['init', '--quiet'], { cwd: folder, timeout: GIT_TIMEOUT_MS, windowsHide: true });\n }\n return folder;\n}\n"],"mappings":";;;;;;AAOA,MAAM,oBAAoB;AAC1B,MAAM,iBAAiB;AACvB,MAAM,iBAAiB;AAEvB,MAAM,MAAM,UAAU,QAAQ;;;;;;;;AAS9B,eAAsB,6BAA6B,MAAc,eAAwC;CACvG,IAAI,SAAS,MAAM,KAAK,SAAS,IAAI,MAAM,QAAQ,SAAS,OAAO,SAAS,MAC1E,MAAM,IAAI,MAAM,IAAI,KAAK,6CAA6C;CACxE,MAAM,SAAS,KAAK,KAAK,0BAA0B,aAAa,GAAG,mBAAmB,IAAI;CAC1F,MAAM,GAAG,SAAS,MAAM,QAAQ;EAAE,WAAW;EAAM,MAAM;CAAe,CAAC;CACzE,IAAI,CAAC,GAAG,WAAW,KAAK,KAAK,QAAQ,MAAM,CAAC,GAC1C,MAAM,IAAI,OAAO,CAAC,QAAQ,SAAS,GAAG;EAAE,KAAK;EAAQ,SAAS;EAAgB,aAAa;CAAK,CAAC;CAEnG,OAAO;AACT"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const require_runtime = require("../../../_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
let node_fs = require("node:fs");
|
|
3
|
+
node_fs = require_runtime.__toESM(node_fs, 1);
|
|
4
|
+
let node_path = require("node:path");
|
|
5
|
+
node_path = require_runtime.__toESM(node_path, 1);
|
|
6
|
+
/** Quiet time before an idle restart, re-checked once it elapses. */
|
|
7
|
+
const RESTART_DEBOUNCE_MS = 3e4;
|
|
8
|
+
/** Written only by a deliberate idle restart; a crash leaves none. */
|
|
9
|
+
const RECYCLE_FILE = "recycle.json";
|
|
10
|
+
/**
|
|
11
|
+
* Background work a restart would kill, matching po-autostop's gate: runners (open
|
|
12
|
+
* terminals and background commands) and running subagents. Workflow runs are left out,
|
|
13
|
+
* as they are there. Subagents count because a restart ends them mid-run just like a turn.
|
|
14
|
+
*/
|
|
15
|
+
const RESTART_BLOCKING_PROVIDERS = [
|
|
16
|
+
"doom-runner",
|
|
17
|
+
"team-direct-runs",
|
|
18
|
+
"doom-task"
|
|
19
|
+
];
|
|
20
|
+
const RUNNER_PROVIDER = "doom-runner";
|
|
21
|
+
const RUNNER_WORKING_STATUS = "running";
|
|
22
|
+
/** True when a session's background-work snapshot holds work a restart would kill; fails closed. */
|
|
23
|
+
function holdsRestart(snapshot) {
|
|
24
|
+
if (snapshot.errors.some((error) => RESTART_BLOCKING_PROVIDERS.includes(error.provider))) return true;
|
|
25
|
+
return snapshot.items.some((item) => {
|
|
26
|
+
if (!RESTART_BLOCKING_PROVIDERS.includes(item.provider)) return false;
|
|
27
|
+
if (item.provider !== RUNNER_PROVIDER || item.status === void 0) return true;
|
|
28
|
+
return item.status === RUNNER_WORKING_STATUS;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/** Test-only: lowers the superseded-version limit so e2e tests can force an idle restart. */
|
|
32
|
+
const TEST_IDLE_RESTART_LIMIT_ENV = "PO_TEST_IDLE_RESTART_LIMIT";
|
|
33
|
+
/** Test-only: shortens the idle-restart debounce for e2e tests. */
|
|
34
|
+
const TEST_IDLE_RESTART_DEBOUNCE_ENV = "PO_TEST_IDLE_RESTART_DEBOUNCE_MS";
|
|
35
|
+
function parseTestOverride(environment, name, minimum) {
|
|
36
|
+
const raw = environment[name];
|
|
37
|
+
if (raw === void 0 || raw === "") return void 0;
|
|
38
|
+
const value = Number(raw);
|
|
39
|
+
if (!Number.isSafeInteger(value) || value < minimum) throw new Error(`${name} must be an integer of at least ${String(minimum)}; got '${raw}'.`);
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Test-only overrides for the idle restart limit and debounce, read from the environment.
|
|
44
|
+
* Production never sets these; an invalid value fails startup rather than being ignored.
|
|
45
|
+
*/
|
|
46
|
+
function testIdleRestartOverrides(environment) {
|
|
47
|
+
const limit = parseTestOverride(environment, TEST_IDLE_RESTART_LIMIT_ENV, 1);
|
|
48
|
+
const debounceMs = parseTestOverride(environment, TEST_IDLE_RESTART_DEBOUNCE_ENV, 0);
|
|
49
|
+
return {
|
|
50
|
+
...limit === void 0 ? {} : { limit },
|
|
51
|
+
...debounceMs === void 0 ? {} : { debounceMs }
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const defaultTimer = (callback, ms) => {
|
|
55
|
+
const timer = setTimeout(callback, ms);
|
|
56
|
+
timer.unref();
|
|
57
|
+
return () => clearTimeout(timer);
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Restarts the server at idle once old package versions pile up in memory.
|
|
61
|
+
*
|
|
62
|
+
* Native `import()` never unloads a module, so every superseded URL stays resident. At
|
|
63
|
+
* `limit` the restart is pending; it fires after `debounceMs` with no busy work, and the
|
|
64
|
+
* busy check runs again when the debounce elapses, so a turn that started meanwhile
|
|
65
|
+
* defers it to the next poke.
|
|
66
|
+
*/
|
|
67
|
+
function createIdleRestart(options) {
|
|
68
|
+
const limit = options.limit ?? 8;
|
|
69
|
+
const debounceMs = options.debounceMs ?? 3e4;
|
|
70
|
+
const setTimer = options.setTimer ?? defaultTimer;
|
|
71
|
+
const superseded = /* @__PURE__ */ new Set();
|
|
72
|
+
let cancel;
|
|
73
|
+
let fired = false;
|
|
74
|
+
const pending = () => superseded.size >= limit;
|
|
75
|
+
const poke = () => {
|
|
76
|
+
if (!pending() || fired) return;
|
|
77
|
+
cancel?.();
|
|
78
|
+
cancel = setTimer(() => {
|
|
79
|
+
cancel = void 0;
|
|
80
|
+
options.busy().then((busy) => {
|
|
81
|
+
if (busy || fired) return;
|
|
82
|
+
fired = true;
|
|
83
|
+
options.restart();
|
|
84
|
+
}).catch((error) => options.onError?.(error));
|
|
85
|
+
}, debounceMs);
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
supersede(urls) {
|
|
89
|
+
for (const url of urls) superseded.add(url);
|
|
90
|
+
poke();
|
|
91
|
+
},
|
|
92
|
+
poke,
|
|
93
|
+
pending,
|
|
94
|
+
dispose() {
|
|
95
|
+
cancel?.();
|
|
96
|
+
cancel = void 0;
|
|
97
|
+
fired = true;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function writeRecycleFile(directory, sessionIds) {
|
|
102
|
+
node_fs.default.mkdirSync(directory, { recursive: true });
|
|
103
|
+
const target = node_path.default.join(directory, RECYCLE_FILE);
|
|
104
|
+
const staging = `${target}.${String(process.pid)}.tmp`;
|
|
105
|
+
node_fs.default.writeFileSync(staging, `${JSON.stringify({ sessionIds }, null, 2)}\n`);
|
|
106
|
+
node_fs.default.renameSync(staging, target);
|
|
107
|
+
}
|
|
108
|
+
/** The session ids a deliberate restart left open; empty when there is no valid file. */
|
|
109
|
+
function readRecycleFile(directory) {
|
|
110
|
+
try {
|
|
111
|
+
const ids = JSON.parse(node_fs.default.readFileSync(node_path.default.join(directory, RECYCLE_FILE), "utf8"))?.sessionIds;
|
|
112
|
+
return Array.isArray(ids) ? ids.filter((id) => typeof id === "string") : [];
|
|
113
|
+
} catch {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function removeRecycleFile(directory) {
|
|
118
|
+
node_fs.default.rmSync(node_path.default.join(directory, RECYCLE_FILE), { force: true });
|
|
119
|
+
}
|
|
120
|
+
/** Reopens exactly the recycled sessions, then deletes the file whatever the outcome. */
|
|
121
|
+
async function reopenRecycledSessions(directory, reopen, onError) {
|
|
122
|
+
const ids = readRecycleFile(directory);
|
|
123
|
+
const reopened = [];
|
|
124
|
+
try {
|
|
125
|
+
for (const id of ids) try {
|
|
126
|
+
await reopen(id);
|
|
127
|
+
reopened.push(id);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
onError(id, error);
|
|
130
|
+
}
|
|
131
|
+
} finally {
|
|
132
|
+
removeRecycleFile(directory);
|
|
133
|
+
}
|
|
134
|
+
return reopened;
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
exports.RECYCLE_FILE = RECYCLE_FILE;
|
|
138
|
+
exports.RESTART_DEBOUNCE_MS = RESTART_DEBOUNCE_MS;
|
|
139
|
+
exports.TEST_IDLE_RESTART_DEBOUNCE_ENV = TEST_IDLE_RESTART_DEBOUNCE_ENV;
|
|
140
|
+
exports.TEST_IDLE_RESTART_LIMIT_ENV = TEST_IDLE_RESTART_LIMIT_ENV;
|
|
141
|
+
exports.createIdleRestart = createIdleRestart;
|
|
142
|
+
exports.holdsRestart = holdsRestart;
|
|
143
|
+
exports.readRecycleFile = readRecycleFile;
|
|
144
|
+
exports.removeRecycleFile = removeRecycleFile;
|
|
145
|
+
exports.reopenRecycledSessions = reopenRecycledSessions;
|
|
146
|
+
exports.testIdleRestartOverrides = testIdleRestartOverrides;
|
|
147
|
+
exports.writeRecycleFile = writeRecycleFile;
|
|
148
|
+
|
|
149
|
+
//# sourceMappingURL=idleRestart.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idleRestart.cjs","names":["path","fs"],"sources":["../../../../src/builders/server/idleRestart.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { DoomBackgroundWorkSnapshot } from '@agimon-ai/po-core/backgroundWork';\n\n/** Superseded package versions held in memory before a restart is pending. */\nexport const SUPERSEDED_LIMIT = 8;\n/** Quiet time before an idle restart, re-checked once it elapses. */\nexport const RESTART_DEBOUNCE_MS = 30_000;\n/** Written only by a deliberate idle restart; a crash leaves none. */\nexport const RECYCLE_FILE = 'recycle.json';\n/** Temporary failure: a supervisor restarts the server on the same port and token. */\nexport const RESTART_EXIT_CODE = 75;\n\n/**\n * Background work a restart would kill, matching po-autostop's gate: runners (open\n * terminals and background commands) and running subagents. Workflow runs are left out,\n * as they are there. Subagents count because a restart ends them mid-run just like a turn.\n */\nconst RESTART_BLOCKING_PROVIDERS: readonly string[] = ['doom-runner', 'team-direct-runs', 'doom-task'];\nconst RUNNER_PROVIDER = 'doom-runner';\nconst RUNNER_WORKING_STATUS = 'running';\n\n/** True when a session's background-work snapshot holds work a restart would kill; fails closed. */\nexport function holdsRestart(snapshot: DoomBackgroundWorkSnapshot): boolean {\n if (snapshot.errors.some((error) => RESTART_BLOCKING_PROVIDERS.includes(error.provider))) return true;\n return snapshot.items.some((item) => {\n if (!RESTART_BLOCKING_PROVIDERS.includes(item.provider)) return false;\n // A runner item without a status is not known to be done.\n if (item.provider !== RUNNER_PROVIDER || item.status === undefined) return true;\n return item.status === RUNNER_WORKING_STATUS;\n });\n}\n\n/** Test-only: lowers the superseded-version limit so e2e tests can force an idle restart. */\nexport const TEST_IDLE_RESTART_LIMIT_ENV = 'PO_TEST_IDLE_RESTART_LIMIT';\n/** Test-only: shortens the idle-restart debounce for e2e tests. */\nexport const TEST_IDLE_RESTART_DEBOUNCE_ENV = 'PO_TEST_IDLE_RESTART_DEBOUNCE_MS';\n\nfunction parseTestOverride(environment: NodeJS.ProcessEnv, name: string, minimum: number): number | undefined {\n const raw = environment[name];\n if (raw === undefined || raw === '') return undefined;\n const value = Number(raw);\n if (!Number.isSafeInteger(value) || value < minimum)\n throw new Error(`${name} must be an integer of at least ${String(minimum)}; got '${raw}'.`);\n return value;\n}\n\n/**\n * Test-only overrides for the idle restart limit and debounce, read from the environment.\n * Production never sets these; an invalid value fails startup rather than being ignored.\n */\nexport function testIdleRestartOverrides(\n environment: NodeJS.ProcessEnv,\n): Pick<IdleRestartOptions, 'limit' | 'debounceMs'> {\n const limit = parseTestOverride(environment, TEST_IDLE_RESTART_LIMIT_ENV, 1);\n const debounceMs = parseTestOverride(environment, TEST_IDLE_RESTART_DEBOUNCE_ENV, 0);\n return { ...(limit === undefined ? {} : { limit }), ...(debounceMs === undefined ? {} : { debounceMs }) };\n}\n\nexport interface IdleRestartOptions {\n /** True while any session runs a turn or a compaction, or a terminal is open. */\n readonly busy: () => Promise<boolean>;\n /** Starts the restart. Called at most once. */\n readonly restart: () => void;\n readonly limit?: number;\n readonly debounceMs?: number;\n readonly setTimer?: (callback: () => void, ms: number) => () => void;\n readonly onError?: (error: unknown) => void;\n}\n\nexport interface IdleRestart {\n /** Records package URLs replaced by newer ones; each URL counts once. */\n supersede(urls: readonly string[]): void;\n /** Re-arms the debounce when a restart is pending; call when activity may have stopped. */\n poke(): void;\n readonly pending: () => boolean;\n dispose(): void;\n}\n\nconst defaultTimer = (callback: () => void, ms: number): (() => void) => {\n const timer = setTimeout(callback, ms);\n timer.unref();\n return () => clearTimeout(timer);\n};\n\n/**\n * Restarts the server at idle once old package versions pile up in memory.\n *\n * Native `import()` never unloads a module, so every superseded URL stays resident. At\n * `limit` the restart is pending; it fires after `debounceMs` with no busy work, and the\n * busy check runs again when the debounce elapses, so a turn that started meanwhile\n * defers it to the next poke.\n */\nexport function createIdleRestart(options: IdleRestartOptions): IdleRestart {\n const limit = options.limit ?? SUPERSEDED_LIMIT;\n const debounceMs = options.debounceMs ?? RESTART_DEBOUNCE_MS;\n const setTimer = options.setTimer ?? defaultTimer;\n const superseded = new Set<string>();\n let cancel: (() => void) | undefined;\n let fired = false;\n const pending = () => superseded.size >= limit;\n const poke = (): void => {\n if (!pending() || fired) return;\n cancel?.();\n cancel = setTimer(() => {\n cancel = undefined;\n void options\n .busy()\n .then((busy) => {\n if (busy || fired) return;\n fired = true;\n options.restart();\n })\n .catch((error: unknown) => options.onError?.(error));\n }, debounceMs);\n };\n return {\n supersede(urls) {\n for (const url of urls) superseded.add(url);\n poke();\n },\n poke,\n pending,\n dispose() {\n cancel?.();\n cancel = undefined;\n fired = true;\n },\n };\n}\n\nexport function writeRecycleFile(directory: string, sessionIds: readonly string[]): void {\n fs.mkdirSync(directory, { recursive: true });\n const target = path.join(directory, RECYCLE_FILE);\n const staging = `${target}.${String(process.pid)}.tmp`;\n fs.writeFileSync(staging, `${JSON.stringify({ sessionIds }, null, 2)}\\n`);\n fs.renameSync(staging, target);\n}\n\n/** The session ids a deliberate restart left open; empty when there is no valid file. */\nexport function readRecycleFile(directory: string): string[] {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(directory, RECYCLE_FILE), 'utf8')) as unknown;\n const ids = (parsed as { sessionIds?: unknown } | null)?.sessionIds;\n return Array.isArray(ids) ? ids.filter((id): id is string => typeof id === 'string') : [];\n } catch {\n // Missing (the usual case, including after a crash) or unreadable: nothing to reopen.\n return [];\n }\n}\n\nexport function removeRecycleFile(directory: string): void {\n fs.rmSync(path.join(directory, RECYCLE_FILE), { force: true });\n}\n\n/** Reopens exactly the recycled sessions, then deletes the file whatever the outcome. */\nexport async function reopenRecycledSessions(\n directory: string,\n reopen: (sessionId: string) => Promise<void>,\n onError: (sessionId: string, error: unknown) => void,\n): Promise<string[]> {\n const ids = readRecycleFile(directory);\n const reopened: string[] = [];\n try {\n for (const id of ids) {\n try {\n await reopen(id);\n reopened.push(id);\n } catch (error) {\n onError(id, error);\n }\n }\n } finally {\n removeRecycleFile(directory);\n }\n return reopened;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/** Quiet time before an idle restart, re-checked once it elapses. */
|
|
4
|
+
const RESTART_DEBOUNCE_MS = 3e4;
|
|
5
|
+
/** Written only by a deliberate idle restart; a crash leaves none. */
|
|
6
|
+
const RECYCLE_FILE = "recycle.json";
|
|
7
|
+
/**
|
|
8
|
+
* Background work a restart would kill, matching po-autostop's gate: runners (open
|
|
9
|
+
* terminals and background commands) and running subagents. Workflow runs are left out,
|
|
10
|
+
* as they are there. Subagents count because a restart ends them mid-run just like a turn.
|
|
11
|
+
*/
|
|
12
|
+
const RESTART_BLOCKING_PROVIDERS = [
|
|
13
|
+
"doom-runner",
|
|
14
|
+
"team-direct-runs",
|
|
15
|
+
"doom-task"
|
|
16
|
+
];
|
|
17
|
+
const RUNNER_PROVIDER = "doom-runner";
|
|
18
|
+
const RUNNER_WORKING_STATUS = "running";
|
|
19
|
+
/** True when a session's background-work snapshot holds work a restart would kill; fails closed. */
|
|
20
|
+
function holdsRestart(snapshot) {
|
|
21
|
+
if (snapshot.errors.some((error) => RESTART_BLOCKING_PROVIDERS.includes(error.provider))) return true;
|
|
22
|
+
return snapshot.items.some((item) => {
|
|
23
|
+
if (!RESTART_BLOCKING_PROVIDERS.includes(item.provider)) return false;
|
|
24
|
+
if (item.provider !== RUNNER_PROVIDER || item.status === void 0) return true;
|
|
25
|
+
return item.status === RUNNER_WORKING_STATUS;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** Test-only: lowers the superseded-version limit so e2e tests can force an idle restart. */
|
|
29
|
+
const TEST_IDLE_RESTART_LIMIT_ENV = "PO_TEST_IDLE_RESTART_LIMIT";
|
|
30
|
+
/** Test-only: shortens the idle-restart debounce for e2e tests. */
|
|
31
|
+
const TEST_IDLE_RESTART_DEBOUNCE_ENV = "PO_TEST_IDLE_RESTART_DEBOUNCE_MS";
|
|
32
|
+
function parseTestOverride(environment, name, minimum) {
|
|
33
|
+
const raw = environment[name];
|
|
34
|
+
if (raw === void 0 || raw === "") return void 0;
|
|
35
|
+
const value = Number(raw);
|
|
36
|
+
if (!Number.isSafeInteger(value) || value < minimum) throw new Error(`${name} must be an integer of at least ${String(minimum)}; got '${raw}'.`);
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Test-only overrides for the idle restart limit and debounce, read from the environment.
|
|
41
|
+
* Production never sets these; an invalid value fails startup rather than being ignored.
|
|
42
|
+
*/
|
|
43
|
+
function testIdleRestartOverrides(environment) {
|
|
44
|
+
const limit = parseTestOverride(environment, TEST_IDLE_RESTART_LIMIT_ENV, 1);
|
|
45
|
+
const debounceMs = parseTestOverride(environment, TEST_IDLE_RESTART_DEBOUNCE_ENV, 0);
|
|
46
|
+
return {
|
|
47
|
+
...limit === void 0 ? {} : { limit },
|
|
48
|
+
...debounceMs === void 0 ? {} : { debounceMs }
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const defaultTimer = (callback, ms) => {
|
|
52
|
+
const timer = setTimeout(callback, ms);
|
|
53
|
+
timer.unref();
|
|
54
|
+
return () => clearTimeout(timer);
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Restarts the server at idle once old package versions pile up in memory.
|
|
58
|
+
*
|
|
59
|
+
* Native `import()` never unloads a module, so every superseded URL stays resident. At
|
|
60
|
+
* `limit` the restart is pending; it fires after `debounceMs` with no busy work, and the
|
|
61
|
+
* busy check runs again when the debounce elapses, so a turn that started meanwhile
|
|
62
|
+
* defers it to the next poke.
|
|
63
|
+
*/
|
|
64
|
+
function createIdleRestart(options) {
|
|
65
|
+
const limit = options.limit ?? 8;
|
|
66
|
+
const debounceMs = options.debounceMs ?? 3e4;
|
|
67
|
+
const setTimer = options.setTimer ?? defaultTimer;
|
|
68
|
+
const superseded = /* @__PURE__ */ new Set();
|
|
69
|
+
let cancel;
|
|
70
|
+
let fired = false;
|
|
71
|
+
const pending = () => superseded.size >= limit;
|
|
72
|
+
const poke = () => {
|
|
73
|
+
if (!pending() || fired) return;
|
|
74
|
+
cancel?.();
|
|
75
|
+
cancel = setTimer(() => {
|
|
76
|
+
cancel = void 0;
|
|
77
|
+
options.busy().then((busy) => {
|
|
78
|
+
if (busy || fired) return;
|
|
79
|
+
fired = true;
|
|
80
|
+
options.restart();
|
|
81
|
+
}).catch((error) => options.onError?.(error));
|
|
82
|
+
}, debounceMs);
|
|
83
|
+
};
|
|
84
|
+
return {
|
|
85
|
+
supersede(urls) {
|
|
86
|
+
for (const url of urls) superseded.add(url);
|
|
87
|
+
poke();
|
|
88
|
+
},
|
|
89
|
+
poke,
|
|
90
|
+
pending,
|
|
91
|
+
dispose() {
|
|
92
|
+
cancel?.();
|
|
93
|
+
cancel = void 0;
|
|
94
|
+
fired = true;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function writeRecycleFile(directory, sessionIds) {
|
|
99
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
100
|
+
const target = path.join(directory, RECYCLE_FILE);
|
|
101
|
+
const staging = `${target}.${String(process.pid)}.tmp`;
|
|
102
|
+
fs.writeFileSync(staging, `${JSON.stringify({ sessionIds }, null, 2)}\n`);
|
|
103
|
+
fs.renameSync(staging, target);
|
|
104
|
+
}
|
|
105
|
+
/** The session ids a deliberate restart left open; empty when there is no valid file. */
|
|
106
|
+
function readRecycleFile(directory) {
|
|
107
|
+
try {
|
|
108
|
+
const ids = JSON.parse(fs.readFileSync(path.join(directory, RECYCLE_FILE), "utf8"))?.sessionIds;
|
|
109
|
+
return Array.isArray(ids) ? ids.filter((id) => typeof id === "string") : [];
|
|
110
|
+
} catch {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function removeRecycleFile(directory) {
|
|
115
|
+
fs.rmSync(path.join(directory, RECYCLE_FILE), { force: true });
|
|
116
|
+
}
|
|
117
|
+
/** Reopens exactly the recycled sessions, then deletes the file whatever the outcome. */
|
|
118
|
+
async function reopenRecycledSessions(directory, reopen, onError) {
|
|
119
|
+
const ids = readRecycleFile(directory);
|
|
120
|
+
const reopened = [];
|
|
121
|
+
try {
|
|
122
|
+
for (const id of ids) try {
|
|
123
|
+
await reopen(id);
|
|
124
|
+
reopened.push(id);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
onError(id, error);
|
|
127
|
+
}
|
|
128
|
+
} finally {
|
|
129
|
+
removeRecycleFile(directory);
|
|
130
|
+
}
|
|
131
|
+
return reopened;
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
export { RECYCLE_FILE, RESTART_DEBOUNCE_MS, TEST_IDLE_RESTART_DEBOUNCE_ENV, TEST_IDLE_RESTART_LIMIT_ENV, createIdleRestart, holdsRestart, readRecycleFile, removeRecycleFile, reopenRecycledSessions, testIdleRestartOverrides, writeRecycleFile };
|
|
135
|
+
|
|
136
|
+
//# sourceMappingURL=idleRestart.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idleRestart.mjs","names":[],"sources":["../../../../src/builders/server/idleRestart.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { DoomBackgroundWorkSnapshot } from '@agimon-ai/po-core/backgroundWork';\n\n/** Superseded package versions held in memory before a restart is pending. */\nexport const SUPERSEDED_LIMIT = 8;\n/** Quiet time before an idle restart, re-checked once it elapses. */\nexport const RESTART_DEBOUNCE_MS = 30_000;\n/** Written only by a deliberate idle restart; a crash leaves none. */\nexport const RECYCLE_FILE = 'recycle.json';\n/** Temporary failure: a supervisor restarts the server on the same port and token. */\nexport const RESTART_EXIT_CODE = 75;\n\n/**\n * Background work a restart would kill, matching po-autostop's gate: runners (open\n * terminals and background commands) and running subagents. Workflow runs are left out,\n * as they are there. Subagents count because a restart ends them mid-run just like a turn.\n */\nconst RESTART_BLOCKING_PROVIDERS: readonly string[] = ['doom-runner', 'team-direct-runs', 'doom-task'];\nconst RUNNER_PROVIDER = 'doom-runner';\nconst RUNNER_WORKING_STATUS = 'running';\n\n/** True when a session's background-work snapshot holds work a restart would kill; fails closed. */\nexport function holdsRestart(snapshot: DoomBackgroundWorkSnapshot): boolean {\n if (snapshot.errors.some((error) => RESTART_BLOCKING_PROVIDERS.includes(error.provider))) return true;\n return snapshot.items.some((item) => {\n if (!RESTART_BLOCKING_PROVIDERS.includes(item.provider)) return false;\n // A runner item without a status is not known to be done.\n if (item.provider !== RUNNER_PROVIDER || item.status === undefined) return true;\n return item.status === RUNNER_WORKING_STATUS;\n });\n}\n\n/** Test-only: lowers the superseded-version limit so e2e tests can force an idle restart. */\nexport const TEST_IDLE_RESTART_LIMIT_ENV = 'PO_TEST_IDLE_RESTART_LIMIT';\n/** Test-only: shortens the idle-restart debounce for e2e tests. */\nexport const TEST_IDLE_RESTART_DEBOUNCE_ENV = 'PO_TEST_IDLE_RESTART_DEBOUNCE_MS';\n\nfunction parseTestOverride(environment: NodeJS.ProcessEnv, name: string, minimum: number): number | undefined {\n const raw = environment[name];\n if (raw === undefined || raw === '') return undefined;\n const value = Number(raw);\n if (!Number.isSafeInteger(value) || value < minimum)\n throw new Error(`${name} must be an integer of at least ${String(minimum)}; got '${raw}'.`);\n return value;\n}\n\n/**\n * Test-only overrides for the idle restart limit and debounce, read from the environment.\n * Production never sets these; an invalid value fails startup rather than being ignored.\n */\nexport function testIdleRestartOverrides(\n environment: NodeJS.ProcessEnv,\n): Pick<IdleRestartOptions, 'limit' | 'debounceMs'> {\n const limit = parseTestOverride(environment, TEST_IDLE_RESTART_LIMIT_ENV, 1);\n const debounceMs = parseTestOverride(environment, TEST_IDLE_RESTART_DEBOUNCE_ENV, 0);\n return { ...(limit === undefined ? {} : { limit }), ...(debounceMs === undefined ? {} : { debounceMs }) };\n}\n\nexport interface IdleRestartOptions {\n /** True while any session runs a turn or a compaction, or a terminal is open. */\n readonly busy: () => Promise<boolean>;\n /** Starts the restart. Called at most once. */\n readonly restart: () => void;\n readonly limit?: number;\n readonly debounceMs?: number;\n readonly setTimer?: (callback: () => void, ms: number) => () => void;\n readonly onError?: (error: unknown) => void;\n}\n\nexport interface IdleRestart {\n /** Records package URLs replaced by newer ones; each URL counts once. */\n supersede(urls: readonly string[]): void;\n /** Re-arms the debounce when a restart is pending; call when activity may have stopped. */\n poke(): void;\n readonly pending: () => boolean;\n dispose(): void;\n}\n\nconst defaultTimer = (callback: () => void, ms: number): (() => void) => {\n const timer = setTimeout(callback, ms);\n timer.unref();\n return () => clearTimeout(timer);\n};\n\n/**\n * Restarts the server at idle once old package versions pile up in memory.\n *\n * Native `import()` never unloads a module, so every superseded URL stays resident. At\n * `limit` the restart is pending; it fires after `debounceMs` with no busy work, and the\n * busy check runs again when the debounce elapses, so a turn that started meanwhile\n * defers it to the next poke.\n */\nexport function createIdleRestart(options: IdleRestartOptions): IdleRestart {\n const limit = options.limit ?? SUPERSEDED_LIMIT;\n const debounceMs = options.debounceMs ?? RESTART_DEBOUNCE_MS;\n const setTimer = options.setTimer ?? defaultTimer;\n const superseded = new Set<string>();\n let cancel: (() => void) | undefined;\n let fired = false;\n const pending = () => superseded.size >= limit;\n const poke = (): void => {\n if (!pending() || fired) return;\n cancel?.();\n cancel = setTimer(() => {\n cancel = undefined;\n void options\n .busy()\n .then((busy) => {\n if (busy || fired) return;\n fired = true;\n options.restart();\n })\n .catch((error: unknown) => options.onError?.(error));\n }, debounceMs);\n };\n return {\n supersede(urls) {\n for (const url of urls) superseded.add(url);\n poke();\n },\n poke,\n pending,\n dispose() {\n cancel?.();\n cancel = undefined;\n fired = true;\n },\n };\n}\n\nexport function writeRecycleFile(directory: string, sessionIds: readonly string[]): void {\n fs.mkdirSync(directory, { recursive: true });\n const target = path.join(directory, RECYCLE_FILE);\n const staging = `${target}.${String(process.pid)}.tmp`;\n fs.writeFileSync(staging, `${JSON.stringify({ sessionIds }, null, 2)}\\n`);\n fs.renameSync(staging, target);\n}\n\n/** The session ids a deliberate restart left open; empty when there is no valid file. */\nexport function readRecycleFile(directory: string): string[] {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(directory, RECYCLE_FILE), 'utf8')) as unknown;\n const ids = (parsed as { sessionIds?: unknown } | null)?.sessionIds;\n return Array.isArray(ids) ? ids.filter((id): id is string => typeof id === 'string') : [];\n } catch {\n // Missing (the usual case, including after a crash) or unreadable: nothing to reopen.\n return [];\n }\n}\n\nexport function removeRecycleFile(directory: string): void {\n fs.rmSync(path.join(directory, RECYCLE_FILE), { force: true });\n}\n\n/** Reopens exactly the recycled sessions, then deletes the file whatever the outcome. */\nexport async function reopenRecycledSessions(\n directory: string,\n reopen: (sessionId: string) => Promise<void>,\n onError: (sessionId: string, error: unknown) => void,\n): Promise<string[]> {\n const ids = readRecycleFile(directory);\n const reopened: string[] = [];\n try {\n for (const id of ids) {\n try {\n await reopen(id);\n reopened.push(id);\n } catch (error) {\n onError(id, error);\n }\n }\n } finally {\n removeRecycleFile(directory);\n }\n return reopened;\n}\n"],"mappings":"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"}
|