@vtxmacro/cli 2026.8.39 → 2026.8.41

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 +49 -13
  2. package/bin/vtx.js +1215 -108
  3. package/package.json +4 -2
package/bin/vtx.js CHANGED
@@ -38,9 +38,13 @@ 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.39",
41
+ package_version: "2026.8.41",
42
42
  codex_package_name: "@openai/codex",
43
43
  codex_version: "0.147.0",
44
+ copilot_sdk_package_name: "@github/copilot-sdk",
45
+ copilot_sdk_version: "1.0.0-beta.8",
46
+ copilot_cli_package_name: "@github/copilot",
47
+ copilot_cli_version: "1.0.80",
44
48
  platforms: {
45
49
  "linux-x64": {
46
50
  package_name: "@openai/codex-linux-x64",
@@ -14725,6 +14729,7 @@ var init_external_inference_contract = __esm({
14725
14729
  key_generation: generationSchema,
14726
14730
  adapter: externalInferenceAdapterIdSchema,
14727
14731
  display_name: displayNameSchema,
14732
+ authenticated_account_identity: safeCodeSchema.nullable().optional(),
14728
14733
  authenticated_account_email: authenticatedAccountEmailSchema.nullable().optional(),
14729
14734
  authenticated_account_plan: safeCodeSchema.nullable().optional(),
14730
14735
  protocol_version: protocolVersionSchema,
@@ -15644,6 +15649,7 @@ var init_external_inference_contract = __esm({
15644
15649
  hostStatusReadSchema = external_exports.strictObject({
15645
15650
  host_id: identifierSchema,
15646
15651
  display_name: displayNameSchema,
15652
+ authenticated_account_identity: safeCodeSchema.nullable().optional(),
15647
15653
  authenticated_account_email: authenticatedAccountEmailSchema.nullable().optional(),
15648
15654
  authenticated_account_plan: safeCodeSchema.nullable().optional(),
15649
15655
  adapter: externalInferenceAdapterIdSchema,
@@ -18179,7 +18185,7 @@ var init_mcp_client = __esm({
18179
18185
  if (!host) return invalidBoundResult(name);
18180
18186
  if (host.host_id !== request.host_id || host.host_generation !== request.host_generation || host.advertisement_generation !== request.advertisement_generation || host.key_generation !== request.key_generation || request.adapter !== void 0 && host.adapter !== request.adapter) invalidBoundResult(name);
18181
18187
  if (name !== "inference.host.heartbeat") {
18182
- if (host.display_name !== request.display_name || host.authenticated_account_email !== void 0 && host.authenticated_account_email !== null && host.authenticated_account_email !== request.authenticated_account_email || (host.authenticated_account_plan ?? null) !== (request.authenticated_account_plan ?? null) || !exactJson(host.models, request.models) || host.status === "revoked") invalidBoundResult(name);
18188
+ if (host.display_name !== request.display_name || host.authenticated_account_identity !== void 0 && host.authenticated_account_identity !== null && host.authenticated_account_identity !== request.authenticated_account_identity || host.authenticated_account_email !== void 0 && host.authenticated_account_email !== null && host.authenticated_account_email !== request.authenticated_account_email || (host.authenticated_account_plan ?? null) !== (request.authenticated_account_plan ?? null) || !exactJson(host.models, request.models) || host.status === "revoked") invalidBoundResult(name);
18183
18189
  return;
18184
18190
  }
18185
18191
  const expectedStatus = request.status === "healthy" ? "online" : request.status;
@@ -19478,8 +19484,8 @@ async function preflightCodexSubscription(options) {
19478
19484
  });
19479
19485
  }
19480
19486
  const rateLimits = await session.readAccountRateLimits(options.deadlineAtMs, options.signal);
19481
- const modelCapabilities = await session.readModelCapabilities(options.deadlineAtMs, options.signal);
19482
- if (modelCapabilities.length === 0) {
19487
+ const modelCapabilities2 = await session.readModelCapabilities(options.deadlineAtMs, options.signal);
19488
+ if (modelCapabilities2.length === 0) {
19483
19489
  throw new CodexAppServerError({
19484
19490
  message: "Codex did not return any visible models with supported reasoning efforts.",
19485
19491
  category: "model",
@@ -19491,7 +19497,7 @@ async function preflightCodexSubscription(options) {
19491
19497
  authenticated_account_email: account.authenticatedChatGptAccount.email,
19492
19498
  authenticated_account_plan: account.authenticatedChatGptAccount.planType,
19493
19499
  rate_limits: rateLimits,
19494
- model_capabilities: modelCapabilities
19500
+ model_capabilities: modelCapabilities2
19495
19501
  };
19496
19502
  } finally {
19497
19503
  await session.close();
@@ -21473,10 +21479,10 @@ import {
21473
21479
  realpath as realpath3,
21474
21480
  rm as rm3
21475
21481
  } from "node:fs/promises";
21476
- import { randomBytes as randomBytes3 } from "node:crypto";
21482
+ import { createHash as createHash4, randomBytes as randomBytes3 } from "node:crypto";
21477
21483
  import { tmpdir as tmpdir2 } from "node:os";
21478
21484
  import { isAbsolute as isAbsolute3, join as join4, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
21479
- 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;
21485
+ var MAX_PROMPT_BYTES2, CODEX_MODEL_NAME_PATTERN, CODEX_REASONING_EFFORT_PATTERN, codexRecoveryCheckpointProcessFingerprint, proveCodexSameBootQuiescence, tokenUsageReceiptSchema, turnResultReceiptSchema, terminalReceiptSchema, recoveryCheckpointSchema, recoveryFileSchema, FileCodexAttemptRecoveryStore, utf8Bytes, assertAttemptActive, tomlString, permissionConfig, ensureDedicatedCodexHome, createIsolatedCodexAttemptResources, createIsolatedCodexHostResources, validateAttemptInput, isStrictDescendant, assertRecoveryResourceScope, removeRecoveredThread, confirmGuardianTerminatedForRecovery, reconcileCodexAttemptRecovery, CodexSubscriptionAdapter;
21480
21486
  var init_codex_adapter = __esm({
21481
21487
  "lib/inference-host/codex-adapter.ts"() {
21482
21488
  "use strict";
@@ -21487,6 +21493,34 @@ var init_codex_adapter = __esm({
21487
21493
  MAX_PROMPT_BYTES2 = 3e5;
21488
21494
  CODEX_MODEL_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/u;
21489
21495
  CODEX_REASONING_EFFORT_PATTERN = /^[a-z][a-z0-9_-]{0,31}$/u;
21496
+ codexRecoveryCheckpointProcessFingerprint = (checkpoint) => createHash4("sha256").update(JSON.stringify({
21497
+ attemptId: checkpoint.attemptId,
21498
+ processToken: checkpoint.processToken,
21499
+ processReceiptPath: checkpoint.processReceiptPath,
21500
+ processState: checkpoint.processState,
21501
+ bootIdentity: checkpoint.bootIdentity ?? null,
21502
+ cleanupConfirmed: checkpoint.cleanupConfirmed
21503
+ })).digest("hex");
21504
+ proveCodexSameBootQuiescence = async (options) => {
21505
+ const readBootIdentity = options.readBootIdentity ?? readInferenceSystemBootIdentity;
21506
+ const bootIdentityBefore = await readBootIdentity();
21507
+ await options.confirmNoManagedCodexProcesses();
21508
+ const bootIdentityAfter = await readBootIdentity();
21509
+ if (bootIdentityAfter !== bootIdentityBefore) {
21510
+ throw new Error("System boot identity changed during Codex recovery process inspection.");
21511
+ }
21512
+ return {
21513
+ schemaVersion: "vtx_codex_same_boot_quiescence_v1",
21514
+ bootIdentity: bootIdentityAfter,
21515
+ observedAtMs: (options.now ?? Date.now)(),
21516
+ checkpointFingerprints: Object.fromEntries(
21517
+ Object.entries(options.attempts).map(([attemptId, checkpoint]) => [
21518
+ attemptId,
21519
+ codexRecoveryCheckpointProcessFingerprint(checkpoint)
21520
+ ])
21521
+ )
21522
+ };
21523
+ };
21490
21524
  tokenUsageReceiptSchema = external_exports.strictObject({
21491
21525
  inputTokens: external_exports.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
21492
21526
  cachedInputTokens: external_exports.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
@@ -21959,11 +21993,20 @@ var init_codex_adapter = __esm({
21959
21993
  throw new Error("Codex cross-boot recovery receipt ownership is unconfirmed.");
21960
21994
  }
21961
21995
  } else {
21962
- await confirmGuardianTerminatedForRecovery(
21963
- checkpoint,
21964
- guardian,
21965
- options.deadlineMs ?? 5e3
21966
- );
21996
+ const receipt = await readCodexGuardianReceipt(guardian.receiptPath);
21997
+ if (receipt === null && options.sameBootQuiescenceProof) {
21998
+ const proof = options.sameBootQuiescenceProof;
21999
+ const expectedFingerprint = proof.checkpointFingerprints[checkpoint.attemptId];
22000
+ if (proof.schemaVersion !== "vtx_codex_same_boot_quiescence_v1" || proof.bootIdentity !== currentBootIdentity || checkpoint.bootIdentity !== currentBootIdentity || !Number.isFinite(proof.observedAtMs) || proof.observedAtMs > Date.now() + 1e3 || Date.now() - proof.observedAtMs > 3e4 || expectedFingerprint !== codexRecoveryCheckpointProcessFingerprint(checkpoint)) {
22001
+ throw new Error("Codex same-boot recovery process fence is invalid or stale.");
22002
+ }
22003
+ } else {
22004
+ await confirmGuardianTerminatedForRecovery(
22005
+ checkpoint,
22006
+ guardian,
22007
+ options.deadlineMs ?? 5e3
22008
+ );
22009
+ }
21967
22010
  }
21968
22011
  assertRecoveryResourceScope(checkpoint);
21969
22012
  await removeRecoveredThread(checkpoint, options.codexHome);
@@ -22607,11 +22650,458 @@ var init_codex_adapter = __esm({
22607
22650
  }
22608
22651
  });
22609
22652
 
22653
+ // lib/inference-host/codex-recovery-process.ts
22654
+ import { spawn as spawn5 } from "node:child_process";
22655
+ var normalizedWindowsPath, classifyCodexRecoveryProcesses, WINDOWS_PROCESS_QUERY, queryWindowsProcesses, confirmNoVtxManagedCodexProcesses;
22656
+ var init_codex_recovery_process = __esm({
22657
+ "lib/inference-host/codex-recovery-process.ts"() {
22658
+ "use strict";
22659
+ normalizedWindowsPath = (value) => value.replaceAll("\\", "/").replaceAll(/\/+$/gu, "").toLowerCase();
22660
+ classifyCodexRecoveryProcesses = (processes, pinnedBinaryPath) => {
22661
+ const pinned = normalizedWindowsPath(pinnedBinaryPath);
22662
+ let managedProcessCount = 0;
22663
+ let ambiguousProcessCount = 0;
22664
+ for (const process3 of processes) {
22665
+ const name = process3.name.trim().toLowerCase();
22666
+ if (name === "codex.exe") {
22667
+ if (!process3.executable_path || !process3.command_line || !process3.creation_date) {
22668
+ ambiguousProcessCount += 1;
22669
+ continue;
22670
+ }
22671
+ const executable = normalizedWindowsPath(process3.executable_path);
22672
+ const packageOwned = executable === pinned || executable.includes("/node_modules/@vtxmacro/") && executable.includes("/node_modules/@openai/codex-win32-x64/") || executable.includes("/node_modules/@vtxmacro/.cli-") && executable.includes("/node_modules/@openai/codex-win32-x64/");
22673
+ if (packageOwned) managedProcessCount += 1;
22674
+ continue;
22675
+ }
22676
+ if (name !== "node.exe") continue;
22677
+ if (!process3.executable_path || !process3.command_line || !process3.creation_date) {
22678
+ ambiguousProcessCount += 1;
22679
+ continue;
22680
+ }
22681
+ if (process3.command_line.includes("--input-type=module") && process3.command_line.includes("vtx_codex_guardian_v1") && process3.command_line.includes("processToken") && process3.command_line.includes("receiptPath")) {
22682
+ managedProcessCount += 1;
22683
+ }
22684
+ }
22685
+ return {
22686
+ managed_process_count: managedProcessCount,
22687
+ ambiguous_process_count: ambiguousProcessCount
22688
+ };
22689
+ };
22690
+ WINDOWS_PROCESS_QUERY = String.raw`
22691
+ $ErrorActionPreference = 'Stop'
22692
+ $items = @(Get-CimInstance Win32_Process | Where-Object { $_.Name -in @('node.exe', 'codex.exe') } | ForEach-Object {
22693
+ [ordered]@{
22694
+ process_id = [int]$_.ProcessId
22695
+ name = [string]$_.Name
22696
+ executable_path = if ($null -eq $_.ExecutablePath) { $null } else { [string]$_.ExecutablePath }
22697
+ command_line = if ($null -eq $_.CommandLine) { $null } else { [string]$_.CommandLine }
22698
+ creation_date = if ($null -eq $_.CreationDate) { $null } else { [string]$_.CreationDate }
22699
+ }
22700
+ })
22701
+ [Console]::Out.Write(($items | ConvertTo-Json -Depth 3 -Compress))
22702
+ `;
22703
+ queryWindowsProcesses = async () => {
22704
+ const args = [
22705
+ "-NoLogo",
22706
+ "-NoProfile",
22707
+ "-NonInteractive",
22708
+ "-EncodedCommand",
22709
+ Buffer.from(WINDOWS_PROCESS_QUERY, "utf16le").toString("base64")
22710
+ ];
22711
+ const result2 = await new Promise(
22712
+ (resolvePromise, reject) => {
22713
+ const child = spawn5("powershell.exe", args, {
22714
+ windowsHide: true,
22715
+ stdio: ["ignore", "pipe", "pipe"]
22716
+ });
22717
+ let stdout = "";
22718
+ let stderr = "";
22719
+ child.stdout.on("data", (chunk) => {
22720
+ stdout = `${stdout}${chunk.toString("utf8")}`.slice(-4e6);
22721
+ });
22722
+ child.stderr.on("data", (chunk) => {
22723
+ stderr = `${stderr}${chunk.toString("utf8")}`.slice(-4096);
22724
+ });
22725
+ child.once("error", reject);
22726
+ child.once("close", (code) => {
22727
+ if (code !== 0) {
22728
+ reject(new Error("Windows process ownership inventory failed."));
22729
+ return;
22730
+ }
22731
+ resolvePromise({ exitCode: code ?? 1, stdout });
22732
+ });
22733
+ }
22734
+ );
22735
+ if (result2.exitCode !== 0) throw new Error("Windows process ownership inventory failed.");
22736
+ let parsed;
22737
+ try {
22738
+ parsed = result2.stdout.trim() ? JSON.parse(result2.stdout) : [];
22739
+ } catch {
22740
+ throw new Error("Windows process ownership inventory was invalid.");
22741
+ }
22742
+ const entries = Array.isArray(parsed) ? parsed : [parsed];
22743
+ return entries.map((entry) => {
22744
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
22745
+ throw new Error("Windows process ownership inventory was invalid.");
22746
+ }
22747
+ const record2 = entry;
22748
+ if (!Number.isSafeInteger(record2.process_id) || Number(record2.process_id) < 1 || typeof record2.name !== "string" || record2.executable_path !== null && typeof record2.executable_path !== "string" || record2.command_line !== null && typeof record2.command_line !== "string" || record2.creation_date !== null && typeof record2.creation_date !== "string") {
22749
+ throw new Error("Windows process ownership inventory was invalid.");
22750
+ }
22751
+ return {
22752
+ process_id: Number(record2.process_id),
22753
+ name: record2.name,
22754
+ executable_path: record2.executable_path,
22755
+ command_line: record2.command_line,
22756
+ creation_date: record2.creation_date
22757
+ };
22758
+ });
22759
+ };
22760
+ confirmNoVtxManagedCodexProcesses = async (options) => {
22761
+ if ((options.platform ?? process.platform) !== "win32") {
22762
+ throw new Error("Same-boot service recovery is currently supported only on Windows.");
22763
+ }
22764
+ const queryProcesses = options.queryProcesses ?? queryWindowsProcesses;
22765
+ const sleep4 = options.sleep ?? (async (milliseconds) => {
22766
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
22767
+ });
22768
+ for (let observation = 0; observation < 2; observation += 1) {
22769
+ const classification = classifyCodexRecoveryProcesses(
22770
+ await queryProcesses(),
22771
+ options.pinnedBinaryPath
22772
+ );
22773
+ if (classification.ambiguous_process_count > 0) {
22774
+ throw new Error(
22775
+ "Codex process ownership could not be inspected completely; recovery evidence was preserved."
22776
+ );
22777
+ }
22778
+ if (classification.managed_process_count > 0) {
22779
+ throw new Error(
22780
+ "VTX-managed Codex automation is still running; recovery evidence was preserved."
22781
+ );
22782
+ }
22783
+ if (observation === 0) await sleep4(250);
22784
+ }
22785
+ };
22786
+ }
22787
+ });
22788
+
22789
+ // lib/inference-host/copilot-adapter.ts
22790
+ import { mkdtemp as mkdtemp2, rm as rm4 } from "node:fs/promises";
22791
+ import { tmpdir as tmpdir3 } from "node:os";
22792
+ import { join as join5 } from "node:path";
22793
+ import { CopilotClient } from "@github/copilot-sdk";
22794
+ var COPILOT_SDK_VERSION, MAX_RESULT_BYTES, createPrivateWorkspace, cleanCopilotEnvironment, defaultClient, modelCapabilities, requiredUsageInteger, optionalUsageInteger, CopilotSubscriptionAdapter;
22795
+ var init_copilot_adapter = __esm({
22796
+ "lib/inference-host/copilot-adapter.ts"() {
22797
+ "use strict";
22798
+ init_codex_app_server();
22799
+ COPILOT_SDK_VERSION = "1.0.0-beta.8";
22800
+ MAX_RESULT_BYTES = 3e5;
22801
+ createPrivateWorkspace = async () => {
22802
+ const path = await mkdtemp2(join5(tmpdir3(), "vtx-copilot-"));
22803
+ return {
22804
+ path,
22805
+ cleanup: async () => {
22806
+ await rm4(path, { recursive: true, force: true });
22807
+ }
22808
+ };
22809
+ };
22810
+ cleanCopilotEnvironment = () => {
22811
+ const environment = { ...process.env };
22812
+ for (const name of [
22813
+ "COPILOT_GITHUB_TOKEN",
22814
+ "GH_TOKEN",
22815
+ "GITHUB_TOKEN",
22816
+ "COPILOT_PROVIDER_API_KEY",
22817
+ "COPILOT_PROVIDER_BASE_URL",
22818
+ "COPILOT_PROVIDER_TYPE",
22819
+ "COPILOT_CLI_PATH",
22820
+ "COPILOT_SDK_AUTH_TOKEN",
22821
+ "COPILOT_CONNECTION_TOKEN",
22822
+ "COPILOT_HOME"
22823
+ ]) delete environment[name];
22824
+ return environment;
22825
+ };
22826
+ defaultClient = (workingDirectory) => new CopilotClient({
22827
+ workingDirectory,
22828
+ env: cleanCopilotEnvironment(),
22829
+ useLoggedInUser: true,
22830
+ logLevel: "error"
22831
+ });
22832
+ modelCapabilities = (models) => {
22833
+ const capabilities = models.filter((model) => model.policy?.state !== "disabled" && model.policy?.state !== "unconfigured").flatMap((model) => {
22834
+ const efforts = model.capabilities.supports.reasoningEffort ? [...model.supportedReasoningEfforts ?? []] : ["none"];
22835
+ if (efforts.length === 0) return [];
22836
+ const defaultEffort = model.defaultReasoningEffort && efforts.includes(model.defaultReasoningEffort) ? model.defaultReasoningEffort : efforts[0];
22837
+ return [{
22838
+ id: model.id,
22839
+ model: model.id,
22840
+ displayName: model.name,
22841
+ hidden: false,
22842
+ supportedReasoningEfforts: efforts,
22843
+ defaultReasoningEffort: defaultEffort,
22844
+ isDefault: false
22845
+ }];
22846
+ });
22847
+ return capabilities.map((capability, index) => ({
22848
+ ...capability,
22849
+ isDefault: index === 0
22850
+ }));
22851
+ };
22852
+ requiredUsageInteger = (value, field) => {
22853
+ if (!Number.isSafeInteger(value) || Number(value) < 0) {
22854
+ throw new Error(`copilot_usage_${field}_missing`);
22855
+ }
22856
+ return Number(value);
22857
+ };
22858
+ optionalUsageInteger = (value, field) => {
22859
+ if (value === void 0) return 0;
22860
+ if (!Number.isSafeInteger(value) || Number(value) < 0) {
22861
+ throw new Error(`copilot_usage_${field}_invalid`);
22862
+ }
22863
+ return Number(value);
22864
+ };
22865
+ CopilotSubscriptionAdapter = class {
22866
+ constructor(dependencies = {}) {
22867
+ this.inFlight = /* @__PURE__ */ new Map();
22868
+ this.dependencies = dependencies;
22869
+ }
22870
+ async preflight(signal) {
22871
+ const workspace = await (this.dependencies.createWorkspace ?? createPrivateWorkspace)();
22872
+ const client = (this.dependencies.createClient ?? defaultClient)(workspace.path);
22873
+ try {
22874
+ signal?.throwIfAborted();
22875
+ await client.start();
22876
+ const [status, auth, models] = await Promise.all([
22877
+ client.getStatus(),
22878
+ client.getAuthStatus(),
22879
+ client.listModels()
22880
+ ]);
22881
+ if (!auth.isAuthenticated || auth.authType !== "user" && auth.authType !== "gh-cli") {
22882
+ throw new CodexAppServerError({
22883
+ message: "GitHub Copilot must use a logged-in Copilot subscription, not BYOK.",
22884
+ category: "auth",
22885
+ code: "copilot_subscription_auth_required",
22886
+ retryable: false
22887
+ });
22888
+ }
22889
+ const accountIdentity = auth.login?.trim().toLowerCase() ?? "";
22890
+ if (!/^[a-z0-9][a-z0-9._-]{0,95}$/u.test(accountIdentity)) {
22891
+ throw new CodexAppServerError({
22892
+ message: "GitHub Copilot did not return a stable authenticated login identity.",
22893
+ category: "auth",
22894
+ code: "copilot_account_identity_required",
22895
+ retryable: false
22896
+ });
22897
+ }
22898
+ const capabilities = modelCapabilities(models);
22899
+ if (capabilities.length === 0) {
22900
+ throw new CodexAppServerError({
22901
+ message: "GitHub Copilot did not return an enabled model catalog.",
22902
+ category: "model",
22903
+ code: "copilot_model_catalog_empty",
22904
+ retryable: false
22905
+ });
22906
+ }
22907
+ return {
22908
+ adapterId: "copilot",
22909
+ runtimeVersion: `${COPILOT_SDK_VERSION}+cli.${status.version}.rpc${status.protocolVersion}`,
22910
+ authenticatedAccountIdentity: accountIdentity,
22911
+ authenticatedAccountEmail: null,
22912
+ authenticatedAccountPlan: null,
22913
+ modelCapabilities: capabilities,
22914
+ rateLimits: null,
22915
+ sameAttemptRecovery: false
22916
+ };
22917
+ } finally {
22918
+ await client.stop().catch(async () => {
22919
+ await client.forceStop();
22920
+ });
22921
+ await workspace.cleanup();
22922
+ }
22923
+ }
22924
+ runAttempt(input) {
22925
+ const active = this.inFlight.get(input.attemptId);
22926
+ if (active) return active;
22927
+ const promise2 = this.executeAttempt(input).finally(() => {
22928
+ if (this.inFlight.get(input.attemptId) === promise2) this.inFlight.delete(input.attemptId);
22929
+ });
22930
+ this.inFlight.set(input.attemptId, promise2);
22931
+ return promise2;
22932
+ }
22933
+ async executeAttempt(input) {
22934
+ const startedAt = (this.dependencies.now ?? Date.now)();
22935
+ const workspace = await (this.dependencies.createWorkspace ?? createPrivateWorkspace)();
22936
+ const client = (this.dependencies.createClient ?? defaultClient)(workspace.path);
22937
+ let session = null;
22938
+ let dispatchEntered = false;
22939
+ let providerReceiptObserved = false;
22940
+ try {
22941
+ input.signal?.throwIfAborted();
22942
+ if (Date.now() >= input.deadlineAtMs) throw new Error("deadline_exceeded");
22943
+ await client.start();
22944
+ session = await client.createSession({
22945
+ clientName: "@vtxmacro/cli durable Copilot inference host",
22946
+ model: input.requestedModel,
22947
+ ...input.requestedReasoningEffort === "none" ? {} : { reasoningEffort: input.requestedReasoningEffort },
22948
+ systemMessage: {
22949
+ mode: "replace",
22950
+ content: `${input.systemPrompt}
22951
+
22952
+ Return only one JSON value matching this JSON Schema exactly:
22953
+ ${input.outputSchemaJson}`
22954
+ },
22955
+ tools: [],
22956
+ availableTools: [],
22957
+ enableConfigDiscovery: false,
22958
+ enableSessionTelemetry: true,
22959
+ workingDirectory: workspace.path,
22960
+ streaming: true
22961
+ });
22962
+ const usageEvents = [];
22963
+ session.on((event) => {
22964
+ if (event.type === "assistant.usage" && !event.agentId) {
22965
+ providerReceiptObserved = true;
22966
+ usageEvents.push(event);
22967
+ }
22968
+ });
22969
+ const onAbort = () => {
22970
+ void session?.abort().catch(() => void 0);
22971
+ };
22972
+ input.signal?.addEventListener("abort", onAbort, { once: true });
22973
+ dispatchEntered = true;
22974
+ let response;
22975
+ try {
22976
+ response = await session.sendAndWait(
22977
+ { prompt: input.userPrompt },
22978
+ Math.max(1, input.deadlineAtMs - Date.now())
22979
+ );
22980
+ providerReceiptObserved = true;
22981
+ } finally {
22982
+ input.signal?.removeEventListener("abort", onAbort);
22983
+ }
22984
+ if (!response?.data.content || Buffer.byteLength(response.data.content, "utf8") > MAX_RESULT_BYTES) {
22985
+ throw new Error("copilot_invalid_result");
22986
+ }
22987
+ const observedModels = /* @__PURE__ */ new Set();
22988
+ const observedEfforts = /* @__PURE__ */ new Set();
22989
+ let inputTokens = 0;
22990
+ let cachedInputTokens = 0;
22991
+ let outputTokens = 0;
22992
+ let reasoningOutputTokens = 0;
22993
+ let cacheWriteInputTokens = 0;
22994
+ let cacheWriteSupported = false;
22995
+ let timeToFirstTokenMs = null;
22996
+ let providerCallId = null;
22997
+ for (const event of usageEvents) {
22998
+ if (event.type !== "assistant.usage") continue;
22999
+ observedModels.add(event.data.model);
23000
+ if (event.data.reasoningEffort) observedEfforts.add(event.data.reasoningEffort);
23001
+ inputTokens += requiredUsageInteger(event.data.inputTokens, "input_tokens");
23002
+ cachedInputTokens += optionalUsageInteger(event.data.cacheReadTokens, "cache_read_tokens");
23003
+ outputTokens += requiredUsageInteger(event.data.outputTokens, "output_tokens");
23004
+ reasoningOutputTokens += optionalUsageInteger(event.data.reasoningTokens, "reasoning_tokens");
23005
+ if (event.data.cacheWriteTokens !== void 0) {
23006
+ cacheWriteSupported = true;
23007
+ cacheWriteInputTokens += optionalUsageInteger(
23008
+ event.data.cacheWriteTokens,
23009
+ "cache_write_tokens"
23010
+ );
23011
+ }
23012
+ if (timeToFirstTokenMs === null && Number.isSafeInteger(event.data.timeToFirstTokenMs)) {
23013
+ timeToFirstTokenMs = Number(event.data.timeToFirstTokenMs);
23014
+ }
23015
+ providerCallId = event.data.providerCallId ?? event.data.apiCallId ?? providerCallId;
23016
+ }
23017
+ if (usageEvents.length === 0) {
23018
+ throw new Error("copilot_usage_receipt_missing");
23019
+ }
23020
+ if (response.data.model) observedModels.add(response.data.model);
23021
+ if (observedModels.size !== 1 || !observedModels.has(input.requestedModel)) {
23022
+ throw new Error("copilot_effective_model_mismatch");
23023
+ }
23024
+ const effectiveEffort = input.requestedReasoningEffort === "none" ? observedEfforts.size === 0 ? "none" : observedEfforts.size === 1 ? [...observedEfforts][0] : null : observedEfforts.size === 1 ? [...observedEfforts][0] : null;
23025
+ if (effectiveEffort !== input.requestedReasoningEffort) {
23026
+ throw new Error("copilot_effective_effort_mismatch");
23027
+ }
23028
+ const usage = {
23029
+ inputTokens,
23030
+ cachedInputTokens: Math.min(cachedInputTokens, inputTokens),
23031
+ outputTokens,
23032
+ reasoningOutputTokens: Math.min(reasoningOutputTokens, outputTokens),
23033
+ totalTokens: inputTokens + outputTokens,
23034
+ cacheWriteInputTokens: cacheWriteSupported ? cacheWriteInputTokens : null,
23035
+ cacheWriteSupported
23036
+ };
23037
+ return {
23038
+ text: response.data.content,
23039
+ reasoningContent: response.data.reasoningText ?? null,
23040
+ reasoningSummary: null,
23041
+ requestedModel: input.requestedModel,
23042
+ effectiveModel: input.requestedModel,
23043
+ requestedReasoningEffort: input.requestedReasoningEffort,
23044
+ effectiveReasoningEffort: effectiveEffort,
23045
+ adapterRequestId: providerCallId ?? response.data.requestId ?? response.data.messageId,
23046
+ adapterResponseId: response.data.serviceRequestId ?? response.data.messageId,
23047
+ usage,
23048
+ latencyMs: Math.max(0, (this.dependencies.now ?? Date.now)() - startedAt),
23049
+ timeToFirstTokenMs,
23050
+ terminalStatus: "completed"
23051
+ };
23052
+ } catch (error48) {
23053
+ if (error48 instanceof CodexAppServerError) throw error48;
23054
+ const cancelled = input.signal?.aborted === true;
23055
+ const deadline = Date.now() >= input.deadlineAtMs;
23056
+ if (dispatchEntered && session) {
23057
+ await session.abort().catch(() => void 0);
23058
+ }
23059
+ throw new CodexAppServerError({
23060
+ message: cancelled ? "GitHub Copilot attempt was cancelled." : deadline ? "GitHub Copilot attempt exceeded its immutable deadline." : error48 instanceof Error ? error48.message : "GitHub Copilot adapter failed.",
23061
+ category: cancelled ? "cancelled" : deadline ? "timeout" : "adapter",
23062
+ code: cancelled ? "cancelled" : deadline ? "deadline_exceeded" : "copilot_adapter_failure",
23063
+ retryable: false,
23064
+ dispatchOutcome: providerReceiptObserved ? "confirmed_dispatched" : dispatchEntered ? "outcome_unknown" : "not_dispatched",
23065
+ cause: error48
23066
+ });
23067
+ } finally {
23068
+ if (session) {
23069
+ const sessionId = session.sessionId;
23070
+ await session.disconnect().catch(() => void 0);
23071
+ await client.deleteSession(sessionId).catch(() => void 0);
23072
+ }
23073
+ await client.stop().catch(async () => {
23074
+ await client.forceStop();
23075
+ });
23076
+ await workspace.cleanup();
23077
+ }
23078
+ }
23079
+ };
23080
+ }
23081
+ });
23082
+
23083
+ // lib/inference-host/durable-adapter.ts
23084
+ var DURABLE_INFERENCE_ADAPTER_IDS, isDurableInferenceAdapterId, durableAdapterDisplayName;
23085
+ var init_durable_adapter = __esm({
23086
+ "lib/inference-host/durable-adapter.ts"() {
23087
+ "use strict";
23088
+ DURABLE_INFERENCE_ADAPTER_IDS = [
23089
+ "codex",
23090
+ "copilot"
23091
+ ];
23092
+ isDurableInferenceAdapterId = (value) => DURABLE_INFERENCE_ADAPTER_IDS.includes(value);
23093
+ durableAdapterDisplayName = (adapter) => ({
23094
+ codex: "Codex",
23095
+ copilot: "GitHub Copilot"
23096
+ })[adapter];
23097
+ }
23098
+ });
23099
+
22610
23100
  // lib/inference-host/crypto.ts
22611
23101
  import {
22612
23102
  createCipheriv,
22613
23103
  createDecipheriv,
22614
- createHash as createHash4,
23104
+ createHash as createHash5,
22615
23105
  createPrivateKey,
22616
23106
  createPublicKey,
22617
23107
  diffieHellman,
@@ -22650,7 +23140,7 @@ var init_crypto = __esm({
22650
23140
  const aad = envelopeAadSchema.parse(input);
22651
23141
  return Buffer.from(JSON.stringify(canonicalize(aad)), "utf8");
22652
23142
  };
22653
- externalInferenceSha256 = (value) => createHash4("sha256").update(value).digest("hex");
23143
+ externalInferenceSha256 = (value) => createHash5("sha256").update(value).digest("hex");
22654
23144
  decodeCanonicalBase64Url = (value, fieldName, expectedBytes) => {
22655
23145
  if (!/^[A-Za-z0-9_-]+$/u.test(value)) {
22656
23146
  throw new ExternalInferenceEnvelopeError("invalid_encoding", `${fieldName} is invalid.`);
@@ -22749,7 +23239,7 @@ var init_crypto = __esm({
22749
23239
  return Buffer.from(hkdfSync(
22750
23240
  "sha256",
22751
23241
  root,
22752
- createHash4("sha256").update(aadBytes).digest(),
23242
+ createHash5("sha256").update(aadBytes).digest(),
22753
23243
  info,
22754
23244
  32
22755
23245
  ));
@@ -29163,7 +29653,7 @@ var require_ajv = __commonJS({
29163
29653
  });
29164
29654
 
29165
29655
  // lib/inference-host/runner.ts
29166
- import { createHash as createHash5 } from "node:crypto";
29656
+ import { createHash as createHash6 } from "node:crypto";
29167
29657
  function createDefaultInferenceHostRunnerDependencies(options) {
29168
29658
  const fetchImpl = options.fetchImpl ?? fetch;
29169
29659
  return {
@@ -29198,7 +29688,7 @@ function createDefaultInferenceHostRunnerDependencies(options) {
29198
29688
  envelopePublicKey: options.envelopePublicKey
29199
29689
  };
29200
29690
  }
29201
- 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;
29691
+ 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, buildInferenceAdvertisedModels, 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;
29202
29692
  var init_runner = __esm({
29203
29693
  "lib/inference-host/runner.ts"() {
29204
29694
  "use strict";
@@ -29223,18 +29713,18 @@ var init_runner = __esm({
29223
29713
  DEFAULT_CODEX_ACCOUNT_COOLDOWN_MS = 5 * 60 * 1e3;
29224
29714
  MIN_CLAIM_START_WINDOW_MS = 5e3;
29225
29715
  UNBOUNDED_AVAILABLE_SLOTS = Number.MAX_SAFE_INTEGER;
29226
- buildCodexInferenceAdvertisedModels = (capabilities, adapterRuntimeVersion) => {
29716
+ buildInferenceAdvertisedModels = (capabilities, adapterRuntimeVersion, adapter = "codex", structuredOutput = true, sameAttemptRecovery = adapter === "codex") => {
29227
29717
  const visible = capabilities.filter((capability) => !capability.hidden);
29228
29718
  if (visible.length === 0 || visible.length > 64) {
29229
29719
  throw new InferenceHostRunnerError(
29230
29720
  "invalid_configuration",
29231
- visible.length === 0 ? "Codex did not return any visible models that VTX can advertise." : "Codex returned more models than the VTX advertisement contract can represent."
29721
+ visible.length === 0 ? `${adapter} did not return any visible models that VTX can advertise.` : `${adapter} returned more models than the VTX advertisement contract can represent.`
29232
29722
  );
29233
29723
  }
29234
29724
  const models = visible.map((capability) => ({
29235
29725
  model_id: capability.model,
29236
29726
  label: capability.displayName,
29237
- adapter: "codex",
29727
+ adapter,
29238
29728
  supported_lanes: ["main", "review", "screener"],
29239
29729
  supported_reasoning_efforts: [...capability.supportedReasoningEfforts],
29240
29730
  default_reasoning_effort: capability.defaultReasoningEffort,
@@ -29243,14 +29733,14 @@ var init_runner = __esm({
29243
29733
  requested_model: capability.model,
29244
29734
  effective_models: [capability.model]
29245
29735
  }],
29246
- structured_output: true,
29247
- same_attempt_recovery: true,
29736
+ structured_output: structuredOutput,
29737
+ same_attempt_recovery: sameAttemptRecovery,
29248
29738
  adapter_runtime_version: adapterRuntimeVersion
29249
29739
  }));
29250
29740
  if (new Set(models.map((model) => model.model_id)).size !== models.length) {
29251
29741
  throw new InferenceHostRunnerError(
29252
29742
  "invalid_configuration",
29253
- "Codex returned duplicate runtime model identities."
29743
+ `${adapter} returned duplicate runtime model identities.`
29254
29744
  );
29255
29745
  }
29256
29746
  return models;
@@ -29471,7 +29961,7 @@ var init_runner = __esm({
29471
29961
  attempts
29472
29962
  };
29473
29963
  };
29474
- sha256 = (value) => createHash5("sha256").update(value, "utf8").digest("hex");
29964
+ sha256 = (value) => createHash6("sha256").update(value, "utf8").digest("hex");
29475
29965
  stableOperationId = (kind, parts) => `${kind}_${sha256(JSON.stringify(parts)).slice(0, 48)}`;
29476
29966
  buildAttemptStartRequest = (claim, attemptId, startedAt) => attemptStartRequestSchema.parse({
29477
29967
  schema_version: "external_inference_attempt_start_v1",
@@ -29526,6 +30016,10 @@ var init_runner = __esm({
29526
30016
  return result2;
29527
30017
  };
29528
30018
  validateRunnerOptions = (options) => {
30019
+ const adapterId = options.adapterId ?? "codex";
30020
+ if (!/^[a-z0-9][a-z0-9._-]{0,63}$/u.test(adapterId)) {
30021
+ throw new InferenceHostRunnerError("invalid_configuration", "Inference adapter ID is invalid.");
30022
+ }
29529
30023
  if (!options.displayName.trim() || options.displayName.length > 128) {
29530
30024
  throw new InferenceHostRunnerError("invalid_configuration", "Inference host display name is invalid.");
29531
30025
  }
@@ -29549,9 +30043,15 @@ var init_runner = __esm({
29549
30043
  );
29550
30044
  }
29551
30045
  return {
29552
- advertisedModels: buildCodexInferenceAdvertisedModels(
30046
+ adapterId,
30047
+ usageSource: options.usageSource ?? (adapterId === "codex" ? "codex_app_server" : adapterId),
30048
+ sameAttemptRecovery: options.sameAttemptRecovery ?? adapterId === "codex",
30049
+ advertisedModels: buildInferenceAdvertisedModels(
29553
30050
  options.codexModelCapabilities,
29554
- options.adapterRuntimeVersion
30051
+ options.adapterRuntimeVersion,
30052
+ adapterId,
30053
+ options.structuredOutput ?? adapterId === "codex",
30054
+ options.sameAttemptRecovery ?? adapterId === "codex"
29555
30055
  ),
29556
30056
  maxConcurrency: options.maxConcurrency === null || options.maxConcurrency === void 0 ? null : finitePositiveOption(options.maxConcurrency, 1, "Maximum concurrency"),
29557
30057
  advertisementTtlMs,
@@ -29756,12 +30256,12 @@ var init_runner = __esm({
29756
30256
  };
29757
30257
  assertAdvertisementResult = (advertisement, result2) => {
29758
30258
  const host = result2.host;
29759
- if (host.host_id !== advertisement.host_id || host.host_generation !== advertisement.host_generation || host.advertisement_generation !== advertisement.advertisement_generation || host.key_generation !== advertisement.key_generation || host.display_name !== advertisement.display_name || host.authenticated_account_email !== void 0 && host.authenticated_account_email !== null && host.authenticated_account_email !== advertisement.authenticated_account_email || (host.authenticated_account_plan ?? null) !== (advertisement.authenticated_account_plan ?? null) || host.adapter !== advertisement.adapter || host.status === "revoked" || !exactJson2(host.models, advertisement.models)) runnerIdentityMismatch("Host advertisement");
30259
+ if (host.host_id !== advertisement.host_id || host.host_generation !== advertisement.host_generation || host.advertisement_generation !== advertisement.advertisement_generation || host.key_generation !== advertisement.key_generation || host.display_name !== advertisement.display_name || host.authenticated_account_identity !== void 0 && host.authenticated_account_identity !== null && host.authenticated_account_identity !== advertisement.authenticated_account_identity || host.authenticated_account_email !== void 0 && host.authenticated_account_email !== null && host.authenticated_account_email !== advertisement.authenticated_account_email || (host.authenticated_account_plan ?? null) !== (advertisement.authenticated_account_plan ?? null) || host.adapter !== advertisement.adapter || host.status === "revoked" || !exactJson2(host.models, advertisement.models)) runnerIdentityMismatch("Host advertisement");
29760
30260
  };
29761
- assertHostHeartbeatResult = (request, result2, allowOfflineProjection = false) => {
30261
+ assertHostHeartbeatResult = (request, result2, expectedAdapter, allowOfflineProjection = false) => {
29762
30262
  const expectedStatus = request.status === "healthy" ? "online" : request.status;
29763
30263
  const expectedTemporaryOffline = allowOfflineProjection && request.status === "healthy" && result2.host.status === "offline";
29764
- if (result2.host.host_id !== request.host_id || result2.host.host_generation !== request.host_generation || result2.host.advertisement_generation !== request.advertisement_generation || result2.host.key_generation !== request.key_generation || result2.host.adapter !== "codex" || result2.host.status !== expectedStatus && !expectedTemporaryOffline) runnerIdentityMismatch("Host heartbeat");
30264
+ if (result2.host.host_id !== request.host_id || result2.host.host_generation !== request.host_generation || result2.host.advertisement_generation !== request.advertisement_generation || result2.host.key_generation !== request.key_generation || result2.host.adapter !== expectedAdapter || result2.host.status !== expectedStatus && !expectedTemporaryOffline) runnerIdentityMismatch("Host heartbeat");
29765
30265
  };
29766
30266
  assertClaimResult = (request, result2) => {
29767
30267
  if (result2.claim_request_id !== request.claim_request_id) {
@@ -29837,10 +30337,10 @@ var init_runner = __esm({
29837
30337
  "authentication_timeout"
29838
30338
  ].includes(code);
29839
30339
  };
29840
- reportedTokenUsage = (usage) => ({
30340
+ reportedTokenUsage = (usage, source = "codex_app_server") => ({
29841
30341
  schema_version: EXTERNAL_INFERENCE_USAGE_SCHEMA_VERSION,
29842
30342
  availability: "reported",
29843
- source: "codex_app_server",
30343
+ source,
29844
30344
  input_tokens: usage.inputTokens,
29845
30345
  cached_input_tokens: usage.cachedInputTokens,
29846
30346
  output_tokens: usage.outputTokens,
@@ -29849,7 +30349,7 @@ var init_runner = __esm({
29849
30349
  cache_write_input_tokens: usage.cacheWriteInputTokens,
29850
30350
  cache_write_support: usage.cacheWriteSupported ? "reported" : "unsupported"
29851
30351
  });
29852
- reportedUsage = (result2) => reportedTokenUsage(result2.usage);
30352
+ reportedUsage = (result2, source) => reportedTokenUsage(result2.usage, source);
29853
30353
  InferenceHostRunner = class {
29854
30354
  constructor(dependencies, options) {
29855
30355
  this.receiptWriteChain = Promise.resolve();
@@ -30079,8 +30579,9 @@ var init_runner = __esm({
30079
30579
  host_generation: localState.host_generation,
30080
30580
  advertisement_generation: generation,
30081
30581
  key_generation: localState.key_generation,
30082
- adapter: "codex",
30582
+ adapter: settings.adapterId,
30083
30583
  display_name: this.options.displayName.trim(),
30584
+ authenticated_account_identity: this.options.authenticatedAccountIdentity ?? null,
30084
30585
  authenticated_account_email: this.options.authenticatedAccountEmail ?? null,
30085
30586
  authenticated_account_plan: this.options.authenticatedAccountPlan ?? null,
30086
30587
  protocol_version: this.options.protocolVersion,
@@ -30189,7 +30690,7 @@ var init_runner = __esm({
30189
30690
  }
30190
30691
  throw error48;
30191
30692
  }
30192
- assertHostHeartbeatResult(request, result2, allowOfflineProjection);
30693
+ assertHostHeartbeatResult(request, result2, settings.adapterId, allowOfflineProjection);
30193
30694
  if (result2.host.status === "revoked" || result2.host.status === "offline" && !allowOfflineProjection) {
30194
30695
  const error48 = new InferenceHostRunnerError(
30195
30696
  result2.host.status === "revoked" ? "host_revoked" : "host_offline",
@@ -30633,6 +31134,9 @@ var init_runner = __esm({
30633
31134
  signal,
30634
31135
  now
30635
31136
  } = options;
31137
+ const adapterId = this.options.adapterId ?? "codex";
31138
+ const usageSource = this.options.usageSource ?? (adapterId === "codex" ? "codex_app_server" : adapterId);
31139
+ const sameAttemptRecovery = this.options.sameAttemptRecovery ?? adapterId === "codex";
30636
31140
  const decryptClaim = this.dependencies.decryptClaim ?? decryptExternalInferenceClaim;
30637
31141
  const sealCandidate = this.dependencies.sealCandidate ?? sealExternalInferenceCandidate;
30638
31142
  const validateOutput = this.dependencies.validateOutput ?? defaultValidateOutput;
@@ -30809,6 +31313,15 @@ var init_runner = __esm({
30809
31313
  phase: "dispatched",
30810
31314
  dispatch_outcome: "outcome_unknown"
30811
31315
  });
31316
+ if (options.resumeReceipt?.phase === "dispatched" && !sameAttemptRecovery) {
31317
+ throw new CodexAppServerError({
31318
+ message: `${adapterId} cannot prove the exact result of the interrupted attempt.`,
31319
+ category: "adapter",
31320
+ code: "same_attempt_recovery_unavailable",
31321
+ retryable: false,
31322
+ dispatchOutcome: "outcome_unknown"
31323
+ });
31324
+ }
30812
31325
  adapterResult = await this.dependencies.codexAdapter.runAttempt({
30813
31326
  attemptId,
30814
31327
  inputSha256: jobInput.input_sha256,
@@ -30834,7 +31347,7 @@ var init_runner = __esm({
30834
31347
  } catch (error48) {
30835
31348
  throw new InferenceHostRunnerError(
30836
31349
  "invalid_structured_output",
30837
- "Codex did not return valid structured JSON.",
31350
+ `${adapterId} did not return valid structured JSON.`,
30838
31351
  { cause: error48 }
30839
31352
  );
30840
31353
  }
@@ -30846,7 +31359,7 @@ var init_runner = __esm({
30846
31359
  if (Buffer.byteLength(adapterResult.text, "utf8") > outputContract.providerResponseTextMaxUtf8Bytes) {
30847
31360
  throw new InferenceHostRunnerError(
30848
31361
  "output_schema_response_text_too_large",
30849
- "Codex response text exceeds the immutable provider-response UTF-8 byte limit."
31362
+ `${adapterId} response text exceeds the immutable provider-response UTF-8 byte limit.`
30850
31363
  );
30851
31364
  }
30852
31365
  if (outputContract.providerReasoningContentSupported && outputContract.providerReasoningContentMaxUtf8Bytes !== null && adapterResult.reasoningContent !== null && Buffer.byteLength(adapterResult.reasoningContent, "utf8") > outputContract.providerReasoningContentMaxUtf8Bytes) {
@@ -30919,7 +31432,7 @@ var init_runner = __esm({
30919
31432
  adapter_request_id: adapterResult.adapterRequestId,
30920
31433
  adapter_response_id: adapterResult.adapterResponseId
30921
31434
  },
30922
- usage: reportedUsage(adapterResult),
31435
+ usage: reportedUsage(adapterResult, usageSource),
30923
31436
  outcome: {
30924
31437
  dispatch_outcome: "confirmed_dispatched",
30925
31438
  response_outcome: "completed"
@@ -31034,7 +31547,7 @@ var init_runner = __esm({
31034
31547
  adapter_request_id: canReportEffective ? adapterResult?.adapterRequestId ?? null : null,
31035
31548
  adapter_response_id: canReportEffective ? adapterResult?.adapterResponseId ?? null : null
31036
31549
  },
31037
- usage: adapterResult ? reportedUsage(adapterResult) : error48 instanceof CodexAppServerError && error48.usage ? reportedTokenUsage(error48.usage) : null,
31550
+ usage: adapterResult ? reportedUsage(adapterResult, usageSource) : error48 instanceof CodexAppServerError && error48.usage ? reportedTokenUsage(error48.usage, usageSource) : null,
31038
31551
  failure_category: failure.category,
31039
31552
  failure_code: failure.code,
31040
31553
  retryable: failure.retryable,
@@ -31101,7 +31614,7 @@ var init_runner = __esm({
31101
31614
  if (result2.requestedModel !== jobInput.requested_model || result2.requestedReasoningEffort !== jobInput.requested_reasoning_effort || !mapping?.effective_models.includes(result2.effectiveModel) || !jobInput.allowed_effective_reasoning_efforts.includes(result2.effectiveReasoningEffort)) {
31102
31615
  throw new InferenceHostRunnerError(
31103
31616
  "effective_model_mismatch",
31104
- "Codex effective model provenance did not match the immutable allowance."
31617
+ "The effective adapter model provenance did not match the immutable allowance."
31105
31618
  );
31106
31619
  }
31107
31620
  }
@@ -31110,16 +31623,17 @@ var init_runner = __esm({
31110
31623
  });
31111
31624
 
31112
31625
  // lib/inference-host/service.ts
31113
- import { spawn as spawn5 } from "node:child_process";
31626
+ import { spawn as spawn6 } from "node:child_process";
31114
31627
  import { randomUUID } from "node:crypto";
31115
31628
  import { createWriteStream, readFileSync } from "node:fs";
31116
- import { access as access3, chmod as chmod3, mkdir as mkdir3, readFile as readFile5, rm as rm4, writeFile as writeFile2 } from "node:fs/promises";
31629
+ import { access as access3, chmod as chmod3, mkdir as mkdir3, readFile as readFile5, rm as rm5, writeFile as writeFile2 } from "node:fs/promises";
31117
31630
  import { homedir as homedir2 } from "node:os";
31118
- import { dirname as dirname4, join as join5, resolve as resolve4 } from "node:path";
31631
+ import { dirname as dirname4, join as join6, resolve as resolve4 } from "node:path";
31119
31632
  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;
31120
31633
  var init_service = __esm({
31121
31634
  "lib/inference-host/service.ts"() {
31122
31635
  "use strict";
31636
+ init_durable_adapter();
31123
31637
  init_config();
31124
31638
  SERVICE_NAME = "VTX Macro Inference Host";
31125
31639
  SYSTEMD_UNIT = "vtx-inference-host.service";
@@ -31156,7 +31670,7 @@ var init_service = __esm({
31156
31670
  plistEscape = xmlEscape;
31157
31671
  systemdQuote = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%")}"`;
31158
31672
  defaultRunCommand = async (command, args) => await new Promise((resolvePromise) => {
31159
- const child = spawn5(command, [...args], {
31673
+ const child = spawn6(command, [...args], {
31160
31674
  windowsHide: true,
31161
31675
  stdio: ["ignore", "pipe", "pipe"]
31162
31676
  });
@@ -31208,14 +31722,15 @@ var init_service = __esm({
31208
31722
  throw new Error("Inference-host service worker is invalid.");
31209
31723
  }
31210
31724
  const record2 = value;
31211
- 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 || record2.max_concurrency !== null && (!Number.isSafeInteger(record2.max_concurrency) || Number(record2.max_concurrency) < 1) || record2.authenticated_account_email !== null && typeof record2.authenticated_account_email !== "string" || record2.authenticated_account_plan !== null && typeof record2.authenticated_account_plan !== "string") {
31725
+ if (typeof record2.instance_name !== "string" || !/^[a-z0-9](?:[a-z0-9-]{0,46}[a-z0-9])?$/u.test(record2.instance_name) || typeof record2.adapter !== "string" || !isDurableInferenceAdapterId(record2.adapter) || typeof record2.display_name !== "string" || !record2.display_name || record2.max_concurrency !== null && (!Number.isSafeInteger(record2.max_concurrency) || Number(record2.max_concurrency) < 1) || record2.authenticated_account_email !== null && typeof record2.authenticated_account_email !== "string" || record2.authenticated_account_identity !== void 0 && record2.authenticated_account_identity !== null && typeof record2.authenticated_account_identity !== "string" || record2.authenticated_account_plan !== null && typeof record2.authenticated_account_plan !== "string") {
31212
31726
  throw new Error("Inference-host service worker is invalid.");
31213
31727
  }
31214
31728
  return {
31215
31729
  instance_name: record2.instance_name,
31216
- adapter: "codex",
31730
+ adapter: record2.adapter,
31217
31731
  display_name: record2.display_name,
31218
31732
  max_concurrency: record2.max_concurrency === null ? null : Number(record2.max_concurrency),
31733
+ authenticated_account_identity: record2.authenticated_account_identity,
31219
31734
  authenticated_account_email: record2.authenticated_account_email,
31220
31735
  authenticated_account_plan: record2.authenticated_account_plan,
31221
31736
  runtime_environment: assertRuntimeEnvironment(record2.runtime_environment)
@@ -31582,8 +32097,8 @@ WantedBy=default.target
31582
32097
  }
31583
32098
  definitionPath() {
31584
32099
  if (this.platform === "win32") return `${this.config.supervisorStatePath}.service-task.xml`;
31585
- if (this.platform === "darwin") return join5(this.home, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
31586
- return join5(this.home, ".config", "systemd", "user", SYSTEMD_UNIT);
32100
+ if (this.platform === "darwin") return join6(this.home, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
32101
+ return join6(this.home, ".config", "systemd", "user", SYSTEMD_UNIT);
31587
32102
  }
31588
32103
  windowsLauncherPath() {
31589
32104
  return `${this.config.supervisorStatePath}.service-launcher.vbs`;
@@ -31629,8 +32144,10 @@ WantedBy=default.target
31629
32144
  }
31630
32145
  throw new Error("Background service did not reach an active state within 10 seconds.");
31631
32146
  }
31632
- async restoreRunningSupervisor(manifest) {
31633
- const restoredManifest = assertManifest({
32147
+ async restoreRunningSupervisor(manifest, preserveGeneration = false, targetInstanceName) {
32148
+ const previousRuntime = await readRuntimeAcrossAtomicReplacement(this.runtimePath()).catch(() => null);
32149
+ const previousRuntimeUpdatedAt = previousRuntime?.manifest_generation === manifest.generation ? Date.parse(previousRuntime.updated_at) : Number.NaN;
32150
+ const restoredManifest = preserveGeneration ? manifest : assertManifest({
31634
32151
  ...manifest,
31635
32152
  generation: randomUUID(),
31636
32153
  installed_at: this.now().toISOString()
@@ -31658,10 +32175,15 @@ ${result2.stderr}`)) {
31658
32175
  }
31659
32176
  await this.waitForManagerActive();
31660
32177
  if (!this.confirmInitialReadiness) return await this.status();
31661
- const runtime = await this.waitForManifestApplied(restoredManifest);
32178
+ const runtime = await this.waitForManifestApplied(
32179
+ restoredManifest,
32180
+ targetInstanceName,
32181
+ this.reconcileWaitAttempts,
32182
+ Number.isFinite(previousRuntimeUpdatedAt) ? previousRuntimeUpdatedAt : void 0
32183
+ );
31662
32184
  return await this.status(runtime);
31663
32185
  }
31664
- async waitForManifestApplied(manifest, targetInstanceName, maxAttempts = this.reconcileWaitAttempts) {
32186
+ async waitForManifestApplied(manifest, targetInstanceName, maxAttempts = this.reconcileWaitAttempts, minimumRuntimeUpdatedAtExclusive) {
31665
32187
  let consecutiveReadyObservations = 0;
31666
32188
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
31667
32189
  let runtime = null;
@@ -31672,7 +32194,9 @@ ${result2.stderr}`)) {
31672
32194
  throw error48;
31673
32195
  }
31674
32196
  }
31675
- if (runtime?.manifest_generation === manifest.generation) {
32197
+ const runtimeUpdatedAt = runtime ? Date.parse(runtime.updated_at) : Number.NaN;
32198
+ const runtimeIsFresh = minimumRuntimeUpdatedAtExclusive === void 0 || Number.isFinite(runtimeUpdatedAt) && runtimeUpdatedAt > minimumRuntimeUpdatedAtExclusive;
32199
+ if (runtime?.manifest_generation === manifest.generation && runtimeIsFresh) {
31676
32200
  const configuredNames = manifest.workers.map((worker) => worker.instance_name).sort();
31677
32201
  const runtimeNames = runtime.workers.map((worker) => worker.instance_name).sort();
31678
32202
  const exactWorkerSet = JSON.stringify(configuredNames) === JSON.stringify(runtimeNames);
@@ -31756,13 +32280,13 @@ ${cleanup.stderr}`)) {
31756
32280
  );
31757
32281
  }
31758
32282
  }
31759
- await rm4(this.definitionPath(), { force: true }).catch(() => void 0);
32283
+ await rm5(this.definitionPath(), { force: true }).catch(() => void 0);
31760
32284
  if (this.platform === "win32") {
31761
- await rm4(this.windowsLauncherPath(), { force: true }).catch(() => void 0);
32285
+ await rm5(this.windowsLauncherPath(), { force: true }).catch(() => void 0);
31762
32286
  }
31763
- await rm4(this.manifestPath(), { force: true }).catch(() => void 0);
31764
- await rm4(this.desiredPath(), { force: true }).catch(() => void 0);
31765
- await rm4(this.runtimePath(), { force: true }).catch(() => void 0);
32287
+ await rm5(this.manifestPath(), { force: true }).catch(() => void 0);
32288
+ await rm5(this.desiredPath(), { force: true }).catch(() => void 0);
32289
+ await rm5(this.runtimePath(), { force: true }).catch(() => void 0);
31766
32290
  if (this.platform === "linux") {
31767
32291
  await this.runCommand("systemctl", ["--user", "daemon-reload"]).catch(() => void 0);
31768
32292
  }
@@ -31858,18 +32382,31 @@ ${cleanup.stderr}`)) {
31858
32382
  }
31859
32383
  async installUnlocked(options) {
31860
32384
  const existing = await readInferenceHostServiceManifest(this.manifestPath());
32385
+ const currentWorker = existing?.workers.find(
32386
+ (worker) => worker.instance_name === this.config.instanceName
32387
+ );
32388
+ if (currentWorker && currentWorker.adapter !== options.adapter) {
32389
+ throw new Error(
32390
+ `Inference-host instance ${this.config.instanceName} is already bound to ${currentWorker.adapter}; install ${options.adapter} under a distinct --instance and VTX grant.`
32391
+ );
32392
+ }
31861
32393
  const normalizedEmail = options.authenticatedAccountEmail?.trim().toLowerCase() || null;
32394
+ const normalizedAccountIdentity = options.authenticatedAccountIdentity?.trim().toLowerCase() || null;
32395
+ const normalizedIdentity = normalizedAccountIdentity || normalizedEmail;
31862
32396
  const otherWorkers = existing?.workers.filter(
31863
32397
  (worker) => worker.instance_name !== this.config.instanceName
31864
32398
  ) ?? [];
31865
- if (otherWorkers.length > 0 && (!normalizedEmail || otherWorkers.some((worker) => !worker.authenticated_account_email))) {
32399
+ const peerAdapterWorkers = otherWorkers.filter(
32400
+ (worker) => worker.adapter === options.adapter
32401
+ );
32402
+ if (peerAdapterWorkers.length > 0 && (!normalizedIdentity || peerAdapterWorkers.some((worker) => !(worker.authenticated_account_identity || worker.authenticated_account_email)))) {
31866
32403
  throw new Error(
31867
- "Cannot prove that every installed instance uses a distinct ChatGPT subscription. Reinstall instances with live account identity before adding another."
32404
+ `Cannot prove that every installed ${options.adapter} instance uses a distinct subscription identity.`
31868
32405
  );
31869
32406
  }
31870
- if (normalizedEmail && otherWorkers.some((worker) => worker.authenticated_account_email?.trim().toLowerCase() === normalizedEmail)) {
32407
+ if (normalizedIdentity && peerAdapterWorkers.some((worker) => (worker.authenticated_account_identity || worker.authenticated_account_email)?.trim().toLowerCase() === normalizedIdentity)) {
31871
32408
  throw new Error(
31872
- "This ChatGPT subscription is already installed under another inference-host instance."
32409
+ `This ${options.adapter} subscription is already installed under another inference-host instance.`
31873
32410
  );
31874
32411
  }
31875
32412
  if (this.config.credentialFilePath && otherWorkers.some((worker) => {
@@ -31890,6 +32427,7 @@ ${cleanup.stderr}`)) {
31890
32427
  adapter: options.adapter,
31891
32428
  display_name: options.displayName,
31892
32429
  max_concurrency: maxConcurrency,
32430
+ ...normalizedAccountIdentity ? { authenticated_account_identity: normalizedAccountIdentity } : {},
31893
32431
  authenticated_account_email: normalizedEmail,
31894
32432
  authenticated_account_plan: options.authenticatedAccountPlan ?? null,
31895
32433
  runtime_environment: runtimeEnvironment(this.config, maxConcurrency)
@@ -31934,7 +32472,71 @@ ${result2.stderr}`)) {
31934
32472
  async stop() {
31935
32473
  return await this.withControlLock(async () => await this.stopUnlocked());
31936
32474
  }
31937
- async stopUnlocked() {
32475
+ /**
32476
+ * Quiesce the complete installed worker boundary while one exact instance's
32477
+ * durable Codex recovery evidence is reconciled. The service process lock is
32478
+ * held for the callback, so neither the manager nor a manual run-internal can
32479
+ * recreate workers during the same-boot process inspection.
32480
+ */
32481
+ async recoverInstance(instanceName, operation, options = {}) {
32482
+ return await this.withControlLock(async () => {
32483
+ const manifest = await readInferenceHostServiceManifest(this.manifestPath());
32484
+ if (!manifest) throw new Error("Inference-host service is not installed.");
32485
+ if (!manifest.workers.some((worker) => worker.instance_name === instanceName)) {
32486
+ throw new Error(`Inference-host instance ${instanceName} is not installed.`);
32487
+ }
32488
+ if (options.expectedManifestGeneration && options.expectedManifestGeneration !== manifest.generation) {
32489
+ throw new Error("Inference-host service manifest changed during recovery.");
32490
+ }
32491
+ const recordedDesiredRunning = await readInferenceHostServiceDesired(this.desiredPath());
32492
+ const desiredRunning = options.restoreDesiredRunning ?? recordedDesiredRunning;
32493
+ const context = { manifest, desiredRunning };
32494
+ await options.prepare?.(context);
32495
+ const before = await this.status();
32496
+ if (recordedDesiredRunning || before.manager_active) {
32497
+ await this.stopUnlocked(true);
32498
+ } else {
32499
+ await writeDesired(this.desiredPath(), false, this.now());
32500
+ }
32501
+ let serviceFence = null;
32502
+ let result2;
32503
+ let operationError = null;
32504
+ try {
32505
+ serviceFence = await this.acquireProcessLock(
32506
+ `${this.config.supervisorProcessLockPath}.service`
32507
+ );
32508
+ const stopped = await this.status();
32509
+ if (stopped.manager_active) {
32510
+ throw new Error("Background service manager became active during recovery quiescence.");
32511
+ }
32512
+ result2 = await operation(context);
32513
+ } catch (error48) {
32514
+ operationError = error48;
32515
+ } finally {
32516
+ await serviceFence?.release().catch((error48) => {
32517
+ operationError = operationError ?? error48;
32518
+ });
32519
+ }
32520
+ let restoredStatus;
32521
+ try {
32522
+ restoredStatus = desiredRunning ? await this.restoreRunningSupervisor(manifest, true, instanceName) : await this.status();
32523
+ } catch (restoreError) {
32524
+ if (operationError) {
32525
+ throw new Error(
32526
+ "Inference-host recovery failed and the previous supervisor could not be restored.",
32527
+ { cause: new AggregateError([operationError, restoreError]) }
32528
+ );
32529
+ }
32530
+ throw new Error(
32531
+ "Inference-host recovery completed, but the previous supervisor could not be restored.",
32532
+ { cause: restoreError }
32533
+ );
32534
+ }
32535
+ if (operationError) throw operationError;
32536
+ return { result: result2, status: restoredStatus };
32537
+ });
32538
+ }
32539
+ async stopUnlocked(preserveManifestGenerationOnFailure = false) {
31938
32540
  const manifest = await readInferenceHostServiceManifest(this.manifestPath());
31939
32541
  if (!manifest) {
31940
32542
  throw new Error("Inference-host service is not installed.");
@@ -31976,7 +32578,7 @@ ${result2.stderr}`)) {
31976
32578
  } catch (error48) {
31977
32579
  if (!restoreOnFailure) throw error48;
31978
32580
  try {
31979
- await this.restoreRunningSupervisor(manifest);
32581
+ await this.restoreRunningSupervisor(manifest, preserveManifestGenerationOnFailure);
31980
32582
  } catch (restoreError) {
31981
32583
  throw new Error(
31982
32584
  "Inference-host stop failed and the previous supervisor could not be restored.",
@@ -31999,12 +32601,14 @@ ${result2.stderr}`)) {
31999
32601
  manager_active: managerActive,
32000
32602
  manager: managerName(this.platform),
32001
32603
  manager_state: result2.exitCode === 0 ? output3 || "installed" : "not-installed",
32002
- adapter: manifest ? "codex" : null,
32604
+ adapter: manifest && new Set(manifest.workers.map((worker) => worker.adapter)).size === 1 ? manifest.workers[0]?.adapter ?? null : null,
32003
32605
  log_path: manifest?.log_path ?? this.logPath(),
32004
32606
  workers: manifest?.workers.map((worker) => ({
32005
32607
  instance_name: worker.instance_name,
32608
+ adapter: worker.adapter,
32006
32609
  display_name: worker.display_name,
32007
32610
  max_concurrency: worker.max_concurrency,
32611
+ authenticated_account_identity: worker.authenticated_account_identity ?? null,
32008
32612
  authenticated_account_email: worker.authenticated_account_email,
32009
32613
  authenticated_account_plan: worker.authenticated_account_plan,
32010
32614
  runtime_state: runtime?.manifest_generation === manifest.generation && managerActive ? runtime.workers.find((item) => item.instance_name === worker.instance_name)?.state ?? "unknown" : "unknown"
@@ -32068,12 +32672,12 @@ ${result2.stderr}`)) {
32068
32672
  ${result2.stderr}`)) {
32069
32673
  throw new Error(`Background service uninstall failed: ${result2.stderr.trim()}`);
32070
32674
  }
32071
- await rm4(this.definitionPath(), { force: true });
32072
- if (this.platform === "win32") await rm4(this.windowsLauncherPath(), { force: true });
32675
+ await rm5(this.definitionPath(), { force: true });
32676
+ if (this.platform === "win32") await rm5(this.windowsLauncherPath(), { force: true });
32073
32677
  if (this.platform === "linux") await this.runCommand("systemctl", ["--user", "daemon-reload"]);
32074
- await rm4(this.manifestPath(), { force: true });
32075
- await rm4(this.desiredPath(), { force: true });
32076
- await rm4(this.runtimePath(), { force: true });
32678
+ await rm5(this.manifestPath(), { force: true });
32679
+ await rm5(this.desiredPath(), { force: true });
32680
+ await rm5(this.runtimePath(), { force: true });
32077
32681
  return {
32078
32682
  installed: false,
32079
32683
  desired_running: false,
@@ -32102,6 +32706,8 @@ ${result2.stderr}`)) {
32102
32706
  "inference-host",
32103
32707
  "run",
32104
32708
  "--json",
32709
+ "--adapter",
32710
+ worker.adapter,
32105
32711
  "--display-name",
32106
32712
  worker.display_name
32107
32713
  ];
@@ -32143,7 +32749,7 @@ ${result2.stderr}`)) {
32143
32749
  `);
32144
32750
  }
32145
32751
  };
32146
- const child = spawn5(manifest.executable, args, {
32752
+ const child = spawn6(manifest.executable, args, {
32147
32753
  env: inferenceHostServiceChildEnvironment(worker.runtime_environment),
32148
32754
  windowsHide: true,
32149
32755
  stdio: ["pipe", "pipe", "pipe"]
@@ -32214,7 +32820,7 @@ ${result2.stderr}`)) {
32214
32820
  });
32215
32821
  const launch = options.runWorker ?? options.spawnChild ?? spawnInferenceHostServiceChild;
32216
32822
  await appendServiceLog(initialManifest.log_path, "service_supervisor_started", {
32217
- adapter: "codex",
32823
+ adapters: [...new Set(initialManifest.workers.map((worker) => worker.adapter))].sort(),
32218
32824
  instances: initialManifest.workers.map((worker) => worker.instance_name)
32219
32825
  });
32220
32826
  const workers = /* @__PURE__ */ new Map();
@@ -32433,10 +33039,10 @@ __export(cli_exports, {
32433
33039
  registerInferenceHostServiceControlInput: () => registerInferenceHostServiceControlInput,
32434
33040
  runInferenceHostCli: () => runInferenceHostCli
32435
33041
  });
32436
- import { randomUUID as randomUUID2 } from "node:crypto";
32437
- import { spawn as spawn6 } from "node:child_process";
32438
- import { lstat as lstat4, realpath as realpath4, rm as rm5 } from "node:fs/promises";
32439
- import { join as join6, resolve as resolve5 } from "node:path";
33042
+ import { createHash as createHash7, randomUUID as randomUUID2 } from "node:crypto";
33043
+ import { spawn as spawn7 } from "node:child_process";
33044
+ import { lstat as lstat4, realpath as realpath4, rm as rm6 } from "node:fs/promises";
33045
+ import { join as join7, resolve as resolve5 } from "node:path";
32440
33046
  async function runInferenceHostCli(argv2, env = process.env, dependencies = {}) {
32441
33047
  const warnings = [];
32442
33048
  try {
@@ -32444,6 +33050,21 @@ async function runInferenceHostCli(argv2, env = process.env, dependencies = {})
32444
33050
  return { exitCode: 0, stdout: INFERENCE_HOST_HELP, stderr: "" };
32445
33051
  }
32446
33052
  const parsed = parseInferenceHostArgs(argv2, env);
33053
+ if (parsed.forceRecovery && !(parsed.command === "service" && parsed.serviceAction === "recover")) {
33054
+ throw new Error("--force-recovery is accepted only by service recover.");
33055
+ }
33056
+ if (parsed.command === "service" && parsed.serviceAction === "recover") {
33057
+ if (!parsed.instanceFlagExplicit) {
33058
+ throw new Error(
33059
+ "Service recovery requires an explicit --instance NAME; an environment or default instance is not accepted."
33060
+ );
33061
+ }
33062
+ if (!parsed.forceRecovery) {
33063
+ throw new Error(
33064
+ `Service recovery is disruptive. Rerun with --instance ${parsed.instanceName} --force-recovery after checking service status and logs.`
33065
+ );
33066
+ }
33067
+ }
32447
33068
  const config2 = await resolveInferenceHostCommandConfig(parsed, env, dependencies);
32448
33069
  if (parsed.command === "login") {
32449
33070
  return await login(config2, parsed, env, dependencies, warnings);
@@ -32482,7 +33103,18 @@ async function runInferenceHostCli(argv2, env = process.env, dependencies = {})
32482
33103
  );
32483
33104
  }
32484
33105
  if (parsed.command === "service") {
32485
- return await serviceCommand(config2, parsed, env, dependencies, warnings);
33106
+ const mutationActions = /* @__PURE__ */ new Set(["install", "start", "stop", "recover", "uninstall"]);
33107
+ if (!parsed.serviceAction || !mutationActions.has(parsed.serviceAction)) {
33108
+ return await serviceCommand(config2, parsed, env, dependencies, warnings);
33109
+ }
33110
+ const recoveryCommandLock = await acquireInferenceHostProcessLock(
33111
+ serviceRecoveryCommandLockPath(config2)
33112
+ );
33113
+ try {
33114
+ return await serviceCommand(config2, parsed, env, dependencies, warnings);
33115
+ } finally {
33116
+ await recoveryCommandLock.release();
33117
+ }
32486
33118
  }
32487
33119
  if (parsed.command === "status") {
32488
33120
  return await localStatus(config2, parsed, dependencies, warnings);
@@ -32511,7 +33143,7 @@ async function runInferenceHostCli(argv2, env = process.env, dependencies = {})
32511
33143
  };
32512
33144
  }
32513
33145
  }
32514
- 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;
33146
+ var INFERENCE_HOST_CLI_VERSION, runtimeReceiptPath, codexRecoveryPath, codexGuardianReceiptRoot, revocationCheckpointPath, foregroundHostLockPath, credentialLifecycleLockPath, serviceRecoveryCommandLockPath, 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, preparePortableDurableAdapter, defaultRunPortableDurableAdapter, login, codexLogin, codexLogout, localStatus, doctor, cleanupLogin, runHost, defaultReadStdin, parseAgentStdin, agentOperationId, agentSession, agentConnect, agentRun, agentNext, agentComplete, agentFail, withAgentCommandLock, recoveryBackupPath, serviceRecoveryTransactionPath, readServiceRecoveryTransaction, assertResumableCodexRecoveryEvidence, recoverInstalledCodexService, serviceCommand, hasExplicitCredentialStoreConfiguration, credentialEnvironmentForIdentity, commandRequiresRetainedCredentialStore, commandUsesInstalledServiceCredentials, resolveInferenceHostCommandConfig;
32515
33147
  var init_cli = __esm({
32516
33148
  "lib/inference-host/cli.ts"() {
32517
33149
  "use strict";
@@ -32520,6 +33152,9 @@ var init_cli = __esm({
32520
33152
  init_agent_client();
32521
33153
  init_agent_state();
32522
33154
  init_codex_adapter();
33155
+ init_codex_recovery_process();
33156
+ init_copilot_adapter();
33157
+ init_durable_adapter();
32523
33158
  init_codex_app_server();
32524
33159
  init_codex_binary();
32525
33160
  init_config();
@@ -32536,6 +33171,7 @@ var init_cli = __esm({
32536
33171
  revocationCheckpointPath = (config2) => `${config2.statePath}.revoke.json`;
32537
33172
  foregroundHostLockPath = (config2) => `${config2.processLockPath}.foreground`;
32538
33173
  credentialLifecycleLockPath = (config2) => `${inferenceHostCredentialContextPath(config2)}.lock`;
33174
+ serviceRecoveryCommandLockPath = (config2) => `${config2.supervisorProcessLockPath}.service-recovery-command`;
32539
33175
  AGENT_HEARTBEAT_INTERVAL_MS = 3e3;
32540
33176
  AGENT_HEARTBEAT_MAX_RETRY_DELAY_MS = 9e3;
32541
33177
  retryableAgentHeartbeatError = (error48) => {
@@ -32601,7 +33237,7 @@ var init_cli = __esm({
32601
33237
  Commands:
32602
33238
  login Authorize the isolated VTX insights:inference grant
32603
33239
  codex-login Sign the automated Codex host into a ChatGPT subscription
32604
- run Run the automated Codex inference host in the foreground
33240
+ run Run a supported durable inference adapter in the foreground
32605
33241
  agent-connect Advertise a model from a compatible agent harness
32606
33242
  agent-run Keep an agent-driven host online in the foreground
32607
33243
  agent-next Claim the next exact VTX inference request
@@ -32609,7 +33245,7 @@ Commands:
32609
33245
  agent-fail Submit one truthful agent failure from stdin
32610
33246
  service Install and control the durable background host
32611
33247
  status Inspect local host and credential state
32612
- doctor Verify credentials, Codex, and private runtime state
33248
+ doctor Verify credentials, provider runtime, and private state
32613
33249
  logout Remove local VTX host state without revoking the grant
32614
33250
  revoke Revoke the VTX grant and remove local host state
32615
33251
  codex-logout Remove the automated host's dedicated Codex login
@@ -32617,7 +33253,8 @@ Commands:
32617
33253
  Common options:
32618
33254
  --json Emit machine-readable JSON
32619
33255
  --instance NAME Target an isolated local subscription instance (default: default)
32620
- --max-concurrency N Optional positive-integer Codex slot limit (default: unlimited)
33256
+ --adapter ID Durable adapter: codex or copilot
33257
+ --max-concurrency N Optional positive-integer local slot limit (default: unlimited)
32621
33258
  --help, -h Show this help
32622
33259
 
32623
33260
  If the OS credential store cannot retain the VTX grant, set
@@ -32631,8 +33268,10 @@ Durable service:
32631
33268
  vtx inference-host login --instance codex-2
32632
33269
  vtx inference-host codex-login --instance codex-2
32633
33270
  vtx inference-host service install --instance codex-2
33271
+ vtx inference-host service install --adapter copilot --instance copilot-1
32634
33272
  vtx inference-host service uninstall --instance codex-2
32635
- vtx inference-host service <start|stop|status|logs|uninstall>
33273
+ vtx inference-host service recover --instance codex-1 --force-recovery
33274
+ vtx inference-host service <start|stop|status|logs|recover|uninstall>
32636
33275
  `;
32637
33276
  parseHostConcurrency = (raw, label) => {
32638
33277
  const value = Number(raw);
@@ -32648,6 +33287,7 @@ Durable service:
32648
33287
  let maxConcurrency = configuredConcurrency ? parseHostConcurrency(configuredConcurrency, "VTX_INFERENCE_HOST_MAX_CONCURRENCY") : null;
32649
33288
  let instanceName = String(env.VTX_INFERENCE_HOST_INSTANCE || "default").trim();
32650
33289
  let instanceExplicit = Boolean(String(env.VTX_INFERENCE_HOST_INSTANCE || "").trim());
33290
+ let instanceFlagExplicit = false;
32651
33291
  let displayName = String(env.VTX_INFERENCE_HOST_DISPLAY_NAME || "").trim() || "Codex subscription host";
32652
33292
  let displayNameExplicit = Boolean(String(env.VTX_INFERENCE_HOST_DISPLAY_NAME || "").trim());
32653
33293
  let adapter = null;
@@ -32657,6 +33297,7 @@ Durable service:
32657
33297
  let waitSeconds = 50;
32658
33298
  let lines = 100;
32659
33299
  let serviceManifestPath = null;
33300
+ let forceRecovery = false;
32660
33301
  const positionals = [];
32661
33302
  for (let index = 0; index < argv2.length; index += 1) {
32662
33303
  const argument = argv2[index];
@@ -32668,6 +33309,10 @@ Durable service:
32668
33309
  once = true;
32669
33310
  continue;
32670
33311
  }
33312
+ if (argument === "--force-recovery") {
33313
+ forceRecovery = true;
33314
+ continue;
33315
+ }
32671
33316
  if (argument === "--max-concurrency") {
32672
33317
  const raw = argv2[index + 1];
32673
33318
  if (!raw) throw new Error("--max-concurrency requires a value.");
@@ -32706,6 +33351,7 @@ Durable service:
32706
33351
  if (argument === "--instance") {
32707
33352
  instanceName = raw.trim();
32708
33353
  instanceExplicit = true;
33354
+ instanceFlagExplicit = true;
32709
33355
  }
32710
33356
  index += 1;
32711
33357
  continue;
@@ -32728,6 +33374,8 @@ Durable service:
32728
33374
  }
32729
33375
  if (command === "agent-connect" && !displayNameExplicit) {
32730
33376
  displayName = "Agent-driven inference host";
33377
+ } else if (adapter && isDurableInferenceAdapterId(adapter) && !displayNameExplicit) {
33378
+ displayName = `${durableAdapterDisplayName(adapter)} subscription host`;
32731
33379
  }
32732
33380
  return {
32733
33381
  command,
@@ -32736,6 +33384,7 @@ Durable service:
32736
33384
  maxConcurrency,
32737
33385
  instanceName,
32738
33386
  instanceExplicit,
33387
+ instanceFlagExplicit,
32739
33388
  displayName,
32740
33389
  adapter,
32741
33390
  modelId,
@@ -32744,13 +33393,14 @@ Durable service:
32744
33393
  waitSeconds,
32745
33394
  lines,
32746
33395
  serviceAction,
32747
- serviceManifestPath
33396
+ serviceManifestPath,
33397
+ forceRecovery
32748
33398
  };
32749
33399
  };
32750
33400
  defaultOpenBrowser = (url2) => {
32751
33401
  const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
32752
33402
  const args = process.platform === "win32" ? ["/c", "start", "", url2] : [url2];
32753
- const child = spawn6(command, args, {
33403
+ const child = spawn7(command, args, {
32754
33404
  detached: true,
32755
33405
  stdio: "ignore",
32756
33406
  windowsHide: true
@@ -32870,7 +33520,7 @@ Durable service:
32870
33520
  return raw !== null;
32871
33521
  };
32872
33522
  inspectCodexAuthentication = async (config2) => {
32873
- const path = join6(config2.codexHomePath, "auth.json");
33523
+ const path = join7(config2.codexHomePath, "auth.json");
32874
33524
  try {
32875
33525
  const before = await lstat4(path);
32876
33526
  const canonical = await realpath4(path);
@@ -32888,7 +33538,7 @@ Durable service:
32888
33538
  runtimeReceiptPath(config2),
32889
33539
  "Inference host runtime receipt file"
32890
33540
  );
32891
- await rm5(codexGuardianReceiptRoot(config2), { recursive: true, force: true });
33541
+ await rm6(codexGuardianReceiptRoot(config2), { recursive: true, force: true });
32892
33542
  await clearInferencePrivateFile(
32893
33543
  codexRecoveryPath(config2),
32894
33544
  "Codex attempt recovery file"
@@ -32914,10 +33564,14 @@ Durable service:
32914
33564
  codexRecoveryPath(config2),
32915
33565
  "Codex attempt recovery file"
32916
33566
  );
33567
+ const serviceRecoveryPresent = await fileExistsPrivately(
33568
+ `${config2.supervisorStatePath}.service-recovery.json`,
33569
+ "Inference-host service recovery transaction"
33570
+ );
32917
33571
  const revocationCheckpointPresent = await readRevocationCheckpoint(config2) !== null;
32918
33572
  const agentAttemptPresent = await readInferenceAgentAttemptState(config2.statePath) !== null;
32919
33573
  const agentNextRecoveryPresent = await readInferenceAgentNextState(config2.statePath) !== null;
32920
- if (pendingAttempts > 0 || codexRecoveryPresent || revocationCheckpointPresent || agentAttemptPresent || agentNextRecoveryPresent) {
33574
+ if (pendingAttempts > 0 || codexRecoveryPresent || serviceRecoveryPresent || revocationCheckpointPresent || agentAttemptPresent || agentNextRecoveryPresent) {
32921
33575
  throw new Error(
32922
33576
  "Inference host logout refused because attempt recovery is still pending (Codex, agent-driven, or revocation recovery). Rerun agent-next, complete or fail the active attempt, run the automated host to reconcile it, or resume revoke."
32923
33577
  );
@@ -32966,6 +33620,49 @@ Durable service:
32966
33620
  signal: options.signal
32967
33621
  }).run();
32968
33622
  };
33623
+ preparePortableDurableAdapter = async (adapterId, signal) => {
33624
+ const adapter = new CopilotSubscriptionAdapter();
33625
+ return { adapter, preflight: await adapter.preflight(signal) };
33626
+ };
33627
+ defaultRunPortableDurableAdapter = async (options) => {
33628
+ const dependencies = createDefaultInferenceHostRunnerDependencies({
33629
+ apiUrl: options.config.apiUrl,
33630
+ statePath: options.config.statePath,
33631
+ processLockPath: options.config.processLockPath,
33632
+ credentialStore: options.credentialStore,
33633
+ codexAdapter: options.adapter,
33634
+ envelopePublicKey: (credential) => externalInferenceEnvelopePublicKey(credential.x25519_private_key),
33635
+ clientVersion: INFERENCE_HOST_CLI_VERSION,
33636
+ runtimeReceiptPath: runtimeReceiptPath(options.config)
33637
+ });
33638
+ const borrowedProcessLock = {
33639
+ path: options.processLock.path,
33640
+ ownerToken: options.processLock.ownerToken,
33641
+ release: async () => void 0
33642
+ };
33643
+ return await new InferenceHostRunner({
33644
+ ...dependencies,
33645
+ acquireProcessLock: async () => borrowedProcessLock
33646
+ }, {
33647
+ adapterId: options.preflight.adapterId,
33648
+ usageSource: options.preflight.adapterId,
33649
+ structuredOutput: false,
33650
+ sameAttemptRecovery: options.preflight.sameAttemptRecovery,
33651
+ instanceName: options.config.instanceName,
33652
+ displayName: options.displayName,
33653
+ authenticatedAccountIdentity: options.preflight.authenticatedAccountIdentity,
33654
+ authenticatedAccountEmail: options.preflight.authenticatedAccountEmail,
33655
+ authenticatedAccountPlan: options.preflight.authenticatedAccountPlan,
33656
+ codexRateLimits: options.preflight.rateLimits,
33657
+ codexModelCapabilities: options.preflight.modelCapabilities,
33658
+ protocolVersion: EXTERNAL_INFERENCE_CONTRACT_VERSION,
33659
+ adapterRuntimeVersion: options.preflight.runtimeVersion,
33660
+ maxConcurrency: options.maxConcurrency,
33661
+ once: options.once,
33662
+ emitDiagnosticEvent: options.emitDiagnosticEvent,
33663
+ signal: options.signal
33664
+ }).run();
33665
+ };
32969
33666
  login = async (config2, parsed, env, dependencies, warnings) => {
32970
33667
  const credentialLock = await acquireInferenceHostProcessLock(
32971
33668
  credentialLifecycleLockPath(config2)
@@ -33232,6 +33929,10 @@ Waiting for approval...
33232
33929
  codexRecoveryPath(config2),
33233
33930
  "Codex attempt recovery file"
33234
33931
  );
33932
+ const serviceRecoveryPresent = await fileExistsPrivately(
33933
+ `${config2.supervisorStatePath}.service-recovery.json`,
33934
+ "Inference-host service recovery transaction"
33935
+ );
33235
33936
  const agentAttempt = await readInferenceAgentAttemptState(config2.statePath);
33236
33937
  const agentNextRecoveryPresent = await readInferenceAgentNextState(config2.statePath) !== null;
33237
33938
  return {
@@ -33260,6 +33961,7 @@ Waiting for approval...
33260
33961
  } : null,
33261
33962
  agent_next_recovery_present: agentNextRecoveryPresent,
33262
33963
  codex_recovery_present: recoveryPresent,
33964
+ service_recovery_present: serviceRecoveryPresent,
33263
33965
  codex_auth: codexAuthentication,
33264
33966
  source: "local_only"
33265
33967
  }, parsed.json),
@@ -33308,6 +34010,35 @@ Waiting for approval...
33308
34010
  detail: error48 instanceof Error ? error48.message : "unavailable"
33309
34011
  });
33310
34012
  }
34013
+ const adapterId = parsed.adapter ?? "codex";
34014
+ if (!isDurableInferenceAdapterId(adapterId)) {
34015
+ checks.push({ name: "provider_runtime", ok: false, detail: "unsupported-adapter" });
34016
+ } else if (adapterId !== "codex") {
34017
+ try {
34018
+ const prepared = await (dependencies.prepareDurableAdapter ?? preparePortableDurableAdapter)(
34019
+ adapterId
34020
+ );
34021
+ checks.push({
34022
+ name: "provider_runtime",
34023
+ ok: true,
34024
+ detail: `${adapterId}:${prepared.preflight.runtimeVersion}:${prepared.preflight.modelCapabilities.length}-models`
34025
+ });
34026
+ await prepared.adapter.close?.();
34027
+ } catch (error48) {
34028
+ checks.push({
34029
+ name: "provider_runtime",
34030
+ ok: false,
34031
+ detail: error48 instanceof Error ? error48.message : "unavailable"
34032
+ });
34033
+ }
34034
+ const healthy2 = checks.every((check2) => check2.ok);
34035
+ return {
34036
+ exitCode: healthy2 ? 0 : 1,
34037
+ stdout: render({ healthy: healthy2, adapter: adapterId, checks, source: "local_only" }, parsed.json),
34038
+ stderr: warnings.length > 0 ? `${warnings.join("\n")}
34039
+ ` : ""
34040
+ };
34041
+ }
33311
34042
  try {
33312
34043
  const binary = await (dependencies.resolveBinary ?? resolvePinnedCodexBinary)(env);
33313
34044
  checks.push({
@@ -33524,6 +34255,37 @@ Waiting for approval...
33524
34255
  if (await store.readRecovery?.()) {
33525
34256
  throw new Error("Inference host credential recovery is required before the runner can start.");
33526
34257
  }
34258
+ const adapterId = parsed.adapter ?? "codex";
34259
+ if (!isDurableInferenceAdapterId(adapterId)) {
34260
+ throw new Error(`Adapter ${adapterId} does not have a supported durable integration.`);
34261
+ }
34262
+ const emitDiagnosticEvent = parsed.json ? (event) => (dependencies.emitStdout ?? ((text) => {
34263
+ process.stdout.write(text);
34264
+ }))(`${JSON.stringify(event)}
34265
+ `) : void 0;
34266
+ if (adapterId !== "codex") {
34267
+ const prepared = await (dependencies.prepareDurableAdapter ?? preparePortableDurableAdapter)(
34268
+ adapterId,
34269
+ cancellation.signal
34270
+ );
34271
+ const summary = await (dependencies.runPortableDurableAdapter ?? defaultRunPortableDurableAdapter)({
34272
+ config: config2,
34273
+ credentialStore: store,
34274
+ ...prepared,
34275
+ displayName: parsed.displayName,
34276
+ maxConcurrency: parsed.maxConcurrency,
34277
+ once: parsed.once,
34278
+ processLock,
34279
+ emitDiagnosticEvent,
34280
+ signal: cancellation.signal
34281
+ });
34282
+ return {
34283
+ exitCode: 0,
34284
+ stdout: render({ status: "stopped", ...summary }, parsed.json),
34285
+ stderr: warnings.length > 0 ? `${warnings.join("\n")}
34286
+ ` : ""
34287
+ };
34288
+ }
33527
34289
  const verifiedBinary = await (dependencies.resolveBinary ?? resolvePinnedCodexBinary)(env);
33528
34290
  cancellation.signal.throwIfAborted();
33529
34291
  await reconcileCodexAttemptRecovery({
@@ -33554,10 +34316,7 @@ Waiting for approval...
33554
34316
  once: parsed.once,
33555
34317
  env,
33556
34318
  processLock,
33557
- emitDiagnosticEvent: parsed.json ? (event) => (dependencies.emitStdout ?? ((text) => {
33558
- process.stdout.write(text);
33559
- }))(`${JSON.stringify(event)}
33560
- `) : void 0,
34319
+ emitDiagnosticEvent,
33561
34320
  signal: cancellation.signal
33562
34321
  });
33563
34322
  return {
@@ -34016,10 +34775,303 @@ Waiting for approval...
34016
34775
  await lock2.release();
34017
34776
  }
34018
34777
  };
34778
+ recoveryBackupPath = (config2, transactionId) => `${codexRecoveryPath(config2)}.service-recovery-${transactionId}.backup`;
34779
+ serviceRecoveryTransactionPath = (config2) => `${config2.supervisorStatePath}.service-recovery.json`;
34780
+ readServiceRecoveryTransaction = async (config2) => {
34781
+ const raw = await readInferencePrivateFile(
34782
+ serviceRecoveryTransactionPath(config2),
34783
+ "Inference-host service recovery transaction"
34784
+ );
34785
+ if (raw === null) return null;
34786
+ let value;
34787
+ try {
34788
+ value = JSON.parse(raw);
34789
+ } catch {
34790
+ throw new Error("Inference-host service recovery transaction is not valid JSON.");
34791
+ }
34792
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
34793
+ throw new Error("Inference-host service recovery transaction is invalid.");
34794
+ }
34795
+ const record2 = value;
34796
+ const exactKeys = [
34797
+ "schema_version",
34798
+ "phase",
34799
+ "transaction_id",
34800
+ "instance_name",
34801
+ "manifest_generation",
34802
+ "restore_desired_running",
34803
+ "recovery_backup_path",
34804
+ "recovery_backup_sha256",
34805
+ "cleanup_confirmed_attempt_ids",
34806
+ "started_at"
34807
+ ];
34808
+ if (Object.keys(record2).sort().join("\0") !== exactKeys.sort().join("\0") || record2.schema_version !== "vtx_inference_service_recovery_v1" || record2.phase !== "prepared" && record2.phase !== "reconciled" || typeof record2.transaction_id !== "string" || !/^[0-9a-f-]{36}$/u.test(record2.transaction_id) || typeof record2.instance_name !== "string" || typeof record2.manifest_generation !== "string" || typeof record2.restore_desired_running !== "boolean" || typeof record2.recovery_backup_path !== "string" || typeof record2.recovery_backup_sha256 !== "string" || !/^[0-9a-f]{64}$/u.test(record2.recovery_backup_sha256) || !Array.isArray(record2.cleanup_confirmed_attempt_ids) || record2.cleanup_confirmed_attempt_ids.some(
34809
+ (attemptId) => typeof attemptId !== "string" || attemptId.length < 1 || attemptId.length > 256
34810
+ ) || new Set(record2.cleanup_confirmed_attempt_ids).size !== record2.cleanup_confirmed_attempt_ids.length || [...record2.cleanup_confirmed_attempt_ids].sort().join("\0") !== record2.cleanup_confirmed_attempt_ids.join("\0") || typeof record2.started_at !== "string" || !Number.isFinite(Date.parse(record2.started_at)) || new Date(record2.started_at).toISOString() !== record2.started_at) {
34811
+ throw new Error("Inference-host service recovery transaction is invalid.");
34812
+ }
34813
+ if (resolve5(record2.recovery_backup_path) !== resolve5(recoveryBackupPath(config2, record2.transaction_id))) {
34814
+ throw new Error("Inference-host service recovery backup path is invalid.");
34815
+ }
34816
+ return record2;
34817
+ };
34818
+ assertResumableCodexRecoveryEvidence = (backupAttempts, currentAttempts, cleanupConfirmedAttemptIds, phase) => {
34819
+ const backupIds = Object.keys(backupAttempts).sort();
34820
+ const currentIds = Object.keys(currentAttempts).sort();
34821
+ const backupIdSet = new Set(backupIds);
34822
+ const cleanupConfirmedIdSet = new Set(cleanupConfirmedAttemptIds);
34823
+ if (backupIds.length === 0 || currentIds.some((attemptId) => !backupIdSet.has(attemptId)) || cleanupConfirmedAttemptIds.some((attemptId) => !backupIdSet.has(attemptId)) || phase === "reconciled" && backupIds.some((attemptId) => !cleanupConfirmedIdSet.has(attemptId))) {
34824
+ throw new Error("Codex recovery evidence does not match the retained service recovery backup.");
34825
+ }
34826
+ for (const attemptId of backupIds) {
34827
+ const original = backupAttempts[attemptId];
34828
+ const current = currentAttempts[attemptId];
34829
+ if (!current) {
34830
+ if (!cleanupConfirmedIdSet.has(attemptId)) {
34831
+ throw new Error("Codex recovery evidence does not match the retained service recovery backup.");
34832
+ }
34833
+ continue;
34834
+ }
34835
+ if (cleanupConfirmedIdSet.has(attemptId) && !original.cleanupConfirmed && !(current.processState === "terminated" && current.cleanupConfirmed)) {
34836
+ throw new Error("Codex recovery evidence regressed after confirmed cleanup.");
34837
+ }
34838
+ const {
34839
+ processState: _originalProcessState,
34840
+ cleanupConfirmed: _originalCleanupConfirmed,
34841
+ ...originalStable
34842
+ } = original;
34843
+ const {
34844
+ processState: _currentProcessState,
34845
+ cleanupConfirmed: _currentCleanupConfirmed,
34846
+ ...currentStable
34847
+ } = current;
34848
+ if (JSON.stringify(originalStable) !== JSON.stringify(currentStable) || !(current.processState === original.processState && current.cleanupConfirmed === original.cleanupConfirmed) && !(current.processState === "terminated" && current.cleanupConfirmed)) {
34849
+ throw new Error("Codex recovery evidence changed outside an exact cleanup transition.");
34850
+ }
34851
+ }
34852
+ };
34853
+ recoverInstalledCodexService = async (config2, parsed, env, dependencies, manager) => {
34854
+ if (!parsed.instanceFlagExplicit) {
34855
+ throw new Error(
34856
+ "Service recovery requires an explicit --instance NAME; an environment or default instance is not accepted."
34857
+ );
34858
+ }
34859
+ if (!parsed.forceRecovery) {
34860
+ throw new Error(
34861
+ `Service recovery is disruptive. Rerun with --instance ${config2.instanceName} --force-recovery after checking service status and logs.`
34862
+ );
34863
+ }
34864
+ const credentialLock = await acquireInferenceHostProcessLock(
34865
+ credentialLifecycleLockPath(config2)
34866
+ );
34867
+ try {
34868
+ const installedManifest = await readInferenceHostServiceManifest(
34869
+ `${config2.supervisorStatePath}.service.json`
34870
+ );
34871
+ const installedWorker = installedManifest?.workers.find(
34872
+ (worker) => worker.instance_name === config2.instanceName
34873
+ );
34874
+ if (installedWorker && installedWorker.adapter !== "codex") {
34875
+ throw new Error(
34876
+ `Guarded same-attempt recovery is not supported for ${installedWorker.adapter}; interrupted attempts remain quarantined as outcome_unknown.`
34877
+ );
34878
+ }
34879
+ const recoveryStore = new FileCodexAttemptRecoveryStore(codexRecoveryPath(config2));
34880
+ const existingTransaction = await readServiceRecoveryTransaction(config2);
34881
+ if (existingTransaction && existingTransaction.instance_name !== config2.instanceName) {
34882
+ throw new Error(
34883
+ `Service recovery for instance ${existingTransaction.instance_name} must finish before another instance can recover.`
34884
+ );
34885
+ }
34886
+ const initialAttempts = await recoveryStore.loadAll();
34887
+ const pendingAttempts = Object.values(initialAttempts).filter(
34888
+ (checkpoint) => !checkpoint.cleanupConfirmed
34889
+ );
34890
+ if (pendingAttempts.length === 0 && !existingTransaction) {
34891
+ const status = await manager.status();
34892
+ return {
34893
+ exitCode: 0,
34894
+ stdout: render({
34895
+ status: "service_recovery_not_needed",
34896
+ instance_name: config2.instanceName,
34897
+ recovery_needed: false,
34898
+ recovered_attempt_count: 0,
34899
+ outcome_unknown_attempt_count: 0,
34900
+ desired_running: status.desired_running,
34901
+ manager_active: status.manager_active,
34902
+ worker_runtime_state: status.workers.find(
34903
+ (worker) => worker.instance_name === config2.instanceName
34904
+ )?.runtime_state ?? "unknown"
34905
+ }, parsed.json),
34906
+ stderr: ""
34907
+ };
34908
+ }
34909
+ const recoveryRaw = await readInferencePrivateFile(
34910
+ codexRecoveryPath(config2),
34911
+ "Codex attempt recovery file"
34912
+ );
34913
+ if (recoveryRaw === null && existingTransaction?.phase !== "reconciled") {
34914
+ throw new Error("Codex recovery evidence changed before service recovery began.");
34915
+ }
34916
+ const transactionId = existingTransaction?.transaction_id ?? randomUUID2();
34917
+ const backupPath = existingTransaction?.recovery_backup_path ?? recoveryBackupPath(config2, transactionId);
34918
+ if (!existingTransaction) {
34919
+ await writeAtomicInferencePrivateFile(backupPath, recoveryRaw);
34920
+ }
34921
+ const backupRaw = await readInferencePrivateFile(
34922
+ backupPath,
34923
+ "Codex service recovery backup"
34924
+ );
34925
+ const backupSha256 = backupRaw === null ? null : createHash7("sha256").update(backupRaw).digest("hex");
34926
+ if (backupSha256 === null || existingTransaction && backupSha256 !== existingTransaction.recovery_backup_sha256) {
34927
+ throw new Error("Codex service recovery backup is missing or changed.");
34928
+ }
34929
+ const backupAttempts = await new FileCodexAttemptRecoveryStore(backupPath).loadAll();
34930
+ assertResumableCodexRecoveryEvidence(
34931
+ backupAttempts,
34932
+ initialAttempts,
34933
+ existingTransaction?.cleanup_confirmed_attempt_ids ?? [],
34934
+ existingTransaction?.phase ?? "prepared"
34935
+ );
34936
+ const recoverySha256 = recoveryRaw === null ? null : createHash7("sha256").update(recoveryRaw).digest("hex");
34937
+ const unresolvedAttemptIds = Object.keys(backupAttempts).filter((attemptId) => !existingTransaction?.cleanup_confirmed_attempt_ids.includes(attemptId) && !initialAttempts[attemptId]?.cleanupConfirmed);
34938
+ const binary = unresolvedAttemptIds.length > 0 ? await (dependencies.resolveBinary ?? resolvePinnedCodexBinary)(env) : null;
34939
+ let activeTransaction = existingTransaction;
34940
+ let completed = false;
34941
+ try {
34942
+ const recovered = await manager.recoverInstance(config2.instanceName, async ({ manifest }) => {
34943
+ const workerLocks = [];
34944
+ try {
34945
+ for (const worker of [...manifest.workers].sort(
34946
+ (left, right) => left.instance_name.localeCompare(right.instance_name)
34947
+ )) {
34948
+ const workerConfig = resolveInferenceHostConfig({
34949
+ ...env,
34950
+ ...worker.runtime_environment,
34951
+ VTX_INFERENCE_HOST_INSTANCE: worker.instance_name
34952
+ });
34953
+ workerLocks.push(await acquireInferenceHostProcessLock(
34954
+ foregroundHostLockPath(workerConfig)
34955
+ ));
34956
+ workerLocks.push(await acquireInferenceHostProcessLock(workerConfig.processLockPath));
34957
+ }
34958
+ try {
34959
+ if (!activeTransaction) {
34960
+ throw new Error("Inference-host service recovery transaction was not prepared.");
34961
+ }
34962
+ const attempts = await recoveryStore.loadAll();
34963
+ assertResumableCodexRecoveryEvidence(
34964
+ backupAttempts,
34965
+ attempts,
34966
+ activeTransaction.cleanup_confirmed_attempt_ids,
34967
+ activeTransaction.phase
34968
+ );
34969
+ const unresolved = Object.keys(backupAttempts).filter((attemptId) => !activeTransaction.cleanup_confirmed_attempt_ids.includes(attemptId) && !attempts[attemptId]?.cleanupConfirmed);
34970
+ if (unresolved.length === 0) return 0;
34971
+ const currentRaw = await readInferencePrivateFile(
34972
+ codexRecoveryPath(config2),
34973
+ "Codex attempt recovery file"
34974
+ );
34975
+ if (currentRaw === null || recoverySha256 === null || createHash7("sha256").update(currentRaw).digest("hex") !== recoverySha256) {
34976
+ throw new Error("Codex recovery evidence changed while the service was stopping.");
34977
+ }
34978
+ const proof = await (dependencies.proveCodexQuiescence ?? proveCodexSameBootQuiescence)({
34979
+ attempts,
34980
+ confirmNoManagedCodexProcesses: async () => await (dependencies.confirmNoManagedCodexProcesses ?? confirmNoVtxManagedCodexProcesses)({ pinnedBinaryPath: binary.path })
34981
+ });
34982
+ return await (dependencies.reconcileCodexRecovery ?? reconcileCodexAttemptRecovery)({
34983
+ recoveryHooks: recoveryStore,
34984
+ codexHome: config2.codexHomePath,
34985
+ guardianReceiptRoot: codexGuardianReceiptRoot(config2),
34986
+ sameBootQuiescenceProof: proof
34987
+ });
34988
+ } finally {
34989
+ if (!activeTransaction) {
34990
+ throw new Error("Inference-host service recovery transaction was not prepared.");
34991
+ }
34992
+ const progressedAttempts = await recoveryStore.loadAll();
34993
+ assertResumableCodexRecoveryEvidence(
34994
+ backupAttempts,
34995
+ progressedAttempts,
34996
+ activeTransaction.cleanup_confirmed_attempt_ids,
34997
+ activeTransaction.phase
34998
+ );
34999
+ const priorConfirmedIds = new Set(activeTransaction.cleanup_confirmed_attempt_ids);
35000
+ const cleanupConfirmedAttemptIds = Object.keys(backupAttempts).filter((attemptId) => priorConfirmedIds.has(attemptId) || progressedAttempts[attemptId]?.cleanupConfirmed).sort();
35001
+ activeTransaction = {
35002
+ ...activeTransaction,
35003
+ phase: cleanupConfirmedAttemptIds.length === Object.keys(backupAttempts).length ? "reconciled" : "prepared",
35004
+ cleanup_confirmed_attempt_ids: cleanupConfirmedAttemptIds
35005
+ };
35006
+ await writeAtomicInferencePrivateFile(
35007
+ serviceRecoveryTransactionPath(config2),
35008
+ `${JSON.stringify(activeTransaction, null, 2)}
35009
+ `
35010
+ );
35011
+ }
35012
+ } finally {
35013
+ for (const lock2 of workerLocks.reverse()) await lock2.release();
35014
+ }
35015
+ }, {
35016
+ restoreDesiredRunning: existingTransaction?.restore_desired_running,
35017
+ expectedManifestGeneration: existingTransaction?.manifest_generation,
35018
+ prepare: existingTransaction ? void 0 : async ({ manifest, desiredRunning }) => {
35019
+ activeTransaction = {
35020
+ schema_version: "vtx_inference_service_recovery_v1",
35021
+ phase: "prepared",
35022
+ transaction_id: transactionId,
35023
+ instance_name: config2.instanceName,
35024
+ manifest_generation: manifest.generation,
35025
+ restore_desired_running: desiredRunning,
35026
+ recovery_backup_path: backupPath,
35027
+ recovery_backup_sha256: backupSha256,
35028
+ cleanup_confirmed_attempt_ids: [],
35029
+ started_at: (/* @__PURE__ */ new Date()).toISOString()
35030
+ };
35031
+ await writeAtomicInferencePrivateFile(
35032
+ serviceRecoveryTransactionPath(config2),
35033
+ `${JSON.stringify(activeTransaction, null, 2)}
35034
+ `
35035
+ );
35036
+ }
35037
+ });
35038
+ completed = true;
35039
+ const outcomeUnknownAttemptCount = Object.values(backupAttempts).filter(
35040
+ (checkpoint) => checkpoint.dispatchOutcome === "outcome_unknown"
35041
+ ).length;
35042
+ return {
35043
+ exitCode: 0,
35044
+ stdout: render({
35045
+ status: "service_recovered",
35046
+ instance_name: config2.instanceName,
35047
+ recovery_needed: true,
35048
+ recovered_attempt_count: recovered.result,
35049
+ outcome_unknown_attempt_count: outcomeUnknownAttemptCount,
35050
+ desired_running: recovered.status.desired_running,
35051
+ manager_active: recovered.status.manager_active,
35052
+ worker_runtime_state: recovered.status.workers.find(
35053
+ (worker) => worker.instance_name === config2.instanceName
35054
+ )?.runtime_state ?? "unknown"
35055
+ }, parsed.json),
35056
+ stderr: ""
35057
+ };
35058
+ } finally {
35059
+ if (completed) {
35060
+ await clearInferencePrivateFile(
35061
+ serviceRecoveryTransactionPath(config2),
35062
+ "Inference-host service recovery transaction"
35063
+ );
35064
+ await rm6(backupPath, { force: true });
35065
+ }
35066
+ }
35067
+ } finally {
35068
+ await credentialLock.release();
35069
+ }
35070
+ };
34019
35071
  serviceCommand = async (config2, parsed, env, dependencies, warnings) => {
34020
35072
  const action = parsed.serviceAction;
34021
35073
  if (!action) {
34022
- throw new Error("Usage: vtx inference-host service <install|start|stop|status|logs|uninstall>.");
35074
+ throw new Error("Usage: vtx inference-host service <install|start|stop|status|logs|recover|uninstall>.");
34023
35075
  }
34024
35076
  if (action === "run-internal") {
34025
35077
  if (!parsed.serviceManifestPath) throw new Error("Internal service manifest path is required.");
@@ -34046,17 +35098,34 @@ Waiting for approval...
34046
35098
  await serviceLock.release();
34047
35099
  }
34048
35100
  }
35101
+ if (parsed.forceRecovery && action !== "recover") {
35102
+ throw new Error("--force-recovery is accepted only by service recover.");
35103
+ }
34049
35104
  const manager = dependencies.createServiceManager?.(
34050
35105
  config2,
34051
35106
  dependencies.serviceDependencies
34052
35107
  ) ?? new InferenceHostServiceManager(config2, dependencies.serviceDependencies);
35108
+ if (action === "recover") {
35109
+ return await recoverInstalledCodexService(
35110
+ config2,
35111
+ parsed,
35112
+ env,
35113
+ dependencies,
35114
+ manager
35115
+ );
35116
+ }
34053
35117
  if (action === "install") {
34054
35118
  const credentialLock = await acquireInferenceHostProcessLock(
34055
35119
  credentialLifecycleLockPath(config2)
34056
35120
  );
34057
35121
  try {
35122
+ if (await readServiceRecoveryTransaction(config2)) {
35123
+ throw new Error(
35124
+ "A guarded service recovery transaction must finish before installing a subscription."
35125
+ );
35126
+ }
34058
35127
  const adapter = parsed.adapter || "codex";
34059
- if (adapter !== "codex") {
35128
+ if (!isDurableInferenceAdapterId(adapter)) {
34060
35129
  throw new Error(
34061
35130
  `Adapter ${adapter} does not have a supported durable service integration. Use foreground agent-run instead.`
34062
35131
  );
@@ -34068,7 +35137,31 @@ Waiting for approval...
34068
35137
  if (!credential) throw new Error("Inference-host credential is missing. Run login again.");
34069
35138
  assertCredentialMatchesState2(state, credential);
34070
35139
  if (parsed.modelId || parsed.modelLabel || parsed.reasoningEffort) {
34071
- throw new Error("Automated Codex service does not accept agent model or effort options.");
35140
+ throw new Error("Durable services discover their exact model and effort catalog from the authenticated runtime.");
35141
+ }
35142
+ if (adapter !== "codex") {
35143
+ const prepared = await (dependencies.prepareDurableAdapter ?? preparePortableDurableAdapter)(
35144
+ adapter
35145
+ );
35146
+ try {
35147
+ const status2 = await manager.install({
35148
+ adapter,
35149
+ displayName: parsed.displayName,
35150
+ maxConcurrency: parsed.maxConcurrency,
35151
+ authenticatedAccountIdentity: prepared.preflight.authenticatedAccountIdentity,
35152
+ authenticatedAccountEmail: prepared.preflight.authenticatedAccountEmail,
35153
+ authenticatedAccountPlan: prepared.preflight.authenticatedAccountPlan
35154
+ });
35155
+ await writeInferenceHostCredentialContext(config2);
35156
+ return {
35157
+ exitCode: 0,
35158
+ stdout: render({ status: "service_installed", ...status2 }, parsed.json),
35159
+ stderr: warnings.length > 0 ? `${warnings.join("\n")}
35160
+ ` : ""
35161
+ };
35162
+ } finally {
35163
+ await prepared.adapter.close?.();
35164
+ }
34072
35165
  }
34073
35166
  const auth = await inspectCodexAuthentication(config2);
34074
35167
  if (!auth.present || !auth.private) {
@@ -34112,9 +35205,15 @@ Waiting for approval...
34112
35205
  };
34113
35206
  }
34114
35207
  if (action === "start") {
35208
+ if (await readServiceRecoveryTransaction(config2)) {
35209
+ throw new Error("A guarded service recovery transaction must finish before starting the service.");
35210
+ }
34115
35211
  return { exitCode: 0, stdout: render({ status: "service_started", ...await manager.start() }, parsed.json), stderr: "" };
34116
35212
  }
34117
35213
  if (action === "stop") {
35214
+ if (await readServiceRecoveryTransaction(config2)) {
35215
+ throw new Error("A guarded service recovery transaction must finish before stopping the service.");
35216
+ }
34118
35217
  return { exitCode: 0, stdout: render({ status: "service_stopped", ...await manager.stop() }, parsed.json), stderr: "" };
34119
35218
  }
34120
35219
  if (action === "status") {
@@ -34127,6 +35226,11 @@ Waiting for approval...
34127
35226
  credentialLifecycleLockPath(config2)
34128
35227
  );
34129
35228
  try {
35229
+ if (await readServiceRecoveryTransaction(config2)) {
35230
+ throw new Error(
35231
+ "A guarded service recovery transaction must finish before uninstalling a subscription."
35232
+ );
35233
+ }
34130
35234
  if (parsed.instanceExplicit) {
34131
35235
  const retained = await readInferenceHostCredentialContext(config2);
34132
35236
  const retainedIdentity = retained ? resolveInferenceHostConfig({
@@ -34145,7 +35249,7 @@ Waiting for approval...
34145
35249
  await credentialLock.release();
34146
35250
  }
34147
35251
  }
34148
- throw new Error("Usage: vtx inference-host service <install|start|stop|status|logs|uninstall>.");
35252
+ throw new Error("Usage: vtx inference-host service <install|start|stop|status|logs|recover|uninstall>.");
34149
35253
  };
34150
35254
  hasExplicitCredentialStoreConfiguration = (env) => Boolean(
34151
35255
  String(env.VTX_INFERENCE_HOST_CREDENTIAL_STORE || "").trim() || String(env.VTX_INFERENCE_HOST_CREDENTIAL_FILE || "").trim()
@@ -34167,8 +35271,8 @@ Waiting for approval...
34167
35271
  }
34168
35272
  throw new Error("Inference host credential store identity is invalid.");
34169
35273
  };
34170
- commandRequiresRetainedCredentialStore = (parsed) => parsed.command === "logout" || parsed.command === "revoke" || parsed.command === "service" && parsed.instanceExplicit && (parsed.serviceAction === "install" || parsed.serviceAction === "uninstall");
34171
- 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;
35274
+ commandRequiresRetainedCredentialStore = (parsed) => parsed.command === "logout" || parsed.command === "revoke" || parsed.command === "service" && parsed.instanceExplicit && (parsed.serviceAction === "install" || parsed.serviceAction === "recover" || parsed.serviceAction === "uninstall");
35275
+ 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 === "recover" || parsed.command === "service" && parsed.serviceAction === "uninstall" && parsed.instanceExplicit;
34172
35276
  resolveInferenceHostCommandConfig = async (parsed, env, dependencies) => {
34173
35277
  const selectedEnv = {
34174
35278
  ...env,
@@ -34191,6 +35295,9 @@ Waiting for approval...
34191
35295
  const installedEnvironment = manifest?.workers.find(
34192
35296
  (worker) => worker.instance_name === baseConfig.instanceName
34193
35297
  )?.runtime_environment;
35298
+ if (parsed.command === "service" && parsed.serviceAction === "recover" && !installedEnvironment) {
35299
+ throw new Error(`Inference-host instance ${baseConfig.instanceName} is not installed.`);
35300
+ }
34194
35301
  const persistedContext = installedEnvironment ? null : await readInferenceHostCredentialContext(baseConfig);
34195
35302
  const revocationCheckpoint = !installedEnvironment && !persistedContext && parsed.command === "revoke" ? await readRevocationCheckpoint(baseConfig) : null;
34196
35303
  const retainedEnvironment = installedEnvironment ?? (persistedContext ? {
@@ -34221,11 +35328,11 @@ Waiting for approval...
34221
35328
  });
34222
35329
 
34223
35330
  // lib/agent-core/config.ts
34224
- import { mkdir as mkdir5, readFile as readFile6, rm as rm6, writeFile as writeFile3 } from "node:fs/promises";
34225
- import { dirname as dirname5, join as join7 } from "node:path";
35331
+ import { mkdir as mkdir5, readFile as readFile6, rm as rm7, writeFile as writeFile3 } from "node:fs/promises";
35332
+ import { dirname as dirname5, join as join8 } from "node:path";
34226
35333
  import { homedir as homedir3 } from "node:os";
34227
35334
  function defaultBaseDir() {
34228
- return join7(homedir3(), ".vtx");
35335
+ return join8(homedir3(), ".vtx");
34229
35336
  }
34230
35337
  function resolveAgentCliConfig(env = process.env) {
34231
35338
  const baseDir = String(env.VTX_HOME || "").trim() || defaultBaseDir();
@@ -34233,8 +35340,8 @@ function resolveAgentCliConfig(env = process.env) {
34233
35340
  const parsedProfile = rawProfile ? Number(rawProfile) : NaN;
34234
35341
  return {
34235
35342
  apiUrl: String(env.VTX_API_URL || "http://localhost:8000").replace(/\/+$/, ""),
34236
- tokenPath: String(env.VTX_TOKEN_PATH || "").trim() || join7(baseDir, "token.json"),
34237
- statePath: String(env.VTX_RUNTIME_STATE_PATH || "").trim() || join7(baseDir, "runtime-state.json"),
35343
+ tokenPath: String(env.VTX_TOKEN_PATH || "").trim() || join8(baseDir, "token.json"),
35344
+ statePath: String(env.VTX_RUNTIME_STATE_PATH || "").trim() || join8(baseDir, "runtime-state.json"),
34238
35345
  runtimeDeviceId: String(env.VTX_RUNTIME_DEVICE_ID || "").trim() || null,
34239
35346
  activeProfileId: Number.isFinite(parsedProfile) && parsedProfile > 0 ? parsedProfile : null,
34240
35347
  outputJson: String(env.VTX_OUTPUT || "").trim().toLowerCase() === "json"
@@ -34265,7 +35372,7 @@ async function writeStoredAgentAuth(path, auth) {
34265
35372
  `, { encoding: "utf8", mode: 384 });
34266
35373
  }
34267
35374
  async function clearStoredAgentAuth(path) {
34268
- await rm6(path, { force: true });
35375
+ await rm7(path, { force: true });
34269
35376
  }
34270
35377
  async function readRuntimeState(path) {
34271
35378
  try {
@@ -34298,7 +35405,7 @@ async function writeRuntimeState(path, state) {
34298
35405
  `, { encoding: "utf8", mode: 384 });
34299
35406
  }
34300
35407
  async function clearRuntimeState(path) {
34301
- await rm6(path, { force: true });
35408
+ await rm7(path, { force: true });
34302
35409
  }
34303
35410
  var init_config2 = __esm({
34304
35411
  "lib/agent-core/config.ts"() {
@@ -38718,7 +39825,7 @@ var init_utils = __esm({
38718
39825
  });
38719
39826
 
38720
39827
  // node_modules/ethers/lib.esm/crypto/crypto.js
38721
- import { createHash as createHash6, createHmac, pbkdf2Sync, randomBytes as randomBytes5 } from "crypto";
39828
+ import { createHash as createHash8, createHmac, pbkdf2Sync, randomBytes as randomBytes5 } from "crypto";
38722
39829
  var init_crypto2 = __esm({
38723
39830
  "node_modules/ethers/lib.esm/crypto/crypto.js"() {
38724
39831
  }
@@ -39932,10 +41039,10 @@ var init_sha22 = __esm({
39932
41039
  init_crypto2();
39933
41040
  init_utils();
39934
41041
  _sha256 = function(data) {
39935
- return createHash6("sha256").update(data).digest();
41042
+ return createHash8("sha256").update(data).digest();
39936
41043
  };
39937
41044
  _sha512 = function(data) {
39938
- return createHash6("sha512").update(data).digest();
41045
+ return createHash8("sha512").update(data).digest();
39939
41046
  };
39940
41047
  __sha256 = _sha256;
39941
41048
  __sha512 = _sha512;
@@ -52112,7 +53219,7 @@ __export(vtx_exports, {
52112
53219
  runVtxCli: () => runVtxCli
52113
53220
  });
52114
53221
  import { randomUUID as randomUUID6 } from "node:crypto";
52115
- import { spawn as spawn7 } from "node:child_process";
53222
+ import { spawn as spawn8 } from "node:child_process";
52116
53223
  function render2(value, json2) {
52117
53224
  if (json2) {
52118
53225
  return `${JSON.stringify(value, null, 2)}
@@ -52142,7 +53249,7 @@ function openBrowser(url2) {
52142
53249
  const platform = process.platform;
52143
53250
  const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
52144
53251
  const args = platform === "win32" ? ["/c", "start", "", url2] : [url2];
52145
- const child = spawn7(command, args, { detached: true, stdio: "ignore" });
53252
+ const child = spawn8(command, args, { detached: true, stdio: "ignore" });
52146
53253
  child.unref();
52147
53254
  }
52148
53255
  function parseFlags(args) {