@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
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {
|
|
2
|
+
closeSync,
|
|
3
|
+
constants as fsConstants,
|
|
4
|
+
fstatSync,
|
|
5
|
+
fsyncSync,
|
|
6
|
+
lstatSync,
|
|
7
|
+
openSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
readdirSync,
|
|
10
|
+
unlinkSync,
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { basename, dirname, join } from "node:path";
|
|
13
|
+
import { isPrivateFileStageName } from "./private-file";
|
|
14
|
+
import { PublicEvidenceValidationError } from "./validate";
|
|
15
|
+
|
|
16
|
+
const O_NOFOLLOW = (fsConstants as { O_NOFOLLOW?: number }).O_NOFOLLOW ?? 0;
|
|
17
|
+
|
|
18
|
+
export interface PrivateRegularFileReadOptions {
|
|
19
|
+
maxBytes: number;
|
|
20
|
+
errorCode: string;
|
|
21
|
+
errorMessage: string;
|
|
22
|
+
sizeErrorCode?: string;
|
|
23
|
+
sizeErrorMessage?: string;
|
|
24
|
+
requireMode600?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function sizeError(options: PrivateRegularFileReadOptions): PublicEvidenceValidationError {
|
|
28
|
+
return new PublicEvidenceValidationError(
|
|
29
|
+
options.sizeErrorCode ?? options.errorCode,
|
|
30
|
+
options.sizeErrorMessage ?? options.errorMessage,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Heal only the publication-specific hard link left behind when the final name was linked
|
|
36
|
+
* but the parent-directory durability check failed. The stage must be target-scoped and
|
|
37
|
+
* inode-identical to the final file; unrelated hard links remain and are rejected below.
|
|
38
|
+
*/
|
|
39
|
+
function recoverPublishedPrivateFileStage(path: string): void {
|
|
40
|
+
if (process.platform === "win32") return;
|
|
41
|
+
const finalStats = lstatSync(path);
|
|
42
|
+
if (finalStats.isSymbolicLink() || !finalStats.isFile() || finalStats.nlink <= 1) return;
|
|
43
|
+
|
|
44
|
+
const dir = dirname(path);
|
|
45
|
+
const prefix = `.${basename(path)}.`;
|
|
46
|
+
const candidates: string[] = [];
|
|
47
|
+
for (const name of readdirSync(dir)) {
|
|
48
|
+
if (!name.startsWith(prefix) || !isPrivateFileStageName(name)) continue;
|
|
49
|
+
const stagePath = join(dir, name);
|
|
50
|
+
try {
|
|
51
|
+
const stageStats = lstatSync(stagePath);
|
|
52
|
+
if (
|
|
53
|
+
stageStats.isFile()
|
|
54
|
+
&& !stageStats.isSymbolicLink()
|
|
55
|
+
&& stageStats.dev === finalStats.dev
|
|
56
|
+
&& stageStats.ino === finalStats.ino
|
|
57
|
+
) {
|
|
58
|
+
candidates.push(stagePath);
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (candidates.length === 0) return;
|
|
65
|
+
|
|
66
|
+
// The final directory entry already exists. Make that entry durable before deleting
|
|
67
|
+
// the recovery witness. A real fsync failure propagates and the strict read stays closed.
|
|
68
|
+
let dirFd: number | null = null;
|
|
69
|
+
try {
|
|
70
|
+
dirFd = openSync(dir, fsConstants.O_RDONLY);
|
|
71
|
+
fsyncSync(dirFd);
|
|
72
|
+
} finally {
|
|
73
|
+
if (dirFd !== null) closeSync(dirFd);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (const stagePath of candidates) {
|
|
77
|
+
try {
|
|
78
|
+
const stageStats = lstatSync(stagePath);
|
|
79
|
+
if (
|
|
80
|
+
stageStats.isFile()
|
|
81
|
+
&& !stageStats.isSymbolicLink()
|
|
82
|
+
&& stageStats.dev === finalStats.dev
|
|
83
|
+
&& stageStats.ino === finalStats.ino
|
|
84
|
+
) {
|
|
85
|
+
unlinkSync(stagePath);
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function withPrivateRegularFile<T>(
|
|
94
|
+
path: string,
|
|
95
|
+
options: PrivateRegularFileReadOptions,
|
|
96
|
+
consume: (fd: number, size: number) => T,
|
|
97
|
+
): T {
|
|
98
|
+
let pathStats = lstatSync(path);
|
|
99
|
+
if (!pathStats.isSymbolicLink() && pathStats.isFile() && pathStats.nlink > 1) {
|
|
100
|
+
recoverPublishedPrivateFileStage(path);
|
|
101
|
+
pathStats = lstatSync(path);
|
|
102
|
+
}
|
|
103
|
+
if (pathStats.isSymbolicLink() || !pathStats.isFile() || pathStats.nlink !== 1) {
|
|
104
|
+
throw new PublicEvidenceValidationError(options.errorCode, options.errorMessage);
|
|
105
|
+
}
|
|
106
|
+
if (pathStats.size > options.maxBytes) throw sizeError(options);
|
|
107
|
+
|
|
108
|
+
const fd = openSync(path, fsConstants.O_RDONLY | O_NOFOLLOW);
|
|
109
|
+
try {
|
|
110
|
+
const stats = fstatSync(fd);
|
|
111
|
+
if (
|
|
112
|
+
!stats.isFile()
|
|
113
|
+
|| stats.nlink !== 1
|
|
114
|
+
|| stats.dev !== pathStats.dev
|
|
115
|
+
|| stats.ino !== pathStats.ino
|
|
116
|
+
) {
|
|
117
|
+
throw new PublicEvidenceValidationError(options.errorCode, options.errorMessage);
|
|
118
|
+
}
|
|
119
|
+
if (stats.size > options.maxBytes) throw sizeError(options);
|
|
120
|
+
if (options.requireMode600 && process.platform !== "win32" && (stats.mode & 0o777) !== 0o600) {
|
|
121
|
+
throw new PublicEvidenceValidationError(options.errorCode, options.errorMessage);
|
|
122
|
+
}
|
|
123
|
+
return consume(fd, stats.size);
|
|
124
|
+
} finally {
|
|
125
|
+
closeSync(fd);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Inspect a file only after proving that the pathname and checked descriptor refer to
|
|
131
|
+
* the same private regular file. This keeps quota scans descriptor-bound without
|
|
132
|
+
* reading every cached object into memory.
|
|
133
|
+
*/
|
|
134
|
+
export function privateRegularFileSize(
|
|
135
|
+
path: string,
|
|
136
|
+
options: PrivateRegularFileReadOptions,
|
|
137
|
+
): number {
|
|
138
|
+
return withPrivateRegularFile(path, options, (_fd, size) => size);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Read bytes only after proving that the pathname and the consumed descriptor refer to
|
|
143
|
+
* the same private regular file. The lstat/dev+ino comparison keeps the protection on
|
|
144
|
+
* platforms where O_NOFOLLOW is unavailable instead of silently following a symlink.
|
|
145
|
+
*/
|
|
146
|
+
export function readPrivateRegularFile(
|
|
147
|
+
path: string,
|
|
148
|
+
options: PrivateRegularFileReadOptions,
|
|
149
|
+
): Buffer {
|
|
150
|
+
return withPrivateRegularFile(path, options, (fd) => {
|
|
151
|
+
const bytes = readFileSync(fd);
|
|
152
|
+
if (bytes.byteLength > options.maxBytes) throw sizeError(options);
|
|
153
|
+
return bytes;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { domainHash, jcsStringify } from "../digest";
|
|
2
|
+
|
|
3
|
+
export type PublicEvidenceIdKind =
|
|
4
|
+
| "subject"
|
|
5
|
+
| "record"
|
|
6
|
+
| "bundle"
|
|
7
|
+
| "bundle_digest"
|
|
8
|
+
| "artifact"
|
|
9
|
+
| "publisher_key"
|
|
10
|
+
| "revocation"
|
|
11
|
+
| "route_registry";
|
|
12
|
+
|
|
13
|
+
const PUBLIC_EVIDENCE_DOMAIN: Record<PublicEvidenceIdKind, string> = {
|
|
14
|
+
subject: "ocx-lab-public:subject:v1",
|
|
15
|
+
record: "ocx-lab-public:record:v1",
|
|
16
|
+
bundle: "ocx-lab-public:bundle:v1",
|
|
17
|
+
bundle_digest: "ocx-lab-public:bundle-digest:v1",
|
|
18
|
+
artifact: "ocx-lab-public:artifact:v1",
|
|
19
|
+
publisher_key: "ocx-lab-public:publisher-key:v1",
|
|
20
|
+
revocation: "ocx-lab-public:revocation:v1",
|
|
21
|
+
route_registry: "ocx-lab-public:route-registry:v1",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function publicEvidenceId(kind: PublicEvidenceIdKind, payload: unknown): string {
|
|
25
|
+
return domainHash(PUBLIC_EVIDENCE_DOMAIN[kind], jcsStringify(payload));
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from "./types";
|
|
2
|
+
export * from "./ids";
|
|
3
|
+
export * from "./registry";
|
|
4
|
+
export * from "./validate";
|
|
5
|
+
export * from "./privacy";
|
|
6
|
+
export * from "./project";
|
|
7
|
+
export * from "./bundle";
|
|
8
|
+
export * from "./signature";
|
|
9
|
+
export * from "./storage";
|
|
10
|
+
export * from "./community-authority";
|
|
11
|
+
export * from "./revocation";
|
|
12
|
+
export * from "./community";
|
|
13
|
+
export * from "./strict-json";
|
|
14
|
+
export * from "./origin";
|
|
15
|
+
export * from "./operator";
|
|
16
|
+
export * from "./purge";
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
lstatSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readdirSync,
|
|
6
|
+
renameSync,
|
|
7
|
+
rmSync,
|
|
8
|
+
unlinkSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
import { ensureLabDirs, labCommunityDir } from "../paths";
|
|
13
|
+
import { readPrivateRegularFile } from "./file-safety";
|
|
14
|
+
import { PublicEvidenceValidationError } from "./validate";
|
|
15
|
+
|
|
16
|
+
const PUBLIC_EVIDENCE_MUTATION_LOCK_NAME = ".mutation-lock";
|
|
17
|
+
const PUBLIC_EVIDENCE_MUTATION_LOCK_OWNER = "owner.json";
|
|
18
|
+
const PUBLIC_EVIDENCE_MUTATION_LOCK_RECLAIM = ".reclaim.json";
|
|
19
|
+
const PUBLIC_EVIDENCE_MUTATION_LOCK_INCOMPLETE_STALE_MS = 24 * 60 * 60 * 1000;
|
|
20
|
+
const PUBLIC_EVIDENCE_MUTATION_LOCK_ABSOLUTE_STALE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
21
|
+
const DETACHED_MUTATION_LOCK_RE = /^\.mutation-lock-(?:stale|release)-\d+-[0-9a-f-]{36}$/;
|
|
22
|
+
const MUTATION_LOCK_META_FILE_OPTIONS = {
|
|
23
|
+
maxBytes: 1024,
|
|
24
|
+
errorCode: "community_cache_lock",
|
|
25
|
+
errorMessage: "community cache mutation lock metadata is unsafe",
|
|
26
|
+
sizeErrorCode: "community_cache_lock",
|
|
27
|
+
sizeErrorMessage: "community cache mutation lock metadata exceeds its size bound",
|
|
28
|
+
requireMode600: true,
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
type MutationLockOwner = {
|
|
32
|
+
pid: number;
|
|
33
|
+
token: string;
|
|
34
|
+
createdAt: number;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type MutationLockReclaim = {
|
|
38
|
+
pid: number;
|
|
39
|
+
token: string;
|
|
40
|
+
createdAt: number;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type MutationLockDirectoryIdentity = {
|
|
44
|
+
dev: number;
|
|
45
|
+
ino: number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function mutationLockPath(configDir?: string): string {
|
|
49
|
+
return join(labCommunityDir(configDir), PUBLIC_EVIDENCE_MUTATION_LOCK_NAME);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function mutationLockOwnerPath(lockPath: string): string {
|
|
53
|
+
return join(lockPath, PUBLIC_EVIDENCE_MUTATION_LOCK_OWNER);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function mutationLockReclaimPath(lockPath: string): string {
|
|
57
|
+
return join(lockPath, PUBLIC_EVIDENCE_MUTATION_LOCK_RECLAIM);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function pidDefinitelyDead(pid: number): boolean {
|
|
61
|
+
try {
|
|
62
|
+
process.kill(pid, 0);
|
|
63
|
+
return false;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
return (error as NodeJS.ErrnoException).code === "ESRCH";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function exceedsAbsoluteLockAge(createdAt: number, nowMs: number): boolean {
|
|
70
|
+
return nowMs - createdAt > PUBLIC_EVIDENCE_MUTATION_LOCK_ABSOLUTE_STALE_MS;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function readLockMetadata<T extends MutationLockOwner | MutationLockReclaim>(
|
|
74
|
+
path: string,
|
|
75
|
+
): T | null {
|
|
76
|
+
try {
|
|
77
|
+
const bytes = readPrivateRegularFile(path, MUTATION_LOCK_META_FILE_OPTIONS);
|
|
78
|
+
const raw = JSON.parse(bytes.toString("utf8")) as Partial<T>;
|
|
79
|
+
if (
|
|
80
|
+
Number.isSafeInteger(raw.pid)
|
|
81
|
+
&& Number(raw.pid) > 0
|
|
82
|
+
&& typeof raw.token === "string"
|
|
83
|
+
&& /^[0-9a-f-]{36}$/.test(raw.token)
|
|
84
|
+
&& Number.isSafeInteger(raw.createdAt)
|
|
85
|
+
&& Number(raw.createdAt) > 0
|
|
86
|
+
) {
|
|
87
|
+
return {
|
|
88
|
+
pid: Number(raw.pid),
|
|
89
|
+
token: raw.token,
|
|
90
|
+
createdAt: Number(raw.createdAt),
|
|
91
|
+
} as T;
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
// Incomplete metadata is handled conservatively by the age fallback where
|
|
95
|
+
// applicable. Normal acquisition never trusts malformed metadata.
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function readMutationLockOwner(lockPath: string): MutationLockOwner | null {
|
|
101
|
+
return readLockMetadata<MutationLockOwner>(mutationLockOwnerPath(lockPath));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function readMutationLockReclaim(lockPath: string): MutationLockReclaim | null {
|
|
105
|
+
return readLockMetadata<MutationLockReclaim>(mutationLockReclaimPath(lockPath));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function assertMutationLockDirectory(lockPath: string) {
|
|
109
|
+
const stat = lstatSync(lockPath);
|
|
110
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
111
|
+
throw new PublicEvidenceValidationError(
|
|
112
|
+
"community_cache_lock",
|
|
113
|
+
"community cache mutation lock is not a directory",
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return stat;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function sameDirectoryIdentity(
|
|
120
|
+
left: MutationLockDirectoryIdentity,
|
|
121
|
+
right: MutationLockDirectoryIdentity,
|
|
122
|
+
): boolean {
|
|
123
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function currentDirectoryIdentity(lockPath: string): MutationLockDirectoryIdentity {
|
|
127
|
+
const stat = assertMutationLockDirectory(lockPath);
|
|
128
|
+
return { dev: stat.dev, ino: stat.ino };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function mutationLockIsReclaimable(lockPath: string, nowMs: number): boolean {
|
|
132
|
+
const stat = assertMutationLockDirectory(lockPath);
|
|
133
|
+
const owner = readMutationLockOwner(lockPath);
|
|
134
|
+
if (owner) {
|
|
135
|
+
// A live PID is strong evidence only while the recorded ownership generation is
|
|
136
|
+
// reasonably recent. The absolute ceiling recovers from PID reuse after a crash.
|
|
137
|
+
return pidDefinitelyDead(owner.pid) || exceedsAbsoluteLockAge(owner.createdAt, nowMs);
|
|
138
|
+
}
|
|
139
|
+
// The only ownerless state is the tiny mkdir-to-owner publication window. Use
|
|
140
|
+
// a deliberately long fallback so a crashed acquisition can eventually heal
|
|
141
|
+
// without treating an ordinary pause as proof that the owner disappeared.
|
|
142
|
+
return nowMs - stat.mtimeMs > PUBLIC_EVIDENCE_MUTATION_LOCK_INCOMPLETE_STALE_MS;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function unlinkIfPresent(path: string): void {
|
|
146
|
+
try {
|
|
147
|
+
unlinkSync(path);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function cleanupDetachedMutationLocks(lockPath: string): void {
|
|
154
|
+
const dir = dirname(lockPath);
|
|
155
|
+
for (const name of readdirSync(dir)) {
|
|
156
|
+
if (!DETACHED_MUTATION_LOCK_RE.test(name)) continue;
|
|
157
|
+
// Detached lock directories are no longer authoritative once a new canonical
|
|
158
|
+
// lock has been acquired. Removing only their UUID-scoped names prevents them
|
|
159
|
+
// from leaking storage or being mistaken for community cache objects.
|
|
160
|
+
rmSync(join(dir, name), { recursive: true, force: true });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function publishMutationLockOwner(
|
|
165
|
+
lockPath: string,
|
|
166
|
+
owner: MutationLockOwner,
|
|
167
|
+
expectedDirectory: MutationLockDirectoryIdentity,
|
|
168
|
+
): void {
|
|
169
|
+
// Write through a unique temporary pathname first. If an ancient ownerless
|
|
170
|
+
// lock is reclaimed while this process was suspended, the inode check prevents
|
|
171
|
+
// this acquisition from publishing its owner metadata into a replacement lock.
|
|
172
|
+
const tempPath = join(lockPath, `.owner-${owner.token}.tmp`);
|
|
173
|
+
try {
|
|
174
|
+
writeFileSync(tempPath, JSON.stringify(owner), {
|
|
175
|
+
encoding: "utf8",
|
|
176
|
+
flag: "wx",
|
|
177
|
+
mode: 0o600,
|
|
178
|
+
});
|
|
179
|
+
if (!sameDirectoryIdentity(currentDirectoryIdentity(lockPath), expectedDirectory)) {
|
|
180
|
+
throw new PublicEvidenceValidationError(
|
|
181
|
+
"community_cache_lock",
|
|
182
|
+
"community cache mutation lock changed during owner publication",
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
renameSync(tempPath, mutationLockOwnerPath(lockPath));
|
|
186
|
+
if (!sameDirectoryIdentity(currentDirectoryIdentity(lockPath), expectedDirectory)) {
|
|
187
|
+
throw new PublicEvidenceValidationError(
|
|
188
|
+
"community_cache_lock",
|
|
189
|
+
"community cache mutation lock changed after owner publication",
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
const persisted = readMutationLockOwner(lockPath);
|
|
193
|
+
if (!persisted || persisted.pid !== owner.pid || persisted.token !== owner.token) {
|
|
194
|
+
throw new PublicEvidenceValidationError(
|
|
195
|
+
"community_cache_lock",
|
|
196
|
+
"community cache mutation lock owner publication was not durable",
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
} finally {
|
|
200
|
+
// The rename normally makes this ENOENT. If the lock pathname was replaced,
|
|
201
|
+
// the UUID-scoped temporary name can be removed without touching successor state.
|
|
202
|
+
unlinkIfPresent(tempPath);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function reclaimClaimIsRecoverable(lockPath: string, nowMs: number): boolean {
|
|
207
|
+
const claimPath = mutationLockReclaimPath(lockPath);
|
|
208
|
+
const claim = readMutationLockReclaim(lockPath);
|
|
209
|
+
if (claim) {
|
|
210
|
+
return pidDefinitelyDead(claim.pid) || exceedsAbsoluteLockAge(claim.createdAt, nowMs);
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const stat = lstatSync(claimPath);
|
|
214
|
+
return nowMs - stat.mtimeMs > PUBLIC_EVIDENCE_MUTATION_LOCK_INCOMPLETE_STALE_MS;
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return true;
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function recoverStaleReclaimClaim(lockPath: string, nowMs: number): boolean {
|
|
222
|
+
if (!reclaimClaimIsRecoverable(lockPath, nowMs)) return false;
|
|
223
|
+
const claimPath = mutationLockReclaimPath(lockPath);
|
|
224
|
+
const quarantinePath = join(lockPath, `.reclaim-stale-${randomUUID()}.json`);
|
|
225
|
+
try {
|
|
226
|
+
renameSync(claimPath, quarantinePath);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return true;
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
unlinkIfPresent(quarantinePath);
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function tryAcquireReclaimClaim(
|
|
236
|
+
lockPath: string,
|
|
237
|
+
nowMs: number,
|
|
238
|
+
): MutationLockReclaim | null {
|
|
239
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
240
|
+
const claim: MutationLockReclaim = {
|
|
241
|
+
pid: process.pid,
|
|
242
|
+
token: randomUUID(),
|
|
243
|
+
createdAt: nowMs,
|
|
244
|
+
};
|
|
245
|
+
try {
|
|
246
|
+
writeFileSync(mutationLockReclaimPath(lockPath), JSON.stringify(claim), {
|
|
247
|
+
encoding: "utf8",
|
|
248
|
+
flag: "wx",
|
|
249
|
+
mode: 0o600,
|
|
250
|
+
});
|
|
251
|
+
return claim;
|
|
252
|
+
} catch (error) {
|
|
253
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
254
|
+
if (code === "ENOENT") return null;
|
|
255
|
+
if (code !== "EEXIST") throw error;
|
|
256
|
+
if (!recoverStaleReclaimClaim(lockPath, nowMs)) return null;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function reclaimClaimStillOwned(lockPath: string, claim: MutationLockReclaim): boolean {
|
|
263
|
+
const current = readMutationLockReclaim(lockPath);
|
|
264
|
+
return current?.pid === claim.pid && current.token === claim.token;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function releaseReclaimClaim(lockPath: string, claim: MutationLockReclaim): void {
|
|
268
|
+
if (!reclaimClaimStillOwned(lockPath, claim)) return;
|
|
269
|
+
unlinkIfPresent(mutationLockReclaimPath(lockPath));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function tryReclaimMutationLock(lockPath: string, nowMs: number): boolean {
|
|
273
|
+
// Capture the exact stale directory before the claim write changes its mtime.
|
|
274
|
+
// After claiming, revalidate the inode and owner instead of reusing an age check
|
|
275
|
+
// that our own .reclaim.json creation would make appear fresh.
|
|
276
|
+
if (!mutationLockIsReclaimable(lockPath, nowMs)) return false;
|
|
277
|
+
const staleDirectory = currentDirectoryIdentity(lockPath);
|
|
278
|
+
const claim = tryAcquireReclaimClaim(lockPath, nowMs);
|
|
279
|
+
if (!claim) return false;
|
|
280
|
+
|
|
281
|
+
let moved = false;
|
|
282
|
+
try {
|
|
283
|
+
if (!reclaimClaimStillOwned(lockPath, claim)) return false;
|
|
284
|
+
if (!sameDirectoryIdentity(currentDirectoryIdentity(lockPath), staleDirectory)) return false;
|
|
285
|
+
const currentOwner = readMutationLockOwner(lockPath);
|
|
286
|
+
if (
|
|
287
|
+
currentOwner
|
|
288
|
+
&& !pidDefinitelyDead(currentOwner.pid)
|
|
289
|
+
&& !exceedsAbsoluteLockAge(currentOwner.createdAt, nowMs)
|
|
290
|
+
) return false;
|
|
291
|
+
if (!reclaimClaimStillOwned(lockPath, claim)) return false;
|
|
292
|
+
|
|
293
|
+
const quarantinePath = join(
|
|
294
|
+
dirname(lockPath),
|
|
295
|
+
`.mutation-lock-stale-${process.pid}-${randomUUID()}`,
|
|
296
|
+
);
|
|
297
|
+
try {
|
|
298
|
+
// Rename the exact claimed directory away from the canonical pathname before
|
|
299
|
+
// deleting it. A successor can create a new lock immediately afterwards, but
|
|
300
|
+
// cleanup is confined to this unique quarantine path and cannot delete it.
|
|
301
|
+
renameSync(lockPath, quarantinePath);
|
|
302
|
+
moved = true;
|
|
303
|
+
} catch (error) {
|
|
304
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return true;
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
rmSync(quarantinePath, { recursive: true, force: true });
|
|
308
|
+
return true;
|
|
309
|
+
} finally {
|
|
310
|
+
if (!moved) releaseReclaimClaim(lockPath, claim);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function discardUncommittedMutationLock(
|
|
315
|
+
lockPath: string,
|
|
316
|
+
expectedDirectory: MutationLockDirectoryIdentity,
|
|
317
|
+
): void {
|
|
318
|
+
try {
|
|
319
|
+
if (!sameDirectoryIdentity(currentDirectoryIdentity(lockPath), expectedDirectory)) return;
|
|
320
|
+
const quarantinePath = join(
|
|
321
|
+
dirname(lockPath),
|
|
322
|
+
`.mutation-lock-release-${process.pid}-${randomUUID()}`,
|
|
323
|
+
);
|
|
324
|
+
renameSync(lockPath, quarantinePath);
|
|
325
|
+
rmSync(quarantinePath, { recursive: true, force: true });
|
|
326
|
+
} catch {
|
|
327
|
+
// Preserve the owner-publication error. An unrecoverable cleanup witness stays
|
|
328
|
+
// ownerless and can be reclaimed by the long incomplete-acquisition fallback.
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function releaseMutationLock(
|
|
333
|
+
lockPath: string,
|
|
334
|
+
owner: MutationLockOwner,
|
|
335
|
+
expectedDirectory: MutationLockDirectoryIdentity,
|
|
336
|
+
): void {
|
|
337
|
+
let currentDirectory: MutationLockDirectoryIdentity;
|
|
338
|
+
try {
|
|
339
|
+
currentDirectory = currentDirectoryIdentity(lockPath);
|
|
340
|
+
} catch (error) {
|
|
341
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return;
|
|
342
|
+
throw error;
|
|
343
|
+
}
|
|
344
|
+
if (!sameDirectoryIdentity(currentDirectory, expectedDirectory)) return;
|
|
345
|
+
const current = readMutationLockOwner(lockPath);
|
|
346
|
+
if (!current || current.pid !== owner.pid || current.token !== owner.token) return;
|
|
347
|
+
|
|
348
|
+
const quarantinePath = join(
|
|
349
|
+
dirname(lockPath),
|
|
350
|
+
`.mutation-lock-release-${process.pid}-${randomUUID()}`,
|
|
351
|
+
);
|
|
352
|
+
try {
|
|
353
|
+
renameSync(lockPath, quarantinePath);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return;
|
|
356
|
+
throw error;
|
|
357
|
+
}
|
|
358
|
+
rmSync(quarantinePath, { recursive: true, force: true });
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Serialize public-evidence mutations across CLI/server processes with ownership-safe stale recovery. */
|
|
362
|
+
export function withPublicEvidenceMutationLock<T>(
|
|
363
|
+
configDir: string | undefined,
|
|
364
|
+
run: () => T,
|
|
365
|
+
): T {
|
|
366
|
+
ensureLabDirs(configDir);
|
|
367
|
+
const lockPath = mutationLockPath(configDir);
|
|
368
|
+
let owner: MutationLockOwner | null = null;
|
|
369
|
+
let ownedDirectory: MutationLockDirectoryIdentity | null = null;
|
|
370
|
+
|
|
371
|
+
while (true) {
|
|
372
|
+
try {
|
|
373
|
+
mkdirSync(lockPath, { mode: 0o700 });
|
|
374
|
+
} catch (error) {
|
|
375
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
376
|
+
try {
|
|
377
|
+
if (tryReclaimMutationLock(lockPath, Date.now())) continue;
|
|
378
|
+
} catch (reclaimError) {
|
|
379
|
+
if ((reclaimError as NodeJS.ErrnoException).code === "ENOENT") continue;
|
|
380
|
+
throw reclaimError;
|
|
381
|
+
}
|
|
382
|
+
throw new PublicEvidenceValidationError("community_cache_busy", "community cache is busy");
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const directory = currentDirectoryIdentity(lockPath);
|
|
386
|
+
const candidate: MutationLockOwner = {
|
|
387
|
+
pid: process.pid,
|
|
388
|
+
token: randomUUID(),
|
|
389
|
+
createdAt: Date.now(),
|
|
390
|
+
};
|
|
391
|
+
try {
|
|
392
|
+
publishMutationLockOwner(lockPath, candidate, directory);
|
|
393
|
+
} catch (error) {
|
|
394
|
+
discardUncommittedMutationLock(lockPath, directory);
|
|
395
|
+
throw error;
|
|
396
|
+
}
|
|
397
|
+
owner = candidate;
|
|
398
|
+
ownedDirectory = directory;
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
cleanupDetachedMutationLocks(lockPath);
|
|
404
|
+
return run();
|
|
405
|
+
} finally {
|
|
406
|
+
if (owner && ownedDirectory) releaseMutationLock(lockPath, owner, ownedDirectory);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** Test-only seam for stale-owner policy. This module is not barrel-exported. */
|
|
411
|
+
export function publicEvidenceMutationLockIsReclaimableForTests(
|
|
412
|
+
configDir: string | undefined,
|
|
413
|
+
nowMs = Date.now(),
|
|
414
|
+
): boolean {
|
|
415
|
+
return mutationLockIsReclaimable(mutationLockPath(configDir), nowMs);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** Test-only seam for the exclusive stale-reclaimer claim. */
|
|
419
|
+
export function publicEvidenceTryReclaimMutationLockForTests(
|
|
420
|
+
configDir: string | undefined,
|
|
421
|
+
nowMs = Date.now(),
|
|
422
|
+
): boolean {
|
|
423
|
+
return tryReclaimMutationLock(mutationLockPath(configDir), nowMs);
|
|
424
|
+
}
|