@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,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-flight controller for trash restore runs.
|
|
3
|
+
*
|
|
4
|
+
* Heavy work (file moves, SQLite reconcile) runs in a Bun Worker so the proxy
|
|
5
|
+
* event loop stays responsive while the management API awaits the outcome.
|
|
6
|
+
*
|
|
7
|
+
* Restore shares the CODEX_HOME storage-mutation coordinator with manual cleanup
|
|
8
|
+
* and (Phase 3) policy-driven cleanup. Concurrent callers receive
|
|
9
|
+
* `storage_mutation_busy` (409) instead of queueing.
|
|
10
|
+
*/
|
|
11
|
+
import { resolveCodexHomeDir } from "../codex/home";
|
|
12
|
+
import { restoreTrashEntry, type RestoreResult, type RestoreTestHooks } from "./cleanup";
|
|
13
|
+
import {
|
|
14
|
+
resetStorageMutationCoordinatorForTests,
|
|
15
|
+
setStorageMutationCoordinatorTestHooks,
|
|
16
|
+
withStorageMutationSlot,
|
|
17
|
+
type StorageMutationCoordinatorTestHooks,
|
|
18
|
+
} from "./storage-mutation-coordinator";
|
|
19
|
+
import {
|
|
20
|
+
cancelQueuedStorageWorkerSpawns,
|
|
21
|
+
drainStorageWorkers,
|
|
22
|
+
StorageWorkerAdmissionBusyError,
|
|
23
|
+
terminateStorageWorker,
|
|
24
|
+
tryReserveStorageWorker,
|
|
25
|
+
withStorageWorkerSpawnGate,
|
|
26
|
+
} from "./worker-lifecycle";
|
|
27
|
+
|
|
28
|
+
export interface RestoreJobTestHooks extends StorageMutationCoordinatorTestHooks {
|
|
29
|
+
/**
|
|
30
|
+
* When true, run on the main thread via dynamic import + optional sleep.
|
|
31
|
+
* Responsiveness tests must leave this unset so work stays in a Worker.
|
|
32
|
+
*/
|
|
33
|
+
runInProcess?: boolean;
|
|
34
|
+
/** Expose GET /api/storage/trash/restore/test-stream for responsiveness tests. */
|
|
35
|
+
enableTestStream?: boolean;
|
|
36
|
+
/** Forwarded to restoreTrashEntry _test hooks. */
|
|
37
|
+
restoreTest?: RestoreTestHooks;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const WORKER_TIMEOUT_MS = 10 * 60 * 1000;
|
|
41
|
+
|
|
42
|
+
const WORKER_REJECTION_CODES: Record<string, RestoreResult["error"]> = {
|
|
43
|
+
restore_worker_timeout: "restore_worker_timeout",
|
|
44
|
+
aborted: "restore_worker_aborted",
|
|
45
|
+
worker_failed: "restore_worker_failed",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** Map a rejected worker promise to a precise restore outcome (exported for tests). */
|
|
49
|
+
export function restoreResultFromWorkerRejection(err: unknown, trashId: string): RestoreResult {
|
|
50
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
51
|
+
const error = WORKER_REJECTION_CODES[raw] ?? "restore_worker_failed";
|
|
52
|
+
const detail =
|
|
53
|
+
error === "restore_worker_failed" && raw !== "worker_failed" && raw.trim().length > 0
|
|
54
|
+
? raw
|
|
55
|
+
: undefined;
|
|
56
|
+
console.error(
|
|
57
|
+
`[storage] trash restore worker failed (${error}) trashId=${trashId}${detail ? `: ${detail}` : ""}`,
|
|
58
|
+
);
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
count: 0,
|
|
62
|
+
bytes: 0,
|
|
63
|
+
restoredPaths: [],
|
|
64
|
+
error,
|
|
65
|
+
...(detail ? { message: detail } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let activeWorker: Worker | null = null;
|
|
70
|
+
let testHooks: RestoreJobTestHooks | null = null;
|
|
71
|
+
let cancelActiveRun: (() => void) | null = null;
|
|
72
|
+
|
|
73
|
+
export function setRestoreTrashJobTestHooks(hooks: RestoreJobTestHooks | null): void {
|
|
74
|
+
testHooks = hooks;
|
|
75
|
+
setStorageMutationCoordinatorTestHooks(hooks);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Fire-and-forget reset. Prefer {@link resetRestoreTrashJobForTestsAsync} from
|
|
80
|
+
* test beforeEach/afterEach under `bun test --isolate` on Windows.
|
|
81
|
+
*/
|
|
82
|
+
export function resetRestoreTrashJobForTests(): void {
|
|
83
|
+
cancelQueuedStorageWorkerSpawns();
|
|
84
|
+
if (activeWorker) {
|
|
85
|
+
void terminateStorageWorker(activeWorker);
|
|
86
|
+
activeWorker = null;
|
|
87
|
+
}
|
|
88
|
+
cancelActiveRun?.();
|
|
89
|
+
cancelActiveRun = null;
|
|
90
|
+
testHooks = null;
|
|
91
|
+
resetStorageMutationCoordinatorForTests();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Await-able reset for test teardown; see policy-job's equivalent for why.
|
|
96
|
+
* Awaits the active worker directly so drain cannot race a fire-and-forget
|
|
97
|
+
* deregister from the sync reset.
|
|
98
|
+
*/
|
|
99
|
+
export async function resetRestoreTrashJobForTestsAsync(): Promise<void> {
|
|
100
|
+
cancelQueuedStorageWorkerSpawns();
|
|
101
|
+
const worker = activeWorker;
|
|
102
|
+
activeWorker = null;
|
|
103
|
+
cancelActiveRun?.();
|
|
104
|
+
cancelActiveRun = null;
|
|
105
|
+
testHooks = null;
|
|
106
|
+
// Join the worker before clearing the mutation coordinator so a concurrent
|
|
107
|
+
// run cannot acquire CODEX_HOME while the aborted thread is still mutating.
|
|
108
|
+
try {
|
|
109
|
+
if (worker) await terminateStorageWorker(worker);
|
|
110
|
+
await drainStorageWorkers();
|
|
111
|
+
} finally {
|
|
112
|
+
resetStorageMutationCoordinatorForTests();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Terminate an in-flight worker during process shutdown. */
|
|
117
|
+
export function abortRestoreTrashJob(): void {
|
|
118
|
+
cancelQueuedStorageWorkerSpawns();
|
|
119
|
+
if (activeWorker) {
|
|
120
|
+
void terminateStorageWorker(activeWorker);
|
|
121
|
+
activeWorker = null;
|
|
122
|
+
}
|
|
123
|
+
cancelActiveRun?.();
|
|
124
|
+
cancelActiveRun = null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Await-able shutdown sibling; joins the restore worker thread before returning. */
|
|
128
|
+
export async function abortRestoreTrashJobAsync(): Promise<void> {
|
|
129
|
+
cancelQueuedStorageWorkerSpawns();
|
|
130
|
+
const worker = activeWorker;
|
|
131
|
+
activeWorker = null;
|
|
132
|
+
cancelActiveRun?.();
|
|
133
|
+
cancelActiveRun = null;
|
|
134
|
+
if (worker) await terminateStorageWorker(worker);
|
|
135
|
+
await drainStorageWorkers();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Test-only SSE/text stream served from the proxy while a worker is blocked. */
|
|
139
|
+
export function getRestoreTrashTestStreamResponse(): Response | null {
|
|
140
|
+
if (!testHooks?.enableTestStream) return null;
|
|
141
|
+
const encoder = new TextEncoder();
|
|
142
|
+
return new Response(
|
|
143
|
+
new ReadableStream({
|
|
144
|
+
async start(controller) {
|
|
145
|
+
for (let i = 0; i < 8; i++) {
|
|
146
|
+
controller.enqueue(encoder.encode(`chunk-${i}\n`));
|
|
147
|
+
await Bun.sleep(50);
|
|
148
|
+
}
|
|
149
|
+
controller.close();
|
|
150
|
+
},
|
|
151
|
+
}),
|
|
152
|
+
{ headers: { "content-type": "text/plain; charset=utf-8" } },
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function runInWorker(opts: {
|
|
157
|
+
trashId: string;
|
|
158
|
+
codexHome?: string;
|
|
159
|
+
busyTimeoutMs?: number;
|
|
160
|
+
blockMs?: number;
|
|
161
|
+
restoreTest?: RestoreTestHooks;
|
|
162
|
+
}): Promise<RestoreResult> {
|
|
163
|
+
const reservation = tryReserveStorageWorker();
|
|
164
|
+
if (!reservation) return Promise.reject(new StorageWorkerAdmissionBusyError());
|
|
165
|
+
return withStorageWorkerSpawnGate(() => new Promise<RestoreResult>((resolve, reject) => {
|
|
166
|
+
const requestId = crypto.randomUUID();
|
|
167
|
+
let settled = false;
|
|
168
|
+
let worker: Worker;
|
|
169
|
+
try {
|
|
170
|
+
worker = new Worker(new URL("./restore-worker.ts", import.meta.url).href);
|
|
171
|
+
reservation.bind(worker);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
reservation.release();
|
|
174
|
+
reject(error);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
activeWorker = worker;
|
|
178
|
+
|
|
179
|
+
const finish = (fn: () => void) => {
|
|
180
|
+
if (settled) return;
|
|
181
|
+
settled = true;
|
|
182
|
+
cancelActiveRun = null;
|
|
183
|
+
clearTimeout(timer);
|
|
184
|
+
if (activeWorker === worker) activeWorker = null;
|
|
185
|
+
// Join before settle so mutation coordination inside the restore worker
|
|
186
|
+
// cannot overlap a follow-on run after a watchdog timeout.
|
|
187
|
+
void terminateStorageWorker(worker).then(fn, fn);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const timer = setTimeout(() => {
|
|
191
|
+
finish(() => reject(new Error("restore_worker_timeout")));
|
|
192
|
+
}, WORKER_TIMEOUT_MS);
|
|
193
|
+
|
|
194
|
+
cancelActiveRun = () => {
|
|
195
|
+
finish(() => reject(new Error("aborted")));
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
worker.onmessage = (event: MessageEvent<unknown>) => {
|
|
199
|
+
const data = event.data;
|
|
200
|
+
if (!data || typeof data !== "object") return;
|
|
201
|
+
const msg = data as Record<string, unknown>;
|
|
202
|
+
if (msg.requestId !== requestId) return;
|
|
203
|
+
if (msg.type === "done" && msg.result && typeof msg.result === "object") {
|
|
204
|
+
finish(() => resolve(msg.result as RestoreResult));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (msg.type === "error") {
|
|
208
|
+
const message = typeof msg.message === "string" ? msg.message : "worker_failed";
|
|
209
|
+
finish(() => reject(new Error(message)));
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
worker.onerror = (err: ErrorEvent) => {
|
|
214
|
+
finish(() => reject(err.error instanceof Error ? err.error : new Error(err.message || "worker_failed")));
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
worker.postMessage({
|
|
218
|
+
type: "run",
|
|
219
|
+
requestId,
|
|
220
|
+
trashId: opts.trashId,
|
|
221
|
+
...(opts.codexHome ? { codexHome: opts.codexHome } : {}),
|
|
222
|
+
...(opts.busyTimeoutMs !== undefined ? { busyTimeoutMs: opts.busyTimeoutMs } : {}),
|
|
223
|
+
...(opts.blockMs !== undefined ? { blockMs: opts.blockMs } : {}),
|
|
224
|
+
...(opts.restoreTest ? { restoreTest: opts.restoreTest } : {}),
|
|
225
|
+
env: {
|
|
226
|
+
...(process.env.CODEX_HOME ? { CODEX_HOME: process.env.CODEX_HOME } : {}),
|
|
227
|
+
...(process.env.OPENCODEX_HOME ? { OPENCODEX_HOME: process.env.OPENCODEX_HOME } : {}),
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
})).catch(error => {
|
|
231
|
+
reservation.release();
|
|
232
|
+
throw error;
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function executeRestore(opts: {
|
|
237
|
+
trashId: string;
|
|
238
|
+
codexHome?: string;
|
|
239
|
+
busyTimeoutMs?: number;
|
|
240
|
+
_test?: RestoreTestHooks;
|
|
241
|
+
}): Promise<RestoreResult> {
|
|
242
|
+
const blockMs = testHooks?.blockMs;
|
|
243
|
+
const restoreTest = opts._test ?? testHooks?.restoreTest;
|
|
244
|
+
|
|
245
|
+
if (testHooks?.runInProcess) {
|
|
246
|
+
if (typeof blockMs === "number" && blockMs > 0) await Bun.sleep(blockMs);
|
|
247
|
+
return restoreTrashEntry(opts.trashId, {
|
|
248
|
+
...(opts.codexHome ? { codexHome: opts.codexHome } : {}),
|
|
249
|
+
...(opts.busyTimeoutMs !== undefined ? { busyTimeoutMs: opts.busyTimeoutMs } : {}),
|
|
250
|
+
...(restoreTest ? { _test: restoreTest } : {}),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
return await runInWorker({
|
|
256
|
+
trashId: opts.trashId,
|
|
257
|
+
...(opts.codexHome ? { codexHome: opts.codexHome } : {}),
|
|
258
|
+
...(opts.busyTimeoutMs !== undefined ? { busyTimeoutMs: opts.busyTimeoutMs } : {}),
|
|
259
|
+
...(typeof blockMs === "number" && blockMs > 0 ? { blockMs } : {}),
|
|
260
|
+
...(restoreTest ? { restoreTest } : {}),
|
|
261
|
+
});
|
|
262
|
+
} catch (err) {
|
|
263
|
+
if (err instanceof StorageWorkerAdmissionBusyError) return busyRestoreResult();
|
|
264
|
+
return restoreResultFromWorkerRejection(err, opts.trashId);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function busyRestoreResult(): RestoreResult {
|
|
269
|
+
return {
|
|
270
|
+
ok: false,
|
|
271
|
+
count: 0,
|
|
272
|
+
bytes: 0,
|
|
273
|
+
restoredPaths: [],
|
|
274
|
+
error: "storage_mutation_busy",
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Run trash restore off the event loop under the shared storage-mutation gate.
|
|
280
|
+
* Returns `storage_mutation_busy` when cleanup or another restore is in flight.
|
|
281
|
+
*/
|
|
282
|
+
export async function runRestoreTrashEntryJob(
|
|
283
|
+
trashId: string,
|
|
284
|
+
options?: {
|
|
285
|
+
codexHome?: string;
|
|
286
|
+
busyTimeoutMs?: number;
|
|
287
|
+
_test?: RestoreTestHooks;
|
|
288
|
+
},
|
|
289
|
+
): Promise<RestoreResult> {
|
|
290
|
+
const codexHome = options?.codexHome ?? resolveCodexHomeDir();
|
|
291
|
+
const result = await withStorageMutationSlot("restore", codexHome, () =>
|
|
292
|
+
executeRestore({ trashId, ...options }),
|
|
293
|
+
);
|
|
294
|
+
if (result && typeof result === "object" && "ok" in result && result.ok === false
|
|
295
|
+
&& "error" in result && result.error === "storage_mutation_busy") {
|
|
296
|
+
return busyRestoreResult();
|
|
297
|
+
}
|
|
298
|
+
return result as RestoreResult;
|
|
299
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker-thread entry for trash restore runs.
|
|
3
|
+
* Keeps file moves and SQLite reconcile off the proxy event loop.
|
|
4
|
+
*/
|
|
5
|
+
import { restoreTrashEntry, type RestoreResult, type RestoreTestHooks } from "./cleanup";
|
|
6
|
+
|
|
7
|
+
interface RunMessage {
|
|
8
|
+
type: "run";
|
|
9
|
+
requestId: string;
|
|
10
|
+
trashId: string;
|
|
11
|
+
codexHome?: string;
|
|
12
|
+
busyTimeoutMs?: number;
|
|
13
|
+
/** Test-only: block before restore so responsiveness tests can probe /healthz. */
|
|
14
|
+
blockMs?: number;
|
|
15
|
+
restoreTest?: RestoreTestHooks;
|
|
16
|
+
/** Env snapshot — Workers may not see parent mutations on all platforms. */
|
|
17
|
+
env?: { CODEX_HOME?: string; OPENCODEX_HOME?: string };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isRunMessage(data: unknown): data is RunMessage {
|
|
21
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return false;
|
|
22
|
+
const o = data as Record<string, unknown>;
|
|
23
|
+
return o.type === "run" && typeof o.requestId === "string" && typeof o.trashId === "string";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
declare const self: Worker;
|
|
27
|
+
|
|
28
|
+
self.onmessage = async (event: MessageEvent<unknown>) => {
|
|
29
|
+
if (!isRunMessage(event.data)) return;
|
|
30
|
+
const { requestId, trashId, codexHome, busyTimeoutMs, blockMs, restoreTest, env } = event.data;
|
|
31
|
+
try {
|
|
32
|
+
if (env?.CODEX_HOME) process.env.CODEX_HOME = env.CODEX_HOME;
|
|
33
|
+
if (env?.OPENCODEX_HOME) process.env.OPENCODEX_HOME = env.OPENCODEX_HOME;
|
|
34
|
+
if (typeof blockMs === "number" && Number.isFinite(blockMs) && blockMs > 0) {
|
|
35
|
+
await Bun.sleep(Math.floor(blockMs));
|
|
36
|
+
}
|
|
37
|
+
const result = restoreTrashEntry(trashId, {
|
|
38
|
+
...(codexHome ? { codexHome } : {}),
|
|
39
|
+
...(busyTimeoutMs !== undefined ? { busyTimeoutMs } : {}),
|
|
40
|
+
...(restoreTest ? { _test: restoreTest } : {}),
|
|
41
|
+
});
|
|
42
|
+
self.postMessage({ type: "done", requestId, result });
|
|
43
|
+
} catch (err) {
|
|
44
|
+
self.postMessage({
|
|
45
|
+
type: "error",
|
|
46
|
+
requestId,
|
|
47
|
+
message: err instanceof Error ? err.message : "worker_failed",
|
|
48
|
+
});
|
|
49
|
+
} finally {
|
|
50
|
+
// Close from inside the worker so the thread begins exiting before the
|
|
51
|
+
// parent’s terminate() races isolate realm reclaim on Windows.
|
|
52
|
+
try {
|
|
53
|
+
(self as unknown as { close?: () => void }).close?.();
|
|
54
|
+
} catch { /* already closing */ }
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type { RestoreResult };
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { Database, constants } from "bun:sqlite";
|
|
5
|
+
import { resolveCodexHomeDir } from "../codex/home";
|
|
6
|
+
import { TRASH_DIR } from "./cleanup";
|
|
7
|
+
|
|
8
|
+
// SQLITE_OPEN_READONLY alone is not filesystem-read-only for a WAL-mode DB: Bun's
|
|
9
|
+
// `{ readonly: true }` can still materialize *.sqlite-wal/-shm sidecars the first time a
|
|
10
|
+
// checkpointed WAL database (no live sidecars yet) is opened and queried. `immutable=1`
|
|
11
|
+
// (via a file: URI, which requires the SQLITE_OPEN_URI flag) tells SQLite the file will
|
|
12
|
+
// never change for this connection's lifetime, so it skips WAL/shm entirely — the
|
|
13
|
+
// tradeoff is reading the last-checkpointed snapshot instead of blocking on a live writer,
|
|
14
|
+
// which is the right tradeoff for a passive diagnostics scan that must never write.
|
|
15
|
+
const IMMUTABLE_READONLY_FLAGS = constants.SQLITE_OPEN_READONLY | constants.SQLITE_OPEN_URI;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Read-only CODEX_HOME storage scanner — Phase 1 of the Storage page epic
|
|
19
|
+
* (devlog/_plan/500_storage-page-session-cleanup). Pure measurement: sizes via
|
|
20
|
+
* fs.stat walks, DB row counts via immutable readonly opens that degrade to
|
|
21
|
+
* null on lock/corruption. Performs zero writes under CODEX_HOME.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export type StorageBucketKey =
|
|
25
|
+
| "sessions"
|
|
26
|
+
| "archived_sessions"
|
|
27
|
+
| "logs_db"
|
|
28
|
+
| "state_db"
|
|
29
|
+
| "attachments"
|
|
30
|
+
| "deletion_manifests"
|
|
31
|
+
| "other";
|
|
32
|
+
|
|
33
|
+
export interface StorageLargestEntry {
|
|
34
|
+
/** Path relative to CODEX_HOME, forward-slash separated on every platform. */
|
|
35
|
+
path: string;
|
|
36
|
+
bytes: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface StorageBucket {
|
|
40
|
+
key: StorageBucketKey;
|
|
41
|
+
label: string;
|
|
42
|
+
bytes: number;
|
|
43
|
+
fileCount: number;
|
|
44
|
+
/** Epoch ms of the oldest/newest file mtime; absent for empty buckets. */
|
|
45
|
+
oldest?: number;
|
|
46
|
+
newest?: number;
|
|
47
|
+
largest?: StorageLargestEntry[];
|
|
48
|
+
/** sqlite buckets only: row count from the newest versioned DB, null when locked/unreadable. */
|
|
49
|
+
rows?: number | null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface StorageReport {
|
|
53
|
+
codexHome: string;
|
|
54
|
+
generatedAt: number;
|
|
55
|
+
total: { bytes: number; fileCount: number };
|
|
56
|
+
buckets: StorageBucket[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const LARGEST_CAP = 5;
|
|
60
|
+
|
|
61
|
+
const BUCKET_LABELS: Record<StorageBucketKey, string> = {
|
|
62
|
+
sessions: "Active sessions",
|
|
63
|
+
archived_sessions: "Archived sessions",
|
|
64
|
+
logs_db: "Logs database",
|
|
65
|
+
state_db: "State database",
|
|
66
|
+
attachments: "Attachments",
|
|
67
|
+
deletion_manifests: "Deletion manifests",
|
|
68
|
+
other: "Other",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/** Dirs under CODEX_HOME that map to a dedicated bucket; anything else is "other". */
|
|
72
|
+
const DIR_BUCKETS: Record<string, StorageBucketKey> = {
|
|
73
|
+
sessions: "sessions",
|
|
74
|
+
archived_sessions: "archived_sessions",
|
|
75
|
+
attachments: "attachments",
|
|
76
|
+
deletion_manifests: "deletion_manifests",
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// state_5.sqlite / logs_2.sqlite carry a version suffix and live WAL/SHM siblings.
|
|
80
|
+
const STATE_DB_FILE = /^state_(\d+)\.sqlite(-wal|-shm)?$/;
|
|
81
|
+
const LOGS_DB_FILE = /^logs_(\d+)\.sqlite(-wal|-shm)?$/;
|
|
82
|
+
|
|
83
|
+
interface FileEntry {
|
|
84
|
+
relPath: string;
|
|
85
|
+
bytes: number;
|
|
86
|
+
mtimeMs: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Recursive fs.stat walk. Unreadable entries (races, broken symlinks) are skipped, never fatal. */
|
|
90
|
+
function walkFiles(dir: string, relPrefix: string, out: FileEntry[]): void {
|
|
91
|
+
let entries;
|
|
92
|
+
try {
|
|
93
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
94
|
+
} catch {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
for (const entry of entries) {
|
|
98
|
+
const full = join(dir, entry.name);
|
|
99
|
+
const relPath = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
|
|
100
|
+
try {
|
|
101
|
+
if (entry.isDirectory()) {
|
|
102
|
+
walkFiles(full, relPath, out);
|
|
103
|
+
} else if (entry.isFile()) {
|
|
104
|
+
const stat = statSync(full);
|
|
105
|
+
out.push({ relPath, bytes: stat.size, mtimeMs: stat.mtimeMs });
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
/* entry vanished mid-scan — diagnostics tolerate racy trees */
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function buildBucket(key: StorageBucketKey, files: FileEntry[]): StorageBucket {
|
|
114
|
+
const bucket: StorageBucket = {
|
|
115
|
+
key,
|
|
116
|
+
label: BUCKET_LABELS[key],
|
|
117
|
+
bytes: 0,
|
|
118
|
+
fileCount: files.length,
|
|
119
|
+
};
|
|
120
|
+
for (const file of files) {
|
|
121
|
+
bucket.bytes += file.bytes;
|
|
122
|
+
if (bucket.oldest === undefined || file.mtimeMs < bucket.oldest) bucket.oldest = file.mtimeMs;
|
|
123
|
+
if (bucket.newest === undefined || file.mtimeMs > bucket.newest) bucket.newest = file.mtimeMs;
|
|
124
|
+
}
|
|
125
|
+
if (files.length > 0) {
|
|
126
|
+
bucket.largest = [...files]
|
|
127
|
+
.sort((a, b) => b.bytes - a.bytes)
|
|
128
|
+
.slice(0, LARGEST_CAP)
|
|
129
|
+
.map(f => ({ path: f.relPath, bytes: f.bytes }));
|
|
130
|
+
}
|
|
131
|
+
return bucket;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Row count via an immutable readonly open — guarantees zero writes under CODEX_HOME even
|
|
136
|
+
* for a checkpointed WAL-mode DB with no sidecars yet. Any error (corruption, a file that
|
|
137
|
+
* vanished mid-scan, a future schema change) degrades to null — "unknown", never a crash.
|
|
138
|
+
*/
|
|
139
|
+
function countRowsReadonly(dbPath: string, table: string): number | null {
|
|
140
|
+
try {
|
|
141
|
+
// pathToFileURL percent-encodes reserved characters (space, #, ?, %) that a naive
|
|
142
|
+
// `file:${dbPath}` concatenation would misparse as a URI fragment/query/escape.
|
|
143
|
+
const uri = `${pathToFileURL(dbPath).href}?immutable=1`;
|
|
144
|
+
const db = new Database(uri, IMMUTABLE_READONLY_FLAGS);
|
|
145
|
+
try {
|
|
146
|
+
const row = db.query<{ n: number }, []>(`SELECT count(*) AS n FROM "${table}"`).get();
|
|
147
|
+
return row?.n ?? null;
|
|
148
|
+
} finally {
|
|
149
|
+
db.close();
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Newest versioned DB main file (e.g. state_5.sqlite over state_4.sqlite), or null. */
|
|
157
|
+
function newestVersionedDb(names: string[], pattern: RegExp): string | null {
|
|
158
|
+
let best: string | null = null;
|
|
159
|
+
let bestVersion = -1;
|
|
160
|
+
for (const name of names) {
|
|
161
|
+
const match = name.match(pattern);
|
|
162
|
+
if (!match || match[2]) continue; // -wal/-shm siblings never win
|
|
163
|
+
const version = Number(match[1]);
|
|
164
|
+
if (version > bestVersion) {
|
|
165
|
+
bestVersion = version;
|
|
166
|
+
best = name;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return best;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function scanStorage(codexHome: string = resolveCodexHomeDir()): StorageReport {
|
|
173
|
+
const files: Record<StorageBucketKey, FileEntry[]> = {
|
|
174
|
+
sessions: [],
|
|
175
|
+
archived_sessions: [],
|
|
176
|
+
logs_db: [],
|
|
177
|
+
state_db: [],
|
|
178
|
+
attachments: [],
|
|
179
|
+
deletion_manifests: [],
|
|
180
|
+
other: [],
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
let rootNames: string[] = [];
|
|
184
|
+
try {
|
|
185
|
+
rootNames = readdirSync(codexHome);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
// A missing home is a normal fresh-machine state — report zeros. Anything else
|
|
188
|
+
// (e.g. ENOTDIR: CODEX_HOME points at a file) is a broken setup the caller
|
|
189
|
+
// must surface as a scan failure, not silently render as an empty home.
|
|
190
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
191
|
+
if (code !== "ENOENT") throw error;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
for (const name of rootNames) {
|
|
195
|
+
const full = join(codexHome, name);
|
|
196
|
+
let stat;
|
|
197
|
+
try {
|
|
198
|
+
stat = statSync(full);
|
|
199
|
+
} catch {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (stat.isDirectory()) {
|
|
203
|
+
// Quarantine trash (Phase 2) must not inflate "other" or totals.
|
|
204
|
+
if (name === TRASH_DIR) continue;
|
|
205
|
+
walkFiles(full, name, files[DIR_BUCKETS[name] ?? "other"]);
|
|
206
|
+
} else if (stat.isFile()) {
|
|
207
|
+
const key: StorageBucketKey = STATE_DB_FILE.test(name) ? "state_db" : LOGS_DB_FILE.test(name) ? "logs_db" : "other";
|
|
208
|
+
files[key].push({ relPath: name, bytes: stat.size, mtimeMs: stat.mtimeMs });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const buckets = (Object.keys(files) as StorageBucketKey[]).map(key => buildBucket(key, files[key]));
|
|
213
|
+
|
|
214
|
+
const stateDbName = newestVersionedDb(rootNames, STATE_DB_FILE);
|
|
215
|
+
const stateBucket = buckets.find(b => b.key === "state_db");
|
|
216
|
+
if (stateBucket && stateBucket.fileCount > 0) {
|
|
217
|
+
stateBucket.rows = stateDbName ? countRowsReadonly(join(codexHome, stateDbName), "threads") : null;
|
|
218
|
+
}
|
|
219
|
+
const logsDbName = newestVersionedDb(rootNames, LOGS_DB_FILE);
|
|
220
|
+
const logsBucket = buckets.find(b => b.key === "logs_db");
|
|
221
|
+
if (logsBucket && logsBucket.fileCount > 0) {
|
|
222
|
+
logsBucket.rows = logsDbName ? countRowsReadonly(join(codexHome, logsDbName), "logs") : null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let totalBytes = 0;
|
|
226
|
+
let totalFiles = 0;
|
|
227
|
+
for (const bucket of buckets) {
|
|
228
|
+
totalBytes += bucket.bytes;
|
|
229
|
+
totalFiles += bucket.fileCount;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
codexHome,
|
|
234
|
+
generatedAt: Date.now(),
|
|
235
|
+
total: { bytes: totalBytes, fileCount: totalFiles },
|
|
236
|
+
buckets,
|
|
237
|
+
};
|
|
238
|
+
}
|