@vtxmacro/cli 2026.8.39 → 2026.8.40
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.
- package/README.md +20 -0
- package/bin/vtx.js +646 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,11 @@ The VTX host verifies that native runtime's version and digest before use; it
|
|
|
15
15
|
does not borrow a Codex binary from PATH or an editor extension.
|
|
16
16
|
For Windows-login auto-start, install and configure the CLI from native Windows;
|
|
17
17
|
a CLI installed only inside WSL cannot start the WSL virtual machine at login.
|
|
18
|
+
An npm update does not replace code already loaded by a running durable service.
|
|
19
|
+
After updating, check `vtx inference-host service status --json`. If the service
|
|
20
|
+
was already desired-running, run `vtx inference-host service stop` followed by
|
|
21
|
+
`vtx inference-host service start`, then verify status and logs. Leave an
|
|
22
|
+
intentionally stopped service stopped until you want to run it.
|
|
18
23
|
|
|
19
24
|
## Configure
|
|
20
25
|
|
|
@@ -86,6 +91,21 @@ distinct. Use `service uninstall --instance <name>` to remove one worker;
|
|
|
86
91
|
unqualified `service uninstall` removes the whole supervisor. The legacy
|
|
87
92
|
unqualified host commands continue to target `default`.
|
|
88
93
|
|
|
94
|
+
On native Windows, if service status or logs show that guarded recovery is
|
|
95
|
+
blocking one installed subscription, recover that exact instance without rebooting:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
vtx inference-host service recover --instance <name> --force-recovery --json
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Recovery is intentionally noninteractive and requires both `--instance` and
|
|
102
|
+
`--force-recovery`; `--json` changes only the output format. The shared
|
|
103
|
+
supervisor briefly quiesces so every installed worker can stop cooperatively,
|
|
104
|
+
then VTX restores its previous desired state and the same peer subscriptions.
|
|
105
|
+
The command never kills an arbitrary process, logs either account out, changes
|
|
106
|
+
VTX profile settings, or controls a Trader. If any package-owned VTX automation
|
|
107
|
+
is still live, recovery fails closed and preserves its evidence.
|
|
108
|
+
|
|
89
109
|
For each VTX profile and lane, **Subscription #1** is tried first and later
|
|
90
110
|
compatible subscriptions follow in the saved Account cascade order.
|
|
91
111
|
Quota/credits exhaustion, unusable authentication, and exhausted recoverable
|
package/bin/vtx.js
CHANGED
|
@@ -38,7 +38,7 @@ var init_agent_cli_release = __esm({
|
|
|
38
38
|
"agent-cli-release.json"() {
|
|
39
39
|
agent_cli_release_default = {
|
|
40
40
|
package_name: "@vtxmacro/cli",
|
|
41
|
-
package_version: "2026.8.
|
|
41
|
+
package_version: "2026.8.40",
|
|
42
42
|
codex_package_name: "@openai/codex",
|
|
43
43
|
codex_version: "0.147.0",
|
|
44
44
|
platforms: {
|
|
@@ -21473,10 +21473,10 @@ import {
|
|
|
21473
21473
|
realpath as realpath3,
|
|
21474
21474
|
rm as rm3
|
|
21475
21475
|
} from "node:fs/promises";
|
|
21476
|
-
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
21476
|
+
import { createHash as createHash4, randomBytes as randomBytes3 } from "node:crypto";
|
|
21477
21477
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
21478
21478
|
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;
|
|
21479
|
+
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
21480
|
var init_codex_adapter = __esm({
|
|
21481
21481
|
"lib/inference-host/codex-adapter.ts"() {
|
|
21482
21482
|
"use strict";
|
|
@@ -21487,6 +21487,34 @@ var init_codex_adapter = __esm({
|
|
|
21487
21487
|
MAX_PROMPT_BYTES2 = 3e5;
|
|
21488
21488
|
CODEX_MODEL_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/u;
|
|
21489
21489
|
CODEX_REASONING_EFFORT_PATTERN = /^[a-z][a-z0-9_-]{0,31}$/u;
|
|
21490
|
+
codexRecoveryCheckpointProcessFingerprint = (checkpoint) => createHash4("sha256").update(JSON.stringify({
|
|
21491
|
+
attemptId: checkpoint.attemptId,
|
|
21492
|
+
processToken: checkpoint.processToken,
|
|
21493
|
+
processReceiptPath: checkpoint.processReceiptPath,
|
|
21494
|
+
processState: checkpoint.processState,
|
|
21495
|
+
bootIdentity: checkpoint.bootIdentity ?? null,
|
|
21496
|
+
cleanupConfirmed: checkpoint.cleanupConfirmed
|
|
21497
|
+
})).digest("hex");
|
|
21498
|
+
proveCodexSameBootQuiescence = async (options) => {
|
|
21499
|
+
const readBootIdentity = options.readBootIdentity ?? readInferenceSystemBootIdentity;
|
|
21500
|
+
const bootIdentityBefore = await readBootIdentity();
|
|
21501
|
+
await options.confirmNoManagedCodexProcesses();
|
|
21502
|
+
const bootIdentityAfter = await readBootIdentity();
|
|
21503
|
+
if (bootIdentityAfter !== bootIdentityBefore) {
|
|
21504
|
+
throw new Error("System boot identity changed during Codex recovery process inspection.");
|
|
21505
|
+
}
|
|
21506
|
+
return {
|
|
21507
|
+
schemaVersion: "vtx_codex_same_boot_quiescence_v1",
|
|
21508
|
+
bootIdentity: bootIdentityAfter,
|
|
21509
|
+
observedAtMs: (options.now ?? Date.now)(),
|
|
21510
|
+
checkpointFingerprints: Object.fromEntries(
|
|
21511
|
+
Object.entries(options.attempts).map(([attemptId, checkpoint]) => [
|
|
21512
|
+
attemptId,
|
|
21513
|
+
codexRecoveryCheckpointProcessFingerprint(checkpoint)
|
|
21514
|
+
])
|
|
21515
|
+
)
|
|
21516
|
+
};
|
|
21517
|
+
};
|
|
21490
21518
|
tokenUsageReceiptSchema = external_exports.strictObject({
|
|
21491
21519
|
inputTokens: external_exports.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
21492
21520
|
cachedInputTokens: external_exports.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
|
|
@@ -21959,11 +21987,20 @@ var init_codex_adapter = __esm({
|
|
|
21959
21987
|
throw new Error("Codex cross-boot recovery receipt ownership is unconfirmed.");
|
|
21960
21988
|
}
|
|
21961
21989
|
} else {
|
|
21962
|
-
await
|
|
21963
|
-
|
|
21964
|
-
|
|
21965
|
-
|
|
21966
|
-
|
|
21990
|
+
const receipt = await readCodexGuardianReceipt(guardian.receiptPath);
|
|
21991
|
+
if (receipt === null && options.sameBootQuiescenceProof) {
|
|
21992
|
+
const proof = options.sameBootQuiescenceProof;
|
|
21993
|
+
const expectedFingerprint = proof.checkpointFingerprints[checkpoint.attemptId];
|
|
21994
|
+
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)) {
|
|
21995
|
+
throw new Error("Codex same-boot recovery process fence is invalid or stale.");
|
|
21996
|
+
}
|
|
21997
|
+
} else {
|
|
21998
|
+
await confirmGuardianTerminatedForRecovery(
|
|
21999
|
+
checkpoint,
|
|
22000
|
+
guardian,
|
|
22001
|
+
options.deadlineMs ?? 5e3
|
|
22002
|
+
);
|
|
22003
|
+
}
|
|
21967
22004
|
}
|
|
21968
22005
|
assertRecoveryResourceScope(checkpoint);
|
|
21969
22006
|
await removeRecoveredThread(checkpoint, options.codexHome);
|
|
@@ -22607,11 +22644,147 @@ var init_codex_adapter = __esm({
|
|
|
22607
22644
|
}
|
|
22608
22645
|
});
|
|
22609
22646
|
|
|
22647
|
+
// lib/inference-host/codex-recovery-process.ts
|
|
22648
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
22649
|
+
var normalizedWindowsPath, classifyCodexRecoveryProcesses, WINDOWS_PROCESS_QUERY, queryWindowsProcesses, confirmNoVtxManagedCodexProcesses;
|
|
22650
|
+
var init_codex_recovery_process = __esm({
|
|
22651
|
+
"lib/inference-host/codex-recovery-process.ts"() {
|
|
22652
|
+
"use strict";
|
|
22653
|
+
normalizedWindowsPath = (value) => value.replaceAll("\\", "/").replaceAll(/\/+$/gu, "").toLowerCase();
|
|
22654
|
+
classifyCodexRecoveryProcesses = (processes, pinnedBinaryPath) => {
|
|
22655
|
+
const pinned = normalizedWindowsPath(pinnedBinaryPath);
|
|
22656
|
+
let managedProcessCount = 0;
|
|
22657
|
+
let ambiguousProcessCount = 0;
|
|
22658
|
+
for (const process3 of processes) {
|
|
22659
|
+
const name = process3.name.trim().toLowerCase();
|
|
22660
|
+
if (name === "codex.exe") {
|
|
22661
|
+
if (!process3.executable_path || !process3.command_line || !process3.creation_date) {
|
|
22662
|
+
ambiguousProcessCount += 1;
|
|
22663
|
+
continue;
|
|
22664
|
+
}
|
|
22665
|
+
const executable = normalizedWindowsPath(process3.executable_path);
|
|
22666
|
+
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/");
|
|
22667
|
+
if (packageOwned) managedProcessCount += 1;
|
|
22668
|
+
continue;
|
|
22669
|
+
}
|
|
22670
|
+
if (name !== "node.exe") continue;
|
|
22671
|
+
if (!process3.executable_path || !process3.command_line || !process3.creation_date) {
|
|
22672
|
+
ambiguousProcessCount += 1;
|
|
22673
|
+
continue;
|
|
22674
|
+
}
|
|
22675
|
+
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")) {
|
|
22676
|
+
managedProcessCount += 1;
|
|
22677
|
+
}
|
|
22678
|
+
}
|
|
22679
|
+
return {
|
|
22680
|
+
managed_process_count: managedProcessCount,
|
|
22681
|
+
ambiguous_process_count: ambiguousProcessCount
|
|
22682
|
+
};
|
|
22683
|
+
};
|
|
22684
|
+
WINDOWS_PROCESS_QUERY = String.raw`
|
|
22685
|
+
$ErrorActionPreference = 'Stop'
|
|
22686
|
+
$items = @(Get-CimInstance Win32_Process | Where-Object { $_.Name -in @('node.exe', 'codex.exe') } | ForEach-Object {
|
|
22687
|
+
[ordered]@{
|
|
22688
|
+
process_id = [int]$_.ProcessId
|
|
22689
|
+
name = [string]$_.Name
|
|
22690
|
+
executable_path = if ($null -eq $_.ExecutablePath) { $null } else { [string]$_.ExecutablePath }
|
|
22691
|
+
command_line = if ($null -eq $_.CommandLine) { $null } else { [string]$_.CommandLine }
|
|
22692
|
+
creation_date = if ($null -eq $_.CreationDate) { $null } else { [string]$_.CreationDate }
|
|
22693
|
+
}
|
|
22694
|
+
})
|
|
22695
|
+
[Console]::Out.Write(($items | ConvertTo-Json -Depth 3 -Compress))
|
|
22696
|
+
`;
|
|
22697
|
+
queryWindowsProcesses = async () => {
|
|
22698
|
+
const args = [
|
|
22699
|
+
"-NoLogo",
|
|
22700
|
+
"-NoProfile",
|
|
22701
|
+
"-NonInteractive",
|
|
22702
|
+
"-EncodedCommand",
|
|
22703
|
+
Buffer.from(WINDOWS_PROCESS_QUERY, "utf16le").toString("base64")
|
|
22704
|
+
];
|
|
22705
|
+
const result2 = await new Promise(
|
|
22706
|
+
(resolvePromise, reject) => {
|
|
22707
|
+
const child = spawn5("powershell.exe", args, {
|
|
22708
|
+
windowsHide: true,
|
|
22709
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
22710
|
+
});
|
|
22711
|
+
let stdout = "";
|
|
22712
|
+
let stderr = "";
|
|
22713
|
+
child.stdout.on("data", (chunk) => {
|
|
22714
|
+
stdout = `${stdout}${chunk.toString("utf8")}`.slice(-4e6);
|
|
22715
|
+
});
|
|
22716
|
+
child.stderr.on("data", (chunk) => {
|
|
22717
|
+
stderr = `${stderr}${chunk.toString("utf8")}`.slice(-4096);
|
|
22718
|
+
});
|
|
22719
|
+
child.once("error", reject);
|
|
22720
|
+
child.once("close", (code) => {
|
|
22721
|
+
if (code !== 0) {
|
|
22722
|
+
reject(new Error("Windows process ownership inventory failed."));
|
|
22723
|
+
return;
|
|
22724
|
+
}
|
|
22725
|
+
resolvePromise({ exitCode: code ?? 1, stdout });
|
|
22726
|
+
});
|
|
22727
|
+
}
|
|
22728
|
+
);
|
|
22729
|
+
if (result2.exitCode !== 0) throw new Error("Windows process ownership inventory failed.");
|
|
22730
|
+
let parsed;
|
|
22731
|
+
try {
|
|
22732
|
+
parsed = result2.stdout.trim() ? JSON.parse(result2.stdout) : [];
|
|
22733
|
+
} catch {
|
|
22734
|
+
throw new Error("Windows process ownership inventory was invalid.");
|
|
22735
|
+
}
|
|
22736
|
+
const entries = Array.isArray(parsed) ? parsed : [parsed];
|
|
22737
|
+
return entries.map((entry) => {
|
|
22738
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
22739
|
+
throw new Error("Windows process ownership inventory was invalid.");
|
|
22740
|
+
}
|
|
22741
|
+
const record2 = entry;
|
|
22742
|
+
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") {
|
|
22743
|
+
throw new Error("Windows process ownership inventory was invalid.");
|
|
22744
|
+
}
|
|
22745
|
+
return {
|
|
22746
|
+
process_id: Number(record2.process_id),
|
|
22747
|
+
name: record2.name,
|
|
22748
|
+
executable_path: record2.executable_path,
|
|
22749
|
+
command_line: record2.command_line,
|
|
22750
|
+
creation_date: record2.creation_date
|
|
22751
|
+
};
|
|
22752
|
+
});
|
|
22753
|
+
};
|
|
22754
|
+
confirmNoVtxManagedCodexProcesses = async (options) => {
|
|
22755
|
+
if ((options.platform ?? process.platform) !== "win32") {
|
|
22756
|
+
throw new Error("Same-boot service recovery is currently supported only on Windows.");
|
|
22757
|
+
}
|
|
22758
|
+
const queryProcesses = options.queryProcesses ?? queryWindowsProcesses;
|
|
22759
|
+
const sleep4 = options.sleep ?? (async (milliseconds) => {
|
|
22760
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
|
|
22761
|
+
});
|
|
22762
|
+
for (let observation = 0; observation < 2; observation += 1) {
|
|
22763
|
+
const classification = classifyCodexRecoveryProcesses(
|
|
22764
|
+
await queryProcesses(),
|
|
22765
|
+
options.pinnedBinaryPath
|
|
22766
|
+
);
|
|
22767
|
+
if (classification.ambiguous_process_count > 0) {
|
|
22768
|
+
throw new Error(
|
|
22769
|
+
"Codex process ownership could not be inspected completely; recovery evidence was preserved."
|
|
22770
|
+
);
|
|
22771
|
+
}
|
|
22772
|
+
if (classification.managed_process_count > 0) {
|
|
22773
|
+
throw new Error(
|
|
22774
|
+
"VTX-managed Codex automation is still running; recovery evidence was preserved."
|
|
22775
|
+
);
|
|
22776
|
+
}
|
|
22777
|
+
if (observation === 0) await sleep4(250);
|
|
22778
|
+
}
|
|
22779
|
+
};
|
|
22780
|
+
}
|
|
22781
|
+
});
|
|
22782
|
+
|
|
22610
22783
|
// lib/inference-host/crypto.ts
|
|
22611
22784
|
import {
|
|
22612
22785
|
createCipheriv,
|
|
22613
22786
|
createDecipheriv,
|
|
22614
|
-
createHash as
|
|
22787
|
+
createHash as createHash5,
|
|
22615
22788
|
createPrivateKey,
|
|
22616
22789
|
createPublicKey,
|
|
22617
22790
|
diffieHellman,
|
|
@@ -22650,7 +22823,7 @@ var init_crypto = __esm({
|
|
|
22650
22823
|
const aad = envelopeAadSchema.parse(input);
|
|
22651
22824
|
return Buffer.from(JSON.stringify(canonicalize(aad)), "utf8");
|
|
22652
22825
|
};
|
|
22653
|
-
externalInferenceSha256 = (value) =>
|
|
22826
|
+
externalInferenceSha256 = (value) => createHash5("sha256").update(value).digest("hex");
|
|
22654
22827
|
decodeCanonicalBase64Url = (value, fieldName, expectedBytes) => {
|
|
22655
22828
|
if (!/^[A-Za-z0-9_-]+$/u.test(value)) {
|
|
22656
22829
|
throw new ExternalInferenceEnvelopeError("invalid_encoding", `${fieldName} is invalid.`);
|
|
@@ -22749,7 +22922,7 @@ var init_crypto = __esm({
|
|
|
22749
22922
|
return Buffer.from(hkdfSync(
|
|
22750
22923
|
"sha256",
|
|
22751
22924
|
root,
|
|
22752
|
-
|
|
22925
|
+
createHash5("sha256").update(aadBytes).digest(),
|
|
22753
22926
|
info,
|
|
22754
22927
|
32
|
|
22755
22928
|
));
|
|
@@ -29163,7 +29336,7 @@ var require_ajv = __commonJS({
|
|
|
29163
29336
|
});
|
|
29164
29337
|
|
|
29165
29338
|
// lib/inference-host/runner.ts
|
|
29166
|
-
import { createHash as
|
|
29339
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
29167
29340
|
function createDefaultInferenceHostRunnerDependencies(options) {
|
|
29168
29341
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
29169
29342
|
return {
|
|
@@ -29471,7 +29644,7 @@ var init_runner = __esm({
|
|
|
29471
29644
|
attempts
|
|
29472
29645
|
};
|
|
29473
29646
|
};
|
|
29474
|
-
sha256 = (value) =>
|
|
29647
|
+
sha256 = (value) => createHash6("sha256").update(value, "utf8").digest("hex");
|
|
29475
29648
|
stableOperationId = (kind, parts) => `${kind}_${sha256(JSON.stringify(parts)).slice(0, 48)}`;
|
|
29476
29649
|
buildAttemptStartRequest = (claim, attemptId, startedAt) => attemptStartRequestSchema.parse({
|
|
29477
29650
|
schema_version: "external_inference_attempt_start_v1",
|
|
@@ -31110,7 +31283,7 @@ var init_runner = __esm({
|
|
|
31110
31283
|
});
|
|
31111
31284
|
|
|
31112
31285
|
// lib/inference-host/service.ts
|
|
31113
|
-
import { spawn as
|
|
31286
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
31114
31287
|
import { randomUUID } from "node:crypto";
|
|
31115
31288
|
import { createWriteStream, readFileSync } from "node:fs";
|
|
31116
31289
|
import { access as access3, chmod as chmod3, mkdir as mkdir3, readFile as readFile5, rm as rm4, writeFile as writeFile2 } from "node:fs/promises";
|
|
@@ -31156,7 +31329,7 @@ var init_service = __esm({
|
|
|
31156
31329
|
plistEscape = xmlEscape;
|
|
31157
31330
|
systemdQuote = (value) => `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%")}"`;
|
|
31158
31331
|
defaultRunCommand = async (command, args) => await new Promise((resolvePromise) => {
|
|
31159
|
-
const child =
|
|
31332
|
+
const child = spawn6(command, [...args], {
|
|
31160
31333
|
windowsHide: true,
|
|
31161
31334
|
stdio: ["ignore", "pipe", "pipe"]
|
|
31162
31335
|
});
|
|
@@ -31629,8 +31802,10 @@ WantedBy=default.target
|
|
|
31629
31802
|
}
|
|
31630
31803
|
throw new Error("Background service did not reach an active state within 10 seconds.");
|
|
31631
31804
|
}
|
|
31632
|
-
async restoreRunningSupervisor(manifest) {
|
|
31633
|
-
const
|
|
31805
|
+
async restoreRunningSupervisor(manifest, preserveGeneration = false, targetInstanceName) {
|
|
31806
|
+
const previousRuntime = await readRuntimeAcrossAtomicReplacement(this.runtimePath()).catch(() => null);
|
|
31807
|
+
const previousRuntimeUpdatedAt = previousRuntime?.manifest_generation === manifest.generation ? Date.parse(previousRuntime.updated_at) : Number.NaN;
|
|
31808
|
+
const restoredManifest = preserveGeneration ? manifest : assertManifest({
|
|
31634
31809
|
...manifest,
|
|
31635
31810
|
generation: randomUUID(),
|
|
31636
31811
|
installed_at: this.now().toISOString()
|
|
@@ -31658,10 +31833,15 @@ ${result2.stderr}`)) {
|
|
|
31658
31833
|
}
|
|
31659
31834
|
await this.waitForManagerActive();
|
|
31660
31835
|
if (!this.confirmInitialReadiness) return await this.status();
|
|
31661
|
-
const runtime = await this.waitForManifestApplied(
|
|
31836
|
+
const runtime = await this.waitForManifestApplied(
|
|
31837
|
+
restoredManifest,
|
|
31838
|
+
targetInstanceName,
|
|
31839
|
+
this.reconcileWaitAttempts,
|
|
31840
|
+
Number.isFinite(previousRuntimeUpdatedAt) ? previousRuntimeUpdatedAt : void 0
|
|
31841
|
+
);
|
|
31662
31842
|
return await this.status(runtime);
|
|
31663
31843
|
}
|
|
31664
|
-
async waitForManifestApplied(manifest, targetInstanceName, maxAttempts = this.reconcileWaitAttempts) {
|
|
31844
|
+
async waitForManifestApplied(manifest, targetInstanceName, maxAttempts = this.reconcileWaitAttempts, minimumRuntimeUpdatedAtExclusive) {
|
|
31665
31845
|
let consecutiveReadyObservations = 0;
|
|
31666
31846
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
31667
31847
|
let runtime = null;
|
|
@@ -31672,7 +31852,9 @@ ${result2.stderr}`)) {
|
|
|
31672
31852
|
throw error48;
|
|
31673
31853
|
}
|
|
31674
31854
|
}
|
|
31675
|
-
|
|
31855
|
+
const runtimeUpdatedAt = runtime ? Date.parse(runtime.updated_at) : Number.NaN;
|
|
31856
|
+
const runtimeIsFresh = minimumRuntimeUpdatedAtExclusive === void 0 || Number.isFinite(runtimeUpdatedAt) && runtimeUpdatedAt > minimumRuntimeUpdatedAtExclusive;
|
|
31857
|
+
if (runtime?.manifest_generation === manifest.generation && runtimeIsFresh) {
|
|
31676
31858
|
const configuredNames = manifest.workers.map((worker) => worker.instance_name).sort();
|
|
31677
31859
|
const runtimeNames = runtime.workers.map((worker) => worker.instance_name).sort();
|
|
31678
31860
|
const exactWorkerSet = JSON.stringify(configuredNames) === JSON.stringify(runtimeNames);
|
|
@@ -31934,7 +32116,71 @@ ${result2.stderr}`)) {
|
|
|
31934
32116
|
async stop() {
|
|
31935
32117
|
return await this.withControlLock(async () => await this.stopUnlocked());
|
|
31936
32118
|
}
|
|
31937
|
-
|
|
32119
|
+
/**
|
|
32120
|
+
* Quiesce the complete installed worker boundary while one exact instance's
|
|
32121
|
+
* durable Codex recovery evidence is reconciled. The service process lock is
|
|
32122
|
+
* held for the callback, so neither the manager nor a manual run-internal can
|
|
32123
|
+
* recreate workers during the same-boot process inspection.
|
|
32124
|
+
*/
|
|
32125
|
+
async recoverInstance(instanceName, operation, options = {}) {
|
|
32126
|
+
return await this.withControlLock(async () => {
|
|
32127
|
+
const manifest = await readInferenceHostServiceManifest(this.manifestPath());
|
|
32128
|
+
if (!manifest) throw new Error("Inference-host service is not installed.");
|
|
32129
|
+
if (!manifest.workers.some((worker) => worker.instance_name === instanceName)) {
|
|
32130
|
+
throw new Error(`Inference-host instance ${instanceName} is not installed.`);
|
|
32131
|
+
}
|
|
32132
|
+
if (options.expectedManifestGeneration && options.expectedManifestGeneration !== manifest.generation) {
|
|
32133
|
+
throw new Error("Inference-host service manifest changed during recovery.");
|
|
32134
|
+
}
|
|
32135
|
+
const recordedDesiredRunning = await readInferenceHostServiceDesired(this.desiredPath());
|
|
32136
|
+
const desiredRunning = options.restoreDesiredRunning ?? recordedDesiredRunning;
|
|
32137
|
+
const context = { manifest, desiredRunning };
|
|
32138
|
+
await options.prepare?.(context);
|
|
32139
|
+
const before = await this.status();
|
|
32140
|
+
if (recordedDesiredRunning || before.manager_active) {
|
|
32141
|
+
await this.stopUnlocked(true);
|
|
32142
|
+
} else {
|
|
32143
|
+
await writeDesired(this.desiredPath(), false, this.now());
|
|
32144
|
+
}
|
|
32145
|
+
let serviceFence = null;
|
|
32146
|
+
let result2;
|
|
32147
|
+
let operationError = null;
|
|
32148
|
+
try {
|
|
32149
|
+
serviceFence = await this.acquireProcessLock(
|
|
32150
|
+
`${this.config.supervisorProcessLockPath}.service`
|
|
32151
|
+
);
|
|
32152
|
+
const stopped = await this.status();
|
|
32153
|
+
if (stopped.manager_active) {
|
|
32154
|
+
throw new Error("Background service manager became active during recovery quiescence.");
|
|
32155
|
+
}
|
|
32156
|
+
result2 = await operation(context);
|
|
32157
|
+
} catch (error48) {
|
|
32158
|
+
operationError = error48;
|
|
32159
|
+
} finally {
|
|
32160
|
+
await serviceFence?.release().catch((error48) => {
|
|
32161
|
+
operationError = operationError ?? error48;
|
|
32162
|
+
});
|
|
32163
|
+
}
|
|
32164
|
+
let restoredStatus;
|
|
32165
|
+
try {
|
|
32166
|
+
restoredStatus = desiredRunning ? await this.restoreRunningSupervisor(manifest, true, instanceName) : await this.status();
|
|
32167
|
+
} catch (restoreError) {
|
|
32168
|
+
if (operationError) {
|
|
32169
|
+
throw new Error(
|
|
32170
|
+
"Inference-host recovery failed and the previous supervisor could not be restored.",
|
|
32171
|
+
{ cause: new AggregateError([operationError, restoreError]) }
|
|
32172
|
+
);
|
|
32173
|
+
}
|
|
32174
|
+
throw new Error(
|
|
32175
|
+
"Inference-host recovery completed, but the previous supervisor could not be restored.",
|
|
32176
|
+
{ cause: restoreError }
|
|
32177
|
+
);
|
|
32178
|
+
}
|
|
32179
|
+
if (operationError) throw operationError;
|
|
32180
|
+
return { result: result2, status: restoredStatus };
|
|
32181
|
+
});
|
|
32182
|
+
}
|
|
32183
|
+
async stopUnlocked(preserveManifestGenerationOnFailure = false) {
|
|
31938
32184
|
const manifest = await readInferenceHostServiceManifest(this.manifestPath());
|
|
31939
32185
|
if (!manifest) {
|
|
31940
32186
|
throw new Error("Inference-host service is not installed.");
|
|
@@ -31976,7 +32222,7 @@ ${result2.stderr}`)) {
|
|
|
31976
32222
|
} catch (error48) {
|
|
31977
32223
|
if (!restoreOnFailure) throw error48;
|
|
31978
32224
|
try {
|
|
31979
|
-
await this.restoreRunningSupervisor(manifest);
|
|
32225
|
+
await this.restoreRunningSupervisor(manifest, preserveManifestGenerationOnFailure);
|
|
31980
32226
|
} catch (restoreError) {
|
|
31981
32227
|
throw new Error(
|
|
31982
32228
|
"Inference-host stop failed and the previous supervisor could not be restored.",
|
|
@@ -32143,7 +32389,7 @@ ${result2.stderr}`)) {
|
|
|
32143
32389
|
`);
|
|
32144
32390
|
}
|
|
32145
32391
|
};
|
|
32146
|
-
const child =
|
|
32392
|
+
const child = spawn6(manifest.executable, args, {
|
|
32147
32393
|
env: inferenceHostServiceChildEnvironment(worker.runtime_environment),
|
|
32148
32394
|
windowsHide: true,
|
|
32149
32395
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -32433,8 +32679,8 @@ __export(cli_exports, {
|
|
|
32433
32679
|
registerInferenceHostServiceControlInput: () => registerInferenceHostServiceControlInput,
|
|
32434
32680
|
runInferenceHostCli: () => runInferenceHostCli
|
|
32435
32681
|
});
|
|
32436
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
32437
|
-
import { spawn as
|
|
32682
|
+
import { createHash as createHash7, randomUUID as randomUUID2 } from "node:crypto";
|
|
32683
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
32438
32684
|
import { lstat as lstat4, realpath as realpath4, rm as rm5 } from "node:fs/promises";
|
|
32439
32685
|
import { join as join6, resolve as resolve5 } from "node:path";
|
|
32440
32686
|
async function runInferenceHostCli(argv2, env = process.env, dependencies = {}) {
|
|
@@ -32444,6 +32690,21 @@ async function runInferenceHostCli(argv2, env = process.env, dependencies = {})
|
|
|
32444
32690
|
return { exitCode: 0, stdout: INFERENCE_HOST_HELP, stderr: "" };
|
|
32445
32691
|
}
|
|
32446
32692
|
const parsed = parseInferenceHostArgs(argv2, env);
|
|
32693
|
+
if (parsed.forceRecovery && !(parsed.command === "service" && parsed.serviceAction === "recover")) {
|
|
32694
|
+
throw new Error("--force-recovery is accepted only by service recover.");
|
|
32695
|
+
}
|
|
32696
|
+
if (parsed.command === "service" && parsed.serviceAction === "recover") {
|
|
32697
|
+
if (!parsed.instanceFlagExplicit) {
|
|
32698
|
+
throw new Error(
|
|
32699
|
+
"Service recovery requires an explicit --instance NAME; an environment or default instance is not accepted."
|
|
32700
|
+
);
|
|
32701
|
+
}
|
|
32702
|
+
if (!parsed.forceRecovery) {
|
|
32703
|
+
throw new Error(
|
|
32704
|
+
`Service recovery is disruptive. Rerun with --instance ${parsed.instanceName} --force-recovery after checking service status and logs.`
|
|
32705
|
+
);
|
|
32706
|
+
}
|
|
32707
|
+
}
|
|
32447
32708
|
const config2 = await resolveInferenceHostCommandConfig(parsed, env, dependencies);
|
|
32448
32709
|
if (parsed.command === "login") {
|
|
32449
32710
|
return await login(config2, parsed, env, dependencies, warnings);
|
|
@@ -32482,7 +32743,18 @@ async function runInferenceHostCli(argv2, env = process.env, dependencies = {})
|
|
|
32482
32743
|
);
|
|
32483
32744
|
}
|
|
32484
32745
|
if (parsed.command === "service") {
|
|
32485
|
-
|
|
32746
|
+
const mutationActions = /* @__PURE__ */ new Set(["install", "start", "stop", "recover", "uninstall"]);
|
|
32747
|
+
if (!parsed.serviceAction || !mutationActions.has(parsed.serviceAction)) {
|
|
32748
|
+
return await serviceCommand(config2, parsed, env, dependencies, warnings);
|
|
32749
|
+
}
|
|
32750
|
+
const recoveryCommandLock = await acquireInferenceHostProcessLock(
|
|
32751
|
+
serviceRecoveryCommandLockPath(config2)
|
|
32752
|
+
);
|
|
32753
|
+
try {
|
|
32754
|
+
return await serviceCommand(config2, parsed, env, dependencies, warnings);
|
|
32755
|
+
} finally {
|
|
32756
|
+
await recoveryCommandLock.release();
|
|
32757
|
+
}
|
|
32486
32758
|
}
|
|
32487
32759
|
if (parsed.command === "status") {
|
|
32488
32760
|
return await localStatus(config2, parsed, dependencies, warnings);
|
|
@@ -32511,7 +32783,7 @@ async function runInferenceHostCli(argv2, env = process.env, dependencies = {})
|
|
|
32511
32783
|
};
|
|
32512
32784
|
}
|
|
32513
32785
|
}
|
|
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;
|
|
32786
|
+
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, 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
32787
|
var init_cli = __esm({
|
|
32516
32788
|
"lib/inference-host/cli.ts"() {
|
|
32517
32789
|
"use strict";
|
|
@@ -32520,6 +32792,7 @@ var init_cli = __esm({
|
|
|
32520
32792
|
init_agent_client();
|
|
32521
32793
|
init_agent_state();
|
|
32522
32794
|
init_codex_adapter();
|
|
32795
|
+
init_codex_recovery_process();
|
|
32523
32796
|
init_codex_app_server();
|
|
32524
32797
|
init_codex_binary();
|
|
32525
32798
|
init_config();
|
|
@@ -32536,6 +32809,7 @@ var init_cli = __esm({
|
|
|
32536
32809
|
revocationCheckpointPath = (config2) => `${config2.statePath}.revoke.json`;
|
|
32537
32810
|
foregroundHostLockPath = (config2) => `${config2.processLockPath}.foreground`;
|
|
32538
32811
|
credentialLifecycleLockPath = (config2) => `${inferenceHostCredentialContextPath(config2)}.lock`;
|
|
32812
|
+
serviceRecoveryCommandLockPath = (config2) => `${config2.supervisorProcessLockPath}.service-recovery-command`;
|
|
32539
32813
|
AGENT_HEARTBEAT_INTERVAL_MS = 3e3;
|
|
32540
32814
|
AGENT_HEARTBEAT_MAX_RETRY_DELAY_MS = 9e3;
|
|
32541
32815
|
retryableAgentHeartbeatError = (error48) => {
|
|
@@ -32632,7 +32906,8 @@ Durable service:
|
|
|
32632
32906
|
vtx inference-host codex-login --instance codex-2
|
|
32633
32907
|
vtx inference-host service install --instance codex-2
|
|
32634
32908
|
vtx inference-host service uninstall --instance codex-2
|
|
32635
|
-
vtx inference-host service
|
|
32909
|
+
vtx inference-host service recover --instance codex-1 --force-recovery
|
|
32910
|
+
vtx inference-host service <start|stop|status|logs|recover|uninstall>
|
|
32636
32911
|
`;
|
|
32637
32912
|
parseHostConcurrency = (raw, label) => {
|
|
32638
32913
|
const value = Number(raw);
|
|
@@ -32648,6 +32923,7 @@ Durable service:
|
|
|
32648
32923
|
let maxConcurrency = configuredConcurrency ? parseHostConcurrency(configuredConcurrency, "VTX_INFERENCE_HOST_MAX_CONCURRENCY") : null;
|
|
32649
32924
|
let instanceName = String(env.VTX_INFERENCE_HOST_INSTANCE || "default").trim();
|
|
32650
32925
|
let instanceExplicit = Boolean(String(env.VTX_INFERENCE_HOST_INSTANCE || "").trim());
|
|
32926
|
+
let instanceFlagExplicit = false;
|
|
32651
32927
|
let displayName = String(env.VTX_INFERENCE_HOST_DISPLAY_NAME || "").trim() || "Codex subscription host";
|
|
32652
32928
|
let displayNameExplicit = Boolean(String(env.VTX_INFERENCE_HOST_DISPLAY_NAME || "").trim());
|
|
32653
32929
|
let adapter = null;
|
|
@@ -32657,6 +32933,7 @@ Durable service:
|
|
|
32657
32933
|
let waitSeconds = 50;
|
|
32658
32934
|
let lines = 100;
|
|
32659
32935
|
let serviceManifestPath = null;
|
|
32936
|
+
let forceRecovery = false;
|
|
32660
32937
|
const positionals = [];
|
|
32661
32938
|
for (let index = 0; index < argv2.length; index += 1) {
|
|
32662
32939
|
const argument = argv2[index];
|
|
@@ -32668,6 +32945,10 @@ Durable service:
|
|
|
32668
32945
|
once = true;
|
|
32669
32946
|
continue;
|
|
32670
32947
|
}
|
|
32948
|
+
if (argument === "--force-recovery") {
|
|
32949
|
+
forceRecovery = true;
|
|
32950
|
+
continue;
|
|
32951
|
+
}
|
|
32671
32952
|
if (argument === "--max-concurrency") {
|
|
32672
32953
|
const raw = argv2[index + 1];
|
|
32673
32954
|
if (!raw) throw new Error("--max-concurrency requires a value.");
|
|
@@ -32706,6 +32987,7 @@ Durable service:
|
|
|
32706
32987
|
if (argument === "--instance") {
|
|
32707
32988
|
instanceName = raw.trim();
|
|
32708
32989
|
instanceExplicit = true;
|
|
32990
|
+
instanceFlagExplicit = true;
|
|
32709
32991
|
}
|
|
32710
32992
|
index += 1;
|
|
32711
32993
|
continue;
|
|
@@ -32736,6 +33018,7 @@ Durable service:
|
|
|
32736
33018
|
maxConcurrency,
|
|
32737
33019
|
instanceName,
|
|
32738
33020
|
instanceExplicit,
|
|
33021
|
+
instanceFlagExplicit,
|
|
32739
33022
|
displayName,
|
|
32740
33023
|
adapter,
|
|
32741
33024
|
modelId,
|
|
@@ -32744,13 +33027,14 @@ Durable service:
|
|
|
32744
33027
|
waitSeconds,
|
|
32745
33028
|
lines,
|
|
32746
33029
|
serviceAction,
|
|
32747
|
-
serviceManifestPath
|
|
33030
|
+
serviceManifestPath,
|
|
33031
|
+
forceRecovery
|
|
32748
33032
|
};
|
|
32749
33033
|
};
|
|
32750
33034
|
defaultOpenBrowser = (url2) => {
|
|
32751
33035
|
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
32752
33036
|
const args = process.platform === "win32" ? ["/c", "start", "", url2] : [url2];
|
|
32753
|
-
const child =
|
|
33037
|
+
const child = spawn7(command, args, {
|
|
32754
33038
|
detached: true,
|
|
32755
33039
|
stdio: "ignore",
|
|
32756
33040
|
windowsHide: true
|
|
@@ -32914,10 +33198,14 @@ Durable service:
|
|
|
32914
33198
|
codexRecoveryPath(config2),
|
|
32915
33199
|
"Codex attempt recovery file"
|
|
32916
33200
|
);
|
|
33201
|
+
const serviceRecoveryPresent = await fileExistsPrivately(
|
|
33202
|
+
`${config2.supervisorStatePath}.service-recovery.json`,
|
|
33203
|
+
"Inference-host service recovery transaction"
|
|
33204
|
+
);
|
|
32917
33205
|
const revocationCheckpointPresent = await readRevocationCheckpoint(config2) !== null;
|
|
32918
33206
|
const agentAttemptPresent = await readInferenceAgentAttemptState(config2.statePath) !== null;
|
|
32919
33207
|
const agentNextRecoveryPresent = await readInferenceAgentNextState(config2.statePath) !== null;
|
|
32920
|
-
if (pendingAttempts > 0 || codexRecoveryPresent || revocationCheckpointPresent || agentAttemptPresent || agentNextRecoveryPresent) {
|
|
33208
|
+
if (pendingAttempts > 0 || codexRecoveryPresent || serviceRecoveryPresent || revocationCheckpointPresent || agentAttemptPresent || agentNextRecoveryPresent) {
|
|
32921
33209
|
throw new Error(
|
|
32922
33210
|
"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
33211
|
);
|
|
@@ -33232,6 +33520,10 @@ Waiting for approval...
|
|
|
33232
33520
|
codexRecoveryPath(config2),
|
|
33233
33521
|
"Codex attempt recovery file"
|
|
33234
33522
|
);
|
|
33523
|
+
const serviceRecoveryPresent = await fileExistsPrivately(
|
|
33524
|
+
`${config2.supervisorStatePath}.service-recovery.json`,
|
|
33525
|
+
"Inference-host service recovery transaction"
|
|
33526
|
+
);
|
|
33235
33527
|
const agentAttempt = await readInferenceAgentAttemptState(config2.statePath);
|
|
33236
33528
|
const agentNextRecoveryPresent = await readInferenceAgentNextState(config2.statePath) !== null;
|
|
33237
33529
|
return {
|
|
@@ -33260,6 +33552,7 @@ Waiting for approval...
|
|
|
33260
33552
|
} : null,
|
|
33261
33553
|
agent_next_recovery_present: agentNextRecoveryPresent,
|
|
33262
33554
|
codex_recovery_present: recoveryPresent,
|
|
33555
|
+
service_recovery_present: serviceRecoveryPresent,
|
|
33263
33556
|
codex_auth: codexAuthentication,
|
|
33264
33557
|
source: "local_only"
|
|
33265
33558
|
}, parsed.json),
|
|
@@ -34016,10 +34309,292 @@ Waiting for approval...
|
|
|
34016
34309
|
await lock2.release();
|
|
34017
34310
|
}
|
|
34018
34311
|
};
|
|
34312
|
+
recoveryBackupPath = (config2, transactionId) => `${codexRecoveryPath(config2)}.service-recovery-${transactionId}.backup`;
|
|
34313
|
+
serviceRecoveryTransactionPath = (config2) => `${config2.supervisorStatePath}.service-recovery.json`;
|
|
34314
|
+
readServiceRecoveryTransaction = async (config2) => {
|
|
34315
|
+
const raw = await readInferencePrivateFile(
|
|
34316
|
+
serviceRecoveryTransactionPath(config2),
|
|
34317
|
+
"Inference-host service recovery transaction"
|
|
34318
|
+
);
|
|
34319
|
+
if (raw === null) return null;
|
|
34320
|
+
let value;
|
|
34321
|
+
try {
|
|
34322
|
+
value = JSON.parse(raw);
|
|
34323
|
+
} catch {
|
|
34324
|
+
throw new Error("Inference-host service recovery transaction is not valid JSON.");
|
|
34325
|
+
}
|
|
34326
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
34327
|
+
throw new Error("Inference-host service recovery transaction is invalid.");
|
|
34328
|
+
}
|
|
34329
|
+
const record2 = value;
|
|
34330
|
+
const exactKeys = [
|
|
34331
|
+
"schema_version",
|
|
34332
|
+
"phase",
|
|
34333
|
+
"transaction_id",
|
|
34334
|
+
"instance_name",
|
|
34335
|
+
"manifest_generation",
|
|
34336
|
+
"restore_desired_running",
|
|
34337
|
+
"recovery_backup_path",
|
|
34338
|
+
"recovery_backup_sha256",
|
|
34339
|
+
"cleanup_confirmed_attempt_ids",
|
|
34340
|
+
"started_at"
|
|
34341
|
+
];
|
|
34342
|
+
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(
|
|
34343
|
+
(attemptId) => typeof attemptId !== "string" || attemptId.length < 1 || attemptId.length > 256
|
|
34344
|
+
) || 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) {
|
|
34345
|
+
throw new Error("Inference-host service recovery transaction is invalid.");
|
|
34346
|
+
}
|
|
34347
|
+
if (resolve5(record2.recovery_backup_path) !== resolve5(recoveryBackupPath(config2, record2.transaction_id))) {
|
|
34348
|
+
throw new Error("Inference-host service recovery backup path is invalid.");
|
|
34349
|
+
}
|
|
34350
|
+
return record2;
|
|
34351
|
+
};
|
|
34352
|
+
assertResumableCodexRecoveryEvidence = (backupAttempts, currentAttempts, cleanupConfirmedAttemptIds, phase) => {
|
|
34353
|
+
const backupIds = Object.keys(backupAttempts).sort();
|
|
34354
|
+
const currentIds = Object.keys(currentAttempts).sort();
|
|
34355
|
+
const backupIdSet = new Set(backupIds);
|
|
34356
|
+
const cleanupConfirmedIdSet = new Set(cleanupConfirmedAttemptIds);
|
|
34357
|
+
if (backupIds.length === 0 || currentIds.some((attemptId) => !backupIdSet.has(attemptId)) || cleanupConfirmedAttemptIds.some((attemptId) => !backupIdSet.has(attemptId)) || phase === "reconciled" && backupIds.some((attemptId) => !cleanupConfirmedIdSet.has(attemptId))) {
|
|
34358
|
+
throw new Error("Codex recovery evidence does not match the retained service recovery backup.");
|
|
34359
|
+
}
|
|
34360
|
+
for (const attemptId of backupIds) {
|
|
34361
|
+
const original = backupAttempts[attemptId];
|
|
34362
|
+
const current = currentAttempts[attemptId];
|
|
34363
|
+
if (!current) {
|
|
34364
|
+
if (!cleanupConfirmedIdSet.has(attemptId)) {
|
|
34365
|
+
throw new Error("Codex recovery evidence does not match the retained service recovery backup.");
|
|
34366
|
+
}
|
|
34367
|
+
continue;
|
|
34368
|
+
}
|
|
34369
|
+
if (cleanupConfirmedIdSet.has(attemptId) && !original.cleanupConfirmed && !(current.processState === "terminated" && current.cleanupConfirmed)) {
|
|
34370
|
+
throw new Error("Codex recovery evidence regressed after confirmed cleanup.");
|
|
34371
|
+
}
|
|
34372
|
+
const {
|
|
34373
|
+
processState: _originalProcessState,
|
|
34374
|
+
cleanupConfirmed: _originalCleanupConfirmed,
|
|
34375
|
+
...originalStable
|
|
34376
|
+
} = original;
|
|
34377
|
+
const {
|
|
34378
|
+
processState: _currentProcessState,
|
|
34379
|
+
cleanupConfirmed: _currentCleanupConfirmed,
|
|
34380
|
+
...currentStable
|
|
34381
|
+
} = current;
|
|
34382
|
+
if (JSON.stringify(originalStable) !== JSON.stringify(currentStable) || !(current.processState === original.processState && current.cleanupConfirmed === original.cleanupConfirmed) && !(current.processState === "terminated" && current.cleanupConfirmed)) {
|
|
34383
|
+
throw new Error("Codex recovery evidence changed outside an exact cleanup transition.");
|
|
34384
|
+
}
|
|
34385
|
+
}
|
|
34386
|
+
};
|
|
34387
|
+
recoverInstalledCodexService = async (config2, parsed, env, dependencies, manager) => {
|
|
34388
|
+
if (!parsed.instanceFlagExplicit) {
|
|
34389
|
+
throw new Error(
|
|
34390
|
+
"Service recovery requires an explicit --instance NAME; an environment or default instance is not accepted."
|
|
34391
|
+
);
|
|
34392
|
+
}
|
|
34393
|
+
if (!parsed.forceRecovery) {
|
|
34394
|
+
throw new Error(
|
|
34395
|
+
`Service recovery is disruptive. Rerun with --instance ${config2.instanceName} --force-recovery after checking service status and logs.`
|
|
34396
|
+
);
|
|
34397
|
+
}
|
|
34398
|
+
const credentialLock = await acquireInferenceHostProcessLock(
|
|
34399
|
+
credentialLifecycleLockPath(config2)
|
|
34400
|
+
);
|
|
34401
|
+
try {
|
|
34402
|
+
const recoveryStore = new FileCodexAttemptRecoveryStore(codexRecoveryPath(config2));
|
|
34403
|
+
const existingTransaction = await readServiceRecoveryTransaction(config2);
|
|
34404
|
+
if (existingTransaction && existingTransaction.instance_name !== config2.instanceName) {
|
|
34405
|
+
throw new Error(
|
|
34406
|
+
`Service recovery for instance ${existingTransaction.instance_name} must finish before another instance can recover.`
|
|
34407
|
+
);
|
|
34408
|
+
}
|
|
34409
|
+
const initialAttempts = await recoveryStore.loadAll();
|
|
34410
|
+
const pendingAttempts = Object.values(initialAttempts).filter(
|
|
34411
|
+
(checkpoint) => !checkpoint.cleanupConfirmed
|
|
34412
|
+
);
|
|
34413
|
+
if (pendingAttempts.length === 0 && !existingTransaction) {
|
|
34414
|
+
const status = await manager.status();
|
|
34415
|
+
return {
|
|
34416
|
+
exitCode: 0,
|
|
34417
|
+
stdout: render({
|
|
34418
|
+
status: "service_recovery_not_needed",
|
|
34419
|
+
instance_name: config2.instanceName,
|
|
34420
|
+
recovery_needed: false,
|
|
34421
|
+
recovered_attempt_count: 0,
|
|
34422
|
+
outcome_unknown_attempt_count: 0,
|
|
34423
|
+
desired_running: status.desired_running,
|
|
34424
|
+
manager_active: status.manager_active,
|
|
34425
|
+
worker_runtime_state: status.workers.find(
|
|
34426
|
+
(worker) => worker.instance_name === config2.instanceName
|
|
34427
|
+
)?.runtime_state ?? "unknown"
|
|
34428
|
+
}, parsed.json),
|
|
34429
|
+
stderr: ""
|
|
34430
|
+
};
|
|
34431
|
+
}
|
|
34432
|
+
const recoveryRaw = await readInferencePrivateFile(
|
|
34433
|
+
codexRecoveryPath(config2),
|
|
34434
|
+
"Codex attempt recovery file"
|
|
34435
|
+
);
|
|
34436
|
+
if (recoveryRaw === null && existingTransaction?.phase !== "reconciled") {
|
|
34437
|
+
throw new Error("Codex recovery evidence changed before service recovery began.");
|
|
34438
|
+
}
|
|
34439
|
+
const transactionId = existingTransaction?.transaction_id ?? randomUUID2();
|
|
34440
|
+
const backupPath = existingTransaction?.recovery_backup_path ?? recoveryBackupPath(config2, transactionId);
|
|
34441
|
+
if (!existingTransaction) {
|
|
34442
|
+
await writeAtomicInferencePrivateFile(backupPath, recoveryRaw);
|
|
34443
|
+
}
|
|
34444
|
+
const backupRaw = await readInferencePrivateFile(
|
|
34445
|
+
backupPath,
|
|
34446
|
+
"Codex service recovery backup"
|
|
34447
|
+
);
|
|
34448
|
+
const backupSha256 = backupRaw === null ? null : createHash7("sha256").update(backupRaw).digest("hex");
|
|
34449
|
+
if (backupSha256 === null || existingTransaction && backupSha256 !== existingTransaction.recovery_backup_sha256) {
|
|
34450
|
+
throw new Error("Codex service recovery backup is missing or changed.");
|
|
34451
|
+
}
|
|
34452
|
+
const backupAttempts = await new FileCodexAttemptRecoveryStore(backupPath).loadAll();
|
|
34453
|
+
assertResumableCodexRecoveryEvidence(
|
|
34454
|
+
backupAttempts,
|
|
34455
|
+
initialAttempts,
|
|
34456
|
+
existingTransaction?.cleanup_confirmed_attempt_ids ?? [],
|
|
34457
|
+
existingTransaction?.phase ?? "prepared"
|
|
34458
|
+
);
|
|
34459
|
+
const recoverySha256 = recoveryRaw === null ? null : createHash7("sha256").update(recoveryRaw).digest("hex");
|
|
34460
|
+
const unresolvedAttemptIds = Object.keys(backupAttempts).filter((attemptId) => !existingTransaction?.cleanup_confirmed_attempt_ids.includes(attemptId) && !initialAttempts[attemptId]?.cleanupConfirmed);
|
|
34461
|
+
const binary = unresolvedAttemptIds.length > 0 ? await (dependencies.resolveBinary ?? resolvePinnedCodexBinary)(env) : null;
|
|
34462
|
+
let activeTransaction = existingTransaction;
|
|
34463
|
+
let completed = false;
|
|
34464
|
+
try {
|
|
34465
|
+
const recovered = await manager.recoverInstance(config2.instanceName, async ({ manifest }) => {
|
|
34466
|
+
const workerLocks = [];
|
|
34467
|
+
try {
|
|
34468
|
+
for (const worker of [...manifest.workers].sort(
|
|
34469
|
+
(left, right) => left.instance_name.localeCompare(right.instance_name)
|
|
34470
|
+
)) {
|
|
34471
|
+
const workerConfig = resolveInferenceHostConfig({
|
|
34472
|
+
...env,
|
|
34473
|
+
...worker.runtime_environment,
|
|
34474
|
+
VTX_INFERENCE_HOST_INSTANCE: worker.instance_name
|
|
34475
|
+
});
|
|
34476
|
+
workerLocks.push(await acquireInferenceHostProcessLock(
|
|
34477
|
+
foregroundHostLockPath(workerConfig)
|
|
34478
|
+
));
|
|
34479
|
+
workerLocks.push(await acquireInferenceHostProcessLock(workerConfig.processLockPath));
|
|
34480
|
+
}
|
|
34481
|
+
try {
|
|
34482
|
+
if (!activeTransaction) {
|
|
34483
|
+
throw new Error("Inference-host service recovery transaction was not prepared.");
|
|
34484
|
+
}
|
|
34485
|
+
const attempts = await recoveryStore.loadAll();
|
|
34486
|
+
assertResumableCodexRecoveryEvidence(
|
|
34487
|
+
backupAttempts,
|
|
34488
|
+
attempts,
|
|
34489
|
+
activeTransaction.cleanup_confirmed_attempt_ids,
|
|
34490
|
+
activeTransaction.phase
|
|
34491
|
+
);
|
|
34492
|
+
const unresolved = Object.keys(backupAttempts).filter((attemptId) => !activeTransaction.cleanup_confirmed_attempt_ids.includes(attemptId) && !attempts[attemptId]?.cleanupConfirmed);
|
|
34493
|
+
if (unresolved.length === 0) return 0;
|
|
34494
|
+
const currentRaw = await readInferencePrivateFile(
|
|
34495
|
+
codexRecoveryPath(config2),
|
|
34496
|
+
"Codex attempt recovery file"
|
|
34497
|
+
);
|
|
34498
|
+
if (currentRaw === null || recoverySha256 === null || createHash7("sha256").update(currentRaw).digest("hex") !== recoverySha256) {
|
|
34499
|
+
throw new Error("Codex recovery evidence changed while the service was stopping.");
|
|
34500
|
+
}
|
|
34501
|
+
const proof = await (dependencies.proveCodexQuiescence ?? proveCodexSameBootQuiescence)({
|
|
34502
|
+
attempts,
|
|
34503
|
+
confirmNoManagedCodexProcesses: async () => await (dependencies.confirmNoManagedCodexProcesses ?? confirmNoVtxManagedCodexProcesses)({ pinnedBinaryPath: binary.path })
|
|
34504
|
+
});
|
|
34505
|
+
return await (dependencies.reconcileCodexRecovery ?? reconcileCodexAttemptRecovery)({
|
|
34506
|
+
recoveryHooks: recoveryStore,
|
|
34507
|
+
codexHome: config2.codexHomePath,
|
|
34508
|
+
guardianReceiptRoot: codexGuardianReceiptRoot(config2),
|
|
34509
|
+
sameBootQuiescenceProof: proof
|
|
34510
|
+
});
|
|
34511
|
+
} finally {
|
|
34512
|
+
if (!activeTransaction) {
|
|
34513
|
+
throw new Error("Inference-host service recovery transaction was not prepared.");
|
|
34514
|
+
}
|
|
34515
|
+
const progressedAttempts = await recoveryStore.loadAll();
|
|
34516
|
+
assertResumableCodexRecoveryEvidence(
|
|
34517
|
+
backupAttempts,
|
|
34518
|
+
progressedAttempts,
|
|
34519
|
+
activeTransaction.cleanup_confirmed_attempt_ids,
|
|
34520
|
+
activeTransaction.phase
|
|
34521
|
+
);
|
|
34522
|
+
const priorConfirmedIds = new Set(activeTransaction.cleanup_confirmed_attempt_ids);
|
|
34523
|
+
const cleanupConfirmedAttemptIds = Object.keys(backupAttempts).filter((attemptId) => priorConfirmedIds.has(attemptId) || progressedAttempts[attemptId]?.cleanupConfirmed).sort();
|
|
34524
|
+
activeTransaction = {
|
|
34525
|
+
...activeTransaction,
|
|
34526
|
+
phase: cleanupConfirmedAttemptIds.length === Object.keys(backupAttempts).length ? "reconciled" : "prepared",
|
|
34527
|
+
cleanup_confirmed_attempt_ids: cleanupConfirmedAttemptIds
|
|
34528
|
+
};
|
|
34529
|
+
await writeAtomicInferencePrivateFile(
|
|
34530
|
+
serviceRecoveryTransactionPath(config2),
|
|
34531
|
+
`${JSON.stringify(activeTransaction, null, 2)}
|
|
34532
|
+
`
|
|
34533
|
+
);
|
|
34534
|
+
}
|
|
34535
|
+
} finally {
|
|
34536
|
+
for (const lock2 of workerLocks.reverse()) await lock2.release();
|
|
34537
|
+
}
|
|
34538
|
+
}, {
|
|
34539
|
+
restoreDesiredRunning: existingTransaction?.restore_desired_running,
|
|
34540
|
+
expectedManifestGeneration: existingTransaction?.manifest_generation,
|
|
34541
|
+
prepare: existingTransaction ? void 0 : async ({ manifest, desiredRunning }) => {
|
|
34542
|
+
activeTransaction = {
|
|
34543
|
+
schema_version: "vtx_inference_service_recovery_v1",
|
|
34544
|
+
phase: "prepared",
|
|
34545
|
+
transaction_id: transactionId,
|
|
34546
|
+
instance_name: config2.instanceName,
|
|
34547
|
+
manifest_generation: manifest.generation,
|
|
34548
|
+
restore_desired_running: desiredRunning,
|
|
34549
|
+
recovery_backup_path: backupPath,
|
|
34550
|
+
recovery_backup_sha256: backupSha256,
|
|
34551
|
+
cleanup_confirmed_attempt_ids: [],
|
|
34552
|
+
started_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
34553
|
+
};
|
|
34554
|
+
await writeAtomicInferencePrivateFile(
|
|
34555
|
+
serviceRecoveryTransactionPath(config2),
|
|
34556
|
+
`${JSON.stringify(activeTransaction, null, 2)}
|
|
34557
|
+
`
|
|
34558
|
+
);
|
|
34559
|
+
}
|
|
34560
|
+
});
|
|
34561
|
+
completed = true;
|
|
34562
|
+
const outcomeUnknownAttemptCount = Object.values(backupAttempts).filter(
|
|
34563
|
+
(checkpoint) => checkpoint.dispatchOutcome === "outcome_unknown"
|
|
34564
|
+
).length;
|
|
34565
|
+
return {
|
|
34566
|
+
exitCode: 0,
|
|
34567
|
+
stdout: render({
|
|
34568
|
+
status: "service_recovered",
|
|
34569
|
+
instance_name: config2.instanceName,
|
|
34570
|
+
recovery_needed: true,
|
|
34571
|
+
recovered_attempt_count: recovered.result,
|
|
34572
|
+
outcome_unknown_attempt_count: outcomeUnknownAttemptCount,
|
|
34573
|
+
desired_running: recovered.status.desired_running,
|
|
34574
|
+
manager_active: recovered.status.manager_active,
|
|
34575
|
+
worker_runtime_state: recovered.status.workers.find(
|
|
34576
|
+
(worker) => worker.instance_name === config2.instanceName
|
|
34577
|
+
)?.runtime_state ?? "unknown"
|
|
34578
|
+
}, parsed.json),
|
|
34579
|
+
stderr: ""
|
|
34580
|
+
};
|
|
34581
|
+
} finally {
|
|
34582
|
+
if (completed) {
|
|
34583
|
+
await clearInferencePrivateFile(
|
|
34584
|
+
serviceRecoveryTransactionPath(config2),
|
|
34585
|
+
"Inference-host service recovery transaction"
|
|
34586
|
+
);
|
|
34587
|
+
await rm5(backupPath, { force: true });
|
|
34588
|
+
}
|
|
34589
|
+
}
|
|
34590
|
+
} finally {
|
|
34591
|
+
await credentialLock.release();
|
|
34592
|
+
}
|
|
34593
|
+
};
|
|
34019
34594
|
serviceCommand = async (config2, parsed, env, dependencies, warnings) => {
|
|
34020
34595
|
const action = parsed.serviceAction;
|
|
34021
34596
|
if (!action) {
|
|
34022
|
-
throw new Error("Usage: vtx inference-host service <install|start|stop|status|logs|uninstall>.");
|
|
34597
|
+
throw new Error("Usage: vtx inference-host service <install|start|stop|status|logs|recover|uninstall>.");
|
|
34023
34598
|
}
|
|
34024
34599
|
if (action === "run-internal") {
|
|
34025
34600
|
if (!parsed.serviceManifestPath) throw new Error("Internal service manifest path is required.");
|
|
@@ -34046,15 +34621,32 @@ Waiting for approval...
|
|
|
34046
34621
|
await serviceLock.release();
|
|
34047
34622
|
}
|
|
34048
34623
|
}
|
|
34624
|
+
if (parsed.forceRecovery && action !== "recover") {
|
|
34625
|
+
throw new Error("--force-recovery is accepted only by service recover.");
|
|
34626
|
+
}
|
|
34049
34627
|
const manager = dependencies.createServiceManager?.(
|
|
34050
34628
|
config2,
|
|
34051
34629
|
dependencies.serviceDependencies
|
|
34052
34630
|
) ?? new InferenceHostServiceManager(config2, dependencies.serviceDependencies);
|
|
34631
|
+
if (action === "recover") {
|
|
34632
|
+
return await recoverInstalledCodexService(
|
|
34633
|
+
config2,
|
|
34634
|
+
parsed,
|
|
34635
|
+
env,
|
|
34636
|
+
dependencies,
|
|
34637
|
+
manager
|
|
34638
|
+
);
|
|
34639
|
+
}
|
|
34053
34640
|
if (action === "install") {
|
|
34054
34641
|
const credentialLock = await acquireInferenceHostProcessLock(
|
|
34055
34642
|
credentialLifecycleLockPath(config2)
|
|
34056
34643
|
);
|
|
34057
34644
|
try {
|
|
34645
|
+
if (await readServiceRecoveryTransaction(config2)) {
|
|
34646
|
+
throw new Error(
|
|
34647
|
+
"A guarded service recovery transaction must finish before installing a subscription."
|
|
34648
|
+
);
|
|
34649
|
+
}
|
|
34058
34650
|
const adapter = parsed.adapter || "codex";
|
|
34059
34651
|
if (adapter !== "codex") {
|
|
34060
34652
|
throw new Error(
|
|
@@ -34112,9 +34704,15 @@ Waiting for approval...
|
|
|
34112
34704
|
};
|
|
34113
34705
|
}
|
|
34114
34706
|
if (action === "start") {
|
|
34707
|
+
if (await readServiceRecoveryTransaction(config2)) {
|
|
34708
|
+
throw new Error("A guarded service recovery transaction must finish before starting the service.");
|
|
34709
|
+
}
|
|
34115
34710
|
return { exitCode: 0, stdout: render({ status: "service_started", ...await manager.start() }, parsed.json), stderr: "" };
|
|
34116
34711
|
}
|
|
34117
34712
|
if (action === "stop") {
|
|
34713
|
+
if (await readServiceRecoveryTransaction(config2)) {
|
|
34714
|
+
throw new Error("A guarded service recovery transaction must finish before stopping the service.");
|
|
34715
|
+
}
|
|
34118
34716
|
return { exitCode: 0, stdout: render({ status: "service_stopped", ...await manager.stop() }, parsed.json), stderr: "" };
|
|
34119
34717
|
}
|
|
34120
34718
|
if (action === "status") {
|
|
@@ -34127,6 +34725,11 @@ Waiting for approval...
|
|
|
34127
34725
|
credentialLifecycleLockPath(config2)
|
|
34128
34726
|
);
|
|
34129
34727
|
try {
|
|
34728
|
+
if (await readServiceRecoveryTransaction(config2)) {
|
|
34729
|
+
throw new Error(
|
|
34730
|
+
"A guarded service recovery transaction must finish before uninstalling a subscription."
|
|
34731
|
+
);
|
|
34732
|
+
}
|
|
34130
34733
|
if (parsed.instanceExplicit) {
|
|
34131
34734
|
const retained = await readInferenceHostCredentialContext(config2);
|
|
34132
34735
|
const retainedIdentity = retained ? resolveInferenceHostConfig({
|
|
@@ -34145,7 +34748,7 @@ Waiting for approval...
|
|
|
34145
34748
|
await credentialLock.release();
|
|
34146
34749
|
}
|
|
34147
34750
|
}
|
|
34148
|
-
throw new Error("Usage: vtx inference-host service <install|start|stop|status|logs|uninstall>.");
|
|
34751
|
+
throw new Error("Usage: vtx inference-host service <install|start|stop|status|logs|recover|uninstall>.");
|
|
34149
34752
|
};
|
|
34150
34753
|
hasExplicitCredentialStoreConfiguration = (env) => Boolean(
|
|
34151
34754
|
String(env.VTX_INFERENCE_HOST_CREDENTIAL_STORE || "").trim() || String(env.VTX_INFERENCE_HOST_CREDENTIAL_FILE || "").trim()
|
|
@@ -34167,8 +34770,8 @@ Waiting for approval...
|
|
|
34167
34770
|
}
|
|
34168
34771
|
throw new Error("Inference host credential store identity is invalid.");
|
|
34169
34772
|
};
|
|
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;
|
|
34773
|
+
commandRequiresRetainedCredentialStore = (parsed) => parsed.command === "logout" || parsed.command === "revoke" || parsed.command === "service" && parsed.instanceExplicit && (parsed.serviceAction === "install" || parsed.serviceAction === "recover" || parsed.serviceAction === "uninstall");
|
|
34774
|
+
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
34775
|
resolveInferenceHostCommandConfig = async (parsed, env, dependencies) => {
|
|
34173
34776
|
const selectedEnv = {
|
|
34174
34777
|
...env,
|
|
@@ -34191,6 +34794,9 @@ Waiting for approval...
|
|
|
34191
34794
|
const installedEnvironment = manifest?.workers.find(
|
|
34192
34795
|
(worker) => worker.instance_name === baseConfig.instanceName
|
|
34193
34796
|
)?.runtime_environment;
|
|
34797
|
+
if (parsed.command === "service" && parsed.serviceAction === "recover" && !installedEnvironment) {
|
|
34798
|
+
throw new Error(`Inference-host instance ${baseConfig.instanceName} is not installed.`);
|
|
34799
|
+
}
|
|
34194
34800
|
const persistedContext = installedEnvironment ? null : await readInferenceHostCredentialContext(baseConfig);
|
|
34195
34801
|
const revocationCheckpoint = !installedEnvironment && !persistedContext && parsed.command === "revoke" ? await readRevocationCheckpoint(baseConfig) : null;
|
|
34196
34802
|
const retainedEnvironment = installedEnvironment ?? (persistedContext ? {
|
|
@@ -38718,7 +39324,7 @@ var init_utils = __esm({
|
|
|
38718
39324
|
});
|
|
38719
39325
|
|
|
38720
39326
|
// node_modules/ethers/lib.esm/crypto/crypto.js
|
|
38721
|
-
import { createHash as
|
|
39327
|
+
import { createHash as createHash8, createHmac, pbkdf2Sync, randomBytes as randomBytes5 } from "crypto";
|
|
38722
39328
|
var init_crypto2 = __esm({
|
|
38723
39329
|
"node_modules/ethers/lib.esm/crypto/crypto.js"() {
|
|
38724
39330
|
}
|
|
@@ -39932,10 +40538,10 @@ var init_sha22 = __esm({
|
|
|
39932
40538
|
init_crypto2();
|
|
39933
40539
|
init_utils();
|
|
39934
40540
|
_sha256 = function(data) {
|
|
39935
|
-
return
|
|
40541
|
+
return createHash8("sha256").update(data).digest();
|
|
39936
40542
|
};
|
|
39937
40543
|
_sha512 = function(data) {
|
|
39938
|
-
return
|
|
40544
|
+
return createHash8("sha512").update(data).digest();
|
|
39939
40545
|
};
|
|
39940
40546
|
__sha256 = _sha256;
|
|
39941
40547
|
__sha512 = _sha512;
|
|
@@ -52112,7 +52718,7 @@ __export(vtx_exports, {
|
|
|
52112
52718
|
runVtxCli: () => runVtxCli
|
|
52113
52719
|
});
|
|
52114
52720
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
52115
|
-
import { spawn as
|
|
52721
|
+
import { spawn as spawn8 } from "node:child_process";
|
|
52116
52722
|
function render2(value, json2) {
|
|
52117
52723
|
if (json2) {
|
|
52118
52724
|
return `${JSON.stringify(value, null, 2)}
|
|
@@ -52142,7 +52748,7 @@ function openBrowser(url2) {
|
|
|
52142
52748
|
const platform = process.platform;
|
|
52143
52749
|
const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
52144
52750
|
const args = platform === "win32" ? ["/c", "start", "", url2] : [url2];
|
|
52145
|
-
const child =
|
|
52751
|
+
const child = spawn8(command, args, { detached: true, stdio: "ignore" });
|
|
52146
52752
|
child.unref();
|
|
52147
52753
|
}
|
|
52148
52754
|
function parseFlags(args) {
|