@agentcomms/resend 0.18.2 → 0.19.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/README.md +8 -0
- package/dist/cli.mjs +5 -4
- package/dist/index.d.mts +12 -1
- package/dist/index.mjs +2 -2
- package/dist/{send-7cKVm9mA.mjs → send-Ch-s9Q6H.mjs} +423 -99
- package/dist/{server-CSe84nMI.mjs → server-ERFs-XiT.mjs} +4 -3
- package/dist/{stdio-entry-CskP8Urx.mjs → stdio-entry-FGuDOmop.mjs} +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -29,6 +29,14 @@ approves, whatever the policy. The approval id is the request's `Idempotency-Key
|
|
|
29
29
|
send whose outcome is unknown (`SEND_OUTCOME_UNKNOWN`) is never repeated, only checked, and a scheduled one is
|
|
30
30
|
"accepted", never "sent", until Resend's own last event for it says so.
|
|
31
31
|
|
|
32
|
+
**An agent's email says so in its From.** Prepare labels it `"Acme (agent)" <hello@acme.test>`: the name the From
|
|
33
|
+
gave, or, for a bare address, the label name the person set in the core (its CLI's `label name`, or `comms_label`), then their word —
|
|
34
|
+
`agent` unless they chose another ([design](../../docs/superpowers/specs/2026-10-15-agent-label-design.md), §6). With
|
|
35
|
+
neither name, or a label over Resend's 100 characters, prepare refuses. `--as me` (`as: "me"` on
|
|
36
|
+
`resend_send_prepare`) sends the From exactly as given, for when the person said so for that email; their label rules
|
|
37
|
+
decide otherwise, and with none an email is labelled. The preview's first line says which, and the approval binds the
|
|
38
|
+
whole From.
|
|
39
|
+
|
|
32
40
|
**The key is typed by a person, at a terminal.** `agent-resend account add` reads it from a hidden prompt, or from
|
|
33
41
|
`RESEND_API_KEY` in that terminal, and stores it in the system keychain. No MCP tool accepts a key: a key typed into
|
|
34
42
|
a chat stays in the transcript.
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { A as removeAccountChange, At as updateGateAtTerminal, C as inspectKey, Ct as runUpdateCheckChild, D as policyChange, Dt as terminalUpdateHooks, E as policyApprovalRefusal, Ft as writeResult, G as SEND_LOOKUP, H as CommsError, Ht as VERSION, J as approvalsOf, K as UPDATE_CHECK_CHILD_COMMAND, Lt as RESEND_CALLER, M as showAccount, Nt as waitForApproval, O as policyReport, P as ResendContext, Pt as wholeNumber, R as checkNewName, S as addAccountChange, St as runCommand, T as modeOf, Tt as serverPruneChange, U as EXIT_CODES, V as CHANNEL_SERVERS, W as PATH_OPTIONS, X as approveRefusedHint, Y as approveChangeAtTerminal, Z as canPrompt, _ as showSentEmail, _t as refuseUnclaimedApproval, a as revokeSendApproval, at as exemptFromUpdateGate, bt as renderPrune, c as downloadReceived, ct as handoffSentence, d as listReceived, dt as kindOf, et as cliHandoffs, f as listScheduled, g as showReceived, gt as pathOverridesFromCliOptions, ht as paint, i as prepareSend, it as escapeForDisplay, j as sendPolicyOf, k as policyWanted, kt as truncateDisplay, l as getMetrics, lt as handoffSentenceToFill, m as listSuppressions, mt as openCore, n as executeSend, nt as commandPathOf, o as sendStatus, p as listSentEmails, q as agentMarker, r as finishSendApproval, rt as describeFenceReport, s as cancelScheduledChange, st as gatedChangeAtTerminal, t as beginSendApproval, tt as colorEnabled, u as listDomains, ut as installExitStatus, vt as renderApprovalWait, w as listAccounts, wt as serverInstallChange, x as runDoctor, xt as resolvePaths, yt as renderInstall } from "./send-
|
|
2
|
+
import { A as removeAccountChange, At as updateGateAtTerminal, C as inspectKey, Ct as runUpdateCheckChild, D as policyChange, Dt as terminalUpdateHooks, E as policyApprovalRefusal, Ft as writeResult, G as SEND_LOOKUP, H as CommsError, Ht as VERSION, J as approvalsOf, K as UPDATE_CHECK_CHILD_COMMAND, Lt as RESEND_CALLER, M as showAccount, Nt as waitForApproval, O as policyReport, P as ResendContext, Pt as wholeNumber, R as checkNewName, S as addAccountChange, St as runCommand, T as modeOf, Tt as serverPruneChange, U as EXIT_CODES, V as CHANNEL_SERVERS, W as PATH_OPTIONS, X as approveRefusedHint, Y as approveChangeAtTerminal, Z as canPrompt, _ as showSentEmail, _t as refuseUnclaimedApproval, a as revokeSendApproval, at as exemptFromUpdateGate, bt as renderPrune, c as downloadReceived, ct as handoffSentence, d as listReceived, dt as kindOf, et as cliHandoffs, f as listScheduled, g as showReceived, gt as pathOverridesFromCliOptions, ht as paint, i as prepareSend, it as escapeForDisplay, j as sendPolicyOf, k as policyWanted, kt as truncateDisplay, l as getMetrics, lt as handoffSentenceToFill, m as listSuppressions, mt as openCore, n as executeSend, nt as commandPathOf, o as sendStatus, p as listSentEmails, q as agentMarker, r as finishSendApproval, rt as describeFenceReport, s as cancelScheduledChange, st as gatedChangeAtTerminal, t as beginSendApproval, tt as colorEnabled, u as listDomains, ut as installExitStatus, vt as renderApprovalWait, w as listAccounts, wt as serverInstallChange, x as runDoctor, xt as resolvePaths, yt as renderInstall } from "./send-Ch-s9Q6H.mjs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createInterface } from "node:readline/promises";
|
|
5
5
|
import { stripVTControlCharacters } from "node:util";
|
|
@@ -3407,7 +3407,7 @@ is out: update first, or put it off (the stop names both commands) · 64 usage
|
|
|
3407
3407
|
writeResult(result, output(), (data) => renderRead(data, options.color), streams);
|
|
3408
3408
|
}));
|
|
3409
3409
|
const send = program.command("send").description("prepare an email, and send it once a person has approved it");
|
|
3410
|
-
accountOption(send.command("prepare")).description("build an email and show exactly what would go, to whom. Sends nothing").requiredOption("--from <address>", "the sender, at a verified domain: `Name <you@example.com>` or a bare address").requiredOption("--to <address>", "a recipient; repeat for more", collect, []).option("--cc <address>", "a copy recipient; repeat for more", collect, []).option("--bcc <address>", "a blind recipient; repeat for more — the preview lists every one", collect, []).requiredOption("--subject <subject>", "the subject").option("--text <text>", "the plain-text body").option("--html <html>", "an HTML body whose visible text is the text body; no images, forms or hidden parts").option("--attach <path>", "a file to attach, from an allowed folder; repeat for more", collect, []).option("--reply-to <address>", "where replies go; repeat for more", collect, []).option("--in-reply-to <messageId>", "the Message-ID this replies to, to keep the thread").option("--references <messageId>", "an earlier Message-ID in the thread; repeat for more", collect, []).option("--scheduled-at <time>", "send later: an ISO 8601 time with a zone, at most 30 days ahead").action(act(async (context, _options, flags) => {
|
|
3410
|
+
accountOption(send.command("prepare")).description("build an email and show exactly what would go, to whom. Sends nothing").requiredOption("--from <address>", "the sender, at a verified domain: `Name <you@example.com>` or a bare address").requiredOption("--to <address>", "a recipient; repeat for more", collect, []).option("--cc <address>", "a copy recipient; repeat for more", collect, []).option("--bcc <address>", "a blind recipient; repeat for more — the preview lists every one", collect, []).requiredOption("--subject <subject>", "the subject").option("--text <text>", "the plain-text body").option("--html <html>", "an HTML body whose visible text is the text body; no images, forms or hidden parts").option("--attach <path>", "a file to attach, from an allowed folder; repeat for more", collect, []).option("--reply-to <address>", "where replies go; repeat for more", collect, []).option("--in-reply-to <messageId>", "the Message-ID this replies to, to keep the thread").option("--references <messageId>", "an earlier Message-ID in the thread; repeat for more", collect, []).option("--scheduled-at <time>", "send later: an ISO 8601 time with a zone, at most 30 days ahead").option("--as <who>", "who it appears to come from: agent (From labelled) or me (From as given, only when the person said so)").action(act(async (context, _options, flags) => {
|
|
3411
3411
|
const prepared = await prepareSend(context, String(flags.account), {
|
|
3412
3412
|
from: String(flags.from),
|
|
3413
3413
|
to: flags.to,
|
|
@@ -3420,7 +3420,8 @@ is out: update first, or put it off (the stop names both commands) · 64 usage
|
|
|
3420
3420
|
replyTo: flags.replyTo,
|
|
3421
3421
|
inReplyTo: flags.inReplyTo === void 0 ? void 0 : String(flags.inReplyTo),
|
|
3422
3422
|
references: flags.references,
|
|
3423
|
-
scheduledAt: flags.scheduledAt === void 0 ? void 0 : String(flags.scheduledAt)
|
|
3423
|
+
scheduledAt: flags.scheduledAt === void 0 ? void 0 : String(flags.scheduledAt),
|
|
3424
|
+
as: flags.as === void 0 ? void 0 : String(flags.as)
|
|
3424
3425
|
});
|
|
3425
3426
|
writeResult(prepared, output(), renderPrepared, streams);
|
|
3426
3427
|
}));
|
|
@@ -3505,7 +3506,7 @@ is out: update first, or put it off (the stop names both commands) · 64 usage
|
|
|
3505
3506
|
}));
|
|
3506
3507
|
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) => {
|
|
3507
3508
|
ran = true;
|
|
3508
|
-
const startResendStdioServer = deps.startMcp ?? (await import("./stdio-entry-
|
|
3509
|
+
const startResendStdioServer = deps.startMcp ?? (await import("./stdio-entry-FGuDOmop.mjs")).startResendStdioServer;
|
|
3509
3510
|
const core = coreForInvocation();
|
|
3510
3511
|
await startResendStdioServer({
|
|
3511
3512
|
...deps,
|
package/dist/index.d.mts
CHANGED
|
@@ -4322,7 +4322,8 @@ 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/change-flow-
|
|
4325
|
+
//#region ../core/dist/change-flow-DOyBTYin.d.mts
|
|
4326
|
+
//#endregion
|
|
4326
4327
|
//#region src/config-hooks.d.ts
|
|
4327
4328
|
/**
|
|
4328
4329
|
* Test-only pauses inside `ConfigStore.convertToVersion3`, for the tests that prove a write racing the conversion is
|
|
@@ -4780,6 +4781,16 @@ interface ConfigBody {
|
|
|
4780
4781
|
/** Superseded rotating credentials which still need conclusive per-token cleanup. */
|
|
4781
4782
|
pendingRevocations?: PendingRevocation[] | undefined;
|
|
4782
4783
|
defaults: Defaults;
|
|
4784
|
+
/**
|
|
4785
|
+
* How what an agent sends is labelled (design 2026-10-15-agent-label, L5): the word, the name, the rules that say
|
|
4786
|
+
* who a message appears to come from, and each Slack account's agent emoji.
|
|
4787
|
+
*
|
|
4788
|
+
* `unknown` here, on purpose: it is read only through `agentLabelOf`, which reads a value it does not understand
|
|
4789
|
+
* as the stricter one — an unknown word as `agent`, a rule list it cannot read as no rules — so a hand edit or a
|
|
4790
|
+
* later release's key can only tighten. Kept through every write as the schemas keep every key they do not name, and
|
|
4791
|
+
* judged by `classifyChange`.
|
|
4792
|
+
*/
|
|
4793
|
+
agentLabel?: unknown;
|
|
4783
4794
|
}
|
|
4784
4795
|
interface ConfigV1 extends ConfigBody {
|
|
4785
4796
|
version: 1;
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Bt as RESEND_DOWNLOAD_ORIGIN, Et as stripInvisible, H as CommsError, Ht as VERSION, It as PACKAGE_NAME, Lt as RESEND_CALLER, N as resendRequest, P as ResendContext, Rt as REFUSED, Vt as ROUTES, b as readBody, ft as neutralise, h as resendId, pt as newBoundary, v as addressesOf, y as attachmentRisks, z as keyPermissionOf, zt as RESEND_API_ORIGIN } from "./send-
|
|
2
|
-
import { t as createResendMcpServer } from "./server-
|
|
1
|
+
import { Bt as RESEND_DOWNLOAD_ORIGIN, Et as stripInvisible, H as CommsError, Ht as VERSION, It as PACKAGE_NAME, Lt as RESEND_CALLER, N as resendRequest, P as ResendContext, Rt as REFUSED, Vt as ROUTES, b as readBody, ft as neutralise, h as resendId, pt as newBoundary, v as addressesOf, y as attachmentRisks, z as keyPermissionOf, zt as RESEND_API_ORIGIN } from "./send-Ch-s9Q6H.mjs";
|
|
2
|
+
import { t as createResendMcpServer } from "./server-ERFs-XiT.mjs";
|
|
3
3
|
//#region src/operations/events.ts
|
|
4
4
|
const BODY_LIMIT = 2e4;
|
|
5
5
|
const STATUS = /* @__PURE__ */ new Set([
|
|
@@ -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
|
+
const VERSION$1 = "0.19.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-
|
|
204
|
+
//#region ../core/dist/update-check-CVvpp5G8.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
|
|
@@ -1150,6 +1150,228 @@ var TaintCollector = class {
|
|
|
1150
1150
|
await store.record(this.#observations, exclusions, this.#handles);
|
|
1151
1151
|
}
|
|
1152
1152
|
};
|
|
1153
|
+
/**
|
|
1154
|
+
* One table of dangerous characters, shared by the inbound sanitiser (which strips them from sender-controlled text)
|
|
1155
|
+
* and the preview renderer (which makes them visible), so the two can never disagree.
|
|
1156
|
+
*/
|
|
1157
|
+
/** Zero-width and formatting characters, bidi controls, variation selectors and Unicode tag characters. */
|
|
1158
|
+
const INVISIBLE_RANGES = [
|
|
1159
|
+
[173, 173],
|
|
1160
|
+
[847, 847],
|
|
1161
|
+
[1564, 1564],
|
|
1162
|
+
[4447, 4448],
|
|
1163
|
+
[6068, 6069],
|
|
1164
|
+
[6155, 6159],
|
|
1165
|
+
[8203, 8207],
|
|
1166
|
+
[8232, 8233],
|
|
1167
|
+
[8234, 8238],
|
|
1168
|
+
[8288, 8292],
|
|
1169
|
+
[8294, 8303],
|
|
1170
|
+
[12644, 12644],
|
|
1171
|
+
[65024, 65039],
|
|
1172
|
+
[65279, 65279],
|
|
1173
|
+
[65440, 65440],
|
|
1174
|
+
[917504, 917631]
|
|
1175
|
+
];
|
|
1176
|
+
function isInvisible(codePoint) {
|
|
1177
|
+
for (const [from, to] of INVISIBLE_RANGES) if (codePoint >= from && codePoint <= to) return true;
|
|
1178
|
+
return false;
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* C0 controls except tab and newline (ESC, and so every ANSI/OSC sequence, starts here), DEL, and C1 controls (CSI
|
|
1182
|
+
* among them). A lone carriage return counts: it moves a terminal cursor back over text already printed.
|
|
1183
|
+
*/
|
|
1184
|
+
function isControl(codePoint) {
|
|
1185
|
+
if (codePoint === 9 || codePoint === 10) return false;
|
|
1186
|
+
return codePoint < 32 || codePoint === 127 || codePoint >= 128 && codePoint <= 159;
|
|
1187
|
+
}
|
|
1188
|
+
function isDangerous(codePoint) {
|
|
1189
|
+
return isControl(codePoint) || isInvisible(codePoint);
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* Strips control characters (ESC, CSI, OSC and the rest), DEL, lone carriage returns, zero-width, bidi-control and tag
|
|
1193
|
+
* characters from sender-controlled text; returns the text and how many were removed. CRLF becomes LF first.
|
|
1194
|
+
*
|
|
1195
|
+
* This lives beside the table rather than in the sanitiser because `neutralise` needs it too, and the two must not
|
|
1196
|
+
* drift: a pattern that looks for `</untrusted-content` cannot see it through a zero-width space, so stripping
|
|
1197
|
+
* has to happen before any such pattern runs, on every path, not only on the ones that render a body.
|
|
1198
|
+
*/
|
|
1199
|
+
function stripInvisible(text) {
|
|
1200
|
+
let removed = 0;
|
|
1201
|
+
let out = "";
|
|
1202
|
+
for (const char of text.replace(/\r\n/g, "\n")) if (isDangerous(char.codePointAt(0) ?? 0)) removed += 1;
|
|
1203
|
+
else out += char;
|
|
1204
|
+
return {
|
|
1205
|
+
text: out,
|
|
1206
|
+
removed
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Labelling what an agent sends (design 2026-10-15-agent-label): the phrase `<name> (<word>)`, who a message appears to
|
|
1211
|
+
* come from, and which setting changes loosen that. Pure: nothing here reads the configuration or talks to a channel,
|
|
1212
|
+
* so every channel decides the same way from the same settings.
|
|
1213
|
+
*/
|
|
1214
|
+
/** L2: the words a label may use, exactly as written. Adding one is a code change with its own review. */
|
|
1215
|
+
const AGENT_LABEL_WORDS = Object.freeze([
|
|
1216
|
+
"agent",
|
|
1217
|
+
"assistant",
|
|
1218
|
+
"twin",
|
|
1219
|
+
"minion",
|
|
1220
|
+
"bot",
|
|
1221
|
+
"fake",
|
|
1222
|
+
"AI"
|
|
1223
|
+
]);
|
|
1224
|
+
/** L4: the kinds of act a rule may name. Slack posts, replies, reacts, edits and shares files; mail sends and replies. */
|
|
1225
|
+
const ACT_KINDS = Object.freeze([
|
|
1226
|
+
"post",
|
|
1227
|
+
"reply",
|
|
1228
|
+
"reaction",
|
|
1229
|
+
"edit",
|
|
1230
|
+
"file",
|
|
1231
|
+
"send"
|
|
1232
|
+
]);
|
|
1233
|
+
/** L2's characters a name may not hold: parentheses would fake a second label, the rest break an address header. */
|
|
1234
|
+
const FORBIDDEN_NAME_CHARACTERS = /[()<>"@,;:\\]/u;
|
|
1235
|
+
function isAgentLabelWord(value) {
|
|
1236
|
+
return typeof value === "string" && AGENT_LABEL_WORDS.includes(value);
|
|
1237
|
+
}
|
|
1238
|
+
/** Why a name cannot be labelled, or undefined when it can. */
|
|
1239
|
+
function nameProblem(name) {
|
|
1240
|
+
if (name.length === 0) return "the name is empty";
|
|
1241
|
+
if (name.trim() !== name) return "the name starts or ends with a space";
|
|
1242
|
+
if (FORBIDDEN_NAME_CHARACTERS.test(name)) return "the name holds a parenthesis or one of <>\"@,;:\\";
|
|
1243
|
+
for (const char of name) if (isDangerous(char.codePointAt(0) ?? 0)) return "the name holds a hidden or control character";
|
|
1244
|
+
}
|
|
1245
|
+
/** L1: `<name> (<word>)`, refused rather than cut short when it does not fit `limit` characters. */
|
|
1246
|
+
function labelPhrase(name, word, limit) {
|
|
1247
|
+
const suffix = ` (${word})`;
|
|
1248
|
+
const bare = name.endsWith(suffix) ? name.slice(0, -suffix.length) : name;
|
|
1249
|
+
const problem = nameProblem(bare);
|
|
1250
|
+
if (problem !== void 0) throw new CommsError("USAGE", `the label cannot be made: ${problem}`);
|
|
1251
|
+
const phrase = `${bare}${suffix}`;
|
|
1252
|
+
if ([...phrase].length > limit) throw new CommsError("USAGE", `the label "${phrase}" is longer than this platform's ${limit} characters`, { hint: "Set a shorter name with `label name`." });
|
|
1253
|
+
return phrase;
|
|
1254
|
+
}
|
|
1255
|
+
/** L3's sentence for an unlabelled message, the first line of its preview. */
|
|
1256
|
+
const UNLABELLED_PREVIEW_LINE = "Sent as you, with no label: it will look as if you wrote it yourself";
|
|
1257
|
+
function addressMatches(pattern, address) {
|
|
1258
|
+
const want = pattern.toLowerCase();
|
|
1259
|
+
const got = address.toLowerCase();
|
|
1260
|
+
return want.startsWith("@") ? got.endsWith(want) && got.indexOf("@") === got.length - want.length : got === want;
|
|
1261
|
+
}
|
|
1262
|
+
function ruleMatches(rule, query) {
|
|
1263
|
+
if (rule.account !== query.account) return false;
|
|
1264
|
+
if (rule.accountId !== void 0 && rule.accountId !== query.accountId) return false;
|
|
1265
|
+
if (rule.kinds !== void 0 && !rule.kinds.includes(query.kind)) return false;
|
|
1266
|
+
if (rule.conversation === void 0) return true;
|
|
1267
|
+
if (query.slack !== void 0) return rule.conversation === query.slack.channel || rule.conversation === query.slack.dmWith;
|
|
1268
|
+
if (query.mail !== void 0) {
|
|
1269
|
+
const conversation = rule.conversation;
|
|
1270
|
+
return query.mail.recipients.length > 0 && query.mail.recipients.every((address) => addressMatches(conversation, address));
|
|
1271
|
+
}
|
|
1272
|
+
return false;
|
|
1273
|
+
}
|
|
1274
|
+
/** L4: conversation and kinds, then conversation, then kinds, then the bare account rule. */
|
|
1275
|
+
function specificity(rule) {
|
|
1276
|
+
return (rule.conversation === void 0 ? 0 : 2) + (rule.kinds === void 0 ? 0 : 1);
|
|
1277
|
+
}
|
|
1278
|
+
/** L3: the message, then the most specific matching rule (`agent` winning a tie), then the default `agent`. */
|
|
1279
|
+
function resolveIdentity(settings, query) {
|
|
1280
|
+
if (query.requested !== void 0) return {
|
|
1281
|
+
identity: query.requested,
|
|
1282
|
+
source: "message"
|
|
1283
|
+
};
|
|
1284
|
+
let best;
|
|
1285
|
+
for (const rule of settings?.rules ?? []) {
|
|
1286
|
+
if (!ruleMatches(rule, query)) continue;
|
|
1287
|
+
if (best === void 0 || specificity(rule) > specificity(best) || specificity(rule) === specificity(best) && rule.as === "agent") best = rule;
|
|
1288
|
+
}
|
|
1289
|
+
return best === void 0 ? {
|
|
1290
|
+
identity: "agent",
|
|
1291
|
+
source: "default"
|
|
1292
|
+
} : {
|
|
1293
|
+
identity: best.as,
|
|
1294
|
+
source: "rule",
|
|
1295
|
+
rule: best
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
const ruleKey = (rule) => JSON.stringify([
|
|
1299
|
+
rule.account,
|
|
1300
|
+
rule.accountId ?? null,
|
|
1301
|
+
rule.conversation ?? null,
|
|
1302
|
+
rule.kinds === void 0 ? null : [...rule.kinds].sort(),
|
|
1303
|
+
rule.as
|
|
1304
|
+
]);
|
|
1305
|
+
/**
|
|
1306
|
+
* L5: the changes that need the person's confirmation — anything that could leave a message unlabelled or change how
|
|
1307
|
+
* the label looks. Adding an `agent` rule or removing a `me` rule tightens, and is not listed.
|
|
1308
|
+
*/
|
|
1309
|
+
function classifyAgentLabelChange(before, after) {
|
|
1310
|
+
const changes = [];
|
|
1311
|
+
const wordBefore = before?.word ?? "agent";
|
|
1312
|
+
const wordAfter = after?.word ?? "agent";
|
|
1313
|
+
if (wordBefore !== wordAfter) changes.push({
|
|
1314
|
+
path: "agentLabel.word",
|
|
1315
|
+
was: wordBefore,
|
|
1316
|
+
now: wordAfter
|
|
1317
|
+
});
|
|
1318
|
+
if ((before?.name ?? null) !== (after?.name ?? null)) changes.push({
|
|
1319
|
+
path: "agentLabel.name",
|
|
1320
|
+
was: before?.name,
|
|
1321
|
+
now: after?.name
|
|
1322
|
+
});
|
|
1323
|
+
const rulesBefore = new Map((before?.rules ?? []).map((rule) => [ruleKey(rule), rule]));
|
|
1324
|
+
const rulesAfter = new Map((after?.rules ?? []).map((rule) => [ruleKey(rule), rule]));
|
|
1325
|
+
const meAdded = [...rulesAfter].some(([key, rule]) => rule.as === "me" && !rulesBefore.has(key));
|
|
1326
|
+
const agentRemoved = [...rulesBefore].some(([key, rule]) => rule.as === "agent" && !rulesAfter.has(key));
|
|
1327
|
+
if (meAdded || agentRemoved) changes.push({
|
|
1328
|
+
path: "agentLabel.rules",
|
|
1329
|
+
was: before?.rules ?? [],
|
|
1330
|
+
now: after?.rules ?? []
|
|
1331
|
+
});
|
|
1332
|
+
if (JSON.stringify(sorted(before?.slackIcons)) !== JSON.stringify(sorted(after?.slackIcons))) changes.push({
|
|
1333
|
+
path: "agentLabel.slackIcons",
|
|
1334
|
+
was: before?.slackIcons ?? {},
|
|
1335
|
+
now: after?.slackIcons ?? {}
|
|
1336
|
+
});
|
|
1337
|
+
return changes;
|
|
1338
|
+
}
|
|
1339
|
+
function sorted(record) {
|
|
1340
|
+
return Object.entries(record ?? {}).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
1341
|
+
}
|
|
1342
|
+
const EMOJI_NAME = /^:[a-z0-9_+'-]{1,100}:$/u;
|
|
1343
|
+
function isRule(value) {
|
|
1344
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1345
|
+
const rule = value;
|
|
1346
|
+
if (typeof rule.account !== "string" || rule.account.length === 0) return false;
|
|
1347
|
+
if (rule.as !== "agent" && rule.as !== "me") return false;
|
|
1348
|
+
if (rule.accountId !== void 0 && (typeof rule.accountId !== "string" || rule.accountId.length === 0)) return false;
|
|
1349
|
+
if (rule.conversation !== void 0 && (typeof rule.conversation !== "string" || rule.conversation.length === 0)) return false;
|
|
1350
|
+
if (rule.kinds !== void 0 && !(Array.isArray(rule.kinds) && rule.kinds.length > 0 && rule.kinds.every((kind) => ACT_KINDS.includes(kind)))) return false;
|
|
1351
|
+
return true;
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* The stored `agentLabel`, read so that a bad value can only tighten. An unknown word reads as `agent`, an invalid
|
|
1355
|
+
* name as no name, and a rule list with any invalid rule is ignored whole: dropping one `agent` rule could leave a
|
|
1356
|
+
* broader `me` rule in charge. A malformed icon entry is dropped, falling back to the plain photo.
|
|
1357
|
+
*/
|
|
1358
|
+
function readAgentLabelSettings(raw) {
|
|
1359
|
+
if (typeof raw !== "object" || raw === null) return {};
|
|
1360
|
+
const stored = raw;
|
|
1361
|
+
const settings = {};
|
|
1362
|
+
if (isAgentLabelWord(stored.word)) settings.word = stored.word;
|
|
1363
|
+
if (typeof stored.name === "string" && nameProblem(stored.name) === void 0) settings.name = stored.name;
|
|
1364
|
+
if (Array.isArray(stored.rules) && stored.rules.every(isRule)) settings.rules = stored.rules.map((rule) => ({ ...rule }));
|
|
1365
|
+
if (typeof stored.slackIcons === "object" && stored.slackIcons !== null) {
|
|
1366
|
+
const icons = Object.entries(stored.slackIcons).filter((entry) => typeof entry[1] === "string" && EMOJI_NAME.test(entry[1]));
|
|
1367
|
+
if (icons.length > 0) settings.slackIcons = Object.fromEntries(icons);
|
|
1368
|
+
}
|
|
1369
|
+
return settings;
|
|
1370
|
+
}
|
|
1371
|
+
/** The person's label settings from a loaded configuration, read as `readAgentLabelSettings` reads them. */
|
|
1372
|
+
function agentLabelOf(config) {
|
|
1373
|
+
return readAgentLabelSettings(config.agentLabel);
|
|
1374
|
+
}
|
|
1153
1375
|
/** Every first-party channel, the core first: what core knows about each without installing it. */
|
|
1154
1376
|
const CHANNEL_SNAPSHOT = [
|
|
1155
1377
|
{
|
|
@@ -1319,7 +1541,11 @@ const CHANNEL_SNAPSHOT = [
|
|
|
1319
1541
|
word: "slack",
|
|
1320
1542
|
can: "post to Slack"
|
|
1321
1543
|
},
|
|
1322
|
-
hosts: [
|
|
1544
|
+
hosts: [
|
|
1545
|
+
"slack.com",
|
|
1546
|
+
"files.slack.com",
|
|
1547
|
+
"avatars.slack-edge.com"
|
|
1548
|
+
],
|
|
1323
1549
|
approve: "agent-slack approve",
|
|
1324
1550
|
skills: {
|
|
1325
1551
|
prefix: "slack-",
|
|
@@ -3237,6 +3463,67 @@ function commandLinePackage(command, args) {
|
|
|
3237
3463
|
const line = [command, ...withoutPathOptions(args).words].join(" ");
|
|
3238
3464
|
return /(@[\w.-]+\/[\w.-]+|(?<=\s)[\w.-]+-mcp)(?=@|\s|$)/.exec(line)?.[1];
|
|
3239
3465
|
}
|
|
3466
|
+
/**
|
|
3467
|
+
* The environment a child process is started with: `env` as it is, and on Windows also
|
|
3468
|
+
* `NoDefaultCurrentDirectoryInExePath=1`, so that the child — `rundll32.exe`, a Node started again — never takes a
|
|
3469
|
+
* program from its current folder either. Elsewhere nothing is added: a Unix shell looks in the current folder only
|
|
3470
|
+
* when `PATH` says to.
|
|
3471
|
+
*/
|
|
3472
|
+
function childEnvironment(env = process.env, platform = process.platform) {
|
|
3473
|
+
if (platform !== "win32") return env;
|
|
3474
|
+
return {
|
|
3475
|
+
...env,
|
|
3476
|
+
NoDefaultCurrentDirectoryInExePath: "1"
|
|
3477
|
+
};
|
|
3478
|
+
}
|
|
3479
|
+
/**
|
|
3480
|
+
* The directories of a search path that name a folder on their own: absolute, and on Windows on a drive. An empty
|
|
3481
|
+
* entry means the current folder to a Unix shell, and a relative one is read against it; either would make the program
|
|
3482
|
+
* found — and run — depend on where this was started, which is where a download may have saved a stranger's file.
|
|
3483
|
+
*/
|
|
3484
|
+
function absoluteSearchPath(entries, platform = process.platform) {
|
|
3485
|
+
return entries.filter((entry) => platform === "win32" ? /^[A-Za-z]:[\\/]/.test(entry) : entry !== "" && path.posix.isAbsolute(entry));
|
|
3486
|
+
}
|
|
3487
|
+
/** This Node's real file, or its path as reported when that cannot be resolved. */
|
|
3488
|
+
function realFile(path) {
|
|
3489
|
+
try {
|
|
3490
|
+
return realpathSync(path);
|
|
3491
|
+
} catch {
|
|
3492
|
+
return path;
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
/** Two real paths are one file: compared without case on Windows, whose paths are. */
|
|
3496
|
+
function sameRealPath(a, b, platform) {
|
|
3497
|
+
return platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
3498
|
+
}
|
|
3499
|
+
/** The names `node` is found by in one folder of PATH, as a shell finds it: with each PATHEXT extension on Windows. */
|
|
3500
|
+
function nodeNames(env, platform) {
|
|
3501
|
+
if (platform !== "win32") return ["node"];
|
|
3502
|
+
return (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean).map((extension) => `node${extension.toLowerCase()}`);
|
|
3503
|
+
}
|
|
3504
|
+
/**
|
|
3505
|
+
* The path a command names this Node by: the first `node` on `env`'s PATH that is the same real file as
|
|
3506
|
+
* `process.execPath`, as PATH spells it; otherwise `process.execPath`. With no `env`, nothing is looked for.
|
|
3507
|
+
*
|
|
3508
|
+
* Read when a command is located, as everything else in it is (D6): a link removed or repointed later is what the
|
|
3509
|
+
* command then meets. Only absolute folders of PATH are read, as the registrations' own search reads them.
|
|
3510
|
+
*/
|
|
3511
|
+
function nodeProgram(env, platform = process.platform) {
|
|
3512
|
+
const own = process.execPath;
|
|
3513
|
+
if (env === void 0) return own;
|
|
3514
|
+
const real = realFile(own);
|
|
3515
|
+
const search = env.PATH ?? env.Path ?? "";
|
|
3516
|
+
for (const directory of absoluteSearchPath(search.split(delimiter), platform)) for (const name of nodeNames(env, platform)) {
|
|
3517
|
+
const candidate = join(directory, name);
|
|
3518
|
+
try {
|
|
3519
|
+
if (!statSync(candidate).isFile()) continue;
|
|
3520
|
+
accessSync(candidate, constants$1.X_OK);
|
|
3521
|
+
if (!sameRealPath(realpathSync(candidate), real, platform)) continue;
|
|
3522
|
+
return sameRealPath(candidate, own, platform) ? own : candidate;
|
|
3523
|
+
} catch {}
|
|
3524
|
+
}
|
|
3525
|
+
return own;
|
|
3526
|
+
}
|
|
3240
3527
|
const RECEIPT_VERSION = 1;
|
|
3241
3528
|
const RECEIPT_FILE = "managed-shims.json";
|
|
3242
3529
|
const MUTATION_LOCK_FILE = "managed-shims.lock";
|
|
@@ -3574,27 +3861,6 @@ async function removeManagedShimsForRuntimes(context, runtimes, options = {}) {
|
|
|
3574
3861
|
};
|
|
3575
3862
|
});
|
|
3576
3863
|
}
|
|
3577
|
-
/**
|
|
3578
|
-
* The environment a child process is started with: `env` as it is, and on Windows also
|
|
3579
|
-
* `NoDefaultCurrentDirectoryInExePath=1`, so that the child — `rundll32.exe`, a Node started again — never takes a
|
|
3580
|
-
* program from its current folder either. Elsewhere nothing is added: a Unix shell looks in the current folder only
|
|
3581
|
-
* when `PATH` says to.
|
|
3582
|
-
*/
|
|
3583
|
-
function childEnvironment(env = process.env, platform = process.platform) {
|
|
3584
|
-
if (platform !== "win32") return env;
|
|
3585
|
-
return {
|
|
3586
|
-
...env,
|
|
3587
|
-
NoDefaultCurrentDirectoryInExePath: "1"
|
|
3588
|
-
};
|
|
3589
|
-
}
|
|
3590
|
-
/**
|
|
3591
|
-
* The directories of a search path that name a folder on their own: absolute, and on Windows on a drive. An empty
|
|
3592
|
-
* entry means the current folder to a Unix shell, and a relative one is read against it; either would make the program
|
|
3593
|
-
* found — and run — depend on where this was started, which is where a download may have saved a stranger's file.
|
|
3594
|
-
*/
|
|
3595
|
-
function absoluteSearchPath(entries, platform = process.platform) {
|
|
3596
|
-
return entries.filter((entry) => platform === "win32" ? /^[A-Za-z]:[\\/]/.test(entry) : entry !== "" && path.posix.isAbsolute(entry));
|
|
3597
|
-
}
|
|
3598
3864
|
/** The install options that pin a server to one account: the generic one, and Gmail's and Slack's own names. */
|
|
3599
3865
|
const PIN_OPTIONS = Object.freeze([
|
|
3600
3866
|
"account",
|
|
@@ -5192,62 +5458,6 @@ const CONVERSION_HOOKS = Symbol("agentcomms.configConversionHooks");
|
|
|
5192
5458
|
* by path can name.
|
|
5193
5459
|
*/
|
|
5194
5460
|
const SEND_POLICY_HOOKS = Symbol("agentcomms.sendPolicyHooks");
|
|
5195
|
-
/**
|
|
5196
|
-
* One table of dangerous characters, shared by the inbound sanitiser (which strips them from sender-controlled text)
|
|
5197
|
-
* and the preview renderer (which makes them visible), so the two can never disagree.
|
|
5198
|
-
*/
|
|
5199
|
-
/** Zero-width and formatting characters, bidi controls, variation selectors and Unicode tag characters. */
|
|
5200
|
-
const INVISIBLE_RANGES = [
|
|
5201
|
-
[173, 173],
|
|
5202
|
-
[847, 847],
|
|
5203
|
-
[1564, 1564],
|
|
5204
|
-
[4447, 4448],
|
|
5205
|
-
[6068, 6069],
|
|
5206
|
-
[6155, 6159],
|
|
5207
|
-
[8203, 8207],
|
|
5208
|
-
[8232, 8233],
|
|
5209
|
-
[8234, 8238],
|
|
5210
|
-
[8288, 8292],
|
|
5211
|
-
[8294, 8303],
|
|
5212
|
-
[12644, 12644],
|
|
5213
|
-
[65024, 65039],
|
|
5214
|
-
[65279, 65279],
|
|
5215
|
-
[65440, 65440],
|
|
5216
|
-
[917504, 917631]
|
|
5217
|
-
];
|
|
5218
|
-
function isInvisible(codePoint) {
|
|
5219
|
-
for (const [from, to] of INVISIBLE_RANGES) if (codePoint >= from && codePoint <= to) return true;
|
|
5220
|
-
return false;
|
|
5221
|
-
}
|
|
5222
|
-
/**
|
|
5223
|
-
* C0 controls except tab and newline (ESC, and so every ANSI/OSC sequence, starts here), DEL, and C1 controls (CSI
|
|
5224
|
-
* among them). A lone carriage return counts: it moves a terminal cursor back over text already printed.
|
|
5225
|
-
*/
|
|
5226
|
-
function isControl(codePoint) {
|
|
5227
|
-
if (codePoint === 9 || codePoint === 10) return false;
|
|
5228
|
-
return codePoint < 32 || codePoint === 127 || codePoint >= 128 && codePoint <= 159;
|
|
5229
|
-
}
|
|
5230
|
-
function isDangerous(codePoint) {
|
|
5231
|
-
return isControl(codePoint) || isInvisible(codePoint);
|
|
5232
|
-
}
|
|
5233
|
-
/**
|
|
5234
|
-
* Strips control characters (ESC, CSI, OSC and the rest), DEL, lone carriage returns, zero-width, bidi-control and tag
|
|
5235
|
-
* characters from sender-controlled text; returns the text and how many were removed. CRLF becomes LF first.
|
|
5236
|
-
*
|
|
5237
|
-
* This lives beside the table rather than in the sanitiser because `neutralise` needs it too, and the two must not
|
|
5238
|
-
* drift: a pattern that looks for `</untrusted-content` cannot see it through a zero-width space, so stripping
|
|
5239
|
-
* has to happen before any such pattern runs, on every path, not only on the ones that render a body.
|
|
5240
|
-
*/
|
|
5241
|
-
function stripInvisible(text) {
|
|
5242
|
-
let removed = 0;
|
|
5243
|
-
let out = "";
|
|
5244
|
-
for (const char of text.replace(/\r\n/g, "\n")) if (isDangerous(char.codePointAt(0) ?? 0)) removed += 1;
|
|
5245
|
-
else out += char;
|
|
5246
|
-
return {
|
|
5247
|
-
text: out,
|
|
5248
|
-
removed
|
|
5249
|
-
};
|
|
5250
|
-
}
|
|
5251
5461
|
const WINDOWS_RESERVED$1 = /^(con|prn|aux|nul|conin\$|conout\$|com[0-9¹²³]|lpt[0-9¹²³])$/i;
|
|
5252
5462
|
const UNSAFE_FILENAME_CHARS = /[/\\\u0000-\u001f\u007f<>:"|?*]/g;
|
|
5253
5463
|
/** Truncates a string to at most `maxBytes` UTF-8 bytes without splitting a code point. */
|
|
@@ -6586,11 +6796,39 @@ function classifyChange(before, after) {
|
|
|
6586
6796
|
if (before.secrets === void 0 && after.secrets !== void 0 && after.secrets.store !== "keychain") {
|
|
6587
6797
|
if (holdsSecrets(before)) loosen("secrets.store", secretsStoreOf(before), after.secrets.store);
|
|
6588
6798
|
}
|
|
6799
|
+
for (const loosening of classifyAgentLabelChange(agentLabelOf(before), agentLabelOf(after))) loosen(loosening.path, labelSettingValue(loosening, loosening.was), labelSettingValue(loosening, loosening.now));
|
|
6589
6800
|
return {
|
|
6590
6801
|
loosened: changes.map((change) => change.path),
|
|
6591
6802
|
changes
|
|
6592
6803
|
};
|
|
6593
6804
|
}
|
|
6805
|
+
/**
|
|
6806
|
+
* One label rule in words, as a preview lists it: `acme/slack in C0ACME for post/reply: me`. No comma inside, since a
|
|
6807
|
+
* preview joins a list of them with commas.
|
|
6808
|
+
*/
|
|
6809
|
+
function agentLabelRuleText(rule) {
|
|
6810
|
+
const where = rule.conversation === void 0 ? "" : ` in ${rule.conversation}`;
|
|
6811
|
+
const kinds = rule.kinds === void 0 ? "" : ` for ${rule.kinds.join("/")}`;
|
|
6812
|
+
return `${rule.account}${where}${kinds}: ${rule.as}`;
|
|
6813
|
+
}
|
|
6814
|
+
/** A label setting's value as a loosening carries it: the rules in words, the icons as they are, the rest as text. */
|
|
6815
|
+
function labelSettingValue(loosening, value) {
|
|
6816
|
+
if (value === void 0 || value === null) return void 0;
|
|
6817
|
+
if (loosening.path === "agentLabel.rules") return value.map(agentLabelRuleText);
|
|
6818
|
+
if (loosening.path === "agentLabel.slackIcons") return { ...value };
|
|
6819
|
+
return String(value);
|
|
6820
|
+
}
|
|
6821
|
+
/** The label settings a change can write, as the file holds them — raw, as `changedSettings` records every setting. */
|
|
6822
|
+
const LABEL_SETTINGS = [
|
|
6823
|
+
"word",
|
|
6824
|
+
"name",
|
|
6825
|
+
"rules",
|
|
6826
|
+
"slackIcons"
|
|
6827
|
+
];
|
|
6828
|
+
function storedAgentLabel(config) {
|
|
6829
|
+
const stored = config.agentLabel;
|
|
6830
|
+
return typeof stored === "object" && stored !== null && !Array.isArray(stored) ? stored : {};
|
|
6831
|
+
}
|
|
6594
6832
|
/** The settings of a mailbox, and of an account, that a change can write — the ones the classifier judges. */
|
|
6595
6833
|
const INBOX_SETTINGS = [
|
|
6596
6834
|
"sendPolicy",
|
|
@@ -6604,7 +6842,7 @@ const ACCOUNT_SETTINGS = [
|
|
|
6604
6842
|
];
|
|
6605
6843
|
/**
|
|
6606
6844
|
* Every setting that differs between `before` and `after`, loosened or tightened: the defaults, the secret store,
|
|
6607
|
-
* and each mailbox's and account's own settings — including those of one added or removed.
|
|
6845
|
+
* the agent label's settings, and each mailbox's and account's own settings — including those of one added or removed.
|
|
6608
6846
|
*
|
|
6609
6847
|
* What a change approval binds, beside its loosenings. A preview shows the whole change — "sends approved by never;
|
|
6610
6848
|
* changes approved by chat" — and binding only the loosenings let a claim drop the tightening a person read, since
|
|
@@ -6642,6 +6880,9 @@ function changedSettings(before, after) {
|
|
|
6642
6880
|
const defaultsAfter = after.defaults;
|
|
6643
6881
|
for (const key of [.../* @__PURE__ */ new Set([...Object.keys(defaultsBefore), ...Object.keys(defaultsAfter)])].sort()) record(`defaults.${key}`, defaultsBefore[key], defaultsAfter[key]);
|
|
6644
6882
|
record("secrets.store", before.secrets?.store, after.secrets?.store);
|
|
6883
|
+
const labelBefore = storedAgentLabel(before);
|
|
6884
|
+
const labelAfter = storedAgentLabel(after);
|
|
6885
|
+
for (const key of LABEL_SETTINGS) record(`agentLabel.${key}`, labelBefore[key], labelAfter[key]);
|
|
6645
6886
|
return changes;
|
|
6646
6887
|
}
|
|
6647
6888
|
/**
|
|
@@ -10090,7 +10331,7 @@ function folding(platform) {
|
|
|
10090
10331
|
return platform === "win32" ? (text) => text.toLowerCase() : (text) => text;
|
|
10091
10332
|
}
|
|
10092
10333
|
/** The package version, read from package.json at build time. */
|
|
10093
|
-
const VERSION = "0.
|
|
10334
|
+
const VERSION = "0.19.0";
|
|
10094
10335
|
/**
|
|
10095
10336
|
* Versions as semver writes and orders them — what an update compares, and what the daily update check's reader needs
|
|
10096
10337
|
* to decide whether the release it last heard of is newer than the one running.
|
|
@@ -10268,8 +10509,10 @@ const CHANNEL_PACKAGES = new Set(CHANNEL_SNAPSHOT.filter(({ manifest }) => manif
|
|
|
10268
10509
|
/**
|
|
10269
10510
|
* Locates the command that runs `request.target`'s CLI with `request.words`, or says why there is none.
|
|
10270
10511
|
*
|
|
10271
|
-
* The program is always this process's Node
|
|
10272
|
-
*
|
|
10512
|
+
* The program is always this process's Node, which must satisfy the target's `engines.node`; no interpreter named
|
|
10513
|
+
* anywhere else is run, or asked its version. It is named as PATH finds it — the first `node` on `request.env`'s PATH
|
|
10514
|
+
* that is the same real file as `process.execPath` — and otherwise by `process.execPath` (CUE-492): the real file only
|
|
10515
|
+
* identifies it, and `process.version` is that file's. A TypeScript entry gets `--experimental-strip-types`,
|
|
10273
10516
|
* which Node 22.12–22.17 needs, and `--experimental-transform-types` only when this source run was started with it;
|
|
10274
10517
|
* no other flag this process was started with is carried — debug, preload, condition and memory flags are not what the
|
|
10275
10518
|
* CLI needs. The suite directories the command uses go between the entry and its words, one canonical option each,
|
|
@@ -10310,6 +10553,7 @@ function locateCliCommand(request, runtime = {
|
|
|
10310
10553
|
return fail("caller", `${nearest.root} cannot be resolved`);
|
|
10311
10554
|
}
|
|
10312
10555
|
if (!isAbsolute(process.execPath)) return fail("interpreter", `this Node, ${process.execPath}, is not a full path`);
|
|
10556
|
+
const program = nodeProgram(request.env);
|
|
10313
10557
|
const pins = {};
|
|
10314
10558
|
for (const key of request.uses) pins[key] = request.paths[key];
|
|
10315
10559
|
let words;
|
|
@@ -10318,7 +10562,7 @@ function locateCliCommand(request, runtime = {
|
|
|
10318
10562
|
} catch (error) {
|
|
10319
10563
|
return fail("arguments", error instanceof Error ? error.message : String(error));
|
|
10320
10564
|
}
|
|
10321
|
-
const build = (found) => buildCommand(target, found, words, runtime, callerSource, platform);
|
|
10565
|
+
const build = (found) => buildCommand(target, found, words, runtime, callerSource, platform, program);
|
|
10322
10566
|
const located = (built, found, direction) => Object.freeze({
|
|
10323
10567
|
ok: true,
|
|
10324
10568
|
command: built.command,
|
|
@@ -10458,7 +10702,7 @@ function locateCliCommand(request, runtime = {
|
|
|
10458
10702
|
* Checks what was found and makes its command: the target's Node range against this Node, then the entry — the source
|
|
10459
10703
|
* CLI or the manifest's bin for the command it declares — as a readable file inside its package.
|
|
10460
10704
|
*/
|
|
10461
|
-
function buildCommand(target, found, words, runtime, callerSource, platform) {
|
|
10705
|
+
function buildCommand(target, found, words, runtime, callerSource, platform, program) {
|
|
10462
10706
|
const engines = found.manifest.engines;
|
|
10463
10707
|
const range = typeof engines === "object" && engines !== null ? engines.node : void 0;
|
|
10464
10708
|
if (typeof range !== "string") return {
|
|
@@ -10495,7 +10739,7 @@ function buildCommand(target, found, words, runtime, callerSource, platform) {
|
|
|
10495
10739
|
return {
|
|
10496
10740
|
ok: true,
|
|
10497
10741
|
command: new PrintedCommand(GATE, [
|
|
10498
|
-
|
|
10742
|
+
program,
|
|
10499
10743
|
...flags,
|
|
10500
10744
|
checked.path,
|
|
10501
10745
|
...words
|
|
@@ -10799,7 +11043,8 @@ function cliHandoffs(options) {
|
|
|
10799
11043
|
uses: usesOf(use),
|
|
10800
11044
|
paths,
|
|
10801
11045
|
platform,
|
|
10802
|
-
...registrations === void 0 ? {} : { registrations }
|
|
11046
|
+
...registrations === void 0 ? {} : { registrations },
|
|
11047
|
+
...options.env === void 0 ? {} : { env: options.env }
|
|
10803
11048
|
}, ...options.runtime === void 0 ? [] : [options.runtime]);
|
|
10804
11049
|
return result.ok ? result.command : result;
|
|
10805
11050
|
};
|
|
@@ -11441,7 +11686,11 @@ const SETTING_LABELS = {
|
|
|
11441
11686
|
"defaults.downloadsDir": "downloads folder",
|
|
11442
11687
|
"defaults.confirm.elicitationClients": "clients trusted to ask for a send approval",
|
|
11443
11688
|
"defaults.updateCheck": "daily update check",
|
|
11444
|
-
"secrets.store": "where credentials are kept"
|
|
11689
|
+
"secrets.store": "where credentials are kept",
|
|
11690
|
+
"agentLabel.word": "the word after your name in the agent label",
|
|
11691
|
+
"agentLabel.name": "the name in the agent label",
|
|
11692
|
+
"agentLabel.rules": "who each kind of message appears to come from",
|
|
11693
|
+
"agentLabel.slackIcons": "the icon of the agent’s Slack posts"
|
|
11445
11694
|
};
|
|
11446
11695
|
/**
|
|
11447
11696
|
* What the loosening means for the person, in their terms.
|
|
@@ -11466,6 +11715,10 @@ function meaning(field, after, scope) {
|
|
|
11466
11715
|
case "defaults.confirm.elicitationClients": return "these clients may ask you to approve a send in their own window";
|
|
11467
11716
|
case "defaults.updateCheck": return "nothing on this machine will ask npm for a newer release, or stop until one is installed";
|
|
11468
11717
|
case "secrets.store": return "credentials will move out of the system keychain into files on this disk";
|
|
11718
|
+
case "agentLabel.word": return "what an agent sends will carry this word after your name";
|
|
11719
|
+
case "agentLabel.name": return after === null ? "where a channel has no name of its own for you, an agent’s message will be refused until a name is set" : "where a channel has no name of its own for you, what an agent sends will carry this name";
|
|
11720
|
+
case "agentLabel.rules": return "what a rule sends as `me` goes out as you, with no label: it will look as if you wrote it yourself";
|
|
11721
|
+
case "agentLabel.slackIcons": return "the app’s Slack posts will show this icon beside your name";
|
|
11469
11722
|
default: return "";
|
|
11470
11723
|
}
|
|
11471
11724
|
}
|
|
@@ -12814,7 +13067,8 @@ async function withManagedShim(context, product, installed) {
|
|
|
12814
13067
|
...context.core.paths,
|
|
12815
13068
|
downloadsDir: context.core.paths.dataDir
|
|
12816
13069
|
},
|
|
12817
|
-
platform: context.platform
|
|
13070
|
+
platform: context.platform,
|
|
13071
|
+
env: context.env
|
|
12818
13072
|
});
|
|
12819
13073
|
if (!located.ok) throw new CommsError("CONFIG", `the managed CLI command could not be located: ${located.message}`);
|
|
12820
13074
|
shim = await (context.writeShim ?? writeManagedShim)(shimContext, {
|
|
@@ -12877,7 +13131,7 @@ function serverPruneChange(core, env, request, own) {
|
|
|
12877
13131
|
};
|
|
12878
13132
|
const { packageName, version } = ownProduct(request.channel, own) ?? {
|
|
12879
13133
|
packageName: serverFacts(request.channel).packageName,
|
|
12880
|
-
version: "0.
|
|
13134
|
+
version: "0.19.0"
|
|
12881
13135
|
};
|
|
12882
13136
|
const product = {
|
|
12883
13137
|
packageName,
|
|
@@ -14611,7 +14865,7 @@ function terminalUpdateHooks(core, env, options) {
|
|
|
14611
14865
|
};
|
|
14612
14866
|
}
|
|
14613
14867
|
//#endregion
|
|
14614
|
-
//#region ../core/dist/tool-arguments-
|
|
14868
|
+
//#region ../core/dist/tool-arguments-BkdTZEb8.mjs
|
|
14615
14869
|
const APPLIED = Symbol.for("agentcomms.strictToolArguments");
|
|
14616
14870
|
const TARGET = "draft-2020-12";
|
|
14617
14871
|
/**
|
|
@@ -27012,16 +27266,34 @@ function parseFrom(raw) {
|
|
|
27012
27266
|
if (!angled) return {
|
|
27013
27267
|
from: fromAddress,
|
|
27014
27268
|
fromAddress,
|
|
27015
|
-
fromDomain
|
|
27269
|
+
fromDomain,
|
|
27270
|
+
name: ""
|
|
27016
27271
|
};
|
|
27017
27272
|
const name = headerText((angled[1] ?? "").trim().replace(/^"(.*)"$/, "$1"), "the From name", 100);
|
|
27018
27273
|
if (/[<>"@,;]/.test(name)) throw bad("the From name may not contain < > \" @ , or ;");
|
|
27019
27274
|
return {
|
|
27020
27275
|
from: name ? `${name} <${fromAddress}>` : fromAddress,
|
|
27021
27276
|
fromAddress,
|
|
27022
|
-
fromDomain
|
|
27277
|
+
fromDomain,
|
|
27278
|
+
name
|
|
27023
27279
|
};
|
|
27024
27280
|
}
|
|
27281
|
+
/**
|
|
27282
|
+
* The From an agent's send carries (design 2026-10-15-agent-label, R1): L1's phrase, quoted — unquoted, `(agent)` is
|
|
27283
|
+
* an RFC 5322 comment, which a mail client may drop — then the address. `parseFrom` never writes a quote, so a quoted
|
|
27284
|
+
* From is always one this package labelled.
|
|
27285
|
+
*/
|
|
27286
|
+
function labelledFrom(phrase, fromAddress) {
|
|
27287
|
+
return `"${phrase}" <${fromAddress}>`;
|
|
27288
|
+
}
|
|
27289
|
+
/** Who a prepared From says the email comes from: the label this package wrote, with its phrase, or the person. */
|
|
27290
|
+
function identityOfFrom(from) {
|
|
27291
|
+
const phrase = /^"(.+)" <[^<>]+>$/.exec(from)?.[1];
|
|
27292
|
+
return phrase !== void 0 && AGENT_LABEL_WORDS.some((word) => phrase.endsWith(` (${word})`)) ? {
|
|
27293
|
+
identity: "agent",
|
|
27294
|
+
phrase
|
|
27295
|
+
} : { identity: "me" };
|
|
27296
|
+
}
|
|
27025
27297
|
/** A body: controls other than newline and tab are refused, invisible characters are counted for a warning. */
|
|
27026
27298
|
function bodyText(value) {
|
|
27027
27299
|
const text = value.replace(/\r\n/g, "\n");
|
|
@@ -27081,7 +27353,7 @@ function linksInText(text) {
|
|
|
27081
27353
|
/** Builds and checks the outbound message. Refuses anything an agent could not show a person in full. */
|
|
27082
27354
|
async function buildMessage(input, options) {
|
|
27083
27355
|
const warnings = [];
|
|
27084
|
-
const { from, fromAddress, fromDomain } = parseFrom(input.from);
|
|
27356
|
+
const { from, fromAddress, fromDomain, name: fromName } = parseFrom(input.from);
|
|
27085
27357
|
const to = list(input.to, "to");
|
|
27086
27358
|
const cc = list(input.cc, "cc");
|
|
27087
27359
|
const bcc = list(input.bcc, "bcc");
|
|
@@ -27167,6 +27439,7 @@ async function buildMessage(input, options) {
|
|
|
27167
27439
|
references,
|
|
27168
27440
|
scheduledAt
|
|
27169
27441
|
},
|
|
27442
|
+
fromName,
|
|
27170
27443
|
links,
|
|
27171
27444
|
warnings
|
|
27172
27445
|
};
|
|
@@ -28946,7 +29219,7 @@ function previewOf(options) {
|
|
|
28946
29219
|
...options.study.warnings,
|
|
28947
29220
|
...options.built.warnings
|
|
28948
29221
|
];
|
|
28949
|
-
|
|
29222
|
+
const preview = {
|
|
28950
29223
|
recipients: {
|
|
28951
29224
|
from: message.from,
|
|
28952
29225
|
to: message.to,
|
|
@@ -28971,7 +29244,51 @@ function previewOf(options) {
|
|
|
28971
29244
|
links: options.built.links,
|
|
28972
29245
|
warnings,
|
|
28973
29246
|
policy: describePolicy(options.effective, options.approvalId, options.flags, options.handoffs)
|
|
28974
|
-
}
|
|
29247
|
+
};
|
|
29248
|
+
return `${identityLine(identityOfFrom(message.from))}\n${renderMessagePreview(preview)}`;
|
|
29249
|
+
}
|
|
29250
|
+
/** L3: the first line of a preview — the label recipients will see, or that there is none. */
|
|
29251
|
+
function identityLine(identity) {
|
|
29252
|
+
if (identity.identity === "me" || identity.phrase === void 0) return UNLABELLED_PREVIEW_LINE;
|
|
29253
|
+
return `Sent as ${truncateDisplay(identity.phrase, 120)}: labelled in the From line, so whoever receives it can tell your agent sent it`;
|
|
29254
|
+
}
|
|
29255
|
+
/**
|
|
29256
|
+
* R1: who the email appears to come from, and its From. For `agent`, L1's phrase from the caller's From name, or the
|
|
29257
|
+
* core's `agentLabel.name` when the caller gave a bare address, refused when there is neither or it does not fit; for
|
|
29258
|
+
* `me`, the From as the caller gave it.
|
|
29259
|
+
*/
|
|
29260
|
+
function labelled(config, account, accountId, built, requested) {
|
|
29261
|
+
if (requested !== void 0 && requested !== "agent" && requested !== "me") throw new CommsError("USAGE", `"${requested}" is not who an email appears to come from`, { hint: "Use agent or me." });
|
|
29262
|
+
const settings = agentLabelOf(config);
|
|
29263
|
+
if (resolveIdentity(settings, {
|
|
29264
|
+
account,
|
|
29265
|
+
accountId,
|
|
29266
|
+
kind: "send",
|
|
29267
|
+
requested,
|
|
29268
|
+
mail: { recipients: uniqueRecipients(built.message) }
|
|
29269
|
+
}).identity === "me") return {
|
|
29270
|
+
message: built.message,
|
|
29271
|
+
identity: "me"
|
|
29272
|
+
};
|
|
29273
|
+
const name = built.fromName !== "" ? built.fromName : settings.name;
|
|
29274
|
+
if (name === void 0) throw new CommsError("USAGE", "this email is to be labelled as sent by your agent, and there is no name to label", { hint: "Give From a name, `Name <you@example.com>`, or set one with `label name`." });
|
|
29275
|
+
let phrase;
|
|
29276
|
+
try {
|
|
29277
|
+
phrase = labelPhrase(name, settings.word ?? "agent", 100);
|
|
29278
|
+
} catch (error) {
|
|
29279
|
+
if (!(error instanceof CommsError) || error.hint !== void 0) throw error;
|
|
29280
|
+
throw new CommsError(error.code, error.message, {
|
|
29281
|
+
hint: "Give From a name the label can use, or set one with `label name` and give From a bare address.",
|
|
29282
|
+
cause: error
|
|
29283
|
+
});
|
|
29284
|
+
}
|
|
29285
|
+
return {
|
|
29286
|
+
message: {
|
|
29287
|
+
...built.message,
|
|
29288
|
+
from: labelledFrom(phrase, built.message.fromAddress)
|
|
29289
|
+
},
|
|
29290
|
+
identity: "agent"
|
|
29291
|
+
};
|
|
28975
29292
|
}
|
|
28976
29293
|
async function attachPolicy(context) {
|
|
28977
29294
|
const config = await context.config();
|
|
@@ -29000,11 +29317,17 @@ async function prepareSend(context, name, input) {
|
|
|
29000
29317
|
"--send",
|
|
29001
29318
|
"confirm"
|
|
29002
29319
|
]), (command) => `A person can change it with ${command}.`) });
|
|
29003
|
-
const
|
|
29320
|
+
const { as: requested, ...email } = input;
|
|
29321
|
+
const composed = await buildMessage(email, {
|
|
29004
29322
|
now: context.now(),
|
|
29005
29323
|
attach: await attachPolicy(context)
|
|
29006
29324
|
});
|
|
29007
|
-
const domainNote = await checkFromDomain(context, named,
|
|
29325
|
+
const domainNote = await checkFromDomain(context, named, composed.message.fromDomain);
|
|
29326
|
+
const { message: outbound, identity } = labelled(config, named.name, named.account.id, composed, requested);
|
|
29327
|
+
const built = {
|
|
29328
|
+
...composed,
|
|
29329
|
+
message: outbound
|
|
29330
|
+
};
|
|
29008
29331
|
const study = await studyRecipients(context, named, built.message, config.defaults.riskEscalation);
|
|
29009
29332
|
const requiredPolicy = study.flags.length > 0 ? "confirm" : "chat";
|
|
29010
29333
|
const effective = stricterPolicy(livePolicy, requiredPolicy);
|
|
@@ -29046,6 +29369,7 @@ async function prepareSend(context, name, input) {
|
|
|
29046
29369
|
return {
|
|
29047
29370
|
approvalId: record.approvalId,
|
|
29048
29371
|
account: name,
|
|
29372
|
+
identity,
|
|
29049
29373
|
preview: previewOf({
|
|
29050
29374
|
name,
|
|
29051
29375
|
approvalId: record.approvalId,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as checkForUpdates, A as removeAccountChange, B as strictToolArguments, D as policyChange, E as policyApprovalRefusal, F as CHANGE_POLICIES, G as SEND_LOOKUP, H as CommsError, Ht as VERSION, I as MODES, L as SEND_POLICIES, M as showAccount, Mt as waitCallOptions, Nt as waitForApproval, O as policyReport, Ot as toCommsError, P as ResendContext, Q as changeToolResult, _ as showSentEmail, _t as refuseUnclaimedApproval, c as downloadReceived, d as listReceived, f as listScheduled, g as showReceived, i as prepareSend, jt as updateToolGate, k as policyWanted, l as getMetrics, m as listSuppressions, n as executeSend, o as sendStatus, ot as gatedChange, p as listSentEmails, s as cancelScheduledChange, u as listDomains, w as listAccounts, x as runDoctor } from "./send-
|
|
1
|
+
import { $ as checkForUpdates, A as removeAccountChange, B as strictToolArguments, D as policyChange, E as policyApprovalRefusal, F as CHANGE_POLICIES, G as SEND_LOOKUP, H as CommsError, Ht as VERSION, I as MODES, L as SEND_POLICIES, M as showAccount, Mt as waitCallOptions, Nt as waitForApproval, O as policyReport, Ot as toCommsError, P as ResendContext, Q as changeToolResult, _ as showSentEmail, _t as refuseUnclaimedApproval, c as downloadReceived, d as listReceived, f as listScheduled, g as showReceived, i as prepareSend, jt as updateToolGate, k as policyWanted, l as getMetrics, m as listSuppressions, n as executeSend, o as sendStatus, ot as gatedChange, p as listSentEmails, s as cancelScheduledChange, u as listDomains, w as listAccounts, x as runDoctor } from "./send-Ch-s9Q6H.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
|
|
@@ -314,7 +314,7 @@ async function createResendMcpServer(options = {}) {
|
|
|
314
314
|
})));
|
|
315
315
|
server.registerTool("resend_send_prepare", {
|
|
316
316
|
title: "Prepare an email",
|
|
317
|
-
description: "Build an email and return the exact preview — every recipient, BCC included, the reach and the From domain — with an approval id. Nothing is sent. Show the preview in full and wait for the person’s yes.",
|
|
317
|
+
description: "Build an email and return the exact preview — every recipient, BCC included, the reach and the From domain — with an approval id. Nothing is sent. Its From is labelled `<name> (agent)` — the From’s name, or the person’s label name for a bare address — unless `as` is `me`, and the preview’s first line says which. Show the preview in full and wait for the person’s yes.",
|
|
318
318
|
inputSchema: {
|
|
319
319
|
...accountArg,
|
|
320
320
|
from: string().describe("the sender at a verified domain: `Name <you@example.com>` or a bare address"),
|
|
@@ -328,7 +328,8 @@ async function createResendMcpServer(options = {}) {
|
|
|
328
328
|
replyTo: array(string()).optional(),
|
|
329
329
|
inReplyTo: string().optional().describe("the Message-ID this replies to, to keep the thread"),
|
|
330
330
|
references: array(string()).optional(),
|
|
331
|
-
scheduledAt: string().optional().describe("send later: an ISO 8601 time with a zone, at most 30 days ahead")
|
|
331
|
+
scheduledAt: string().optional().describe("send later: an ISO 8601 time with a zone, at most 30 days ahead"),
|
|
332
|
+
as: string().min(1).optional().describe("who it appears to come from: agent (From labelled \"<name> (agent)\") or me (From as given). Pass `me` only when the person said so for this email; left out, their rules decide, and then agent")
|
|
332
333
|
},
|
|
333
334
|
annotations: {
|
|
334
335
|
readOnlyHint: false,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as createResendMcpServer } from "./server-
|
|
1
|
+
import { t as createResendMcpServer } from "./server-ERFs-XiT.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.
|
|
3
|
+
"version": "0.19.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.
|
|
34
|
+
"@agentcomms/core": "0.19.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": "
|
|
110
|
+
"gitHead": "29a7121620a9ef305a4260f193c40db1980312bd"
|
|
111
111
|
}
|