@agenticmail/claudecode 0.2.2 → 0.2.4

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.
@@ -12191,7 +12191,7 @@ function rememberBounded(set, item) {
12191
12191
  for (const x of drop) set.delete(x);
12192
12192
  }
12193
12193
  }
12194
- var DEFAULT_MAX_CONCURRENT = 10;
12194
+ var DEFAULT_MAX_CONCURRENT = 50;
12195
12195
  var DEFAULT_SYNC_INTERVAL_MS = 3e4;
12196
12196
  var DEFAULT_RECONNECT_BASE_MS = 2e3;
12197
12197
  var DEFAULT_RECONNECT_MAX_MS = 6e4;
@@ -13184,6 +13184,7 @@ var Dispatcher = class {
13184
13184
  }
13185
13185
  /** Acquire a concurrency slot, run a worker, release the slot. */
13186
13186
  async spawnWorker(account, prompt, ctx) {
13187
+ const releaseAgentLock = await this.acquireAgentSerial(account.id);
13187
13188
  await this.acquireSlot();
13188
13189
  const workerId = `${account.id}:${ctx.kind}:${ctx.uid ?? ctx.taskId ?? ""}:${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
13189
13190
  let workerResult = null;
@@ -13221,12 +13222,18 @@ var Dispatcher = class {
13221
13222
  let turnCount = 0;
13222
13223
  let lastTool = "";
13223
13224
  let lastUsage;
13225
+ const digestedUids = /* @__PURE__ */ new Set();
13224
13226
  const observer = {
13225
13227
  onMessage: (tag, summary) => {
13226
13228
  writeLog(`${tag} ${summary}`);
13227
13229
  if (tag === "tool_use") {
13228
13230
  lastTool = summary.split(" ")[0];
13229
13231
  turnCount++;
13232
+ const m = /read_email\b[^}]*"uid"\s*:\s*(\d+)/.exec(summary);
13233
+ if (m) {
13234
+ const uid = parseInt(m[1], 10);
13235
+ if (Number.isFinite(uid) && uid > 0) digestedUids.add(uid);
13236
+ }
13230
13237
  }
13231
13238
  if (tag === "usage") lastUsage = summary;
13232
13239
  }
@@ -13266,6 +13273,30 @@ var Dispatcher = class {
13266
13273
  } finally {
13267
13274
  clearInterval(heartbeatHandle);
13268
13275
  this.releaseSlot();
13276
+ if (digestedUids.size > 0) {
13277
+ const prefix = `${account.id}::`;
13278
+ for (const [key, entry] of this.wakeCoalesce.entries()) {
13279
+ if (!key.startsWith(prefix)) continue;
13280
+ const before = entry.events.length;
13281
+ entry.events = entry.events.filter((e) => !(typeof e.uid === "number" && digestedUids.has(e.uid)));
13282
+ if (entry.events.length < before) {
13283
+ this.log("info", `[dispatcher] dropped ${before - entry.events.length} queued wake(s) for "${account.name}" \u2014 UIDs already digested this turn`);
13284
+ }
13285
+ if (entry.events.length === 0) {
13286
+ try {
13287
+ clearTimeout(entry.timer);
13288
+ } catch {
13289
+ }
13290
+ this.wakeCoalesce.delete(key);
13291
+ }
13292
+ }
13293
+ const ch = this.channels.get(account.id);
13294
+ if (ch) for (const uid of digestedUids) rememberBounded(ch.seenUids, uid);
13295
+ }
13296
+ try {
13297
+ releaseAgentLock();
13298
+ } catch {
13299
+ }
13269
13300
  const ok = workerResult?.ok === true;
13270
13301
  const preview = workerResult?.ok ? workerResult.text : workerResult ? workerResult.error : "worker did not start";
13271
13302
  writeLog(`worker_finished ok=${ok} chars=${preview.length}`);
@@ -13371,6 +13402,41 @@ var Dispatcher = class {
13371
13402
  const next = this.waiters.shift();
13372
13403
  if (next) next();
13373
13404
  }
13405
+ /**
13406
+ * Per-agent serialization. At most ONE worker runs for any
13407
+ * given agent at a time. When a new wake fires for an agent
13408
+ * whose worker is still running, the new wake's spawnWorker
13409
+ * waits on the prior worker's tail before proceeding.
13410
+ *
13411
+ * This is the fix for the "dispatcher crashed when sender
13412
+ * broadcast to a 5-CC thread" failure mode: under the old
13413
+ * design, 5 emails landing for vesper-on-3-different-threads
13414
+ * in the same second spawned 5 simultaneous vesper workers,
13415
+ * each opening its own IMAP connection, each calling the
13416
+ * SDK, racing on the same inbox cache. With this gate they
13417
+ * queue tail-to-head and run sequentially.
13418
+ *
13419
+ * `nextRun` is a chained promise: each new spawn calls
13420
+ * `then()` on the previous tail so the order is preserved.
13421
+ * When the chain resolves to a no-op (empty queue), the
13422
+ * entry is garbage-collected from the map so memory stays
13423
+ * bounded at #active-agents.
13424
+ */
13425
+ agentSerial = /* @__PURE__ */ new Map();
13426
+ async acquireAgentSerial(agentId) {
13427
+ const prev = this.agentSerial.get(agentId);
13428
+ let release;
13429
+ const next = new Promise((resolve) => {
13430
+ release = resolve;
13431
+ });
13432
+ this.agentSerial.set(agentId, prev ? prev.then(() => next).catch(() => next) : next);
13433
+ if (prev) await prev.catch(() => {
13434
+ });
13435
+ return () => {
13436
+ release();
13437
+ if (this.agentSerial.get(agentId) === next) this.agentSerial.delete(agentId);
13438
+ };
13439
+ }
13374
13440
  };
13375
13441
  function sleep(ms) {
13376
13442
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  Dispatcher
4
- } from "./chunk-T3J2WZMM.js";
4
+ } from "./chunk-V5R2D3QD.js";
5
5
  import "./chunk-DDJNA5HP.js";
6
6
  import "./chunk-B276KPVO.js";
7
7
  import "./chunk-RB5MGRT3.js";
@@ -31,7 +31,10 @@ async function main() {
31
31
  process.once("SIGINT", shutdown);
32
32
  process.once("SIGTERM", shutdown);
33
33
  process.on("unhandledRejection", (reason) => {
34
- console.error("[dispatcher-bin] unhandledRejection:", reason);
34
+ console.error("[dispatcher-bin] unhandledRejection (continuing):", reason);
35
+ });
36
+ process.on("uncaughtException", (err) => {
37
+ console.error("[dispatcher-bin] uncaughtException (continuing):", err);
35
38
  });
36
39
  await dispatcher.start();
37
40
  }
@@ -348,6 +348,28 @@ declare class Dispatcher {
348
348
  private buildMcpEnv;
349
349
  private acquireSlot;
350
350
  private releaseSlot;
351
+ /**
352
+ * Per-agent serialization. At most ONE worker runs for any
353
+ * given agent at a time. When a new wake fires for an agent
354
+ * whose worker is still running, the new wake's spawnWorker
355
+ * waits on the prior worker's tail before proceeding.
356
+ *
357
+ * This is the fix for the "dispatcher crashed when sender
358
+ * broadcast to a 5-CC thread" failure mode: under the old
359
+ * design, 5 emails landing for vesper-on-3-different-threads
360
+ * in the same second spawned 5 simultaneous vesper workers,
361
+ * each opening its own IMAP connection, each calling the
362
+ * SDK, racing on the same inbox cache. With this gate they
363
+ * queue tail-to-head and run sequentially.
364
+ *
365
+ * `nextRun` is a chained promise: each new spawn calls
366
+ * `then()` on the previous tail so the order is preserved.
367
+ * When the chain resolves to a no-op (empty queue), the
368
+ * entry is garbage-collected from the map so memory stays
369
+ * bounded at #active-agents.
370
+ */
371
+ private agentSerial;
372
+ private acquireAgentSerial;
351
373
  }
352
374
 
353
375
  export { Dispatcher, type DispatcherOptions, type QueryFn, type WorkerObserver };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Dispatcher
3
- } from "./chunk-T3J2WZMM.js";
3
+ } from "./chunk-V5R2D3QD.js";
4
4
  import "./chunk-DDJNA5HP.js";
5
5
  import "./chunk-B276KPVO.js";
6
6
  import "./chunk-RB5MGRT3.js";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Dispatcher,
3
3
  loadPersonaForAgent
4
- } from "./chunk-T3J2WZMM.js";
4
+ } from "./chunk-V5R2D3QD.js";
5
5
  import "./chunk-DDJNA5HP.js";
6
6
  import "./chunk-B276KPVO.js";
7
7
  import "./chunk-RB5MGRT3.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/claudecode",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
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",