@agenticmail/claudecode 0.1.5 → 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.
- package/dist/{chunk-3ULRGCUI.js → chunk-3ZBSRXAK.js} +196 -8
- package/dist/{chunk-OZPRB336.js → chunk-N43A7EQB.js} +4 -4
- package/dist/{chunk-UZZNLWBR.js → chunk-UC63VEBP.js} +1 -1
- package/dist/{chunk-4JURQ6QD.js → chunk-WP2ELPRM.js} +1 -1
- package/dist/{chunk-5S377IWV.js → chunk-XGBVWZ3M.js} +1 -1
- package/dist/{chunk-W2R3GH54.js → chunk-YWSO3QOQ.js} +19 -6
- package/dist/cli.js +4 -4
- package/dist/dispatcher-bin.js +2 -2
- package/dist/dispatcher.d.ts +40 -0
- package/dist/dispatcher.js +2 -2
- package/dist/http-routes.js +5 -5
- package/dist/index.js +6 -6
- package/dist/install.js +2 -2
- package/dist/status.js +2 -2
- package/dist/uninstall.js +2 -2
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
listAccounts,
|
|
3
3
|
renderPersonaBody,
|
|
4
4
|
resolveConfig
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-YWSO3QOQ.js";
|
|
6
6
|
|
|
7
7
|
// src/persona-loader.ts
|
|
8
8
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -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;
|
|
@@ -94,6 +94,7 @@ async function runWorker(query, persona, userPrompt, agent, mcpServerName, mcpCo
|
|
|
94
94
|
`mcp__${mcpServerName}__list_agents`,
|
|
95
95
|
`mcp__${mcpServerName}__message_agent`,
|
|
96
96
|
`mcp__${mcpServerName}__call_agent`,
|
|
97
|
+
`mcp__${mcpServerName}__wait_for_email`,
|
|
97
98
|
`mcp__${mcpServerName}__check_tasks`,
|
|
98
99
|
`mcp__${mcpServerName}__claim_task`,
|
|
99
100
|
`mcp__${mcpServerName}__submit_result`,
|
|
@@ -143,13 +144,55 @@ function newMailPrompt(agent, event) {
|
|
|
143
144
|
`- Subject: ${subject}`,
|
|
144
145
|
uid ? `- UID: ${uid}` : "",
|
|
145
146
|
``,
|
|
146
|
-
|
|
147
|
-
` - if it's a question or request \u2192 reply with reply_email`,
|
|
148
|
-
` - if it requires research \u2192 use call_agent to ask the right teammate, then reply`,
|
|
149
|
-
` - if it's an FYI \u2192 archive (mark_read) and move on`,
|
|
150
|
-
` - if it's spam-looking \u2192 trust the auto-spam-filter; only intervene if it slipped through`,
|
|
147
|
+
`## Thread-aware coordination protocol`,
|
|
151
148
|
``,
|
|
152
|
-
`
|
|
149
|
+
`You are ${agent.name}. Multiple agents may be CC'd on the same thread \u2014`,
|
|
150
|
+
`that is intentional: a thread is the shared workspace, and turn-taking is`,
|
|
151
|
+
`implicit from context (who was addressed last, whose stage of the workflow`,
|
|
152
|
+
`is next, who was @mentioned). Follow these steps in order:`,
|
|
153
|
+
``,
|
|
154
|
+
`1. **Read this message.** read_email({ uid: ${uid ?? "<uid>"}, _account: "${agent.name}" }).`,
|
|
155
|
+
``,
|
|
156
|
+
`2. **If this is a reply (Subject starts with "Re:" or an In-Reply-To header is present), load the rest of the thread.**`,
|
|
157
|
+
` Use search_emails({ subject: "<core subject without Re:>", _account: "${agent.name}" })`,
|
|
158
|
+
` to surface earlier messages in the thread, then read_email each prior UID.`,
|
|
159
|
+
` You MUST read the full thread before deciding what to do.`,
|
|
160
|
+
``,
|
|
161
|
+
`3. **Identify the participants.** Look at To + CC across the thread. Those`,
|
|
162
|
+
` are your collaborators. Their names map to AgenticMail agents at`,
|
|
163
|
+
` <name>@localhost. They will each be woken on every reply-all the same way you were.`,
|
|
164
|
+
``,
|
|
165
|
+
`4. **Decide: is it MY turn?** Yes if any of:`,
|
|
166
|
+
` - The latest message addresses you by name ("Vesper, please \u2026", "@${agent.name} \u2026").`,
|
|
167
|
+
` - The previous-stage handoff is to your role (e.g. designer \u2192 developer, and you are the developer).`,
|
|
168
|
+
` - You were directly asked a question and nobody has answered yet.`,
|
|
169
|
+
` No if:`,
|
|
170
|
+
` - The current ask is targeted at a teammate (their turn, not yours).`,
|
|
171
|
+
` - You have nothing substantive to add right now.`,
|
|
172
|
+
` When in doubt, stay silent \u2014 over-replying creates noise. Better to let`,
|
|
173
|
+
` the right teammate take the turn than to step on theirs.`,
|
|
174
|
+
``,
|
|
175
|
+
`5. **If it's your turn \u2014 reply-all so the whole thread sees it.**`,
|
|
176
|
+
` reply_email({ uid: ${uid ?? "<uid>"}, replyAll: true, text: "...", _account: "${agent.name}" })`,
|
|
177
|
+
` Sign with your name. Be substantive but concise. If you are handing off`,
|
|
178
|
+
` to the next teammate, name them explicitly in your reply ("Orion \u2014 over to you, please \u2026").`,
|
|
179
|
+
``,
|
|
180
|
+
`6. **If you need additional help from a teammate not yet on the thread,**`,
|
|
181
|
+
` include them by CC'ing in your reply-all \u2014 DO NOT spin up a separate`,
|
|
182
|
+
` call_agent / message_agent side-channel. The thread is the workspace;`,
|
|
183
|
+
` everyone stays in context.`,
|
|
184
|
+
``,
|
|
185
|
+
`7. **If it's NOT your turn,** mark the message read with mark_read and return.`,
|
|
186
|
+
` Do not reply just to acknowledge. Silence IS a valid contribution.`,
|
|
187
|
+
``,
|
|
188
|
+
`When you finish, return a one-line summary of what you did:`,
|
|
189
|
+
` "Contributed: <one-line description>" OR "Stayed silent \u2014 not my turn."`,
|
|
190
|
+
``,
|
|
191
|
+
`## Fallback for non-thread mail`,
|
|
192
|
+
``,
|
|
193
|
+
`If this is a fresh standalone email (not part of a thread, only addressed`,
|
|
194
|
+
`to you), handle it directly: answer the question, do the work, reply.`,
|
|
195
|
+
`Spam: trust the auto-filter unless something obviously slipped through.`
|
|
153
196
|
].filter(Boolean).join("\n");
|
|
154
197
|
}
|
|
155
198
|
function taskPrompt(agent, event) {
|
|
@@ -186,6 +229,7 @@ var Dispatcher = class {
|
|
|
186
229
|
channels = /* @__PURE__ */ new Map();
|
|
187
230
|
// keyed by account.id
|
|
188
231
|
accountSyncTimer = null;
|
|
232
|
+
systemChannelController = null;
|
|
189
233
|
running = 0;
|
|
190
234
|
waiters = [];
|
|
191
235
|
stopped = false;
|
|
@@ -208,11 +252,19 @@ var Dispatcher = class {
|
|
|
208
252
|
this.accountSyncTimer = setInterval(() => {
|
|
209
253
|
this.syncAccounts().catch((err) => this.log("warn", `[dispatcher] account sync failed: ${err}`));
|
|
210
254
|
}, this.syncIntervalMs);
|
|
255
|
+
void this.runSystemChannel();
|
|
211
256
|
}
|
|
212
257
|
async stop() {
|
|
213
258
|
this.stopped = true;
|
|
214
259
|
if (this.accountSyncTimer) clearInterval(this.accountSyncTimer);
|
|
215
260
|
this.accountSyncTimer = null;
|
|
261
|
+
if (this.systemChannelController) {
|
|
262
|
+
try {
|
|
263
|
+
this.systemChannelController.abort();
|
|
264
|
+
} catch {
|
|
265
|
+
}
|
|
266
|
+
this.systemChannelController = null;
|
|
267
|
+
}
|
|
216
268
|
for (const ch of this.channels.values()) {
|
|
217
269
|
ch.stopping = true;
|
|
218
270
|
ch.controller?.abort();
|
|
@@ -248,6 +300,29 @@ var Dispatcher = class {
|
|
|
248
300
|
return;
|
|
249
301
|
}
|
|
250
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Should the dispatcher own a wake-channel for this account?
|
|
305
|
+
*
|
|
306
|
+
* We skip the bridge agent (default name "claudecode"). The bridge is
|
|
307
|
+
* the host session's own inbox proxy — when mail lands there, the
|
|
308
|
+
* HOST Claude Code session reads it via MCP (`list_inbox` /
|
|
309
|
+
* `wait_for_email` / `read_email`), NOT via a separately-spawned
|
|
310
|
+
* dispatcher worker. Spawning a worker for the bridge would:
|
|
311
|
+
* 1. Compete with the host (two Claude instances trying to "be"
|
|
312
|
+
* Claude Code, both potentially replying autonomously).
|
|
313
|
+
* 2. Waste tokens — the host is already aware via its MCP polling.
|
|
314
|
+
* 3. Send the bridge into an autonomous loop if it ever replies-all
|
|
315
|
+
* (because that mail would wake it again, ad infinitum).
|
|
316
|
+
*
|
|
317
|
+
* Role="bridge" is also skipped for symmetry with selectExposableAgents
|
|
318
|
+
* in install.ts — anything tagged as a bridge is host-managed.
|
|
319
|
+
*/
|
|
320
|
+
shouldWatch(account) {
|
|
321
|
+
const bridgeName = this.cfg.bridgeAgentName.toLowerCase();
|
|
322
|
+
if (account.name.toLowerCase() === bridgeName) return false;
|
|
323
|
+
if (account.role === "bridge") return false;
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
251
326
|
/** Re-fetch /accounts; open SSE for new ones, close for vanished ones. */
|
|
252
327
|
async syncAccounts() {
|
|
253
328
|
let accounts;
|
|
@@ -257,6 +332,7 @@ var Dispatcher = class {
|
|
|
257
332
|
this.log("warn", `[dispatcher] could not list accounts: ${err.message}`);
|
|
258
333
|
return;
|
|
259
334
|
}
|
|
335
|
+
accounts = accounts.filter((a) => this.shouldWatch(a));
|
|
260
336
|
const liveIds = new Set(accounts.map((a) => a.id));
|
|
261
337
|
for (const [id, ch] of this.channels) {
|
|
262
338
|
if (!liveIds.has(id)) {
|
|
@@ -285,6 +361,118 @@ var Dispatcher = class {
|
|
|
285
361
|
void this.runChannel(ch);
|
|
286
362
|
}
|
|
287
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
|
+
}
|
|
288
476
|
/** Watch one account's SSE stream forever; reconnect with backoff on drop. */
|
|
289
477
|
async runChannel(ch) {
|
|
290
478
|
while (!ch.stopping && !this.stopped) {
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
install
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-WP2ELPRM.js";
|
|
4
4
|
import {
|
|
5
5
|
status
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-UC63VEBP.js";
|
|
7
7
|
import {
|
|
8
8
|
uninstall
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-XGBVWZ3M.js";
|
|
10
10
|
import {
|
|
11
11
|
AgenticMailApiError
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-YWSO3QOQ.js";
|
|
13
13
|
|
|
14
14
|
// src/http-routes.ts
|
|
15
15
|
import { Router } from "express";
|
|
@@ -130,12 +130,16 @@ var ESSENTIAL_TOOL_NAMES = [
|
|
|
130
130
|
"search_emails",
|
|
131
131
|
"list_agents",
|
|
132
132
|
"message_agent",
|
|
133
|
-
// call_agent is the
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
// call request_tools just to discover it would be a usability disaster
|
|
137
|
-
// for the most common coordination pattern.
|
|
133
|
+
// call_agent is the one-shot RPC primitive — sync request, sync answer.
|
|
134
|
+
// For multi-step coordination use the thread pattern (send_email with
|
|
135
|
+
// CC + reply_email with replyAll) instead.
|
|
138
136
|
"call_agent",
|
|
137
|
+
// wait_for_email is the thread-coordination primitive: block until a
|
|
138
|
+
// specific reply lands in your inbox (filter by from / subject /
|
|
139
|
+
// inReplyTo / participants). Essential for delegate-then-wait flows;
|
|
140
|
+
// making subagents discover it via request_tools would be a usability
|
|
141
|
+
// disaster for the most common coordination pattern.
|
|
142
|
+
"wait_for_email",
|
|
139
143
|
"check_tasks",
|
|
140
144
|
// Meta-tools — these unlock the other ~50 tools on demand.
|
|
141
145
|
"request_tools",
|
|
@@ -201,7 +205,16 @@ function renderPersonaBody(input) {
|
|
|
201
205
|
`${tool("whoami")}({ _account: "${agent.name}" })`,
|
|
202
206
|
"```",
|
|
203
207
|
"",
|
|
204
|
-
`**Coordination
|
|
208
|
+
`**Coordination \u2014 the thread is the workspace.** When you wake on new mail and it's part of a thread (Subject starts with "Re:" or an In-Reply-To header is present):`,
|
|
209
|
+
"",
|
|
210
|
+
` 1. Read the new message with \`${tool("read_email")}\`.`,
|
|
211
|
+
` 2. Load the rest of the thread with \`${tool("search_emails")}({ subject: "<core subject>", _account: "${agent.name}" })\` and read each prior message. You MUST have full thread context before acting.`,
|
|
212
|
+
` 3. Look at To + CC across the thread \u2014 those are your teammates. They will each be woken on every reply-all just like you were.`,
|
|
213
|
+
` 4. Decide if it's YOUR turn: are you addressed by name? Is the previous-stage handoff to your role? Is a question pending for you? When in doubt, stay silent \u2014 over-replying creates noise.`,
|
|
214
|
+
` 5. If yes: \`${tool("reply_email")}({ uid, replyAll: true, text: "...", _account: "${agent.name}" })\`. Sign with your name. If you're handing off, name the next teammate explicitly ("Orion \u2014 over to you, please \u2026"). To bring a new teammate in, just add them to CC.`,
|
|
215
|
+
` 6. If no: \`mark_read\` and return. Silence IS a valid contribution.`,
|
|
216
|
+
"",
|
|
217
|
+
`**When to use \`${tool("call_agent")}\` instead:** only when you need ONE structured answer from ONE teammate, inline in your current turn \u2014 e.g. "give me a JSON list of X". For multi-step / multi-agent work, the thread pattern above is the right primitive.`,
|
|
205
218
|
"",
|
|
206
219
|
`On-demand (via invoke) examples \u2014 anything NOT in the pre-loaded list:`,
|
|
207
220
|
"",
|
package/dist/cli.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
install
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-WP2ELPRM.js";
|
|
5
5
|
import {
|
|
6
6
|
status
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-UC63VEBP.js";
|
|
8
8
|
import {
|
|
9
9
|
uninstall
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-XGBVWZ3M.js";
|
|
11
11
|
import "./chunk-US5FT2UB.js";
|
|
12
12
|
import {
|
|
13
13
|
AgenticMailApiError
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-YWSO3QOQ.js";
|
|
15
15
|
|
|
16
16
|
// src/cli.ts
|
|
17
17
|
var GREEN = (s) => `\x1B[32m${s}\x1B[0m`;
|
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;
|
|
@@ -111,8 +112,47 @@ declare class Dispatcher {
|
|
|
111
112
|
stop(): Promise<void>;
|
|
112
113
|
/** Public for tests — directly hand an event to the routing path. */
|
|
113
114
|
handleEvent(account: AgenticMailAccount, event: SSEEvent): Promise<void>;
|
|
115
|
+
/**
|
|
116
|
+
* Should the dispatcher own a wake-channel for this account?
|
|
117
|
+
*
|
|
118
|
+
* We skip the bridge agent (default name "claudecode"). The bridge is
|
|
119
|
+
* the host session's own inbox proxy — when mail lands there, the
|
|
120
|
+
* HOST Claude Code session reads it via MCP (`list_inbox` /
|
|
121
|
+
* `wait_for_email` / `read_email`), NOT via a separately-spawned
|
|
122
|
+
* dispatcher worker. Spawning a worker for the bridge would:
|
|
123
|
+
* 1. Compete with the host (two Claude instances trying to "be"
|
|
124
|
+
* Claude Code, both potentially replying autonomously).
|
|
125
|
+
* 2. Waste tokens — the host is already aware via its MCP polling.
|
|
126
|
+
* 3. Send the bridge into an autonomous loop if it ever replies-all
|
|
127
|
+
* (because that mail would wake it again, ad infinitum).
|
|
128
|
+
*
|
|
129
|
+
* Role="bridge" is also skipped for symmetry with selectExposableAgents
|
|
130
|
+
* in install.ts — anything tagged as a bridge is host-managed.
|
|
131
|
+
*/
|
|
132
|
+
private shouldWatch;
|
|
114
133
|
/** Re-fetch /accounts; open SSE for new ones, close for vanished ones. */
|
|
115
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;
|
|
116
156
|
/** Watch one account's SSE stream forever; reconnect with backoff on drop. */
|
|
117
157
|
private runChannel;
|
|
118
158
|
/** Single SSE attach. Returns when the stream closes for any reason. */
|
package/dist/dispatcher.js
CHANGED
package/dist/http-routes.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createIntegrationRoutes
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-N43A7EQB.js";
|
|
4
|
+
import "./chunk-WP2ELPRM.js";
|
|
5
|
+
import "./chunk-UC63VEBP.js";
|
|
6
|
+
import "./chunk-XGBVWZ3M.js";
|
|
7
7
|
import "./chunk-US5FT2UB.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-YWSO3QOQ.js";
|
|
9
9
|
export {
|
|
10
10
|
createIntegrationRoutes
|
|
11
11
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Dispatcher,
|
|
3
3
|
loadPersonaForAgent
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-3ZBSRXAK.js";
|
|
5
5
|
import {
|
|
6
6
|
createIntegrationRoutes
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-N43A7EQB.js";
|
|
8
8
|
import {
|
|
9
9
|
install
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-WP2ELPRM.js";
|
|
11
11
|
import {
|
|
12
12
|
status
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-UC63VEBP.js";
|
|
14
14
|
import {
|
|
15
15
|
uninstall
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-XGBVWZ3M.js";
|
|
17
17
|
import "./chunk-US5FT2UB.js";
|
|
18
18
|
import {
|
|
19
19
|
AgenticMailApiError,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
renderPersonaBody,
|
|
27
27
|
renderSubagentMarkdown,
|
|
28
28
|
resolveConfig
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-YWSO3QOQ.js";
|
|
30
30
|
export {
|
|
31
31
|
AgenticMailApiError,
|
|
32
32
|
Dispatcher,
|
package/dist/install.js
CHANGED
package/dist/status.js
CHANGED
package/dist/uninstall.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",
|