@iislee/opencodex 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +225 -0
- package/assets/architecture.png +0 -0
- package/assets/banner.png +0 -0
- package/assets/claude-code-models.gif +0 -0
- package/assets/codex-app-picker.png +0 -0
- package/bin/ocx.mjs +451 -0
- package/bin/package-main.mjs +9 -0
- package/gui/dist/assets/index-DTpMHS4F.js +67 -0
- package/gui/dist/assets/index-ZNVDE3C7.css +1 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/icons.svg +24 -0
- package/gui/dist/index.html +25 -0
- package/gui/dist/logo.png +0 -0
- package/gui/dist/provider-icons/alibaba-color.svg +1 -0
- package/gui/dist/provider-icons/antigravity-color.svg +1 -0
- package/gui/dist/provider-icons/antigravity.svg +1 -0
- package/gui/dist/provider-icons/claude-color.svg +1 -0
- package/gui/dist/provider-icons/claude.svg +1 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -0
- package/gui/dist/provider-icons/copilot.svg +1 -0
- package/gui/dist/provider-icons/cursor-color.svg +2 -0
- package/gui/dist/provider-icons/cursor.svg +2 -0
- package/gui/dist/provider-icons/deepseek-color.svg +1 -0
- package/gui/dist/provider-icons/discord.svg +1 -0
- package/gui/dist/provider-icons/firepass-color.svg +1 -0
- package/gui/dist/provider-icons/fireworks-color.svg +1 -0
- package/gui/dist/provider-icons/gemini-color.svg +1 -0
- package/gui/dist/provider-icons/gemini.svg +1 -0
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
- package/gui/dist/provider-icons/grok-color.svg +1 -0
- package/gui/dist/provider-icons/grok.svg +1 -0
- package/gui/dist/provider-icons/groq-color.svg +1 -0
- package/gui/dist/provider-icons/huggingface-color.svg +1 -0
- package/gui/dist/provider-icons/kimi-color.svg +1 -0
- package/gui/dist/provider-icons/kiro-color.svg +15 -0
- package/gui/dist/provider-icons/kiro.svg +14 -0
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
- package/gui/dist/provider-icons/mistral-color.svg +1 -0
- package/gui/dist/provider-icons/moonshot-color.svg +1 -0
- package/gui/dist/provider-icons/nvidia-color.svg +1 -0
- package/gui/dist/provider-icons/ollama-color.svg +1 -0
- package/gui/dist/provider-icons/openai.svg +1 -0
- package/gui/dist/provider-icons/opencode.svg +1 -0
- package/gui/dist/provider-icons/openrouter-color.svg +1 -0
- package/gui/dist/provider-icons/pi.svg +21 -0
- package/gui/dist/provider-icons/qianfan-color.svg +1 -0
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
- package/gui/dist/provider-icons/telegram.svg +1 -0
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/vllm-color.svg +1 -0
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
- package/package.json +102 -0
- package/src/AGENTS.md +28 -0
- package/src/adapters/anthropic-image-guard.ts +251 -0
- package/src/adapters/anthropic-image-normalize.ts +518 -0
- package/src/adapters/anthropic.ts +1003 -0
- package/src/adapters/azure.ts +36 -0
- package/src/adapters/base.ts +72 -0
- package/src/adapters/client-fingerprint.ts +59 -0
- package/src/adapters/cursor/arg-codec.ts +38 -0
- package/src/adapters/cursor/arg-normalize.ts +104 -0
- package/src/adapters/cursor/cursor-errors.ts +165 -0
- package/src/adapters/cursor/discovery.ts +276 -0
- package/src/adapters/cursor/effort-map.ts +127 -0
- package/src/adapters/cursor/exec-policy.ts +88 -0
- package/src/adapters/cursor/framing.ts +211 -0
- package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
- package/src/adapters/cursor/kv-store.ts +52 -0
- package/src/adapters/cursor/live-models.ts +153 -0
- package/src/adapters/cursor/live-smoke-gate.ts +41 -0
- package/src/adapters/cursor/live-transport.ts +1214 -0
- package/src/adapters/cursor/mcp-config.ts +42 -0
- package/src/adapters/cursor/mcp-manager.ts +333 -0
- package/src/adapters/cursor/message-mapper.ts +49 -0
- package/src/adapters/cursor/native-exec-common.ts +55 -0
- package/src/adapters/cursor/native-exec-desktop.ts +184 -0
- package/src/adapters/cursor/native-exec-fs.ts +329 -0
- package/src/adapters/cursor/native-exec-mcp.ts +153 -0
- package/src/adapters/cursor/native-exec-network.ts +43 -0
- package/src/adapters/cursor/native-exec-shell.ts +548 -0
- package/src/adapters/cursor/native-exec-tools.ts +118 -0
- package/src/adapters/cursor/native-exec.ts +576 -0
- package/src/adapters/cursor/protobuf-events.ts +563 -0
- package/src/adapters/cursor/protobuf-request.ts +714 -0
- package/src/adapters/cursor/request-builder.ts +255 -0
- package/src/adapters/cursor/thread-continuity.ts +67 -0
- package/src/adapters/cursor/tool-definitions.ts +505 -0
- package/src/adapters/cursor/transport-retry.ts +132 -0
- package/src/adapters/cursor/transport.ts +57 -0
- package/src/adapters/cursor/types.ts +52 -0
- package/src/adapters/cursor.ts +196 -0
- package/src/adapters/google-antigravity-replay.ts +303 -0
- package/src/adapters/google-antigravity-wire.ts +108 -0
- package/src/adapters/google-errors.ts +85 -0
- package/src/adapters/google-http.ts +100 -0
- package/src/adapters/google-tool-schema.ts +173 -0
- package/src/adapters/google-truncation.ts +13 -0
- package/src/adapters/google-wire-compiler.ts +232 -0
- package/src/adapters/google.ts +758 -0
- package/src/adapters/identity.ts +44 -0
- package/src/adapters/image.ts +23 -0
- package/src/adapters/kiro-constants.ts +16 -0
- package/src/adapters/kiro-errors.ts +197 -0
- package/src/adapters/kiro-events.ts +179 -0
- package/src/adapters/kiro-images.ts +129 -0
- package/src/adapters/kiro-retry.ts +312 -0
- package/src/adapters/kiro-thinking.ts +96 -0
- package/src/adapters/kiro-tool-fallback.ts +36 -0
- package/src/adapters/kiro-tools.ts +215 -0
- package/src/adapters/kiro-truncation.ts +33 -0
- package/src/adapters/kiro-wire.ts +129 -0
- package/src/adapters/kiro.ts +1898 -0
- package/src/adapters/mimo-free.ts +263 -0
- package/src/adapters/openai-chat.ts +1005 -0
- package/src/adapters/openai-responses.ts +1137 -0
- package/src/adapters/run-turn-queue.ts +114 -0
- package/src/adapters/tool-catalog-nudge.ts +71 -0
- package/src/adapters/upstream-http-error.ts +48 -0
- package/src/bridge.ts +1619 -0
- package/src/chat/inbound.ts +295 -0
- package/src/chat/outbound.ts +765 -0
- package/src/claude/agents-inject.ts +243 -0
- package/src/claude/alias.ts +149 -0
- package/src/claude/auth-detect.ts +229 -0
- package/src/claude/auth-mode-migration.ts +32 -0
- package/src/claude/auth-mode.ts +62 -0
- package/src/claude/context-windows.ts +189 -0
- package/src/claude/desktop-3p-guard.ts +35 -0
- package/src/claude/desktop-3p-paths.ts +84 -0
- package/src/claude/desktop-3p.ts +381 -0
- package/src/claude/desktop-health.ts +26 -0
- package/src/claude/desktop-profile.ts +263 -0
- package/src/claude/gateway-cache.ts +70 -0
- package/src/claude/inbound-debug.ts +163 -0
- package/src/claude/inbound.ts +509 -0
- package/src/claude/model-info.ts +151 -0
- package/src/claude/outbound.ts +872 -0
- package/src/cli/access.ts +108 -0
- package/src/cli/account-api.ts +268 -0
- package/src/cli/account-auth.ts +223 -0
- package/src/cli/account-extended.ts +350 -0
- package/src/cli/account.ts +275 -0
- package/src/cli/agent-driven.ts +70 -0
- package/src/cli/agent.ts +184 -0
- package/src/cli/catalog-prewarm.ts +27 -0
- package/src/cli/claude-desktop.ts +188 -0
- package/src/cli/claude.ts +286 -0
- package/src/cli/codex-shim-autorestore.ts +45 -0
- package/src/cli/combo.ts +119 -0
- package/src/cli/config-command.ts +145 -0
- package/src/cli/debug.ts +228 -0
- package/src/cli/doctor.ts +930 -0
- package/src/cli/export-command.ts +187 -0
- package/src/cli/help.ts +354 -0
- package/src/cli/index.ts +1113 -0
- package/src/cli/init.ts +224 -0
- package/src/cli/integrations.ts +142 -0
- package/src/cli/interactive-confirm.ts +133 -0
- package/src/cli/internal-dispatch.ts +20 -0
- package/src/cli/models-runtime.ts +212 -0
- package/src/cli/models.ts +336 -0
- package/src/cli/observe.ts +117 -0
- package/src/cli/opencode.ts +586 -0
- package/src/cli/pi.ts +188 -0
- package/src/cli/provider-runtime.ts +162 -0
- package/src/cli/provider.ts +463 -0
- package/src/cli/runtime-api.ts +325 -0
- package/src/cli/star-prompt.ts +155 -0
- package/src/cli/status-oauth.ts +78 -0
- package/src/cli/status.ts +321 -0
- package/src/cli/sync-cloud.ts +283 -0
- package/src/cli/system-command.ts +112 -0
- package/src/cli/tray-proxy.ts +52 -0
- package/src/cli/v2.ts +173 -0
- package/src/cli.ts +10 -0
- package/src/clients/config-export.ts +377 -0
- package/src/clients/effective-status.ts +385 -0
- package/src/clients/probes/agy.ts +55 -0
- package/src/clients/probes/cc-switch.ts +110 -0
- package/src/clients/probes/claude.ts +90 -0
- package/src/clients/probes/codex.ts +125 -0
- package/src/clients/probes/grok.ts +29 -0
- package/src/clients/probes/opencode.ts +109 -0
- package/src/clients/probes/paseo.ts +55 -0
- package/src/clients/probes/pi.ts +55 -0
- package/src/cloud/onedrive-auth.ts +666 -0
- package/src/cloud/onedrive-graph.ts +108 -0
- package/src/cloud/settings.ts +75 -0
- package/src/cloud/sync.ts +212 -0
- package/src/cloud/types.ts +56 -0
- package/src/cloud/vault.ts +89 -0
- package/src/codex/account-id.ts +34 -0
- package/src/codex/account-label.ts +34 -0
- package/src/codex/account-lifecycle.ts +55 -0
- package/src/codex/account-namespace-match.ts +63 -0
- package/src/codex/account-namespaces.ts +149 -0
- package/src/codex/account-pause.ts +20 -0
- package/src/codex/account-runtime-state.ts +31 -0
- package/src/codex/account-store.ts +517 -0
- package/src/codex/account-usability.ts +20 -0
- package/src/codex/app-server-processes.ts +756 -0
- package/src/codex/auth-api.ts +1540 -0
- package/src/codex/auth-collision.ts +107 -0
- package/src/codex/auth-context.ts +352 -0
- package/src/codex/autostart-health.ts +149 -0
- package/src/codex/catalog/aggregation.ts +378 -0
- package/src/codex/catalog/bundled.ts +251 -0
- package/src/codex/catalog/effort.ts +355 -0
- package/src/codex/catalog/metadata.ts +180 -0
- package/src/codex/catalog/parsing.ts +456 -0
- package/src/codex/catalog/provider-fetch.ts +902 -0
- package/src/codex/catalog/sync.ts +620 -0
- package/src/codex/catalog.ts +12 -0
- package/src/codex/data/upstream-models.json +830 -0
- package/src/codex/exec-invocation.ts +22 -0
- package/src/codex/features.ts +969 -0
- package/src/codex/history-migration-guardian.ts +102 -0
- package/src/codex/history-provider.ts +776 -0
- package/src/codex/home.ts +206 -0
- package/src/codex/inject.ts +799 -0
- package/src/codex/injected-marker.ts +72 -0
- package/src/codex/journal.ts +163 -0
- package/src/codex/main-account-cache.ts +32 -0
- package/src/codex/main-account.ts +40 -0
- package/src/codex/model-cache.ts +227 -0
- package/src/codex/paths.ts +65 -0
- package/src/codex/plugins-doctor.ts +242 -0
- package/src/codex/pool-rotation.ts +225 -0
- package/src/codex/project-config-warnings.ts +411 -0
- package/src/codex/quota.ts +411 -0
- package/src/codex/refresh.ts +53 -0
- package/src/codex/routing.ts +1477 -0
- package/src/codex/runtime.ts +538 -0
- package/src/codex/shim.ts +1189 -0
- package/src/codex/subagent-defaults.ts +550 -0
- package/src/codex/subagent-model-fallback.ts +469 -0
- package/src/codex/sync.ts +130 -0
- package/src/codex/warmup.ts +192 -0
- package/src/codex/websocket-registry.ts +100 -0
- package/src/combos/failover.ts +140 -0
- package/src/combos/index.ts +41 -0
- package/src/combos/request.ts +62 -0
- package/src/combos/resolve.ts +232 -0
- package/src/combos/types.ts +326 -0
- package/src/config.ts +2356 -0
- package/src/generated/jawcode-model-metadata.ts +104 -0
- package/src/github/star-state.ts +203 -0
- package/src/grok/inject.ts +545 -0
- package/src/grok/status.ts +121 -0
- package/src/grok/sync.ts +103 -0
- package/src/grok/usage-hook/report.mjs +348 -0
- package/src/grok/usage-hook.ts +278 -0
- package/src/images/artifacts.ts +516 -0
- package/src/images/fulfill-video.ts +163 -0
- package/src/images/fulfill.ts +149 -0
- package/src/images/index.ts +4 -0
- package/src/images/loop.ts +829 -0
- package/src/images/plan.ts +133 -0
- package/src/images/synthetic-tool.ts +133 -0
- package/src/images/types.ts +41 -0
- package/src/images/xai-client.ts +141 -0
- package/src/images/xai-video-client.ts +163 -0
- package/src/index.ts +22 -0
- package/src/lib/abort.ts +146 -0
- package/src/lib/admin-secrets.ts +25 -0
- package/src/lib/admission.ts +83 -0
- package/src/lib/app-owned-memory-stores.ts +173 -0
- package/src/lib/app-owned-memory.ts +265 -0
- package/src/lib/bounded-body.ts +202 -0
- package/src/lib/bun-binary-validator.d.mts +3 -0
- package/src/lib/bun-binary-validator.mjs +18 -0
- package/src/lib/bun-runtime.ts +71 -0
- package/src/lib/bun-stream-caps.ts +126 -0
- package/src/lib/config-ownership.ts +360 -0
- package/src/lib/crash-guard.ts +344 -0
- package/src/lib/debug-log-buffer.ts +83 -0
- package/src/lib/debug-settings.ts +108 -0
- package/src/lib/debug.ts +31 -0
- package/src/lib/destination-policy.ts +316 -0
- package/src/lib/errors.ts +364 -0
- package/src/lib/eventstream-decoder.ts +253 -0
- package/src/lib/gcp-adc.ts +341 -0
- package/src/lib/injection-debug-log.ts +58 -0
- package/src/lib/open-url.ts +25 -0
- package/src/lib/pinned-http.ts +151 -0
- package/src/lib/privacy.ts +20 -0
- package/src/lib/process-control.ts +165 -0
- package/src/lib/provider-outbound.ts +170 -0
- package/src/lib/provider-url.ts +14 -0
- package/src/lib/proxy-env.ts +18 -0
- package/src/lib/redact.ts +105 -0
- package/src/lib/retry-after.ts +55 -0
- package/src/lib/service-secrets.ts +25 -0
- package/src/lib/shadow-call.ts +30 -0
- package/src/lib/sidecar-tracker.ts +52 -0
- package/src/lib/sse-decoder.ts +323 -0
- package/src/lib/state-store-registrations.ts +109 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/test-home-guard.ts +90 -0
- package/src/lib/token-estimate.ts +69 -0
- package/src/lib/translator-budget.ts +356 -0
- package/src/lib/upstream-retry.ts +239 -0
- package/src/lib/win-exec.ts +115 -0
- package/src/lib/win-paths.ts +68 -0
- package/src/lib/windows-elevation.ts +705 -0
- package/src/lib/windows-secret-acl.ts +514 -0
- package/src/lib/winsw.ts +375 -0
- package/src/oauth/anthropic-routing.ts +594 -0
- package/src/oauth/anthropic.ts +177 -0
- package/src/oauth/callback-server.ts +294 -0
- package/src/oauth/chatgpt.ts +150 -0
- package/src/oauth/cursor.ts +211 -0
- package/src/oauth/github-copilot.ts +428 -0
- package/src/oauth/google-antigravity.ts +230 -0
- package/src/oauth/health.ts +399 -0
- package/src/oauth/index.ts +1174 -0
- package/src/oauth/key-providers.ts +108 -0
- package/src/oauth/kimi.ts +213 -0
- package/src/oauth/kiro-credentials.ts +726 -0
- package/src/oauth/kiro.ts +577 -0
- package/src/oauth/local-token-detect.ts +121 -0
- package/src/oauth/log.ts +48 -0
- package/src/oauth/login-cli.ts +163 -0
- package/src/oauth/pkce.ts +15 -0
- package/src/oauth/store.ts +630 -0
- package/src/oauth/token-guardian.ts +303 -0
- package/src/oauth/types.ts +62 -0
- package/src/oauth/xai.ts +241 -0
- package/src/pi/extensions.ts +72 -0
- package/src/pi/home.ts +42 -0
- package/src/pi/index.ts +40 -0
- package/src/pi/models.ts +278 -0
- package/src/pi/packages.ts +219 -0
- package/src/pi/settings.ts +365 -0
- package/src/pi/status.ts +68 -0
- package/src/pi/sync.ts +75 -0
- package/src/providers/alibaba-region-backup.ts +75 -0
- package/src/providers/alibaba-region-migration.ts +156 -0
- package/src/providers/alibaba-region-startup.ts +36 -0
- package/src/providers/antigravity-models.ts +205 -0
- package/src/providers/api-keys.ts +140 -0
- package/src/providers/base-url-choices.ts +64 -0
- package/src/providers/context-cap.ts +65 -0
- package/src/providers/derive.ts +339 -0
- package/src/providers/free-directory.ts +184 -0
- package/src/providers/github-copilot-transport.ts +56 -0
- package/src/providers/key-failover.ts +203 -0
- package/src/providers/kiro-models.ts +67 -0
- package/src/providers/label.ts +19 -0
- package/src/providers/model-discovery.ts +356 -0
- package/src/providers/openai-sidecar.ts +175 -0
- package/src/providers/openai-tier-startup.ts +27 -0
- package/src/providers/openai-tiers.ts +301 -0
- package/src/providers/openai-virtual-models.ts +82 -0
- package/src/providers/openrouter-routing.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +150 -0
- package/src/providers/quota.ts +1260 -0
- package/src/providers/registry.ts +1600 -0
- package/src/providers/slug-codec.ts +67 -0
- package/src/providers/xai-transport.ts +141 -0
- package/src/reasoning-effort.ts +135 -0
- package/src/responses/compaction.ts +117 -0
- package/src/responses/parser.ts +656 -0
- package/src/responses/reasoning-envelope.ts +52 -0
- package/src/responses/schema.ts +159 -0
- package/src/responses/spill-store.ts +394 -0
- package/src/responses/state.ts +895 -0
- package/src/responses/tool-groups.ts +19 -0
- package/src/router.ts +425 -0
- package/src/server/adapter-resolve.ts +80 -0
- package/src/server/auth-cors.ts +530 -0
- package/src/server/chat-completions.ts +368 -0
- package/src/server/claude-messages.ts +914 -0
- package/src/server/effort-policy.ts +172 -0
- package/src/server/gui-static.ts +123 -0
- package/src/server/image-retry.ts +42 -0
- package/src/server/images.ts +476 -0
- package/src/server/index.ts +1126 -0
- package/src/server/lifecycle.ts +227 -0
- package/src/server/live.ts +598 -0
- package/src/server/management/agent-settings-routes.ts +1169 -0
- package/src/server/management/api-access.ts +141 -0
- package/src/server/management/api-key-usage.ts +167 -0
- package/src/server/management/body.ts +35 -0
- package/src/server/management/clients-routes.ts +63 -0
- package/src/server/management/cloud-sync-routes.ts +266 -0
- package/src/server/management/combo-routes.ts +220 -0
- package/src/server/management/config-routes.ts +422 -0
- package/src/server/management/context.ts +31 -0
- package/src/server/management/logs-usage-routes.ts +707 -0
- package/src/server/management/model-routes.ts +525 -0
- package/src/server/management/oauth-account-routes.ts +563 -0
- package/src/server/management/provider-routes.ts +556 -0
- package/src/server/management/shared.ts +277 -0
- package/src/server/management/sidebar-routes.ts +90 -0
- package/src/server/management/system-restart.ts +179 -0
- package/src/server/management/system-routes.ts +117 -0
- package/src/server/management/usage-summary-cache.ts +86 -0
- package/src/server/management-api.ts +215 -0
- package/src/server/management-auth.ts +267 -0
- package/src/server/memory-watchdog.ts +156 -0
- package/src/server/port-reclaim.ts +307 -0
- package/src/server/ports.ts +116 -0
- package/src/server/proxy-liveness.ts +201 -0
- package/src/server/relay-eager.ts +313 -0
- package/src/server/relay.ts +1049 -0
- package/src/server/request-decompress.ts +132 -0
- package/src/server/request-log-conversation.ts +168 -0
- package/src/server/request-log.ts +1046 -0
- package/src/server/responses/collaboration.ts +354 -0
- package/src/server/responses/compact.ts +384 -0
- package/src/server/responses/core.ts +2758 -0
- package/src/server/responses/encrypted-payload.ts +308 -0
- package/src/server/responses/fetch-helpers.ts +157 -0
- package/src/server/responses/passthrough-error.ts +78 -0
- package/src/server/responses/terminal-guard.ts +230 -0
- package/src/server/responses/upstream-error.ts +48 -0
- package/src/server/responses-image-gen-repair.ts +132 -0
- package/src/server/responses-item-id-repair.ts +224 -0
- package/src/server/responses.ts +9 -0
- package/src/server/search.ts +136 -0
- package/src/server/sse-payload-rewrite.ts +175 -0
- package/src/server/startup-action-control.ts +308 -0
- package/src/server/startup-health-cache.ts +113 -0
- package/src/server/system-env.ts +413 -0
- package/src/server/windows-tcp-drop.ts +184 -0
- package/src/server/windows-tray-control.ts +41 -0
- package/src/server/ws-bridge.ts +471 -0
- package/src/service.ts +2554 -0
- package/src/stall-timeout.ts +20 -0
- package/src/storage/cleanup-job.ts +57 -0
- package/src/storage/cleanup.ts +3085 -0
- package/src/storage/policy-job.ts +457 -0
- package/src/storage/policy-scheduler.ts +40 -0
- package/src/storage/policy-worker.ts +59 -0
- package/src/storage/policy.ts +527 -0
- package/src/storage/restore-job.ts +299 -0
- package/src/storage/restore-worker.ts +58 -0
- package/src/storage/scanner.ts +238 -0
- package/src/storage/storage-mutation-coordinator.ts +139 -0
- package/src/storage/worker-lifecycle.ts +215 -0
- package/src/tray/assets/opencodex-tray-offline.ico +0 -0
- package/src/tray/assets/opencodex-tray-online.ico +0 -0
- package/src/tray/assets/opencodex-tray-warning.ico +0 -0
- package/src/tray/assets/opencodex-tray.png +0 -0
- package/src/tray/windows-tray.ps1 +290 -0
- package/src/tray/windows.ts +730 -0
- package/src/types.ts +1237 -0
- package/src/update/badge.ts +72 -0
- package/src/update/index.ts +407 -0
- package/src/update/job.ts +1520 -0
- package/src/update/notify.ts +257 -0
- package/src/update/npm-invocation.d.mts +23 -0
- package/src/update/npm-invocation.mjs +94 -0
- package/src/update/tray-update-plan.d.mts +18 -0
- package/src/update/tray-update-plan.mjs +38 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/debug.ts +97 -0
- package/src/usage/expected-prices.ts +164 -0
- package/src/usage/log.ts +658 -0
- package/src/usage/summary.ts +585 -0
- package/src/usage/totals.ts +14 -0
- package/src/vision/anthropic-describe.ts +185 -0
- package/src/vision/describe.ts +125 -0
- package/src/vision/index.ts +467 -0
- package/src/web-search/anthropic-executor.ts +189 -0
- package/src/web-search/executor.ts +105 -0
- package/src/web-search/format-result.ts +89 -0
- package/src/web-search/index.ts +196 -0
- package/src/web-search/loop.ts +664 -0
- package/src/web-search/parse.ts +220 -0
- package/src/web-search/progress-stream.ts +342 -0
- package/src/web-search/synthetic-tool.ts +47 -0
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
import { spawn, type ChildProcess, type SpawnOptions } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { isAbsolute, join, relative, resolve as resolvePath, sep } from "node:path";
|
|
4
|
+
import { dlopen, ptr, type Pointer } from "bun:ffi";
|
|
5
|
+
|
|
6
|
+
type ElevationSpawn = (
|
|
7
|
+
command: string,
|
|
8
|
+
args: ReadonlyArray<string>,
|
|
9
|
+
options: SpawnOptions,
|
|
10
|
+
) => ChildProcess;
|
|
11
|
+
|
|
12
|
+
let elevationSpawn: ElevationSpawn = spawn;
|
|
13
|
+
|
|
14
|
+
/** Test-only seam for the elevated PowerShell launcher. */
|
|
15
|
+
export function setWindowsElevationSpawnForTests(next: ElevationSpawn | null): void {
|
|
16
|
+
elevationSpawn = next ?? spawn;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type GetSystemDirectoryW = (buffer: Pointer, size: number) => number;
|
|
20
|
+
type TrustedSystemDirectoryResolver = () => string;
|
|
21
|
+
type IsUserAnAdmin = () => number;
|
|
22
|
+
type WindowsElevationProbe = () => boolean | null;
|
|
23
|
+
|
|
24
|
+
let getSystemDirectoryWFn: GetSystemDirectoryW | null | undefined;
|
|
25
|
+
let trustedSystemDirectoryResolverForTests: TrustedSystemDirectoryResolver | null = null;
|
|
26
|
+
let isUserAnAdminFn: (() => boolean) | null | undefined;
|
|
27
|
+
let windowsElevationProbeForTests: WindowsElevationProbe | null = null;
|
|
28
|
+
|
|
29
|
+
/** Test-only seam to replace GetSystemDirectoryW-backed resolution. */
|
|
30
|
+
export function setTrustedWindowsSystemDirectoryResolverForTests(
|
|
31
|
+
next: TrustedSystemDirectoryResolver | null,
|
|
32
|
+
): void {
|
|
33
|
+
trustedSystemDirectoryResolverForTests = next;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Test-only seam for the locale-independent current-token elevation probe. */
|
|
37
|
+
export function setWindowsElevationProbeForTests(next: WindowsElevationProbe | null): void {
|
|
38
|
+
windowsElevationProbeForTests = next;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function loadIsUserAnAdmin(): (() => boolean) | null {
|
|
42
|
+
if (isUserAnAdminFn !== undefined) return isUserAnAdminFn;
|
|
43
|
+
if (process.platform !== "win32") {
|
|
44
|
+
isUserAnAdminFn = null;
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const lib = dlopen("shell32.dll", {
|
|
49
|
+
IsUserAnAdmin: {
|
|
50
|
+
args: [],
|
|
51
|
+
// Win32 BOOL is a signed 32-bit integer, not C/C++ bool.
|
|
52
|
+
returns: "i32",
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
const isUserAnAdmin = lib.symbols.IsUserAnAdmin as IsUserAnAdmin;
|
|
56
|
+
isUserAnAdminFn = () => isUserAnAdmin() !== 0;
|
|
57
|
+
} catch {
|
|
58
|
+
isUserAnAdminFn = null;
|
|
59
|
+
}
|
|
60
|
+
return isUserAnAdminFn;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Probe the effective Windows token without parsing localized command output.
|
|
65
|
+
* `null` fails closed: callers must retain the original scheduler error rather
|
|
66
|
+
* than infer that an unknown token state needs elevation.
|
|
67
|
+
*/
|
|
68
|
+
export function isCurrentWindowsProcessElevated(): boolean | null {
|
|
69
|
+
if (windowsElevationProbeForTests) return windowsElevationProbeForTests();
|
|
70
|
+
const isUserAnAdmin = loadIsUserAnAdmin();
|
|
71
|
+
if (!isUserAnAdmin) return null;
|
|
72
|
+
try {
|
|
73
|
+
return isUserAnAdmin();
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function loadGetSystemDirectoryW(): GetSystemDirectoryW | null {
|
|
80
|
+
if (getSystemDirectoryWFn !== undefined) return getSystemDirectoryWFn;
|
|
81
|
+
if (process.platform !== "win32") {
|
|
82
|
+
getSystemDirectoryWFn = null;
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const lib = dlopen("kernel32.dll", {
|
|
87
|
+
GetSystemDirectoryW: {
|
|
88
|
+
args: ["ptr", "u32"],
|
|
89
|
+
returns: "u32",
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
getSystemDirectoryWFn = (buffer, size) => lib.symbols.GetSystemDirectoryW(buffer, size) as number;
|
|
93
|
+
} catch {
|
|
94
|
+
getSystemDirectoryWFn = null;
|
|
95
|
+
}
|
|
96
|
+
return getSystemDirectoryWFn;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function decodeWideCString(buf: Uint16Array, length: number): string {
|
|
100
|
+
return String.fromCharCode(...buf.subarray(0, length));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Resolve the real Windows system directory via GetSystemDirectoryW.
|
|
105
|
+
* Must never trust process.env.SystemRoot / WINDIR — those are caller-controlled and
|
|
106
|
+
* must not select binaries for UAC elevation.
|
|
107
|
+
*/
|
|
108
|
+
export function resolveTrustedWindowsSystemDirectory(): string {
|
|
109
|
+
if (trustedSystemDirectoryResolverForTests) {
|
|
110
|
+
return trustedSystemDirectoryResolverForTests();
|
|
111
|
+
}
|
|
112
|
+
if (process.platform !== "win32") {
|
|
113
|
+
throw new Error("Trusted Windows system directory resolution is only supported on Windows.");
|
|
114
|
+
}
|
|
115
|
+
const getSystemDirectoryW = loadGetSystemDirectoryW();
|
|
116
|
+
if (!getSystemDirectoryW) {
|
|
117
|
+
throw new Error("Failed to load GetSystemDirectoryW from kernel32.dll.");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let size = 260;
|
|
121
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
122
|
+
const buf = new Uint16Array(size);
|
|
123
|
+
const written = getSystemDirectoryW(ptr(buf.buffer), size);
|
|
124
|
+
if (written === 0) {
|
|
125
|
+
throw new Error("GetSystemDirectoryW failed while resolving the trusted system directory.");
|
|
126
|
+
}
|
|
127
|
+
// When the buffer is too small, the return value is the required size including NUL.
|
|
128
|
+
if (written >= size) {
|
|
129
|
+
size = written + 1;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const directory = decodeWideCString(buf, written).replace(/[/\\]+$/, "");
|
|
133
|
+
if (!directory || !existsSync(directory)) {
|
|
134
|
+
throw new Error("GetSystemDirectoryW returned an unusable system directory.");
|
|
135
|
+
}
|
|
136
|
+
return resolvePath(directory);
|
|
137
|
+
}
|
|
138
|
+
throw new Error("GetSystemDirectoryW required a system directory path larger than expected.");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* True when `candidatePath` is the trusted directory itself, or a contained child.
|
|
143
|
+
* Uses path.relative so containment works with host-native separators (Linux/macOS CI
|
|
144
|
+
* hosts fake win32 with `/tmp/...` paths; a literal `\\` prefix check rejects them).
|
|
145
|
+
*/
|
|
146
|
+
function isPathInsideTrustedDirectory(trustedDirectory: string, candidatePath: string): boolean {
|
|
147
|
+
const relativePath = relative(trustedDirectory, candidatePath);
|
|
148
|
+
return (
|
|
149
|
+
relativePath === "" ||
|
|
150
|
+
(!isAbsolute(relativePath) && relativePath !== ".." && !relativePath.startsWith(`..${sep}`))
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function assertTrustedSystemExecutable(candidate: string, label: string): string {
|
|
155
|
+
const systemDir = resolveTrustedWindowsSystemDirectory();
|
|
156
|
+
const resolved = resolvePath(candidate);
|
|
157
|
+
if (!isPathInsideTrustedDirectory(systemDir, resolved)) {
|
|
158
|
+
throw new Error(`${label} resolved outside the trusted Windows system directory.`);
|
|
159
|
+
}
|
|
160
|
+
if (!existsSync(resolved)) {
|
|
161
|
+
throw new Error(`Trusted ${label} was not found at ${resolved}.`);
|
|
162
|
+
}
|
|
163
|
+
return resolved;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Test-only access to the trusted-path containment check. */
|
|
167
|
+
export function assertTrustedSystemExecutableForTests(candidate: string, label: string): string {
|
|
168
|
+
return assertTrustedSystemExecutable(candidate, label);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
type ElevationExeOverrides = { powershell?: string; schtasks?: string };
|
|
172
|
+
let elevationExeOverridesForTests: ElevationExeOverrides | null = null;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Test-only absolute executable overrides for Linux CI (which fakes win32 but has
|
|
176
|
+
* no kernel32/System32). Production resolution never consults this seam.
|
|
177
|
+
*/
|
|
178
|
+
export function setTrustedWindowsElevationExecutablesForTests(
|
|
179
|
+
next: ElevationExeOverrides | null,
|
|
180
|
+
): void {
|
|
181
|
+
elevationExeOverridesForTests = next;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Absolute path to System32\\WindowsPowerShell\\v1.0\\powershell.exe from a trusted system directory. */
|
|
185
|
+
export function resolveTrustedWindowsPowerShellExe(): string {
|
|
186
|
+
if (elevationExeOverridesForTests?.powershell) {
|
|
187
|
+
return elevationExeOverridesForTests.powershell;
|
|
188
|
+
}
|
|
189
|
+
const candidate = join(
|
|
190
|
+
resolveTrustedWindowsSystemDirectory(),
|
|
191
|
+
"WindowsPowerShell",
|
|
192
|
+
"v1.0",
|
|
193
|
+
"powershell.exe",
|
|
194
|
+
);
|
|
195
|
+
return assertTrustedSystemExecutable(candidate, "PowerShell");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Absolute path to System32\\schtasks.exe from a trusted system directory. */
|
|
199
|
+
export function resolveTrustedWindowsSchtasksExe(): string {
|
|
200
|
+
if (elevationExeOverridesForTests?.schtasks) {
|
|
201
|
+
return elevationExeOverridesForTests.schtasks;
|
|
202
|
+
}
|
|
203
|
+
const candidate = join(resolveTrustedWindowsSystemDirectory(), "schtasks.exe");
|
|
204
|
+
return assertTrustedSystemExecutable(candidate, "schtasks.exe");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Stable machine-readable marker for a denied `schtasks /create`. Crosses the CLI→proxy boundary. */
|
|
208
|
+
export const WINDOWS_SCHTASKS_CREATE_ACCESS_DENIED_MARKER =
|
|
209
|
+
"OCX_ERROR_CODE=WINDOWS_SCHTASKS_CREATE_ACCESS_DENIED";
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Reserved elevated-scheduler protocol exit codes.
|
|
213
|
+
* These are OpenCodex-owned values returned by the elevated PowerShell transaction.
|
|
214
|
+
* They must never collide with UAC cancellation (1223).
|
|
215
|
+
*
|
|
216
|
+
* The elevated script never exits with raw schtasks codes — only these protocol values —
|
|
217
|
+
* so the parent can classify the transaction without a user-controlled result file.
|
|
218
|
+
*/
|
|
219
|
+
export const OCX_ELEVATED_SUCCESS = 0;
|
|
220
|
+
export const OCX_ELEVATED_CREATE_FAILED = 10;
|
|
221
|
+
export const OCX_ELEVATED_RUN_FAILED_ROLLED_BACK = 11;
|
|
222
|
+
export const OCX_ELEVATED_RUN_FAILED_ROLLBACK_FAILED = 12;
|
|
223
|
+
export const OCX_ELEVATED_PROTOCOL_FAILED = 13;
|
|
224
|
+
/** Windows ERROR_CANCELLED — reserved for UAC denial; never emitted by the elevated script. */
|
|
225
|
+
export const OCX_ELEVATED_UAC_CANCELLED = 1223;
|
|
226
|
+
|
|
227
|
+
export const OCX_ELEVATED_PROTOCOL_CODES = [
|
|
228
|
+
OCX_ELEVATED_SUCCESS,
|
|
229
|
+
OCX_ELEVATED_CREATE_FAILED,
|
|
230
|
+
OCX_ELEVATED_RUN_FAILED_ROLLED_BACK,
|
|
231
|
+
OCX_ELEVATED_RUN_FAILED_ROLLBACK_FAILED,
|
|
232
|
+
OCX_ELEVATED_PROTOCOL_FAILED,
|
|
233
|
+
] as const;
|
|
234
|
+
|
|
235
|
+
export type WindowsSchtasksOperation = "create" | "run" | "query" | "delete" | "end" | "other";
|
|
236
|
+
export type WindowsSchtasksFailureReason = "access-denied" | "other";
|
|
237
|
+
|
|
238
|
+
export type WindowsElevationFailureReason =
|
|
239
|
+
| "cancelled"
|
|
240
|
+
| "timeout"
|
|
241
|
+
| "launch-failed"
|
|
242
|
+
| "child-failed"
|
|
243
|
+
| "terminated";
|
|
244
|
+
|
|
245
|
+
export type ElevatedSchedulerOutcome =
|
|
246
|
+
| "success"
|
|
247
|
+
| "create-failed"
|
|
248
|
+
| "run-failed-rolled-back"
|
|
249
|
+
| "run-failed-rollback-failed"
|
|
250
|
+
| "protocol-failed";
|
|
251
|
+
|
|
252
|
+
const ELEVATION_OUTPUT_LIMIT = 256 * 1024;
|
|
253
|
+
|
|
254
|
+
function windowsAccessDeniedText(text: string): boolean {
|
|
255
|
+
const normalized = text.toLowerCase();
|
|
256
|
+
return normalized.includes("access denied")
|
|
257
|
+
|| normalized.includes("access is denied")
|
|
258
|
+
|| normalized.includes("denied access")
|
|
259
|
+
|| normalized.includes("zugriff verweigert");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function windowsUacCancelledText(text: string): boolean {
|
|
263
|
+
const normalized = text.toLowerCase();
|
|
264
|
+
return normalized.includes("operation was canceled by the user")
|
|
265
|
+
|| normalized.includes("operation was cancelled by the user")
|
|
266
|
+
|| normalized.includes("the operation was canceled")
|
|
267
|
+
|| normalized.includes("the operation was cancelled")
|
|
268
|
+
|| normalized.includes("vom benutzer abgebrochen")
|
|
269
|
+
|| normalized.includes("durch den benutzer abgebrochen");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** True when a captured stderr/stdout/message indicates Windows access denial. */
|
|
273
|
+
export function isWindowsAccessDenied(detail: string): boolean {
|
|
274
|
+
return windowsAccessDeniedText(detail);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** True when a thrown exec error looks like Windows access denial. */
|
|
278
|
+
export function isWindowsAccessDeniedError(error: unknown): boolean {
|
|
279
|
+
if (error instanceof WindowsSchtasksError) return error.reason === "access-denied";
|
|
280
|
+
if (!(error instanceof Error)) return isWindowsAccessDenied(String(error));
|
|
281
|
+
const exec = error as NodeJS.ErrnoException & { stderr?: string | Buffer; stdout?: string | Buffer };
|
|
282
|
+
const parts = [error.message, exec.stderr, exec.stdout]
|
|
283
|
+
.map(part => (typeof part === "string" ? part : part ? String(part) : ""));
|
|
284
|
+
return parts.some(part => windowsAccessDeniedText(part));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** True only for a structured Task Scheduler `/create` access-denied failure. */
|
|
288
|
+
export function isWindowsSchtasksCreateAccessDenied(detail: string): boolean {
|
|
289
|
+
return detail.includes(WINDOWS_SCHTASKS_CREATE_ACCESS_DENIED_MARKER);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function schtasksOperationFromArgs(args: string[]): WindowsSchtasksOperation {
|
|
293
|
+
const flag = (args[0] ?? "").toLowerCase();
|
|
294
|
+
if (flag === "/create") return "create";
|
|
295
|
+
if (flag === "/run") return "run";
|
|
296
|
+
if (flag === "/query") return "query";
|
|
297
|
+
if (flag === "/delete") return "delete";
|
|
298
|
+
if (flag === "/end") return "end";
|
|
299
|
+
return "other";
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function schedulerExitStatus(error: unknown): number | null {
|
|
303
|
+
if (!error || typeof error !== "object") return null;
|
|
304
|
+
const status = (error as { status?: unknown }).status;
|
|
305
|
+
return typeof status === "number" && Number.isInteger(status) ? status : null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* The owned scheduler install has a fixed shape. Restrict the locale-independent
|
|
310
|
+
* fallback to that shape so unrelated schtasks failures cannot request elevation.
|
|
311
|
+
*/
|
|
312
|
+
function isOwnedSchedulerCreate(args: string[]): boolean {
|
|
313
|
+
const normalized = args.map(arg => arg.toLowerCase());
|
|
314
|
+
const taskIndex = normalized.indexOf("/tn");
|
|
315
|
+
const xmlIndex = normalized.indexOf("/xml");
|
|
316
|
+
return normalized[0] === "/create"
|
|
317
|
+
&& normalized[taskIndex + 1] === "opencodex-proxy"
|
|
318
|
+
&& taskIndex > 0
|
|
319
|
+
&& xmlIndex > 0
|
|
320
|
+
&& Boolean(args[xmlIndex + 1])
|
|
321
|
+
&& normalized.includes("/f");
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function isWindowsSchtasksAccessDeniedError(error: unknown, args: string[]): boolean {
|
|
325
|
+
if (!isOwnedSchedulerCreate(args)) return false;
|
|
326
|
+
return isWindowsAccessDeniedError(error)
|
|
327
|
+
|| (schedulerExitStatus(error) === 1 && isCurrentWindowsProcessElevated() === false);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** Structured Task Scheduler failure that survives formatting and process boundaries. */
|
|
331
|
+
export class WindowsSchtasksError extends Error {
|
|
332
|
+
readonly code = "WINDOWS_SCHTASKS_ERROR" as const;
|
|
333
|
+
|
|
334
|
+
constructor(
|
|
335
|
+
readonly operation: WindowsSchtasksOperation,
|
|
336
|
+
readonly reason: WindowsSchtasksFailureReason,
|
|
337
|
+
message: string,
|
|
338
|
+
) {
|
|
339
|
+
super(message);
|
|
340
|
+
this.name = "WindowsSchtasksError";
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
get machineMarker(): string | null {
|
|
344
|
+
return this.operation === "create" && this.reason === "access-denied"
|
|
345
|
+
? WINDOWS_SCHTASKS_CREATE_ACCESS_DENIED_MARKER
|
|
346
|
+
: null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export class WindowsElevationError extends Error {
|
|
351
|
+
readonly code = "WINDOWS_ELEVATION_ERROR" as const;
|
|
352
|
+
|
|
353
|
+
constructor(
|
|
354
|
+
readonly reason: WindowsElevationFailureReason,
|
|
355
|
+
message: string,
|
|
356
|
+
) {
|
|
357
|
+
super(message);
|
|
358
|
+
this.name = "WindowsElevationError";
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Replace raw schtasks access-denied output with dashboard-friendly guidance. */
|
|
363
|
+
export function formatWindowsSchtasksError(error: unknown, args: string[]): string {
|
|
364
|
+
const operation = schtasksOperationFromArgs(args);
|
|
365
|
+
const ownedCreateAccessDenied = isWindowsSchtasksAccessDeniedError(error, args);
|
|
366
|
+
const accessDenied = ownedCreateAccessDenied || isWindowsAccessDeniedError(error);
|
|
367
|
+
if (!accessDenied) {
|
|
368
|
+
return error instanceof Error ? error.message : String(error);
|
|
369
|
+
}
|
|
370
|
+
const argsText = args.map(arg => (/\s/.test(arg) ? `"${arg}"` : arg)).join(" ");
|
|
371
|
+
const guidance = [
|
|
372
|
+
"Windows access denied while running Task Scheduler.",
|
|
373
|
+
`Command: schtasks ${argsText}`,
|
|
374
|
+
"Approve the Windows UAC prompt to install the background service, or run `ocx service install` from an elevated PowerShell window.",
|
|
375
|
+
].join(" ");
|
|
376
|
+
if (operation === "create" && ownedCreateAccessDenied) {
|
|
377
|
+
return `${guidance}\n${WINDOWS_SCHTASKS_CREATE_ACCESS_DENIED_MARKER}`;
|
|
378
|
+
}
|
|
379
|
+
return guidance;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export function toWindowsSchtasksError(error: unknown, args: string[]): WindowsSchtasksError {
|
|
383
|
+
if (error instanceof WindowsSchtasksError) return error;
|
|
384
|
+
const operation = schtasksOperationFromArgs(args);
|
|
385
|
+
const reason: WindowsSchtasksFailureReason = isWindowsSchtasksAccessDeniedError(error, args)
|
|
386
|
+
? "access-denied"
|
|
387
|
+
: "other";
|
|
388
|
+
return new WindowsSchtasksError(operation, reason, formatWindowsSchtasksError(error, args));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Quote one argument for Win32 CommandLineToArgvW / Start-Process -ArgumentList.
|
|
393
|
+
* Handles empty args, spaces, embedded quotes, and trailing backslashes.
|
|
394
|
+
*/
|
|
395
|
+
export function windowsCmdQuote(value: string): string {
|
|
396
|
+
if (value.length === 0) return '""';
|
|
397
|
+
if (!/[\s"]/.test(value)) return value;
|
|
398
|
+
let result = '"';
|
|
399
|
+
let numBackslashes = 0;
|
|
400
|
+
for (const ch of value) {
|
|
401
|
+
if (ch === "\\") {
|
|
402
|
+
numBackslashes += 1;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
if (ch === '"') {
|
|
406
|
+
result += "\\".repeat(numBackslashes * 2 + 1) + '"';
|
|
407
|
+
numBackslashes = 0;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
result += "\\".repeat(numBackslashes) + ch;
|
|
411
|
+
numBackslashes = 0;
|
|
412
|
+
}
|
|
413
|
+
result += "\\".repeat(numBackslashes * 2) + '"';
|
|
414
|
+
return result;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/** Build one Win32 argument-list string for Start-Process -ArgumentList. */
|
|
418
|
+
export function buildWindowsElevatedArgumentList(args: string[]): string {
|
|
419
|
+
return args.map(windowsCmdQuote).join(" ");
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** Classify an elevated-process exit code into a stable scheduler outcome. */
|
|
423
|
+
export function classifyElevatedSchedulerExitCode(exitCode: number): ElevatedSchedulerOutcome {
|
|
424
|
+
if (exitCode === OCX_ELEVATED_SUCCESS) return "success";
|
|
425
|
+
if (exitCode === OCX_ELEVATED_CREATE_FAILED) return "create-failed";
|
|
426
|
+
if (exitCode === OCX_ELEVATED_RUN_FAILED_ROLLED_BACK) return "run-failed-rolled-back";
|
|
427
|
+
if (exitCode === OCX_ELEVATED_RUN_FAILED_ROLLBACK_FAILED) return "run-failed-rollback-failed";
|
|
428
|
+
// Malformed / unexpected / missing-ExitCode-normalized codes fail closed.
|
|
429
|
+
return "protocol-failed";
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function windowsPowerShell(): string {
|
|
433
|
+
return resolveTrustedWindowsPowerShellExe();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function psSingleQuote(value: string): string {
|
|
437
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function appendBounded(current: string, chunk: string, limit = ELEVATION_OUTPUT_LIMIT): string {
|
|
441
|
+
if (current.length >= limit) return current;
|
|
442
|
+
const remaining = limit - current.length;
|
|
443
|
+
return current + chunk.slice(0, remaining);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export interface WindowsElevationResult {
|
|
447
|
+
exitCode: number;
|
|
448
|
+
stdout: string;
|
|
449
|
+
stderr: string;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export interface WindowsElevationExecution {
|
|
453
|
+
/** Settles only on launcher close/error — never killed by a request-level timeout. */
|
|
454
|
+
completion: Promise<WindowsElevationResult>;
|
|
455
|
+
launcherPid: number | null;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** Request-facing wait for elevation; process observation continues separately. */
|
|
459
|
+
export const ELEVATION_REQUEST_TIMEOUT_MS = 120_000;
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Wait for `promise` up to `timeoutMs` without cancelling it.
|
|
463
|
+
* On timeout the original promise remains active for late settlement.
|
|
464
|
+
*/
|
|
465
|
+
export function raceWithTimeout<T>(
|
|
466
|
+
promise: Promise<T>,
|
|
467
|
+
timeoutMs: number,
|
|
468
|
+
): Promise<{ status: "completed"; value: T } | { status: "timed-out" }> {
|
|
469
|
+
return new Promise((resolve, reject) => {
|
|
470
|
+
let settled = false;
|
|
471
|
+
const timer = setTimeout(() => {
|
|
472
|
+
if (settled) return;
|
|
473
|
+
settled = true;
|
|
474
|
+
resolve({ status: "timed-out" });
|
|
475
|
+
}, timeoutMs);
|
|
476
|
+
promise.then(
|
|
477
|
+
value => {
|
|
478
|
+
if (settled) return;
|
|
479
|
+
settled = true;
|
|
480
|
+
clearTimeout(timer);
|
|
481
|
+
resolve({ status: "completed", value });
|
|
482
|
+
},
|
|
483
|
+
error => {
|
|
484
|
+
if (settled) return;
|
|
485
|
+
settled = true;
|
|
486
|
+
clearTimeout(timer);
|
|
487
|
+
reject(error);
|
|
488
|
+
},
|
|
489
|
+
);
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Spawn non-elevated PowerShell to run a -Command script.
|
|
495
|
+
* Does not kill the launcher on any timeout — callers race a separate request timeout.
|
|
496
|
+
*/
|
|
497
|
+
export function startPowerShellCommand(commandScript: string): WindowsElevationExecution {
|
|
498
|
+
if (process.platform !== "win32") {
|
|
499
|
+
return {
|
|
500
|
+
launcherPid: null,
|
|
501
|
+
completion: Promise.reject(new WindowsElevationError(
|
|
502
|
+
"launch-failed",
|
|
503
|
+
"Windows elevation is only supported on Windows.",
|
|
504
|
+
)),
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
let child: ChildProcess;
|
|
509
|
+
try {
|
|
510
|
+
child = elevationSpawn(
|
|
511
|
+
windowsPowerShell(),
|
|
512
|
+
["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", commandScript],
|
|
513
|
+
{
|
|
514
|
+
windowsHide: true,
|
|
515
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
516
|
+
},
|
|
517
|
+
);
|
|
518
|
+
} catch (error) {
|
|
519
|
+
return {
|
|
520
|
+
launcherPid: null,
|
|
521
|
+
completion: Promise.reject(new WindowsElevationError(
|
|
522
|
+
"launch-failed",
|
|
523
|
+
error instanceof Error ? error.message : String(error),
|
|
524
|
+
)),
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const completion = new Promise<WindowsElevationResult>((resolve, reject) => {
|
|
529
|
+
let settled = false;
|
|
530
|
+
let stdout = "";
|
|
531
|
+
let stderr = "";
|
|
532
|
+
|
|
533
|
+
const settle = (fn: () => void) => {
|
|
534
|
+
if (settled) return;
|
|
535
|
+
settled = true;
|
|
536
|
+
fn();
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
child.stdout?.setEncoding?.("utf8");
|
|
540
|
+
child.stderr?.setEncoding?.("utf8");
|
|
541
|
+
child.stdout?.on("data", (chunk: string | Buffer) => {
|
|
542
|
+
stdout = appendBounded(stdout, typeof chunk === "string" ? chunk : chunk.toString("utf8"));
|
|
543
|
+
});
|
|
544
|
+
child.stderr?.on("data", (chunk: string | Buffer) => {
|
|
545
|
+
stderr = appendBounded(stderr, typeof chunk === "string" ? chunk : chunk.toString("utf8"));
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
child.once("error", (error: NodeJS.ErrnoException) => {
|
|
549
|
+
settle(() => reject(new WindowsElevationError(
|
|
550
|
+
"launch-failed",
|
|
551
|
+
error.code === "ENOENT"
|
|
552
|
+
? "Windows PowerShell was not found for elevation."
|
|
553
|
+
: (error.message || "Windows elevation failed to launch."),
|
|
554
|
+
)));
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
child.once("close", (code, signal) => {
|
|
558
|
+
const detail = [stderr.trim(), stdout.trim()].filter(Boolean).join("\n");
|
|
559
|
+
if (typeof code === "number") {
|
|
560
|
+
// Only treat UAC-cancellation text as cancelled when the process did not succeed.
|
|
561
|
+
if (
|
|
562
|
+
code === OCX_ELEVATED_UAC_CANCELLED
|
|
563
|
+
|| (code !== 0 && windowsUacCancelledText(detail))
|
|
564
|
+
) {
|
|
565
|
+
settle(() => reject(new WindowsElevationError(
|
|
566
|
+
"cancelled",
|
|
567
|
+
"Windows administrator approval was required, but the UAC prompt was cancelled or denied.",
|
|
568
|
+
)));
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
settle(() => resolve({ exitCode: code, stdout, stderr }));
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
// Launcher terminated without an exit code — elevated child may still exist.
|
|
575
|
+
settle(() => reject(new WindowsElevationError(
|
|
576
|
+
"terminated",
|
|
577
|
+
`Windows elevation terminated by ${signal ?? "unknown signal"}.`,
|
|
578
|
+
)));
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
return { completion, launcherPid: child.pid ?? null };
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Launch a file with UAC elevation and wait for it to exit.
|
|
587
|
+
* Throws WindowsElevationError for cancellation, launch failure, or signal termination.
|
|
588
|
+
* Returns the elevated process exit code for completed launches (including non-zero).
|
|
589
|
+
*/
|
|
590
|
+
export function runWindowsElevated(file: string, args: string[]): Promise<number> {
|
|
591
|
+
const argumentList = buildWindowsElevatedArgumentList(args);
|
|
592
|
+
// Touch .Handle so Windows PowerShell 5.1 keeps a process handle; ExitCode can
|
|
593
|
+
// otherwise stay $null after -Wait and `exit $null` becomes exit 0 (false success).
|
|
594
|
+
const script = [
|
|
595
|
+
`$p = Start-Process -FilePath ${psSingleQuote(file)}`,
|
|
596
|
+
argumentList.length > 0 ? ` -ArgumentList ${psSingleQuote(argumentList)}` : "",
|
|
597
|
+
" -Verb RunAs -WindowStyle Hidden -PassThru -Wait;",
|
|
598
|
+
`if ($null -eq $p) { exit ${OCX_ELEVATED_UAC_CANCELLED} }`,
|
|
599
|
+
"$null = $p.Handle;",
|
|
600
|
+
// Missing ExitCode is a protocol failure for single-file elevation (not success).
|
|
601
|
+
`if ($null -eq $p.ExitCode) { exit ${OCX_ELEVATED_PROTOCOL_FAILED} }`,
|
|
602
|
+
"exit $p.ExitCode",
|
|
603
|
+
].join("");
|
|
604
|
+
|
|
605
|
+
return startPowerShellCommand(script).completion.then(result => result.exitCode);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Build the elevated (post-UAC) script: create → run → optional delete rollback.
|
|
610
|
+
* Returns only OpenCodex protocol exit codes (never raw schtasks codes, never 1223).
|
|
611
|
+
* Does not write through any user-controlled pathname.
|
|
612
|
+
*/
|
|
613
|
+
export function buildElevatedSchtasksCreateAndRunScript(
|
|
614
|
+
schtasksPath: string,
|
|
615
|
+
createArgs: string[],
|
|
616
|
+
runArgs: string[],
|
|
617
|
+
deleteArgs: string[],
|
|
618
|
+
): string {
|
|
619
|
+
const createList = buildWindowsElevatedArgumentList(createArgs);
|
|
620
|
+
const runList = buildWindowsElevatedArgumentList(runArgs);
|
|
621
|
+
const deleteList = buildWindowsElevatedArgumentList(deleteArgs);
|
|
622
|
+
return [
|
|
623
|
+
`$schtasks = ${psSingleQuote(schtasksPath)}`,
|
|
624
|
+
"function Invoke-OcxSchtasks([string]$ArgList) {",
|
|
625
|
+
" $p = Start-Process -FilePath $schtasks -ArgumentList $ArgList -Wait -PassThru -WindowStyle Hidden",
|
|
626
|
+
" if ($null -eq $p) { return 1 }",
|
|
627
|
+
" $null = $p.Handle",
|
|
628
|
+
" if ($null -eq $p.ExitCode) { return 1 }",
|
|
629
|
+
" return [int]$p.ExitCode",
|
|
630
|
+
"}",
|
|
631
|
+
`$createCode = Invoke-OcxSchtasks ${psSingleQuote(createList)}`,
|
|
632
|
+
`if ($createCode -ne 0) { exit ${OCX_ELEVATED_CREATE_FAILED} }`,
|
|
633
|
+
`$runCode = Invoke-OcxSchtasks ${psSingleQuote(runList)}`,
|
|
634
|
+
`if ($runCode -eq 0) { exit ${OCX_ELEVATED_SUCCESS} }`,
|
|
635
|
+
`$deleteCode = Invoke-OcxSchtasks ${psSingleQuote(deleteList)}`,
|
|
636
|
+
`if ($deleteCode -eq 0) { exit ${OCX_ELEVATED_RUN_FAILED_ROLLED_BACK} }`,
|
|
637
|
+
`exit ${OCX_ELEVATED_RUN_FAILED_ROLLBACK_FAILED}`,
|
|
638
|
+
].join("; ");
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
export interface ElevatedSchtasksCreateAndRunResult {
|
|
642
|
+
outcome: ElevatedSchedulerOutcome;
|
|
643
|
+
exitCode: number;
|
|
644
|
+
stdout: string;
|
|
645
|
+
stderr: string;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
export interface ElevatedSchtasksCreateAndRunExecution {
|
|
649
|
+
completion: Promise<ElevatedSchtasksCreateAndRunResult>;
|
|
650
|
+
launcherPid: number | null;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Start create/run/rollback inside one elevated PowerShell process (one UAC prompt).
|
|
655
|
+
* The completion promise outlives any request-level timeout — do not kill the launcher.
|
|
656
|
+
*/
|
|
657
|
+
export function startElevatedSchtasksCreateAndRun(
|
|
658
|
+
schtasksPath: string,
|
|
659
|
+
createArgs: string[],
|
|
660
|
+
runArgs: string[],
|
|
661
|
+
deleteArgs: string[],
|
|
662
|
+
): ElevatedSchtasksCreateAndRunExecution {
|
|
663
|
+
const inner = buildElevatedSchtasksCreateAndRunScript(schtasksPath, createArgs, runArgs, deleteArgs);
|
|
664
|
+
const launcher = [
|
|
665
|
+
`$p = Start-Process -FilePath ${psSingleQuote(windowsPowerShell())}`,
|
|
666
|
+
` -ArgumentList ${psSingleQuote(buildWindowsElevatedArgumentList([
|
|
667
|
+
"-NoProfile",
|
|
668
|
+
"-NonInteractive",
|
|
669
|
+
"-ExecutionPolicy",
|
|
670
|
+
"Bypass",
|
|
671
|
+
"-Command",
|
|
672
|
+
inner,
|
|
673
|
+
]))}`,
|
|
674
|
+
" -Verb RunAs -WindowStyle Hidden -PassThru -Wait;",
|
|
675
|
+
`if ($null -eq $p) { exit ${OCX_ELEVATED_UAC_CANCELLED} }`,
|
|
676
|
+
"$null = $p.Handle;",
|
|
677
|
+
`if ($null -eq $p.ExitCode) { exit ${OCX_ELEVATED_PROTOCOL_FAILED} }`,
|
|
678
|
+
"exit $p.ExitCode",
|
|
679
|
+
].join("");
|
|
680
|
+
|
|
681
|
+
const started = startPowerShellCommand(launcher);
|
|
682
|
+
return {
|
|
683
|
+
launcherPid: started.launcherPid,
|
|
684
|
+
completion: started.completion.then(result => ({
|
|
685
|
+
outcome: classifyElevatedSchedulerExitCode(result.exitCode),
|
|
686
|
+
exitCode: result.exitCode,
|
|
687
|
+
stdout: result.stdout,
|
|
688
|
+
stderr: result.stderr,
|
|
689
|
+
})),
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Create, run, and (on /run failure) roll back the scheduler task inside one elevated
|
|
695
|
+
* PowerShell process — one UAC prompt, no user-controlled result file.
|
|
696
|
+
* Waits for the full elevated transaction (no request-timeout kill).
|
|
697
|
+
*/
|
|
698
|
+
export function runElevatedSchtasksCreateAndRun(
|
|
699
|
+
schtasksPath: string,
|
|
700
|
+
createArgs: string[],
|
|
701
|
+
runArgs: string[],
|
|
702
|
+
deleteArgs: string[],
|
|
703
|
+
): Promise<ElevatedSchtasksCreateAndRunResult> {
|
|
704
|
+
return startElevatedSchtasksCreateAndRun(schtasksPath, createArgs, runArgs, deleteArgs).completion;
|
|
705
|
+
}
|