@agentcomms/resend 0.24.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.
- package/dist/cli.mjs +3 -3
- package/dist/index.d.mts +23 -1
- package/dist/index.mjs +2 -2
- package/dist/{send-CNNEFUeO.mjs → send-1tzjQOn0.mjs} +912 -164
- package/dist/{server-ubSGtZsV.mjs → server-D-DV-Uif.mjs} +2 -2
- package/dist/{stdio-entry-DrmLCvk-.mjs → stdio-entry--Q0UoMIV.mjs} +1 -1
- package/package.json +3 -3
|
@@ -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.
|
|
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-
|
|
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
|
*
|
|
@@ -7815,6 +7813,24 @@ function parseConfig(text, source = "config.json") {
|
|
|
7815
7813
|
if (!parsed.success) throw new CommsError("CONFIG", `${source} is invalid: ${describeIssues(parsed.error, naming === 1 ? 1 : 2)}`);
|
|
7816
7814
|
return parsed.data;
|
|
7817
7815
|
}
|
|
7816
|
+
/**
|
|
7817
|
+
* How a config write waits for the config lock: five seconds for each holder, thirty seconds in all.
|
|
7818
|
+
*
|
|
7819
|
+
* The lock is a queue. Every send prepare, claim and approval on every channel runs `ensureSendEpochConfig` first, and
|
|
7820
|
+
* while a legacy drain is open that is a config write or two per call, so an agent's parallel calls across several MCP
|
|
7821
|
+
* servers arrive at this lock together, each holding it for one read, check and fsynced rename (and the drain's, for a
|
|
7822
|
+
* list of the approvals too). Counted from when each waiter began, the lock's five seconds were a budget for the whole
|
|
7823
|
+
* line ahead: on the Windows runner a line of twelve writes gave up at the ninth, "another agent-communications process
|
|
7824
|
+
* is holding", while the lock was being handed on exactly as it should be. A holder there took about half a second, and
|
|
7825
|
+
* on Windows a rename alone may retry for one. Counted per holder, as Resend's throttle and Gmail's history caches count
|
|
7826
|
+
* theirs, a write gives up only on a holder that keeps the lock for the whole five seconds: the stuck lock the timeout is
|
|
7827
|
+
* for. The lock is polled rather than queued, so thirty seconds in all bounds the rest, and leaves room inside the minute
|
|
7828
|
+
* an MCP client waits for a tool call.
|
|
7829
|
+
*/
|
|
7830
|
+
const CONFIG_WRITE_LOCK = {
|
|
7831
|
+
timeoutPerHolder: true,
|
|
7832
|
+
maxWaitMs: 3e4
|
|
7833
|
+
};
|
|
7818
7834
|
var ConfigStore = class {
|
|
7819
7835
|
path;
|
|
7820
7836
|
#lockPath;
|
|
@@ -7835,6 +7851,10 @@ var ConfigStore = class {
|
|
|
7835
7851
|
#namesMigrateAgain(why) {
|
|
7836
7852
|
return handoffSentence(requiredHandoffs(this.#handoffs).core(["names", "migrate"]), (command) => `Run ${command} again ${why}.`, { instead: `Call comms_names_migrate again ${why}.` });
|
|
7837
7853
|
}
|
|
7854
|
+
/** Runs `fn` holding the config lock, waited for as a write waits for it (`CONFIG_WRITE_LOCK`). */
|
|
7855
|
+
#withWriteLock(fn) {
|
|
7856
|
+
return withFileLock(this.#lockPath, fn, CONFIG_WRITE_LOCK);
|
|
7857
|
+
}
|
|
7838
7858
|
/** The current config; an empty one when the file does not exist yet. */
|
|
7839
7859
|
async load() {
|
|
7840
7860
|
let key;
|
|
@@ -7861,7 +7881,7 @@ var ConfigStore = class {
|
|
|
7861
7881
|
let committed;
|
|
7862
7882
|
let result;
|
|
7863
7883
|
try {
|
|
7864
|
-
result = await
|
|
7884
|
+
result = await this.#withWriteLock(async () => {
|
|
7865
7885
|
options.signal?.throwIfAborted();
|
|
7866
7886
|
this.#cache = null;
|
|
7867
7887
|
const current = structuredClone(await this.load());
|
|
@@ -12976,7 +12996,7 @@ function folding(platform) {
|
|
|
12976
12996
|
return platform === "win32" ? (text) => text.toLowerCase() : (text) => text;
|
|
12977
12997
|
}
|
|
12978
12998
|
/** The package version, read from package.json at build time. */
|
|
12979
|
-
const VERSION = "0.
|
|
12999
|
+
const VERSION = "0.26.0";
|
|
12980
13000
|
/**
|
|
12981
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).
|
|
12982
13002
|
*
|
|
@@ -14349,7 +14369,7 @@ const PER_WORKSPACE = {
|
|
|
14349
14369
|
"agentLabel.slackStyles": "app",
|
|
14350
14370
|
"agentLabel.slackIcons": "your Slack photo"
|
|
14351
14371
|
};
|
|
14352
|
-
function recordOf$
|
|
14372
|
+
function recordOf$2(value) {
|
|
14353
14373
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
14354
14374
|
}
|
|
14355
14375
|
/**
|
|
@@ -14359,7 +14379,7 @@ function recordOf$1(value) {
|
|
|
14359
14379
|
*/
|
|
14360
14380
|
function perWorkspace(field, before, after) {
|
|
14361
14381
|
const none = PER_WORKSPACE[field];
|
|
14362
|
-
const [was, now] = [recordOf$
|
|
14382
|
+
const [was, now] = [recordOf$2(before ?? {}), recordOf$2(after ?? {})];
|
|
14363
14383
|
if (none === void 0 || was === void 0 || now === void 0) return void 0;
|
|
14364
14384
|
const value = (record, name) => Object.hasOwn(record, name) ? String(record[name]) : none;
|
|
14365
14385
|
const moved = [.../* @__PURE__ */ new Set([...Object.keys(was), ...Object.keys(now)])].filter((name) => value(was, name) !== value(now, name)).sort();
|
|
@@ -14372,7 +14392,7 @@ function perWorkspace(field, before, after) {
|
|
|
14372
14392
|
* commas. A level one side does not list allows everything there. Undefined for a value of another shape.
|
|
14373
14393
|
*/
|
|
14374
14394
|
function perLevel(field, before, after) {
|
|
14375
|
-
const [was, now] = [recordOf$
|
|
14395
|
+
const [was, now] = [recordOf$2(before ?? {}), recordOf$2(after ?? {})];
|
|
14376
14396
|
if (field !== "behaviour.rules" || was === void 0 || now === void 0) return void 0;
|
|
14377
14397
|
const value = (record, level) => Object.hasOwn(record, level) ? String(record[level]) : "everything";
|
|
14378
14398
|
const levels = [.../* @__PURE__ */ new Set([...Object.keys(was), ...Object.keys(now)])].sort();
|
|
@@ -14990,7 +15010,7 @@ const OUTCOMES = [
|
|
|
14990
15010
|
"unknown"
|
|
14991
15011
|
];
|
|
14992
15012
|
const isString = (value) => typeof value === "string";
|
|
14993
|
-
const isTime$
|
|
15013
|
+
const isTime$3 = (value) => isString(value) && ISO.test(value) && !Number.isNaN(Date.parse(value));
|
|
14994
15014
|
/** One line as an entry, or null when it is not one this release wrote. */
|
|
14995
15015
|
function entryOf(line) {
|
|
14996
15016
|
let raw;
|
|
@@ -15001,7 +15021,7 @@ function entryOf(line) {
|
|
|
15001
15021
|
}
|
|
15002
15022
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
|
|
15003
15023
|
const entry = raw;
|
|
15004
|
-
if (!isTime$
|
|
15024
|
+
if (!isTime$3(entry.at)) return null;
|
|
15005
15025
|
if (entry.kind === "use") {
|
|
15006
15026
|
if (![
|
|
15007
15027
|
"standingId",
|
|
@@ -15033,7 +15053,7 @@ function entryOf(line) {
|
|
|
15033
15053
|
if (entry.kind === "outcome") {
|
|
15034
15054
|
if (!isString(entry.childId) || !isString(entry.outcome) || !OUTCOMES.includes(entry.outcome)) return null;
|
|
15035
15055
|
if (!isString(entry.notice) || entry.link !== void 0 && !isString(entry.link)) return null;
|
|
15036
|
-
if (entry.actAt !== void 0 && !isTime$
|
|
15056
|
+
if (entry.actAt !== void 0 && !isTime$3(entry.actAt)) return null;
|
|
15037
15057
|
return {
|
|
15038
15058
|
kind: "outcome",
|
|
15039
15059
|
at: entry.at,
|
|
@@ -15478,7 +15498,7 @@ function updateJobSupported(platform) {
|
|
|
15478
15498
|
function updateJobMarkerPath(stateDir) {
|
|
15479
15499
|
return join(stateDir, "update-job.json");
|
|
15480
15500
|
}
|
|
15481
|
-
const isTime$
|
|
15501
|
+
const isTime$2 = (value) => typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
15482
15502
|
/** The record, or null when there is none — or none this release can read, which is treated as none. */
|
|
15483
15503
|
async function readUpdateJobMarker(stateDir) {
|
|
15484
15504
|
let parsed;
|
|
@@ -15493,11 +15513,11 @@ async function readUpdateJobMarker(stateDir) {
|
|
|
15493
15513
|
platform: record.platform,
|
|
15494
15514
|
installedAt: record.installedAt,
|
|
15495
15515
|
label: record.label,
|
|
15496
|
-
...isTime$
|
|
15516
|
+
...isTime$2(record.lastRun) ? { lastRun: record.lastRun } : {}
|
|
15497
15517
|
};
|
|
15498
15518
|
}
|
|
15499
15519
|
function within$1(at, now, intervalMs) {
|
|
15500
|
-
if (at === void 0 || !isTime$
|
|
15520
|
+
if (at === void 0 || !isTime$2(at)) return false;
|
|
15501
15521
|
const since = now.getTime() - Date.parse(at);
|
|
15502
15522
|
return since >= -3e5 && since < intervalMs;
|
|
15503
15523
|
}
|
|
@@ -15578,7 +15598,7 @@ const NOTICE_OUTCOMES = [
|
|
|
15578
15598
|
function readBackgroundNotice(value) {
|
|
15579
15599
|
if (typeof value !== "object" || value === null) return null;
|
|
15580
15600
|
const { at, outcome, version, text } = value;
|
|
15581
|
-
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;
|
|
15582
15602
|
return {
|
|
15583
15603
|
at,
|
|
15584
15604
|
outcome,
|
|
@@ -15607,7 +15627,7 @@ const EMPTY_UPDATE_CHECK = Object.freeze({
|
|
|
15607
15627
|
function updateCheckPath(stateDir) {
|
|
15608
15628
|
return join(stateDir, UPDATE_CHECK_FILE);
|
|
15609
15629
|
}
|
|
15610
|
-
const isTime = (value) => typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
15630
|
+
const isTime$1 = (value) => typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
15611
15631
|
const isWords = (value) => Array.isArray(value) && value.every((word) => typeof word === "string");
|
|
15612
15632
|
function readRelease(value) {
|
|
15613
15633
|
if (typeof value !== "object" || value === null) return null;
|
|
@@ -15640,17 +15660,17 @@ async function readUpdateCheck(stateDir) {
|
|
|
15640
15660
|
}
|
|
15641
15661
|
const held = typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
15642
15662
|
return {
|
|
15643
|
-
lastChecked: isTime(held.lastChecked) ? held.lastChecked : null,
|
|
15663
|
+
lastChecked: isTime$1(held.lastChecked) ? held.lastChecked : null,
|
|
15644
15664
|
latest: isPlainVersion(held.latest) ? held.latest : null,
|
|
15645
15665
|
behind: typeof held.behind === "boolean" ? held.behind : null,
|
|
15646
15666
|
current: readCurrent(held.current),
|
|
15647
15667
|
lastError: typeof held.lastError === "string" ? held.lastError.slice(0, 300) : null,
|
|
15648
|
-
snoozedUntil: isTime(held.snoozedUntil) ? held.snoozedUntil : null,
|
|
15649
|
-
checking: isTime(held.checking) ? held.checking : null,
|
|
15650
|
-
commandNoticeAt: isTime(held.commandNoticeAt) ? held.commandNoticeAt : null,
|
|
15651
|
-
noticeAt: isTime(held.noticeAt) ? held.noticeAt : null,
|
|
15652
|
-
pausedAt: isTime(held.pausedAt) ? held.pausedAt : null,
|
|
15653
|
-
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,
|
|
15654
15674
|
release: readRelease(held.release),
|
|
15655
15675
|
securityAsked: isPlainVersion(held.securityAsked) ? held.securityAsked : null,
|
|
15656
15676
|
securityShown: readSecurityShown(held.securityShown),
|
|
@@ -15659,11 +15679,41 @@ async function readUpdateCheck(stateDir) {
|
|
|
15659
15679
|
};
|
|
15660
15680
|
}
|
|
15661
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
|
+
/**
|
|
15662
15709
|
* Reads the record, changes it and writes it back, under a lock beside it, atomically — so a server recording a check
|
|
15663
15710
|
* and a person's "not now" arriving at the same moment each keep what the other wrote. Returns what `change` returned
|
|
15664
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.
|
|
15665
15715
|
*/
|
|
15666
|
-
async function changeUpdateCheck(stateDir, change) {
|
|
15716
|
+
async function changeUpdateCheck(stateDir, change, lock = UPDATE_CHECK_LOCK) {
|
|
15667
15717
|
return withFileLock(join(stateDir, ".update-check.lock"), async () => {
|
|
15668
15718
|
const current = await readUpdateCheck(stateDir);
|
|
15669
15719
|
const next = change(current);
|
|
@@ -15678,7 +15728,7 @@ async function changeUpdateCheck(stateDir, change) {
|
|
|
15678
15728
|
result: next.result,
|
|
15679
15729
|
written: true
|
|
15680
15730
|
};
|
|
15681
|
-
},
|
|
15731
|
+
}, lock);
|
|
15682
15732
|
}
|
|
15683
15733
|
/**
|
|
15684
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
|
|
@@ -15938,7 +15988,7 @@ async function securityAnswered(stateDir, approvalId) {
|
|
|
15938
15988
|
securityAsked: record.securityShown.version,
|
|
15939
15989
|
securityShown: null
|
|
15940
15990
|
} };
|
|
15941
|
-
});
|
|
15991
|
+
}, UPDATE_NOTICE_LOCK);
|
|
15942
15992
|
} catch {}
|
|
15943
15993
|
}
|
|
15944
15994
|
/** A local time as a person reads it in a preview: `2026-09-29 00:00`. */
|
|
@@ -16500,34 +16550,47 @@ function releaseIntegrity(packument, version) {
|
|
|
16500
16550
|
return typeof integrity === "string" && integrity.trim() !== "" ? integrity : null;
|
|
16501
16551
|
}
|
|
16502
16552
|
/**
|
|
16503
|
-
* What a release's core manifest declares under `agentcommsRelease` (U5): a security fix,
|
|
16504
|
-
*
|
|
16505
|
-
*
|
|
16506
|
-
* decision
|
|
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.
|
|
16507
16559
|
*/
|
|
16508
16560
|
function releaseDeclaration(manifest) {
|
|
16509
16561
|
const declared = manifest?.agentcommsRelease;
|
|
16510
16562
|
if (declared === void 0 || declared === null) return {
|
|
16511
16563
|
security: false,
|
|
16512
|
-
needsDecision: false
|
|
16564
|
+
needsDecision: false,
|
|
16565
|
+
needsDecisionSince: null
|
|
16513
16566
|
};
|
|
16514
16567
|
if (typeof declared !== "object" || Array.isArray(declared)) return {
|
|
16515
16568
|
security: false,
|
|
16516
|
-
needsDecision: true
|
|
16569
|
+
needsDecision: true,
|
|
16570
|
+
needsDecisionSince: null
|
|
16517
16571
|
};
|
|
16518
|
-
const { security, needsDecision } = declared;
|
|
16572
|
+
const { security, needsDecision, needsDecisionSince } = declared;
|
|
16573
|
+
const since = isPlainVersion(needsDecisionSince) ? needsDecisionSince : null;
|
|
16519
16574
|
return {
|
|
16520
16575
|
security: security === true,
|
|
16521
|
-
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
|
|
16522
16578
|
};
|
|
16523
16579
|
}
|
|
16524
16580
|
/**
|
|
16525
|
-
*
|
|
16526
|
-
*
|
|
16527
|
-
*
|
|
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`.
|
|
16528
16587
|
*/
|
|
16529
|
-
function
|
|
16530
|
-
|
|
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;
|
|
16531
16594
|
}
|
|
16532
16595
|
/** The oldest of `versions` that are versions, or null. */
|
|
16533
16596
|
function oldestVersion(versions) {
|
|
@@ -16631,42 +16694,6 @@ async function verifyReleaseSet(sources, wanted, act = "update", noVerifierHint)
|
|
|
16631
16694
|
return verified;
|
|
16632
16695
|
}
|
|
16633
16696
|
/**
|
|
16634
|
-
* What every release after `from`, up to `latest`, declares (design 2026-10-16 U5, U7; security review of 0.20.0,
|
|
16635
|
-
* severity 6): a decision any of them needs — a later release that does not say so again does not end it — and whether
|
|
16636
|
-
* `latest` is a security fix. Each declaration acted on is verified first, with `verify`; one that does not verify, or
|
|
16637
|
-
* where nothing here can verify (`verify` null: a channel's process, which carries no Sigstore), counts for nothing.
|
|
16638
|
-
* `from` null reads `latest` alone. Throws when the registry's document cannot be read.
|
|
16639
|
-
*/
|
|
16640
|
-
async function declaredSince(registry, verify, packageName, from, latest) {
|
|
16641
|
-
const { packument } = await registry(packageName);
|
|
16642
|
-
const declared = (version) => releaseDeclaration(packument.versions?.[version]);
|
|
16643
|
-
const range = from === null ? [] : releasesBetween(packument, from, latest);
|
|
16644
|
-
const deciding = [.../* @__PURE__ */ new Set([...range, latest])].filter((version) => declared(version).needsDecision);
|
|
16645
|
-
const verified = /* @__PURE__ */ new Map();
|
|
16646
|
-
const check = async (version) => {
|
|
16647
|
-
const known = verified.get(version);
|
|
16648
|
-
if (known !== void 0) return known;
|
|
16649
|
-
const integrity = releaseIntegrity(packument, version);
|
|
16650
|
-
const ok = verify !== null && integrity !== null && await verify({
|
|
16651
|
-
packageName,
|
|
16652
|
-
version,
|
|
16653
|
-
integrity
|
|
16654
|
-
}).then((v) => v.ok);
|
|
16655
|
-
verified.set(version, ok);
|
|
16656
|
-
return ok;
|
|
16657
|
-
};
|
|
16658
|
-
const decisions = [];
|
|
16659
|
-
for (const version of deciding) decisions.push({
|
|
16660
|
-
version,
|
|
16661
|
-
verified: await check(version)
|
|
16662
|
-
});
|
|
16663
|
-
return {
|
|
16664
|
-
security: declared(latest).security && await check(latest),
|
|
16665
|
-
needsDecision: decisions.some((decision) => decision.verified),
|
|
16666
|
-
deciding: decisions
|
|
16667
|
-
};
|
|
16668
|
-
}
|
|
16669
|
-
/**
|
|
16670
16697
|
* Registering, pruning and listing every declared channel or service MCP server, from the core.
|
|
16671
16698
|
*
|
|
16672
16699
|
* `agentcomms mcp install` registers core or a declared service; `comms_server_install` registers any declared
|
|
@@ -16984,6 +17011,7 @@ function serverInstallChange(core, env, request, own, deps = {}) {
|
|
|
16984
17011
|
});
|
|
16985
17012
|
const unverifiedOwn = fetches && deps.verifyRelease === void 0 && running !== void 0 && running.packageName === product.packageName && running.version === version;
|
|
16986
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");
|
|
16987
17015
|
planned = {
|
|
16988
17016
|
product,
|
|
16989
17017
|
install: plannedInstall(preflight),
|
|
@@ -17163,7 +17191,7 @@ function serverPruneChange(core, env, request, own) {
|
|
|
17163
17191
|
};
|
|
17164
17192
|
const { packageName, version } = ownProduct(request.channel, own) ?? {
|
|
17165
17193
|
packageName: serverFacts(request.channel).packageName,
|
|
17166
|
-
version: "0.
|
|
17194
|
+
version: "0.26.0"
|
|
17167
17195
|
};
|
|
17168
17196
|
const product = {
|
|
17169
17197
|
packageName,
|
|
@@ -17317,6 +17345,599 @@ Object.freeze([
|
|
|
17317
17345
|
"off"
|
|
17318
17346
|
]);
|
|
17319
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
|
+
}
|
|
17320
17941
|
/**
|
|
17321
17942
|
* Bringing a machine to the latest release, from a terminal (`agentcomms update`) or a chat (`comms_update`).
|
|
17322
17943
|
*
|
|
@@ -17485,6 +18106,7 @@ async function inspect(core, env, deps) {
|
|
|
17485
18106
|
package: server.args.some((arg) => arg.startsWith(`${facts.npxPackage}@`)) ? facts.npxPackage : facts.packageName
|
|
17486
18107
|
};
|
|
17487
18108
|
});
|
|
18109
|
+
const removedGlobals = (await readRemovedGlobals(core.paths.stateDir)).filter((one) => PUBLISHED_NAMES.includes(one.package));
|
|
17488
18110
|
let installed = {};
|
|
17489
18111
|
try {
|
|
17490
18112
|
const listed = await (deps.globalPackages ?? (() => npmGlobalPackages(env, PUBLISHED_NAMES)))();
|
|
@@ -17502,6 +18124,7 @@ async function inspect(core, env, deps) {
|
|
|
17502
18124
|
wanted.add(entry.package);
|
|
17503
18125
|
}
|
|
17504
18126
|
for (const name of Object.keys(installed)) wanted.add(name);
|
|
18127
|
+
for (const removed of removedGlobals) wanted.add(removed.package);
|
|
17505
18128
|
const latest = await latestReleases([...wanted].sort(), deps.latestVersion ?? ((name) => npmLatestVersion(name, { env })));
|
|
17506
18129
|
let config = null;
|
|
17507
18130
|
try {
|
|
@@ -17603,16 +18226,19 @@ async function inspect(core, env, deps) {
|
|
|
17603
18226
|
}
|
|
17604
18227
|
const globals = [];
|
|
17605
18228
|
for (const name of PUBLISHED_NAMES) {
|
|
17606
|
-
const
|
|
18229
|
+
const removed = removedGlobals.find((one) => one.package === name);
|
|
18230
|
+
const version = installed[name] ?? removed?.version;
|
|
17607
18231
|
const newestRelease = latest[name];
|
|
17608
18232
|
if (version === void 0 || newestRelease === void 0) continue;
|
|
18233
|
+
const reinstall = removed !== void 0 && !isBehind(version, newestRelease);
|
|
17609
18234
|
const item = {
|
|
17610
18235
|
kind: "global",
|
|
17611
18236
|
package: name,
|
|
17612
18237
|
version,
|
|
17613
|
-
latest: newestRelease
|
|
18238
|
+
latest: newestRelease,
|
|
18239
|
+
...reinstall ? { reinstall: true } : {}
|
|
17614
18240
|
};
|
|
17615
|
-
if (isBehind(version, newestRelease)) {
|
|
18241
|
+
if (reinstall || isBehind(version, newestRelease)) {
|
|
17616
18242
|
behind.push(item);
|
|
17617
18243
|
globals.push(item);
|
|
17618
18244
|
} else upToDate.push(item);
|
|
@@ -17647,54 +18273,87 @@ async function inspect(core, env, deps) {
|
|
|
17647
18273
|
*/
|
|
17648
18274
|
async function updateCheck(core, env, deps = {}, claim) {
|
|
17649
18275
|
const { report } = await inspect(core, env, deps);
|
|
17650
|
-
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));
|
|
17651
18277
|
return report;
|
|
17652
18278
|
}
|
|
17653
18279
|
/**
|
|
17654
|
-
* What the latest release's core manifest declares, for the daily check's file
|
|
17655
|
-
*
|
|
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.
|
|
17656
18285
|
*/
|
|
17657
|
-
async function releaseOf(report, env, deps) {
|
|
18286
|
+
async function releaseOf(core, report, env, deps) {
|
|
17658
18287
|
const latest = report.latest[CORE_PACKAGE$1];
|
|
17659
|
-
if (latest === void 0 || !isPlainVersion(latest)) return
|
|
17660
|
-
const read = deps.releaseFacts !== void 0 ? deps.releaseFacts : releaseFactsReader(env, deps);
|
|
17661
|
-
if (read === null) return
|
|
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;
|
|
17662
18291
|
try {
|
|
17663
|
-
|
|
18292
|
+
const facts = await read(CORE_PACKAGE$1, latest, oldestHere(report));
|
|
18293
|
+
return facts === null ? null : {
|
|
17664
18294
|
version: latest,
|
|
17665
|
-
|
|
18295
|
+
security: facts.security,
|
|
18296
|
+
needsDecision: facts.needsDecision
|
|
17666
18297
|
};
|
|
17667
18298
|
} catch {
|
|
17668
18299
|
return null;
|
|
17669
18300
|
}
|
|
17670
18301
|
}
|
|
17671
18302
|
/**
|
|
17672
|
-
* The oldest release this machine runs, as the report saw it — what
|
|
17673
|
-
* 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.
|
|
17674
18305
|
*/
|
|
17675
18306
|
function oldestHere(report) {
|
|
17676
18307
|
return oldestVersion([report.core, ...report.behind.map((item) => item.version)]);
|
|
17677
18308
|
}
|
|
17678
18309
|
/**
|
|
17679
|
-
* The daily check's and `update --check`'s reader of what
|
|
17680
|
-
*
|
|
17681
|
-
*
|
|
17682
|
-
*
|
|
17683
|
-
|
|
17684
|
-
|
|
17685
|
-
|
|
17686
|
-
|
|
17687
|
-
|
|
17688
|
-
|
|
17689
|
-
|
|
17690
|
-
|
|
17691
|
-
|
|
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;
|
|
17692
18351
|
};
|
|
17693
18352
|
return async (packageName, version, from = null) => {
|
|
17694
|
-
const
|
|
17695
|
-
return {
|
|
17696
|
-
security,
|
|
17697
|
-
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
|
|
17698
18357
|
};
|
|
17699
18358
|
};
|
|
17700
18359
|
}
|
|
@@ -17730,25 +18389,34 @@ const registrationKey = (entry) => [
|
|
|
17730
18389
|
* when this is that background check's own finding, is the claim it was made under: the finding is dropped when the
|
|
17731
18390
|
* file no longer holds it, because something newer — a check a person asked for, an update — was written meanwhile.
|
|
17732
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.
|
|
17733
18396
|
*/
|
|
17734
18397
|
async function recordFound(core, env, report, deps, after = null, claim, release) {
|
|
17735
18398
|
const latest = report.latest[CORE_PACKAGE$1];
|
|
17736
|
-
if (updateCheckSwitchedOff(env) !== null || latest === void 0 || !isVersion(latest)) return;
|
|
18399
|
+
if (updateCheckSwitchedOff(env) !== null || latest === void 0 || !isVersion(latest)) return "unchanged";
|
|
17737
18400
|
const at = (deps.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
|
|
17738
18401
|
const { behind, current } = updateCheckFindings(report, latest, after);
|
|
17739
18402
|
const moves = movesLeft(report, after);
|
|
17740
|
-
const facts = (record) => release
|
|
17741
|
-
|
|
17742
|
-
|
|
17743
|
-
|
|
17744
|
-
|
|
17745
|
-
|
|
17746
|
-
|
|
17747
|
-
|
|
17748
|
-
|
|
17749
|
-
|
|
17750
|
-
|
|
17751
|
-
|
|
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
|
+
}
|
|
17752
18420
|
}
|
|
17753
18421
|
/**
|
|
17754
18422
|
* How many managed registrations still name a versioned runtime once `after` is done, for the notice (design
|
|
@@ -17798,7 +18466,7 @@ function updateCheckFindings(report, latest, after = null) {
|
|
|
17798
18466
|
const globals = items.filter((item) => item.kind === "global");
|
|
17799
18467
|
const global = CHANNELS.filter((channel) => {
|
|
17800
18468
|
const name = serverFacts(channel).packageName;
|
|
17801
|
-
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));
|
|
17802
18470
|
});
|
|
17803
18471
|
const stillBehind = after === null ? report.behind.length > 0 : !(after.ok && after.manual.length === 0 && after.status !== "manual");
|
|
17804
18472
|
const unknown = report.unreadable.length > 0 || report.unpinned.length > 0;
|
|
@@ -17909,7 +18577,7 @@ async function decideNotice(options, where) {
|
|
|
17909
18577
|
};
|
|
17910
18578
|
};
|
|
17911
18579
|
if (due(await readUpdateCheck(options.core.paths.stateDir), now()) === null) return null;
|
|
17912
|
-
|
|
18580
|
+
const decide = (record) => {
|
|
17913
18581
|
const at = now();
|
|
17914
18582
|
const decision = due(record, at);
|
|
17915
18583
|
if (decision === null) return null;
|
|
@@ -17933,7 +18601,8 @@ async function decideNotice(options, where) {
|
|
|
17933
18601
|
}
|
|
17934
18602
|
}
|
|
17935
18603
|
};
|
|
17936
|
-
}
|
|
18604
|
+
};
|
|
18605
|
+
return (await changeUpdateCheck(options.core.paths.stateDir, decide, UPDATE_NOTICE_LOCK)).result ?? null;
|
|
17937
18606
|
}
|
|
17938
18607
|
/**
|
|
17939
18608
|
* What a background run left, when it is this process's to say: an install only to a process running a release before
|
|
@@ -18101,7 +18770,7 @@ async function securityNotice(options) {
|
|
|
18101
18770
|
record: withSecurityShown(record, now, approvalId),
|
|
18102
18771
|
result: now
|
|
18103
18772
|
};
|
|
18104
|
-
});
|
|
18773
|
+
}, UPDATE_NOTICE_LOCK);
|
|
18105
18774
|
if (decided.result === void 0) return null;
|
|
18106
18775
|
shown = decided.result;
|
|
18107
18776
|
}
|
|
@@ -18257,7 +18926,7 @@ async function checkForUpdates(core, env, options = {}) {
|
|
|
18257
18926
|
asked: false,
|
|
18258
18927
|
record: await readUpdateCheck(stateDir)
|
|
18259
18928
|
};
|
|
18260
|
-
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);
|
|
18261
18930
|
return {
|
|
18262
18931
|
asked: true,
|
|
18263
18932
|
record: await readUpdateCheck(stateDir)
|
|
@@ -18278,6 +18947,11 @@ async function checkForUpdates(core, env, options = {}) {
|
|
|
18278
18947
|
* claim written before anyone asks, so of two processes that both found it a day old, one asks. The time is read under
|
|
18279
18948
|
* the lock too: read before it, the second process's time could be earlier than the first's claim, which reads as a
|
|
18280
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).
|
|
18281
18955
|
*/
|
|
18282
18956
|
async function claimUpdateCheck(core, options = {}) {
|
|
18283
18957
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
@@ -18294,7 +18968,7 @@ async function claimUpdateCheck(core, options = {}) {
|
|
|
18294
18968
|
},
|
|
18295
18969
|
result: checking
|
|
18296
18970
|
};
|
|
18297
|
-
}).catch(() => null))?.result ?? null;
|
|
18971
|
+
}, UPDATE_NOTICE_LOCK).catch(() => null))?.result ?? null;
|
|
18298
18972
|
}
|
|
18299
18973
|
/**
|
|
18300
18974
|
* The ask, under a claim already taken (`claimUpdateCheck`): the registry, then `comms_update`'s own check. What it
|
|
@@ -18319,7 +18993,7 @@ async function ask(core, env, options) {
|
|
|
18319
18993
|
*/
|
|
18320
18994
|
const settle = (found) => changeUpdateCheck(core.paths.stateDir, (record) => record.checking === options.claimedAt ? { record: {
|
|
18321
18995
|
...record,
|
|
18322
|
-
...found,
|
|
18996
|
+
...typeof found === "function" ? found(record) : found,
|
|
18323
18997
|
lastChecked: options.claimedAt,
|
|
18324
18998
|
checking: null
|
|
18325
18999
|
} } : null).catch(() => void 0);
|
|
@@ -18341,12 +19015,12 @@ async function ask(core, env, options) {
|
|
|
18341
19015
|
});
|
|
18342
19016
|
return;
|
|
18343
19017
|
}
|
|
18344
|
-
const read = deps.releaseFacts !== void 0 ? deps.releaseFacts : releaseFactsReader(env, deps);
|
|
18345
|
-
const release = read === null ?
|
|
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 : {
|
|
18346
19020
|
version: latest,
|
|
18347
19021
|
security: facts.security,
|
|
18348
19022
|
needsDecision: facts.needsDecision
|
|
18349
|
-
}
|
|
19023
|
+
}, () => null);
|
|
18350
19024
|
try {
|
|
18351
19025
|
await updateCheck(core, env, {
|
|
18352
19026
|
...deps,
|
|
@@ -18355,13 +19029,13 @@ async function ask(core, env, options) {
|
|
|
18355
19029
|
now: options.now
|
|
18356
19030
|
}, options.claimedAt);
|
|
18357
19031
|
} catch (error) {
|
|
18358
|
-
await settle({
|
|
19032
|
+
await settle((record) => ({
|
|
18359
19033
|
latest,
|
|
18360
19034
|
behind: null,
|
|
18361
19035
|
current: null,
|
|
18362
19036
|
lastError: reason(error),
|
|
18363
|
-
release
|
|
18364
|
-
});
|
|
19037
|
+
release: release ?? (record.release?.version === latest ? record.release : null)
|
|
19038
|
+
}));
|
|
18365
19039
|
}
|
|
18366
19040
|
}
|
|
18367
19041
|
/**
|
|
@@ -18480,8 +19154,13 @@ function updateCheckChildEnvironment(env, platform = process.platform) {
|
|
|
18480
19154
|
* message — or whatever reads that output would wait for the child as it waited for the check. Detached, so a
|
|
18481
19155
|
* Ctrl-C at the terminal does not cut the ask short, and hidden, so Windows opens no console for it.
|
|
18482
19156
|
*
|
|
18483
|
-
*
|
|
18484
|
-
*
|
|
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.
|
|
18485
19164
|
*/
|
|
18486
19165
|
async function handUpdateCheckToChild(core, env, options) {
|
|
18487
19166
|
const deadline = Date.now() + TERMINAL_CHECK_WAIT_MS;
|
|
@@ -18493,59 +19172,127 @@ async function handUpdateCheckToChild(core, env, options) {
|
|
|
18493
19172
|
intervalMs: enabled.intervalMs
|
|
18494
19173
|
});
|
|
18495
19174
|
if (claimedAt === null) return;
|
|
19175
|
+
if (options.handOff !== void 0 && await handToCore(core, env, claimedAt, options.handOff, deadline)) return;
|
|
18496
19176
|
const entry = options.entry === void 0 ? updateCheckChildEntry() : options.entry;
|
|
18497
|
-
if (!(entry !== null && await
|
|
19177
|
+
if (!(entry !== null && await startedSafely(options.handOff?.start ?? startCheckChild, ownChildWords(entry, claimedAt, core.paths), updateCheckChildEnvironment(env), deadline))) await askUnderClaim(core, env, claimedAt, {
|
|
18498
19178
|
now: options.now,
|
|
18499
19179
|
...options.verifyRelease ? { deps: { verifyRelease: options.verifyRelease } } : {}
|
|
18500
19180
|
});
|
|
18501
19181
|
} catch {}
|
|
18502
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
|
+
}
|
|
18503
19214
|
/**
|
|
18504
|
-
*
|
|
18505
|
-
*
|
|
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.
|
|
18506
19219
|
*/
|
|
18507
|
-
async function
|
|
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;
|
|
18508
19252
|
let child;
|
|
18509
19253
|
try {
|
|
18510
|
-
|
|
18511
|
-
child = spawn(entry.command, [
|
|
18512
|
-
...entry.args,
|
|
18513
|
-
...pathArgs,
|
|
18514
|
-
UPDATE_CHECK_CHILD_COMMAND,
|
|
18515
|
-
claimedAt
|
|
18516
|
-
], {
|
|
19254
|
+
child = spawn(command, args, {
|
|
18517
19255
|
detached: true,
|
|
18518
19256
|
windowsHide: true,
|
|
19257
|
+
shell: false,
|
|
18519
19258
|
stdio: [
|
|
18520
19259
|
"ignore",
|
|
18521
19260
|
"ignore",
|
|
18522
19261
|
"ignore",
|
|
18523
19262
|
"ipc"
|
|
18524
19263
|
],
|
|
18525
|
-
env
|
|
19264
|
+
env
|
|
18526
19265
|
});
|
|
18527
19266
|
} catch {
|
|
18528
19267
|
return false;
|
|
18529
19268
|
}
|
|
18530
|
-
|
|
18531
|
-
|
|
18532
|
-
|
|
18533
|
-
|
|
18534
|
-
await new Promise((resolve) => {
|
|
18535
|
-
const timer = setTimeout(resolve, Math.max(0, deadline - Date.now()));
|
|
18536
|
-
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) => {
|
|
18537
19273
|
clearTimeout(timer);
|
|
18538
|
-
resolve();
|
|
19274
|
+
resolve(started);
|
|
18539
19275
|
};
|
|
18540
|
-
child.once("error", done);
|
|
18541
|
-
child.once("exit", done);
|
|
19276
|
+
child.once("error", () => done(child.pid !== void 0));
|
|
18542
19277
|
child.on("message", (message) => {
|
|
18543
|
-
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();
|
|
18544
19290
|
});
|
|
19291
|
+
child.once("disconnect", over);
|
|
18545
19292
|
});
|
|
18546
19293
|
if (child.connected) child.disconnect();
|
|
18547
19294
|
child.unref();
|
|
18548
|
-
return
|
|
19295
|
+
return started;
|
|
18549
19296
|
}
|
|
18550
19297
|
/**
|
|
18551
19298
|
* The hidden command's work, in the child a command started (`UPDATE_CHECK_CHILD_COMMAND`): the ask, under the claim
|
|
@@ -18557,7 +19304,7 @@ async function startUpdateCheckChild(entry, claimedAt, paths, env, deadline) {
|
|
|
18557
19304
|
async function runUpdateCheckChild(core, env, claimedAt, options = {}) {
|
|
18558
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.`) });
|
|
18559
19306
|
try {
|
|
18560
|
-
if ((await readUpdateCheck(core.paths.stateDir)).checking === claimedAt) await askUnderClaim(core, env, claimedAt, options.
|
|
19307
|
+
if ((await readUpdateCheck(core.paths.stateDir)).checking === claimedAt) await askUnderClaim(core, env, claimedAt, options.deps !== void 0 ? { deps: options.deps } : {});
|
|
18561
19308
|
} finally {
|
|
18562
19309
|
settled();
|
|
18563
19310
|
}
|
|
@@ -18586,12 +19333,13 @@ function terminalUpdateHooks(core, env, options = {}) {
|
|
|
18586
19333
|
await handUpdateCheckToChild(core, env, {
|
|
18587
19334
|
now: options.now,
|
|
18588
19335
|
entry: options.childEntry,
|
|
18589
|
-
verifyRelease: options.verifyRelease
|
|
19336
|
+
verifyRelease: options.verifyRelease,
|
|
19337
|
+
handOff: options.handOff
|
|
18590
19338
|
});
|
|
18591
19339
|
} };
|
|
18592
19340
|
}
|
|
18593
19341
|
//#endregion
|
|
18594
|
-
//#region ../core/dist/tool-arguments-
|
|
19342
|
+
//#region ../core/dist/tool-arguments-C8Oy4uOx.mjs
|
|
18595
19343
|
const APPLIED = Symbol.for("agentcomms.strictToolArguments");
|
|
18596
19344
|
const TARGET = "draft-2020-12";
|
|
18597
19345
|
/**
|