@zixt/host 0.0.82 → 0.0.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +96 -22
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ import { homedir as homedir3 } from "node:os";
31
31
  // package.json
32
32
  var package_default = {
33
33
  name: "@zixt/host",
34
- version: "0.0.82",
34
+ version: "0.0.84",
35
35
  type: "module",
36
36
  exports: {
37
37
  ".": "./src/client.ts",
@@ -16805,6 +16805,7 @@ var ListTasksResponse = external_exports.object({
16805
16805
  // ../../packages/contracts/src/protocol.ts
16806
16806
  var PROTOCOL_VERSION = 8;
16807
16807
  var BROWSER_PROFILE_INVENTORY_PAGE_SIZE = 200;
16808
+ var HELLO_UNWOUND_ASSIGNMENT_LIMIT = 1e3;
16808
16809
  var TASK_CANCEL_ACK_EVENT = "zixt.task.cancel.acknowledged";
16809
16810
  var TASK_CREDENTIAL_ROLLOVER_ACK_EVENT = "zixt.task.credential_rollover.acknowledged";
16810
16811
  var UnwoundAssignmentRef = external_exports.object({
@@ -18051,7 +18052,7 @@ var HelloFrame = external_exports.object({
18051
18052
  * prior socket died. Optional for rolling v5 hosts; repeated references are
18052
18053
  * idempotent and remain presence/host/org/epoch fenced by the cloud.
18053
18054
  */
18054
- unwoundAssignments: external_exports.array(UnwoundAssignmentRef).max(1e3).optional()
18055
+ unwoundAssignments: external_exports.array(UnwoundAssignmentRef).max(HELLO_UNWOUND_ASSIGNMENT_LIMIT).optional()
18055
18056
  });
18056
18057
  var BrowserProfileInventoryFrame = external_exports.object({
18057
18058
  type: external_exports.literal("browser.profile.inventory"),
@@ -18176,6 +18177,8 @@ var CLOSE_CODES = {
18176
18177
  replacedByNewerConnection: 4003,
18177
18178
  revoked: 4004
18178
18179
  };
18180
+ var UNSUPPORTED_PROTOCOL_CLOSE_REASON = "unsupported protocol version";
18181
+ var CLOSE_REASON_MAX_BYTES = 123;
18179
18182
 
18180
18183
  // ../../packages/contracts/src/manager.ts
18181
18184
  var MANAGER_DEFAULT_MODEL = "gpt-5.6-luna";
@@ -21315,6 +21318,17 @@ function operationGrantRetryCause(code) {
21315
21318
  }
21316
21319
  return "GitHub is temporarily unavailable for credential issuance";
21317
21320
  }
21321
+ function closeDetail(reason) {
21322
+ if (reason === void 0) return void 0;
21323
+ const raw = typeof reason === "string" ? reason : reason.toString("utf8");
21324
+ let printable = "";
21325
+ for (const character of raw) {
21326
+ const code = character.codePointAt(0) ?? 0;
21327
+ printable += code < 32 || code === 127 ? " " : character;
21328
+ }
21329
+ const text = printable.trim().slice(0, CLOSE_REASON_MAX_BYTES);
21330
+ return text.length > 0 ? text : void 0;
21331
+ }
21318
21332
  var HostClient = class _HostClient {
21319
21333
  constructor(opts) {
21320
21334
  this.opts = opts;
@@ -21687,9 +21701,13 @@ var HostClient = class _HostClient {
21687
21701
  const inventoryConfigured = Boolean(
21688
21702
  this.opts.browser?.profileInventoryPages || this.opts.browser?.profileInventory
21689
21703
  );
21690
- const unwoundAssignments = [...this.pendingUnwoundAssignments.values()].sort(
21704
+ const disclaimable = [...this.pendingUnwoundAssignments.values()].sort(
21691
21705
  (left, right) => left.taskId === right.taskId ? left.epoch - right.epoch : left.taskId.localeCompare(right.taskId)
21692
21706
  );
21707
+ const unwoundAssignments = disclaimable.slice(0, HELLO_UNWOUND_ASSIGNMENT_LIMIT);
21708
+ if (disclaimable.length > unwoundAssignments.length) {
21709
+ this.opts.onDisclaimerBacklog?.(disclaimable.length, unwoundAssignments.length);
21710
+ }
21693
21711
  this.send(
21694
21712
  {
21695
21713
  type: "hello",
@@ -21721,14 +21739,14 @@ var HostClient = class _HostClient {
21721
21739
  if (this.ws === ws) ws.terminate();
21722
21740
  });
21723
21741
  });
21724
- ws.on("close", (code) => {
21742
+ ws.on("close", (code, reason) => {
21725
21743
  if (inventoryRetryTimer) clearTimeout(inventoryRetryTimer);
21726
- void this.handleClose(ws, code, closeReason, frameTail);
21744
+ void this.handleClose(ws, code, closeReason, frameTail, closeDetail(reason));
21727
21745
  });
21728
21746
  ws.on("error", () => {
21729
21747
  });
21730
21748
  }
21731
- async handleClose(ws, code, closeReason, frameTail = Promise.resolve()) {
21749
+ async handleClose(ws, code, closeReason, frameTail = Promise.resolve(), detail) {
21732
21750
  if (this.ws !== ws) return;
21733
21751
  this.ws = void 0;
21734
21752
  this.protocolReady = false;
@@ -21767,13 +21785,18 @@ var HostClient = class _HostClient {
21767
21785
  await this.unwindAllRuns("host connection was replaced by a newer client", "client_shutdown");
21768
21786
  return;
21769
21787
  }
21770
- if (code === CLOSE_CODES.protocolError) {
21771
- this.opts.onStatus?.("incompatible");
21788
+ const versionMismatch = code === CLOSE_CODES.protocolError && detail === UNSUPPORTED_PROTOCOL_CLOSE_REASON;
21789
+ if (versionMismatch) {
21790
+ this.opts.onStatus?.("incompatible", detail);
21772
21791
  this.stopped = true;
21773
21792
  await this.unwindAllRuns("host protocol is incompatible", "client_shutdown");
21774
21793
  return;
21775
21794
  }
21776
- this.opts.onStatus?.(closeReason === "heartbeat_timeout" ? "unresponsive" : "disconnected");
21795
+ if (code === CLOSE_CODES.protocolError) {
21796
+ this.opts.onStatus?.("rejected", detail);
21797
+ } else {
21798
+ this.opts.onStatus?.(closeReason === "heartbeat_timeout" ? "unresponsive" : "disconnected");
21799
+ }
21777
21800
  if (this.stopped) return;
21778
21801
  await this.unwindAllRuns(
21779
21802
  closeReason === "heartbeat_timeout" ? "Zixt Cloud stopped answering heartbeat probes" : "host connection was lost",
@@ -25446,6 +25469,9 @@ var DO_NOT_RESTART_EXIT_CODE = 64;
25446
25469
  var RESTART_BACKOFF_MS = 2e3;
25447
25470
  var MAX_RESTART_BACKOFF_MS = 6e4;
25448
25471
  var CRASH_STREAK_RESET_MS = 6e4;
25472
+ function streakBackoffMs(streak) {
25473
+ return Math.min(MAX_RESTART_BACKOFF_MS, RESTART_BACKOFF_MS * 2 ** Math.min(streak - 1, 10));
25474
+ }
25449
25475
  var RELEASE_PROBATION_MS = 5 * 6e4;
25450
25476
  var FAST_UPDATE_EXIT_MS = 1e4;
25451
25477
  var STOP_GRACE_MS = 3e4;
@@ -25496,6 +25522,23 @@ function versionsRoot() {
25496
25522
  function npmCommand(platform = process.platform) {
25497
25523
  return platform === "win32" ? "npm.cmd" : "npm";
25498
25524
  }
25525
+ async function resolveInstallerCommand(options = {}) {
25526
+ const platform = options.platform ?? process.platform;
25527
+ const command = npmCommand(platform);
25528
+ if (platform !== "win32") return command;
25529
+ const execPath = options.execPath ?? process.execPath;
25530
+ const resolution = {
25531
+ platform,
25532
+ ...options.searchPath !== void 0 ? { searchPath: options.searchPath } : {}
25533
+ };
25534
+ return (
25535
+ // npm ships beside the `node` running this process, and that copy is the
25536
+ // one matching it. A per-user Scheduled Task may carry no npm on PATH.
25537
+ await resolveTrustedCliCommand(join8(dirname4(execPath), command), resolution) ?? await resolveTrustedCliCommand(command, resolution) ?? // Nothing resolved: keep the previous behaviour, so a Machine without npm
25538
+ // fails one install rather than changing how failure is handled.
25539
+ command
25540
+ );
25541
+ }
25499
25542
  function windowsInstallerCommandLine(command, args) {
25500
25543
  return [command, ...args].map(quoteForCmd).join(" ");
25501
25544
  }
@@ -25788,12 +25831,12 @@ var InstallerContainmentError = class extends Error {
25788
25831
  async function installRelease(version2, options = {}) {
25789
25832
  if (!VERSION_DIR.test(version2)) return null;
25790
25833
  const platform = options.platform ?? process.platform;
25791
- const installerCommand = options.installerCommand ?? npmCommand(platform);
25792
25834
  const root = options.root ?? versionsRoot();
25793
25835
  const prefix = join8(root, version2);
25794
25836
  const entry = installedReleaseEntry(version2, root);
25795
25837
  if (await validReleaseAtPrefix(prefix, version2)) return entry;
25796
25838
  if (options.signal?.aborted) return null;
25839
+ const installerCommand = options.installerCommand ?? await resolveInstallerCommand({ platform });
25797
25840
  await mkdir5(root, { recursive: true, mode: 448 });
25798
25841
  const staging = join8(root, `.install-${version2}-${process.pid}-${crypto.randomUUID()}`);
25799
25842
  const quarantine = join8(root, `.invalid-${version2}-${process.pid}-${crypto.randomUUID()}`);
@@ -26602,6 +26645,7 @@ async function superviseHost(options = {}) {
26602
26645
  announceShutdown = resolve18;
26603
26646
  });
26604
26647
  const attempted = /* @__PURE__ */ new Set();
26648
+ let unsatisfiableUpdates = 0;
26605
26649
  const waitOrShutdown = async (ms) => {
26606
26650
  if (shuttingDown2) return false;
26607
26651
  if (!customDelay) {
@@ -26622,11 +26666,7 @@ async function superviseHost(options = {}) {
26622
26666
  const waitAfterCrash = async (runtimeMs) => {
26623
26667
  if (runtimeMs >= CRASH_STREAK_RESET_MS) consecutiveCrashes = 0;
26624
26668
  consecutiveCrashes++;
26625
- const backoff = Math.min(
26626
- MAX_RESTART_BACKOFF_MS,
26627
- RESTART_BACKOFF_MS * 2 ** Math.min(consecutiveCrashes - 1, 10)
26628
- );
26629
- return waitOrShutdown(backoff);
26669
+ return waitOrShutdown(streakBackoffMs(consecutiveCrashes));
26630
26670
  };
26631
26671
  const cleanupExitedWorker = async (exitedChild, childExited, workerBoundary, containment) => {
26632
26672
  for (let attempt = 1; attempt <= CLEANUP_ATTEMPTS2; attempt++) {
@@ -26989,10 +27029,15 @@ async function superviseHost(options = {}) {
26989
27029
  consecutiveCrashes = 0;
26990
27030
  if (orderlyValidatedBoundary) rollbackCandidate = null;
26991
27031
  const target = await published();
27032
+ let updateLanded = false;
26992
27033
  if (target === null) {
26993
27034
  log2(
26994
27035
  "Zixt Host: a newer release was reported but the registry is unreachable; staying on the current version"
26995
27036
  );
27037
+ } else if (target === command.version) {
27038
+ log2(
27039
+ `Zixt Host: this Machine already runs the newest published release (${target}), so an update cannot satisfy the cloud; waiting for a newer one`
27040
+ );
26996
27041
  } else if (attempted.has(target)) {
26997
27042
  log2(`Zixt Host: already running ${target}; ignoring a repeated update request`);
26998
27043
  } else {
@@ -27066,6 +27111,7 @@ async function superviseHost(options = {}) {
27066
27111
  }
27067
27112
  rollbackCandidate = { failedVersion: target, command: fallback };
27068
27113
  command = { entry, version: target };
27114
+ updateLanded = true;
27069
27115
  if (reloadAfterActivation) return SUPERVISOR_RELOAD_EXIT_CODE;
27070
27116
  if (releaseStore) {
27071
27117
  const probation = durableState("probation", target, fallback.version);
@@ -27093,7 +27139,10 @@ async function superviseHost(options = {}) {
27093
27139
  log2(`Zixt Host: could not install ${target}; staying on the current version`);
27094
27140
  }
27095
27141
  }
27096
- if (now() - startedAt < FAST_UPDATE_EXIT_MS && !await waitOrShutdown(RESTART_BACKOFF_MS)) {
27142
+ unsatisfiableUpdates = updateLanded ? 0 : unsatisfiableUpdates + 1;
27143
+ if (unsatisfiableUpdates > 0) {
27144
+ if (!await waitOrShutdown(streakBackoffMs(unsatisfiableUpdates))) return 0;
27145
+ } else if (now() - startedAt < FAST_UPDATE_EXIT_MS && !await waitOrShutdown(RESTART_BACKOFF_MS)) {
27097
27146
  return 0;
27098
27147
  }
27099
27148
  if (shuttingDown2) return 0;
@@ -40385,16 +40434,26 @@ var colorOverride = forcedColor(cliOptions.color) ?? (process.env[HOST_CONSOLE_C
40385
40434
  var hostConsoleTail = [];
40386
40435
  var hostConsoleSequence = 0;
40387
40436
  var clientForConsole = {};
40437
+ function recordConsoleLine(line) {
40438
+ const entry = { sequence: ++hostConsoleSequence, line };
40439
+ hostConsoleTail.push(entry);
40440
+ if (hostConsoleTail.length > 200) hostConsoleTail.shift();
40441
+ clientForConsole.current?.hostConsoleLine(entry);
40442
+ }
40388
40443
  var log = createHostLogger({
40389
40444
  ...colorOverride === void 0 ? {} : { color: colorOverride },
40390
40445
  write: (line) => {
40391
40446
  console.error(line);
40392
- const entry = { sequence: ++hostConsoleSequence, line };
40393
- hostConsoleTail.push(entry);
40394
- if (hostConsoleTail.length > 200) hostConsoleTail.shift();
40395
- clientForConsole.current?.hostConsoleLine(entry);
40447
+ recordConsoleLine(line);
40396
40448
  }
40397
40449
  });
40450
+ function replayConsoleLine(level, message, context) {
40451
+ recordConsoleLine(
40452
+ formatHostLogLine(level, message, context, {
40453
+ ...colorOverride === void 0 ? {} : { color: colorOverride }
40454
+ })
40455
+ );
40456
+ }
40398
40457
  if (cliOptions.help) {
40399
40458
  process.stdout.write(`${hostHelpText()}
40400
40459
  `);
@@ -40779,6 +40838,14 @@ var client = new HostClient({
40779
40838
  forgetAcknowledgedAcceptedAssignments(assignments, acceptedAssignmentRoot)
40780
40839
  ]);
40781
40840
  },
40841
+ onDisclaimerBacklog: (pending, reported) => {
40842
+ log.warn("More unfinished Task claims than one connection can report", {
40843
+ machine,
40844
+ pending,
40845
+ reported,
40846
+ next: "The rest are reported as this Machine reconnects; report this if it does not clear"
40847
+ });
40848
+ },
40782
40849
  persistAcceptedAssignment: (assignment) => recordAcceptedAssignment(assignment, acceptedAssignmentRoot).then(() => void 0),
40783
40850
  forgetAcceptedAssignment: (assignment) => forgetAcceptedAssignment(assignment, acceptedAssignmentRoot),
40784
40851
  persistTerminalOutcome: (hostId, outcome) => recordTerminalOutcome(hostId, outcome, terminalOutcomeRoot),
@@ -40810,7 +40877,7 @@ var client = new HostClient({
40810
40877
  onUsableTelemetryReport: () => {
40811
40878
  reportWorkerReadiness();
40812
40879
  },
40813
- onStatus: (status) => {
40880
+ onStatus: (status, detail) => {
40814
40881
  if (status === lastReportedStatus) return;
40815
40882
  lastReportedStatus = status;
40816
40883
  switch (status) {
@@ -40850,6 +40917,13 @@ var client = new HostClient({
40850
40917
  });
40851
40918
  queueMicrotask(() => shutdown(packagedBuild ? UPDATE_EXIT_CODE : DO_NOT_RESTART_EXIT_CODE));
40852
40919
  break;
40920
+ case "rejected":
40921
+ log.error("Zixt Cloud refused this Machine", {
40922
+ ...connectionContext,
40923
+ reason: detail ?? "the cloud gave no reason",
40924
+ next: "Zixt keeps retrying; report this reason if the Machine stays offline, because no update changes it"
40925
+ });
40926
+ break;
40853
40927
  }
40854
40928
  },
40855
40929
  executor,
@@ -40867,7 +40941,7 @@ for (const { record: record2 } of reportedWorkerExits) {
40867
40941
  ...context,
40868
40942
  next: "This Machine restarted itself; report it if the same exit repeats"
40869
40943
  });
40870
- for (const line of record2.stderr) log.error("Previous worker output", { machine, line });
40944
+ for (const line of record2.stderr) replayConsoleLine("error", "Previous worker output", { line });
40871
40945
  }
40872
40946
  log.info("Starting Zixt Host", connectionContext);
40873
40947
  client.start();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zixt/host",
3
- "version": "0.0.82",
3
+ "version": "0.0.84",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/client.ts",