@vtxmacro/cli 2026.8.30 → 2026.8.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +16 -0
  2. package/bin/vtx.js +403 -189
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -71,6 +71,22 @@ vtx inference-host codex-logout
71
71
  vtx inference-host revoke
72
72
  ```
73
73
 
74
+ To connect another Codex subscription to the same VTX account, repeat the
75
+ login and service-install commands with a stable local name such as
76
+ `--instance codex-2`. Each named instance has an isolated VTX grant, Codex
77
+ home, credential file, runtime state, recovery state, and worker; one per-user
78
+ OS supervisor runs all installed instances. Use `service status --json` to
79
+ inspect the installed workers. The installer rejects duplicate authenticated
80
+ ChatGPT emails and fails closed when it cannot prove multiple subscriptions are
81
+ distinct. Use `service uninstall --instance <name>` to remove one worker;
82
+ unqualified `service uninstall` removes the whole supervisor. The legacy
83
+ unqualified host commands continue to target `default`.
84
+
85
+ Automated hosts use three concurrent slots per subscription by default. Set a
86
+ bounded value from 1 through 8 with `--max-concurrency`. Slots share the live
87
+ subscription rate-limit gate and cooldown; this does not guarantee capacity
88
+ beyond the authenticated account's current entitlement.
89
+
74
90
  This durable host uses a separate least-privilege `insights:inference`
75
91
  OAuth grant. It also keeps its ChatGPT subscription login in a dedicated private
76
92
  Codex home. Neither credential is copied from or widens the ordinary CLI or Codex
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.30",
41
+ package_version: "2026.8.31",
42
42
  codex_package_name: "@openai/codex",
43
43
  codex_version: "0.147.0",
44
44
  platforms: {
@@ -16084,13 +16084,26 @@ import { spawn } from "node:child_process";
16084
16084
  import { constants } from "node:fs";
16085
16085
  import { lstat, mkdir, open, readFile, realpath, rename, rm } from "node:fs/promises";
16086
16086
  import { homedir } from "node:os";
16087
- import { dirname, join, relative, resolve } from "node:path";
16087
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
16088
16088
  function inferenceBaseDir(env) {
16089
16089
  const configured = String(env.VTX_INFERENCE_HOST_HOME || "").trim();
16090
16090
  return configured || join(homedir(), ".vtx", "inference-host");
16091
16091
  }
16092
+ function normalizeInferenceHostInstanceName(raw) {
16093
+ const value = raw.trim().toLowerCase();
16094
+ if (!SAFE_INFERENCE_HOST_INSTANCE.test(value)) {
16095
+ throw new Error(
16096
+ "Inference host instance must be 1-48 lowercase letters, numbers, or hyphens, and cannot start or end with a hyphen."
16097
+ );
16098
+ }
16099
+ return value;
16100
+ }
16092
16101
  function resolveInferenceHostConfig(env = process.env) {
16093
16102
  const baseDir = inferenceBaseDir(env);
16103
+ const instanceName = normalizeInferenceHostInstanceName(
16104
+ String(env.VTX_INFERENCE_HOST_INSTANCE || DEFAULT_INFERENCE_HOST_INSTANCE)
16105
+ );
16106
+ const instanceDir = instanceName === DEFAULT_INFERENCE_HOST_INSTANCE ? baseDir : join(baseDir, "instances", instanceName);
16094
16107
  const rawMode = String(env.VTX_INFERENCE_HOST_CREDENTIAL_STORE || "os").trim().toLowerCase();
16095
16108
  if (rawMode !== "os" && rawMode !== "file") {
16096
16109
  throw new Error("VTX_INFERENCE_HOST_CREDENTIAL_STORE must be os or file.");
@@ -16103,15 +16116,36 @@ function resolveInferenceHostConfig(env = process.env) {
16103
16116
  "VTX_INFERENCE_HOST_CREDENTIAL_FILE requires explicit file credential-store mode."
16104
16117
  );
16105
16118
  }
16106
- const credentialFilePath = rawMode === "file" ? explicitCredentialPath || join(baseDir, "credentials.json") : null;
16119
+ const credentialFilePath = rawMode === "file" ? requireNamedInstancePath(
16120
+ instanceName,
16121
+ instanceDir,
16122
+ explicitCredentialPath,
16123
+ "VTX_INFERENCE_HOST_CREDENTIAL_FILE"
16124
+ ) || join(instanceDir, "credentials.json") : null;
16125
+ const explicitStatePath = requireNamedInstancePath(
16126
+ instanceName,
16127
+ instanceDir,
16128
+ String(env.VTX_INFERENCE_HOST_STATE_PATH || "").trim(),
16129
+ "VTX_INFERENCE_HOST_STATE_PATH"
16130
+ );
16131
+ const explicitLockPath = requireNamedInstancePath(
16132
+ instanceName,
16133
+ instanceDir,
16134
+ String(env.VTX_INFERENCE_HOST_LOCK_PATH || "").trim(),
16135
+ "VTX_INFERENCE_HOST_LOCK_PATH"
16136
+ );
16107
16137
  return {
16108
16138
  apiUrl: String(env.VTX_API_URL || "https://api.vtxmacro.com").replace(/\/+$/, ""),
16139
+ baseDir,
16140
+ instanceName,
16109
16141
  credentialStoreMode: rawMode,
16110
16142
  credentialFilePath,
16111
16143
  credentialStoreIdentity: credentialStoreIdentity(rawMode, credentialFilePath, env),
16112
- codexHomePath: join(baseDir, "codex-home"),
16113
- statePath: String(env.VTX_INFERENCE_HOST_STATE_PATH || "").trim() || join(baseDir, "state.json"),
16114
- processLockPath: String(env.VTX_INFERENCE_HOST_LOCK_PATH || "").trim() || join(baseDir, "host.lock")
16144
+ codexHomePath: join(instanceDir, "codex-home"),
16145
+ statePath: explicitStatePath || join(instanceDir, "state.json"),
16146
+ processLockPath: explicitLockPath || join(instanceDir, "host.lock"),
16147
+ supervisorStatePath: join(baseDir, "state.json"),
16148
+ supervisorProcessLockPath: join(baseDir, "host.lock")
16115
16149
  };
16116
16150
  }
16117
16151
  function assertLocalState(value) {
@@ -16488,12 +16522,14 @@ async function acquireInferenceHostProcessLock(path, dependencies = {}) {
16488
16522
  }
16489
16523
  };
16490
16524
  }
16491
- var INFERENCE_CREDENTIAL_NAMESPACE, MAX_INFERENCE_PRIVATE_FILE_BYTES, WINDOWS_PRIVATE_ACL_SCRIPT, windowsPrivateAclInvocation, runWindowsPrivateAcl, windowsAclCache, windowsAclIdentity, aclCacheKey, secureWindowsPrivatePath, invalidateWindowsAclCache, rebindHardenedWindowsAclAfterRename, rebindHardenedWindowsDirectoryAfterOwnedMutation, isDefaultWindowsPrivatePath, secureExistingWindowsPath, linuxProcessIdentity, windowsProcessIdentity, darwinProcessIdentity, runSmallIdentityCommand, cachedSystemBootIdentity, readInferenceSystemBootIdentity, defaultProcessIdentity, defaultCurrentProcessIdentity, processLockObservationCache, inferenceProcessIdentitiesMatch, credentialStoreIdentity;
16525
+ var INFERENCE_CREDENTIAL_NAMESPACE, MAX_INFERENCE_PRIVATE_FILE_BYTES, DEFAULT_CODEX_HOST_CONCURRENCY, MAX_CODEX_HOST_CONCURRENCY, WINDOWS_PRIVATE_ACL_SCRIPT, windowsPrivateAclInvocation, 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;
16492
16526
  var init_config = __esm({
16493
16527
  "lib/inference-host/config.ts"() {
16494
16528
  "use strict";
16495
16529
  INFERENCE_CREDENTIAL_NAMESPACE = "vtxmacro-insights-inference";
16496
16530
  MAX_INFERENCE_PRIVATE_FILE_BYTES = 16 * 1024 * 1024;
16531
+ DEFAULT_CODEX_HOST_CONCURRENCY = 3;
16532
+ MAX_CODEX_HOST_CONCURRENCY = 8;
16497
16533
  WINDOWS_PRIVATE_ACL_SCRIPT = `$ErrorActionPreference='Stop'
16498
16534
  $path=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($env:VTX_PRIVATE_PATH_B64))
16499
16535
  $kind=$env:VTX_PRIVATE_PATH_KIND
@@ -16845,6 +16881,16 @@ if(-not $userFull) { throw 'private ACL does not grant the current user full con
16845
16881
  };
16846
16882
  processLockObservationCache = /* @__PURE__ */ new Map();
16847
16883
  inferenceProcessIdentitiesMatch = (stored, observed) => stored === observed;
16884
+ DEFAULT_INFERENCE_HOST_INSTANCE = "default";
16885
+ SAFE_INFERENCE_HOST_INSTANCE = /^[a-z0-9](?:[a-z0-9-]{0,46}[a-z0-9])?$/u;
16886
+ requireNamedInstancePath = (instanceName, instanceDir, rawPath, label) => {
16887
+ if (!rawPath || instanceName === DEFAULT_INFERENCE_HOST_INSTANCE) return rawPath;
16888
+ const relativePath = relative(resolve(instanceDir), resolve(rawPath));
16889
+ if (relativePath === ".." || relativePath.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute(relativePath)) {
16890
+ throw new Error(`${label} for a named instance must stay inside its isolated instance directory.`);
16891
+ }
16892
+ return rawPath;
16893
+ };
16848
16894
  credentialStoreIdentity = (mode, filePath, env) => {
16849
16895
  if (mode === "file") {
16850
16896
  if (!filePath) throw new Error("Inference credential fallback path is unavailable.");
@@ -19029,7 +19075,7 @@ var init_agent_state = __esm({
19029
19075
  import { spawn as spawn3 } from "node:child_process";
19030
19076
  import { chmod, lstat as lstat2, open as open2, readFile as readFile2, rename as rename2, rm as rm2, writeFile } from "node:fs/promises";
19031
19077
  import { tmpdir } from "node:os";
19032
- import { dirname as dirname2, isAbsolute, join as join2, resolve as resolve2, sep } from "node:path";
19078
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, resolve as resolve2, sep } from "node:path";
19033
19079
  import { createInterface } from "node:readline";
19034
19080
  async function loginCodexSubscription(options) {
19035
19081
  const session = await CodexAppServerSession.start(options);
@@ -19726,7 +19772,10 @@ child.once('close', async () => {
19726
19772
  title: "VTX External Inference Host",
19727
19773
  version: "external_inference.v1"
19728
19774
  },
19729
- capabilities: { experimentalApi: true }
19775
+ capabilities: {
19776
+ experimentalApi: true,
19777
+ optOutNotificationMethods: ["thread/started"]
19778
+ }
19730
19779
  }, {
19731
19780
  timeoutMs: Math.max(1, options.deadlineAtMs - Date.now()),
19732
19781
  signal: options.signal
@@ -20297,7 +20346,7 @@ child.once('close', async () => {
20297
20346
  const resolvedHome = resolve2(this.codexHome);
20298
20347
  const resolvedThreadPath = resolve2(threadPath);
20299
20348
  const resolvedSessionsRoot = join2(resolvedHome, "sessions");
20300
- if (!threadId || !threadPath || !isAbsolute(threadPath) || !resolvedThreadPath.startsWith(`${resolvedSessionsRoot}${sep}`) || !resolvedThreadPath.endsWith(".jsonl") || thread?.ephemeral !== false || thread?.modelProvider !== "openai" || !Array.isArray(thread?.turns) || thread.turns.length !== 0 || result2.model !== options.requestedModel || result2.modelProvider !== "openai" || result2.serviceTier !== "default" || result2.reasoningEffort !== options.requestedReasoningEffort || String(result2.cwd || "") !== options.workspacePath || !Array.isArray(result2.runtimeWorkspaceRoots) || result2.runtimeWorkspaceRoots.length !== 0 || !Array.isArray(result2.instructionSources) || result2.instructionSources.length !== 0 || result2.approvalPolicy !== "never" || result2.approvalsReviewer !== "user" || sandbox?.type !== "readOnly" || sandbox.networkAccess !== false || permission?.id !== CODEX_INFERENCE_PERMISSION_PROFILE || permission.extends !== null) {
20349
+ if (!threadId || !threadPath || !isAbsolute2(threadPath) || !resolvedThreadPath.startsWith(`${resolvedSessionsRoot}${sep}`) || !resolvedThreadPath.endsWith(".jsonl") || thread?.ephemeral !== false || thread?.modelProvider !== "openai" || !Array.isArray(thread?.turns) || thread.turns.length !== 0 || result2.model !== options.requestedModel || result2.modelProvider !== "openai" || result2.serviceTier !== "default" || result2.reasoningEffort !== options.requestedReasoningEffort || String(result2.cwd || "") !== options.workspacePath || !Array.isArray(result2.runtimeWorkspaceRoots) || result2.runtimeWorkspaceRoots.length !== 0 || !Array.isArray(result2.instructionSources) || result2.instructionSources.length !== 0 || result2.approvalPolicy !== "never" || result2.approvalsReviewer !== "user" || sandbox?.type !== "readOnly" || sandbox.networkAccess !== false || permission?.id !== CODEX_INFERENCE_PERMISSION_PROFILE || permission.extends !== null) {
20301
20350
  throw new CodexAppServerError({
20302
20351
  message: "Codex thread isolation or effective settings were invalid.",
20303
20352
  category: "adapter",
@@ -20353,6 +20402,19 @@ child.once('close', async () => {
20353
20402
  });
20354
20403
  const startedAt = Date.now();
20355
20404
  const unsubscribe = this.subscribe(({ method, params }) => {
20405
+ if (method === "thread/started") {
20406
+ const startedThread = objectOrNull(params.thread);
20407
+ if (!String(startedThread?.id || "")) {
20408
+ terminalReject(new CodexAppServerError({
20409
+ message: "Codex returned an invalid thread lifecycle notification.",
20410
+ category: "adapter",
20411
+ code: "invalid_thread_started_notification",
20412
+ retryable: false,
20413
+ dispatchOutcome: requestWritten ? turnId ? "confirmed_dispatched" : "outcome_unknown" : "not_dispatched"
20414
+ }));
20415
+ }
20416
+ return;
20417
+ }
20356
20418
  const eventThreadId = String(params.threadId || "");
20357
20419
  const scopedMethod = method.startsWith("thread/") || method.startsWith("turn/") || method.startsWith("item/") || method.startsWith("rawResponse") || method === "model/rerouted";
20358
20420
  if (scopedMethod && !eventThreadId) {
@@ -20992,7 +21054,7 @@ import {
20992
21054
  } from "node:fs/promises";
20993
21055
  import { randomBytes as randomBytes3 } from "node:crypto";
20994
21056
  import { tmpdir as tmpdir2 } from "node:os";
20995
- import { isAbsolute as isAbsolute2, join as join4, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
21057
+ import { isAbsolute as isAbsolute3, join as join4, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
20996
21058
  var MAX_PROMPT_BYTES2, CODEX_MODEL_NAME_PATTERN, CODEX_REASONING_EFFORT_PATTERN, tokenUsageReceiptSchema, turnResultReceiptSchema, terminalReceiptSchema, recoveryCheckpointSchema, recoveryFileSchema, FileCodexAttemptRecoveryStore, utf8Bytes, assertAttemptActive, tomlString, permissionConfig, ensureDedicatedCodexHome, createIsolatedCodexAttemptResources, createIsolatedCodexHostResources, validateAttemptInput, isStrictDescendant, assertRecoveryResourceScope, removeRecoveredThread, confirmGuardianTerminatedForRecovery, reconcileCodexAttemptRecovery, CodexSubscriptionAdapter;
20997
21059
  var init_codex_adapter = __esm({
20998
21060
  "lib/inference-host/codex-adapter.ts"() {
@@ -21323,7 +21385,7 @@ var init_codex_adapter = __esm({
21323
21385
  const parentPath = resolve3(parent);
21324
21386
  const candidatePath = resolve3(candidate);
21325
21387
  const scoped = relative2(parentPath, candidatePath);
21326
- return scoped.length > 0 && !scoped.startsWith(`..${sep2}`) && scoped !== ".." && !isAbsolute2(scoped);
21388
+ return scoped.length > 0 && !scoped.startsWith(`..${sep2}`) && scoped !== ".." && !isAbsolute3(scoped);
21327
21389
  };
21328
21390
  assertRecoveryResourceScope = (checkpoint) => {
21329
21391
  const temporaryRoot = resolve3(tmpdir2());
@@ -21413,7 +21475,7 @@ var init_codex_adapter = __esm({
21413
21475
  }
21414
21476
  }
21415
21477
  if (checkpoint.threadPath !== null) {
21416
- if (!isAbsolute2(checkpoint.threadPath) || !isStrictDescendant(
21478
+ if (!isAbsolute3(checkpoint.threadPath) || !isStrictDescendant(
21417
21479
  checkpoint.threadPath,
21418
21480
  sessionsRoot
21419
21481
  ) || !checkpoint.threadPath.endsWith(".jsonl")) {
@@ -28992,11 +29054,20 @@ var init_runner = __esm({
28992
29054
  options.codexModelCapabilities,
28993
29055
  options.adapterRuntimeVersion
28994
29056
  ),
28995
- maxConcurrency: finitePositiveOption(
28996
- options.maxConcurrency,
28997
- DEFAULT_MAX_CONCURRENCY,
28998
- "Maximum concurrency"
28999
- ),
29057
+ maxConcurrency: (() => {
29058
+ const value = finitePositiveOption(
29059
+ options.maxConcurrency,
29060
+ DEFAULT_MAX_CONCURRENCY,
29061
+ "Maximum concurrency"
29062
+ );
29063
+ if (value > MAX_CODEX_HOST_CONCURRENCY) {
29064
+ throw new InferenceHostRunnerError(
29065
+ "invalid_configuration",
29066
+ `Maximum concurrency must not exceed ${MAX_CODEX_HOST_CONCURRENCY}.`
29067
+ );
29068
+ }
29069
+ return value;
29070
+ })(),
29000
29071
  advertisementTtlMs,
29001
29072
  advertisementRefreshLeadMs,
29002
29073
  hostHeartbeatMs: finitePositiveOption(
@@ -30335,7 +30406,7 @@ import { createWriteStream, readFileSync } from "node:fs";
30335
30406
  import { access as access3, chmod as chmod3, mkdir as mkdir3, readFile as readFile5, rm as rm4, writeFile as writeFile2 } from "node:fs/promises";
30336
30407
  import { homedir as homedir2 } from "node:os";
30337
30408
  import { dirname as dirname4, join as join5, resolve as resolve4 } from "node:path";
30338
- var SERVICE_NAME, SYSTEMD_UNIT, LAUNCHD_LABEL, SERVICE_COOPERATIVE_STOP_SECONDS, isWindowsSubsystemForLinux, inferenceHostServiceManifestPath, inferenceHostServiceDesiredPath, inferenceHostServiceLogPath, xmlEscape, plistEscape, systemdQuote, defaultRunCommand, managerName, assertManifest, assertDesiredState, readInferenceHostServiceManifest, readInferenceHostServiceDesired, readDesiredAcrossAtomicReplacement, writeDesired, runtimeEnvironment, serviceArguments, windowsOwnedCommandLine, vbScriptString, windowsServiceLauncher, windowsTaskXml, systemdUnit, launchAgentPlist, InferenceHostServiceManager, appendServiceLog, spawnServiceChild, runInferenceHostServiceSupervisor;
30409
+ var SERVICE_NAME, SYSTEMD_UNIT, LAUNCHD_LABEL, SERVICE_COOPERATIVE_STOP_SECONDS, isWindowsSubsystemForLinux, inferenceHostServiceManifestPath, inferenceHostServiceDesiredPath, inferenceHostServiceLogPath, xmlEscape, plistEscape, systemdQuote, defaultRunCommand, managerName, assertRuntimeEnvironment, assertServicePath, assertWorker, assertManifest, assertDesiredState, readInferenceHostServiceManifest, readInferenceHostServiceDesired, readDesiredAcrossAtomicReplacement, writeDesired, runtimeEnvironment, serviceArguments, windowsOwnedCommandLine, vbScriptString, windowsServiceLauncher, windowsTaskXml, systemdUnit, launchAgentPlist, InferenceHostServiceManager, appendServiceLog, spawnServiceChild, runInferenceHostServiceSupervisor;
30339
30410
  var init_service = __esm({
30340
30411
  "lib/inference-host/service.ts"() {
30341
30412
  "use strict";
@@ -30353,9 +30424,9 @@ var init_service = __esm({
30353
30424
  }
30354
30425
  })())
30355
30426
  );
30356
- inferenceHostServiceManifestPath = (config2) => `${config2.statePath}.service.json`;
30357
- inferenceHostServiceDesiredPath = (config2) => `${config2.statePath}.service-desired.json`;
30358
- inferenceHostServiceLogPath = (config2) => `${config2.statePath}.service.log`;
30427
+ inferenceHostServiceManifestPath = (config2) => `${config2.supervisorStatePath}.service.json`;
30428
+ inferenceHostServiceDesiredPath = (config2) => `${config2.supervisorStatePath}.service-desired.json`;
30429
+ inferenceHostServiceLogPath = (config2) => `${config2.supervisorStatePath}.service.log`;
30359
30430
  xmlEscape = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
30360
30431
  plistEscape = xmlEscape;
30361
30432
  systemdQuote = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%")}"`;
@@ -30385,29 +30456,79 @@ var init_service = __esm({
30385
30456
  if (platform === "linux") return "systemd-user";
30386
30457
  throw new Error(`Inference-host background service is unsupported on ${platform}.`);
30387
30458
  };
30459
+ assertRuntimeEnvironment = (value) => {
30460
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
30461
+ throw new Error("Inference-host service runtime environment is invalid.");
30462
+ }
30463
+ for (const [key, entry] of Object.entries(value)) {
30464
+ if (!key.startsWith("VTX_") || typeof entry !== "string") {
30465
+ throw new Error("Inference-host service runtime environment is invalid.");
30466
+ }
30467
+ }
30468
+ return value;
30469
+ };
30470
+ assertServicePath = (value) => {
30471
+ if (typeof value !== "string" || !value || /[\r\n\0]/u.test(value)) {
30472
+ throw new Error("Inference-host service manifest paths contain control characters.");
30473
+ }
30474
+ return value;
30475
+ };
30476
+ assertWorker = (value) => {
30477
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
30478
+ throw new Error("Inference-host service worker is invalid.");
30479
+ }
30480
+ const record2 = value;
30481
+ if (typeof record2.instance_name !== "string" || !/^[a-z0-9](?:[a-z0-9-]{0,46}[a-z0-9])?$/u.test(record2.instance_name) || record2.adapter !== "codex" || typeof record2.display_name !== "string" || !record2.display_name || !Number.isSafeInteger(record2.max_concurrency) || Number(record2.max_concurrency) < 1 || Number(record2.max_concurrency) > MAX_CODEX_HOST_CONCURRENCY || record2.authenticated_account_email !== null && typeof record2.authenticated_account_email !== "string" || record2.authenticated_account_plan !== null && typeof record2.authenticated_account_plan !== "string") {
30482
+ throw new Error("Inference-host service worker is invalid.");
30483
+ }
30484
+ return {
30485
+ instance_name: record2.instance_name,
30486
+ adapter: "codex",
30487
+ display_name: record2.display_name,
30488
+ max_concurrency: Number(record2.max_concurrency),
30489
+ authenticated_account_email: record2.authenticated_account_email,
30490
+ authenticated_account_plan: record2.authenticated_account_plan,
30491
+ runtime_environment: assertRuntimeEnvironment(record2.runtime_environment)
30492
+ };
30493
+ };
30388
30494
  assertManifest = (value) => {
30389
30495
  if (!value || typeof value !== "object" || Array.isArray(value)) {
30390
30496
  throw new Error("Inference-host service manifest is invalid.");
30391
30497
  }
30392
30498
  const record2 = value;
30393
- if (record2.schema_version !== "vtx_inference_service_v1" || record2.adapter !== "codex" || typeof record2.installed_at !== "string" || !Number.isFinite(Date.parse(record2.installed_at)) || typeof record2.executable !== "string" || !record2.executable || typeof record2.script !== "string" || !record2.script || typeof record2.display_name !== "string" || !record2.display_name || typeof record2.log_path !== "string" || !record2.log_path || !record2.runtime_environment || typeof record2.runtime_environment !== "object" || Array.isArray(record2.runtime_environment)) {
30394
- throw new Error("Inference-host service manifest is invalid.");
30499
+ if (record2.schema_version === "vtx_inference_service_v1") {
30500
+ const legacy = record2;
30501
+ if (legacy.adapter !== "codex" || typeof legacy.installed_at !== "string" || !Number.isFinite(Date.parse(legacy.installed_at)) || typeof legacy.executable !== "string" || !legacy.executable || typeof legacy.script !== "string" || !legacy.script || typeof legacy.display_name !== "string" || !legacy.display_name || typeof legacy.log_path !== "string" || !legacy.log_path) throw new Error("Inference-host service manifest is invalid.");
30502
+ const runtime_environment = assertRuntimeEnvironment(legacy.runtime_environment);
30503
+ const worker = assertWorker({
30504
+ instance_name: "default",
30505
+ adapter: "codex",
30506
+ display_name: legacy.display_name,
30507
+ max_concurrency: Number(runtime_environment.VTX_INFERENCE_HOST_MAX_CONCURRENCY || 1),
30508
+ authenticated_account_email: null,
30509
+ authenticated_account_plan: null,
30510
+ runtime_environment
30511
+ });
30512
+ return {
30513
+ schema_version: "vtx_inference_service_v2",
30514
+ installed_at: legacy.installed_at,
30515
+ executable: assertServicePath(legacy.executable),
30516
+ script: assertServicePath(legacy.script),
30517
+ log_path: assertServicePath(legacy.log_path),
30518
+ workers: [worker]
30519
+ };
30395
30520
  }
30396
- for (const value2 of [
30397
- record2.executable,
30398
- record2.script,
30399
- record2.log_path
30400
- ]) {
30401
- if (typeof value2 === "string" && /[\r\n\0]/u.test(value2)) {
30402
- throw new Error("Inference-host service manifest paths contain control characters.");
30403
- }
30521
+ if (record2.schema_version !== "vtx_inference_service_v2" || typeof record2.installed_at !== "string" || !Number.isFinite(Date.parse(record2.installed_at)) || typeof record2.executable !== "string" || !record2.executable || typeof record2.script !== "string" || !record2.script || typeof record2.log_path !== "string" || !record2.log_path || !Array.isArray(record2.workers) || record2.workers.length < 1) {
30522
+ throw new Error("Inference-host service manifest is invalid.");
30404
30523
  }
30405
- for (const [key, entry] of Object.entries(record2.runtime_environment)) {
30406
- if (!key.startsWith("VTX_") || typeof entry !== "string") {
30407
- throw new Error("Inference-host service runtime environment is invalid.");
30408
- }
30524
+ assertServicePath(record2.executable);
30525
+ assertServicePath(record2.script);
30526
+ assertServicePath(record2.log_path);
30527
+ const workers = record2.workers.map(assertWorker);
30528
+ if (new Set(workers.map((worker) => worker.instance_name)).size !== workers.length) {
30529
+ throw new Error("Inference-host service worker names must be unique.");
30409
30530
  }
30410
- return record2;
30531
+ return { ...record2, workers };
30411
30532
  };
30412
30533
  assertDesiredState = (value) => {
30413
30534
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -30459,13 +30580,15 @@ var init_service = __esm({
30459
30580
  }, null, 2)}
30460
30581
  `);
30461
30582
  };
30462
- runtimeEnvironment = (config2) => {
30583
+ runtimeEnvironment = (config2, maxConcurrency) => {
30463
30584
  const result2 = {
30464
30585
  VTX_API_URL: config2.apiUrl,
30465
- VTX_INFERENCE_HOST_HOME: dirname4(config2.codexHomePath),
30586
+ VTX_INFERENCE_HOST_HOME: config2.baseDir,
30587
+ VTX_INFERENCE_HOST_INSTANCE: config2.instanceName,
30466
30588
  VTX_INFERENCE_HOST_CREDENTIAL_STORE: config2.credentialStoreMode,
30467
30589
  VTX_INFERENCE_HOST_STATE_PATH: config2.statePath,
30468
- VTX_INFERENCE_HOST_LOCK_PATH: config2.processLockPath
30590
+ VTX_INFERENCE_HOST_LOCK_PATH: config2.processLockPath,
30591
+ VTX_INFERENCE_HOST_MAX_CONCURRENCY: String(maxConcurrency)
30469
30592
  };
30470
30593
  if (config2.credentialFilePath) {
30471
30594
  result2.VTX_INFERENCE_HOST_CREDENTIAL_FILE = config2.credentialFilePath;
@@ -30581,7 +30704,7 @@ WantedBy=default.target
30581
30704
  return inferenceHostServiceLogPath(this.config);
30582
30705
  }
30583
30706
  controlLockPath() {
30584
- return `${this.config.processLockPath}.service-control`;
30707
+ return `${this.config.supervisorProcessLockPath}.service-control`;
30585
30708
  }
30586
30709
  async withControlLock(operation) {
30587
30710
  let lock2 = null;
@@ -30617,12 +30740,12 @@ WantedBy=default.target
30617
30740
  ];
30618
30741
  }
30619
30742
  definitionPath() {
30620
- if (this.platform === "win32") return `${this.config.statePath}.service-task.xml`;
30743
+ if (this.platform === "win32") return `${this.config.supervisorStatePath}.service-task.xml`;
30621
30744
  if (this.platform === "darwin") return join5(this.home, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
30622
30745
  return join5(this.home, ".config", "systemd", "user", SYSTEMD_UNIT);
30623
30746
  }
30624
30747
  windowsLauncherPath() {
30625
- return `${this.config.statePath}.service-launcher.vbs`;
30748
+ return `${this.config.supervisorStatePath}.service-launcher.vbs`;
30626
30749
  }
30627
30750
  async managerCommand(action) {
30628
30751
  const definition = this.definitionPath();
@@ -30665,37 +30788,19 @@ WantedBy=default.target
30665
30788
  }
30666
30789
  throw new Error("Background service did not reach an active state within 10 seconds.");
30667
30790
  }
30668
- async install(options) {
30669
- return await this.withControlLock(async () => await this.installUnlocked(options));
30670
- }
30671
- async installUnlocked(options) {
30672
- if (await readInferenceHostServiceManifest(this.manifestPath())) {
30673
- await this.uninstallUnlocked();
30674
- }
30675
- await access3(this.executable);
30676
- await access3(this.script);
30677
- const manifest = assertManifest({
30678
- schema_version: "vtx_inference_service_v1",
30679
- installed_at: this.now().toISOString(),
30680
- adapter: options.adapter,
30681
- executable: this.executable,
30682
- script: this.script,
30683
- display_name: options.displayName,
30684
- log_path: this.logPath(),
30685
- runtime_environment: runtimeEnvironment(this.config)
30686
- });
30687
- const args = serviceArguments(this.script, this.manifestPath());
30688
- const definition = this.platform === "win32" ? windowsTaskXml(this.windowsLauncherPath(), this.windowsDirectory, this.username) : this.platform === "darwin" ? launchAgentPlist(this.executable, args, this.logPath()) : systemdUnit(this.executable, args);
30791
+ async registerManifestUnlocked(manifest, desiredRunning) {
30792
+ const args = serviceArguments(manifest.script, this.manifestPath());
30793
+ 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);
30689
30794
  let managerInstallAttempted = false;
30690
30795
  try {
30691
30796
  await writeAtomicInferencePrivateFile(this.manifestPath(), `${JSON.stringify(manifest, null, 2)}
30692
30797
  `);
30693
- await writeDesired(this.desiredPath(), options.startImmediately !== false, this.now());
30798
+ await writeDesired(this.desiredPath(), desiredRunning, this.now());
30694
30799
  await mkdir3(dirname4(this.definitionPath()), { recursive: true, mode: 448 });
30695
30800
  if (this.platform === "win32") {
30696
30801
  await writeAtomicInferencePrivateFile(
30697
30802
  this.windowsLauncherPath(),
30698
- windowsServiceLauncher(this.executable, args)
30803
+ windowsServiceLauncher(manifest.executable, args)
30699
30804
  );
30700
30805
  await writeFile2(this.definitionPath(), `\uFEFF${definition}`, {
30701
30806
  encoding: "utf16le",
@@ -30712,7 +30817,7 @@ WantedBy=default.target
30712
30817
  managerInstallAttempted = true;
30713
30818
  const installed = await this.managerCommand("install");
30714
30819
  if (installed.exitCode !== 0) throw new Error(`Background service installation failed: ${installed.stderr.trim()}`);
30715
- if (options.startImmediately !== false) {
30820
+ if (desiredRunning) {
30716
30821
  const started = await this.managerCommand("start");
30717
30822
  if (started.exitCode !== 0 && !/already running|in progress/iu.test(`${started.stdout}
30718
30823
  ${started.stderr}`)) {
@@ -30720,6 +30825,7 @@ ${started.stderr}`)) {
30720
30825
  }
30721
30826
  await this.waitForManagerActive();
30722
30827
  }
30828
+ return await this.status();
30723
30829
  } catch (error48) {
30724
30830
  await writeDesired(this.desiredPath(), false, this.now()).catch(() => void 0);
30725
30831
  if (managerInstallAttempted) {
@@ -30747,7 +30853,83 @@ ${cleanup.stderr}`)) {
30747
30853
  }
30748
30854
  throw error48;
30749
30855
  }
30750
- return await this.status();
30856
+ }
30857
+ async replaceManifestUnlocked(next, desiredRunning) {
30858
+ const previous = await readInferenceHostServiceManifest(this.manifestPath());
30859
+ const previousDesired = previous ? await readInferenceHostServiceDesired(this.desiredPath()) : false;
30860
+ if (previous) await this.uninstallUnlocked();
30861
+ try {
30862
+ return await this.registerManifestUnlocked(next, desiredRunning);
30863
+ } catch (error48) {
30864
+ if (!previous) throw error48;
30865
+ try {
30866
+ await this.registerManifestUnlocked(previous, previousDesired);
30867
+ } catch (rollbackError) {
30868
+ throw new Error(
30869
+ "Inference-host service reconfiguration failed and the previous supervisor could not be restored.",
30870
+ { cause: new AggregateError([error48, rollbackError]) }
30871
+ );
30872
+ }
30873
+ throw new Error(
30874
+ "Inference-host service reconfiguration failed; the previous supervisor was restored.",
30875
+ { cause: error48 }
30876
+ );
30877
+ }
30878
+ }
30879
+ async install(options) {
30880
+ return await this.withControlLock(async () => await this.installUnlocked(options));
30881
+ }
30882
+ async installUnlocked(options) {
30883
+ const existing = await readInferenceHostServiceManifest(this.manifestPath());
30884
+ const normalizedEmail = options.authenticatedAccountEmail?.trim().toLowerCase() || null;
30885
+ const otherWorkers = existing?.workers.filter(
30886
+ (worker) => worker.instance_name !== this.config.instanceName
30887
+ ) ?? [];
30888
+ if (otherWorkers.length > 0 && (!normalizedEmail || otherWorkers.some((worker) => !worker.authenticated_account_email))) {
30889
+ throw new Error(
30890
+ "Cannot prove that every installed instance uses a distinct ChatGPT subscription. Reinstall instances with live account identity before adding another."
30891
+ );
30892
+ }
30893
+ if (normalizedEmail && otherWorkers.some((worker) => worker.authenticated_account_email?.trim().toLowerCase() === normalizedEmail)) {
30894
+ throw new Error(
30895
+ "This ChatGPT subscription is already installed under another inference-host instance."
30896
+ );
30897
+ }
30898
+ if (this.config.credentialFilePath && otherWorkers.some((worker) => {
30899
+ const otherPath = worker.runtime_environment.VTX_INFERENCE_HOST_CREDENTIAL_FILE;
30900
+ return otherPath && resolve4(otherPath) === resolve4(this.config.credentialFilePath);
30901
+ })) {
30902
+ throw new Error(
30903
+ "Each inference-host instance must use a different private credential file."
30904
+ );
30905
+ }
30906
+ await access3(this.executable);
30907
+ await access3(this.script);
30908
+ const maxConcurrency = options.maxConcurrency ?? 1;
30909
+ const workers = [
30910
+ ...existing?.workers.filter((worker) => worker.instance_name !== this.config.instanceName) ?? [],
30911
+ {
30912
+ instance_name: this.config.instanceName,
30913
+ adapter: options.adapter,
30914
+ display_name: options.displayName,
30915
+ max_concurrency: maxConcurrency,
30916
+ authenticated_account_email: normalizedEmail,
30917
+ authenticated_account_plan: options.authenticatedAccountPlan ?? null,
30918
+ runtime_environment: runtimeEnvironment(this.config, maxConcurrency)
30919
+ }
30920
+ ].sort((left, right) => left.instance_name.localeCompare(right.instance_name));
30921
+ const manifest = assertManifest({
30922
+ schema_version: "vtx_inference_service_v2",
30923
+ installed_at: this.now().toISOString(),
30924
+ executable: this.executable,
30925
+ script: this.script,
30926
+ log_path: this.logPath(),
30927
+ workers
30928
+ });
30929
+ return await this.replaceManifestUnlocked(
30930
+ manifest,
30931
+ options.startImmediately !== false
30932
+ );
30751
30933
  }
30752
30934
  async start() {
30753
30935
  return await this.withControlLock(async () => await this.startUnlocked());
@@ -30774,11 +30956,12 @@ ${result2.stderr}`)) {
30774
30956
  return await this.withControlLock(async () => await this.stopUnlocked());
30775
30957
  }
30776
30958
  async stopUnlocked() {
30777
- if (!await readInferenceHostServiceManifest(this.manifestPath())) {
30959
+ const manifest = await readInferenceHostServiceManifest(this.manifestPath());
30960
+ if (!manifest) {
30778
30961
  throw new Error("Inference-host service is not installed.");
30779
30962
  }
30780
30963
  await writeDesired(this.desiredPath(), false, this.now());
30781
- const serviceLockPath = `${this.config.processLockPath}.service`;
30964
+ const serviceLockPath = `${this.config.supervisorProcessLockPath}.service`;
30782
30965
  let serviceReleased = false;
30783
30966
  for (let attempt = 0; attempt < this.stopWaitAttempts; attempt += 1) {
30784
30967
  try {
@@ -30796,7 +30979,7 @@ ${result2.stderr}`)) {
30796
30979
  }
30797
30980
  if (!serviceReleased) {
30798
30981
  throw new Error(
30799
- `Inference-host worker did not stop cooperatively within ${SERVICE_COOPERATIVE_STOP_SECONDS} seconds; refusing forced termination while cleanup may be pending.`
30982
+ `Inference-host workers did not stop cooperatively within ${SERVICE_COOPERATIVE_STOP_SECONDS} seconds; refusing forced termination while cleanup may be pending.`
30800
30983
  );
30801
30984
  }
30802
30985
  const result2 = await this.managerCommand("stop");
@@ -30822,8 +31005,15 @@ ${result2.stderr}`)) {
30822
31005
  manager_active: managerActive,
30823
31006
  manager: managerName(this.platform),
30824
31007
  manager_state: result2.exitCode === 0 ? output3 || "installed" : "not-installed",
30825
- adapter: manifest?.adapter ?? null,
30826
- log_path: manifest?.log_path ?? this.logPath()
31008
+ adapter: manifest ? "codex" : null,
31009
+ log_path: manifest?.log_path ?? this.logPath(),
31010
+ workers: manifest?.workers.map((worker) => ({
31011
+ instance_name: worker.instance_name,
31012
+ display_name: worker.display_name,
31013
+ max_concurrency: worker.max_concurrency,
31014
+ authenticated_account_email: worker.authenticated_account_email,
31015
+ authenticated_account_plan: worker.authenticated_account_plan
31016
+ })) ?? []
30827
31017
  };
30828
31018
  }
30829
31019
  async logs(lines = 100) {
@@ -30838,6 +31028,24 @@ ${result2.stderr}`)) {
30838
31028
  throw error48;
30839
31029
  }
30840
31030
  }
31031
+ async uninstallInstance(instanceName) {
31032
+ return await this.withControlLock(async () => {
31033
+ const manifest = await readInferenceHostServiceManifest(this.manifestPath());
31034
+ if (!manifest) throw new Error("Inference-host service is not installed.");
31035
+ if (!manifest.workers.some((worker) => worker.instance_name === instanceName)) {
31036
+ throw new Error(`Inference-host instance ${instanceName} is not installed.`);
31037
+ }
31038
+ const remaining = manifest.workers.filter(
31039
+ (worker) => worker.instance_name !== instanceName
31040
+ );
31041
+ if (remaining.length === 0) return await this.uninstallUnlocked();
31042
+ return await this.replaceManifestUnlocked({
31043
+ ...manifest,
31044
+ installed_at: this.now().toISOString(),
31045
+ workers: remaining
31046
+ }, await readInferenceHostServiceDesired(this.desiredPath()));
31047
+ });
31048
+ }
30841
31049
  async uninstall() {
30842
31050
  return await this.withControlLock(async () => await this.uninstallUnlocked());
30843
31051
  }
@@ -30867,7 +31075,8 @@ ${result2.stderr}`)) {
30867
31075
  manager: managerName(this.platform),
30868
31076
  manager_state: "not-installed",
30869
31077
  adapter: null,
30870
- log_path: manifest.log_path
31078
+ log_path: manifest.log_path,
31079
+ workers: []
30871
31080
  };
30872
31081
  }
30873
31082
  };
@@ -30880,21 +31089,21 @@ ${result2.stderr}`)) {
30880
31089
  `, resolvePromise);
30881
31090
  });
30882
31091
  };
30883
- spawnServiceChild = async (manifest, signal) => {
31092
+ spawnServiceChild = async (manifest, worker, signal) => {
30884
31093
  const args = [
30885
31094
  manifest.script,
30886
31095
  "inference-host",
30887
31096
  "run",
30888
31097
  "--json",
30889
31098
  "--display-name",
30890
- manifest.display_name
31099
+ worker.display_name
30891
31100
  ];
30892
31101
  const startedAt = Date.now();
30893
31102
  const log = createWriteStream(manifest.log_path, { flags: "a", mode: 384 });
30894
31103
  return await new Promise((resolvePromise, reject) => {
30895
31104
  let stdout = "";
30896
31105
  const child = spawn5(manifest.executable, args, {
30897
- env: { ...process.env, ...manifest.runtime_environment },
31106
+ env: { ...process.env, ...worker.runtime_environment },
30898
31107
  windowsHide: true,
30899
31108
  stdio: ["ignore", "pipe", "pipe"]
30900
31109
  });
@@ -30938,64 +31147,72 @@ ${result2.stderr}`)) {
30938
31147
  runInferenceHostServiceSupervisor = async (manifestPath, options = {}) => {
30939
31148
  const manifest = await readInferenceHostServiceManifest(manifestPath);
30940
31149
  if (!manifest) throw new Error("Inference-host service manifest is missing.");
30941
- const desiredPath = `${manifest.runtime_environment.VTX_INFERENCE_HOST_STATE_PATH}.service-desired.json`;
31150
+ const desiredPath = manifestPath.replace(/\.service\.json$/u, ".service-desired.json");
30942
31151
  const signal = options.signal ?? new AbortController().signal;
30943
31152
  const sleep4 = options.sleep ?? (async (milliseconds) => {
30944
31153
  await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
30945
31154
  });
30946
31155
  const launch = options.runWorker ?? options.spawnChild ?? spawnServiceChild;
30947
- let failures = 0;
30948
- await appendServiceLog(manifest.log_path, "service_supervisor_started", { adapter: manifest.adapter });
30949
- while (!signal.aborted && await readDesiredAcrossAtomicReplacement(desiredPath)) {
30950
- try {
30951
- const workerController = new AbortController();
30952
- const forwardAbort = () => workerController.abort();
30953
- signal.addEventListener("abort", forwardAbort, { once: true });
30954
- let workerComplete = false;
30955
- let monitorError = null;
30956
- const desiredMonitor = (async () => {
30957
- while (!workerComplete && !workerController.signal.aborted) {
30958
- await sleep4(500);
30959
- if (!await readDesiredAcrossAtomicReplacement(desiredPath)) {
30960
- workerController.abort();
30961
- break;
30962
- }
30963
- }
30964
- })().catch((error48) => {
30965
- monitorError = error48;
30966
- workerController.abort();
30967
- });
30968
- let result2;
31156
+ await appendServiceLog(manifest.log_path, "service_supervisor_started", {
31157
+ adapter: "codex",
31158
+ instances: manifest.workers.map((worker) => worker.instance_name)
31159
+ });
31160
+ const superviseWorker = async (worker) => {
31161
+ let failures = 0;
31162
+ while (!signal.aborted && await readDesiredAcrossAtomicReplacement(desiredPath)) {
30969
31163
  try {
30970
- result2 = await launch(manifest, workerController.signal);
30971
- } finally {
30972
- workerComplete = true;
30973
- workerController.abort();
30974
- signal.removeEventListener("abort", forwardAbort);
30975
- await desiredMonitor;
30976
- }
30977
- if (monitorError) throw monitorError;
30978
- if (signal.aborted || !await readDesiredAcrossAtomicReplacement(desiredPath)) break;
30979
- failures = result2.uptimeMs >= 6e4 ? 0 : failures + 1;
30980
- const retryAfterMs = Math.min(1e3 * 2 ** Math.min(failures, 5), 3e4);
30981
- await appendServiceLog(manifest.log_path, "worker_exited", {
30982
- exit_code: result2.exitCode,
30983
- uptime_ms: result2.uptimeMs,
30984
- drain_reason: result2.drainReason ?? null,
30985
- retry_after_ms: retryAfterMs
30986
- });
30987
- await sleep4(retryAfterMs);
30988
- } catch (error48) {
30989
- if (signal.aborted) break;
30990
- failures += 1;
30991
- const retryAfterMs = Math.min(1e3 * 2 ** Math.min(failures, 5), 3e4);
30992
- await appendServiceLog(manifest.log_path, "worker_launch_failed", {
30993
- error: error48 instanceof Error ? error48.message : "unknown",
30994
- retry_after_ms: retryAfterMs
30995
- });
30996
- await sleep4(retryAfterMs);
31164
+ const workerController = new AbortController();
31165
+ const forwardAbort = () => workerController.abort();
31166
+ signal.addEventListener("abort", forwardAbort, { once: true });
31167
+ let workerComplete = false;
31168
+ let monitorError = null;
31169
+ const desiredMonitor = (async () => {
31170
+ while (!workerComplete && !workerController.signal.aborted) {
31171
+ await sleep4(500);
31172
+ if (!await readDesiredAcrossAtomicReplacement(desiredPath)) {
31173
+ workerController.abort();
31174
+ break;
31175
+ }
31176
+ }
31177
+ })().catch((error48) => {
31178
+ monitorError = error48;
31179
+ workerController.abort();
31180
+ });
31181
+ let result2;
31182
+ try {
31183
+ result2 = await launch(manifest, worker, workerController.signal);
31184
+ } finally {
31185
+ workerComplete = true;
31186
+ workerController.abort();
31187
+ signal.removeEventListener("abort", forwardAbort);
31188
+ await desiredMonitor;
31189
+ }
31190
+ if (monitorError) throw monitorError;
31191
+ if (signal.aborted || !await readDesiredAcrossAtomicReplacement(desiredPath)) break;
31192
+ failures = result2.uptimeMs >= 6e4 ? 0 : failures + 1;
31193
+ const retryAfterMs = Math.min(1e3 * 2 ** Math.min(failures, 5), 3e4);
31194
+ await appendServiceLog(manifest.log_path, "worker_exited", {
31195
+ instance_name: worker.instance_name,
31196
+ exit_code: result2.exitCode,
31197
+ uptime_ms: result2.uptimeMs,
31198
+ drain_reason: result2.drainReason ?? null,
31199
+ retry_after_ms: retryAfterMs
31200
+ });
31201
+ await sleep4(retryAfterMs);
31202
+ } catch (error48) {
31203
+ if (signal.aborted) break;
31204
+ failures += 1;
31205
+ const retryAfterMs = Math.min(1e3 * 2 ** Math.min(failures, 5), 3e4);
31206
+ await appendServiceLog(manifest.log_path, "worker_launch_failed", {
31207
+ instance_name: worker.instance_name,
31208
+ error: error48 instanceof Error ? error48.message : "unknown",
31209
+ retry_after_ms: retryAfterMs
31210
+ });
31211
+ await sleep4(retryAfterMs);
31212
+ }
30997
31213
  }
30998
- }
31214
+ };
31215
+ await Promise.all(manifest.workers.map(superviseWorker));
30999
31216
  await appendServiceLog(manifest.log_path, "service_supervisor_stopped");
31000
31217
  };
31001
31218
  }
@@ -31085,7 +31302,7 @@ async function runInferenceHostCli(argv2, env = process.env, dependencies = {})
31085
31302
  };
31086
31303
  }
31087
31304
  }
31088
- var INFERENCE_HOST_CLI_VERSION, runtimeReceiptPath, codexRecoveryPath, codexGuardianReceiptRoot, revocationCheckpointPath, foregroundHostLockPath, AGENT_HEARTBEAT_INTERVAL_MS, AGENT_HEARTBEAT_MAX_RETRY_DELAY_MS, retryableAgentHeartbeatError, assertRevocationCheckpoint, readRevocationCheckpoint, writeRevocationCheckpoint, clearRevocationCheckpoint, render, INFERENCE_HOST_HELP, parsePositiveInteger, parseInferenceHostArgs, defaultOpenBrowser, defaultRegisterLifecycleSignalHandlers, lifecycleCancellation, configuredCredentialStore, accountKeyForState, assertCredentialMatchesState2, revocationCheckpointForCredential, assertRevocationCheckpointMatchesLocalIdentity, fileExistsPrivately, inspectCodexAuthentication, clearLocalRuntimeArtifacts, assertDurableServiceUninstalled, assertLocalRuntimeArtifactsMayBeDiscarded, defaultRunForeground, login, codexLogin, codexLogout, localStatus, doctor, cleanupLogin, runHost, defaultReadStdin, parseAgentStdin, agentOperationId, agentSession, agentConnect, agentRun, agentNext, agentComplete, agentFail, withAgentCommandLock, serviceCommand, hasExplicitCredentialStoreConfiguration, commandUsesInstalledServiceCredentials, resolveInferenceHostCommandConfig;
31305
+ 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;
31089
31306
  var init_cli = __esm({
31090
31307
  "lib/inference-host/cli.ts"() {
31091
31308
  "use strict";
@@ -31189,6 +31406,8 @@ Commands:
31189
31406
 
31190
31407
  Common options:
31191
31408
  --json Emit machine-readable JSON
31409
+ --instance NAME Target an isolated local subscription instance (default: default)
31410
+ --max-concurrency N Automated Codex slots for this subscription (1-8; default: 3)
31192
31411
  --help, -h Show this help
31193
31412
 
31194
31413
  If the OS credential store cannot retain the VTX grant, set
@@ -31196,23 +31415,31 @@ VTX_INFERENCE_HOST_CREDENTIAL_STORE=file before login to use the supported
31196
31415
  private-file fallback. See https://vtxmacro.com/insights#subscription-inference.
31197
31416
 
31198
31417
  Durable service:
31199
- vtx inference-host service install
31418
+ vtx inference-host login --instance codex-1
31419
+ vtx inference-host codex-login --instance codex-1
31420
+ vtx inference-host service install --instance codex-1
31421
+ vtx inference-host login --instance codex-2
31422
+ vtx inference-host codex-login --instance codex-2
31423
+ vtx inference-host service install --instance codex-2
31424
+ vtx inference-host service uninstall --instance codex-2
31200
31425
  vtx inference-host service <start|stop|status|logs|uninstall>
31201
31426
  `;
31202
- parsePositiveInteger = (raw, label) => {
31427
+ parseHostConcurrency = (raw, label) => {
31203
31428
  const value = Number(raw);
31204
- if (value !== 1) {
31205
- throw new Error(`${label} must be exactly 1 for the subscription-backed Codex host.`);
31429
+ if (!Number.isSafeInteger(value) || value < 1 || value > MAX_CODEX_HOST_CONCURRENCY) {
31430
+ throw new Error(`${label} must be an integer from 1 through ${MAX_CODEX_HOST_CONCURRENCY}.`);
31206
31431
  }
31207
31432
  return value;
31208
31433
  };
31209
31434
  parseInferenceHostArgs = (argv2, env) => {
31210
31435
  let json2 = String(env.VTX_OUTPUT_JSON || "").trim().toLowerCase() === "true";
31211
31436
  let once = false;
31212
- let maxConcurrency = parsePositiveInteger(
31213
- String(env.VTX_INFERENCE_HOST_MAX_CONCURRENCY || "1"),
31437
+ let maxConcurrency = parseHostConcurrency(
31438
+ String(env.VTX_INFERENCE_HOST_MAX_CONCURRENCY || DEFAULT_CODEX_HOST_CONCURRENCY),
31214
31439
  "VTX_INFERENCE_HOST_MAX_CONCURRENCY"
31215
31440
  );
31441
+ let instanceName = String(env.VTX_INFERENCE_HOST_INSTANCE || "default").trim();
31442
+ let instanceExplicit = Boolean(String(env.VTX_INFERENCE_HOST_INSTANCE || "").trim());
31216
31443
  let displayName = String(env.VTX_INFERENCE_HOST_DISPLAY_NAME || "").trim() || "Codex subscription host";
31217
31444
  let displayNameExplicit = Boolean(String(env.VTX_INFERENCE_HOST_DISPLAY_NAME || "").trim());
31218
31445
  let adapter = null;
@@ -31236,7 +31463,7 @@ Durable service:
31236
31463
  if (argument === "--max-concurrency") {
31237
31464
  const raw = argv2[index + 1];
31238
31465
  if (!raw) throw new Error("--max-concurrency requires a value.");
31239
- maxConcurrency = parsePositiveInteger(raw, "--max-concurrency");
31466
+ maxConcurrency = parseHostConcurrency(raw, "--max-concurrency");
31240
31467
  index += 1;
31241
31468
  continue;
31242
31469
  }
@@ -31248,7 +31475,7 @@ Durable service:
31248
31475
  index += 1;
31249
31476
  continue;
31250
31477
  }
31251
- if (["--adapter", "--model", "--model-label", "--effort", "--wait-seconds", "--lines", "--service-manifest"].includes(argument)) {
31478
+ if (["--adapter", "--model", "--model-label", "--effort", "--wait-seconds", "--lines", "--service-manifest", "--instance"].includes(argument)) {
31252
31479
  const raw = argv2[index + 1];
31253
31480
  if (!raw?.trim()) throw new Error(`${argument} requires a value.`);
31254
31481
  if (argument === "--adapter") adapter = raw.trim();
@@ -31268,6 +31495,10 @@ Durable service:
31268
31495
  }
31269
31496
  }
31270
31497
  if (argument === "--service-manifest") serviceManifestPath = resolve5(raw.trim());
31498
+ if (argument === "--instance") {
31499
+ instanceName = raw.trim();
31500
+ instanceExplicit = true;
31501
+ }
31271
31502
  index += 1;
31272
31503
  continue;
31273
31504
  }
@@ -31295,6 +31526,8 @@ Durable service:
31295
31526
  json: json2,
31296
31527
  once,
31297
31528
  maxConcurrency,
31529
+ instanceName,
31530
+ instanceExplicit,
31298
31531
  displayName,
31299
31532
  adapter,
31300
31533
  modelId,
@@ -31406,9 +31639,10 @@ Durable service:
31406
31639
  await clearInferenceHostLocalState(config2.statePath);
31407
31640
  };
31408
31641
  assertDurableServiceUninstalled = async (config2) => {
31409
- if (await readInferenceHostServiceManifest(`${config2.statePath}.service.json`)) {
31642
+ const manifest = await readInferenceHostServiceManifest(`${config2.supervisorStatePath}.service.json`);
31643
+ if (manifest?.workers.some((worker) => worker.instance_name === config2.instanceName)) {
31410
31644
  throw new Error(
31411
- "Uninstall the durable inference-host service before logout, revoke, or Codex logout."
31645
+ `Uninstall the durable inference-host service before changing credentials for instance ${config2.instanceName}.`
31412
31646
  );
31413
31647
  }
31414
31648
  };
@@ -32436,55 +32670,19 @@ Waiting for approval...
32436
32670
  if (!parsed.serviceManifestPath) throw new Error("Internal service manifest path is required.");
32437
32671
  const serviceManifest = await readInferenceHostServiceManifest(parsed.serviceManifestPath);
32438
32672
  if (!serviceManifest) throw new Error("Inference-host service manifest is missing.");
32439
- const serviceConfig = resolveInferenceHostConfig({
32673
+ const supervisorConfig = resolveInferenceHostConfig({
32440
32674
  ...env,
32441
- ...serviceManifest.runtime_environment
32675
+ ...serviceManifest.workers[0].runtime_environment
32442
32676
  });
32443
32677
  const serviceLock = await acquireInferenceHostProcessLock(
32444
- `${serviceConfig.processLockPath}.service`
32678
+ `${supervisorConfig.supervisorProcessLockPath}.service`
32445
32679
  );
32446
32680
  const cancellation = lifecycleCancellation(dependencies);
32447
32681
  try {
32448
32682
  await (dependencies.runServiceSupervisor ?? runInferenceHostServiceSupervisor)(
32449
32683
  parsed.serviceManifestPath,
32450
32684
  {
32451
- signal: cancellation.signal,
32452
- runWorker: async (manifest, signal) => {
32453
- const startedAt = Date.now();
32454
- const serviceEnv = { ...env, ...manifest.runtime_environment };
32455
- const serviceConfig2 = resolveInferenceHostConfig(serviceEnv);
32456
- const unregister = (abort) => {
32457
- const onAbort = () => abort("SIGTERM");
32458
- signal.addEventListener("abort", onAbort, { once: true });
32459
- return () => signal.removeEventListener("abort", onAbort);
32460
- };
32461
- const result2 = await runHost(serviceConfig2, {
32462
- ...parsed,
32463
- command: "run",
32464
- serviceAction: null,
32465
- serviceManifestPath: null,
32466
- displayName: manifest.display_name,
32467
- once: false
32468
- }, serviceEnv, {
32469
- ...dependencies,
32470
- registerLifecycleSignalHandlers: unregister
32471
- }, warnings);
32472
- let drainReason = null;
32473
- try {
32474
- const summary = JSON.parse(result2.stdout);
32475
- if (summary && typeof summary === "object" && !Array.isArray(summary) && typeof summary.drain_reason === "string" && /^[a-z0-9_]{1,96}$/u.test(
32476
- summary.drain_reason
32477
- )) {
32478
- drainReason = summary.drain_reason;
32479
- }
32480
- } catch {
32481
- }
32482
- return {
32483
- exitCode: result2.exitCode,
32484
- uptimeMs: Date.now() - startedAt,
32485
- drainReason
32486
- };
32487
- }
32685
+ signal: cancellation.signal
32488
32686
  }
32489
32687
  );
32490
32688
  return { exitCode: 0, stdout: "", stderr: "" };
@@ -32518,12 +32716,18 @@ Waiting for approval...
32518
32716
  throw new Error("Run vtx inference-host codex-login before installing the automated Codex service.");
32519
32717
  }
32520
32718
  const binary = await (dependencies.resolveBinary ?? resolvePinnedCodexBinary)(env);
32521
- await (dependencies.preflightCodex ?? preflightCodexSubscription)({
32719
+ const preflight = await (dependencies.preflightCodex ?? preflightCodexSubscription)({
32522
32720
  binary,
32523
32721
  codexHome: config2.codexHomePath,
32524
32722
  deadlineAtMs: Date.now() + 3e4
32525
32723
  });
32526
- const status = await manager.install({ adapter: "codex", displayName: parsed.displayName });
32724
+ const status = await manager.install({
32725
+ adapter: "codex",
32726
+ displayName: parsed.displayName,
32727
+ maxConcurrency: parsed.maxConcurrency,
32728
+ authenticatedAccountEmail: preflight?.authenticated_account_email ?? null,
32729
+ authenticatedAccountPlan: preflight?.authenticated_account_plan ?? null
32730
+ });
32527
32731
  return {
32528
32732
  exitCode: 0,
32529
32733
  stdout: render({ status: "service_installed", ...status }, parsed.json),
@@ -32549,7 +32753,8 @@ Waiting for approval...
32549
32753
  return { exitCode: lifecycleMatches ? 0 : 1, stdout: render(status, parsed.json), stderr: "" };
32550
32754
  }
32551
32755
  if (action === "uninstall") {
32552
- return { exitCode: 0, stdout: render({ status: "service_uninstalled", ...await manager.uninstall() }, parsed.json), stderr: "" };
32756
+ const status = parsed.instanceExplicit ? await manager.uninstallInstance(config2.instanceName) : await manager.uninstall();
32757
+ return { exitCode: 0, stdout: render({ status: "service_uninstalled", ...status }, parsed.json), stderr: "" };
32553
32758
  }
32554
32759
  throw new Error("Usage: vtx inference-host service <install|start|stop|status|logs|uninstall>.");
32555
32760
  };
@@ -32558,16 +32763,20 @@ Waiting for approval...
32558
32763
  );
32559
32764
  commandUsesInstalledServiceCredentials = (parsed) => parsed.command === "status" || parsed.command === "doctor" || parsed.command === "service" && parsed.serviceAction === "install";
32560
32765
  resolveInferenceHostCommandConfig = async (parsed, env) => {
32561
- const baseConfig = resolveInferenceHostConfig(env);
32766
+ const selectedEnv = {
32767
+ ...env,
32768
+ VTX_INFERENCE_HOST_INSTANCE: parsed.instanceName
32769
+ };
32770
+ const baseConfig = resolveInferenceHostConfig(selectedEnv);
32562
32771
  if (!commandUsesInstalledServiceCredentials(parsed) || hasExplicitCredentialStoreConfiguration(env)) {
32563
32772
  return baseConfig;
32564
32773
  }
32565
- const manifestPath = `${baseConfig.statePath}.service.json`;
32774
+ const manifestPath = `${baseConfig.supervisorStatePath}.service.json`;
32566
32775
  const manifest = await readInferenceHostServiceManifest(manifestPath);
32567
32776
  if (!manifest) return baseConfig;
32568
32777
  const installedConfig = resolveInferenceHostConfig({
32569
- ...env,
32570
- ...manifest.runtime_environment
32778
+ ...selectedEnv,
32779
+ ...manifest.workers.find((worker) => worker.instance_name === baseConfig.instanceName)?.runtime_environment
32571
32780
  });
32572
32781
  if (resolve5(installedConfig.statePath) !== resolve5(baseConfig.statePath)) {
32573
32782
  throw new Error(
@@ -49994,7 +50203,11 @@ function buildHeadlessExchangeConfig(input) {
49994
50203
  user_fills_ttl_seconds: readCacheNumberField("user_fills_ttl_seconds"),
49995
50204
  stale_if_429_max_age_seconds: readCacheNumberField("stale_if_429_max_age_seconds")
49996
50205
  },
49997
- client_runtime_hyperliquid_case_sensitive_symbols: Array.isArray(input.client_runtime_hyperliquid_case_sensitive_symbols) ? input.client_runtime_hyperliquid_case_sensitive_symbols : null
50206
+ client_runtime_hyperliquid_case_sensitive_symbols: Array.isArray(input.client_runtime_hyperliquid_case_sensitive_symbols) ? input.client_runtime_hyperliquid_case_sensitive_symbols : null,
50207
+ client_runtime_hyperliquid_perp_dexs: input.client_runtime_hyperliquid_perp_dexs ? {
50208
+ enabled: input.client_runtime_hyperliquid_perp_dexs.enabled === true,
50209
+ allowed_dexes: Array.isArray(input.client_runtime_hyperliquid_perp_dexs.allowed_dexes) ? input.client_runtime_hyperliquid_perp_dexs.allowed_dexes.map((dex) => String(dex).trim()).filter(Boolean) : []
50210
+ } : void 0
49998
50211
  };
49999
50212
  }
50000
50213
  async function fetchLocalHyperliquidSnapshot(input) {
@@ -50010,7 +50223,7 @@ async function fetchLocalHyperliquidSnapshot(input) {
50010
50223
  symbol: input.symbol,
50011
50224
  tickerPrice: 0,
50012
50225
  leverage,
50013
- aggregatePerpDexs: false
50226
+ aggregatePerpDexs: true
50014
50227
  });
50015
50228
  const clientExchangeSnapshot = buildClientExchangeSnapshot({
50016
50229
  walletAddress,
@@ -51041,7 +51254,8 @@ var INFERENCE_HOST_VALUE_OPTIONS = /* @__PURE__ */ new Set([
51041
51254
  "--effort",
51042
51255
  "--wait-seconds",
51043
51256
  "--lines",
51044
- "--service-manifest"
51257
+ "--service-manifest",
51258
+ "--instance"
51045
51259
  ]);
51046
51260
  var isInferenceHostCliInvocation = (argv2) => {
51047
51261
  for (let index = 0; index < argv2.length; index += 1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtxmacro/cli",
3
- "version": "2026.8.30",
3
+ "version": "2026.8.31",
4
4
  "description": "VTX Macro CLI, MCP server, and durable subscription inference host.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",