@agenticmail/claudecode 0.1.6 → 0.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.
|
@@ -59,7 +59,7 @@ function rememberBounded(set, item) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
var DEFAULT_MAX_CONCURRENT = 10;
|
|
62
|
-
var DEFAULT_SYNC_INTERVAL_MS =
|
|
62
|
+
var DEFAULT_SYNC_INTERVAL_MS = 3e4;
|
|
63
63
|
var DEFAULT_RECONNECT_BASE_MS = 2e3;
|
|
64
64
|
var DEFAULT_RECONNECT_MAX_MS = 6e4;
|
|
65
65
|
var TASK_MAIL_SUPPRESS_WINDOW_MS = 3e4;
|
|
@@ -229,6 +229,7 @@ var Dispatcher = class {
|
|
|
229
229
|
channels = /* @__PURE__ */ new Map();
|
|
230
230
|
// keyed by account.id
|
|
231
231
|
accountSyncTimer = null;
|
|
232
|
+
systemChannelController = null;
|
|
232
233
|
running = 0;
|
|
233
234
|
waiters = [];
|
|
234
235
|
stopped = false;
|
|
@@ -251,11 +252,19 @@ var Dispatcher = class {
|
|
|
251
252
|
this.accountSyncTimer = setInterval(() => {
|
|
252
253
|
this.syncAccounts().catch((err) => this.log("warn", `[dispatcher] account sync failed: ${err}`));
|
|
253
254
|
}, this.syncIntervalMs);
|
|
255
|
+
void this.runSystemChannel();
|
|
254
256
|
}
|
|
255
257
|
async stop() {
|
|
256
258
|
this.stopped = true;
|
|
257
259
|
if (this.accountSyncTimer) clearInterval(this.accountSyncTimer);
|
|
258
260
|
this.accountSyncTimer = null;
|
|
261
|
+
if (this.systemChannelController) {
|
|
262
|
+
try {
|
|
263
|
+
this.systemChannelController.abort();
|
|
264
|
+
} catch {
|
|
265
|
+
}
|
|
266
|
+
this.systemChannelController = null;
|
|
267
|
+
}
|
|
259
268
|
for (const ch of this.channels.values()) {
|
|
260
269
|
ch.stopping = true;
|
|
261
270
|
ch.controller?.abort();
|
|
@@ -352,6 +361,118 @@ var Dispatcher = class {
|
|
|
352
361
|
void this.runChannel(ch);
|
|
353
362
|
}
|
|
354
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Subscribe to the API's master-scoped system events SSE.
|
|
366
|
+
*
|
|
367
|
+
* Pushes from /system/events arrive as JSON-per-frame just like the
|
|
368
|
+
* per-account stream:
|
|
369
|
+
* { type: "connected" }
|
|
370
|
+
* { type: "account_created", account: { id, name, email, apiKey, ... } }
|
|
371
|
+
* { type: "account_deleted", accountId, name }
|
|
372
|
+
*
|
|
373
|
+
* On `account_created` we eagerly open a per-account SSE channel using
|
|
374
|
+
* the apiKey carried in the event payload — no extra round trip, the
|
|
375
|
+
* channel is live within milliseconds of the POST /accounts response.
|
|
376
|
+
*
|
|
377
|
+
* Reconnect with the same exponential backoff scheme as per-account
|
|
378
|
+
* channels. If the API is older and doesn't expose /system/events
|
|
379
|
+
* (404), we log once and stop trying — polling-only fallback still
|
|
380
|
+
* works.
|
|
381
|
+
*/
|
|
382
|
+
async runSystemChannel() {
|
|
383
|
+
let backoff = this.reconnectBaseMs;
|
|
384
|
+
let giveUp = false;
|
|
385
|
+
while (!this.stopped && !giveUp) {
|
|
386
|
+
this.systemChannelController = new AbortController();
|
|
387
|
+
try {
|
|
388
|
+
const url = `${this.cfg.apiUrl.replace(/\/$/, "")}/api/agenticmail/system/events`;
|
|
389
|
+
const res = await this.fetchImpl(url, {
|
|
390
|
+
headers: {
|
|
391
|
+
"Authorization": `Bearer ${this.cfg.masterKey}`,
|
|
392
|
+
"Accept": "text/event-stream"
|
|
393
|
+
},
|
|
394
|
+
signal: this.systemChannelController.signal
|
|
395
|
+
});
|
|
396
|
+
if (res.status === 404) {
|
|
397
|
+
this.log("warn", "[dispatcher] /system/events not available on this API \u2014 falling back to polling-only account discovery (please upgrade @agenticmail/api to >=0.7.3)");
|
|
398
|
+
giveUp = true;
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
if (!res.ok || !res.body) {
|
|
402
|
+
throw new Error(`system/events HTTP ${res.status}`);
|
|
403
|
+
}
|
|
404
|
+
backoff = this.reconnectBaseMs;
|
|
405
|
+
const reader = res.body.getReader();
|
|
406
|
+
const decoder = new TextDecoder();
|
|
407
|
+
let buffer = "";
|
|
408
|
+
while (!this.stopped) {
|
|
409
|
+
const { value, done } = await reader.read();
|
|
410
|
+
if (done) break;
|
|
411
|
+
buffer += decoder.decode(value, { stream: true });
|
|
412
|
+
let boundary;
|
|
413
|
+
while ((boundary = buffer.indexOf("\n\n")) !== -1) {
|
|
414
|
+
const frame = buffer.slice(0, boundary);
|
|
415
|
+
buffer = buffer.slice(boundary + 2);
|
|
416
|
+
for (const line of frame.split("\n")) {
|
|
417
|
+
if (!line.startsWith("data: ")) continue;
|
|
418
|
+
try {
|
|
419
|
+
const event = JSON.parse(line.slice(6));
|
|
420
|
+
this.handleSystemEvent(event);
|
|
421
|
+
} catch {
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
} catch (err) {
|
|
427
|
+
if (this.stopped) break;
|
|
428
|
+
this.log("warn", `[dispatcher] system-events stream error: ${err.message}; reconnecting in ${backoff}ms`);
|
|
429
|
+
}
|
|
430
|
+
if (this.stopped || giveUp) break;
|
|
431
|
+
await sleep(backoff);
|
|
432
|
+
backoff = Math.min(backoff * 2, this.reconnectMaxMs);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
/** Apply an account-lifecycle event from /system/events. */
|
|
436
|
+
handleSystemEvent(event) {
|
|
437
|
+
const type = typeof event.type === "string" ? event.type : "";
|
|
438
|
+
if (type === "account_created" && event.account && typeof event.account === "object") {
|
|
439
|
+
const account = event.account;
|
|
440
|
+
if (!account.id || !account.name || !account.apiKey) {
|
|
441
|
+
this.log("warn", "[dispatcher] account_created event missing required fields; ignoring");
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
if (!this.shouldWatch(account)) {
|
|
445
|
+
this.log("info", `[dispatcher] account_created "${account.name}" \u2014 skipping (bridge/role excluded)`);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (this.channels.has(account.id)) return;
|
|
449
|
+
const ch = {
|
|
450
|
+
account,
|
|
451
|
+
controller: null,
|
|
452
|
+
stopping: false,
|
|
453
|
+
backoffMs: this.reconnectBaseMs,
|
|
454
|
+
seenUids: /* @__PURE__ */ new Set(),
|
|
455
|
+
seenTaskIds: /* @__PURE__ */ new Set(),
|
|
456
|
+
suppressTaskMailUntilMs: 0
|
|
457
|
+
};
|
|
458
|
+
this.channels.set(account.id, ch);
|
|
459
|
+
this.log("info", `[dispatcher] account_created "${account.name}" (${account.email}) \u2014 opening SSE channel immediately`);
|
|
460
|
+
void this.runChannel(ch);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (type === "account_deleted" && typeof event.accountId === "string") {
|
|
464
|
+
const ch = this.channels.get(event.accountId);
|
|
465
|
+
if (!ch) return;
|
|
466
|
+
ch.stopping = true;
|
|
467
|
+
try {
|
|
468
|
+
ch.controller?.abort();
|
|
469
|
+
} catch {
|
|
470
|
+
}
|
|
471
|
+
this.channels.delete(event.accountId);
|
|
472
|
+
this.log("info", `[dispatcher] account_deleted "${ch.account.name}" \u2014 closed SSE channel`);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
355
476
|
/** Watch one account's SSE stream forever; reconnect with backoff on drop. */
|
|
356
477
|
async runChannel(ch) {
|
|
357
478
|
while (!ch.stopping && !this.stopped) {
|
package/dist/dispatcher-bin.js
CHANGED
package/dist/dispatcher.d.ts
CHANGED
|
@@ -103,6 +103,7 @@ declare class Dispatcher {
|
|
|
103
103
|
private log;
|
|
104
104
|
private channels;
|
|
105
105
|
private accountSyncTimer;
|
|
106
|
+
private systemChannelController;
|
|
106
107
|
private running;
|
|
107
108
|
private waiters;
|
|
108
109
|
private stopped;
|
|
@@ -131,6 +132,27 @@ declare class Dispatcher {
|
|
|
131
132
|
private shouldWatch;
|
|
132
133
|
/** Re-fetch /accounts; open SSE for new ones, close for vanished ones. */
|
|
133
134
|
private syncAccounts;
|
|
135
|
+
/**
|
|
136
|
+
* Subscribe to the API's master-scoped system events SSE.
|
|
137
|
+
*
|
|
138
|
+
* Pushes from /system/events arrive as JSON-per-frame just like the
|
|
139
|
+
* per-account stream:
|
|
140
|
+
* { type: "connected" }
|
|
141
|
+
* { type: "account_created", account: { id, name, email, apiKey, ... } }
|
|
142
|
+
* { type: "account_deleted", accountId, name }
|
|
143
|
+
*
|
|
144
|
+
* On `account_created` we eagerly open a per-account SSE channel using
|
|
145
|
+
* the apiKey carried in the event payload — no extra round trip, the
|
|
146
|
+
* channel is live within milliseconds of the POST /accounts response.
|
|
147
|
+
*
|
|
148
|
+
* Reconnect with the same exponential backoff scheme as per-account
|
|
149
|
+
* channels. If the API is older and doesn't expose /system/events
|
|
150
|
+
* (404), we log once and stop trying — polling-only fallback still
|
|
151
|
+
* works.
|
|
152
|
+
*/
|
|
153
|
+
private runSystemChannel;
|
|
154
|
+
/** Apply an account-lifecycle event from /system/events. */
|
|
155
|
+
private handleSystemEvent;
|
|
134
156
|
/** Watch one account's SSE stream forever; reconnect with backoff on drop. */
|
|
135
157
|
private runChannel;
|
|
136
158
|
/** Single SSE attach. Returns when the stream closes for any reason. */
|
package/dist/dispatcher.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agenticmail/claudecode",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Claude Code integration for AgenticMail — surfaces every AgenticMail agent as a native Claude Code subagent so any Claude Code session can delegate to them with the Agent tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|