@vtxmacro/cli 2026.8.11 → 2026.8.12

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 +8 -3
  2. package/bin/vtx.js +183 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -77,6 +77,7 @@ vtx inference-host agent-connect \
77
77
  --model-label "<display label>" \
78
78
  --effort <reasoning-effort> \
79
79
  --json
80
+ vtx inference-host agent-run
80
81
  vtx inference-host agent-next --wait-seconds 50 --json
81
82
  ```
82
83
 
@@ -99,7 +100,9 @@ Use `not_dispatched` only when no harness inference began,
99
100
  `outcome_unknown` when dispatch itself cannot be confirmed. Pipe exactly one
100
101
  object to `vtx inference-host agent-fail --json`.
101
102
 
102
- Keep calling `agent-next` while the host should remain available. This same loop
103
+ Keep `agent-run` open in a separate terminal and keep calling `agent-next`
104
+ while the host should remain available. The independent keeper preserves
105
+ truthful liveness while the harness reasons. This same loop
103
106
  supports Main, Review, and Screener; Provider response and Decision candidate;
104
107
  and normally running VTX Client Mode or Server Mode bots. Server Mode keeps the
105
108
  normal VTX call fee. Client Mode has no VTX platform fee. Neither mode falls back
@@ -107,8 +110,10 @@ to another model when the agent host is unavailable.
107
110
 
108
111
  ## Remove
109
112
 
110
- First revoke the VTX inference grant, then uninstall the package. Automated
111
- Codex users should also remove the dedicated Codex login:
113
+ First stop the foreground run or agent-run process, revoke the VTX inference
114
+ grant, then uninstall the package. Cleanup refuses while a foreground host
115
+ process still owns the host. Automated Codex users should also remove the
116
+ dedicated Codex login:
112
117
 
113
118
  ```bash
114
119
  vtx inference-host revoke
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.11",
41
+ package_version: "2026.8.12",
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, 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, 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";
@@ -15051,6 +15051,12 @@ var init_external_inference_contract = __esm({
15051
15051
  host_id: identifierSchema,
15052
15052
  requested_at: timestampSchema
15053
15053
  });
15054
+ agentHeartbeatRequestSchema = external_exports.strictObject({
15055
+ operation_id: operationIdentifierSchema,
15056
+ host_id: identifierSchema,
15057
+ attempt_id: identifierSchema.nullable().optional(),
15058
+ requested_at: timestampSchema
15059
+ });
15054
15060
  emptyAgentNextResultSchema = external_exports.strictObject({
15055
15061
  claim_state: external_exports.literal("empty"),
15056
15062
  retry_after_ms: nonNegativeSafeIntegerSchema
@@ -16979,7 +16985,7 @@ ${body}`;
16979
16985
  });
16980
16986
 
16981
16987
  // lib/inference-host/mcp-client.ts
16982
- 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, jobCompletionResultSchema, jobFailureResultSchema, toolContracts, discoveryResultSchema, toolListResultSchema, inlineStructuredContentSchema, protocolMeta, parseJsonDocument, parseSseDocuments, parseResponseDocuments, exactOperationalInventory, exactJson, invalidBoundResult, verifyToolResult, ExternalInferenceMcpClient;
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;
16983
16989
  var init_mcp_client = __esm({
16984
16990
  "lib/inference-host/mcp-client.ts"() {
16985
16991
  "use strict";
@@ -16999,6 +17005,7 @@ var init_mcp_client = __esm({
16999
17005
  EXTERNAL_INFERENCE_OPERATIONAL_TOOLS = [
17000
17006
  ...EXTERNAL_INFERENCE_AUTOMATED_HOST_TOOLS,
17001
17007
  "inference.agent.connect",
17008
+ "inference.agent.heartbeat",
17002
17009
  "inference.agent.next",
17003
17010
  "inference.agent.complete",
17004
17011
  "inference.agent.fail"
@@ -17030,6 +17037,20 @@ var init_mcp_client = __esm({
17030
17037
  reasoning_effort: safeCodeSchema2,
17031
17038
  replayed: external_exports.boolean()
17032
17039
  });
17040
+ agentHeartbeatResultSchema = external_exports.strictObject({
17041
+ host: hostStatusReadSchema,
17042
+ active_attempts: external_exports.number().int().min(0).max(1),
17043
+ available_slots: external_exports.number().int().min(0).max(1),
17044
+ replayed: external_exports.boolean()
17045
+ }).superRefine((value, context) => {
17046
+ if (value.active_attempts + value.available_slots !== 1) {
17047
+ context.addIssue({
17048
+ code: "custom",
17049
+ message: "Agent heartbeat capacity must describe exactly one slot.",
17050
+ path: ["available_slots"]
17051
+ });
17052
+ }
17053
+ });
17033
17054
  jobCompletionResultSchema = external_exports.strictObject({
17034
17055
  job_id: identifierSchema2,
17035
17056
  attempt_id: identifierSchema2,
@@ -17099,6 +17120,10 @@ var init_mcp_client = __esm({
17099
17120
  arguments: agentConnectRequestSchema,
17100
17121
  result: agentConnectResultSchema
17101
17122
  },
17123
+ "inference.agent.heartbeat": {
17124
+ arguments: agentHeartbeatRequestSchema,
17125
+ result: agentHeartbeatResultSchema
17126
+ },
17102
17127
  "inference.agent.next": {
17103
17128
  arguments: agentNextRequestSchema,
17104
17129
  result: agentNextResultSchema
@@ -17225,6 +17250,12 @@ var init_mcp_client = __esm({
17225
17250
  if (!host || host.host_id !== request.host_id || host.adapter !== request.adapter || result2.model_id !== request.model_id || result2.reasoning_effort !== request.reasoning_effort) invalidBoundResult(name);
17226
17251
  return;
17227
17252
  }
17253
+ if (name === "inference.agent.heartbeat") {
17254
+ const host = result2.host;
17255
+ const expectedActive = request.attempt_id == null ? 0 : 1;
17256
+ if (!host || host.host_id !== request.host_id || host.status !== "online" || result2.active_attempts !== expectedActive || result2.available_slots !== 1 - expectedActive) invalidBoundResult(name);
17257
+ return;
17258
+ }
17228
17259
  if (name === "inference.agent.next") {
17229
17260
  if (result2.claim_state === "claimed" && !result2.job_id) invalidBoundResult(name);
17230
17261
  return;
@@ -17343,7 +17374,7 @@ var init_mcp_client = __esm({
17343
17374
  }).passthrough().parse(rawResult);
17344
17375
  if (callResult.isError === true) {
17345
17376
  const errorText = (callResult.content ?? []).filter((block) => block.type === "text").map((block) => block.text ?? "").join("\n");
17346
- const definitivelyNotApplied = (name === "inference.host.register" || name === "inference.host.advertise") && errorText.includes("Advertisement is already expired.") || name === "inference.agent.next" && (errorText.includes("Advertisement time is outside the allowed clock skew.") || errorText.includes("Advertisement is already expired.") || errorText.includes("Heartbeat time is outside the allowed clock skew."));
17377
+ const definitivelyNotApplied = (name === "inference.host.register" || name === "inference.host.advertise") && errorText.includes("Advertisement is already expired.") || (name === "inference.agent.next" || name === "inference.agent.heartbeat") && (errorText.includes("Advertisement time is outside the allowed clock skew.") || errorText.includes("Advertisement is already expired.") || errorText.includes("Heartbeat time is outside the allowed clock skew."));
17347
17378
  throw new ExternalInferenceMcpError(
17348
17379
  "tool_rejected",
17349
17380
  `Insights MCP rejected ${name}.`,
@@ -28794,6 +28825,9 @@ async function runInferenceHostCli(argv2, env = process.env, dependencies = {})
28794
28825
  async () => await agentConnect(config2, parsed, dependencies, warnings)
28795
28826
  );
28796
28827
  }
28828
+ if (parsed.command === "agent-run") {
28829
+ return await agentRun(config2, parsed, dependencies, warnings);
28830
+ }
28797
28831
  if (parsed.command === "agent-next") {
28798
28832
  return await withAgentCommandLock(
28799
28833
  config2,
@@ -28828,7 +28862,7 @@ async function runInferenceHostCli(argv2, env = process.env, dependencies = {})
28828
28862
  return await cleanupLogin(config2, parsed, dependencies, warnings, true);
28829
28863
  }
28830
28864
  throw new Error(
28831
- "Usage: vtx inference-host <login|codex-login|run|agent-connect|agent-next|agent-complete|agent-fail|status|doctor|logout|codex-logout|revoke> [--json]"
28865
+ "Usage: vtx inference-host <login|codex-login|run|agent-connect|agent-run|agent-next|agent-complete|agent-fail|status|doctor|logout|codex-logout|revoke> [--json]"
28832
28866
  );
28833
28867
  } catch (error48) {
28834
28868
  return {
@@ -28839,7 +28873,7 @@ async function runInferenceHostCli(argv2, env = process.env, dependencies = {})
28839
28873
  };
28840
28874
  }
28841
28875
  }
28842
- var INFERENCE_HOST_CLI_VERSION, runtimeReceiptPath, codexRecoveryPath, codexGuardianReceiptRoot, revocationCheckpointPath, 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, agentNext, agentComplete, agentFail, withAgentCommandLock;
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;
28843
28877
  var init_cli = __esm({
28844
28878
  "lib/inference-host/cli.ts"() {
28845
28879
  "use strict";
@@ -28855,11 +28889,20 @@ var init_cli = __esm({
28855
28889
  init_crypto();
28856
28890
  init_oauth();
28857
28891
  init_runner();
28892
+ init_mcp_client();
28858
28893
  INFERENCE_HOST_CLI_VERSION = agent_cli_release_default.package_version;
28859
28894
  runtimeReceiptPath = (config2) => `${config2.statePath}.runtime.json`;
28860
28895
  codexRecoveryPath = (config2) => `${config2.statePath}.codex-recovery.json`;
28861
28896
  codexGuardianReceiptRoot = (config2) => `${config2.statePath}.codex-processes`;
28862
28897
  revocationCheckpointPath = (config2) => `${config2.statePath}.revoke.json`;
28898
+ foregroundHostLockPath = (config2) => `${config2.processLockPath}.foreground`;
28899
+ AGENT_HEARTBEAT_INTERVAL_MS = 3e3;
28900
+ AGENT_HEARTBEAT_MAX_RETRY_DELAY_MS = 9e3;
28901
+ retryableAgentHeartbeatError = (error48) => {
28902
+ if (!(error48 instanceof ExternalInferenceMcpError)) return false;
28903
+ if (error48.code === "network_error" || error48.code === "request_timeout") return true;
28904
+ return error48.code === "transport_rejected" && /HTTP (?:408|425|429|5\d\d)\b/u.test(error48.message);
28905
+ };
28863
28906
  assertRevocationCheckpoint = (value) => {
28864
28907
  if (!value || typeof value !== "object" || Array.isArray(value)) {
28865
28908
  throw new Error("Inference host revocation checkpoint is invalid.");
@@ -29489,7 +29532,18 @@ Waiting for approval...
29489
29532
  };
29490
29533
  cleanupLogin = async (config2, parsed, dependencies, warnings, revoke) => {
29491
29534
  const lock2 = await acquireInferenceHostProcessLock(config2.processLockPath);
29535
+ let keeperLock = null;
29492
29536
  try {
29537
+ try {
29538
+ keeperLock = await acquireInferenceHostProcessLock(foregroundHostLockPath(config2));
29539
+ } catch (error48) {
29540
+ if (error48 instanceof Error && error48.message.includes("Another inference host process already owns")) {
29541
+ throw new Error(
29542
+ "Stop vtx inference-host agent-run before logging out or revoking this host."
29543
+ );
29544
+ }
29545
+ throw error48;
29546
+ }
29493
29547
  if (!revoke) {
29494
29548
  await assertLocalRuntimeArtifactsMayBeDiscarded(config2);
29495
29549
  }
@@ -29592,13 +29646,16 @@ Waiting for approval...
29592
29646
  ` : ""
29593
29647
  };
29594
29648
  } finally {
29649
+ await keeperLock?.release();
29595
29650
  await lock2.release();
29596
29651
  }
29597
29652
  };
29598
29653
  runHost = async (config2, parsed, env, dependencies, warnings) => {
29599
29654
  const cancellation = lifecycleCancellation(dependencies);
29655
+ let foregroundLock = null;
29600
29656
  let processLock = null;
29601
29657
  try {
29658
+ foregroundLock = await acquireInferenceHostProcessLock(foregroundHostLockPath(config2));
29602
29659
  processLock = await acquireInferenceHostProcessLock(config2.processLockPath);
29603
29660
  cancellation.signal.throwIfAborted();
29604
29661
  if (await readRevocationCheckpoint(config2)) {
@@ -29649,6 +29706,7 @@ Waiting for approval...
29649
29706
  }
29650
29707
  } finally {
29651
29708
  await processLock?.release();
29709
+ await foregroundLock?.release();
29652
29710
  cancellation.unregister();
29653
29711
  }
29654
29712
  };
@@ -29728,12 +29786,130 @@ Waiting for approval...
29728
29786
  lanes: ["main", "review", "screener"],
29729
29787
  response_modes: ["provider_response", "decision_candidate"],
29730
29788
  execution_modes: ["client", "server"],
29731
- next_command: "vtx inference-host agent-next --wait-seconds 50 --json"
29789
+ next_command: "vtx inference-host agent-next --wait-seconds 50 --json",
29790
+ keeper_command: "vtx inference-host agent-run"
29732
29791
  }, parsed.json),
29733
29792
  stderr: warnings.length > 0 ? `${warnings.join("\n")}
29734
29793
  ` : ""
29735
29794
  };
29736
29795
  };
29796
+ agentRun = async (config2, parsed, dependencies, warnings) => {
29797
+ const cancellation = lifecycleCancellation(dependencies);
29798
+ let keeperLock = null;
29799
+ let heartbeatCount = 0;
29800
+ try {
29801
+ keeperLock = await acquireInferenceHostProcessLock(foregroundHostLockPath(config2));
29802
+ const store = configuredCredentialStore(config2, dependencies, (message) => {
29803
+ warnings.push(message);
29804
+ });
29805
+ const session = await (dependencies.createAgentSession ?? createInferenceAgentMcpSession)({
29806
+ config: config2,
29807
+ credentialStore: store,
29808
+ clientVersion: INFERENCE_HOST_CLI_VERSION,
29809
+ signal: cancellation.signal
29810
+ });
29811
+ const emitStdout = dependencies.emitStdout ?? ((text) => {
29812
+ process.stdout.write(text);
29813
+ });
29814
+ const emitStderr = dependencies.emitStderr ?? ((text) => {
29815
+ process.stderr.write(text);
29816
+ });
29817
+ let consecutiveHeartbeatFailures = 0;
29818
+ let retryDelayMs = AGENT_HEARTBEAT_INTERVAL_MS;
29819
+ let pendingHeartbeatRequest = null;
29820
+ emitStdout(parsed.json ? `${JSON.stringify({ status: "running", host_id: session.localState.host_id })}
29821
+ ` : "Agent-driven inference host keeper is running.\n");
29822
+ while (!cancellation.signal.aborted) {
29823
+ let commandLock = null;
29824
+ try {
29825
+ commandLock = await acquireInferenceHostProcessLock(config2.processLockPath);
29826
+ } catch (error48) {
29827
+ if (!(error48 instanceof Error) || !error48.message.includes("Another inference host process already owns")) {
29828
+ throw error48;
29829
+ }
29830
+ }
29831
+ if (commandLock) {
29832
+ let heartbeatSuppressed = false;
29833
+ try {
29834
+ const active = await readInferenceAgentAttemptState(config2.statePath);
29835
+ const pendingNext = await readInferenceAgentNextState(config2.statePath);
29836
+ heartbeatSuppressed = Boolean(pendingNext && !active);
29837
+ if (heartbeatSuppressed) pendingHeartbeatRequest = null;
29838
+ if (!heartbeatSuppressed) {
29839
+ if (active && active.host_id !== session.localState.host_id) {
29840
+ throw new Error("Active agent-driven attempt belongs to another inference host.");
29841
+ }
29842
+ const activeAttemptId = active?.attempt_id ?? null;
29843
+ if (pendingHeartbeatRequest?.attempt_id !== activeAttemptId) {
29844
+ pendingHeartbeatRequest = null;
29845
+ }
29846
+ const heartbeatRequest = pendingHeartbeatRequest ?? {
29847
+ operation_id: agentOperationId("agent-heartbeat"),
29848
+ host_id: session.localState.host_id,
29849
+ attempt_id: activeAttemptId,
29850
+ requested_at: (dependencies.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
29851
+ };
29852
+ try {
29853
+ await session.client.callTool(
29854
+ "inference.agent.heartbeat",
29855
+ heartbeatRequest,
29856
+ { signal: cancellation.signal }
29857
+ );
29858
+ heartbeatCount += 1;
29859
+ pendingHeartbeatRequest = null;
29860
+ consecutiveHeartbeatFailures = 0;
29861
+ retryDelayMs = AGENT_HEARTBEAT_INTERVAL_MS;
29862
+ } catch (error48) {
29863
+ if (cancellation.signal.aborted) break;
29864
+ if (error48 instanceof ExternalInferenceMcpError && error48.definitivelyNotApplied) {
29865
+ pendingHeartbeatRequest = null;
29866
+ consecutiveHeartbeatFailures = 0;
29867
+ retryDelayMs = AGENT_HEARTBEAT_INTERVAL_MS;
29868
+ emitStderr(parsed.json ? `${JSON.stringify({
29869
+ status: "heartbeat_retry",
29870
+ retry_after_ms: retryDelayMs
29871
+ })}
29872
+ ` : `Agent-driven inference heartbeat was not applied; retrying in ${retryDelayMs}ms.
29873
+ `);
29874
+ } else {
29875
+ if (!retryableAgentHeartbeatError(error48)) throw error48;
29876
+ pendingHeartbeatRequest = heartbeatRequest;
29877
+ consecutiveHeartbeatFailures += 1;
29878
+ retryDelayMs = Math.min(
29879
+ AGENT_HEARTBEAT_INTERVAL_MS * 2 ** Math.max(0, consecutiveHeartbeatFailures - 1),
29880
+ AGENT_HEARTBEAT_MAX_RETRY_DELAY_MS
29881
+ );
29882
+ const message = `Agent-driven inference heartbeat was temporarily unavailable; retrying in ${retryDelayMs}ms.
29883
+ `;
29884
+ emitStderr(parsed.json ? `${JSON.stringify({
29885
+ status: "heartbeat_retry",
29886
+ retry_after_ms: retryDelayMs
29887
+ })}
29888
+ ` : message);
29889
+ }
29890
+ }
29891
+ }
29892
+ } finally {
29893
+ await commandLock.release();
29894
+ }
29895
+ if (parsed.once && (heartbeatCount > 0 || heartbeatSuppressed)) break;
29896
+ }
29897
+ if (parsed.once && !commandLock) break;
29898
+ await (dependencies.sleep ?? (async (milliseconds) => {
29899
+ await new Promise((resolve5) => setTimeout(resolve5, milliseconds));
29900
+ }))(retryDelayMs);
29901
+ }
29902
+ return {
29903
+ exitCode: 0,
29904
+ stdout: render({ status: "stopped", heartbeats: heartbeatCount }, parsed.json),
29905
+ stderr: warnings.length > 0 ? `${warnings.join("\n")}
29906
+ ` : ""
29907
+ };
29908
+ } finally {
29909
+ await keeperLock?.release();
29910
+ cancellation.unregister();
29911
+ }
29912
+ };
29737
29913
  agentNext = async (config2, parsed, dependencies, warnings) => {
29738
29914
  const active = await readInferenceAgentAttemptState(config2.statePath);
29739
29915
  const recoveredNext = await readInferenceAgentNextState(config2.statePath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtxmacro/cli",
3
- "version": "2026.8.11",
3
+ "version": "2026.8.12",
4
4
  "description": "VTX Macro CLI, MCP server, and foreground subscription inference host.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",