dodo-mcp 1.0.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 +425 -0
- package/dist/auth/adapter.js +43 -0
- package/dist/auth/adapter.js.map +1 -0
- package/dist/auth/clientDeletion.js +45 -0
- package/dist/auth/clientDeletion.js.map +1 -0
- package/dist/auth/clients.js +55 -0
- package/dist/auth/clients.js.map +1 -0
- package/dist/auth/constants.js +10 -0
- package/dist/auth/constants.js.map +1 -0
- package/dist/auth/interactions.js +184 -0
- package/dist/auth/interactions.js.map +1 -0
- package/dist/auth/keys.js +56 -0
- package/dist/auth/keys.js.map +1 -0
- package/dist/auth/provider.js +131 -0
- package/dist/auth/provider.js.map +1 -0
- package/dist/auth/verifier.js +74 -0
- package/dist/auth/verifier.js.map +1 -0
- package/dist/cli/kill.js +124 -0
- package/dist/cli/kill.js.map +1 -0
- package/dist/cli/main.js +1393 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/menu.js +127 -0
- package/dist/cli/menu.js.map +1 -0
- package/dist/cli/terminal.js +30 -0
- package/dist/cli/terminal.js.map +1 -0
- package/dist/config/globalConfig.js +144 -0
- package/dist/config/globalConfig.js.map +1 -0
- package/dist/config/limits.js +42 -0
- package/dist/config/limits.js.map +1 -0
- package/dist/config/paths.js +94 -0
- package/dist/config/paths.js.map +1 -0
- package/dist/config/projectConfig.js +63 -0
- package/dist/config/projectConfig.js.map +1 -0
- package/dist/config/stateImport.js +306 -0
- package/dist/config/stateImport.js.map +1 -0
- package/dist/config/tunnelConfig.js +23 -0
- package/dist/config/tunnelConfig.js.map +1 -0
- package/dist/errors.js +82 -0
- package/dist/errors.js.map +1 -0
- package/dist/evaluation/contracts.js +86 -0
- package/dist/evaluation/contracts.js.map +1 -0
- package/dist/evaluation/dodoBench.js +69 -0
- package/dist/evaluation/dodoBench.js.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/ipc/authentication.js +117 -0
- package/dist/ipc/authentication.js.map +1 -0
- package/dist/ipc/client.js +86 -0
- package/dist/ipc/client.js.map +1 -0
- package/dist/ipc/installationClient.js +83 -0
- package/dist/ipc/installationClient.js.map +1 -0
- package/dist/ipc/ownerControl.js +46 -0
- package/dist/ipc/ownerControl.js.map +1 -0
- package/dist/ipc/protocol.js +2 -0
- package/dist/ipc/protocol.js.map +1 -0
- package/dist/ipc/server.js +108 -0
- package/dist/ipc/server.js.map +1 -0
- package/dist/platform/execResolve.js +64 -0
- package/dist/platform/execResolve.js.map +1 -0
- package/dist/platform/fileIdentity.js +26 -0
- package/dist/platform/fileIdentity.js.map +1 -0
- package/dist/platform/fsRetry.js +22 -0
- package/dist/platform/fsRetry.js.map +1 -0
- package/dist/platform/pathPolicy.js +29 -0
- package/dist/platform/pathPolicy.js.map +1 -0
- package/dist/platform/privateFs.js +116 -0
- package/dist/platform/privateFs.js.map +1 -0
- package/dist/platform/processTree.js +31 -0
- package/dist/platform/processTree.js.map +1 -0
- package/dist/platform/shell.js +74 -0
- package/dist/platform/shell.js.map +1 -0
- package/dist/platform/speech.js +94 -0
- package/dist/platform/speech.js.map +1 -0
- package/dist/platform/system.js +28 -0
- package/dist/platform/system.js.map +1 -0
- package/dist/platform/windowsSandbox.js +129 -0
- package/dist/platform/windowsSandbox.js.map +1 -0
- package/dist/projects/federation.js +312 -0
- package/dist/projects/federation.js.map +1 -0
- package/dist/projects/registry.js +291 -0
- package/dist/projects/registry.js.map +1 -0
- package/dist/security/accessMode.js +19 -0
- package/dist/security/accessMode.js.map +1 -0
- package/dist/security/approvals.js +56 -0
- package/dist/security/approvals.js.map +1 -0
- package/dist/security/env.js +57 -0
- package/dist/security/env.js.map +1 -0
- package/dist/security/mutationQueue.js +78 -0
- package/dist/security/mutationQueue.js.map +1 -0
- package/dist/security/policy.js +22 -0
- package/dist/security/policy.js.map +1 -0
- package/dist/security/projectAuthority.js +22 -0
- package/dist/security/projectAuthority.js.map +1 -0
- package/dist/security/redact.js +30 -0
- package/dist/security/redact.js.map +1 -0
- package/dist/server/aiAdmin.js +201 -0
- package/dist/server/aiAdmin.js.map +1 -0
- package/dist/server/appServer.js +461 -0
- package/dist/server/appServer.js.map +1 -0
- package/dist/server/bootstrap.js +185 -0
- package/dist/server/bootstrap.js.map +1 -0
- package/dist/server/configUi/app.css +347 -0
- package/dist/server/configUi/app.js +958 -0
- package/dist/server/configUi/index.html +379 -0
- package/dist/server/configUi/ui/alerts.js +92 -0
- package/dist/server/configUi/ui/dom.js +19 -0
- package/dist/server/configUi/ui/tooltips.js +115 -0
- package/dist/server/configUi/vendor/VERSION.txt +1 -0
- package/dist/server/configUi/vendor/sweetalert2.min.css +1 -0
- package/dist/server/configUi/vendor/sweetalert2.min.js +5 -0
- package/dist/server/configUi/workbench.css +62 -0
- package/dist/server/configUi/workbench.js +394 -0
- package/dist/server/installationRuntime.js +155 -0
- package/dist/server/installationRuntime.js.map +1 -0
- package/dist/server/instructions.js +17 -0
- package/dist/server/instructions.js.map +1 -0
- package/dist/server/ipcDispatch.js +225 -0
- package/dist/server/ipcDispatch.js.map +1 -0
- package/dist/server/localConfig.js +561 -0
- package/dist/server/localConfig.js.map +1 -0
- package/dist/server/optionalServices.js +30 -0
- package/dist/server/optionalServices.js.map +1 -0
- package/dist/server/projectLease.js +46 -0
- package/dist/server/projectLease.js.map +1 -0
- package/dist/server/stdioServer.js +75 -0
- package/dist/server/stdioServer.js.map +1 -0
- package/dist/server/version.js +13 -0
- package/dist/server/version.js.map +1 -0
- package/dist/server/workspaceHost.js +194 -0
- package/dist/server/workspaceHost.js.map +1 -0
- package/dist/services/agent/agentService.js +598 -0
- package/dist/services/agent/agentService.js.map +1 -0
- package/dist/services/agent/contracts.js +102 -0
- package/dist/services/agent/contracts.js.map +1 -0
- package/dist/services/ai/adapters.js +287 -0
- package/dist/services/ai/adapters.js.map +1 -0
- package/dist/services/ai/contracts.js +39 -0
- package/dist/services/ai/contracts.js.map +1 -0
- package/dist/services/ai/credentials.js +77 -0
- package/dist/services/ai/credentials.js.map +1 -0
- package/dist/services/ai/network.js +113 -0
- package/dist/services/ai/network.js.map +1 -0
- package/dist/services/ai/settings.js +178 -0
- package/dist/services/ai/settings.js.map +1 -0
- package/dist/services/ai/subagents.js +468 -0
- package/dist/services/ai/subagents.js.map +1 -0
- package/dist/services/assistance/analysis.js +352 -0
- package/dist/services/assistance/analysis.js.map +1 -0
- package/dist/services/assistance/contracts.js +54 -0
- package/dist/services/assistance/contracts.js.map +1 -0
- package/dist/services/assistance/verification.js +274 -0
- package/dist/services/assistance/verification.js.map +1 -0
- package/dist/services/brain/brainParser.js +97 -0
- package/dist/services/brain/brainParser.js.map +1 -0
- package/dist/services/brain/brainService.js +733 -0
- package/dist/services/brain/brainService.js.map +1 -0
- package/dist/services/brain/brainWorker.js +212 -0
- package/dist/services/brain/brainWorker.js.map +1 -0
- package/dist/services/brain/contracts.js +154 -0
- package/dist/services/brain/contracts.js.map +1 -0
- package/dist/services/brain/protocol.js +2 -0
- package/dist/services/brain/protocol.js.map +1 -0
- package/dist/services/changes/applier.js +544 -0
- package/dist/services/changes/applier.js.map +1 -0
- package/dist/services/changes/contentBudget.js +11 -0
- package/dist/services/changes/contentBudget.js.map +1 -0
- package/dist/services/changes/planner.js +284 -0
- package/dist/services/changes/planner.js.map +1 -0
- package/dist/services/changes/types.js +3 -0
- package/dist/services/changes/types.js.map +1 -0
- package/dist/services/context/contextEngine.js +746 -0
- package/dist/services/context/contextEngine.js.map +1 -0
- package/dist/services/context/contracts.js +88 -0
- package/dist/services/context/contracts.js.map +1 -0
- package/dist/services/desktop/desktopPolicy.js +76 -0
- package/dist/services/desktop/desktopPolicy.js.map +1 -0
- package/dist/services/desktop/desktopService.js +130 -0
- package/dist/services/desktop/desktopService.js.map +1 -0
- package/dist/services/desktop/nativeBackend.js +174 -0
- package/dist/services/desktop/nativeBackend.js.map +1 -0
- package/dist/services/desktop/protocol.js +32 -0
- package/dist/services/desktop/protocol.js.map +1 -0
- package/dist/services/files/readService.js +136 -0
- package/dist/services/files/readService.js.map +1 -0
- package/dist/services/git/gitService.js +369 -0
- package/dist/services/git/gitService.js.map +1 -0
- package/dist/services/intelligence/intelService.js +136 -0
- package/dist/services/intelligence/intelService.js.map +1 -0
- package/dist/services/intelligence/protocol.js +2 -0
- package/dist/services/intelligence/protocol.js.map +1 -0
- package/dist/services/intelligence/tsWorker.js +419 -0
- package/dist/services/intelligence/tsWorker.js.map +1 -0
- package/dist/services/jobs/commandInput.js +34 -0
- package/dist/services/jobs/commandInput.js.map +1 -0
- package/dist/services/jobs/jobManager.js +398 -0
- package/dist/services/jobs/jobManager.js.map +1 -0
- package/dist/services/jobs/sandbox.js +156 -0
- package/dist/services/jobs/sandbox.js.map +1 -0
- package/dist/services/jobs/sandboxWiring.js +37 -0
- package/dist/services/jobs/sandboxWiring.js.map +1 -0
- package/dist/services/jobs/spool.js +112 -0
- package/dist/services/jobs/spool.js.map +1 -0
- package/dist/services/jobs/windowsSandboxChild.js +24 -0
- package/dist/services/jobs/windowsSandboxChild.js.map +1 -0
- package/dist/services/jobs/windowsSandboxWorker.js +155 -0
- package/dist/services/jobs/windowsSandboxWorker.js.map +1 -0
- package/dist/services/lsp/documentUri.js +16 -0
- package/dist/services/lsp/documentUri.js.map +1 -0
- package/dist/services/lsp/jsonrpc.js +382 -0
- package/dist/services/lsp/jsonrpc.js.map +1 -0
- package/dist/services/lsp/lspService.js +1051 -0
- package/dist/services/lsp/lspService.js.map +1 -0
- package/dist/services/memory/contracts.js +94 -0
- package/dist/services/memory/contracts.js.map +1 -0
- package/dist/services/memory/memoryService.js +683 -0
- package/dist/services/memory/memoryService.js.map +1 -0
- package/dist/services/multimodal/browserService.js +407 -0
- package/dist/services/multimodal/browserService.js.map +1 -0
- package/dist/services/multimodal/byteRange.js +31 -0
- package/dist/services/multimodal/byteRange.js.map +1 -0
- package/dist/services/multimodal/contracts.js +59 -0
- package/dist/services/multimodal/contracts.js.map +1 -0
- package/dist/services/multimodal/gameService.js +95 -0
- package/dist/services/multimodal/gameService.js.map +1 -0
- package/dist/services/multimodal/images.js +107 -0
- package/dist/services/multimodal/images.js.map +1 -0
- package/dist/services/multimodal/mediaService.js +279 -0
- package/dist/services/multimodal/mediaService.js.map +1 -0
- package/dist/services/multimodal/mediaWorker.js +143 -0
- package/dist/services/multimodal/mediaWorker.js.map +1 -0
- package/dist/services/multimodal/multimodalService.js +51 -0
- package/dist/services/multimodal/multimodalService.js.map +1 -0
- package/dist/services/multimodal/network.js +69 -0
- package/dist/services/multimodal/network.js.map +1 -0
- package/dist/services/multimodal/operations.js +40 -0
- package/dist/services/multimodal/operations.js.map +1 -0
- package/dist/services/multimodal/storage.js +160 -0
- package/dist/services/multimodal/storage.js.map +1 -0
- package/dist/services/multimodal/workflowService.js +179 -0
- package/dist/services/multimodal/workflowService.js.map +1 -0
- package/dist/services/overview.js +284 -0
- package/dist/services/overview.js.map +1 -0
- package/dist/services/resources/casStore.js +304 -0
- package/dist/services/resources/casStore.js.map +1 -0
- package/dist/services/resources/contracts.js +75 -0
- package/dist/services/resources/contracts.js.map +1 -0
- package/dist/services/resources/mime.js +77 -0
- package/dist/services/resources/mime.js.map +1 -0
- package/dist/services/resources/providerRegistry.js +22 -0
- package/dist/services/resources/providerRegistry.js.map +1 -0
- package/dist/services/resources/providers.js +120 -0
- package/dist/services/resources/providers.js.map +1 -0
- package/dist/services/resources/resourceService.js +282 -0
- package/dist/services/resources/resourceService.js.map +1 -0
- package/dist/services/runtime/contracts.js +65 -0
- package/dist/services/runtime/contracts.js.map +1 -0
- package/dist/services/runtime/runtimeService.js +364 -0
- package/dist/services/runtime/runtimeService.js.map +1 -0
- package/dist/services/schedules/scheduleService.js +198 -0
- package/dist/services/schedules/scheduleService.js.map +1 -0
- package/dist/services/search/searchService.js +434 -0
- package/dist/services/search/searchService.js.map +1 -0
- package/dist/services/search/searchWorker.js +58 -0
- package/dist/services/search/searchWorker.js.map +1 -0
- package/dist/setup/componentProbes.js +259 -0
- package/dist/setup/componentProbes.js.map +1 -0
- package/dist/setup/download.js +139 -0
- package/dist/setup/download.js.map +1 -0
- package/dist/setup/managedTools.js +118 -0
- package/dist/setup/managedTools.js.map +1 -0
- package/dist/setup/sandboxProbe.js +172 -0
- package/dist/setup/sandboxProbe.js.map +1 -0
- package/dist/setup/setup.js +522 -0
- package/dist/setup/setup.js.map +1 -0
- package/dist/setup/windowsChromiumAcl.js +56 -0
- package/dist/setup/windowsChromiumAcl.js.map +1 -0
- package/dist/setup/windowsSandboxInstall.js +94 -0
- package/dist/setup/windowsSandboxInstall.js.map +1 -0
- package/dist/setup/windowsSpeechInstall.js +86 -0
- package/dist/setup/windowsSpeechInstall.js.map +1 -0
- package/dist/store/db.js +711 -0
- package/dist/store/db.js.map +1 -0
- package/dist/store/scopeEncoding.js +15 -0
- package/dist/store/scopeEncoding.js.map +1 -0
- package/dist/store/store.js +467 -0
- package/dist/store/store.js.map +1 -0
- package/dist/tools/agentRuntimeTools.js +216 -0
- package/dist/tools/agentRuntimeTools.js.map +1 -0
- package/dist/tools/agentTools.js +317 -0
- package/dist/tools/agentTools.js.map +1 -0
- package/dist/tools/assistanceTools.js +126 -0
- package/dist/tools/assistanceTools.js.map +1 -0
- package/dist/tools/brainTools.js +130 -0
- package/dist/tools/brainTools.js.map +1 -0
- package/dist/tools/catalog.js +30 -0
- package/dist/tools/catalog.js.map +1 -0
- package/dist/tools/changeTools.js +188 -0
- package/dist/tools/changeTools.js.map +1 -0
- package/dist/tools/context.js +243 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/contextTools.js +57 -0
- package/dist/tools/contextTools.js.map +1 -0
- package/dist/tools/coreCatalog.js +82 -0
- package/dist/tools/coreCatalog.js.map +1 -0
- package/dist/tools/desktopTools.js +41 -0
- package/dist/tools/desktopTools.js.map +1 -0
- package/dist/tools/directTools.js +273 -0
- package/dist/tools/directTools.js.map +1 -0
- package/dist/tools/envelope.js +71 -0
- package/dist/tools/envelope.js.map +1 -0
- package/dist/tools/extraTools.js +298 -0
- package/dist/tools/extraTools.js.map +1 -0
- package/dist/tools/gitTools.js +93 -0
- package/dist/tools/gitTools.js.map +1 -0
- package/dist/tools/jobTools.js +454 -0
- package/dist/tools/jobTools.js.map +1 -0
- package/dist/tools/memoryTools.js +102 -0
- package/dist/tools/memoryTools.js.map +1 -0
- package/dist/tools/metaTools.js +153 -0
- package/dist/tools/metaTools.js.map +1 -0
- package/dist/tools/multimodalTools.js +262 -0
- package/dist/tools/multimodalTools.js.map +1 -0
- package/dist/tools/projectTools.js +261 -0
- package/dist/tools/projectTools.js.map +1 -0
- package/dist/tools/resourceTools.js +81 -0
- package/dist/tools/resourceTools.js.map +1 -0
- package/dist/tools/runtimeTools.js +137 -0
- package/dist/tools/runtimeTools.js.map +1 -0
- package/dist/tools/scheduleTools.js +10 -0
- package/dist/tools/scheduleTools.js.map +1 -0
- package/dist/tools/semanticTools.js +141 -0
- package/dist/tools/semanticTools.js.map +1 -0
- package/dist/tools/subagentTools.js +32 -0
- package/dist/tools/subagentTools.js.map +1 -0
- package/dist/tools/surface.js +644 -0
- package/dist/tools/surface.js.map +1 -0
- package/dist/tunnel/control.js +113 -0
- package/dist/tunnel/control.js.map +1 -0
- package/dist/tunnel/credentials.js +392 -0
- package/dist/tunnel/credentials.js.map +1 -0
- package/dist/tunnel/log.js +84 -0
- package/dist/tunnel/log.js.map +1 -0
- package/dist/tunnel/runtime.js +55 -0
- package/dist/tunnel/runtime.js.map +1 -0
- package/dist/tunnel/supervisor.js +280 -0
- package/dist/tunnel/supervisor.js.map +1 -0
- package/dist/util/bytes.js +53 -0
- package/dist/util/bytes.js.map +1 -0
- package/dist/util/hash.js +53 -0
- package/dist/util/hash.js.map +1 -0
- package/dist/workspace/fs.js +305 -0
- package/dist/workspace/fs.js.map +1 -0
- package/dist/workspace/identity.js +17 -0
- package/dist/workspace/identity.js.map +1 -0
- package/dist/workspace/ignores.js +173 -0
- package/dist/workspace/ignores.js.map +1 -0
- package/dist/workspace/root.js +59 -0
- package/dist/workspace/root.js.map +1 -0
- package/docs/AGENT_RUNTIME.md +154 -0
- package/docs/AI_PROVIDERS.md +110 -0
- package/docs/ARCHITECTURE.md +331 -0
- package/docs/ASSISTANCE.md +31 -0
- package/docs/AUTH.md +59 -0
- package/docs/BRAIN.md +92 -0
- package/docs/COMPATIBILITY.md +92 -0
- package/docs/CONFIRMATION.md +13 -0
- package/docs/CONTEXT.md +87 -0
- package/docs/DESKTOP.md +35 -0
- package/docs/EVALUATION.md +86 -0
- package/docs/LARGE_INPUTS.md +21 -0
- package/docs/LSP.md +21 -0
- package/docs/MANUAL_ACCEPTANCE.md +283 -0
- package/docs/MEMORY.md +128 -0
- package/docs/MULTIMODAL.md +32 -0
- package/docs/PROJECTS.md +146 -0
- package/docs/RELEASE_1.0.0.md +111 -0
- package/docs/RELEASE_NOTES.md +92 -0
- package/docs/RESOURCES.md +83 -0
- package/docs/RUNTIME.md +125 -0
- package/docs/SCHEDULES.md +23 -0
- package/docs/SECURITY.md +250 -0
- package/docs/TEST_REPORT.md +312 -0
- package/docs/TUNNEL.md +86 -0
- package/docs/WEB_CLIENTS.md +43 -0
- package/docs/WINDOWS.md +79 -0
- package/docs/adr/001-one-process-one-workspace.md +22 -0
- package/docs/adr/002-external-tunnel.md +19 -0
- package/docs/adr/003-sdk-v2-legacy-stateless.md +20 -0
- package/docs/adr/004-embedded-oauth-provider.md +19 -0
- package/docs/adr/005-static-client-baseline.md +19 -0
- package/docs/adr/006-guarded-fs-explicit-exec.md +16 -0
- package/docs/adr/007-preview-hash-journal.md +18 -0
- package/docs/adr/008-ts-js-semantics-first.md +17 -0
- package/docs/adr/009-jobs-polling.md +18 -0
- package/docs/adr/010-sqlite-better-sqlite3.md +18 -0
- package/docs/adr/011-env-example-secure-default.md +19 -0
- package/docs/adr/012-workspace-scope-grant.md +24 -0
- package/docs/adr/013-loopback-proxy-trust.md +18 -0
- package/docs/adr/014-direct-coding-tier.md +48 -0
- package/docs/adr/015-coding-agent-parity.md +56 -0
- package/docs/adr/016-oidc-openid-consent.md +15 -0
- package/docs/adr/017-installation-identity-local-config.md +44 -0
- package/docs/adr/018-access-scope-encoding.md +11 -0
- package/docs/adr/019-runtime-workspace-switch.md +59 -0
- package/docs/adr/020-desktop-access.md +50 -0
- package/docs/adr/021-persistent-desktop-consent.md +39 -0
- package/docs/adr/022-owner-kill-preserves-login.md +30 -0
- package/docs/adr/023-large-coding-inputs.md +49 -0
- package/docs/adr/024-usage-rounds-and-schedules.md +72 -0
- package/docs/adr/025-owner-chat-whitelist.md +53 -0
- package/docs/adr/026-remove-conversation-confirmation.md +35 -0
- package/docs/adr/027-workspace-client-list.md +20 -0
- package/docs/adr/028-owner-client-deletion.md +25 -0
- package/docs/adr/029-compact-tool-surface.md +121 -0
- package/docs/adr/030-windows-native-candidate.md +71 -0
- package/docs/adr/031-owner-selected-tunnel-supervision.md +13 -0
- package/docs/adr/032-owner-project-registry.md +43 -0
- package/docs/adr/033-read-only-project-federation.md +61 -0
- package/docs/adr/034-universal-resource-cas.md +60 -0
- package/docs/adr/035-project-brain-incremental-index.md +51 -0
- package/docs/adr/036-context-engine-evidence.md +53 -0
- package/docs/adr/037-owner-reviewed-memory.md +46 -0
- package/docs/adr/038-runtime-intelligence.md +46 -0
- package/docs/adr/039-advanced-agent-runtime.md +57 -0
- package/docs/adr/040-dodobench-release-gate.md +43 -0
- package/docs/adr/041-local-macos-docker-linux-gates.md +32 -0
- package/docs/adr/042-project-directory-generation-identity.md +36 -0
- package/docs/adr/043-trusted-self-hosted-platform-gates.md +38 -0
- package/docs/adr/044-global-launcher-and-cli-menu.md +43 -0
- package/docs/adr/045-ai-providers-multiproject.md +56 -0
- package/docs/adr/046-personal-access-mode.md +32 -0
- package/docs/adr/README.md +64 -0
- package/examples/.dodo.json.example +20 -0
- package/examples/cloudflared.yml +23 -0
- package/examples/reverse-proxy-fixture.mjs +37 -0
- package/native/desktop-linux.py +387 -0
- package/native/desktop-windows.cs +180 -0
- package/native/desktop.swift +212 -0
- package/package.json +145 -0
- package/schemas/global-config.schema.json +497 -0
- package/schemas/project-config.schema.json +75 -0
- package/schemas/tools.compact.json +3236 -0
- package/schemas/tools.hybrid.json +8194 -0
- package/schemas/tools.json +34518 -0
- package/scripts/guard-publish.mjs +12 -0
- package/scripts/setup-multimodal.mjs +70 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import net from 'node:net';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { randomBytes } from 'node:crypto';
|
|
5
|
+
import { ensurePrivateDirectory } from '../platform/privateFs.js';
|
|
6
|
+
import { IPC_FRAME_BYTES, newIpcCredential, publishIpcCredential, removeIpcCredential, ipcMac, validMac } from './authentication.js';
|
|
7
|
+
/** Authenticated one-shot IPC on Unix sockets / Windows named pipes. The
|
|
8
|
+
* token is never sent over the wire; both peers prove possession using fresh
|
|
9
|
+
* challenges, and request/response bodies are authenticated and bounded. */
|
|
10
|
+
export async function startIpcServer(socketPath, handler, options = {}) {
|
|
11
|
+
ensurePrivateDirectory(path.dirname(socketPath));
|
|
12
|
+
const credential = newIpcCredential(socketPath, options.stableWindowsEndpoint === undefined ? {} : { stableWindowsEndpoint: options.stableWindowsEndpoint });
|
|
13
|
+
if (process.platform !== 'win32' && fs.existsSync(socketPath)) {
|
|
14
|
+
const stat = fs.lstatSync(socketPath);
|
|
15
|
+
if (!stat.isSocket() || stat.isSymbolicLink() || stat.uid !== process.getuid?.())
|
|
16
|
+
throw new Error('refusing unexpected IPC path');
|
|
17
|
+
if (options.replaceExisting === false)
|
|
18
|
+
throw new Error('authenticated IPC listener already exists');
|
|
19
|
+
// Preserve the legacy root alias: older clients retain their independent
|
|
20
|
+
// instance socket, while the newest local client owns this root alias.
|
|
21
|
+
fs.unlinkSync(socketPath);
|
|
22
|
+
}
|
|
23
|
+
const connections = new Set();
|
|
24
|
+
const server = net.createServer(conn => {
|
|
25
|
+
if (connections.size >= 64) {
|
|
26
|
+
conn.destroy();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
connections.add(conn);
|
|
30
|
+
let buffer = '', phase = 'hello';
|
|
31
|
+
let clientNonce = '', serverNonce = '';
|
|
32
|
+
conn.setEncoding('utf8');
|
|
33
|
+
conn.setTimeout(10000, () => conn.destroy());
|
|
34
|
+
conn.on('error', () => conn.destroy());
|
|
35
|
+
conn.on('close', () => connections.delete(conn));
|
|
36
|
+
conn.on('data', (chunk) => {
|
|
37
|
+
buffer += chunk;
|
|
38
|
+
if (Buffer.byteLength(buffer, 'utf8') > IPC_FRAME_BYTES) {
|
|
39
|
+
conn.destroy();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const index = buffer.indexOf('\n');
|
|
43
|
+
if (index === -1)
|
|
44
|
+
return;
|
|
45
|
+
const line = buffer.slice(0, index);
|
|
46
|
+
buffer = buffer.slice(index + 1);
|
|
47
|
+
if (buffer.length || phase === 'done') {
|
|
48
|
+
conn.destroy();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const frame = JSON.parse(line);
|
|
53
|
+
if (phase === 'hello') {
|
|
54
|
+
if (frame['version'] !== 1 || typeof frame['nonce'] !== 'string' || !/^[a-f0-9]{64}$/.test(frame['nonce']))
|
|
55
|
+
throw new Error('bad hello');
|
|
56
|
+
clientNonce = frame['nonce'];
|
|
57
|
+
serverNonce = randomBytes(32).toString('hex');
|
|
58
|
+
phase = 'request';
|
|
59
|
+
conn.write(JSON.stringify({ version: 1, nonce: serverNonce, mac: ipcMac(credential.token, 'hello', clientNonce, serverNonce) }) + '\n');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
phase = 'done'; // one-use even if parsing/dispatch fails
|
|
63
|
+
const payload = frame['payload'];
|
|
64
|
+
if (typeof payload !== 'string' || !validMac(frame['mac'], ipcMac(credential.token, 'request', clientNonce, serverNonce, payload)))
|
|
65
|
+
throw new Error('bad request authentication');
|
|
66
|
+
const req = JSON.parse(payload);
|
|
67
|
+
if (!req || !Number.isSafeInteger(req.id) || typeof req.cmd !== 'string' || req.cmd.length > 100 || (req.args !== undefined && (!req.args || typeof req.args !== 'object' || Array.isArray(req.args))))
|
|
68
|
+
throw new Error('invalid IPC request');
|
|
69
|
+
void (async () => {
|
|
70
|
+
let response;
|
|
71
|
+
try {
|
|
72
|
+
response = { id: req.id, ok: true, data: await handler(req.cmd, req.args ?? {}) };
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
response = { id: req.id, ok: false, error: error instanceof Error ? error.message : 'owner request failed' };
|
|
76
|
+
}
|
|
77
|
+
const text = JSON.stringify(response);
|
|
78
|
+
const wire = JSON.stringify({ payload: text, mac: ipcMac(credential.token, 'response', clientNonce, serverNonce, text) }) + '\n';
|
|
79
|
+
if (Buffer.byteLength(wire, 'utf8') > IPC_FRAME_BYTES)
|
|
80
|
+
conn.destroy();
|
|
81
|
+
else
|
|
82
|
+
conn.end(wire);
|
|
83
|
+
})().catch(() => conn.destroy());
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
conn.destroy();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
await new Promise((resolve, reject) => {
|
|
91
|
+
server.once('error', reject);
|
|
92
|
+
server.listen(credential.endpoint, () => { server.removeListener('error', reject); resolve(); });
|
|
93
|
+
});
|
|
94
|
+
try {
|
|
95
|
+
if (process.platform !== 'win32')
|
|
96
|
+
fs.chmodSync(socketPath, 0o600);
|
|
97
|
+
publishIpcCredential(socketPath, credential);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
server.close();
|
|
101
|
+
for (const conn of connections)
|
|
102
|
+
conn.destroy();
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
server.on('close', () => removeIpcCredential(socketPath, credential));
|
|
106
|
+
return server;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/ipc/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { DodoError } from '../errors.js';
|
|
4
|
+
import { buildChildEnv } from '../security/env.js';
|
|
5
|
+
import { isWithinPath } from './pathPolicy.js';
|
|
6
|
+
export function executableNames(name, pathext, platform = process.platform, allowBatch = true) {
|
|
7
|
+
if (!name || /[\\/:\u0000-\u001f]/u.test(name))
|
|
8
|
+
throw new DodoError('INVALID_INPUT', 'expected a bare executable name');
|
|
9
|
+
if (platform !== 'win32')
|
|
10
|
+
return [name];
|
|
11
|
+
const allowed = allowBatch ? ['.EXE', '.COM', '.CMD', '.BAT'] : ['.EXE', '.COM'];
|
|
12
|
+
const ext = path.win32.extname(name).toUpperCase();
|
|
13
|
+
if (ext)
|
|
14
|
+
return allowed.includes(ext) ? [name] : [];
|
|
15
|
+
const extensions = (pathext ?? '.COM;.EXE;.BAT;.CMD').split(';').map(e => e.toUpperCase()).filter(e => allowed.includes(e));
|
|
16
|
+
return [...new Set(extensions.length ? extensions : allowed)].map(e => name + e);
|
|
17
|
+
}
|
|
18
|
+
/** A repository may never supply an implicit helper (git, rg, node, LSP...). */
|
|
19
|
+
export function trustedExecutable(abs, workspaceRoot, allowBatch = true) {
|
|
20
|
+
try {
|
|
21
|
+
if (!path.isAbsolute(abs) || isWithinPath(workspaceRoot, abs))
|
|
22
|
+
return undefined;
|
|
23
|
+
const real = fs.realpathSync.native(abs);
|
|
24
|
+
if (isWithinPath(fs.realpathSync.native(workspaceRoot), real))
|
|
25
|
+
return undefined;
|
|
26
|
+
const st = fs.statSync(real);
|
|
27
|
+
if (!st.isFile())
|
|
28
|
+
return undefined;
|
|
29
|
+
if (process.platform === 'win32') {
|
|
30
|
+
if (!/\.(?:exe|com)$/i.test(real) && !(allowBatch && /\.(?:cmd|bat)$/i.test(real)))
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
else if ((st.mode & 0o111) === 0)
|
|
34
|
+
return undefined;
|
|
35
|
+
return real;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function resolveTrustedExecutable(name, workspaceRoot, opts = {}) {
|
|
42
|
+
const allowBatch = opts.allowBatch ?? true;
|
|
43
|
+
if (opts.allowAbsolute && path.isAbsolute(name)) {
|
|
44
|
+
const resolved = trustedExecutable(name, workspaceRoot, allowBatch);
|
|
45
|
+
if (resolved)
|
|
46
|
+
return resolved;
|
|
47
|
+
throw new DodoError('PATH_DENIED', 'executable must be a supported regular file outside the workspace');
|
|
48
|
+
}
|
|
49
|
+
const env = buildChildEnv({ parentEnv: opts.env ?? process.env, workspaceRoot, extraAllowlist: [] });
|
|
50
|
+
const names = executableNames(name, env['PATHEXT'], process.platform, allowBatch);
|
|
51
|
+
for (const directory of (env['PATH'] ?? '').split(path.delimiter)) {
|
|
52
|
+
if (!directory || !path.isAbsolute(directory))
|
|
53
|
+
continue;
|
|
54
|
+
for (const candidate of names) {
|
|
55
|
+
const resolved = trustedExecutable(path.join(directory, candidate), workspaceRoot, allowBatch);
|
|
56
|
+
if (resolved)
|
|
57
|
+
return resolved;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
throw new DodoError('NOT_FOUND', `program not found on trusted PATH: ${name}`, {
|
|
61
|
+
recovery: 'Install outside the workspace and add the directory to PATH. Windows LSP batch shims are refused; register node.exe with the server JavaScript path instead.',
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=execResolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execResolve.js","sourceRoot":"","sources":["../../src/platform/execResolve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { DodoError } from '../errors.js';
|
|
2
|
+
const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
|
|
3
|
+
const MAX_U64 = (1n << 64n) - 1n;
|
|
4
|
+
export function encodeFileIdentity(value) {
|
|
5
|
+
if (value < 0n || value > MAX_U64)
|
|
6
|
+
throw new DodoError('NOT_SUPPORTED', 'filesystem identity is outside the unsigned 64-bit range');
|
|
7
|
+
return value <= MAX_SAFE ? Number(value) : `u64:${value}`;
|
|
8
|
+
}
|
|
9
|
+
export function parseFileIdentity(value) {
|
|
10
|
+
if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0)
|
|
11
|
+
return value;
|
|
12
|
+
if (typeof value !== 'string' || !/^u64:[1-9][0-9]{0,19}$/.test(value))
|
|
13
|
+
return undefined;
|
|
14
|
+
const integer = BigInt(value.slice(4));
|
|
15
|
+
return integer > MAX_SAFE && integer <= MAX_U64 ? value : undefined;
|
|
16
|
+
}
|
|
17
|
+
export function requireFileIdentity(value) {
|
|
18
|
+
const identity = parseFileIdentity(value);
|
|
19
|
+
if (identity === undefined)
|
|
20
|
+
throw new DodoError('INTERNAL_ERROR', 'stored filesystem identity is invalid; owner review required');
|
|
21
|
+
return identity;
|
|
22
|
+
}
|
|
23
|
+
export function fileIdentityBigInt(value) {
|
|
24
|
+
return typeof value === 'number' ? BigInt(value) : BigInt(value.slice(4));
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=fileIdentity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileIdentity.js","sourceRoot":"","sources":["../../src/platform/fileIdentity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAKzC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;AACjD,MAAM,OAAO,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC;AAEjC,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,IAAI,KAAK,GAAG,EAAE,IAAI,KAAK,GAAG,OAAO;QAAE,MAAM,IAAI,SAAS,CAAC,eAAe,EAAE,0DAA0D,CAAC,CAAC;IACpI,OAAO,KAAK,IAAI,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,KAAK,EAAE,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACzF,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACzF,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACvC,OAAO,OAAO,GAAG,QAAQ,IAAI,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,KAAqB,CAAC,CAAC,CAAC,SAAS,CAAC;AACtF,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAc;IAChD,MAAM,QAAQ,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAC1C,IAAI,QAAQ,KAAK,SAAS;QAAE,MAAM,IAAI,SAAS,CAAC,gBAAgB,EAAE,8DAA8D,CAAC,CAAC;IAClI,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAmB;IACpD,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5E,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
/** Retry transient Windows sharing locks, never delete a destination or clear
|
|
3
|
+
* a read-only attribute to force an overwrite. Total delay is bounded. */
|
|
4
|
+
export function retryWindowsFs(operation, platform = process.platform) {
|
|
5
|
+
const pauses = [10, 25, 50, 100];
|
|
6
|
+
for (let attempt = 0;; attempt++) {
|
|
7
|
+
try {
|
|
8
|
+
return operation();
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
const code = error.code;
|
|
12
|
+
if (platform !== 'win32' || !['EPERM', 'EBUSY', 'EACCES'].includes(code ?? '') || attempt >= pauses.length)
|
|
13
|
+
throw error;
|
|
14
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, pauses[attempt]);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function renameWithRetry(source, destination) { retryWindowsFs(() => fs.renameSync(source, destination)); }
|
|
19
|
+
export function removeWithRetry(target, recursive = false) {
|
|
20
|
+
retryWindowsFs(() => fs.rmSync(target, { recursive, force: true, maxRetries: process.platform === 'win32' ? 3 : 0, retryDelay: 40 }));
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=fsRetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fsRetry.js","sourceRoot":"","sources":["../../src/platform/fsRetry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB;0EAC0E;AAC1E,MAAM,UAAU,cAAc,CAAI,SAAkB,EAAE,WAA4B,OAAO,CAAC,QAAQ;IAChG,MAAM,MAAM,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;IACjC,KAAK,IAAI,OAAO,GAAG,CAAC,GAAI,OAAO,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC;YAAC,OAAO,SAAS,EAAE,CAAC;QAAC,CAAC;QAC3B,OAAO,KAAK,EAAE,CAAC;YACb,MAAM,IAAI,GAAI,KAA+B,CAAC,IAAI,CAAC;YACnD,IAAI,QAAQ,KAAK,OAAO,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,IAAI,OAAO,IAAI,MAAM,CAAC,MAAM;gBAAE,MAAM,KAAK,CAAC;YACxH,OAAO,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,OAAO,CAAE,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;AACH,CAAC;AACD,MAAM,UAAU,eAAe,CAAC,MAAc,EAAE,WAAmB,IAAU,cAAc,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;AACxI,MAAM,UAAU,eAAe,CAAC,MAAc,EAAE,SAAS,GAAG,KAAK;IAC/D,cAAc,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;AACxI,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { DodoError } from '../errors.js';
|
|
3
|
+
/** Segment-aware containment, including drive/case semantics on Windows. */
|
|
4
|
+
export function isWithinPath(root, target, platform = process.platform) {
|
|
5
|
+
const p = platform === 'win32' ? path.win32 : path.posix;
|
|
6
|
+
const rel = p.relative(p.resolve(root), p.resolve(target));
|
|
7
|
+
return rel === '' || (rel !== '..' && !rel.startsWith(`..${p.sep}`) && !p.isAbsolute(rel));
|
|
8
|
+
}
|
|
9
|
+
/** Reject ambiguous Win32/NTFS spellings before a filesystem call. */
|
|
10
|
+
export function assertWindowsSegment(segment) {
|
|
11
|
+
if (/[<>:"|?*\u0000-\u001f]/u.test(segment) || /[ .]$/u.test(segment)) {
|
|
12
|
+
throw new DodoError('PATH_DENIED', 'Windows paths cannot contain ADS, control characters, wildcards, or trailing dots/spaces');
|
|
13
|
+
}
|
|
14
|
+
if (/^(?:con|prn|aux|nul|clock\$|conin\$|conout\$|com[1-9¹²³]|lpt[1-9¹²³])(?:[ .]|$)/iu.test(segment)) {
|
|
15
|
+
throw new DodoError('PATH_DENIED', 'Windows device names are not workspace files');
|
|
16
|
+
}
|
|
17
|
+
// Canonical resolution is still required: not all aliases contain a tilde.
|
|
18
|
+
if (/~[0-9]+(?:\.|$)/u.test(segment)) {
|
|
19
|
+
throw new DodoError('PATH_DENIED', 'Windows short-name aliases are refused; use the original long filename');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function assertLocalWindowsRoot(root) {
|
|
23
|
+
if (!/^[a-z]:[\\/]/i.test(root) || root.startsWith('\\\\')) {
|
|
24
|
+
throw new DodoError('PATH_DENIED', 'Windows native workspaces require a local drive path; UNC and device namespaces are not supported');
|
|
25
|
+
}
|
|
26
|
+
for (const segment of root.slice(3).split(/[\\/]/u).filter(Boolean))
|
|
27
|
+
assertWindowsSegment(segment);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=pathPolicy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathPolicy.js","sourceRoot":"","sources":["../../src/platform/pathPolicy.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,4EAA4E;AAC5E,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,MAAc,EAAE,WAA4B,OAAO,CAAC,QAAQ;IACrG,MAAM,CAAC,GAAG,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;IACzD,MAAM,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3D,OAAO,GAAG,KAAK,EAAE,IAAI,CAAC,GAAG,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;AAC7F,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,IAAI,yBAAyB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,SAAS,CAAC,aAAa,EAAE,0FAA0F,CAAC,CAAC;IACjI,CAAC;IACD,IAAI,mFAAmF,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACtG,MAAM,IAAI,SAAS,CAAC,aAAa,EAAE,8CAA8C,CAAC,CAAC;IACrF,CAAC;IACD,2EAA2E;IAC3E,IAAI,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,SAAS,CAAC,aAAa,EAAE,wEAAwE,CAAC,CAAC;IAC/G,CAAC;AACH,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,SAAS,CAAC,aAAa,EAAE,mGAAmG,CAAC,CAAC;IAC1I,CAAC;IACD,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;QAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;AACrG,CAAC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { DodoError } from '../errors.js';
|
|
5
|
+
import { envValue, windowsSystemExecutable } from './system.js';
|
|
6
|
+
// Constant PowerShell/.NET code: the path is data in the child's environment,
|
|
7
|
+
// never interpolated into source. Only the current SID, SYSTEM and local
|
|
8
|
+
// administrators may access state. A failed/unsupported ACL is a hard error.
|
|
9
|
+
const ACL_SCRIPT = String.raw `
|
|
10
|
+
$ErrorActionPreference = 'Stop'
|
|
11
|
+
$p = $env:DODO_PRIVATE_PATH
|
|
12
|
+
$item = Get-Item -LiteralPath $p -Force
|
|
13
|
+
if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw 'reparse point' }
|
|
14
|
+
$currentIdentity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
15
|
+
$sid = $currentIdentity.User
|
|
16
|
+
$administratorsSid = [Security.Principal.SecurityIdentifier]::new('S-1-5-32-544')
|
|
17
|
+
$principal = [Security.Principal.WindowsPrincipal]::new($currentIdentity)
|
|
18
|
+
$allowed = @($sid.Value, 'S-1-5-18', $administratorsSid.Value)
|
|
19
|
+
|
|
20
|
+
function Assert-PrivateDacl($candidate) {
|
|
21
|
+
$rules = $candidate.GetAccessRules($true, $true, [Security.Principal.SecurityIdentifier])
|
|
22
|
+
$ownerAllowed = $false
|
|
23
|
+
foreach ($rule in $rules) {
|
|
24
|
+
if ($rule.AccessControlType -eq 'Allow') {
|
|
25
|
+
if ($allowed -notcontains $rule.IdentityReference.Value) { throw 'non-private DACL' }
|
|
26
|
+
if ($rule.IdentityReference.Value -eq $sid.Value) { $ownerAllowed = $true }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (-not $ownerAllowed) { throw 'owner access missing' }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
$acl = Get-Acl -LiteralPath $p
|
|
33
|
+
$owner = $acl.GetOwner([Security.Principal.SecurityIdentifier]).Value
|
|
34
|
+
# An enabled Administrators token may normalize Windows' default group owner.
|
|
35
|
+
# IsInRole checks the effective token; a UAC deny-only group does not qualify.
|
|
36
|
+
$repairAdminOwner = ($owner -eq $administratorsSid.Value) -and $principal.IsInRole($administratorsSid)
|
|
37
|
+
if ($owner -ne $sid.Value -and -not $repairAdminOwner) { throw 'unexpected owner' }
|
|
38
|
+
if ($env:DODO_PRIVATE_MODE -eq 'protect') {
|
|
39
|
+
if (-not $item.PSIsContainer) { throw 'expected directory' }
|
|
40
|
+
$acl = New-Object Security.AccessControl.DirectorySecurity
|
|
41
|
+
$acl.SetOwner($sid)
|
|
42
|
+
$acl.SetAccessRuleProtection($true, $false)
|
|
43
|
+
foreach ($id in $allowed) {
|
|
44
|
+
$identity = New-Object Security.Principal.SecurityIdentifier($id)
|
|
45
|
+
$rule = New-Object Security.AccessControl.FileSystemAccessRule($identity, 'FullControl', 'ContainerInherit, ObjectInherit', 'None', 'Allow')
|
|
46
|
+
$acl.AddAccessRule($rule)
|
|
47
|
+
}
|
|
48
|
+
Set-Acl -LiteralPath $p -AclObject $acl
|
|
49
|
+
} elseif ($env:DODO_PRIVATE_MODE -eq 'verify') {
|
|
50
|
+
# Verify may repair ONLY the admitted group owner, never a permissive DACL.
|
|
51
|
+
# New files (notably IPC descriptors) can receive that default owner too.
|
|
52
|
+
Assert-PrivateDacl $acl
|
|
53
|
+
if ($repairAdminOwner) {
|
|
54
|
+
$beforeDacl = $acl.GetSecurityDescriptorSddlForm([Security.AccessControl.AccessControlSections]::Access)
|
|
55
|
+
$acl.SetOwner($sid)
|
|
56
|
+
Set-Acl -LiteralPath $p -AclObject $acl
|
|
57
|
+
$acl = Get-Acl -LiteralPath $p
|
|
58
|
+
if ($acl.GetSecurityDescriptorSddlForm([Security.AccessControl.AccessControlSections]::Access) -ne $beforeDacl) { throw 'DACL changed during owner repair' }
|
|
59
|
+
}
|
|
60
|
+
} else { throw 'invalid private ACL mode' }
|
|
61
|
+
# Do not trust Set-Acl succeeding: re-read the object and enforce the final
|
|
62
|
+
# user-SID owner and the same private DACL in BOTH modes, after any repair.
|
|
63
|
+
$item = Get-Item -LiteralPath $p -Force
|
|
64
|
+
if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw 'reparse point' }
|
|
65
|
+
$acl = Get-Acl -LiteralPath $p
|
|
66
|
+
if ($acl.GetOwner([Security.Principal.SecurityIdentifier]).Value -ne $sid.Value) { throw 'unexpected owner after ACL operation' }
|
|
67
|
+
Assert-PrivateDacl $acl
|
|
68
|
+
[Console]::Write('private')
|
|
69
|
+
`;
|
|
70
|
+
function windowsAcl(target, protect) {
|
|
71
|
+
const env = {
|
|
72
|
+
SYSTEMROOT: envValue(process.env, 'SystemRoot'),
|
|
73
|
+
WINDIR: envValue(process.env, 'windir'),
|
|
74
|
+
USERPROFILE: envValue(process.env, 'USERPROFILE'),
|
|
75
|
+
TEMP: envValue(process.env, 'TEMP'),
|
|
76
|
+
TMP: envValue(process.env, 'TMP'),
|
|
77
|
+
DODO_PRIVATE_PATH: target,
|
|
78
|
+
DODO_PRIVATE_MODE: protect ? 'protect' : 'verify',
|
|
79
|
+
};
|
|
80
|
+
try {
|
|
81
|
+
const output = execFileSync(windowsSystemExecutable('WindowsPowerShell/v1.0/powershell.exe'), ['-NoLogo', '-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(ACL_SCRIPT, 'utf16le').toString('base64')], { env, shell: false, windowsHide: true, timeout: 10000, maxBuffer: 4096, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
82
|
+
if (output.trim() !== 'private')
|
|
83
|
+
throw new Error('ACL not verified');
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
throw new DodoError('PATH_DENIED', 'private Windows state ACL could not be established or verified; use a local NTFS directory owned by your user');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function ensurePrivateDirectory(directory) {
|
|
90
|
+
if (!path.isAbsolute(directory) || directory === path.parse(directory).root)
|
|
91
|
+
throw new DodoError('PATH_DENIED', 'private state must use a dedicated absolute directory');
|
|
92
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
93
|
+
const stat = fs.lstatSync(directory);
|
|
94
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
95
|
+
throw new DodoError('PATH_DENIED', 'private state directory must not be a link');
|
|
96
|
+
if (process.platform === 'win32')
|
|
97
|
+
windowsAcl(directory, true);
|
|
98
|
+
else {
|
|
99
|
+
if (stat.uid !== process.getuid?.())
|
|
100
|
+
throw new DodoError('PATH_DENIED', 'private state directory has a different owner');
|
|
101
|
+
fs.chmodSync(directory, 0o700);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** Verify private access; on Windows, normalize an Administrators default owner
|
|
105
|
+
* only for an enabled admin token and an already-private DACL. Never widen ACLs. */
|
|
106
|
+
export function assertPrivatePath(target, directory = false) {
|
|
107
|
+
const stat = fs.lstatSync(target);
|
|
108
|
+
if (stat.isSymbolicLink() || (directory ? !stat.isDirectory() : !stat.isFile() || stat.nlink !== 1))
|
|
109
|
+
throw new DodoError('PATH_DENIED', 'refusing non-private or unexpected IPC path');
|
|
110
|
+
if (process.platform === 'win32')
|
|
111
|
+
windowsAcl(target, false);
|
|
112
|
+
else if (stat.uid !== process.getuid?.() || (stat.mode & 0o077) !== 0)
|
|
113
|
+
throw new DodoError('PATH_DENIED', 'refusing non-private or unexpected IPC path');
|
|
114
|
+
return stat;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=privateFs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"privateFs.js","sourceRoot":"","sources":["../../src/platform/privateFs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { DodoError } from '../errors.js';
|
|
3
|
+
import { windowsSystemExecutable } from './system.js';
|
|
4
|
+
/** Only accepts a live owned ChildProcess, never a saved PID. Windows v1 is
|
|
5
|
+
* hard-kill only; Job Objects/escaped-descendant confinement are not claimed. */
|
|
6
|
+
export function signalOwnedProcess(child, signal, processGroup = false) {
|
|
7
|
+
if (!child.pid || child.exitCode !== null || child.signalCode !== null)
|
|
8
|
+
return;
|
|
9
|
+
if (process.platform === 'win32') {
|
|
10
|
+
try {
|
|
11
|
+
execFileSync(windowsSystemExecutable('taskkill.exe'), ['/PID', String(child.pid), '/T', '/F'], { shell: false, windowsHide: true, timeout: 5000, stdio: 'ignore' });
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
throw new DodoError('INTERNAL_ERROR', 'Windows owned-process tree termination failed; termination was not confirmed');
|
|
15
|
+
}
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
if (processGroup)
|
|
20
|
+
process.kill(-child.pid, signal);
|
|
21
|
+
else
|
|
22
|
+
child.kill(signal);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
try {
|
|
26
|
+
child.kill(signal);
|
|
27
|
+
}
|
|
28
|
+
catch { /* already closed */ }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=processTree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"processTree.js","sourceRoot":"","sources":["../../src/platform/processTree.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAqB,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAEtD;iFACiF;AACjF,MAAM,UAAU,kBAAkB,CAAC,KAAmB,EAAE,MAAsB,EAAE,YAAY,GAAG,KAAK;IAClG,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,IAAI,KAAK,CAAC,UAAU,KAAK,IAAI;QAAE,OAAO;IAC/E,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,IAAI,CAAC;YACH,YAAY,CAAC,uBAAuB,CAAC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACtK,CAAC;QAAC,MAAM,CAAC;YAAC,MAAM,IAAI,SAAS,CAAC,gBAAgB,EAAE,8EAA8E,CAAC,CAAC;QAAC,CAAC;QAClI,OAAO;IACT,CAAC;IACD,IAAI,CAAC;QACH,IAAI,YAAY;YAAE,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;;YAC9C,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QAAC,IAAI,CAAC;YAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,CAAC;IAAC,CAAC;AACxE,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { DodoError } from '../errors.js';
|
|
3
|
+
import { resolveTrustedExecutable, trustedExecutable } from './execResolve.js';
|
|
4
|
+
import { envValue, windowsSystemExecutable } from './system.js';
|
|
5
|
+
export function shellSpec(workspaceRoot) {
|
|
6
|
+
if (process.platform !== 'win32') {
|
|
7
|
+
for (const file of ['/bin/bash', '/usr/bin/bash', '/bin/sh']) {
|
|
8
|
+
const executable = trustedExecutable(file, workspaceRoot, false);
|
|
9
|
+
if (executable)
|
|
10
|
+
return { kind: 'posix', executable, extension: '.sh' };
|
|
11
|
+
}
|
|
12
|
+
throw new DodoError('NOT_FOUND', 'no trusted POSIX shell found');
|
|
13
|
+
}
|
|
14
|
+
const candidates = [];
|
|
15
|
+
try {
|
|
16
|
+
const git = resolveTrustedExecutable('git', workspaceRoot, { allowBatch: false });
|
|
17
|
+
for (const parent of [path.dirname(git), path.resolve(path.dirname(git), '..'), path.resolve(path.dirname(git), '../..')])
|
|
18
|
+
candidates.push(path.join(parent, 'bin', 'bash.exe'), path.join(parent, 'usr', 'bin', 'bash.exe'));
|
|
19
|
+
}
|
|
20
|
+
catch { /* cmd is the fallback when Git for Windows is absent. */ }
|
|
21
|
+
for (const key of ['ProgramFiles', 'ProgramFiles(x86)']) {
|
|
22
|
+
const base = envValue(process.env, key);
|
|
23
|
+
if (base)
|
|
24
|
+
candidates.push(path.join(base, 'Git', 'bin', 'bash.exe'));
|
|
25
|
+
}
|
|
26
|
+
for (const file of candidates) {
|
|
27
|
+
const executable = trustedExecutable(file, workspaceRoot, false);
|
|
28
|
+
if (executable)
|
|
29
|
+
return { kind: 'posix', executable, extension: '.sh' };
|
|
30
|
+
}
|
|
31
|
+
const executable = trustedExecutable(windowsSystemExecutable('cmd.exe'), workspaceRoot, false);
|
|
32
|
+
if (!executable)
|
|
33
|
+
throw new DodoError('NOT_SUPPORTED', 'trusted Windows cmd.exe is unavailable');
|
|
34
|
+
return { kind: 'cmd', executable, extension: '.cmd' };
|
|
35
|
+
}
|
|
36
|
+
/** Strict batch argv, not an escaping promise for arbitrary cmd.exe input. */
|
|
37
|
+
export function quoteBatchArgument(argument) {
|
|
38
|
+
if (/[\u0000-\u001f%!"&|<>^()]/u.test(argument))
|
|
39
|
+
throw new DodoError('INVALID_INPUT', 'Windows batch arguments cannot contain control characters, %, !, quotes, or shell metacharacters; use a native executable or saved script');
|
|
40
|
+
return `"${argument}"`;
|
|
41
|
+
}
|
|
42
|
+
export function batchInvocation(program, args, workspaceRoot) {
|
|
43
|
+
const command = `"${[program, ...args].map(quoteBatchArgument).join(' ')}"`;
|
|
44
|
+
if (command.length > 8000)
|
|
45
|
+
throw new DodoError('RESOURCE_LIMIT', 'Windows batch command exceeds the cmd.exe budget; pass short file paths');
|
|
46
|
+
const executable = trustedExecutable(windowsSystemExecutable('cmd.exe'), workspaceRoot, false);
|
|
47
|
+
if (!executable)
|
|
48
|
+
throw new DodoError('NOT_SUPPORTED', 'trusted Windows cmd.exe is unavailable');
|
|
49
|
+
return { program: executable, args: ['/d', '/s', '/v:off', '/c', command], windowsVerbatimArguments: true };
|
|
50
|
+
}
|
|
51
|
+
export function shellInvocation(spec, command, scriptPath) {
|
|
52
|
+
if (spec.kind === 'posix')
|
|
53
|
+
return { program: spec.executable, args: scriptPath ? ['--', process.platform === 'win32' ? scriptPath.replace(/\\/g, '/') : scriptPath] : ['-c', command], windowsVerbatimArguments: false };
|
|
54
|
+
if (command.split(/\r?\n/u).some(line => line.length > 8000))
|
|
55
|
+
throw new DodoError('RESOURCE_LIMIT', 'a cmd.exe script line exceeds 8000 characters; install Git for Windows or use separate source files');
|
|
56
|
+
const source = scriptPath ? quoteBatchArgument(scriptPath) : command;
|
|
57
|
+
return { program: spec.executable, args: ['/d', '/s', '/v:off', '/c', `"${source}"`], windowsVerbatimArguments: true };
|
|
58
|
+
}
|
|
59
|
+
/** libuv-style quoting length counted in UTF-16 units including the final NUL. */
|
|
60
|
+
export function windowsCommandLineLength(program, args, verbatim = false) {
|
|
61
|
+
const quote = (value) => {
|
|
62
|
+
if (value !== '' && !/[\s"]/u.test(value))
|
|
63
|
+
return value;
|
|
64
|
+
return '"' + value.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\+)$/g, '$1$1') + '"';
|
|
65
|
+
};
|
|
66
|
+
return quote(program).length + 1 + args.map(a => verbatim ? a : quote(a)).join(' ').length + 1;
|
|
67
|
+
}
|
|
68
|
+
export function assertWindowsArgv(program, args, verbatim = false) {
|
|
69
|
+
if ([program, ...args].some(a => a.includes('\0')))
|
|
70
|
+
throw new DodoError('INVALID_INPUT', 'native argv contains NUL');
|
|
71
|
+
if (windowsCommandLineLength(program, args, verbatim) > 32767)
|
|
72
|
+
throw new DodoError('RESOURCE_LIMIT', 'Windows native command line exceeds 32767 UTF-16 units; save source/data to files and pass short paths');
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=shell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/platform/shell.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
+
import { DodoError } from '../errors.js';
|
|
4
|
+
import { resolveTrustedExecutable } from './execResolve.js';
|
|
5
|
+
import { windowsSystemExecutable } from './system.js';
|
|
6
|
+
import { signalOwnedProcess } from './processTree.js';
|
|
7
|
+
/** Constant code: paths/voice are JSON on stdin, never interpolated PowerShell or SSML. */
|
|
8
|
+
export const WINDOWS_SPEECH_SCRIPT = String.raw `
|
|
9
|
+
$ErrorActionPreference = 'Stop'
|
|
10
|
+
[Console]::InputEncoding = [Text.UTF8Encoding]::new($false)
|
|
11
|
+
[Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)
|
|
12
|
+
Add-Type -AssemblyName System.Speech
|
|
13
|
+
$s = [System.Speech.Synthesis.SpeechSynthesizer]::new()
|
|
14
|
+
try {
|
|
15
|
+
$request = [Console]::In.ReadToEnd() | ConvertFrom-Json
|
|
16
|
+
$voices = @($s.GetInstalledVoices() | Where-Object { $_.Enabled })
|
|
17
|
+
if ($voices.Count -eq 0) { throw 'no installed speech voice' }
|
|
18
|
+
if ($request.mode -eq 'probe') { [Console]::Write('ready'); exit 0 }
|
|
19
|
+
if ($request.mode -ne 'speak') { throw 'invalid speech mode' }
|
|
20
|
+
if ($request.voice) { $s.SelectVoice([string]$request.voice) }
|
|
21
|
+
$text = [IO.File]::ReadAllText([string]$request.input, [Text.Encoding]::UTF8)
|
|
22
|
+
if ($text.Length -gt 8000) { throw 'speech input too large' }
|
|
23
|
+
if ([IO.File]::Exists([string]$request.output)) { throw 'speech output already exists' }
|
|
24
|
+
$s.SetOutputToWaveFile([string]$request.output)
|
|
25
|
+
$s.Speak($text)
|
|
26
|
+
$s.SetOutputToNull()
|
|
27
|
+
[Console]::Write('completed')
|
|
28
|
+
} finally { $s.Dispose() }
|
|
29
|
+
`;
|
|
30
|
+
export const windowsSpeechArgs = () => ['-NoLogo', '-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(WINDOWS_SPEECH_SCRIPT, 'utf16le').toString('base64')];
|
|
31
|
+
/** Availability is actually probed on Windows (a PowerShell binary alone is not a voice). */
|
|
32
|
+
export function findSpeechEngine(root) {
|
|
33
|
+
if (process.platform === 'darwin') {
|
|
34
|
+
try {
|
|
35
|
+
return { kind: 'macos-say', program: resolveTrustedExecutable('say', root, { allowBatch: false }) };
|
|
36
|
+
}
|
|
37
|
+
catch { /* try portable engine */ }
|
|
38
|
+
}
|
|
39
|
+
if (process.platform === 'win32') {
|
|
40
|
+
try {
|
|
41
|
+
const program = windowsSystemExecutable('WindowsPowerShell/v1.0/powershell.exe');
|
|
42
|
+
const probe = spawnSync(program, windowsSpeechArgs(), { shell: false, windowsHide: true, input: JSON.stringify({ mode: 'probe' }), encoding: 'utf8', timeout: 10000, maxBuffer: 16384 });
|
|
43
|
+
if (!probe.error && probe.status === 0 && probe.stdout.trim() === 'ready')
|
|
44
|
+
return { kind: 'windows-sapi', program };
|
|
45
|
+
}
|
|
46
|
+
catch { /* an absent voice needs eSpeak, not an invented capability */ }
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const program = resolveTrustedExecutable('espeak-ng', root, { allowBatch: false });
|
|
50
|
+
const probe = spawnSync(program, ['--version'], { shell: false, windowsHide: true, timeout: 5000, maxBuffer: 16384 });
|
|
51
|
+
if (!probe.error && probe.status === 0)
|
|
52
|
+
return { kind: 'espeak-ng', program };
|
|
53
|
+
}
|
|
54
|
+
catch { /* reported by setup */ }
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
/** Render to a private file only. Never plays audio, opens a microphone or downloads a voice. */
|
|
58
|
+
export async function renderSpeech(engine, input, output, voice) {
|
|
59
|
+
const st = fs.lstatSync(input);
|
|
60
|
+
if (!st.isFile() || st.isSymbolicLink() || st.nlink !== 1 || st.size > 32000)
|
|
61
|
+
throw new DodoError('PATH_DENIED', 'invalid private speech input');
|
|
62
|
+
if (fs.existsSync(output))
|
|
63
|
+
throw new DodoError('CONFLICT', 'speech output already exists');
|
|
64
|
+
const args = engine.kind === 'windows-sapi' ? windowsSpeechArgs() : engine.kind === 'macos-say'
|
|
65
|
+
? [...(voice ? ['-v', voice] : []), '-f', input, '-o', output]
|
|
66
|
+
: ['-b', '1', ...(voice ? ['-v', voice] : []), '-f', input, '-w', output];
|
|
67
|
+
await new Promise((resolve, reject) => {
|
|
68
|
+
const child = spawn(engine.program, args, { shell: false, windowsHide: true, stdio: ['pipe', 'ignore', 'pipe'] });
|
|
69
|
+
let ended = false, failure;
|
|
70
|
+
const timer = setTimeout(() => { failure = new DodoError('TIMEOUT', 'speech synthesis timed out'); try {
|
|
71
|
+
signalOwnedProcess(child, 'SIGKILL');
|
|
72
|
+
}
|
|
73
|
+
catch { /* report failure, never success */ } }, 90000);
|
|
74
|
+
child.stderr.resume(); // Speech input and OS details must not leak in error text.
|
|
75
|
+
child.stdin.on('error', () => undefined);
|
|
76
|
+
child.on('error', () => { ended = true; clearTimeout(timer); reject(new DodoError('NOT_SUPPORTED', 'speech engine could not start')); });
|
|
77
|
+
child.on('close', code => {
|
|
78
|
+
clearTimeout(timer);
|
|
79
|
+
if (ended)
|
|
80
|
+
return;
|
|
81
|
+
if (failure)
|
|
82
|
+
reject(failure);
|
|
83
|
+
else if (code !== 0)
|
|
84
|
+
reject(new DodoError('NOT_SUPPORTED', 'speech engine failed; check the installed voice in dodo setup'));
|
|
85
|
+
else
|
|
86
|
+
resolve();
|
|
87
|
+
});
|
|
88
|
+
child.stdin.end(engine.kind === 'windows-sapi' ? JSON.stringify({ mode: 'speak', input, output, voice }) : undefined);
|
|
89
|
+
});
|
|
90
|
+
const result = fs.lstatSync(output);
|
|
91
|
+
if (!result.isFile() || result.isSymbolicLink() || result.nlink !== 1 || result.size < 44 || result.size > 32 * 1024 * 1024)
|
|
92
|
+
throw new DodoError('RESOURCE_LIMIT', 'speech output is missing, unsafe or oversized');
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=speech.js.map
|