@agentcomms/resend 0.25.0 → 0.26.0

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.
@@ -9,10 +9,11 @@ import { styleText } from "node:util";
9
9
  import { homedir, platform, userInfo } from "node:os";
10
10
  import { accessSync, constants as constants$1, lstatSync, readFileSync, readlinkSync, realpathSync, statSync } from "node:fs";
11
11
  import { execFile, spawn } from "node:child_process";
12
+ import { gunzipSync } from "node:zlib";
12
13
  //#endregion
13
14
  //#region src/version.ts
14
15
  /** The package version, read from package.json at build time. */
15
- const VERSION$2 = "0.25.0";
16
+ const VERSION$2 = "0.26.0";
16
17
  //#endregion
17
18
  //#region src/api/routes.ts
18
19
  /**
@@ -201,7 +202,7 @@ const RESEND_CALLER = Object.freeze({
201
202
  packageName: PACKAGE_NAME
202
203
  });
203
204
  //#endregion
204
- //#region ../core/dist/update-check-CMD_Te5S.mjs
205
+ //#region ../core/dist/update-check-C3j1VxDg.mjs
205
206
  /**
206
207
  * Every failure a user or an agent can act on carries a specific, stable code from this registry, a message, an
207
208
  * optional one-line hint and structured `details`. The code decides the process exit status; agents and skills branch
@@ -4447,9 +4448,6 @@ function isBehind(version, latest) {
4447
4448
  function isPlainVersion(value) {
4448
4449
  return typeof value === "string" && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value);
4449
4450
  }
4450
- function isPrerelease(version) {
4451
- return VERSION_PATTERN.exec(version)?.[4] !== void 0;
4452
- }
4453
4451
  /**
4454
4452
  * Whether `version` is within a package's `engines` range: true or false, or null when the range is not one this reads.
4455
4453
  *
@@ -12998,7 +12996,7 @@ function folding(platform) {
12998
12996
  return platform === "win32" ? (text) => text.toLowerCase() : (text) => text;
12999
12997
  }
13000
12998
  /** The package version, read from package.json at build time. */
13001
- const VERSION = "0.25.0";
12999
+ const VERSION = "0.26.0";
13002
13000
  /**
13003
13001
  * The command a person is told to run for one of this suite's own CLIs (design 2026-10-04, D1 and D5; CUE-403).
13004
13002
  *
@@ -14371,7 +14369,7 @@ const PER_WORKSPACE = {
14371
14369
  "agentLabel.slackStyles": "app",
14372
14370
  "agentLabel.slackIcons": "your Slack photo"
14373
14371
  };
14374
- function recordOf$1(value) {
14372
+ function recordOf$2(value) {
14375
14373
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
14376
14374
  }
14377
14375
  /**
@@ -14381,7 +14379,7 @@ function recordOf$1(value) {
14381
14379
  */
14382
14380
  function perWorkspace(field, before, after) {
14383
14381
  const none = PER_WORKSPACE[field];
14384
- const [was, now] = [recordOf$1(before ?? {}), recordOf$1(after ?? {})];
14382
+ const [was, now] = [recordOf$2(before ?? {}), recordOf$2(after ?? {})];
14385
14383
  if (none === void 0 || was === void 0 || now === void 0) return void 0;
14386
14384
  const value = (record, name) => Object.hasOwn(record, name) ? String(record[name]) : none;
14387
14385
  const moved = [.../* @__PURE__ */ new Set([...Object.keys(was), ...Object.keys(now)])].filter((name) => value(was, name) !== value(now, name)).sort();
@@ -14394,7 +14392,7 @@ function perWorkspace(field, before, after) {
14394
14392
  * commas. A level one side does not list allows everything there. Undefined for a value of another shape.
14395
14393
  */
14396
14394
  function perLevel(field, before, after) {
14397
- const [was, now] = [recordOf$1(before ?? {}), recordOf$1(after ?? {})];
14395
+ const [was, now] = [recordOf$2(before ?? {}), recordOf$2(after ?? {})];
14398
14396
  if (field !== "behaviour.rules" || was === void 0 || now === void 0) return void 0;
14399
14397
  const value = (record, level) => Object.hasOwn(record, level) ? String(record[level]) : "everything";
14400
14398
  const levels = [.../* @__PURE__ */ new Set([...Object.keys(was), ...Object.keys(now)])].sort();
@@ -15012,7 +15010,7 @@ const OUTCOMES = [
15012
15010
  "unknown"
15013
15011
  ];
15014
15012
  const isString = (value) => typeof value === "string";
15015
- const isTime$2 = (value) => isString(value) && ISO.test(value) && !Number.isNaN(Date.parse(value));
15013
+ const isTime$3 = (value) => isString(value) && ISO.test(value) && !Number.isNaN(Date.parse(value));
15016
15014
  /** One line as an entry, or null when it is not one this release wrote. */
15017
15015
  function entryOf(line) {
15018
15016
  let raw;
@@ -15023,7 +15021,7 @@ function entryOf(line) {
15023
15021
  }
15024
15022
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
15025
15023
  const entry = raw;
15026
- if (!isTime$2(entry.at)) return null;
15024
+ if (!isTime$3(entry.at)) return null;
15027
15025
  if (entry.kind === "use") {
15028
15026
  if (![
15029
15027
  "standingId",
@@ -15055,7 +15053,7 @@ function entryOf(line) {
15055
15053
  if (entry.kind === "outcome") {
15056
15054
  if (!isString(entry.childId) || !isString(entry.outcome) || !OUTCOMES.includes(entry.outcome)) return null;
15057
15055
  if (!isString(entry.notice) || entry.link !== void 0 && !isString(entry.link)) return null;
15058
- if (entry.actAt !== void 0 && !isTime$2(entry.actAt)) return null;
15056
+ if (entry.actAt !== void 0 && !isTime$3(entry.actAt)) return null;
15059
15057
  return {
15060
15058
  kind: "outcome",
15061
15059
  at: entry.at,
@@ -15500,7 +15498,7 @@ function updateJobSupported(platform) {
15500
15498
  function updateJobMarkerPath(stateDir) {
15501
15499
  return join(stateDir, "update-job.json");
15502
15500
  }
15503
- const isTime$1 = (value) => typeof value === "string" && Number.isFinite(Date.parse(value));
15501
+ const isTime$2 = (value) => typeof value === "string" && Number.isFinite(Date.parse(value));
15504
15502
  /** The record, or null when there is none — or none this release can read, which is treated as none. */
15505
15503
  async function readUpdateJobMarker(stateDir) {
15506
15504
  let parsed;
@@ -15515,11 +15513,11 @@ async function readUpdateJobMarker(stateDir) {
15515
15513
  platform: record.platform,
15516
15514
  installedAt: record.installedAt,
15517
15515
  label: record.label,
15518
- ...isTime$1(record.lastRun) ? { lastRun: record.lastRun } : {}
15516
+ ...isTime$2(record.lastRun) ? { lastRun: record.lastRun } : {}
15519
15517
  };
15520
15518
  }
15521
15519
  function within$1(at, now, intervalMs) {
15522
- if (at === void 0 || !isTime$1(at)) return false;
15520
+ if (at === void 0 || !isTime$2(at)) return false;
15523
15521
  const since = now.getTime() - Date.parse(at);
15524
15522
  return since >= -3e5 && since < intervalMs;
15525
15523
  }
@@ -15600,7 +15598,7 @@ const NOTICE_OUTCOMES = [
15600
15598
  function readBackgroundNotice(value) {
15601
15599
  if (typeof value !== "object" || value === null) return null;
15602
15600
  const { at, outcome, version, text } = value;
15603
- if (!isTime(at) || !NOTICE_OUTCOMES.includes(outcome) || typeof text !== "string") return null;
15601
+ if (!isTime$1(at) || !NOTICE_OUTCOMES.includes(outcome) || typeof text !== "string") return null;
15604
15602
  return {
15605
15603
  at,
15606
15604
  outcome,
@@ -15629,7 +15627,7 @@ const EMPTY_UPDATE_CHECK = Object.freeze({
15629
15627
  function updateCheckPath(stateDir) {
15630
15628
  return join(stateDir, UPDATE_CHECK_FILE);
15631
15629
  }
15632
- const isTime = (value) => typeof value === "string" && Number.isFinite(Date.parse(value));
15630
+ const isTime$1 = (value) => typeof value === "string" && Number.isFinite(Date.parse(value));
15633
15631
  const isWords = (value) => Array.isArray(value) && value.every((word) => typeof word === "string");
15634
15632
  function readRelease(value) {
15635
15633
  if (typeof value !== "object" || value === null) return null;
@@ -15662,17 +15660,17 @@ async function readUpdateCheck(stateDir) {
15662
15660
  }
15663
15661
  const held = typeof parsed === "object" && parsed !== null ? parsed : {};
15664
15662
  return {
15665
- lastChecked: isTime(held.lastChecked) ? held.lastChecked : null,
15663
+ lastChecked: isTime$1(held.lastChecked) ? held.lastChecked : null,
15666
15664
  latest: isPlainVersion(held.latest) ? held.latest : null,
15667
15665
  behind: typeof held.behind === "boolean" ? held.behind : null,
15668
15666
  current: readCurrent(held.current),
15669
15667
  lastError: typeof held.lastError === "string" ? held.lastError.slice(0, 300) : null,
15670
- snoozedUntil: isTime(held.snoozedUntil) ? held.snoozedUntil : null,
15671
- checking: isTime(held.checking) ? held.checking : null,
15672
- commandNoticeAt: isTime(held.commandNoticeAt) ? held.commandNoticeAt : null,
15673
- noticeAt: isTime(held.noticeAt) ? held.noticeAt : null,
15674
- pausedAt: isTime(held.pausedAt) ? held.pausedAt : null,
15675
- pausedUntil: isTime(held.pausedUntil) ? held.pausedUntil : null,
15668
+ snoozedUntil: isTime$1(held.snoozedUntil) ? held.snoozedUntil : null,
15669
+ checking: isTime$1(held.checking) ? held.checking : null,
15670
+ commandNoticeAt: isTime$1(held.commandNoticeAt) ? held.commandNoticeAt : null,
15671
+ noticeAt: isTime$1(held.noticeAt) ? held.noticeAt : null,
15672
+ pausedAt: isTime$1(held.pausedAt) ? held.pausedAt : null,
15673
+ pausedUntil: isTime$1(held.pausedUntil) ? held.pausedUntil : null,
15676
15674
  release: readRelease(held.release),
15677
15675
  securityAsked: isPlainVersion(held.securityAsked) ? held.securityAsked : null,
15678
15676
  securityShown: readSecurityShown(held.securityShown),
@@ -15681,11 +15679,41 @@ async function readUpdateCheck(stateDir) {
15681
15679
  };
15682
15680
  }
15683
15681
  /**
15682
+ * How every writer of the record takes the lock beside it: 2 s for each holder, and 15 s in all (design 2026-10-22, R2).
15683
+ *
15684
+ * Every server's tool call takes this lock for its refresh, so on a busy machine a writer often waits behind several
15685
+ * holders in turn, each holding it for a moment. Counted from the start of the wait, 2 s was a budget for that whole
15686
+ * queue, and a writer at the back of it gave up while the lock was being handed on exactly as it should be: a
15687
+ * person's update was installed and its record dropped. Counted per holder, a writer gives up only on one holder that
15688
+ * kept the lock for 2 s, which is a stuck lock; 15 s bounds a queue that never empties, as config writes are bounded.
15689
+ */
15690
+ const UPDATE_CHECK_LOCK = Object.freeze({
15691
+ timeoutPerHolder: true,
15692
+ timeoutMs: 2e3,
15693
+ maxWaitMs: 15e3
15694
+ });
15695
+ /**
15696
+ * How a notice takes the lock: 2 s in all, counted from the start of the wait, however often it changes hands meanwhile.
15697
+ *
15698
+ * A notice is best effort. Deciding a tool call's or a command's line, the security line in a preview, and recording
15699
+ * the person's answer to that line take the lock only for what is said, and each runs while a person or an agent
15700
+ * waits. Waited out holder by holder, as `UPDATE_CHECK_LOCK` waits, a busy machine's queue of refreshes held them for
15701
+ * up to 15 s. Busy past 2 s, a notice decides nothing this time, and a later call says it.
15702
+ *
15703
+ * Claiming the day's check is best effort too: busy past 2 s, nothing is claimed, and a later process claims it (0.26.0
15704
+ * review B). The claim's function is not named here: this comment ships in WhatsApp's bundle, which carries none of the
15705
+ * checker, not even its name (`packages/whatsapp/test/update-gate.test.ts`).
15706
+ */
15707
+ const UPDATE_NOTICE_LOCK = Object.freeze({ timeoutMs: 2e3 });
15708
+ /**
15684
15709
  * Reads the record, changes it and writes it back, under a lock beside it, atomically — so a server recording a check
15685
15710
  * and a person's "not now" arriving at the same moment each keep what the other wrote. Returns what `change` returned
15686
15711
  * alongside the record written; `change` returning `null` writes nothing.
15712
+ *
15713
+ * `lock` is how long to wait for the lock: `UPDATE_CHECK_LOCK`, for a record that matters, unless a notice or the
15714
+ * day's claim passes `UPDATE_NOTICE_LOCK`. Busy past it, this throws LOCK_TIMEOUT, and nothing is written.
15687
15715
  */
15688
- async function changeUpdateCheck(stateDir, change) {
15716
+ async function changeUpdateCheck(stateDir, change, lock = UPDATE_CHECK_LOCK) {
15689
15717
  return withFileLock(join(stateDir, ".update-check.lock"), async () => {
15690
15718
  const current = await readUpdateCheck(stateDir);
15691
15719
  const next = change(current);
@@ -15700,7 +15728,7 @@ async function changeUpdateCheck(stateDir, change) {
15700
15728
  result: next.result,
15701
15729
  written: true
15702
15730
  };
15703
- }, { timeoutMs: 2e3 });
15731
+ }, lock);
15704
15732
  }
15705
15733
  /**
15706
15734
  * Whether the check is due: never asked, asked a day or more ago, or — a clock that went back — asked more than a few
@@ -15960,7 +15988,7 @@ async function securityAnswered(stateDir, approvalId) {
15960
15988
  securityAsked: record.securityShown.version,
15961
15989
  securityShown: null
15962
15990
  } };
15963
- });
15991
+ }, UPDATE_NOTICE_LOCK);
15964
15992
  } catch {}
15965
15993
  }
15966
15994
  /** A local time as a person reads it in a preview: `2026-09-29 00:00`. */
@@ -16522,34 +16550,47 @@ function releaseIntegrity(packument, version) {
16522
16550
  return typeof integrity === "string" && integrity.trim() !== "" ? integrity : null;
16523
16551
  }
16524
16552
  /**
16525
- * What a release's core manifest declares under `agentcommsRelease` (U5): a security fix, and a decision it needs from
16526
- * the person — as flags only. The decision's own words are never read out: they are the registry's, and an update says
16527
- * that one is needed in its own words. A declaration this release cannot read waits for the person: it may be the
16528
- * decision it cannot see.
16553
+ * What a release's core manifest declares under `agentcommsRelease` (U5, design 2026-10-22 §8.1): a security fix, a
16554
+ * decision it needs from the person, and the newest release up to it that needed one — as flags and a plain version
16555
+ * only. The manifest is the one in the release's verified tarball (`release-declarations.ts`, design 2026-10-22 P1),
16556
+ * never the registry's copy. The decision's own words are never read out: an update says that one is needed in its own
16557
+ * words. A declaration this release cannot read waits for the person, as it may be the decision it cannot see: a field
16558
+ * that is not an object, a `needsDecision` that is not false, or a `needsDecisionSince` that is not a plain version.
16529
16559
  */
16530
16560
  function releaseDeclaration(manifest) {
16531
16561
  const declared = manifest?.agentcommsRelease;
16532
16562
  if (declared === void 0 || declared === null) return {
16533
16563
  security: false,
16534
- needsDecision: false
16564
+ needsDecision: false,
16565
+ needsDecisionSince: null
16535
16566
  };
16536
16567
  if (typeof declared !== "object" || Array.isArray(declared)) return {
16537
16568
  security: false,
16538
- needsDecision: true
16569
+ needsDecision: true,
16570
+ needsDecisionSince: null
16539
16571
  };
16540
- const { security, needsDecision } = declared;
16572
+ const { security, needsDecision, needsDecisionSince } = declared;
16573
+ const since = isPlainVersion(needsDecisionSince) ? needsDecisionSince : null;
16541
16574
  return {
16542
16575
  security: security === true,
16543
- needsDecision: !(needsDecision === void 0 || needsDecision === null || needsDecision === false)
16576
+ needsDecision: needsDecisionSince !== void 0 && since === null || !(needsDecision === void 0 || needsDecision === null || needsDecision === false),
16577
+ needsDecisionSince: since
16544
16578
  };
16545
16579
  }
16546
16580
  /**
16547
- * Every release the registry lists after `from`, up to and including `to`, oldest first: what an update takes this
16548
- * machine past. Prereleases are left out — no update installs one — and so is anything that is not a version, or that
16549
- * carries build metadata, which no published version does.
16581
+ * The release a machine running `from` must decide on before it runs `declared` (design 2026-10-22 §8.1), or null when
16582
+ * there is none: `declared` itself when it declares `needsDecision`; otherwise the release its `needsDecisionSince`
16583
+ * names, when that is after `from` — a decision this machine has not taken. One at or before `from` was taken when
16584
+ * that release was installed. Nothing else counts: no release the registry lists, hidden or added, decides anything.
16585
+ * A `from` that is not known, or not a version, cannot be shown to be past it, so it waits; a `since` after
16586
+ * `declared`, which no release check lets through, is said as `declared`.
16550
16587
  */
16551
- function releasesBetween(packument, from, to) {
16552
- return Object.keys(packument.versions ?? {}).filter((version) => isVersion(version) && !isPrerelease(version) && !version.includes("+") && (compareVersions(version, from) ?? 0) > 0 && (compareVersions(version, to) ?? 1) <= 0).sort((a, b) => compareVersions(a, b) ?? 0);
16588
+ function decisionNeeded(declared, from) {
16589
+ if (declared.needsDecision) return declared.version;
16590
+ const since = declared.needsDecisionSince;
16591
+ if (since === null) return null;
16592
+ if (from !== null && (compareVersions(since, from) ?? 1) <= 0) return null;
16593
+ return (compareVersions(since, declared.version) ?? 1) > 0 ? declared.version : since;
16553
16594
  }
16554
16595
  /** The oldest of `versions` that are versions, or null. */
16555
16596
  function oldestVersion(versions) {
@@ -16653,42 +16694,6 @@ async function verifyReleaseSet(sources, wanted, act = "update", noVerifierHint)
16653
16694
  return verified;
16654
16695
  }
16655
16696
  /**
16656
- * What every release after `from`, up to `latest`, declares (design 2026-10-16 U5, U7; security review of 0.20.0,
16657
- * severity 6): a decision any of them needs — a later release that does not say so again does not end it — and whether
16658
- * `latest` is a security fix. Each declaration acted on is verified first, with `verify`; one that does not verify, or
16659
- * where nothing here can verify (`verify` null: a channel's process, which carries no Sigstore), counts for nothing.
16660
- * `from` null reads `latest` alone. Throws when the registry's document cannot be read.
16661
- */
16662
- async function declaredSince(registry, verify, packageName, from, latest) {
16663
- const { packument } = await registry(packageName);
16664
- const declared = (version) => releaseDeclaration(packument.versions?.[version]);
16665
- const range = from === null ? [] : releasesBetween(packument, from, latest);
16666
- const deciding = [.../* @__PURE__ */ new Set([...range, latest])].filter((version) => declared(version).needsDecision);
16667
- const verified = /* @__PURE__ */ new Map();
16668
- const check = async (version) => {
16669
- const known = verified.get(version);
16670
- if (known !== void 0) return known;
16671
- const integrity = releaseIntegrity(packument, version);
16672
- const ok = verify !== null && integrity !== null && await verify({
16673
- packageName,
16674
- version,
16675
- integrity
16676
- }).then((v) => v.ok);
16677
- verified.set(version, ok);
16678
- return ok;
16679
- };
16680
- const decisions = [];
16681
- for (const version of deciding) decisions.push({
16682
- version,
16683
- verified: await check(version)
16684
- });
16685
- return {
16686
- security: declared(latest).security && await check(latest),
16687
- needsDecision: decisions.some((decision) => decision.verified),
16688
- deciding: decisions
16689
- };
16690
- }
16691
- /**
16692
16697
  * Registering, pruning and listing every declared channel or service MCP server, from the core.
16693
16698
  *
16694
16699
  * `agentcomms mcp install` registers core or a declared service; `comms_server_install` registers any declared
@@ -17006,6 +17011,7 @@ function serverInstallChange(core, env, request, own, deps = {}) {
17006
17011
  });
17007
17012
  const unverifiedOwn = fetches && deps.verifyRelease === void 0 && running !== void 0 && running.packageName === product.packageName && running.version === version;
17008
17013
  const verified = fetches && !unverifiedOwn || checksDisk ? (await verifyReleaseSet(from, /* @__PURE__ */ new Map([[product.packageName, version]]), "install", from.registry === null || from.verify === null ? verifyInCore() : void 0)).get(product.packageName) : void 0;
17014
+ if (request.print !== true && launcher === "npx" && target.writes && deps.verifyRelease !== void 0) await verifyReleaseSet(from, /* @__PURE__ */ new Map([[facts.npxPackage, version]]), "install");
17009
17015
  planned = {
17010
17016
  product,
17011
17017
  install: plannedInstall(preflight),
@@ -17185,7 +17191,7 @@ function serverPruneChange(core, env, request, own) {
17185
17191
  };
17186
17192
  const { packageName, version } = ownProduct(request.channel, own) ?? {
17187
17193
  packageName: serverFacts(request.channel).packageName,
17188
- version: "0.25.0"
17194
+ version: "0.26.0"
17189
17195
  };
17190
17196
  const product = {
17191
17197
  packageName,
@@ -17339,6 +17345,599 @@ Object.freeze([
17339
17345
  "off"
17340
17346
  ]);
17341
17347
  Object.keys(STANDING_LIMITS);
17348
+ const UNPACKED_MAX_BYTES = 134217728;
17349
+ const NPM_REGISTRY = "https://registry.npmjs.org/";
17350
+ const REDIRECTS = /* @__PURE__ */ new Set([
17351
+ 301,
17352
+ 302,
17353
+ 303,
17354
+ 307,
17355
+ 308
17356
+ ]);
17357
+ const BLOCK = 512;
17358
+ /** Where npm's own registry serves a release's tarball: `https://registry.npmjs.org/<name>/-/<file>-<version>.tgz`. */
17359
+ function canonicalTarballUrl(packageName, version) {
17360
+ return `${NPM_REGISTRY}${packageName}/-/${packageName.split("/").at(-1) ?? packageName}-${version}.tgz`;
17361
+ }
17362
+ /** `value` as an absolute `http:` or `https:` address (against `base` when given), without a user or password. */
17363
+ function httpAddress(value, base) {
17364
+ if (typeof value !== "string" || value.trim() === "") return null;
17365
+ let url;
17366
+ try {
17367
+ url = base === void 0 ? new URL(value) : new URL(value, base);
17368
+ } catch {
17369
+ return null;
17370
+ }
17371
+ if (url.protocol !== "http:" && url.protocol !== "https:") return null;
17372
+ url.username = "";
17373
+ url.password = "";
17374
+ return url.href;
17375
+ }
17376
+ /**
17377
+ * `dist.tarball` of `version` when it is an absolute http(s) URL; otherwise null. A user and password in it are left
17378
+ * out: no credential is sent with a tarball request, so a mirror that needs one falls back to npm's address.
17379
+ */
17380
+ function tarballLocation(packument, version) {
17381
+ return httpAddress(packument.versions?.[version]?.dist?.tarball);
17382
+ }
17383
+ /**
17384
+ * The fetch a tarball is asked with, unless a caller hands in another: Node's own, asking for the bytes as they are
17385
+ * stored (`accept-encoding: identity` — otherwise it asks for a compressed transfer and decodes it), with no
17386
+ * credential and no cookie.
17387
+ */
17388
+ const fetchTarball = (url, init) => fetch(url, {
17389
+ ...init,
17390
+ headers: { "accept-encoding": "identity" },
17391
+ credentials: "omit"
17392
+ });
17393
+ function discard(response) {
17394
+ response.body?.cancel().catch(() => void 0);
17395
+ }
17396
+ /**
17397
+ * One location: its redirects followed by hand, at most five, each to http(s); its body counted as it arrives; ended
17398
+ * by its own time, or by `deadline` — the caller's — whichever is first.
17399
+ */
17400
+ async function fetchOne(ask, url, timeoutMs, deadline) {
17401
+ const own = AbortSignal.timeout(timeoutMs);
17402
+ const signal = deadline === void 0 ? own : AbortSignal.any([own, deadline]);
17403
+ let current = url;
17404
+ try {
17405
+ for (let redirects = 0;; redirects += 1) {
17406
+ const response = await ask(current, {
17407
+ redirect: "manual",
17408
+ signal
17409
+ });
17410
+ if (REDIRECTS.has(response.status)) {
17411
+ discard(response);
17412
+ const next = httpAddress(response.headers.get("location"), current);
17413
+ if (next === null || redirects >= 5) return {
17414
+ ok: false,
17415
+ problem: "download"
17416
+ };
17417
+ current = next;
17418
+ continue;
17419
+ }
17420
+ if (!response.ok) {
17421
+ discard(response);
17422
+ return {
17423
+ ok: false,
17424
+ problem: "download"
17425
+ };
17426
+ }
17427
+ const declared = Number(response.headers.get("content-length") ?? NaN);
17428
+ if (Number.isFinite(declared) && declared > 33554432) {
17429
+ discard(response);
17430
+ return {
17431
+ ok: false,
17432
+ problem: "size"
17433
+ };
17434
+ }
17435
+ if (response.body === null) return {
17436
+ ok: true,
17437
+ bytes: Buffer.alloc(0)
17438
+ };
17439
+ const reader = response.body.getReader();
17440
+ const chunks = [];
17441
+ let total = 0;
17442
+ for (;;) {
17443
+ const { done, value } = await reader.read();
17444
+ if (done) break;
17445
+ total += value.byteLength;
17446
+ if (total > 33554432) {
17447
+ reader.cancel().catch(() => void 0);
17448
+ return {
17449
+ ok: false,
17450
+ problem: "size"
17451
+ };
17452
+ }
17453
+ chunks.push(Buffer.from(value.buffer, value.byteOffset, value.byteLength));
17454
+ }
17455
+ return {
17456
+ ok: true,
17457
+ bytes: Buffer.concat(chunks, total)
17458
+ };
17459
+ }
17460
+ } catch {
17461
+ return {
17462
+ ok: false,
17463
+ problem: "download"
17464
+ };
17465
+ }
17466
+ }
17467
+ /** Of two failed downloads, the one that says more: bytes that came and were not the release's, then too many. */
17468
+ const RANK = {
17469
+ download: 0,
17470
+ size: 1,
17471
+ digest: 2
17472
+ };
17473
+ /**
17474
+ * The release's tarball: from `location`, then — if that fails in any way — once from npm's canonical address, which
17475
+ * is never asked twice. Kept only when its sha512 is the verified one; otherwise the problem that says the most of the
17476
+ * two tries. A caller's `signal` ends it: what is being fetched is cut off, and nothing more is asked.
17477
+ */
17478
+ async function downloadVerifiedTarball(release, location, options = {}) {
17479
+ const ask = options.fetch ?? fetchTarball;
17480
+ const timeoutMs = options.timeoutMs ?? 6e4;
17481
+ const want = release.sha512.toLowerCase();
17482
+ const canonical = canonicalTarballUrl(release.packageName, release.version);
17483
+ const first = location === null ? null : httpAddress(location);
17484
+ let problem = "download";
17485
+ for (const place of first === null || first === canonical ? [canonical] : [first, canonical]) {
17486
+ if (options.signal?.aborted) break;
17487
+ const fetched = await fetchOne(ask, place, timeoutMs, options.signal);
17488
+ let failed;
17489
+ if (fetched.ok) {
17490
+ if (createHash("sha512").update(fetched.bytes).digest("hex") === want) return fetched;
17491
+ failed = "digest";
17492
+ } else failed = fetched.problem;
17493
+ if (RANK[failed] > RANK[problem]) problem = failed;
17494
+ }
17495
+ return {
17496
+ ok: false,
17497
+ problem
17498
+ };
17499
+ }
17500
+ /** A header field's text: its bytes up to the first NUL. */
17501
+ function fieldText(header, at, width) {
17502
+ const raw = header.subarray(at, at + width);
17503
+ const end = raw.indexOf(0);
17504
+ return raw.subarray(0, end === -1 ? width : end).toString("utf8");
17505
+ }
17506
+ /** A header's octal number, spaces around it allowed; null for base-256 (the high bit set) or anything not octal. */
17507
+ function octalField(header, at, width) {
17508
+ if ((header[at] & 128) !== 0) return null;
17509
+ const digits = fieldText(header, at, width).trim();
17510
+ if (digits === "") return 0;
17511
+ if (!/^[0-7]+$/.test(digits)) return null;
17512
+ const value = Number.parseInt(digits, 8);
17513
+ return Number.isSafeInteger(value) ? value : null;
17514
+ }
17515
+ /** The checksum ustar stores at 148: the header's byte sum, its own eight bytes read as spaces. */
17516
+ function checksumHolds(header) {
17517
+ const stored = octalField(header, 148, 8);
17518
+ if (stored === null) return false;
17519
+ let sum = 0;
17520
+ for (let at = 0; at < BLOCK; at += 1) sum += at >= 148 && at < 156 ? 32 : header[at];
17521
+ return sum === stored;
17522
+ }
17523
+ /** A pax extended header's records, `<length> <key>=<value>\n` each; null when one is not that. */
17524
+ function paxRecords(data) {
17525
+ const records = /* @__PURE__ */ new Map();
17526
+ let at = 0;
17527
+ while (at < data.length) {
17528
+ if (data.subarray(at).every((byte) => byte === 0)) break;
17529
+ const space = data.indexOf(32, at);
17530
+ if (space === -1) return null;
17531
+ const length = data.subarray(at, space).toString("latin1");
17532
+ if (!/^[1-9]\d*$/.test(length)) return null;
17533
+ const end = at + Number(length);
17534
+ if (end > data.length || end <= space + 1 || data[end - 1] !== 10) return null;
17535
+ const record = data.subarray(space + 1, end - 1).toString("utf8");
17536
+ const equals = record.indexOf("=");
17537
+ if (equals <= 0) return null;
17538
+ records.set(record.slice(0, equals), record.slice(equals + 1));
17539
+ at = end;
17540
+ }
17541
+ return records;
17542
+ }
17543
+ /**
17544
+ * An entry's path below `package/`, POSIX, with any leading `./` gone and empty or `.` segments dropped: `''` for the
17545
+ * folder itself. Null for anything outside `package/`, a `..` segment, or a backslash, which no npm pack writes and
17546
+ * which some file systems read as a separator.
17547
+ */
17548
+ function belowPackage(name) {
17549
+ let rest = name;
17550
+ while (rest.startsWith("./")) rest = rest.slice(2);
17551
+ if (rest.includes("\\")) return null;
17552
+ const parts = rest.split("/");
17553
+ if (parts[0] !== "package" || parts.includes("..")) return null;
17554
+ return parts.slice(1).filter((part) => part !== "" && part !== ".").join("/");
17555
+ }
17556
+ function entryType(flag) {
17557
+ if (flag === "0" || flag === "\0") return "file";
17558
+ if (flag === "1" || flag === "2") return "link";
17559
+ if (flag === "5") return "directory";
17560
+ return "other";
17561
+ }
17562
+ /**
17563
+ * Every entry of a release's archive, in order: gunzipped with `node:zlib`, stopping past 128 MiB, then walked as
17564
+ * ustar — 512-byte headers, each checksum checked, to the first all-zero block. A pax `x` header's `path` and `size`,
17565
+ * and a GNU `L` long name, apply to the next entry. `package/` itself is not listed.
17566
+ */
17567
+ function tarballEntries(gz) {
17568
+ let tar;
17569
+ try {
17570
+ tar = gunzipSync(gz, { maxOutputLength: UNPACKED_MAX_BYTES });
17571
+ } catch {
17572
+ return {
17573
+ ok: false,
17574
+ problem: "gzip"
17575
+ };
17576
+ }
17577
+ const broken = {
17578
+ ok: false,
17579
+ problem: "archive"
17580
+ };
17581
+ const entries = [];
17582
+ let pax = {};
17583
+ let longName;
17584
+ for (let offset = 0; offset + BLOCK <= tar.length;) {
17585
+ const header = tar.subarray(offset, offset + BLOCK);
17586
+ if (header.every((byte) => byte === 0)) break;
17587
+ if (!checksumHolds(header)) return broken;
17588
+ const ownSize = octalField(header, 124, 12);
17589
+ if (ownSize === null) return broken;
17590
+ const flag = String.fromCharCode(header[156]);
17591
+ const size = flag === "x" || flag === "L" || flag === "K" || flag === "g" ? ownSize : pax.size ?? ownSize;
17592
+ const start = offset + BLOCK;
17593
+ if (start + size > tar.length) return broken;
17594
+ const data = tar.subarray(start, start + size);
17595
+ offset = start + Math.ceil(size / BLOCK) * BLOCK;
17596
+ if (flag === "g") return broken;
17597
+ if (flag === "x") {
17598
+ const records = paxRecords(data);
17599
+ if (records === null) return broken;
17600
+ const path = records.get("path");
17601
+ const paxSize = records.get("size");
17602
+ if (paxSize !== void 0 && !/^\d+$/.test(paxSize)) return broken;
17603
+ pax = {
17604
+ ...pax,
17605
+ ...path !== void 0 ? { path } : {},
17606
+ ...paxSize !== void 0 ? { size: Number(paxSize) } : {}
17607
+ };
17608
+ if (pax.size !== void 0 && !Number.isSafeInteger(pax.size)) return broken;
17609
+ continue;
17610
+ }
17611
+ if (flag === "L") {
17612
+ const end = data.indexOf(0);
17613
+ longName = data.subarray(0, end === -1 ? data.length : end).toString("utf8");
17614
+ continue;
17615
+ }
17616
+ if (flag === "K") continue;
17617
+ const prefix = fieldText(header, 257, 6) === "ustar" ? fieldText(header, 345, 155) : "";
17618
+ const own = fieldText(header, 0, 100);
17619
+ const name = pax.path ?? longName ?? (prefix === "" ? own : `${prefix}/${own}`);
17620
+ pax = {};
17621
+ longName = void 0;
17622
+ const path = belowPackage(name);
17623
+ const type = entryType(flag);
17624
+ if (path === null) return broken;
17625
+ if (path === "") {
17626
+ if (type === "directory") continue;
17627
+ return broken;
17628
+ }
17629
+ entries.push({
17630
+ path,
17631
+ type,
17632
+ data
17633
+ });
17634
+ }
17635
+ return {
17636
+ ok: true,
17637
+ entries
17638
+ };
17639
+ }
17640
+ /**
17641
+ * The release's `package/package.json`, as an object: exactly one entry by that name (in any case), a regular file of
17642
+ * at most 1 MiB, holding a JSON object. Anything else is `manifest`; an archive that cannot be walked, `archive`.
17643
+ */
17644
+ function tarballManifest(gz) {
17645
+ const read = tarballEntries(gz);
17646
+ if (!read.ok) return read;
17647
+ const none = {
17648
+ ok: false,
17649
+ problem: "manifest"
17650
+ };
17651
+ const named = read.entries.filter((entry) => entry.path.toLowerCase() === "package.json");
17652
+ const [entry] = named;
17653
+ if (named.length !== 1 || entry === void 0) return none;
17654
+ if (entry.path !== "package.json" || entry.type !== "file" || entry.data.length > 1048576) return none;
17655
+ let manifest;
17656
+ try {
17657
+ manifest = JSON.parse(entry.data.toString("utf8").replace(/^/, ""));
17658
+ } catch {
17659
+ return none;
17660
+ }
17661
+ if (manifest === null || typeof manifest !== "object" || Array.isArray(manifest)) return none;
17662
+ return {
17663
+ ok: true,
17664
+ manifest
17665
+ };
17666
+ }
17667
+ const REMOVED_GLOBALS_FILE = "removed-globals.json";
17668
+ function isTime(value) {
17669
+ if (typeof value !== "string") return false;
17670
+ const at = new Date(value);
17671
+ return !Number.isNaN(at.getTime()) && at.toISOString() === value;
17672
+ }
17673
+ /**
17674
+ * The globals recorded for the next update to reinstall, each once: none when there is no record or it cannot be
17675
+ * read. Only a name of this suite and a plain release are read from it.
17676
+ */
17677
+ async function readRemovedGlobals(stateDir) {
17678
+ let parsed;
17679
+ try {
17680
+ parsed = JSON.parse(await readFile(join(stateDir, REMOVED_GLOBALS_FILE), "utf8"));
17681
+ } catch {
17682
+ return [];
17683
+ }
17684
+ const listed = typeof parsed === "object" && parsed !== null ? parsed.removed : void 0;
17685
+ if (!Array.isArray(listed)) return [];
17686
+ const removed = [];
17687
+ for (const item of listed) {
17688
+ if (typeof item !== "object" || item === null) continue;
17689
+ const { package: name, version, removedAt } = item;
17690
+ if (typeof name !== "string" || !SUITE_NAME.test(name) || typeof version !== "string") continue;
17691
+ if (!isPlainVersion(version) || !isTime(removedAt)) continue;
17692
+ if (removed.some((one) => one.package === name)) continue;
17693
+ removed.push({
17694
+ package: name,
17695
+ version,
17696
+ removedAt
17697
+ });
17698
+ }
17699
+ return removed;
17700
+ }
17701
+ /** A name of this suite, as npm writes one: only then is it said in a detail. */
17702
+ const SUITE_NAME = /^@agentcomms\/[a-z0-9][a-z0-9._-]{0,213}$/;
17703
+ /**
17704
+ * What a release declares — a security fix, a decision it needs from the person — read from its verified tarball, and
17705
+ * never from the registry (design 2026-10-22 P1). The registry's copy of a manifest is the registry's word: a mirror
17706
+ * could add or remove either flag on a genuine release. The tarball whose sha512 is the verified provenance digest is
17707
+ * the release's own.
17708
+ *
17709
+ * What was read is kept per integrity, content-free — the version, the integrity, the two flags and the release a
17710
+ * needed decision is carried from (design 2026-10-22 §8.1), never a word of the manifest — in
17711
+ * `<state>/release-declarations.json`, so neither the daily check nor the background run downloads a release it read.
17712
+ * A declaration that cannot be read is unknown: it is never cached, so the next check or run tries it again, and the
17713
+ * caller fails closed. The cache saves downloads, not verification: the caller verifies the release first, every time.
17714
+ */
17715
+ const DECLARATIONS_FILE = "release-declarations.json";
17716
+ const LOCK_FILE = ".release-declarations.lock";
17717
+ const INTEGRITY = /^sha512-[A-Za-z0-9+/]{86}==$/;
17718
+ function declarationsPath(stateDir) {
17719
+ return join(stateDir, DECLARATIONS_FILE);
17720
+ }
17721
+ /**
17722
+ * A record as the cache keeps it, or null for anything else: only these five keys, each of its type. One without
17723
+ * `needsDecisionSince`, as a build before it was carried wrote, is no record: it costs a download, never a decision.
17724
+ */
17725
+ function recordOf(value) {
17726
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
17727
+ const { version, integrity, security, needsDecision, needsDecisionSince } = value;
17728
+ if (!isVersion(version) || typeof integrity !== "string" || !INTEGRITY.test(integrity)) return null;
17729
+ if (typeof security !== "boolean" || typeof needsDecision !== "boolean") return null;
17730
+ if (needsDecisionSince !== null && !isPlainVersion(needsDecisionSince)) return null;
17731
+ return {
17732
+ version,
17733
+ integrity,
17734
+ security,
17735
+ needsDecision,
17736
+ needsDecisionSince
17737
+ };
17738
+ }
17739
+ /** The cache's records; a file that is missing or cannot be read counts as empty. */
17740
+ async function readCache(stateDir) {
17741
+ try {
17742
+ const listed = JSON.parse(await readFile(declarationsPath(stateDir), "utf8"))?.declarations;
17743
+ if (!Array.isArray(listed)) return [];
17744
+ return listed.map(recordOf).filter((record) => record !== null);
17745
+ } catch {
17746
+ return [];
17747
+ }
17748
+ }
17749
+ /**
17750
+ * Adds one record, under the cache's lock, to the file as it is then — so two processes reading at once each keep
17751
+ * the other's — keeping the newest 64. Best effort: a write that fails changes no answer, and costs a download later.
17752
+ */
17753
+ async function remember(stateDir, record) {
17754
+ try {
17755
+ await withFileLock(join(stateDir, LOCK_FILE), async () => {
17756
+ const declarations = [...(await readCache(stateDir)).filter((one) => one.integrity !== record.integrity || one.version !== record.version), record].slice(-64);
17757
+ await writeFileAtomic(declarationsPath(stateDir), `${JSON.stringify({ declarations }, null, 2)}\n`);
17758
+ });
17759
+ } catch {}
17760
+ }
17761
+ /**
17762
+ * What a verified release declares: from the cache, by its integrity and version, or else from its tarball — the bytes
17763
+ * whose sha512 is `provenance.sha512`, from `location` or npm's canonical address — whose `package/package.json` must
17764
+ * name the release itself. Unknown, with the problem, when it cannot be read, or when `signal` ends the read first. A
17765
+ * known declaration is cached unless `writeCache` is false (a dry run); an unknown one never is.
17766
+ */
17767
+ async function readVerifiedDeclaration(provenance, location, options) {
17768
+ const { version } = provenance;
17769
+ const integrity = `sha512-${Buffer.from(provenance.sha512, "hex").toString("base64")}`;
17770
+ const hit = (await readCache(options.stateDir)).find((record) => record.integrity === integrity && record.version === version);
17771
+ if (hit !== void 0) {
17772
+ const { security, needsDecision, needsDecisionSince } = hit;
17773
+ return {
17774
+ known: true,
17775
+ version,
17776
+ security,
17777
+ needsDecision,
17778
+ needsDecisionSince,
17779
+ from: "cache"
17780
+ };
17781
+ }
17782
+ const download = await downloadVerifiedTarball({
17783
+ packageName: provenance.package,
17784
+ version,
17785
+ sha512: provenance.sha512
17786
+ }, location, {
17787
+ ...options.fetch !== void 0 ? { fetch: options.fetch } : {},
17788
+ ...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
17789
+ ...options.signal !== void 0 ? { signal: options.signal } : {}
17790
+ });
17791
+ if (!download.ok) return {
17792
+ known: false,
17793
+ version,
17794
+ problem: download.problem
17795
+ };
17796
+ const read = tarballManifest(download.bytes);
17797
+ if (!read.ok) return {
17798
+ known: false,
17799
+ version,
17800
+ problem: read.problem
17801
+ };
17802
+ if (read.manifest.name !== provenance.package || read.manifest.version !== version) return {
17803
+ known: false,
17804
+ version,
17805
+ problem: "identity"
17806
+ };
17807
+ const { security, needsDecision, needsDecisionSince } = releaseDeclaration(read.manifest);
17808
+ if (options.writeCache !== false) await remember(options.stateDir, {
17809
+ version,
17810
+ integrity,
17811
+ security,
17812
+ needsDecision,
17813
+ needsDecisionSince
17814
+ });
17815
+ return {
17816
+ known: true,
17817
+ version,
17818
+ security,
17819
+ needsDecision,
17820
+ needsDecisionSince,
17821
+ from: "tarball"
17822
+ };
17823
+ }
17824
+ /**
17825
+ * What the release an update goes to declares, believed only from its verified tarball (design 2026-10-22 P1, P2,
17826
+ * §8.1): for the daily check's record, `update --check`, and the by-hand update's preview.
17827
+ *
17828
+ * Only that one release is read. A needed decision is carried forward from release to release in core's
17829
+ * `agentcommsRelease.needsDecisionSince`, which the release check holds to the changelog, so the release an update goes
17830
+ * to says itself whether a release on the way needed one: which versions the registry lists decides nothing. A registry
17831
+ * or mirror that leaves out the release needing a decision, adds an unverifiable one, or lists five hundred, changes no
17832
+ * fact and costs no request.
17833
+ *
17834
+ * The registry's document is read once, for that release's integrity and where its tarball is — never for what it
17835
+ * declares: a registry or a mirror can add or remove either flag on a genuine release. The release is verified, and
17836
+ * then its declaration read (`readVerifiedDeclaration`) from the bytes whose sha512 its verified provenance signs. One
17837
+ * that does not verify, that the registry gives no integrity for, whose tarball cannot be read, or whose read is cut off
17838
+ * by its deadline is *unknown*, and counts for nothing either way: neither a security fix nor its absence, neither a
17839
+ * decision nor none.
17840
+ *
17841
+ * Only core reads these, since only core carries the verifier: a channel's process reads none (`releaseFactsReader`).
17842
+ * No Sigstore here, and nothing runs when this module is loaded: WhatsApp's bundle, which reaches the module that
17843
+ * imports this one, drops it.
17844
+ */
17845
+ /**
17846
+ * How long a check gives its target's declaration — its document, its verification and its tarball, from both places —
17847
+ * before it counts it unknown (review B of 0.26.0, finding 2). A check runs under a claim of `UPDATE_CHECK_LEASE_MS`,
17848
+ * two minutes, which also holds the registry's ten seconds for `latest`, `npm ls`'s minute and up to 15 seconds for
17849
+ * the record's lock: 30 seconds beside them still records inside the lease, where a tarball's two tries of a minute
17850
+ * each did not, and the check's finding was dropped as superseded.
17851
+ */
17852
+ const DECLARATION_DEADLINE_MS = 3e4;
17853
+ /**
17854
+ * What `version` declares, read from its verified tarball, and nothing of any other release. Unknown when the
17855
+ * registry's document cannot be read or gives no integrity for it, when it does not verify, when its tarball cannot be
17856
+ * read, or when `signal` ends the read first. Never throws.
17857
+ */
17858
+ async function targetDeclaration(sources, packageName, version, options = {}) {
17859
+ const unknown = {
17860
+ known: false,
17861
+ version
17862
+ };
17863
+ try {
17864
+ const { packument } = await sources.registry(packageName);
17865
+ const integrity = releaseIntegrity(packument, version);
17866
+ if (integrity === null || options.signal?.aborted) return unknown;
17867
+ const verdict = await sources.verify({
17868
+ packageName,
17869
+ version,
17870
+ integrity
17871
+ });
17872
+ if (!verdict.ok || options.signal?.aborted) return unknown;
17873
+ const read = await readVerifiedDeclaration(verdict.provenance, tarballLocation(packument, version), {
17874
+ stateDir: sources.stateDir,
17875
+ ...sources.tarballs !== void 0 ? { fetch: sources.tarballs } : {},
17876
+ ...options.signal !== void 0 ? { signal: options.signal } : {}
17877
+ });
17878
+ if (!read.known) return unknown;
17879
+ const { security, needsDecision, needsDecisionSince } = read;
17880
+ return {
17881
+ known: true,
17882
+ version,
17883
+ security,
17884
+ needsDecision,
17885
+ needsDecisionSince
17886
+ };
17887
+ } catch {
17888
+ return unknown;
17889
+ }
17890
+ }
17891
+ /**
17892
+ * `read`, given `deadlineMs` at most: unknown once that has passed, and the read cut off then — never a guess of
17893
+ * `false`. The timer is cleared as soon as the read is done.
17894
+ */
17895
+ async function withinDeadline(read, version, deadlineMs = DECLARATION_DEADLINE_MS) {
17896
+ const controller = new AbortController();
17897
+ let timer;
17898
+ const passed = new Promise((resolve) => {
17899
+ timer = setTimeout(() => {
17900
+ controller.abort();
17901
+ resolve({
17902
+ known: false,
17903
+ version
17904
+ });
17905
+ }, deadlineMs);
17906
+ });
17907
+ try {
17908
+ return await Promise.race([read(controller.signal), passed]);
17909
+ } finally {
17910
+ clearTimeout(timer);
17911
+ }
17912
+ }
17913
+ /**
17914
+ * What the release declares for a machine whose oldest release is `from` (design 2026-10-22 §8.1): its own security
17915
+ * flag, and a decision when it declares one or carries one after `from` (`decisionNeeded`).
17916
+ */
17917
+ function declaredFor(declared, from) {
17918
+ if (!declared.known) return {
17919
+ security: null,
17920
+ needsDecision: null,
17921
+ deciding: null,
17922
+ unread: true
17923
+ };
17924
+ const deciding = decisionNeeded(declared, from);
17925
+ return {
17926
+ security: declared.security,
17927
+ needsDecision: deciding !== null,
17928
+ deciding,
17929
+ unread: false
17930
+ };
17931
+ }
17932
+ /** The record's facts for `version`: null unless its declaration was read. */
17933
+ function factsOf(version, declared) {
17934
+ const { security, needsDecision } = declared;
17935
+ return security === null || needsDecision === null ? null : {
17936
+ version,
17937
+ security,
17938
+ needsDecision
17939
+ };
17940
+ }
17342
17941
  /**
17343
17942
  * Bringing a machine to the latest release, from a terminal (`agentcomms update`) or a chat (`comms_update`).
17344
17943
  *
@@ -17507,6 +18106,7 @@ async function inspect(core, env, deps) {
17507
18106
  package: server.args.some((arg) => arg.startsWith(`${facts.npxPackage}@`)) ? facts.npxPackage : facts.packageName
17508
18107
  };
17509
18108
  });
18109
+ const removedGlobals = (await readRemovedGlobals(core.paths.stateDir)).filter((one) => PUBLISHED_NAMES.includes(one.package));
17510
18110
  let installed = {};
17511
18111
  try {
17512
18112
  const listed = await (deps.globalPackages ?? (() => npmGlobalPackages(env, PUBLISHED_NAMES)))();
@@ -17524,6 +18124,7 @@ async function inspect(core, env, deps) {
17524
18124
  wanted.add(entry.package);
17525
18125
  }
17526
18126
  for (const name of Object.keys(installed)) wanted.add(name);
18127
+ for (const removed of removedGlobals) wanted.add(removed.package);
17527
18128
  const latest = await latestReleases([...wanted].sort(), deps.latestVersion ?? ((name) => npmLatestVersion(name, { env })));
17528
18129
  let config = null;
17529
18130
  try {
@@ -17625,16 +18226,19 @@ async function inspect(core, env, deps) {
17625
18226
  }
17626
18227
  const globals = [];
17627
18228
  for (const name of PUBLISHED_NAMES) {
17628
- const version = installed[name];
18229
+ const removed = removedGlobals.find((one) => one.package === name);
18230
+ const version = installed[name] ?? removed?.version;
17629
18231
  const newestRelease = latest[name];
17630
18232
  if (version === void 0 || newestRelease === void 0) continue;
18233
+ const reinstall = removed !== void 0 && !isBehind(version, newestRelease);
17631
18234
  const item = {
17632
18235
  kind: "global",
17633
18236
  package: name,
17634
18237
  version,
17635
- latest: newestRelease
18238
+ latest: newestRelease,
18239
+ ...reinstall ? { reinstall: true } : {}
17636
18240
  };
17637
- if (isBehind(version, newestRelease)) {
18241
+ if (reinstall || isBehind(version, newestRelease)) {
17638
18242
  behind.push(item);
17639
18243
  globals.push(item);
17640
18244
  } else upToDate.push(item);
@@ -17669,54 +18273,87 @@ async function inspect(core, env, deps) {
17669
18273
  */
17670
18274
  async function updateCheck(core, env, deps = {}, claim) {
17671
18275
  const { report } = await inspect(core, env, deps);
17672
- await recordFound(core, env, report, deps, null, claim, await releaseOf(report, env, deps));
18276
+ await recordFound(core, env, report, deps, null, claim, await releaseOf(core, report, env, deps));
17673
18277
  return report;
17674
18278
  }
17675
18279
  /**
17676
- * What the latest release's core manifest declares, for the daily check's file — or null, where it is not to be read
17677
- * or could not be: never a reason for the check to fail. Read only for a release newer than nothing — a counted one.
18280
+ * What the latest release's core manifest declares, for the daily check's file, as its verified tarball says (design
18281
+ * 2026-10-22 P1, §8.1): the facts when it was read; null when this check read it and it could not be read; and
18282
+ * undefined where this process reads none — a channel's, with no verifier, or a test's stand-in registry (P2). For
18283
+ * null and undefined alike the record keeps what core's verified check found of the same version, and otherwise makes
18284
+ * no claim. Never a reason for the check to fail.
17678
18285
  */
17679
- async function releaseOf(report, env, deps) {
18286
+ async function releaseOf(core, report, env, deps) {
17680
18287
  const latest = report.latest[CORE_PACKAGE$1];
17681
- if (latest === void 0 || !isPlainVersion(latest)) return null;
17682
- const read = deps.releaseFacts !== void 0 ? deps.releaseFacts : releaseFactsReader(env, deps);
17683
- if (read === null) return null;
18288
+ if (latest === void 0 || !isPlainVersion(latest)) return void 0;
18289
+ const read = deps.releaseFacts !== void 0 ? deps.releaseFacts : releaseFactsReader(env, deps, core.paths.stateDir);
18290
+ if (read === null) return void 0;
17684
18291
  try {
17685
- return {
18292
+ const facts = await read(CORE_PACKAGE$1, latest, oldestHere(report));
18293
+ return facts === null ? null : {
17686
18294
  version: latest,
17687
- ...await read(CORE_PACKAGE$1, latest, oldestHere(report))
18295
+ security: facts.security,
18296
+ needsDecision: facts.needsDecision
17688
18297
  };
17689
18298
  } catch {
17690
18299
  return null;
17691
18300
  }
17692
18301
  }
17693
18302
  /**
17694
- * The oldest release this machine runs, as the report saw it — what every release after it is read for: the core
17695
- * answering, and every registration, runtime and global package behind.
18303
+ * The oldest release this machine runs, as the report saw it — what a decision the latest carries is counted from: the
18304
+ * core answering, and every registration, runtime and global package behind.
17696
18305
  */
17697
18306
  function oldestHere(report) {
17698
18307
  return oldestVersion([report.core, ...report.behind.map((item) => item.version)]);
17699
18308
  }
17700
18309
  /**
17701
- * The daily check's and `update --check`'s reader of what releases declare (`declaredSince`): the registry handed in,
17702
- * or the one npm reads — but only beside the registry's own `latest`: a test with a stand-in `latestVersion` hands in
17703
- * `releaseFacts` itself, or none is read. With the verifier handed in, and none where none was: a channel's process
17704
- * carries no Sigstore, so what it reads counts for nothing. Each package's document is read once, however often asked.
17705
- */
17706
- function releaseFactsReader(env, deps) {
17707
- if (deps.latestVersion !== void 0) return null;
17708
- const read = deps.registry ?? npmRegistry(env);
17709
- const documents = /* @__PURE__ */ new Map();
17710
- const registry = (packageName) => {
17711
- const known = documents.get(packageName) ?? read(packageName);
17712
- documents.set(packageName, known);
17713
- return known;
18310
+ * The daily check's and `update --check`'s reader of what a release declares (design 2026-10-22 P1, §8.1): the release
18311
+ * verified, then its declaration read from its tarball and cached in `stateDir`; its facts for each machine's `from`,
18312
+ * or null when it could not be read (`factsOf`). Nothing of any other release is read: a needed decision is carried in
18313
+ * the release itself.
18314
+ *
18315
+ * Each release is read once however often it is asked — the check asks once for the core answering, and again for the
18316
+ * oldest release it finds here — and known or unknown, the answer stands for the reader's life: an unreadable tarball
18317
+ * is asked for at most twice in a check, at its location and at npm's own address (review B of 0.26.0, finding 2).
18318
+ * And the read is given `deadlineMs` at most (`DECLARATION_DEADLINE_MS` unless a test gives another), and stops at
18319
+ * `deadline` — a time, by `Date.now()` — when that comes first: the day's check passes its claim's time plus
18320
+ * `DECLARATION_DEADLINE_MS`, well inside the claim, however late its ask began. After it the read is unknown — never
18321
+ * false — and cut off, and one not started by then is not started at all, so the check records what it found while
18322
+ * its claim is still its own.
18323
+ *
18324
+ * The registry handed in, or the one npm reads — but only beside the registry's own `latest`: a test with a stand-in
18325
+ * `latestVersion` hands in `releaseFacts` itself, or none is read. And only with a verifier (P2): a channel's process
18326
+ * carries no Sigstore, so it reads nothing a release declares — no document, no tarball — and its check keeps what
18327
+ * core's verified check recorded.
18328
+ */
18329
+ function releaseFactsReader(env, deps, stateDir, options = {}) {
18330
+ const verify = deps.verifyRelease;
18331
+ if (deps.latestVersion !== void 0 || verify === void 0) return null;
18332
+ const sources = {
18333
+ registry: deps.registry ?? npmRegistry(env),
18334
+ verify,
18335
+ stateDir,
18336
+ tarballs: deps.tarballs
18337
+ };
18338
+ const declarations = /* @__PURE__ */ new Map();
18339
+ const declarationOf = (packageName, version) => {
18340
+ const key = `${packageName}@${version}`;
18341
+ let read = declarations.get(key);
18342
+ if (read === void 0) {
18343
+ const left = Math.min(options.deadlineMs ?? 3e4, options.deadline === void 0 ? Number.POSITIVE_INFINITY : options.deadline - Date.now());
18344
+ read = left > 0 ? withinDeadline((signal) => targetDeclaration(sources, packageName, version, { signal }), version, left) : Promise.resolve({
18345
+ known: false,
18346
+ version
18347
+ });
18348
+ declarations.set(key, read);
18349
+ }
18350
+ return read;
17714
18351
  };
17715
18352
  return async (packageName, version, from = null) => {
17716
- const { security, needsDecision } = await declaredSince(registry, deps.verifyRelease ?? null, packageName, from, version);
17717
- return {
17718
- security,
17719
- needsDecision
18353
+ const facts = factsOf(version, declaredFor(await declarationOf(packageName, version), from));
18354
+ return facts === null ? null : {
18355
+ security: facts.security,
18356
+ needsDecision: facts.needsDecision
17720
18357
  };
17721
18358
  };
17722
18359
  }
@@ -17752,25 +18389,34 @@ const registrationKey = (entry) => [
17752
18389
  * when this is that background check's own finding, is the claim it was made under: the finding is dropped when the
17753
18390
  * file no longer holds it, because something newer — a check a person asked for, an update — was written meanwhile.
17754
18391
  * The machine it describes was read before that, and put back it would say "update" where "restart" is right.
18392
+ *
18393
+ * Says how it went: `written`; `busy`, when the file's lock stayed held past its bound, so an update can try once
18394
+ * more (design 2026-10-22, R2); and `unchanged` for everything else — switched off, a claim given up, or a write that
18395
+ * failed for any other reason, each left as it always was.
17755
18396
  */
17756
18397
  async function recordFound(core, env, report, deps, after = null, claim, release) {
17757
18398
  const latest = report.latest[CORE_PACKAGE$1];
17758
- if (updateCheckSwitchedOff(env) !== null || latest === void 0 || !isVersion(latest)) return;
18399
+ if (updateCheckSwitchedOff(env) !== null || latest === void 0 || !isVersion(latest)) return "unchanged";
17759
18400
  const at = (deps.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
17760
18401
  const { behind, current } = updateCheckFindings(report, latest, after);
17761
18402
  const moves = movesLeft(report, after);
17762
- const facts = (record) => release !== void 0 ? release : record.release?.version === latest ? record.release : null;
17763
- await changeUpdateCheck(core.paths.stateDir, (record) => claim !== void 0 && record.checking !== claim ? null : { record: {
17764
- ...record,
17765
- lastChecked: at,
17766
- latest,
17767
- behind,
17768
- current,
17769
- lastError: null,
17770
- checking: null,
17771
- release: facts(record),
17772
- moves
17773
- } }).catch(() => void 0);
18403
+ const facts = (record) => release ?? (record.release?.version === latest ? record.release : null);
18404
+ try {
18405
+ const { written } = await changeUpdateCheck(core.paths.stateDir, (record) => claim !== void 0 && record.checking !== claim ? null : { record: {
18406
+ ...record,
18407
+ lastChecked: at,
18408
+ latest,
18409
+ behind,
18410
+ current,
18411
+ lastError: null,
18412
+ checking: null,
18413
+ release: facts(record),
18414
+ moves
18415
+ } });
18416
+ return written ? "written" : "unchanged";
18417
+ } catch (error) {
18418
+ return toCommsError(error).code === "LOCK_TIMEOUT" ? "busy" : "unchanged";
18419
+ }
17774
18420
  }
17775
18421
  /**
17776
18422
  * How many managed registrations still name a versioned runtime once `after` is done, for the notice (design
@@ -17820,7 +18466,7 @@ function updateCheckFindings(report, latest, after = null) {
17820
18466
  const globals = items.filter((item) => item.kind === "global");
17821
18467
  const global = CHANNELS.filter((channel) => {
17822
18468
  const name = serverFacts(channel).packageName;
17823
- return globals.some((item) => item.package === name && atLatest(installed.has(name) ? item.latest : item.version));
18469
+ return globals.some((item) => item.package === name && atLatest(installed.has(name) ? item.latest : item.reinstall ? null : item.version));
17824
18470
  });
17825
18471
  const stillBehind = after === null ? report.behind.length > 0 : !(after.ok && after.manual.length === 0 && after.status !== "manual");
17826
18472
  const unknown = report.unreadable.length > 0 || report.unpinned.length > 0;
@@ -17931,7 +18577,7 @@ async function decideNotice(options, where) {
17931
18577
  };
17932
18578
  };
17933
18579
  if (due(await readUpdateCheck(options.core.paths.stateDir), now()) === null) return null;
17934
- return (await changeUpdateCheck(options.core.paths.stateDir, (record) => {
18580
+ const decide = (record) => {
17935
18581
  const at = now();
17936
18582
  const decision = due(record, at);
17937
18583
  if (decision === null) return null;
@@ -17955,7 +18601,8 @@ async function decideNotice(options, where) {
17955
18601
  }
17956
18602
  }
17957
18603
  };
17958
- })).result ?? null;
18604
+ };
18605
+ return (await changeUpdateCheck(options.core.paths.stateDir, decide, UPDATE_NOTICE_LOCK)).result ?? null;
17959
18606
  }
17960
18607
  /**
17961
18608
  * What a background run left, when it is this process's to say: an install only to a process running a release before
@@ -18123,7 +18770,7 @@ async function securityNotice(options) {
18123
18770
  record: withSecurityShown(record, now, approvalId),
18124
18771
  result: now
18125
18772
  };
18126
- });
18773
+ }, UPDATE_NOTICE_LOCK);
18127
18774
  if (decided.result === void 0) return null;
18128
18775
  shown = decided.result;
18129
18776
  }
@@ -18279,7 +18926,7 @@ async function checkForUpdates(core, env, options = {}) {
18279
18926
  asked: false,
18280
18927
  record: await readUpdateCheck(stateDir)
18281
18928
  };
18282
- await askUnderClaim(core, env, claimedAt, options);
18929
+ if (!(options.handOff !== void 0 && options.deps === void 0 && await handToCore(core, env, claimedAt, options.handOff, Date.now() + 12e4))) await askUnderClaim(core, env, claimedAt, options);
18283
18930
  return {
18284
18931
  asked: true,
18285
18932
  record: await readUpdateCheck(stateDir)
@@ -18300,6 +18947,11 @@ async function checkForUpdates(core, env, options = {}) {
18300
18947
  * claim written before anyone asks, so of two processes that both found it a day old, one asks. The time is read under
18301
18948
  * the lock too: read before it, the second process's time could be earlier than the first's claim, which reads as a
18302
18949
  * clock gone back.
18950
+ *
18951
+ * A claim is best effort, as a notice is (`UPDATE_NOTICE_LOCK`): 2 s at most for the lock, however often it changes
18952
+ * hands meanwhile, then null, and a later process claims. Every command's and every tool call's check claims here,
18953
+ * before the person's output is done; waited out holder by holder, as a record that matters waits, a busy machine's
18954
+ * refreshes kept a finished command's process and its stdout open for up to 15 s (0.26.0 review B).
18303
18955
  */
18304
18956
  async function claimUpdateCheck(core, options = {}) {
18305
18957
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
@@ -18316,7 +18968,7 @@ async function claimUpdateCheck(core, options = {}) {
18316
18968
  },
18317
18969
  result: checking
18318
18970
  };
18319
- }).catch(() => null))?.result ?? null;
18971
+ }, UPDATE_NOTICE_LOCK).catch(() => null))?.result ?? null;
18320
18972
  }
18321
18973
  /**
18322
18974
  * The ask, under a claim already taken (`claimUpdateCheck`): the registry, then `comms_update`'s own check. What it
@@ -18341,7 +18993,7 @@ async function ask(core, env, options) {
18341
18993
  */
18342
18994
  const settle = (found) => changeUpdateCheck(core.paths.stateDir, (record) => record.checking === options.claimedAt ? { record: {
18343
18995
  ...record,
18344
- ...found,
18996
+ ...typeof found === "function" ? found(record) : found,
18345
18997
  lastChecked: options.claimedAt,
18346
18998
  checking: null
18347
18999
  } } : null).catch(() => void 0);
@@ -18363,12 +19015,12 @@ async function ask(core, env, options) {
18363
19015
  });
18364
19016
  return;
18365
19017
  }
18366
- const read = deps.releaseFacts !== void 0 ? deps.releaseFacts : releaseFactsReader(env, deps);
18367
- const release = read === null ? null : await read(CORE_PACKAGE, latest, VERSION).then((facts) => ({
19018
+ const read = deps.releaseFacts !== void 0 ? deps.releaseFacts : releaseFactsReader(env, deps, core.paths.stateDir, { deadline: Date.parse(options.claimedAt) + DECLARATION_DEADLINE_MS });
19019
+ const release = read === null ? void 0 : await read(CORE_PACKAGE, latest, VERSION).then((facts) => facts === null ? null : {
18368
19020
  version: latest,
18369
19021
  security: facts.security,
18370
19022
  needsDecision: facts.needsDecision
18371
- }), () => null);
19023
+ }, () => null);
18372
19024
  try {
18373
19025
  await updateCheck(core, env, {
18374
19026
  ...deps,
@@ -18377,13 +19029,13 @@ async function ask(core, env, options) {
18377
19029
  now: options.now
18378
19030
  }, options.claimedAt);
18379
19031
  } catch (error) {
18380
- await settle({
19032
+ await settle((record) => ({
18381
19033
  latest,
18382
19034
  behind: null,
18383
19035
  current: null,
18384
19036
  lastError: reason(error),
18385
- release
18386
- });
19037
+ release: release ?? (record.release?.version === latest ? record.release : null)
19038
+ }));
18387
19039
  }
18388
19040
  }
18389
19041
  /**
@@ -18502,8 +19154,13 @@ function updateCheckChildEnvironment(env, platform = process.platform) {
18502
19154
  * message — or whatever reads that output would wait for the child as it waited for the check. Detached, so a
18503
19155
  * Ctrl-C at the terminal does not cut the ask short, and hidden, so Windows opens no console for it.
18504
19156
  *
18505
- * When the child cannot be started — no entry to start, a program not there or not allowed — the check is asked in
18506
- * this process instead, as it was before. It never throws.
19157
+ * A channel's command hands the ask to core's CLI first (design 2026-10-22, P2): core's command, located at the
19158
+ * channel's own version, with the same hidden command, the same claim and the same environment. Core's child verifies
19159
+ * what a release declares, which the channel's own could not.
19160
+ *
19161
+ * When the child cannot be started — no core, no entry to start, a program not there or not allowed, a `.cmd` that
19162
+ * would need a shell, a launcher whose Node was removed, which starts and ends at once — the next is tried: the
19163
+ * channel's own child, then the check in this process, as it was before. It never throws.
18507
19164
  */
18508
19165
  async function handUpdateCheckToChild(core, env, options) {
18509
19166
  const deadline = Date.now() + TERMINAL_CHECK_WAIT_MS;
@@ -18515,59 +19172,127 @@ async function handUpdateCheckToChild(core, env, options) {
18515
19172
  intervalMs: enabled.intervalMs
18516
19173
  });
18517
19174
  if (claimedAt === null) return;
19175
+ if (options.handOff !== void 0 && await handToCore(core, env, claimedAt, options.handOff, deadline)) return;
18518
19176
  const entry = options.entry === void 0 ? updateCheckChildEntry() : options.entry;
18519
- if (!(entry !== null && await startUpdateCheckChild(entry, claimedAt, core.paths, env, deadline))) await askUnderClaim(core, env, claimedAt, {
19177
+ if (!(entry !== null && await startedSafely(options.handOff?.start ?? startCheckChild, ownChildWords(entry, claimedAt, core.paths), updateCheckChildEnvironment(env), deadline))) await askUnderClaim(core, env, claimedAt, {
18520
19178
  now: options.now,
18521
19179
  ...options.verifyRelease ? { deps: { verifyRelease: options.verifyRelease } } : {}
18522
19180
  });
18523
19181
  } catch {}
18524
19182
  }
19183
+ /** This CLI again, as its child: the entry, every folder pinned, the hidden command and the claim. */
19184
+ function ownChildWords(entry, claimedAt, paths) {
19185
+ const pathArgs = PATH_OPTIONS.flatMap(({ key, flag }) => [flag, paths[key]]);
19186
+ return [
19187
+ entry.command,
19188
+ ...entry.args,
19189
+ ...pathArgs,
19190
+ UPDATE_CHECK_CHILD_COMMAND,
19191
+ claimedAt
19192
+ ];
19193
+ }
19194
+ /**
19195
+ * Core's own command for `update-check-child <claimedAt>`, as the words to start: `core.handoffs.core(…)` with every
19196
+ * folder pinned, located as the channel finds core — its installed dependency, at exactly the channel's own version,
19197
+ * through the stable way in a managed runtime keeps (`locateCliCommand`). Locating checks files and runs nothing.
19198
+ *
19199
+ * Located for this process's own system, whatever shell its printed commands are quoted for: these words are started
19200
+ * here, and a managed runtime's stable launcher is started one way on Windows and another elsewhere.
19201
+ *
19202
+ * Null for core's own process, which asks itself and is never handed its own check; for a core opened without its
19203
+ * caller, which can locate nothing; and where no such core is found.
19204
+ */
19205
+ function coreUpdateCheckWords(core, claimedAt) {
19206
+ if (core.handoffs === void 0 || core.handoffs.caller.packageName === CORE_PACKAGE) return null;
19207
+ try {
19208
+ const handoff = requireHandoffs(core, process.platform).core([UPDATE_CHECK_CHILD_COMMAND, claimedAt], { downloads: true });
19209
+ return isCommand(handoff) ? handoff.words : null;
19210
+ } catch {
19211
+ return null;
19212
+ }
19213
+ }
18525
19214
  /**
18526
- * Starts the child and waits, until `deadline` at most, for it to say the ask is over, or to end. False when it could
18527
- * not be started at all; then nothing was asked, and the claim is still this process's to ask under.
19215
+ * The ask handed to core's CLI (design 2026-10-22, P2): its command located (`handOff.locate`), started with the
19216
+ * claim and this command's child environment (`handOff.start`), and waited for until `deadline`. False when there is
19217
+ * no such command, or it did not start: then nothing was asked, and the claim is still this process's to ask under. It
19218
+ * never throws.
18528
19219
  */
18529
- async function startUpdateCheckChild(entry, claimedAt, paths, env, deadline) {
19220
+ async function handToCore(core, env, claimedAt, handOff, deadline) {
19221
+ let words;
19222
+ try {
19223
+ words = (handOff.locate ?? coreUpdateCheckWords)(core, claimedAt);
19224
+ } catch {
19225
+ return false;
19226
+ }
19227
+ if (words === null || words.length === 0) return false;
19228
+ return startedSafely(handOff.start ?? startCheckChild, words, updateCheckChildEnvironment(env), deadline);
19229
+ }
19230
+ /** A start's answer, with one that throws counted as not started: the fallback after it is all that is left. */
19231
+ async function startedSafely(start, words, env, deadline) {
19232
+ try {
19233
+ return await start(words, env, deadline) === true;
19234
+ } catch {
19235
+ return false;
19236
+ }
19237
+ }
19238
+ /**
19239
+ * Starts `words` — a program, then its arguments — detached and hidden, with no shell and `env` as given, and waits,
19240
+ * until `deadline` at most, for it to say the ask is over, or to end. False when it could not be started at all, or
19241
+ * when it ended with a code other than 0, or by a signal, before it said the ask is over — a launcher whose Node was
19242
+ * removed starts, and ends at once with 127 (0.26.0 review B). Either way nothing was asked, and the claim is still
19243
+ * this process's to ask under.
19244
+ *
19245
+ * Never through a shell: each word is one argument, as located, and nothing in them is read as a shell would read it.
19246
+ * A program only a shell could start — a Windows `.cmd` or `.bat`, which Node refuses without one (EINVAL) — is not
19247
+ * started, and the caller falls back.
19248
+ */
19249
+ async function startCheckChild(words, env, deadline) {
19250
+ const [command, ...args] = words;
19251
+ if (command === void 0) return false;
18530
19252
  let child;
18531
19253
  try {
18532
- const pathArgs = PATH_OPTIONS.flatMap(({ key, flag }) => [flag, paths[key]]);
18533
- child = spawn(entry.command, [
18534
- ...entry.args,
18535
- ...pathArgs,
18536
- UPDATE_CHECK_CHILD_COMMAND,
18537
- claimedAt
18538
- ], {
19254
+ child = spawn(command, args, {
18539
19255
  detached: true,
18540
19256
  windowsHide: true,
19257
+ shell: false,
18541
19258
  stdio: [
18542
19259
  "ignore",
18543
19260
  "ignore",
18544
19261
  "ignore",
18545
19262
  "ipc"
18546
19263
  ],
18547
- env: updateCheckChildEnvironment(env)
19264
+ env
18548
19265
  });
18549
19266
  } catch {
18550
19267
  return false;
18551
19268
  }
18552
- let failed = false;
18553
- child.on("error", () => {
18554
- if (child.pid === void 0) failed = true;
18555
- });
18556
- await new Promise((resolve) => {
18557
- const timer = setTimeout(resolve, Math.max(0, deadline - Date.now()));
18558
- const done = () => {
19269
+ child.on("error", () => void 0);
19270
+ const started = await new Promise((resolve) => {
19271
+ const timer = setTimeout(() => resolve(true), Math.max(0, deadline - Date.now()));
19272
+ const done = (started) => {
18559
19273
  clearTimeout(timer);
18560
- resolve();
19274
+ resolve(started);
18561
19275
  };
18562
- child.once("error", done);
18563
- child.once("exit", done);
19276
+ child.once("error", () => done(child.pid !== void 0));
18564
19277
  child.on("message", (message) => {
18565
- if (message?.type === "settled") done();
19278
+ if (message?.type === "settled") done(true);
19279
+ });
19280
+ let ended = null;
19281
+ const over = () => {
19282
+ if (ended !== null && !child.connected) done(ended.code === 0 && ended.signal === null);
19283
+ };
19284
+ child.once("exit", (code, signal) => {
19285
+ ended = {
19286
+ code,
19287
+ signal
19288
+ };
19289
+ over();
18566
19290
  });
19291
+ child.once("disconnect", over);
18567
19292
  });
18568
19293
  if (child.connected) child.disconnect();
18569
19294
  child.unref();
18570
- return !failed;
19295
+ return started;
18571
19296
  }
18572
19297
  /**
18573
19298
  * The hidden command's work, in the child a command started (`UPDATE_CHECK_CHILD_COMMAND`): the ask, under the claim
@@ -18579,7 +19304,7 @@ async function startUpdateCheckChild(entry, claimedAt, paths, env, deadline) {
18579
19304
  async function runUpdateCheckChild(core, env, claimedAt, options = {}) {
18580
19305
  if (claimedAt === void 0 || !Number.isFinite(Date.parse(claimedAt))) throw new CommsError("USAGE", `${UPDATE_CHECK_CHILD_COMMAND} takes the time of the claim it asks under`, { hint: handoffSentence(requireHandoffs(core).core(["update", "--check"]), (command) => `Nothing runs this but a command finishing the day’s update check; ${command} asks now.`) });
18581
19306
  try {
18582
- if ((await readUpdateCheck(core.paths.stateDir)).checking === claimedAt) await askUnderClaim(core, env, claimedAt, options.verifyRelease ? { deps: { verifyRelease: options.verifyRelease } } : {});
19307
+ if ((await readUpdateCheck(core.paths.stateDir)).checking === claimedAt) await askUnderClaim(core, env, claimedAt, options.deps !== void 0 ? { deps: options.deps } : {});
18583
19308
  } finally {
18584
19309
  settled();
18585
19310
  }
@@ -18608,12 +19333,13 @@ function terminalUpdateHooks(core, env, options = {}) {
18608
19333
  await handUpdateCheckToChild(core, env, {
18609
19334
  now: options.now,
18610
19335
  entry: options.childEntry,
18611
- verifyRelease: options.verifyRelease
19336
+ verifyRelease: options.verifyRelease,
19337
+ handOff: options.handOff
18612
19338
  });
18613
19339
  } };
18614
19340
  }
18615
19341
  //#endregion
18616
- //#region ../core/dist/tool-arguments-BW3WUyxT.mjs
19342
+ //#region ../core/dist/tool-arguments-C8Oy4uOx.mjs
18617
19343
  const APPLIED = Symbol.for("agentcomms.strictToolArguments");
18618
19344
  const TARGET = "draft-2020-12";
18619
19345
  /**