@iislee/opencodex 2.11.0 → 2.31.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/AGENTS_INSTALL.md +109 -0
- package/README.md +114 -19
- package/bin/ocx.mjs +164 -36
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-BF38heuV.js +104 -0
- package/gui/dist/assets/index-DMiI18Kv.css +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +19 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +376 -52
- package/src/adapters/base.ts +54 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +601 -0
- package/src/adapters/cursor/checkpoint-store.ts +303 -0
- package/src/adapters/cursor/cursor-errors.ts +129 -6
- package/src/adapters/cursor/discovery.ts +61 -4
- package/src/adapters/cursor/effort-map.ts +27 -3
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +174 -58
- package/src/adapters/cursor/live-transport.ts +609 -170
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +0 -1
- package/src/adapters/cursor/native-exec.ts +101 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +383 -65
- package/src/adapters/cursor/request-builder.ts +237 -31
- package/src/adapters/cursor/tool-definitions.ts +242 -12
- package/src/adapters/cursor/tool-result-normalize.ts +92 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +28 -1
- package/src/adapters/cursor.ts +133 -7
- package/src/adapters/google-antigravity-replay.ts +558 -34
- package/src/adapters/google-antigravity-wire.ts +43 -10
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +618 -74
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +10 -1
- package/src/adapters/kiro.ts +92 -54
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1311 -336
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +830 -56
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +104 -21
- package/src/adapters/xai-web-search.ts +185 -0
- package/src/bridge.ts +428 -61
- package/src/chat/inbound.ts +43 -19
- package/src/chat/outbound.ts +82 -26
- package/src/claude/agents-inject.ts +32 -9
- package/src/claude/context-windows.ts +21 -5
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +70 -16
- package/src/cli/account-api.ts +35 -1
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +389 -2
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +30 -6
- package/src/cli/agent.ts +45 -1
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +29 -4
- package/src/cli/claude.ts +103 -34
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +593 -0
- package/src/cli/doctor.ts +315 -43
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +20 -271
- package/src/cli/index.ts +378 -519
- package/src/cli/init.ts +4 -17
- package/src/cli/integrations.ts +120 -2
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +35 -2
- package/src/cli/models.ts +100 -14
- package/src/cli/observe.ts +92 -3
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +18 -1
- package/src/cli/provider.ts +24 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +437 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +6 -3
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +10 -3
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/v2.ts +105 -10
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1358 -21
- package/src/codex/account-label.ts +14 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +493 -106
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +849 -242
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +345 -32
- package/src/codex/autostart-health.ts +8 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +68 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +121 -30
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +529 -45
- package/src/codex/catalog/native-models.ts +72 -0
- package/src/codex/catalog/parsing.ts +224 -30
- package/src/codex/catalog/provider-fetch.ts +1460 -134
- package/src/codex/catalog/sync.ts +1449 -186
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +6 -3
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +651 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +407 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +26 -20
- package/src/codex/history-provider.ts +231 -28
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +220 -0
- package/src/codex/inject-coordination.ts +290 -0
- package/src/codex/inject.ts +1073 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +80 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +506 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +353 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +492 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +682 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +175 -13
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +505 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1009 -28
- package/src/codex/subagent-model-fallback.ts +350 -35
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +20 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +81 -9
- package/src/config/provider-name.ts +24 -0
- package/src/config.ts +1762 -140
- package/src/generated/compatibility-version.json +3164 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +152 -29
- package/src/images/plan.ts +23 -13
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +159 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +361 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +691 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +153 -9
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +6 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +44 -2
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +4 -1
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +202 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +33 -5
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +8 -2
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +398 -66
- package/src/oauth/key-providers.ts +16 -0
- package/src/oauth/kimi.ts +16 -2
- package/src/oauth/kiro.ts +50 -6
- package/src/oauth/local-token-detect.ts +11 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/store.ts +119 -21
- package/src/oauth/token-guardian.ts +9 -3
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +144 -0
- package/src/providers/context-cap.ts +22 -5
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/derive.ts +253 -6
- package/src/providers/fastwire.ts +501 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +1 -1
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +29 -0
- package/src/providers/quota.ts +1319 -38
- package/src/providers/registry.ts +1429 -111
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +42 -6
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +11 -4
- package/src/reasoning-effort.ts +49 -1
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +266 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +355 -0
- package/src/responses/parser.ts +220 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +426 -0
- package/src/responses/schema.ts +7 -1
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +565 -27
- package/src/responses/thought-signature-replay.ts +347 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/router.ts +366 -30
- package/src/routing/analytics.ts +378 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +267 -46
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +130 -56
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +426 -0
- package/src/server/claude-messages.ts +159 -43
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +865 -181
- package/src/server/lifecycle.ts +284 -13
- package/src/server/live.ts +136 -17
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +398 -103
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/combo-routes.ts +62 -24
- package/src/server/management/config-routes.ts +464 -51
- package/src/server/management/context.ts +80 -2
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +101 -32
- package/src/server/management/model-routes.ts +189 -131
- package/src/server/management/model-rows.ts +163 -0
- package/src/server/management/native-integration-routes.ts +769 -0
- package/src/server/management/oauth-account-routes.ts +80 -4
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +764 -157
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +288 -32
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +115 -14
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +132 -5
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +82 -42
- package/src/server/relay.ts +236 -76
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log.ts +235 -22
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/collaboration.ts +204 -35
- package/src/server/responses/compact.ts +442 -55
- package/src/server/responses/core.ts +2872 -331
- package/src/server/responses/empty-completion-guard.ts +276 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +79 -4
- package/src/server/responses/input-admission.ts +185 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +251 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +308 -0
- package/src/server/responses-custom-tool-repair.ts +282 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +342 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +153 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +39 -38
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1111 -90
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +845 -0
- package/src/types/provider.ts +545 -0
- package/src/types/request.ts +384 -0
- package/src/types/tools.ts +131 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +104 -1236
- package/src/update/index.ts +32 -19
- package/src/update/job.ts +442 -67
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +259 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +268 -16
- package/src/usage/log.ts +606 -41
- package/src/usage/summary.ts +177 -9
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +9 -3
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +88 -0
- package/src/web-search/executor.ts +11 -3
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +279 -50
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
|
@@ -7,9 +7,13 @@
|
|
|
7
7
|
* Never match broad `*codex*` patterns that hit unrelated tools such as
|
|
8
8
|
* `hermes-codex-bridge-mcp`.
|
|
9
9
|
*/
|
|
10
|
-
import { execFileSync } from "node:child_process";
|
|
10
|
+
import { execFile, execFileSync, type ExecFileException } from "node:child_process";
|
|
11
11
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
12
12
|
import { isProcessAlive, waitForExit } from "../lib/process-control";
|
|
13
|
+
import {
|
|
14
|
+
resolveTrustedWindowsPowerShellExe,
|
|
15
|
+
resolveTrustedWindowsTaskkillExe,
|
|
16
|
+
} from "../lib/windows-elevation";
|
|
13
17
|
import { readCodexCatalogPath } from "./catalog/parsing";
|
|
14
18
|
|
|
15
19
|
export const STALE_CODEX_APP_SERVER_HINT =
|
|
@@ -71,6 +75,7 @@ export interface CodexAppServerProcess {
|
|
|
71
75
|
export interface ProcessSnapshot {
|
|
72
76
|
pid: number;
|
|
73
77
|
commandLine: string;
|
|
78
|
+
executable?: string;
|
|
74
79
|
uid?: number;
|
|
75
80
|
owner?: string;
|
|
76
81
|
startedAtMs?: number;
|
|
@@ -82,12 +87,41 @@ export interface CodexAppServerProcessIo {
|
|
|
82
87
|
listSnapshots?: () => ProcessSnapshot[];
|
|
83
88
|
isAlive?: (pid: number) => boolean;
|
|
84
89
|
kill?: (pid: number, signal: NodeJS.Signals) => void;
|
|
90
|
+
/** Windows termination seam: drives the taskkill branch without a real exec. */
|
|
91
|
+
execFile?: (file: string, args: readonly string[]) => void;
|
|
92
|
+
/**
|
|
93
|
+
* Signal seam for the Unix branch and the taskkill fallback. Without it,
|
|
94
|
+
* injecting `kill` bypasses defaultKillCodexAppServer entirely, so the
|
|
95
|
+
* fallback could never be observed.
|
|
96
|
+
*/
|
|
97
|
+
processKill?: (pid: number, signal: NodeJS.Signals) => void;
|
|
85
98
|
waitExit?: (pid: number, timeoutMs: number) => boolean;
|
|
86
99
|
now?: () => number;
|
|
87
100
|
readStartMs?: (pid: number) => number | null;
|
|
101
|
+
/** Async process-list seam used by the request-path Windows collector. */
|
|
102
|
+
listSnapshotsAsync?: () => Promise<ProcessSnapshot[]>;
|
|
103
|
+
/** Async batch start-time seam used by the request-path Windows collector. */
|
|
104
|
+
readStartMsBatchAsync?: (pids: readonly number[]) => Promise<Map<number, number | null>>;
|
|
88
105
|
catalogMtimeMs?: () => number | null;
|
|
89
106
|
}
|
|
90
107
|
|
|
108
|
+
function execFileTextAsync(
|
|
109
|
+
file: string,
|
|
110
|
+
args: readonly string[],
|
|
111
|
+
timeoutMs: number,
|
|
112
|
+
): Promise<string> {
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
execFile(file, [...args], {
|
|
115
|
+
encoding: "utf-8",
|
|
116
|
+
timeout: timeoutMs,
|
|
117
|
+
windowsHide: true,
|
|
118
|
+
}, (error: ExecFileException | null, stdout: string) => {
|
|
119
|
+
if (error) reject(error);
|
|
120
|
+
else resolve(stdout);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
91
125
|
/** Split a process command line into argv-like tokens (handles simple quotes). */
|
|
92
126
|
export function tokenizeCommandLine(commandLine: string): string[] {
|
|
93
127
|
const tokens: string[] = [];
|
|
@@ -211,13 +245,20 @@ export function codexAppServerProcessIdentity(proc: Pick<CodexAppServerProcess,
|
|
|
211
245
|
}
|
|
212
246
|
|
|
213
247
|
/** True when the command line is a Codex app-server (or code-mode host) worth restarting. */
|
|
214
|
-
export function isCodexAppServerCommandLine(commandLine: string): boolean {
|
|
215
|
-
const
|
|
248
|
+
export function isCodexAppServerCommandLine(commandLine: string, executable?: string): boolean {
|
|
249
|
+
const trimmed = commandLine.trim();
|
|
250
|
+
let tokens = tokenizeCommandLine(trimmed);
|
|
251
|
+
if (executable) {
|
|
252
|
+
if (isCodeModeHostToken(executable)) return true;
|
|
253
|
+
if (isCodexExecutableToken(executable)) {
|
|
254
|
+
let remainder = trimmed;
|
|
255
|
+
if (remainder.startsWith(executable)) remainder = remainder.slice(executable.length).trimStart();
|
|
256
|
+
else if (remainder.startsWith(`"${executable}"`)) remainder = remainder.slice(executable.length + 2).trimStart();
|
|
257
|
+
tokens = [executable, ...tokenizeCommandLine(remainder)];
|
|
258
|
+
}
|
|
259
|
+
}
|
|
216
260
|
if (tokens.length === 0) return false;
|
|
217
261
|
if (isCodeModeHostProcess(tokens)) return true;
|
|
218
|
-
|
|
219
|
-
// Require Codex as argv0 so later-argument occurrences stay unmatched
|
|
220
|
-
// (e.g. `node worker.js codex app-server`).
|
|
221
262
|
if (!isCodexExecutableToken(tokens[0]!)) return false;
|
|
222
263
|
|
|
223
264
|
let i = 1;
|
|
@@ -227,7 +268,6 @@ export function isCodexAppServerCommandLine(commandLine: string): boolean {
|
|
|
227
268
|
i = advancePastCodexGlobalOption(tokens, i);
|
|
228
269
|
continue;
|
|
229
270
|
}
|
|
230
|
-
// First non-option after globals is the Codex subcommand.
|
|
231
271
|
return token.toLowerCase() === "app-server";
|
|
232
272
|
}
|
|
233
273
|
return false;
|
|
@@ -253,12 +293,13 @@ function listUnixProcSnapshots(uid: number | undefined): ProcessSnapshot[] {
|
|
|
253
293
|
const status = readFileSync(`/proc/${pid}/status`, "utf8");
|
|
254
294
|
const processUid = parseUnixProcStatusUid(status);
|
|
255
295
|
if (uid !== undefined && processUid !== undefined && processUid !== uid) continue;
|
|
256
|
-
const
|
|
296
|
+
const argv = readFileSync(`/proc/${pid}/cmdline`)
|
|
257
297
|
.toString("utf8")
|
|
258
|
-
.
|
|
259
|
-
.
|
|
298
|
+
.split("\0")
|
|
299
|
+
.filter(Boolean);
|
|
300
|
+
const commandLine = argv.join(" ").trim();
|
|
260
301
|
if (!commandLine) continue;
|
|
261
|
-
out.push({ pid, commandLine, uid: processUid });
|
|
302
|
+
out.push({ pid, commandLine, executable: argv[0], uid: processUid });
|
|
262
303
|
} catch {
|
|
263
304
|
/* process exited mid-scan */
|
|
264
305
|
}
|
|
@@ -268,20 +309,29 @@ function listUnixProcSnapshots(uid: number | undefined): ProcessSnapshot[] {
|
|
|
268
309
|
|
|
269
310
|
function listDarwinSnapshots(uid: number | undefined): ProcessSnapshot[] {
|
|
270
311
|
const out: ProcessSnapshot[] = [];
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
? execFileSync("ps", ["-u", String(uid), "-o", "pid=,command="], {
|
|
275
|
-
encoding: "utf-8",
|
|
276
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
277
|
-
timeout: 5_000,
|
|
312
|
+
const commandOutput = uid !== undefined
|
|
313
|
+
? execFileSync("/bin/ps", ["-u", String(uid), "-o", "pid=,command="], {
|
|
314
|
+
encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], timeout: 5_000,
|
|
278
315
|
})
|
|
279
|
-
: execFileSync("ps", ["-axo", "pid=,uid=,command="], {
|
|
280
|
-
encoding: "utf-8",
|
|
281
|
-
|
|
282
|
-
|
|
316
|
+
: execFileSync("/bin/ps", ["-axo", "pid=,uid=,command="], {
|
|
317
|
+
encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], timeout: 5_000,
|
|
318
|
+
});
|
|
319
|
+
const executableOutput = uid !== undefined
|
|
320
|
+
? execFileSync("/bin/ps", ["-u", String(uid), "-o", "pid=,comm="], {
|
|
321
|
+
encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], timeout: 5_000,
|
|
322
|
+
})
|
|
323
|
+
: execFileSync("/bin/ps", ["-axo", "pid=,comm="], {
|
|
324
|
+
encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], timeout: 5_000,
|
|
283
325
|
});
|
|
284
|
-
|
|
326
|
+
const executableByPid = new Map<number, string>();
|
|
327
|
+
for (const raw of executableOutput.split(/\r?\n/)) {
|
|
328
|
+
const match = /^\s*(\d+)\s+(.+)$/.exec(raw);
|
|
329
|
+
if (!match) continue;
|
|
330
|
+
const pid = Number(match[1]);
|
|
331
|
+
const executable = match[2]?.trim() ?? "";
|
|
332
|
+
if (Number.isSafeInteger(pid) && pid > 0 && executable) executableByPid.set(pid, executable);
|
|
333
|
+
}
|
|
334
|
+
for (const raw of commandOutput.split(/\r?\n/)) {
|
|
285
335
|
const line = raw.trim();
|
|
286
336
|
if (!line) continue;
|
|
287
337
|
if (uid !== undefined) {
|
|
@@ -289,8 +339,8 @@ function listDarwinSnapshots(uid: number | undefined): ProcessSnapshot[] {
|
|
|
289
339
|
if (!match) continue;
|
|
290
340
|
const pid = Number(match[1]);
|
|
291
341
|
const commandLine = match[2]?.trim() ?? "";
|
|
292
|
-
if (!Number.isSafeInteger(pid) || pid <=
|
|
293
|
-
out.push({ pid, commandLine, uid });
|
|
342
|
+
if (!Number.isSafeInteger(pid) || pid <= 0 || !commandLine) continue;
|
|
343
|
+
out.push({ pid, commandLine, executable: executableByPid.get(pid), uid });
|
|
294
344
|
continue;
|
|
295
345
|
}
|
|
296
346
|
const match = /^(\d+)\s+(\d+)\s+(.*)$/.exec(line);
|
|
@@ -298,8 +348,11 @@ function listDarwinSnapshots(uid: number | undefined): ProcessSnapshot[] {
|
|
|
298
348
|
const pid = Number(match[1]);
|
|
299
349
|
const processUid = Number(match[2]);
|
|
300
350
|
const commandLine = match[3]?.trim() ?? "";
|
|
301
|
-
if (!Number.isSafeInteger(pid) || pid <=
|
|
302
|
-
out.push({
|
|
351
|
+
if (!Number.isSafeInteger(pid) || pid <= 0 || !commandLine) continue;
|
|
352
|
+
out.push({
|
|
353
|
+
pid, commandLine, executable: executableByPid.get(pid),
|
|
354
|
+
uid: Number.isSafeInteger(processUid) ? processUid : undefined,
|
|
355
|
+
});
|
|
303
356
|
}
|
|
304
357
|
return out;
|
|
305
358
|
}
|
|
@@ -316,8 +369,34 @@ function listDarwinSnapshots(uid: number | undefined): ProcessSnapshot[] {
|
|
|
316
369
|
* Exported for the Windows integration regression that exercises the real
|
|
317
370
|
* PowerShell enumeration.
|
|
318
371
|
*/
|
|
319
|
-
|
|
372
|
+
/**
|
|
373
|
+
* Turn one PowerShell enumeration's stdout into snapshots.
|
|
374
|
+
*
|
|
375
|
+
* Split out from the spawn so the failure contract is testable off-Windows: the
|
|
376
|
+
* sentinel path is the difference between "no Codex process is running" and "we could
|
|
377
|
+
* not read the process list", and only one of those is safe to act on.
|
|
378
|
+
*/
|
|
379
|
+
export function parseWindowsSnapshotOutput(output: string): ProcessSnapshot[] {
|
|
320
380
|
const out: ProcessSnapshot[] = [];
|
|
381
|
+
for (const line of output.split(/\r?\n/)) {
|
|
382
|
+
// A candidate whose owner could not be verified — or a top-level query that
|
|
383
|
+
// failed outright — makes the whole enumeration incomplete. The staleness
|
|
384
|
+
// collector must not read the partial result as "nothing running".
|
|
385
|
+
if (line.trim() === "__OCX_ENUM_INCOMPLETE__") throw new Error("windows_enum_incomplete");
|
|
386
|
+
const tab = line.indexOf("\t");
|
|
387
|
+
if (tab <= 0) continue;
|
|
388
|
+
const tab2 = line.indexOf("\t", tab + 1);
|
|
389
|
+
if (tab2 <= tab) continue;
|
|
390
|
+
const pid = Number(line.slice(0, tab));
|
|
391
|
+
const commandLine = line.slice(tab + 1, tab2).trim();
|
|
392
|
+
const owner = line.slice(tab2 + 1).trim();
|
|
393
|
+
if (!Number.isSafeInteger(pid) || pid <= 1 || !commandLine || !owner) continue;
|
|
394
|
+
out.push({ pid, commandLine, owner });
|
|
395
|
+
}
|
|
396
|
+
return out;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function windowsSnapshotPowerShellCommand(): string {
|
|
321
400
|
// Newlines keep -Command as a real script (space-joined statements need ';').
|
|
322
401
|
// Double-quoted format string so `t expands to a real tab.
|
|
323
402
|
// Codex candidates only: basename token codex / codex.exe / codex.cmd /
|
|
@@ -326,10 +405,18 @@ export function listWindowsSnapshots(): ProcessSnapshot[] {
|
|
|
326
405
|
// path with "opencodex".
|
|
327
406
|
const basenameMatch = powerShellSingleQuotedIgnoreCaseMatch(WINDOWS_CODEX_BASENAME_CANDIDATE_RE.source);
|
|
328
407
|
const codeModeMatch = powerShellSingleQuotedIgnoreCaseMatch(WINDOWS_CODEX_CODE_MODE_HOST_CANDIDATE_RE.source);
|
|
329
|
-
|
|
408
|
+
return [
|
|
330
409
|
"$ErrorActionPreference='SilentlyContinue'",
|
|
331
410
|
"$me=[System.Security.Principal.WindowsIdentity]::GetCurrent().Name",
|
|
332
|
-
|
|
411
|
+
// -ErrorAction Stop plus the outer try is what makes a TOP-LEVEL query failure
|
|
412
|
+
// observable. Under SilentlyContinue alone, a failing Get-CimInstance emits nothing
|
|
413
|
+
// and the enumeration is indistinguishable from "no Codex process is running" —
|
|
414
|
+
// the parse loop finds no rows, no sentinel is produced, and the staleness collector
|
|
415
|
+
// reports not_running for a machine whose process list it never actually read.
|
|
416
|
+
// The per-process catch below cannot cover this: it only runs once the pipeline has
|
|
417
|
+
// objects to iterate.
|
|
418
|
+
"try {",
|
|
419
|
+
"Get-CimInstance Win32_Process -ErrorAction Stop | Where-Object {",
|
|
333
420
|
" -not [string]::IsNullOrWhiteSpace($_.CommandLine) -and (",
|
|
334
421
|
` $_.CommandLine -match ${basenameMatch} -or`,
|
|
335
422
|
` $_.CommandLine -match ${codeModeMatch}`,
|
|
@@ -344,29 +431,32 @@ export function listWindowsSnapshots(): ProcessSnapshot[] {
|
|
|
344
431
|
" \"{0}`t{1}`t{2}\" -f $_.ProcessId, $cmd, $owner",
|
|
345
432
|
" } catch { \"__OCX_ENUM_INCOMPLETE__\" }",
|
|
346
433
|
"}",
|
|
434
|
+
"} catch { \"__OCX_ENUM_INCOMPLETE__\" }",
|
|
347
435
|
].join("\n");
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export function listWindowsSnapshots(runPowerShell?: (psCommand: string) => string): ProcessSnapshot[] {
|
|
439
|
+
// Top-level exec failure propagates (see listDarwinSnapshots note). The
|
|
440
|
+
// executable resolves from the trusted System32 directory (never PATH), and
|
|
441
|
+
// windowsHide keeps the enumeration console-less on desktop sessions (#1278).
|
|
442
|
+
const psCommand = windowsSnapshotPowerShellCommand();
|
|
443
|
+
const output = runPowerShell
|
|
444
|
+
? runPowerShell(psCommand)
|
|
445
|
+
: execFileSync(resolveTrustedWindowsPowerShellExe(), [
|
|
446
|
+
"-NoProfile", "-NoLogo", "-NonInteractive",
|
|
447
|
+
"-Command",
|
|
448
|
+
psCommand,
|
|
449
|
+
], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], timeout: 8_000, windowsHide: true });
|
|
450
|
+
return parseWindowsSnapshotOutput(output);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async function listWindowsSnapshotsAsync(): Promise<ProcessSnapshot[]> {
|
|
454
|
+
const output = await execFileTextAsync(resolveTrustedWindowsPowerShellExe(), [
|
|
455
|
+
"-NoProfile", "-NoLogo", "-NonInteractive",
|
|
351
456
|
"-Command",
|
|
352
|
-
|
|
353
|
-
],
|
|
354
|
-
|
|
355
|
-
// A candidate whose owner could not be verified makes the whole
|
|
356
|
-
// enumeration incomplete — the staleness collector must not read the
|
|
357
|
-
// partial result as "nothing running".
|
|
358
|
-
if (line.trim() === "__OCX_ENUM_INCOMPLETE__") throw new Error("windows_enum_incomplete");
|
|
359
|
-
const tab = line.indexOf("\t");
|
|
360
|
-
if (tab <= 0) continue;
|
|
361
|
-
const tab2 = line.indexOf("\t", tab + 1);
|
|
362
|
-
if (tab2 <= tab) continue;
|
|
363
|
-
const pid = Number(line.slice(0, tab));
|
|
364
|
-
const commandLine = line.slice(tab + 1, tab2).trim();
|
|
365
|
-
const owner = line.slice(tab2 + 1).trim();
|
|
366
|
-
if (!Number.isSafeInteger(pid) || pid <= 1 || !commandLine || !owner) continue;
|
|
367
|
-
out.push({ pid, commandLine, owner });
|
|
368
|
-
}
|
|
369
|
-
return out;
|
|
457
|
+
windowsSnapshotPowerShellCommand(),
|
|
458
|
+
], 8_000);
|
|
459
|
+
return parseWindowsSnapshotOutput(output);
|
|
370
460
|
}
|
|
371
461
|
|
|
372
462
|
function defaultListSnapshots(platform: NodeJS.Platform, getuid: () => number | undefined): ProcessSnapshot[] {
|
|
@@ -400,14 +490,16 @@ export function listCodexAppServerProcesses(io: CodexAppServerProcessIo = {}): C
|
|
|
400
490
|
const matched: CodexAppServerProcess[] = [];
|
|
401
491
|
for (const snapshot of snapshots) {
|
|
402
492
|
if (seen.has(snapshot.pid)) continue;
|
|
403
|
-
if (!isCodexAppServerCommandLine(snapshot.commandLine)) continue;
|
|
493
|
+
if (!isCodexAppServerCommandLine(snapshot.commandLine, snapshot.executable)) continue;
|
|
404
494
|
seen.add(snapshot.pid);
|
|
405
495
|
matched.push({ pid: snapshot.pid, commandLine: snapshot.commandLine });
|
|
406
496
|
}
|
|
407
497
|
return matched;
|
|
408
498
|
}
|
|
409
499
|
|
|
410
|
-
export function formatStaleCodexAppServerWarning(
|
|
500
|
+
export function formatStaleCodexAppServerWarning(
|
|
501
|
+
processes: readonly { pid: number }[],
|
|
502
|
+
): string {
|
|
411
503
|
const pids = processes.map(process => process.pid).join(", ");
|
|
412
504
|
return (
|
|
413
505
|
`WARNING: ${processes.length} Codex app-server process(es) still running (PID${processes.length === 1 ? "" : "s"}: ${pids}). `
|
|
@@ -439,7 +531,7 @@ function readLinuxProcStartMs(pid: number): number | null {
|
|
|
439
531
|
/** `ps` lstart → epoch ms, or null (macOS). */
|
|
440
532
|
function readDarwinProcStartMs(pid: number): number | null {
|
|
441
533
|
try {
|
|
442
|
-
const out = execFileSync("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
534
|
+
const out = execFileSync("/bin/ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
443
535
|
encoding: "utf-8",
|
|
444
536
|
stdio: ["ignore", "pipe", "ignore"],
|
|
445
537
|
timeout: 4_000,
|
|
@@ -455,8 +547,8 @@ function readDarwinProcStartMs(pid: number): number | null {
|
|
|
455
547
|
/** Win32_Process.CreationDate → epoch ms, or null (Windows). */
|
|
456
548
|
function readWindowsProcStartMs(pid: number): number | null {
|
|
457
549
|
try {
|
|
458
|
-
const out = execFileSync(
|
|
459
|
-
"-NoProfile", "-NoLogo", "-NonInteractive",
|
|
550
|
+
const out = execFileSync(resolveTrustedWindowsPowerShellExe(), [
|
|
551
|
+
"-NoProfile", "-NoLogo", "-NonInteractive",
|
|
460
552
|
"-Command",
|
|
461
553
|
`(Get-CimInstance Win32_Process -Filter "ProcessId=${pid}").CreationDate.ToUniversalTime().ToString("o")`,
|
|
462
554
|
], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], timeout: 8_000, windowsHide: true }).trim();
|
|
@@ -475,6 +567,41 @@ export function readProcessStartMs(pid: number, platform: NodeJS.Platform = proc
|
|
|
475
567
|
return readLinuxProcStartMs(pid);
|
|
476
568
|
}
|
|
477
569
|
|
|
570
|
+
function windowsProcessStartPowerShellCommand(pids: readonly number[]): string {
|
|
571
|
+
const filter = pids.map(pid => `ProcessId=${pid}`).join(" OR ");
|
|
572
|
+
return `Get-CimInstance Win32_Process -Filter "${filter}" | ForEach-Object { "$($_.ProcessId)\t$($_.CreationDate.ToUniversalTime().ToString("o"))" }`;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function parseWindowsProcessStartTimes(
|
|
576
|
+
stdout: string,
|
|
577
|
+
pids: readonly number[],
|
|
578
|
+
): Map<number, number | null> {
|
|
579
|
+
const byPid = new Map<number, number>();
|
|
580
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
581
|
+
const tab = line.indexOf("\t");
|
|
582
|
+
if (tab <= 0) continue;
|
|
583
|
+
const pid = Number(line.slice(0, tab));
|
|
584
|
+
const parsed = Date.parse(line.slice(tab + 1).trim());
|
|
585
|
+
if (Number.isSafeInteger(pid) && Number.isFinite(parsed)) byPid.set(pid, parsed);
|
|
586
|
+
}
|
|
587
|
+
return new Map(pids.map(pid => [pid, byPid.get(pid) ?? null]));
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
async function readWindowsProcessStartMsBatchAsync(
|
|
591
|
+
pids: readonly number[],
|
|
592
|
+
): Promise<Map<number, number | null>> {
|
|
593
|
+
try {
|
|
594
|
+
const stdout = await execFileTextAsync(resolveTrustedWindowsPowerShellExe(), [
|
|
595
|
+
"-NoProfile", "-NoLogo", "-NonInteractive",
|
|
596
|
+
"-Command",
|
|
597
|
+
windowsProcessStartPowerShellCommand(pids),
|
|
598
|
+
], 5_000);
|
|
599
|
+
return parseWindowsProcessStartTimes(stdout, pids);
|
|
600
|
+
} catch {
|
|
601
|
+
return new Map(pids.map(pid => [pid, null]));
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
478
605
|
/**
|
|
479
606
|
* Start times for many pids in ONE platform call where possible, so the
|
|
480
607
|
* staleness check does not serialize per-process ps/PowerShell invocations
|
|
@@ -488,7 +615,7 @@ export function readProcessStartMsBatch(
|
|
|
488
615
|
if (pids.length === 0) return out;
|
|
489
616
|
if (platform === "darwin") {
|
|
490
617
|
try {
|
|
491
|
-
const stdout = execFileSync("ps", ["-o", "pid=,lstart=", "-p", pids.join(",")], {
|
|
618
|
+
const stdout = execFileSync("/bin/ps", ["-o", "pid=,lstart=", "-p", pids.join(",")], {
|
|
492
619
|
encoding: "utf-8",
|
|
493
620
|
stdio: ["ignore", "pipe", "ignore"],
|
|
494
621
|
timeout: 3_000,
|
|
@@ -510,22 +637,12 @@ export function readProcessStartMsBatch(
|
|
|
510
637
|
}
|
|
511
638
|
if (platform === "win32") {
|
|
512
639
|
try {
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
"-NoProfile", "-NoLogo", "-NonInteractive", "-WindowStyle", "Hidden",
|
|
640
|
+
const stdout = execFileSync(resolveTrustedWindowsPowerShellExe(), [
|
|
641
|
+
"-NoProfile", "-NoLogo", "-NonInteractive",
|
|
516
642
|
"-Command",
|
|
517
|
-
|
|
643
|
+
windowsProcessStartPowerShellCommand(pids),
|
|
518
644
|
], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"], timeout: 5_000, windowsHide: true });
|
|
519
|
-
|
|
520
|
-
for (const line of stdout.split(/\r?\n/)) {
|
|
521
|
-
const tab = line.indexOf("\t");
|
|
522
|
-
if (tab <= 0) continue;
|
|
523
|
-
const pid = Number(line.slice(0, tab));
|
|
524
|
-
const parsed = Date.parse(line.slice(tab + 1).trim());
|
|
525
|
-
if (Number.isSafeInteger(pid) && Number.isFinite(parsed)) byPid.set(pid, parsed);
|
|
526
|
-
}
|
|
527
|
-
for (const pid of pids) out.set(pid, byPid.get(pid) ?? null);
|
|
528
|
-
return out;
|
|
645
|
+
return parseWindowsProcessStartTimes(stdout, pids);
|
|
529
646
|
} catch {
|
|
530
647
|
for (const pid of pids) out.set(pid, null);
|
|
531
648
|
return out;
|
|
@@ -552,10 +669,91 @@ function defaultCatalogMtimeMs(): number | null {
|
|
|
552
669
|
}
|
|
553
670
|
}
|
|
554
671
|
|
|
672
|
+
function codexAppServerProcessesFromSnapshots(
|
|
673
|
+
snapshots: readonly ProcessSnapshot[],
|
|
674
|
+
): CodexAppServerProcess[] {
|
|
675
|
+
const processes: CodexAppServerProcess[] = [];
|
|
676
|
+
const seen = new Set<number>();
|
|
677
|
+
for (const snapshot of snapshots) {
|
|
678
|
+
if (seen.has(snapshot.pid)) continue;
|
|
679
|
+
if (!isCodexAppServerCommandLine(snapshot.commandLine, snapshot.executable)) continue;
|
|
680
|
+
seen.add(snapshot.pid);
|
|
681
|
+
processes.push({ pid: snapshot.pid, commandLine: snapshot.commandLine });
|
|
682
|
+
}
|
|
683
|
+
return processes;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function catalogStatusFromProcesses(
|
|
687
|
+
processes: readonly CodexAppServerProcess[],
|
|
688
|
+
catalogMtimeMs: number | null,
|
|
689
|
+
starts: ReadonlyMap<number, number | null>,
|
|
690
|
+
): CodexAppServerCatalogStatus {
|
|
691
|
+
const withStarts = processes.map(proc => ({
|
|
692
|
+
pid: proc.pid,
|
|
693
|
+
startedAtMs: starts.get(proc.pid) ?? null,
|
|
694
|
+
}));
|
|
695
|
+
if (catalogMtimeMs === null || withStarts.some(proc => proc.startedAtMs === null)) {
|
|
696
|
+
return { state: "unknown", processes: withStarts, catalogMtimeMs };
|
|
697
|
+
}
|
|
698
|
+
// `<=` is deliberate: coarse clocks (ps lstart is second-granularity) can
|
|
699
|
+
// report equal values when the catalog actually changed after startup.
|
|
700
|
+
const stale = withStarts.some(proc => proc.startedAtMs! <= catalogMtimeMs);
|
|
701
|
+
return { state: stale ? "stale" : "fresh", processes: withStarts, catalogMtimeMs };
|
|
702
|
+
}
|
|
703
|
+
|
|
555
704
|
// Short TTL: process listing + stat run once per window even under per-turn
|
|
556
705
|
// guidance calls (#857).
|
|
557
706
|
let catalogStateCache: { atMs: number; status: CodexAppServerCatalogStatus } | null = null;
|
|
707
|
+
interface RequestCatalogStateIdentity {
|
|
708
|
+
platform: NodeJS.Platform;
|
|
709
|
+
listSnapshots?: CodexAppServerProcessIo["listSnapshots"];
|
|
710
|
+
listSnapshotsAsync?: CodexAppServerProcessIo["listSnapshotsAsync"];
|
|
711
|
+
readStartMs?: CodexAppServerProcessIo["readStartMs"];
|
|
712
|
+
readStartMsBatchAsync?: CodexAppServerProcessIo["readStartMsBatchAsync"];
|
|
713
|
+
catalogMtimeMs?: CodexAppServerProcessIo["catalogMtimeMs"];
|
|
714
|
+
now?: CodexAppServerProcessIo["now"];
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
interface RequestCatalogStateFlight {
|
|
718
|
+
generation: number;
|
|
719
|
+
identity: RequestCatalogStateIdentity;
|
|
720
|
+
promise: Promise<CodexAppServerCatalogStatus>;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
let requestCatalogStateGeneration = 0;
|
|
724
|
+
let requestCatalogStateCache: {
|
|
725
|
+
generation: number;
|
|
726
|
+
identity: RequestCatalogStateIdentity;
|
|
727
|
+
atMs: number;
|
|
728
|
+
status: CodexAppServerCatalogStatus;
|
|
729
|
+
} | null = null;
|
|
730
|
+
let requestCatalogStateFlight: RequestCatalogStateFlight | null = null;
|
|
558
731
|
const CATALOG_STATE_TTL_MS = 5_000;
|
|
732
|
+
/**
|
|
733
|
+
* `unknown` is a failure to observe, not an observation, so it gets a much shorter
|
|
734
|
+
* window than a real reading. At the full 5s a single transient enumeration failure
|
|
735
|
+
* suppresses guidance for every call in that window, and the retry that would have
|
|
736
|
+
* succeeded never runs. Keeping a brief window still collapses a burst of per-turn
|
|
737
|
+
* calls into one probe, which is what the cache is for.
|
|
738
|
+
*/
|
|
739
|
+
const CATALOG_STATE_UNKNOWN_TTL_MS = 250;
|
|
740
|
+
|
|
741
|
+
export function catalogStateTtlMs(state: CodexAppServerCatalogState): number {
|
|
742
|
+
return state === "unknown" ? CATALOG_STATE_UNKNOWN_TTL_MS : CATALOG_STATE_TTL_MS;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function sameRequestCatalogStateIdentity(
|
|
746
|
+
left: RequestCatalogStateIdentity,
|
|
747
|
+
right: RequestCatalogStateIdentity,
|
|
748
|
+
): boolean {
|
|
749
|
+
return left.platform === right.platform
|
|
750
|
+
&& left.listSnapshots === right.listSnapshots
|
|
751
|
+
&& left.listSnapshotsAsync === right.listSnapshotsAsync
|
|
752
|
+
&& left.readStartMs === right.readStartMs
|
|
753
|
+
&& left.readStartMsBatchAsync === right.readStartMsBatchAsync
|
|
754
|
+
&& left.catalogMtimeMs === right.catalogMtimeMs
|
|
755
|
+
&& left.now === right.now;
|
|
756
|
+
}
|
|
559
757
|
|
|
560
758
|
/**
|
|
561
759
|
* Compare the on-disk catalog mtime against the start time of running Codex
|
|
@@ -580,7 +778,8 @@ export function collectCodexAppServerCatalogState(
|
|
|
580
778
|
const fullyDefault = !io.listSnapshots && !io.readStartMs && !io.catalogMtimeMs
|
|
581
779
|
&& !io.platform && !io.getuid && !io.now;
|
|
582
780
|
if (fullyDefault
|
|
583
|
-
&& catalogStateCache
|
|
781
|
+
&& catalogStateCache
|
|
782
|
+
&& now - catalogStateCache.atMs < catalogStateTtlMs(catalogStateCache.status.state)) {
|
|
584
783
|
return catalogStateCache.status;
|
|
585
784
|
}
|
|
586
785
|
const compute = (): CodexAppServerCatalogStatus => {
|
|
@@ -594,45 +793,28 @@ export function collectCodexAppServerCatalogState(
|
|
|
594
793
|
});
|
|
595
794
|
let snapshots: ProcessSnapshot[];
|
|
596
795
|
let enumerationFailed = false;
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
const processes: CodexAppServerProcess[] = [];
|
|
610
|
-
const seen = new Set<number>();
|
|
611
|
-
for (const snapshot of snapshots) {
|
|
612
|
-
if (seen.has(snapshot.pid)) continue;
|
|
613
|
-
if (!isCodexAppServerCommandLine(snapshot.commandLine)) continue;
|
|
614
|
-
seen.add(snapshot.pid);
|
|
615
|
-
processes.push({ pid: snapshot.pid, commandLine: snapshot.commandLine });
|
|
796
|
+
const enumerate = io.listSnapshots ?? (() => defaultListSnapshots(platform, getuid));
|
|
797
|
+
try {
|
|
798
|
+
snapshots = enumerate();
|
|
799
|
+
} catch {
|
|
800
|
+
// Enumeration failure must never read as "nothing running" — that would let
|
|
801
|
+
// positive model guidance through on guesswork (#857). The injected seam gets
|
|
802
|
+
// the same contract as the default path: whoever enumerates, a failure to read
|
|
803
|
+
// the process list is unknown, not an empty machine.
|
|
804
|
+
snapshots = [];
|
|
805
|
+
enumerationFailed = true;
|
|
616
806
|
}
|
|
807
|
+
const processes = codexAppServerProcessesFromSnapshots(snapshots);
|
|
617
808
|
if (processes.length === 0) {
|
|
618
809
|
return enumerationFailed
|
|
619
810
|
? { state: "unknown", processes: [], catalogMtimeMs: null }
|
|
620
811
|
: { state: "not_running", processes: [], catalogMtimeMs: null };
|
|
621
812
|
}
|
|
622
813
|
const catalogMtimeMs = (io.catalogMtimeMs ?? defaultCatalogMtimeMs)();
|
|
623
|
-
const
|
|
624
|
-
? processes.map(proc =>
|
|
625
|
-
: ((
|
|
626
|
-
|
|
627
|
-
return processes.map(proc => ({ pid: proc.pid, startedAtMs: batch.get(proc.pid) ?? null }));
|
|
628
|
-
})();
|
|
629
|
-
if (catalogMtimeMs === null || withStarts.some(proc => proc.startedAtMs === null)) {
|
|
630
|
-
return { state: "unknown", processes: withStarts, catalogMtimeMs };
|
|
631
|
-
}
|
|
632
|
-
// `<=` is deliberate: coarse clocks (ps lstart is second-granularity) can
|
|
633
|
-
// report equal values when the catalog actually changed after startup.
|
|
634
|
-
const stale = withStarts.some(proc => proc.startedAtMs! <= catalogMtimeMs);
|
|
635
|
-
return { state: stale ? "stale" : "fresh", processes: withStarts, catalogMtimeMs };
|
|
814
|
+
const starts = io.readStartMs
|
|
815
|
+
? new Map(processes.map(proc => [proc.pid, io.readStartMs!(proc.pid)] as const))
|
|
816
|
+
: readProcessStartMsBatch(processes.map(proc => proc.pid), platform);
|
|
817
|
+
return catalogStatusFromProcesses(processes, catalogMtimeMs, starts);
|
|
636
818
|
};
|
|
637
819
|
const status = compute();
|
|
638
820
|
if (fullyDefault) {
|
|
@@ -641,9 +823,129 @@ export function collectCodexAppServerCatalogState(
|
|
|
641
823
|
return status;
|
|
642
824
|
}
|
|
643
825
|
|
|
644
|
-
/**
|
|
826
|
+
/**
|
|
827
|
+
* Request-path catalog state collector.
|
|
828
|
+
*
|
|
829
|
+
* [Decision Log]
|
|
830
|
+
* - 목적과 의도: keep Windows CIM discovery from blocking Bun's event loop while v2 guidance is built.
|
|
831
|
+
* - 기존 구현 및 제약 조건: CLI/service operations still need the synchronous, fail-closed collector; the request path needs only advisory state.
|
|
832
|
+
* - 검토한 주요 대안: remove stale-catalog guidance, move all process work to workers, or add a Windows-only async boundary.
|
|
833
|
+
* - 선택한 방식: retain the synchronous API and use async PowerShell plus an identity-scoped in-flight refresh, short cache, and invalidation generation only for Windows requests.
|
|
834
|
+
* - 다른 대안 대신 이 방식을 선택한 이유: it fixes unrelated `/healthz` starvation without widening the process-matching or restart contract.
|
|
835
|
+
* - 장점, 단점 및 영향: concurrent turns share one CIM walk, invalidated pre-write results cannot repopulate the cache, and the event loop stays responsive; a cold v2 turn can still await the bounded advisory probe.
|
|
836
|
+
*/
|
|
837
|
+
export async function collectCodexAppServerCatalogStateForRequest(
|
|
838
|
+
io: CodexAppServerProcessIo = {},
|
|
839
|
+
): Promise<CodexAppServerCatalogStatus> {
|
|
840
|
+
const platform = io.platform ?? process.platform;
|
|
841
|
+
if (platform !== "win32") return collectCodexAppServerCatalogState(io);
|
|
842
|
+
|
|
843
|
+
const now = (io.now ?? Date.now)();
|
|
844
|
+
const generation = requestCatalogStateGeneration;
|
|
845
|
+
const identity: RequestCatalogStateIdentity = {
|
|
846
|
+
platform,
|
|
847
|
+
listSnapshots: io.listSnapshots,
|
|
848
|
+
listSnapshotsAsync: io.listSnapshotsAsync,
|
|
849
|
+
readStartMs: io.readStartMs,
|
|
850
|
+
readStartMsBatchAsync: io.readStartMsBatchAsync,
|
|
851
|
+
catalogMtimeMs: io.catalogMtimeMs,
|
|
852
|
+
now: io.now,
|
|
853
|
+
};
|
|
854
|
+
if (requestCatalogStateCache
|
|
855
|
+
&& requestCatalogStateCache.generation === generation
|
|
856
|
+
&& sameRequestCatalogStateIdentity(requestCatalogStateCache.identity, identity)
|
|
857
|
+
&& now - requestCatalogStateCache.atMs < catalogStateTtlMs(requestCatalogStateCache.status.state)) {
|
|
858
|
+
return requestCatalogStateCache.status;
|
|
859
|
+
}
|
|
860
|
+
if (requestCatalogStateFlight
|
|
861
|
+
&& requestCatalogStateFlight.generation === generation
|
|
862
|
+
&& sameRequestCatalogStateIdentity(requestCatalogStateFlight.identity, identity)) {
|
|
863
|
+
return requestCatalogStateFlight.promise;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const refresh = async (): Promise<CodexAppServerCatalogStatus> => {
|
|
867
|
+
let snapshots: ProcessSnapshot[];
|
|
868
|
+
try {
|
|
869
|
+
snapshots = io.listSnapshotsAsync
|
|
870
|
+
? await io.listSnapshotsAsync()
|
|
871
|
+
: io.listSnapshots
|
|
872
|
+
? io.listSnapshots()
|
|
873
|
+
: await listWindowsSnapshotsAsync();
|
|
874
|
+
} catch {
|
|
875
|
+
return { state: "unknown", processes: [], catalogMtimeMs: null };
|
|
876
|
+
}
|
|
877
|
+
const processes = codexAppServerProcessesFromSnapshots(snapshots);
|
|
878
|
+
if (processes.length === 0) {
|
|
879
|
+
return { state: "not_running", processes: [], catalogMtimeMs: null };
|
|
880
|
+
}
|
|
881
|
+
let catalogMtimeMs: number | null;
|
|
882
|
+
try {
|
|
883
|
+
catalogMtimeMs = (io.catalogMtimeMs ?? defaultCatalogMtimeMs)();
|
|
884
|
+
} catch {
|
|
885
|
+
catalogMtimeMs = null;
|
|
886
|
+
}
|
|
887
|
+
const pids = processes.map(proc => proc.pid);
|
|
888
|
+
const starts = io.readStartMsBatchAsync
|
|
889
|
+
? await io.readStartMsBatchAsync(pids)
|
|
890
|
+
: io.readStartMs
|
|
891
|
+
? new Map(pids.map(pid => [pid, io.readStartMs!(pid)] as const))
|
|
892
|
+
: await readWindowsProcessStartMsBatchAsync(pids);
|
|
893
|
+
return catalogStatusFromProcesses(processes, catalogMtimeMs, starts);
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
const pending = refresh().catch(() => ({
|
|
897
|
+
state: "unknown" as const,
|
|
898
|
+
processes: [],
|
|
899
|
+
catalogMtimeMs: null,
|
|
900
|
+
}));
|
|
901
|
+
let flight: RequestCatalogStateFlight;
|
|
902
|
+
const promise = pending.then(status => {
|
|
903
|
+
// A catalog write can invalidate while slow CIM is still running. The result
|
|
904
|
+
// describes the PRE-write world, so it must neither repopulate the post-write
|
|
905
|
+
// cache nor reach the caller.
|
|
906
|
+
//
|
|
907
|
+
// Suppressing only the cache write is not enough. The awaiting request still
|
|
908
|
+
// received `fresh`, and `fresh` is the one state that authorizes positive
|
|
909
|
+
// guidance (`src/server/responses/collaboration.ts:279-280` returns null for
|
|
910
|
+
// `stale`/`unknown` but describes the catalog for `fresh`). So the request
|
|
911
|
+
// would advertise the newly written disk catalog to an app-server whose
|
|
912
|
+
// in-memory copy the write just made stale — a wrong answer, which is worse
|
|
913
|
+
// than the slow answer this whole change exists to fix.
|
|
914
|
+
//
|
|
915
|
+
// Degrade to `unknown` instead: it is the honest description of what an
|
|
916
|
+
// invalidated observation knows, and the guidance path already treats it as
|
|
917
|
+
// "say nothing positive".
|
|
918
|
+
if (requestCatalogStateGeneration !== generation) {
|
|
919
|
+
return { state: "unknown" as const, processes: [], catalogMtimeMs: null };
|
|
920
|
+
}
|
|
921
|
+
if (requestCatalogStateFlight === flight) {
|
|
922
|
+
requestCatalogStateCache = {
|
|
923
|
+
generation,
|
|
924
|
+
identity,
|
|
925
|
+
atMs: (io.now ?? Date.now)(),
|
|
926
|
+
status,
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
return status;
|
|
930
|
+
}).finally(() => {
|
|
931
|
+
if (requestCatalogStateFlight === flight) requestCatalogStateFlight = null;
|
|
932
|
+
});
|
|
933
|
+
flight = { generation, identity, promise };
|
|
934
|
+
requestCatalogStateFlight = flight;
|
|
935
|
+
return flight.promise;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Drop memoized catalog state after a relevant catalog/cache write and before
|
|
940
|
+
* the post-write state read. Advancing the generation prevents an older
|
|
941
|
+
* in-flight Windows CIM refresh from publishing its pre-write result after the
|
|
942
|
+
* write has completed.
|
|
943
|
+
*/
|
|
645
944
|
export function resetCodexAppServerCatalogStateCache(): void {
|
|
646
945
|
catalogStateCache = null;
|
|
946
|
+
requestCatalogStateGeneration += 1;
|
|
947
|
+
requestCatalogStateCache = null;
|
|
948
|
+
requestCatalogStateFlight = null;
|
|
647
949
|
}
|
|
648
950
|
|
|
649
951
|
export interface RestartCodexAppServersResult {
|
|
@@ -653,13 +955,57 @@ export interface RestartCodexAppServersResult {
|
|
|
653
955
|
failed: Array<{ pid: number; error: string }>;
|
|
654
956
|
}
|
|
655
957
|
|
|
958
|
+
/**
|
|
959
|
+
* Platform-appropriate termination for a matched app-server.
|
|
960
|
+
*
|
|
961
|
+
* On Windows `process.kill(pid, "SIGTERM")` is not a graceful signal — it is an
|
|
962
|
+
* unconditional terminate of that one process, and it leaves the process tree
|
|
963
|
+
* behind. `taskkill /T /F` is therefore not an escalation there: the kill was
|
|
964
|
+
* hard either way, and `/T` adds the child cleanup that keeps an app-server's
|
|
965
|
+
* children from being orphaned when the window is closed without a quit
|
|
966
|
+
* affordance. That matters most on Windows precisely because there is no Ctrl+Q.
|
|
967
|
+
*
|
|
968
|
+
* The asymmetry with Unix is deliberate and must not be "fixed" into symmetry:
|
|
969
|
+
* on Unix, SIGTERM really is graceful, and following it with SIGKILL would ask a
|
|
970
|
+
* harsher consent than a restart click gives. Survivors are reported instead.
|
|
971
|
+
*
|
|
972
|
+
* The executable is resolved from a trusted system directory rather than PATH,
|
|
973
|
+
* matching how this file already resolves PowerShell — an unqualified
|
|
974
|
+
* `taskkill` is a hijack surface.
|
|
975
|
+
*/
|
|
976
|
+
function defaultKillCodexAppServer(
|
|
977
|
+
pid: number,
|
|
978
|
+
signal: NodeJS.Signals,
|
|
979
|
+
io: CodexAppServerProcessIo = {},
|
|
980
|
+
): void {
|
|
981
|
+
const platform = io.platform ?? process.platform;
|
|
982
|
+
const signalProcess = io.processKill ?? ((target: number, sig: NodeJS.Signals) => {
|
|
983
|
+
process.kill(target, sig);
|
|
984
|
+
});
|
|
985
|
+
if (platform !== "win32") {
|
|
986
|
+
signalProcess(pid, signal);
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
const exec = io.execFile ?? ((file: string, args: readonly string[]) => {
|
|
990
|
+
execFileSync(file, [...args], { stdio: "ignore", timeout: 5_000, windowsHide: true });
|
|
991
|
+
});
|
|
992
|
+
try {
|
|
993
|
+
exec(resolveTrustedWindowsTaskkillExe(), ["/PID", String(pid), "/T", "/F"]);
|
|
994
|
+
} catch {
|
|
995
|
+
// Fall back to the previous behavior rather than reporting a failure the old
|
|
996
|
+
// code would not have reported.
|
|
997
|
+
signalProcess(pid, signal);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
|
|
656
1002
|
/** Send SIGTERM to matched processes and wait briefly; never escalates to SIGKILL. */
|
|
657
1003
|
export function restartCodexAppServers(
|
|
658
1004
|
processes: readonly CodexAppServerProcess[] = listCodexAppServerProcesses(),
|
|
659
1005
|
io: CodexAppServerProcessIo = {},
|
|
660
1006
|
): RestartCodexAppServersResult {
|
|
661
1007
|
const isAlive = io.isAlive ?? isProcessAlive;
|
|
662
|
-
const kill = io.kill ?? ((pid, signal) => {
|
|
1008
|
+
const kill = io.kill ?? ((pid, signal) => { defaultKillCodexAppServer(pid, signal, io); });
|
|
663
1009
|
const wait = io.waitExit ?? waitForExit;
|
|
664
1010
|
const now = io.now ?? Date.now;
|
|
665
1011
|
const requested = processes.map(process => process.pid);
|
|
@@ -754,3 +1100,44 @@ export function afterCatalogWriteHandleAppServers(
|
|
|
754
1100
|
}
|
|
755
1101
|
return { processes, warned: false, restart, hint };
|
|
756
1102
|
}
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* Startup-safe counterpart to {@link afterCatalogWriteHandleAppServers} (#1046).
|
|
1106
|
+
*
|
|
1107
|
+
* Service startup rewrites the catalog and the models cache, but an app-server
|
|
1108
|
+
* that booted earlier keeps an in-memory model list — Codex builds a static
|
|
1109
|
+
* manager from the catalog once and never rereads the file — so the picker shows
|
|
1110
|
+
* a roster that no longer exists on disk. Every check a user runs reads the file;
|
|
1111
|
+
* the picker renders memory.
|
|
1112
|
+
*
|
|
1113
|
+
* Two things this deliberately does NOT do, both of which the `--restart-codex`
|
|
1114
|
+
* path does:
|
|
1115
|
+
*
|
|
1116
|
+
* - It never signals anything. Killing an app-server on an unattended boot would
|
|
1117
|
+
* interrupt whatever turn the user has in flight. A human typing
|
|
1118
|
+
* `ocx sync --restart-codex` is consenting to that; a login is not.
|
|
1119
|
+
* - It never warns about a merely-running app-server. It asks the mtime
|
|
1120
|
+
* classifier whether one is actually stale, so a boot with Codex open and a
|
|
1121
|
+
* current catalog stays quiet.
|
|
1122
|
+
*
|
|
1123
|
+
* Failure is swallowed: startup synchronization is best-effort and must not stop
|
|
1124
|
+
* the proxy from coming up.
|
|
1125
|
+
*
|
|
1126
|
+
* The memoized state is dropped first. {@link collectCodexAppServerCatalogState}
|
|
1127
|
+
* caches for 5s when every io field is defaulted, so a `fresh` reading taken
|
|
1128
|
+
* before the write would otherwise be replayed after it and this would stay
|
|
1129
|
+
* silent about the very staleness it exists to report.
|
|
1130
|
+
*/
|
|
1131
|
+
export function warnIfStaleCodexAppServersAfterStartupWrite(
|
|
1132
|
+
options: { log?: Pick<Console, "error">; io?: CodexAppServerProcessIo } = {},
|
|
1133
|
+
): { warned: boolean } {
|
|
1134
|
+
try {
|
|
1135
|
+
resetCodexAppServerCatalogStateCache();
|
|
1136
|
+
const status = collectCodexAppServerCatalogState(options.io ?? {});
|
|
1137
|
+
if (status.state !== "stale") return { warned: false };
|
|
1138
|
+
options.log?.error(formatStaleCodexAppServerWarning(status.processes));
|
|
1139
|
+
return { warned: true };
|
|
1140
|
+
} catch {
|
|
1141
|
+
return { warned: false };
|
|
1142
|
+
}
|
|
1143
|
+
}
|