@vtxmacro/cli 2026.8.12 → 2026.8.13

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.
Files changed (3) hide show
  1. package/README.md +34 -0
  2. package/bin/vtx.js +488 -97
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -47,6 +47,15 @@ credential.
47
47
 
48
48
  ### Automated Codex host
49
49
 
50
+ Before the first Codex login, enable **Device code authorization for Codex** in
51
+ ChatGPT Security settings. Only enter a device code from a login you initiated,
52
+ and never share it. The automated host advertises `gpt-5.6-sol` with
53
+ `medium`, `high`, and `xhigh` reasoning effort. Before starting a Trader,
54
+ confirm that the VTX AI page shows the intended authenticated ChatGPT email and
55
+ plan. The host reads the live Codex account window: a reached limit pauses new
56
+ dispatch until its reported reset, while a transient throttle uses a short
57
+ bounded cooldown.
58
+
50
59
  ```bash
51
60
  vtx inference-host login
52
61
  vtx inference-host codex-login
@@ -63,6 +72,21 @@ OAuth grant. It also keeps its ChatGPT subscription login in a dedicated private
63
72
  Codex home. Neither credential is copied from or widens the ordinary CLI or Codex
64
73
  session. VTX logout/revoke and Codex logout are intentionally separate operations.
65
74
 
75
+ Use `vtx inference-host --help` for command discovery. The VTX login verifies
76
+ that the selected credential store can read back the exact saved grant. If an
77
+ OS credential store such as Windows Credential Manager cannot retain it, VTX
78
+ revokes the new remote grant and fails with an actionable error. Retry with the
79
+ supported private-file store by setting
80
+ `VTX_INFERENCE_HOST_CREDENTIAL_STORE=file` before running
81
+ `vtx inference-host login` again. In PowerShell use
82
+ `$env:VTX_INFERENCE_HOST_CREDENTIAL_STORE="file"`; in bash use
83
+ `export VTX_INFERENCE_HOST_CREDENTIAL_STORE=file`.
84
+
85
+ The browser callback shows a VTX authorization-complete page. If a browser
86
+ extension instead shows `ERR_BLOCKED_BY_CLIENT` but the CLI reports
87
+ `logged_in` and `doctor` reports `credential-present`, only the local
88
+ confirmation page was blocked; the grant succeeded.
89
+
66
90
  ### Any compatible agent harness
67
91
 
68
92
  Codex, Claude Code, Cursor, GitHub Copilot, Antigravity, and other agents that can
@@ -108,6 +132,12 @@ and normally running VTX Client Mode or Server Mode bots. Server Mode keeps the
108
132
  normal VTX call fee. Client Mode has no VTX platform fee. Neither mode falls back
109
133
  to another model when the agent host is unavailable.
110
134
 
135
+ After any approved Trader start, use `vtx --profile <profile-id> bots status`
136
+ to confirm the normal Trader remains running. If it returns to Stopped, use
137
+ `vtx --profile <profile-id> runtime events` to read the latest error before one
138
+ controlled retry. A start receipt or a brief Live label is not proof of a
139
+ running bot.
140
+
111
141
  ## Remove
112
142
 
113
143
  First stop the foreground run or agent-run process, revoke the VTX inference
@@ -121,3 +151,7 @@ vtx inference-host revoke
121
151
  vtx inference-host codex-logout
122
152
  npm uninstall -g @vtxmacro/cli
123
153
  ```
154
+
155
+ If an older failed login left no local credential to revoke, disconnect that
156
+ inactive inference grant from Connected Agent Apps on the VTX Insights page
157
+ before logging in again.
package/bin/vtx.js CHANGED
@@ -38,7 +38,7 @@ var init_agent_cli_release = __esm({
38
38
  "agent-cli-release.json"() {
39
39
  agent_cli_release_default = {
40
40
  package_name: "@vtxmacro/cli",
41
- package_version: "2026.8.12",
41
+ package_version: "2026.8.13",
42
42
  codex_package_name: "@openai/codex",
43
43
  codex_version: "0.147.0",
44
44
  platforms: {
@@ -14375,7 +14375,7 @@ var init_zod = __esm({
14375
14375
  });
14376
14376
 
14377
14377
  // lib/external-inference-contract.ts
14378
- var EXTERNAL_INFERENCE_CONTRACT_VERSION, EXTERNAL_INFERENCE_CONTROLLER_SCHEMA_VERSION, EXTERNAL_INFERENCE_ADVERTISEMENT_SCHEMA_VERSION, EXTERNAL_INFERENCE_ENVELOPE_SCHEMA_VERSION, EXTERNAL_INFERENCE_JOB_INPUT_SCHEMA_VERSION, EXTERNAL_INFERENCE_USAGE_SCHEMA_VERSION, EXTERNAL_INFERENCE_INPUT_HASH_DOMAIN, MAX_EXTERNAL_INFERENCE_STATUS_HOSTS, MAX_GENERATION, MAX_SAFE_INTEGER, MAX_PROMPT_BYTES, MAX_IMMUTABLE_JOB_INPUT_BYTES, MAX_CANDIDATE_PLAINTEXT_BYTES, MAX_CIPHERTEXT_CHARS, SHA256_INITIAL_STATE, SHA256_ROUND_CONSTANTS, rotateRight, sha256BytesHex, sha256Utf8Hex, computeImmutableJobInputSha256, identifierSchema, operationIdentifierSchema, sha256HexSchema, generationSchema, zeroBasedGenerationSchema, nonNegativeSafeIntegerSchema, positiveSafeIntegerSchema, sqlIntegerTokenCountSchema, timestampSchema, safeCodeSchema, externalInferenceAdapterIdSchema, reasoningEffortSchema, modelNameSchema, displayNameSchema, protocolVersionSchema, base64UrlAlphabet, isCanonicalBase64Url, base64Url32BytesSchema, base64UrlNonceSchema, base64UrlCiphertextSchema, claimHandleSchema, boundedJsonTextSchema, boundedPromptTextSchema, laneSchema, botModeSchema, executionModeSchema, responseModeSchema, dispatchOutcomeSchema, responseOutcomeSchema, nativeModelControllerSchema, externalAgentModelControllerSchema, externalAgentUnboundControllerSchema, liveModelControllerSchema, snapshotModelControllerSchema, modelIdentityMappingSchema, advertisedModelSchema, hostAdvertisementSchema, envelopeAadSchema, validateEnvelopeAad, commonEnvelopeShape, sealedKeyWrapEnvelopeSchema, sealedInputEnvelopeSchema, sealedCandidateEnvelopeSchema, sealedEnvelopeSchema, screenerCandidateRuntimeBindingSchema, screenerClientLeaseBindingSchema, screenerRuntimeBindingSchema, serverRuntimeBindingSchema, clientRuntimeBindingSchema, runtimeBindingSchema, immutableJobInputSchema, canonicalizeJsonValue, serializeImmutableJobInput, reportedTokenUsageSchema, unavailableTokenUsageSchema, tokenUsageSchema, agentConnectRequestSchema, agentNextRequestSchema, agentHeartbeatRequestSchema, emptyAgentNextResultSchema, claimedAgentNextResultSchema, agentNextResultSchema, agentCompleteRequestSchema, agentFailRequestSchema, requestedModelSelectionSchema, completedModelProvenanceSchema, attemptModelProvenanceSchema, hostHeartbeatRequestSchema, jobClaimRequestSchema, emptyJobClaimSchema, claimedJobSchema, jobClaimResultSchema, attemptStartRequestSchema, jobHeartbeatRequestSchema, continueHeartbeatDirectiveSchema, cancelHeartbeatDirectiveSchema, jobHeartbeatResultSchema, adapterOutcomeEvidenceSchema, completionBaseShape, validateCompletion, providerResponseCompletionSchema, decisionCandidateCompletionSchema, jobCompleteRequestSchema, jobFailRequestSchema, jobCancelRequestSchema, jobCancellationReceiptSchema, jobDeliveryRequestSchema, deliveryBaseShape, validateDeliveryCandidate, providerResponseDeliverySchema, decisionCandidateDeliverySchema, jobDeliveryResultSchema, jobConsumptionRequestSchema, jobConsumptionReceiptSchema, jobStateSchema, jobStatusReadSchema, hostStatusReadSchema, controllerBindingStatusReadSchema, queueStatusReadSchema, outcomeStatusReadSchema, externalInferenceStatusReadSchema, nativeResearchEvidenceSourceSchema, externalAgentResearchEvidenceSourceSchema, researchEvidenceSourceSchema, reportedResearchTokenUsageSchema, unavailableResearchTokenUsageSchema, researchTokenUsageSchema, externalInferenceProductLineageBaseShape, traderExternalInferenceProductLineageSchema, decisionChatExternalInferenceProductLineageSchema, screenerExternalInferenceProductLineageSchema, externalInferenceProductLineageSchema, normalProductLinkageSchema, evidenceMaterializationMarkerSchema, deliveredExternalAgentSourceIdentitySchema, deliveredRequestEvidenceSchema, researchEvidenceBaseShape, researchEvidenceBaseSchema, validateExactUtf8, validateResearchEvidence, providerResponseResearchEvidenceSchema, decisionCandidateResearchEvidenceSchema, researchEvidenceSchema;
14378
+ var EXTERNAL_INFERENCE_CONTRACT_VERSION, EXTERNAL_INFERENCE_CONTROLLER_SCHEMA_VERSION, EXTERNAL_INFERENCE_ADVERTISEMENT_SCHEMA_VERSION, EXTERNAL_INFERENCE_ENVELOPE_SCHEMA_VERSION, EXTERNAL_INFERENCE_JOB_INPUT_SCHEMA_VERSION, EXTERNAL_INFERENCE_USAGE_SCHEMA_VERSION, EXTERNAL_INFERENCE_INPUT_HASH_DOMAIN, MAX_EXTERNAL_INFERENCE_STATUS_HOSTS, MAX_GENERATION, MAX_SAFE_INTEGER, MAX_PROMPT_BYTES, MAX_IMMUTABLE_JOB_INPUT_BYTES, MAX_CANDIDATE_PLAINTEXT_BYTES, MAX_CIPHERTEXT_CHARS, SHA256_INITIAL_STATE, SHA256_ROUND_CONSTANTS, rotateRight, sha256BytesHex, sha256Utf8Hex, computeImmutableJobInputSha256, identifierSchema, operationIdentifierSchema, sha256HexSchema, generationSchema, zeroBasedGenerationSchema, nonNegativeSafeIntegerSchema, positiveSafeIntegerSchema, sqlIntegerTokenCountSchema, timestampSchema, safeCodeSchema, externalInferenceAdapterIdSchema, reasoningEffortSchema, modelNameSchema, displayNameSchema, authenticatedAccountEmailSchema, protocolVersionSchema, base64UrlAlphabet, isCanonicalBase64Url, base64Url32BytesSchema, base64UrlNonceSchema, base64UrlCiphertextSchema, claimHandleSchema, boundedJsonTextSchema, boundedPromptTextSchema, laneSchema, botModeSchema, executionModeSchema, responseModeSchema, dispatchOutcomeSchema, responseOutcomeSchema, nativeModelControllerSchema, externalAgentModelControllerSchema, externalAgentUnboundControllerSchema, liveModelControllerSchema, snapshotModelControllerSchema, modelIdentityMappingSchema, advertisedModelSchema, hostAdvertisementSchema, envelopeAadSchema, validateEnvelopeAad, commonEnvelopeShape, sealedKeyWrapEnvelopeSchema, sealedInputEnvelopeSchema, sealedCandidateEnvelopeSchema, sealedEnvelopeSchema, screenerCandidateRuntimeBindingSchema, screenerClientLeaseBindingSchema, screenerRuntimeBindingSchema, serverRuntimeBindingSchema, clientRuntimeBindingSchema, runtimeBindingSchema, immutableJobInputSchema, canonicalizeJsonValue, serializeImmutableJobInput, reportedTokenUsageSchema, unavailableTokenUsageSchema, tokenUsageSchema, agentConnectRequestSchema, agentNextRequestSchema, agentHeartbeatRequestSchema, emptyAgentNextResultSchema, claimedAgentNextResultSchema, agentNextResultSchema, agentCompleteRequestSchema, agentFailRequestSchema, requestedModelSelectionSchema, completedModelProvenanceSchema, attemptModelProvenanceSchema, hostHeartbeatRequestSchema, jobClaimRequestSchema, emptyJobClaimSchema, claimedJobSchema, jobClaimResultSchema, attemptStartRequestSchema, jobHeartbeatRequestSchema, continueHeartbeatDirectiveSchema, cancelHeartbeatDirectiveSchema, jobHeartbeatResultSchema, adapterOutcomeEvidenceSchema, completionBaseShape, validateCompletion, providerResponseCompletionSchema, decisionCandidateCompletionSchema, jobCompleteRequestSchema, jobFailRequestSchema, jobCancelRequestSchema, jobCancellationReceiptSchema, jobDeliveryRequestSchema, deliveryBaseShape, validateDeliveryCandidate, providerResponseDeliverySchema, decisionCandidateDeliverySchema, jobDeliveryResultSchema, jobConsumptionRequestSchema, jobConsumptionReceiptSchema, jobStateSchema, jobStatusReadSchema, hostStatusReadSchema, controllerBindingStatusReadSchema, queueStatusReadSchema, outcomeStatusReadSchema, externalInferenceStatusReadSchema, nativeResearchEvidenceSourceSchema, externalAgentResearchEvidenceSourceSchema, researchEvidenceSourceSchema, reportedResearchTokenUsageSchema, unavailableResearchTokenUsageSchema, researchTokenUsageSchema, externalInferenceProductLineageBaseShape, traderExternalInferenceProductLineageSchema, decisionChatExternalInferenceProductLineageSchema, screenerExternalInferenceProductLineageSchema, externalInferenceProductLineageSchema, normalProductLinkageSchema, evidenceMaterializationMarkerSchema, deliveredExternalAgentSourceIdentitySchema, deliveredRequestEvidenceSchema, researchEvidenceBaseShape, researchEvidenceBaseSchema, validateExactUtf8, validateResearchEvidence, providerResponseResearchEvidenceSchema, decisionCandidateResearchEvidenceSchema, researchEvidenceSchema;
14379
14379
  var init_external_inference_contract = __esm({
14380
14380
  "lib/external-inference-contract.ts"() {
14381
14381
  "use strict";
@@ -14580,6 +14580,7 @@ var init_external_inference_contract = __esm({
14580
14580
  reasoningEffortSchema = external_exports.string().min(1).max(32).regex(/^[a-z][a-z0-9_-]{0,31}$/);
14581
14581
  modelNameSchema = external_exports.string().min(1).max(256);
14582
14582
  displayNameSchema = external_exports.string().min(1).max(128);
14583
+ authenticatedAccountEmailSchema = external_exports.string().max(320).email();
14583
14584
  protocolVersionSchema = external_exports.string().min(1).max(64);
14584
14585
  base64UrlAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
14585
14586
  isCanonicalBase64Url = (value, exactBytes) => {
@@ -14713,6 +14714,8 @@ var init_external_inference_contract = __esm({
14713
14714
  key_generation: generationSchema,
14714
14715
  adapter: externalInferenceAdapterIdSchema,
14715
14716
  display_name: displayNameSchema,
14717
+ authenticated_account_email: authenticatedAccountEmailSchema.nullable().optional(),
14718
+ authenticated_account_plan: safeCodeSchema.nullable().optional(),
14716
14719
  protocol_version: protocolVersionSchema,
14717
14720
  envelope_public_key: base64Url32BytesSchema,
14718
14721
  health: external_exports.enum(["healthy", "degraded", "draining"]),
@@ -15576,6 +15579,8 @@ var init_external_inference_contract = __esm({
15576
15579
  hostStatusReadSchema = external_exports.strictObject({
15577
15580
  host_id: identifierSchema,
15578
15581
  display_name: displayNameSchema,
15582
+ authenticated_account_email: authenticatedAccountEmailSchema.nullable().optional(),
15583
+ authenticated_account_plan: safeCodeSchema.nullable().optional(),
15579
15584
  adapter: externalInferenceAdapterIdSchema,
15580
15585
  status: external_exports.enum(["online", "degraded", "offline", "draining", "revoked"]),
15581
15586
  host_generation: generationSchema,
@@ -16653,6 +16658,10 @@ var init_credential_store = __esm({
16653
16658
  }
16654
16659
  const result2 = await this.runner(command, { stdin: serialized, ...options });
16655
16660
  if (result2.exitCode !== 0) throw new Error("OS credential write failed.");
16661
+ const retained = await this.read(accountKey, options);
16662
+ if (!retained || JSON.stringify(retained) !== JSON.stringify(validated)) {
16663
+ throw new Error("OS credential write could not be verified.");
16664
+ }
16656
16665
  }
16657
16666
  async remove(accountKey, options = {}) {
16658
16667
  const command = this.commands.remove(INFERENCE_CREDENTIAL_NAMESPACE, accountKey);
@@ -16870,7 +16879,7 @@ public static class VtxInferenceCredential {
16870
16879
  }`;
16871
16880
  powershellCredentialCommand = (operation, namespace, accountKey) => {
16872
16881
  const target = JSON.stringify(`${namespace}:${accountKey}`);
16873
- const body = operation === "read" ? `try { [Console]::Out.Write([VtxInferenceCredential]::Read(${target})) } catch [System.ComponentModel.Win32Exception] { if ($_.Exception.NativeErrorCode -eq 1168) { exit 4 }; exit 1 }` : operation === "write" ? `[VtxInferenceCredential]::Write(${target}, [Console]::In.ReadToEnd())` : `try { [VtxInferenceCredential]::Delete(${target}) } catch [System.ComponentModel.Win32Exception] { if ($_.Exception.NativeErrorCode -eq 1168) { exit 4 }; exit 1 }`;
16882
+ const body = operation === "read" ? `try { [Console]::Out.Write([VtxInferenceCredential]::Read(${target})) } catch [System.ComponentModel.Win32Exception] { if ($_.Exception.NativeErrorCode -eq 1168) { exit 4 }; exit 1 }` : operation === "write" ? `try { [VtxInferenceCredential]::Write(${target}, [Console]::In.ReadToEnd()); exit 0 } catch { exit 1 }` : `try { [VtxInferenceCredential]::Delete(${target}) } catch [System.ComponentModel.Win32Exception] { if ($_.Exception.NativeErrorCode -eq 1168) { exit 4 }; exit 1 }`;
16874
16883
  const script = `Add-Type -TypeDefinition @'
16875
16884
  ${windowsCredentialInterop}
16876
16885
  '@
@@ -16985,7 +16994,7 @@ ${body}`;
16985
16994
  });
16986
16995
 
16987
16996
  // lib/inference-host/mcp-client.ts
16988
- var EXTERNAL_INFERENCE_MCP_PROTOCOL_VERSION, EXTERNAL_INFERENCE_AUTOMATED_HOST_TOOLS, EXTERNAL_INFERENCE_OPERATIONAL_TOOLS, ExternalInferenceMcpError, DEFAULT_REQUEST_TIMEOUT_MS, identifierSchema2, safeCodeSchema2, hostMutationResultSchema, attemptStartResultSchema, agentConnectResultSchema, agentHeartbeatResultSchema, jobCompletionResultSchema, jobFailureResultSchema, toolContracts, discoveryResultSchema, toolListResultSchema, inlineStructuredContentSchema, protocolMeta, parseJsonDocument, parseSseDocuments, parseResponseDocuments, exactOperationalInventory, exactJson, invalidBoundResult, verifyToolResult, ExternalInferenceMcpClient;
16997
+ var EXTERNAL_INFERENCE_MCP_PROTOCOL_VERSION, EXTERNAL_INFERENCE_AUTOMATED_HOST_TOOLS, EXTERNAL_INFERENCE_OPERATIONAL_TOOLS, ExternalInferenceMcpError, DEFAULT_REQUEST_TIMEOUT_MS, MAX_RETRY_AFTER_MS, parseRetryAfterMs, identifierSchema2, safeCodeSchema2, hostMutationResultSchema, attemptStartResultSchema, agentConnectResultSchema, agentHeartbeatResultSchema, jobCompletionResultSchema, jobFailureResultSchema, toolContracts, discoveryResultSchema, toolListResultSchema, inlineStructuredContentSchema, protocolMeta, parseJsonDocument, parseSseDocuments, parseResponseDocuments, exactOperationalInventory, exactJson, invalidBoundResult, verifyToolResult, ExternalInferenceMcpClient;
16989
16998
  var init_mcp_client = __esm({
16990
16999
  "lib/inference-host/mcp-client.ts"() {
16991
17000
  "use strict";
@@ -17016,9 +17025,29 @@ var init_mcp_client = __esm({
17016
17025
  this.name = "ExternalInferenceMcpError";
17017
17026
  this.code = code;
17018
17027
  this.definitivelyNotApplied = options.definitivelyNotApplied ?? false;
17028
+ this.httpStatusCode = options.httpStatusCode ?? null;
17029
+ this.retryAfterMs = options.retryAfterMs ?? null;
17030
+ this.retryable = options.retryable ?? [
17031
+ "network_error",
17032
+ "transport_rejected",
17033
+ "server_rejected",
17034
+ "rate_limited",
17035
+ "invalid_response",
17036
+ "request_timeout",
17037
+ "authentication_timeout"
17038
+ ].includes(code);
17019
17039
  }
17020
17040
  };
17021
17041
  DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
17042
+ MAX_RETRY_AFTER_MS = 24 * 60 * 60 * 1e3;
17043
+ parseRetryAfterMs = (response, observedAtMs = Date.now()) => {
17044
+ const value = response.headers.get("retry-after")?.trim();
17045
+ if (!value) return null;
17046
+ const seconds = Number(value);
17047
+ const milliseconds = Number.isFinite(seconds) && seconds >= 0 ? Math.ceil(seconds * 1e3) : Math.max(0, Date.parse(value) - observedAtMs);
17048
+ if (!Number.isSafeInteger(milliseconds)) return null;
17049
+ return Math.min(milliseconds, MAX_RETRY_AFTER_MS);
17050
+ };
17022
17051
  identifierSchema2 = external_exports.string().min(1).max(128);
17023
17052
  safeCodeSchema2 = external_exports.string().min(1).max(96);
17024
17053
  hostMutationResultSchema = external_exports.strictObject({
@@ -17238,11 +17267,11 @@ var init_mcp_client = __esm({
17238
17267
  if (!host) return invalidBoundResult(name);
17239
17268
  if (host.host_id !== request.host_id || host.host_generation !== request.host_generation || host.advertisement_generation !== request.advertisement_generation || host.key_generation !== request.key_generation || request.adapter !== void 0 && host.adapter !== request.adapter) invalidBoundResult(name);
17240
17269
  if (name !== "inference.host.heartbeat") {
17241
- if (host.display_name !== request.display_name || !exactJson(host.models, request.models) || host.status === "revoked") invalidBoundResult(name);
17270
+ if (host.display_name !== request.display_name || (host.authenticated_account_email ?? null) !== (request.authenticated_account_email ?? null) || (host.authenticated_account_plan ?? null) !== (request.authenticated_account_plan ?? null) || !exactJson(host.models, request.models) || host.status === "revoked") invalidBoundResult(name);
17242
17271
  return;
17243
17272
  }
17244
17273
  const expectedStatus = request.status === "healthy" ? "online" : request.status;
17245
- if (host.status !== expectedStatus) invalidBoundResult(name);
17274
+ if (host.status !== expectedStatus && !(request.status === "healthy" && host.status === "offline")) invalidBoundResult(name);
17246
17275
  return;
17247
17276
  }
17248
17277
  if (name === "inference.agent.connect") {
@@ -17454,9 +17483,14 @@ var init_mcp_client = __esm({
17454
17483
  );
17455
17484
  }
17456
17485
  if (!response.ok) {
17486
+ const httpStatusCode = response.status;
17487
+ const retryAfterMs = parseRetryAfterMs(response);
17488
+ await response.body?.cancel().catch(() => void 0);
17489
+ const retryable = httpStatusCode === 408 || httpStatusCode === 425 || httpStatusCode === 429 || httpStatusCode >= 500;
17457
17490
  throw new ExternalInferenceMcpError(
17458
- response.status === 401 ? "authentication_rejected" : "transport_rejected",
17459
- `Insights MCP request failed with HTTP ${response.status}.`
17491
+ httpStatusCode === 401 ? "authentication_rejected" : httpStatusCode === 429 ? "rate_limited" : httpStatusCode >= 500 ? "server_rejected" : retryable ? "transport_rejected" : "http_rejected",
17492
+ `Insights MCP request failed with HTTP ${httpStatusCode}.`,
17493
+ { httpStatusCode, retryAfterMs, retryable }
17460
17494
  );
17461
17495
  }
17462
17496
  let documents;
@@ -18052,13 +18086,13 @@ async function beginInferenceOAuthLogin(options) {
18052
18086
  );
18053
18087
  }
18054
18088
  const fetchImpl = options.fetchImpl || fetch;
18055
- const callbackPath = `/oauth/callback/${randomBytes2(16).toString("hex")}`;
18089
+ const callbackPath = `/vtx-inference-authorized/${randomBytes2(16).toString("hex")}`;
18056
18090
  let handler = null;
18057
18091
  const server = createServer((request, response) => {
18058
18092
  response.setHeader("Cache-Control", "no-store");
18059
18093
  response.setHeader("Pragma", "no-cache");
18060
18094
  response.setHeader("Referrer-Policy", "no-referrer");
18061
- response.setHeader("Content-Type", "text/plain; charset=utf-8");
18095
+ response.setHeader("Content-Type", "text/html; charset=utf-8");
18062
18096
  const writeResponse = (status, body) => {
18063
18097
  response.statusCode = status;
18064
18098
  response.end(body);
@@ -18162,9 +18196,12 @@ async function beginInferenceOAuthLogin(options) {
18162
18196
  cause: error48
18163
18197
  });
18164
18198
  }
18165
- throw error48;
18199
+ throw new InferenceOAuthCredentialStoreError({ cause: error48 });
18166
18200
  }
18167
- writeResponse(200, "Authorization complete. Return to VTX CLI.");
18201
+ writeResponse(
18202
+ 200,
18203
+ '<!doctype html><html><head><meta charset="utf-8"><title>VTX authorization complete</title></head><body><main><h1>VTX authorization complete</h1><p>The VTX CLI received your approval. You can close this tab and return to the terminal.</p></main></body></html>'
18204
+ );
18168
18205
  await closeServer(server);
18169
18206
  resolve5({
18170
18207
  accessToken: tokens.accessToken,
@@ -18173,9 +18210,12 @@ async function beginInferenceOAuthLogin(options) {
18173
18210
  accountKey
18174
18211
  });
18175
18212
  } catch (error48) {
18176
- writeResponse(400, "Authorization failed. Return to VTX CLI.");
18213
+ writeResponse(
18214
+ 400,
18215
+ '<!doctype html><html><head><meta charset="utf-8"><title>VTX authorization failed</title></head><body><main><h1>VTX authorization failed</h1><p>Return to the VTX CLI for the actionable error.</p></main></body></html>'
18216
+ );
18177
18217
  await closeServer(server);
18178
- reject(error48 instanceof InferenceOAuthRequestError ? error48 : new Error("Inference OAuth authorization failed."));
18218
+ reject(error48 instanceof InferenceOAuthRequestError || error48 instanceof InferenceOAuthCredentialStoreError ? error48 : new Error("Inference OAuth authorization failed."));
18179
18219
  }
18180
18220
  };
18181
18221
  });
@@ -18225,7 +18265,7 @@ async function beginInferenceOAuthLogin(options) {
18225
18265
  throw error48;
18226
18266
  }
18227
18267
  }
18228
- var INFERENCE_SCOPE, CALLBACK_HOST, TOKEN_PREFIX, DEFAULT_OAUTH_REQUEST_TIMEOUT_MS, InferenceOAuthRequestError;
18268
+ var INFERENCE_SCOPE, CALLBACK_HOST, TOKEN_PREFIX, DEFAULT_OAUTH_REQUEST_TIMEOUT_MS, InferenceOAuthRequestError, InferenceOAuthCredentialStoreError;
18229
18269
  var init_oauth = __esm({
18230
18270
  "lib/inference-host/oauth.ts"() {
18231
18271
  "use strict";
@@ -18241,6 +18281,15 @@ var init_oauth = __esm({
18241
18281
  this.code = code;
18242
18282
  }
18243
18283
  };
18284
+ InferenceOAuthCredentialStoreError = class extends Error {
18285
+ constructor(options = {}) {
18286
+ super(
18287
+ "VTX authorization succeeded, but the credential store did not retain the grant. VTX revoked the new remote grant. Set VTX_INFERENCE_HOST_CREDENTIAL_STORE=file and run vtx inference-host login again to use the secure private-file fallback.",
18288
+ { cause: options.cause }
18289
+ );
18290
+ this.name = "InferenceOAuthCredentialStoreError";
18291
+ }
18292
+ };
18244
18293
  }
18245
18294
  });
18246
18295
 
@@ -18488,7 +18537,25 @@ async function loginCodexSubscription(options) {
18488
18537
  async function preflightCodexSubscription(options) {
18489
18538
  const session = await CodexAppServerSession.start(options);
18490
18539
  try {
18491
- await session.requireManagedChatGptAuth(options.deadlineAtMs, options.signal, true);
18540
+ const account = await session.readAccountSnapshot(
18541
+ options.deadlineAtMs,
18542
+ options.signal,
18543
+ true
18544
+ );
18545
+ if (!account.requiresOpenaiAuth || account.authenticatedChatGptAccount === null) {
18546
+ throw new CodexAppServerError({
18547
+ message: "Codex managed ChatGPT authentication is required.",
18548
+ category: "auth",
18549
+ code: "managed_chatgpt_auth_required",
18550
+ retryable: false
18551
+ });
18552
+ }
18553
+ const rateLimits = await session.readAccountRateLimits(options.deadlineAtMs, options.signal);
18554
+ return {
18555
+ authenticated_account_email: account.authenticatedChatGptAccount.email,
18556
+ authenticated_account_plan: account.authenticatedChatGptAccount.planType,
18557
+ rate_limits: rateLimits
18558
+ };
18492
18559
  } finally {
18493
18560
  await session.close();
18494
18561
  }
@@ -18501,11 +18568,28 @@ async function logoutCodexSubscription(options) {
18501
18568
  await session.close();
18502
18569
  }
18503
18570
  }
18504
- var CODEX_INFERENCE_PERMISSION_PROFILE, CodexAppServerError, objectOrNull, finiteToken, tokenUsageFromBreakdown, usageFromNotification, forbiddenMethod, forbiddenTerminalItem, classifyCodexTurnFailure, scrubbedCodexEnvironment, killWindowsProcessTree, appServerArgs, GUARDIAN_SCRIPT, writeCodexGuardianSpawnIntent, parseGuardianReceipt, readCodexGuardianReceipt, waitForCodexGuardianState, defaultSpawn, CodexAppServerSession;
18571
+ var CODEX_INFERENCE_PERMISSION_PROFILE, CODEX_ACCOUNT_PLAN_TYPES, CodexAppServerError, objectOrNull, finiteToken, tokenUsageFromBreakdown, usageFromNotification, validPlanType, nonnegativeSafeIntegerOrNull, nonnegativeFiniteNumberOrNull, parseRateLimitWindow, RATE_LIMIT_REACHED_TYPES, DEFAULT_CODEX_QUOTA_COOLDOWN_MS, MAX_CODEX_QUOTA_COOLDOWN_MS, CODEX_TRANSIENT_RATE_LIMIT_COOLDOWN_MS, parseRateLimitSnapshot, codexRateLimitRetryAtMs, codexAccountRateLimitReached, forbiddenMethod, forbiddenTerminalItem, classifyCodexTurnFailure, scrubbedCodexEnvironment, killWindowsProcessTree, appServerArgs, GUARDIAN_SCRIPT, writeCodexGuardianSpawnIntent, parseGuardianReceipt, readCodexGuardianReceipt, waitForCodexGuardianState, defaultSpawn, CodexAppServerSession;
18505
18572
  var init_codex_app_server = __esm({
18506
18573
  "lib/inference-host/codex-app-server.ts"() {
18507
18574
  "use strict";
18508
18575
  CODEX_INFERENCE_PERMISSION_PROFILE = "vtx_inference_readonly";
18576
+ CODEX_ACCOUNT_PLAN_TYPES = [
18577
+ "free",
18578
+ "go",
18579
+ "plus",
18580
+ "pro",
18581
+ "prolite",
18582
+ "team",
18583
+ "self_serve_business_prolite",
18584
+ "self_serve_business_usage_based",
18585
+ "business",
18586
+ "ent26",
18587
+ "enterprise_cbp_automation",
18588
+ "enterprise_cbp_usage_based",
18589
+ "enterprise",
18590
+ "edu",
18591
+ "unknown"
18592
+ ];
18509
18593
  CodexAppServerError = class extends Error {
18510
18594
  constructor(options) {
18511
18595
  super(options.message, { cause: options.cause });
@@ -18514,6 +18598,9 @@ var init_codex_app_server = __esm({
18514
18598
  this.code = options.code;
18515
18599
  this.retryable = options.retryable;
18516
18600
  this.dispatchOutcome = options.dispatchOutcome ?? "not_dispatched";
18601
+ this.httpStatusCode = options.httpStatusCode ?? null;
18602
+ this.retryAtMs = options.retryAtMs ?? null;
18603
+ this.usage = options.usage ?? null;
18517
18604
  }
18518
18605
  };
18519
18606
  objectOrNull = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -18554,6 +18641,72 @@ var init_codex_app_server = __esm({
18554
18641
  }
18555
18642
  return null;
18556
18643
  };
18644
+ validPlanType = (value) => typeof value === "string" && CODEX_ACCOUNT_PLAN_TYPES.includes(value);
18645
+ nonnegativeSafeIntegerOrNull = (value) => Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : null;
18646
+ nonnegativeFiniteNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
18647
+ parseRateLimitWindow = (value) => {
18648
+ if (value === null) return null;
18649
+ const candidate = objectOrNull(value);
18650
+ if (!candidate) throw new Error("invalid rate-limit window");
18651
+ const usedPercent = nonnegativeFiniteNumberOrNull(candidate.usedPercent);
18652
+ const windowDurationMins = candidate.windowDurationMins == null ? null : nonnegativeSafeIntegerOrNull(candidate.windowDurationMins);
18653
+ const resetsAt = candidate.resetsAt == null ? null : nonnegativeSafeIntegerOrNull(candidate.resetsAt);
18654
+ if (usedPercent === null || windowDurationMins === null && candidate.windowDurationMins != null || resetsAt === null && candidate.resetsAt != null) {
18655
+ throw new Error("invalid rate-limit window");
18656
+ }
18657
+ return { usedPercent, windowDurationMins, resetsAt };
18658
+ };
18659
+ RATE_LIMIT_REACHED_TYPES = [
18660
+ "rate_limit_reached",
18661
+ "workspace_owner_credits_depleted",
18662
+ "workspace_member_credits_depleted",
18663
+ "workspace_owner_usage_limit_reached",
18664
+ "workspace_member_usage_limit_reached"
18665
+ ];
18666
+ DEFAULT_CODEX_QUOTA_COOLDOWN_MS = 5 * 60 * 1e3;
18667
+ MAX_CODEX_QUOTA_COOLDOWN_MS = 8 * 24 * 60 * 60 * 1e3;
18668
+ CODEX_TRANSIENT_RATE_LIMIT_COOLDOWN_MS = 30 * 1e3;
18669
+ parseRateLimitSnapshot = (value) => {
18670
+ const candidate = objectOrNull(value);
18671
+ if (!candidate) throw new Error("invalid rate-limit snapshot");
18672
+ const limitId = candidate.limitId === null || candidate.limitId === void 0 ? null : typeof candidate.limitId === "string" && candidate.limitId.trim() ? candidate.limitId.trim() : void 0;
18673
+ const limitName = candidate.limitName === null || candidate.limitName === void 0 ? null : typeof candidate.limitName === "string" && candidate.limitName.trim() ? candidate.limitName.trim() : void 0;
18674
+ const planType = candidate.planType === null || candidate.planType === void 0 ? null : validPlanType(candidate.planType) ? candidate.planType : void 0;
18675
+ const reached = candidate.rateLimitReachedType === null || candidate.rateLimitReachedType === void 0 ? null : typeof candidate.rateLimitReachedType === "string" && RATE_LIMIT_REACHED_TYPES.includes(candidate.rateLimitReachedType) ? candidate.rateLimitReachedType : void 0;
18676
+ const spendControlReached = candidate.spendControlReached === null || candidate.spendControlReached === void 0 ? null : typeof candidate.spendControlReached === "boolean" ? candidate.spendControlReached : void 0;
18677
+ if (limitId === void 0 || limitName === void 0 || planType === void 0 || reached === void 0 || spendControlReached === void 0) throw new Error("invalid rate-limit snapshot");
18678
+ return {
18679
+ limitId,
18680
+ limitName,
18681
+ planType,
18682
+ primary: parseRateLimitWindow(candidate.primary ?? null),
18683
+ secondary: parseRateLimitWindow(candidate.secondary ?? null),
18684
+ rateLimitReachedType: reached,
18685
+ spendControlReached
18686
+ };
18687
+ };
18688
+ codexRateLimitRetryAtMs = (accountRateLimits) => {
18689
+ const snapshots = [accountRateLimits.rateLimits];
18690
+ const exhaustedResets = [];
18691
+ const reachedResets = [];
18692
+ for (const snapshot of snapshots) {
18693
+ const windows = [snapshot.primary, snapshot.secondary].filter(
18694
+ (window2) => window2 !== null
18695
+ );
18696
+ for (const window2 of windows) {
18697
+ if (window2.resetsAt === null) continue;
18698
+ const resetAtMs = window2.resetsAt * 1e3;
18699
+ if (!Number.isSafeInteger(resetAtMs)) continue;
18700
+ if (window2.usedPercent >= 100) exhaustedResets.push(resetAtMs);
18701
+ if (snapshot.rateLimitReachedType !== null || snapshot.spendControlReached === true) {
18702
+ reachedResets.push(resetAtMs);
18703
+ }
18704
+ }
18705
+ }
18706
+ const candidates = exhaustedResets.length > 0 ? exhaustedResets : reachedResets.length > 0 ? reachedResets : [];
18707
+ return candidates.length > 0 ? Math.max(...candidates) : null;
18708
+ };
18709
+ codexAccountRateLimitReached = (accountRateLimits) => [accountRateLimits.rateLimits].some((snapshot) => snapshot.rateLimitReachedType !== null || snapshot.spendControlReached === true || snapshot.primary?.usedPercent !== void 0 && snapshot.primary.usedPercent >= 100 || snapshot.secondary?.usedPercent !== void 0 && snapshot.secondary.usedPercent >= 100);
18557
18710
  forbiddenMethod = (method) => method.startsWith("mcpServer/") || method.startsWith("serverRequest/") || method.startsWith("item/mcpToolCall") || method.startsWith("item/commandExecution") || method.startsWith("item/fileChange") || method.startsWith("item/dynamicToolCall") || method.startsWith("item/collabAgentToolCall");
18558
18711
  forbiddenTerminalItem = (item) => ![
18559
18712
  "userMessage",
@@ -18562,13 +18715,13 @@ var init_codex_app_server = __esm({
18562
18715
  ].includes(String(item.type || ""));
18563
18716
  classifyCodexTurnFailure = (codexErrorInfo, terminalStatus) => {
18564
18717
  if (terminalStatus === "interrupted") {
18565
- return { code: "cancelled", category: "cancelled", retryable: false };
18718
+ return { code: "cancelled", category: "cancelled", retryable: false, httpStatusCode: null };
18566
18719
  }
18567
18720
  if (codexErrorInfo === "usageLimitExceeded") {
18568
- return { code: "quota_exceeded", category: "model", retryable: false };
18721
+ return { code: "quota_exceeded", category: "model", retryable: false, httpStatusCode: null };
18569
18722
  }
18570
18723
  if (codexErrorInfo === "unauthorized") {
18571
- return { code: "auth_expired", category: "auth", retryable: false };
18724
+ return { code: "auth_expired", category: "auth", retryable: false, httpStatusCode: null };
18572
18725
  }
18573
18726
  const stableStringCodes = {
18574
18727
  badRequest: "codex_bad_request",
@@ -18585,22 +18738,40 @@ var init_codex_app_server = __esm({
18585
18738
  return {
18586
18739
  code,
18587
18740
  category: "adapter",
18588
- retryable: code === "codex_internal_error" || code === "codex_server_overloaded"
18741
+ retryable: code === "codex_internal_error" || code === "codex_server_overloaded",
18742
+ httpStatusCode: null
18589
18743
  };
18590
18744
  }
18591
18745
  const structured = objectOrNull(codexErrorInfo);
18592
18746
  if (structured) {
18593
- if ("httpConnectionFailed" in structured) {
18594
- return { code: "codex_http_connection_failed", category: "adapter", retryable: true };
18595
- }
18596
- if ("responseStreamConnectionFailed" in structured) {
18597
- return { code: "codex_response_stream_connection_failed", category: "adapter", retryable: true };
18598
- }
18599
- if ("responseStreamDisconnected" in structured) {
18600
- return { code: "codex_response_stream_disconnected", category: "adapter", retryable: true };
18747
+ const variants = [
18748
+ ["httpConnectionFailed", "codex_http_connection_failed"],
18749
+ ["responseStreamConnectionFailed", "codex_response_stream_connection_failed"],
18750
+ ["responseStreamDisconnected", "codex_response_stream_disconnected"],
18751
+ ["responseTooManyFailedAttempts", "codex_provider_retry_exhausted"]
18752
+ ];
18753
+ for (const [variant, code] of variants) {
18754
+ if (!(variant in structured)) continue;
18755
+ const details = objectOrNull(structured[variant]);
18756
+ const rawStatus = details?.httpStatusCode;
18757
+ const httpStatusCode = Number.isSafeInteger(rawStatus) && Number(rawStatus) >= 100 && Number(rawStatus) <= 599 ? Number(rawStatus) : null;
18758
+ if (httpStatusCode === 429) {
18759
+ return {
18760
+ code: "codex_rate_limited",
18761
+ category: "adapter",
18762
+ retryable: true,
18763
+ httpStatusCode
18764
+ };
18765
+ }
18766
+ return {
18767
+ code,
18768
+ category: "adapter",
18769
+ retryable: httpStatusCode === null ? variant !== "responseTooManyFailedAttempts" : httpStatusCode === 408 || httpStatusCode === 425 || httpStatusCode >= 500,
18770
+ httpStatusCode
18771
+ };
18601
18772
  }
18602
18773
  }
18603
- return { code: "turn_failed", category: "adapter", retryable: false };
18774
+ return { code: "turn_failed", category: "adapter", retryable: false, httpStatusCode: null };
18604
18775
  };
18605
18776
  scrubbedCodexEnvironment = (codexHome) => {
18606
18777
  if (process.platform === "win32") {
@@ -19080,13 +19251,80 @@ child.once('close', async () => {
19080
19251
  this.fatalListeners.add(listener);
19081
19252
  return () => this.fatalListeners.delete(listener);
19082
19253
  }
19083
- async hasManagedChatGptAuth(deadlineAtMs, signal, refreshToken = false) {
19254
+ async readAccountSnapshot(deadlineAtMs, signal, refreshToken = false) {
19084
19255
  const result2 = await this.request("account/read", { refreshToken }, {
19085
19256
  timeoutMs: Math.max(1, deadlineAtMs - Date.now()),
19086
19257
  signal
19087
19258
  });
19088
19259
  const account = objectOrNull(result2.account);
19089
- return account?.type === "chatgpt" && result2.requiresOpenaiAuth === true;
19260
+ if (typeof result2.requiresOpenaiAuth !== "boolean") {
19261
+ throw new CodexAppServerError({
19262
+ message: "Codex returned invalid account metadata.",
19263
+ category: "auth",
19264
+ code: "invalid_account_metadata",
19265
+ retryable: false
19266
+ });
19267
+ }
19268
+ if (account?.type === "chatgpt") {
19269
+ const email3 = account.email === null ? null : typeof account.email === "string" && account.email.trim() ? account.email.trim() : void 0;
19270
+ if (email3 === void 0 || !validPlanType(account.planType)) {
19271
+ throw new CodexAppServerError({
19272
+ message: "Codex returned invalid managed ChatGPT account metadata.",
19273
+ category: "auth",
19274
+ code: "invalid_account_metadata",
19275
+ retryable: false
19276
+ });
19277
+ }
19278
+ return {
19279
+ requiresOpenaiAuth: result2.requiresOpenaiAuth,
19280
+ authenticatedChatGptAccount: { email: email3, planType: account.planType }
19281
+ };
19282
+ }
19283
+ if (account !== null && account?.type !== "apiKey" && account?.type !== "amazonBedrock") {
19284
+ throw new CodexAppServerError({
19285
+ message: "Codex returned an unsupported account type.",
19286
+ category: "auth",
19287
+ code: "invalid_account_metadata",
19288
+ retryable: false
19289
+ });
19290
+ }
19291
+ return {
19292
+ requiresOpenaiAuth: result2.requiresOpenaiAuth,
19293
+ authenticatedChatGptAccount: null
19294
+ };
19295
+ }
19296
+ async hasManagedChatGptAuth(deadlineAtMs, signal, refreshToken = false) {
19297
+ const snapshot = await this.readAccountSnapshot(deadlineAtMs, signal, refreshToken);
19298
+ return snapshot.authenticatedChatGptAccount !== null && snapshot.requiresOpenaiAuth;
19299
+ }
19300
+ async readAccountRateLimits(deadlineAtMs, signal) {
19301
+ const result2 = await this.request("account/rateLimits/read", void 0, {
19302
+ timeoutMs: Math.max(1, deadlineAtMs - Date.now()),
19303
+ signal
19304
+ });
19305
+ try {
19306
+ const rateLimits = parseRateLimitSnapshot(result2.rateLimits);
19307
+ const rawByLimitId = result2.rateLimitsByLimitId;
19308
+ let rateLimitsByLimitId = null;
19309
+ if (rawByLimitId !== null && rawByLimitId !== void 0) {
19310
+ const byLimitId = objectOrNull(rawByLimitId);
19311
+ if (!byLimitId) throw new Error("invalid rate-limit bucket map");
19312
+ rateLimitsByLimitId = {};
19313
+ for (const [limitId, snapshot] of Object.entries(byLimitId)) {
19314
+ if (!limitId.trim()) throw new Error("invalid rate-limit bucket identity");
19315
+ rateLimitsByLimitId[limitId] = parseRateLimitSnapshot(snapshot);
19316
+ }
19317
+ }
19318
+ return { rateLimits, rateLimitsByLimitId };
19319
+ } catch (error48) {
19320
+ throw new CodexAppServerError({
19321
+ message: "Codex returned invalid account rate-limit metadata.",
19322
+ category: "adapter",
19323
+ code: "invalid_rate_limit_metadata",
19324
+ retryable: false,
19325
+ cause: error48
19326
+ });
19327
+ }
19090
19328
  }
19091
19329
  async requireManagedChatGptAuth(deadlineAtMs, signal, refreshToken = false) {
19092
19330
  if (!await this.hasManagedChatGptAuth(deadlineAtMs, signal, refreshToken)) {
@@ -19623,13 +19861,54 @@ child.once('close', async () => {
19623
19861
  }
19624
19862
  if (terminal.status !== "completed") {
19625
19863
  const turnError = objectOrNull(terminal.error);
19626
- const failure = classifyCodexTurnFailure(turnError?.codexErrorInfo, String(terminal.status));
19864
+ let failure = classifyCodexTurnFailure(
19865
+ turnError?.codexErrorInfo,
19866
+ String(terminal.status)
19867
+ );
19868
+ const observedUsage = threadUsage ?? rawResponseUsage;
19869
+ let retryAtMs = null;
19870
+ if (failure.code === "quota_exceeded" || failure.httpStatusCode === 429) {
19871
+ const observedAtMs = Date.now();
19872
+ let accountLimitReached = failure.code === "quota_exceeded";
19873
+ try {
19874
+ const metadataDeadlineAtMs = Math.min(
19875
+ request.deadlineAtMs,
19876
+ observedAtMs + 5e3
19877
+ );
19878
+ if (metadataDeadlineAtMs <= observedAtMs) throw new Error("rate-limit deadline elapsed");
19879
+ const rateLimits = await this.readAccountRateLimits(metadataDeadlineAtMs, request.signal);
19880
+ accountLimitReached = accountLimitReached || codexAccountRateLimitReached(rateLimits);
19881
+ retryAtMs = codexRateLimitRetryAtMs(rateLimits);
19882
+ } catch {
19883
+ }
19884
+ if (accountLimitReached) {
19885
+ failure = {
19886
+ ...failure,
19887
+ code: "quota_exceeded",
19888
+ category: "model",
19889
+ retryable: false
19890
+ };
19891
+ if (retryAtMs === null || retryAtMs <= observedAtMs) {
19892
+ retryAtMs = observedAtMs + DEFAULT_CODEX_QUOTA_COOLDOWN_MS;
19893
+ } else {
19894
+ retryAtMs = Math.min(
19895
+ retryAtMs,
19896
+ observedAtMs + MAX_CODEX_QUOTA_COOLDOWN_MS
19897
+ );
19898
+ }
19899
+ } else {
19900
+ retryAtMs = observedAtMs + CODEX_TRANSIENT_RATE_LIMIT_COOLDOWN_MS;
19901
+ }
19902
+ }
19627
19903
  throw new CodexAppServerError({
19628
19904
  message: "Codex turn did not complete successfully.",
19629
19905
  category: failure.category,
19630
19906
  code: failure.code,
19631
19907
  retryable: failure.retryable,
19632
- dispatchOutcome: "confirmed_dispatched"
19908
+ dispatchOutcome: "confirmed_dispatched",
19909
+ httpStatusCode: failure.httpStatusCode,
19910
+ retryAtMs,
19911
+ usage: observedUsage
19633
19912
  });
19634
19913
  }
19635
19914
  const items = Array.isArray(terminal.items) ? terminal.items.map(objectOrNull) : [];
@@ -19989,7 +20268,10 @@ var init_codex_adapter = __esm({
19989
20268
  category: external_exports.enum(["auth", "model", "schema", "timeout", "cancelled", "transport", "adapter"]),
19990
20269
  code: external_exports.string().min(1).max(96),
19991
20270
  retryable: external_exports.boolean(),
19992
- dispatchOutcome: external_exports.enum(["not_dispatched", "confirmed_dispatched", "outcome_unknown"])
20271
+ dispatchOutcome: external_exports.enum(["not_dispatched", "confirmed_dispatched", "outcome_unknown"]),
20272
+ httpStatusCode: external_exports.number().int().min(100).max(599).nullable().optional(),
20273
+ retryAtMs: external_exports.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).nullable().optional(),
20274
+ usage: tokenUsageReceiptSchema.nullable().optional()
19993
20275
  })
19994
20276
  })
19995
20277
  ]);
@@ -20510,7 +20792,10 @@ var init_codex_adapter = __esm({
20510
20792
  category: recovered.category,
20511
20793
  code: recovered.code,
20512
20794
  retryable: recovered.retryable,
20513
- dispatchOutcome: recovered.dispatchOutcome
20795
+ dispatchOutcome: recovered.dispatchOutcome,
20796
+ httpStatusCode: recovered.httpStatusCode,
20797
+ retryAtMs: recovered.retryAtMs,
20798
+ usage: recovered.usage
20514
20799
  });
20515
20800
  }
20516
20801
  throw new CodexAppServerError({
@@ -20649,7 +20934,10 @@ var init_codex_adapter = __esm({
20649
20934
  category: error48.category,
20650
20935
  code: error48.code,
20651
20936
  retryable: error48.retryable,
20652
- dispatchOutcome
20937
+ dispatchOutcome,
20938
+ httpStatusCode: error48.httpStatusCode,
20939
+ retryAtMs: error48.retryAtMs,
20940
+ usage: error48.usage
20653
20941
  }
20654
20942
  }
20655
20943
  };
@@ -20662,6 +20950,9 @@ var init_codex_adapter = __esm({
20662
20950
  code: error48.code,
20663
20951
  retryable: error48.retryable,
20664
20952
  dispatchOutcome,
20953
+ httpStatusCode: error48.httpStatusCode,
20954
+ retryAtMs: error48.retryAtMs,
20955
+ usage: error48.usage,
20665
20956
  cause: error48
20666
20957
  });
20667
20958
  }
@@ -27341,7 +27632,7 @@ function createDefaultInferenceHostRunnerDependencies(options) {
27341
27632
  envelopePublicKey: options.envelopePublicKey
27342
27633
  };
27343
27634
  }
27344
- var import_ajv, RUNTIME_RECEIPT_SCHEMA_VERSION, ATTEMPT_RECEIPT_SCHEMA_VERSION, DEFAULT_ADVERTISEMENT_TTL_MS, DEFAULT_ADVERTISEMENT_REFRESH_LEAD_MS, DEFAULT_HOST_HEARTBEAT_MS, DEFAULT_ATTEMPT_HEARTBEAT_MS, DEFAULT_DRAIN_TIMEOUT_MS, DEFAULT_REMOTE_RETRY_LIMIT, DEFAULT_MAX_CONCURRENCY, MIN_SLEEP_MS, CODEX_INFERENCE_ADVERTISED_MODEL, FileInferenceHostRuntimeReceiptStore, InferenceHostRecoveryRequiredError, InferenceHostRunnerError, defaultSleep, abortError, defaultRegisterSignalHandlers, safeInteger, exactObjectKeys, validIso, validateAttemptReceipt, validateRuntimeReceipt, sha256, stableOperationId, buildAttemptStartRequest, isoAt, finitePositiveOption, validateRunnerOptions, assertLocalCredentialIdentity, retryableRemoteError, controlPlaneFatal, defaultValidateOutput, objectRecord, positiveUtf8ByteLimit, assertCompilableOutputSchema, parseOutputContract, safeFailureCode, exactJson2, runnerIdentityMismatch, assertAdvertisementResult, assertHostHeartbeatResult, assertClaimResult, assertStartResult, assertJobHeartbeatResult, assertTerminalResult, classifyFailure, ambiguousHeartbeatTransport, reportedUsage, InferenceHostRunner;
27635
+ var import_ajv, RUNTIME_RECEIPT_SCHEMA_VERSION, ATTEMPT_RECEIPT_SCHEMA_VERSION, DEFAULT_ADVERTISEMENT_TTL_MS, DEFAULT_ADVERTISEMENT_REFRESH_LEAD_MS, DEFAULT_HOST_HEARTBEAT_MS, DEFAULT_ATTEMPT_HEARTBEAT_MS, DEFAULT_DRAIN_TIMEOUT_MS, DEFAULT_REMOTE_RETRY_LIMIT, DEFAULT_MAX_CONCURRENCY, MIN_SLEEP_MS, DEFAULT_CODEX_ACCOUNT_COOLDOWN_MS, CODEX_INFERENCE_ADVERTISED_MODEL, FileInferenceHostRuntimeReceiptStore, InferenceHostRecoveryRequiredError, InferenceHostRunnerError, defaultSleep, abortError, defaultRegisterSignalHandlers, safeInteger, exactObjectKeys, validIso, validateAttemptReceipt, validateRuntimeReceipt, sha256, stableOperationId, buildAttemptStartRequest, isoAt, finitePositiveOption, validateRunnerOptions, assertLocalCredentialIdentity, retryableRemoteError, remoteRetryAfterMs, controlPlaneFatal, defaultValidateOutput, objectRecord, positiveUtf8ByteLimit, assertCompilableOutputSchema, parseOutputContract, safeFailureCode, exactJson2, runnerIdentityMismatch, assertAdvertisementResult, assertHostHeartbeatResult, assertClaimResult, assertStartResult, assertJobHeartbeatResult, assertTerminalResult, classifyFailure, ambiguousHeartbeatTransport, reportedTokenUsage, reportedUsage, InferenceHostRunner;
27345
27636
  var init_runner = __esm({
27346
27637
  "lib/inference-host/runner.ts"() {
27347
27638
  "use strict";
@@ -27364,6 +27655,7 @@ var init_runner = __esm({
27364
27655
  DEFAULT_REMOTE_RETRY_LIMIT = 3;
27365
27656
  DEFAULT_MAX_CONCURRENCY = 1;
27366
27657
  MIN_SLEEP_MS = 10;
27658
+ DEFAULT_CODEX_ACCOUNT_COOLDOWN_MS = 5 * 60 * 1e3;
27367
27659
  CODEX_INFERENCE_ADVERTISED_MODEL = Object.freeze({
27368
27660
  model_id: INITIAL_CODEX_INFERENCE_MODEL,
27369
27661
  label: "Codex GPT-5.6 Sol",
@@ -27653,19 +27945,14 @@ var init_runner = __esm({
27653
27945
  };
27654
27946
  retryableRemoteError = (error48) => {
27655
27947
  if (error48 instanceof ExternalInferenceMcpError) {
27656
- return [
27657
- "network_error",
27658
- "transport_rejected",
27659
- "invalid_response",
27660
- "request_timeout",
27661
- "authentication_timeout"
27662
- ].includes(error48.code);
27948
+ return error48.retryable;
27663
27949
  }
27664
27950
  if (error48 && typeof error48 === "object" && "retryable" in error48) {
27665
27951
  return error48.retryable === true;
27666
27952
  }
27667
27953
  return false;
27668
27954
  };
27955
+ remoteRetryAfterMs = (error48, fallbackMs) => error48 instanceof ExternalInferenceMcpError && error48.retryAfterMs !== null && Number.isSafeInteger(error48.retryAfterMs) && error48.retryAfterMs >= 0 ? Math.max(fallbackMs, error48.retryAfterMs) : fallbackMs;
27669
27956
  controlPlaneFatal = (error48) => {
27670
27957
  const code = error48 && typeof error48 === "object" && "code" in error48 ? String(error48.code) : "";
27671
27958
  return [
@@ -27788,11 +28075,12 @@ var init_runner = __esm({
27788
28075
  };
27789
28076
  assertAdvertisementResult = (advertisement, result2) => {
27790
28077
  const host = result2.host;
27791
- if (host.host_id !== advertisement.host_id || host.host_generation !== advertisement.host_generation || host.advertisement_generation !== advertisement.advertisement_generation || host.key_generation !== advertisement.key_generation || host.display_name !== advertisement.display_name || host.adapter !== advertisement.adapter || host.status === "revoked" || !exactJson2(host.models, advertisement.models)) runnerIdentityMismatch("Host advertisement");
28078
+ if (host.host_id !== advertisement.host_id || host.host_generation !== advertisement.host_generation || host.advertisement_generation !== advertisement.advertisement_generation || host.key_generation !== advertisement.key_generation || host.display_name !== advertisement.display_name || (host.authenticated_account_email ?? null) !== (advertisement.authenticated_account_email ?? null) || (host.authenticated_account_plan ?? null) !== (advertisement.authenticated_account_plan ?? null) || host.adapter !== advertisement.adapter || host.status === "revoked" || !exactJson2(host.models, advertisement.models)) runnerIdentityMismatch("Host advertisement");
27792
28079
  };
27793
- assertHostHeartbeatResult = (request, result2) => {
28080
+ assertHostHeartbeatResult = (request, result2, allowOfflineProjection = false) => {
27794
28081
  const expectedStatus = request.status === "healthy" ? "online" : request.status;
27795
- if (result2.host.host_id !== request.host_id || result2.host.host_generation !== request.host_generation || result2.host.advertisement_generation !== request.advertisement_generation || result2.host.key_generation !== request.key_generation || result2.host.adapter !== "codex" || result2.host.status !== expectedStatus) runnerIdentityMismatch("Host heartbeat");
28082
+ const expectedTemporaryOffline = allowOfflineProjection && request.status === "healthy" && result2.host.status === "offline";
28083
+ if (result2.host.host_id !== request.host_id || result2.host.host_generation !== request.host_generation || result2.host.advertisement_generation !== request.advertisement_generation || result2.host.key_generation !== request.key_generation || result2.host.adapter !== "codex" || result2.host.status !== expectedStatus && !expectedTemporaryOffline) runnerIdentityMismatch("Host heartbeat");
27796
28084
  };
27797
28085
  assertClaimResult = (request, result2) => {
27798
28086
  if (result2.claim_request_id !== request.claim_request_id) {
@@ -27828,7 +28116,13 @@ var init_runner = __esm({
27828
28116
  };
27829
28117
  }
27830
28118
  if (error48 instanceof ExternalInferenceMcpError) {
27831
- const category2 = error48.code.startsWith("authentication") ? "auth" : ["network_error", "transport_rejected", "request_timeout"].includes(error48.code) ? "network" : "transport";
28119
+ const category2 = error48.code.startsWith("authentication") ? "auth" : [
28120
+ "network_error",
28121
+ "transport_rejected",
28122
+ "server_rejected",
28123
+ "rate_limited",
28124
+ "request_timeout"
28125
+ ].includes(error48.code) ? "network" : "transport";
27832
28126
  return {
27833
28127
  category: category2,
27834
28128
  code: safeFailureCode(error48.code, "transport_failure"),
@@ -27854,24 +28148,27 @@ var init_runner = __esm({
27854
28148
  return [
27855
28149
  "network_error",
27856
28150
  "transport_rejected",
28151
+ "server_rejected",
28152
+ "rate_limited",
27857
28153
  "invalid_response",
27858
28154
  "identity_mismatch",
27859
28155
  "request_timeout",
27860
28156
  "authentication_timeout"
27861
28157
  ].includes(code);
27862
28158
  };
27863
- reportedUsage = (result2) => ({
28159
+ reportedTokenUsage = (usage) => ({
27864
28160
  schema_version: EXTERNAL_INFERENCE_USAGE_SCHEMA_VERSION,
27865
28161
  availability: "reported",
27866
28162
  source: "codex_app_server",
27867
- input_tokens: result2.usage.inputTokens,
27868
- cached_input_tokens: result2.usage.cachedInputTokens,
27869
- output_tokens: result2.usage.outputTokens,
27870
- reasoning_output_tokens: result2.usage.reasoningOutputTokens,
27871
- total_tokens: result2.usage.totalTokens,
27872
- cache_write_input_tokens: result2.usage.cacheWriteInputTokens,
27873
- cache_write_support: result2.usage.cacheWriteSupported ? "reported" : "unsupported"
27874
- });
28163
+ input_tokens: usage.inputTokens,
28164
+ cached_input_tokens: usage.cachedInputTokens,
28165
+ output_tokens: usage.outputTokens,
28166
+ reasoning_output_tokens: usage.reasoningOutputTokens,
28167
+ total_tokens: usage.totalTokens,
28168
+ cache_write_input_tokens: usage.cacheWriteInputTokens,
28169
+ cache_write_support: usage.cacheWriteSupported ? "reported" : "unsupported"
28170
+ });
28171
+ reportedUsage = (result2) => reportedTokenUsage(result2.usage);
27875
28172
  InferenceHostRunner = class {
27876
28173
  constructor(dependencies, options) {
27877
28174
  this.receiptWriteChain = Promise.resolve();
@@ -28034,24 +28331,24 @@ var init_runner = __esm({
28034
28331
  } catch (error48) {
28035
28332
  lastError = error48;
28036
28333
  if (!retryableRemoteError(error48) || attempt === settings.remoteRetryLimit) throw error48;
28037
- const retryDelay = Math.min(
28038
- 250 * attempt,
28039
- 1e3,
28040
- boundary.deadlineAtMs === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, boundary.deadlineAtMs - now())
28334
+ const requestedRetryDelay = remoteRetryAfterMs(
28335
+ error48,
28336
+ Math.min(250 * attempt, 1e3)
28041
28337
  );
28042
- if (retryDelay < 1) {
28338
+ const remaining = boundary.deadlineAtMs === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, boundary.deadlineAtMs - now());
28339
+ if (requestedRetryDelay < 1 || requestedRetryDelay >= remaining) {
28043
28340
  throw new ExternalInferenceMcpError(
28044
28341
  "request_timeout",
28045
28342
  "Insights MCP retry deadline elapsed.",
28046
28343
  { cause: error48 }
28047
28344
  );
28048
28345
  }
28049
- await sleep4(retryDelay, boundary.signal);
28346
+ await sleep4(requestedRetryDelay, boundary.signal);
28050
28347
  }
28051
28348
  }
28052
28349
  throw lastError;
28053
28350
  };
28054
- const publishAdvertisement = async (health, forceAdvance) => {
28351
+ const publishAdvertisement = async (health, forceAdvance, retryRemote = true) => {
28055
28352
  let advertisement = receipt.pending_advertisement;
28056
28353
  if (!advertisement) {
28057
28354
  const generation = receipt.registered && forceAdvance ? receipt.advertisement_generation + 1 : receipt.advertisement_generation;
@@ -28072,6 +28369,8 @@ var init_runner = __esm({
28072
28369
  key_generation: localState.key_generation,
28073
28370
  adapter: "codex",
28074
28371
  display_name: this.options.displayName.trim(),
28372
+ authenticated_account_email: this.options.authenticatedAccountEmail ?? null,
28373
+ authenticated_account_plan: this.options.authenticatedAccountPlan ?? null,
28075
28374
  protocol_version: this.options.protocolVersion,
28076
28375
  envelope_public_key: envelopePublicKey,
28077
28376
  health,
@@ -28094,15 +28393,12 @@ var init_runner = __esm({
28094
28393
  const tool = receipt.registered ? "inference.host.advertise" : "inference.host.register";
28095
28394
  let result2;
28096
28395
  try {
28097
- result2 = tool === "inference.host.register" ? await retryExact(() => mcp.callTool(
28098
- tool,
28099
- advertisement,
28100
- { signal: this.options.signal }
28101
- ), { signal: this.options.signal }) : await retryExact(() => mcp.callTool(
28396
+ const advertiseOperation = () => mcp.callTool(
28102
28397
  tool,
28103
28398
  advertisement,
28104
28399
  { signal: this.options.signal }
28105
- ), { signal: this.options.signal });
28400
+ );
28401
+ result2 = retryRemote ? await retryExact(advertiseOperation, { signal: this.options.signal }) : await advertiseOperation();
28106
28402
  } catch (error48) {
28107
28403
  const definitivelyNotApplied = error48 instanceof ExternalInferenceMcpError ? error48.definitivelyNotApplied : Boolean(error48 && typeof error48 === "object" && error48.definitivelyNotApplied === true);
28108
28404
  const expectedGeneration = receipt.registered ? receipt.advertisement_generation + 1 : receipt.advertisement_generation;
@@ -28113,7 +28409,7 @@ var init_runner = __esm({
28113
28409
  updated_at: isoAt(now())
28114
28410
  };
28115
28411
  await this.persistReceipt(receipt, now);
28116
- return await publishAdvertisement(health, forceAdvance);
28412
+ return await publishAdvertisement(health, forceAdvance, retryRemote);
28117
28413
  }
28118
28414
  throw error48;
28119
28415
  }
@@ -28135,7 +28431,7 @@ var init_runner = __esm({
28135
28431
  }
28136
28432
  const hostHeartbeat = async (status, requestOptions = {
28137
28433
  signal: this.options.signal
28138
- }) => {
28434
+ }, retryRemote = true, allowOfflineProjection = false) => {
28139
28435
  const sequence = receipt.host_heartbeat_sequence + 1;
28140
28436
  const request = {
28141
28437
  schema_version: "external_inference_host_heartbeat_v1",
@@ -28161,9 +28457,9 @@ var init_runner = __esm({
28161
28457
  request,
28162
28458
  requestOptions
28163
28459
  );
28164
- const result2 = status === "draining" ? await heartbeatOperation() : await retryExact(heartbeatOperation, requestOptions);
28165
- assertHostHeartbeatResult(request, result2);
28166
- if (["revoked", "offline"].includes(result2.host.status)) {
28460
+ const result2 = status === "draining" || !retryRemote ? await heartbeatOperation() : await retryExact(heartbeatOperation, requestOptions);
28461
+ assertHostHeartbeatResult(request, result2, allowOfflineProjection);
28462
+ if (result2.host.status === "revoked" || result2.host.status === "offline" && !allowOfflineProjection) {
28167
28463
  const error48 = new InferenceHostRunnerError(
28168
28464
  result2.host.status === "revoked" ? "host_revoked" : "host_offline",
28169
28465
  "The external inference host is no longer active."
@@ -28173,7 +28469,9 @@ var init_runner = __esm({
28173
28469
  };
28174
28470
  await hostHeartbeat("healthy");
28175
28471
  let nextHostHeartbeatAt = now() + settings.hostHeartbeatMs;
28176
- let nextClaimAt = now();
28472
+ let nextAdvertisementAttemptAt = 0;
28473
+ let providerRetryAtMs = this.options.codexRateLimits && codexAccountRateLimitReached(this.options.codexRateLimits) ? codexRateLimitRetryAtMs(this.options.codexRateLimits) ?? now() + DEFAULT_CODEX_ACCOUNT_COOLDOWN_MS : null;
28474
+ let nextClaimAt = Math.max(now(), providerRetryAtMs ?? 0);
28177
28475
  let onceClaimed = false;
28178
28476
  const launchClaim = (claim, recovery) => {
28179
28477
  if (!recovery) {
@@ -28211,6 +28509,11 @@ var init_runner = __esm({
28211
28509
  attemptHeartbeatMs: settings.attemptHeartbeatMs,
28212
28510
  sleep: sleep4,
28213
28511
  now,
28512
+ onProviderCooldown: (retryAtMs) => {
28513
+ if (!Number.isSafeInteger(retryAtMs) || retryAtMs <= now()) return;
28514
+ providerRetryAtMs = Math.max(providerRetryAtMs ?? 0, retryAtMs);
28515
+ nextClaimAt = Math.max(nextClaimAt, providerRetryAtMs);
28516
+ },
28214
28517
  resumeReceipt: recovery
28215
28518
  }).then((outcome) => {
28216
28519
  if (outcome === "completed") completed += 1;
@@ -28270,22 +28573,46 @@ var init_runner = __esm({
28270
28573
  while (!drainRequested) {
28271
28574
  const currentTime = now();
28272
28575
  const expiresAt = receipt.advertisement_expires_at ? Date.parse(receipt.advertisement_expires_at) : 0;
28273
- if (expiresAt - currentTime <= settings.advertisementRefreshLeadMs) {
28576
+ if (currentTime >= nextAdvertisementAttemptAt && expiresAt - currentTime <= settings.advertisementRefreshLeadMs) {
28274
28577
  try {
28275
- await publishAdvertisement("healthy", true);
28578
+ await publishAdvertisement("healthy", true, false);
28579
+ nextAdvertisementAttemptAt = 0;
28276
28580
  } catch (error48) {
28277
- requestDrain(controlPlaneFatal(error48) ? "authority_lost" : "advertisement_failed");
28278
- break;
28581
+ if (controlPlaneFatal(error48)) {
28582
+ requestDrain("authority_lost");
28583
+ break;
28584
+ }
28585
+ if (!retryableRemoteError(error48)) {
28586
+ requestDrain("advertisement_failed");
28587
+ break;
28588
+ }
28589
+ nextAdvertisementAttemptAt = now() + remoteRetryAfterMs(error48, 1e3);
28590
+ nextClaimAt = Math.max(nextClaimAt, nextAdvertisementAttemptAt);
28279
28591
  }
28280
28592
  }
28281
28593
  if (currentTime >= nextHostHeartbeatAt) {
28282
28594
  try {
28283
- await hostHeartbeat("healthy");
28595
+ const refreshPendingAcrossExpiry = receipt.pending_advertisement !== null && expiresAt <= currentTime && nextAdvertisementAttemptAt > currentTime;
28596
+ await hostHeartbeat(
28597
+ "healthy",
28598
+ { signal: this.options.signal },
28599
+ false,
28600
+ refreshPendingAcrossExpiry
28601
+ );
28284
28602
  } catch (error48) {
28285
- requestDrain(controlPlaneFatal(error48) ? "authority_lost" : "host_heartbeat_failed");
28286
- break;
28603
+ if (controlPlaneFatal(error48)) {
28604
+ requestDrain("authority_lost");
28605
+ break;
28606
+ }
28607
+ if (!retryableRemoteError(error48)) {
28608
+ requestDrain("host_heartbeat_failed");
28609
+ break;
28610
+ }
28611
+ nextHostHeartbeatAt = now() + remoteRetryAfterMs(error48, settings.hostHeartbeatMs);
28612
+ }
28613
+ if (!drainRequested && currentTime >= nextHostHeartbeatAt) {
28614
+ nextHostHeartbeatAt = now() + settings.hostHeartbeatMs;
28287
28615
  }
28288
- nextHostHeartbeatAt = now() + settings.hostHeartbeatMs;
28289
28616
  }
28290
28617
  while (recoveryQueue.length > 0 && active.size < settings.maxConcurrency) {
28291
28618
  const recovery = recoveryQueue.shift();
@@ -28316,15 +28643,19 @@ var init_runner = __esm({
28316
28643
  await this.persistReceipt(receipt, now);
28317
28644
  let claim;
28318
28645
  try {
28319
- claim = await retryExact(() => mcp.callTool(
28646
+ claim = await mcp.callTool(
28320
28647
  "inference.job.claim",
28321
28648
  request,
28322
28649
  { signal: this.options.signal }
28323
- ), { signal: this.options.signal });
28650
+ );
28324
28651
  assertClaimResult(request, claim);
28325
28652
  } catch (error48) {
28326
- if (!this.options.signal?.aborted) {
28327
- requestDrain(controlPlaneFatal(error48) ? "authority_lost" : "claim_failed");
28653
+ if (!this.options.signal?.aborted && controlPlaneFatal(error48)) {
28654
+ requestDrain("authority_lost");
28655
+ } else if (!this.options.signal?.aborted && retryableRemoteError(error48)) {
28656
+ nextClaimAt = now() + remoteRetryAfterMs(error48, 1e3);
28657
+ } else if (!this.options.signal?.aborted) {
28658
+ requestDrain("claim_failed");
28328
28659
  }
28329
28660
  break;
28330
28661
  }
@@ -28337,10 +28668,12 @@ var init_runner = __esm({
28337
28668
  if (this.options.once) requestDrain("once_complete");
28338
28669
  }
28339
28670
  if (drainRequested) break;
28340
- const wakeAt = Math.min(nextClaimAt, nextHostHeartbeatAt, Math.max(
28341
- now() + MIN_SLEEP_MS,
28342
- receipt.advertisement_expires_at ? Date.parse(receipt.advertisement_expires_at) - settings.advertisementRefreshLeadMs : now() + MIN_SLEEP_MS
28343
- ));
28671
+ const advertisementDueAt = nextAdvertisementAttemptAt > now() ? nextAdvertisementAttemptAt : receipt.advertisement_expires_at ? Date.parse(receipt.advertisement_expires_at) - settings.advertisementRefreshLeadMs : now() + MIN_SLEEP_MS;
28672
+ const wakeAt = Math.min(
28673
+ nextClaimAt,
28674
+ nextHostHeartbeatAt,
28675
+ Math.max(now() + MIN_SLEEP_MS, advertisementDueAt)
28676
+ );
28344
28677
  await Promise.race([
28345
28678
  sleep4(Math.max(MIN_SLEEP_MS, wakeAt - now())),
28346
28679
  drainSignal,
@@ -28684,6 +29017,9 @@ var init_runner = __esm({
28684
29017
  if (error48 instanceof InferenceHostRunnerError && error48.code === "terminal_outcome_unconfirmed") {
28685
29018
  throw error48;
28686
29019
  }
29020
+ if (error48 instanceof CodexAppServerError && error48.retryAtMs !== null && ["quota_exceeded", "codex_rate_limited"].includes(error48.code)) {
29021
+ options.onProviderCooldown?.(error48.retryAtMs);
29022
+ }
28687
29023
  const classified = classifyFailure(heartbeatFailure ?? error48, adapterResult);
28688
29024
  const heartbeatAmbiguous = ambiguousHeartbeatTransport(heartbeatFailure);
28689
29025
  const failure = heartbeatAmbiguous ? {
@@ -28734,7 +29070,7 @@ var init_runner = __esm({
28734
29070
  adapter_request_id: canReportEffective ? adapterResult?.adapterRequestId ?? null : null,
28735
29071
  adapter_response_id: canReportEffective ? adapterResult?.adapterResponseId ?? null : null
28736
29072
  },
28737
- usage: adapterResult ? reportedUsage(adapterResult) : null,
29073
+ usage: adapterResult ? reportedUsage(adapterResult) : error48 instanceof CodexAppServerError && error48.usage ? reportedTokenUsage(error48.usage) : null,
28738
29074
  failure_category: failure.category,
28739
29075
  failure_code: failure.code,
28740
29076
  retryable: failure.retryable,
@@ -28808,6 +29144,9 @@ import { join as join5, resolve as resolve4 } from "node:path";
28808
29144
  async function runInferenceHostCli(argv2, env = process.env, dependencies = {}) {
28809
29145
  const warnings = [];
28810
29146
  try {
29147
+ if (argv2.includes("--help") || argv2.includes("-h") || argv2[1] === "help") {
29148
+ return { exitCode: 0, stdout: INFERENCE_HOST_HELP, stderr: "" };
29149
+ }
28811
29150
  const parsed = parseInferenceHostArgs(argv2, env);
28812
29151
  const config2 = resolveInferenceHostConfig(env);
28813
29152
  if (parsed.command === "login") {
@@ -28873,7 +29212,7 @@ async function runInferenceHostCli(argv2, env = process.env, dependencies = {})
28873
29212
  };
28874
29213
  }
28875
29214
  }
28876
- var INFERENCE_HOST_CLI_VERSION, runtimeReceiptPath, codexRecoveryPath, codexGuardianReceiptRoot, revocationCheckpointPath, foregroundHostLockPath, AGENT_HEARTBEAT_INTERVAL_MS, AGENT_HEARTBEAT_MAX_RETRY_DELAY_MS, retryableAgentHeartbeatError, assertRevocationCheckpoint, readRevocationCheckpoint, writeRevocationCheckpoint, clearRevocationCheckpoint, render, parsePositiveInteger, parseInferenceHostArgs, defaultOpenBrowser, defaultRegisterLifecycleSignalHandlers, lifecycleCancellation, configuredCredentialStore, accountKeyForState, assertCredentialMatchesState2, revocationCheckpointForCredential, assertRevocationCheckpointMatchesLocalIdentity, fileExistsPrivately, inspectCodexAuthentication, clearLocalRuntimeArtifacts, assertLocalRuntimeArtifactsMayBeDiscarded, defaultRunForeground, login, codexLogin, codexLogout, localStatus, doctor, cleanupLogin, runHost, defaultReadStdin, parseAgentStdin, agentOperationId, agentSession, agentConnect, agentRun, agentNext, agentComplete, agentFail, withAgentCommandLock;
29215
+ var INFERENCE_HOST_CLI_VERSION, runtimeReceiptPath, codexRecoveryPath, codexGuardianReceiptRoot, revocationCheckpointPath, foregroundHostLockPath, AGENT_HEARTBEAT_INTERVAL_MS, AGENT_HEARTBEAT_MAX_RETRY_DELAY_MS, retryableAgentHeartbeatError, assertRevocationCheckpoint, readRevocationCheckpoint, writeRevocationCheckpoint, clearRevocationCheckpoint, render, INFERENCE_HOST_HELP, parsePositiveInteger, parseInferenceHostArgs, defaultOpenBrowser, defaultRegisterLifecycleSignalHandlers, lifecycleCancellation, configuredCredentialStore, accountKeyForState, assertCredentialMatchesState2, revocationCheckpointForCredential, assertRevocationCheckpointMatchesLocalIdentity, fileExistsPrivately, inspectCodexAuthentication, clearLocalRuntimeArtifacts, assertLocalRuntimeArtifactsMayBeDiscarded, defaultRunForeground, login, codexLogin, codexLogout, localStatus, doctor, cleanupLogin, runHost, defaultReadStdin, parseAgentStdin, agentOperationId, agentSession, agentConnect, agentRun, agentNext, agentComplete, agentFail, withAgentCommandLock;
28877
29216
  var init_cli = __esm({
28878
29217
  "lib/inference-host/cli.ts"() {
28879
29218
  "use strict";
@@ -28955,6 +29294,31 @@ var init_cli = __esm({
28955
29294
  render = (value, json2) => json2 ? `${JSON.stringify(value, null, 2)}
28956
29295
  ` : typeof value === "string" ? `${value}
28957
29296
  ` : `${JSON.stringify(value, null, 2)}
29297
+ `;
29298
+ INFERENCE_HOST_HELP = `Usage: vtx inference-host <command> [options]
29299
+
29300
+ Commands:
29301
+ login Authorize the isolated VTX insights:inference grant
29302
+ codex-login Sign the automated Codex host into a ChatGPT subscription
29303
+ run Run the automated Codex inference host in the foreground
29304
+ agent-connect Advertise a model from a compatible agent harness
29305
+ agent-run Keep an agent-driven host online in the foreground
29306
+ agent-next Claim the next exact VTX inference request
29307
+ agent-complete Submit one completed agent result from stdin
29308
+ agent-fail Submit one truthful agent failure from stdin
29309
+ status Inspect local host and credential state
29310
+ doctor Verify credentials, Codex, and private runtime state
29311
+ logout Remove local VTX host state without revoking the grant
29312
+ revoke Revoke the VTX grant and remove local host state
29313
+ codex-logout Remove the automated host's dedicated Codex login
29314
+
29315
+ Common options:
29316
+ --json Emit machine-readable JSON
29317
+ --help, -h Show this help
29318
+
29319
+ If the OS credential store cannot retain the VTX grant, set
29320
+ VTX_INFERENCE_HOST_CREDENTIAL_STORE=file before login to use the supported
29321
+ private-file fallback. See https://vtxmacro.com/insights#subscription-inference.
28958
29322
  `;
28959
29323
  parsePositiveInteger = (raw, label) => {
28960
29324
  const value = Number(raw);
@@ -29194,6 +29558,9 @@ var init_cli = __esm({
29194
29558
  acquireProcessLock: async () => borrowedProcessLock
29195
29559
  }, {
29196
29560
  displayName: options.displayName,
29561
+ authenticatedAccountEmail: options.authenticatedAccountEmail,
29562
+ authenticatedAccountPlan: options.authenticatedAccountPlan,
29563
+ codexRateLimits: options.codexRateLimits,
29197
29564
  protocolVersion: EXTERNAL_INFERENCE_CONTRACT_VERSION,
29198
29565
  adapterRuntimeVersion: INFERENCE_HOST_CLI_VERSION,
29199
29566
  maxConcurrency: options.maxConcurrency,
@@ -29677,7 +30044,7 @@ Waiting for approval...
29677
30044
  cancellation.signal.throwIfAborted();
29678
30045
  const hostResources = await (dependencies.createHostResources ?? createIsolatedCodexHostResources)(config2.codexHomePath);
29679
30046
  try {
29680
- await (dependencies.preflightCodex ?? preflightCodexSubscription)({
30047
+ const preflight = await (dependencies.preflightCodex ?? preflightCodexSubscription)({
29681
30048
  binary: verifiedBinary,
29682
30049
  codexHome: config2.codexHomePath,
29683
30050
  deadlineAtMs: Date.now() + 3e4,
@@ -29689,6 +30056,9 @@ Waiting for approval...
29689
30056
  verifiedBinary,
29690
30057
  hostResources,
29691
30058
  displayName: parsed.displayName,
30059
+ authenticatedAccountEmail: preflight?.authenticated_account_email ?? null,
30060
+ authenticatedAccountPlan: preflight?.authenticated_account_plan ?? null,
30061
+ codexRateLimits: preflight?.rate_limits ?? null,
29692
30062
  maxConcurrency: parsed.maxConcurrency,
29693
30063
  once: parsed.once,
29694
30064
  env,
@@ -48303,6 +48673,9 @@ Waiting for approval...
48303
48673
  }
48304
48674
  async function runVtxCli(argv2, env = process.env) {
48305
48675
  try {
48676
+ if (argv2.length === 0 || argv2.includes("--help") || argv2.includes("-h") || argv2[0] === "help") {
48677
+ return { exitCode: 0, stdout: VTX_CLI_HELP, stderr: "" };
48678
+ }
48306
48679
  const parsed = parseFlags(argv2);
48307
48680
  const [group, command, ...args] = parsed.rest;
48308
48681
  const config2 = resolveAgentCliConfig(env);
@@ -48550,7 +48923,7 @@ async function runVtxCli(argv2, env = process.env) {
48550
48923
  ` };
48551
48924
  }
48552
48925
  }
48553
- var WRITABLE_SECRET_FIELDS, delay;
48926
+ var VTX_CLI_HELP, WRITABLE_SECRET_FIELDS, delay;
48554
48927
  var init_vtx = __esm({
48555
48928
  "lib/agent-cli/vtx.ts"() {
48556
48929
  "use strict";
@@ -48559,6 +48932,24 @@ var init_vtx = __esm({
48559
48932
  init_headless_runtime();
48560
48933
  init_types();
48561
48934
  init_hyperliquid_market_symbol();
48935
+ VTX_CLI_HELP = `Usage: vtx [--json] [--profile <id>] <command>
48936
+
48937
+ Common commands:
48938
+ auth login Authorize the normal VTX CLI
48939
+ auth whoami Show the current VTX CLI identity
48940
+ profiles list List accessible profiles
48941
+ bots status Read the selected profile's Trader state
48942
+ bots start Start the normal VTX Trader
48943
+ bots stop Stop the normal VTX Trader
48944
+ runtime events Read recent runtime events and errors
48945
+ inference-host --help Set up an automated or agent-driven inference host
48946
+
48947
+ Global options:
48948
+ --json Emit machine-readable JSON
48949
+ --profile <id> Select one VTX profile
48950
+ --version, -v Show the installed CLI version
48951
+ --help, -h Show this help
48952
+ `;
48562
48953
  WRITABLE_SECRET_FIELDS = /* @__PURE__ */ new Set([
48563
48954
  "hyperliquid_signing_key",
48564
48955
  "local_ai_api_key",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtxmacro/cli",
3
- "version": "2026.8.12",
3
+ "version": "2026.8.13",
4
4
  "description": "VTX Macro CLI, MCP server, and foreground subscription inference host.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",