@agenticmail/core 0.5.59 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +83 -10
- package/dist/index.d.cts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +83 -10
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3653,6 +3653,40 @@ var import_nodemailer2 = __toESM(require("nodemailer"), 1);
|
|
|
3653
3653
|
var import_mail_composer2 = __toESM(require("nodemailer/lib/mail-composer/index.js"), 1);
|
|
3654
3654
|
var import_imapflow3 = require("imapflow");
|
|
3655
3655
|
var import_mailparser2 = require("mailparser");
|
|
3656
|
+
function formatPollError(err) {
|
|
3657
|
+
if (!err) return "unknown error (no error object)";
|
|
3658
|
+
if (typeof err !== "object") return String(err);
|
|
3659
|
+
const e = err;
|
|
3660
|
+
const parts = [];
|
|
3661
|
+
const head = (e.message ?? String(err)).toString().trim();
|
|
3662
|
+
if (head) parts.push(head);
|
|
3663
|
+
if (e.code && e.code !== head) parts.push(`code=${e.code}`);
|
|
3664
|
+
if (typeof e.errno === "number") parts.push(`errno=${e.errno}`);
|
|
3665
|
+
if (e.syscall) parts.push(`syscall=${e.syscall}`);
|
|
3666
|
+
if (e.hostname) parts.push(`host=${e.hostname}`);
|
|
3667
|
+
if (typeof e.port === "number") parts.push(`port=${e.port}`);
|
|
3668
|
+
if (e.responseText && e.responseText !== head) {
|
|
3669
|
+
parts.push(`response=${truncate(String(e.responseText), 240)}`);
|
|
3670
|
+
} else if (e.response && e.response !== head) {
|
|
3671
|
+
parts.push(`response=${truncate(String(e.response), 240)}`);
|
|
3672
|
+
}
|
|
3673
|
+
if (e.command) parts.push(`command=${e.command}`);
|
|
3674
|
+
if (typeof e.exitCode === "number") parts.push(`exit=${e.exitCode}`);
|
|
3675
|
+
else if (e.code && /^\d+$/.test(String(e.code))) parts.push(`exit=${e.code}`);
|
|
3676
|
+
if (e.signal) parts.push(`signal=${e.signal}`);
|
|
3677
|
+
const stderr = (e.stderr ?? "").toString().trim();
|
|
3678
|
+
if (stderr) parts.push(`stderr=${truncate(stderr, 240)}`);
|
|
3679
|
+
const stdout = (e.stdout ?? "").toString().trim();
|
|
3680
|
+
if (stdout && !stderr) parts.push(`stdout=${truncate(stdout, 240)}`);
|
|
3681
|
+
if (parts.length === 1 && /^command failed$/i.test(head)) {
|
|
3682
|
+
return `${head} (no further detail available \u2014 wrapping error did not carry stderr/code/response)`;
|
|
3683
|
+
}
|
|
3684
|
+
return parts.join(" | ");
|
|
3685
|
+
}
|
|
3686
|
+
function truncate(s, max) {
|
|
3687
|
+
if (s.length <= max) return s;
|
|
3688
|
+
return s.slice(0, max - 1).trimEnd() + "\u2026";
|
|
3689
|
+
}
|
|
3656
3690
|
var RelayGateway = class {
|
|
3657
3691
|
smtpTransport = null;
|
|
3658
3692
|
pollTimer = null;
|
|
@@ -3827,7 +3861,7 @@ var RelayGateway = class {
|
|
|
3827
3861
|
this.consecutiveFailures = 0;
|
|
3828
3862
|
} catch (err) {
|
|
3829
3863
|
this.consecutiveFailures++;
|
|
3830
|
-
const msg =
|
|
3864
|
+
const msg = formatPollError(err);
|
|
3831
3865
|
console.error(`[RelayGateway] Poll failed (attempt ${this.consecutiveFailures}): ${msg}`);
|
|
3832
3866
|
if (this.consecutiveFailures >= 5 && this.consecutiveFailures % 5 === 0) {
|
|
3833
3867
|
console.error(`[RelayGateway] ${this.consecutiveFailures} consecutive failures \u2014 check IMAP credentials and connectivity (${this.config?.imapHost}:${this.config?.imapPort})`);
|
|
@@ -6085,20 +6119,23 @@ var GatewayManager = class {
|
|
|
6085
6119
|
}
|
|
6086
6120
|
/**
|
|
6087
6121
|
* Resume gateway from saved config (e.g., after server restart).
|
|
6122
|
+
*
|
|
6123
|
+
* Issue #31 — On a Docker container restart the API can come up
|
|
6124
|
+
* before Stalwart / Gmail IMAP / DNS is reachable, so the very first
|
|
6125
|
+
* setup() can fail with a transient network error. Previously that
|
|
6126
|
+
* single failure was logged and never retried, leaving polling
|
|
6127
|
+
* permanently dead until someone noticed and manually revived the
|
|
6128
|
+
* relay. We now schedule background retries with exponential backoff
|
|
6129
|
+
* (5s, 10s, 20s, 40s, 60s cap, indefinite) so the relay
|
|
6130
|
+
* self-recovers as soon as the dependency is reachable again.
|
|
6088
6131
|
*/
|
|
6089
6132
|
async resume() {
|
|
6090
6133
|
if (this.config.mode === "relay" && this.config.relay) {
|
|
6091
6134
|
try {
|
|
6092
|
-
await this.
|
|
6093
|
-
const savedUid = this.loadLastSeenUid();
|
|
6094
|
-
if (savedUid > 0) {
|
|
6095
|
-
this.relay.setLastSeenUid(savedUid);
|
|
6096
|
-
console.log(`[GatewayManager] Restored lastSeenUid=${savedUid} from database`);
|
|
6097
|
-
}
|
|
6098
|
-
this.relay.onUidAdvance = (uid) => this.saveLastSeenUid(uid);
|
|
6099
|
-
await this.relay.startPolling();
|
|
6135
|
+
await this._resumeRelayOnce();
|
|
6100
6136
|
} catch (err) {
|
|
6101
|
-
console.error("[GatewayManager]
|
|
6137
|
+
console.error("[GatewayManager] Initial relay resume failed; scheduling retries:", formatPollError(err));
|
|
6138
|
+
this._scheduleRelayResumeRetry();
|
|
6102
6139
|
}
|
|
6103
6140
|
}
|
|
6104
6141
|
if (this.smsManager && this.accountManager) {
|
|
@@ -6125,6 +6162,42 @@ var GatewayManager = class {
|
|
|
6125
6162
|
}
|
|
6126
6163
|
}
|
|
6127
6164
|
}
|
|
6165
|
+
// ─── Issue #31 helpers — resume retry with backoff ───
|
|
6166
|
+
_resumeRetryTimer = null;
|
|
6167
|
+
_resumeRetryAttempt = 0;
|
|
6168
|
+
async _resumeRelayOnce() {
|
|
6169
|
+
if (!this.config.relay) throw new Error("No relay config to resume");
|
|
6170
|
+
await this.relay.setup(this.config.relay);
|
|
6171
|
+
const savedUid = this.loadLastSeenUid();
|
|
6172
|
+
if (savedUid > 0) {
|
|
6173
|
+
this.relay.setLastSeenUid(savedUid);
|
|
6174
|
+
console.log(`[GatewayManager] Restored lastSeenUid=${savedUid} from database`);
|
|
6175
|
+
}
|
|
6176
|
+
this.relay.onUidAdvance = (uid) => this.saveLastSeenUid(uid);
|
|
6177
|
+
await this.relay.startPolling();
|
|
6178
|
+
if (this._resumeRetryAttempt > 0) {
|
|
6179
|
+
console.log(`[GatewayManager] Relay polling resumed after ${this._resumeRetryAttempt} retry attempt${this._resumeRetryAttempt !== 1 ? "s" : ""}`);
|
|
6180
|
+
}
|
|
6181
|
+
this._resumeRetryAttempt = 0;
|
|
6182
|
+
}
|
|
6183
|
+
_scheduleRelayResumeRetry() {
|
|
6184
|
+
if (this._resumeRetryTimer) return;
|
|
6185
|
+
this._resumeRetryAttempt++;
|
|
6186
|
+
const base = Math.min(5e3 * Math.pow(2, this._resumeRetryAttempt - 1), 6e4);
|
|
6187
|
+
const jitter = base * (0.8 + Math.random() * 0.4);
|
|
6188
|
+
const delay = Math.round(jitter);
|
|
6189
|
+
console.log(`[GatewayManager] Will retry relay resume in ${(delay / 1e3).toFixed(1)}s (attempt ${this._resumeRetryAttempt + 1})`);
|
|
6190
|
+
this._resumeRetryTimer = setTimeout(async () => {
|
|
6191
|
+
this._resumeRetryTimer = null;
|
|
6192
|
+
if (this.config.mode !== "relay" || !this.config.relay) return;
|
|
6193
|
+
try {
|
|
6194
|
+
await this._resumeRelayOnce();
|
|
6195
|
+
} catch (err) {
|
|
6196
|
+
console.error(`[GatewayManager] Relay resume retry ${this._resumeRetryAttempt} failed:`, formatPollError(err));
|
|
6197
|
+
this._scheduleRelayResumeRetry();
|
|
6198
|
+
}
|
|
6199
|
+
}, delay);
|
|
6200
|
+
}
|
|
6128
6201
|
// --- Persistence ---
|
|
6129
6202
|
loadConfig() {
|
|
6130
6203
|
const row = this.db.prepare("SELECT * FROM gateway_config WHERE id = ?").get("default");
|
package/dist/index.d.cts
CHANGED
|
@@ -1244,8 +1244,21 @@ declare class GatewayManager {
|
|
|
1244
1244
|
shutdown(): Promise<void>;
|
|
1245
1245
|
/**
|
|
1246
1246
|
* Resume gateway from saved config (e.g., after server restart).
|
|
1247
|
+
*
|
|
1248
|
+
* Issue #31 — On a Docker container restart the API can come up
|
|
1249
|
+
* before Stalwart / Gmail IMAP / DNS is reachable, so the very first
|
|
1250
|
+
* setup() can fail with a transient network error. Previously that
|
|
1251
|
+
* single failure was logged and never retried, leaving polling
|
|
1252
|
+
* permanently dead until someone noticed and manually revived the
|
|
1253
|
+
* relay. We now schedule background retries with exponential backoff
|
|
1254
|
+
* (5s, 10s, 20s, 40s, 60s cap, indefinite) so the relay
|
|
1255
|
+
* self-recovers as soon as the dependency is reachable again.
|
|
1247
1256
|
*/
|
|
1248
1257
|
resume(): Promise<void>;
|
|
1258
|
+
private _resumeRetryTimer;
|
|
1259
|
+
private _resumeRetryAttempt;
|
|
1260
|
+
private _resumeRelayOnce;
|
|
1261
|
+
private _scheduleRelayResumeRetry;
|
|
1249
1262
|
private loadConfig;
|
|
1250
1263
|
private saveConfig;
|
|
1251
1264
|
private saveLastSeenUid;
|
package/dist/index.d.ts
CHANGED
|
@@ -1244,8 +1244,21 @@ declare class GatewayManager {
|
|
|
1244
1244
|
shutdown(): Promise<void>;
|
|
1245
1245
|
/**
|
|
1246
1246
|
* Resume gateway from saved config (e.g., after server restart).
|
|
1247
|
+
*
|
|
1248
|
+
* Issue #31 — On a Docker container restart the API can come up
|
|
1249
|
+
* before Stalwart / Gmail IMAP / DNS is reachable, so the very first
|
|
1250
|
+
* setup() can fail with a transient network error. Previously that
|
|
1251
|
+
* single failure was logged and never retried, leaving polling
|
|
1252
|
+
* permanently dead until someone noticed and manually revived the
|
|
1253
|
+
* relay. We now schedule background retries with exponential backoff
|
|
1254
|
+
* (5s, 10s, 20s, 40s, 60s cap, indefinite) so the relay
|
|
1255
|
+
* self-recovers as soon as the dependency is reachable again.
|
|
1247
1256
|
*/
|
|
1248
1257
|
resume(): Promise<void>;
|
|
1258
|
+
private _resumeRetryTimer;
|
|
1259
|
+
private _resumeRetryAttempt;
|
|
1260
|
+
private _resumeRelayOnce;
|
|
1261
|
+
private _scheduleRelayResumeRetry;
|
|
1249
1262
|
private loadConfig;
|
|
1250
1263
|
private saveConfig;
|
|
1251
1264
|
private saveLastSeenUid;
|
package/dist/index.js
CHANGED
|
@@ -2896,6 +2896,40 @@ import nodemailer2 from "nodemailer";
|
|
|
2896
2896
|
import MailComposer2 from "nodemailer/lib/mail-composer/index.js";
|
|
2897
2897
|
import { ImapFlow as ImapFlow3 } from "imapflow";
|
|
2898
2898
|
import { simpleParser as simpleParser2 } from "mailparser";
|
|
2899
|
+
function formatPollError(err) {
|
|
2900
|
+
if (!err) return "unknown error (no error object)";
|
|
2901
|
+
if (typeof err !== "object") return String(err);
|
|
2902
|
+
const e = err;
|
|
2903
|
+
const parts = [];
|
|
2904
|
+
const head = (e.message ?? String(err)).toString().trim();
|
|
2905
|
+
if (head) parts.push(head);
|
|
2906
|
+
if (e.code && e.code !== head) parts.push(`code=${e.code}`);
|
|
2907
|
+
if (typeof e.errno === "number") parts.push(`errno=${e.errno}`);
|
|
2908
|
+
if (e.syscall) parts.push(`syscall=${e.syscall}`);
|
|
2909
|
+
if (e.hostname) parts.push(`host=${e.hostname}`);
|
|
2910
|
+
if (typeof e.port === "number") parts.push(`port=${e.port}`);
|
|
2911
|
+
if (e.responseText && e.responseText !== head) {
|
|
2912
|
+
parts.push(`response=${truncate(String(e.responseText), 240)}`);
|
|
2913
|
+
} else if (e.response && e.response !== head) {
|
|
2914
|
+
parts.push(`response=${truncate(String(e.response), 240)}`);
|
|
2915
|
+
}
|
|
2916
|
+
if (e.command) parts.push(`command=${e.command}`);
|
|
2917
|
+
if (typeof e.exitCode === "number") parts.push(`exit=${e.exitCode}`);
|
|
2918
|
+
else if (e.code && /^\d+$/.test(String(e.code))) parts.push(`exit=${e.code}`);
|
|
2919
|
+
if (e.signal) parts.push(`signal=${e.signal}`);
|
|
2920
|
+
const stderr = (e.stderr ?? "").toString().trim();
|
|
2921
|
+
if (stderr) parts.push(`stderr=${truncate(stderr, 240)}`);
|
|
2922
|
+
const stdout = (e.stdout ?? "").toString().trim();
|
|
2923
|
+
if (stdout && !stderr) parts.push(`stdout=${truncate(stdout, 240)}`);
|
|
2924
|
+
if (parts.length === 1 && /^command failed$/i.test(head)) {
|
|
2925
|
+
return `${head} (no further detail available \u2014 wrapping error did not carry stderr/code/response)`;
|
|
2926
|
+
}
|
|
2927
|
+
return parts.join(" | ");
|
|
2928
|
+
}
|
|
2929
|
+
function truncate(s, max) {
|
|
2930
|
+
if (s.length <= max) return s;
|
|
2931
|
+
return s.slice(0, max - 1).trimEnd() + "\u2026";
|
|
2932
|
+
}
|
|
2899
2933
|
var RelayGateway = class {
|
|
2900
2934
|
smtpTransport = null;
|
|
2901
2935
|
pollTimer = null;
|
|
@@ -3070,7 +3104,7 @@ var RelayGateway = class {
|
|
|
3070
3104
|
this.consecutiveFailures = 0;
|
|
3071
3105
|
} catch (err) {
|
|
3072
3106
|
this.consecutiveFailures++;
|
|
3073
|
-
const msg =
|
|
3107
|
+
const msg = formatPollError(err);
|
|
3074
3108
|
console.error(`[RelayGateway] Poll failed (attempt ${this.consecutiveFailures}): ${msg}`);
|
|
3075
3109
|
if (this.consecutiveFailures >= 5 && this.consecutiveFailures % 5 === 0) {
|
|
3076
3110
|
console.error(`[RelayGateway] ${this.consecutiveFailures} consecutive failures \u2014 check IMAP credentials and connectivity (${this.config?.imapHost}:${this.config?.imapPort})`);
|
|
@@ -5327,20 +5361,23 @@ var GatewayManager = class {
|
|
|
5327
5361
|
}
|
|
5328
5362
|
/**
|
|
5329
5363
|
* Resume gateway from saved config (e.g., after server restart).
|
|
5364
|
+
*
|
|
5365
|
+
* Issue #31 — On a Docker container restart the API can come up
|
|
5366
|
+
* before Stalwart / Gmail IMAP / DNS is reachable, so the very first
|
|
5367
|
+
* setup() can fail with a transient network error. Previously that
|
|
5368
|
+
* single failure was logged and never retried, leaving polling
|
|
5369
|
+
* permanently dead until someone noticed and manually revived the
|
|
5370
|
+
* relay. We now schedule background retries with exponential backoff
|
|
5371
|
+
* (5s, 10s, 20s, 40s, 60s cap, indefinite) so the relay
|
|
5372
|
+
* self-recovers as soon as the dependency is reachable again.
|
|
5330
5373
|
*/
|
|
5331
5374
|
async resume() {
|
|
5332
5375
|
if (this.config.mode === "relay" && this.config.relay) {
|
|
5333
5376
|
try {
|
|
5334
|
-
await this.
|
|
5335
|
-
const savedUid = this.loadLastSeenUid();
|
|
5336
|
-
if (savedUid > 0) {
|
|
5337
|
-
this.relay.setLastSeenUid(savedUid);
|
|
5338
|
-
console.log(`[GatewayManager] Restored lastSeenUid=${savedUid} from database`);
|
|
5339
|
-
}
|
|
5340
|
-
this.relay.onUidAdvance = (uid) => this.saveLastSeenUid(uid);
|
|
5341
|
-
await this.relay.startPolling();
|
|
5377
|
+
await this._resumeRelayOnce();
|
|
5342
5378
|
} catch (err) {
|
|
5343
|
-
console.error("[GatewayManager]
|
|
5379
|
+
console.error("[GatewayManager] Initial relay resume failed; scheduling retries:", formatPollError(err));
|
|
5380
|
+
this._scheduleRelayResumeRetry();
|
|
5344
5381
|
}
|
|
5345
5382
|
}
|
|
5346
5383
|
if (this.smsManager && this.accountManager) {
|
|
@@ -5367,6 +5404,42 @@ var GatewayManager = class {
|
|
|
5367
5404
|
}
|
|
5368
5405
|
}
|
|
5369
5406
|
}
|
|
5407
|
+
// ─── Issue #31 helpers — resume retry with backoff ───
|
|
5408
|
+
_resumeRetryTimer = null;
|
|
5409
|
+
_resumeRetryAttempt = 0;
|
|
5410
|
+
async _resumeRelayOnce() {
|
|
5411
|
+
if (!this.config.relay) throw new Error("No relay config to resume");
|
|
5412
|
+
await this.relay.setup(this.config.relay);
|
|
5413
|
+
const savedUid = this.loadLastSeenUid();
|
|
5414
|
+
if (savedUid > 0) {
|
|
5415
|
+
this.relay.setLastSeenUid(savedUid);
|
|
5416
|
+
console.log(`[GatewayManager] Restored lastSeenUid=${savedUid} from database`);
|
|
5417
|
+
}
|
|
5418
|
+
this.relay.onUidAdvance = (uid) => this.saveLastSeenUid(uid);
|
|
5419
|
+
await this.relay.startPolling();
|
|
5420
|
+
if (this._resumeRetryAttempt > 0) {
|
|
5421
|
+
console.log(`[GatewayManager] Relay polling resumed after ${this._resumeRetryAttempt} retry attempt${this._resumeRetryAttempt !== 1 ? "s" : ""}`);
|
|
5422
|
+
}
|
|
5423
|
+
this._resumeRetryAttempt = 0;
|
|
5424
|
+
}
|
|
5425
|
+
_scheduleRelayResumeRetry() {
|
|
5426
|
+
if (this._resumeRetryTimer) return;
|
|
5427
|
+
this._resumeRetryAttempt++;
|
|
5428
|
+
const base = Math.min(5e3 * Math.pow(2, this._resumeRetryAttempt - 1), 6e4);
|
|
5429
|
+
const jitter = base * (0.8 + Math.random() * 0.4);
|
|
5430
|
+
const delay = Math.round(jitter);
|
|
5431
|
+
console.log(`[GatewayManager] Will retry relay resume in ${(delay / 1e3).toFixed(1)}s (attempt ${this._resumeRetryAttempt + 1})`);
|
|
5432
|
+
this._resumeRetryTimer = setTimeout(async () => {
|
|
5433
|
+
this._resumeRetryTimer = null;
|
|
5434
|
+
if (this.config.mode !== "relay" || !this.config.relay) return;
|
|
5435
|
+
try {
|
|
5436
|
+
await this._resumeRelayOnce();
|
|
5437
|
+
} catch (err) {
|
|
5438
|
+
console.error(`[GatewayManager] Relay resume retry ${this._resumeRetryAttempt} failed:`, formatPollError(err));
|
|
5439
|
+
this._scheduleRelayResumeRetry();
|
|
5440
|
+
}
|
|
5441
|
+
}, delay);
|
|
5442
|
+
}
|
|
5370
5443
|
// --- Persistence ---
|
|
5371
5444
|
loadConfig() {
|
|
5372
5445
|
const row = this.db.prepare("SELECT * FROM gateway_config WHERE id = ?").get("default");
|