@vtxmacro/cli 2026.8.37 → 2026.8.38

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 (2) hide show
  1. package/bin/vtx.js +369 -60
  2. package/package.json +1 -1
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.37",
41
+ package_version: "2026.8.38",
42
42
  codex_package_name: "@openai/codex",
43
43
  codex_version: "0.147.0",
44
44
  platforms: {
@@ -16245,6 +16245,109 @@ function resolveInferenceHostConfig(env = process.env) {
16245
16245
  supervisorProcessLockPath: join(baseDir, "host.lock")
16246
16246
  };
16247
16247
  }
16248
+ async function writeInferenceHostCredentialContext(config2) {
16249
+ const context = credentialContextForConfig(config2);
16250
+ await writeAtomicInferencePrivateFile(
16251
+ inferenceHostCredentialContextPath(config2),
16252
+ `${JSON.stringify(context, null, 2)}
16253
+ `
16254
+ );
16255
+ }
16256
+ async function writeInferenceHostCredentialContextTransition(config2, previous, candidate = credentialContextForConfig(config2), operation = "selection", phase = "prepared") {
16257
+ const transition = {
16258
+ schema_version: "vtx_inference_credential_context_transition_v1",
16259
+ operation,
16260
+ phase,
16261
+ previous,
16262
+ candidate
16263
+ };
16264
+ await writeAtomicInferencePrivateFile(
16265
+ inferenceHostCredentialContextTransitionPath(config2),
16266
+ `${JSON.stringify(transition, null, 2)}
16267
+ `
16268
+ );
16269
+ }
16270
+ async function readInferenceHostCredentialContextTransition(config2) {
16271
+ const raw = await readInferencePrivateFile(
16272
+ inferenceHostCredentialContextTransitionPath(config2),
16273
+ "Inference host credential context transition"
16274
+ );
16275
+ if (raw === null) return null;
16276
+ let value;
16277
+ try {
16278
+ value = JSON.parse(raw);
16279
+ } catch {
16280
+ throw new Error("Inference host credential context transition is not valid JSON.");
16281
+ }
16282
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
16283
+ throw new Error("Inference host credential context transition is invalid.");
16284
+ }
16285
+ const record2 = value;
16286
+ const expectedKeys = ["schema_version", "operation", "phase", "previous", "candidate"];
16287
+ if (Object.keys(record2).sort().join("\0") !== expectedKeys.sort().join("\0") || record2.schema_version !== "vtx_inference_credential_context_transition_v1" || record2.operation !== "selection" && record2.operation !== "cleanup" || record2.phase !== "prepared" && record2.phase !== "committed") {
16288
+ throw new Error("Inference host credential context transition is invalid.");
16289
+ }
16290
+ return {
16291
+ schema_version: "vtx_inference_credential_context_transition_v1",
16292
+ operation: record2.operation,
16293
+ phase: record2.phase,
16294
+ previous: record2.previous === null ? null : assertInferenceHostCredentialContext(record2.previous, config2),
16295
+ candidate: record2.candidate === null ? null : assertInferenceHostCredentialContext(record2.candidate, config2)
16296
+ };
16297
+ }
16298
+ async function clearInferenceHostCredentialContextTransition(config2) {
16299
+ await clearInferencePrivateFile(
16300
+ inferenceHostCredentialContextTransitionPath(config2),
16301
+ "Inference host credential context transition"
16302
+ );
16303
+ }
16304
+ async function clearInferenceHostCredentialContext(config2) {
16305
+ await clearInferencePrivateFile(
16306
+ inferenceHostCredentialContextPath(config2),
16307
+ "Inference host credential context"
16308
+ );
16309
+ }
16310
+ async function readInferenceHostCredentialContext(config2) {
16311
+ const raw = await readInferencePrivateFile(
16312
+ inferenceHostCredentialContextPath(config2),
16313
+ "Inference host credential context"
16314
+ );
16315
+ if (raw === null) return null;
16316
+ let value;
16317
+ try {
16318
+ value = JSON.parse(raw);
16319
+ } catch {
16320
+ throw new Error("Inference host credential context is not valid JSON.");
16321
+ }
16322
+ return assertInferenceHostCredentialContext(value, config2);
16323
+ }
16324
+ function assertInferenceHostCredentialContext(value, config2) {
16325
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
16326
+ throw new Error("Inference host credential context is invalid.");
16327
+ }
16328
+ const record2 = value;
16329
+ const expectedKeys = [
16330
+ "schema_version",
16331
+ "instance_name",
16332
+ "credential_store_mode",
16333
+ "credential_file_path"
16334
+ ];
16335
+ if (Object.keys(record2).sort().join("\0") !== expectedKeys.sort().join("\0") || record2.schema_version !== "vtx_inference_credential_context_v1" || record2.instance_name !== config2.instanceName || record2.credential_store_mode !== "os" && record2.credential_store_mode !== "file" || (record2.credential_store_mode === "os" ? record2.credential_file_path !== null : typeof record2.credential_file_path !== "string" || !record2.credential_file_path)) {
16336
+ throw new Error("Inference host credential context is invalid.");
16337
+ }
16338
+ const credentialFilePath = record2.credential_store_mode === "file" ? requireNamedInstancePath(
16339
+ config2.instanceName,
16340
+ config2.instanceName === DEFAULT_INFERENCE_HOST_INSTANCE ? config2.baseDir : join(config2.baseDir, "instances", config2.instanceName),
16341
+ record2.credential_file_path,
16342
+ "Stored inference credential path"
16343
+ ) : null;
16344
+ return {
16345
+ schema_version: "vtx_inference_credential_context_v1",
16346
+ instance_name: config2.instanceName,
16347
+ credential_store_mode: record2.credential_store_mode,
16348
+ credential_file_path: credentialFilePath
16349
+ };
16350
+ }
16248
16351
  function assertLocalState(value) {
16249
16352
  if (!value || typeof value !== "object" || Array.isArray(value)) {
16250
16353
  throw new Error("Inference host local state is invalid.");
@@ -16619,7 +16722,7 @@ async function acquireInferenceHostProcessLock(path, dependencies = {}) {
16619
16722
  }
16620
16723
  };
16621
16724
  }
16622
- var INFERENCE_CREDENTIAL_NAMESPACE, MAX_INFERENCE_PRIVATE_FILE_BYTES, WINDOWS_PRIVATE_ACL_BROKER_SCRIPT, windowsPrivateAclBrokerInvocation, WindowsPrivateAclBroker, defaultWindowsPrivateAclBroker, runWindowsPrivateAcl, windowsAclCache, windowsAclIdentity, aclCacheKey, secureWindowsPrivatePath, invalidateWindowsAclCache, rebindHardenedWindowsAclAfterRename, rebindHardenedWindowsDirectoryAfterOwnedMutation, isDefaultWindowsPrivatePath, secureExistingWindowsPath, linuxProcessIdentity, windowsProcessIdentity, darwinProcessIdentity, runSmallIdentityCommand, cachedSystemBootIdentity, readInferenceSystemBootIdentity, defaultProcessIdentity, defaultCurrentProcessIdentity, processLockObservationCache, inferenceProcessIdentitiesMatch, DEFAULT_INFERENCE_HOST_INSTANCE, SAFE_INFERENCE_HOST_INSTANCE, requireNamedInstancePath, credentialStoreIdentity;
16725
+ var INFERENCE_CREDENTIAL_NAMESPACE, MAX_INFERENCE_PRIVATE_FILE_BYTES, WINDOWS_PRIVATE_ACL_BROKER_SCRIPT, windowsPrivateAclBrokerInvocation, WindowsPrivateAclBroker, defaultWindowsPrivateAclBroker, runWindowsPrivateAcl, windowsAclCache, windowsAclIdentity, aclCacheKey, secureWindowsPrivatePath, invalidateWindowsAclCache, rebindHardenedWindowsAclAfterRename, rebindHardenedWindowsDirectoryAfterOwnedMutation, isDefaultWindowsPrivatePath, secureExistingWindowsPath, linuxProcessIdentity, windowsProcessIdentity, darwinProcessIdentity, runSmallIdentityCommand, cachedSystemBootIdentity, readInferenceSystemBootIdentity, defaultProcessIdentity, defaultCurrentProcessIdentity, processLockObservationCache, inferenceProcessIdentitiesMatch, DEFAULT_INFERENCE_HOST_INSTANCE, SAFE_INFERENCE_HOST_INSTANCE, requireNamedInstancePath, credentialStoreIdentity, inferenceHostCredentialContextPath, inferenceHostCredentialContextTransitionPath, credentialContextForConfig;
16623
16726
  var init_config = __esm({
16624
16727
  "lib/inference-host/config.ts"() {
16625
16728
  "use strict";
@@ -17138,6 +17241,17 @@ while(($line=[Console]::In.ReadLine()) -ne $null) {
17138
17241
  if (process.platform === "darwin") return "os:macos-keychain";
17139
17242
  return `os:${process.platform}:unsupported`;
17140
17243
  };
17244
+ inferenceHostCredentialContextPath = (config2) => join(
17245
+ config2.instanceName === DEFAULT_INFERENCE_HOST_INSTANCE ? config2.baseDir : join(config2.baseDir, "instances", config2.instanceName),
17246
+ "credential-context.json"
17247
+ );
17248
+ inferenceHostCredentialContextTransitionPath = (config2) => `${inferenceHostCredentialContextPath(config2)}.pending`;
17249
+ credentialContextForConfig = (config2) => ({
17250
+ schema_version: "vtx_inference_credential_context_v1",
17251
+ instance_name: config2.instanceName,
17252
+ credential_store_mode: config2.credentialStoreMode,
17253
+ credential_file_path: config2.credentialStoreMode === "file" ? resolve(config2.credentialFilePath) : null
17254
+ });
17141
17255
  }
17142
17256
  });
17143
17257
 
@@ -31510,9 +31624,9 @@ ${result2.stderr}`)) {
31510
31624
  const runtime = await this.waitForManifestApplied(restoredManifest);
31511
31625
  return await this.status(runtime);
31512
31626
  }
31513
- async waitForManifestApplied(manifest, targetInstanceName) {
31627
+ async waitForManifestApplied(manifest, targetInstanceName, maxAttempts = this.reconcileWaitAttempts) {
31514
31628
  let consecutiveReadyObservations = 0;
31515
- for (let attempt = 0; attempt < this.reconcileWaitAttempts; attempt += 1) {
31629
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
31516
31630
  let runtime = null;
31517
31631
  try {
31518
31632
  runtime = await readRuntimeAcrossAtomicReplacement(this.runtimePath());
@@ -31648,7 +31762,12 @@ ${cleanup.stderr}`)) {
31648
31762
  previous,
31649
31763
  targetInstanceName && previous.workers.some(
31650
31764
  (worker) => worker.instance_name === targetInstanceName
31651
- ) ? targetInstanceName : void 0
31765
+ ) ? targetInstanceName : void 0,
31766
+ // A failed generation may still be draining while the manager
31767
+ // restores the previous manifest. Never give rollback less than
31768
+ // a bounded 100-observation confirmation window, even when a
31769
+ // caller intentionally shortens forward-readiness checks.
31770
+ Math.max(this.reconcileWaitAttempts, 100)
31652
31771
  );
31653
31772
  } catch (rollbackError) {
31654
31773
  throw new Error(
@@ -32288,7 +32407,7 @@ async function runInferenceHostCli(argv2, env = process.env, dependencies = {})
32288
32407
  return { exitCode: 0, stdout: INFERENCE_HOST_HELP, stderr: "" };
32289
32408
  }
32290
32409
  const parsed = parseInferenceHostArgs(argv2, env);
32291
- const config2 = await resolveInferenceHostCommandConfig(parsed, env);
32410
+ const config2 = await resolveInferenceHostCommandConfig(parsed, env, dependencies);
32292
32411
  if (parsed.command === "login") {
32293
32412
  return await login(config2, parsed, env, dependencies, warnings);
32294
32413
  }
@@ -32355,7 +32474,7 @@ async function runInferenceHostCli(argv2, env = process.env, dependencies = {})
32355
32474
  };
32356
32475
  }
32357
32476
  }
32358
- 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, parseHostConcurrency, parseInferenceHostArgs, defaultOpenBrowser, registerInferenceHostServiceControlInput, defaultRegisterLifecycleSignalHandlers, lifecycleCancellation, configuredCredentialStore, accountKeyForState, assertCredentialMatchesState2, revocationCheckpointForCredential, assertRevocationCheckpointMatchesLocalIdentity, fileExistsPrivately, inspectCodexAuthentication, clearLocalRuntimeArtifacts, assertDurableServiceUninstalled, assertLocalRuntimeArtifactsMayBeDiscarded, defaultRunForeground, login, codexLogin, codexLogout, localStatus, doctor, cleanupLogin, runHost, defaultReadStdin, parseAgentStdin, agentOperationId, agentSession, agentConnect, agentRun, agentNext, agentComplete, agentFail, withAgentCommandLock, serviceCommand, hasExplicitCredentialStoreConfiguration, commandUsesInstalledServiceCredentials, resolveInferenceHostCommandConfig;
32477
+ var INFERENCE_HOST_CLI_VERSION, runtimeReceiptPath, codexRecoveryPath, codexGuardianReceiptRoot, revocationCheckpointPath, foregroundHostLockPath, credentialLifecycleLockPath, AGENT_HEARTBEAT_INTERVAL_MS, AGENT_HEARTBEAT_MAX_RETRY_DELAY_MS, retryableAgentHeartbeatError, assertRevocationCheckpoint, readRevocationCheckpoint, writeRevocationCheckpoint, clearRevocationCheckpoint, render, INFERENCE_HOST_HELP, parseHostConcurrency, parseInferenceHostArgs, defaultOpenBrowser, registerInferenceHostServiceControlInput, defaultRegisterLifecycleSignalHandlers, lifecycleCancellation, configuredCredentialStore, restoreInferenceHostCredentialContext, recoverInferenceHostCredentialContextTransition, accountKeyForState, assertCredentialMatchesState2, revocationCheckpointForCredential, assertRevocationCheckpointMatchesLocalIdentity, fileExistsPrivately, inspectCodexAuthentication, clearLocalRuntimeArtifacts, assertDurableServiceUninstalled, assertLocalRuntimeArtifactsMayBeDiscarded, defaultRunForeground, login, codexLogin, codexLogout, localStatus, doctor, cleanupLogin, runHost, defaultReadStdin, parseAgentStdin, agentOperationId, agentSession, agentConnect, agentRun, agentNext, agentComplete, agentFail, withAgentCommandLock, serviceCommand, hasExplicitCredentialStoreConfiguration, credentialEnvironmentForIdentity, commandRequiresRetainedCredentialStore, commandUsesInstalledServiceCredentials, resolveInferenceHostCommandConfig;
32359
32478
  var init_cli = __esm({
32360
32479
  "lib/inference-host/cli.ts"() {
32361
32480
  "use strict";
@@ -32379,6 +32498,7 @@ var init_cli = __esm({
32379
32498
  codexGuardianReceiptRoot = (config2) => `${config2.statePath}.codex-processes`;
32380
32499
  revocationCheckpointPath = (config2) => `${config2.statePath}.revoke.json`;
32381
32500
  foregroundHostLockPath = (config2) => `${config2.processLockPath}.foreground`;
32501
+ credentialLifecycleLockPath = (config2) => `${inferenceHostCredentialContextPath(config2)}.lock`;
32382
32502
  AGENT_HEARTBEAT_INTERVAL_MS = 3e3;
32383
32503
  AGENT_HEARTBEAT_MAX_RETRY_DELAY_MS = 9e3;
32384
32504
  retryableAgentHeartbeatError = (error48) => {
@@ -32647,6 +32767,36 @@ Durable service:
32647
32767
  };
32648
32768
  };
32649
32769
  configuredCredentialStore = (config2, dependencies, warn) => dependencies.createCredentialStore?.(config2, warn) ?? createConfiguredInferenceCredentialStore(config2, { warn });
32770
+ restoreInferenceHostCredentialContext = async (config2, previous) => {
32771
+ if (!previous) {
32772
+ await clearInferenceHostCredentialContext(config2);
32773
+ return;
32774
+ }
32775
+ await writeInferenceHostCredentialContext({
32776
+ ...config2,
32777
+ credentialStoreMode: previous.credential_store_mode,
32778
+ credentialFilePath: previous.credential_file_path
32779
+ });
32780
+ };
32781
+ recoverInferenceHostCredentialContextTransition = async (config2, selectedEnv, dependencies) => {
32782
+ const transition = await readInferenceHostCredentialContextTransition(config2);
32783
+ if (!transition) return;
32784
+ const localState = await readInferenceHostLocalState(config2.statePath);
32785
+ const candidateRecovery = transition.candidate ? await configuredCredentialStore(
32786
+ resolveInferenceHostConfig({
32787
+ ...selectedEnv,
32788
+ VTX_INFERENCE_HOST_CREDENTIAL_STORE: transition.candidate.credential_store_mode,
32789
+ VTX_INFERENCE_HOST_CREDENTIAL_FILE: transition.candidate.credential_file_path ?? void 0
32790
+ }),
32791
+ dependencies,
32792
+ () => void 0
32793
+ ).readRecovery?.() ?? null : null;
32794
+ await restoreInferenceHostCredentialContext(
32795
+ config2,
32796
+ transition.operation === "selection" ? localState || candidateRecovery ? transition.candidate : transition.previous : transition.phase === "committed" && !localState ? null : transition.previous
32797
+ );
32798
+ await clearInferenceHostCredentialContextTransition(config2);
32799
+ };
32650
32800
  accountKeyForState = (state) => inferenceCredentialAccountKey({
32651
32801
  issuer: state.issuer,
32652
32802
  clientId: state.client_id,
@@ -32780,8 +32930,15 @@ Durable service:
32780
32930
  }).run();
32781
32931
  };
32782
32932
  login = async (config2, parsed, env, dependencies, warnings) => {
32783
- const lock2 = await acquireInferenceHostProcessLock(config2.processLockPath);
32933
+ const credentialLock = await acquireInferenceHostProcessLock(
32934
+ credentialLifecycleLockPath(config2)
32935
+ );
32936
+ let lock2 = null;
32937
+ let previousCredentialContext;
32938
+ let selectedStore = null;
32939
+ let localStateCommitted = false;
32784
32940
  try {
32941
+ lock2 = await acquireInferenceHostProcessLock(config2.processLockPath);
32785
32942
  if (await readRevocationCheckpoint(config2)) {
32786
32943
  throw new Error("Inference host revocation recovery must finish before login.");
32787
32944
  }
@@ -32791,18 +32948,41 @@ Durable service:
32791
32948
  const store = configuredCredentialStore(config2, dependencies, (message) => {
32792
32949
  warnings.push(message);
32793
32950
  });
32951
+ selectedStore = store;
32952
+ if (!store.writeRecovery || !store.removeRecovery || !store.readRecovery) {
32953
+ throw new Error("Inference host credential store does not support durable login recovery.");
32954
+ }
32794
32955
  if (await store.readRecovery?.()) {
32795
32956
  throw new Error("Inference host credential recovery is required before login.");
32796
32957
  }
32958
+ previousCredentialContext = await readInferenceHostCredentialContext(config2);
32959
+ await writeInferenceHostCredentialContextTransition(config2, previousCredentialContext);
32960
+ await writeInferenceHostCredentialContext(config2);
32797
32961
  const keyPair = generateExternalInferenceEnvelopeKeyPair();
32798
32962
  const hostId = randomUUID2();
32799
32963
  const beginLogin = dependencies.beginLogin ?? beginInferenceOAuthLogin;
32964
+ const loginStore = {
32965
+ kind: store.kind,
32966
+ read: store.read.bind(store),
32967
+ remove: store.remove.bind(store),
32968
+ readRecovery: store.readRecovery.bind(store),
32969
+ writeRecovery: store.writeRecovery.bind(store),
32970
+ removeRecovery: store.removeRecovery.bind(store),
32971
+ write: async (accountKey, credential, options) => {
32972
+ await store.writeRecovery(accountKey, credential);
32973
+ try {
32974
+ await store.write(accountKey, credential, options);
32975
+ } catch (error48) {
32976
+ throw error48;
32977
+ }
32978
+ }
32979
+ };
32800
32980
  const pending = await beginLogin({
32801
32981
  apiUrl: config2.apiUrl,
32802
32982
  hostId,
32803
32983
  x25519PrivateKey: keyPair.private_key,
32804
32984
  keyGeneration: 1,
32805
- store
32985
+ store: loginStore
32806
32986
  });
32807
32987
  const emitStdout = dependencies.emitStdout ?? ((text) => {
32808
32988
  process.stdout.write(text);
@@ -32846,6 +33026,7 @@ Waiting for approval...
32846
33026
  store
32847
33027
  });
32848
33028
  remoteCleanupConfirmed = true;
33029
+ await store.removeRecovery?.();
32849
33030
  } catch {
32850
33031
  if (!store.writeRecovery) {
32851
33032
  throw new Error(
@@ -32864,6 +33045,9 @@ Waiting for approval...
32864
33045
  }
32865
33046
  throw stateError;
32866
33047
  }
33048
+ localStateCommitted = true;
33049
+ await store.removeRecovery?.();
33050
+ await clearInferenceHostCredentialContextTransition(config2);
32867
33051
  return {
32868
33052
  exitCode: 0,
32869
33053
  stdout: render({
@@ -32876,8 +33060,18 @@ Waiting for approval...
32876
33060
  stderr: warnings.length > 0 ? `${warnings.join("\n")}
32877
33061
  ` : ""
32878
33062
  };
33063
+ } catch (error48) {
33064
+ const recovery = await selectedStore?.readRecovery?.() ?? null;
33065
+ if (!recovery && !localStateCommitted && previousCredentialContext !== void 0) {
33066
+ await restoreInferenceHostCredentialContext(config2, previousCredentialContext);
33067
+ await clearInferenceHostCredentialContextTransition(config2);
33068
+ } else if (recovery) {
33069
+ await clearInferenceHostCredentialContextTransition(config2);
33070
+ }
33071
+ throw error48;
32879
33072
  } finally {
32880
- await lock2.release();
33073
+ await lock2?.release();
33074
+ await credentialLock.release();
32881
33075
  }
32882
33076
  };
32883
33077
  codexLogin = async (config2, parsed, env, dependencies) => {
@@ -33111,10 +33305,14 @@ Waiting for approval...
33111
33305
  };
33112
33306
  };
33113
33307
  cleanupLogin = async (config2, parsed, dependencies, warnings, revoke) => {
33114
- await assertDurableServiceUninstalled(config2);
33115
- const lock2 = await acquireInferenceHostProcessLock(config2.processLockPath);
33308
+ const credentialLock = await acquireInferenceHostProcessLock(
33309
+ credentialLifecycleLockPath(config2)
33310
+ );
33311
+ let lock2 = null;
33116
33312
  let keeperLock = null;
33117
33313
  try {
33314
+ await assertDurableServiceUninstalled(config2);
33315
+ lock2 = await acquireInferenceHostProcessLock(config2.processLockPath);
33118
33316
  try {
33119
33317
  keeperLock = await acquireInferenceHostProcessLock(foregroundHostLockPath(config2));
33120
33318
  } catch (error48) {
@@ -33141,8 +33339,35 @@ Waiting for approval...
33141
33339
  state,
33142
33340
  oauthRecovery
33143
33341
  );
33342
+ }
33343
+ let cleanupTransitionStaged = false;
33344
+ const stageCleanupTransition = async () => {
33345
+ if (cleanupTransitionStaged) return;
33346
+ const selectedContext = await readInferenceHostCredentialContext(config2);
33347
+ await writeInferenceHostCredentialContextTransition(
33348
+ config2,
33349
+ selectedContext,
33350
+ null,
33351
+ "cleanup",
33352
+ "prepared"
33353
+ );
33354
+ cleanupTransitionStaged = true;
33355
+ };
33356
+ const commitCleanupTransition = async () => {
33357
+ await writeInferenceHostCredentialContextTransition(
33358
+ config2,
33359
+ await readInferenceHostCredentialContext(config2),
33360
+ null,
33361
+ "cleanup",
33362
+ "committed"
33363
+ );
33364
+ };
33365
+ if (revocationCheckpoint) {
33366
+ await stageCleanupTransition();
33367
+ await commitCleanupTransition();
33144
33368
  } else if (oauthRecovery) {
33145
33369
  if (revoke) {
33370
+ await stageCleanupTransition();
33146
33371
  const metadata = await (dependencies.discoverOAuth ?? discoverInferenceOAuth)(config2.apiUrl);
33147
33372
  await (dependencies.revokeCredential ?? revokeInferenceCredential)({
33148
33373
  metadata,
@@ -33157,9 +33382,12 @@ Waiting for approval...
33157
33382
  config2.credentialStoreIdentity
33158
33383
  );
33159
33384
  await writeRevocationCheckpoint(config2, revocationCheckpoint);
33385
+ await commitCleanupTransition();
33160
33386
  } else {
33387
+ await stageCleanupTransition();
33161
33388
  await store.remove(oauthRecovery.accountKey);
33162
33389
  await store.removeRecovery?.();
33390
+ await commitCleanupTransition();
33163
33391
  }
33164
33392
  } else if (state) {
33165
33393
  const accountKey = accountKeyForState(state);
@@ -33169,6 +33397,7 @@ Waiting for approval...
33169
33397
  if (!credential) {
33170
33398
  throw new Error("Inference host credential is unavailable; remote revoke cannot be confirmed.");
33171
33399
  }
33400
+ await stageCleanupTransition();
33172
33401
  const metadata = await (dependencies.discoverOAuth ?? discoverInferenceOAuth)(config2.apiUrl);
33173
33402
  await (dependencies.revokeCredential ?? revokeInferenceCredential)({
33174
33403
  metadata,
@@ -33183,11 +33412,16 @@ Waiting for approval...
33183
33412
  config2.credentialStoreIdentity
33184
33413
  );
33185
33414
  await writeRevocationCheckpoint(config2, revocationCheckpoint);
33415
+ await commitCleanupTransition();
33186
33416
  } else {
33417
+ await stageCleanupTransition();
33187
33418
  await store.remove(accountKey);
33419
+ await commitCleanupTransition();
33188
33420
  }
33189
33421
  } else if (!revoke && store.kind === "file") {
33422
+ await stageCleanupTransition();
33190
33423
  await store.remove("");
33424
+ await commitCleanupTransition();
33191
33425
  } else if (revoke) {
33192
33426
  throw new Error("Inference host is not logged in; there is no exact grant to revoke.");
33193
33427
  }
@@ -33207,14 +33441,18 @@ Waiting for approval...
33207
33441
  { cause: error48 }
33208
33442
  );
33209
33443
  }
33210
- await clearLocalRuntimeArtifacts(config2);
33211
33444
  await store.remove(revocationCheckpoint.account_key);
33212
33445
  if (revocationCheckpoint.remove_credential_recovery) {
33213
33446
  await store.removeRecovery?.();
33214
33447
  }
33448
+ await (dependencies.clearLocalRuntimeArtifacts ?? clearLocalRuntimeArtifacts)(config2);
33449
+ await clearInferenceHostCredentialContext(config2);
33215
33450
  await clearRevocationCheckpoint(config2);
33451
+ await clearInferenceHostCredentialContextTransition(config2);
33216
33452
  } else {
33217
- await clearLocalRuntimeArtifacts(config2);
33453
+ await (dependencies.clearLocalRuntimeArtifacts ?? clearLocalRuntimeArtifacts)(config2);
33454
+ await clearInferenceHostCredentialContext(config2);
33455
+ await clearInferenceHostCredentialContextTransition(config2);
33218
33456
  }
33219
33457
  return {
33220
33458
  exitCode: 0,
@@ -33228,7 +33466,8 @@ Waiting for approval...
33228
33466
  };
33229
33467
  } finally {
33230
33468
  await keeperLock?.release();
33231
- await lock2.release();
33469
+ await lock2?.release();
33470
+ await credentialLock.release();
33232
33471
  }
33233
33472
  };
33234
33473
  runHost = async (config2, parsed, env, dependencies, warnings) => {
@@ -33775,44 +34014,52 @@ Waiting for approval...
33775
34014
  dependencies.serviceDependencies
33776
34015
  ) ?? new InferenceHostServiceManager(config2, dependencies.serviceDependencies);
33777
34016
  if (action === "install") {
33778
- const adapter = parsed.adapter || "codex";
33779
- if (adapter !== "codex") {
33780
- throw new Error(
33781
- `Adapter ${adapter} does not have a supported durable service integration. Use foreground agent-run instead.`
33782
- );
33783
- }
33784
- const state = await readInferenceHostLocalState(config2.statePath);
33785
- if (!state) throw new Error("Run vtx inference-host login before installing the service.");
33786
- const store = configuredCredentialStore(config2, dependencies, (message) => warnings.push(message));
33787
- const credential = await store.read(accountKeyForState(state));
33788
- if (!credential) throw new Error("Inference-host credential is missing. Run login again.");
33789
- assertCredentialMatchesState2(state, credential);
33790
- if (parsed.modelId || parsed.modelLabel || parsed.reasoningEffort) {
33791
- throw new Error("Automated Codex service does not accept agent model or effort options.");
33792
- }
33793
- const auth = await inspectCodexAuthentication(config2);
33794
- if (!auth.present || !auth.private) {
33795
- throw new Error("Run vtx inference-host codex-login before installing the automated Codex service.");
33796
- }
33797
- const binary = await (dependencies.resolveBinary ?? resolvePinnedCodexBinary)(env);
33798
- const preflight = await (dependencies.preflightCodex ?? preflightCodexSubscription)({
33799
- binary,
33800
- codexHome: config2.codexHomePath,
33801
- deadlineAtMs: Date.now() + 3e4
33802
- });
33803
- const status = await manager.install({
33804
- adapter: "codex",
33805
- displayName: parsed.displayName,
33806
- maxConcurrency: parsed.maxConcurrency,
33807
- authenticatedAccountEmail: preflight?.authenticated_account_email ?? null,
33808
- authenticatedAccountPlan: preflight?.authenticated_account_plan ?? null
33809
- });
33810
- return {
33811
- exitCode: 0,
33812
- stdout: render({ status: "service_installed", ...status }, parsed.json),
33813
- stderr: warnings.length > 0 ? `${warnings.join("\n")}
34017
+ const credentialLock = await acquireInferenceHostProcessLock(
34018
+ credentialLifecycleLockPath(config2)
34019
+ );
34020
+ try {
34021
+ const adapter = parsed.adapter || "codex";
34022
+ if (adapter !== "codex") {
34023
+ throw new Error(
34024
+ `Adapter ${adapter} does not have a supported durable service integration. Use foreground agent-run instead.`
34025
+ );
34026
+ }
34027
+ const state = await readInferenceHostLocalState(config2.statePath);
34028
+ if (!state) throw new Error("Run vtx inference-host login before installing the service.");
34029
+ const store = configuredCredentialStore(config2, dependencies, (message) => warnings.push(message));
34030
+ const credential = await store.read(accountKeyForState(state));
34031
+ if (!credential) throw new Error("Inference-host credential is missing. Run login again.");
34032
+ assertCredentialMatchesState2(state, credential);
34033
+ if (parsed.modelId || parsed.modelLabel || parsed.reasoningEffort) {
34034
+ throw new Error("Automated Codex service does not accept agent model or effort options.");
34035
+ }
34036
+ const auth = await inspectCodexAuthentication(config2);
34037
+ if (!auth.present || !auth.private) {
34038
+ throw new Error("Run vtx inference-host codex-login before installing the automated Codex service.");
34039
+ }
34040
+ const binary = await (dependencies.resolveBinary ?? resolvePinnedCodexBinary)(env);
34041
+ const preflight = await (dependencies.preflightCodex ?? preflightCodexSubscription)({
34042
+ binary,
34043
+ codexHome: config2.codexHomePath,
34044
+ deadlineAtMs: Date.now() + 3e4
34045
+ });
34046
+ const status = await manager.install({
34047
+ adapter: "codex",
34048
+ displayName: parsed.displayName,
34049
+ maxConcurrency: parsed.maxConcurrency,
34050
+ authenticatedAccountEmail: preflight?.authenticated_account_email ?? null,
34051
+ authenticatedAccountPlan: preflight?.authenticated_account_plan ?? null
34052
+ });
34053
+ await writeInferenceHostCredentialContext(config2);
34054
+ return {
34055
+ exitCode: 0,
34056
+ stdout: render({ status: "service_installed", ...status }, parsed.json),
34057
+ stderr: warnings.length > 0 ? `${warnings.join("\n")}
33814
34058
  ` : ""
33815
- };
34059
+ };
34060
+ } finally {
34061
+ await credentialLock.release();
34062
+ }
33816
34063
  }
33817
34064
  if (parsed.adapter || parsed.modelId || parsed.modelLabel || parsed.reasoningEffort) {
33818
34065
  throw new Error("Service adapter and model options are accepted only by service install.");
@@ -33839,36 +34086,98 @@ Waiting for approval...
33839
34086
  return { exitCode: lifecycleMatches ? 0 : 1, stdout: render(status, parsed.json), stderr: "" };
33840
34087
  }
33841
34088
  if (action === "uninstall") {
33842
- const status = parsed.instanceExplicit ? await manager.uninstallInstance(config2.instanceName) : await manager.uninstall();
33843
- return { exitCode: 0, stdout: render({ status: "service_uninstalled", ...status }, parsed.json), stderr: "" };
34089
+ const credentialLock = await acquireInferenceHostProcessLock(
34090
+ credentialLifecycleLockPath(config2)
34091
+ );
34092
+ try {
34093
+ if (parsed.instanceExplicit) {
34094
+ const retained = await readInferenceHostCredentialContext(config2);
34095
+ const retainedIdentity = retained ? resolveInferenceHostConfig({
34096
+ ...env,
34097
+ VTX_INFERENCE_HOST_INSTANCE: config2.instanceName,
34098
+ VTX_INFERENCE_HOST_CREDENTIAL_STORE: retained.credential_store_mode,
34099
+ VTX_INFERENCE_HOST_CREDENTIAL_FILE: retained.credential_file_path ?? void 0
34100
+ }).credentialStoreIdentity : null;
34101
+ if (retainedIdentity !== config2.credentialStoreIdentity) {
34102
+ await writeInferenceHostCredentialContext(config2);
34103
+ }
34104
+ }
34105
+ const status = parsed.instanceExplicit ? await manager.uninstallInstance(config2.instanceName) : await manager.uninstall();
34106
+ return { exitCode: 0, stdout: render({ status: "service_uninstalled", ...status }, parsed.json), stderr: "" };
34107
+ } finally {
34108
+ await credentialLock.release();
34109
+ }
33844
34110
  }
33845
34111
  throw new Error("Usage: vtx inference-host service <install|start|stop|status|logs|uninstall>.");
33846
34112
  };
33847
34113
  hasExplicitCredentialStoreConfiguration = (env) => Boolean(
33848
34114
  String(env.VTX_INFERENCE_HOST_CREDENTIAL_STORE || "").trim() || String(env.VTX_INFERENCE_HOST_CREDENTIAL_FILE || "").trim()
33849
34115
  );
33850
- commandUsesInstalledServiceCredentials = (parsed) => parsed.command === "status" || parsed.command === "doctor" || parsed.command === "service" && parsed.serviceAction === "install";
33851
- resolveInferenceHostCommandConfig = async (parsed, env) => {
34116
+ credentialEnvironmentForIdentity = (identity) => {
34117
+ if (identity.startsWith("file:")) {
34118
+ const path = identity.slice("file:".length);
34119
+ if (!path) throw new Error("Inference host credential store identity is invalid.");
34120
+ return {
34121
+ VTX_INFERENCE_HOST_CREDENTIAL_STORE: "file",
34122
+ VTX_INFERENCE_HOST_CREDENTIAL_FILE: path
34123
+ };
34124
+ }
34125
+ if (identity.startsWith("os:")) {
34126
+ return {
34127
+ VTX_INFERENCE_HOST_CREDENTIAL_STORE: "os",
34128
+ VTX_INFERENCE_HOST_CREDENTIAL_FILE: void 0
34129
+ };
34130
+ }
34131
+ throw new Error("Inference host credential store identity is invalid.");
34132
+ };
34133
+ commandRequiresRetainedCredentialStore = (parsed) => parsed.command === "logout" || parsed.command === "revoke" || parsed.command === "service" && parsed.instanceExplicit && (parsed.serviceAction === "install" || parsed.serviceAction === "uninstall");
34134
+ commandUsesInstalledServiceCredentials = (parsed) => parsed.command === "login" || parsed.command === "logout" || parsed.command === "revoke" || parsed.command === "status" || parsed.command === "doctor" || parsed.command === "service" && parsed.serviceAction === "install" || parsed.command === "service" && parsed.serviceAction === "uninstall" && parsed.instanceExplicit;
34135
+ resolveInferenceHostCommandConfig = async (parsed, env, dependencies) => {
33852
34136
  const selectedEnv = {
33853
34137
  ...env,
33854
34138
  VTX_INFERENCE_HOST_INSTANCE: parsed.instanceName
33855
34139
  };
33856
34140
  const baseConfig = resolveInferenceHostConfig(selectedEnv);
33857
- if (!commandUsesInstalledServiceCredentials(parsed) || hasExplicitCredentialStoreConfiguration(env)) {
33858
- return baseConfig;
34141
+ if (!commandUsesInstalledServiceCredentials(parsed)) return baseConfig;
34142
+ if (await readInferenceHostCredentialContextTransition(baseConfig)) {
34143
+ const credentialLock = await acquireInferenceHostProcessLock(
34144
+ credentialLifecycleLockPath(baseConfig)
34145
+ );
34146
+ try {
34147
+ await recoverInferenceHostCredentialContextTransition(baseConfig, selectedEnv, dependencies);
34148
+ } finally {
34149
+ await credentialLock.release();
34150
+ }
33859
34151
  }
33860
34152
  const manifestPath = `${baseConfig.supervisorStatePath}.service.json`;
33861
34153
  const manifest = await readInferenceHostServiceManifest(manifestPath);
33862
- if (!manifest) return baseConfig;
34154
+ const installedEnvironment = manifest?.workers.find(
34155
+ (worker) => worker.instance_name === baseConfig.instanceName
34156
+ )?.runtime_environment;
34157
+ const persistedContext = installedEnvironment ? null : await readInferenceHostCredentialContext(baseConfig);
34158
+ const revocationCheckpoint = !installedEnvironment && !persistedContext && parsed.command === "revoke" ? await readRevocationCheckpoint(baseConfig) : null;
34159
+ const retainedEnvironment = installedEnvironment ?? (persistedContext ? {
34160
+ VTX_INFERENCE_HOST_CREDENTIAL_STORE: persistedContext.credential_store_mode,
34161
+ VTX_INFERENCE_HOST_CREDENTIAL_FILE: persistedContext.credential_file_path ?? void 0
34162
+ } : revocationCheckpoint ? credentialEnvironmentForIdentity(revocationCheckpoint.credential_store_identity) : null);
34163
+ if (!retainedEnvironment) return baseConfig;
33863
34164
  const installedConfig = resolveInferenceHostConfig({
33864
34165
  ...selectedEnv,
33865
- ...manifest.workers.find((worker) => worker.instance_name === baseConfig.instanceName)?.runtime_environment
34166
+ ...retainedEnvironment
33866
34167
  });
33867
34168
  if (resolve5(installedConfig.statePath) !== resolve5(baseConfig.statePath)) {
33868
34169
  throw new Error(
33869
34170
  "Installed inference-host service manifest does not match the requested local state path."
33870
34171
  );
33871
34172
  }
34173
+ if (hasExplicitCredentialStoreConfiguration(env)) {
34174
+ if (commandRequiresRetainedCredentialStore(parsed) && baseConfig.credentialStoreIdentity !== installedConfig.credentialStoreIdentity) {
34175
+ throw new Error(
34176
+ "Explicit inference credential store does not match the retained store for this host. Log out or revoke with the retained store before switching."
34177
+ );
34178
+ }
34179
+ return baseConfig;
34180
+ }
33872
34181
  return installedConfig;
33873
34182
  };
33874
34183
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtxmacro/cli",
3
- "version": "2026.8.37",
3
+ "version": "2026.8.38",
4
4
  "description": "VTX Macro CLI, MCP server, and durable subscription inference host.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",