apex-code 0.0.1-alpha.7 → 0.0.1-alpha.9
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/CHANGELOG.md +229 -0
- package/README.md +5 -0
- package/dist/cli/args.d.ts +4 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +44 -9
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/concurrent-session.d.ts +3 -0
- package/dist/cli/concurrent-session.d.ts.map +1 -0
- package/dist/cli/concurrent-session.js +13 -0
- package/dist/cli/concurrent-session.js.map +1 -0
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +2 -1
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +3 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +21 -13
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +20 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +41 -9
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +172 -101
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +4 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +12 -9
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +10 -9
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +3 -0
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +4 -3
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +41 -32
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/defaults.d.ts +1 -0
- package/dist/core/defaults.d.ts.map +1 -1
- package/dist/core/defaults.js +9 -0
- package/dist/core/defaults.js.map +1 -1
- package/dist/core/durable-state/sqlite.d.ts.map +1 -1
- package/dist/core/durable-state/sqlite.js +26 -2
- package/dist/core/durable-state/sqlite.js.map +1 -1
- package/dist/core/experimental.d.ts +4 -0
- package/dist/core/experimental.d.ts.map +1 -1
- package/dist/core/experimental.js +4 -0
- package/dist/core/experimental.js.map +1 -1
- package/dist/core/extensions/index.d.ts +2 -2
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +115 -48
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +42 -9
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js +3 -0
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +1 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts +1 -2
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +2 -17
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/http-idle-timeout.d.ts +12 -0
- package/dist/core/http-idle-timeout.d.ts.map +1 -0
- package/dist/core/http-idle-timeout.js +27 -0
- package/dist/core/http-idle-timeout.js.map +1 -0
- package/dist/core/keybindings.d.ts +38 -13
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +26 -5
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/lsp/client.d.ts +2 -0
- package/dist/core/lsp/client.d.ts.map +1 -1
- package/dist/core/lsp/client.js +27 -2
- package/dist/core/lsp/client.js.map +1 -1
- package/dist/core/model-config.d.ts +10 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +4 -1
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-resolver.d.ts +1 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +14 -8
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/models-store.d.ts.map +1 -1
- package/dist/core/models-store.js +2 -1
- package/dist/core/models-store.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +22 -14
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/pi-manifest.d.ts.map +1 -1
- package/dist/core/pi-manifest.js +2 -1
- package/dist/core/pi-manifest.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +2 -1
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +3 -2
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sandbox/child-entry.d.ts.map +1 -1
- package/dist/core/sandbox/child-entry.js +8 -1
- package/dist/core/sandbox/child-entry.js.map +1 -1
- package/dist/core/sandbox/cli-launch.d.ts +3 -0
- package/dist/core/sandbox/cli-launch.d.ts.map +1 -1
- package/dist/core/sandbox/cli-launch.js +25 -4
- package/dist/core/sandbox/cli-launch.js.map +1 -1
- package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -1
- package/dist/core/sandbox/cli-supervisor.js +53 -13
- package/dist/core/sandbox/cli-supervisor.js.map +1 -1
- package/dist/core/sandbox/linux-backend.d.ts.map +1 -1
- package/dist/core/sandbox/linux-backend.js +15 -0
- package/dist/core/sandbox/linux-backend.js.map +1 -1
- package/dist/core/sandbox/macos-backend.d.ts.map +1 -1
- package/dist/core/sandbox/macos-backend.js +24 -1
- package/dist/core/sandbox/macos-backend.js.map +1 -1
- package/dist/core/sandbox/rpc/credential-client.d.ts +20 -0
- package/dist/core/sandbox/rpc/credential-client.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/credential-client.js +170 -0
- package/dist/core/sandbox/rpc/credential-client.js.map +1 -0
- package/dist/core/sandbox/rpc/credential-proxy.d.ts +44 -0
- package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/credential-proxy.js +381 -0
- package/dist/core/sandbox/rpc/credential-proxy.js.map +1 -0
- package/dist/core/sandbox/supervisor.d.ts +11 -0
- package/dist/core/sandbox/supervisor.d.ts.map +1 -1
- package/dist/core/sandbox/supervisor.js.map +1 -1
- package/dist/core/sandbox/terminal-size.d.ts +40 -0
- package/dist/core/sandbox/terminal-size.d.ts.map +1 -0
- package/dist/core/sandbox/terminal-size.js +105 -0
- package/dist/core/sandbox/terminal-size.js.map +1 -0
- package/dist/core/sdk.d.ts +8 -5
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +26 -7
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-export.d.ts +4 -0
- package/dist/core/session-export.d.ts.map +1 -0
- package/dist/core/session-export.js +32 -0
- package/dist/core/session-export.js.map +1 -0
- package/dist/core/session-lease.d.ts +31 -0
- package/dist/core/session-lease.d.ts.map +1 -0
- package/dist/core/session-lease.js +138 -0
- package/dist/core/session-lease.js.map +1 -0
- package/dist/core/session-manager.d.ts +5 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +5 -4
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-diagnostics.d.ts +9 -0
- package/dist/core/settings-diagnostics.d.ts.map +1 -0
- package/dist/core/settings-diagnostics.js +21 -0
- package/dist/core/settings-diagnostics.js.map +1 -0
- package/dist/core/settings-manager.d.ts +35 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +93 -12
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +51 -33
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +4 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts +2 -2
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +53 -62
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +14 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +38 -20
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/delegate.d.ts.map +1 -1
- package/dist/core/tools/delegate.js +24 -0
- package/dist/core/tools/delegate.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +0 -5
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +2 -5
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +22 -4
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +2 -0
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +35 -10
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +4 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +9 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +2 -0
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +5 -1
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/powershell.d.ts +16 -0
- package/dist/core/tools/powershell.d.ts.map +1 -0
- package/dist/core/tools/powershell.js +39 -0
- package/dist/core/tools/powershell.js.map +1 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +2 -0
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/web-search-exa.d.ts +33 -0
- package/dist/core/tools/web-search-exa.d.ts.map +1 -0
- package/dist/core/tools/web-search-exa.js +128 -0
- package/dist/core/tools/web-search-exa.js.map +1 -0
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +2 -0
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/trust-manager.d.ts.map +1 -1
- package/dist/core/trust-manager.js +2 -1
- package/dist/core/trust-manager.js.map +1 -1
- package/dist/core/web-search-provider.d.ts +64 -0
- package/dist/core/web-search-provider.d.ts.map +1 -0
- package/dist/core/web-search-provider.js +149 -0
- package/dist/core/web-search-provider.js.map +1 -0
- package/dist/extensions/llama/index.d.ts.map +1 -1
- package/dist/extensions/llama/index.js +2 -0
- package/dist/extensions/llama/index.js.map +1 -1
- package/dist/extensions/llama/provider.d.ts.map +1 -1
- package/dist/extensions/llama/provider.js +6 -2
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +5 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +38 -16
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +4 -3
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +23 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +105 -2
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.d.ts +7 -1
- package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
- package/dist/modes/interactive/components/dynamic-border.js +11 -2
- package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +4 -0
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +24 -6
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.js +6 -3
- package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
- package/dist/modes/interactive/components/first-use-hints.d.ts +8 -0
- package/dist/modes/interactive/components/first-use-hints.d.ts.map +1 -0
- package/dist/modes/interactive/components/first-use-hints.js +22 -0
- package/dist/modes/interactive/components/first-use-hints.js.map +1 -0
- package/dist/modes/interactive/components/footer.d.ts +12 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +128 -9
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +12 -3
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +71 -14
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +10 -3
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +128 -56
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-submenu.d.ts +72 -0
- package/dist/modes/interactive/components/settings-submenu.d.ts.map +1 -0
- package/dist/modes/interactive/components/settings-submenu.js +165 -0
- package/dist/modes/interactive/components/settings-submenu.js.map +1 -0
- package/dist/modes/interactive/components/splash-header.d.ts +29 -4
- package/dist/modes/interactive/components/splash-header.d.ts.map +1 -1
- package/dist/modes/interactive/components/splash-header.js +58 -23
- package/dist/modes/interactive/components/splash-header.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +74 -17
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +14 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +113 -35
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-panel.d.ts +30 -0
- package/dist/modes/interactive/components/tool-panel.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-panel.js +130 -0
- package/dist/modes/interactive/components/tool-panel.js.map +1 -0
- package/dist/modes/interactive/external-editor.d.ts.map +1 -1
- package/dist/modes/interactive/external-editor.js +2 -1
- package/dist/modes/interactive/external-editor.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +55 -9
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +446 -87
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/model-catalog-refresh.d.ts +7 -0
- package/dist/modes/interactive/model-catalog-refresh.d.ts.map +1 -0
- package/dist/modes/interactive/model-catalog-refresh.js +34 -0
- package/dist/modes/interactive/model-catalog-refresh.js.map +1 -0
- package/dist/modes/interactive/theme/apex.json +62 -63
- package/dist/modes/interactive/theme/dark.json +2 -0
- package/dist/modes/interactive/theme/light.json +2 -0
- package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
- package/dist/modes/interactive/theme/theme-controller.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme-controller.js +25 -11
- package/dist/modes/interactive/theme/theme-controller.js.map +1 -1
- package/dist/modes/interactive/theme/theme-schema.json +9 -1
- package/dist/modes/interactive/theme/theme.d.ts +14 -3
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +60 -10
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/json-event.d.ts +18 -12
- package/dist/modes/json-event.d.ts.map +1 -1
- package/dist/modes/json-event.js +22 -5
- package/dist/modes/json-event.js.map +1 -1
- package/dist/package-manager-cli.d.ts +1 -0
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +197 -5
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/server/create-harness.d.ts.map +1 -1
- package/dist/server/create-harness.js +2 -0
- package/dist/server/create-harness.js.map +1 -1
- package/dist/themes/apex-logo.d.ts +36 -16
- package/dist/themes/apex-logo.d.ts.map +1 -1
- package/dist/themes/apex-logo.js +32 -28
- package/dist/themes/apex-logo.js.map +1 -1
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +2 -1
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/management-http.d.ts +5 -3
- package/dist/utils/management-http.d.ts.map +1 -1
- package/dist/utils/management-http.js +14 -10
- package/dist/utils/management-http.js.map +1 -1
- package/dist/utils/shell.d.ts +3 -0
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +17 -5
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/syntax-highlight.d.ts +1 -0
- package/dist/utils/syntax-highlight.d.ts.map +1 -1
- package/dist/utils/syntax-highlight.js +60 -1
- package/dist/utils/syntax-highlight.js.map +1 -1
- package/dist/utils/text.d.ts +8 -0
- package/dist/utils/text.d.ts.map +1 -0
- package/dist/utils/text.js +9 -0
- package/dist/utils/text.js.map +1 -0
- package/dist/utils/tools-manager.d.ts +10 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +15 -20
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/compaction.md +18 -3
- package/docs/custom-provider.md +6 -3
- package/docs/environment-variables.md +1 -0
- package/docs/extensions.md +21 -7
- package/docs/json.md +12 -5
- package/docs/keybindings.md +15 -9
- package/docs/llama-cpp.md +2 -0
- package/docs/models.md +8 -2
- package/docs/packages.md +2 -2
- package/docs/rpc.md +24 -7
- package/docs/sdk.md +16 -2
- package/docs/settings.md +73 -2
- package/docs/skills.md +4 -3
- package/docs/terminal-setup.md +34 -9
- package/docs/themes.md +24 -3
- package/docs/tui.md +2 -2
- package/docs/usage.md +9 -4
- package/docs/windows.md +24 -2
- package/examples/extensions/border-status-editor.ts +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/git-checkpoint.ts +2 -2
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/notify.ts +3 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/README.md +3 -1
- package/examples/extensions/subagent/agents.ts +40 -9
- package/examples/extensions/subagent/index.ts +27 -4
- package/examples/extensions/titlebar-spinner.ts +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/rpc-extension-ui.ts +1 -1
- package/examples/sdk/06-extensions.ts +1 -1
- package/examples/sdk/README.md +1 -1
- package/npm-shrinkwrap.json +280 -445
- package/package.json +9 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools-manager.d.ts","sourceRoot":"","sources":["../../src/utils/tools-manager.ts"],"names":[],"mappings":"AAqEA,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,GAAG,IAAI,CAwBvG;AA6GD,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,QAAQ,GAAG,KAAK,CAAC;CAC9B;AAED,oFAAoF;AACpF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE;IACnD,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC;CACrB,GAAG,mBAAmB,CAetB;AAmJD,MAAM,WAAW,gCAAgC;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,gCAAgC,GAAG,OAAO,CAAC,MAAM,CAAC,CAiD1G;AAmCD,MAAM,WAAW,cAAc;IAC9B,uFAAuF;IACvF,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;CACb;AAaD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACpC,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;CAAO,GACvF,MAAM,GAAG,SAAS,CA0BpB;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,GAAE,OAAe,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAUhG;AAUD,wBAAsB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM,GAAE,OAAe,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA2CxG","sourcesContent":["import chalk from \"chalk\";\nimport { type SpawnSyncReturns, spawnSync } from \"child_process\";\nimport { createHash } from \"crypto\";\nimport {\n\tchmodSync,\n\tcreateReadStream,\n\tcreateWriteStream,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\trenameSync,\n\trmSync,\n\tstatSync,\n} from \"fs\";\nimport { arch, platform } from \"os\";\nimport { basename, join, relative, resolve as resolvePath } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { getBinDir } from \"../config.ts\";\nimport { getApexEnvironment } from \"../core/environment.ts\";\nimport { fetchWithRetry } from \"./management-http.ts\";\n\nconst TOOLS_DIR = getBinDir();\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\nfunction isOfflineModeEnabled(): boolean {\n\tconst value = getApexEnvironment(\"APEX_CODE_OFFLINE\");\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\ninterface ToolConfig {\n\tname: string;\n\trepo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n\tbinaryName: string; // Name of the binary inside the archive\n\tsystemBinaryNames?: string[]; // Alternative system command names to try before downloading\n\ttagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n}\n\n// Per-platform/architecture asset names live in MANAGED_TOOL_PINS (ADR 0017) as the\n// single reviewed source of truth, not derived here.\nconst TOOLS: Record<string, ToolConfig> = {\n\tfd: {\n\t\tname: \"fd\",\n\t\trepo: \"sharkdp/fd\",\n\t\tbinaryName: \"fd\",\n\t\tsystemBinaryNames: [\"fd\", \"fdfind\"],\n\t\ttagPrefix: \"v\",\n\t},\n\trg: {\n\t\tname: \"ripgrep\",\n\t\trepo: \"BurntSushi/ripgrep\",\n\t\tbinaryName: \"rg\",\n\t\ttagPrefix: \"\",\n\t},\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n\ttry {\n\t\tconst result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n\t\t// Check for ENOENT error (command not found)\n\t\treturn result.error === undefined || result.error === null;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// Get the path to a tool (system-wide or in our tools dir)\nexport function getToolPath(tool: \"fd\" | \"rg\", options: { toolsDirectory?: string } = {}): string | null {\n\tconst config = TOOLS[tool];\n\tif (!config) return null;\n\n\t// Check our tools directory first. Existence alone is not enough: the sandbox\n\t// projects host tools by bind-mounting over a file here, and bwrap leaves that\n\t// mountpoint behind as an empty, non-executable stub once the namespace is gone.\n\tconst managedPath = join(\n\t\toptions.toolsDirectory ?? TOOLS_DIR,\n\t\tconfig.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"),\n\t);\n\tif (isExecutableFile(managedPath, platform())) {\n\t\treturn managedPath;\n\t}\n\n\t// Check system PATH - if found, just return the command name (it's in PATH)\n\tconst systemBinaryNames = config.systemBinaryNames ?? [config.binaryName];\n\tfor (const systemBinaryName of systemBinaryNames) {\n\t\tif (commandExists(systemBinaryName)) {\n\t\t\treturn systemBinaryName;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Reviewed, pinned release metadata for auto-downloaded executable tools (ADR 0017).\n * Never resolved from a mutable \"latest\" API call. Updating a version, asset name, or\n * digest here is itself the reviewed change that authorizes installing a new release.\n *\n * Every digest and byte size below was established by downloading the artifact,\n * computing its SHA-256 locally, and cross-checking that value against the\n * upstream-published digest (ripgrep's own `.sha256` release sidecars; GitHub's own\n * per-asset `digest` field for both projects) — not invented, and not taken from a\n * single unverified source.\n */\ninterface ManagedToolPin {\n\tversion: string;\n\tassetName: string;\n\tsha256: string;\n\texpectedBytes: number;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n}\n\nconst MANAGED_TOOL_PINS: Record<\"fd\" | \"rg\", Partial<Record<string, ManagedToolPin>>> = {\n\trg: {\n\t\t\"linux:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz\",\n\t\t\tsha256: \"4cf9f2741e6c465ffdb7c26f38056a59e2a2544b51f7cc128ef28337eeae4d8e\",\n\t\t\texpectedBytes: 2566310,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"linux:arm64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-aarch64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"c827481c4ff4ea10c9dc7a4022c8de5db34a5737cb74484d62eb94a95841ab2f\",\n\t\t\texpectedBytes: 2047405,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"fc87e78f7cb3fea12d69072e7ef3b21509754717b746368fd40d88963630e2b3\",\n\t\t\texpectedBytes: 2082672,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:arm64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-aarch64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"24ad76777745fbff131c8fbc466742b011f925bfa4fffa2ded6def23b5b937be\",\n\t\t\texpectedBytes: 1787248,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"win32:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"d0f534024c42afd6cb4d38907c25cd2b249b79bbe6cc1dbee8e3e37c2b6e25a1\",\n\t\t\texpectedBytes: 2058893,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t\t// No upstream win32/arm64 ripgrep release asset exists at 14.1.1; resolution\n\t\t// fails closed for that combination rather than constructing a URL that 404s.\n\t},\n\tfd: {\n\t\t\"linux:x64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-x86_64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"def59805cd14b5651b68990855f426ad087f3b96881296d963910431ba3143c8\",\n\t\t\texpectedBytes: 1700779,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"linux:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"6c51f7c5446b3338b1e401ff15dc194c590bb2fa64fd43ff3278300f073adec5\",\n\t\t\texpectedBytes: 1559490,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t// Upstream stopped shipping an Intel macOS fd binary after 10.3.0 (see\n\t\t// 7afd80d78, \"pin fd for macos x64\"); preserved here rather than special-cased.\n\t\t\"darwin:x64\": {\n\t\t\tversion: \"10.3.0\",\n\t\t\tassetName: \"fd-v10.3.0-x86_64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"50d30f13fe3d5914b14c4fff5abcbd4d0cdab4b855970a6956f4f006c17117a3\",\n\t\t\texpectedBytes: 1430203,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"623dc0afc81b92e4d4606b380d7bc91916ba7b97814263e554d50923a39e480a\",\n\t\t\texpectedBytes: 1328933,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"win32:x64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-x86_64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"b2816e506390a89941c63c9187d58a3cc10e9a55f2ef0685f9ea0eccaf7c98c8\",\n\t\t\texpectedBytes: 1525898,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t\t\"win32:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"4f9110c2d5b33a7f760bfa5510f4c113d828109f7277d421b1053a9943c0fc92\",\n\t\t\texpectedBytes: 1370473,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t},\n};\n\nexport interface ManagedToolArtifact {\n\tversion: string;\n\turl: string;\n\tsha256: string;\n\texpectedBytes: number;\n\t/** Bounded download ceiling (ADR 0017): four times the reviewed expected size. */\n\tmaxBytes: number;\n\tbinaryName: string;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n}\n\n/** Resolve reviewed, pinned metadata for a managed tool. Never queries \"latest\". */\nexport function resolveManagedToolArtifact(options: {\n\ttool: \"fd\" | \"rg\";\n\tplatform: NodeJS.Platform | string;\n\tarchitecture: string;\n}): ManagedToolArtifact {\n\tconst config = TOOLS[options.tool];\n\tconst pin = MANAGED_TOOL_PINS[options.tool]?.[`${options.platform}:${options.architecture}`];\n\tif (!config || !pin) {\n\t\tthrow new Error(`No reviewed pinned artifact for ${options.tool} on ${options.platform}/${options.architecture}`);\n\t}\n\treturn {\n\t\tversion: pin.version,\n\t\turl: `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${pin.version}/${pin.assetName}`,\n\t\tsha256: pin.sha256,\n\t\texpectedBytes: pin.expectedBytes,\n\t\tmaxBytes: pin.expectedBytes * 4,\n\t\tbinaryName: config.binaryName,\n\t\tarchiveKind: pin.archiveKind,\n\t};\n}\n\n// Download a file from URL, aborting if the declared or received size exceeds the\n// bounded ceiling (ADR 0017) rather than writing an unbounded stream to disk.\nasync function downloadFile(url: string, dest: string, maxBytes: number): Promise<void> {\n\tconst response = await fetchWithRetry(url, undefined, { timeoutMs: DOWNLOAD_TIMEOUT_MS });\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to download: ${response.status}`);\n\t}\n\n\tif (!response.body) {\n\t\tthrow new Error(\"No response body\");\n\t}\n\n\tconst declaredLength = Number(response.headers.get(\"content-length\") ?? Number.NaN);\n\tif (Number.isFinite(declaredLength) && declaredLength > maxBytes) {\n\t\tthrow new Error(`Refusing download: declared size ${declaredLength} exceeds bounded limit ${maxBytes}`);\n\t}\n\n\tlet received = 0;\n\tconst source = Readable.fromWeb(response.body as any);\n\tsource.on(\"data\", (chunk: Buffer) => {\n\t\treceived += chunk.length;\n\t\tif (received > maxBytes) {\n\t\t\tsource.destroy(new Error(`Refusing download: exceeded bounded limit ${maxBytes} bytes`));\n\t\t}\n\t});\n\n\tconst fileStream = createWriteStream(dest);\n\ttry {\n\t\tawait pipeline(source, fileStream);\n\t} catch (error) {\n\t\trmSync(dest, { force: true });\n\t\tthrow error;\n\t}\n}\n\nfunction findBinaryRecursively(rootDir: string, binaryFileName: string): string | null {\n\tconst stack: string[] = [rootDir];\n\n\twhile (stack.length > 0) {\n\t\tconst currentDir = stack.pop();\n\t\tif (!currentDir) continue;\n\n\t\tconst entries = readdirSync(currentDir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(currentDir, entry.name);\n\t\t\tif (entry.isFile() && entry.name === binaryFileName) {\n\t\t\t\treturn fullPath;\n\t\t\t}\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tstack.push(fullPath);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction formatSpawnFailure(result: SpawnSyncReturns<Buffer>): string {\n\tif (result.error?.message) {\n\t\treturn result.error.message;\n\t}\n\tconst stderr = result.stderr?.toString().trim();\n\tif (stderr) {\n\t\treturn stderr;\n\t}\n\tconst stdout = result.stdout?.toString().trim();\n\tif (stdout) {\n\t\treturn stdout;\n\t}\n\treturn `exit status ${result.status ?? \"unknown\"}`;\n}\n\nfunction runExtractionCommand(command: string, args: string[]): string | null {\n\tconst result = spawnSync(command, args, { stdio: \"pipe\" });\n\tif (!result.error && result.status === 0) {\n\t\treturn null;\n\t}\n\treturn `${command}: ${formatSpawnFailure(result)}`;\n}\n\nfunction extractTarGzArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failure = runExtractionCommand(\"tar\", [\"xzf\", archivePath, \"-C\", extractDir]);\n\tif (failure) {\n\t\tthrow new Error(`Failed to extract ${assetName}: ${failure}`);\n\t}\n}\n\nfunction getWindowsTarCommand(): string {\n\tconst systemRoot = process.env.SystemRoot ?? process.env.WINDIR;\n\tif (systemRoot) {\n\t\tconst systemTar = join(systemRoot, \"System32\", \"tar.exe\");\n\t\tif (existsSync(systemTar)) {\n\t\t\treturn systemTar;\n\t\t}\n\t}\n\treturn \"tar.exe\";\n}\n\nfunction extractZipArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failures: string[] = [];\n\n\tif (platform() === \"win32\") {\n\t\t// Windows ships bsdtar as tar.exe, which supports zip files. Prefer the\n\t\t// System32 binary over Git Bash's GNU tar, which does not handle zip archives.\n\t\tconst tarFailure = runExtractionCommand(getWindowsTarCommand(), [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\n\t\tconst script =\n\t\t\t\"& { param($archive, $destination) $ErrorActionPreference = 'Stop'; Expand-Archive -LiteralPath $archive -DestinationPath $destination -Force }\";\n\t\tconst powershellFailure = runExtractionCommand(\"powershell.exe\", [\n\t\t\t\"-NoLogo\",\n\t\t\t\"-NoProfile\",\n\t\t\t\"-NonInteractive\",\n\t\t\t\"-ExecutionPolicy\",\n\t\t\t\"Bypass\",\n\t\t\t\"-Command\",\n\t\t\tscript,\n\t\t\tarchivePath,\n\t\t\textractDir,\n\t\t]);\n\t\tif (!powershellFailure) return;\n\t\tfailures.push(powershellFailure);\n\t} else {\n\t\tconst unzipFailure = runExtractionCommand(\"unzip\", [\"-q\", archivePath, \"-d\", extractDir]);\n\t\tif (!unzipFailure) return;\n\t\tfailures.push(unzipFailure);\n\n\t\tconst tarFailure = runExtractionCommand(\"tar\", [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\t}\n\n\tthrow new Error(`Failed to extract ${assetName}: ${failures.join(\"; \")}`);\n}\n\nasync function sha256File(path: string): Promise<string> {\n\tconst hash = createHash(\"sha256\");\n\tfor await (const chunk of createReadStream(path)) {\n\t\thash.update(chunk as Buffer);\n\t}\n\treturn hash.digest(\"hex\");\n}\n\nexport interface InstallManagedToolArchiveOptions {\n\tarchivePath: string;\n\tdestination: string;\n\tbinaryName: string;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n\texpectedSha256: string;\n\tmaxBytes: number;\n}\n\n/**\n * Verify a downloaded tool archive against its pinned digest, extract it into a\n * per-install quarantine directory, confirm the located binary did not escape that\n * quarantine, and only then atomically promote it into the live tools directory\n * (ADR 0017). Rejects a digest mismatch or an out-of-bounds archive before anything\n * is written to `destination`.\n */\nexport async function installManagedToolArchive(options: InstallManagedToolArchiveOptions): Promise<string> {\n\tconst { archivePath, destination, binaryName, archiveKind, expectedSha256, maxBytes } = options;\n\n\tconst { size } = statSync(archivePath);\n\tif (size > maxBytes) {\n\t\tthrow new Error(`Refusing install: archive size ${size} exceeds bounded limit ${maxBytes}`);\n\t}\n\n\tconst actualSha256 = await sha256File(archivePath);\n\tif (actualSha256.toLowerCase() !== expectedSha256.toLowerCase()) {\n\t\tthrow new Error(`Refusing install: digest mismatch for ${basename(archivePath)}`);\n\t}\n\n\tmkdirSync(destination, { recursive: true });\n\tconst quarantineDirectory = join(\n\t\tdestination,\n\t\t`.quarantine_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n\t);\n\tmkdirSync(quarantineDirectory, { recursive: true });\n\n\ttry {\n\t\tconst assetName = basename(archivePath);\n\t\tif (archiveKind === \"tar.gz\") {\n\t\t\textractTarGzArchive(archivePath, quarantineDirectory, assetName);\n\t\t} else {\n\t\t\textractZipArchive(archivePath, quarantineDirectory, assetName);\n\t\t}\n\n\t\tconst binaryFileName = binaryName + (platform() === \"win32\" ? \".exe\" : \"\");\n\t\tconst extractedBinary = findBinaryRecursively(quarantineDirectory, binaryFileName);\n\t\tif (!extractedBinary) {\n\t\t\tthrow new Error(`Binary not found in verified archive: expected ${binaryFileName}`);\n\t\t}\n\n\t\tconst resolvedBinary = resolvePath(extractedBinary);\n\t\tconst containmentPath = relative(resolvePath(quarantineDirectory), resolvedBinary);\n\t\tif (containmentPath.startsWith(\"..\") || resolvePath(containmentPath) === containmentPath) {\n\t\t\tthrow new Error(`Refusing install: archive entry escaped its quarantine directory: ${extractedBinary}`);\n\t\t}\n\n\t\tconst finalPath = join(destination, binaryFileName);\n\t\trenameSync(resolvedBinary, finalPath);\n\t\tif (platform() !== \"win32\") {\n\t\t\tchmodSync(finalPath, 0o755);\n\t\t}\n\t\treturn finalPath;\n\t} finally {\n\t\trmSync(quarantineDirectory, { recursive: true, force: true });\n\t}\n}\n\n// Download and install a tool from reviewed, pinned metadata (ADR 0017): bounded\n// download, digest verification, quarantine extraction, then atomic promotion.\nasync function downloadTool(tool: \"fd\" | \"rg\"): Promise<string> {\n\tconst plat = platform();\n\tconst architecture = arch();\n\tconst artifact = resolveManagedToolArtifact({ tool, platform: plat, architecture });\n\n\tmkdirSync(TOOLS_DIR, { recursive: true });\n\n\t// Unique download filename: fd and rg downloads can run concurrently during\n\t// startup, so sharing a fixed path causes races.\n\tconst archivePath = join(\n\t\tTOOLS_DIR,\n\t\t`download_${artifact.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}${\n\t\t\tartifact.archiveKind === \"zip\" ? \".zip\" : \".tar.gz\"\n\t\t}`,\n\t);\n\n\ttry {\n\t\tawait downloadFile(artifact.url, archivePath, artifact.maxBytes);\n\t\treturn await installManagedToolArchive({\n\t\t\tarchivePath,\n\t\t\tdestination: TOOLS_DIR,\n\t\t\tbinaryName: artifact.binaryName,\n\t\t\tarchiveKind: artifact.archiveKind,\n\t\t\texpectedSha256: artifact.sha256,\n\t\t\tmaxBytes: artifact.maxBytes,\n\t\t});\n\t} finally {\n\t\trmSync(archivePath, { force: true });\n\t}\n}\n\nexport interface HostToolBinary {\n\t/** File name the child looks for in its own tools directory (\"fd\", \"rg\", \"fd.exe\"). */\n\tname: string;\n\t/** Absolute host path to the executable backing that name. */\n\tpath: string;\n}\n\nfunction isExecutableFile(candidate: string, targetPlatform: NodeJS.Platform): boolean {\n\ttry {\n\t\tconst stats = statSync(candidate);\n\t\tif (!stats.isFile()) return false;\n\t\t// Windows has no POSIX execute bit; presence of the file is the only signal.\n\t\treturn targetPlatform === \"win32\" || (stats.mode & 0o111) !== 0;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Absolute host path for a managed tool, or undefined when it is installed nowhere.\n *\n * `getToolPath` may return a bare command name because its caller spawns through PATH.\n * A sandboxed child has no such luxury: its PATH still lists host directories that the\n * OS boundary has already replaced, so the supervisor must resolve a real path here and\n * project it in (ADR 0017, and the sandbox spec's tool-projection amendment).\n */\nexport function resolveHostToolBinary(\n\ttool: \"fd\" | \"rg\",\n\toptions: { toolsDirectory?: string; pathValue?: string; platform?: NodeJS.Platform } = {},\n): string | undefined {\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tconst targetPlatform = options.platform ?? platform();\n\tconst executableSuffix = targetPlatform === \"win32\" ? \".exe\" : \"\";\n\tconst managedPath = join(options.toolsDirectory ?? TOOLS_DIR, config.binaryName + executableSuffix);\n\tif (isExecutableFile(managedPath, targetPlatform)) {\n\t\treturn managedPath;\n\t}\n\n\tconst pathEntries = (options.pathValue ?? process.env.PATH ?? \"\")\n\t\t.split(targetPlatform === \"win32\" ? \";\" : \":\")\n\t\t.filter((entry) => entry.length > 0);\n\t// Preference runs by name first, matching getToolPath: a real `fd` anywhere on PATH\n\t// beats a `fdfind` alias, rather than whichever directory happens to come first.\n\tfor (const systemBinaryName of config.systemBinaryNames ?? [config.binaryName]) {\n\t\tfor (const entry of pathEntries) {\n\t\t\tconst candidate = join(entry, systemBinaryName + executableSuffix);\n\t\t\tif (isExecutableFile(candidate, targetPlatform)) {\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Install any missing managed tool on the host, then report absolute paths for the ones\n * that exist. Downloading here rather than inside the child means one install serves every\n * workspace, and it happens on the side of the boundary that still has network access.\n */\nexport async function prepareHostToolBinaries(silent: boolean = false): Promise<HostToolBinary[]> {\n\tconst binaries: HostToolBinary[] = [];\n\tfor (const tool of [\"fd\", \"rg\"] as const) {\n\t\tawait ensureTool(tool, silent);\n\t\tconst hostPath = resolveHostToolBinary(tool);\n\t\tif (hostPath) {\n\t\t\tbinaries.push({ name: TOOLS[tool].binaryName + (platform() === \"win32\" ? \".exe\" : \"\"), path: hostPath });\n\t\t}\n\t}\n\treturn binaries;\n}\n\n// Termux package names for tools\nconst TERMUX_PACKAGES: Record<string, string> = {\n\tfd: \"fd\",\n\trg: \"ripgrep\",\n};\n\n// Ensure a tool is available, downloading if necessary\n// Returns the path to the tool, or null if unavailable\nexport async function ensureTool(tool: \"fd\" | \"rg\", silent: boolean = false): Promise<string | undefined> {\n\tconst existingPath = getToolPath(tool);\n\tif (existingPath) {\n\t\treturn existingPath;\n\t}\n\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tif (isOfflineModeEnabled()) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`${config.name} not found. Offline mode enabled, skipping download.`));\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.\n\t// Users must install via pkg.\n\tif (platform() === \"android\") {\n\t\tconst pkgName = TERMUX_PACKAGES[tool] ?? tool;\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`${config.name} not found. Install with: pkg install ${pkgName}`));\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// Tool not found - download it\n\tif (!silent) {\n\t\tconsole.log(chalk.dim(`${config.name} not found. Downloading...`));\n\t}\n\n\ttry {\n\t\tconst path = await downloadTool(tool);\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.dim(`${config.name} installed to ${path}`));\n\t\t}\n\t\treturn path;\n\t} catch (e) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`));\n\t\t}\n\t\treturn undefined;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"tools-manager.d.ts","sourceRoot":"","sources":["../../src/utils/tools-manager.ts"],"names":[],"mappings":"AAoEA,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,MAAM,GAAG,IAAI,CAwBvG;AA6GD,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,QAAQ,GAAG,KAAK,CAAC;CAC9B;AAED,oFAAoF;AACpF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE;IACnD,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC;CACrB,GAAG,mBAAmB,CAetB;AAmJD,MAAM,WAAW,gCAAgC;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,gCAAgC,GAAG,OAAO,CAAC,MAAM,CAAC,CAiD1G;AAmCD,MAAM,WAAW,cAAc;IAC9B,uFAAuF;IACvF,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;CACb;AAaD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACpC,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;CAAO,GACvF,MAAM,GAAG,SAAS,CA0BpB;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,GAAE,OAAe,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAUhG;AAQD,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAC/B,IAAI,EAAE,IAAI,GAAG,IAAI,EACjB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,GACrC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAoC7B","sourcesContent":["import { type SpawnSyncReturns, spawnSync } from \"child_process\";\nimport { createHash } from \"crypto\";\nimport {\n\tchmodSync,\n\tcreateReadStream,\n\tcreateWriteStream,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\trenameSync,\n\trmSync,\n\tstatSync,\n} from \"fs\";\nimport { arch, platform } from \"os\";\nimport { basename, join, relative, resolve as resolvePath } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { getBinDir } from \"../config.ts\";\nimport { getApexEnvironment } from \"../core/environment.ts\";\nimport { fetchWithRetry } from \"./management-http.ts\";\n\nconst TOOLS_DIR = getBinDir();\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\nfunction isOfflineModeEnabled(): boolean {\n\tconst value = getApexEnvironment(\"APEX_CODE_OFFLINE\");\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\ninterface ToolConfig {\n\tname: string;\n\trepo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n\tbinaryName: string; // Name of the binary inside the archive\n\tsystemBinaryNames?: string[]; // Alternative system command names to try before downloading\n\ttagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n}\n\n// Per-platform/architecture asset names live in MANAGED_TOOL_PINS (ADR 0017) as the\n// single reviewed source of truth, not derived here.\nconst TOOLS: Record<string, ToolConfig> = {\n\tfd: {\n\t\tname: \"fd\",\n\t\trepo: \"sharkdp/fd\",\n\t\tbinaryName: \"fd\",\n\t\tsystemBinaryNames: [\"fd\", \"fdfind\"],\n\t\ttagPrefix: \"v\",\n\t},\n\trg: {\n\t\tname: \"ripgrep\",\n\t\trepo: \"BurntSushi/ripgrep\",\n\t\tbinaryName: \"rg\",\n\t\ttagPrefix: \"\",\n\t},\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n\ttry {\n\t\tconst result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n\t\t// Check for ENOENT error (command not found)\n\t\treturn result.error === undefined || result.error === null;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// Get the path to a tool (system-wide or in our tools dir)\nexport function getToolPath(tool: \"fd\" | \"rg\", options: { toolsDirectory?: string } = {}): string | null {\n\tconst config = TOOLS[tool];\n\tif (!config) return null;\n\n\t// Check our tools directory first. Existence alone is not enough: the sandbox\n\t// projects host tools by bind-mounting over a file here, and bwrap leaves that\n\t// mountpoint behind as an empty, non-executable stub once the namespace is gone.\n\tconst managedPath = join(\n\t\toptions.toolsDirectory ?? TOOLS_DIR,\n\t\tconfig.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"),\n\t);\n\tif (isExecutableFile(managedPath, platform())) {\n\t\treturn managedPath;\n\t}\n\n\t// Check system PATH - if found, just return the command name (it's in PATH)\n\tconst systemBinaryNames = config.systemBinaryNames ?? [config.binaryName];\n\tfor (const systemBinaryName of systemBinaryNames) {\n\t\tif (commandExists(systemBinaryName)) {\n\t\t\treturn systemBinaryName;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Reviewed, pinned release metadata for auto-downloaded executable tools (ADR 0017).\n * Never resolved from a mutable \"latest\" API call. Updating a version, asset name, or\n * digest here is itself the reviewed change that authorizes installing a new release.\n *\n * Every digest and byte size below was established by downloading the artifact,\n * computing its SHA-256 locally, and cross-checking that value against the\n * upstream-published digest (ripgrep's own `.sha256` release sidecars; GitHub's own\n * per-asset `digest` field for both projects) — not invented, and not taken from a\n * single unverified source.\n */\ninterface ManagedToolPin {\n\tversion: string;\n\tassetName: string;\n\tsha256: string;\n\texpectedBytes: number;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n}\n\nconst MANAGED_TOOL_PINS: Record<\"fd\" | \"rg\", Partial<Record<string, ManagedToolPin>>> = {\n\trg: {\n\t\t\"linux:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz\",\n\t\t\tsha256: \"4cf9f2741e6c465ffdb7c26f38056a59e2a2544b51f7cc128ef28337eeae4d8e\",\n\t\t\texpectedBytes: 2566310,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"linux:arm64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-aarch64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"c827481c4ff4ea10c9dc7a4022c8de5db34a5737cb74484d62eb94a95841ab2f\",\n\t\t\texpectedBytes: 2047405,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"fc87e78f7cb3fea12d69072e7ef3b21509754717b746368fd40d88963630e2b3\",\n\t\t\texpectedBytes: 2082672,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:arm64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-aarch64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"24ad76777745fbff131c8fbc466742b011f925bfa4fffa2ded6def23b5b937be\",\n\t\t\texpectedBytes: 1787248,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"win32:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"d0f534024c42afd6cb4d38907c25cd2b249b79bbe6cc1dbee8e3e37c2b6e25a1\",\n\t\t\texpectedBytes: 2058893,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t\t// No upstream win32/arm64 ripgrep release asset exists at 14.1.1; resolution\n\t\t// fails closed for that combination rather than constructing a URL that 404s.\n\t},\n\tfd: {\n\t\t\"linux:x64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-x86_64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"def59805cd14b5651b68990855f426ad087f3b96881296d963910431ba3143c8\",\n\t\t\texpectedBytes: 1700779,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"linux:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"6c51f7c5446b3338b1e401ff15dc194c590bb2fa64fd43ff3278300f073adec5\",\n\t\t\texpectedBytes: 1559490,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t// Upstream stopped shipping an Intel macOS fd binary after 10.3.0 (see\n\t\t// 7afd80d78, \"pin fd for macos x64\"); preserved here rather than special-cased.\n\t\t\"darwin:x64\": {\n\t\t\tversion: \"10.3.0\",\n\t\t\tassetName: \"fd-v10.3.0-x86_64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"50d30f13fe3d5914b14c4fff5abcbd4d0cdab4b855970a6956f4f006c17117a3\",\n\t\t\texpectedBytes: 1430203,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"623dc0afc81b92e4d4606b380d7bc91916ba7b97814263e554d50923a39e480a\",\n\t\t\texpectedBytes: 1328933,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"win32:x64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-x86_64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"b2816e506390a89941c63c9187d58a3cc10e9a55f2ef0685f9ea0eccaf7c98c8\",\n\t\t\texpectedBytes: 1525898,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t\t\"win32:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"4f9110c2d5b33a7f760bfa5510f4c113d828109f7277d421b1053a9943c0fc92\",\n\t\t\texpectedBytes: 1370473,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t},\n};\n\nexport interface ManagedToolArtifact {\n\tversion: string;\n\turl: string;\n\tsha256: string;\n\texpectedBytes: number;\n\t/** Bounded download ceiling (ADR 0017): four times the reviewed expected size. */\n\tmaxBytes: number;\n\tbinaryName: string;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n}\n\n/** Resolve reviewed, pinned metadata for a managed tool. Never queries \"latest\". */\nexport function resolveManagedToolArtifact(options: {\n\ttool: \"fd\" | \"rg\";\n\tplatform: NodeJS.Platform | string;\n\tarchitecture: string;\n}): ManagedToolArtifact {\n\tconst config = TOOLS[options.tool];\n\tconst pin = MANAGED_TOOL_PINS[options.tool]?.[`${options.platform}:${options.architecture}`];\n\tif (!config || !pin) {\n\t\tthrow new Error(`No reviewed pinned artifact for ${options.tool} on ${options.platform}/${options.architecture}`);\n\t}\n\treturn {\n\t\tversion: pin.version,\n\t\turl: `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${pin.version}/${pin.assetName}`,\n\t\tsha256: pin.sha256,\n\t\texpectedBytes: pin.expectedBytes,\n\t\tmaxBytes: pin.expectedBytes * 4,\n\t\tbinaryName: config.binaryName,\n\t\tarchiveKind: pin.archiveKind,\n\t};\n}\n\n// Download a file from URL, aborting if the declared or received size exceeds the\n// bounded ceiling (ADR 0017) rather than writing an unbounded stream to disk.\nasync function downloadFile(url: string, dest: string, maxBytes: number): Promise<void> {\n\tconst response = await fetchWithRetry(url, undefined, { timeoutMs: DOWNLOAD_TIMEOUT_MS });\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to download: ${response.status}`);\n\t}\n\n\tif (!response.body) {\n\t\tthrow new Error(\"No response body\");\n\t}\n\n\tconst declaredLength = Number(response.headers.get(\"content-length\") ?? Number.NaN);\n\tif (Number.isFinite(declaredLength) && declaredLength > maxBytes) {\n\t\tthrow new Error(`Refusing download: declared size ${declaredLength} exceeds bounded limit ${maxBytes}`);\n\t}\n\n\tlet received = 0;\n\tconst source = Readable.fromWeb(response.body as any);\n\tsource.on(\"data\", (chunk: Buffer) => {\n\t\treceived += chunk.length;\n\t\tif (received > maxBytes) {\n\t\t\tsource.destroy(new Error(`Refusing download: exceeded bounded limit ${maxBytes} bytes`));\n\t\t}\n\t});\n\n\tconst fileStream = createWriteStream(dest);\n\ttry {\n\t\tawait pipeline(source, fileStream);\n\t} catch (error) {\n\t\trmSync(dest, { force: true });\n\t\tthrow error;\n\t}\n}\n\nfunction findBinaryRecursively(rootDir: string, binaryFileName: string): string | null {\n\tconst stack: string[] = [rootDir];\n\n\twhile (stack.length > 0) {\n\t\tconst currentDir = stack.pop();\n\t\tif (!currentDir) continue;\n\n\t\tconst entries = readdirSync(currentDir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(currentDir, entry.name);\n\t\t\tif (entry.isFile() && entry.name === binaryFileName) {\n\t\t\t\treturn fullPath;\n\t\t\t}\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tstack.push(fullPath);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction formatSpawnFailure(result: SpawnSyncReturns<Buffer>): string {\n\tif (result.error?.message) {\n\t\treturn result.error.message;\n\t}\n\tconst stderr = result.stderr?.toString().trim();\n\tif (stderr) {\n\t\treturn stderr;\n\t}\n\tconst stdout = result.stdout?.toString().trim();\n\tif (stdout) {\n\t\treturn stdout;\n\t}\n\treturn `exit status ${result.status ?? \"unknown\"}`;\n}\n\nfunction runExtractionCommand(command: string, args: string[]): string | null {\n\tconst result = spawnSync(command, args, { stdio: \"pipe\" });\n\tif (!result.error && result.status === 0) {\n\t\treturn null;\n\t}\n\treturn `${command}: ${formatSpawnFailure(result)}`;\n}\n\nfunction extractTarGzArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failure = runExtractionCommand(\"tar\", [\"xzf\", archivePath, \"-C\", extractDir]);\n\tif (failure) {\n\t\tthrow new Error(`Failed to extract ${assetName}: ${failure}`);\n\t}\n}\n\nfunction getWindowsTarCommand(): string {\n\tconst systemRoot = process.env.SystemRoot ?? process.env.WINDIR;\n\tif (systemRoot) {\n\t\tconst systemTar = join(systemRoot, \"System32\", \"tar.exe\");\n\t\tif (existsSync(systemTar)) {\n\t\t\treturn systemTar;\n\t\t}\n\t}\n\treturn \"tar.exe\";\n}\n\nfunction extractZipArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failures: string[] = [];\n\n\tif (platform() === \"win32\") {\n\t\t// Windows ships bsdtar as tar.exe, which supports zip files. Prefer the\n\t\t// System32 binary over Git Bash's GNU tar, which does not handle zip archives.\n\t\tconst tarFailure = runExtractionCommand(getWindowsTarCommand(), [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\n\t\tconst script =\n\t\t\t\"& { param($archive, $destination) $ErrorActionPreference = 'Stop'; Expand-Archive -LiteralPath $archive -DestinationPath $destination -Force }\";\n\t\tconst powershellFailure = runExtractionCommand(\"powershell.exe\", [\n\t\t\t\"-NoLogo\",\n\t\t\t\"-NoProfile\",\n\t\t\t\"-NonInteractive\",\n\t\t\t\"-ExecutionPolicy\",\n\t\t\t\"Bypass\",\n\t\t\t\"-Command\",\n\t\t\tscript,\n\t\t\tarchivePath,\n\t\t\textractDir,\n\t\t]);\n\t\tif (!powershellFailure) return;\n\t\tfailures.push(powershellFailure);\n\t} else {\n\t\tconst unzipFailure = runExtractionCommand(\"unzip\", [\"-q\", archivePath, \"-d\", extractDir]);\n\t\tif (!unzipFailure) return;\n\t\tfailures.push(unzipFailure);\n\n\t\tconst tarFailure = runExtractionCommand(\"tar\", [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\t}\n\n\tthrow new Error(`Failed to extract ${assetName}: ${failures.join(\"; \")}`);\n}\n\nasync function sha256File(path: string): Promise<string> {\n\tconst hash = createHash(\"sha256\");\n\tfor await (const chunk of createReadStream(path)) {\n\t\thash.update(chunk as Buffer);\n\t}\n\treturn hash.digest(\"hex\");\n}\n\nexport interface InstallManagedToolArchiveOptions {\n\tarchivePath: string;\n\tdestination: string;\n\tbinaryName: string;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n\texpectedSha256: string;\n\tmaxBytes: number;\n}\n\n/**\n * Verify a downloaded tool archive against its pinned digest, extract it into a\n * per-install quarantine directory, confirm the located binary did not escape that\n * quarantine, and only then atomically promote it into the live tools directory\n * (ADR 0017). Rejects a digest mismatch or an out-of-bounds archive before anything\n * is written to `destination`.\n */\nexport async function installManagedToolArchive(options: InstallManagedToolArchiveOptions): Promise<string> {\n\tconst { archivePath, destination, binaryName, archiveKind, expectedSha256, maxBytes } = options;\n\n\tconst { size } = statSync(archivePath);\n\tif (size > maxBytes) {\n\t\tthrow new Error(`Refusing install: archive size ${size} exceeds bounded limit ${maxBytes}`);\n\t}\n\n\tconst actualSha256 = await sha256File(archivePath);\n\tif (actualSha256.toLowerCase() !== expectedSha256.toLowerCase()) {\n\t\tthrow new Error(`Refusing install: digest mismatch for ${basename(archivePath)}`);\n\t}\n\n\tmkdirSync(destination, { recursive: true });\n\tconst quarantineDirectory = join(\n\t\tdestination,\n\t\t`.quarantine_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n\t);\n\tmkdirSync(quarantineDirectory, { recursive: true });\n\n\ttry {\n\t\tconst assetName = basename(archivePath);\n\t\tif (archiveKind === \"tar.gz\") {\n\t\t\textractTarGzArchive(archivePath, quarantineDirectory, assetName);\n\t\t} else {\n\t\t\textractZipArchive(archivePath, quarantineDirectory, assetName);\n\t\t}\n\n\t\tconst binaryFileName = binaryName + (platform() === \"win32\" ? \".exe\" : \"\");\n\t\tconst extractedBinary = findBinaryRecursively(quarantineDirectory, binaryFileName);\n\t\tif (!extractedBinary) {\n\t\t\tthrow new Error(`Binary not found in verified archive: expected ${binaryFileName}`);\n\t\t}\n\n\t\tconst resolvedBinary = resolvePath(extractedBinary);\n\t\tconst containmentPath = relative(resolvePath(quarantineDirectory), resolvedBinary);\n\t\tif (containmentPath.startsWith(\"..\") || resolvePath(containmentPath) === containmentPath) {\n\t\t\tthrow new Error(`Refusing install: archive entry escaped its quarantine directory: ${extractedBinary}`);\n\t\t}\n\n\t\tconst finalPath = join(destination, binaryFileName);\n\t\trenameSync(resolvedBinary, finalPath);\n\t\tif (platform() !== \"win32\") {\n\t\t\tchmodSync(finalPath, 0o755);\n\t\t}\n\t\treturn finalPath;\n\t} finally {\n\t\trmSync(quarantineDirectory, { recursive: true, force: true });\n\t}\n}\n\n// Download and install a tool from reviewed, pinned metadata (ADR 0017): bounded\n// download, digest verification, quarantine extraction, then atomic promotion.\nasync function downloadTool(tool: \"fd\" | \"rg\"): Promise<string> {\n\tconst plat = platform();\n\tconst architecture = arch();\n\tconst artifact = resolveManagedToolArtifact({ tool, platform: plat, architecture });\n\n\tmkdirSync(TOOLS_DIR, { recursive: true });\n\n\t// Unique download filename: fd and rg downloads can run concurrently during\n\t// startup, so sharing a fixed path causes races.\n\tconst archivePath = join(\n\t\tTOOLS_DIR,\n\t\t`download_${artifact.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}${\n\t\t\tartifact.archiveKind === \"zip\" ? \".zip\" : \".tar.gz\"\n\t\t}`,\n\t);\n\n\ttry {\n\t\tawait downloadFile(artifact.url, archivePath, artifact.maxBytes);\n\t\treturn await installManagedToolArchive({\n\t\t\tarchivePath,\n\t\t\tdestination: TOOLS_DIR,\n\t\t\tbinaryName: artifact.binaryName,\n\t\t\tarchiveKind: artifact.archiveKind,\n\t\t\texpectedSha256: artifact.sha256,\n\t\t\tmaxBytes: artifact.maxBytes,\n\t\t});\n\t} finally {\n\t\trmSync(archivePath, { force: true });\n\t}\n}\n\nexport interface HostToolBinary {\n\t/** File name the child looks for in its own tools directory (\"fd\", \"rg\", \"fd.exe\"). */\n\tname: string;\n\t/** Absolute host path to the executable backing that name. */\n\tpath: string;\n}\n\nfunction isExecutableFile(candidate: string, targetPlatform: NodeJS.Platform): boolean {\n\ttry {\n\t\tconst stats = statSync(candidate);\n\t\tif (!stats.isFile()) return false;\n\t\t// Windows has no POSIX execute bit; presence of the file is the only signal.\n\t\treturn targetPlatform === \"win32\" || (stats.mode & 0o111) !== 0;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Absolute host path for a managed tool, or undefined when it is installed nowhere.\n *\n * `getToolPath` may return a bare command name because its caller spawns through PATH.\n * A sandboxed child has no such luxury: its PATH still lists host directories that the\n * OS boundary has already replaced, so the supervisor must resolve a real path here and\n * project it in (ADR 0017, and the sandbox spec's tool-projection amendment).\n */\nexport function resolveHostToolBinary(\n\ttool: \"fd\" | \"rg\",\n\toptions: { toolsDirectory?: string; pathValue?: string; platform?: NodeJS.Platform } = {},\n): string | undefined {\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tconst targetPlatform = options.platform ?? platform();\n\tconst executableSuffix = targetPlatform === \"win32\" ? \".exe\" : \"\";\n\tconst managedPath = join(options.toolsDirectory ?? TOOLS_DIR, config.binaryName + executableSuffix);\n\tif (isExecutableFile(managedPath, targetPlatform)) {\n\t\treturn managedPath;\n\t}\n\n\tconst pathEntries = (options.pathValue ?? process.env.PATH ?? \"\")\n\t\t.split(targetPlatform === \"win32\" ? \";\" : \":\")\n\t\t.filter((entry) => entry.length > 0);\n\t// Preference runs by name first, matching getToolPath: a real `fd` anywhere on PATH\n\t// beats a `fdfind` alias, rather than whichever directory happens to come first.\n\tfor (const systemBinaryName of config.systemBinaryNames ?? [config.binaryName]) {\n\t\tfor (const entry of pathEntries) {\n\t\t\tconst candidate = join(entry, systemBinaryName + executableSuffix);\n\t\t\tif (isExecutableFile(candidate, targetPlatform)) {\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Install any missing managed tool on the host, then report absolute paths for the ones\n * that exist. Downloading here rather than inside the child means one install serves every\n * workspace, and it happens on the side of the boundary that still has network access.\n */\nexport async function prepareHostToolBinaries(silent: boolean = false): Promise<HostToolBinary[]> {\n\tconst binaries: HostToolBinary[] = [];\n\tfor (const tool of [\"fd\", \"rg\"] as const) {\n\t\tawait ensureTool(tool, silent ? undefined : ({ message }) => console.error(message));\n\t\tconst hostPath = resolveHostToolBinary(tool);\n\t\tif (hostPath) {\n\t\t\tbinaries.push({ name: TOOLS[tool].binaryName + (platform() === \"win32\" ? \".exe\" : \"\"), path: hostPath });\n\t\t}\n\t}\n\treturn binaries;\n}\n\n// Termux package names for tools\nconst TERMUX_PACKAGES: Record<string, string> = {\n\tfd: \"fd\",\n\trg: \"ripgrep\",\n};\n\nexport interface ToolStatus {\n\ttype: \"info\" | \"warning\";\n\tmessage: string;\n}\n\n/**\n * Ensure a tool is available, downloading if necessary.\n * Reports progress through `onStatus`; status messages are otherwise silent.\n * Returns the tool path, or undefined if unavailable.\n */\nexport async function ensureTool(\n\ttool: \"fd\" | \"rg\",\n\tonStatus?: (status: ToolStatus) => void,\n): Promise<string | undefined> {\n\tconst existingPath = getToolPath(tool);\n\tif (existingPath) {\n\t\treturn existingPath;\n\t}\n\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tif (isOfflineModeEnabled()) {\n\t\tonStatus?.({ type: \"warning\", message: `${config.name} not found. Offline mode enabled, skipping download.` });\n\t\treturn undefined;\n\t}\n\n\t// On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.\n\t// Users must install via pkg.\n\tif (platform() === \"android\") {\n\t\tconst pkgName = TERMUX_PACKAGES[tool] ?? tool;\n\t\tonStatus?.({ type: \"warning\", message: `${config.name} not found. Install with: pkg install ${pkgName}` });\n\t\treturn undefined;\n\t}\n\n\t// Tool not found - download it\n\tonStatus?.({ type: \"info\", message: `${config.name} not found. Downloading...` });\n\n\ttry {\n\t\tconst path = await downloadTool(tool);\n\t\tonStatus?.({ type: \"info\", message: `${config.name} installed to ${path}` });\n\t\treturn path;\n\t} catch (e) {\n\t\tonStatus?.({\n\t\t\ttype: \"warning\",\n\t\t\tmessage: `Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`,\n\t\t});\n\t\treturn undefined;\n\t}\n}\n"]}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
1
|
import { spawnSync } from "child_process";
|
|
3
2
|
import { createHash } from "crypto";
|
|
4
3
|
import { chmodSync, createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync, } from "fs";
|
|
@@ -428,7 +427,7 @@ export function resolveHostToolBinary(tool, options = {}) {
|
|
|
428
427
|
export async function prepareHostToolBinaries(silent = false) {
|
|
429
428
|
const binaries = [];
|
|
430
429
|
for (const tool of ["fd", "rg"]) {
|
|
431
|
-
await ensureTool(tool, silent);
|
|
430
|
+
await ensureTool(tool, silent ? undefined : ({ message }) => console.error(message));
|
|
432
431
|
const hostPath = resolveHostToolBinary(tool);
|
|
433
432
|
if (hostPath) {
|
|
434
433
|
binaries.push({ name: TOOLS[tool].binaryName + (platform() === "win32" ? ".exe" : ""), path: hostPath });
|
|
@@ -441,9 +440,12 @@ const TERMUX_PACKAGES = {
|
|
|
441
440
|
fd: "fd",
|
|
442
441
|
rg: "ripgrep",
|
|
443
442
|
};
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
443
|
+
/**
|
|
444
|
+
* Ensure a tool is available, downloading if necessary.
|
|
445
|
+
* Reports progress through `onStatus`; status messages are otherwise silent.
|
|
446
|
+
* Returns the tool path, or undefined if unavailable.
|
|
447
|
+
*/
|
|
448
|
+
export async function ensureTool(tool, onStatus) {
|
|
447
449
|
const existingPath = getToolPath(tool);
|
|
448
450
|
if (existingPath) {
|
|
449
451
|
return existingPath;
|
|
@@ -452,35 +454,28 @@ export async function ensureTool(tool, silent = false) {
|
|
|
452
454
|
if (!config)
|
|
453
455
|
return undefined;
|
|
454
456
|
if (isOfflineModeEnabled()) {
|
|
455
|
-
|
|
456
|
-
console.log(chalk.yellow(`${config.name} not found. Offline mode enabled, skipping download.`));
|
|
457
|
-
}
|
|
457
|
+
onStatus?.({ type: "warning", message: `${config.name} not found. Offline mode enabled, skipping download.` });
|
|
458
458
|
return undefined;
|
|
459
459
|
}
|
|
460
460
|
// On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.
|
|
461
461
|
// Users must install via pkg.
|
|
462
462
|
if (platform() === "android") {
|
|
463
463
|
const pkgName = TERMUX_PACKAGES[tool] ?? tool;
|
|
464
|
-
|
|
465
|
-
console.log(chalk.yellow(`${config.name} not found. Install with: pkg install ${pkgName}`));
|
|
466
|
-
}
|
|
464
|
+
onStatus?.({ type: "warning", message: `${config.name} not found. Install with: pkg install ${pkgName}` });
|
|
467
465
|
return undefined;
|
|
468
466
|
}
|
|
469
467
|
// Tool not found - download it
|
|
470
|
-
|
|
471
|
-
console.log(chalk.dim(`${config.name} not found. Downloading...`));
|
|
472
|
-
}
|
|
468
|
+
onStatus?.({ type: "info", message: `${config.name} not found. Downloading...` });
|
|
473
469
|
try {
|
|
474
470
|
const path = await downloadTool(tool);
|
|
475
|
-
|
|
476
|
-
console.log(chalk.dim(`${config.name} installed to ${path}`));
|
|
477
|
-
}
|
|
471
|
+
onStatus?.({ type: "info", message: `${config.name} installed to ${path}` });
|
|
478
472
|
return path;
|
|
479
473
|
}
|
|
480
474
|
catch (e) {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
475
|
+
onStatus?.({
|
|
476
|
+
type: "warning",
|
|
477
|
+
message: `Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`,
|
|
478
|
+
});
|
|
484
479
|
return undefined;
|
|
485
480
|
}
|
|
486
481
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools-manager.js","sourceRoot":"","sources":["../../src/utils/tools-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAyB,SAAS,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EACN,SAAS,EACT,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,MAAM,EACN,QAAQ,GACR,MAAM,IAAI,CAAC;AACZ,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,MAAM,CAAC;AACxE,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,MAAM,SAAS,GAAG,SAAS,EAAE,CAAC;AAC9B,MAAM,mBAAmB,GAAG,OAAO,CAAC;AAEpC,SAAS,oBAAoB,GAAY;IACxC,MAAM,KAAK,GAAG,kBAAkB,CAAC,mBAAmB,CAAC,CAAC;IACtD,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,OAAO,KAAK,KAAK,GAAG,IAAI,KAAK,CAAC,WAAW,EAAE,KAAK,MAAM,IAAI,KAAK,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC;AAAA,CACxF;AAUD,oFAAoF;AACpF,qDAAqD;AACrD,MAAM,KAAK,GAA+B;IACzC,EAAE,EAAE;QACH,IAAI,EAAE,IAAI;QACV,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE,IAAI;QAChB,iBAAiB,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC;QACnC,SAAS,EAAE,GAAG;KACd;IACD,EAAE,EAAE;QACH,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,oBAAoB;QAC1B,UAAU,EAAE,IAAI;QAChB,SAAS,EAAE,EAAE;KACb;CACD,CAAC;AAEF,wDAAwD;AACxD,SAAS,aAAa,CAAC,GAAW,EAAW;IAC5C,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QAChE,6CAA6C;QAC7C,OAAO,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AAAA,CACD;AAED,2DAA2D;AAC3D,MAAM,UAAU,WAAW,CAAC,IAAiB,EAAE,OAAO,GAAgC,EAAE,EAAiB;IACxG,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,8EAA8E;IAC9E,+EAA+E;IAC/E,iFAAiF;IACjF,MAAM,WAAW,GAAG,IAAI,CACvB,OAAO,CAAC,cAAc,IAAI,SAAS,EACnC,MAAM,CAAC,UAAU,GAAG,CAAC,QAAQ,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAC1D,CAAC;IACF,IAAI,gBAAgB,CAAC,WAAW,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;QAC/C,OAAO,WAAW,CAAC;IACpB,CAAC;IAED,4EAA4E;IAC5E,MAAM,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC1E,KAAK,MAAM,gBAAgB,IAAI,iBAAiB,EAAE,CAAC;QAClD,IAAI,aAAa,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACrC,OAAO,gBAAgB,CAAC;QACzB,CAAC;IACF,CAAC;IAED,OAAO,IAAI,CAAC;AAAA,CACZ;AAqBD,MAAM,iBAAiB,GAAiE;IACvF,EAAE,EAAE;QACH,WAAW,EAAE;YACZ,OAAO,EAAE,QAAQ;YACjB,SAAS,EAAE,iDAAiD;YAC5D,MAAM,EAAE,kEAAkE;YAC1E,aAAa,EAAE,OAAO;YACtB,WAAW,EAAE,QAAQ;SACrB;QACD,aAAa,EAAE;YACd,OAAO,EAAE,QAAQ;YACjB,SAAS,EAAE,iDAAiD;YAC5D,MAAM,EAAE,kEAAkE;YAC1E,aAAa,EAAE,OAAO;YACtB,WAAW,EAAE,QAAQ;SACrB;QACD,YAAY,EAAE;YACb,OAAO,EAAE,QAAQ;YACjB,SAAS,EAAE,2CAA2C;YACtD,MAAM,EAAE,kEAAkE;YAC1E,aAAa,EAAE,OAAO;YACtB,WAAW,EAAE,QAAQ;SACrB;QACD,cAAc,EAAE;YACf,OAAO,EAAE,QAAQ;YACjB,SAAS,EAAE,4CAA4C;YACvD,MAAM,EAAE,kEAAkE;YAC1E,aAAa,EAAE,OAAO;YACtB,WAAW,EAAE,QAAQ;SACrB;QACD,WAAW,EAAE;YACZ,OAAO,EAAE,QAAQ;YACjB,SAAS,EAAE,2CAA2C;YACtD,MAAM,EAAE,kEAAkE;YAC1E,aAAa,EAAE,OAAO;YACtB,WAAW,EAAE,KAAK;SAClB;QACD,6EAA6E;QAC7E,8EAA8E;KAC9E;IACD,EAAE,EAAE;QACH,WAAW,EAAE;YACZ,OAAO,EAAE,QAAQ;YACjB,SAAS,EAAE,4CAA4C;YACvD,MAAM,EAAE,kEAAkE;YAC1E,aAAa,EAAE,OAAO;YACtB,WAAW,EAAE,QAAQ;SACrB;QACD,aAAa,EAAE;YACd,OAAO,EAAE,QAAQ;YACjB,SAAS,EAAE,6CAA6C;YACxD,MAAM,EAAE,kEAAkE;YAC1E,aAAa,EAAE,OAAO;YACtB,WAAW,EAAE,QAAQ;SACrB;QACD,uEAAuE;QACvE,gFAAgF;QAChF,YAAY,EAAE;YACb,OAAO,EAAE,QAAQ;YACjB,SAAS,EAAE,uCAAuC;YAClD,MAAM,EAAE,kEAAkE;YAC1E,aAAa,EAAE,OAAO;YACtB,WAAW,EAAE,QAAQ;SACrB;QACD,cAAc,EAAE;YACf,OAAO,EAAE,QAAQ;YACjB,SAAS,EAAE,wCAAwC;YACnD,MAAM,EAAE,kEAAkE;YAC1E,aAAa,EAAE,OAAO;YACtB,WAAW,EAAE,QAAQ;SACrB;QACD,WAAW,EAAE;YACZ,OAAO,EAAE,QAAQ;YACjB,SAAS,EAAE,uCAAuC;YAClD,MAAM,EAAE,kEAAkE;YAC1E,aAAa,EAAE,OAAO;YACtB,WAAW,EAAE,KAAK;SAClB;QACD,aAAa,EAAE;YACd,OAAO,EAAE,QAAQ;YACjB,SAAS,EAAE,wCAAwC;YACnD,MAAM,EAAE,kEAAkE;YAC1E,aAAa,EAAE,OAAO;YACtB,WAAW,EAAE,KAAK;SAClB;KACD;CACD,CAAC;AAaF,oFAAoF;AACpF,MAAM,UAAU,0BAA0B,CAAC,OAI1C,EAAuB;IACvB,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,GAAG,GAAG,iBAAiB,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAC7F,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,mCAAmC,OAAO,CAAC,IAAI,OAAO,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IACnH,CAAC;IACD,OAAO;QACN,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,GAAG,EAAE,sBAAsB,MAAM,CAAC,IAAI,sBAAsB,MAAM,CAAC,SAAS,GAAG,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,SAAS,EAAE;QAC7G,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,aAAa,EAAE,GAAG,CAAC,aAAa;QAChC,QAAQ,EAAE,GAAG,CAAC,aAAa,GAAG,CAAC;QAC/B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,WAAW,EAAE,GAAG,CAAC,WAAW;KAC5B,CAAC;AAAA,CACF;AAED,kFAAkF;AAClF,8EAA8E;AAC9E,KAAK,UAAU,YAAY,CAAC,GAAW,EAAE,IAAY,EAAE,QAAgB,EAAiB;IACvF,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,mBAAmB,EAAE,CAAC,CAAC;IAE1F,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,uBAAuB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACrC,CAAC;IAED,MAAM,cAAc,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC;IACpF,IAAI,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,cAAc,GAAG,QAAQ,EAAE,CAAC;QAClE,MAAM,IAAI,KAAK,CAAC,oCAAoC,cAAc,0BAA0B,QAAQ,EAAE,CAAC,CAAC;IACzG,CAAC;IAED,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAW,CAAC,CAAC;IACtD,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC;QACpC,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC;QACzB,IAAI,QAAQ,GAAG,QAAQ,EAAE,CAAC;YACzB,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,6CAA6C,QAAQ,QAAQ,CAAC,CAAC,CAAC;QAC1F,CAAC;IAAA,CACD,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC3C,IAAI,CAAC;QACJ,MAAM,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9B,MAAM,KAAK,CAAC;IACb,CAAC;AAAA,CACD;AAED,SAAS,qBAAqB,CAAC,OAAe,EAAE,cAAsB,EAAiB;IACtF,MAAM,KAAK,GAAa,CAAC,OAAO,CAAC,CAAC;IAElC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QAC/B,IAAI,CAAC,UAAU;YAAE,SAAS;QAE1B,MAAM,OAAO,GAAG,WAAW,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACjE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAC9C,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;gBACrD,OAAO,QAAQ,CAAC;YACjB,CAAC;YACD,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACzB,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACtB,CAAC;QACF,CAAC;IACF,CAAC;IAED,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,SAAS,kBAAkB,CAAC,MAAgC,EAAU;IACrE,IAAI,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC;QAC3B,OAAO,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC;IAC7B,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;IAChD,IAAI,MAAM,EAAE,CAAC;QACZ,OAAO,MAAM,CAAC;IACf,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;IAChD,IAAI,MAAM,EAAE,CAAC;QACZ,OAAO,MAAM,CAAC;IACf,CAAC;IACD,OAAO,eAAe,MAAM,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;AAAA,CACnD;AAED,SAAS,oBAAoB,CAAC,OAAe,EAAE,IAAc,EAAiB;IAC7E,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3D,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC;IACb,CAAC;IACD,OAAO,GAAG,OAAO,KAAK,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;AAAA,CACnD;AAED,SAAS,mBAAmB,CAAC,WAAmB,EAAE,UAAkB,EAAE,SAAiB,EAAQ;IAC9F,MAAM,OAAO,GAAG,oBAAoB,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;IACpF,IAAI,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,qBAAqB,SAAS,KAAK,OAAO,EAAE,CAAC,CAAC;IAC/D,CAAC;AAAA,CACD;AAED,SAAS,oBAAoB,GAAW;IACvC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;IAChE,IAAI,UAAU,EAAE,CAAC;QAChB,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;QAC1D,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3B,OAAO,SAAS,CAAC;QAClB,CAAC;IACF,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB;AAED,SAAS,iBAAiB,CAAC,WAAmB,EAAE,UAAkB,EAAE,SAAiB,EAAQ;IAC5F,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,IAAI,QAAQ,EAAE,KAAK,OAAO,EAAE,CAAC;QAC5B,wEAAwE;QACxE,+EAA+E;QAC/E,MAAM,UAAU,GAAG,oBAAoB,CAAC,oBAAoB,EAAE,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;QACvG,IAAI,CAAC,UAAU;YAAE,OAAO;QACxB,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAE1B,MAAM,MAAM,GACX,gJAAgJ,CAAC;QAClJ,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,gBAAgB,EAAE;YAChE,SAAS;YACT,YAAY;YACZ,iBAAiB;YACjB,kBAAkB;YAClB,QAAQ;YACR,UAAU;YACV,MAAM;YACN,WAAW;YACX,UAAU;SACV,CAAC,CAAC;QACH,IAAI,CAAC,iBAAiB;YAAE,OAAO;QAC/B,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAClC,CAAC;SAAM,CAAC;QACP,MAAM,YAAY,GAAG,oBAAoB,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;QAC1F,IAAI,CAAC,YAAY;YAAE,OAAO;QAC1B,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAE5B,MAAM,UAAU,GAAG,oBAAoB,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;QACtF,IAAI,CAAC,UAAU;YAAE,OAAO;QACxB,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC3B,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,qBAAqB,SAAS,KAAK,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAAA,CAC1E;AAED,KAAK,UAAU,UAAU,CAAC,IAAY,EAAmB;IACxD,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAClC,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;QAClD,IAAI,CAAC,MAAM,CAAC,KAAe,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAAA,CAC1B;AAWD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,OAAyC,EAAmB;IAC3G,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC;IAEhG,MAAM,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;IACvC,IAAI,IAAI,GAAG,QAAQ,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,kCAAkC,IAAI,0BAA0B,QAAQ,EAAE,CAAC,CAAC;IAC7F,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,UAAU,CAAC,WAAW,CAAC,CAAC;IACnD,IAAI,YAAY,CAAC,WAAW,EAAE,KAAK,cAAc,CAAC,WAAW,EAAE,EAAE,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,yCAAyC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IACnF,CAAC;IAED,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,MAAM,mBAAmB,GAAG,IAAI,CAC/B,WAAW,EACX,eAAe,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CACrF,CAAC;IACF,SAAS,CAAC,mBAAmB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEpD,IAAI,CAAC;QACJ,MAAM,SAAS,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;QACxC,IAAI,WAAW,KAAK,QAAQ,EAAE,CAAC;YAC9B,mBAAmB,CAAC,WAAW,EAAE,mBAAmB,EAAE,SAAS,CAAC,CAAC;QAClE,CAAC;aAAM,CAAC;YACP,iBAAiB,CAAC,WAAW,EAAE,mBAAmB,EAAE,SAAS,CAAC,CAAC;QAChE,CAAC;QAED,MAAM,cAAc,GAAG,UAAU,GAAG,CAAC,QAAQ,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC3E,MAAM,eAAe,GAAG,qBAAqB,CAAC,mBAAmB,EAAE,cAAc,CAAC,CAAC;QACnF,IAAI,CAAC,eAAe,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,kDAAkD,cAAc,EAAE,CAAC,CAAC;QACrF,CAAC;QAED,MAAM,cAAc,GAAG,WAAW,CAAC,eAAe,CAAC,CAAC;QACpD,MAAM,eAAe,GAAG,QAAQ,CAAC,WAAW,CAAC,mBAAmB,CAAC,EAAE,cAAc,CAAC,CAAC;QACnF,IAAI,eAAe,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC,eAAe,CAAC,KAAK,eAAe,EAAE,CAAC;YAC1F,MAAM,IAAI,KAAK,CAAC,qEAAqE,eAAe,EAAE,CAAC,CAAC;QACzG,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;QACpD,UAAU,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;QACtC,IAAI,QAAQ,EAAE,KAAK,OAAO,EAAE,CAAC;YAC5B,SAAS,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAC7B,CAAC;QACD,OAAO,SAAS,CAAC;IAClB,CAAC;YAAS,CAAC;QACV,MAAM,CAAC,mBAAmB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,CAAC;AAAA,CACD;AAED,iFAAiF;AACjF,+EAA+E;AAC/E,KAAK,UAAU,YAAY,CAAC,IAAiB,EAAmB;IAC/D,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC;IACxB,MAAM,YAAY,GAAG,IAAI,EAAE,CAAC;IAC5B,MAAM,QAAQ,GAAG,0BAA0B,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;IAEpF,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE1C,4EAA4E;IAC5E,iDAAiD;IACjD,MAAM,WAAW,GAAG,IAAI,CACvB,SAAS,EACT,YAAY,QAAQ,CAAC,UAAU,IAAI,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GACtG,QAAQ,CAAC,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAC3C,EAAE,CACF,CAAC;IAEF,IAAI,CAAC;QACJ,MAAM,YAAY,CAAC,QAAQ,CAAC,GAAG,EAAE,WAAW,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACjE,OAAO,MAAM,yBAAyB,CAAC;YACtC,WAAW;YACX,WAAW,EAAE,SAAS;YACtB,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,WAAW,EAAE,QAAQ,CAAC,WAAW;YACjC,cAAc,EAAE,QAAQ,CAAC,MAAM;YAC/B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;SAC3B,CAAC,CAAC;IACJ,CAAC;YAAS,CAAC;QACV,MAAM,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC;AAAA,CACD;AASD,SAAS,gBAAgB,CAAC,SAAiB,EAAE,cAA+B,EAAW;IACtF,IAAI,CAAC;QACJ,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;YAAE,OAAO,KAAK,CAAC;QAClC,6EAA6E;QAC7E,OAAO,cAAc,KAAK,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AAAA,CACD;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CACpC,IAAiB,EACjB,OAAO,GAAgF,EAAE,EACpE;IACrB,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAE9B,MAAM,cAAc,GAAG,OAAO,CAAC,QAAQ,IAAI,QAAQ,EAAE,CAAC;IACtD,MAAM,gBAAgB,GAAG,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAClE,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,IAAI,SAAS,EAAE,MAAM,CAAC,UAAU,GAAG,gBAAgB,CAAC,CAAC;IACpG,IAAI,gBAAgB,CAAC,WAAW,EAAE,cAAc,CAAC,EAAE,CAAC;QACnD,OAAO,WAAW,CAAC;IACpB,CAAC;IAED,MAAM,WAAW,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;SAC/D,KAAK,CAAC,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;SAC7C,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACtC,oFAAoF;IACpF,iFAAiF;IACjF,KAAK,MAAM,gBAAgB,IAAI,MAAM,CAAC,iBAAiB,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;QAChF,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;YACjC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,CAAC,CAAC;YACnE,IAAI,gBAAgB,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE,CAAC;gBACjD,OAAO,SAAS,CAAC;YAClB,CAAC;QACF,CAAC;IACF,CAAC;IAED,OAAO,SAAS,CAAC;AAAA,CACjB;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,MAAM,GAAY,KAAK,EAA6B;IACjG,MAAM,QAAQ,GAAqB,EAAE,CAAC;IACtC,KAAK,MAAM,IAAI,IAAI,CAAC,IAAI,EAAE,IAAI,CAAU,EAAE,CAAC;QAC1C,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC/B,MAAM,QAAQ,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,QAAQ,EAAE,CAAC;YACd,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,GAAG,CAAC,QAAQ,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC1G,CAAC;IACF,CAAC;IACD,OAAO,QAAQ,CAAC;AAAA,CAChB;AAED,iCAAiC;AACjC,MAAM,eAAe,GAA2B;IAC/C,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,SAAS;CACb,CAAC;AAEF,uDAAuD;AACvD,uDAAuD;AACvD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAiB,EAAE,MAAM,GAAY,KAAK,EAA+B;IACzG,MAAM,YAAY,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,YAAY,EAAE,CAAC;QAClB,OAAO,YAAY,CAAC;IACrB,CAAC;IAED,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAE9B,IAAI,oBAAoB,EAAE,EAAE,CAAC;QAC5B,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,sDAAsD,CAAC,CAAC,CAAC;QACjG,CAAC;QACD,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,mFAAmF;IACnF,8BAA8B;IAC9B,IAAI,QAAQ,EAAE,KAAK,SAAS,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;QAC9C,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,yCAAyC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC7F,CAAC;QACD,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,+BAA+B;IAC/B,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,4BAA4B,CAAC,CAAC,CAAC;IACpE,CAAC;IAED,IAAI,CAAC;QACJ,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,iBAAiB,IAAI,EAAE,CAAC,CAAC,CAAC;QAC/D,CAAC;QACD,OAAO,IAAI,CAAC;IACb,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACZ,IAAI,CAAC,MAAM,EAAE,CAAC;YACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,sBAAsB,MAAM,CAAC,IAAI,KAAK,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACvG,CAAC;QACD,OAAO,SAAS,CAAC;IAClB,CAAC;AAAA,CACD","sourcesContent":["import chalk from \"chalk\";\nimport { type SpawnSyncReturns, spawnSync } from \"child_process\";\nimport { createHash } from \"crypto\";\nimport {\n\tchmodSync,\n\tcreateReadStream,\n\tcreateWriteStream,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\trenameSync,\n\trmSync,\n\tstatSync,\n} from \"fs\";\nimport { arch, platform } from \"os\";\nimport { basename, join, relative, resolve as resolvePath } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { getBinDir } from \"../config.ts\";\nimport { getApexEnvironment } from \"../core/environment.ts\";\nimport { fetchWithRetry } from \"./management-http.ts\";\n\nconst TOOLS_DIR = getBinDir();\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\nfunction isOfflineModeEnabled(): boolean {\n\tconst value = getApexEnvironment(\"APEX_CODE_OFFLINE\");\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\ninterface ToolConfig {\n\tname: string;\n\trepo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n\tbinaryName: string; // Name of the binary inside the archive\n\tsystemBinaryNames?: string[]; // Alternative system command names to try before downloading\n\ttagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n}\n\n// Per-platform/architecture asset names live in MANAGED_TOOL_PINS (ADR 0017) as the\n// single reviewed source of truth, not derived here.\nconst TOOLS: Record<string, ToolConfig> = {\n\tfd: {\n\t\tname: \"fd\",\n\t\trepo: \"sharkdp/fd\",\n\t\tbinaryName: \"fd\",\n\t\tsystemBinaryNames: [\"fd\", \"fdfind\"],\n\t\ttagPrefix: \"v\",\n\t},\n\trg: {\n\t\tname: \"ripgrep\",\n\t\trepo: \"BurntSushi/ripgrep\",\n\t\tbinaryName: \"rg\",\n\t\ttagPrefix: \"\",\n\t},\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n\ttry {\n\t\tconst result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n\t\t// Check for ENOENT error (command not found)\n\t\treturn result.error === undefined || result.error === null;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// Get the path to a tool (system-wide or in our tools dir)\nexport function getToolPath(tool: \"fd\" | \"rg\", options: { toolsDirectory?: string } = {}): string | null {\n\tconst config = TOOLS[tool];\n\tif (!config) return null;\n\n\t// Check our tools directory first. Existence alone is not enough: the sandbox\n\t// projects host tools by bind-mounting over a file here, and bwrap leaves that\n\t// mountpoint behind as an empty, non-executable stub once the namespace is gone.\n\tconst managedPath = join(\n\t\toptions.toolsDirectory ?? TOOLS_DIR,\n\t\tconfig.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"),\n\t);\n\tif (isExecutableFile(managedPath, platform())) {\n\t\treturn managedPath;\n\t}\n\n\t// Check system PATH - if found, just return the command name (it's in PATH)\n\tconst systemBinaryNames = config.systemBinaryNames ?? [config.binaryName];\n\tfor (const systemBinaryName of systemBinaryNames) {\n\t\tif (commandExists(systemBinaryName)) {\n\t\t\treturn systemBinaryName;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Reviewed, pinned release metadata for auto-downloaded executable tools (ADR 0017).\n * Never resolved from a mutable \"latest\" API call. Updating a version, asset name, or\n * digest here is itself the reviewed change that authorizes installing a new release.\n *\n * Every digest and byte size below was established by downloading the artifact,\n * computing its SHA-256 locally, and cross-checking that value against the\n * upstream-published digest (ripgrep's own `.sha256` release sidecars; GitHub's own\n * per-asset `digest` field for both projects) — not invented, and not taken from a\n * single unverified source.\n */\ninterface ManagedToolPin {\n\tversion: string;\n\tassetName: string;\n\tsha256: string;\n\texpectedBytes: number;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n}\n\nconst MANAGED_TOOL_PINS: Record<\"fd\" | \"rg\", Partial<Record<string, ManagedToolPin>>> = {\n\trg: {\n\t\t\"linux:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz\",\n\t\t\tsha256: \"4cf9f2741e6c465ffdb7c26f38056a59e2a2544b51f7cc128ef28337eeae4d8e\",\n\t\t\texpectedBytes: 2566310,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"linux:arm64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-aarch64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"c827481c4ff4ea10c9dc7a4022c8de5db34a5737cb74484d62eb94a95841ab2f\",\n\t\t\texpectedBytes: 2047405,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"fc87e78f7cb3fea12d69072e7ef3b21509754717b746368fd40d88963630e2b3\",\n\t\t\texpectedBytes: 2082672,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:arm64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-aarch64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"24ad76777745fbff131c8fbc466742b011f925bfa4fffa2ded6def23b5b937be\",\n\t\t\texpectedBytes: 1787248,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"win32:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"d0f534024c42afd6cb4d38907c25cd2b249b79bbe6cc1dbee8e3e37c2b6e25a1\",\n\t\t\texpectedBytes: 2058893,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t\t// No upstream win32/arm64 ripgrep release asset exists at 14.1.1; resolution\n\t\t// fails closed for that combination rather than constructing a URL that 404s.\n\t},\n\tfd: {\n\t\t\"linux:x64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-x86_64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"def59805cd14b5651b68990855f426ad087f3b96881296d963910431ba3143c8\",\n\t\t\texpectedBytes: 1700779,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"linux:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"6c51f7c5446b3338b1e401ff15dc194c590bb2fa64fd43ff3278300f073adec5\",\n\t\t\texpectedBytes: 1559490,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t// Upstream stopped shipping an Intel macOS fd binary after 10.3.0 (see\n\t\t// 7afd80d78, \"pin fd for macos x64\"); preserved here rather than special-cased.\n\t\t\"darwin:x64\": {\n\t\t\tversion: \"10.3.0\",\n\t\t\tassetName: \"fd-v10.3.0-x86_64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"50d30f13fe3d5914b14c4fff5abcbd4d0cdab4b855970a6956f4f006c17117a3\",\n\t\t\texpectedBytes: 1430203,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"623dc0afc81b92e4d4606b380d7bc91916ba7b97814263e554d50923a39e480a\",\n\t\t\texpectedBytes: 1328933,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"win32:x64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-x86_64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"b2816e506390a89941c63c9187d58a3cc10e9a55f2ef0685f9ea0eccaf7c98c8\",\n\t\t\texpectedBytes: 1525898,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t\t\"win32:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"4f9110c2d5b33a7f760bfa5510f4c113d828109f7277d421b1053a9943c0fc92\",\n\t\t\texpectedBytes: 1370473,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t},\n};\n\nexport interface ManagedToolArtifact {\n\tversion: string;\n\turl: string;\n\tsha256: string;\n\texpectedBytes: number;\n\t/** Bounded download ceiling (ADR 0017): four times the reviewed expected size. */\n\tmaxBytes: number;\n\tbinaryName: string;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n}\n\n/** Resolve reviewed, pinned metadata for a managed tool. Never queries \"latest\". */\nexport function resolveManagedToolArtifact(options: {\n\ttool: \"fd\" | \"rg\";\n\tplatform: NodeJS.Platform | string;\n\tarchitecture: string;\n}): ManagedToolArtifact {\n\tconst config = TOOLS[options.tool];\n\tconst pin = MANAGED_TOOL_PINS[options.tool]?.[`${options.platform}:${options.architecture}`];\n\tif (!config || !pin) {\n\t\tthrow new Error(`No reviewed pinned artifact for ${options.tool} on ${options.platform}/${options.architecture}`);\n\t}\n\treturn {\n\t\tversion: pin.version,\n\t\turl: `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${pin.version}/${pin.assetName}`,\n\t\tsha256: pin.sha256,\n\t\texpectedBytes: pin.expectedBytes,\n\t\tmaxBytes: pin.expectedBytes * 4,\n\t\tbinaryName: config.binaryName,\n\t\tarchiveKind: pin.archiveKind,\n\t};\n}\n\n// Download a file from URL, aborting if the declared or received size exceeds the\n// bounded ceiling (ADR 0017) rather than writing an unbounded stream to disk.\nasync function downloadFile(url: string, dest: string, maxBytes: number): Promise<void> {\n\tconst response = await fetchWithRetry(url, undefined, { timeoutMs: DOWNLOAD_TIMEOUT_MS });\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to download: ${response.status}`);\n\t}\n\n\tif (!response.body) {\n\t\tthrow new Error(\"No response body\");\n\t}\n\n\tconst declaredLength = Number(response.headers.get(\"content-length\") ?? Number.NaN);\n\tif (Number.isFinite(declaredLength) && declaredLength > maxBytes) {\n\t\tthrow new Error(`Refusing download: declared size ${declaredLength} exceeds bounded limit ${maxBytes}`);\n\t}\n\n\tlet received = 0;\n\tconst source = Readable.fromWeb(response.body as any);\n\tsource.on(\"data\", (chunk: Buffer) => {\n\t\treceived += chunk.length;\n\t\tif (received > maxBytes) {\n\t\t\tsource.destroy(new Error(`Refusing download: exceeded bounded limit ${maxBytes} bytes`));\n\t\t}\n\t});\n\n\tconst fileStream = createWriteStream(dest);\n\ttry {\n\t\tawait pipeline(source, fileStream);\n\t} catch (error) {\n\t\trmSync(dest, { force: true });\n\t\tthrow error;\n\t}\n}\n\nfunction findBinaryRecursively(rootDir: string, binaryFileName: string): string | null {\n\tconst stack: string[] = [rootDir];\n\n\twhile (stack.length > 0) {\n\t\tconst currentDir = stack.pop();\n\t\tif (!currentDir) continue;\n\n\t\tconst entries = readdirSync(currentDir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(currentDir, entry.name);\n\t\t\tif (entry.isFile() && entry.name === binaryFileName) {\n\t\t\t\treturn fullPath;\n\t\t\t}\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tstack.push(fullPath);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction formatSpawnFailure(result: SpawnSyncReturns<Buffer>): string {\n\tif (result.error?.message) {\n\t\treturn result.error.message;\n\t}\n\tconst stderr = result.stderr?.toString().trim();\n\tif (stderr) {\n\t\treturn stderr;\n\t}\n\tconst stdout = result.stdout?.toString().trim();\n\tif (stdout) {\n\t\treturn stdout;\n\t}\n\treturn `exit status ${result.status ?? \"unknown\"}`;\n}\n\nfunction runExtractionCommand(command: string, args: string[]): string | null {\n\tconst result = spawnSync(command, args, { stdio: \"pipe\" });\n\tif (!result.error && result.status === 0) {\n\t\treturn null;\n\t}\n\treturn `${command}: ${formatSpawnFailure(result)}`;\n}\n\nfunction extractTarGzArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failure = runExtractionCommand(\"tar\", [\"xzf\", archivePath, \"-C\", extractDir]);\n\tif (failure) {\n\t\tthrow new Error(`Failed to extract ${assetName}: ${failure}`);\n\t}\n}\n\nfunction getWindowsTarCommand(): string {\n\tconst systemRoot = process.env.SystemRoot ?? process.env.WINDIR;\n\tif (systemRoot) {\n\t\tconst systemTar = join(systemRoot, \"System32\", \"tar.exe\");\n\t\tif (existsSync(systemTar)) {\n\t\t\treturn systemTar;\n\t\t}\n\t}\n\treturn \"tar.exe\";\n}\n\nfunction extractZipArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failures: string[] = [];\n\n\tif (platform() === \"win32\") {\n\t\t// Windows ships bsdtar as tar.exe, which supports zip files. Prefer the\n\t\t// System32 binary over Git Bash's GNU tar, which does not handle zip archives.\n\t\tconst tarFailure = runExtractionCommand(getWindowsTarCommand(), [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\n\t\tconst script =\n\t\t\t\"& { param($archive, $destination) $ErrorActionPreference = 'Stop'; Expand-Archive -LiteralPath $archive -DestinationPath $destination -Force }\";\n\t\tconst powershellFailure = runExtractionCommand(\"powershell.exe\", [\n\t\t\t\"-NoLogo\",\n\t\t\t\"-NoProfile\",\n\t\t\t\"-NonInteractive\",\n\t\t\t\"-ExecutionPolicy\",\n\t\t\t\"Bypass\",\n\t\t\t\"-Command\",\n\t\t\tscript,\n\t\t\tarchivePath,\n\t\t\textractDir,\n\t\t]);\n\t\tif (!powershellFailure) return;\n\t\tfailures.push(powershellFailure);\n\t} else {\n\t\tconst unzipFailure = runExtractionCommand(\"unzip\", [\"-q\", archivePath, \"-d\", extractDir]);\n\t\tif (!unzipFailure) return;\n\t\tfailures.push(unzipFailure);\n\n\t\tconst tarFailure = runExtractionCommand(\"tar\", [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\t}\n\n\tthrow new Error(`Failed to extract ${assetName}: ${failures.join(\"; \")}`);\n}\n\nasync function sha256File(path: string): Promise<string> {\n\tconst hash = createHash(\"sha256\");\n\tfor await (const chunk of createReadStream(path)) {\n\t\thash.update(chunk as Buffer);\n\t}\n\treturn hash.digest(\"hex\");\n}\n\nexport interface InstallManagedToolArchiveOptions {\n\tarchivePath: string;\n\tdestination: string;\n\tbinaryName: string;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n\texpectedSha256: string;\n\tmaxBytes: number;\n}\n\n/**\n * Verify a downloaded tool archive against its pinned digest, extract it into a\n * per-install quarantine directory, confirm the located binary did not escape that\n * quarantine, and only then atomically promote it into the live tools directory\n * (ADR 0017). Rejects a digest mismatch or an out-of-bounds archive before anything\n * is written to `destination`.\n */\nexport async function installManagedToolArchive(options: InstallManagedToolArchiveOptions): Promise<string> {\n\tconst { archivePath, destination, binaryName, archiveKind, expectedSha256, maxBytes } = options;\n\n\tconst { size } = statSync(archivePath);\n\tif (size > maxBytes) {\n\t\tthrow new Error(`Refusing install: archive size ${size} exceeds bounded limit ${maxBytes}`);\n\t}\n\n\tconst actualSha256 = await sha256File(archivePath);\n\tif (actualSha256.toLowerCase() !== expectedSha256.toLowerCase()) {\n\t\tthrow new Error(`Refusing install: digest mismatch for ${basename(archivePath)}`);\n\t}\n\n\tmkdirSync(destination, { recursive: true });\n\tconst quarantineDirectory = join(\n\t\tdestination,\n\t\t`.quarantine_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n\t);\n\tmkdirSync(quarantineDirectory, { recursive: true });\n\n\ttry {\n\t\tconst assetName = basename(archivePath);\n\t\tif (archiveKind === \"tar.gz\") {\n\t\t\textractTarGzArchive(archivePath, quarantineDirectory, assetName);\n\t\t} else {\n\t\t\textractZipArchive(archivePath, quarantineDirectory, assetName);\n\t\t}\n\n\t\tconst binaryFileName = binaryName + (platform() === \"win32\" ? \".exe\" : \"\");\n\t\tconst extractedBinary = findBinaryRecursively(quarantineDirectory, binaryFileName);\n\t\tif (!extractedBinary) {\n\t\t\tthrow new Error(`Binary not found in verified archive: expected ${binaryFileName}`);\n\t\t}\n\n\t\tconst resolvedBinary = resolvePath(extractedBinary);\n\t\tconst containmentPath = relative(resolvePath(quarantineDirectory), resolvedBinary);\n\t\tif (containmentPath.startsWith(\"..\") || resolvePath(containmentPath) === containmentPath) {\n\t\t\tthrow new Error(`Refusing install: archive entry escaped its quarantine directory: ${extractedBinary}`);\n\t\t}\n\n\t\tconst finalPath = join(destination, binaryFileName);\n\t\trenameSync(resolvedBinary, finalPath);\n\t\tif (platform() !== \"win32\") {\n\t\t\tchmodSync(finalPath, 0o755);\n\t\t}\n\t\treturn finalPath;\n\t} finally {\n\t\trmSync(quarantineDirectory, { recursive: true, force: true });\n\t}\n}\n\n// Download and install a tool from reviewed, pinned metadata (ADR 0017): bounded\n// download, digest verification, quarantine extraction, then atomic promotion.\nasync function downloadTool(tool: \"fd\" | \"rg\"): Promise<string> {\n\tconst plat = platform();\n\tconst architecture = arch();\n\tconst artifact = resolveManagedToolArtifact({ tool, platform: plat, architecture });\n\n\tmkdirSync(TOOLS_DIR, { recursive: true });\n\n\t// Unique download filename: fd and rg downloads can run concurrently during\n\t// startup, so sharing a fixed path causes races.\n\tconst archivePath = join(\n\t\tTOOLS_DIR,\n\t\t`download_${artifact.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}${\n\t\t\tartifact.archiveKind === \"zip\" ? \".zip\" : \".tar.gz\"\n\t\t}`,\n\t);\n\n\ttry {\n\t\tawait downloadFile(artifact.url, archivePath, artifact.maxBytes);\n\t\treturn await installManagedToolArchive({\n\t\t\tarchivePath,\n\t\t\tdestination: TOOLS_DIR,\n\t\t\tbinaryName: artifact.binaryName,\n\t\t\tarchiveKind: artifact.archiveKind,\n\t\t\texpectedSha256: artifact.sha256,\n\t\t\tmaxBytes: artifact.maxBytes,\n\t\t});\n\t} finally {\n\t\trmSync(archivePath, { force: true });\n\t}\n}\n\nexport interface HostToolBinary {\n\t/** File name the child looks for in its own tools directory (\"fd\", \"rg\", \"fd.exe\"). */\n\tname: string;\n\t/** Absolute host path to the executable backing that name. */\n\tpath: string;\n}\n\nfunction isExecutableFile(candidate: string, targetPlatform: NodeJS.Platform): boolean {\n\ttry {\n\t\tconst stats = statSync(candidate);\n\t\tif (!stats.isFile()) return false;\n\t\t// Windows has no POSIX execute bit; presence of the file is the only signal.\n\t\treturn targetPlatform === \"win32\" || (stats.mode & 0o111) !== 0;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Absolute host path for a managed tool, or undefined when it is installed nowhere.\n *\n * `getToolPath` may return a bare command name because its caller spawns through PATH.\n * A sandboxed child has no such luxury: its PATH still lists host directories that the\n * OS boundary has already replaced, so the supervisor must resolve a real path here and\n * project it in (ADR 0017, and the sandbox spec's tool-projection amendment).\n */\nexport function resolveHostToolBinary(\n\ttool: \"fd\" | \"rg\",\n\toptions: { toolsDirectory?: string; pathValue?: string; platform?: NodeJS.Platform } = {},\n): string | undefined {\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tconst targetPlatform = options.platform ?? platform();\n\tconst executableSuffix = targetPlatform === \"win32\" ? \".exe\" : \"\";\n\tconst managedPath = join(options.toolsDirectory ?? TOOLS_DIR, config.binaryName + executableSuffix);\n\tif (isExecutableFile(managedPath, targetPlatform)) {\n\t\treturn managedPath;\n\t}\n\n\tconst pathEntries = (options.pathValue ?? process.env.PATH ?? \"\")\n\t\t.split(targetPlatform === \"win32\" ? \";\" : \":\")\n\t\t.filter((entry) => entry.length > 0);\n\t// Preference runs by name first, matching getToolPath: a real `fd` anywhere on PATH\n\t// beats a `fdfind` alias, rather than whichever directory happens to come first.\n\tfor (const systemBinaryName of config.systemBinaryNames ?? [config.binaryName]) {\n\t\tfor (const entry of pathEntries) {\n\t\t\tconst candidate = join(entry, systemBinaryName + executableSuffix);\n\t\t\tif (isExecutableFile(candidate, targetPlatform)) {\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Install any missing managed tool on the host, then report absolute paths for the ones\n * that exist. Downloading here rather than inside the child means one install serves every\n * workspace, and it happens on the side of the boundary that still has network access.\n */\nexport async function prepareHostToolBinaries(silent: boolean = false): Promise<HostToolBinary[]> {\n\tconst binaries: HostToolBinary[] = [];\n\tfor (const tool of [\"fd\", \"rg\"] as const) {\n\t\tawait ensureTool(tool, silent);\n\t\tconst hostPath = resolveHostToolBinary(tool);\n\t\tif (hostPath) {\n\t\t\tbinaries.push({ name: TOOLS[tool].binaryName + (platform() === \"win32\" ? \".exe\" : \"\"), path: hostPath });\n\t\t}\n\t}\n\treturn binaries;\n}\n\n// Termux package names for tools\nconst TERMUX_PACKAGES: Record<string, string> = {\n\tfd: \"fd\",\n\trg: \"ripgrep\",\n};\n\n// Ensure a tool is available, downloading if necessary\n// Returns the path to the tool, or null if unavailable\nexport async function ensureTool(tool: \"fd\" | \"rg\", silent: boolean = false): Promise<string | undefined> {\n\tconst existingPath = getToolPath(tool);\n\tif (existingPath) {\n\t\treturn existingPath;\n\t}\n\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tif (isOfflineModeEnabled()) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`${config.name} not found. Offline mode enabled, skipping download.`));\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.\n\t// Users must install via pkg.\n\tif (platform() === \"android\") {\n\t\tconst pkgName = TERMUX_PACKAGES[tool] ?? tool;\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`${config.name} not found. Install with: pkg install ${pkgName}`));\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// Tool not found - download it\n\tif (!silent) {\n\t\tconsole.log(chalk.dim(`${config.name} not found. Downloading...`));\n\t}\n\n\ttry {\n\t\tconst path = await downloadTool(tool);\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.dim(`${config.name} installed to ${path}`));\n\t\t}\n\t\treturn path;\n\t} catch (e) {\n\t\tif (!silent) {\n\t\t\tconsole.log(chalk.yellow(`Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`));\n\t\t}\n\t\treturn undefined;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"tools-manager.js","sourceRoot":"","sources":["../../src/utils/tools-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import { type SpawnSyncReturns, spawnSync } from \"child_process\";\nimport { createHash } from \"crypto\";\nimport {\n\tchmodSync,\n\tcreateReadStream,\n\tcreateWriteStream,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\trenameSync,\n\trmSync,\n\tstatSync,\n} from \"fs\";\nimport { arch, platform } from \"os\";\nimport { basename, join, relative, resolve as resolvePath } from \"path\";\nimport { Readable } from \"stream\";\nimport { pipeline } from \"stream/promises\";\nimport { getBinDir } from \"../config.ts\";\nimport { getApexEnvironment } from \"../core/environment.ts\";\nimport { fetchWithRetry } from \"./management-http.ts\";\n\nconst TOOLS_DIR = getBinDir();\nconst DOWNLOAD_TIMEOUT_MS = 120_000;\n\nfunction isOfflineModeEnabled(): boolean {\n\tconst value = getApexEnvironment(\"APEX_CODE_OFFLINE\");\n\tif (!value) return false;\n\treturn value === \"1\" || value.toLowerCase() === \"true\" || value.toLowerCase() === \"yes\";\n}\n\ninterface ToolConfig {\n\tname: string;\n\trepo: string; // GitHub repo (e.g., \"sharkdp/fd\")\n\tbinaryName: string; // Name of the binary inside the archive\n\tsystemBinaryNames?: string[]; // Alternative system command names to try before downloading\n\ttagPrefix: string; // Prefix for tags (e.g., \"v\" for v1.0.0, \"\" for 1.0.0)\n}\n\n// Per-platform/architecture asset names live in MANAGED_TOOL_PINS (ADR 0017) as the\n// single reviewed source of truth, not derived here.\nconst TOOLS: Record<string, ToolConfig> = {\n\tfd: {\n\t\tname: \"fd\",\n\t\trepo: \"sharkdp/fd\",\n\t\tbinaryName: \"fd\",\n\t\tsystemBinaryNames: [\"fd\", \"fdfind\"],\n\t\ttagPrefix: \"v\",\n\t},\n\trg: {\n\t\tname: \"ripgrep\",\n\t\trepo: \"BurntSushi/ripgrep\",\n\t\tbinaryName: \"rg\",\n\t\ttagPrefix: \"\",\n\t},\n};\n\n// Check if a command exists in PATH by trying to run it\nfunction commandExists(cmd: string): boolean {\n\ttry {\n\t\tconst result = spawnSync(cmd, [\"--version\"], { stdio: \"pipe\" });\n\t\t// Check for ENOENT error (command not found)\n\t\treturn result.error === undefined || result.error === null;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// Get the path to a tool (system-wide or in our tools dir)\nexport function getToolPath(tool: \"fd\" | \"rg\", options: { toolsDirectory?: string } = {}): string | null {\n\tconst config = TOOLS[tool];\n\tif (!config) return null;\n\n\t// Check our tools directory first. Existence alone is not enough: the sandbox\n\t// projects host tools by bind-mounting over a file here, and bwrap leaves that\n\t// mountpoint behind as an empty, non-executable stub once the namespace is gone.\n\tconst managedPath = join(\n\t\toptions.toolsDirectory ?? TOOLS_DIR,\n\t\tconfig.binaryName + (platform() === \"win32\" ? \".exe\" : \"\"),\n\t);\n\tif (isExecutableFile(managedPath, platform())) {\n\t\treturn managedPath;\n\t}\n\n\t// Check system PATH - if found, just return the command name (it's in PATH)\n\tconst systemBinaryNames = config.systemBinaryNames ?? [config.binaryName];\n\tfor (const systemBinaryName of systemBinaryNames) {\n\t\tif (commandExists(systemBinaryName)) {\n\t\t\treturn systemBinaryName;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Reviewed, pinned release metadata for auto-downloaded executable tools (ADR 0017).\n * Never resolved from a mutable \"latest\" API call. Updating a version, asset name, or\n * digest here is itself the reviewed change that authorizes installing a new release.\n *\n * Every digest and byte size below was established by downloading the artifact,\n * computing its SHA-256 locally, and cross-checking that value against the\n * upstream-published digest (ripgrep's own `.sha256` release sidecars; GitHub's own\n * per-asset `digest` field for both projects) — not invented, and not taken from a\n * single unverified source.\n */\ninterface ManagedToolPin {\n\tversion: string;\n\tassetName: string;\n\tsha256: string;\n\texpectedBytes: number;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n}\n\nconst MANAGED_TOOL_PINS: Record<\"fd\" | \"rg\", Partial<Record<string, ManagedToolPin>>> = {\n\trg: {\n\t\t\"linux:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz\",\n\t\t\tsha256: \"4cf9f2741e6c465ffdb7c26f38056a59e2a2544b51f7cc128ef28337eeae4d8e\",\n\t\t\texpectedBytes: 2566310,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"linux:arm64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-aarch64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"c827481c4ff4ea10c9dc7a4022c8de5db34a5737cb74484d62eb94a95841ab2f\",\n\t\t\texpectedBytes: 2047405,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"fc87e78f7cb3fea12d69072e7ef3b21509754717b746368fd40d88963630e2b3\",\n\t\t\texpectedBytes: 2082672,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:arm64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-aarch64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"24ad76777745fbff131c8fbc466742b011f925bfa4fffa2ded6def23b5b937be\",\n\t\t\texpectedBytes: 1787248,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"win32:x64\": {\n\t\t\tversion: \"14.1.1\",\n\t\t\tassetName: \"ripgrep-14.1.1-x86_64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"d0f534024c42afd6cb4d38907c25cd2b249b79bbe6cc1dbee8e3e37c2b6e25a1\",\n\t\t\texpectedBytes: 2058893,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t\t// No upstream win32/arm64 ripgrep release asset exists at 14.1.1; resolution\n\t\t// fails closed for that combination rather than constructing a URL that 404s.\n\t},\n\tfd: {\n\t\t\"linux:x64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-x86_64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"def59805cd14b5651b68990855f426ad087f3b96881296d963910431ba3143c8\",\n\t\t\texpectedBytes: 1700779,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"linux:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-unknown-linux-gnu.tar.gz\",\n\t\t\tsha256: \"6c51f7c5446b3338b1e401ff15dc194c590bb2fa64fd43ff3278300f073adec5\",\n\t\t\texpectedBytes: 1559490,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t// Upstream stopped shipping an Intel macOS fd binary after 10.3.0 (see\n\t\t// 7afd80d78, \"pin fd for macos x64\"); preserved here rather than special-cased.\n\t\t\"darwin:x64\": {\n\t\t\tversion: \"10.3.0\",\n\t\t\tassetName: \"fd-v10.3.0-x86_64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"50d30f13fe3d5914b14c4fff5abcbd4d0cdab4b855970a6956f4f006c17117a3\",\n\t\t\texpectedBytes: 1430203,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"darwin:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-apple-darwin.tar.gz\",\n\t\t\tsha256: \"623dc0afc81b92e4d4606b380d7bc91916ba7b97814263e554d50923a39e480a\",\n\t\t\texpectedBytes: 1328933,\n\t\t\tarchiveKind: \"tar.gz\",\n\t\t},\n\t\t\"win32:x64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-x86_64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"b2816e506390a89941c63c9187d58a3cc10e9a55f2ef0685f9ea0eccaf7c98c8\",\n\t\t\texpectedBytes: 1525898,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t\t\"win32:arm64\": {\n\t\t\tversion: \"10.4.2\",\n\t\t\tassetName: \"fd-v10.4.2-aarch64-pc-windows-msvc.zip\",\n\t\t\tsha256: \"4f9110c2d5b33a7f760bfa5510f4c113d828109f7277d421b1053a9943c0fc92\",\n\t\t\texpectedBytes: 1370473,\n\t\t\tarchiveKind: \"zip\",\n\t\t},\n\t},\n};\n\nexport interface ManagedToolArtifact {\n\tversion: string;\n\turl: string;\n\tsha256: string;\n\texpectedBytes: number;\n\t/** Bounded download ceiling (ADR 0017): four times the reviewed expected size. */\n\tmaxBytes: number;\n\tbinaryName: string;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n}\n\n/** Resolve reviewed, pinned metadata for a managed tool. Never queries \"latest\". */\nexport function resolveManagedToolArtifact(options: {\n\ttool: \"fd\" | \"rg\";\n\tplatform: NodeJS.Platform | string;\n\tarchitecture: string;\n}): ManagedToolArtifact {\n\tconst config = TOOLS[options.tool];\n\tconst pin = MANAGED_TOOL_PINS[options.tool]?.[`${options.platform}:${options.architecture}`];\n\tif (!config || !pin) {\n\t\tthrow new Error(`No reviewed pinned artifact for ${options.tool} on ${options.platform}/${options.architecture}`);\n\t}\n\treturn {\n\t\tversion: pin.version,\n\t\turl: `https://github.com/${config.repo}/releases/download/${config.tagPrefix}${pin.version}/${pin.assetName}`,\n\t\tsha256: pin.sha256,\n\t\texpectedBytes: pin.expectedBytes,\n\t\tmaxBytes: pin.expectedBytes * 4,\n\t\tbinaryName: config.binaryName,\n\t\tarchiveKind: pin.archiveKind,\n\t};\n}\n\n// Download a file from URL, aborting if the declared or received size exceeds the\n// bounded ceiling (ADR 0017) rather than writing an unbounded stream to disk.\nasync function downloadFile(url: string, dest: string, maxBytes: number): Promise<void> {\n\tconst response = await fetchWithRetry(url, undefined, { timeoutMs: DOWNLOAD_TIMEOUT_MS });\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to download: ${response.status}`);\n\t}\n\n\tif (!response.body) {\n\t\tthrow new Error(\"No response body\");\n\t}\n\n\tconst declaredLength = Number(response.headers.get(\"content-length\") ?? Number.NaN);\n\tif (Number.isFinite(declaredLength) && declaredLength > maxBytes) {\n\t\tthrow new Error(`Refusing download: declared size ${declaredLength} exceeds bounded limit ${maxBytes}`);\n\t}\n\n\tlet received = 0;\n\tconst source = Readable.fromWeb(response.body as any);\n\tsource.on(\"data\", (chunk: Buffer) => {\n\t\treceived += chunk.length;\n\t\tif (received > maxBytes) {\n\t\t\tsource.destroy(new Error(`Refusing download: exceeded bounded limit ${maxBytes} bytes`));\n\t\t}\n\t});\n\n\tconst fileStream = createWriteStream(dest);\n\ttry {\n\t\tawait pipeline(source, fileStream);\n\t} catch (error) {\n\t\trmSync(dest, { force: true });\n\t\tthrow error;\n\t}\n}\n\nfunction findBinaryRecursively(rootDir: string, binaryFileName: string): string | null {\n\tconst stack: string[] = [rootDir];\n\n\twhile (stack.length > 0) {\n\t\tconst currentDir = stack.pop();\n\t\tif (!currentDir) continue;\n\n\t\tconst entries = readdirSync(currentDir, { withFileTypes: true });\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(currentDir, entry.name);\n\t\t\tif (entry.isFile() && entry.name === binaryFileName) {\n\t\t\t\treturn fullPath;\n\t\t\t}\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tstack.push(fullPath);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction formatSpawnFailure(result: SpawnSyncReturns<Buffer>): string {\n\tif (result.error?.message) {\n\t\treturn result.error.message;\n\t}\n\tconst stderr = result.stderr?.toString().trim();\n\tif (stderr) {\n\t\treturn stderr;\n\t}\n\tconst stdout = result.stdout?.toString().trim();\n\tif (stdout) {\n\t\treturn stdout;\n\t}\n\treturn `exit status ${result.status ?? \"unknown\"}`;\n}\n\nfunction runExtractionCommand(command: string, args: string[]): string | null {\n\tconst result = spawnSync(command, args, { stdio: \"pipe\" });\n\tif (!result.error && result.status === 0) {\n\t\treturn null;\n\t}\n\treturn `${command}: ${formatSpawnFailure(result)}`;\n}\n\nfunction extractTarGzArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failure = runExtractionCommand(\"tar\", [\"xzf\", archivePath, \"-C\", extractDir]);\n\tif (failure) {\n\t\tthrow new Error(`Failed to extract ${assetName}: ${failure}`);\n\t}\n}\n\nfunction getWindowsTarCommand(): string {\n\tconst systemRoot = process.env.SystemRoot ?? process.env.WINDIR;\n\tif (systemRoot) {\n\t\tconst systemTar = join(systemRoot, \"System32\", \"tar.exe\");\n\t\tif (existsSync(systemTar)) {\n\t\t\treturn systemTar;\n\t\t}\n\t}\n\treturn \"tar.exe\";\n}\n\nfunction extractZipArchive(archivePath: string, extractDir: string, assetName: string): void {\n\tconst failures: string[] = [];\n\n\tif (platform() === \"win32\") {\n\t\t// Windows ships bsdtar as tar.exe, which supports zip files. Prefer the\n\t\t// System32 binary over Git Bash's GNU tar, which does not handle zip archives.\n\t\tconst tarFailure = runExtractionCommand(getWindowsTarCommand(), [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\n\t\tconst script =\n\t\t\t\"& { param($archive, $destination) $ErrorActionPreference = 'Stop'; Expand-Archive -LiteralPath $archive -DestinationPath $destination -Force }\";\n\t\tconst powershellFailure = runExtractionCommand(\"powershell.exe\", [\n\t\t\t\"-NoLogo\",\n\t\t\t\"-NoProfile\",\n\t\t\t\"-NonInteractive\",\n\t\t\t\"-ExecutionPolicy\",\n\t\t\t\"Bypass\",\n\t\t\t\"-Command\",\n\t\t\tscript,\n\t\t\tarchivePath,\n\t\t\textractDir,\n\t\t]);\n\t\tif (!powershellFailure) return;\n\t\tfailures.push(powershellFailure);\n\t} else {\n\t\tconst unzipFailure = runExtractionCommand(\"unzip\", [\"-q\", archivePath, \"-d\", extractDir]);\n\t\tif (!unzipFailure) return;\n\t\tfailures.push(unzipFailure);\n\n\t\tconst tarFailure = runExtractionCommand(\"tar\", [\"xf\", archivePath, \"-C\", extractDir]);\n\t\tif (!tarFailure) return;\n\t\tfailures.push(tarFailure);\n\t}\n\n\tthrow new Error(`Failed to extract ${assetName}: ${failures.join(\"; \")}`);\n}\n\nasync function sha256File(path: string): Promise<string> {\n\tconst hash = createHash(\"sha256\");\n\tfor await (const chunk of createReadStream(path)) {\n\t\thash.update(chunk as Buffer);\n\t}\n\treturn hash.digest(\"hex\");\n}\n\nexport interface InstallManagedToolArchiveOptions {\n\tarchivePath: string;\n\tdestination: string;\n\tbinaryName: string;\n\tarchiveKind: \"tar.gz\" | \"zip\";\n\texpectedSha256: string;\n\tmaxBytes: number;\n}\n\n/**\n * Verify a downloaded tool archive against its pinned digest, extract it into a\n * per-install quarantine directory, confirm the located binary did not escape that\n * quarantine, and only then atomically promote it into the live tools directory\n * (ADR 0017). Rejects a digest mismatch or an out-of-bounds archive before anything\n * is written to `destination`.\n */\nexport async function installManagedToolArchive(options: InstallManagedToolArchiveOptions): Promise<string> {\n\tconst { archivePath, destination, binaryName, archiveKind, expectedSha256, maxBytes } = options;\n\n\tconst { size } = statSync(archivePath);\n\tif (size > maxBytes) {\n\t\tthrow new Error(`Refusing install: archive size ${size} exceeds bounded limit ${maxBytes}`);\n\t}\n\n\tconst actualSha256 = await sha256File(archivePath);\n\tif (actualSha256.toLowerCase() !== expectedSha256.toLowerCase()) {\n\t\tthrow new Error(`Refusing install: digest mismatch for ${basename(archivePath)}`);\n\t}\n\n\tmkdirSync(destination, { recursive: true });\n\tconst quarantineDirectory = join(\n\t\tdestination,\n\t\t`.quarantine_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,\n\t);\n\tmkdirSync(quarantineDirectory, { recursive: true });\n\n\ttry {\n\t\tconst assetName = basename(archivePath);\n\t\tif (archiveKind === \"tar.gz\") {\n\t\t\textractTarGzArchive(archivePath, quarantineDirectory, assetName);\n\t\t} else {\n\t\t\textractZipArchive(archivePath, quarantineDirectory, assetName);\n\t\t}\n\n\t\tconst binaryFileName = binaryName + (platform() === \"win32\" ? \".exe\" : \"\");\n\t\tconst extractedBinary = findBinaryRecursively(quarantineDirectory, binaryFileName);\n\t\tif (!extractedBinary) {\n\t\t\tthrow new Error(`Binary not found in verified archive: expected ${binaryFileName}`);\n\t\t}\n\n\t\tconst resolvedBinary = resolvePath(extractedBinary);\n\t\tconst containmentPath = relative(resolvePath(quarantineDirectory), resolvedBinary);\n\t\tif (containmentPath.startsWith(\"..\") || resolvePath(containmentPath) === containmentPath) {\n\t\t\tthrow new Error(`Refusing install: archive entry escaped its quarantine directory: ${extractedBinary}`);\n\t\t}\n\n\t\tconst finalPath = join(destination, binaryFileName);\n\t\trenameSync(resolvedBinary, finalPath);\n\t\tif (platform() !== \"win32\") {\n\t\t\tchmodSync(finalPath, 0o755);\n\t\t}\n\t\treturn finalPath;\n\t} finally {\n\t\trmSync(quarantineDirectory, { recursive: true, force: true });\n\t}\n}\n\n// Download and install a tool from reviewed, pinned metadata (ADR 0017): bounded\n// download, digest verification, quarantine extraction, then atomic promotion.\nasync function downloadTool(tool: \"fd\" | \"rg\"): Promise<string> {\n\tconst plat = platform();\n\tconst architecture = arch();\n\tconst artifact = resolveManagedToolArtifact({ tool, platform: plat, architecture });\n\n\tmkdirSync(TOOLS_DIR, { recursive: true });\n\n\t// Unique download filename: fd and rg downloads can run concurrently during\n\t// startup, so sharing a fixed path causes races.\n\tconst archivePath = join(\n\t\tTOOLS_DIR,\n\t\t`download_${artifact.binaryName}_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}${\n\t\t\tartifact.archiveKind === \"zip\" ? \".zip\" : \".tar.gz\"\n\t\t}`,\n\t);\n\n\ttry {\n\t\tawait downloadFile(artifact.url, archivePath, artifact.maxBytes);\n\t\treturn await installManagedToolArchive({\n\t\t\tarchivePath,\n\t\t\tdestination: TOOLS_DIR,\n\t\t\tbinaryName: artifact.binaryName,\n\t\t\tarchiveKind: artifact.archiveKind,\n\t\t\texpectedSha256: artifact.sha256,\n\t\t\tmaxBytes: artifact.maxBytes,\n\t\t});\n\t} finally {\n\t\trmSync(archivePath, { force: true });\n\t}\n}\n\nexport interface HostToolBinary {\n\t/** File name the child looks for in its own tools directory (\"fd\", \"rg\", \"fd.exe\"). */\n\tname: string;\n\t/** Absolute host path to the executable backing that name. */\n\tpath: string;\n}\n\nfunction isExecutableFile(candidate: string, targetPlatform: NodeJS.Platform): boolean {\n\ttry {\n\t\tconst stats = statSync(candidate);\n\t\tif (!stats.isFile()) return false;\n\t\t// Windows has no POSIX execute bit; presence of the file is the only signal.\n\t\treturn targetPlatform === \"win32\" || (stats.mode & 0o111) !== 0;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Absolute host path for a managed tool, or undefined when it is installed nowhere.\n *\n * `getToolPath` may return a bare command name because its caller spawns through PATH.\n * A sandboxed child has no such luxury: its PATH still lists host directories that the\n * OS boundary has already replaced, so the supervisor must resolve a real path here and\n * project it in (ADR 0017, and the sandbox spec's tool-projection amendment).\n */\nexport function resolveHostToolBinary(\n\ttool: \"fd\" | \"rg\",\n\toptions: { toolsDirectory?: string; pathValue?: string; platform?: NodeJS.Platform } = {},\n): string | undefined {\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tconst targetPlatform = options.platform ?? platform();\n\tconst executableSuffix = targetPlatform === \"win32\" ? \".exe\" : \"\";\n\tconst managedPath = join(options.toolsDirectory ?? TOOLS_DIR, config.binaryName + executableSuffix);\n\tif (isExecutableFile(managedPath, targetPlatform)) {\n\t\treturn managedPath;\n\t}\n\n\tconst pathEntries = (options.pathValue ?? process.env.PATH ?? \"\")\n\t\t.split(targetPlatform === \"win32\" ? \";\" : \":\")\n\t\t.filter((entry) => entry.length > 0);\n\t// Preference runs by name first, matching getToolPath: a real `fd` anywhere on PATH\n\t// beats a `fdfind` alias, rather than whichever directory happens to come first.\n\tfor (const systemBinaryName of config.systemBinaryNames ?? [config.binaryName]) {\n\t\tfor (const entry of pathEntries) {\n\t\t\tconst candidate = join(entry, systemBinaryName + executableSuffix);\n\t\t\tif (isExecutableFile(candidate, targetPlatform)) {\n\t\t\t\treturn candidate;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\n/**\n * Install any missing managed tool on the host, then report absolute paths for the ones\n * that exist. Downloading here rather than inside the child means one install serves every\n * workspace, and it happens on the side of the boundary that still has network access.\n */\nexport async function prepareHostToolBinaries(silent: boolean = false): Promise<HostToolBinary[]> {\n\tconst binaries: HostToolBinary[] = [];\n\tfor (const tool of [\"fd\", \"rg\"] as const) {\n\t\tawait ensureTool(tool, silent ? undefined : ({ message }) => console.error(message));\n\t\tconst hostPath = resolveHostToolBinary(tool);\n\t\tif (hostPath) {\n\t\t\tbinaries.push({ name: TOOLS[tool].binaryName + (platform() === \"win32\" ? \".exe\" : \"\"), path: hostPath });\n\t\t}\n\t}\n\treturn binaries;\n}\n\n// Termux package names for tools\nconst TERMUX_PACKAGES: Record<string, string> = {\n\tfd: \"fd\",\n\trg: \"ripgrep\",\n};\n\nexport interface ToolStatus {\n\ttype: \"info\" | \"warning\";\n\tmessage: string;\n}\n\n/**\n * Ensure a tool is available, downloading if necessary.\n * Reports progress through `onStatus`; status messages are otherwise silent.\n * Returns the tool path, or undefined if unavailable.\n */\nexport async function ensureTool(\n\ttool: \"fd\" | \"rg\",\n\tonStatus?: (status: ToolStatus) => void,\n): Promise<string | undefined> {\n\tconst existingPath = getToolPath(tool);\n\tif (existingPath) {\n\t\treturn existingPath;\n\t}\n\n\tconst config = TOOLS[tool];\n\tif (!config) return undefined;\n\n\tif (isOfflineModeEnabled()) {\n\t\tonStatus?.({ type: \"warning\", message: `${config.name} not found. Offline mode enabled, skipping download.` });\n\t\treturn undefined;\n\t}\n\n\t// On Android/Termux, Linux binaries don't work due to Bionic libc incompatibility.\n\t// Users must install via pkg.\n\tif (platform() === \"android\") {\n\t\tconst pkgName = TERMUX_PACKAGES[tool] ?? tool;\n\t\tonStatus?.({ type: \"warning\", message: `${config.name} not found. Install with: pkg install ${pkgName}` });\n\t\treturn undefined;\n\t}\n\n\t// Tool not found - download it\n\tonStatus?.({ type: \"info\", message: `${config.name} not found. Downloading...` });\n\n\ttry {\n\t\tconst path = await downloadTool(tool);\n\t\tonStatus?.({ type: \"info\", message: `${config.name} installed to ${path}` });\n\t\treturn path;\n\t} catch (e) {\n\t\tonStatus?.({\n\t\t\ttype: \"warning\",\n\t\t\tmessage: `Failed to download ${config.name}: ${e instanceof Error ? e.message : e}`,\n\t\t});\n\t\treturn undefined;\n\t}\n}\n"]}
|
package/docs/compaction.md
CHANGED
|
@@ -42,13 +42,13 @@ You can also trigger manually with `/compact [instructions]`, where optional ins
|
|
|
42
42
|
2. **Extract messages**: Collect messages from the previous kept boundary (or session start) up to the cut point
|
|
43
43
|
3. **Generate summary**: Call LLM to summarize with structured format, passing the previous summary as iterative context when present
|
|
44
44
|
4. **Append entry**: Save `CompactionEntry` with summary and `firstKeptEntryId`
|
|
45
|
-
5. **
|
|
45
|
+
5. **Rebuilds context**: Session rebuilds the context for the next request, using summary + messages from `firstKeptEntryId` onwards
|
|
46
46
|
|
|
47
47
|
```
|
|
48
48
|
Before compaction:
|
|
49
49
|
|
|
50
50
|
entry: 0 1 2 3 4 5 6 7 8 9
|
|
51
|
-
|
|
51
|
+
┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬─────┐
|
|
52
52
|
│ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│
|
|
53
53
|
└─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┘
|
|
54
54
|
└────────┬───────┘ └──────────────┬──────────────┘
|
|
@@ -59,7 +59,7 @@ Before compaction:
|
|
|
59
59
|
After compaction (new entry appended):
|
|
60
60
|
|
|
61
61
|
entry: 0 1 2 3 4 5 6 7 8 9 10
|
|
62
|
-
|
|
62
|
+
┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬─────┬─────┐
|
|
63
63
|
│ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool│ cmp │
|
|
64
64
|
└─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴─────┴─────┘
|
|
65
65
|
└──────────┬──────┘ └──────────────────────┬───────────────────┘
|
|
@@ -346,6 +346,21 @@ pi.on("session_before_compact", async (event, ctx) => {
|
|
|
346
346
|
|
|
347
347
|
See [custom-compaction.ts](../examples/extensions/custom-compaction.ts) for a complete example using a different model.
|
|
348
348
|
|
|
349
|
+
### session_compact_failed
|
|
350
|
+
|
|
351
|
+
Fired when manual or automatic compaction fails or is aborted. This is useful for telemetry extensions that need to pair `session_before_compact` attempts with terminal outcomes.
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
pi.on("session_compact_failed", async (event, ctx) => {
|
|
355
|
+
const { reason, errorMessage, aborted, willRetry, fromExtension } = event;
|
|
356
|
+
// reason - "manual" (/compact), "threshold", or "overflow"
|
|
357
|
+
// errorMessage - present for non-abort failures
|
|
358
|
+
// aborted - true for cancelled/aborted compactions
|
|
359
|
+
// willRetry - whether the aborted turn would have retried after compaction
|
|
360
|
+
// fromExtension - whether extension-provided compaction content was being used
|
|
361
|
+
});
|
|
362
|
+
```
|
|
363
|
+
|
|
349
364
|
### session_before_tree
|
|
350
365
|
|
|
351
366
|
Fired before `/tree` navigation. Always fires regardless of whether user chose to summarize. Can cancel navigation or provide custom summary.
|
package/docs/custom-provider.md
CHANGED
|
@@ -227,7 +227,7 @@ The `api` field determines which streaming implementation is used:
|
|
|
227
227
|
| `openai-responses` | OpenAI Responses API |
|
|
228
228
|
| `azure-openai-responses` | Azure OpenAI Responses API |
|
|
229
229
|
| `openai-codex-responses` | OpenAI Codex Responses API |
|
|
230
|
-
| `mistral-conversations` | Mistral
|
|
230
|
+
| `mistral-conversations` | Native Mistral Chat Completions streaming |
|
|
231
231
|
| `google-generative-ai` | Google Generative AI API |
|
|
232
232
|
| `google-vertex` | Google Vertex AI API |
|
|
233
233
|
| `bedrock-converse-stream` | Amazon Bedrock Converse API |
|
|
@@ -752,8 +752,10 @@ interface ProviderModelConfig {
|
|
|
752
752
|
requiresThinkingAsText?: boolean;
|
|
753
753
|
requiresReasoningContentOnAssistantMessages?: boolean;
|
|
754
754
|
thinkingFormat?: "openai" | "openrouter" | "deepseek" | "together" | "baseten" | "zai" | "qwen" | "chat-template" | "qwen-chat-template" | "string-thinking" | "ant-ling";
|
|
755
|
-
chatTemplateKwargs?: Record<string, string | number | boolean | null | { "$var": "thinking.enabled" | "thinking.effort"; omitWhenOff?: boolean }>;
|
|
756
|
-
chatTemplateArgs?: Record<string, string | number | boolean | null | { "$var": "thinking.enabled" | "thinking.effort"; omitWhenOff?: boolean }>;
|
|
755
|
+
chatTemplateKwargs?: Record<string, string | number | boolean | null | { "$var": "thinking.enabled" | "thinking.effort" | "thinking.budget"; omitWhenOff?: boolean }>;
|
|
756
|
+
chatTemplateArgs?: Record<string, string | number | boolean | null | { "$var": "thinking.enabled" | "thinking.effort" | "thinking.budget"; omitWhenOff?: boolean }>;
|
|
757
|
+
thinkingTokenBudgetField?: "thinking_token_budget" | "thinking_budget" | "thinking_budget_tokens";
|
|
758
|
+
supportsThinkingTokenBudget?: boolean;
|
|
757
759
|
cacheControlFormat?: "anthropic";
|
|
758
760
|
sessionAffinityFormat?: "openai" | "openai-nosession" | "openrouter";
|
|
759
761
|
sendSessionAffinityHeaders?: boolean;
|
|
@@ -771,4 +773,5 @@ interface ProviderModelConfig {
|
|
|
771
773
|
```
|
|
772
774
|
|
|
773
775
|
`openrouter` sends `reasoning: { effort }`. `deepseek` sends `thinking: { type: "enabled" | "disabled" }` and `reasoning_effort` when enabled. `together` sends `reasoning: { enabled }` and also `reasoning_effort` when `supportsReasoningEffort` is enabled. `qwen` is for DashScope-style top-level `enable_thinking`. Use `qwen-chat-template` for local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking` and need `preserve_thinking`. Use `chat-template` for configurable `chat_template_kwargs`, for example DeepSeek V3.x behind vLLM with `chatTemplateKwargs: { "thinking": { "$var": "thinking.enabled" } }`. Use `thinkingFormat: "baseten"` with `chatTemplateArgs` when the provider expects toggle values under `chat_template_args` and optionally supports top-level `reasoning_effort`.
|
|
776
|
+
`thinkingTokenBudgetField` sends a clamped per-level thinking budget as a top-level request field (`thinking_token_budget` on vLLM, `thinking_budget` on Qwen/SGLang, `thinking_budget_tokens` on llama.cpp). `supportsThinkingTokenBudget: true` is an alias for the vLLM field name. Do not combine it with `reasoning_effort` on DashScope Qwen models.
|
|
774
777
|
`cacheControlFormat: "anthropic"` applies Anthropic-style `cache_control` markers to the system prompt, last tool definition, and last user, assistant, or tool-result text content.
|
|
@@ -22,6 +22,7 @@ subprocess metadata. Provider API-key variables remain documented in
|
|
|
22
22
|
| `APEX_CODE_SHARE_VIEWER_URL` | Optional base URL for preview links after `/share`; unset returns the GitHub Gist URL only |
|
|
23
23
|
| `VISUAL`, `EDITOR` | External editor fallback |
|
|
24
24
|
| `HTTP_PROXY`, `HTTPS_PROXY` | Proxy outbound HTTP requests |
|
|
25
|
+
| `EXA_API_KEY` | Key for the `web_search` tool's Exa backend; unset leaves `web_search` unconfigured |
|
|
25
26
|
|
|
26
27
|
The CLI and RPC entry points set `APEX_CODE_CODING_AGENT=true`. Commands run through
|
|
27
28
|
the built-in bash tool receive `APEX_CODE_SESSION_ID`, `APEX_CODE_SESSION_FILE`,
|
package/docs/extensions.md
CHANGED
|
@@ -330,7 +330,8 @@ user sends another prompt ◄─────────────────
|
|
|
330
330
|
|
|
331
331
|
/compact or auto-compaction
|
|
332
332
|
├─► session_before_compact (can cancel or customize)
|
|
333
|
-
|
|
333
|
+
├─► session_compact (success)
|
|
334
|
+
└─► session_compact_failed (failure or abort)
|
|
334
335
|
|
|
335
336
|
/tree navigation
|
|
336
337
|
├─► session_before_tree (can cancel or customize)
|
|
@@ -448,7 +449,7 @@ pi.on("session_before_fork", async (event, ctx) => {
|
|
|
448
449
|
After a successful fork or clone, Apex Code emits `session_shutdown` for the old extension instance, reloads and rebinds extensions for the new session, then emits `session_start` with `reason: "fork"` and `previousSessionFile`.
|
|
449
450
|
Do cleanup work in `session_shutdown`, then reestablish any in-memory state in `session_start`.
|
|
450
451
|
|
|
451
|
-
#### session_before_compact / session_compact
|
|
452
|
+
#### session_before_compact / session_compact / session_compact_failed
|
|
452
453
|
|
|
453
454
|
Fired on compaction. See [compaction.md](compaction.md) for details.
|
|
454
455
|
|
|
@@ -479,6 +480,14 @@ pi.on("session_compact", async (event, ctx) => {
|
|
|
479
480
|
// event.reason - "manual" (/compact), "threshold", or "overflow"
|
|
480
481
|
// event.willRetry - whether the aborted turn is retried after compaction (overflow recovery)
|
|
481
482
|
});
|
|
483
|
+
|
|
484
|
+
pi.on("session_compact_failed", async (event, ctx) => {
|
|
485
|
+
// event.reason - "manual" (/compact), "threshold", or "overflow"
|
|
486
|
+
// event.errorMessage - present for non-abort failures
|
|
487
|
+
// event.aborted - true for cancelled/aborted compactions
|
|
488
|
+
// event.willRetry - whether the aborted turn would have retried after compaction
|
|
489
|
+
// event.fromExtension - whether extension-provided compaction content was being used
|
|
490
|
+
});
|
|
482
491
|
```
|
|
483
492
|
|
|
484
493
|
#### session_before_tree / session_tree
|
|
@@ -1426,12 +1435,16 @@ pi.sendUserMessage([
|
|
|
1426
1435
|
// During streaming - must specify delivery mode
|
|
1427
1436
|
pi.sendUserMessage("Focus on error handling", { deliverAs: "steer" });
|
|
1428
1437
|
pi.sendUserMessage("And then summarize", { deliverAs: "followUp" });
|
|
1438
|
+
|
|
1439
|
+
// Opt in to extension command dispatch and skill/prompt template expansion
|
|
1440
|
+
pi.sendUserMessage("/review src/index.ts", { expandPromptTemplates: true });
|
|
1429
1441
|
```
|
|
1430
1442
|
|
|
1431
1443
|
**Options:**
|
|
1432
1444
|
- `deliverAs` - Required when agent is streaming:
|
|
1433
1445
|
- `"steer"` - Queues the message for delivery after the current assistant turn finishes executing its tool calls
|
|
1434
1446
|
- `"followUp"` - Waits for agent to finish all tools
|
|
1447
|
+
- `expandPromptTemplates` - Dispatch extension commands and expand skill commands and prompt templates. Defaults to `false`.
|
|
1435
1448
|
|
|
1436
1449
|
When not streaming, the message is sent immediately and triggers a new turn. When streaming without `deliverAs`, throws an error.
|
|
1437
1450
|
|
|
@@ -2046,7 +2059,7 @@ pi.registerTool({
|
|
|
2046
2059
|
|
|
2047
2060
|
### Overriding Built-in Tools
|
|
2048
2061
|
|
|
2049
|
-
Extensions can override built-in tools (`read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`) by registering a tool with the same name. Interactive mode displays a warning when this happens.
|
|
2062
|
+
Extensions can override built-in tools (`read`, `bash`, `powershell`, `edit`, `write`, `grep`, `find`, `ls`) by registering a tool with the same name. Interactive mode displays a warning when this happens.
|
|
2050
2063
|
|
|
2051
2064
|
```bash
|
|
2052
2065
|
# Extension's read tool replaces built-in read
|
|
@@ -2070,6 +2083,7 @@ See [examples/extensions/tool-override.ts](../examples/extensions/tool-override.
|
|
|
2070
2083
|
Built-in tool implementations:
|
|
2071
2084
|
- [read.ts](https://github.com/Fchery87/apex-code/blob/main/packages/coding-agent/src/core/tools/read.ts) - `ReadToolDetails`
|
|
2072
2085
|
- [bash.ts](https://github.com/Fchery87/apex-code/blob/main/packages/coding-agent/src/core/tools/bash.ts) - `BashToolDetails`
|
|
2086
|
+
- [powershell.ts](https://github.com/Fchery87/apex-code/blob/main/packages/coding-agent/src/core/tools/powershell.ts) - `PowerShellToolDetails`
|
|
2073
2087
|
- [edit.ts](https://github.com/Fchery87/apex-code/blob/main/packages/coding-agent/src/core/tools/edit.ts)
|
|
2074
2088
|
- [write.ts](https://github.com/Fchery87/apex-code/blob/main/packages/coding-agent/src/core/tools/write.ts)
|
|
2075
2089
|
- [grep.ts](https://github.com/Fchery87/apex-code/blob/main/packages/coding-agent/src/core/tools/grep.ts) - `GrepToolDetails`
|
|
@@ -2105,11 +2119,11 @@ pi.registerTool({
|
|
|
2105
2119
|
});
|
|
2106
2120
|
```
|
|
2107
2121
|
|
|
2108
|
-
**Operations interfaces:** `ReadOperations`, `WriteOperations`, `EditOperations`, `BashOperations`, `LsOperations`, `GrepOperations`, `FindOperations`
|
|
2122
|
+
**Operations interfaces:** `ReadOperations`, `WriteOperations`, `EditOperations`, `BashOperations`, `PowerShellOperations`, `LsOperations`, `GrepOperations`, `FindOperations`
|
|
2109
2123
|
|
|
2110
2124
|
For `user_bash`, extensions can reuse Apex Code's local shell backend via `createLocalBashOperations()` instead of reimplementing local process spawning, shell resolution, and process-tree termination.
|
|
2111
2125
|
|
|
2112
|
-
The bash
|
|
2126
|
+
The `bash` and `powershell` tools also support a spawn hook to adjust the command, cwd, or env before execution:
|
|
2113
2127
|
|
|
2114
2128
|
```typescript
|
|
2115
2129
|
import { createBashTool } from "apex-code";
|
|
@@ -2123,7 +2137,7 @@ const bashTool = createBashTool(cwd, {
|
|
|
2123
2137
|
});
|
|
2124
2138
|
```
|
|
2125
2139
|
|
|
2126
|
-
`createBashTool()`
|
|
2140
|
+
`createBashTool()` and `createPowerShellTool()` expose the current session to commands through `PI_SESSION_ID`, `PI_SESSION_FILE`, `PI_PROVIDER`, `PI_MODEL`, and `PI_REASONING_LEVEL`. Injection happens before `spawnHook`, so hooks receive these values in `env` and preserve them when they spread the existing environment as above. Set `exposeSessionEnvironment: false` to disable them:
|
|
2127
2141
|
|
|
2128
2142
|
```typescript
|
|
2129
2143
|
const bashTool = createBashTool(cwd, {
|
|
@@ -2131,7 +2145,7 @@ const bashTool = createBashTool(cwd, {
|
|
|
2131
2145
|
});
|
|
2132
2146
|
```
|
|
2133
2147
|
|
|
2134
|
-
See [
|
|
2148
|
+
See [Shell tool session environment](environment-variables.md#shell-tool-session-environment) for variable semantics. See [examples/extensions/ssh.ts](../examples/extensions/ssh.ts) for a complete SSH example with `--ssh` flag.
|
|
2135
2149
|
|
|
2136
2150
|
### Output Truncation
|
|
2137
2151
|
|
package/docs/json.md
CHANGED
|
@@ -15,11 +15,16 @@ except that streaming message updates omit cumulative snapshots:
|
|
|
15
15
|
```typescript
|
|
16
16
|
type WithoutPartial<T> = T extends { partial: unknown } ? Omit<T, "partial"> : T;
|
|
17
17
|
|
|
18
|
+
type JsonAssistantMessageEvent<T> = T extends { type: "toolcall_start"; partial: unknown }
|
|
19
|
+
? WithoutPartial<T> & { id: string; toolName: string }
|
|
20
|
+
: WithoutPartial<T>;
|
|
21
|
+
|
|
18
22
|
type JsonAgentSessionEvent =
|
|
19
23
|
| Exclude<AgentSessionEvent, { type: "message_update" }>
|
|
20
24
|
| {
|
|
21
25
|
type: "message_update";
|
|
22
|
-
|
|
26
|
+
usage: Usage;
|
|
27
|
+
assistantMessageEvent: JsonAssistantMessageEvent<AssistantMessageEvent>;
|
|
23
28
|
};
|
|
24
29
|
```
|
|
25
30
|
|
|
@@ -73,16 +78,18 @@ Followed by events as they occur:
|
|
|
73
78
|
{"type":"agent_start"}
|
|
74
79
|
{"type":"turn_start"}
|
|
75
80
|
{"type":"message_start","message":{"role":"assistant","content":[],...}}
|
|
76
|
-
{"type":"message_update","assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Hello"}}
|
|
81
|
+
{"type":"message_update","usage":{...},"assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Hello"}}
|
|
77
82
|
{"type":"message_end","message":{...}}
|
|
78
83
|
{"type":"turn_end","message":{...},"toolResults":[]}
|
|
79
84
|
{"type":"agent_end","messages":[...]}
|
|
80
85
|
```
|
|
81
86
|
|
|
82
87
|
`message_update` records are delta-only. They omit both the cumulative `message` field and
|
|
83
|
-
`assistantMessageEvent.partial` to keep stream size linear.
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
`assistantMessageEvent.partial` to keep stream size linear. The top-level `usage` field contains
|
|
89
|
+
the latest cumulative provider-reported usage and may remain zero when a provider only reports
|
|
90
|
+
usage at completion. Use `contentIndex` and `delta` to assemble live text, thinking, or tool-call
|
|
91
|
+
arguments if needed. A `toolcall_start` event also includes the constant-sized `id` and `toolName`
|
|
92
|
+
fields. `message_end` contains the final authoritative message.
|
|
86
93
|
|
|
87
94
|
## Example
|
|
88
95
|
|