@vtxmacro/cli 2026.9.29 → 2026.9.30

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 CHANGED
@@ -16,16 +16,20 @@ verifies the Codex runtime's version and digest before use; it does not borrow a
16
16
  Codex binary from PATH or an editor extension.
17
17
  For Windows-login auto-start, install and configure the CLI from native Windows;
18
18
  a CLI installed only inside WSL cannot start the WSL virtual machine at login.
19
- New service installations use a private verified launcher. At login and every
20
- service start, it checks the official npm registry, verifies the exact release
21
- integrity and installed CLI version in a new versioned directory, starts it as a
22
- candidate, and selects it only after every configured worker reports ready. A
19
+ New service installations use a private verified launcher. At login, every
20
+ service start, and periodically while running, it checks the official npm registry
21
+ and verifies the exact release integrity and installed CLI version in a new
22
+ versioned directory. Before switching, it stops new admission and lets active
23
+ Provider calls and Agent turns finish while keeping heartbeats alive. It selects
24
+ the candidate only after every configured worker reports ready. A
23
25
  failed candidate is rejected and the last working runtime resumes. The service
24
26
  keeps only the active release and one rollback release. If the update check is
25
27
  unavailable, it keeps the last verified runtime. An intentionally stopped
26
28
  service stays stopped and makes no update request. Hosts installed before this
27
- automatic updater need one final normal npm update and service reinstall to
28
- adopt it.
29
+ private launcher need one final normal npm update and service reinstall to
30
+ adopt it. Startup-only launchers adopt background checking after their next normal
31
+ start activates a release that includes it; publishing a release cannot replace
32
+ an older process that is already running.
29
33
 
30
34
  ## Configure
31
35
 
@@ -16,7 +16,7 @@ import { fileURLToPath } from "node:url";
16
16
  // agent-cli-release.json
17
17
  var agent_cli_release_default = {
18
18
  package_name: "@vtxmacro/cli",
19
- package_version: "2026.9.29",
19
+ package_version: "2026.9.30",
20
20
  codex_package_name: "@openai/codex",
21
21
  codex_version: "0.153.3",
22
22
  copilot_sdk_package_name: "@github/copilot-sdk",
@@ -115,6 +115,121 @@ var agent_cli_release_default = {
115
115
  }
116
116
  };
117
117
 
118
+ // lib/runtime-policy.generated.json
119
+ var runtime_policy_generated_default = {
120
+ ui: {
121
+ auth_bootstrap_retry: {
122
+ max_attempts: 3,
123
+ jitter_milliseconds: 750,
124
+ base_delay_milliseconds: 500,
125
+ min_delay_milliseconds: 250,
126
+ max_delay_milliseconds: 1e4
127
+ }
128
+ },
129
+ ai_dashboard: {
130
+ market_news_bounds: {
131
+ max_items_min: 1,
132
+ max_items_max: 100,
133
+ lookback_hours_min: 1,
134
+ lookback_hours_max: 168,
135
+ max_items_steps: [
136
+ 1,
137
+ 2,
138
+ 3,
139
+ 4,
140
+ 5,
141
+ 6,
142
+ 7,
143
+ 8,
144
+ 9,
145
+ 10,
146
+ 15,
147
+ 20,
148
+ 25,
149
+ 30,
150
+ 40,
151
+ 50,
152
+ 60,
153
+ 75,
154
+ 90,
155
+ 100
156
+ ],
157
+ lookback_hours_steps: [
158
+ 1,
159
+ 2,
160
+ 4,
161
+ 8,
162
+ 12,
163
+ 24,
164
+ 48,
165
+ 72,
166
+ 168
167
+ ]
168
+ }
169
+ },
170
+ trading: {
171
+ engine: {
172
+ client_runtime: {
173
+ recovery: {
174
+ resume_retry_base_delay_ms: 1e3,
175
+ resume_retry_max_delay_ms: 3e4,
176
+ transient_load_min_delay_ms: 3e4,
177
+ transient_load_max_delay_ms: 12e4,
178
+ rate_limit_buffer_ms: 1e3,
179
+ rate_limit_max_delay_ms: 3e5,
180
+ engine_stop_settle_retry_delay_ms: 250,
181
+ recoverable_reset_settle_retry_delay_ms: 1500,
182
+ local_ai_preflight_timeout_ms: 3e3,
183
+ surge_stagger_max_ms: 6e4,
184
+ surge_stagger_min_ms: 2e3,
185
+ surge_stagger_min_candidates: 4,
186
+ stagger_deadline_ttl_ms: 3e5,
187
+ server_active_reprobe_delay_ms: 6e4,
188
+ transient_load_jitter_ms: 1e4,
189
+ cross_host_completed_ttl_ms: 15e3,
190
+ telemetry_dedupe_ttl_ms: 5e3,
191
+ browser_presence_heartbeat_interval_ms: 3e4,
192
+ host_liveness_heartbeat_interval_ms: 3e4,
193
+ host_liveness_recovery_nudge_ttl_ms: 1e4
194
+ }
195
+ }
196
+ }
197
+ },
198
+ external_inference: {
199
+ service: {
200
+ update_check_interval_ms: 3e5,
201
+ update_registry_timeout_ms: 15e3,
202
+ startup_max_attempts: 40,
203
+ startup_poll_interval_ms: 250,
204
+ restart_stable_uptime_ms: 6e4,
205
+ restart_backoff_base_ms: 1e3,
206
+ restart_backoff_max_ms: 3e4,
207
+ restart_exponent_max: 5
208
+ }
209
+ },
210
+ desktop: {
211
+ runtime_watchdog: {
212
+ ping_interval_milliseconds: 15e3,
213
+ telemetry_interval_milliseconds: 3e5
214
+ }
215
+ },
216
+ screener: {
217
+ resource_scheduler: {
218
+ client_lock_ttl_milliseconds: 12e4,
219
+ client_poll_interval_milliseconds: 250
220
+ }
221
+ },
222
+ deployment: {
223
+ cutover_buffer: {
224
+ target_timeout_ms: 3e3,
225
+ retired_timeout_ms: 45e3,
226
+ max_body_bytes: 33554432,
227
+ max_inflight: 16,
228
+ max_queue: 64
229
+ }
230
+ }
231
+ };
232
+
118
233
  // lib/inference-host/config.ts
119
234
  import { randomBytes } from "node:crypto";
120
235
  import { spawn } from "node:child_process";
@@ -983,6 +1098,7 @@ var UPDATE_ATTEMPTS = 3;
983
1098
  var UPDATE_RETRY_MS = 2e3;
984
1099
  var CANDIDATE_READINESS_TIMEOUT_MS = 10 * 60 * 1e3;
985
1100
  var SERVICE_DRAIN_COMMAND = "vtx-inference-host-service-drain-v1";
1101
+ var SERVICE_QUIESCE_COMMAND = "vtx-inference-host-service-quiesce-v1";
986
1102
  var safePath = (value, label) => {
987
1103
  if (typeof value !== "string" || !value || /[\r\n\0]/u.test(value)) {
988
1104
  throw new Error(`Inference-host bootstrap ${label} is invalid.`);
@@ -1127,7 +1243,11 @@ var runInferenceHostBootstrapNpmCommand = async (executable, args, platform = pr
1127
1243
  var defaultRunSupervisor = (executable, args) => {
1128
1244
  const child = spawn2(executable, [...args], {
1129
1245
  windowsHide: true,
1130
- env: { ...process.env, VTX_INFERENCE_HOST_SERVICE_CHILD: "1" },
1246
+ env: {
1247
+ ...process.env,
1248
+ VTX_INFERENCE_HOST_SERVICE_CHILD: "1",
1249
+ VTX_INFERENCE_HOST_BOOTSTRAP_VERSION: agent_cli_release_default.package_version
1250
+ },
1131
1251
  stdio: ["pipe", "inherit", "inherit"]
1132
1252
  });
1133
1253
  const completion = new Promise((resolvePromise, reject) => {
@@ -1139,7 +1259,11 @@ var defaultRunSupervisor = (executable, args) => {
1139
1259
  return {
1140
1260
  completion,
1141
1261
  requestDrain: () => {
1142
- child.stdin.end(`${SERVICE_DRAIN_COMMAND}
1262
+ child.stdin.write(`${SERVICE_DRAIN_COMMAND}
1263
+ `);
1264
+ },
1265
+ requestQuiesce: () => {
1266
+ child.stdin.write(`${SERVICE_QUIESCE_COMMAND}
1143
1267
  `);
1144
1268
  }
1145
1269
  };
@@ -1153,9 +1277,12 @@ var appendBootstrapLog = async (path, event, fields = {}) => {
1153
1277
  { encoding: "utf8", mode: 384 }
1154
1278
  );
1155
1279
  };
1156
- var fetchLatestRelease = async (fetchImpl) => {
1280
+ var fetchLatestRelease = async (fetchImpl, signal) => {
1157
1281
  const response = await fetchImpl(`${OFFICIAL_NPM_REGISTRY}/%40vtxmacro%2Fcli/latest`, {
1158
- signal: AbortSignal.timeout(1e4)
1282
+ signal: AbortSignal.any([
1283
+ AbortSignal.timeout(runtime_policy_generated_default.external_inference.service.update_registry_timeout_ms),
1284
+ ...signal ? [signal] : []
1285
+ ])
1159
1286
  });
1160
1287
  if (!response.ok) throw new Error("registry_request_failed");
1161
1288
  const value = await response.json();
@@ -1323,10 +1450,12 @@ var updateSelection = async (manifestPath, manifest, dependencies) => {
1323
1450
  let lastError = null;
1324
1451
  for (let attempt = 0; attempt < UPDATE_ATTEMPTS; attempt += 1) {
1325
1452
  try {
1326
- latest = await fetchLatestRelease(dependencies.fetch);
1453
+ if (dependencies.signal?.aborted) throw new Error("service_update_cancelled");
1454
+ latest = await fetchLatestRelease(dependencies.fetch, dependencies.signal);
1327
1455
  break;
1328
1456
  } catch (error) {
1329
1457
  lastError = error;
1458
+ if (dependencies.signal?.aborted) throw error;
1330
1459
  if (attempt + 1 < UPDATE_ATTEMPTS) await dependencies.sleep(UPDATE_RETRY_MS);
1331
1460
  }
1332
1461
  }
@@ -1340,7 +1469,7 @@ var updateSelection = async (manifestPath, manifest, dependencies) => {
1340
1469
  }
1341
1470
  const rejection = await parseRejection(manifestPath);
1342
1471
  if (rejection?.package_version === latest.version) return { active, candidate: null };
1343
- if (!await readDesired(manifestPath)) return { active, candidate: null };
1472
+ if (dependencies.signal?.aborted || !await readDesired(manifestPath)) return { active, candidate: null };
1344
1473
  return {
1345
1474
  active,
1346
1475
  candidate: await stageRelease(
@@ -1353,21 +1482,23 @@ var updateSelection = async (manifestPath, manifest, dependencies) => {
1353
1482
  )
1354
1483
  };
1355
1484
  };
1356
- var waitForCandidateReadiness = async (manifestPath, manifest, candidate, signal, sleep, timeoutMs) => {
1485
+ var waitForCandidateReadiness = async (manifestPath, candidate, signal, timeoutMs) => {
1357
1486
  const deadline = Date.now() + timeoutMs;
1358
1487
  while (!signal.aborted && Date.now() < deadline) {
1488
+ if (!await readDesired(manifestPath)) return "aborted";
1359
1489
  try {
1490
+ const currentManifest = parseManifest(JSON.parse(await readFile2(manifestPath, "utf8")));
1360
1491
  const value = JSON.parse(await readFile2(runtimePathFor(manifestPath), "utf8"));
1361
1492
  if (value && typeof value === "object" && !Array.isArray(value)) {
1362
1493
  const runtime = value;
1363
1494
  const workers = Array.isArray(runtime.workers) ? runtime.workers : [];
1364
1495
  const workerNames = workers.map((worker) => worker && typeof worker === "object" && !Array.isArray(worker) ? String(worker.instance_name ?? "") : "").sort();
1365
1496
  const everyWorkerRunning = workers.every((worker) => worker && typeof worker === "object" && !Array.isArray(worker) && worker.state === "running");
1366
- if (runtime.schema_version === "vtx_inference_service_runtime_v1" && runtime.manifest_generation === manifest.generation && typeof runtime.updated_at === "string" && Date.parse(runtime.updated_at) >= Date.parse(candidate.selected_at) && JSON.stringify(workerNames) === JSON.stringify(manifest.worker_names) && everyWorkerRunning) return "ready";
1497
+ if (runtime.schema_version === "vtx_inference_service_runtime_v1" && runtime.manifest_generation === currentManifest.generation && typeof runtime.updated_at === "string" && Date.parse(runtime.updated_at) >= Date.parse(candidate.selected_at) && JSON.stringify(workerNames) === JSON.stringify(currentManifest.worker_names) && everyWorkerRunning) return "ready";
1367
1498
  }
1368
1499
  } catch {
1369
1500
  }
1370
- await sleep(250);
1501
+ await waitForUpdateCheck(250, signal);
1371
1502
  }
1372
1503
  return signal.aborted ? "aborted" : "timeout";
1373
1504
  };
@@ -1399,6 +1530,18 @@ var supervisorArguments = (script, manifestPath) => [
1399
1530
  "--service-manifest",
1400
1531
  manifestPath
1401
1532
  ];
1533
+ var waitForUpdateCheck = async (milliseconds, signal) => {
1534
+ if (signal.aborted) return;
1535
+ await new Promise((resolvePromise) => {
1536
+ const finish = () => {
1537
+ clearTimeout(timer);
1538
+ signal.removeEventListener("abort", finish);
1539
+ resolvePromise();
1540
+ };
1541
+ const timer = setTimeout(finish, milliseconds);
1542
+ signal.addEventListener("abort", finish, { once: true });
1543
+ });
1544
+ };
1402
1545
  var runInferenceHostServiceBootstrap = async (manifestPathValue, dependencies = {}, lockPathValue) => {
1403
1546
  const manifestPath = resolve2(manifestPathValue);
1404
1547
  const lockPath = safePath(
@@ -1406,127 +1549,217 @@ var runInferenceHostServiceBootstrap = async (manifestPathValue, dependencies =
1406
1549
  "lock path"
1407
1550
  );
1408
1551
  const lock = await (dependencies.acquireProcessLock ?? acquireInferenceHostProcessLock)(lockPath);
1552
+ const lifetime = new AbortController();
1553
+ const live = { supervisor: null };
1554
+ const stop = () => {
1555
+ lifetime.abort();
1556
+ try {
1557
+ live.supervisor?.requestDrain();
1558
+ } catch {
1559
+ }
1560
+ };
1561
+ dependencies.signal?.addEventListener("abort", stop, { once: true });
1562
+ process.on("SIGTERM", stop);
1563
+ process.on("SIGINT", stop);
1564
+ if (dependencies.signal?.aborted) stop();
1409
1565
  try {
1410
- const manifest = parseManifest(JSON.parse(await readFile2(manifestPath, "utf8")));
1411
- if (!await readDesired(manifestPath)) {
1566
+ let manifest = parseManifest(JSON.parse(await readFile2(manifestPath, "utf8")));
1567
+ const shouldRun = async () => !lifetime.signal.aborted && await readDesired(manifestPath);
1568
+ if (!await shouldRun()) {
1412
1569
  await appendBootstrapLog(manifest.log_path, "service_bootstrap_stopped");
1413
1570
  return 0;
1414
1571
  }
1415
1572
  const now = dependencies.now ?? (() => /* @__PURE__ */ new Date());
1416
1573
  const sleep = dependencies.sleep ?? (async (milliseconds) => {
1417
- await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
1574
+ await waitForUpdateCheck(milliseconds, lifetime.signal);
1418
1575
  });
1419
1576
  const runSupervisor = dependencies.runSupervisor ?? defaultRunSupervisor;
1420
- const selectedBeforeUpdate = await parseSelection(manifestPath);
1421
- let updatePlan = { active: selectedBeforeUpdate, candidate: null };
1422
- try {
1423
- updatePlan = await updateSelection(manifestPath, manifest, {
1424
- fetch: dependencies.fetch ?? fetch,
1425
- now,
1426
- platform: dependencies.platform ?? process.platform,
1427
- runCommand: dependencies.runCommand ?? defaultRunCommand,
1428
- sleep
1429
- });
1430
- } catch (error) {
1431
- await appendBootstrapLog(manifest.log_path, "service_update_deferred", {
1432
- reason: error instanceof Error ? error.message.slice(0, 160) : "unknown"
1433
- });
1434
- }
1435
- if (!await readDesired(manifestPath)) {
1436
- await appendBootstrapLog(manifest.log_path, "service_bootstrap_stopped");
1437
- return 0;
1438
- }
1577
+ const updateDependencies = {
1578
+ fetch: dependencies.fetch ?? fetch,
1579
+ now,
1580
+ platform: dependencies.platform ?? process.platform,
1581
+ runCommand: dependencies.runCommand ?? defaultRunCommand,
1582
+ sleep,
1583
+ signal: lifetime.signal
1584
+ };
1585
+ const checkUpdate = async () => {
1586
+ manifest = parseManifest(JSON.parse(await readFile2(manifestPath, "utf8")));
1587
+ const active = await parseSelection(manifestPath);
1588
+ try {
1589
+ return await updateSelection(manifestPath, manifest, updateDependencies);
1590
+ } catch (error) {
1591
+ await appendBootstrapLog(manifest.log_path, "service_update_deferred", {
1592
+ reason: error instanceof Error ? error.message.slice(0, 160) : "unknown"
1593
+ });
1594
+ return { active, candidate: null };
1595
+ }
1596
+ };
1439
1597
  const launch = async (selection) => {
1598
+ manifest = parseManifest(JSON.parse(await readFile2(manifestPath, "utf8")));
1440
1599
  const script = selection?.script ?? manifest.script;
1441
1600
  await access(manifest.executable);
1442
1601
  await access(script);
1602
+ if (!await shouldRun()) return null;
1443
1603
  await appendBootstrapLog(manifest.log_path, "service_bootstrap_launch", {
1444
1604
  package_version: selection?.package_version ?? agent_cli_release_default.package_version
1445
1605
  });
1446
- return await runSupervisor(
1447
- manifest.executable,
1448
- supervisorArguments(script, manifestPath)
1449
- ).completion;
1606
+ if (!await shouldRun()) return null;
1607
+ live.supervisor = runSupervisor(manifest.executable, supervisorArguments(script, manifestPath));
1608
+ return live.supervisor;
1450
1609
  };
1451
- if (!updatePlan.candidate) return await launch(updatePlan.active);
1452
- const candidate = updatePlan.candidate;
1453
- await appendBootstrapLog(manifest.log_path, "service_release_candidate_started", {
1454
- package_version: candidate.package_version
1455
- });
1456
- let candidateSupervisor = null;
1457
- let candidateFailure = "candidate_launch_failed";
1458
- let candidateExitCode = 1;
1459
- const readinessController = new AbortController();
1460
- try {
1461
- candidateSupervisor = runSupervisor(
1462
- manifest.executable,
1463
- supervisorArguments(candidate.script, manifestPath)
1464
- );
1465
- const first = await Promise.race([
1466
- candidateSupervisor.completion.then(
1467
- (exitCode) => ({ kind: "exit", exitCode }),
1468
- () => ({ kind: "error", exitCode: 1 })
1469
- ),
1470
- waitForCandidateReadiness(
1471
- manifestPath,
1472
- manifest,
1473
- candidate,
1474
- readinessController.signal,
1475
- sleep,
1476
- dependencies.readinessTimeoutMs ?? CANDIDATE_READINESS_TIMEOUT_MS
1477
- ).then((status) => ({ kind: status, exitCode: 1 }))
1478
- ]);
1479
- if (first.kind === "ready") {
1480
- readinessController.abort();
1481
- await atomicWriteSelection(manifestPath, candidate);
1482
- await rm2(rejectionPathFor(manifestPath), { force: true }).catch(() => void 0);
1483
- await prunePrivateReleases(manifestPath, [candidate, updatePlan.active]).catch(
1484
- async () => await appendBootstrapLog(
1485
- manifest.log_path,
1486
- "service_release_cleanup_deferred",
1487
- { package_version: candidate.package_version }
1488
- ).catch(() => void 0)
1489
- );
1490
- await appendBootstrapLog(manifest.log_path, "service_release_selected", {
1610
+ let updatePlan = await checkUpdate();
1611
+ while (await shouldRun()) {
1612
+ if (updatePlan.candidate) {
1613
+ const candidate = { ...updatePlan.candidate, selected_at: now().toISOString() };
1614
+ await rm2(runtimePathFor(manifestPath), { force: true });
1615
+ await appendBootstrapLog(manifest.log_path, "service_release_candidate_started", {
1491
1616
  package_version: candidate.package_version
1492
- }).catch(() => void 0);
1493
- return await candidateSupervisor.completion;
1494
- }
1495
- if (first.kind === "timeout") {
1496
- candidateFailure = "candidate_readiness_timeout";
1497
- candidateSupervisor.requestDrain();
1498
- candidateExitCode = await candidateSupervisor.completion.catch(() => 1);
1499
- } else {
1500
- candidateFailure = first.kind === "error" ? "candidate_launch_failed" : "candidate_exited_before_readiness";
1501
- candidateExitCode = first.exitCode;
1502
- }
1503
- } catch {
1504
- candidateFailure = "candidate_launch_failed";
1505
- candidateExitCode = 1;
1506
- if (candidateSupervisor) {
1617
+ });
1618
+ let candidateFailure = "candidate_launch_failed";
1619
+ let promoted = false;
1620
+ const readinessController = new AbortController();
1621
+ const abortReadiness = () => readinessController.abort();
1622
+ lifetime.signal.addEventListener("abort", abortReadiness, { once: true });
1507
1623
  try {
1508
- candidateSupervisor.requestDrain();
1624
+ const candidateSupervisor = await launch(candidate);
1625
+ if (!candidateSupervisor) return 0;
1626
+ const first = await Promise.race([
1627
+ candidateSupervisor.completion.then(
1628
+ (exitCode) => ({ kind: "exit", exitCode }),
1629
+ () => ({ kind: "error", exitCode: 1 })
1630
+ ),
1631
+ waitForCandidateReadiness(
1632
+ manifestPath,
1633
+ candidate,
1634
+ readinessController.signal,
1635
+ dependencies.readinessTimeoutMs ?? CANDIDATE_READINESS_TIMEOUT_MS
1636
+ ).then((status) => ({ kind: status, exitCode: 1 }))
1637
+ ]);
1638
+ if (first.kind === "ready" && await shouldRun()) {
1639
+ await atomicWriteSelection(manifestPath, candidate);
1640
+ await rm2(rejectionPathFor(manifestPath), { force: true }).catch(() => void 0);
1641
+ await prunePrivateReleases(manifestPath, [candidate, updatePlan.active]).catch(
1642
+ async () => await appendBootstrapLog(
1643
+ manifest.log_path,
1644
+ "service_release_cleanup_deferred",
1645
+ { package_version: candidate.package_version }
1646
+ ).catch(() => void 0)
1647
+ );
1648
+ await appendBootstrapLog(manifest.log_path, "service_release_selected", {
1649
+ package_version: candidate.package_version
1650
+ }).catch(() => void 0);
1651
+ updatePlan = { active: candidate, candidate: null };
1652
+ promoted = true;
1653
+ } else {
1654
+ candidateFailure = first.kind === "timeout" ? "candidate_readiness_timeout" : "candidate_exited_before_readiness";
1655
+ if (first.kind !== "exit" && first.kind !== "error") candidateSupervisor.requestDrain();
1656
+ await candidateSupervisor.completion.catch(() => 1);
1657
+ live.supervisor = null;
1658
+ }
1509
1659
  } catch {
1660
+ candidateFailure = "candidate_launch_failed";
1661
+ if (live.supervisor) {
1662
+ live.supervisor.requestDrain();
1663
+ await live.supervisor.completion.catch(() => 1);
1664
+ live.supervisor = null;
1665
+ }
1666
+ } finally {
1667
+ readinessController.abort();
1668
+ lifetime.signal.removeEventListener("abort", abortReadiness);
1669
+ }
1670
+ if (!await shouldRun()) {
1671
+ if (live.supervisor) {
1672
+ live.supervisor.requestDrain();
1673
+ await live.supervisor.completion.catch(() => 1);
1674
+ }
1675
+ return 0;
1676
+ }
1677
+ if (!promoted) {
1678
+ await writeRejection(manifestPath, {
1679
+ schema_version: "vtx_inference_service_release_rejection_v1",
1680
+ package_version: candidate.package_version,
1681
+ rejected_at: now().toISOString(),
1682
+ reason: candidateFailure
1683
+ });
1684
+ await prunePrivateReleases(manifestPath, [updatePlan.active]);
1685
+ await appendBootstrapLog(manifest.log_path, "service_release_rolled_back", {
1686
+ package_version: candidate.package_version,
1687
+ reason: candidateFailure
1688
+ });
1689
+ updatePlan = { active: updatePlan.active, candidate: null };
1510
1690
  }
1511
- candidateExitCode = await candidateSupervisor.completion.catch(() => 1);
1512
1691
  }
1513
- } finally {
1514
- readinessController.abort();
1692
+ const supervisor = live.supervisor ?? await launch(updatePlan.active);
1693
+ if (!supervisor) return 0;
1694
+ let exited = false;
1695
+ const completion = supervisor.completion.then(
1696
+ (exitCode) => {
1697
+ exited = true;
1698
+ return exitCode;
1699
+ },
1700
+ () => {
1701
+ exited = true;
1702
+ return 1;
1703
+ }
1704
+ );
1705
+ while (!exited) {
1706
+ const pollController = new AbortController();
1707
+ const abortPoll = () => pollController.abort();
1708
+ lifetime.signal.addEventListener("abort", abortPoll, { once: true });
1709
+ if (lifetime.signal.aborted) pollController.abort();
1710
+ try {
1711
+ await Promise.race([
1712
+ completion,
1713
+ waitForUpdateCheck(
1714
+ dependencies.updateCheckIntervalMs ?? runtime_policy_generated_default.external_inference.service.update_check_interval_ms,
1715
+ pollController.signal
1716
+ )
1717
+ ]);
1718
+ } finally {
1719
+ pollController.abort();
1720
+ lifetime.signal.removeEventListener("abort", abortPoll);
1721
+ }
1722
+ if (exited) {
1723
+ live.supervisor = null;
1724
+ return await completion;
1725
+ }
1726
+ if (!await shouldRun()) {
1727
+ supervisor.requestDrain();
1728
+ return await completion;
1729
+ }
1730
+ const next = await checkUpdate();
1731
+ if (exited) {
1732
+ live.supervisor = null;
1733
+ return await completion;
1734
+ }
1735
+ if (!await shouldRun()) {
1736
+ supervisor.requestDrain();
1737
+ return await completion;
1738
+ }
1739
+ if (!next.candidate) continue;
1740
+ await appendBootstrapLog(manifest.log_path, "service_release_quiescing", {
1741
+ package_version: next.candidate.package_version
1742
+ });
1743
+ supervisor.requestQuiesce();
1744
+ await completion;
1745
+ live.supervisor = null;
1746
+ updatePlan = next;
1747
+ break;
1748
+ }
1515
1749
  }
1516
- if (!await readDesired(manifestPath)) return candidateExitCode;
1517
- await writeRejection(manifestPath, {
1518
- schema_version: "vtx_inference_service_release_rejection_v1",
1519
- package_version: candidate.package_version,
1520
- rejected_at: now().toISOString(),
1521
- reason: candidateFailure
1522
- });
1523
- await prunePrivateReleases(manifestPath, [updatePlan.active]);
1524
- await appendBootstrapLog(manifest.log_path, "service_release_rolled_back", {
1525
- package_version: candidate.package_version,
1526
- reason: candidateFailure
1527
- });
1528
- return await launch(updatePlan.active);
1750
+ return 0;
1529
1751
  } finally {
1752
+ lifetime.abort();
1753
+ if (live.supervisor) {
1754
+ try {
1755
+ live.supervisor.requestDrain();
1756
+ } catch {
1757
+ }
1758
+ await live.supervisor.completion.catch(() => 1);
1759
+ }
1760
+ dependencies.signal?.removeEventListener("abort", stop);
1761
+ process.removeListener("SIGTERM", stop);
1762
+ process.removeListener("SIGINT", stop);
1530
1763
  await lock.release();
1531
1764
  }
1532
1765
  };
package/bin/vtx.js CHANGED
@@ -77,7 +77,7 @@ var init_agent_cli_release = __esm({
77
77
  "agent-cli-release.json"() {
78
78
  agent_cli_release_default = {
79
79
  package_name: "@vtxmacro/cli",
80
- package_version: "2026.9.29",
80
+ package_version: "2026.9.30",
81
81
  codex_package_name: "@openai/codex",
82
82
  codex_version: "0.153.3",
83
83
  copilot_sdk_package_name: "@github/copilot-sdk",
@@ -37893,6 +37893,13 @@ var init_runner = __esm({
37893
37893
  };
37894
37894
  let removeAbortListener = () => {
37895
37895
  };
37896
+ let removeQuiesceListener = () => {
37897
+ };
37898
+ let quiesceRequested = Boolean(this.options.quiesceSignal?.aborted);
37899
+ let wakeQuiesce;
37900
+ const quiesceWake = new Promise((resolve12) => {
37901
+ wakeQuiesce = resolve12;
37902
+ });
37896
37903
  let drainRequested = false;
37897
37904
  let drainReason = "stopped";
37898
37905
  let receipt;
@@ -37931,6 +37938,13 @@ var init_runner = __esm({
37931
37938
  wakeDrain();
37932
37939
  };
37933
37940
  try {
37941
+ const onQuiesce = () => {
37942
+ quiesceRequested = true;
37943
+ wakeQuiesce();
37944
+ };
37945
+ this.options.quiesceSignal?.addEventListener("abort", onQuiesce, { once: true });
37946
+ removeQuiesceListener = () => this.options.quiesceSignal?.removeEventListener("abort", onQuiesce);
37947
+ if (quiesceRequested) return { claimed, completed, failed, drain_reason: "service_update" };
37934
37948
  unregisterSignals = (this.dependencies.registerSignalHandlers ?? defaultRegisterSignalHandlers)(requestDrain);
37935
37949
  if (this.options.signal) {
37936
37950
  const onAbort = () => requestDrain("aborted");
@@ -38033,8 +38047,9 @@ var init_runner = __esm({
38033
38047
  await mcp.initialize({ signal: this.options.signal });
38034
38048
  if (settings.agentRuntime) {
38035
38049
  const agentRuntimeSettings = settings.agentRuntime;
38050
+ const agentIdleSignal = this.options.quiesceSignal ? AbortSignal.any([agentAbort.signal, this.options.quiesceSignal]) : agentAbort.signal;
38036
38051
  agentLoop = (async () => {
38037
- while (!agentAbort.signal.aborted) {
38052
+ while (!agentAbort.signal.aborted && !quiesceRequested) {
38038
38053
  const existingFence = await readInferenceAgentFailureFence(
38039
38054
  agentRuntimeSettings.statePath
38040
38055
  );
@@ -38067,7 +38082,7 @@ var init_runner = __esm({
38067
38082
  });
38068
38083
  if (this.options.once) return;
38069
38084
  try {
38070
- await sleep4(Math.max(MIN_SLEEP_MS, retryAtMs - now()), agentAbort.signal);
38085
+ await sleep4(Math.max(MIN_SLEEP_MS, retryAtMs - now()), agentIdleSignal);
38071
38086
  } catch {
38072
38087
  return;
38073
38088
  }
@@ -38082,7 +38097,8 @@ var init_runner = __esm({
38082
38097
  statePath: agentRuntimeSettings.statePath,
38083
38098
  controlClient: createInferenceAgentControlClient(mcp),
38084
38099
  adapter: agentRuntimeSettings.adapter,
38085
- dataRequestTimeoutMs: agentRuntimeSettings.dataRequestTimeoutMs
38100
+ dataRequestTimeoutMs: agentRuntimeSettings.dataRequestTimeoutMs,
38101
+ quiesceSignal: this.options.quiesceSignal
38086
38102
  });
38087
38103
  try {
38088
38104
  if (this.options.once) {
@@ -38151,7 +38167,7 @@ var init_runner = __esm({
38151
38167
  );
38152
38168
  if (this.options.once || retryAtMs === null) return;
38153
38169
  try {
38154
- await sleep4(Math.max(MIN_SLEEP_MS, retryAtMs - now()), agentAbort.signal);
38170
+ await sleep4(Math.max(MIN_SLEEP_MS, retryAtMs - now()), agentIdleSignal);
38155
38171
  } catch {
38156
38172
  return;
38157
38173
  }
@@ -38167,7 +38183,9 @@ var init_runner = __esm({
38167
38183
  const retained = await readInferenceAgentRuntimeState(agentRuntimeSettings.statePath);
38168
38184
  if (!retained || retained.schema_version !== "vtx_inference_agent_runtime_v3" || !retained.pending_turn?.terminal_receipt) return;
38169
38185
  if (this.options.once) return;
38170
- await sleep4(1e3, agentAbort.signal);
38186
+ await sleep4(1e3, agentIdleSignal).catch((error49) => {
38187
+ if (!quiesceRequested) throw error49;
38188
+ });
38171
38189
  continue;
38172
38190
  }
38173
38191
  if (error48 instanceof InferenceHostRunnerError && error48.code === "agent_terminal_failure_fenced") {
@@ -38179,7 +38197,9 @@ var init_runner = __esm({
38179
38197
  });
38180
38198
  if (this.options.once) return;
38181
38199
  try {
38182
- await sleep4(1e3, agentAbort.signal);
38200
+ await sleep4(1e3, agentIdleSignal).catch((error49) => {
38201
+ if (!quiesceRequested) throw error49;
38202
+ });
38183
38203
  } catch {
38184
38204
  return;
38185
38205
  }
@@ -39010,11 +39030,11 @@ var init_runner = __esm({
39010
39030
  )
39011
39031
  });
39012
39032
  }
39013
- while (!drainRequested && recoveryQueue.length > 0 && (settings.maxConcurrency === null || active.size < settings.maxConcurrency)) {
39033
+ while (!drainRequested && !quiesceRequested && recoveryQueue.length > 0 && (settings.maxConcurrency === null || active.size < settings.maxConcurrency)) {
39014
39034
  const recovery = recoveryQueue.shift();
39015
39035
  launchClaim(recovery.claim, recovery);
39016
39036
  }
39017
- while (!drainRequested) {
39037
+ while (!drainRequested && !quiesceRequested) {
39018
39038
  const currentTime = now();
39019
39039
  if (providerFailureCooldownReason !== null && currentTime >= providerFailureRetryAtMs) {
39020
39040
  emitDiagnostic("provider_cooldown_cleared", {
@@ -39138,12 +39158,12 @@ var init_runner = __esm({
39138
39158
  nextClaimAt = Math.max(nextClaimAt, nextAdvertisementAttemptAt);
39139
39159
  }
39140
39160
  }
39141
- while (recoveryQueue.length > 0 && (settings.maxConcurrency === null || active.size < settings.maxConcurrency)) {
39161
+ while (!quiesceRequested && recoveryQueue.length > 0 && (settings.maxConcurrency === null || active.size < settings.maxConcurrency)) {
39142
39162
  const recovery = recoveryQueue.shift();
39143
39163
  launchClaim(recovery.claim, recovery);
39144
39164
  }
39145
39165
  let claimOpportunities = 0;
39146
- while (!drainRequested && (!yieldRenewalToClaim || claimOpportunities === 0) && recoveryQueue.length === 0 && (!this.options.once || !onceClaimed) && (settings.maxConcurrency === null || active.size < settings.maxConcurrency) && pendingClaimPromotions === 0 && now() >= Math.max(
39166
+ while (!drainRequested && !quiesceRequested && (!yieldRenewalToClaim || claimOpportunities === 0) && recoveryQueue.length === 0 && (!this.options.once || !onceClaimed) && (settings.maxConcurrency === null || active.size < settings.maxConcurrency) && pendingClaimPromotions === 0 && now() >= Math.max(
39147
39167
  nextClaimAt,
39148
39168
  claimAvailabilityBackoff?.untilMs ?? 0,
39149
39169
  providerRetryAtMs ?? 0,
@@ -39165,6 +39185,7 @@ var init_runner = __esm({
39165
39185
  let claim;
39166
39186
  let claimTiming;
39167
39187
  const claimCallStartedAtMs = now();
39188
+ if (quiesceRequested) break;
39168
39189
  try {
39169
39190
  claimOpportunities += 1;
39170
39191
  if (claimPreparation.unsent === request) claimPreparation.unsent = null;
@@ -39415,9 +39436,14 @@ var init_runner = __esm({
39415
39436
  await Promise.race([
39416
39437
  sleep4(Math.max(MIN_SLEEP_MS, wakeAt - now())),
39417
39438
  drainSignal,
39439
+ quiesceWake,
39418
39440
  ...active.values()
39419
39441
  ]);
39420
39442
  }
39443
+ if (quiesceRequested && !drainRequested) {
39444
+ await Promise.race([Promise.allSettled([...active.values(), agentLoop]), drainSignal]);
39445
+ if (!drainRequested) requestDrain("service_update");
39446
+ }
39421
39447
  stopHostHeartbeatLoop();
39422
39448
  if (providerDispatchWatchdogDrainRequested) {
39423
39449
  await settlesWithin(hostHeartbeatLoop, MIN_SLEEP_MS);
@@ -39475,6 +39501,7 @@ var init_runner = __esm({
39475
39501
  await hostHeartbeatLoop;
39476
39502
  }
39477
39503
  removeAbortListener();
39504
+ removeQuiesceListener();
39478
39505
  unregisterSignals();
39479
39506
  try {
39480
39507
  await this.dependencies.codexAdapter.close?.();
@@ -40508,9 +40535,9 @@ var init_runner = __esm({
40508
40535
  async run(signal) {
40509
40536
  let exitedNormally = false;
40510
40537
  try {
40511
- while (!this.stopped && !signal?.aborted) {
40538
+ while (!this.stopped && !signal?.aborted && !this.options.quiesceSignal?.aborted) {
40512
40539
  const nextWakeAt = await this.runOnce(signal);
40513
- if (this.stopped || signal?.aborted) break;
40540
+ if (this.stopped || signal?.aborted || this.options.quiesceSignal?.aborted) break;
40514
40541
  await this.waitUntilWake(nextWakeAt, signal);
40515
40542
  }
40516
40543
  exitedNormally = true;
@@ -40533,6 +40560,7 @@ var init_runner = __esm({
40533
40560
  }
40534
40561
  async runOnce(signal) {
40535
40562
  const nowIso = () => new Date(this.now()).toISOString();
40563
+ if (this.options.quiesceSignal?.aborted) return this.now();
40536
40564
  const recoveredState = await readInferenceAgentRuntimeState(this.options.statePath);
40537
40565
  const failureFence = await readInferenceAgentFailureFence(this.options.statePath);
40538
40566
  if (failureFence?.retry_at === null) {
@@ -40588,6 +40616,7 @@ var init_runner = __esm({
40588
40616
  }
40589
40617
  }
40590
40618
  if (!state) {
40619
+ if (this.options.quiesceSignal?.aborted) return this.now();
40591
40620
  const assignment2 = await this.options.controlClient.nextAssignment({
40592
40621
  operation_id: randomUUID7(),
40593
40622
  host_id: this.options.hostId,
@@ -40690,6 +40719,7 @@ var init_runner = __esm({
40690
40719
  await this.handleCancelledAssignment(state, signal);
40691
40720
  return this.now() + (this.options.idlePollMs ?? 5e3);
40692
40721
  }
40722
+ if (this.options.quiesceSignal?.aborted) return this.now();
40693
40723
  assignment = {
40694
40724
  ...assignment,
40695
40725
  lease_expires_at: preTurnHeartbeat.lease_expires_at
@@ -41085,9 +41115,14 @@ var init_runner = __esm({
41085
41115
  }
41086
41116
  async waitUntilWake(nextWakeAtMs, signal) {
41087
41117
  const heartbeatIntervalMs = this.options.heartbeatIntervalMs ?? 3e3;
41088
- while (!this.stopped && !signal?.aborted && this.now() < nextWakeAtMs) {
41089
- await this.sleep(Math.min(heartbeatIntervalMs, nextWakeAtMs - this.now()), signal);
41090
- if (this.stopped || signal?.aborted) return "stopped";
41118
+ while (!this.stopped && !signal?.aborted && !this.options.quiesceSignal?.aborted && this.now() < nextWakeAtMs) {
41119
+ const waitSignal = this.options.quiesceSignal ? AbortSignal.any([this.options.quiesceSignal, ...signal ? [signal] : []]) : signal;
41120
+ try {
41121
+ await this.sleep(Math.min(heartbeatIntervalMs, nextWakeAtMs - this.now()), waitSignal);
41122
+ } catch (error48) {
41123
+ if (!this.options.quiesceSignal?.aborted) throw error48;
41124
+ }
41125
+ if (this.stopped || signal?.aborted || this.options.quiesceSignal?.aborted) return "stopped";
41091
41126
  if (this.now() >= nextWakeAtMs) return "ready";
41092
41127
  const state = await readInferenceAgentRuntimeState(this.options.statePath);
41093
41128
  if (state?.schema_version !== "vtx_inference_agent_runtime_v3" || state.adapter_id !== this.options.adapterId) return "cancelled";
@@ -41112,7 +41147,7 @@ var init_runner = __esm({
41112
41147
  updated_at: requestedAt
41113
41148
  });
41114
41149
  }
41115
- return this.stopped || signal?.aborted ? "stopped" : "ready";
41150
+ return this.stopped || signal?.aborted || this.options.quiesceSignal?.aborted ? "stopped" : "ready";
41116
41151
  }
41117
41152
  async releaseForHostStop(reasonCode = "host_stopped") {
41118
41153
  const recovered = await readInferenceAgentRuntimeState(this.options.statePath);
@@ -41239,6 +41274,8 @@ var init_runtime_policy_generated = __esm({
41239
41274
  },
41240
41275
  external_inference: {
41241
41276
  service: {
41277
+ update_check_interval_ms: 3e5,
41278
+ update_registry_timeout_ms: 15e3,
41242
41279
  startup_max_attempts: 40,
41243
41280
  startup_poll_interval_ms: 250,
41244
41281
  restart_stable_uptime_ms: 6e4,
@@ -41279,13 +41316,14 @@ import { createWriteStream, readFileSync } from "node:fs";
41279
41316
  import { access as access4, chmod as chmod3, mkdir as mkdir3, readFile as readFile9, rm as rm10, writeFile as writeFile4 } from "node:fs/promises";
41280
41317
  import { homedir as homedir2 } from "node:os";
41281
41318
  import { dirname as dirname12, join as join13, resolve as resolve10 } from "node:path";
41282
- var SERVICE_NAME, SYSTEMD_UNIT, LAUNCHD_LABEL, SERVICE_COOPERATIVE_STOP_SECONDS, SERVICE_UPDATE_STARTUP_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, serviceBootstrapArguments, windowsOwnedCommandLine, vbScriptString, windowsServiceLauncher, windowsTaskXml, systemdUnit, launchAgentPlist, sameWorker, sameServiceDefinition, sameWorkerSet, InferenceHostServiceManager, appendServiceLog, spawnInferenceHostServiceChild, runInferenceHostServiceSupervisor;
41319
+ var SERVICE_NAME, SYSTEMD_UNIT, LAUNCHD_LABEL, SERVICE_COOPERATIVE_STOP_SECONDS, SERVICE_UPDATE_STARTUP_SECONDS, INFERENCE_HOST_SERVICE_DRAIN_COMMAND, INFERENCE_HOST_SERVICE_QUIESCE_COMMAND, InferenceHostBootstrapRestartRequiredError, 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, serviceBootstrapArguments, windowsOwnedCommandLine, vbScriptString, windowsServiceLauncher, windowsTaskXml, systemdUnit, launchAgentPlist, sameWorker, sameServiceDefinition, sameWorkerSet, InferenceHostServiceManager, appendServiceLog, spawnInferenceHostServiceChild, runInferenceHostServiceSupervisor;
41283
41320
  var init_service = __esm({
41284
41321
  "lib/inference-host/service.ts"() {
41285
41322
  "use strict";
41286
41323
  init_define_VTX_EXO_POLICY();
41287
41324
  init_define_VTX_GROK_POLICY();
41288
41325
  init_define_VTX_PI_MODEL_POLICY();
41326
+ init_agent_cli_release();
41289
41327
  init_runtime_policy_generated();
41290
41328
  init_durable_adapter();
41291
41329
  init_config();
@@ -41295,6 +41333,12 @@ var init_service = __esm({
41295
41333
  SERVICE_COOPERATIVE_STOP_SECONDS = 75;
41296
41334
  SERVICE_UPDATE_STARTUP_SECONDS = 600;
41297
41335
  INFERENCE_HOST_SERVICE_DRAIN_COMMAND = "vtx-inference-host-service-drain-v1";
41336
+ INFERENCE_HOST_SERVICE_QUIESCE_COMMAND = "vtx-inference-host-service-quiesce-v1";
41337
+ InferenceHostBootstrapRestartRequiredError = class extends Error {
41338
+ constructor() {
41339
+ super("The verified service bootstrap is ready for restart.");
41340
+ }
41341
+ };
41298
41342
  inferenceHostServiceChildEnvironment = (runtimeEnvironment2, inheritedEnvironment = process.env) => {
41299
41343
  const environment = { ...inheritedEnvironment };
41300
41344
  for (const key of Object.keys(environment)) {
@@ -42414,7 +42458,7 @@ ${result2.stderr}`)) {
42414
42458
  });
42415
42459
  };
42416
42460
  spawnInferenceHostServiceChild = async (manifest, worker, signal, onReady = () => {
42417
- }) => {
42461
+ }, quiesceSignal) => {
42418
42462
  const args = [
42419
42463
  manifest.script,
42420
42464
  "inference-host",
@@ -42476,6 +42520,12 @@ ${result2.stderr}`)) {
42476
42520
  child.stderr.on("data", (chunk) => writeTaggedOutput("stderr", chunk.toString("utf8")));
42477
42521
  child.stdin.on("error", () => {
42478
42522
  });
42523
+ const onQuiesce = () => {
42524
+ child.stdin.write(`${INFERENCE_HOST_SERVICE_QUIESCE_COMMAND}
42525
+ `);
42526
+ };
42527
+ if (quiesceSignal?.aborted) onQuiesce();
42528
+ else quiesceSignal?.addEventListener("abort", onQuiesce, { once: true });
42479
42529
  const onAbort = () => {
42480
42530
  child.stdin.end(`${INFERENCE_HOST_SERVICE_DRAIN_COMMAND}
42481
42531
  `);
@@ -42489,6 +42539,7 @@ ${result2.stderr}`)) {
42489
42539
  else signal.addEventListener("abort", onAbort, { once: true });
42490
42540
  child.once("error", (error48) => {
42491
42541
  signal.removeEventListener("abort", onAbort);
42542
+ quiesceSignal?.removeEventListener("abort", onQuiesce);
42492
42543
  if (cooperativeStopTimer) clearTimeout(cooperativeStopTimer);
42493
42544
  writeTaggedOutput("stdout", "", true);
42494
42545
  writeTaggedOutput("stderr", "", true);
@@ -42497,6 +42548,7 @@ ${result2.stderr}`)) {
42497
42548
  });
42498
42549
  child.once("exit", (code) => {
42499
42550
  signal.removeEventListener("abort", onAbort);
42551
+ quiesceSignal?.removeEventListener("abort", onQuiesce);
42500
42552
  if (cooperativeStopTimer) clearTimeout(cooperativeStopTimer);
42501
42553
  writeTaggedOutput("stdout", "", true);
42502
42554
  writeTaggedOutput("stderr", "", true);
@@ -42532,6 +42584,38 @@ ${result2.stderr}`)) {
42532
42584
  const desiredPath = manifestPath.replace(/\.service\.json$/u, ".service-desired.json");
42533
42585
  const runtimePath = manifestPath.replace(/\.service\.json$/u, ".service-runtime.json");
42534
42586
  const signal = options.signal ?? new AbortController().signal;
42587
+ const migrationQuiesce = new AbortController();
42588
+ const quiesceSignal = options.quiesceSignal ? AbortSignal.any([options.quiesceSignal, migrationQuiesce.signal]) : migrationQuiesce.signal;
42589
+ let bootstrapRestartRequired = false;
42590
+ let bootstrapMigrationErrorReported = false;
42591
+ const migrateSelectedBootstrap = async () => {
42592
+ if (!serviceScript || !options.controlLockPath || bootstrapRestartRequired || (options.bootstrapVersion ?? process.env.VTX_INFERENCE_HOST_BOOTSTRAP_VERSION) === agent_cli_release_default.package_version || quiesceSignal.aborted || signal.aborted) return;
42593
+ let controlLock;
42594
+ try {
42595
+ controlLock = await acquireInferenceHostProcessLock(options.controlLockPath);
42596
+ } catch (error48) {
42597
+ if (/already owns/iu.test(error48 instanceof Error ? error48.message : "")) return;
42598
+ throw error48;
42599
+ }
42600
+ try {
42601
+ const current = await readManifestAcrossAtomicReplacement(manifestPath);
42602
+ if (!current || current.generation !== appliedManifest.generation || !await readDesiredAcrossAtomicReplacement(desiredPath) || signal.aborted) return;
42603
+ let selected;
42604
+ try {
42605
+ selected = JSON.parse(await readFile9(manifestPath.replace(/\.service\.json$/u, ".service-release.json"), "utf8"));
42606
+ } catch (error48) {
42607
+ if (error48.code === "ENOENT") return;
42608
+ throw error48;
42609
+ }
42610
+ if (selected.schema_version !== "vtx_inference_service_release_v1" || selected.package_name !== "@vtxmacro/cli" || selected.package_version !== agent_cli_release_default.package_version || typeof selected.script !== "string" || resolve10(selected.script) !== serviceScript) return;
42611
+ const bytes3 = await readFile9(join13(dirname12(serviceScript), "vtx-service-bootstrap.js"), "utf8");
42612
+ await writeAtomicInferencePrivateFile(manifestPath.replace(/\.service\.json$/u, ".service-bootstrap.js"), bytes3);
42613
+ bootstrapRestartRequired = true;
42614
+ migrationQuiesce.abort();
42615
+ } finally {
42616
+ await controlLock.release();
42617
+ }
42618
+ };
42535
42619
  const sleep4 = options.sleep ?? (async (milliseconds) => {
42536
42620
  await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
42537
42621
  });
@@ -42585,7 +42669,7 @@ ${result2.stderr}`)) {
42585
42669
  };
42586
42670
  const superviseWorker = async (serviceManifest, record4) => {
42587
42671
  let failures = 0;
42588
- while (!signal.aborted && !record4.controller.signal.aborted && await readDesiredAcrossAtomicReplacement(desiredPath)) {
42672
+ while (!signal.aborted && !quiesceSignal.aborted && !record4.controller.signal.aborted && await readDesiredAcrossAtomicReplacement(desiredPath)) {
42589
42673
  try {
42590
42674
  await updateRecord(record4, "starting");
42591
42675
  const attemptController = new AbortController();
@@ -42610,7 +42694,8 @@ ${result2.stderr}`)) {
42610
42694
  await updateRecord(record4, "running");
42611
42695
  }
42612
42696
  })();
42613
- }
42697
+ },
42698
+ quiesceSignal
42614
42699
  );
42615
42700
  } finally {
42616
42701
  launchSettled = true;
@@ -42626,7 +42711,7 @@ ${result2.stderr}`)) {
42626
42711
  });
42627
42712
  throw new Error("worker_cooperative_stop_timeout");
42628
42713
  }
42629
- if (signal.aborted || record4.controller.signal.aborted || !await readDesiredAcrossAtomicReplacement(desiredPath)) break;
42714
+ if (signal.aborted || quiesceSignal.aborted || record4.controller.signal.aborted || !await readDesiredAcrossAtomicReplacement(desiredPath)) break;
42630
42715
  failures = result2.uptimeMs >= runtime_policy_generated_default.external_inference.service.restart_stable_uptime_ms ? 0 : failures + 1;
42631
42716
  const retryAfterMs = Math.min(runtime_policy_generated_default.external_inference.service.restart_backoff_base_ms * 2 ** Math.min(failures, runtime_policy_generated_default.external_inference.service.restart_exponent_max), runtime_policy_generated_default.external_inference.service.restart_backoff_max_ms);
42632
42717
  await updateRecord(record4, "failed", "worker_exited");
@@ -42640,7 +42725,7 @@ ${result2.stderr}`)) {
42640
42725
  await waitRetry(retryAfterMs, record4.controller);
42641
42726
  } catch (error48) {
42642
42727
  if (error48 instanceof Error && error48.message === "worker_cooperative_stop_timeout") throw error48;
42643
- if (signal.aborted || record4.controller.signal.aborted) break;
42728
+ if (signal.aborted || quiesceSignal.aborted || record4.controller.signal.aborted) break;
42644
42729
  failures += 1;
42645
42730
  const retryAfterMs = Math.min(runtime_policy_generated_default.external_inference.service.restart_backoff_base_ms * 2 ** Math.min(failures, runtime_policy_generated_default.external_inference.service.restart_exponent_max), runtime_policy_generated_default.external_inference.service.restart_backoff_max_ms);
42646
42731
  await updateRecord(record4, "failed", "worker_launch_failed");
@@ -42723,14 +42808,41 @@ ${result2.stderr}`)) {
42723
42808
  };
42724
42809
  await reconcile(initialManifest);
42725
42810
  try {
42726
- while (!signal.aborted) {
42727
- while (!signal.aborted && await readDesiredAcrossAtomicReplacement(desiredPath)) {
42811
+ while (!signal.aborted && !quiesceSignal.aborted) {
42812
+ while (!signal.aborted && !quiesceSignal.aborted && await readDesiredAcrossAtomicReplacement(desiredPath)) {
42813
+ try {
42814
+ await migrateSelectedBootstrap();
42815
+ } catch {
42816
+ if (!bootstrapMigrationErrorReported) {
42817
+ bootstrapMigrationErrorReported = true;
42818
+ await appendServiceLog(appliedManifest.log_path, "service_bootstrap_migration_deferred");
42819
+ }
42820
+ }
42821
+ if (quiesceSignal.aborted) break;
42728
42822
  const storedNext = await readManifestAcrossAtomicReplacement(manifestPath);
42729
42823
  if (!storedNext) throw new Error("Inference-host service manifest is missing.");
42730
42824
  const next = withServiceScript(storedNext);
42731
42825
  if (next.generation !== appliedManifest.generation && next.generation !== rejectedManifestGeneration) await reconcile(next);
42732
42826
  await sleep4(250);
42733
42827
  }
42828
+ if (quiesceSignal.aborted && !signal.aborted) {
42829
+ let finished = false;
42830
+ const completion = Promise.allSettled([...workers.values()].map((record4) => record4.promise)).then((results) => {
42831
+ finished = true;
42832
+ return results;
42833
+ });
42834
+ while (!finished && !signal.aborted && await readDesiredAcrossAtomicReplacement(desiredPath)) {
42835
+ await Promise.race([completion, sleep4(250)]);
42836
+ }
42837
+ if (!finished) {
42838
+ await drainWorkers();
42839
+ break;
42840
+ }
42841
+ const settled = await completion;
42842
+ const failure2 = settled.find((result2) => result2.status === "rejected");
42843
+ if (failure2) throw failure2.reason;
42844
+ break;
42845
+ }
42734
42846
  await drainWorkers();
42735
42847
  if (signal.aborted || !await readDesiredAcrossAtomicReplacement(desiredPath)) break;
42736
42848
  const storedRestored = await readManifestAcrossAtomicReplacement(manifestPath);
@@ -42747,6 +42859,9 @@ ${result2.stderr}`)) {
42747
42859
  await runtimeWriteChain;
42748
42860
  await appendServiceLog(appliedManifest.log_path, "service_supervisor_stopped");
42749
42861
  }
42862
+ if (bootstrapRestartRequired && !signal.aborted && await readDesiredAcrossAtomicReplacement(desiredPath)) {
42863
+ throw new InferenceHostBootstrapRestartRequiredError();
42864
+ }
42750
42865
  };
42751
42866
  }
42752
42867
  });
@@ -43242,7 +43357,7 @@ Durable service:
43242
43357
  });
43243
43358
  child.unref();
43244
43359
  };
43245
- registerInferenceHostServiceControlInput = (input, abort) => {
43360
+ registerInferenceHostServiceControlInput = (input, abort, quiesce) => {
43246
43361
  let pendingControlInput = "";
43247
43362
  const onControlInput = (chunk) => {
43248
43363
  pendingControlInput = `${pendingControlInput}${chunk.toString()}`.slice(-4096);
@@ -43250,6 +43365,8 @@ Durable service:
43250
43365
  pendingControlInput = lines.pop() ?? "";
43251
43366
  if (lines.some((line) => line === INFERENCE_HOST_SERVICE_DRAIN_COMMAND)) {
43252
43367
  abort();
43368
+ } else if (lines.some((line) => line === INFERENCE_HOST_SERVICE_QUIESCE_COMMAND)) {
43369
+ quiesce?.();
43253
43370
  }
43254
43371
  };
43255
43372
  input.on("data", onControlInput);
@@ -43259,13 +43376,13 @@ Durable service:
43259
43376
  input.pause();
43260
43377
  };
43261
43378
  };
43262
- defaultRegisterLifecycleSignalHandlers = (abort) => {
43379
+ defaultRegisterLifecycleSignalHandlers = (abort, quiesce) => {
43263
43380
  const onSigint = () => abort("SIGINT");
43264
43381
  const onSigterm = () => abort("SIGTERM");
43265
43382
  process.on("SIGINT", onSigint);
43266
43383
  process.on("SIGTERM", onSigterm);
43267
43384
  const serviceChild = process.env.VTX_INFERENCE_HOST_SERVICE_CHILD === "1";
43268
- const unregisterControlInput = serviceChild ? registerInferenceHostServiceControlInput(process.stdin, onSigterm) : () => {
43385
+ const unregisterControlInput = serviceChild ? registerInferenceHostServiceControlInput(process.stdin, onSigterm, quiesce) : () => {
43269
43386
  };
43270
43387
  return () => {
43271
43388
  process.off("SIGINT", onSigint);
@@ -43275,12 +43392,14 @@ Durable service:
43275
43392
  };
43276
43393
  lifecycleCancellation = (dependencies) => {
43277
43394
  const controller = new AbortController();
43395
+ const quiesceController = new AbortController();
43278
43396
  let unregistered = false;
43279
43397
  const unregisterSignalHandlers = (dependencies.registerLifecycleSignalHandlers ?? defaultRegisterLifecycleSignalHandlers)(() => {
43280
43398
  if (!controller.signal.aborted) controller.abort();
43281
- });
43399
+ }, () => quiesceController.abort());
43282
43400
  return {
43283
43401
  signal: controller.signal,
43402
+ quiesceSignal: quiesceController.signal,
43284
43403
  unregister: () => {
43285
43404
  if (unregistered) return;
43286
43405
  unregistered = true;
@@ -43486,7 +43605,8 @@ Durable service:
43486
43605
  adapter: agentAdapter,
43487
43606
  dataRequestTimeoutMs: await loadInferenceAgentDataRequestTimeoutMs()
43488
43607
  },
43489
- signal: options.signal
43608
+ signal: options.signal,
43609
+ quiesceSignal: options.quiesceSignal
43490
43610
  }).run();
43491
43611
  };
43492
43612
  preparePortableDurableAdapter = async (adapterId, config2, signal) => {
@@ -43557,7 +43677,8 @@ Durable service:
43557
43677
  adapter: agentAdapter,
43558
43678
  dataRequestTimeoutMs: await loadInferenceAgentDataRequestTimeoutMs()
43559
43679
  },
43560
- signal: options.signal
43680
+ signal: options.signal,
43681
+ quiesceSignal: options.quiesceSignal
43561
43682
  }).run();
43562
43683
  };
43563
43684
  login = async (config2, parsed, env, dependencies, warnings) => {
@@ -44355,7 +44476,8 @@ Waiting for approval...
44355
44476
  once: parsed.once,
44356
44477
  processLock,
44357
44478
  emitDiagnosticEvent,
44358
- signal: cancellation.signal
44479
+ signal: cancellation.signal,
44480
+ quiesceSignal: cancellation.quiesceSignal
44359
44481
  });
44360
44482
  return {
44361
44483
  exitCode: 0,
@@ -44395,7 +44517,8 @@ Waiting for approval...
44395
44517
  env,
44396
44518
  processLock,
44397
44519
  emitDiagnosticEvent,
44398
- signal: cancellation.signal
44520
+ signal: cancellation.signal,
44521
+ quiesceSignal: cancellation.quiesceSignal
44399
44522
  });
44400
44523
  return {
44401
44524
  exitCode: 0,
@@ -45719,10 +45842,15 @@ Waiting for approval...
45719
45842
  parsed.serviceManifestPath,
45720
45843
  {
45721
45844
  signal: cancellation.signal,
45722
- serviceScript: resolve11(process.argv[1] ?? "")
45845
+ quiesceSignal: cancellation.quiesceSignal,
45846
+ serviceScript: resolve11(process.argv[1] ?? ""),
45847
+ controlLockPath: `${supervisorConfig.supervisorProcessLockPath}.service-control`
45723
45848
  }
45724
45849
  );
45725
45850
  return { exitCode: 0, stdout: "", stderr: "" };
45851
+ } catch (error48) {
45852
+ if (error48 instanceof InferenceHostBootstrapRestartRequiredError) return { exitCode: 75, stdout: "", stderr: "" };
45853
+ throw error48;
45726
45854
  } finally {
45727
45855
  cancellation.unregister();
45728
45856
  await serviceLock.release();
@@ -49775,6 +49903,8 @@ var init_runtime_policy_generated2 = __esm({
49775
49903
  },
49776
49904
  external_inference: {
49777
49905
  service: {
49906
+ update_check_interval_ms: 3e5,
49907
+ update_registry_timeout_ms: 15e3,
49778
49908
  startup_max_attempts: 40,
49779
49909
  startup_poll_interval_ms: 250,
49780
49910
  restart_stable_uptime_ms: 6e4,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtxmacro/cli",
3
- "version": "2026.9.29",
3
+ "version": "2026.9.30",
4
4
  "description": "VTX Macro CLI, MCP server, and durable external inference host.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",