@agenticmail/core 0.9.9 → 0.9.11

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.cjs CHANGED
@@ -713,6 +713,7 @@ __export(index_exports, {
713
713
  AgentDeletionService: () => AgentDeletionService,
714
714
  AgentMemoryStore: () => AgentMemoryStore,
715
715
  AgenticMailClient: () => AgenticMailClient,
716
+ BRIDGE_OPERATOR_LIVE_WINDOW_MS: () => BRIDGE_OPERATOR_LIVE_WINDOW_MS,
716
717
  CloudflareClient: () => CloudflareClient,
717
718
  DEFAULT_AGENT_NAME: () => DEFAULT_AGENT_NAME,
718
719
  DEFAULT_AGENT_ROLE: () => DEFAULT_AGENT_ROLE,
@@ -743,10 +744,14 @@ __export(index_exports, {
743
744
  UnsafeApiUrlError: () => UnsafeApiUrlError,
744
745
  WARNING_THRESHOLD: () => WARNING_THRESHOLD,
745
746
  assertWithinBase: () => assertWithinBase,
747
+ bridgeWakeErrorMessage: () => bridgeWakeErrorMessage,
748
+ bridgeWakeLastSeenAgeMs: () => bridgeWakeLastSeenAgeMs,
746
749
  buildApiUrl: () => buildApiUrl,
747
750
  buildInboundSecurityAdvisory: () => buildInboundSecurityAdvisory,
748
751
  classifyEmailRoute: () => classifyEmailRoute,
752
+ classifyResumeError: () => classifyResumeError,
749
753
  closeDatabase: () => closeDatabase,
754
+ composeBridgeWakePrompt: () => composeBridgeWakePrompt,
750
755
  createTestDatabase: () => createTestDatabase,
751
756
  debug: () => debug,
752
757
  debugWarn: () => debugWarn,
@@ -769,6 +774,7 @@ __export(index_exports, {
769
774
  operatorPrefsStoragePath: () => operatorPrefsStoragePath,
770
775
  parseEmail: () => parseEmail,
771
776
  parseGoogleVoiceSms: () => parseGoogleVoiceSms,
777
+ planBridgeWake: () => planBridgeWake,
772
778
  recordToolCall: () => recordToolCall,
773
779
  redactObject: () => redactObject,
774
780
  redactSecret: () => redactSecret,
@@ -782,6 +788,7 @@ __export(index_exports, {
782
788
  scoreEmail: () => scoreEmail,
783
789
  setOperatorEmail: () => setOperatorEmail,
784
790
  setTelemetryVersion: () => setTelemetryVersion,
791
+ shouldSkipBridgeWakeForLiveOperator: () => shouldSkipBridgeWakeForLiveOperator,
785
792
  startRelayBridge: () => startRelayBridge,
786
793
  threadIdFor: () => threadIdFor,
787
794
  tryJoin: () => tryJoin,
@@ -805,8 +812,7 @@ var MailSender = class {
805
812
  pass: options.password
806
813
  },
807
814
  tls: {
808
- rejectUnauthorized: false
809
- // Local dev — no TLS
815
+ rejectUnauthorized: options.tlsRejectUnauthorized ?? true
810
816
  },
811
817
  connectionTimeout: 1e4,
812
818
  // 10s to establish TCP connection
@@ -2074,7 +2080,13 @@ function rowToAgent(row) {
2074
2080
  // Old rows (pre-migration-016) have undefined `wake_on_cc`;
2075
2081
  // treat that as the default-true (respect sender's wake list
2076
2082
  // as-is). Only explicit 0 disables CC wakes for this agent.
2077
- wakeOnCc: row.wake_on_cc !== void 0 ? row.wake_on_cc !== 0 : true
2083
+ wakeOnCc: row.wake_on_cc !== void 0 ? row.wake_on_cc !== 0 : true,
2084
+ // Pre-migration-017 rows have undefined `stopped`; treat as
2085
+ // not-stopped. The dispatcher only blocks wakes when this is
2086
+ // explicitly truthy, so the default mirrors back-compat.
2087
+ stopped: row.stopped !== void 0 ? row.stopped !== 0 : false,
2088
+ stoppedAt: row.stopped_at ?? null,
2089
+ stoppedReason: row.stopped_reason ?? null
2078
2090
  };
2079
2091
  }
2080
2092
  var AccountManager = class {
@@ -3692,6 +3704,22 @@ ALTER TABLE drafts ADD COLUMN attachments TEXT;
3692
3704
  -- explicitly named" preference from the wake-thrash feedback.
3693
3705
  -- Defaults to 1 (respect the senders wake list as-is).
3694
3706
  ALTER TABLE agents ADD COLUMN wake_on_cc INTEGER NOT NULL DEFAULT 1;
3707
+ `,
3708
+ "017_agent_stopped.sql": `
3709
+ -- Soft-stop for an agent mid-task. When 1, the dispatcher refuses
3710
+ -- to wake this agent for ANY reason \u2014 allowlists, To/Cc, task
3711
+ -- notifications, all of it. Mail still gets delivered to the
3712
+ -- mailbox so the audit trail of the thread stays intact. This
3713
+ -- is the non-destructive counterpart to delete_agent: stops an
3714
+ -- agent that's currently churning without losing its inbox or
3715
+ -- the thread history.
3716
+ --
3717
+ -- Companion columns capture WHEN it was stopped and the OPTIONAL
3718
+ -- reason the caller passed (e.g. "task superseded", "budget
3719
+ -- exhausted") so an operator can audit later.
3720
+ ALTER TABLE agents ADD COLUMN stopped INTEGER NOT NULL DEFAULT 0;
3721
+ ALTER TABLE agents ADD COLUMN stopped_at TEXT;
3722
+ ALTER TABLE agents ADD COLUMN stopped_reason TEXT;
3695
3723
  `
3696
3724
  };
3697
3725
  function runMigrations(database) {
@@ -3965,9 +3993,67 @@ function formatPollError(err) {
3965
3993
  const stdout = (e.stdout ?? "").toString().trim();
3966
3994
  if (stdout && !stderr) parts.push(`stdout=${truncate(stdout, 240)}`);
3967
3995
  if (parts.length === 1 && /^command failed$/i.test(head)) {
3968
- return `${head} (no further detail available \u2014 wrapping error did not carry stderr/code/response)`;
3996
+ return redactCredentialTokens(`${head} (no further detail available \u2014 wrapping error did not carry stderr/code/response)`);
3969
3997
  }
3970
- return parts.join(" | ");
3998
+ return redactCredentialTokens(parts.join(" | "));
3999
+ }
4000
+ function redactCredentialTokens(text) {
4001
+ return text.replace(/\b(AUTH(?:ENTICATE)?)\s+(PLAIN|LOGIN|XOAUTH2|CRAM-MD5|EXTERNAL)\s+\S+/gi, "$1 $2 [redacted]").replace(/\b(AUTH(?:ENTICATE)?)\s+([A-Za-z0-9+/]{16,}={0,2})\b/gi, "$1 [redacted]");
4002
+ }
4003
+ function isRelayCredentialError(err) {
4004
+ if (!err) return false;
4005
+ const haystack = typeof err === "object" ? (() => {
4006
+ const e = err;
4007
+ return [
4008
+ e.message,
4009
+ e.code,
4010
+ e.responseCode,
4011
+ e.response,
4012
+ e.responseText,
4013
+ e.command,
4014
+ e.serverResponse
4015
+ ].filter(Boolean).join(" ").toLowerCase();
4016
+ })() : String(err).toLowerCase();
4017
+ return [
4018
+ "eauth",
4019
+ "authenticationfailed",
4020
+ "authentication failed",
4021
+ "authenticate failed",
4022
+ "invalid credentials",
4023
+ "invalid login",
4024
+ "login failed",
4025
+ "username and password not accepted",
4026
+ "application-specific password",
4027
+ "app password",
4028
+ "badcredentials",
4029
+ "invalid_grant",
4030
+ "invalid_token",
4031
+ "expired token",
4032
+ "token expired",
4033
+ "access token has expired",
4034
+ "token has expired",
4035
+ "token is expired",
4036
+ "token is invalid",
4037
+ "token revoked",
4038
+ "xoauth2",
4039
+ "aadsts",
4040
+ "535",
4041
+ "534",
4042
+ "5.7.8"
4043
+ ].some((marker) => haystack.includes(marker));
4044
+ }
4045
+ function formatRelayError(err, config, phase) {
4046
+ const detail = formatPollError(err);
4047
+ if (!isRelayCredentialError(err)) {
4048
+ return `Relay ${phase} failed: ${detail}`;
4049
+ }
4050
+ const provider = config.provider === "gmail" ? "Gmail" : config.provider === "outlook" ? "Outlook/Microsoft 365" : "custom";
4051
+ const action = config.provider === "gmail" ? "Create a fresh Gmail app password or reconnect the relay, then run agenticmail setup-relay again." : config.provider === "outlook" ? "Refresh/recreate the Microsoft relay credential or OAuth token, then run agenticmail setup-relay again." : "Refresh the relay credential, then run agenticmail setup-relay again.";
4052
+ return [
4053
+ `Relay ${phase} failed: ${provider} relay authentication for ${config.email} is invalid, expired, or revoked.`,
4054
+ action,
4055
+ `Original error: ${detail}`
4056
+ ].join(" ");
3971
4057
  }
3972
4058
  function truncate(s, max) {
3973
4059
  if (s.length <= max) return s;
@@ -4022,7 +4108,11 @@ var RelayGateway = class {
4022
4108
  pass: config.password
4023
4109
  }
4024
4110
  });
4025
- await this.smtpTransport.verify();
4111
+ try {
4112
+ await this.smtpTransport.verify();
4113
+ } catch (err) {
4114
+ throw new Error(formatRelayError(err, config, "SMTP verification"));
4115
+ }
4026
4116
  const imap = new import_imapflow3.ImapFlow({
4027
4117
  host: config.imapHost,
4028
4118
  port: config.imapPort,
@@ -4033,8 +4123,16 @@ var RelayGateway = class {
4033
4123
  },
4034
4124
  logger: false
4035
4125
  });
4036
- await imap.connect();
4037
- await imap.logout();
4126
+ try {
4127
+ await imap.connect();
4128
+ } catch (err) {
4129
+ throw new Error(formatRelayError(err, config, "IMAP verification"));
4130
+ } finally {
4131
+ try {
4132
+ await imap.logout();
4133
+ } catch {
4134
+ }
4135
+ }
4038
4136
  }
4039
4137
  /**
4040
4138
  * Send an email through the relay SMTP server.
@@ -4044,9 +4142,10 @@ var RelayGateway = class {
4044
4142
  if (!this.config || !this.smtpTransport) {
4045
4143
  throw new Error("Relay not configured. Call setup() first.");
4046
4144
  }
4047
- const atIdx = this.config.email.lastIndexOf("@");
4048
- const localPart2 = this.config.email.slice(0, atIdx);
4049
- const domain = this.config.email.slice(atIdx + 1);
4145
+ const relayConfig = this.config;
4146
+ const atIdx = relayConfig.email.lastIndexOf("@");
4147
+ const localPart2 = relayConfig.email.slice(0, atIdx);
4148
+ const domain = relayConfig.email.slice(atIdx + 1);
4050
4149
  const relayFrom = `${localPart2}+${agentName}@${domain}`;
4051
4150
  const displayName = mail.fromName || agentName;
4052
4151
  const mailOpts = {
@@ -4070,7 +4169,12 @@ var RelayGateway = class {
4070
4169
  };
4071
4170
  const composer = new import_mail_composer2.default(mailOpts);
4072
4171
  const raw = await composer.compile().build();
4073
- const result = await this.smtpTransport.sendMail(mailOpts);
4172
+ let result;
4173
+ try {
4174
+ result = await this.smtpTransport.sendMail(mailOpts);
4175
+ } catch (err) {
4176
+ throw new Error(formatRelayError(err, relayConfig, "SMTP send"));
4177
+ }
4074
4178
  if (result.messageId) {
4075
4179
  this.sentMessageIds.set(result.messageId, agentName);
4076
4180
  if (this.sentMessageIds.size > 1e4) {
@@ -4147,7 +4251,7 @@ var RelayGateway = class {
4147
4251
  this.consecutiveFailures = 0;
4148
4252
  } catch (err) {
4149
4253
  this.consecutiveFailures++;
4150
- const msg = formatPollError(err);
4254
+ const msg = this.config ? formatRelayError(err, this.config, "IMAP poll") : formatPollError(err);
4151
4255
  console.error(`[RelayGateway] Poll failed (attempt ${this.consecutiveFailures}): ${msg}`);
4152
4256
  if (this.consecutiveFailures >= 5 && this.consecutiveFailures % 5 === 0) {
4153
4257
  console.error(`[RelayGateway] ${this.consecutiveFailures} consecutive failures \u2014 check IMAP credentials and connectivity (${this.config?.imapHost}:${this.config?.imapPort})`);
@@ -4380,13 +4484,14 @@ var RelayGateway = class {
4380
4484
  */
4381
4485
  async searchRelay(criteria, maxResults = 50) {
4382
4486
  if (!this.config) throw new Error("Relay not configured");
4487
+ const relayConfig = this.config;
4383
4488
  const imap = new import_imapflow3.ImapFlow({
4384
- host: this.config.imapHost,
4385
- port: this.config.imapPort,
4386
- secure: this.config.imapPort === 993,
4489
+ host: relayConfig.imapHost,
4490
+ port: relayConfig.imapPort,
4491
+ secure: relayConfig.imapPort === 993,
4387
4492
  auth: {
4388
- user: this.config.email,
4389
- pass: this.config.password
4493
+ user: relayConfig.email,
4494
+ pass: relayConfig.password
4390
4495
  },
4391
4496
  logger: false
4392
4497
  });
@@ -4423,7 +4528,7 @@ var RelayGateway = class {
4423
4528
  results.push({
4424
4529
  uid,
4425
4530
  source: "relay",
4426
- account: this.config.email,
4531
+ account: relayConfig.email,
4427
4532
  messageId: envelope.messageId ?? "",
4428
4533
  subject: envelope.subject ?? "",
4429
4534
  from: (envelope.from ?? []).map((a) => ({ name: a.name, address: a.address ?? "" })),
@@ -4439,7 +4544,7 @@ var RelayGateway = class {
4439
4544
  lock.release();
4440
4545
  }
4441
4546
  } catch (err) {
4442
- console.error("[RelayGateway] Relay search failed:", err instanceof Error ? err.message : err);
4547
+ console.error("[RelayGateway] Relay search failed:", formatRelayError(err, relayConfig, "IMAP search"));
4443
4548
  return [];
4444
4549
  } finally {
4445
4550
  try {
@@ -4454,13 +4559,14 @@ var RelayGateway = class {
4454
4559
  */
4455
4560
  async fetchRelayMessage(uid) {
4456
4561
  if (!this.config) throw new Error("Relay not configured");
4562
+ const relayConfig = this.config;
4457
4563
  const imap = new import_imapflow3.ImapFlow({
4458
- host: this.config.imapHost,
4459
- port: this.config.imapPort,
4460
- secure: this.config.imapPort === 993,
4564
+ host: relayConfig.imapHost,
4565
+ port: relayConfig.imapPort,
4566
+ secure: relayConfig.imapPort === 993,
4461
4567
  auth: {
4462
- user: this.config.email,
4463
- pass: this.config.password
4568
+ user: relayConfig.email,
4569
+ pass: relayConfig.password
4464
4570
  },
4465
4571
  logger: false
4466
4572
  });
@@ -4497,7 +4603,7 @@ var RelayGateway = class {
4497
4603
  lock.release();
4498
4604
  }
4499
4605
  } catch (err) {
4500
- console.error("[RelayGateway] Fetch relay message failed:", err instanceof Error ? err.message : err);
4606
+ console.error("[RelayGateway] Fetch relay message failed:", formatRelayError(err, relayConfig, "IMAP fetch"));
4501
4607
  return null;
4502
4608
  } finally {
4503
4609
  try {
@@ -7233,6 +7339,95 @@ function hostSessionStoragePath() {
7233
7339
  return storagePath();
7234
7340
  }
7235
7341
 
7342
+ // src/host-bridge.ts
7343
+ var BRIDGE_OPERATOR_LIVE_WINDOW_MS = 3e4;
7344
+ var DEFAULT_EXPIRED_MARKERS = [
7345
+ "session not found",
7346
+ "invalid session",
7347
+ "session expired",
7348
+ "no such session",
7349
+ "unknown session",
7350
+ "thread not found",
7351
+ "invalid thread",
7352
+ "thread expired",
7353
+ "no such thread",
7354
+ "unknown thread"
7355
+ ];
7356
+ var DEFAULT_SDK_MISSING_MARKERS = [
7357
+ "cannot find module",
7358
+ "could not be found",
7359
+ "command not found"
7360
+ ];
7361
+ function bridgeWakeErrorMessage(err) {
7362
+ return err?.message ?? String(err);
7363
+ }
7364
+ function classifyResumeError(err, options = {}) {
7365
+ const msg = bridgeWakeErrorMessage(err).toLowerCase();
7366
+ const expiredMarkers = options.expiredMarkers ?? DEFAULT_EXPIRED_MARKERS;
7367
+ const sdkMissingMarkers = options.sdkMissingMarkers ?? DEFAULT_SDK_MISSING_MARKERS;
7368
+ if (expiredMarkers.some((marker) => msg.includes(marker))) return "session-expired";
7369
+ if (sdkMissingMarkers.some((marker) => msg.includes(marker))) return "sdk-missing";
7370
+ return "other";
7371
+ }
7372
+ function bridgeWakeLastSeenAgeMs(session, nowMs = Date.now()) {
7373
+ if (!session) return null;
7374
+ return nowMs - session.lastSeenMs;
7375
+ }
7376
+ function shouldSkipBridgeWakeForLiveOperator(session, nowMs = Date.now(), liveWindowMs = BRIDGE_OPERATOR_LIVE_WINDOW_MS) {
7377
+ const ageMs = bridgeWakeLastSeenAgeMs(session, nowMs);
7378
+ return ageMs !== null && ageMs < liveWindowMs;
7379
+ }
7380
+ function planBridgeWake(args) {
7381
+ const nowMs = args.nowMs ?? Date.now();
7382
+ const liveWindowMs = args.liveWindowMs ?? BRIDGE_OPERATOR_LIVE_WINDOW_MS;
7383
+ const ageMs = bridgeWakeLastSeenAgeMs(args.session, nowMs);
7384
+ if (ageMs !== null && ageMs < liveWindowMs) {
7385
+ return {
7386
+ action: "skip-live",
7387
+ reason: "operator-live",
7388
+ ageMs,
7389
+ mail: args.mail
7390
+ };
7391
+ }
7392
+ if (!args.session) {
7393
+ return {
7394
+ action: "escalate",
7395
+ reason: "no-fresh-session",
7396
+ mail: args.mail
7397
+ };
7398
+ }
7399
+ return {
7400
+ action: "resume",
7401
+ session: args.session,
7402
+ prompt: composeBridgeWakePrompt(args.mail),
7403
+ mail: args.mail
7404
+ };
7405
+ }
7406
+ function composeBridgeWakePrompt(args) {
7407
+ const subject = args.subject ?? "(no subject)";
7408
+ const from = args.from ?? "unknown";
7409
+ const preview = (args.preview ?? "").slice(0, 600);
7410
+ return [
7411
+ `\u{1F380} Bridge mail arrived \u2014 headless wake.`,
7412
+ "",
7413
+ `You are being resumed against your last session because new mail landed in your bridge inbox (${args.bridgeName}@localhost) and you weren't actively at the keyboard.`,
7414
+ "",
7415
+ `Trigger:`,
7416
+ ` UID: ${args.uid}`,
7417
+ ` From: ${from}`,
7418
+ ` Subject: ${subject}`,
7419
+ ` Preview: ${preview}`,
7420
+ "",
7421
+ `Read it with mcp__agenticmail__read_email({ uid: ${args.uid} }) and decide:`,
7422
+ ` \xB7 Does it need a reply from YOU (the operator's session)? Reply via mcp__agenticmail__reply_email.`,
7423
+ ` \xB7 Does it need a teammate to act? Forward / re-route by replying with wake: ["<teammate>"].`,
7424
+ ` \xB7 Is it [NEEDS OPERATOR] / [BLOCKED]? Then it's actually for the human \u2014 mark it unread, and the operator will see it on their next keystroke.`,
7425
+ ` \xB7 Is it FYI noise? mark_read and exit.`,
7426
+ "",
7427
+ `Keep this turn SHORT. You're being resumed to handle ONE piece of mail, not to continue the prior conversation.`
7428
+ ].join("\n");
7429
+ }
7430
+
7236
7431
  // src/util/safe-url.ts
7237
7432
  var UnsafeApiUrlError = class extends Error {
7238
7433
  constructor(raw, reason) {
@@ -8910,6 +9105,7 @@ function parse(raw) {
8910
9105
  AgentDeletionService,
8911
9106
  AgentMemoryStore,
8912
9107
  AgenticMailClient,
9108
+ BRIDGE_OPERATOR_LIVE_WINDOW_MS,
8913
9109
  CloudflareClient,
8914
9110
  DEFAULT_AGENT_NAME,
8915
9111
  DEFAULT_AGENT_ROLE,
@@ -8940,10 +9136,14 @@ function parse(raw) {
8940
9136
  UnsafeApiUrlError,
8941
9137
  WARNING_THRESHOLD,
8942
9138
  assertWithinBase,
9139
+ bridgeWakeErrorMessage,
9140
+ bridgeWakeLastSeenAgeMs,
8943
9141
  buildApiUrl,
8944
9142
  buildInboundSecurityAdvisory,
8945
9143
  classifyEmailRoute,
9144
+ classifyResumeError,
8946
9145
  closeDatabase,
9146
+ composeBridgeWakePrompt,
8947
9147
  createTestDatabase,
8948
9148
  debug,
8949
9149
  debugWarn,
@@ -8966,6 +9166,7 @@ function parse(raw) {
8966
9166
  operatorPrefsStoragePath,
8967
9167
  parseEmail,
8968
9168
  parseGoogleVoiceSms,
9169
+ planBridgeWake,
8969
9170
  recordToolCall,
8970
9171
  redactObject,
8971
9172
  redactSecret,
@@ -8979,6 +9180,7 @@ function parse(raw) {
8979
9180
  scoreEmail,
8980
9181
  setOperatorEmail,
8981
9182
  setTelemetryVersion,
9183
+ shouldSkipBridgeWakeForLiveOperator,
8982
9184
  startRelayBridge,
8983
9185
  threadIdFor,
8984
9186
  tryJoin,
package/dist/index.d.cts CHANGED
@@ -358,6 +358,24 @@ interface Agent {
358
358
  * including them never wastes a Claude turn. Defaults to true
359
359
  * (preserves the 0.9.0 wake-list-respecting behaviour). */
360
360
  wakeOnCc?: boolean;
361
+ /** Soft-stop flag. When true, the dispatcher refuses to wake
362
+ * this agent for ANY reason (allowlist, To/Cc, task events).
363
+ * Mail still lands in the mailbox so the audit trail of the
364
+ * thread is preserved — only Claude/Codex turns are blocked.
365
+ * This is the non-destructive counterpart to delete_agent for
366
+ * stopping a churning agent mid-task without losing context. */
367
+ stopped?: boolean;
368
+ /** ISO timestamp of when `stopped` was set to true. NULL when
369
+ * the agent has never been stopped (or has since been resumed
370
+ * — `resume_agent` clears both `stopped` and the audit fields
371
+ * is a policy decision; the current implementation clears
372
+ * `stopped` only and leaves the timestamp / reason in place so
373
+ * operators can see the most-recent stop history). */
374
+ stoppedAt?: string | null;
375
+ /** Optional free-form reason supplied by the caller when the
376
+ * agent was stopped (e.g. "task superseded by new requirements"
377
+ * or "stop all sub-agents — user request 2025-12-09"). */
378
+ stoppedReason?: string | null;
361
379
  }
362
380
  interface CreateAgentOptions {
363
381
  name: string;
@@ -481,6 +499,7 @@ interface MailSenderOptions {
481
499
  password: string;
482
500
  authUser?: string;
483
501
  secure?: boolean;
502
+ tlsRejectUnauthorized?: boolean;
484
503
  }
485
504
  interface SendResultWithRaw extends SendResult {
486
505
  /** Raw RFC822 message bytes (for appending to Sent folder) */
@@ -1899,8 +1918,8 @@ declare function operatorPrefsStoragePath(): string;
1899
1918
  *
1900
1919
  * # What this is for
1901
1920
  *
1902
- * When a sub-agent replies into the host bridge inbox
1903
- * (`claudecode@localhost` / `codex@localhost`), the dispatcher
1921
+ * When a sub-agent replies into a host bridge inbox
1922
+ * (`claudecode@localhost` / `codex@localhost` / etc.), the dispatcher
1904
1923
  * historically had no way to react: bridges are skipped by
1905
1924
  * `shouldWatch` because they belong to the human operator's host CLI,
1906
1925
  * not to an automated worker. The mail would sit unread until the
@@ -1933,6 +1952,12 @@ declare function operatorPrefsStoragePath(): string;
1933
1952
  * "sessionId": "019a2b3c-…",
1934
1953
  * "workspace": "/Users/ope/Desktop/facebook-project",
1935
1954
  * "lastSeenMs": 1778905100000
1955
+ * },
1956
+ * "openclaw": {
1957
+ * "sessionId": "openclaw-session-key",
1958
+ * "workspace": "/Users/ope/Desktop/facebook-project",
1959
+ * "lastSeenMs": 1778905000000,
1960
+ * "resumeMode": "wake"
1936
1961
  * }
1937
1962
  * }
1938
1963
  * }
@@ -1963,14 +1988,24 @@ declare function operatorPrefsStoragePath(): string;
1963
1988
  * that crashes the next reader. Same shape as `dispatcher-state.ts`.
1964
1989
  */
1965
1990
  /** Canonical names for the host integrations that own bridge inboxes. */
1966
- type HostName = 'claudecode' | 'codex';
1991
+ type HostName = 'claudecode' | 'codex' | 'openclaw' | 'gemini' | 'hermes';
1992
+ /**
1993
+ * How a host can be woken from a persisted session record.
1994
+ *
1995
+ * - `resume`: the host can resume a durable prior conversation/thread.
1996
+ * - `wake`: the host can target a live or recently known session key, but does
1997
+ * not guarantee full headless resume semantics.
1998
+ * - `wake-only`: the host can receive a wake notification, but the dispatcher
1999
+ * must not treat it as a resumed worker turn.
2000
+ */
2001
+ type HostSessionResumeMode = 'resume' | 'wake' | 'wake-only';
1967
2002
  /**
1968
2003
  * A snapshot of one host CLI's last-known session. Persisted to disk
1969
2004
  * by the mail-hook on every fire; loaded by the dispatcher when
1970
2005
  * bridge mail arrives so a resume can be attempted.
1971
2006
  */
1972
2007
  interface HostSession {
1973
- /** Stable session_id from the host CLI (Claude Code or Codex). */
2008
+ /** Stable session_id/thread_id/session key from the host CLI/runtime. */
1974
2009
  sessionId: string;
1975
2010
  /** Wall-clock timestamp of the last hook fire on this session. */
1976
2011
  lastSeenMs: number;
@@ -1980,6 +2015,10 @@ interface HostSession {
1980
2015
  /** Optional: model name the host session was using, surfaced
1981
2016
  * in logs for diagnostic context. */
1982
2017
  model?: string;
2018
+ /** Optional: describes whether this host supports true resume or only wake. */
2019
+ resumeMode?: HostSessionResumeMode;
2020
+ /** Optional host-specific metadata. Must not contain secrets. */
2021
+ hostMetadata?: Record<string, unknown>;
1983
2022
  }
1984
2023
  /** Default freshness window — sessions older than this are skipped. */
1985
2024
  declare const DEFAULT_SESSION_MAX_AGE_MS: number;
@@ -2017,6 +2056,62 @@ declare function forgetHostSession(host: HostName): void;
2017
2056
  /** Exposed for tests + the `agenticmail status` diagnostic command. */
2018
2057
  declare function hostSessionStoragePath(): string;
2019
2058
 
2059
+ type BridgeWakeError = 'session-expired' | 'sdk-missing' | 'timeout' | 'other';
2060
+ interface BridgeWakeResult {
2061
+ ok: boolean;
2062
+ text?: string;
2063
+ error?: BridgeWakeError;
2064
+ errorMessage?: string;
2065
+ durationMs?: number;
2066
+ }
2067
+ interface BridgeWakePromptArgs {
2068
+ bridgeName: string;
2069
+ uid: number;
2070
+ subject?: string;
2071
+ from?: string;
2072
+ preview?: string;
2073
+ }
2074
+ interface BridgeMailContext extends BridgeWakePromptArgs {
2075
+ }
2076
+ type BridgeWakeRoute = {
2077
+ action: 'skip-live';
2078
+ reason: 'operator-live';
2079
+ ageMs: number;
2080
+ mail: BridgeMailContext;
2081
+ } | {
2082
+ action: 'escalate';
2083
+ reason: 'no-fresh-session';
2084
+ mail: BridgeMailContext;
2085
+ } | {
2086
+ action: 'resume';
2087
+ session: HostSession;
2088
+ prompt: string;
2089
+ mail: BridgeMailContext;
2090
+ };
2091
+ interface PlanBridgeWakeArgs {
2092
+ session: HostSession | null;
2093
+ mail: BridgeMailContext;
2094
+ nowMs?: number;
2095
+ liveWindowMs?: number;
2096
+ }
2097
+ interface ResumeErrorClassificationOptions {
2098
+ expiredMarkers?: readonly string[];
2099
+ sdkMissingMarkers?: readonly string[];
2100
+ }
2101
+ declare const BRIDGE_OPERATOR_LIVE_WINDOW_MS = 30000;
2102
+ declare function bridgeWakeErrorMessage(err: unknown): string;
2103
+ declare function classifyResumeError(err: unknown, options?: ResumeErrorClassificationOptions): BridgeWakeError;
2104
+ declare function bridgeWakeLastSeenAgeMs(session: Pick<HostSession, 'lastSeenMs'> | null | undefined, nowMs?: number): number | null;
2105
+ declare function shouldSkipBridgeWakeForLiveOperator(session: Pick<HostSession, 'lastSeenMs'> | null | undefined, nowMs?: number, liveWindowMs?: number): boolean;
2106
+ declare function planBridgeWake(args: PlanBridgeWakeArgs): BridgeWakeRoute;
2107
+ /**
2108
+ * Build the prompt a host session sees on bridge wake. Host adapters
2109
+ * keep their own SDK resume call, but share this operator-facing
2110
+ * instruction shape so Claude Code, Codex, OpenClaw and later hosts
2111
+ * do not drift semantically.
2112
+ */
2113
+ declare function composeBridgeWakePrompt(args: BridgeWakePromptArgs): string;
2114
+
2020
2115
  /**
2021
2116
  * SSRF-safe URL validation for the AgenticMail API base URL.
2022
2117
  *
@@ -2624,4 +2719,4 @@ declare class AgentMemoryStore {
2624
2719
  renderForPrompt(memory: AgentMemoryRead | null): string;
2625
2720
  }
2626
2721
 
2627
- export { AGENT_ROLES, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentMemoryFields, type AgentMemoryOptions, type AgentMemoryRead, AgentMemoryStore, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, type CachedMessage, CloudflareClient, type CreateAgentOptions, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DEFAULT_SESSION_MAX_AGE_MS, DNSConfigurator, type Database, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, type EmailEnvelope, type EmailRouteAction, type EmailRouteClass, type EmailRouteClassification, type EmailRouteInput, EmailSearchIndex, type FolderInfo, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type HostName, type HostSession, type InboundEmail, type InboundSmsEvent, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, type LinkAdvisory, type LocalSmtpConfig, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, type ParsedAttachment, type ParsedEmail, type ParsedSms, PathTraversalError, type PurchasedDomain, REDACTED, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SafeJoinOptions, type SanitizeDetection, type SanitizeResult, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, type SendSmsInput, type SendSmsResult, ServiceManager, type ServiceStatus, type SetupConfig, SetupManager, type SetupResult, type Severity, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SmsProvider, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, ThreadCache, type ThreadCacheEntry, type ThreadCacheOptions, type ThreadIdInput, type TunnelConfig, TunnelManager, UnsafeApiUrlError, WARNING_THRESHOLD, type WatcherOptions, assertWithinBase, buildApiUrl, buildInboundSecurityAdvisory, classifyEmailRoute, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, forgetHostSession, getDatabase, getOperatorEmail, getSmsProvider, hostSessionStoragePath, isInternalEmail, isSessionFresh, isValidPhoneNumber, loadHostSession, mapProviderSmsStatus, normalizeAddress, normalizePhoneNumber, normalizeSubject, operatorPrefsStoragePath, parseEmail, parseGoogleVoiceSms, recordToolCall, redactObject, redactSecret, redactSmsConfig, resolveConfig, safeJoin, sanitizeEmail, saveConfig, saveHostSession, scanOutboundEmail, scoreEmail, setOperatorEmail, setTelemetryVersion, startRelayBridge, threadIdFor, tryJoin, validateApiUrl };
2722
+ export { AGENT_ROLES, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentMemoryFields, type AgentMemoryOptions, type AgentMemoryRead, AgentMemoryStore, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, BRIDGE_OPERATOR_LIVE_WINDOW_MS, type BridgeMailContext, type BridgeWakeError, type BridgeWakePromptArgs, type BridgeWakeResult, type BridgeWakeRoute, type CachedMessage, CloudflareClient, type CreateAgentOptions, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DEFAULT_SESSION_MAX_AGE_MS, DNSConfigurator, type Database, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, type EmailEnvelope, type EmailRouteAction, type EmailRouteClass, type EmailRouteClassification, type EmailRouteInput, EmailSearchIndex, type FolderInfo, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type HostName, type HostSession, type HostSessionResumeMode, type InboundEmail, type InboundSmsEvent, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, type LinkAdvisory, type LocalSmtpConfig, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, type ParsedAttachment, type ParsedEmail, type ParsedSms, PathTraversalError, type PlanBridgeWakeArgs, type PurchasedDomain, REDACTED, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, type ResumeErrorClassificationOptions, SPAM_THRESHOLD, type SafeJoinOptions, type SanitizeDetection, type SanitizeResult, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, type SendSmsInput, type SendSmsResult, ServiceManager, type ServiceStatus, type SetupConfig, SetupManager, type SetupResult, type Severity, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SmsProvider, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, ThreadCache, type ThreadCacheEntry, type ThreadCacheOptions, type ThreadIdInput, type TunnelConfig, TunnelManager, UnsafeApiUrlError, WARNING_THRESHOLD, type WatcherOptions, assertWithinBase, bridgeWakeErrorMessage, bridgeWakeLastSeenAgeMs, buildApiUrl, buildInboundSecurityAdvisory, classifyEmailRoute, classifyResumeError, closeDatabase, composeBridgeWakePrompt, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, forgetHostSession, getDatabase, getOperatorEmail, getSmsProvider, hostSessionStoragePath, isInternalEmail, isSessionFresh, isValidPhoneNumber, loadHostSession, mapProviderSmsStatus, normalizeAddress, normalizePhoneNumber, normalizeSubject, operatorPrefsStoragePath, parseEmail, parseGoogleVoiceSms, planBridgeWake, recordToolCall, redactObject, redactSecret, redactSmsConfig, resolveConfig, safeJoin, sanitizeEmail, saveConfig, saveHostSession, scanOutboundEmail, scoreEmail, setOperatorEmail, setTelemetryVersion, shouldSkipBridgeWakeForLiveOperator, startRelayBridge, threadIdFor, tryJoin, validateApiUrl };
package/dist/index.d.ts CHANGED
@@ -358,6 +358,24 @@ interface Agent {
358
358
  * including them never wastes a Claude turn. Defaults to true
359
359
  * (preserves the 0.9.0 wake-list-respecting behaviour). */
360
360
  wakeOnCc?: boolean;
361
+ /** Soft-stop flag. When true, the dispatcher refuses to wake
362
+ * this agent for ANY reason (allowlist, To/Cc, task events).
363
+ * Mail still lands in the mailbox so the audit trail of the
364
+ * thread is preserved — only Claude/Codex turns are blocked.
365
+ * This is the non-destructive counterpart to delete_agent for
366
+ * stopping a churning agent mid-task without losing context. */
367
+ stopped?: boolean;
368
+ /** ISO timestamp of when `stopped` was set to true. NULL when
369
+ * the agent has never been stopped (or has since been resumed
370
+ * — `resume_agent` clears both `stopped` and the audit fields
371
+ * is a policy decision; the current implementation clears
372
+ * `stopped` only and leaves the timestamp / reason in place so
373
+ * operators can see the most-recent stop history). */
374
+ stoppedAt?: string | null;
375
+ /** Optional free-form reason supplied by the caller when the
376
+ * agent was stopped (e.g. "task superseded by new requirements"
377
+ * or "stop all sub-agents — user request 2025-12-09"). */
378
+ stoppedReason?: string | null;
361
379
  }
362
380
  interface CreateAgentOptions {
363
381
  name: string;
@@ -481,6 +499,7 @@ interface MailSenderOptions {
481
499
  password: string;
482
500
  authUser?: string;
483
501
  secure?: boolean;
502
+ tlsRejectUnauthorized?: boolean;
484
503
  }
485
504
  interface SendResultWithRaw extends SendResult {
486
505
  /** Raw RFC822 message bytes (for appending to Sent folder) */
@@ -1899,8 +1918,8 @@ declare function operatorPrefsStoragePath(): string;
1899
1918
  *
1900
1919
  * # What this is for
1901
1920
  *
1902
- * When a sub-agent replies into the host bridge inbox
1903
- * (`claudecode@localhost` / `codex@localhost`), the dispatcher
1921
+ * When a sub-agent replies into a host bridge inbox
1922
+ * (`claudecode@localhost` / `codex@localhost` / etc.), the dispatcher
1904
1923
  * historically had no way to react: bridges are skipped by
1905
1924
  * `shouldWatch` because they belong to the human operator's host CLI,
1906
1925
  * not to an automated worker. The mail would sit unread until the
@@ -1933,6 +1952,12 @@ declare function operatorPrefsStoragePath(): string;
1933
1952
  * "sessionId": "019a2b3c-…",
1934
1953
  * "workspace": "/Users/ope/Desktop/facebook-project",
1935
1954
  * "lastSeenMs": 1778905100000
1955
+ * },
1956
+ * "openclaw": {
1957
+ * "sessionId": "openclaw-session-key",
1958
+ * "workspace": "/Users/ope/Desktop/facebook-project",
1959
+ * "lastSeenMs": 1778905000000,
1960
+ * "resumeMode": "wake"
1936
1961
  * }
1937
1962
  * }
1938
1963
  * }
@@ -1963,14 +1988,24 @@ declare function operatorPrefsStoragePath(): string;
1963
1988
  * that crashes the next reader. Same shape as `dispatcher-state.ts`.
1964
1989
  */
1965
1990
  /** Canonical names for the host integrations that own bridge inboxes. */
1966
- type HostName = 'claudecode' | 'codex';
1991
+ type HostName = 'claudecode' | 'codex' | 'openclaw' | 'gemini' | 'hermes';
1992
+ /**
1993
+ * How a host can be woken from a persisted session record.
1994
+ *
1995
+ * - `resume`: the host can resume a durable prior conversation/thread.
1996
+ * - `wake`: the host can target a live or recently known session key, but does
1997
+ * not guarantee full headless resume semantics.
1998
+ * - `wake-only`: the host can receive a wake notification, but the dispatcher
1999
+ * must not treat it as a resumed worker turn.
2000
+ */
2001
+ type HostSessionResumeMode = 'resume' | 'wake' | 'wake-only';
1967
2002
  /**
1968
2003
  * A snapshot of one host CLI's last-known session. Persisted to disk
1969
2004
  * by the mail-hook on every fire; loaded by the dispatcher when
1970
2005
  * bridge mail arrives so a resume can be attempted.
1971
2006
  */
1972
2007
  interface HostSession {
1973
- /** Stable session_id from the host CLI (Claude Code or Codex). */
2008
+ /** Stable session_id/thread_id/session key from the host CLI/runtime. */
1974
2009
  sessionId: string;
1975
2010
  /** Wall-clock timestamp of the last hook fire on this session. */
1976
2011
  lastSeenMs: number;
@@ -1980,6 +2015,10 @@ interface HostSession {
1980
2015
  /** Optional: model name the host session was using, surfaced
1981
2016
  * in logs for diagnostic context. */
1982
2017
  model?: string;
2018
+ /** Optional: describes whether this host supports true resume or only wake. */
2019
+ resumeMode?: HostSessionResumeMode;
2020
+ /** Optional host-specific metadata. Must not contain secrets. */
2021
+ hostMetadata?: Record<string, unknown>;
1983
2022
  }
1984
2023
  /** Default freshness window — sessions older than this are skipped. */
1985
2024
  declare const DEFAULT_SESSION_MAX_AGE_MS: number;
@@ -2017,6 +2056,62 @@ declare function forgetHostSession(host: HostName): void;
2017
2056
  /** Exposed for tests + the `agenticmail status` diagnostic command. */
2018
2057
  declare function hostSessionStoragePath(): string;
2019
2058
 
2059
+ type BridgeWakeError = 'session-expired' | 'sdk-missing' | 'timeout' | 'other';
2060
+ interface BridgeWakeResult {
2061
+ ok: boolean;
2062
+ text?: string;
2063
+ error?: BridgeWakeError;
2064
+ errorMessage?: string;
2065
+ durationMs?: number;
2066
+ }
2067
+ interface BridgeWakePromptArgs {
2068
+ bridgeName: string;
2069
+ uid: number;
2070
+ subject?: string;
2071
+ from?: string;
2072
+ preview?: string;
2073
+ }
2074
+ interface BridgeMailContext extends BridgeWakePromptArgs {
2075
+ }
2076
+ type BridgeWakeRoute = {
2077
+ action: 'skip-live';
2078
+ reason: 'operator-live';
2079
+ ageMs: number;
2080
+ mail: BridgeMailContext;
2081
+ } | {
2082
+ action: 'escalate';
2083
+ reason: 'no-fresh-session';
2084
+ mail: BridgeMailContext;
2085
+ } | {
2086
+ action: 'resume';
2087
+ session: HostSession;
2088
+ prompt: string;
2089
+ mail: BridgeMailContext;
2090
+ };
2091
+ interface PlanBridgeWakeArgs {
2092
+ session: HostSession | null;
2093
+ mail: BridgeMailContext;
2094
+ nowMs?: number;
2095
+ liveWindowMs?: number;
2096
+ }
2097
+ interface ResumeErrorClassificationOptions {
2098
+ expiredMarkers?: readonly string[];
2099
+ sdkMissingMarkers?: readonly string[];
2100
+ }
2101
+ declare const BRIDGE_OPERATOR_LIVE_WINDOW_MS = 30000;
2102
+ declare function bridgeWakeErrorMessage(err: unknown): string;
2103
+ declare function classifyResumeError(err: unknown, options?: ResumeErrorClassificationOptions): BridgeWakeError;
2104
+ declare function bridgeWakeLastSeenAgeMs(session: Pick<HostSession, 'lastSeenMs'> | null | undefined, nowMs?: number): number | null;
2105
+ declare function shouldSkipBridgeWakeForLiveOperator(session: Pick<HostSession, 'lastSeenMs'> | null | undefined, nowMs?: number, liveWindowMs?: number): boolean;
2106
+ declare function planBridgeWake(args: PlanBridgeWakeArgs): BridgeWakeRoute;
2107
+ /**
2108
+ * Build the prompt a host session sees on bridge wake. Host adapters
2109
+ * keep their own SDK resume call, but share this operator-facing
2110
+ * instruction shape so Claude Code, Codex, OpenClaw and later hosts
2111
+ * do not drift semantically.
2112
+ */
2113
+ declare function composeBridgeWakePrompt(args: BridgeWakePromptArgs): string;
2114
+
2020
2115
  /**
2021
2116
  * SSRF-safe URL validation for the AgenticMail API base URL.
2022
2117
  *
@@ -2624,4 +2719,4 @@ declare class AgentMemoryStore {
2624
2719
  renderForPrompt(memory: AgentMemoryRead | null): string;
2625
2720
  }
2626
2721
 
2627
- export { AGENT_ROLES, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentMemoryFields, type AgentMemoryOptions, type AgentMemoryRead, AgentMemoryStore, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, type CachedMessage, CloudflareClient, type CreateAgentOptions, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DEFAULT_SESSION_MAX_AGE_MS, DNSConfigurator, type Database, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, type EmailEnvelope, type EmailRouteAction, type EmailRouteClass, type EmailRouteClassification, type EmailRouteInput, EmailSearchIndex, type FolderInfo, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type HostName, type HostSession, type InboundEmail, type InboundSmsEvent, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, type LinkAdvisory, type LocalSmtpConfig, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, type ParsedAttachment, type ParsedEmail, type ParsedSms, PathTraversalError, type PurchasedDomain, REDACTED, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, SPAM_THRESHOLD, type SafeJoinOptions, type SanitizeDetection, type SanitizeResult, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, type SendSmsInput, type SendSmsResult, ServiceManager, type ServiceStatus, type SetupConfig, SetupManager, type SetupResult, type Severity, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SmsProvider, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, ThreadCache, type ThreadCacheEntry, type ThreadCacheOptions, type ThreadIdInput, type TunnelConfig, TunnelManager, UnsafeApiUrlError, WARNING_THRESHOLD, type WatcherOptions, assertWithinBase, buildApiUrl, buildInboundSecurityAdvisory, classifyEmailRoute, closeDatabase, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, forgetHostSession, getDatabase, getOperatorEmail, getSmsProvider, hostSessionStoragePath, isInternalEmail, isSessionFresh, isValidPhoneNumber, loadHostSession, mapProviderSmsStatus, normalizeAddress, normalizePhoneNumber, normalizeSubject, operatorPrefsStoragePath, parseEmail, parseGoogleVoiceSms, recordToolCall, redactObject, redactSecret, redactSmsConfig, resolveConfig, safeJoin, sanitizeEmail, saveConfig, saveHostSession, scanOutboundEmail, scoreEmail, setOperatorEmail, setTelemetryVersion, startRelayBridge, threadIdFor, tryJoin, validateApiUrl };
2722
+ export { AGENT_ROLES, AccountManager, type AddressInfo, type Agent, AgentDeletionService, type AgentMemoryFields, type AgentMemoryOptions, type AgentMemoryRead, AgentMemoryStore, type AgentRole, AgenticMailClient, type AgenticMailClientOptions, type AgenticMailConfig, type ArchiveAndDeleteOptions, type ArchivedEmail, type Attachment, type AttachmentAdvisory, BRIDGE_OPERATOR_LIVE_WINDOW_MS, type BridgeMailContext, type BridgeWakeError, type BridgeWakePromptArgs, type BridgeWakeResult, type BridgeWakeRoute, type CachedMessage, CloudflareClient, type CreateAgentOptions, DEFAULT_AGENT_NAME, DEFAULT_AGENT_ROLE, DEFAULT_SESSION_MAX_AGE_MS, DNSConfigurator, type Database, type DeletionReport, type DeletionSummary, DependencyChecker, DependencyInstaller, type DependencyStatus, type DnsRecord, type DnsSetupResult, type DomainInfo, DomainManager, type DomainModeConfig, type DomainPurchaseResult, DomainPurchaser, type DomainSearchResult, type DomainSetupResult, type EmailEnvelope, type EmailRouteAction, type EmailRouteClass, type EmailRouteClassification, type EmailRouteInput, EmailSearchIndex, type FolderInfo, type GatewayConfig, GatewayManager, type GatewayManagerOptions, type GatewayMode, type GatewayStatus, type HostName, type HostSession, type HostSessionResumeMode, type InboundEmail, type InboundSmsEvent, type InboxEvent, type InboxExpungeEvent, type InboxFlagsEvent, type InboxNewEvent, InboxWatcher, type InboxWatcherOptions, type InstallProgress, type LinkAdvisory, type LocalSmtpConfig, MailReceiver, type MailReceiverOptions, MailSender, type MailSenderOptions, type MailboxInfo, type OutboundCategory, type OutboundScanInput, type OutboundScanResult, type OutboundWarning, type ParsedAttachment, type ParsedEmail, type ParsedSms, PathTraversalError, type PlanBridgeWakeArgs, type PurchasedDomain, REDACTED, RELAY_PRESETS, RelayBridge, type RelayBridgeOptions, type RelayConfig, RelayGateway, type RelayProvider, type RelaySearchResult, type ResumeErrorClassificationOptions, SPAM_THRESHOLD, type SafeJoinOptions, type SanitizeDetection, type SanitizeResult, type SearchCriteria, type SearchableEmail, type SecurityAdvisory, type SendMailOptions, type SendResult, type SendResultWithRaw, type SendSmsInput, type SendSmsResult, ServiceManager, type ServiceStatus, type SetupConfig, SetupManager, type SetupResult, type Severity, type SmsConfig, SmsManager, type SmsMessage, SmsPoller, type SmsProvider, type SpamCategory, type SpamResult, type SpamRuleMatch, StalwartAdmin, type StalwartAdminOptions, type StalwartPrincipal, ThreadCache, type ThreadCacheEntry, type ThreadCacheOptions, type ThreadIdInput, type TunnelConfig, TunnelManager, UnsafeApiUrlError, WARNING_THRESHOLD, type WatcherOptions, assertWithinBase, bridgeWakeErrorMessage, bridgeWakeLastSeenAgeMs, buildApiUrl, buildInboundSecurityAdvisory, classifyEmailRoute, classifyResumeError, closeDatabase, composeBridgeWakePrompt, createTestDatabase, debug, debugWarn, ensureDataDir, extractVerificationCode, flushTelemetry, forgetHostSession, getDatabase, getOperatorEmail, getSmsProvider, hostSessionStoragePath, isInternalEmail, isSessionFresh, isValidPhoneNumber, loadHostSession, mapProviderSmsStatus, normalizeAddress, normalizePhoneNumber, normalizeSubject, operatorPrefsStoragePath, parseEmail, parseGoogleVoiceSms, planBridgeWake, recordToolCall, redactObject, redactSecret, redactSmsConfig, resolveConfig, safeJoin, sanitizeEmail, saveConfig, saveHostSession, scanOutboundEmail, scoreEmail, setOperatorEmail, setTelemetryVersion, shouldSkipBridgeWakeForLiveOperator, startRelayBridge, threadIdFor, tryJoin, validateApiUrl };
package/dist/index.js CHANGED
@@ -24,8 +24,7 @@ var MailSender = class {
24
24
  pass: options.password
25
25
  },
26
26
  tls: {
27
- rejectUnauthorized: false
28
- // Local dev — no TLS
27
+ rejectUnauthorized: options.tlsRejectUnauthorized ?? true
29
28
  },
30
29
  connectionTimeout: 1e4,
31
30
  // 10s to establish TCP connection
@@ -1293,7 +1292,13 @@ function rowToAgent(row) {
1293
1292
  // Old rows (pre-migration-016) have undefined `wake_on_cc`;
1294
1293
  // treat that as the default-true (respect sender's wake list
1295
1294
  // as-is). Only explicit 0 disables CC wakes for this agent.
1296
- wakeOnCc: row.wake_on_cc !== void 0 ? row.wake_on_cc !== 0 : true
1295
+ wakeOnCc: row.wake_on_cc !== void 0 ? row.wake_on_cc !== 0 : true,
1296
+ // Pre-migration-017 rows have undefined `stopped`; treat as
1297
+ // not-stopped. The dispatcher only blocks wakes when this is
1298
+ // explicitly truthy, so the default mirrors back-compat.
1299
+ stopped: row.stopped !== void 0 ? row.stopped !== 0 : false,
1300
+ stoppedAt: row.stopped_at ?? null,
1301
+ stoppedReason: row.stopped_reason ?? null
1297
1302
  };
1298
1303
  }
1299
1304
  var AccountManager = class {
@@ -2907,6 +2912,22 @@ ALTER TABLE drafts ADD COLUMN attachments TEXT;
2907
2912
  -- explicitly named" preference from the wake-thrash feedback.
2908
2913
  -- Defaults to 1 (respect the senders wake list as-is).
2909
2914
  ALTER TABLE agents ADD COLUMN wake_on_cc INTEGER NOT NULL DEFAULT 1;
2915
+ `,
2916
+ "017_agent_stopped.sql": `
2917
+ -- Soft-stop for an agent mid-task. When 1, the dispatcher refuses
2918
+ -- to wake this agent for ANY reason \u2014 allowlists, To/Cc, task
2919
+ -- notifications, all of it. Mail still gets delivered to the
2920
+ -- mailbox so the audit trail of the thread stays intact. This
2921
+ -- is the non-destructive counterpart to delete_agent: stops an
2922
+ -- agent that's currently churning without losing its inbox or
2923
+ -- the thread history.
2924
+ --
2925
+ -- Companion columns capture WHEN it was stopped and the OPTIONAL
2926
+ -- reason the caller passed (e.g. "task superseded", "budget
2927
+ -- exhausted") so an operator can audit later.
2928
+ ALTER TABLE agents ADD COLUMN stopped INTEGER NOT NULL DEFAULT 0;
2929
+ ALTER TABLE agents ADD COLUMN stopped_at TEXT;
2930
+ ALTER TABLE agents ADD COLUMN stopped_reason TEXT;
2910
2931
  `
2911
2932
  };
2912
2933
  function runMigrations(database) {
@@ -3180,9 +3201,67 @@ function formatPollError(err) {
3180
3201
  const stdout = (e.stdout ?? "").toString().trim();
3181
3202
  if (stdout && !stderr) parts.push(`stdout=${truncate(stdout, 240)}`);
3182
3203
  if (parts.length === 1 && /^command failed$/i.test(head)) {
3183
- return `${head} (no further detail available \u2014 wrapping error did not carry stderr/code/response)`;
3204
+ return redactCredentialTokens(`${head} (no further detail available \u2014 wrapping error did not carry stderr/code/response)`);
3184
3205
  }
3185
- return parts.join(" | ");
3206
+ return redactCredentialTokens(parts.join(" | "));
3207
+ }
3208
+ function redactCredentialTokens(text) {
3209
+ return text.replace(/\b(AUTH(?:ENTICATE)?)\s+(PLAIN|LOGIN|XOAUTH2|CRAM-MD5|EXTERNAL)\s+\S+/gi, "$1 $2 [redacted]").replace(/\b(AUTH(?:ENTICATE)?)\s+([A-Za-z0-9+/]{16,}={0,2})\b/gi, "$1 [redacted]");
3210
+ }
3211
+ function isRelayCredentialError(err) {
3212
+ if (!err) return false;
3213
+ const haystack = typeof err === "object" ? (() => {
3214
+ const e = err;
3215
+ return [
3216
+ e.message,
3217
+ e.code,
3218
+ e.responseCode,
3219
+ e.response,
3220
+ e.responseText,
3221
+ e.command,
3222
+ e.serverResponse
3223
+ ].filter(Boolean).join(" ").toLowerCase();
3224
+ })() : String(err).toLowerCase();
3225
+ return [
3226
+ "eauth",
3227
+ "authenticationfailed",
3228
+ "authentication failed",
3229
+ "authenticate failed",
3230
+ "invalid credentials",
3231
+ "invalid login",
3232
+ "login failed",
3233
+ "username and password not accepted",
3234
+ "application-specific password",
3235
+ "app password",
3236
+ "badcredentials",
3237
+ "invalid_grant",
3238
+ "invalid_token",
3239
+ "expired token",
3240
+ "token expired",
3241
+ "access token has expired",
3242
+ "token has expired",
3243
+ "token is expired",
3244
+ "token is invalid",
3245
+ "token revoked",
3246
+ "xoauth2",
3247
+ "aadsts",
3248
+ "535",
3249
+ "534",
3250
+ "5.7.8"
3251
+ ].some((marker) => haystack.includes(marker));
3252
+ }
3253
+ function formatRelayError(err, config, phase) {
3254
+ const detail = formatPollError(err);
3255
+ if (!isRelayCredentialError(err)) {
3256
+ return `Relay ${phase} failed: ${detail}`;
3257
+ }
3258
+ const provider = config.provider === "gmail" ? "Gmail" : config.provider === "outlook" ? "Outlook/Microsoft 365" : "custom";
3259
+ const action = config.provider === "gmail" ? "Create a fresh Gmail app password or reconnect the relay, then run agenticmail setup-relay again." : config.provider === "outlook" ? "Refresh/recreate the Microsoft relay credential or OAuth token, then run agenticmail setup-relay again." : "Refresh the relay credential, then run agenticmail setup-relay again.";
3260
+ return [
3261
+ `Relay ${phase} failed: ${provider} relay authentication for ${config.email} is invalid, expired, or revoked.`,
3262
+ action,
3263
+ `Original error: ${detail}`
3264
+ ].join(" ");
3186
3265
  }
3187
3266
  function truncate(s, max) {
3188
3267
  if (s.length <= max) return s;
@@ -3237,7 +3316,11 @@ var RelayGateway = class {
3237
3316
  pass: config.password
3238
3317
  }
3239
3318
  });
3240
- await this.smtpTransport.verify();
3319
+ try {
3320
+ await this.smtpTransport.verify();
3321
+ } catch (err) {
3322
+ throw new Error(formatRelayError(err, config, "SMTP verification"));
3323
+ }
3241
3324
  const imap = new ImapFlow3({
3242
3325
  host: config.imapHost,
3243
3326
  port: config.imapPort,
@@ -3248,8 +3331,16 @@ var RelayGateway = class {
3248
3331
  },
3249
3332
  logger: false
3250
3333
  });
3251
- await imap.connect();
3252
- await imap.logout();
3334
+ try {
3335
+ await imap.connect();
3336
+ } catch (err) {
3337
+ throw new Error(formatRelayError(err, config, "IMAP verification"));
3338
+ } finally {
3339
+ try {
3340
+ await imap.logout();
3341
+ } catch {
3342
+ }
3343
+ }
3253
3344
  }
3254
3345
  /**
3255
3346
  * Send an email through the relay SMTP server.
@@ -3259,9 +3350,10 @@ var RelayGateway = class {
3259
3350
  if (!this.config || !this.smtpTransport) {
3260
3351
  throw new Error("Relay not configured. Call setup() first.");
3261
3352
  }
3262
- const atIdx = this.config.email.lastIndexOf("@");
3263
- const localPart2 = this.config.email.slice(0, atIdx);
3264
- const domain = this.config.email.slice(atIdx + 1);
3353
+ const relayConfig = this.config;
3354
+ const atIdx = relayConfig.email.lastIndexOf("@");
3355
+ const localPart2 = relayConfig.email.slice(0, atIdx);
3356
+ const domain = relayConfig.email.slice(atIdx + 1);
3265
3357
  const relayFrom = `${localPart2}+${agentName}@${domain}`;
3266
3358
  const displayName = mail.fromName || agentName;
3267
3359
  const mailOpts = {
@@ -3285,7 +3377,12 @@ var RelayGateway = class {
3285
3377
  };
3286
3378
  const composer = new MailComposer2(mailOpts);
3287
3379
  const raw = await composer.compile().build();
3288
- const result = await this.smtpTransport.sendMail(mailOpts);
3380
+ let result;
3381
+ try {
3382
+ result = await this.smtpTransport.sendMail(mailOpts);
3383
+ } catch (err) {
3384
+ throw new Error(formatRelayError(err, relayConfig, "SMTP send"));
3385
+ }
3289
3386
  if (result.messageId) {
3290
3387
  this.sentMessageIds.set(result.messageId, agentName);
3291
3388
  if (this.sentMessageIds.size > 1e4) {
@@ -3362,7 +3459,7 @@ var RelayGateway = class {
3362
3459
  this.consecutiveFailures = 0;
3363
3460
  } catch (err) {
3364
3461
  this.consecutiveFailures++;
3365
- const msg = formatPollError(err);
3462
+ const msg = this.config ? formatRelayError(err, this.config, "IMAP poll") : formatPollError(err);
3366
3463
  console.error(`[RelayGateway] Poll failed (attempt ${this.consecutiveFailures}): ${msg}`);
3367
3464
  if (this.consecutiveFailures >= 5 && this.consecutiveFailures % 5 === 0) {
3368
3465
  console.error(`[RelayGateway] ${this.consecutiveFailures} consecutive failures \u2014 check IMAP credentials and connectivity (${this.config?.imapHost}:${this.config?.imapPort})`);
@@ -3595,13 +3692,14 @@ var RelayGateway = class {
3595
3692
  */
3596
3693
  async searchRelay(criteria, maxResults = 50) {
3597
3694
  if (!this.config) throw new Error("Relay not configured");
3695
+ const relayConfig = this.config;
3598
3696
  const imap = new ImapFlow3({
3599
- host: this.config.imapHost,
3600
- port: this.config.imapPort,
3601
- secure: this.config.imapPort === 993,
3697
+ host: relayConfig.imapHost,
3698
+ port: relayConfig.imapPort,
3699
+ secure: relayConfig.imapPort === 993,
3602
3700
  auth: {
3603
- user: this.config.email,
3604
- pass: this.config.password
3701
+ user: relayConfig.email,
3702
+ pass: relayConfig.password
3605
3703
  },
3606
3704
  logger: false
3607
3705
  });
@@ -3638,7 +3736,7 @@ var RelayGateway = class {
3638
3736
  results.push({
3639
3737
  uid,
3640
3738
  source: "relay",
3641
- account: this.config.email,
3739
+ account: relayConfig.email,
3642
3740
  messageId: envelope.messageId ?? "",
3643
3741
  subject: envelope.subject ?? "",
3644
3742
  from: (envelope.from ?? []).map((a) => ({ name: a.name, address: a.address ?? "" })),
@@ -3654,7 +3752,7 @@ var RelayGateway = class {
3654
3752
  lock.release();
3655
3753
  }
3656
3754
  } catch (err) {
3657
- console.error("[RelayGateway] Relay search failed:", err instanceof Error ? err.message : err);
3755
+ console.error("[RelayGateway] Relay search failed:", formatRelayError(err, relayConfig, "IMAP search"));
3658
3756
  return [];
3659
3757
  } finally {
3660
3758
  try {
@@ -3669,13 +3767,14 @@ var RelayGateway = class {
3669
3767
  */
3670
3768
  async fetchRelayMessage(uid) {
3671
3769
  if (!this.config) throw new Error("Relay not configured");
3770
+ const relayConfig = this.config;
3672
3771
  const imap = new ImapFlow3({
3673
- host: this.config.imapHost,
3674
- port: this.config.imapPort,
3675
- secure: this.config.imapPort === 993,
3772
+ host: relayConfig.imapHost,
3773
+ port: relayConfig.imapPort,
3774
+ secure: relayConfig.imapPort === 993,
3676
3775
  auth: {
3677
- user: this.config.email,
3678
- pass: this.config.password
3776
+ user: relayConfig.email,
3777
+ pass: relayConfig.password
3679
3778
  },
3680
3779
  logger: false
3681
3780
  });
@@ -3712,7 +3811,7 @@ var RelayGateway = class {
3712
3811
  lock.release();
3713
3812
  }
3714
3813
  } catch (err) {
3715
- console.error("[RelayGateway] Fetch relay message failed:", err instanceof Error ? err.message : err);
3814
+ console.error("[RelayGateway] Fetch relay message failed:", formatRelayError(err, relayConfig, "IMAP fetch"));
3716
3815
  return null;
3717
3816
  } finally {
3718
3817
  try {
@@ -6447,6 +6546,95 @@ function hostSessionStoragePath() {
6447
6546
  return storagePath();
6448
6547
  }
6449
6548
 
6549
+ // src/host-bridge.ts
6550
+ var BRIDGE_OPERATOR_LIVE_WINDOW_MS = 3e4;
6551
+ var DEFAULT_EXPIRED_MARKERS = [
6552
+ "session not found",
6553
+ "invalid session",
6554
+ "session expired",
6555
+ "no such session",
6556
+ "unknown session",
6557
+ "thread not found",
6558
+ "invalid thread",
6559
+ "thread expired",
6560
+ "no such thread",
6561
+ "unknown thread"
6562
+ ];
6563
+ var DEFAULT_SDK_MISSING_MARKERS = [
6564
+ "cannot find module",
6565
+ "could not be found",
6566
+ "command not found"
6567
+ ];
6568
+ function bridgeWakeErrorMessage(err) {
6569
+ return err?.message ?? String(err);
6570
+ }
6571
+ function classifyResumeError(err, options = {}) {
6572
+ const msg = bridgeWakeErrorMessage(err).toLowerCase();
6573
+ const expiredMarkers = options.expiredMarkers ?? DEFAULT_EXPIRED_MARKERS;
6574
+ const sdkMissingMarkers = options.sdkMissingMarkers ?? DEFAULT_SDK_MISSING_MARKERS;
6575
+ if (expiredMarkers.some((marker) => msg.includes(marker))) return "session-expired";
6576
+ if (sdkMissingMarkers.some((marker) => msg.includes(marker))) return "sdk-missing";
6577
+ return "other";
6578
+ }
6579
+ function bridgeWakeLastSeenAgeMs(session, nowMs = Date.now()) {
6580
+ if (!session) return null;
6581
+ return nowMs - session.lastSeenMs;
6582
+ }
6583
+ function shouldSkipBridgeWakeForLiveOperator(session, nowMs = Date.now(), liveWindowMs = BRIDGE_OPERATOR_LIVE_WINDOW_MS) {
6584
+ const ageMs = bridgeWakeLastSeenAgeMs(session, nowMs);
6585
+ return ageMs !== null && ageMs < liveWindowMs;
6586
+ }
6587
+ function planBridgeWake(args) {
6588
+ const nowMs = args.nowMs ?? Date.now();
6589
+ const liveWindowMs = args.liveWindowMs ?? BRIDGE_OPERATOR_LIVE_WINDOW_MS;
6590
+ const ageMs = bridgeWakeLastSeenAgeMs(args.session, nowMs);
6591
+ if (ageMs !== null && ageMs < liveWindowMs) {
6592
+ return {
6593
+ action: "skip-live",
6594
+ reason: "operator-live",
6595
+ ageMs,
6596
+ mail: args.mail
6597
+ };
6598
+ }
6599
+ if (!args.session) {
6600
+ return {
6601
+ action: "escalate",
6602
+ reason: "no-fresh-session",
6603
+ mail: args.mail
6604
+ };
6605
+ }
6606
+ return {
6607
+ action: "resume",
6608
+ session: args.session,
6609
+ prompt: composeBridgeWakePrompt(args.mail),
6610
+ mail: args.mail
6611
+ };
6612
+ }
6613
+ function composeBridgeWakePrompt(args) {
6614
+ const subject = args.subject ?? "(no subject)";
6615
+ const from = args.from ?? "unknown";
6616
+ const preview = (args.preview ?? "").slice(0, 600);
6617
+ return [
6618
+ `\u{1F380} Bridge mail arrived \u2014 headless wake.`,
6619
+ "",
6620
+ `You are being resumed against your last session because new mail landed in your bridge inbox (${args.bridgeName}@localhost) and you weren't actively at the keyboard.`,
6621
+ "",
6622
+ `Trigger:`,
6623
+ ` UID: ${args.uid}`,
6624
+ ` From: ${from}`,
6625
+ ` Subject: ${subject}`,
6626
+ ` Preview: ${preview}`,
6627
+ "",
6628
+ `Read it with mcp__agenticmail__read_email({ uid: ${args.uid} }) and decide:`,
6629
+ ` \xB7 Does it need a reply from YOU (the operator's session)? Reply via mcp__agenticmail__reply_email.`,
6630
+ ` \xB7 Does it need a teammate to act? Forward / re-route by replying with wake: ["<teammate>"].`,
6631
+ ` \xB7 Is it [NEEDS OPERATOR] / [BLOCKED]? Then it's actually for the human \u2014 mark it unread, and the operator will see it on their next keystroke.`,
6632
+ ` \xB7 Is it FYI noise? mark_read and exit.`,
6633
+ "",
6634
+ `Keep this turn SHORT. You're being resumed to handle ONE piece of mail, not to continue the prior conversation.`
6635
+ ].join("\n");
6636
+ }
6637
+
6450
6638
  // src/util/safe-url.ts
6451
6639
  var UnsafeApiUrlError = class extends Error {
6452
6640
  constructor(raw, reason) {
@@ -8138,6 +8326,7 @@ export {
8138
8326
  AgentDeletionService,
8139
8327
  AgentMemoryStore,
8140
8328
  AgenticMailClient,
8329
+ BRIDGE_OPERATOR_LIVE_WINDOW_MS,
8141
8330
  CloudflareClient,
8142
8331
  DEFAULT_AGENT_NAME,
8143
8332
  DEFAULT_AGENT_ROLE,
@@ -8168,10 +8357,14 @@ export {
8168
8357
  UnsafeApiUrlError,
8169
8358
  WARNING_THRESHOLD,
8170
8359
  assertWithinBase,
8360
+ bridgeWakeErrorMessage,
8361
+ bridgeWakeLastSeenAgeMs,
8171
8362
  buildApiUrl,
8172
8363
  buildInboundSecurityAdvisory,
8173
8364
  classifyEmailRoute,
8365
+ classifyResumeError,
8174
8366
  closeDatabase,
8367
+ composeBridgeWakePrompt,
8175
8368
  createTestDatabase,
8176
8369
  debug,
8177
8370
  debugWarn,
@@ -8194,6 +8387,7 @@ export {
8194
8387
  operatorPrefsStoragePath,
8195
8388
  parseEmail,
8196
8389
  parseGoogleVoiceSms,
8390
+ planBridgeWake,
8197
8391
  recordToolCall,
8198
8392
  redactObject,
8199
8393
  redactSecret,
@@ -8207,6 +8401,7 @@ export {
8207
8401
  scoreEmail,
8208
8402
  setOperatorEmail,
8209
8403
  setTelemetryVersion,
8404
+ shouldSkipBridgeWakeForLiveOperator,
8210
8405
  startRelayBridge,
8211
8406
  threadIdFor,
8212
8407
  tryJoin,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/core",
3
- "version": "0.9.9",
3
+ "version": "0.9.11",
4
4
  "description": "Core SDK for AgenticMail — email, SMS, and phone number access for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",