@agentcomms/resend 0.15.1 → 0.16.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.
@@ -1,4 +1,4 @@
1
- import { S as union, b as strictObject, c as boolean, h as number, i as _enum, m as looseObject, n as datetime, p as literal$1, s as array, x as string, y as record } from "./iso-USuVU_sk.mjs";
1
+ import { S as union, b as strictObject, c as boolean, h as number, i as _enum, l as discriminatedUnion, m as looseObject, n as datetime, p as literal$1, s as array, x as string, y as record } from "./iso-USuVU_sk.mjs";
2
2
  import { access, chmod, constants, link, lstat, mkdir, open, readFile, readdir, readlink, realpath, rename, rm, stat, unlink, utimes } from "node:fs/promises";
3
3
  import path, { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
4
4
  import { domainToASCII, fileURLToPath, pathToFileURL } from "node:url";
@@ -12,7 +12,7 @@ import { execFile, spawn } from "node:child_process";
12
12
  //#endregion
13
13
  //#region src/version.ts
14
14
  /** The package version, read from package.json at build time. */
15
- const VERSION$1 = "0.15.1";
15
+ const VERSION$1 = "0.16.0";
16
16
  //#endregion
17
17
  //#region src/api/routes.ts
18
18
  /**
@@ -201,7 +201,7 @@ const RESEND_CALLER = Object.freeze({
201
201
  packageName: PACKAGE_NAME
202
202
  });
203
203
  //#endregion
204
- //#region ../core/dist/update-check-CZwCVNLB.mjs
204
+ //#region ../core/dist/update-check-23Ii90fQ.mjs
205
205
  /**
206
206
  * Every failure a user or an agent can act on carries a specific, stable code from this registry, a message, an
207
207
  * optional one-line hint and structured `details`. The code decides the process exit status; agents and skills branch
@@ -317,6 +317,11 @@ const ERROR_REGISTRY = {
317
317
  retryable: true,
318
318
  summary: "the mail provider is unavailable"
319
319
  },
320
+ SOURCE_UNAVAILABLE: {
321
+ exit: EXIT_CODES.UNAVAILABLE,
322
+ retryable: false,
323
+ summary: "the selected local event source is unavailable"
324
+ },
320
325
  SECRET_STORE_UNAVAILABLE: {
321
326
  exit: EXIT_CODES.UNAVAILABLE,
322
327
  retryable: false,
@@ -347,6 +352,11 @@ const ERROR_REGISTRY = {
347
352
  retryable: false,
348
353
  summary: "the inbox was not granted this permission"
349
354
  },
355
+ WHATSAPP_VISIBILITY_SEAM_REQUIRED: {
356
+ exit: EXIT_CODES.CONFIG,
357
+ retryable: false,
358
+ summary: "the WhatsApp event visibility fence is unavailable"
359
+ },
350
360
  CONFIG: {
351
361
  exit: EXIT_CODES.CONFIG,
352
362
  retryable: false,
@@ -843,12 +853,29 @@ function dedupeBy(items, key) {
843
853
  }
844
854
  return out;
845
855
  }
856
+ const ORIGINS$1 = ["event", "read"];
857
+ function sortedOrigins(origins) {
858
+ const known = new Set(origins);
859
+ return ORIGINS$1.filter((origin) => known.has(origin));
860
+ }
861
+ function originEntry(entry) {
862
+ if (entry === void 0) return ["read"];
863
+ const known = Array.isArray(entry.origins) ? entry.origins.filter((origin) => origin === "event" || origin === "read") : [];
864
+ return known.length === 0 ? ["read"] : sortedOrigins(known);
865
+ }
866
+ function isNewer(at, than) {
867
+ const left = new Date(at).getTime();
868
+ const right = new Date(than).getTime();
869
+ return Number.isFinite(left) && Number.isFinite(right) && left > right;
870
+ }
846
871
  var TaintStore = class {
847
872
  directory;
848
873
  #now;
849
- constructor(stateDir, now = () => /* @__PURE__ */ new Date()) {
874
+ #hooks;
875
+ constructor(stateDir, now = () => /* @__PURE__ */ new Date(), hooks = {}) {
850
876
  this.directory = join(stateDir, "taint");
851
877
  this.#now = now;
878
+ this.#hooks = hooks;
852
879
  }
853
880
  get #path() {
854
881
  return join(this.directory, "taint.json");
@@ -856,6 +883,9 @@ var TaintStore = class {
856
883
  get #handlesPath() {
857
884
  return join(this.directory, "handles.json");
858
885
  }
886
+ get #originsPath() {
887
+ return join(this.directory, "origins.json");
888
+ }
859
889
  async #readJson(path, empty) {
860
890
  try {
861
891
  const parsed = JSON.parse(await readFile(path, "utf8"));
@@ -881,6 +911,13 @@ var TaintStore = class {
881
911
  async #readHandles() {
882
912
  return this.#readJson(this.#handlesPath, () => ({ handles: {} }));
883
913
  }
914
+ async #readOrigins() {
915
+ return this.#readJson(this.#originsPath, () => ({
916
+ addresses: {},
917
+ domains: {},
918
+ handles: {}
919
+ }));
920
+ }
884
921
  #prune(file) {
885
922
  const cutoff = this.#now().getTime() - TAINT_WINDOW_MS;
886
923
  const keep = (map) => {
@@ -904,6 +941,35 @@ var TaintStore = class {
904
941
  handles: Object.fromEntries(fresh.slice(0, MAX_ENTRIES))
905
942
  };
906
943
  }
944
+ #mergeOrigins(origins, addresses, domains, handles, touched, at) {
945
+ const merge = (sidecar, base, observed) => {
946
+ const merged = {};
947
+ for (const [key, entry] of Object.entries(sidecar)) {
948
+ const baseEntry = base[key];
949
+ if (baseEntry === void 0) continue;
950
+ const mergedOrigins = new Set(originEntry(entry));
951
+ if (isNewer(baseEntry.at, entry.at)) mergedOrigins.add("read");
952
+ merged[key] = {
953
+ at: isNewer(baseEntry.at, entry.at) ? baseEntry.at : entry.at,
954
+ origins: sortedOrigins(mergedOrigins)
955
+ };
956
+ }
957
+ for (const [key, observedOrigins] of observed) {
958
+ const existing = merged[key];
959
+ merged[key] = {
960
+ at,
961
+ origins: sortedOrigins([...existing?.origins ?? [], ...observedOrigins])
962
+ };
963
+ }
964
+ return merged;
965
+ };
966
+ return {
967
+ ...origins,
968
+ addresses: merge(origins.addresses, addresses, touched.addresses),
969
+ domains: merge(origins.domains, domains, touched.domains),
970
+ handles: merge(origins.handles, handles, touched.handles)
971
+ };
972
+ }
907
973
  /**
908
974
  * Records observations. Throws if it cannot — callers must fail the read rather than return content whose taint
909
975
  * was not recorded.
@@ -922,7 +988,17 @@ var TaintStore = class {
922
988
  })).filter((o) => o.address.includes("@") && !own.has(o.address) && !internal.has(domainOf(o.address) ?? "")).sort((a, b) => a.source === b.source ? 0 : a.source === "header" ? -1 : 1), (o) => o.address).slice(0, MAX_PER_MESSAGE);
923
989
  if (kept.length === 0 && keptHandles.length === 0) return;
924
990
  const at = this.#now().toISOString();
925
- const touch = (map, key, o) => {
991
+ const touched = {
992
+ addresses: /* @__PURE__ */ new Map(),
993
+ domains: /* @__PURE__ */ new Map(),
994
+ handles: /* @__PURE__ */ new Map()
995
+ };
996
+ const rememberOrigin = (map, key, origin) => {
997
+ const origins = touched[map].get(key) ?? /* @__PURE__ */ new Set();
998
+ origins.add(origin ?? "read");
999
+ touched[map].set(key, origins);
1000
+ };
1001
+ const touch = (map, key, o, originMap) => {
926
1002
  const existing = map[key];
927
1003
  const inboxIds = [.../* @__PURE__ */ new Set([...existing?.inboxIds ?? [], o.inboxId])];
928
1004
  const source = existing?.source === "header" ? "header" : o.source;
@@ -931,27 +1007,38 @@ var TaintStore = class {
931
1007
  source,
932
1008
  inboxIds
933
1009
  };
1010
+ rememberOrigin(originMap, key, o.origin);
934
1011
  };
935
- if (kept.length > 0) {
936
- const path = this.#path;
937
- await withFileLock(`${path}.lock`, async () => {
938
- const file = this.#prune(await this.#read());
939
- for (const o of kept) {
940
- touch(file.addresses, o.address, o);
941
- const domain = domainOf(o.address);
942
- if (domain && !PUBLIC_MAILBOX_DOMAINS.has(domain)) touch(file.domains, domain, o);
943
- }
944
- await writeFileAtomic(path, JSON.stringify(file));
945
- });
946
- }
947
- if (keptHandles.length > 0) {
948
- const path = this.#handlesPath;
949
- await withFileLock(`${path}.lock`, async () => {
950
- const file = this.#pruneHandles(await this.#readHandles());
951
- for (const o of keptHandles) touch(file.handles, o.key, o);
952
- await writeFileAtomic(path, JSON.stringify(file));
1012
+ const commit = async () => {
1013
+ const taint = this.#prune(await this.#read());
1014
+ const handles = this.#pruneHandles(await this.#readHandles());
1015
+ if (kept.length > 0) for (const o of kept) {
1016
+ touch(taint.addresses, o.address, o, "addresses");
1017
+ const domain = domainOf(o.address);
1018
+ if (domain && !PUBLIC_MAILBOX_DOMAINS.has(domain)) touch(taint.domains, domain, o, "domains");
1019
+ }
1020
+ if (keptHandles.length > 0) for (const o of keptHandles) touch(handles.handles, o.key, o, "handles");
1021
+ const finalTaint = this.#prune(taint);
1022
+ const finalHandles = this.#pruneHandles(handles);
1023
+ const origins = this.#mergeOrigins(await this.#readOrigins(), finalTaint.addresses, finalTaint.domains, finalHandles.handles, touched, at);
1024
+ await writeFileAtomic(this.#originsPath, JSON.stringify(origins));
1025
+ await this.#hooks.afterOriginsCommit?.();
1026
+ if (kept.length > 0) await writeFileAtomic(this.#path, JSON.stringify(finalTaint));
1027
+ if (keptHandles.length > 0) await writeFileAtomic(this.#handlesPath, JSON.stringify(finalHandles));
1028
+ };
1029
+ const withBaseLocks = async () => {
1030
+ await withFileLock(`${this.#path}.lock`, async () => {
1031
+ this.#hooks.onLockAcquired?.("taint");
1032
+ await withFileLock(`${this.#handlesPath}.lock`, async () => {
1033
+ this.#hooks.onLockAcquired?.("handles");
1034
+ await commit();
1035
+ });
953
1036
  });
954
- }
1037
+ };
1038
+ await withFileLock(`${this.#originsPath}.lock`, async () => {
1039
+ this.#hooks.onLockAcquired?.("origins");
1040
+ await withBaseLocks();
1041
+ });
955
1042
  }
956
1043
  /**
957
1044
  * Whether an address, or its (non-public) domain, was seen in email content in the window — from any inbox — and the
@@ -959,20 +1046,22 @@ var TaintStore = class {
959
1046
  */
960
1047
  async check(address) {
961
1048
  const file = this.#prune(await this.#read());
1049
+ const origins = await this.#readOrigins();
962
1050
  const canonical = canonicalAddress(address);
963
1051
  const domain = domainOf(canonical);
964
1052
  const exact = Object.hasOwn(file.addresses, canonical) ? file.addresses[canonical] : void 0;
965
1053
  const byDomain = domain !== null && !PUBLIC_MAILBOX_DOMAINS.has(domain) && Object.hasOwn(file.domains, domain) ? file.domains[domain] : void 0;
966
- const aggregate = (entry) => ({
1054
+ const aggregate = (entry, origin) => ({
967
1055
  at: entry.at,
968
1056
  source: entry.source,
969
- inboxIds: [...entry.inboxIds]
1057
+ inboxIds: [...entry.inboxIds],
1058
+ origins: sortedOrigins([...originEntry(origin), ...origin !== void 0 && isNewer(entry.at, origin.at) ? ["read"] : []])
970
1059
  });
971
1060
  return {
972
1061
  address: exact !== void 0,
973
1062
  domain: byDomain !== void 0,
974
- ...exact === void 0 ? {} : { addressSeen: aggregate(exact) },
975
- ...byDomain === void 0 ? {} : { domainSeen: aggregate(byDomain) }
1063
+ ...exact === void 0 ? {} : { addressSeen: aggregate(exact, origins.addresses[canonical]) },
1064
+ ...byDomain === void 0 ? {} : { domainSeen: aggregate(byDomain, origins.domains[domain ?? ""]) }
976
1065
  };
977
1066
  }
978
1067
  /**
@@ -985,6 +1074,15 @@ var TaintStore = class {
985
1074
  const file = this.#pruneHandles(await this.#readHandles());
986
1075
  return canonicalHandle(handle) in file.handles;
987
1076
  }
1077
+ /** The merged provenance of a retained handle, or no origins when it is not in the taint window. */
1078
+ async checkHandleOrigins(handle) {
1079
+ const file = this.#pruneHandles(await this.#readHandles());
1080
+ const key = canonicalHandle(handle);
1081
+ const entry = file.handles[key];
1082
+ if (entry === void 0) return [];
1083
+ const sidecar = (await this.#readOrigins()).handles[key];
1084
+ return sortedOrigins([...originEntry(sidecar), ...sidecar !== void 0 && isNewer(entry.at, sidecar.at) ? ["read"] : []]);
1085
+ }
988
1086
  };
989
1087
  /**
990
1088
  * The context every read path uses to put sender-controlled content into a result. It wraps strings in the untrusted
@@ -1000,21 +1098,23 @@ var TaintCollector = class {
1000
1098
  this.#messageId = messageId;
1001
1099
  }
1002
1100
  /** Scans free text (bodies, subjects, snippets, attachment text) for addresses. */
1003
- observeText(text) {
1101
+ observeText(text, origin = "read") {
1004
1102
  for (const address of extractAddresses(text)) this.#observations.push({
1005
1103
  address,
1006
1104
  source: "body",
1105
+ origin,
1007
1106
  inboxId: this.#inboxId,
1008
1107
  messageId: this.#messageId
1009
1108
  });
1010
1109
  }
1011
1110
  /** Records parsed header addresses (From, Reply-To, Sender, To, Cc). */
1012
- observeHeaders(addresses) {
1111
+ observeHeaders(addresses, origin = "read") {
1013
1112
  for (const address of addresses) {
1014
1113
  const canonical = canonicalAddress(address);
1015
1114
  if (canonical.includes("@")) this.#observations.push({
1016
1115
  address: canonical,
1017
1116
  source: "header",
1117
+ origin,
1018
1118
  inboxId: this.#inboxId,
1019
1119
  messageId: this.#messageId
1020
1120
  });
@@ -1027,10 +1127,11 @@ var TaintCollector = class {
1027
1127
  * second place to keep it correct. What core insists on is that the caller hands over ids rather than the display
1028
1128
  * names beside them, which the account being named can change at any time.
1029
1129
  */
1030
- observeHandles(handles, source = "body") {
1130
+ observeHandles(handles, source = "body", origin = "read") {
1031
1131
  for (const handle of handles) this.#handles.push({
1032
1132
  handle,
1033
1133
  source,
1134
+ origin,
1034
1135
  inboxId: this.#inboxId,
1035
1136
  messageId: this.#messageId
1036
1137
  });
@@ -1127,7 +1228,19 @@ const CHANNEL_SNAPSHOT = [
1127
1228
  prefix: "gmail-",
1128
1229
  contract: "skills/_shared/contract-gmail.md"
1129
1230
  },
1130
- approvalGrouping: "draft"
1231
+ approvalGrouping: "draft",
1232
+ events: {
1233
+ types: [
1234
+ "gmail.message.received",
1235
+ "gmail.message.sent",
1236
+ "gmail.message.labelled"
1237
+ ],
1238
+ minimumIntervalMs: 6e4,
1239
+ access: {
1240
+ kind: "oauth-user",
1241
+ requiredScopes: ["https://www.googleapis.com/auth/gmail.readonly"]
1242
+ }
1243
+ }
1131
1244
  }
1132
1245
  },
1133
1246
  {
@@ -1167,7 +1280,12 @@ const CHANNEL_SNAPSHOT = [
1167
1280
  prefix: "resend-",
1168
1281
  contract: "skills/_shared/contract-resend.md"
1169
1282
  },
1170
- approvalGrouping: "draft"
1283
+ approvalGrouping: "draft",
1284
+ events: {
1285
+ types: ["resend.email.received", "resend.email.status_changed"],
1286
+ minimumIntervalMs: 6e4,
1287
+ access: { kind: "resend-full-access" }
1288
+ }
1171
1289
  }
1172
1290
  },
1173
1291
  {
@@ -1207,7 +1325,20 @@ const CHANNEL_SNAPSHOT = [
1207
1325
  prefix: "slack-",
1208
1326
  contract: "skills/_shared/contract-slack.md"
1209
1327
  },
1210
- approvalGrouping: "draft-revision-digest"
1328
+ approvalGrouping: "draft-revision-digest",
1329
+ events: {
1330
+ types: ["slack.message.posted"],
1331
+ minimumIntervalMs: 6e4,
1332
+ access: {
1333
+ kind: "oauth-user",
1334
+ requiredScopes: [
1335
+ "channels:history",
1336
+ "groups:history",
1337
+ "im:history",
1338
+ "mpim:history"
1339
+ ]
1340
+ }
1341
+ }
1211
1342
  }
1212
1343
  },
1213
1344
  {
@@ -1246,10 +1377,76 @@ const CHANNEL_SNAPSHOT = [
1246
1377
  skills: {
1247
1378
  prefix: "whatsapp-",
1248
1379
  contract: "skills/_shared/contract-whatsapp.md"
1380
+ },
1381
+ events: {
1382
+ types: ["whatsapp.message.received"],
1383
+ minimumIntervalMs: 6e4,
1384
+ access: { kind: "local-store" }
1249
1385
  }
1250
1386
  }
1251
1387
  }
1252
1388
  ];
1389
+ const DISCLOSURE_VERSION_KINDS = [
1390
+ "rule",
1391
+ "target",
1392
+ "subscriber",
1393
+ "judge",
1394
+ "judge-budget",
1395
+ "judge-kind"
1396
+ ];
1397
+ const DISCLOSURE_ACTIVATION_KINDS = [
1398
+ "rule",
1399
+ "judge-budget",
1400
+ "judge-kind",
1401
+ "enable-all"
1402
+ ];
1403
+ const HEX64$1 = /^[0-9a-f]{64}$/;
1404
+ function disclosureFailure() {
1405
+ throw new CommsError("BAD_DATA", "a disclosure binding is not in its canonical activation shape");
1406
+ }
1407
+ function compareDisclosureVersions(left, right) {
1408
+ const compareText = (one, two) => one < two ? -1 : one > two ? 1 : 0;
1409
+ return compareText(left.kind, right.kind) || compareText(left.id, right.id) || left.version - right.version;
1410
+ }
1411
+ /**
1412
+ * Validates and copies a disclosure binding without reordering it. A caller must provide the
1413
+ * daemon-derived canonical list, already sorted by (kind, id, version), so a different document
1414
+ * cannot become authorised by a convenient normalisation here.
1415
+ */
1416
+ function canonicalDisclosureBinding(binding) {
1417
+ if (!HEX64$1.test(binding.digest) || typeof binding.activationIntentId !== "string" || binding.activationIntentId.length === 0 || !DISCLOSURE_ACTIVATION_KINDS.includes(binding.activationKind) || !Array.isArray(binding.versions)) return disclosureFailure();
1418
+ const versions = binding.versions.map((entry) => {
1419
+ if (!DISCLOSURE_VERSION_KINDS.includes(entry.kind) || typeof entry.id !== "string" || entry.id.length === 0 || !Number.isSafeInteger(entry.version) || entry.version < 1) return disclosureFailure();
1420
+ return {
1421
+ kind: entry.kind,
1422
+ id: entry.id,
1423
+ version: entry.version
1424
+ };
1425
+ });
1426
+ for (let index = 1; index < versions.length; index += 1) if (compareDisclosureVersions(versions[index - 1], versions[index]) >= 0) return disclosureFailure();
1427
+ const kinds = new Set(versions.map((entry) => entry.kind));
1428
+ if (binding.activationKind === "rule") {
1429
+ if (!kinds.has("rule") || kinds.has("judge-budget") || kinds.has("judge-kind")) return disclosureFailure();
1430
+ } else if (binding.activationKind === "judge-budget") {
1431
+ if (versions.length !== 1 || versions[0]?.kind !== "judge-budget") return disclosureFailure();
1432
+ } else if (binding.activationKind === "judge-kind") {
1433
+ if (versions.length !== 1 || versions[0]?.kind !== "judge-kind") return disclosureFailure();
1434
+ } else if (versions.some((entry) => entry.kind !== "rule")) return disclosureFailure();
1435
+ return {
1436
+ digest: binding.digest,
1437
+ activationIntentId: binding.activationIntentId,
1438
+ activationKind: binding.activationKind,
1439
+ versions
1440
+ };
1441
+ }
1442
+ /** Exact canonical equality for each re-check at approval and claim. */
1443
+ function sameDisclosureBinding(left, right) {
1444
+ try {
1445
+ return canonicalJson$1(canonicalDisclosureBinding(left)) === canonicalJson$1(canonicalDisclosureBinding(right));
1446
+ } catch {
1447
+ return false;
1448
+ }
1449
+ }
1253
1450
  /** The lifetime profile, in milliseconds: pending on each route, approved, and a download's question. */
1254
1451
  const APPROVAL_LIFETIMES = Object.freeze({
1255
1452
  chat: 6e5,
@@ -1327,18 +1524,25 @@ function listedEntry(file) {
1327
1524
  * folders it offered, and the listing exactly as stored, left out when the record has none.
1328
1525
  */
1329
1526
  function bindingObjectOf(record) {
1330
- const identity = identityOf(record);
1331
- if (record.kind === "download") {
1332
- const download = record.download;
1527
+ if (record.kind === "disclosure" && "disclosure" in record && record.disclosure !== void 0) return {
1528
+ v: 2,
1529
+ kind: "disclosure",
1530
+ approvalId: record.approvalId,
1531
+ disclosure: canonicalDisclosureBinding(record.disclosure)
1532
+ };
1533
+ const ordinary = record;
1534
+ const identity = identityOf(ordinary);
1535
+ if (ordinary.kind === "download") {
1536
+ const download = ordinary.download;
1333
1537
  if (download === void 0) throw new CommsError("BAD_DATA", "a download’s approval does not hold the question it was asked for");
1334
1538
  return {
1335
1539
  v: 2,
1336
1540
  kind: "download",
1337
- contentDigest: record.contentDigest,
1541
+ contentDigest: ordinary.contentDigest,
1338
1542
  profile: {
1339
- pendingMs: record.pendingMs,
1340
- policy: record.policy,
1341
- requiredPolicy: record.requiredPolicy
1543
+ pendingMs: ordinary.pendingMs,
1544
+ policy: ordinary.policy,
1545
+ requiredPolicy: ordinary.requiredPolicy
1342
1546
  },
1343
1547
  identity,
1344
1548
  offered: {
@@ -1350,11 +1554,11 @@ function bindingObjectOf(record) {
1350
1554
  }
1351
1555
  return {
1352
1556
  v: 2,
1353
- kind: record.kind,
1354
- contentDigest: record.contentDigest,
1355
- route: record.route,
1356
- pendingMs: record.pendingMs,
1357
- approvedMs: record.approvedMs,
1557
+ kind: ordinary.kind,
1558
+ contentDigest: ordinary.contentDigest,
1559
+ route: ordinary.route,
1560
+ pendingMs: ordinary.pendingMs,
1561
+ approvedMs: ordinary.approvedMs,
1358
1562
  identity
1359
1563
  };
1360
1564
  }
@@ -6342,7 +6546,11 @@ const NO_CONFIGURATION = "this approval store was opened without a configuration
6342
6546
  const sentAtWords = (usedAt) => `sent at ${usedAt}`;
6343
6547
  /** The refusal prefix and detail key of each kind: a send sends nothing, a change changes nothing, a question saves nothing. */
6344
6548
  function refusalOf(kind) {
6345
- const [prefix, idKey, hint] = kind === "change" ? [
6549
+ const [prefix, idKey, hint] = kind === "disclosure" ? [
6550
+ "nothing was disclosed",
6551
+ "approvalId",
6552
+ "Prepare a new standing disclosure authorisation and show its exact activation preview to the person."
6553
+ ] : kind === "change" ? [
6346
6554
  "nothing was changed",
6347
6555
  "approvalId",
6348
6556
  "Prepare the change again and show the new preview to the user."
@@ -6369,7 +6577,7 @@ function refusalOf(kind) {
6369
6577
  * or one a pinned surface may not see — byte for byte the same, with `approval: null`, and nothing of any record in it.
6370
6578
  */
6371
6579
  function approvalNotFound(approvalId, kind) {
6372
- return new CommsError("NOT_FOUND", `${kind === "change" ? "nothing was changed" : kind === "download" ? "nothing was saved" : "nothing was sent"}: no approval ${approvalId}`, {
6580
+ return new CommsError("NOT_FOUND", `${kind === "disclosure" ? "nothing was disclosed" : kind === "change" ? "nothing was changed" : kind === "download" ? "nothing was saved" : "nothing was sent"}: no approval ${approvalId}`, {
6373
6581
  hint: "Check the id: an approval is only found by the surface, the kind and the owner it was prepared for.",
6374
6582
  details: { approval: null }
6375
6583
  });
@@ -6401,6 +6609,24 @@ function objectOf(record, state, claimable, ownerRemoved, reason) {
6401
6609
  ...optional("expiredAt", record.expiredAt)
6402
6610
  };
6403
6611
  }
6612
+ /** Standing disclosure has no channel owner, draft, policy or sender-controlled field to classify. */
6613
+ function disclosureObjectOf(record, claimable, reason) {
6614
+ return {
6615
+ id: record.approvalId,
6616
+ kind: "disclosure",
6617
+ channel: null,
6618
+ state: record.state,
6619
+ claimable,
6620
+ ...reason === void 0 ? {} : { reason },
6621
+ createdAt: record.createdAt,
6622
+ expiresAt: record.expiresAt,
6623
+ ...record.approvedAt === void 0 ? {} : { approvedAt: record.approvedAt },
6624
+ ...record.usableUntil === void 0 ? {} : { usableUntil: record.usableUntil },
6625
+ ...record.usedAt === void 0 ? {} : { usedAt: record.usedAt },
6626
+ ...record.revokedAt === void 0 ? {} : { revokedAt: record.revokedAt },
6627
+ ...record.expiredAt === void 0 ? {} : { expiredAt: record.expiredAt }
6628
+ };
6629
+ }
6404
6630
  Object.freeze([
6405
6631
  "expect.to",
6406
6632
  "expect.cc",
@@ -6436,7 +6662,7 @@ function saidOf(approval, facts, options) {
6436
6662
  if (approval.state === "answered") return facts.answeredWords;
6437
6663
  if (approval.state === "expired") {
6438
6664
  if (facts.kind === "download") return downloadExpiredWords(facts.answered);
6439
- const nothing = `nothing was ${facts.kind === "change" ? "changed" : "sent"} with it`;
6665
+ const nothing = facts.kind === "change" ? "nothing was changed with it" : facts.kind === "disclosure" ? "nothing was disclosed with it" : "nothing was sent with it";
6440
6666
  return approval.reason === "clock-anomaly" ? `the clock moved backwards; this approval was expired safely at ${approval.expiredAt}; ${nothing}` : `this approval expired; ${nothing}`;
6441
6667
  }
6442
6668
  if (approval.state === "used" && facts.kind === "send" && approval.usedAt !== void 0) return options.usedSaid?.(approval.usedAt) ?? `accepted by ${approval.channel === null ? "its provider" : labelOf$1(approval.channel)} at ${approval.usedAt}`;
@@ -6456,7 +6682,7 @@ function publicApproval(stored, outcome, options = {}) {
6456
6682
  field,
6457
6683
  id: approval.id
6458
6684
  }, boundary));
6459
- const source = stored.form === "v2" ? {
6685
+ const source = stored.form === "v2" && stored.record.kind !== "disclosure" ? {
6460
6686
  inboxId: stored.record.inboxId,
6461
6687
  draftId: stored.record.draftId,
6462
6688
  expect: stored.record.expect,
@@ -6470,7 +6696,7 @@ function publicApproval(stored, outcome, options = {}) {
6470
6696
  download: stored.record.download,
6471
6697
  answered: stored.record.approvedVia !== void 0,
6472
6698
  answeredWords: void 0
6473
- } : stored.form === "corrupt" && stored.safe !== null ? {
6699
+ } : stored.form === "corrupt" && stored.safe !== null && stored.safe.kind !== "disclosure" ? {
6474
6700
  inboxId: stored.safe.inboxId,
6475
6701
  draftId: stored.safe.draftId,
6476
6702
  expect: stored.safe.expect,
@@ -6511,6 +6737,7 @@ function publicApproval(stored, outcome, options = {}) {
6511
6737
  * nothing claimable. Pure: what decides is the store's locked classification, and this only says where it stands.
6512
6738
  */
6513
6739
  function approvalObjectOf(record, live, now) {
6740
+ if (record.kind === "disclosure") return classifyDisclosure(record).approval;
6514
6741
  return classifyV2(record, {
6515
6742
  action: "inspect",
6516
6743
  live,
@@ -6590,7 +6817,7 @@ function approvalOutcome(stored, context) {
6590
6817
  approval: {
6591
6818
  id,
6592
6819
  kind: safe?.kind ?? null,
6593
- channel: safe?.channel ?? null,
6820
+ channel: safe?.kind === "disclosure" ? null : safe?.channel ?? null,
6594
6821
  state: "corrupt",
6595
6822
  claimable: false,
6596
6823
  reason
@@ -6601,6 +6828,7 @@ function approvalOutcome(stored, context) {
6601
6828
  return classifyV2(stored.record, context);
6602
6829
  }
6603
6830
  function classifyV2(read, context) {
6831
+ if (read.kind === "disclosure") return classifyDisclosure(read);
6604
6832
  const { live, action } = context;
6605
6833
  const at = context.now.toISOString();
6606
6834
  const ownerRemoved = read.ownerScope === "owner" && live?.owner === "removed";
@@ -6649,12 +6877,42 @@ function classifyV2(read, context) {
6649
6877
  ...error === void 0 ? {} : { error }
6650
6878
  };
6651
6879
  }
6880
+ /** The isolated disclosure state machine never follows a config policy or a channel-owner fence. */
6881
+ function classifyDisclosure(record) {
6882
+ const claimable = record.state === "approved";
6883
+ const approval = disclosureObjectOf(record, claimable, record.reason);
6884
+ let error;
6885
+ switch (record.state) {
6886
+ case "pending":
6887
+ case "approved": break;
6888
+ case "used":
6889
+ error = refusalOf("disclosure")("APPROVAL_VOID", `the standing disclosure approval was used already at ${record.usedAt}`, record, approval);
6890
+ break;
6891
+ case "expired":
6892
+ error = refusalOf("disclosure")("APPROVAL_EXPIRED", "the standing disclosure approval expired unused", record, approval);
6893
+ break;
6894
+ case "revoked":
6895
+ error = refusalOf("disclosure")("APPROVAL_VOID", `the standing disclosure approval was voided (${record.reason ?? "revoked"})`, record, approval);
6896
+ break;
6897
+ default: error = refusalOf("disclosure")("APPROVAL_VOID", "the standing disclosure approval is in an invalid state", record, approval);
6898
+ }
6899
+ return {
6900
+ state: record.state,
6901
+ claimable,
6902
+ ...record.reason === void 0 ? {} : { reason: record.reason },
6903
+ record,
6904
+ revokes: false,
6905
+ approval,
6906
+ ...error === void 0 ? {} : { error }
6907
+ };
6908
+ }
6652
6909
  /** Whether an active record can be claimed under `live` (D2, `claimable`). */
6653
6910
  function isClaimable(record, live) {
6654
6911
  switch (record.kind) {
6655
6912
  case "send": return record.state === "approved" || record.route === "chat" && live.sendPolicy === "chat";
6656
6913
  case "change": return record.state === "approved" || record.route === "chat" && live.changePolicy === "chat";
6657
6914
  case "download": return downloadClaimable(record, live.changePolicy);
6915
+ case "disclosure": return record.state === "approved";
6658
6916
  }
6659
6917
  }
6660
6918
  /**
@@ -6712,6 +6970,7 @@ function errorOf(record, approval, context) {
6712
6970
  });
6713
6971
  }
6714
6972
  case "used":
6973
+ if (record.kind === "disclosure") return refuse("APPROVAL_VOID", `the standing disclosure approval was used already at ${record.usedAt}`, record, approval);
6715
6974
  if (download) return refuse("APPROVAL_VOID", "the question was answered already, and an answer is used once", record, approval);
6716
6975
  if (record.kind === "change") return refuse("APPROVAL_VOID", `the approved change was already claimed at ${record.usedAt}`, record, approval);
6717
6976
  return refuse("APPROVAL_VOID", `the approval was used already: it was ${context.usedSaid(String(record.usedAt))}, message id ${record.sentMessageId}`, record, approval, "Prepare a new send only for a new message.");
@@ -6922,10 +7181,24 @@ function refuseDownload(code, reason, record, hint) {
6922
7181
  } : {}
6923
7182
  });
6924
7183
  }
7184
+ /** The disclosure path has no send, draft or provider outcome: its refusal must never imply one. */
7185
+ function refuseDisclosure(code, reason, record, hint) {
7186
+ return new CommsError(code, `nothing was disclosed: ${reason}`, {
7187
+ hint: hint ?? "Prepare a new standing disclosure authorisation and show its exact activation preview to the person.",
7188
+ details: record ? {
7189
+ approvalId: record.approvalId,
7190
+ state: record.state
7191
+ } : {}
7192
+ });
7193
+ }
6925
7194
  /** The refusal in the words of the record's own kind. */
6926
7195
  function refusalFor(record) {
6927
- const kind = approvalKind(record);
6928
- return kind === "change" ? refuseChange : kind === "download" ? refuseDownload : refuse$1;
7196
+ switch (approvalKind(record)) {
7197
+ case "send": return refuse$1;
7198
+ case "change": return refuseChange;
7199
+ case "download": return refuseDownload;
7200
+ case "disclosure": return refuseDisclosure;
7201
+ }
6929
7202
  }
6930
7203
  /**
6931
7204
  * The refusal of a record prepared under another digest version, in the words of its own kind: the one every locked
@@ -6942,23 +7215,40 @@ function otherVersionRefusal(record) {
6942
7215
  * nothing is sent by answering one. The record is as it was, and the hint says so.
6943
7216
  */
6944
7217
  function cancelledClaim(record) {
6945
- const kind = approvalKind(record);
6946
- if (kind === "download") return new CommsError("USAGE", "cancelled: nothing was saved", {
6947
- hint: "The question was not used: the same call, made again with the same answer, can still use it until it expires.",
6948
- details: {
6949
- choiceId: record.approvalId,
6950
- state: record.state,
6951
- reason: "cancelled"
6952
- }
6953
- });
6954
- return new CommsError("USAGE", `cancelled: ${kind === "change" ? "nothing was changed" : "nothing was sent"}`, {
6955
- hint: "The approval was not used: the same call, made again, can still use it until it expires.",
6956
- details: {
6957
- approvalId: record.approvalId,
6958
- state: record.state,
6959
- reason: "cancelled"
6960
- }
6961
- });
7218
+ switch (approvalKind(record)) {
7219
+ case "send": return new CommsError("USAGE", "cancelled: nothing was sent", {
7220
+ hint: "The approval was not used: the same call, made again, can still use it until it expires.",
7221
+ details: {
7222
+ approvalId: record.approvalId,
7223
+ state: record.state,
7224
+ reason: "cancelled"
7225
+ }
7226
+ });
7227
+ case "change": return new CommsError("USAGE", "cancelled: nothing was changed", {
7228
+ hint: "The approval was not used: the same call, made again, can still use it until it expires.",
7229
+ details: {
7230
+ approvalId: record.approvalId,
7231
+ state: record.state,
7232
+ reason: "cancelled"
7233
+ }
7234
+ });
7235
+ case "download": return new CommsError("USAGE", "cancelled: nothing was saved", {
7236
+ hint: "The question was not used: the same call, made again with the same answer, can still use it until it expires.",
7237
+ details: {
7238
+ choiceId: record.approvalId,
7239
+ state: record.state,
7240
+ reason: "cancelled"
7241
+ }
7242
+ });
7243
+ case "disclosure": return new CommsError("USAGE", "cancelled: nothing was disclosed", {
7244
+ hint: "The standing disclosure approval was not used: it can still be completed until it expires.",
7245
+ details: {
7246
+ approvalId: record.approvalId,
7247
+ state: record.state,
7248
+ reason: "cancelled"
7249
+ }
7250
+ });
7251
+ }
6962
7252
  }
6963
7253
  /** The refusal of a renewal or an outcome from a call that does not hold the send's claim. It names no token. */
6964
7254
  function notTheClaim(approvalId) {
@@ -6985,7 +7275,9 @@ function approvePendingHint(handoffs, approvalId, platform) {
6985
7275
  function matchesExpectation(found, expect) {
6986
7276
  const kind = kindOf(found);
6987
7277
  const owner = ownerOf(found);
6988
- if (kind === null || owner === null) return expect.owner === void 0;
7278
+ if (kind === null) return expect.owner === void 0;
7279
+ if (kind === "disclosure") return (expect.kind === void 0 || expect.kind === "disclosure") && expect.channel === void 0 && expect.owner === void 0;
7280
+ if (owner === null) return expect.owner === void 0;
6989
7281
  if (expect.kind !== void 0 && kind !== expect.kind) return false;
6990
7282
  if (expect.channel !== void 0 && channelOf(found) !== expect.channel) return false;
6991
7283
  return expect.owner === void 0 || owner === expect.owner;
@@ -7110,6 +7402,18 @@ var ApprovalStore = class {
7110
7402
  * profile checked first, so nothing is written that names a channel this release does not know.
7111
7403
  */
7112
7404
  #bound(record) {
7405
+ if (record.kind === "disclosure") {
7406
+ const disclosure = canonicalDisclosureBinding(record.disclosure);
7407
+ return {
7408
+ ...record,
7409
+ disclosure,
7410
+ bindingDigest: bindingDigestOf({
7411
+ approvalId: record.approvalId,
7412
+ kind: "disclosure",
7413
+ disclosure
7414
+ })
7415
+ };
7416
+ }
7113
7417
  requireKnownChannel(record.channel);
7114
7418
  return {
7115
7419
  ...record,
@@ -7157,6 +7461,28 @@ var ApprovalStore = class {
7157
7461
  await this.#write(record);
7158
7462
  return record;
7159
7463
  }
7464
+ /** Creates only a pending standing disclosure approval from a daemon-derived canonical binding. */
7465
+ async createDisclosure(binding) {
7466
+ await this.ensurePruned();
7467
+ const disclosure = canonicalDisclosureBinding(binding);
7468
+ const now = this.#now();
7469
+ const pendingMs = APPROVAL_LIFETIMES.confirm;
7470
+ const record = this.#bound({
7471
+ approvalId: newApprovalId(),
7472
+ kind: "disclosure",
7473
+ digestVersion: 2,
7474
+ pendingMs,
7475
+ approvedMs: APPROVAL_LIFETIMES.approved,
7476
+ disclosure,
7477
+ challengeAttempts: 0,
7478
+ state: "pending",
7479
+ createdAt: now.toISOString(),
7480
+ expiresAt: new Date(now.getTime() + pendingMs).toISOString(),
7481
+ updatedAt: now.toISOString()
7482
+ });
7483
+ await this.#write(record);
7484
+ return record;
7485
+ }
7160
7486
  /**
7161
7487
  * One approval, in whichever of its four forms it is (`StoredApproval`), read against one configuration snapshot —
7162
7488
  * or null when there is none. Nothing is written.
@@ -7307,17 +7633,33 @@ var ApprovalStore = class {
7307
7633
  * approval can be used as it is.
7308
7634
  */
7309
7635
  #alreadyApproved(record, outcome) {
7310
- return new CommsError("USAGE", `${record.kind === "change" ? "nothing was changed" : "nothing was sent"}: it is approved already, and a person approves it once`, {
7311
- hint: "Nothing more is needed from the person: try the same call again with the same approval.",
7312
- details: {
7313
- approvalId: record.approvalId,
7314
- state: record.state,
7315
- approval: outcome.approval
7316
- }
7317
- });
7636
+ const detail = {
7637
+ approvalId: record.approvalId,
7638
+ state: record.state,
7639
+ approval: outcome.approval
7640
+ };
7641
+ switch (record.kind) {
7642
+ case "send": return new CommsError("USAGE", "nothing was sent: it is approved already, and a person approves it once", {
7643
+ hint: "Nothing more is needed from the person: try the same call again with the same approval.",
7644
+ details: detail
7645
+ });
7646
+ case "change": return new CommsError("USAGE", "nothing was changed: it is approved already, and a person approves it once", {
7647
+ hint: "Nothing more is needed from the person: try the same call again with the same approval.",
7648
+ details: detail
7649
+ });
7650
+ case "download": return new CommsError("USAGE", "nothing was saved: it is approved already, and a person approves it once", {
7651
+ hint: "Nothing more is needed from the person: try the same call again with the same approval.",
7652
+ details: detail
7653
+ });
7654
+ case "disclosure": return new CommsError("USAGE", "nothing was disclosed: it is approved already, and a person approves it once", {
7655
+ hint: "Nothing more is needed from the person: complete the same activation with the same approval.",
7656
+ details: detail
7657
+ });
7658
+ }
7318
7659
  }
7319
7660
  /** Issues a new challenge to show a human; only its hash is kept. */
7320
7661
  async issueChallenge(approvalId, kind = "send", _platform = process.platform, options = {}) {
7662
+ if (kind === "disclosure") throw new CommsError("APPROVAL_REQUIRED", "nothing was disclosed: a standing disclosure challenge is only issued to the trusted terminal or app route");
7321
7663
  const challenge = newChallenge();
7322
7664
  await this.#transition(approvalId, { kind }, "approve", (current, outcome) => {
7323
7665
  if (outcome.error) throw outcome.error;
@@ -7329,6 +7671,139 @@ var ApprovalStore = class {
7329
7671
  }, { usedSaid: options.usedSaid });
7330
7672
  return challenge;
7331
7673
  }
7674
+ /** Issues the typed challenge that a terminal or the trusted desktop app must answer for disclosure. */
7675
+ async issueDisclosureChallenge(approvalId) {
7676
+ const challenge = newChallenge();
7677
+ await this.#transition(approvalId, { kind: "disclosure" }, "approve", (current, outcome) => {
7678
+ if (outcome.error) throw outcome.error;
7679
+ if (current.state !== "pending") throw this.#alreadyApproved(current, outcome);
7680
+ return {
7681
+ ...current,
7682
+ challengeHash: hashChallenge(challenge)
7683
+ };
7684
+ });
7685
+ return challenge;
7686
+ }
7687
+ /**
7688
+ * Approves one exact standing disclosure authority. MCP/chat forms are never an approval channel here: `app` means
7689
+ * the desktop application's trusted typed-challenge bridge, not a client form.
7690
+ */
7691
+ async approveDisclosure(approvalId, liveBinding, answer, via) {
7692
+ let failure = null;
7693
+ const done = await this.#transition(approvalId, { kind: "disclosure" }, "approve", (current, outcome) => {
7694
+ if (outcome.error) throw outcome.error;
7695
+ if (current.state !== "pending") throw this.#alreadyApproved(current, outcome);
7696
+ if (via !== "terminal" && via !== "app") throw new CommsError("APPROVAL_REQUIRED", "nothing was disclosed: approval is only at the terminal or the trusted app", { details: {
7697
+ approvalId: current.approvalId,
7698
+ state: current.state
7699
+ } });
7700
+ if (!current.challengeHash) throw new CommsError("APPROVAL_REQUIRED", "nothing was disclosed: no challenge was issued for this approval", { details: {
7701
+ approvalId: current.approvalId,
7702
+ state: current.state
7703
+ } });
7704
+ const at = this.#now();
7705
+ const binding = current.disclosure;
7706
+ if (!sameDisclosureBinding(binding, liveBinding)) {
7707
+ failure = {
7708
+ code: "APPROVAL_VOID",
7709
+ reason: "the disclosure binding changed after it was prepared"
7710
+ };
7711
+ return {
7712
+ ...current,
7713
+ state: "revoked",
7714
+ reason: failure.reason,
7715
+ revokedAt: at.toISOString(),
7716
+ challengeHash: void 0
7717
+ };
7718
+ }
7719
+ if (!challengeMatches(answer, current.challengeHash)) {
7720
+ const attempts = current.challengeAttempts + 1;
7721
+ if (attempts >= 3) {
7722
+ failure = {
7723
+ code: "APPROVAL_VOID",
7724
+ reason: "too many wrong answers to the challenge"
7725
+ };
7726
+ return {
7727
+ ...current,
7728
+ challengeAttempts: attempts,
7729
+ state: "revoked",
7730
+ reason: failure.reason,
7731
+ revokedAt: at.toISOString()
7732
+ };
7733
+ }
7734
+ failure = {
7735
+ code: "APPROVAL_REQUIRED",
7736
+ reason: "the challenge did not match"
7737
+ };
7738
+ return {
7739
+ ...current,
7740
+ challengeAttempts: attempts
7741
+ };
7742
+ }
7743
+ return {
7744
+ ...current,
7745
+ state: "approved",
7746
+ approvedVia: via,
7747
+ approvedAt: at.toISOString(),
7748
+ usableUntil: new Date(at.getTime() + APPROVAL_LIFETIMES.approved).toISOString(),
7749
+ approvedDigest: binding.digest,
7750
+ challengeHash: void 0
7751
+ };
7752
+ });
7753
+ const refused = failure;
7754
+ if (refused !== null) throw refusedAfter(refusalFor(done.record)(refused.code, refused.reason, done.record), done);
7755
+ return done.record;
7756
+ }
7757
+ /**
7758
+ * Claims an approved disclosure once, persisting the exact `usedAt` that recovery must subsequently reuse.
7759
+ * A specialised caller may impose a shorter deadline; it is evaluated while this record's transition lock is held,
7760
+ * so a check made before waiting on that lock cannot accidentally consume the approval after it has expired.
7761
+ */
7762
+ async claimForDisclosure(approvalId, liveBinding, options = {}) {
7763
+ if (options.deadlineMs !== void 0 && (!Number.isSafeInteger(options.deadlineMs) || options.deadlineMs <= 0)) throw new CommsError("BAD_DATA", "the disclosure claim deadline is not a readable instant");
7764
+ let failure = null;
7765
+ const done = await this.#transition(approvalId, { kind: "disclosure" }, "claim", (current, outcome) => {
7766
+ if (outcome.error) throw outcome.error;
7767
+ const now = this.#now();
7768
+ if (options.deadlineMs !== void 0 && now.getTime() >= options.deadlineMs) throw new CommsError("APPROVAL_VOID", "nothing was disclosed: the disclosure challenge expired before claim", { details: {
7769
+ approvalId: current.approvalId,
7770
+ state: current.state,
7771
+ reason: "CHALLENGE_EXPIRED"
7772
+ } });
7773
+ const at = now.toISOString();
7774
+ const binding = current.disclosure;
7775
+ if (current.state === "pending") throw new CommsError("APPROVAL_PENDING", "nothing was disclosed: the standing disclosure approval is pending", { details: {
7776
+ approvalId: current.approvalId,
7777
+ state: current.state
7778
+ } });
7779
+ if (current.state !== "approved") throw new CommsError("APPROVAL_VOID", "nothing was disclosed: the standing disclosure approval was used already", { details: {
7780
+ approvalId: current.approvalId,
7781
+ state: current.state
7782
+ } });
7783
+ if (!sameDisclosureBinding(binding, liveBinding) || current.approvedDigest !== binding.digest) {
7784
+ failure = {
7785
+ code: "APPROVAL_VOID",
7786
+ reason: "the disclosure binding changed after it was approved"
7787
+ };
7788
+ return {
7789
+ ...current,
7790
+ state: "revoked",
7791
+ reason: failure.reason,
7792
+ revokedAt: at,
7793
+ challengeHash: void 0
7794
+ };
7795
+ }
7796
+ return {
7797
+ ...current,
7798
+ state: "used",
7799
+ usedAt: at
7800
+ };
7801
+ });
7802
+ const refused = failure;
7803
+ if (refused !== null) throw refusedAfter(refusalFor(done.record)(refused.code, refused.reason, done.record), done);
7804
+ await this.#markClaimed(done);
7805
+ return done.record;
7806
+ }
7332
7807
  /**
7333
7808
  * A human approved through a confirm channel by typing the issued challenge. The draft must still be exactly what the
7334
7809
  * record was prepared for; otherwise the record is voided, because the human would be approving content the record
@@ -7338,6 +7813,7 @@ var ApprovalStore = class {
7338
7813
  * `createChange`), so a person's typed code means one thing whichever kind of approval it is typed for.
7339
7814
  */
7340
7815
  async approve(approvalId, via, live, answer, kind = "send", _platform = process.platform, options = {}) {
7816
+ if (kind === "disclosure") throw new CommsError("APPROVAL_REQUIRED", "nothing was disclosed: a standing disclosure approval is only accepted on the trusted terminal or app route");
7341
7817
  let failure = null;
7342
7818
  const done = await this.#transition(approvalId, { kind }, "approve", (current, outcome) => {
7343
7819
  if (outcome.error) throw outcome.error;
@@ -8072,6 +8548,7 @@ function fail(reason, attribution = "verified") {
8072
8548
  function checkVerified(record, raw) {
8073
8549
  if (raw.digestVersion !== 2) return fail("digest-version");
8074
8550
  const kind = raw.kind;
8551
+ if (kind === "disclosure") return checkDisclosure(record, raw);
8075
8552
  if (kind !== "send" && kind !== "change" && kind !== "download") return fail("wrong-shape");
8076
8553
  if (!STATES$1.includes(raw.state)) return fail("state-impossible");
8077
8554
  const state = raw.state;
@@ -8095,6 +8572,59 @@ function checkVerified(record, raw) {
8095
8572
  }
8096
8573
  return checkTimestamps(raw, kind, state, times) ?? checkEvidence(record, raw, kind, state);
8097
8574
  }
8575
+ /** Validates the closed disclosure union member without borrowing a sender approval's shape. */
8576
+ function checkDisclosure(record, raw) {
8577
+ if (![
8578
+ "pending",
8579
+ "approved",
8580
+ "used",
8581
+ "expired",
8582
+ "revoked"
8583
+ ].includes(raw.state)) return fail("state-impossible");
8584
+ const disclosure = record.disclosure;
8585
+ if (disclosure === void 0) return fail("wrong-shape");
8586
+ let binding;
8587
+ try {
8588
+ binding = canonicalDisclosureBinding(disclosure);
8589
+ } catch {
8590
+ return fail("wrong-shape");
8591
+ }
8592
+ if (raw.pendingMs !== APPROVAL_LIFETIMES.confirm || raw.approvedMs !== APPROVAL_LIFETIMES.approved) return fail("lifetime-mismatch");
8593
+ const times = {};
8594
+ for (const field of TIMESTAMPS) {
8595
+ const at = timeOf(raw[field]);
8596
+ if (at === "invalid") return fail("timestamp-invalid");
8597
+ if (at !== null) times[field] = at;
8598
+ }
8599
+ const { createdAt, expiresAt, approvedAt, usableUntil, usedAt, revokedAt, expiredAt } = times;
8600
+ if (createdAt === void 0 || expiresAt === void 0) return fail("timestamp-missing");
8601
+ if (expiresAt - createdAt !== APPROVAL_LIFETIMES.confirm) return fail("lifetime-mismatch");
8602
+ const state = raw.state;
8603
+ const approved = approvedAt !== void 0 || usableUntil !== void 0;
8604
+ if (approvedAt === void 0 !== (usableUntil === void 0)) return fail("timestamp-missing");
8605
+ if (approvedAt !== void 0 && usableUntil !== void 0) {
8606
+ if (!(createdAt <= approvedAt && approvedAt < expiresAt)) return fail("timestamp-misordered");
8607
+ if (usableUntil - approvedAt !== APPROVAL_LIFETIMES.approved) return fail("lifetime-mismatch");
8608
+ }
8609
+ if (state === "approved" && !approved) return fail("timestamp-missing");
8610
+ if (state === "used" && usedAt === void 0) return fail("timestamp-missing");
8611
+ if (state !== "used" && usedAt !== void 0) return fail("timestamp-misplaced");
8612
+ if (state === "revoked" && revokedAt === void 0) return fail("timestamp-missing");
8613
+ if (state !== "revoked" && revokedAt !== void 0) return fail("timestamp-misplaced");
8614
+ if (state === "expired" && expiredAt === void 0) return fail("timestamp-missing");
8615
+ if (state !== "expired" && expiredAt !== void 0) return fail("timestamp-misplaced");
8616
+ const deadline = usableUntil ?? expiresAt;
8617
+ if (usedAt !== void 0 && !(approvedAt !== void 0 && approvedAt <= usedAt && usedAt < deadline)) return fail("timestamp-misordered");
8618
+ if (revokedAt !== void 0 && !(createdAt <= revokedAt && revokedAt < deadline)) return fail("timestamp-misordered");
8619
+ if (expiredAt !== void 0 && expiredAt < (approvedAt ?? createdAt)) return fail("timestamp-misordered");
8620
+ if (raw.challengeAttempts !== 0 && raw.challengeAttempts !== 1 && raw.challengeAttempts !== 2 && raw.challengeAttempts !== 3) return fail("evidence-invalid");
8621
+ const via = raw.approvedVia;
8622
+ const digest = raw.approvedDigest;
8623
+ if (!approved) {
8624
+ if (via !== void 0 || digest !== void 0) return fail("evidence-contradictory");
8625
+ } else if (via !== "terminal" && via !== "app" || digest !== binding.digest) return fail("evidence-contradictory");
8626
+ return null;
8627
+ }
8098
8628
  /** The identity the binding covers is also consistent with itself: who it belongs to, and what it is about. */
8099
8629
  function checkIdentity(record, raw, kind) {
8100
8630
  if (!isKnownChannel(raw.channel)) return fail("identity-contradiction");
@@ -8237,7 +8767,8 @@ const STATES = [
8237
8767
  const KINDS = [
8238
8768
  "send",
8239
8769
  "change",
8240
- "download"
8770
+ "download",
8771
+ "disclosure"
8241
8772
  ];
8242
8773
  const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
8243
8774
  const isString = (value) => typeof value === "string";
@@ -8294,6 +8825,59 @@ function commonShape(raw) {
8294
8825
  }
8295
8826
  return null;
8296
8827
  }
8828
+ /** The disclosure union member is deliberately closed: it cannot acquire a send-shaped field by optionality. */
8829
+ function disclosureShape(raw) {
8830
+ for (const field of [
8831
+ "approvalId",
8832
+ "state",
8833
+ "createdAt",
8834
+ "expiresAt",
8835
+ "updatedAt",
8836
+ "bindingDigest"
8837
+ ]) if (!isString(raw[field])) return "wrong-shape";
8838
+ for (const field of [
8839
+ "pendingMs",
8840
+ "approvedMs",
8841
+ "challengeAttempts"
8842
+ ]) if (typeof raw[field] !== "number") return "wrong-shape";
8843
+ for (const field of [
8844
+ "approvedDigest",
8845
+ "approvedVia",
8846
+ "approvedAt",
8847
+ "usableUntil",
8848
+ "challengeHash",
8849
+ "usedAt",
8850
+ "revokedAt",
8851
+ "expiredAt",
8852
+ "reason"
8853
+ ]) if (!optional(raw[field], isString)) return "wrong-shape";
8854
+ const disclosure = raw.disclosure;
8855
+ if (!isObject(disclosure) || !isString(disclosure.digest) || !isString(disclosure.activationIntentId) || !isString(disclosure.activationKind) || !Array.isArray(disclosure.versions) || !disclosure.versions.every((entry) => isObject(entry) && isString(entry.kind) && isString(entry.id) && typeof entry.version === "number")) return "wrong-shape";
8856
+ for (const field of [
8857
+ "channel",
8858
+ "ownerScope",
8859
+ "route",
8860
+ "inboxId",
8861
+ "inboxSub",
8862
+ "draftId",
8863
+ "draftMessageId",
8864
+ "expect",
8865
+ "sendEpoch",
8866
+ "policy",
8867
+ "requiredPolicy",
8868
+ "riskFlags",
8869
+ "approvedBindingDigest",
8870
+ "sendingAt",
8871
+ "sendingHeartbeatAt",
8872
+ "sentAt",
8873
+ "sentMessageId",
8874
+ "failedAt",
8875
+ "change",
8876
+ "download",
8877
+ "contentDigest"
8878
+ ]) if (Object.hasOwn(raw, field)) return "wrong-shape";
8879
+ return null;
8880
+ }
8297
8881
  /** The version-2 fields' types: a number where a number belongs, a string where a string does. */
8298
8882
  function v2Shape(raw) {
8299
8883
  for (const field of [
@@ -8336,6 +8920,24 @@ function decodeStored(fileId, text, config, now) {
8336
8920
  }
8337
8921
  if (!isObject(raw)) return unreadable(fileId, "wrong-shape");
8338
8922
  if (!isString(raw.approvalId)) return unreadable(fileId, "wrong-shape");
8923
+ if (raw.kind === "disclosure") {
8924
+ if (raw.digestVersion === 1) return unreadable(fileId, "wrong-shape");
8925
+ const shape = disclosureShape(raw);
8926
+ if (shape !== null) return unreadable(fileId, shape);
8927
+ const record = raw;
8928
+ const validation = validateV2(record, fileId);
8929
+ if (validation.ok) return {
8930
+ form: "v2",
8931
+ record
8932
+ };
8933
+ return {
8934
+ form: "corrupt",
8935
+ approvalId: fileId,
8936
+ reason: validation.reason,
8937
+ attribution: validation.attribution,
8938
+ safe: validation.attribution === "verified" ? safeFieldsOf(record) : null
8939
+ };
8940
+ }
8339
8941
  const shape = commonShape(raw);
8340
8942
  if (shape !== null) return unreadable(fileId, shape);
8341
8943
  if (raw.digestVersion === 1) {
@@ -8367,6 +8969,7 @@ function decodeStored(fileId, text, config, now) {
8367
8969
  };
8368
8970
  }
8369
8971
  function safeFieldsOf(record) {
8972
+ if (record.kind === "disclosure") return { kind: "disclosure" };
8370
8973
  return {
8371
8974
  kind: record.kind,
8372
8975
  channel: record.channel,
@@ -8394,9 +8997,9 @@ function asLegacy(stored) {
8394
8997
  function ownerOf(stored) {
8395
8998
  if (stored === null) return null;
8396
8999
  switch (stored.form) {
8397
- case "v2": return stored.record.inboxId;
9000
+ case "v2": return stored.record.kind === "disclosure" ? null : stored.record.inboxId;
8398
9001
  case "legacy": return stored.record.inboxId;
8399
- case "corrupt": return stored.safe?.inboxId ?? null;
9002
+ case "corrupt": return stored.safe?.kind === "disclosure" ? null : stored.safe?.inboxId ?? null;
8400
9003
  case "unreadable": return null;
8401
9004
  }
8402
9005
  }
@@ -8418,9 +9021,9 @@ function kindOf(stored) {
8418
9021
  function channelOf(stored) {
8419
9022
  if (stored === null) return null;
8420
9023
  switch (stored.form) {
8421
- case "v2": return stored.record.channel;
9024
+ case "v2": return stored.record.kind === "disclosure" ? null : stored.record.channel;
8422
9025
  case "legacy": return stored.view.channel;
8423
- case "corrupt": return stored.safe?.channel ?? null;
9026
+ case "corrupt": return stored.safe?.kind === "disclosure" ? null : stored.safe?.channel ?? null;
8424
9027
  case "unreadable": return null;
8425
9028
  }
8426
9029
  }
@@ -8569,7 +9172,7 @@ function verdictOf(stored, now) {
8569
9172
  const providerId = record.kind === "send" && record.state === "used" && typeof record.sentMessageId === "string" ? record.sentMessageId : "";
8570
9173
  return {
8571
9174
  keep: false,
8572
- owner: record.inboxId,
9175
+ owner: record.kind === "disclosure" ? "" : record.inboxId,
8573
9176
  row: {
8574
9177
  kind: record.kind,
8575
9178
  state: record.state,
@@ -9040,7 +9643,7 @@ function folding(platform) {
9040
9643
  return platform === "win32" ? (text) => text.toLowerCase() : (text) => text;
9041
9644
  }
9042
9645
  /** The package version, read from package.json at build time. */
9043
- const VERSION = "0.15.1";
9646
+ const VERSION = "0.16.0";
9044
9647
  /**
9045
9648
  * Versions as semver writes and orders them — what an update compares, and what the daily update check's reader needs
9046
9649
  * to decide whether the release it last heard of is newer than the one running.
@@ -11726,7 +12329,7 @@ function serverPruneChange(core, env, request, own) {
11726
12329
  };
11727
12330
  const { packageName, version } = ownProduct(request.channel, own) ?? {
11728
12331
  packageName: channelServer(request.channel).packageName,
11729
- version: "0.15.1"
12332
+ version: "0.16.0"
11730
12333
  };
11731
12334
  const product = {
11732
12335
  packageName,
@@ -13443,7 +14046,7 @@ function terminalUpdateHooks(core, env, options) {
13443
14046
  };
13444
14047
  }
13445
14048
  //#endregion
13446
- //#region ../core/dist/tool-arguments-jABbxaql.mjs
14049
+ //#region ../core/dist/tool-arguments-DA5ZqgoB.mjs
13447
14050
  const APPLIED = Symbol.for("agentcomms.strictToolArguments");
13448
14051
  const TARGET = "draft-2020-12";
13449
14052
  /**
@@ -23087,6 +23690,19 @@ const narrowingSchema = strictObject({
23087
23690
  flag: string().regex(/^--[a-z][a-z-]*$/, "a long flag, `--like-this`"),
23088
23691
  kind: _enum(["pin", "switch"])
23089
23692
  });
23693
+ const eventAccessSchema = discriminatedUnion("kind", [
23694
+ strictObject({
23695
+ kind: literal$1("oauth-user"),
23696
+ requiredScopes: array(line).min(1)
23697
+ }),
23698
+ strictObject({ kind: literal$1("resend-full-access") }),
23699
+ strictObject({ kind: literal$1("local-store") })
23700
+ ]);
23701
+ const eventsSchema = strictObject({
23702
+ types: array(line).min(1),
23703
+ minimumIntervalMs: number().int().positive(),
23704
+ access: eventAccessSchema
23705
+ });
23090
23706
  /**
23091
23707
  * The shapes Gmail and Slack had before there was a manifest, kept because their entries, tools and skills already
23092
23708
  * say them — and nobody else's.
@@ -23169,7 +23785,8 @@ strictObject({
23169
23785
  prefix: string().regex(/^[a-z][a-z0-9]*-$/, "a word and a hyphen: `gmail-`"),
23170
23786
  contract: line
23171
23787
  }).optional(),
23172
- approvalGrouping: _enum(["draft", "draft-revision-digest"]).optional()
23788
+ approvalGrouping: _enum(["draft", "draft-revision-digest"]).optional(),
23789
+ events: eventsSchema.optional()
23173
23790
  }).superRefine((manifest, ctx) => {
23174
23791
  const issue = (path, message) => ctx.addIssue({
23175
23792
  code: "custom",
@@ -24808,13 +25425,11 @@ var Throttle = class {
24808
25425
  * Counted per holder, a caller gives up only once one holder has kept the lock for five seconds: the stuck lock
24809
25426
  * the timeout is there for.
24810
25427
  *
24811
- * Each request takes the lock once, to reserve its slot, and does its waiting outside it, so the line is only the
24812
- * requests in flight. But the lock is polled, not queued: whoever looks first after a release takes it, and a
24813
- * request that keeps looking at the wrong moment watches later ones go ahead of it, for as long as later ones keep
24814
- * arriving — and several servers each paging through sent mail keep them arriving. So the wait also has an end
24815
- * that no hand-over moves, `LOCK_MAX_WAIT_MS`, and past it the request is refused as a busy lock rather than left
24816
- * hanging. `after` waits the same way, because a stop it gave up on recording is a 429 that every other account
24817
- * carries on into.
25428
+ * An interactive request takes the lock once to reserve its slot and waits outside it. A background event read
25429
+ * reserves nothing while it waits, so a person can always reserve the next free slot; it returns to the lock no
25430
+ * more often than once per interval. The lock is polled, not queued, so its overall wait limit still ends a caller
25431
+ * that keeps losing hand-overs to later callers. `after` waits the same way, because a stop it gave up on recording
25432
+ * is a 429 that every other account carries on into.
24818
25433
  */
24819
25434
  #lock;
24820
25435
  /** Takes no account on purpose: see above. Every throttle on a state directory is the same one. */
@@ -24834,7 +25449,8 @@ var Throttle = class {
24834
25449
  const parsed = JSON.parse(await readFile(this.path, "utf8"));
24835
25450
  return {
24836
25451
  next: Number.isFinite(parsed.next) ? Number(parsed.next) : 0,
24837
- blockedUntil: Number.isFinite(parsed.blockedUntil) ? Number(parsed.blockedUntil) : 0
25452
+ blockedUntil: Number.isFinite(parsed.blockedUntil) ? Number(parsed.blockedUntil) : 0,
25453
+ ...Number.isFinite(parsed.lastBackgroundAt) ? { lastBackgroundAt: Number(parsed.lastBackgroundAt) } : {}
24838
25454
  };
24839
25455
  } catch (error) {
24840
25456
  if (error.code === "ENOENT" || error instanceof SyntaxError) return {
@@ -24874,21 +25490,50 @@ var Throttle = class {
24874
25490
  * still waiting when that one comes back 429, and the stop Resend asked for covers it as much as anything asked
24875
25491
  * later.
24876
25492
  */
24877
- async before() {
24878
- const wait = await this.#locked(async () => {
24879
- const state = await this.#read();
24880
- this.#refuseIfHeld(state);
24881
- const now = this.#now();
24882
- const slot = Math.max(now, state.next);
24883
- await writeFileAtomic(this.path, JSON.stringify({
24884
- ...state,
24885
- next: slot + this.#interval
24886
- }));
24887
- return slot - now;
24888
- });
24889
- if (wait > 0) {
24890
- await this.#sleep(wait);
24891
- this.#refuseIfHeld(await this.#read());
25493
+ async before(priority = "interactive") {
25494
+ if (priority === "interactive") {
25495
+ const wait = await this.#locked(async () => {
25496
+ const state = await this.#read();
25497
+ this.#refuseIfHeld(state);
25498
+ const now = this.#now();
25499
+ const slot = Math.max(now, state.next);
25500
+ await writeFileAtomic(this.path, JSON.stringify({
25501
+ next: slot + this.#interval,
25502
+ blockedUntil: state.blockedUntil,
25503
+ ...state.lastBackgroundAt === void 0 ? {} : { lastBackgroundAt: state.lastBackgroundAt }
25504
+ }));
25505
+ return slot - now;
25506
+ });
25507
+ if (wait > 0) {
25508
+ await this.#sleep(wait);
25509
+ this.#refuseIfHeld(await this.#read());
25510
+ }
25511
+ return;
25512
+ }
25513
+ for (;;) {
25514
+ const decision = await this.#locked(async () => {
25515
+ const state = await this.#read();
25516
+ this.#refuseIfHeld(state);
25517
+ const now = this.#now();
25518
+ const backgroundWait = state.lastBackgroundAt === void 0 ? 0 : Math.max(0, state.lastBackgroundAt + 2 * this.#interval - now);
25519
+ if (state.next <= now && backgroundWait === 0) {
25520
+ await writeFileAtomic(this.path, JSON.stringify({
25521
+ next: now + this.#interval,
25522
+ blockedUntil: state.blockedUntil,
25523
+ lastBackgroundAt: now
25524
+ }));
25525
+ return {
25526
+ granted: true,
25527
+ wait: 0
25528
+ };
25529
+ }
25530
+ return {
25531
+ granted: false,
25532
+ wait: Math.max(state.next - now, backgroundWait, this.#interval)
25533
+ };
25534
+ });
25535
+ if (decision.granted) return;
25536
+ await this.#sleep(decision.wait);
24892
25537
  }
24893
25538
  }
24894
25539
  /**
@@ -25244,7 +25889,7 @@ async function resendRequest(transport, method, path, options = {}) {
25244
25889
  if (options.body !== void 0) headers["content-type"] = "application/json";
25245
25890
  if (options.idempotencyKey !== void 0) headers["idempotency-key"] = options.idempotencyKey;
25246
25891
  try {
25247
- await transport.throttle.before();
25892
+ await transport.throttle.before(options.throttlePriority);
25248
25893
  } catch (error) {
25249
25894
  if (writing && error instanceof CommsError) throw new CommsError(error.code, error.message, {
25250
25895
  ...error.hint === void 0 ? {} : { hint: error.hint },
@@ -28045,4 +28690,4 @@ async function revokeSendApproval(context, approvalId) {
28045
28690
  });
28046
28691
  }
28047
28692
  //#endregion
28048
- export { exemptFromUpdateGate as $, CHANGE_POLICIES as A, RESEND_API_ORIGIN as At, UPDATE_CHECK_CHILD_COMMAND as B, policyChange as C, waitCallOptions as Ct, sendPolicyOf as D, PACKAGE_NAME as Dt, removeAccountChange as E, writeResult as Et, CHANNEL_SERVERS as F, canPrompt as G, approvalsOf as H, CommsError as I, cliHandoffs as J, changeToolResult as K, EXIT_CODES as L, SEND_POLICIES as M, ROUTES as Mt, checkNewName as N, VERSION$1 as Nt, showAccount as O, RESEND_CALLER as Ot, strictToolArguments as P, escapeForDisplay as Q, PATH_OPTIONS as R, policyApprovalRefusal as S, updateToolGate as St, policyWanted as T, wholeNumber as Tt, approveChangeAtTerminal as U, agentMarker as V, approveRefusedHint as W, commandPathOf as X, colorEnabled as Y, describeFenceReport as Z, runDoctor as _, serverPruneChange as _t, revokeSendApproval as a, kindOf as at, listAccounts as b, truncateDisplay as bt, downloadReceived as c, pathOverridesFromCliOptions as ct, listReceived as d, renderInstall as dt, gatedChange as et, listScheduled as f, renderPrune as ft, showSentEmail as g, serverInstallChange as gt, showReceived as h, runUpdateCheckChild as ht, prepareSend as i, installExitStatus as it, MODES as j, RESEND_DOWNLOAD_ORIGIN as jt, ResendContext as k, REFUSED as kt, getMetrics as l, refuseUnclaimedApproval as lt, listSuppressions as m, runCommand as mt, executeSend as n, handoffSentence as nt, sendStatus as o, openCore as ot, listSentEmails as p, resolvePaths as pt, checkForUpdates as q, finishSendApproval as r, handoffSentenceToFill as rt, cancelScheduledChange as s, paint as st, beginSendApproval as t, gatedChangeAtTerminal as tt, listDomains as u, renderApprovalWait as ut, addAccountChange as v, terminalUpdateHooks as vt, policyReport as w, waitForApproval as wt, modeOf as x, updateGateAtTerminal as xt, inspectKey as y, toCommsError as yt, SEND_LOOKUP as z };
28693
+ export { checkForUpdates as $, removeAccountChange as A, updateGateAtTerminal as At, strictToolArguments as B, RESEND_DOWNLOAD_ORIGIN as Bt, inspectKey as C, runUpdateCheckChild as Ct, policyChange as D, terminalUpdateHooks as Dt, policyApprovalRefusal as E, stripInvisible as Et, CHANGE_POLICIES as F, writeResult as Ft, SEND_LOOKUP as G, CommsError as H, VERSION$1 as Ht, MODES as I, PACKAGE_NAME as It, approvalsOf as J, UPDATE_CHECK_CHILD_COMMAND as K, SEND_POLICIES as L, RESEND_CALLER as Lt, showAccount as M, waitCallOptions as Mt, resendRequest as N, waitForApproval as Nt, policyReport as O, toCommsError as Ot, ResendContext as P, wholeNumber as Pt, changeToolResult as Q, checkNewName as R, REFUSED as Rt, addAccountChange as S, runCommand as St, modeOf as T, serverPruneChange as Tt, EXIT_CODES as U, CHANNEL_SERVERS as V, ROUTES as Vt, PATH_OPTIONS as W, approveRefusedHint as X, approveChangeAtTerminal as Y, canPrompt as Z, showSentEmail as _, refuseUnclaimedApproval as _t, revokeSendApproval as a, exemptFromUpdateGate as at, readBody as b, renderPrune as bt, downloadReceived as c, handoffSentence as ct, listReceived as d, kindOf as dt, cliHandoffs as et, listScheduled as f, neutralise as ft, showReceived as g, pathOverridesFromCliOptions as gt, resendId as h, paint as ht, prepareSend as i, escapeForDisplay as it, sendPolicyOf as j, updateToolGate as jt, policyWanted as k, truncateDisplay as kt, getMetrics as l, handoffSentenceToFill as lt, listSuppressions as m, openCore as mt, executeSend as n, commandPathOf as nt, sendStatus as o, gatedChange as ot, listSentEmails as p, newBoundary as pt, agentMarker as q, finishSendApproval as r, describeFenceReport as rt, cancelScheduledChange as s, gatedChangeAtTerminal as st, beginSendApproval as t, colorEnabled as tt, listDomains as u, installExitStatus as ut, addressesOf as v, renderApprovalWait as vt, listAccounts as w, serverInstallChange as wt, runDoctor as x, resolvePaths as xt, attachmentRisks as y, renderInstall as yt, keyPermissionOf as z, RESEND_API_ORIGIN as zt };