proton-mail-bridge-client 2.1.5 → 2.1.7

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/index.d.ts CHANGED
@@ -65,6 +65,9 @@ export declare function redactDraftAttachmentsForListing(draft: DraftRecord): {
65
65
  remoteDraft?: import("./types/index.js").RemoteDraftRef;
66
66
  lastSendResult?: import("./types/index.js").DraftSendResult;
67
67
  };
68
+ export declare function truncateDraftBodyForResponse<T extends {
69
+ body: string;
70
+ }>(draft: T): T;
68
71
  export declare function redactQueueRecordAttachments<T extends {
69
72
  payload: SendEmailInput;
70
73
  }>(record: T): T;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -385,12 +385,14 @@ const TOOLS = [
385
385
  },
386
386
  {
387
387
  name: "list_drafts",
388
- description: "List all locally saved drafts with their status, subject, and timestamps. Attachment content is omitted here (filename/type/size only) to keep this unbounded listing from blowing up on large attachments — use get_draft for the full attachment content of one draft. Use to review in-progress or unsent messages. Does NOT list drafts stored only on the Proton server — use list_remote_drafts for those. Prefer get_draft when you already have a draftId and need the full content.",
388
+ description: "List all locally saved drafts with their status, subject, and timestamps. Attachment content is omitted here (filename/type/size only) — use get_draft for the full attachment content of one draft. Each draft's body is also truncated to a short preview (bodyTruncated:true, bodyLength gives the real size) — use get_draft for the full body of one draft. Use to review in-progress or unsent messages. Does NOT list drafts stored only on the Proton server — use list_remote_drafts for those. Prefer get_draft when you already have a draftId and need the full content.",
389
389
  annotations: { readOnlyHint: true },
390
390
  inputSchema: {
391
391
  type: "object",
392
392
  properties: {
393
393
  includeSent: { type: "boolean", description: "Include drafts already sent.", default: false },
394
+ limit: { type: "number", description: "Maximum drafts to return. Omit to return every draft (still with truncated bodies/attachments) — set this when you only need a bounded page." },
395
+ offset: { type: "number", description: "Number of drafts to skip, newest-updated first.", default: 0 },
394
396
  },
395
397
  },
396
398
  },
@@ -2277,6 +2279,34 @@ export function redactDraftAttachmentsForListing(draft) {
2277
2279
  })),
2278
2280
  };
2279
2281
  }
2282
+ // How much of a draft's body survives into a mutation/listing response before
2283
+ // it's truncated — long enough to recognize the draft at a glance, short
2284
+ // enough that a large body (a long thread quoted into a reply, a big pasted
2285
+ // table) doesn't dominate the response the way redactDraftAttachmentsForListing
2286
+ // already stops a base64 attachment from doing.
2287
+ const DRAFT_BODY_PREVIEW_LENGTH = 500;
2288
+ // Found live (external review, repeated across three rounds): create_draft,
2289
+ // update_draft, create_reply_draft, create_forward_draft,
2290
+ // create_thread_reply_draft, sync_draft_to_remote, and list_drafts all echoed
2291
+ // the draft's COMPLETE body back — confirmed live at ~40k tokens for a single
2292
+ // update_draft call that only changed the subject of a ~202 KB draft, and
2293
+ // ~261k tokens for list_drafts with 107 drafts. The caller supplied the body
2294
+ // itself (create/update) or already has it from a prior call (list) — same
2295
+ // "caller already knows what it sent" reasoning as attachment redaction, just
2296
+ // for the text body instead of base64. get_draft deliberately keeps the full
2297
+ // body — it's the "let me look at this one draft" call, not a repeated-edit
2298
+ // or bulk-listing one.
2299
+ export function truncateDraftBodyForResponse(draft) {
2300
+ if (draft.body.length <= DRAFT_BODY_PREVIEW_LENGTH) {
2301
+ return draft;
2302
+ }
2303
+ return {
2304
+ ...draft,
2305
+ body: `${draft.body.slice(0, DRAFT_BODY_PREVIEW_LENGTH)}…`,
2306
+ bodyLength: draft.body.length,
2307
+ bodyTruncated: true,
2308
+ };
2309
+ }
2280
2310
  // Same fix as redactDraftAttachmentsForListing, for list_scheduled_sends — a
2281
2311
  // queued send's payload carries the same full base64 EmailAttachmentInput
2282
2312
  // content (found live: ~955k tokens for a single 1 MiB attachment on ONE
@@ -4041,9 +4071,9 @@ export function createServer(config, options = {}) {
4041
4071
  return presentDraft(createDraftBundle, remoteSync ? { ...synced.draft, remoteSync } : synced.draft);
4042
4072
  });
4043
4073
  // Same fix as update_draft's identical bloat — see its comment. The
4044
- // caller already knows what it attached; get_draft is the "let me see
4045
- // the full attachment content" call, not this one.
4046
- return createTextResult(redactDraftAttachmentsForListing(result), false, [
4074
+ // caller already knows what it attached (and what body it just sent);
4075
+ // get_draft is the "let me see the full content" call, not this one.
4076
+ return createTextResult(truncateDraftBodyForResponse(redactDraftAttachmentsForListing(result)), false, [
4047
4077
  draftSource(result),
4048
4078
  ...(result.remoteDraft?.emailId
4049
4079
  ? [
@@ -4096,6 +4126,14 @@ export function createServer(config, options = {}) {
4096
4126
  subject: prefixedSubject(detail.subject, "Re:"),
4097
4127
  body: buildReplyText(detail, body),
4098
4128
  isHtml,
4129
+ // Found live (external review): a `from` naming a real alias NOT
4130
+ // itself a separately-configured account (e.g. a genuine alias
4131
+ // reachable only via header override, same as send_email's own
4132
+ // fallback) picked the right draftStore bundle above but never
4133
+ // actually stored the address on the draft — draft.from came back
4134
+ // undefined, so a later send_draft used the account's default
4135
+ // sender instead of the alias the caller asked for.
4136
+ from: optionalString(args, "from"),
4099
4137
  inReplyTo: detail.messageId,
4100
4138
  references: detail.messageId ? [detail.messageId] : undefined,
4101
4139
  attachments,
@@ -4118,7 +4156,7 @@ export function createServer(config, options = {}) {
4118
4156
  return presentDraft(createReplyDraftBundle, remoteSync ? { ...synced.draft, remoteSync } : synced.draft);
4119
4157
  });
4120
4158
  // Same fix as create_draft/update_draft's identical bloat — see their comments.
4121
- return createTextResult(redactDraftAttachmentsForListing(result), false, [
4159
+ return createTextResult(truncateDraftBodyForResponse(redactDraftAttachmentsForListing(result)), false, [
4122
4160
  draftSource(result),
4123
4161
  emailSource(detail),
4124
4162
  ...(result.remoteDraft?.emailId
@@ -4171,6 +4209,8 @@ export function createServer(config, options = {}) {
4171
4209
  subject: prefixedSubject(detail.subject, "Fwd:"),
4172
4210
  body: buildForwardText(detail, optionalString(args, "body")),
4173
4211
  isHtml: normalizeBoolean(args.isHtml, false),
4212
+ // Same fix as create_reply_draft — see its comment.
4213
+ from: optionalString(args, "from"),
4174
4214
  attachments,
4175
4215
  sourceEmailId: detail.id,
4176
4216
  sourceMessageId: detail.messageId,
@@ -4191,7 +4231,7 @@ export function createServer(config, options = {}) {
4191
4231
  return presentDraft(createForwardDraftBundle, remoteSync ? { ...synced.draft, remoteSync } : synced.draft);
4192
4232
  });
4193
4233
  // Same fix as create_draft/update_draft's identical bloat — see their comments.
4194
- return createTextResult(redactDraftAttachmentsForListing(result), false, [
4234
+ return createTextResult(truncateDraftBodyForResponse(redactDraftAttachmentsForListing(result)), false, [
4195
4235
  draftSource(result),
4196
4236
  emailSource(detail),
4197
4237
  ...(result.remoteDraft?.emailId
@@ -4210,10 +4250,23 @@ export function createServer(config, options = {}) {
4210
4250
  // drafts only — a non-primary account's drafts aren't listed here yet. Use
4211
4251
  // get_draft with that account's prefixed draftId if you already have one.
4212
4252
  const drafts = await draftStore.listDrafts(normalizeBoolean(args.includeSent, false));
4253
+ // Found live (external review): every draft's full body was returned
4254
+ // alongside the already-redacted attachments — confirmed live at ~261k
4255
+ // tokens for 107 drafts. Bodies are now truncated the same way a
4256
+ // mutation response's body is (see truncateDraftBodyForResponse). limit/
4257
+ // offset are optional and additive — omitting them still returns every
4258
+ // draft (just with truncated bodies/attachments), so this doesn't change
4259
+ // behavior for a caller not using the new params.
4260
+ const offset = normalizeLimit(args.offset, 0, 0, 10_000);
4261
+ const limit = typeof args.limit === "number" ? normalizeLimit(args.limit, drafts.length, 1, 10_000) : undefined;
4262
+ const page = limit !== undefined ? drafts.slice(offset, offset + limit) : drafts.slice(offset);
4213
4263
  return createTextResult({
4214
4264
  total: drafts.length,
4215
- drafts: drafts.map(redactDraftAttachmentsForListing),
4216
- }, false, drafts.map(draftSource));
4265
+ offset,
4266
+ returned: page.length,
4267
+ hasMore: offset + page.length < drafts.length,
4268
+ drafts: page.map((draft) => truncateDraftBodyForResponse(redactDraftAttachmentsForListing(draft))),
4269
+ }, false, page.map(draftSource));
4217
4270
  }
4218
4271
  case "list_remote_drafts": {
4219
4272
  // Scope limitation (follow-up round): stays scoped to the primary account's
@@ -4304,10 +4357,13 @@ export function createServer(config, options = {}) {
4304
4357
  // subject/body — the attachments usually aren't touched by this call at all, so
4305
4358
  // re-echoing their full base64 `content` (serialized twice by createTextResult,
4306
4359
  // same issue redactDraftAttachmentsForListing already fixed for list_drafts)
4307
- // burns tokens on every single edit for a draft that has any attachment. The
4308
- // caller already knows what it attached; get_draft remains full-content for the
4309
- // "let me look at this draft" case, which is a single call rather than a loop.
4310
- return createTextResult(redactDraftAttachmentsForListing(result), false, [
4360
+ // burns tokens on every single edit for a draft that has any attachment. Same
4361
+ // reasoning for the body itself confirmed live at ~40k tokens for a single
4362
+ // subject-only edit of a ~202 KB draft (see truncateDraftBodyForResponse). The
4363
+ // caller already knows what it attached and what body it just sent; get_draft
4364
+ // remains full-content for the "let me look at this draft" case, which is a
4365
+ // single call rather than a loop.
4366
+ return createTextResult(truncateDraftBodyForResponse(redactDraftAttachmentsForListing(result)), false, [
4311
4367
  draftSource(result),
4312
4368
  ...(result.remoteDraft?.emailId
4313
4369
  ? [
@@ -4327,7 +4383,7 @@ export function createServer(config, options = {}) {
4327
4383
  const synced = await withAudit(auditService, name, args, async () => syncDraftToRemote(syncDraftBundle.draftStore, syncDraftBundle.smtpService, syncDraftBundle.imapService, draft));
4328
4384
  const presentedSynced = presentDraft(syncDraftBundle, synced.draft);
4329
4385
  // Same fix as create_draft/update_draft's identical bloat — see their comments.
4330
- return createTextResult({ ...redactDraftAttachmentsForListing(presentedSynced), remoteSync: synced.remoteSync }, false, [
4386
+ return createTextResult({ ...truncateDraftBodyForResponse(redactDraftAttachmentsForListing(presentedSynced)), remoteSync: synced.remoteSync }, false, [
4331
4387
  draftSource(presentedSynced),
4332
4388
  ...(presentedSynced.remoteDraft?.emailId
4333
4389
  ? [
@@ -4647,7 +4703,26 @@ export function createServer(config, options = {}) {
4647
4703
  // requested offset/limit to that merged superset instead of per account.
4648
4704
  const primarySlug = accountManager.primary().account.slug;
4649
4705
  const requestedOffset = getEmailsInput.offset ?? 0;
4650
- const perAccountFetchLimit = requestedOffset + effectiveLimit;
4706
+ // +1 beyond what's strictly needed to fill the page: whether that extra
4707
+ // item actually comes back is what tells us hasMore, independent of any
4708
+ // filter (beforeUid etc.) or of how the raw mailbox total compares to the
4709
+ // page window. Two things were tried and each broke a different case:
4710
+ // (1) using mergedAll.length > offset+limit as hasMore, with NO extra
4711
+ // overfetch — a lopsided distribution (e.g. 300 messages in one account,
4712
+ // 0 in another) fetches exactly offset+limit from the big account, so
4713
+ // mergedAll.length can never exceed that window even though hundreds
4714
+ // more exist, reporting hasMore:false wrongly.
4715
+ // (2) using the raw summed mailbox `total` for hasMore instead — correct
4716
+ // for that case, but wrong under a restrictive filter like beforeUid:
4717
+ // `total` is the UNFILTERED mailbox message count, not the count of
4718
+ // messages actually matching the filter, so a beforeUid narrowing 300
4719
+ // messages down to 2 matches still compared against total=300 and
4720
+ // reported hasMore:true with nothing left to page to.
4721
+ // Fetching one extra item fixes both: it directly observes whether a
4722
+ // NEXT matching item exists, under whatever filter is active, rather
4723
+ // than inferring it from a count that can be either capped-by-fetch or
4724
+ // filter-blind depending on which one you pick.
4725
+ const perAccountFetchLimit = requestedOffset + effectiveLimit + 1;
4651
4726
  const perAccount = await Promise.all(accountManager.all().map(async (bundle) => {
4652
4727
  const result = await bundle.imapService.getEmails({ ...getEmailsInput, offset: 0, limit: perAccountFetchLimit });
4653
4728
  return { bundle, result };
@@ -6492,6 +6567,8 @@ export function createServer(config, options = {}) {
6492
6567
  subject: prefixedSubject(detail.subject, "Re:"),
6493
6568
  body: buildReplyText(detail, body),
6494
6569
  isHtml,
6570
+ // Same fix as create_reply_draft — see its comment.
6571
+ from: optionalString(args, "from"),
6495
6572
  inReplyTo: detail.messageId,
6496
6573
  references: detail.messageId ? [detail.messageId] : undefined,
6497
6574
  attachments,
@@ -6515,7 +6592,7 @@ export function createServer(config, options = {}) {
6515
6592
  return { ...presented, threadId: thread.id };
6516
6593
  });
6517
6594
  // Same fix as create_draft/update_draft's identical bloat — see their comments.
6518
- return createTextResult(redactDraftAttachmentsForListing(result), false, [
6595
+ return createTextResult(truncateDraftBodyForResponse(redactDraftAttachmentsForListing(result)), false, [
6519
6596
  draftSource(result),
6520
6597
  threadSource(thread),
6521
6598
  emailSource(detail),