@vtxmacro/cli 2026.8.36 → 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 +959 -175
  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.36",
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
 
@@ -29047,7 +29161,7 @@ function createDefaultInferenceHostRunnerDependencies(options) {
29047
29161
  envelopePublicKey: options.envelopePublicKey
29048
29162
  };
29049
29163
  }
29050
- 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_PROVIDER_RATE_LIMIT_REFRESH_MS, DEFAULT_ATTEMPT_HEARTBEAT_MS, DEFAULT_DRAIN_TIMEOUT_MS, DEFAULT_REMOTE_RETRY_LIMIT, MIN_SLEEP_MS, DEFAULT_CODEX_ACCOUNT_COOLDOWN_MS, MIN_CLAIM_START_WINDOW_MS, UNBOUNDED_AVAILABLE_SLOTS, buildCodexInferenceAdvertisedModels, FileInferenceHostRuntimeReceiptStore, InferenceHostRecoveryRequiredError, InferenceHostRunnerError, defaultSleep, abortError, defaultRegisterSignalHandlers, safeInteger, exactObjectKeys, validIso, validateAttemptReceipt, validateRuntimeReceipt, sha256, stableOperationId, buildAttemptStartRequest, isoAt, providerWeeklyQuotaFromRateLimits, finitePositiveOption, validateRunnerOptions, assertLocalCredentialIdentity, retryableRemoteError, remoteRetryAfterMs, controlPlaneFatal, defaultValidateOutput, objectRecord, positiveUtf8ByteLimit, assertCompilableOutputSchema, parseOutputContract, membershipFailureDisposition, safeFailureCode, exactJson2, runnerIdentityMismatch, assertAdvertisementResult, assertHostHeartbeatResult, assertClaimResult, assertStartResult, assertJobHeartbeatResult, assertTerminalResult, classifyFailure, ambiguousHeartbeatTransport, reportedTokenUsage, reportedUsage, InferenceHostRunner;
29164
+ 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_PROVIDER_RATE_LIMIT_REFRESH_MS, DEFAULT_ATTEMPT_HEARTBEAT_MS, DEFAULT_DRAIN_TIMEOUT_MS, DEFAULT_REMOTE_RETRY_LIMIT, MIN_SLEEP_MS, DEFAULT_CODEX_ACCOUNT_COOLDOWN_MS, MIN_CLAIM_START_WINDOW_MS, UNBOUNDED_AVAILABLE_SLOTS, buildCodexInferenceAdvertisedModels, summarizeInferenceHostRuntimeRecovery, FileInferenceHostRuntimeReceiptStore, InferenceHostRecoveryRequiredError, InferenceHostRunnerError, defaultSleep, abortError, defaultRegisterSignalHandlers, safeInteger, exactObjectKeys, validIso, validateAttemptReceipt, validateRuntimeReceipt, sha256, stableOperationId, buildAttemptStartRequest, isoAt, providerWeeklyQuotaFromRateLimits, finitePositiveOption, validateRunnerOptions, assertLocalCredentialIdentity, retryableRemoteError, remoteRetryAfterMs, controlPlaneFatal, defaultValidateOutput, objectRecord, positiveUtf8ByteLimit, assertCompilableOutputSchema, parseOutputContract, membershipFailureDisposition, safeFailureCode, exactJson2, runnerIdentityMismatch, assertAdvertisementResult, assertHostHeartbeatResult, assertClaimResult, assertStartResult, assertJobHeartbeatResult, assertTerminalResult, classifyFailure, ambiguousHeartbeatTransport, reportedTokenUsage, reportedUsage, InferenceHostRunner;
29051
29165
  var init_runner = __esm({
29052
29166
  "lib/inference-host/runner.ts"() {
29053
29167
  "use strict";
@@ -29104,6 +29218,35 @@ var init_runner = __esm({
29104
29218
  }
29105
29219
  return models;
29106
29220
  };
29221
+ summarizeInferenceHostRuntimeRecovery = (receipt) => {
29222
+ const phaseCounts = {
29223
+ claimed: 0,
29224
+ started: 0,
29225
+ dispatched: 0,
29226
+ terminal_pending: 0
29227
+ };
29228
+ const terminalOperations = [];
29229
+ for (const attempt of Object.values(receipt.attempts)) {
29230
+ phaseCounts[attempt.phase] += 1;
29231
+ const terminal = attempt.terminal_request;
29232
+ if (!terminal) continue;
29233
+ const failed = terminal.schema_version === "external_inference_job_fail_v1";
29234
+ terminalOperations.push({
29235
+ job_id: attempt.job_id,
29236
+ attempt_id: attempt.attempt_id,
29237
+ operation_kind: failed ? "fail" : "complete",
29238
+ dispatch_outcome: terminal.outcome.dispatch_outcome,
29239
+ failure_category: failed ? terminal.failure_category : null,
29240
+ failure_code: failed ? terminal.failure_code : null,
29241
+ retryable: failed ? terminal.retryable : null
29242
+ });
29243
+ }
29244
+ terminalOperations.sort((left, right) => left.job_id.localeCompare(right.job_id) || left.attempt_id.localeCompare(right.attempt_id));
29245
+ return {
29246
+ phase_counts: phaseCounts,
29247
+ terminal_operations: terminalOperations
29248
+ };
29249
+ };
29107
29250
  FileInferenceHostRuntimeReceiptStore = class {
29108
29251
  constructor(path, readPrivateFile = readInferencePrivateFile) {
29109
29252
  this.path = path;
@@ -29744,6 +29887,22 @@ var init_runner = __esm({
29744
29887
  } catch {
29745
29888
  }
29746
29889
  };
29890
+ const emitTerminalRecoveryFailure = (recovery, error48) => {
29891
+ const cause = error48 instanceof InferenceHostRunnerError && error48.code === "terminal_outcome_unconfirmed" && error48.cause ? error48.cause : error48;
29892
+ const rawCode = cause instanceof ExternalInferenceMcpError || cause instanceof InferenceHostRunnerError || cause instanceof CodexAppServerError ? cause.code : "terminal_recovery_failed";
29893
+ const terminal = recovery.terminal_request;
29894
+ emitDiagnostic("terminal_recovery_failed", {
29895
+ job_id: recovery.job_id,
29896
+ attempt_id: recovery.attempt_id,
29897
+ attempt_phase: recovery.phase,
29898
+ terminal_operation: terminal?.schema_version === "external_inference_job_fail_v1" ? "fail" : "complete",
29899
+ error_code: safeFailureCode(rawCode, "terminal_recovery_failed"),
29900
+ retryable: retryableRemoteError(cause),
29901
+ definitively_not_applied: Boolean(
29902
+ cause && typeof cause === "object" && "definitivelyNotApplied" in cause && cause.definitivelyNotApplied === true
29903
+ )
29904
+ });
29905
+ };
29747
29906
  const accountKey = inferenceCredentialAccountKey({
29748
29907
  issuer: localState.issuer,
29749
29908
  clientId: localState.client_id,
@@ -30009,17 +30168,6 @@ var init_runner = __esm({
30009
30168
  let nextClaimAt = Math.max(now(), providerRetryAtMs ?? 0);
30010
30169
  let pendingClaimPromotions = 0;
30011
30170
  let onceClaimed = false;
30012
- emitDiagnostic("runtime_started", {
30013
- max_concurrency: settings.maxConcurrency,
30014
- active_attempts: active.size,
30015
- provider_cooldown_reason: providerCooldownReason,
30016
- provider_cooldown_until: providerRetryAtMs === null ? null : isoAt(providerRetryAtMs),
30017
- provider_weekly_quota: providerWeeklyQuotaFromRateLimits(
30018
- providerRateLimits,
30019
- providerRateLimitsObservedAtMs,
30020
- now()
30021
- )
30022
- });
30023
30171
  const recoveryQueue = [];
30024
30172
  const launchClaim = (claim, recovery, claimRequest) => {
30025
30173
  if (!recovery) {
@@ -30114,6 +30262,10 @@ var init_runner = __esm({
30114
30262
  }
30115
30263
  }).catch((error48) => {
30116
30264
  failed += 1;
30265
+ const recovery2 = receipt.attempts[attemptId];
30266
+ if (recovery2?.phase === "terminal_pending") {
30267
+ emitTerminalRecoveryFailure(recovery2, error48);
30268
+ }
30117
30269
  requestDrain(controlPlaneFatal(error48) ? "authority_lost" : "attempt_terminal_unconfirmed");
30118
30270
  }).finally(() => {
30119
30271
  settleClaimPromotion();
@@ -30169,6 +30321,7 @@ var init_runner = __esm({
30169
30321
  await this.dependencies.codexAdapter.acknowledgeAttempt?.(recovery.attempt_id);
30170
30322
  await removeRecoveredAttempt(recovery.attempt_id);
30171
30323
  } catch (error48) {
30324
+ emitTerminalRecoveryFailure(recovery, error48);
30172
30325
  requestDrain(controlPlaneFatal(error48) ? "authority_lost" : "attempt_terminal_unconfirmed");
30173
30326
  break;
30174
30327
  }
@@ -30176,6 +30329,19 @@ var init_runner = __esm({
30176
30329
  }
30177
30330
  recoveryQueue.push(recovery);
30178
30331
  }
30332
+ if (!drainRequested) {
30333
+ emitDiagnostic("runtime_started", {
30334
+ max_concurrency: settings.maxConcurrency,
30335
+ active_attempts: active.size,
30336
+ provider_cooldown_reason: providerCooldownReason,
30337
+ provider_cooldown_until: providerRetryAtMs === null ? null : isoAt(providerRetryAtMs),
30338
+ provider_weekly_quota: providerWeeklyQuotaFromRateLimits(
30339
+ providerRateLimits,
30340
+ providerRateLimitsObservedAtMs,
30341
+ now()
30342
+ )
30343
+ });
30344
+ }
30179
30345
  while (!drainRequested && recoveryQueue.length > 0 && (settings.maxConcurrency === null || active.size < settings.maxConcurrency)) {
30180
30346
  const recovery = recoveryQueue.shift();
30181
30347
  launchClaim(recovery.claim, recovery);
@@ -30908,11 +31074,12 @@ var init_runner = __esm({
30908
31074
 
30909
31075
  // lib/inference-host/service.ts
30910
31076
  import { spawn as spawn5 } from "node:child_process";
31077
+ import { randomUUID } from "node:crypto";
30911
31078
  import { createWriteStream, readFileSync } from "node:fs";
30912
31079
  import { access as access3, chmod as chmod3, mkdir as mkdir3, readFile as readFile5, rm as rm4, writeFile as writeFile2 } from "node:fs/promises";
30913
31080
  import { homedir as homedir2 } from "node:os";
30914
31081
  import { dirname as dirname4, join as join5, resolve as resolve4 } from "node:path";
30915
- var SERVICE_NAME, SYSTEMD_UNIT, LAUNCHD_LABEL, SERVICE_COOPERATIVE_STOP_SECONDS, inferenceHostServiceChildEnvironment, isWindowsSubsystemForLinux, inferenceHostServiceManifestPath, inferenceHostServiceDesiredPath, inferenceHostServiceLogPath, xmlEscape, plistEscape, systemdQuote, defaultRunCommand, managerName, assertRuntimeEnvironment, withoutConcurrencyLimit, assertServicePath, assertWorker, assertManifest, assertDesiredState, readInferenceHostServiceManifest, readInferenceHostServiceDesired, readDesiredAcrossAtomicReplacement, writeDesired, runtimeEnvironment, serviceArguments, windowsOwnedCommandLine, vbScriptString, windowsServiceLauncher, windowsTaskXml, systemdUnit, launchAgentPlist, InferenceHostServiceManager, appendServiceLog, spawnServiceChild, runInferenceHostServiceSupervisor;
31082
+ var SERVICE_NAME, SYSTEMD_UNIT, LAUNCHD_LABEL, SERVICE_COOPERATIVE_STOP_SECONDS, INFERENCE_HOST_SERVICE_DRAIN_COMMAND, inferenceHostServiceChildEnvironment, isWindowsSubsystemForLinux, inferenceHostServiceManifestPath, inferenceHostServiceDesiredPath, inferenceHostServiceLogPath, inferenceHostServiceRuntimePath, xmlEscape, plistEscape, systemdQuote, defaultRunCommand, managerName, assertRuntimeEnvironment, withoutConcurrencyLimit, assertServicePath, manifestGeneration, assertWorker, assertManifest, assertServiceRuntimeState, assertDesiredState, readInferenceHostServiceManifest, readManifestAcrossAtomicReplacement, readInferenceHostServiceRuntime, readRuntimeAcrossAtomicReplacement, readInferenceHostServiceDesired, readDesiredAcrossAtomicReplacement, writeDesired, runtimeEnvironment, serviceArguments, windowsOwnedCommandLine, vbScriptString, windowsServiceLauncher, windowsTaskXml, systemdUnit, launchAgentPlist, sameWorker, sameServiceDefinition, sameWorkerSet, InferenceHostServiceManager, appendServiceLog, spawnInferenceHostServiceChild, runInferenceHostServiceSupervisor;
30916
31083
  var init_service = __esm({
30917
31084
  "lib/inference-host/service.ts"() {
30918
31085
  "use strict";
@@ -30921,6 +31088,7 @@ var init_service = __esm({
30921
31088
  SYSTEMD_UNIT = "vtx-inference-host.service";
30922
31089
  LAUNCHD_LABEL = "com.vtxmacro.inference-host";
30923
31090
  SERVICE_COOPERATIVE_STOP_SECONDS = 75;
31091
+ INFERENCE_HOST_SERVICE_DRAIN_COMMAND = "vtx-inference-host-service-drain-v1";
30924
31092
  inferenceHostServiceChildEnvironment = (runtimeEnvironment2, inheritedEnvironment = process.env) => {
30925
31093
  const environment = { ...inheritedEnvironment };
30926
31094
  for (const key of Object.keys(environment)) {
@@ -30928,7 +31096,11 @@ var init_service = __esm({
30928
31096
  delete environment[key];
30929
31097
  }
30930
31098
  }
30931
- return { ...environment, ...runtimeEnvironment2 };
31099
+ return {
31100
+ ...environment,
31101
+ ...runtimeEnvironment2,
31102
+ VTX_INFERENCE_HOST_SERVICE_CHILD: "1"
31103
+ };
30932
31104
  };
30933
31105
  isWindowsSubsystemForLinux = (env = process.env, kernelRelease) => Boolean(
30934
31106
  String(env.WSL_INTEROP || "").trim() || String(env.WSL_DISTRO_NAME || "").trim() || /microsoft/iu.test(kernelRelease ?? (() => {
@@ -30942,6 +31114,7 @@ var init_service = __esm({
30942
31114
  inferenceHostServiceManifestPath = (config2) => `${config2.supervisorStatePath}.service.json`;
30943
31115
  inferenceHostServiceDesiredPath = (config2) => `${config2.supervisorStatePath}.service-desired.json`;
30944
31116
  inferenceHostServiceLogPath = (config2) => `${config2.supervisorStatePath}.service.log`;
31117
+ inferenceHostServiceRuntimePath = (config2) => `${config2.supervisorStatePath}.service-runtime.json`;
30945
31118
  xmlEscape = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
30946
31119
  plistEscape = xmlEscape;
30947
31120
  systemdQuote = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%")}"`;
@@ -30992,6 +31165,7 @@ var init_service = __esm({
30992
31165
  }
30993
31166
  return value;
30994
31167
  };
31168
+ manifestGeneration = (value, installedAt) => typeof value === "string" && /^[A-Za-z0-9._:-]{1,160}$/u.test(value) ? value : `legacy:${installedAt}`;
30995
31169
  assertWorker = (value) => {
30996
31170
  if (!value || typeof value !== "object" || Array.isArray(value)) {
30997
31171
  throw new Error("Inference-host service worker is invalid.");
@@ -31032,6 +31206,7 @@ var init_service = __esm({
31032
31206
  });
31033
31207
  return {
31034
31208
  schema_version: "vtx_inference_service_v3",
31209
+ generation: manifestGeneration(void 0, legacy.installed_at),
31035
31210
  installed_at: legacy.installed_at,
31036
31211
  executable: assertServicePath(legacy.executable),
31037
31212
  script: assertServicePath(legacy.script),
@@ -31065,6 +31240,7 @@ var init_service = __esm({
31065
31240
  }
31066
31241
  return {
31067
31242
  schema_version: "vtx_inference_service_v3",
31243
+ generation: manifestGeneration(void 0, legacy.installed_at),
31068
31244
  installed_at: legacy.installed_at,
31069
31245
  executable: assertServicePath(legacy.executable),
31070
31246
  script: assertServicePath(legacy.script),
@@ -31082,6 +31258,34 @@ var init_service = __esm({
31082
31258
  if (new Set(workers.map((worker) => worker.instance_name)).size !== workers.length) {
31083
31259
  throw new Error("Inference-host service worker names must be unique.");
31084
31260
  }
31261
+ return {
31262
+ ...record2,
31263
+ generation: manifestGeneration(record2.generation, String(record2.installed_at)),
31264
+ workers
31265
+ };
31266
+ };
31267
+ assertServiceRuntimeState = (value) => {
31268
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
31269
+ throw new Error("Inference-host service runtime state is invalid.");
31270
+ }
31271
+ const record2 = value;
31272
+ if (record2.schema_version !== "vtx_inference_service_runtime_v1" || typeof record2.manifest_generation !== "string" || record2.rejected_manifest_generation !== void 0 && record2.rejected_manifest_generation !== null && typeof record2.rejected_manifest_generation !== "string" || typeof record2.updated_at !== "string" || !Number.isFinite(Date.parse(record2.updated_at)) || !Array.isArray(record2.workers)) {
31273
+ throw new Error("Inference-host service runtime state is invalid.");
31274
+ }
31275
+ const workers = record2.workers.map((worker) => {
31276
+ if (!worker || typeof worker !== "object" || Array.isArray(worker)) {
31277
+ throw new Error("Inference-host service worker runtime state is invalid.");
31278
+ }
31279
+ const item = worker;
31280
+ if (typeof item.instance_name !== "string" || !["starting", "running", "failed", "draining"].includes(String(item.state)) || item.error !== null && typeof item.error !== "string") {
31281
+ throw new Error("Inference-host service worker runtime state is invalid.");
31282
+ }
31283
+ return {
31284
+ instance_name: item.instance_name,
31285
+ state: item.state,
31286
+ error: item.error
31287
+ };
31288
+ });
31085
31289
  return { ...record2, workers };
31086
31290
  };
31087
31291
  assertDesiredState = (value) => {
@@ -31104,6 +31308,42 @@ var init_service = __esm({
31104
31308
  throw error48;
31105
31309
  }
31106
31310
  };
31311
+ readManifestAcrossAtomicReplacement = async (path) => {
31312
+ for (let attempt = 0; attempt < 3; attempt += 1) {
31313
+ try {
31314
+ return await readInferenceHostServiceManifest(path);
31315
+ } catch (error48) {
31316
+ if (!(error48 instanceof Error) || !error48.message.includes("changed while it was opened")) {
31317
+ throw error48;
31318
+ }
31319
+ }
31320
+ }
31321
+ return await readInferenceHostServiceManifest(path);
31322
+ };
31323
+ readInferenceHostServiceRuntime = async (path) => {
31324
+ const raw = await readInferencePrivateFile(path, "Inference-host service runtime state");
31325
+ if (raw === null) return null;
31326
+ try {
31327
+ return assertServiceRuntimeState(JSON.parse(raw));
31328
+ } catch (error48) {
31329
+ if (error48 instanceof SyntaxError) {
31330
+ throw new Error("Inference-host service runtime state is not valid JSON.");
31331
+ }
31332
+ throw error48;
31333
+ }
31334
+ };
31335
+ readRuntimeAcrossAtomicReplacement = async (path) => {
31336
+ for (let attempt = 0; attempt < 3; attempt += 1) {
31337
+ try {
31338
+ return await readInferenceHostServiceRuntime(path);
31339
+ } catch (error48) {
31340
+ if (!(error48 instanceof Error) || !error48.message.includes("changed while it was opened")) {
31341
+ throw error48;
31342
+ }
31343
+ }
31344
+ }
31345
+ return await readInferenceHostServiceRuntime(path);
31346
+ };
31107
31347
  readInferenceHostServiceDesired = async (path) => {
31108
31348
  const raw = await readInferencePrivateFile(path, "Inference-host service desired state");
31109
31349
  if (raw === null) return false;
@@ -31226,6 +31466,9 @@ WantedBy=default.target
31226
31466
  <key>StandardErrorPath</key><string>${plistEscape(logPath)}</string>
31227
31467
  </dict></plist>
31228
31468
  `;
31469
+ sameWorker = (left, right) => JSON.stringify(left) === JSON.stringify(right);
31470
+ sameServiceDefinition = (left, right) => left.executable === right.executable && left.script === right.script && left.log_path === right.log_path;
31471
+ sameWorkerSet = (left, right) => left.workers.length === right.workers.length && left.workers.every((worker, index) => sameWorker(worker, right.workers[index]));
31229
31472
  InferenceHostServiceManager = class {
31230
31473
  constructor(config2, dependencies = {}) {
31231
31474
  this.config = config2;
@@ -31242,6 +31485,8 @@ WantedBy=default.target
31242
31485
  });
31243
31486
  this.stopWaitAttempts = dependencies.stopWaitAttempts ?? SERVICE_COOPERATIVE_STOP_SECONDS * 4;
31244
31487
  this.startWaitAttempts = dependencies.startWaitAttempts ?? 40;
31488
+ this.reconcileWaitAttempts = dependencies.reconcileWaitAttempts ?? SERVICE_COOPERATIVE_STOP_SECONDS * 4;
31489
+ this.confirmInitialReadiness = dependencies.confirmInitialReadiness ?? true;
31245
31490
  this.acquireProcessLock = dependencies.acquireProcessLock ?? acquireInferenceHostProcessLock;
31246
31491
  managerName(this.platform);
31247
31492
  if (dependencies.platform === void 0 && this.platform === "linux" && isWindowsSubsystemForLinux()) {
@@ -31259,6 +31504,9 @@ WantedBy=default.target
31259
31504
  logPath() {
31260
31505
  return inferenceHostServiceLogPath(this.config);
31261
31506
  }
31507
+ runtimePath() {
31508
+ return inferenceHostServiceRuntimePath(this.config);
31509
+ }
31262
31510
  controlLockPath() {
31263
31511
  return `${this.config.supervisorProcessLockPath}.service-control`;
31264
31512
  }
@@ -31344,7 +31592,77 @@ WantedBy=default.target
31344
31592
  }
31345
31593
  throw new Error("Background service did not reach an active state within 10 seconds.");
31346
31594
  }
31347
- async registerManifestUnlocked(manifest, desiredRunning) {
31595
+ async restoreRunningSupervisor(manifest) {
31596
+ const restoredManifest = assertManifest({
31597
+ ...manifest,
31598
+ generation: randomUUID(),
31599
+ installed_at: this.now().toISOString()
31600
+ });
31601
+ await writeAtomicInferencePrivateFile(
31602
+ this.manifestPath(),
31603
+ `${JSON.stringify(restoredManifest, null, 2)}
31604
+ `
31605
+ );
31606
+ await writeDesired(this.desiredPath(), true, this.now());
31607
+ const result2 = await this.managerCommand("start");
31608
+ if (result2.exitCode !== 0 && !/already running|in progress|already loaded|service is already loaded/iu.test(`${result2.stdout}
31609
+ ${result2.stderr}`)) {
31610
+ throw new Error(`Background service restoration failed: ${result2.stderr.trim()}`);
31611
+ }
31612
+ if (this.platform === "darwin") {
31613
+ const domain2 = `gui/${typeof process.getuid === "function" ? process.getuid() : 0}`;
31614
+ const kicked = await this.runCommand("launchctl", [
31615
+ "kickstart",
31616
+ `${domain2}/${LAUNCHD_LABEL}`
31617
+ ]);
31618
+ if (kicked.exitCode !== 0) {
31619
+ throw new Error(`Background service restoration kickstart failed: ${kicked.stderr.trim()}`);
31620
+ }
31621
+ }
31622
+ await this.waitForManagerActive();
31623
+ if (!this.confirmInitialReadiness) return await this.status();
31624
+ const runtime = await this.waitForManifestApplied(restoredManifest);
31625
+ return await this.status(runtime);
31626
+ }
31627
+ async waitForManifestApplied(manifest, targetInstanceName, maxAttempts = this.reconcileWaitAttempts) {
31628
+ let consecutiveReadyObservations = 0;
31629
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
31630
+ let runtime = null;
31631
+ try {
31632
+ runtime = await readRuntimeAcrossAtomicReplacement(this.runtimePath());
31633
+ } catch (error48) {
31634
+ if (!(error48 instanceof Error) || !error48.message.includes("changed while it was opened")) {
31635
+ throw error48;
31636
+ }
31637
+ }
31638
+ if (runtime?.manifest_generation === manifest.generation) {
31639
+ const configuredNames = manifest.workers.map((worker) => worker.instance_name).sort();
31640
+ const runtimeNames = runtime.workers.map((worker) => worker.instance_name).sort();
31641
+ const exactWorkerSet = JSON.stringify(configuredNames) === JSON.stringify(runtimeNames);
31642
+ const target = targetInstanceName ? runtime.workers.find((worker) => worker.instance_name === targetInstanceName) : null;
31643
+ const allReady = runtime.workers.every((worker) => worker.state === "running");
31644
+ if (exactWorkerSet && (targetInstanceName ? target?.state === "running" : allReady)) {
31645
+ consecutiveReadyObservations += 1;
31646
+ if (consecutiveReadyObservations >= 2) return runtime;
31647
+ } else {
31648
+ consecutiveReadyObservations = 0;
31649
+ }
31650
+ if (exactWorkerSet && target?.state === "failed") {
31651
+ throw new Error(`Inference-host worker ${targetInstanceName} failed before readiness.`);
31652
+ }
31653
+ } else {
31654
+ consecutiveReadyObservations = 0;
31655
+ }
31656
+ if (runtime?.rejected_manifest_generation === manifest.generation) {
31657
+ throw new Error("Inference-host supervisor rejected the updated worker set.");
31658
+ }
31659
+ await this.sleep(250);
31660
+ }
31661
+ throw new Error(
31662
+ targetInstanceName ? `Inference-host supervisor did not confirm worker ${targetInstanceName} ready before timeout.` : "Inference-host supervisor did not confirm the updated worker set before timeout."
31663
+ );
31664
+ }
31665
+ async registerManifestUnlocked(manifest, desiredRunning, targetInstanceName) {
31348
31666
  const args = serviceArguments(manifest.script, this.manifestPath());
31349
31667
  const definition = this.platform === "win32" ? windowsTaskXml(this.windowsLauncherPath(), this.windowsDirectory, this.username) : this.platform === "darwin" ? launchAgentPlist(manifest.executable, args, manifest.log_path) : systemdUnit(manifest.executable, args);
31350
31668
  let managerInstallAttempted = false;
@@ -31380,6 +31698,9 @@ ${started.stderr}`)) {
31380
31698
  throw new Error(`Background service start failed: ${started.stderr.trim()}`);
31381
31699
  }
31382
31700
  await this.waitForManagerActive();
31701
+ if (this.confirmInitialReadiness) {
31702
+ await this.waitForManifestApplied(manifest, targetInstanceName);
31703
+ }
31383
31704
  }
31384
31705
  return await this.status();
31385
31706
  } catch (error48) {
@@ -31404,22 +31725,85 @@ ${cleanup.stderr}`)) {
31404
31725
  }
31405
31726
  await rm4(this.manifestPath(), { force: true }).catch(() => void 0);
31406
31727
  await rm4(this.desiredPath(), { force: true }).catch(() => void 0);
31728
+ await rm4(this.runtimePath(), { force: true }).catch(() => void 0);
31407
31729
  if (this.platform === "linux") {
31408
31730
  await this.runCommand("systemctl", ["--user", "daemon-reload"]).catch(() => void 0);
31409
31731
  }
31410
31732
  throw error48;
31411
31733
  }
31412
31734
  }
31413
- async replaceManifestUnlocked(next, desiredRunning) {
31735
+ async replaceManifestUnlocked(next, desiredRunning, targetInstanceName) {
31414
31736
  const previous = await readInferenceHostServiceManifest(this.manifestPath());
31415
31737
  const previousDesired = previous ? await readInferenceHostServiceDesired(this.desiredPath()) : false;
31416
- if (previous) await this.uninstallUnlocked();
31738
+ if (previous && sameServiceDefinition(previous, next) && sameWorkerSet(previous, next) && previousDesired === desiredRunning) {
31739
+ return await this.status();
31740
+ }
31741
+ if (previous && previousDesired && desiredRunning && sameServiceDefinition(previous, next)) {
31742
+ const current = await this.status();
31743
+ const runtime = await readRuntimeAcrossAtomicReplacement(this.runtimePath()).catch(() => null);
31744
+ if (current.manager_active && runtime?.manifest_generation === previous.generation) {
31745
+ await writeAtomicInferencePrivateFile(
31746
+ this.manifestPath(),
31747
+ `${JSON.stringify(next, null, 2)}
31748
+ `
31749
+ );
31750
+ if (!this.confirmInitialReadiness) return await this.status();
31751
+ try {
31752
+ const confirmedRuntime = await this.waitForManifestApplied(next, targetInstanceName);
31753
+ return await this.status(confirmedRuntime);
31754
+ } catch (error48) {
31755
+ await writeAtomicInferencePrivateFile(
31756
+ this.manifestPath(),
31757
+ `${JSON.stringify(previous, null, 2)}
31758
+ `
31759
+ );
31760
+ try {
31761
+ await this.waitForManifestApplied(
31762
+ previous,
31763
+ targetInstanceName && previous.workers.some(
31764
+ (worker) => worker.instance_name === targetInstanceName
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)
31771
+ );
31772
+ } catch (rollbackError) {
31773
+ throw new Error(
31774
+ "Inference-host live reconfiguration failed and rollback was not confirmed.",
31775
+ { cause: new AggregateError([error48, rollbackError]) }
31776
+ );
31777
+ }
31778
+ throw new Error(
31779
+ "Inference-host live reconfiguration failed; the previous worker set was restored.",
31780
+ { cause: error48 }
31781
+ );
31782
+ }
31783
+ }
31784
+ }
31785
+ if (previous) {
31786
+ try {
31787
+ await this.uninstallUnlocked();
31788
+ } catch (error48) {
31789
+ throw new Error(
31790
+ "Inference-host service reconfiguration could not stop the previous supervisor; its desired state was restored.",
31791
+ { cause: error48 }
31792
+ );
31793
+ }
31794
+ }
31417
31795
  try {
31418
- return await this.registerManifestUnlocked(next, desiredRunning);
31796
+ return await this.registerManifestUnlocked(next, desiredRunning, targetInstanceName);
31419
31797
  } catch (error48) {
31420
31798
  if (!previous) throw error48;
31421
31799
  try {
31422
- await this.registerManifestUnlocked(previous, previousDesired);
31800
+ await this.registerManifestUnlocked(
31801
+ previous,
31802
+ previousDesired,
31803
+ targetInstanceName && previous.workers.some(
31804
+ (worker) => worker.instance_name === targetInstanceName
31805
+ ) ? targetInstanceName : void 0
31806
+ );
31423
31807
  } catch (rollbackError) {
31424
31808
  throw new Error(
31425
31809
  "Inference-host service reconfiguration failed and the previous supervisor could not be restored.",
@@ -31476,6 +31860,7 @@ ${cleanup.stderr}`)) {
31476
31860
  ].sort((left, right) => left.instance_name.localeCompare(right.instance_name));
31477
31861
  const manifest = assertManifest({
31478
31862
  schema_version: "vtx_inference_service_v3",
31863
+ generation: randomUUID(),
31479
31864
  installed_at: this.now().toISOString(),
31480
31865
  executable: this.executable,
31481
31866
  script: this.script,
@@ -31484,7 +31869,8 @@ ${cleanup.stderr}`)) {
31484
31869
  });
31485
31870
  return await this.replaceManifestUnlocked(
31486
31871
  manifest,
31487
- options.startImmediately !== false
31872
+ options.startImmediately !== false,
31873
+ this.config.instanceName
31488
31874
  );
31489
31875
  }
31490
31876
  async start() {
@@ -31516,41 +31902,56 @@ ${result2.stderr}`)) {
31516
31902
  if (!manifest) {
31517
31903
  throw new Error("Inference-host service is not installed.");
31518
31904
  }
31905
+ const restoreOnFailure = await readInferenceHostServiceDesired(this.desiredPath());
31519
31906
  await writeDesired(this.desiredPath(), false, this.now());
31520
- const serviceLockPath = `${this.config.supervisorProcessLockPath}.service`;
31521
- let serviceReleased = false;
31522
- for (let attempt = 0; attempt < this.stopWaitAttempts; attempt += 1) {
31523
- try {
31524
- const probe = await this.acquireProcessLock(serviceLockPath);
31525
- await probe.release();
31526
- serviceReleased = true;
31527
- break;
31528
- } catch (error48) {
31529
- if (error48 instanceof Error && error48.message.includes("Another inference host process already owns")) {
31530
- await this.sleep(250);
31531
- continue;
31907
+ try {
31908
+ const serviceLockPath = `${this.config.supervisorProcessLockPath}.service`;
31909
+ let serviceReleased = false;
31910
+ for (let attempt = 0; attempt < this.stopWaitAttempts; attempt += 1) {
31911
+ try {
31912
+ const probe = await this.acquireProcessLock(serviceLockPath);
31913
+ await probe.release();
31914
+ serviceReleased = true;
31915
+ break;
31916
+ } catch (error48) {
31917
+ if (error48 instanceof Error && error48.message.includes("Another inference host process already owns")) {
31918
+ await this.sleep(250);
31919
+ continue;
31920
+ }
31921
+ throw error48;
31532
31922
  }
31533
- throw error48;
31534
31923
  }
31535
- }
31536
- if (!serviceReleased) {
31537
- throw new Error(
31538
- `Inference-host workers did not stop cooperatively within ${SERVICE_COOPERATIVE_STOP_SECONDS} seconds; refusing forced termination while cleanup may be pending.`
31539
- );
31540
- }
31541
- const result2 = await this.managerCommand("stop");
31542
- if (result2.exitCode !== 0 && !/not running|not found|does not exist|not loaded|cannot find|no such process/iu.test(`${result2.stdout}
31924
+ if (!serviceReleased) {
31925
+ throw new Error(
31926
+ `Inference-host workers did not stop cooperatively within ${SERVICE_COOPERATIVE_STOP_SECONDS} seconds; refusing forced termination while cleanup may be pending.`
31927
+ );
31928
+ }
31929
+ const result2 = await this.managerCommand("stop");
31930
+ if (result2.exitCode !== 0 && !/not running|not found|does not exist|not loaded|cannot find|no such process/iu.test(`${result2.stdout}
31543
31931
  ${result2.stderr}`)) {
31544
- throw new Error(`Background service stop failed: ${result2.stderr.trim()}`);
31545
- }
31546
- const status = await this.status();
31547
- if (status.manager_active) {
31548
- throw new Error("Background service manager still reports the service active after stop.");
31932
+ throw new Error(`Background service stop failed: ${result2.stderr.trim()}`);
31933
+ }
31934
+ const status = await this.status();
31935
+ if (status.manager_active) {
31936
+ throw new Error("Background service manager still reports the service active after stop.");
31937
+ }
31938
+ return status;
31939
+ } catch (error48) {
31940
+ if (!restoreOnFailure) throw error48;
31941
+ try {
31942
+ await this.restoreRunningSupervisor(manifest);
31943
+ } catch (restoreError) {
31944
+ throw new Error(
31945
+ "Inference-host stop failed and the previous supervisor could not be restored.",
31946
+ { cause: new AggregateError([error48, restoreError]) }
31947
+ );
31948
+ }
31949
+ throw error48;
31549
31950
  }
31550
- return status;
31551
31951
  }
31552
- async status() {
31952
+ async status(confirmedRuntime) {
31553
31953
  const manifest = await readInferenceHostServiceManifest(this.manifestPath());
31954
+ const runtime = confirmedRuntime?.manifest_generation === manifest?.generation ? confirmedRuntime : await readRuntimeAcrossAtomicReplacement(this.runtimePath()).catch(() => null);
31554
31955
  const desired = await readInferenceHostServiceDesired(this.desiredPath());
31555
31956
  const result2 = await this.managerCommand("status");
31556
31957
  const output3 = result2.stdout.trim();
@@ -31568,7 +31969,8 @@ ${result2.stderr}`)) {
31568
31969
  display_name: worker.display_name,
31569
31970
  max_concurrency: worker.max_concurrency,
31570
31971
  authenticated_account_email: worker.authenticated_account_email,
31571
- authenticated_account_plan: worker.authenticated_account_plan
31972
+ authenticated_account_plan: worker.authenticated_account_plan,
31973
+ runtime_state: runtime?.manifest_generation === manifest.generation && managerActive ? runtime.workers.find((item) => item.instance_name === worker.instance_name)?.state ?? "unknown" : "unknown"
31572
31974
  })) ?? []
31573
31975
  };
31574
31976
  }
@@ -31606,6 +32008,7 @@ ${result2.stderr}`)) {
31606
32008
  if (remaining.length === 0) return await this.uninstallUnlocked();
31607
32009
  return await this.replaceManifestUnlocked({
31608
32010
  ...manifest,
32011
+ generation: randomUUID(),
31609
32012
  installed_at: this.now().toISOString(),
31610
32013
  workers: remaining
31611
32014
  }, await readInferenceHostServiceDesired(this.desiredPath()));
@@ -31633,6 +32036,7 @@ ${result2.stderr}`)) {
31633
32036
  if (this.platform === "linux") await this.runCommand("systemctl", ["--user", "daemon-reload"]);
31634
32037
  await rm4(this.manifestPath(), { force: true });
31635
32038
  await rm4(this.desiredPath(), { force: true });
32039
+ await rm4(this.runtimePath(), { force: true });
31636
32040
  return {
31637
32041
  installed: false,
31638
32042
  desired_running: false,
@@ -31654,7 +32058,8 @@ ${result2.stderr}`)) {
31654
32058
  `, resolvePromise);
31655
32059
  });
31656
32060
  };
31657
- spawnServiceChild = async (manifest, worker, signal) => {
32061
+ spawnInferenceHostServiceChild = async (manifest, worker, signal, onReady = () => {
32062
+ }) => {
31658
32063
  const args = [
31659
32064
  manifest.script,
31660
32065
  "inference-host",
@@ -31667,6 +32072,8 @@ ${result2.stderr}`)) {
31667
32072
  const log = createWriteStream(manifest.log_path, { flags: "a", mode: 384 });
31668
32073
  return await new Promise((resolvePromise, reject) => {
31669
32074
  let stdout = "";
32075
+ let cooperativeStopTimedOut = false;
32076
+ let cooperativeStopTimer = null;
31670
32077
  const pending = { stdout: "", stderr: "" };
31671
32078
  const writeTaggedOutput = (stream, text, flush = false) => {
31672
32079
  const lines = `${pending[stream]}${text}`.split(/\r?\n/u);
@@ -31679,6 +32086,7 @@ ${result2.stderr}`)) {
31679
32086
  try {
31680
32087
  const parsed = JSON.parse(line);
31681
32088
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
32089
+ if (parsed.event === "runtime_started") onReady();
31682
32090
  log.write(`${JSON.stringify({
31683
32091
  ...parsed,
31684
32092
  instance_name: worker.instance_name
@@ -31701,7 +32109,7 @@ ${result2.stderr}`)) {
31701
32109
  const child = spawn5(manifest.executable, args, {
31702
32110
  env: inferenceHostServiceChildEnvironment(worker.runtime_environment),
31703
32111
  windowsHide: true,
31704
- stdio: ["ignore", "pipe", "pipe"]
32112
+ stdio: ["pipe", "pipe", "pipe"]
31705
32113
  });
31706
32114
  child.stdout.on("data", (chunk) => {
31707
32115
  const text = chunk.toString("utf8");
@@ -31709,10 +32117,22 @@ ${result2.stderr}`)) {
31709
32117
  stdout = `${stdout}${text}`.slice(-65536);
31710
32118
  });
31711
32119
  child.stderr.on("data", (chunk) => writeTaggedOutput("stderr", chunk.toString("utf8")));
31712
- const onAbort = () => child.kill("SIGTERM");
31713
- signal.addEventListener("abort", onAbort, { once: true });
32120
+ child.stdin.on("error", () => {
32121
+ });
32122
+ const onAbort = () => {
32123
+ child.stdin.end(`${INFERENCE_HOST_SERVICE_DRAIN_COMMAND}
32124
+ `);
32125
+ cooperativeStopTimer = setTimeout(() => {
32126
+ cooperativeStopTimedOut = true;
32127
+ child.kill("SIGKILL");
32128
+ }, SERVICE_COOPERATIVE_STOP_SECONDS * 1e3);
32129
+ cooperativeStopTimer.unref();
32130
+ };
32131
+ if (signal.aborted) onAbort();
32132
+ else signal.addEventListener("abort", onAbort, { once: true });
31714
32133
  child.once("error", (error48) => {
31715
32134
  signal.removeEventListener("abort", onAbort);
32135
+ if (cooperativeStopTimer) clearTimeout(cooperativeStopTimer);
31716
32136
  writeTaggedOutput("stdout", "", true);
31717
32137
  writeTaggedOutput("stderr", "", true);
31718
32138
  log.end();
@@ -31720,6 +32140,7 @@ ${result2.stderr}`)) {
31720
32140
  });
31721
32141
  child.once("exit", (code) => {
31722
32142
  signal.removeEventListener("abort", onAbort);
32143
+ if (cooperativeStopTimer) clearTimeout(cooperativeStopTimer);
31723
32144
  writeTaggedOutput("stdout", "", true);
31724
32145
  writeTaggedOutput("stderr", "", true);
31725
32146
  log.end();
@@ -31739,81 +32160,231 @@ ${result2.stderr}`)) {
31739
32160
  resolvePromise({
31740
32161
  exitCode: code ?? 1,
31741
32162
  uptimeMs: Date.now() - startedAt,
31742
- drainReason
32163
+ drainReason,
32164
+ cooperativeStopTimedOut
31743
32165
  });
31744
32166
  });
31745
32167
  });
31746
32168
  };
31747
32169
  runInferenceHostServiceSupervisor = async (manifestPath, options = {}) => {
31748
- const manifest = await readInferenceHostServiceManifest(manifestPath);
31749
- if (!manifest) throw new Error("Inference-host service manifest is missing.");
32170
+ const initialManifest = await readInferenceHostServiceManifest(manifestPath);
32171
+ if (!initialManifest) throw new Error("Inference-host service manifest is missing.");
31750
32172
  const desiredPath = manifestPath.replace(/\.service\.json$/u, ".service-desired.json");
32173
+ const runtimePath = manifestPath.replace(/\.service\.json$/u, ".service-runtime.json");
31751
32174
  const signal = options.signal ?? new AbortController().signal;
31752
32175
  const sleep4 = options.sleep ?? (async (milliseconds) => {
31753
32176
  await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
31754
32177
  });
31755
- const launch = options.runWorker ?? options.spawnChild ?? spawnServiceChild;
31756
- await appendServiceLog(manifest.log_path, "service_supervisor_started", {
32178
+ const launch = options.runWorker ?? options.spawnChild ?? spawnInferenceHostServiceChild;
32179
+ await appendServiceLog(initialManifest.log_path, "service_supervisor_started", {
31757
32180
  adapter: "codex",
31758
- instances: manifest.workers.map((worker) => worker.instance_name)
32181
+ instances: initialManifest.workers.map((worker) => worker.instance_name)
31759
32182
  });
31760
- const superviseWorker = async (worker) => {
32183
+ const workers = /* @__PURE__ */ new Map();
32184
+ let appliedManifest = initialManifest;
32185
+ let rejectedManifestGeneration = null;
32186
+ let runtimeWriteChain = Promise.resolve();
32187
+ const persistRuntime = async () => {
32188
+ const state = {
32189
+ schema_version: "vtx_inference_service_runtime_v1",
32190
+ manifest_generation: appliedManifest.generation,
32191
+ rejected_manifest_generation: rejectedManifestGeneration,
32192
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
32193
+ workers: [...workers.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([instanceName, record2]) => ({
32194
+ instance_name: instanceName,
32195
+ state: record2.state,
32196
+ error: record2.error
32197
+ }))
32198
+ };
32199
+ runtimeWriteChain = runtimeWriteChain.then(async () => {
32200
+ await writeAtomicInferencePrivateFile(runtimePath, `${JSON.stringify(state, null, 2)}
32201
+ `);
32202
+ });
32203
+ await runtimeWriteChain;
32204
+ };
32205
+ const updateRecord = async (record2, state, error48 = null) => {
32206
+ if (workers.get(record2.worker.instance_name) !== record2) return;
32207
+ record2.state = state;
32208
+ record2.error = error48;
32209
+ await persistRuntime();
32210
+ };
32211
+ const waitRetry = async (milliseconds, controller) => {
32212
+ if (controller.signal.aborted || signal.aborted) return;
32213
+ let finishAbort;
32214
+ const aborted2 = new Promise((resolvePromise) => {
32215
+ finishAbort = resolvePromise;
32216
+ });
32217
+ controller.signal.addEventListener("abort", finishAbort, { once: true });
32218
+ signal.addEventListener("abort", finishAbort, { once: true });
32219
+ try {
32220
+ await Promise.race([sleep4(milliseconds), aborted2]);
32221
+ } finally {
32222
+ controller.signal.removeEventListener("abort", finishAbort);
32223
+ signal.removeEventListener("abort", finishAbort);
32224
+ }
32225
+ };
32226
+ const superviseWorker = async (serviceManifest, record2) => {
31761
32227
  let failures = 0;
31762
- while (!signal.aborted && await readDesiredAcrossAtomicReplacement(desiredPath)) {
32228
+ while (!signal.aborted && !record2.controller.signal.aborted && await readDesiredAcrossAtomicReplacement(desiredPath)) {
31763
32229
  try {
31764
- const workerController = new AbortController();
31765
- const forwardAbort = () => workerController.abort();
32230
+ await updateRecord(record2, "starting");
32231
+ const attemptController = new AbortController();
32232
+ const forwardAbort = () => attemptController.abort();
31766
32233
  signal.addEventListener("abort", forwardAbort, { once: true });
31767
- let workerComplete = false;
31768
- let monitorError = null;
31769
- const desiredMonitor = (async () => {
31770
- while (!workerComplete && !workerController.signal.aborted) {
31771
- await sleep4(500);
31772
- if (!await readDesiredAcrossAtomicReplacement(desiredPath)) {
31773
- workerController.abort();
31774
- break;
31775
- }
31776
- }
31777
- })().catch((error48) => {
31778
- monitorError = error48;
31779
- workerController.abort();
31780
- });
32234
+ record2.controller.signal.addEventListener("abort", forwardAbort, { once: true });
31781
32235
  let result2;
32236
+ let launchSettled = false;
32237
+ let readinessTask = Promise.resolve();
32238
+ let readinessSignalled = false;
31782
32239
  try {
31783
- result2 = await launch(manifest, worker, workerController.signal);
32240
+ result2 = await launch(
32241
+ serviceManifest,
32242
+ record2.worker,
32243
+ attemptController.signal,
32244
+ () => {
32245
+ if (readinessSignalled) return;
32246
+ readinessSignalled = true;
32247
+ readinessTask = (async () => {
32248
+ await sleep4(500);
32249
+ if (!launchSettled && !attemptController.signal.aborted && !record2.controller.signal.aborted && !signal.aborted) {
32250
+ await updateRecord(record2, "running");
32251
+ }
32252
+ })();
32253
+ }
32254
+ );
31784
32255
  } finally {
31785
- workerComplete = true;
31786
- workerController.abort();
32256
+ launchSettled = true;
32257
+ attemptController.abort();
31787
32258
  signal.removeEventListener("abort", forwardAbort);
31788
- await desiredMonitor;
32259
+ record2.controller.signal.removeEventListener("abort", forwardAbort);
32260
+ await readinessTask;
31789
32261
  }
31790
- if (monitorError) throw monitorError;
31791
- if (signal.aborted || !await readDesiredAcrossAtomicReplacement(desiredPath)) break;
32262
+ if (result2.cooperativeStopTimedOut) {
32263
+ await updateRecord(record2, "failed", "worker_cooperative_stop_timeout");
32264
+ await appendServiceLog(serviceManifest.log_path, "worker_cooperative_stop_timeout", {
32265
+ instance_name: record2.worker.instance_name
32266
+ });
32267
+ throw new Error("worker_cooperative_stop_timeout");
32268
+ }
32269
+ if (signal.aborted || record2.controller.signal.aborted || !await readDesiredAcrossAtomicReplacement(desiredPath)) break;
31792
32270
  failures = result2.uptimeMs >= 6e4 ? 0 : failures + 1;
31793
32271
  const retryAfterMs = Math.min(1e3 * 2 ** Math.min(failures, 5), 3e4);
31794
- await appendServiceLog(manifest.log_path, "worker_exited", {
31795
- instance_name: worker.instance_name,
32272
+ await updateRecord(record2, "failed", "worker_exited");
32273
+ await appendServiceLog(serviceManifest.log_path, "worker_exited", {
32274
+ instance_name: record2.worker.instance_name,
31796
32275
  exit_code: result2.exitCode,
31797
32276
  uptime_ms: result2.uptimeMs,
31798
32277
  drain_reason: result2.drainReason ?? null,
31799
32278
  retry_after_ms: retryAfterMs
31800
32279
  });
31801
- await sleep4(retryAfterMs);
32280
+ await waitRetry(retryAfterMs, record2.controller);
31802
32281
  } catch (error48) {
31803
- if (signal.aborted) break;
32282
+ if (error48 instanceof Error && error48.message === "worker_cooperative_stop_timeout") throw error48;
32283
+ if (signal.aborted || record2.controller.signal.aborted) break;
31804
32284
  failures += 1;
31805
32285
  const retryAfterMs = Math.min(1e3 * 2 ** Math.min(failures, 5), 3e4);
31806
- await appendServiceLog(manifest.log_path, "worker_launch_failed", {
31807
- instance_name: worker.instance_name,
32286
+ await updateRecord(record2, "failed", "worker_launch_failed");
32287
+ await appendServiceLog(serviceManifest.log_path, "worker_launch_failed", {
32288
+ instance_name: record2.worker.instance_name,
31808
32289
  error: error48 instanceof Error ? error48.message : "unknown",
31809
32290
  retry_after_ms: retryAfterMs
31810
32291
  });
31811
- await sleep4(retryAfterMs);
32292
+ await waitRetry(retryAfterMs, record2.controller);
31812
32293
  }
31813
32294
  }
31814
32295
  };
31815
- await Promise.all(manifest.workers.map(superviseWorker));
31816
- await appendServiceLog(manifest.log_path, "service_supervisor_stopped");
32296
+ const startWorker = (serviceManifest, worker) => {
32297
+ const record2 = {
32298
+ worker,
32299
+ controller: new AbortController(),
32300
+ promise: Promise.resolve(),
32301
+ state: "starting",
32302
+ error: null
32303
+ };
32304
+ workers.set(worker.instance_name, record2);
32305
+ record2.promise = superviseWorker(serviceManifest, record2);
32306
+ };
32307
+ const reconcile = async (next) => {
32308
+ const nextByName = new Map(next.workers.map((worker) => [worker.instance_name, worker]));
32309
+ const retiring = [...workers.values()].filter((record2) => {
32310
+ const nextWorker = nextByName.get(record2.worker.instance_name);
32311
+ return !nextWorker || !sameWorker(record2.worker, nextWorker);
32312
+ });
32313
+ for (const record2 of retiring) {
32314
+ await updateRecord(record2, "draining");
32315
+ record2.controller.abort();
32316
+ }
32317
+ const settled = await Promise.allSettled(retiring.map((record2) => record2.promise));
32318
+ const failedDrain = settled.find(
32319
+ (result2) => result2.status === "rejected"
32320
+ );
32321
+ if (failedDrain) {
32322
+ for (const record2 of retiring) workers.delete(record2.worker.instance_name);
32323
+ for (const worker of appliedManifest.workers) {
32324
+ if (!workers.has(worker.instance_name)) startWorker(appliedManifest, worker);
32325
+ }
32326
+ rejectedManifestGeneration = next.generation;
32327
+ await persistRuntime();
32328
+ await appendServiceLog(appliedManifest.log_path, "service_manifest_rejected", {
32329
+ manifest_generation: next.generation,
32330
+ reason: failedDrain.reason instanceof Error ? failedDrain.reason.message : "worker_cooperative_stop_failed"
32331
+ });
32332
+ return;
32333
+ }
32334
+ for (const record2 of retiring) workers.delete(record2.worker.instance_name);
32335
+ for (const worker of next.workers) {
32336
+ if (!workers.has(worker.instance_name)) startWorker(next, worker);
32337
+ }
32338
+ appliedManifest = next;
32339
+ rejectedManifestGeneration = null;
32340
+ await persistRuntime();
32341
+ await appendServiceLog(next.log_path, "service_manifest_applied", {
32342
+ manifest_generation: next.generation,
32343
+ instances: next.workers.map((worker) => worker.instance_name)
32344
+ });
32345
+ };
32346
+ const drainWorkers = async () => {
32347
+ const draining = [...workers.values()];
32348
+ for (const record2 of draining) {
32349
+ await updateRecord(record2, "draining");
32350
+ record2.controller.abort();
32351
+ }
32352
+ const settled = await Promise.allSettled(draining.map((record2) => record2.promise));
32353
+ const failedDrain = settled.find(
32354
+ (result2) => result2.status === "rejected"
32355
+ );
32356
+ if (failedDrain) throw failedDrain.reason;
32357
+ for (const record2 of draining) {
32358
+ if (workers.get(record2.worker.instance_name) === record2) {
32359
+ workers.delete(record2.worker.instance_name);
32360
+ }
32361
+ }
32362
+ await persistRuntime();
32363
+ };
32364
+ await reconcile(initialManifest);
32365
+ try {
32366
+ while (!signal.aborted) {
32367
+ while (!signal.aborted && await readDesiredAcrossAtomicReplacement(desiredPath)) {
32368
+ const next = await readManifestAcrossAtomicReplacement(manifestPath);
32369
+ if (!next) throw new Error("Inference-host service manifest is missing.");
32370
+ if (next.generation !== appliedManifest.generation && next.generation !== rejectedManifestGeneration) await reconcile(next);
32371
+ await sleep4(250);
32372
+ }
32373
+ await drainWorkers();
32374
+ if (signal.aborted || !await readDesiredAcrossAtomicReplacement(desiredPath)) break;
32375
+ const restored = await readManifestAcrossAtomicReplacement(manifestPath);
32376
+ if (!restored) throw new Error("Inference-host service manifest is missing.");
32377
+ await reconcile(restored);
32378
+ await appendServiceLog(restored.log_path, "service_desired_state_restored", {
32379
+ manifest_generation: restored.generation,
32380
+ instances: restored.workers.map((worker) => worker.instance_name)
32381
+ });
32382
+ }
32383
+ } finally {
32384
+ await drainWorkers();
32385
+ await runtimeWriteChain;
32386
+ await appendServiceLog(appliedManifest.log_path, "service_supervisor_stopped");
32387
+ }
31817
32388
  };
31818
32389
  }
31819
32390
  });
@@ -31822,9 +32393,10 @@ ${result2.stderr}`)) {
31822
32393
  var cli_exports = {};
31823
32394
  __export(cli_exports, {
31824
32395
  INFERENCE_HOST_CLI_VERSION: () => INFERENCE_HOST_CLI_VERSION,
32396
+ registerInferenceHostServiceControlInput: () => registerInferenceHostServiceControlInput,
31825
32397
  runInferenceHostCli: () => runInferenceHostCli
31826
32398
  });
31827
- import { randomUUID } from "node:crypto";
32399
+ import { randomUUID as randomUUID2 } from "node:crypto";
31828
32400
  import { spawn as spawn6 } from "node:child_process";
31829
32401
  import { lstat as lstat4, realpath as realpath4, rm as rm5 } from "node:fs/promises";
31830
32402
  import { join as join6, resolve as resolve5 } from "node:path";
@@ -31835,7 +32407,7 @@ async function runInferenceHostCli(argv2, env = process.env, dependencies = {})
31835
32407
  return { exitCode: 0, stdout: INFERENCE_HOST_HELP, stderr: "" };
31836
32408
  }
31837
32409
  const parsed = parseInferenceHostArgs(argv2, env);
31838
- const config2 = await resolveInferenceHostCommandConfig(parsed, env);
32410
+ const config2 = await resolveInferenceHostCommandConfig(parsed, env, dependencies);
31839
32411
  if (parsed.command === "login") {
31840
32412
  return await login(config2, parsed, env, dependencies, warnings);
31841
32413
  }
@@ -31902,7 +32474,7 @@ async function runInferenceHostCli(argv2, env = process.env, dependencies = {})
31902
32474
  };
31903
32475
  }
31904
32476
  }
31905
- 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, 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;
31906
32478
  var init_cli = __esm({
31907
32479
  "lib/inference-host/cli.ts"() {
31908
32480
  "use strict";
@@ -31926,6 +32498,7 @@ var init_cli = __esm({
31926
32498
  codexGuardianReceiptRoot = (config2) => `${config2.statePath}.codex-processes`;
31927
32499
  revocationCheckpointPath = (config2) => `${config2.statePath}.revoke.json`;
31928
32500
  foregroundHostLockPath = (config2) => `${config2.processLockPath}.foreground`;
32501
+ credentialLifecycleLockPath = (config2) => `${inferenceHostCredentialContextPath(config2)}.lock`;
31929
32502
  AGENT_HEARTBEAT_INTERVAL_MS = 3e3;
31930
32503
  AGENT_HEARTBEAT_MAX_RETRY_DELAY_MS = 9e3;
31931
32504
  retryableAgentHeartbeatError = (error48) => {
@@ -32147,14 +32720,35 @@ Durable service:
32147
32720
  });
32148
32721
  child.unref();
32149
32722
  };
32723
+ registerInferenceHostServiceControlInput = (input, abort) => {
32724
+ let pendingControlInput = "";
32725
+ const onControlInput = (chunk) => {
32726
+ pendingControlInput = `${pendingControlInput}${chunk.toString()}`.slice(-4096);
32727
+ const lines = pendingControlInput.split(/\r?\n/u);
32728
+ pendingControlInput = lines.pop() ?? "";
32729
+ if (lines.some((line) => line === INFERENCE_HOST_SERVICE_DRAIN_COMMAND)) {
32730
+ abort();
32731
+ }
32732
+ };
32733
+ input.on("data", onControlInput);
32734
+ input.resume();
32735
+ return () => {
32736
+ input.off("data", onControlInput);
32737
+ input.pause();
32738
+ };
32739
+ };
32150
32740
  defaultRegisterLifecycleSignalHandlers = (abort) => {
32151
32741
  const onSigint = () => abort("SIGINT");
32152
32742
  const onSigterm = () => abort("SIGTERM");
32153
32743
  process.on("SIGINT", onSigint);
32154
32744
  process.on("SIGTERM", onSigterm);
32745
+ const serviceChild = process.env.VTX_INFERENCE_HOST_SERVICE_CHILD === "1";
32746
+ const unregisterControlInput = serviceChild ? registerInferenceHostServiceControlInput(process.stdin, onSigterm) : () => {
32747
+ };
32155
32748
  return () => {
32156
32749
  process.off("SIGINT", onSigint);
32157
32750
  process.off("SIGTERM", onSigterm);
32751
+ unregisterControlInput();
32158
32752
  };
32159
32753
  };
32160
32754
  lifecycleCancellation = (dependencies) => {
@@ -32173,6 +32767,36 @@ Durable service:
32173
32767
  };
32174
32768
  };
32175
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
+ };
32176
32800
  accountKeyForState = (state) => inferenceCredentialAccountKey({
32177
32801
  issuer: state.issuer,
32178
32802
  clientId: state.client_id,
@@ -32306,8 +32930,15 @@ Durable service:
32306
32930
  }).run();
32307
32931
  };
32308
32932
  login = async (config2, parsed, env, dependencies, warnings) => {
32309
- 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;
32310
32940
  try {
32941
+ lock2 = await acquireInferenceHostProcessLock(config2.processLockPath);
32311
32942
  if (await readRevocationCheckpoint(config2)) {
32312
32943
  throw new Error("Inference host revocation recovery must finish before login.");
32313
32944
  }
@@ -32317,18 +32948,41 @@ Durable service:
32317
32948
  const store = configuredCredentialStore(config2, dependencies, (message) => {
32318
32949
  warnings.push(message);
32319
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
+ }
32320
32955
  if (await store.readRecovery?.()) {
32321
32956
  throw new Error("Inference host credential recovery is required before login.");
32322
32957
  }
32958
+ previousCredentialContext = await readInferenceHostCredentialContext(config2);
32959
+ await writeInferenceHostCredentialContextTransition(config2, previousCredentialContext);
32960
+ await writeInferenceHostCredentialContext(config2);
32323
32961
  const keyPair = generateExternalInferenceEnvelopeKeyPair();
32324
- const hostId = randomUUID();
32962
+ const hostId = randomUUID2();
32325
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
+ };
32326
32980
  const pending = await beginLogin({
32327
32981
  apiUrl: config2.apiUrl,
32328
32982
  hostId,
32329
32983
  x25519PrivateKey: keyPair.private_key,
32330
32984
  keyGeneration: 1,
32331
- store
32985
+ store: loginStore
32332
32986
  });
32333
32987
  const emitStdout = dependencies.emitStdout ?? ((text) => {
32334
32988
  process.stdout.write(text);
@@ -32372,6 +33026,7 @@ Waiting for approval...
32372
33026
  store
32373
33027
  });
32374
33028
  remoteCleanupConfirmed = true;
33029
+ await store.removeRecovery?.();
32375
33030
  } catch {
32376
33031
  if (!store.writeRecovery) {
32377
33032
  throw new Error(
@@ -32390,6 +33045,9 @@ Waiting for approval...
32390
33045
  }
32391
33046
  throw stateError;
32392
33047
  }
33048
+ localStateCommitted = true;
33049
+ await store.removeRecovery?.();
33050
+ await clearInferenceHostCredentialContextTransition(config2);
32393
33051
  return {
32394
33052
  exitCode: 0,
32395
33053
  stdout: render({
@@ -32402,8 +33060,18 @@ Waiting for approval...
32402
33060
  stderr: warnings.length > 0 ? `${warnings.join("\n")}
32403
33061
  ` : ""
32404
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;
32405
33072
  } finally {
32406
- await lock2.release();
33073
+ await lock2?.release();
33074
+ await credentialLock.release();
32407
33075
  }
32408
33076
  };
32409
33077
  codexLogin = async (config2, parsed, env, dependencies) => {
@@ -32544,6 +33212,7 @@ Waiting for approval...
32544
33212
  registered: receipt.registered,
32545
33213
  advertisement_generation: receipt.advertisement_generation,
32546
33214
  pending_attempts: Object.keys(receipt.attempts).length + (receipt.pending_claim_request ? 1 : 0),
33215
+ recovery: summarizeInferenceHostRuntimeRecovery(receipt),
32547
33216
  updated_at: receipt.updated_at
32548
33217
  } : null,
32549
33218
  agent_attempt: agentAttempt ? {
@@ -32636,10 +33305,14 @@ Waiting for approval...
32636
33305
  };
32637
33306
  };
32638
33307
  cleanupLogin = async (config2, parsed, dependencies, warnings, revoke) => {
32639
- await assertDurableServiceUninstalled(config2);
32640
- const lock2 = await acquireInferenceHostProcessLock(config2.processLockPath);
33308
+ const credentialLock = await acquireInferenceHostProcessLock(
33309
+ credentialLifecycleLockPath(config2)
33310
+ );
33311
+ let lock2 = null;
32641
33312
  let keeperLock = null;
32642
33313
  try {
33314
+ await assertDurableServiceUninstalled(config2);
33315
+ lock2 = await acquireInferenceHostProcessLock(config2.processLockPath);
32643
33316
  try {
32644
33317
  keeperLock = await acquireInferenceHostProcessLock(foregroundHostLockPath(config2));
32645
33318
  } catch (error48) {
@@ -32666,8 +33339,35 @@ Waiting for approval...
32666
33339
  state,
32667
33340
  oauthRecovery
32668
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();
32669
33368
  } else if (oauthRecovery) {
32670
33369
  if (revoke) {
33370
+ await stageCleanupTransition();
32671
33371
  const metadata = await (dependencies.discoverOAuth ?? discoverInferenceOAuth)(config2.apiUrl);
32672
33372
  await (dependencies.revokeCredential ?? revokeInferenceCredential)({
32673
33373
  metadata,
@@ -32682,9 +33382,12 @@ Waiting for approval...
32682
33382
  config2.credentialStoreIdentity
32683
33383
  );
32684
33384
  await writeRevocationCheckpoint(config2, revocationCheckpoint);
33385
+ await commitCleanupTransition();
32685
33386
  } else {
33387
+ await stageCleanupTransition();
32686
33388
  await store.remove(oauthRecovery.accountKey);
32687
33389
  await store.removeRecovery?.();
33390
+ await commitCleanupTransition();
32688
33391
  }
32689
33392
  } else if (state) {
32690
33393
  const accountKey = accountKeyForState(state);
@@ -32694,6 +33397,7 @@ Waiting for approval...
32694
33397
  if (!credential) {
32695
33398
  throw new Error("Inference host credential is unavailable; remote revoke cannot be confirmed.");
32696
33399
  }
33400
+ await stageCleanupTransition();
32697
33401
  const metadata = await (dependencies.discoverOAuth ?? discoverInferenceOAuth)(config2.apiUrl);
32698
33402
  await (dependencies.revokeCredential ?? revokeInferenceCredential)({
32699
33403
  metadata,
@@ -32708,11 +33412,16 @@ Waiting for approval...
32708
33412
  config2.credentialStoreIdentity
32709
33413
  );
32710
33414
  await writeRevocationCheckpoint(config2, revocationCheckpoint);
33415
+ await commitCleanupTransition();
32711
33416
  } else {
33417
+ await stageCleanupTransition();
32712
33418
  await store.remove(accountKey);
33419
+ await commitCleanupTransition();
32713
33420
  }
32714
33421
  } else if (!revoke && store.kind === "file") {
33422
+ await stageCleanupTransition();
32715
33423
  await store.remove("");
33424
+ await commitCleanupTransition();
32716
33425
  } else if (revoke) {
32717
33426
  throw new Error("Inference host is not logged in; there is no exact grant to revoke.");
32718
33427
  }
@@ -32732,14 +33441,18 @@ Waiting for approval...
32732
33441
  { cause: error48 }
32733
33442
  );
32734
33443
  }
32735
- await clearLocalRuntimeArtifacts(config2);
32736
33444
  await store.remove(revocationCheckpoint.account_key);
32737
33445
  if (revocationCheckpoint.remove_credential_recovery) {
32738
33446
  await store.removeRecovery?.();
32739
33447
  }
33448
+ await (dependencies.clearLocalRuntimeArtifacts ?? clearLocalRuntimeArtifacts)(config2);
33449
+ await clearInferenceHostCredentialContext(config2);
32740
33450
  await clearRevocationCheckpoint(config2);
33451
+ await clearInferenceHostCredentialContextTransition(config2);
32741
33452
  } else {
32742
- await clearLocalRuntimeArtifacts(config2);
33453
+ await (dependencies.clearLocalRuntimeArtifacts ?? clearLocalRuntimeArtifacts)(config2);
33454
+ await clearInferenceHostCredentialContext(config2);
33455
+ await clearInferenceHostCredentialContextTransition(config2);
32743
33456
  }
32744
33457
  return {
32745
33458
  exitCode: 0,
@@ -32753,7 +33466,8 @@ Waiting for approval...
32753
33466
  };
32754
33467
  } finally {
32755
33468
  await keeperLock?.release();
32756
- await lock2.release();
33469
+ await lock2?.release();
33470
+ await credentialLock.release();
32757
33471
  }
32758
33472
  };
32759
33473
  runHost = async (config2, parsed, env, dependencies, warnings) => {
@@ -32855,7 +33569,7 @@ Waiting for approval...
32855
33569
  }
32856
33570
  return record2;
32857
33571
  };
32858
- agentOperationId = (kind) => `${kind}-${randomUUID()}`;
33572
+ agentOperationId = (kind) => `${kind}-${randomUUID2()}`;
32859
33573
  agentSession = async (config2, dependencies, warnings, signal) => {
32860
33574
  if (await readRevocationCheckpoint(config2)) {
32861
33575
  throw new Error("Inference host revocation recovery must finish first.");
@@ -33300,44 +34014,52 @@ Waiting for approval...
33300
34014
  dependencies.serviceDependencies
33301
34015
  ) ?? new InferenceHostServiceManager(config2, dependencies.serviceDependencies);
33302
34016
  if (action === "install") {
33303
- const adapter = parsed.adapter || "codex";
33304
- if (adapter !== "codex") {
33305
- throw new Error(
33306
- `Adapter ${adapter} does not have a supported durable service integration. Use foreground agent-run instead.`
33307
- );
33308
- }
33309
- const state = await readInferenceHostLocalState(config2.statePath);
33310
- if (!state) throw new Error("Run vtx inference-host login before installing the service.");
33311
- const store = configuredCredentialStore(config2, dependencies, (message) => warnings.push(message));
33312
- const credential = await store.read(accountKeyForState(state));
33313
- if (!credential) throw new Error("Inference-host credential is missing. Run login again.");
33314
- assertCredentialMatchesState2(state, credential);
33315
- if (parsed.modelId || parsed.modelLabel || parsed.reasoningEffort) {
33316
- throw new Error("Automated Codex service does not accept agent model or effort options.");
33317
- }
33318
- const auth = await inspectCodexAuthentication(config2);
33319
- if (!auth.present || !auth.private) {
33320
- throw new Error("Run vtx inference-host codex-login before installing the automated Codex service.");
33321
- }
33322
- const binary = await (dependencies.resolveBinary ?? resolvePinnedCodexBinary)(env);
33323
- const preflight = await (dependencies.preflightCodex ?? preflightCodexSubscription)({
33324
- binary,
33325
- codexHome: config2.codexHomePath,
33326
- deadlineAtMs: Date.now() + 3e4
33327
- });
33328
- const status = await manager.install({
33329
- adapter: "codex",
33330
- displayName: parsed.displayName,
33331
- maxConcurrency: parsed.maxConcurrency,
33332
- authenticatedAccountEmail: preflight?.authenticated_account_email ?? null,
33333
- authenticatedAccountPlan: preflight?.authenticated_account_plan ?? null
33334
- });
33335
- return {
33336
- exitCode: 0,
33337
- stdout: render({ status: "service_installed", ...status }, parsed.json),
33338
- 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")}
33339
34058
  ` : ""
33340
- };
34059
+ };
34060
+ } finally {
34061
+ await credentialLock.release();
34062
+ }
33341
34063
  }
33342
34064
  if (parsed.adapter || parsed.modelId || parsed.modelLabel || parsed.reasoningEffort) {
33343
34065
  throw new Error("Service adapter and model options are accepted only by service install.");
@@ -33364,36 +34086,98 @@ Waiting for approval...
33364
34086
  return { exitCode: lifecycleMatches ? 0 : 1, stdout: render(status, parsed.json), stderr: "" };
33365
34087
  }
33366
34088
  if (action === "uninstall") {
33367
- const status = parsed.instanceExplicit ? await manager.uninstallInstance(config2.instanceName) : await manager.uninstall();
33368
- 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
+ }
33369
34110
  }
33370
34111
  throw new Error("Usage: vtx inference-host service <install|start|stop|status|logs|uninstall>.");
33371
34112
  };
33372
34113
  hasExplicitCredentialStoreConfiguration = (env) => Boolean(
33373
34114
  String(env.VTX_INFERENCE_HOST_CREDENTIAL_STORE || "").trim() || String(env.VTX_INFERENCE_HOST_CREDENTIAL_FILE || "").trim()
33374
34115
  );
33375
- commandUsesInstalledServiceCredentials = (parsed) => parsed.command === "status" || parsed.command === "doctor" || parsed.command === "service" && parsed.serviceAction === "install";
33376
- 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) => {
33377
34136
  const selectedEnv = {
33378
34137
  ...env,
33379
34138
  VTX_INFERENCE_HOST_INSTANCE: parsed.instanceName
33380
34139
  };
33381
34140
  const baseConfig = resolveInferenceHostConfig(selectedEnv);
33382
- if (!commandUsesInstalledServiceCredentials(parsed) || hasExplicitCredentialStoreConfiguration(env)) {
33383
- 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
+ }
33384
34151
  }
33385
34152
  const manifestPath = `${baseConfig.supervisorStatePath}.service.json`;
33386
34153
  const manifest = await readInferenceHostServiceManifest(manifestPath);
33387
- 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;
33388
34164
  const installedConfig = resolveInferenceHostConfig({
33389
34165
  ...selectedEnv,
33390
- ...manifest.workers.find((worker) => worker.instance_name === baseConfig.instanceName)?.runtime_environment
34166
+ ...retainedEnvironment
33391
34167
  });
33392
34168
  if (resolve5(installedConfig.statePath) !== resolve5(baseConfig.statePath)) {
33393
34169
  throw new Error(
33394
34170
  "Installed inference-host service manifest does not match the requested local state path."
33395
34171
  );
33396
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
+ }
33397
34181
  return installedConfig;
33398
34182
  };
33399
34183
  }
@@ -33503,7 +34287,7 @@ var init_types = __esm({
33503
34287
  });
33504
34288
 
33505
34289
  // lib/agent-core/client.ts
33506
- import { randomUUID as randomUUID2 } from "node:crypto";
34290
+ import { randomUUID as randomUUID3 } from "node:crypto";
33507
34291
  function normalizeApiUrl(value) {
33508
34292
  const parsed = String(value || "").trim();
33509
34293
  if (!parsed) {
@@ -33765,7 +34549,7 @@ var init_client = __esm({
33765
34549
  return this.request("/trading/ai/runtime/decision", {
33766
34550
  method: "POST",
33767
34551
  profileId,
33768
- idempotencyKey: randomUUID2(),
34552
+ idempotencyKey: randomUUID3(),
33769
34553
  headers: { "x-client-runtime-lease": leaseToken },
33770
34554
  body: payload
33771
34555
  });
@@ -33774,7 +34558,7 @@ var init_client = __esm({
33774
34558
  return this.request("/trading/ai/runtime/trade-sync", {
33775
34559
  method: "POST",
33776
34560
  profileId,
33777
- idempotencyKey: randomUUID2(),
34561
+ idempotencyKey: randomUUID3(),
33778
34562
  headers: { "x-client-runtime-lease": leaseToken },
33779
34563
  body: payload
33780
34564
  });
@@ -33783,7 +34567,7 @@ var init_client = __esm({
33783
34567
  return this.request("/trading/ai/runtime/error", {
33784
34568
  method: "POST",
33785
34569
  profileId,
33786
- idempotencyKey: randomUUID2(),
34570
+ idempotencyKey: randomUUID3(),
33787
34571
  headers: { "x-client-runtime-lease": leaseToken },
33788
34572
  body: payload
33789
34573
  });
@@ -33795,7 +34579,7 @@ var init_client = __esm({
33795
34579
  return this.request("/trading/market-order", {
33796
34580
  method: "POST",
33797
34581
  profileId,
33798
- idempotencyKey: randomUUID2(),
34582
+ idempotencyKey: randomUUID3(),
33799
34583
  body: payload
33800
34584
  });
33801
34585
  }
@@ -33803,7 +34587,7 @@ var init_client = __esm({
33803
34587
  return this.request("/trading/limit-order", {
33804
34588
  method: "POST",
33805
34589
  profileId,
33806
- idempotencyKey: randomUUID2(),
34590
+ idempotencyKey: randomUUID3(),
33807
34591
  body: payload
33808
34592
  });
33809
34593
  }
@@ -33811,7 +34595,7 @@ var init_client = __esm({
33811
34595
  return this.request("/trading/cancel-order", {
33812
34596
  method: "POST",
33813
34597
  profileId,
33814
- idempotencyKey: randomUUID2(),
34598
+ idempotencyKey: randomUUID3(),
33815
34599
  body: payload
33816
34600
  });
33817
34601
  }
@@ -33830,7 +34614,7 @@ var init_client = __esm({
33830
34614
  return this.request("/trading/ai/start", {
33831
34615
  method: "POST",
33832
34616
  profileId,
33833
- idempotencyKey: randomUUID2(),
34617
+ idempotencyKey: randomUUID3(),
33834
34618
  body: payload
33835
34619
  });
33836
34620
  }
@@ -33838,7 +34622,7 @@ var init_client = __esm({
33838
34622
  return this.request("/trading/ai/stop", {
33839
34623
  method: "POST",
33840
34624
  profileId,
33841
- idempotencyKey: randomUUID2(),
34625
+ idempotencyKey: randomUUID3(),
33842
34626
  body: {}
33843
34627
  });
33844
34628
  }
@@ -33846,7 +34630,7 @@ var init_client = __esm({
33846
34630
  return this.request("/trading/ai/assistant/start", {
33847
34631
  method: "POST",
33848
34632
  profileId,
33849
- idempotencyKey: randomUUID2(),
34633
+ idempotencyKey: randomUUID3(),
33850
34634
  body: {}
33851
34635
  });
33852
34636
  }
@@ -33854,7 +34638,7 @@ var init_client = __esm({
33854
34638
  return this.request("/trading/ai/assistant/stop", {
33855
34639
  method: "POST",
33856
34640
  profileId,
33857
- idempotencyKey: randomUUID2(),
34641
+ idempotencyKey: randomUUID3(),
33858
34642
  body: {}
33859
34643
  });
33860
34644
  }
@@ -33862,7 +34646,7 @@ var init_client = __esm({
33862
34646
  return this.request("/trading/ai/runtime/session/start", {
33863
34647
  method: "POST",
33864
34648
  profileId,
33865
- idempotencyKey: randomUUID2(),
34649
+ idempotencyKey: randomUUID3(),
33866
34650
  body: payload
33867
34651
  });
33868
34652
  }
@@ -33873,7 +34657,7 @@ var init_client = __esm({
33873
34657
  return this.request("/trading/ai/runtime/session/stop", {
33874
34658
  method: "POST",
33875
34659
  profileId,
33876
- idempotencyKey: randomUUID2(),
34660
+ idempotencyKey: randomUUID3(),
33877
34661
  body: payload
33878
34662
  });
33879
34663
  }
@@ -33890,7 +34674,7 @@ var init_client = __esm({
33890
34674
  }).request("/trading/ai/runtime/session/stop", {
33891
34675
  method: "POST",
33892
34676
  profileId,
33893
- idempotencyKey: randomUUID2(),
34677
+ idempotencyKey: randomUUID3(),
33894
34678
  body: payload
33895
34679
  });
33896
34680
  }
@@ -33899,7 +34683,7 @@ var init_client = __esm({
33899
34683
  });
33900
34684
 
33901
34685
  // lib/agent-core/headless-runtime.ts
33902
- import { randomUUID as randomUUID3 } from "node:crypto";
34686
+ import { randomUUID as randomUUID4 } from "node:crypto";
33903
34687
  import { setTimeout as sleep } from "node:timers/promises";
33904
34688
  function objectOrNull2(value) {
33905
34689
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -33997,8 +34781,8 @@ async function runAndReportLocalWorkCycle(options, state, leaseToken, context) {
33997
34781
  return Boolean(result2.decision || tradeSync || result2.afterDecision);
33998
34782
  }
33999
34783
  async function startHeadlessRuntime(options) {
34000
- const runtimeSessionId = randomUUID3();
34001
- const deviceId = String(options.deviceId || "").trim() || randomUUID3();
34784
+ const runtimeSessionId = randomUUID4();
34785
+ const deviceId = String(options.deviceId || "").trim() || randomUUID4();
34002
34786
  const startResponse = await options.client.startRuntime(options.profileId, {
34003
34787
  session_id: runtimeSessionId,
34004
34788
  device_id: deviceId,
@@ -50616,7 +51400,7 @@ var headless_local_worker_exports = {};
50616
51400
  __export(headless_local_worker_exports, {
50617
51401
  createHeadlessLocalWorker: () => createHeadlessLocalWorker
50618
51402
  });
50619
- import { randomUUID as randomUUID4 } from "node:crypto";
51403
+ import { randomUUID as randomUUID5 } from "node:crypto";
50620
51404
  function objectOrNull3(value) {
50621
51405
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
50622
51406
  }
@@ -51029,7 +51813,7 @@ function createHeadlessLocalWorker(options) {
51029
51813
  const statusMatch = errorText.match(/\b([45]\d{2})\b/);
51030
51814
  const statusCode = statusMatch ? Number(statusMatch[1]) : null;
51031
51815
  const failedInvocation = normalizeAiInvocationTelemetry({
51032
- client_invocation_id: randomUUID4(),
51816
+ client_invocation_id: randomUUID5(),
51033
51817
  use_case: "trader",
51034
51818
  role: "primary",
51035
51819
  attempt_index: 0,
@@ -51085,7 +51869,7 @@ function createHeadlessLocalWorker(options) {
51085
51869
  billable_cached_input_tokens: normalizedUsage.cached_input_tokens
51086
51870
  };
51087
51871
  const invocation = normalizeAiInvocationTelemetry({
51088
- client_invocation_id: randomUUID4(),
51872
+ client_invocation_id: randomUUID5(),
51089
51873
  use_case: "trader",
51090
51874
  role: "primary",
51091
51875
  attempt_index: 0,
@@ -51290,7 +52074,7 @@ var vtx_exports = {};
51290
52074
  __export(vtx_exports, {
51291
52075
  runVtxCli: () => runVtxCli
51292
52076
  });
51293
- import { randomUUID as randomUUID5 } from "node:crypto";
52077
+ import { randomUUID as randomUUID6 } from "node:crypto";
51294
52078
  import { spawn as spawn7 } from "node:child_process";
51295
52079
  function render2(value, json2) {
51296
52080
  if (json2) {
@@ -51704,8 +52488,8 @@ async function runVtxCli(argv2, env = process.env) {
51704
52488
  });
51705
52489
  return { exitCode: 0, stdout: render2(redactCliOutput(response2), json2), stderr: "" };
51706
52490
  }
51707
- const runtimeSessionId = randomUUID5();
51708
- const deviceId = config2.runtimeDeviceId ?? randomUUID5();
52491
+ const runtimeSessionId = randomUUID6();
52492
+ const deviceId = config2.runtimeDeviceId ?? randomUUID6();
51709
52493
  const response = await client.startRuntime(profileId, {
51710
52494
  session_id: runtimeSessionId,
51711
52495
  device_id: deviceId,