@agentcomms/resend 0.20.0 → 0.21.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 CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { $ as colorEnabled, A as removeAccountChange, Bt as VERSION, C as inspectKey, Ct as serverPruneChange, D as policyChange, Dt as truncateDisplay, E as policyApprovalRefusal, Ft as RESEND_CALLER, G as UPDATE_CHECK_CHILD_COMMAND, H as CommsError, J as approveRefusedHint, K as agentMarker, M as showAccount, Mt as wholeNumber, Nt as writeResult, O as policyReport, Ot as updateNoticeAtTerminal, P as ResendContext, Q as cliHandoffs, R as checkNewName, S as addAccountChange, St as serverInstallChange, T as modeOf, Tt as terminalUpdateHooks, U as EXIT_CODES, V as CHANNEL_SERVERS, W as PATH_OPTIONS, Y as canPrompt, _ as showSentEmail, _t as renderInstall, a as revokeSendApproval, at as gatedChangeAtTerminal, bt as runCommand, c as downloadReceived, ct as installExitStatus, d as listReceived, et as commandPathOf, f as listScheduled, ft as openCore, g as showReceived, gt as renderApprovalWait, ht as refuseUnclaimedApproval, i as prepareSend, j as sendPolicyOf, jt as waitForApproval, k as policyWanted, l as getMetrics, lt as kindOf, m as listSuppressions, mt as pathOverridesFromCliOptions, n as executeSend, nt as escapeForDisplay, o as sendStatus, ot as handoffSentence, p as listSentEmails, pt as paint, q as approveChangeAtTerminal, r as finishSendApproval, rt as exemptFromUpdateNotice, s as cancelScheduledChange, st as handoffSentenceToFill, t as beginSendApproval, tt as describeFenceReport, u as listDomains, vt as renderPrune, w as listAccounts, x as runDoctor, xt as runUpdateCheckChild, yt as resolvePaths } from "./send-CE6wM0-w.mjs";
2
+ import { $ as colorEnabled, A as removeAccountChange, Bt as VERSION, C as inspectKey, Ct as serverPruneChange, D as policyChange, Dt as truncateDisplay, E as policyApprovalRefusal, Ft as RESEND_CALLER, G as UPDATE_CHECK_CHILD_COMMAND, H as CommsError, J as approveRefusedHint, K as agentMarker, M as showAccount, Mt as wholeNumber, Nt as writeResult, O as policyReport, Ot as updateNoticeAtTerminal, P as ResendContext, Q as cliHandoffs, R as checkNewName, S as addAccountChange, St as serverInstallChange, T as modeOf, Tt as terminalUpdateHooks, U as EXIT_CODES, V as CHANNEL_SERVERS, W as PATH_OPTIONS, Y as canPrompt, _ as showSentEmail, _t as renderInstall, a as revokeSendApproval, at as gatedChangeAtTerminal, bt as runCommand, c as downloadReceived, ct as installExitStatus, d as listReceived, et as commandPathOf, f as listScheduled, ft as openCore, g as showReceived, gt as renderApprovalWait, ht as refuseUnclaimedApproval, i as prepareSend, j as sendPolicyOf, jt as waitForApproval, k as policyWanted, l as getMetrics, lt as kindOf, m as listSuppressions, mt as pathOverridesFromCliOptions, n as executeSend, nt as escapeForDisplay, o as sendStatus, ot as handoffSentence, p as listSentEmails, pt as paint, q as approveChangeAtTerminal, r as finishSendApproval, rt as exemptFromUpdateNotice, s as cancelScheduledChange, st as handoffSentenceToFill, t as beginSendApproval, tt as describeFenceReport, u as listDomains, vt as renderPrune, w as listAccounts, x as runDoctor, xt as runUpdateCheckChild, yt as resolvePaths } from "./send-tAIE9B1y.mjs";
3
3
  import path from "node:path";
4
4
  import { createInterface } from "node:readline/promises";
5
5
  import { stripVTControlCharacters } from "node:util";
@@ -3488,7 +3488,7 @@ Exit codes: 0 ok · 1 unexpected · 10 a send or a change was refused or needs a
3488
3488
  }));
3489
3489
  const mcp = program.command("mcp").description("run the MCP server on stdio, for a coding agent to connect to").option("--account <name>", "pin the server to one account; every tool then acts on it and no other").action(async (flags) => {
3490
3490
  ran = true;
3491
- const startResendStdioServer = deps.startMcp ?? (await import("./stdio-entry-BlerMgOC.mjs")).startResendStdioServer;
3491
+ const startResendStdioServer = deps.startMcp ?? (await import("./stdio-entry-DPJKJhGj.mjs")).startResendStdioServer;
3492
3492
  const core = coreForInvocation();
3493
3493
  await startResendStdioServer({
3494
3494
  ...deps,
package/dist/index.d.mts CHANGED
@@ -4322,7 +4322,7 @@ declare namespace external_d_exports {
4322
4322
  export { $brand, $input, $output, BRAND, CompileOptions, GlobalMeta, INVALID, Infer$1 as Infer, IssueData, JSONType, NEVER, $RefinementCtx as RefinementCtx, SafeExtendShape, Schema, TimePrecision, TypeOf, ZodAny, ZodArray, ZodBase64, ZodBase64URL, ZodBigInt, ZodBigIntFormat, ZodBoolean, ZodCIDRv4, ZodCIDRv6, ZodCUID, ZodCUID2, ZodCatch, ZodCodec, ZodCoercedBigInt, ZodCoercedBoolean, ZodCoercedDate, ZodCoercedNumber, ZodCoercedString, ZodCompileAsyncError, ZodCompileUnsupportedError, ZodCreditCard, ZodCustom, ZodCustomStringFormat, ZodDate, ZodDefault, ZodDiscriminatedUnion, ZodE164, ZodEmail, ZodEmoji, ZodEnum, ZodError, ZodErrorMap, ZodExactOptional, ZodFile, ZodFirstPartySchemaTypes, ZodFirstPartyTypeKind, ZodFlattenedError, ZodFloat32, ZodFloat64, ZodFormattedError, ZodFunction, ZodGUID, ZodIBAN, ZodIPv4, ZodIPv6, ZodISODate, ZodISODateTime, ZodISODuration, ZodISOTime, ZodInstanceOf, ZodInt, ZodInt32, ZodInt64, ZodIntersection, ZodIssue, ZodIssueCode, ZodJSONSchema, ZodJSONSchemaInternals, ZodJWT, ZodKSUID, ZodLazy, ZodLiteral, ZodMAC, ZodMap, ZodNaN, ZodNanoID, ZodNever, ZodNonOptional, ZodNull, ZodNullable, ZodNumber, ZodNumberFormat, ZodObject, ZodOptional, ZodPipe, ZodPrefault, ZodPreprocess, ZodPromise, ZodRawShape$1 as ZodRawShape, ZodReadonly, ZodRealError, ZodRecord, ZodSafeParseError, ZodSafeParseResult, ZodSafeParseSuccess, ZodSchema, ZodSet, ZodStandardSchemaWithJSON, ZodString, ZodStringFormat, ZodSuccess, ZodSymbol, ZodTemplateLiteral, ZodTransform, ZodTuple, ZodType, ZodTypeAny, ZodUInt32, ZodUInt64, ZodULID, ZodURL, ZodUUID, ZodUndefined, ZodUnion, ZodUnknown, ZodVoid, ZodXID, ZodXor, _ZodBigInt, _ZodBoolean, _ZodDate, _ZodNumber, _ZodString, _ZodType, _default, _function, any, array, base64, base64url, bigint$1 as bigint, boolean$1 as boolean, _catch as catch, check, cidrv4, cidrv6, clone, codec, coerce_d_exports as coerce, compile, config, index_d_exports$1 as core, creditCard, cuid, cuid2, currencyCode, custom, date$2 as date, decode, decodeAsync, deepPartial, describe, discriminatedUnion, e164, email, emoji, encode, encodeAsync, _endsWith as endsWith, _enum as enum, exactOptional, file, flattenError, float32, float64, formatError, fromJSONSchema, _function as function, getDiscriminatedOption, getErrorMap, globalRegistry, _gt as gt, _gte as gte, guid, hash, hex, hostname, httpUrl, iban, _includes as includes, output$1 as infer, inferFlattenedErrors, inferFormattedError, input, _instanceof as instanceof, int, int32, int64, intersection, invertCodec, ipv4, ipv6, iso_d_exports as iso, json, jwt, keyof, ksuid, lazy, _length as length, literal, index_d_exports$2 as locales, looseObject, looseRecord, _lowercase as lowercase, _lt as lt, _lte as lte, mac, map, _maxLength as maxLength, _maxSize as maxSize, memoizer, meta, _mime as mime, _minLength as minLength, _minSize as minSize, _multipleOf as multipleOf, nan, nanoid, nativeEnum, _negative as negative, never, _nonnegative as nonnegative, nonoptional, _nonpositive as nonpositive, _normalize as normalize, _null as null, nullable, nullish, number$1 as number, object, optional, output, _overwrite as overwrite, parse, parseAsync, partialRecord, pipe, _positive as positive, prefault, preprocess, prettifyError, promise, _properties as properties, _property as property, readonly, record, refine, _regex as regex, regexes_d_exports as regexes, registry, safeDecode, safeDecodeAsync, safeEncode, safeEncodeAsync, safeParse, safeParseAsync, set, setErrorMap, _size as size, _slugify as slugify, _startsWith as startsWith, strictObject, string$1 as string, stringFormat, stringbool, success, superRefine, symbol, templateLiteral, toJSONSchema, _toLowerCase as toLowerCase, _toUpperCase as toUpperCase, toZod, transform, treeifyError, _trim as trim, tuple, uint32, uint64, ulid, _undefined as undefined, union, unknown, _uppercase as uppercase, url, util_d_exports as util, uuid, uuidv4, uuidv6, uuidv7, validate, validateAsync, _void as void, withParser, xid, xor };
4323
4323
  }
4324
4324
  //#endregion
4325
- //#region ../core/dist/launcher-VgihcM7Q.d.mts
4325
+ //#region ../core/dist/launcher-BuZRjGA8.d.mts
4326
4326
  //#endregion
4327
4327
  //#region src/config-hooks.d.ts
4328
4328
  /**
@@ -4415,7 +4415,7 @@ interface ResolvedPaths {
4415
4415
  */
4416
4416
  downloadsDir: string;
4417
4417
  }
4418
- type ErrorCode = "UNEXPECTED" | "APPROVAL_REQUIRED" | "APPROVAL_PENDING" | "APPROVAL_EXPIRED" | "APPROVAL_VOID" | "SEND_OUTCOME_UNKNOWN" | "SEND_REFUSED" | "POLICY_NEVER" | "RATE_CAPPED" | "UNSENDABLE_HTML" | "LOOSENING_REFUSED" | "USAGE" | "CURSOR_MISMATCH" | "BAD_DATA" | "REPLY_INVALID" | "NOT_FOUND" | "PROVIDER_UNAVAILABLE" | "SOURCE_UNAVAILABLE" | "SECRET_STORE_UNAVAILABLE" | "TRANSIENT" | "KEYCHAIN_APPROVAL_PENDING" | "LOCK_TIMEOUT" | "AUTH_REQUIRED" | "SCOPE_MISSING" | "WHATSAPP_VISIBILITY_SEAM_REQUIRED" | "CONFIG";
4418
+ type ErrorCode = "UNEXPECTED" | "APPROVAL_REQUIRED" | "APPROVAL_PENDING" | "APPROVAL_EXPIRED" | "APPROVAL_VOID" | "SEND_OUTCOME_UNKNOWN" | "SEND_REFUSED" | "POLICY_NEVER" | "RATE_CAPPED" | "UNSENDABLE_HTML" | "LOOSENING_REFUSED" | "NEEDS_NEWER_RELEASE" | "USAGE" | "CURSOR_MISMATCH" | "BAD_DATA" | "REPLY_INVALID" | "NOT_FOUND" | "PROVIDER_UNAVAILABLE" | "SOURCE_UNAVAILABLE" | "SECRET_STORE_UNAVAILABLE" | "TRANSIENT" | "KEYCHAIN_APPROVAL_PENDING" | "LOCK_TIMEOUT" | "AUTH_REQUIRED" | "SCOPE_MISSING" | "WHATSAPP_VISIBILITY_SEAM_REQUIRED" | "CONFIG";
4419
4419
  interface CommsErrorOptions {
4420
4420
  hint?: string;
4421
4421
  details?: Record<string, unknown>;
@@ -4897,6 +4897,18 @@ interface OrganisationRecord {
4897
4897
  } | undefined;
4898
4898
  /** The organisation's update caps, from the file beside its profile; absent when it names none. */
4899
4899
  updates?: OrganisationUpdates | undefined;
4900
+ /**
4901
+ * The entries of the profile's `extensions` this release did not know and skipped, being optional (design 2026-10-19,
4902
+ * X3): the release that first understands each, and the SHA-256 of the entry's canonical JSON — as
4903
+ * `Record<string, OrganisationIgnoredExtension>`. Kept in step with the profile. Absent when none was skipped.
4904
+ *
4905
+ * Held as the file holds it, whatever its shape, and read only through `ignoredListOf`, which keeps the well-formed
4906
+ * entries and names the rest: 0.21.0 is the first release to read it, so a shape a later release or a hand gave it
4907
+ * must never make the whole configuration unreadable, nor be dropped by an unrelated write. Losing it is safe: a
4908
+ * later release that understands an entry judges applying it by its classifier whether or not the list names it —
4909
+ * a tightening at once, anything else approved first, never a repair of drift (X4).
4910
+ */
4911
+ ignoredExtensions?: unknown;
4900
4912
  }
4901
4913
  interface ConfigV2 extends ConfigBody {
4902
4914
  version: 2;
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { Bt as VERSION, Ft as RESEND_CALLER, H as CommsError, It as REFUSED, Lt as RESEND_API_ORIGIN, N as resendRequest, P as ResendContext, Pt as PACKAGE_NAME, Rt as RESEND_DOWNLOAD_ORIGIN, b as readBody, dt as newBoundary, h as resendId, ut as neutralise, v as addressesOf, wt as stripInvisible, y as attachmentRisks, z as keyPermissionOf, zt as ROUTES } from "./send-CE6wM0-w.mjs";
2
- import { t as createResendMcpServer } from "./server-C1sSuZ8K.mjs";
1
+ import { Bt as VERSION, Ft as RESEND_CALLER, H as CommsError, It as REFUSED, Lt as RESEND_API_ORIGIN, N as resendRequest, P as ResendContext, Pt as PACKAGE_NAME, Rt as RESEND_DOWNLOAD_ORIGIN, b as readBody, dt as newBoundary, h as resendId, ut as neutralise, v as addressesOf, wt as stripInvisible, y as attachmentRisks, z as keyPermissionOf, zt as ROUTES } from "./send-tAIE9B1y.mjs";
2
+ import { t as createResendMcpServer } from "./server-CQ9RE8L7.mjs";
3
3
  //#region src/operations/events.ts
4
4
  const BODY_LIMIT = 2e4;
5
5
  const STATUS = /* @__PURE__ */ new Set([
@@ -1,4 +1,4 @@
1
- import { S as union, b as strictObject, c as boolean, g as object, 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";
1
+ import { C as unknown, S as union, b as strictObject, c as boolean, g as object, 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, posix, relative, resolve, sep, win32 } 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$2 = "0.20.0";
15
+ const VERSION$2 = "0.21.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-N_-uYKZQ.mjs
204
+ //#region ../core/dist/update-check-CrKGPUCv.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
@@ -279,6 +279,11 @@ const ERROR_REGISTRY = {
279
279
  retryable: false,
280
280
  summary: "a safety setting can only be loosened by a person"
281
281
  },
282
+ NEEDS_NEWER_RELEASE: {
283
+ exit: EXIT_CODES.UNEXPECTED,
284
+ retryable: false,
285
+ summary: "this needs a later agent-communications; update first, then run it again"
286
+ },
282
287
  USAGE: {
283
288
  exit: EXIT_CODES.USAGE,
284
289
  retryable: false,
@@ -3973,12 +3978,32 @@ function emptyReceipt() {
3973
3978
  };
3974
3979
  }
3975
3980
  async function readReceipt(context) {
3976
- let parsed;
3981
+ let text;
3982
+ try {
3983
+ text = await readFile(receiptPath(context), "utf8");
3984
+ } catch (error) {
3985
+ return error.code === "ENOENT" ? emptyReceipt() : null;
3986
+ }
3987
+ return receiptFrom(text);
3988
+ }
3989
+ /** `readReceipt`, for a reader that cannot wait: the command locator. */
3990
+ function readReceiptSync(context) {
3991
+ let text;
3977
3992
  try {
3978
- parsed = JSON.parse(await readFile(receiptPath(context), "utf8"));
3993
+ text = readFileSync(receiptPath(context), "utf8");
3979
3994
  } catch (error) {
3980
3995
  return error.code === "ENOENT" ? emptyReceipt() : null;
3981
3996
  }
3997
+ return receiptFrom(text);
3998
+ }
3999
+ /** The receipt a file holds, or null when it is not one this installation writes. */
4000
+ function receiptFrom(text) {
4001
+ let parsed;
4002
+ try {
4003
+ parsed = JSON.parse(text);
4004
+ } catch {
4005
+ return null;
4006
+ }
3982
4007
  if (typeof parsed !== "object" || parsed === null || parsed.version !== RECEIPT_VERSION || typeof parsed.shims !== "object" || parsed.shims === null) return null;
3983
4008
  const shims = {};
3984
4009
  for (const [binary, value] of Object.entries(parsed.shims)) {
@@ -4140,6 +4165,31 @@ function directoryOnPath(context, directory) {
4140
4165
  function pathNotice(directory) {
4141
4166
  return `The shim directory ${directory} is not on PATH. For this shell, run: export PATH=${posixWord(directory)}:"$PATH". No shell profile was edited.`;
4142
4167
  }
4168
+ /**
4169
+ * The managed shim for `binary` that starts `entry`, for a reader that cannot wait — the command locator, which names
4170
+ * it in place of a versioned runtime's entry when this installation has no stable launcher for it (0.21): its path as a
4171
+ * person puts it on PATH, or null.
4172
+ *
4173
+ * Only a shim this installation's receipt records at that very place, whose file is still the one it wrote, unchanged,
4174
+ * and which starts `entry` — the same file — with a Node that is there: what it runs now is exactly what the command it
4175
+ * stands in for would run. Never on a platform that has no shims, and never a file anyone else wrote.
4176
+ */
4177
+ function managedShimStarting(context, binary, entry) {
4178
+ if (!managedShimsSupported(context) || !isSafeBinary(binary)) return null;
4179
+ try {
4180
+ const publicPath = managedShimPath(context, binary);
4181
+ const path = join(realpathSync(dirname(publicPath)), basename(publicPath));
4182
+ const recorded = readReceiptSync(context)?.shims[binary];
4183
+ if (recorded === void 0 || recorded.path !== path) return null;
4184
+ if (!lstatSync(path).isFile() || sha256(readFileSync(path, "utf8")) !== recorded.digest) return null;
4185
+ if (realpathSync(recorded.entry) !== realpathSync(entry)) return null;
4186
+ accessSync(recorded.node, constants$1.X_OK);
4187
+ accessSync(path, constants$1.X_OK);
4188
+ return publicPath;
4189
+ } catch {
4190
+ return null;
4191
+ }
4192
+ }
4143
4193
  /** Create or repoint one shim, but only while its target is free or still matches this installation's receipt. */
4144
4194
  async function writeManagedShim(context, spec) {
4145
4195
  checkedBinary(spec.binary);
@@ -4266,6 +4316,114 @@ async function removeManagedShimsForRuntimes(context, runtimes, options = {}) {
4266
4316
  };
4267
4317
  });
4268
4318
  }
4319
+ /**
4320
+ * Versions as semver writes and orders them — what an update compares, and what the daily update check's reader needs
4321
+ * to decide whether the release it last heard of is newer than the one running.
4322
+ *
4323
+ * Here rather than in `npm.ts`, which also talks to the registry: the reader of `update-check.json` is imported by
4324
+ * every server, WhatsApp's included, and must carry no network code at all (design 2026-09-28 §1). `npm.ts` re-exports
4325
+ * all of it, so nothing that imported these from there changes.
4326
+ */
4327
+ /**
4328
+ * A version, exactly as semver writes one: `1.2.3`, `1.2.3-rc.1`, `1.2.3+build`.
4329
+ *
4330
+ * Anything the registry answers is checked against this before it is used, because a version goes into a directory
4331
+ * name (`runtime/<version>-gmail`), a sentence a person approves, and an argument to `npm install`. A registry — or a
4332
+ * mirror somebody configured — that answered `../x` or `1.0.0 && …` would otherwise put that in all three.
4333
+ */
4334
+ const VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
4335
+ function isVersion(value) {
4336
+ return typeof value === "string" && VERSION_PATTERN.test(value);
4337
+ }
4338
+ /** Two numeric strings with no leading zeros, compared as numbers of any size. */
4339
+ function compareNumeric(a, b) {
4340
+ if (a.length !== b.length) return a.length < b.length ? -1 : 1;
4341
+ return a < b ? -1 : a > b ? 1 : 0;
4342
+ }
4343
+ /**
4344
+ * Semver precedence: -1, 0 or 1, or null when either is not a version.
4345
+ *
4346
+ * Numbers compare as numbers (`0.10.0` is after `0.9.9`), a prerelease comes before its release, and build metadata
4347
+ * counts for nothing. Written out rather than taken from a dependency: it is twenty lines, and the core installs as
4348
+ * few packages as it can.
4349
+ */
4350
+ function compareVersions(a, b) {
4351
+ const left = VERSION_PATTERN.exec(a);
4352
+ const right = VERSION_PATTERN.exec(b);
4353
+ if (!left || !right) return null;
4354
+ for (const index of [
4355
+ 1,
4356
+ 2,
4357
+ 3
4358
+ ]) {
4359
+ const order = compareNumeric(left[index] ?? "0", right[index] ?? "0");
4360
+ if (order !== 0) return order < 0 ? -1 : 1;
4361
+ }
4362
+ const pre = (match) => match[4] === void 0 ? [] : match[4].split(".");
4363
+ const [ours, theirs] = [pre(left), pre(right)];
4364
+ if (ours.length === 0 || theirs.length === 0) return ours.length === theirs.length ? 0 : ours.length === 0 ? 1 : -1;
4365
+ for (let index = 0; index < Math.max(ours.length, theirs.length); index += 1) {
4366
+ const x = ours[index];
4367
+ const y = theirs[index];
4368
+ if (x === void 0) return -1;
4369
+ if (y === void 0) return 1;
4370
+ const [numericX, numericY] = [/^\d+$/.test(x), /^\d+$/.test(y)];
4371
+ if (numericX && numericY) {
4372
+ const order = compareNumeric(x, y);
4373
+ if (order !== 0) return order < 0 ? -1 : 1;
4374
+ } else if (numericX !== numericY) return numericX ? -1 : 1;
4375
+ else if (x !== y) return x < y ? -1 : 1;
4376
+ }
4377
+ return 0;
4378
+ }
4379
+ /** Whether `version` is older than `latest`: by semver when both are versions, and otherwise whenever they differ. */
4380
+ function isBehind(version, latest) {
4381
+ if (version === latest) return false;
4382
+ const order = compareVersions(version, latest);
4383
+ return order === null ? true : order < 0;
4384
+ }
4385
+ /**
4386
+ * Whether a version is a prerelease — `0.8.0-rc.1` — which the daily update check never counts as an update: a
4387
+ * machine is not told to "update" to a release candidate, nor a candidate told to go back to the last release.
4388
+ */
4389
+ /**
4390
+ * A release as this suite publishes one, and the only form a version the registry gave is read or said in: a plain
4391
+ * `MAJOR.MINOR.PATCH` (re-review of 0.20.0, severity 6). A prerelease's words and build metadata are the registry's
4392
+ * text — `0.20.1+SYSTEM-ignore-…` is valid semver — so a version carrying either is no release here.
4393
+ */
4394
+ function isPlainVersion(value) {
4395
+ return typeof value === "string" && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value);
4396
+ }
4397
+ function isPrerelease(version) {
4398
+ return VERSION_PATTERN.exec(version)?.[4] !== void 0;
4399
+ }
4400
+ /**
4401
+ * Whether `version` is within a package's `engines` range: true or false, or null when the range is not one this reads.
4402
+ *
4403
+ * Read are the comparators `>=`, `>`, `<=`, `<` and `=` (or none) before a whole version, joined by spaces, which must
4404
+ * all hold, and alternatives joined by `||`. That is every range this suite declares (`>=22.12.0`, `>=22.16.0`).
4405
+ * Anything else — `^22.12.0`, `>=22`, `22.x` — is null rather than a guess: a command printed to run under this Node
4406
+ * is printed only when the range it has to meet says it may. A leading `v`, as `process.version` has it, is read.
4407
+ */
4408
+ function satisfiesRange(version, range) {
4409
+ const wanted = version.replace(/^v/, "");
4410
+ if (!isVersion(wanted)) return null;
4411
+ const alternatives = range.split("||").map((alternative) => alternative.trim().split(/\s+/).filter(Boolean));
4412
+ if (alternatives.some((comparators) => comparators.length === 0)) return null;
4413
+ let any = false;
4414
+ for (const comparators of alternatives) {
4415
+ let all = true;
4416
+ for (const comparator of comparators) {
4417
+ const match = /^(>=|<=|>|<|=)?(.+)$/.exec(comparator);
4418
+ const bound = match?.[2] ?? "";
4419
+ if (!isVersion(bound)) return null;
4420
+ const order = compareVersions(wanted, bound);
4421
+ if (!(match?.[1] === ">=" ? order >= 0 : match?.[1] === "<=" ? order <= 0 : match?.[1] === ">" ? order > 0 : match?.[1] === "<" ? order < 0 : order === 0)) all = false;
4422
+ }
4423
+ if (all) any = true;
4424
+ }
4425
+ return any;
4426
+ }
4269
4427
  /** The install options that pin a server to one account: the generic one, and Gmail's and Slack's own names. */
4270
4428
  const PIN_OPTIONS = Object.freeze([
4271
4429
  "account",
@@ -5484,6 +5642,10 @@ async function listManagedRuntimes(dataDir, packageName) {
5484
5642
  * - only directories directly inside `<data>/runtime`, named like a version, that hold this product's package —
5485
5643
  * never a symbolic link, never another product's runtime, never anything else in that directory;
5486
5644
  * - never this release's own;
5645
+ * - never one newer than this release, or than the runtime its stable launcher starts: what an update is installing,
5646
+ * or checking before it switches, is newer than anything a pointer names yet. The update lock keeps a person's
5647
+ * prune from running beside an update (`serverPruneChange`); this keeps that runtime even when no lock is held —
5648
+ * an update interrupted half-way, a prune that is not a person's;
5487
5649
  * - never one an entry names in a client config `scanRegisteredServers` reads, in any scope — the ones this
5488
5650
  * environment names, and every one an install recorded registering into, wherever `CLAUDE_CONFIG_DIR` or
5489
5651
  * `CODEX_HOME` pointed then. When one of those files is there and cannot be read, nothing is removed at all: a
@@ -5561,10 +5723,18 @@ async function pruneManagedRuntimes(context, product, options = {}) {
5561
5723
  return null;
5562
5724
  };
5563
5725
  const current = managedRuntimeDir(context.core.paths.dataDir, product.packageName, product.version);
5726
+ const starts = pointer === void 0 ? null : launcherRuntimeVersion(pointer.current, name);
5727
+ /** Why a runtime newer than this release, or than what the launcher starts, stays; null for any other. */
5728
+ const newer = (version) => {
5729
+ const installing = "an update may be installing it, or checking it before it switches";
5730
+ if (compareVersions(version, product.version) === 1) return `newer than this release, ${product.version}: ${installing}`;
5731
+ if (starts !== null && compareVersions(version, starts) === 1) return `newer than ${starts}, which the stable launcher starts: ${installing}`;
5732
+ return null;
5733
+ };
5564
5734
  for (const { path, version, aliases } of candidates) {
5565
5735
  const owner = registered.find((server) => [server.command, ...server.args].some((part) => mentions(aliases, part)));
5566
5736
  const printed = options.includePrinted ? void 0 : handedOut.find((record) => !record.applied && mentions(aliases, record.runtime));
5567
- const reason = path === current ? "this release" : pointed(aliases) ?? (owner ? `registered with ${owner.client} as "${owner.name}"${known.has(owner.path) ? "" : ` in ${owner.path}`}` : printed ? `printed for ${printed.client} as "${printed.name}" on ${printed.at.slice(0, 10)}, and where that entry went cannot be read; once it is gone, \`mcp prune --include-printed\` removes this` : processes.some((line) => mentions(aliases, line)) ? "a running process uses it" : null);
5737
+ const reason = path === current ? "this release" : pointed(aliases) ?? newer(version) ?? (owner ? `registered with ${owner.client} as "${owner.name}"${known.has(owner.path) ? "" : ` in ${owner.path}`}` : printed ? `printed for ${printed.client} as "${printed.name}" on ${printed.at.slice(0, 10)}, and where that entry went cannot be read; once it is gone, \`mcp prune --include-printed\` removes this` : processes.some((line) => mentions(aliases, line)) ? "a running process uses it" : null);
5568
5738
  if (reason) {
5569
5739
  result.kept.push({
5570
5740
  path,
@@ -6485,7 +6655,8 @@ const organisationRecordSchema = looseObject({
6485
6655
  max: string().min(1).optional(),
6486
6656
  background: boolean().optional(),
6487
6657
  sha256: string().min(1)
6488
- }).optional()
6658
+ }).optional(),
6659
+ ignoredExtensions: unknown().optional()
6489
6660
  });
6490
6661
  const formerNameSchema = looseObject({
6491
6662
  name: nameSchema,
@@ -6603,6 +6774,25 @@ function schemaFor(version, naming = 2) {
6603
6774
  function schemaOf(config) {
6604
6775
  return schemaFor(config.version, config.version === 3 ? config.naming : void 0);
6605
6776
  }
6777
+ function connectedAccounts(config) {
6778
+ const mail = Object.entries(config.inboxes).map(([alias, inbox]) => ({
6779
+ kind: "mail",
6780
+ alias,
6781
+ id: inbox.id,
6782
+ platform: inbox.provider,
6783
+ secretRef: inbox.secretRef,
6784
+ inbox
6785
+ }));
6786
+ const channel = Object.entries(config.accounts).map(([alias, account]) => ({
6787
+ kind: "channel",
6788
+ alias,
6789
+ id: account.id,
6790
+ platform: account.platform,
6791
+ secretRef: account.secretRef,
6792
+ account
6793
+ }));
6794
+ return [...mail, ...channel].sort((a, b) => a.alias.localeCompare(b.alias));
6795
+ }
6606
6796
  /** The secret backend in use: the recorded one, or the keychain before anything has been stored. */
6607
6797
  function secretsStoreOf(config) {
6608
6798
  return config.secrets?.store ?? "keychain";
@@ -7303,7 +7493,7 @@ function classifyChange(before, after) {
7303
7493
  if (before.secrets === void 0 && after.secrets !== void 0 && after.secrets.store !== "keychain") {
7304
7494
  if (holdsSecrets(before)) loosen("secrets.store", secretsStoreOf(before), after.secrets.store);
7305
7495
  }
7306
- for (const loosening of classifyAgentLabelChange(agentLabelOf(before), agentLabelOf(after))) loosen(loosening.path, labelSettingValue(loosening, loosening.was), labelSettingValue(loosening, loosening.now));
7496
+ for (const loosening of classifyAgentLabelChange(agentLabelOf(before), agentLabelOf(after))) loosen(loosening.path, labelSettingValue(loosening, loosening.was, before), labelSettingValue(loosening, loosening.now, after));
7307
7497
  return {
7308
7498
  loosened: changes.map((change) => change.path),
7309
7499
  changes
@@ -7319,13 +7509,21 @@ function agentLabelRuleText(rule) {
7319
7509
  return `${rule.account}${where}${kinds}: ${rule.as}`;
7320
7510
  }
7321
7511
  /**
7322
- * A label setting's value as a loosening carries it: the rules in words, the icons and the Slack styles as they are, the
7323
- * rest as text.
7512
+ * A label setting's value as a loosening carries it: the rules in words, the icons and the Slack styles by the name of
7513
+ * the workspace each is for, in `config` — the side the value was read from — and the rest as text.
7514
+ *
7515
+ * Icons and styles are stored by the account's id, so they follow a rename; a preview names the workspace as the person
7516
+ * knows it, `acme/slack`, never its id, which they have never seen (0.21). The ids are still what an approval binds:
7517
+ * `changedSettings` records the stored values as they are. An id that names no connected account — one removed since —
7518
+ * has no other name, and keeps its id.
7324
7519
  */
7325
- function labelSettingValue(loosening, value) {
7520
+ function labelSettingValue(loosening, value, config) {
7326
7521
  if (value === void 0 || value === null) return void 0;
7327
7522
  if (loosening.path === "agentLabel.rules") return value.map(agentLabelRuleText);
7328
- if (loosening.path === "agentLabel.slackIcons" || loosening.path === "agentLabel.slackStyles") return { ...value };
7523
+ if (loosening.path === "agentLabel.slackIcons" || loosening.path === "agentLabel.slackStyles") {
7524
+ const accounts = connectedAccounts(config);
7525
+ return Object.fromEntries(Object.entries(value).map(([id, entry]) => [accounts.find((account) => account.id === id)?.alias ?? id, entry]));
7526
+ }
7329
7527
  return String(value);
7330
7528
  }
7331
7529
  /** The label settings a change can write, as the file holds them — raw, as `changedSettings` records every setting. */
@@ -10907,115 +11105,7 @@ function folding(platform) {
10907
11105
  return platform === "win32" ? (text) => text.toLowerCase() : (text) => text;
10908
11106
  }
10909
11107
  /** The package version, read from package.json at build time. */
10910
- const VERSION = "0.20.0";
10911
- /**
10912
- * Versions as semver writes and orders them — what an update compares, and what the daily update check's reader needs
10913
- * to decide whether the release it last heard of is newer than the one running.
10914
- *
10915
- * Here rather than in `npm.ts`, which also talks to the registry: the reader of `update-check.json` is imported by
10916
- * every server, WhatsApp's included, and must carry no network code at all (design 2026-09-28 §1). `npm.ts` re-exports
10917
- * all of it, so nothing that imported these from there changes.
10918
- */
10919
- /**
10920
- * A version, exactly as semver writes one: `1.2.3`, `1.2.3-rc.1`, `1.2.3+build`.
10921
- *
10922
- * Anything the registry answers is checked against this before it is used, because a version goes into a directory
10923
- * name (`runtime/<version>-gmail`), a sentence a person approves, and an argument to `npm install`. A registry — or a
10924
- * mirror somebody configured — that answered `../x` or `1.0.0 && …` would otherwise put that in all three.
10925
- */
10926
- const VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
10927
- function isVersion(value) {
10928
- return typeof value === "string" && VERSION_PATTERN.test(value);
10929
- }
10930
- /** Two numeric strings with no leading zeros, compared as numbers of any size. */
10931
- function compareNumeric(a, b) {
10932
- if (a.length !== b.length) return a.length < b.length ? -1 : 1;
10933
- return a < b ? -1 : a > b ? 1 : 0;
10934
- }
10935
- /**
10936
- * Semver precedence: -1, 0 or 1, or null when either is not a version.
10937
- *
10938
- * Numbers compare as numbers (`0.10.0` is after `0.9.9`), a prerelease comes before its release, and build metadata
10939
- * counts for nothing. Written out rather than taken from a dependency: it is twenty lines, and the core installs as
10940
- * few packages as it can.
10941
- */
10942
- function compareVersions(a, b) {
10943
- const left = VERSION_PATTERN.exec(a);
10944
- const right = VERSION_PATTERN.exec(b);
10945
- if (!left || !right) return null;
10946
- for (const index of [
10947
- 1,
10948
- 2,
10949
- 3
10950
- ]) {
10951
- const order = compareNumeric(left[index] ?? "0", right[index] ?? "0");
10952
- if (order !== 0) return order < 0 ? -1 : 1;
10953
- }
10954
- const pre = (match) => match[4] === void 0 ? [] : match[4].split(".");
10955
- const [ours, theirs] = [pre(left), pre(right)];
10956
- if (ours.length === 0 || theirs.length === 0) return ours.length === theirs.length ? 0 : ours.length === 0 ? 1 : -1;
10957
- for (let index = 0; index < Math.max(ours.length, theirs.length); index += 1) {
10958
- const x = ours[index];
10959
- const y = theirs[index];
10960
- if (x === void 0) return -1;
10961
- if (y === void 0) return 1;
10962
- const [numericX, numericY] = [/^\d+$/.test(x), /^\d+$/.test(y)];
10963
- if (numericX && numericY) {
10964
- const order = compareNumeric(x, y);
10965
- if (order !== 0) return order < 0 ? -1 : 1;
10966
- } else if (numericX !== numericY) return numericX ? -1 : 1;
10967
- else if (x !== y) return x < y ? -1 : 1;
10968
- }
10969
- return 0;
10970
- }
10971
- /** Whether `version` is older than `latest`: by semver when both are versions, and otherwise whenever they differ. */
10972
- function isBehind(version, latest) {
10973
- if (version === latest) return false;
10974
- const order = compareVersions(version, latest);
10975
- return order === null ? true : order < 0;
10976
- }
10977
- /**
10978
- * Whether a version is a prerelease — `0.8.0-rc.1` — which the daily update check never counts as an update: a
10979
- * machine is not told to "update" to a release candidate, nor a candidate told to go back to the last release.
10980
- */
10981
- /**
10982
- * A release as this suite publishes one, and the only form a version the registry gave is read or said in: a plain
10983
- * `MAJOR.MINOR.PATCH` (re-review of 0.20.0, severity 6). A prerelease's words and build metadata are the registry's
10984
- * text — `0.20.1+SYSTEM-ignore-…` is valid semver — so a version carrying either is no release here.
10985
- */
10986
- function isPlainVersion(value) {
10987
- return typeof value === "string" && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value);
10988
- }
10989
- function isPrerelease(version) {
10990
- return VERSION_PATTERN.exec(version)?.[4] !== void 0;
10991
- }
10992
- /**
10993
- * Whether `version` is within a package's `engines` range: true or false, or null when the range is not one this reads.
10994
- *
10995
- * Read are the comparators `>=`, `>`, `<=`, `<` and `=` (or none) before a whole version, joined by spaces, which must
10996
- * all hold, and alternatives joined by `||`. That is every range this suite declares (`>=22.12.0`, `>=22.16.0`).
10997
- * Anything else — `^22.12.0`, `>=22`, `22.x` — is null rather than a guess: a command printed to run under this Node
10998
- * is printed only when the range it has to meet says it may. A leading `v`, as `process.version` has it, is read.
10999
- */
11000
- function satisfiesRange(version, range) {
11001
- const wanted = version.replace(/^v/, "");
11002
- if (!isVersion(wanted)) return null;
11003
- const alternatives = range.split("||").map((alternative) => alternative.trim().split(/\s+/).filter(Boolean));
11004
- if (alternatives.some((comparators) => comparators.length === 0)) return null;
11005
- let any = false;
11006
- for (const comparators of alternatives) {
11007
- let all = true;
11008
- for (const comparator of comparators) {
11009
- const match = /^(>=|<=|>|<|=)?(.+)$/.exec(comparator);
11010
- const bound = match?.[2] ?? "";
11011
- if (!isVersion(bound)) return null;
11012
- const order = compareVersions(wanted, bound);
11013
- if (!(match?.[1] === ">=" ? order >= 0 : match?.[1] === "<=" ? order <= 0 : match?.[1] === ">" ? order > 0 : match?.[1] === "<" ? order < 0 : order === 0)) all = false;
11014
- }
11015
- if (all) any = true;
11016
- }
11017
- return any;
11018
- }
11108
+ const VERSION = "0.21.0";
11019
11109
  /**
11020
11110
  * 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).
11021
11111
  *
@@ -11146,7 +11236,13 @@ function locateCliCommand(request, runtime = {
11146
11236
  } catch (error) {
11147
11237
  return fail("arguments", error instanceof Error ? error.message : String(error));
11148
11238
  }
11149
- const build = (found) => buildCommand(target, found, words, runtime, callerSource, platform, program);
11239
+ const build = (found) => stableWayIn(buildCommand(target, found, words, runtime, callerSource, platform, program), found, target, {
11240
+ paths: request.paths,
11241
+ env: request.env,
11242
+ words,
11243
+ platform,
11244
+ program
11245
+ });
11150
11246
  const located = (built, found, direction) => Object.freeze({
11151
11247
  ok: true,
11152
11248
  command: built.command,
@@ -11332,6 +11428,70 @@ function buildCommand(target, found, words, runtime, callerSource, platform, pro
11332
11428
  };
11333
11429
  }
11334
11430
  /**
11431
+ * A managed runtime's command, as its stable way in (0.21). A command naming `<data>/runtime/0.20.0-core/…/cli.mjs`
11432
+ * stops working once that runtime is pruned — and the background job prunes old runtimes after each update — so a
11433
+ * command printed from a managed runtime of this installation names what stays put instead:
11434
+ *
11435
+ * - **its stable launcher**, `<data>/launch/<name>` (`launcherPaths`), when it is there and its pointer names a runtime
11436
+ * holding this package at exactly this release, whose CLI can start: the launcher, then the words, as a registration
11437
+ * starts it — on Windows, where the launcher is a script Node runs, this Node first;
11438
+ * - else **its managed CLI shim**, where shims are offered, when this installation's receipt records one that is
11439
+ * unchanged and starts this very entry (`managedShimStarting`): a release before the launcher wrote such shims, and
11440
+ * an update rewrites them;
11441
+ * - else the runtime's entry, as before.
11442
+ *
11443
+ * Only for a package inside `<data>/runtime` of the folders this command pins: a checkout, an npx cache, a global
11444
+ * install or another data folder's runtime is named as it always was. Checked when the command is printed (D6), as the
11445
+ * entry is: a launcher switched later runs what it is then switched to, as every registration that starts it does.
11446
+ */
11447
+ function stableWayIn(built, found, target, printing) {
11448
+ if (!built.ok || found.source) return built;
11449
+ const { paths, words, platform, program } = printing;
11450
+ const runtimes = realpathOfExisting(join(paths.dataDir, "runtime"));
11451
+ if (runtimes === null || !isWithin$1(runtimes, found.root) || resolve(runtimes) === resolve(found.root)) return built;
11452
+ const launcher = stableLauncher(paths.dataDir, target.packageName, found.manifest.version, platform);
11453
+ if (launcher !== null) return {
11454
+ ok: true,
11455
+ command: new PrintedCommand(GATE, platform === "win32" ? [
11456
+ program,
11457
+ launcher,
11458
+ ...words
11459
+ ] : [launcher, ...words], platform, realpathOfExisting(launcher) ?? launcher),
11460
+ entryKind: "launcher"
11461
+ };
11462
+ const shim = managedShimStarting({
11463
+ env: printing.env ?? process.env,
11464
+ dataDir: paths.dataDir,
11465
+ platform
11466
+ }, target.manifest.binary, built.command.entry);
11467
+ if (shim === null) return built;
11468
+ return {
11469
+ ok: true,
11470
+ command: new PrintedCommand(GATE, [shim, ...words], platform, realpathOfExisting(shim) ?? shim),
11471
+ entryKind: "shim"
11472
+ };
11473
+ }
11474
+ /**
11475
+ * This installation's stable launcher for `packageName`, when it starts that package at exactly `version`: the file is
11476
+ * there (executable, where it is a shell script), its pointer is one the launcher reads, and the runtime it names holds
11477
+ * the package at that version with a CLI the launcher can start. Null otherwise — a pointer that cannot be read too.
11478
+ */
11479
+ function stableLauncher(dataDir, packageName, version, platform) {
11480
+ if (typeof version !== "string") return null;
11481
+ try {
11482
+ const name = launcherName(packageName);
11483
+ const { launcher } = launcherPaths(dataDir, name);
11484
+ if (!statSync(launcher).isFile()) return null;
11485
+ accessSync(launcher, platform === "win32" ? constants$1.R_OK : constants$1.X_OK);
11486
+ const pointer = readPointerSync(dataDir, name);
11487
+ if (pointer === void 0 || !launcherCanStartSync(pointer.current, name)) return null;
11488
+ const started = readPackageManifest(join(pointer.current, "node_modules", ...packageName.split("/")));
11489
+ return started?.name === packageName && started.version === version ? launcher : null;
11490
+ } catch {
11491
+ return null;
11492
+ }
11493
+ }
11494
+ /**
11335
11495
  * The command a package declares for itself: a channel's in its `agentcomms` field, a service's in `agentcommsPackage`
11336
11496
  * (D14). A library declares none, and no package declares both: the registry refuses one that does.
11337
11497
  */
@@ -11913,8 +12073,30 @@ function renderInstall(result, color) {
11913
12073
  for (const warning of result.warnings) lines.push("", paint(color, "red", warning));
11914
12074
  return lines.join("\n");
11915
12075
  }
11916
- /** What `mcp prune` removed and kept, and why each one it kept is still needed. */
12076
+ /**
12077
+ * What `mcp prune` removed and kept, and why each one it kept is still needed. A prune of every managed server lists
12078
+ * each server that had a runtime under its own name, in the registry's order.
12079
+ */
11917
12080
  function renderPrune(result, color) {
12081
+ if ("servers" in result) {
12082
+ const groups = result.servers.map((part) => ({
12083
+ label: part.label,
12084
+ lines: pruneLines(part, color)
12085
+ })).filter((group) => group.lines.length > 0).map((group) => [`${group.label}:`, ...group.lines.map((line) => ` ${line}`)].join("\n"));
12086
+ if (groups.length === 0) return `No managed runtimes to remove in ${result.runtimeDir}.`;
12087
+ const unchecked = (result.refusedServers ?? []).map(({ label, refused }) => `${label} (${refused})`);
12088
+ if (unchecked.length > 0) {
12089
+ const its = unchecked.length === 1 ? "its" : "their";
12090
+ groups.push(paint(color, "yellow", `Not checked, so none of ${its} runtimes was removed: ${unchecked.join("; ")}.`));
12091
+ }
12092
+ return groups.join("\n");
12093
+ }
12094
+ const lines = pruneLines(result, color);
12095
+ if (lines.length === 0) lines.push(`No managed runtimes to remove in ${result.runtimeDir}.`);
12096
+ return lines.join("\n");
12097
+ }
12098
+ /** One server's prune, a line a runtime or shim; none when it had nothing to look at. */
12099
+ function pruneLines(result, color) {
11918
12100
  const lines = [];
11919
12101
  if (result.refused) lines.push(paint(color, "yellow", `Nothing removed: ${result.refused}.`));
11920
12102
  const verb = result.dryRun ? "Would remove" : "Removed";
@@ -11922,8 +12104,7 @@ function renderPrune(result, color) {
11922
12104
  for (const path of result.shims.removed) lines.push(paint(color, "green", `${verb} CLI shim: ${path}`));
11923
12105
  for (const path of result.shims.kept) lines.push(`Kept foreign CLI shim: ${path}`);
11924
12106
  for (const item of result.kept) lines.push(`Kept ${item.version} (${item.reason}): ${item.path}`);
11925
- if (lines.length === 0) lines.push(`No managed runtimes to remove in ${result.runtimeDir}.`);
11926
- return lines.join("\n");
12107
+ return lines;
11927
12108
  }
11928
12109
  /**
11929
12110
  * What a change is about: the inbox or account by the id it has before the change, or by name alone when this change
@@ -12017,7 +12198,7 @@ async function prepareChange(core, request, options) {
12017
12198
  preview: renderChangePreview({
12018
12199
  ...record,
12019
12200
  change: binding
12020
- }),
12201
+ }, { askedHere: options.askedHere === true }),
12021
12202
  expiresAt: record.expiresAt,
12022
12203
  next: nextStep(record.approvalId, policy, core, options),
12023
12204
  approval: await core.approvals.approvalOf(record)
@@ -12107,7 +12288,7 @@ async function beginChangeApproval(core, approvalId, options) {
12107
12288
  const challenge = await core.approvals.issueChallenge(approvalId, "change", options.platform);
12108
12289
  return {
12109
12290
  approvalId,
12110
- preview: renderChangePreview(record),
12291
+ preview: renderChangePreview(record, { codeHere: true }),
12111
12292
  challenge
12112
12293
  };
12113
12294
  } catch (error) {
@@ -12287,6 +12468,31 @@ function shown(value) {
12287
12468
  if (typeof value === "object") return Object.entries(value).map(([key, entry]) => `${key} ${String(entry)}`).join(", ");
12288
12469
  return String(value);
12289
12470
  }
12471
+ /**
12472
+ * The label settings kept for each Slack workspace, by its name (`labelSettingValue`), and what a workspace with none
12473
+ * has: its style is `app`, and the app's posts show the person's photo.
12474
+ */
12475
+ const PER_WORKSPACE = {
12476
+ "agentLabel.slackStyles": "app",
12477
+ "agentLabel.slackIcons": "your Slack photo"
12478
+ };
12479
+ function recordOf$1(value) {
12480
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
12481
+ }
12482
+ /**
12483
+ * A setting kept for each workspace, as the person reads it: each workspace it moves, by name, from → to —
12484
+ * `in acme/slack: app → labelled` — and none it leaves as it was. Undefined for any other setting, or a value of
12485
+ * another shape, which is shown as every other setting is.
12486
+ */
12487
+ function perWorkspace(field, before, after) {
12488
+ const none = PER_WORKSPACE[field];
12489
+ const [was, now] = [recordOf$1(before ?? {}), recordOf$1(after ?? {})];
12490
+ if (none === void 0 || was === void 0 || now === void 0) return void 0;
12491
+ const value = (record, name) => Object.hasOwn(record, name) ? String(record[name]) : none;
12492
+ const moved = [.../* @__PURE__ */ new Set([...Object.keys(was), ...Object.keys(now)])].filter((name) => value(was, name) !== value(now, name)).sort();
12493
+ if (moved.length === 0) return void 0;
12494
+ return moved.map((name) => `in ${name}: ${value(was, name)} → ${value(now, name)}`).join(", and ");
12495
+ }
12290
12496
  /** One loosening as a line a person reads: what, from, to, and what that means. */
12291
12497
  function describeLoosening(loosening) {
12292
12498
  const scoped = /^(inboxes|accounts)\.([^.]+)\.(.+)$/.exec(loosening.path);
@@ -12294,7 +12500,8 @@ function describeLoosening(loosening) {
12294
12500
  const label = SETTING_LABELS[field] ?? field;
12295
12501
  const what = scoped ? `${scoped[2]} ${label}${loosening.id === void 0 ? " (connected by this change)" : ""}` : label;
12296
12502
  const words = meaning(field, loosening.after, scoped ? scoped[1] : void 0);
12297
- return truncateDisplay(`${what}: ${shown(loosening.before)} → ${shown(loosening.after)}${words ? ` — ${words}` : ""}`, 300);
12503
+ const workspaces = scoped ? void 0 : perWorkspace(field, loosening.before, loosening.after);
12504
+ return truncateDisplay(`${workspaces === void 0 ? `${what}: ${shown(loosening.before)} → ${shown(loosening.after)}` : `${what} ${workspaces}`}${words ? ` — ${words}` : ""}`, 300);
12298
12505
  }
12299
12506
  /**
12300
12507
  * The change as a person reads it before agreeing: what it is for, every setting it loosens from → to in words, and
@@ -12304,10 +12511,14 @@ function describeLoosening(loosening) {
12304
12511
  * once as it was prepared (`doneAtOnce`). There it says what is true: those lines, listed apart, are done already, and
12305
12512
  * the rest is what waits for the approval. Decided by that field alone, never by the words of an effect, which can
12306
12513
  * carry text a person or a profile chose; every other preview's header is word for word what it was.
12514
+ *
12515
+ * It says how the change is approved: under `confirm`, a code typed at a terminal; under `chat`, a yes in the chat —
12516
+ * or, `askedHere`, a yes typed here, at this terminal, where a CLI asks the person reading it at once; or, `codeHere`,
12517
+ * a code typed here, at this terminal, where `approve <id>` shows a change prepared under `chat` and asks for its code.
12307
12518
  */
12308
- function renderChangePreview(record) {
12519
+ function renderChangePreview(record, options = {}) {
12309
12520
  const { change } = record;
12310
- const how = changePolicyOf$1(record) === "chat" ? "a yes in the chat" : "a code typed at a terminal";
12521
+ const how = changePolicyOf$1(record) === "chat" ? options.codeHere === true ? "a code typed here, at this terminal" : options.askedHere === true ? "a yes typed here, at this terminal" : "a yes in the chat" : "a code typed at a terminal";
12311
12522
  const target = change.target;
12312
12523
  const about = target === null ? "the whole configuration" : `${target.kind} ${target.name}${target.id === void 0 ? ", which this change connects" : ""}`;
12313
12524
  const doneAlready = (change.doneAtOnce ?? []).length > 0;
@@ -12368,7 +12579,8 @@ async function gatedChange(core, change, options) {
12368
12579
  prepared: await prepareChange(core, request, {
12369
12580
  surface: options.surface,
12370
12581
  channel: options.channel,
12371
- platform: options.platform
12582
+ platform: options.platform,
12583
+ askedHere: options.askedHere
12372
12584
  })
12373
12585
  };
12374
12586
  const { consent, approval } = await claimChangeApproval(core, options.approvalId, request, {
@@ -12434,15 +12646,17 @@ function changeToolResult(outcome) {
12434
12646
  */
12435
12647
  async function gatedChangeAtTerminal(core, change, options) {
12436
12648
  const streams = options.streams ?? defaultStreams;
12649
+ const person = agentMarker(options.env) === null && canPrompt(options.env, streams, { json: options.output.json === true });
12437
12650
  const first = await gatedChange(core, change, {
12438
12651
  surface: "cli",
12439
12652
  channel: options.channel,
12440
12653
  approvalId: options.approvalId,
12441
- platform: options.output.platform
12654
+ platform: options.output.platform,
12655
+ askedHere: person
12442
12656
  });
12443
12657
  if (first.status === "applied") return first.result;
12444
12658
  const { prepared } = first;
12445
- if (!(agentMarker(options.env) === null && canPrompt(options.env, streams, { json: options.output.json === true }))) {
12659
+ if (!person) {
12446
12660
  if (options.output.json !== true) streams.stdout.write(`${prepared.preview}\n\n`);
12447
12661
  const carrying = [options.approvalFlag ?? "--approval", prepared.approvalId];
12448
12662
  const handoffs = requireHandoffs(core, options.output.platform);
@@ -12932,9 +13146,10 @@ const GOOGLE_CLIENT_ID_PATTERN = /^[0-9]{1,30}-[a-z0-9]{1,64}\.apps\.googleuserc
12932
13146
  * one place a profile changes the configuration, each change approved first.
12933
13147
  *
12934
13148
  * **A profile is data, never code, and none of it is trusted.** Every field comes from whoever wrote the document, so
12935
- * each has a grammar and a bound, an unknown key is an error, and the two free-text fields are neutralised and put on
12936
- * one line wherever they are shown. The client secret is never shown anywhere — not in a preview, an effect, an error,
12937
- * the audit log, a command's output or a tool's result.
13149
+ * each has a grammar and a bound, an unknown key is an error — anywhere but inside `extensions`, the one door a later
13150
+ * release's fields come in by (design 2026-10-19) — and the two free-text fields are neutralised and put on one line
13151
+ * wherever they are shown. The client secret is never shown anywhere — not in a preview, an effect, an error, the audit
13152
+ * log, a command's output or a tool's result.
12938
13153
  */
12939
13154
  /** What a profile says it is, in its first key. */
12940
13155
  const PROFILE_KIND = "organisation-profile";
@@ -12961,6 +13176,18 @@ const slackAppSchema = strictObject({
12961
13176
  clientId: string().regex(SLACK_CLIENT_ID_PATTERN, "a Slack client id is two runs of digits around a dot"),
12962
13177
  appId: string().regex(SLACK_APP_ID_PATTERN, "a Slack app id is A followed by 2–20 capital letters or digits").optional()
12963
13178
  });
13179
+ /** An extension's name: a lower-case letter, then 1–39 lower-case letters, digits or hyphens (X1). */
13180
+ const EXTENSION_NAME_PATTERN = /^[a-z][a-z0-9-]{1,39}$/;
13181
+ const extensionEntrySchema = looseObject({
13182
+ required: boolean({ message: "required is true or false: whether a release that does not know it must refuse it" }),
13183
+ since: string({ message: "since is the first release that understands it, as MAJOR.MINOR.PATCH" }).regex(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/, "since is the first release that understands it, as MAJOR.MINOR.PATCH: 0.23.0")
13184
+ });
13185
+ /**
13186
+ * The one door a version-1 profile has for what later releases add (design 2026-10-19, X1): at most 16 entries, each
13187
+ * named to the grammar above and each an object carrying `required` and `since`. Its other fields are the extension's;
13188
+ * every other object of the profile stays strict.
13189
+ */
13190
+ const extensionsSchema = record(string().regex(EXTENSION_NAME_PATTERN, "an extension is named by a lower-case letter, then 1–39 lower-case letters, digits or hyphens"), extensionEntrySchema, { message: "extensions is an object of named entries" }).refine((entries) => Object.keys(entries).length <= 16, { message: `extensions has at most 16 entries` });
12964
13191
  strictObject({
12965
13192
  agentcomms: literal$1(PROFILE_KIND),
12966
13193
  version: literal$1(1),
@@ -12980,7 +13207,8 @@ strictObject({
12980
13207
  read: slackAppSchema.optional(),
12981
13208
  send: slackAppSchema.optional()
12982
13209
  })
12983
- }).optional()
13210
+ }).optional(),
13211
+ extensions: extensionsSchema.optional()
12984
13212
  });
12985
13213
  /**
12986
13214
  * A string that came from a profile, as it is shown anywhere: chat-template tokens, role markers and envelope look-alikes
@@ -14001,6 +14229,49 @@ async function npmGlobalPackages(env, names) {
14001
14229
  return found;
14002
14230
  }
14003
14231
  /**
14232
+ * The machine's one update lock (design 2026-10-16 U5; plan, Review focus 1 and 3): a background run, a person's
14233
+ * update (`comms_update`, `agentcomms update`), a rollback and a person's prune (`comms_server_prune`, `mcp prune`)
14234
+ * each hold it for as long as they install, switch or prune, so none of them ever overlaps another — two of them would
14235
+ * switch the same pointers and prune runtimes the other had just installed, or was still checking.
14236
+ *
14237
+ * An install can take minutes, so the holder renews it, and a lock left by a process that died is taken over once it
14238
+ * has gone unrenewed for ten minutes. Nobody waits for it long: a background run that finds it held stops and runs
14239
+ * again the next day, and a person's update is refused at once — before an approval is prepared, where it can be — so
14240
+ * no conversation sits behind an install.
14241
+ */
14242
+ const UPDATE_LOCK_FILE = ".update.lock";
14243
+ function updateLockPath(stateDir) {
14244
+ return join(stateDir, UPDATE_LOCK_FILE);
14245
+ }
14246
+ const HOLD = {
14247
+ staleMs: 6e5,
14248
+ renewMs: 6e4
14249
+ };
14250
+ /** Runs `fn` holding the update lock, waiting at most `waitMs` for it; LOCK_TIMEOUT when another holds it. */
14251
+ async function withUpdateLock(stateDir, fn, waitMs = 1e3) {
14252
+ return withFileLock(updateLockPath(stateDir), fn, {
14253
+ timeoutMs: waitMs,
14254
+ ...HOLD
14255
+ });
14256
+ }
14257
+ /**
14258
+ * A person's update, rollback or prune, holding the update lock: refused, changing nothing, while a background run —
14259
+ * or another person's update — holds it. The refusal says what is under way and when to try again.
14260
+ */
14261
+ async function withPersonUpdateLock(stateDir, fn, what = "update") {
14262
+ try {
14263
+ return await withUpdateLock(stateDir, fn);
14264
+ } catch (error) {
14265
+ const refused = toCommsError(error);
14266
+ if (refused.code !== "LOCK_TIMEOUT" || !refused.message.includes(".update.lock")) throw error;
14267
+ throw new CommsError("LOCK_TIMEOUT", "nothing was changed: a background update run is under way on this machine, or another update is", { hint: `It installs, checks and switches in a few minutes, and the two never overlap. Run the ${what} again in a few minutes; the doctor and comms_audit_tail say what the run did.` });
14268
+ }
14269
+ }
14270
+ /** Refuses at once, changing nothing, while the update lock is held: the check a person's update makes as it plans. */
14271
+ async function refuseWhileUpdating(stateDir, what = "update") {
14272
+ await withPersonUpdateLock(stateDir, async () => void 0, what);
14273
+ }
14274
+ /**
14004
14275
  * Every sentence a background run may leave for the next session, and the one way a notice of it is said (design
14005
14276
  * 2026-10-16 U5 step 7, U9; security review of 0.20.0, severity 6): in this package's own words, with nothing in them
14006
14277
  * the registry or a certificate wrote. A release's version is the one thing a sentence takes from outside, and only as a
@@ -14788,18 +15059,24 @@ async function remedyLocated(error, core) {
14788
15059
  * one effect — the removal cannot be taken back — so the approval is bound to exactly that list; and the removal is
14789
15060
  * then restricted to it (`only`), so a runtime that became unused after the list was shown stays.
14790
15061
  *
15062
+ * A removal never runs beside an update (`update-lock.ts`): a background run installs a release and checks it before
15063
+ * any pointer names it, and a prune beside it would find that runtime unused. So it is refused at once while an update
15064
+ * holds the lock — before an approval is prepared — and the removal itself holds the lock, refused in turn when an
15065
+ * update has started since. A refusal stops nothing else.
15066
+ *
14791
15067
  * `own` is the calling channel's product, from its own `mcp prune`, so the runtime kept as "this release" is the
14792
15068
  * release of the command that was run; checked to be that channel's, as for an install.
14793
15069
  */
14794
15070
  function serverPruneChange(core, env, request, own) {
14795
- if (!isServer(request.channel)) throw new CommsError("USAGE", `"${String(request.channel)}" is not a channel`, { hint: `One of: ${CHANNELS.join(", ")}.` });
15071
+ if (request.channel === void 0 && own === void 0) return everyServerPruneChange(core, env, request);
15072
+ if (request.channel === void 0 || !isServer(request.channel)) throw new CommsError("USAGE", `"${String(request.channel)}" is not a channel`, { hint: `One of: ${CHANNELS.join(", ")}.` });
14796
15073
  const context = {
14797
15074
  env,
14798
15075
  core
14799
15076
  };
14800
15077
  const { packageName, version } = ownProduct(request.channel, own) ?? {
14801
15078
  packageName: serverFacts(request.channel).packageName,
14802
- version: "0.20.0"
15079
+ version: "0.21.0"
14803
15080
  };
14804
15081
  const product = {
14805
15082
  packageName,
@@ -14818,6 +15095,7 @@ function serverPruneChange(core, env, request, own) {
14818
15095
  after: config,
14819
15096
  summary: `Show which ${label} runtimes are unused`
14820
15097
  };
15098
+ await refuseWhileUpdating(core.paths.stateDir, "prune");
14821
15099
  const preview = await pruneManagedRuntimes(context, product, {
14822
15100
  ...base,
14823
15101
  dryRun: true
@@ -14826,15 +15104,124 @@ function serverPruneChange(core, env, request, own) {
14826
15104
  return {
14827
15105
  before: config,
14828
15106
  after: config,
14829
- effects: [...preview.removed.map((item) => `deletes the unused ${label} runtime ${item.version} at ${item.path}`), ...preview.shims.removed.map((path) => `deletes the owned ${label} CLI shim at ${path}`)],
15107
+ effects: pruneEffects(label, preview),
14830
15108
  summary: `Remove ${approved.length} unused ${label} runtime${approved.length === 1 ? "" : "s"}`
14831
15109
  };
14832
15110
  },
14833
- apply: () => pruneManagedRuntimes(context, product, {
15111
+ apply: () => request.dryRun ? pruneManagedRuntimes(context, product, {
14834
15112
  ...base,
14835
- dryRun: request.dryRun === true,
14836
- ...request.dryRun ? {} : { only: approved }
14837
- })
15113
+ dryRun: true
15114
+ }) : withPersonUpdateLock(core.paths.stateDir, () => pruneManagedRuntimes(context, product, {
15115
+ ...base,
15116
+ dryRun: false,
15117
+ only: approved
15118
+ }), "prune")
15119
+ };
15120
+ }
15121
+ /** What a removal of runtimes does, in the preview's words: each runtime, then each owned CLI shim. */
15122
+ function pruneEffects(label, preview) {
15123
+ return [...preview.removed.map((item) => `deletes the unused ${label} runtime ${item.version} at ${item.path}`), ...preview.shims.removed.map((path) => `deletes the owned ${label} CLI shim at ${path}`)];
15124
+ }
15125
+ /**
15126
+ * Every managed server's unused runtimes, as one change (0.21): what an update leaves behind is every server's, and
15127
+ * `--server` for each was a step a person had to know to take. The servers are the channel registry's (`SERVERS`),
15128
+ * channels and declared services alike, never a list written here; each is pruned exactly as `--server` prunes it —
15129
+ * this release's runtime, one any client config or printed entry names, one a running process uses, and the runtime
15130
+ * its stable launcher starts and the one it keeps for a rollback all stay, each said why.
15131
+ *
15132
+ * A dry run is free. Otherwise the plan is every server's dry run, shown grouped by server, and the approval is bound to
15133
+ * exactly that list; the removal is then restricted to it, so nothing that became unused after it was shown goes. A
15134
+ * server whose prune was stopped — a pointer or a config that cannot be read — is named in the preview, beside what the
15135
+ * others remove, and in the result's `refusedServers`. The running processes are listed again for each server, just
15136
+ * before it is decided, as the prune of one server lists them: a listing taken at the start of the pass would not see
15137
+ * a process started from a runtime while the servers before it were pruned. And, as for one server, a removal never
15138
+ * runs beside an update.
15139
+ */
15140
+ function everyServerPruneChange(core, env, request) {
15141
+ const context = {
15142
+ env,
15143
+ core
15144
+ };
15145
+ const servers = SERVERS.map((server) => ({
15146
+ server,
15147
+ label: serverLabel(server),
15148
+ product: {
15149
+ packageName: serverFacts(server).packageName,
15150
+ version: VERSION
15151
+ }
15152
+ }));
15153
+ const pruneEach = async (options) => {
15154
+ const parts = [];
15155
+ for (const { server, label, product } of servers) {
15156
+ const result = await pruneManagedRuntimes(context, product, {
15157
+ includePrinted: request.includePrinted === true,
15158
+ processes: request.processes ?? runningCommandLines,
15159
+ ...options
15160
+ });
15161
+ parts.push({
15162
+ server,
15163
+ label,
15164
+ ...result
15165
+ });
15166
+ }
15167
+ return parts;
15168
+ };
15169
+ /** Each server whose prune was stopped, and why. */
15170
+ const refusedOf = (parts) => parts.flatMap(({ server, label, refused }) => refused === void 0 ? [] : [{
15171
+ server,
15172
+ label,
15173
+ refused
15174
+ }]);
15175
+ let approved = [];
15176
+ return {
15177
+ plan: async (config) => {
15178
+ if (request.dryRun) return {
15179
+ before: config,
15180
+ after: config,
15181
+ summary: "Show which runtimes of every managed server are unused"
15182
+ };
15183
+ await refuseWhileUpdating(core.paths.stateDir, "prune");
15184
+ const previews = await pruneEach({ dryRun: true });
15185
+ approved = previews.flatMap((part) => part.removed.map((item) => item.path));
15186
+ const counts = previews.filter((part) => part.removed.length > 0).map((part) => `${part.removed.length} ${part.label}`);
15187
+ const removals = previews.flatMap((part) => pruneEffects(part.label, part));
15188
+ const unchecked = refusedOf(previews).map(({ label, refused }) => `does not check the ${label} runtimes, and removes none of them: ${refused}`);
15189
+ return {
15190
+ before: config,
15191
+ after: config,
15192
+ effects: removals.length > 0 ? [...removals, ...unchecked] : [],
15193
+ summary: `Remove ${approved.length} unused runtime${approved.length === 1 ? "" : "s"}${counts.length > 0 ? `: ${counts.join(", ")}` : ""}`
15194
+ };
15195
+ },
15196
+ apply: async () => {
15197
+ const dryRun = request.dryRun === true;
15198
+ const parts = dryRun ? await pruneEach({ dryRun }) : await withPersonUpdateLock(core.paths.stateDir, () => pruneEach({
15199
+ dryRun,
15200
+ only: approved
15201
+ }), "prune");
15202
+ const looked = parts.filter((part) => part.removed.length + part.kept.length > 0);
15203
+ const refused = looked.length > 0 && looked.every((part) => part.refused !== void 0);
15204
+ const refusedServers = refusedOf(parts);
15205
+ return {
15206
+ runtimeDir: join(core.paths.dataDir, "runtime"),
15207
+ dryRun,
15208
+ removed: parts.flatMap((part) => part.removed.map((item) => ({
15209
+ ...item,
15210
+ server: part.server
15211
+ }))),
15212
+ kept: parts.flatMap((part) => part.kept.map((item) => ({
15213
+ ...item,
15214
+ server: part.server
15215
+ }))),
15216
+ shims: {
15217
+ removed: parts.flatMap((part) => part.shims.removed),
15218
+ kept: parts.flatMap((part) => part.shims.kept)
15219
+ },
15220
+ ...refused ? { refused: [...new Set(looked.map((part) => part.refused))].join("; ") } : {},
15221
+ servers: parts,
15222
+ ...refusedServers.length > 0 ? { refusedServers } : {}
15223
+ };
15224
+ }
14838
15225
  };
14839
15226
  }
14840
15227
  /**
@@ -16110,7 +16497,7 @@ function terminalUpdateHooks(core, env, options = {}) {
16110
16497
  } };
16111
16498
  }
16112
16499
  //#endregion
16113
- //#region ../core/dist/tool-arguments-4c3WR2Gy.mjs
16500
+ //#region ../core/dist/tool-arguments-C8yi7cpo.mjs
16114
16501
  const APPLIED = Symbol.for("agentcomms.strictToolArguments");
16115
16502
  const TARGET = "draft-2020-12";
16116
16503
  /**
@@ -1,4 +1,4 @@
1
- import { A as removeAccountChange, At as waitCallOptions, B as strictToolArguments, Bt as VERSION, D as policyChange, E as policyApprovalRefusal, Et as toCommsError, F as CHANGE_POLICIES, H as CommsError, I as MODES, L as SEND_POLICIES, M as showAccount, O as policyReport, P as ResendContext, X as changeToolResult, Z as checkForUpdates, _ as showSentEmail, c as downloadReceived, d as listReceived, f as listScheduled, g as showReceived, ht as refuseUnclaimedApproval, i as prepareSend, it as gatedChange, jt as waitForApproval, k as policyWanted, kt as updateToolNotice, l as getMetrics, m as listSuppressions, n as executeSend, o as sendStatus, p as listSentEmails, s as cancelScheduledChange, u as listDomains, w as listAccounts, x as runDoctor } from "./send-CE6wM0-w.mjs";
1
+ import { A as removeAccountChange, At as waitCallOptions, B as strictToolArguments, Bt as VERSION, D as policyChange, E as policyApprovalRefusal, Et as toCommsError, F as CHANGE_POLICIES, H as CommsError, I as MODES, L as SEND_POLICIES, M as showAccount, O as policyReport, P as ResendContext, X as changeToolResult, Z as checkForUpdates, _ as showSentEmail, c as downloadReceived, d as listReceived, f as listScheduled, g as showReceived, ht as refuseUnclaimedApproval, i as prepareSend, it as gatedChange, jt as waitForApproval, k as policyWanted, kt as updateToolNotice, l as getMetrics, m as listSuppressions, n as executeSend, o as sendStatus, p as listSentEmails, s as cancelScheduledChange, u as listDomains, w as listAccounts, x as runDoctor } from "./send-tAIE9B1y.mjs";
2
2
  import { S as union, c as boolean, g as object, h as number, s as array, x as string } from "./iso-USuVU_sk.mjs";
3
3
  import { t as McpServer } from "./mcp-DIH4cS6P-CIWWZQbz.mjs";
4
4
  //#region src/mcp/server.ts
@@ -1,4 +1,4 @@
1
- import { t as createResendMcpServer } from "./server-C1sSuZ8K.mjs";
1
+ import { t as createResendMcpServer } from "./server-CQ9RE8L7.mjs";
2
2
  //#region src/mcp/stdio-entry.ts
3
3
  /**
4
4
  * Starting the stdio server. On stdio, **stdout is the protocol**: one stray `console.log` corrupts the stream, so
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentcomms/resend",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "Resend for coding agents: read domains, sent and received mail, metrics and suppressions, and send mail that nothing sends without a person's approval of exactly what goes out.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -31,7 +31,7 @@
31
31
  "THIRD_PARTY_LICENSES"
32
32
  ],
33
33
  "dependencies": {
34
- "@agentcomms/core": "0.20.0"
34
+ "@agentcomms/core": "0.21.0"
35
35
  },
36
36
  "optionalDependencies": {
37
37
  "@napi-rs/keyring": "2.1.0"
@@ -107,5 +107,5 @@
107
107
  "typecheck": "tsc -p tsconfig.json",
108
108
  "test": "node --experimental-strip-types --test --test-timeout=600000 \"test/**/*.test.ts\""
109
109
  },
110
- "gitHead": "b5e7322e890d2eb6a6f9917612f6d3af1882edbd"
110
+ "gitHead": "3916bacd2b66e5457624f97cd671bee60e704272"
111
111
  }