@zixt/host 0.0.8 → 0.0.10

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 +209 -24
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ import { homedir } from "node:os";
31
31
  // package.json
32
32
  var package_default = {
33
33
  name: "@zixt/host",
34
- version: "0.0.8",
34
+ version: "0.0.10",
35
35
  type: "module",
36
36
  exports: {
37
37
  ".": "./src/client.ts",
@@ -15272,6 +15272,12 @@ var HostTelemetry = external_exports.object({
15272
15272
  * recovery path; this is deliberately not inferred from package version.
15273
15273
  */
15274
15274
  taskAuthorityLifecycle: external_exports.literal(true).optional(),
15275
+ /**
15276
+ * Understands structured provider-operation retry responses and can
15277
+ * wait cancellably before repeating the same logical request. Missing
15278
+ * legacy Hosts must receive the original strict refusal frame.
15279
+ */
15280
+ providerOperationGrantRetry: external_exports.literal(true).optional(),
15275
15281
  linearToolPack: external_exports.boolean().default(false),
15276
15282
  providerToolPacks: external_exports.array(ProviderToolPackCapability).max(20).superRefine((capabilities, ctx) => {
15277
15283
  const providers = capabilities.map(({ provider }) => provider);
@@ -15412,12 +15418,37 @@ var HostRequiredIntegrationSummary = external_exports.object({
15412
15418
  failedCount: external_exports.number().int().min(0),
15413
15419
  unknownCount: external_exports.number().int().min(0)
15414
15420
  });
15421
+ var HostQueuedTaskDetail = external_exports.object({
15422
+ id: TaskId,
15423
+ title: external_exports.string().min(1).max(300),
15424
+ agentName: external_exports.string().min(1).max(120),
15425
+ runnerType: RunnerType,
15426
+ placement: external_exports.enum([
15427
+ "ready_here",
15428
+ "ready_elsewhere",
15429
+ "blocked_everywhere",
15430
+ "assigned_elsewhere",
15431
+ "evaluation_unknown"
15432
+ ]),
15433
+ placementReason: external_exports.string().min(1).max(2e3),
15434
+ eligibleMachineCount: external_exports.number().int().min(0),
15435
+ /** A bounded preview; eligibleMachineCount remains exact when this is truncated. */
15436
+ eligibleMachineNames: external_exports.array(external_exports.string().min(1).max(120)).max(10),
15437
+ /** Queue timing is independent from Machine placement above. */
15438
+ queueState: external_exports.enum(["ready", "decision_pending", "waiting", "retry_due", "retry_overdue"]),
15439
+ queueReason: external_exports.string().min(1).max(2e3),
15440
+ retryAt: IsoDate2.nullable()
15441
+ });
15415
15442
  var HostWorkSummary = external_exports.object({
15416
15443
  activeTaskCount: external_exports.number().int().min(0),
15417
15444
  queuedOrgTaskCount: external_exports.number().int().min(0),
15418
15445
  queuedEligibleTaskCount: external_exports.number().int().min(0),
15446
+ /** Exact count across the full relevant queue, independent of the bounded detail preview. */
15447
+ overdueRelevantTaskCount: external_exports.number().int().min(0),
15419
15448
  /** Runner families used by queued Tasks that may target this Machine. */
15420
15449
  queuedRunnerTypes: external_exports.array(RunnerType),
15450
+ /** Bounded attention-first queue preview with exact Admin/Owner placement evidence. */
15451
+ queuedTaskDetails: external_exports.array(HostQueuedTaskDetail).max(10),
15421
15452
  eligibilityState: HostEligibilityState,
15422
15453
  eligibilityReason: external_exports.string().min(1).max(2e3)
15423
15454
  });
@@ -15907,8 +15938,8 @@ var CreateTaskInput = external_exports.object({
15907
15938
  /** May be empty only when the message carries attachments (TS-15). */
15908
15939
  instructions: external_exports.string().max(1e5),
15909
15940
  priority: Task.shape.priority.default(0),
15910
- /** Must name one of the selected agent's configured workspaces. */
15911
- workspace: SafeDisplayPath.optional(),
15941
+ /** Omitted uses the teammate default; null explicitly uses its Zixt-owned persistent folder. */
15942
+ workspace: SafeDisplayPath.nullable().optional(),
15912
15943
  interactionMode: InteractionMode.optional(),
15913
15944
  /** Ask the agent to post a plan via ask_user before doing any work. */
15914
15945
  planFirst: external_exports.boolean().optional(),
@@ -18133,6 +18164,30 @@ var ProviderOperationGrantRequestFrame = external_exports.discriminatedUnion("op
18133
18164
  }
18134
18165
  )
18135
18166
  ]);
18167
+ var ProviderOperationGrantFailureCode = external_exports.enum([
18168
+ "provider_timeout",
18169
+ "provider_rate_limited",
18170
+ "provider_unavailable",
18171
+ "provider_authority_rejected",
18172
+ "provider_request_rejected",
18173
+ "provider_not_found",
18174
+ "provider_invalid_response",
18175
+ "provider_issuance_unresolved",
18176
+ "provider_grant_delivery_unresolved"
18177
+ ]);
18178
+ var ProviderOperationGrantRetryCode = external_exports.enum([
18179
+ "provider_timeout",
18180
+ "provider_rate_limited",
18181
+ "provider_unavailable",
18182
+ "provider_invalid_response",
18183
+ "provider_issuance_unresolved",
18184
+ "provider_grant_delivery_unresolved"
18185
+ ]);
18186
+ var ProviderOperationGrantRetry = external_exports.object({
18187
+ code: ProviderOperationGrantRetryCode,
18188
+ /** Never earlier than the durable claim's safe reclaim boundary. */
18189
+ retryAt: IsoDate
18190
+ }).strict();
18136
18191
  var ProviderOperationGrantResultFrame = external_exports.discriminatedUnion("ok", [
18137
18192
  external_exports.object({
18138
18193
  type: external_exports.literal("provider.operation.grant.result"),
@@ -18150,7 +18205,9 @@ var ProviderOperationGrantResultFrame = external_exports.discriminatedUnion("ok"
18150
18205
  requestId: external_exports.string().min(1).max(200),
18151
18206
  ok: external_exports.literal(false),
18152
18207
  grant: external_exports.never().optional(),
18153
- error: external_exports.string().min(1).max(2e3).optional()
18208
+ error: external_exports.string().min(1).max(2e3).optional(),
18209
+ /** Sent only when the connected Host explicitly advertised support. */
18210
+ retry: ProviderOperationGrantRetry.optional()
18154
18211
  }).strict()
18155
18212
  ]);
18156
18213
  var BrowserStateFrame = external_exports.object({
@@ -19210,6 +19267,52 @@ var GithubGrantError = class extends Error {
19210
19267
  this.name = `GithubGrantError:${reason}`;
19211
19268
  }
19212
19269
  };
19270
+ async function waitForOperationGrantRetry(retryAt, signal) {
19271
+ const deadline = Date.parse(retryAt);
19272
+ if (!Number.isFinite(deadline) || signal.aborted) return false;
19273
+ if (deadline <= Date.now()) return true;
19274
+ return await new Promise((resolve13) => {
19275
+ let settled = false;
19276
+ let timer;
19277
+ const finish = (ready) => {
19278
+ if (settled) return;
19279
+ settled = true;
19280
+ if (timer) clearTimeout(timer);
19281
+ signal.removeEventListener("abort", onAbort);
19282
+ resolve13(ready);
19283
+ };
19284
+ const onAbort = () => finish(false);
19285
+ const schedule = () => {
19286
+ const remaining = deadline - Date.now();
19287
+ if (remaining <= 0) {
19288
+ finish(true);
19289
+ return;
19290
+ }
19291
+ timer = setTimeout(schedule, Math.min(remaining, 2147e6));
19292
+ timer.unref?.();
19293
+ };
19294
+ signal.addEventListener("abort", onAbort, { once: true });
19295
+ if (signal.aborted) {
19296
+ finish(false);
19297
+ return;
19298
+ }
19299
+ schedule();
19300
+ });
19301
+ }
19302
+ function operationGrantRetryCause(code) {
19303
+ if (code === "provider_timeout") return "GitHub did not answer the credential request in time";
19304
+ if (code === "provider_rate_limited") return "GitHub rate-limited the credential request";
19305
+ if (code === "provider_invalid_response") {
19306
+ return "GitHub returned an invalid credential response";
19307
+ }
19308
+ if (code === "provider_issuance_unresolved") {
19309
+ return "GitHub credential issuance is still unresolved";
19310
+ }
19311
+ if (code === "provider_grant_delivery_unresolved") {
19312
+ return "Zixt is waiting for a previously issued GitHub credential to expire before retrying delivery";
19313
+ }
19314
+ return "GitHub is temporarily unavailable for credential issuance";
19315
+ }
19213
19316
  var HostClient = class _HostClient {
19214
19317
  constructor(opts) {
19215
19318
  this.opts = opts;
@@ -19887,7 +19990,13 @@ var HostClient = class _HostClient {
19887
19990
  if (!frame.ok) {
19888
19991
  clearTimeout(waiter.timer);
19889
19992
  this.operationGrantWaiters.delete(frame.requestId);
19890
- waiter.deny(false, "refused_by_zixt", frame.error);
19993
+ waiter.deny(
19994
+ frame.retry !== void 0,
19995
+ "refused_by_zixt",
19996
+ frame.error,
19997
+ frame.retry?.retryAt,
19998
+ frame.retry?.code
19999
+ );
19891
20000
  return;
19892
20001
  }
19893
20002
  const { grant } = frame;
@@ -19977,6 +20086,7 @@ var HostClient = class _HostClient {
19977
20086
  capabilities: {
19978
20087
  ...measuredCapabilities,
19979
20088
  linearToolPack: measured.capabilities?.linearToolPack ?? false,
20089
+ providerOperationGrantRetry: true,
19980
20090
  ...workerWatchdogIsActive() ? { taskAuthorityLifecycle: true } : {}
19981
20091
  },
19982
20092
  // Evidence, not configuration: the cloud says which paths matter and
@@ -20458,9 +20568,11 @@ var HostClient = class _HostClient {
20458
20568
  throw new Error("No GitHub change was attempted; the authority grant was not received.");
20459
20569
  }
20460
20570
  const attempts = options?.maxAttempts ?? 1;
20571
+ let attemptBudget = attempts;
20572
+ let structuredRetryUsed = false;
20461
20573
  let failureReason = "no_reply_from_zixt";
20462
20574
  let failureDetail;
20463
- for (let attempt = 0; attempt < attempts; attempt++) {
20575
+ for (let attempt = 0; attempt < attemptBudget; attempt++) {
20464
20576
  if (authorityController.signal.aborted) break;
20465
20577
  const socket = this.ws;
20466
20578
  if (!socket || socket.readyState !== WebSocket.OPEN) break;
@@ -20497,7 +20609,14 @@ var HostClient = class _HostClient {
20497
20609
  addSensitiveValues(providerGrantSensitiveValues(grant));
20498
20610
  resolve13({ grant });
20499
20611
  },
20500
- deny: (retryable, reason, detail) => resolve13({ grant: null, retryable, reason, ...detail ? { detail } : {} })
20612
+ deny: (retryable, reason, detail, retryAt, retryCode) => resolve13({
20613
+ grant: null,
20614
+ retryable,
20615
+ reason,
20616
+ ...detail ? { detail } : {},
20617
+ ...retryAt ? { retryAt } : {},
20618
+ ...retryCode ? { retryCode } : {}
20619
+ })
20501
20620
  });
20502
20621
  try {
20503
20622
  socket.send(JSON.stringify(parsed.data));
@@ -20515,7 +20634,22 @@ var HostClient = class _HostClient {
20515
20634
  }
20516
20635
  failureReason = outcome.reason;
20517
20636
  failureDetail = outcome.detail;
20518
- if (!outcome.retryable) break;
20637
+ const structuredRetryAt = outcome.retryAt;
20638
+ const structuredRetryCode = outcome.retryCode;
20639
+ const structuredRetry = structuredRetryAt !== void 0 && structuredRetryCode !== void 0;
20640
+ if (structuredRetry && structuredRetryAt && structuredRetryCode) {
20641
+ if (structuredRetryUsed) break;
20642
+ structuredRetryUsed = true;
20643
+ if (attempt + 1 >= attemptBudget) attemptBudget += 1;
20644
+ emitEvent(
20645
+ "status",
20646
+ `${operationGrantRetryCause(structuredRetryCode)}. Zixt will retry automatically at ${structuredRetryAt}; no repository change was attempted.`
20647
+ );
20648
+ }
20649
+ if (!outcome.retryable || attempt + 1 >= attemptBudget) break;
20650
+ if (outcome.retryAt !== void 0 && !await waitForOperationGrantRetry(outcome.retryAt, authorityController.signal)) {
20651
+ break;
20652
+ }
20519
20653
  }
20520
20654
  throw new GithubGrantError(failureReason, failureDetail);
20521
20655
  };
@@ -20708,6 +20842,10 @@ var HostClient = class _HostClient {
20708
20842
  });
20709
20843
  }
20710
20844
  }
20845
+ try {
20846
+ this.opts.onTaskSettled?.();
20847
+ } catch {
20848
+ }
20711
20849
  }
20712
20850
  }
20713
20851
  /** Dial an MCP server on the cloud's behalf and report its tools/health. */
@@ -20754,6 +20892,28 @@ function releaseSourceUrl() {
20754
20892
  var DEFAULT_INTERVAL_MS = 15 * 6e4;
20755
20893
  var MIN_INTERVAL_MS = 6e4;
20756
20894
  var REQUEST_TIMEOUT_MS2 = 1e4;
20895
+ function createIdleUpdateRestartGate(options) {
20896
+ let pending = null;
20897
+ let restarting = false;
20898
+ const restartIfIdle = () => {
20899
+ if (restarting || pending === null || options.activeTasks() > 0) return;
20900
+ restarting = true;
20901
+ const version2 = pending;
20902
+ pending = null;
20903
+ options.restart(version2);
20904
+ };
20905
+ return {
20906
+ updateAvailable(version2) {
20907
+ if (restarting || pending !== null) return;
20908
+ pending = version2;
20909
+ const activeTasks = options.activeTasks();
20910
+ if (activeTasks > 0) options.deferred?.(version2, activeTasks);
20911
+ restartIfIdle();
20912
+ },
20913
+ taskSettled: restartIfIdle,
20914
+ pendingVersion: () => pending
20915
+ };
20916
+ }
20757
20917
  async function fetchPublishedVersion(registryUrl = DEFAULT_REGISTRY_URL, fetchImpl = fetch) {
20758
20918
  try {
20759
20919
  const response = await fetchImpl(registryUrl, {
@@ -20952,7 +21112,7 @@ function posixProcessRecordsFromPs(output) {
20952
21112
  const seen = /* @__PURE__ */ new Set();
20953
21113
  for (const line of output.split(/\r?\n/)) {
20954
21114
  if (!line.trim()) continue;
20955
- const match = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s*$/.exec(line);
21115
+ const match = /^\s*(?:(\d+)\s+)?(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s*$/.exec(line);
20956
21116
  if (!match) {
20957
21117
  throw new ProcessTreeTerminationError(
20958
21118
  "state_unknown",
@@ -20960,12 +21120,13 @@ function posixProcessRecordsFromPs(output) {
20960
21120
  );
20961
21121
  }
20962
21122
  const record2 = {
20963
- pid: Number(match[1]),
20964
- ppid: Number(match[2]),
20965
- pgid: Number(match[3]),
20966
- stat: match[4]
21123
+ uid: match[1] === void 0 ? null : Number(match[1]),
21124
+ pid: Number(match[2]),
21125
+ ppid: Number(match[3]),
21126
+ pgid: Number(match[4]),
21127
+ stat: match[5]
20967
21128
  };
20968
- if (!Number.isSafeInteger(record2.pid) || record2.pid <= 0 || !Number.isSafeInteger(record2.ppid) || record2.ppid < 0 || !Number.isSafeInteger(record2.pgid) || record2.pgid < 0 || seen.has(record2.pid)) {
21129
+ if (record2.uid !== null && (!Number.isSafeInteger(record2.uid) || record2.uid < 0) || !Number.isSafeInteger(record2.pid) || record2.pid <= 0 || !Number.isSafeInteger(record2.ppid) || record2.ppid < 0 || !Number.isSafeInteger(record2.pgid) || record2.pgid < 0 || seen.has(record2.pid)) {
20969
21130
  throw new ProcessTreeTerminationError(
20970
21131
  "state_unknown",
20971
21132
  "POSIX process-tree observation returned invalid evidence"
@@ -20978,7 +21139,7 @@ function posixProcessRecordsFromPs(output) {
20978
21139
  }
20979
21140
  async function snapshotPosixProcesses() {
20980
21141
  return new Promise((resolve13, reject3) => {
20981
- const observer = spawn2("/bin/ps", ["-axo", "pid=,ppid=,pgid=,stat="], {
21142
+ const observer = spawn2("/bin/ps", ["-axo", "uid=,pid=,ppid=,pgid=,stat="], {
20982
21143
  stdio: ["ignore", "pipe", "ignore"]
20983
21144
  });
20984
21145
  let output = "";
@@ -21042,6 +21203,9 @@ async function snapshotPosixProcesses() {
21042
21203
  });
21043
21204
  });
21044
21205
  }
21206
+ function legacyPosixProcessIsDefinitelyForeign(record2, currentUid = process.getuid?.()) {
21207
+ return record2.uid !== null && currentUid !== void 0 && record2.uid !== currentUid;
21208
+ }
21045
21209
  function capturePosixTree(rootPid, records) {
21046
21210
  const byPid = new Map(records.map((record2) => [record2.pid, record2]));
21047
21211
  const host = byPid.get(process.pid);
@@ -21514,6 +21678,7 @@ async function terminateRecordedProcessTree(pid, identity, options = {}) {
21514
21678
  const liveGroup = records.some((record2) => record2.pgid === pid && !record2.stat.startsWith("Z"));
21515
21679
  if (!root && !liveGroup) return;
21516
21680
  if (!identity) {
21681
+ if (root && legacyPosixProcessIsDefinitelyForeign(root)) return;
21517
21682
  throw new ProcessTreeTerminationError(
21518
21683
  "state_unknown",
21519
21684
  "a live legacy runner has no process identity and cannot be stopped safely"
@@ -27199,7 +27364,10 @@ function createGithubRepositoryCreator(input) {
27199
27364
  operation: "repository.clone_created",
27200
27365
  repositoryId: repository.repositoryId
27201
27366
  },
27202
- { maxAttempts: 1 }
27367
+ // The request only obtains fenced credential authority; it does not
27368
+ // repeat repository creation. A second transport probe is required
27369
+ // to recover a cloud commit whose volatile success frame was lost.
27370
+ { maxAttempts: 2 }
27203
27371
  ),
27204
27372
  {
27205
27373
  operation: "repository.clone_created",
@@ -36245,6 +36413,28 @@ var connectionContext = connectionLogContext({
36245
36413
  cloud: cloudTarget,
36246
36414
  version: HOST_VERSION
36247
36415
  });
36416
+ var stopUpdateWatch = () => {
36417
+ };
36418
+ var updateRestartGate = createIdleUpdateRestartGate({
36419
+ activeTasks: () => activeSessions,
36420
+ deferred: (version2, activeTasks) => {
36421
+ log.info("A newer Zixt Host is ready; update waits for active Tasks to finish", {
36422
+ machine,
36423
+ version: version2,
36424
+ running: HOST_VERSION,
36425
+ activeTasks
36426
+ });
36427
+ },
36428
+ restart: (version2) => {
36429
+ log.info("Active Tasks settled; restarting to update Zixt Host", {
36430
+ machine,
36431
+ version: version2,
36432
+ running: HOST_VERSION
36433
+ });
36434
+ stopUpdateWatch();
36435
+ shutdown(UPDATE_EXIT_CODE);
36436
+ }
36437
+ });
36248
36438
  var client = new HostClient({
36249
36439
  url: url2,
36250
36440
  token,
@@ -36266,6 +36456,7 @@ var client = new HostClient({
36266
36456
  forgetSupersededTerminalOutcomes(hostId, taskId, epoch, terminalOutcomeRoot)
36267
36457
  ]);
36268
36458
  },
36459
+ onTaskSettled: () => updateRestartGate.taskSettled(),
36269
36460
  onUnwindStalled: (reason) => {
36270
36461
  retainRunAssignments();
36271
36462
  log.error("Task cleanup did not finish; restarting the Host to recover safely", {
@@ -36337,17 +36528,11 @@ function shutdown(exitCode = 0) {
36337
36528
  ).unref();
36338
36529
  void client.stop().then(() => process.exit(exitCode));
36339
36530
  }
36340
- var stopUpdateWatch = !packagedBuild ? () => {
36531
+ stopUpdateWatch = !packagedBuild ? () => {
36341
36532
  } : watchForUpdates({
36342
36533
  onUpdateAvailable: (version2) => {
36343
- log.info("A newer Zixt Host was published; restarting to pick it up", {
36344
- machine,
36345
- version: version2,
36346
- running: HOST_VERSION,
36347
- activeTasks: activeSessions
36348
- });
36349
36534
  stopUpdateWatch();
36350
- shutdown(UPDATE_EXIT_CODE);
36535
+ updateRestartGate.updateAvailable(version2);
36351
36536
  }
36352
36537
  });
36353
36538
  process.on("SIGINT", () => shutdown());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zixt/host",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/client.ts",