@agenticmail/claudecode 0.2.7 → 0.2.9

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  removeUserPromptSubmitHook
3
- } from "./chunk-DKTAW2N5.js";
3
+ } from "./chunk-LO5EQSQA.js";
4
4
  import {
5
5
  removeMcpServer,
6
6
  stopDispatcher
@@ -0,0 +1,73 @@
1
+ // src/dispatcher-tuning.ts
2
+ import { readFileSync, existsSync, writeFileSync, mkdirSync, renameSync } from "fs";
3
+ import { join, dirname } from "path";
4
+ import { homedir } from "os";
5
+ function defaultDispatcherConfigPath() {
6
+ return join(homedir(), ".agenticmail", "dispatcher.json");
7
+ }
8
+ function positiveInt(s) {
9
+ if (s === void 0 || s === null || s === "") return void 0;
10
+ const n = typeof s === "number" ? s : parseInt(String(s), 10);
11
+ return Number.isFinite(n) && n > 0 ? n : void 0;
12
+ }
13
+ function readTuningFile(path) {
14
+ if (!existsSync(path)) return {};
15
+ try {
16
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
17
+ if (!parsed || typeof parsed !== "object") return {};
18
+ if (parsed.version !== 1) return {};
19
+ return {
20
+ maxConcurrentWorkers: positiveInt(parsed.maxConcurrentWorkers),
21
+ maxWakesPerThread: positiveInt(parsed.maxWakesPerThread),
22
+ wakeWindowMs: positiveInt(parsed.wakeWindowMs),
23
+ wakeCoalesceMs: positiveInt(parsed.wakeCoalesceMs),
24
+ accountSyncIntervalMs: positiveInt(parsed.accountSyncIntervalMs)
25
+ };
26
+ } catch {
27
+ return {};
28
+ }
29
+ }
30
+ function resolveDispatcherTuning(opts = {}) {
31
+ const env = opts.env ?? process.env;
32
+ const configPath = opts.configPath ?? defaultDispatcherConfigPath();
33
+ const fileLayer = readTuningFile(configPath);
34
+ const envLayer = {
35
+ maxConcurrentWorkers: positiveInt(env.AGENTICMAIL_DISPATCHER_MAX),
36
+ maxWakesPerThread: positiveInt(env.AGENTICMAIL_DISPATCHER_MAX_WAKES_PER_THREAD),
37
+ wakeWindowMs: positiveInt(env.AGENTICMAIL_DISPATCHER_WAKE_WINDOW_MS),
38
+ wakeCoalesceMs: positiveInt(env.AGENTICMAIL_DISPATCHER_COALESCE_MS),
39
+ accountSyncIntervalMs: positiveInt(env.AGENTICMAIL_DISPATCHER_SYNC)
40
+ };
41
+ const explicit = opts.explicit ?? {};
42
+ return {
43
+ maxConcurrentWorkers: explicit.maxConcurrentWorkers ?? envLayer.maxConcurrentWorkers ?? fileLayer.maxConcurrentWorkers,
44
+ maxWakesPerThread: explicit.maxWakesPerThread ?? envLayer.maxWakesPerThread ?? fileLayer.maxWakesPerThread,
45
+ wakeWindowMs: explicit.wakeWindowMs ?? envLayer.wakeWindowMs ?? fileLayer.wakeWindowMs,
46
+ wakeCoalesceMs: explicit.wakeCoalesceMs ?? envLayer.wakeCoalesceMs ?? fileLayer.wakeCoalesceMs,
47
+ accountSyncIntervalMs: explicit.accountSyncIntervalMs ?? envLayer.accountSyncIntervalMs ?? fileLayer.accountSyncIntervalMs
48
+ };
49
+ }
50
+ function writeDispatcherTuning(patch, configPath = defaultDispatcherConfigPath()) {
51
+ const current = readTuningFile(configPath);
52
+ const merged = {
53
+ version: 1,
54
+ updatedAtMs: Date.now(),
55
+ maxConcurrentWorkers: positiveInt(patch.maxConcurrentWorkers) ?? current.maxConcurrentWorkers,
56
+ maxWakesPerThread: positiveInt(patch.maxWakesPerThread) ?? current.maxWakesPerThread,
57
+ wakeWindowMs: positiveInt(patch.wakeWindowMs) ?? current.wakeWindowMs,
58
+ wakeCoalesceMs: positiveInt(patch.wakeCoalesceMs) ?? current.wakeCoalesceMs,
59
+ accountSyncIntervalMs: positiveInt(patch.accountSyncIntervalMs) ?? current.accountSyncIntervalMs
60
+ };
61
+ const dir = dirname(configPath);
62
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
63
+ const tmp = `${configPath}.tmp`;
64
+ writeFileSync(tmp, JSON.stringify(merged, null, 2));
65
+ renameSync(tmp, configPath);
66
+ return merged;
67
+ }
68
+
69
+ export {
70
+ defaultDispatcherConfigPath,
71
+ resolveDispatcherTuning,
72
+ writeDispatcherTuning
73
+ };
@@ -5,8 +5,8 @@ function isAgenticMailHookCommand(command) {
5
5
  if (typeof command !== "string") return false;
6
6
  return command.includes("agenticmail-mail-hook") || command.includes("mail-hook.js");
7
7
  }
8
- var HOOK_EVENTS_TO_REGISTER = ["UserPromptSubmit", "Stop"];
9
- var HOOK_EVENTS_TO_REMOVE = ["UserPromptSubmit", "Stop", "PreToolUse"];
8
+ var HOOK_EVENTS_TO_REGISTER = ["UserPromptSubmit", "Stop", "SessionStart"];
9
+ var HOOK_EVENTS_TO_REMOVE = ["UserPromptSubmit", "Stop", "PreToolUse", "SessionStart"];
10
10
  function readSettings(path) {
11
11
  if (!existsSync(path)) return {};
12
12
  const raw = readFileSync(path, "utf-8");
@@ -1205,7 +1205,10 @@ var Dispatcher = class {
1205
1205
  async fireWakeImmediately(account, event, threadId) {
1206
1206
  const verdict = this.chargeWake(account.id, threadId);
1207
1207
  if (!verdict.ok) {
1208
- this.log("warn", `[dispatcher] wake-budget exhausted for "${account.name}" on thread "${threadId}" \u2014 dropped uid=${event.uid}`);
1208
+ this.log(
1209
+ "warn",
1210
+ `[dispatcher] wake-budget exhausted for "${account.name}" on thread "${threadId}" \u2014 dropped uid=${event.uid} (cap=${this.maxWakesPerThread} per ${Math.round(this.wakeWindowMs / 6e4)}min; raise with AGENTICMAIL_DISPATCHER_MAX_WAKES_PER_THREAD env var, or via ~/.agenticmail/dispatcher.json)`
1211
+ );
1209
1212
  this.postSkipped(account, event, "budget-exhausted", `wake budget exhausted for thread "${threadId}" (count=${verdict.count}, cap=${this.maxWakesPerThread})`);
1210
1213
  return;
1211
1214
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  upsertUserPromptSubmitHook
3
- } from "./chunk-DKTAW2N5.js";
3
+ } from "./chunk-LO5EQSQA.js";
4
4
  import {
5
5
  startDispatcher,
6
6
  upsertMcpServer
@@ -1,12 +1,12 @@
1
+ import {
2
+ status
3
+ } from "./chunk-Q5BA2J2C.js";
1
4
  import {
2
5
  uninstall
3
- } from "./chunk-JUEHPFKY.js";
6
+ } from "./chunk-2RVJ2Q3W.js";
4
7
  import {
5
8
  install
6
- } from "./chunk-4EHV3I6W.js";
7
- import {
8
- status
9
- } from "./chunk-Q5BA2J2C.js";
9
+ } from "./chunk-OUYPF4ER.js";
10
10
  import {
11
11
  AgenticMailApiError
12
12
  } from "./chunk-4VQP57SO.js";
package/dist/cli.js CHANGED
@@ -1,14 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- uninstall
4
- } from "./chunk-JUEHPFKY.js";
5
- import {
6
- install
7
- } from "./chunk-4EHV3I6W.js";
8
- import "./chunk-DKTAW2N5.js";
3
+ defaultDispatcherConfigPath,
4
+ resolveDispatcherTuning,
5
+ writeDispatcherTuning
6
+ } from "./chunk-B5JDOV32.js";
9
7
  import {
10
8
  status
11
9
  } from "./chunk-Q5BA2J2C.js";
10
+ import {
11
+ uninstall
12
+ } from "./chunk-2RVJ2Q3W.js";
13
+ import {
14
+ install
15
+ } from "./chunk-OUYPF4ER.js";
16
+ import "./chunk-LO5EQSQA.js";
12
17
  import "./chunk-US5FT2UB.js";
13
18
  import {
14
19
  AgenticMailApiError
@@ -42,17 +47,31 @@ function usage() {
42
47
  print(` install Register AgenticMail with Claude Code (default)`);
43
48
  print(` uninstall Remove the registration`);
44
49
  print(` status Show what's currently installed`);
50
+ print(` tune View / change dispatcher tuning knobs (rate limits, concurrency)`);
45
51
  print("");
46
52
  print(` ${BOLD("Flags:")}`);
47
- print(` --json (status) Emit machine-readable JSON instead of prose`);
53
+ print(` --json (status / tune) Emit machine-readable JSON instead of prose`);
48
54
  print(` --purge-bridge (uninstall) Also delete the AgenticMail bridge agent`);
55
+ print("");
56
+ print(` ${BOLD("Tune flags:")}`);
57
+ print(` --max-concurrent N Cap total simultaneous workers across all agents (default 50)`);
58
+ print(` --max-wakes-per-thread N Wakes a single (agent, thread) pair gets per window (default 10)`);
59
+ print(` --wake-window-ms N Rolling window for the above counter, ms (default 86400000 = 24h)`);
60
+ print(` --wake-coalesce-ms N Burst-debounce \u2014 collapse rapid replies into one wake (default 30000 = 30s, set 0 to disable)`);
61
+ print(` --sync-ms N How often the dispatcher polls /accounts for new agents (default 30000)`);
62
+ print(` --reset Delete ~/.agenticmail/dispatcher.json, return to defaults`);
49
63
  print(` -h, --help Show this help and exit`);
50
64
  print("");
51
- print(` ${BOLD("Environment overrides:")}`);
52
- print(` AGENTICMAIL_API_URL Override AgenticMail master API URL`);
53
- print(` AGENTICMAIL_MASTER_KEY Override master key (otherwise read from ~/.agenticmail/config.json)`);
54
- print(` CLAUDE_CODE_CONFIG_PATH Override Claude Code config path (default ~/.claude.json)`);
55
- print(` CLAUDE_CODE_AGENTS_DIR Override Claude Code agents dir (default ~/.claude/agents)`);
65
+ print(` ${BOLD("Environment overrides (same effect as the flags above, for PM2 setups):")}`);
66
+ print(` AGENTICMAIL_API_URL Override AgenticMail master API URL`);
67
+ print(` AGENTICMAIL_MASTER_KEY Override master key`);
68
+ print(` CLAUDE_CODE_CONFIG_PATH Override Claude Code config path`);
69
+ print(` CLAUDE_CODE_AGENTS_DIR Override Claude Code agents dir`);
70
+ print(` AGENTICMAIL_DISPATCHER_MAX Same as --max-concurrent`);
71
+ print(` AGENTICMAIL_DISPATCHER_MAX_WAKES_PER_THREAD Same as --max-wakes-per-thread`);
72
+ print(` AGENTICMAIL_DISPATCHER_WAKE_WINDOW_MS Same as --wake-window-ms`);
73
+ print(` AGENTICMAIL_DISPATCHER_COALESCE_MS Same as --wake-coalesce-ms`);
74
+ print(` AGENTICMAIL_DISPATCHER_SYNC Same as --sync-ms`);
56
75
  print("");
57
76
  }
58
77
  function envOptions() {
@@ -148,6 +167,92 @@ async function runStatus(asJson) {
148
167
  return 2;
149
168
  }
150
169
  }
170
+ function argNum(args, flag) {
171
+ for (let i = 0; i < args.length; i++) {
172
+ const a = args[i];
173
+ if (a === flag && i + 1 < args.length) {
174
+ const n = parseInt(args[i + 1], 10);
175
+ return Number.isFinite(n) && n > 0 ? n : void 0;
176
+ }
177
+ if (a.startsWith(`${flag}=`)) {
178
+ const n = parseInt(a.slice(flag.length + 1), 10);
179
+ return Number.isFinite(n) && n > 0 ? n : void 0;
180
+ }
181
+ }
182
+ return void 0;
183
+ }
184
+ async function runTune(args) {
185
+ const { existsSync, unlinkSync } = await import("fs");
186
+ const path = defaultDispatcherConfigPath();
187
+ const asJson = args.includes("--json");
188
+ if (args.includes("--reset")) {
189
+ try {
190
+ if (existsSync(path)) unlinkSync(path);
191
+ } catch {
192
+ }
193
+ if (asJson) {
194
+ print(JSON.stringify({ reset: true, path }, null, 2));
195
+ } else {
196
+ print("");
197
+ print(` ${PINK("\u{1F380} Dispatcher tuning")}`);
198
+ print("");
199
+ ok(`Reset: deleted ${path}`);
200
+ print(` ${DIM("The dispatcher will use built-in defaults until you re-tune.")}`);
201
+ print("");
202
+ }
203
+ return 0;
204
+ }
205
+ const patch = {
206
+ maxConcurrentWorkers: argNum(args, "--max-concurrent"),
207
+ maxWakesPerThread: argNum(args, "--max-wakes-per-thread"),
208
+ wakeWindowMs: argNum(args, "--wake-window-ms"),
209
+ wakeCoalesceMs: argNum(args, "--wake-coalesce-ms"),
210
+ accountSyncIntervalMs: argNum(args, "--sync-ms")
211
+ };
212
+ const anyFlag = Object.values(patch).some((v) => v !== void 0);
213
+ if (anyFlag) {
214
+ const merged = writeDispatcherTuning(patch);
215
+ if (asJson) {
216
+ print(JSON.stringify(merged, null, 2));
217
+ return 0;
218
+ }
219
+ print("");
220
+ print(` ${PINK("\u{1F380} Dispatcher tuning saved")}`);
221
+ print("");
222
+ ok(`Wrote ${path}`);
223
+ dim(` maxConcurrentWorkers: ${merged.maxConcurrentWorkers ?? "(default)"}`);
224
+ dim(` maxWakesPerThread: ${merged.maxWakesPerThread ?? "(default)"}`);
225
+ dim(` wakeWindowMs: ${merged.wakeWindowMs ?? "(default)"}`);
226
+ dim(` wakeCoalesceMs: ${merged.wakeCoalesceMs ?? "(default)"}`);
227
+ dim(` accountSyncIntervalMs: ${merged.accountSyncIntervalMs ?? "(default)"}`);
228
+ print("");
229
+ print(` ${BOLD("Next:")} restart the dispatcher daemon to apply.`);
230
+ print(` ${DIM("pm2 restart agenticmail-claudecode-dispatcher")}`);
231
+ print("");
232
+ return 0;
233
+ }
234
+ const resolved = resolveDispatcherTuning();
235
+ if (asJson) {
236
+ print(JSON.stringify({ resolved, path }, null, 2));
237
+ return 0;
238
+ }
239
+ print("");
240
+ print(` ${PINK("\u{1F380} Dispatcher tuning (current)")}`);
241
+ print("");
242
+ print(` Config file: ${path}`);
243
+ print("");
244
+ print(` ${BOLD("Effective values (env > file > built-in default):")}`);
245
+ dim(` maxConcurrentWorkers: ${resolved.maxConcurrentWorkers ?? "50 (default)"}`);
246
+ dim(` maxWakesPerThread: ${resolved.maxWakesPerThread ?? "10 (default)"}`);
247
+ dim(` wakeWindowMs: ${resolved.wakeWindowMs ?? "86400000 (default \u2014 24h)"}`);
248
+ dim(` wakeCoalesceMs: ${resolved.wakeCoalesceMs ?? "30000 (default \u2014 30s)"}`);
249
+ dim(` accountSyncIntervalMs: ${resolved.accountSyncIntervalMs ?? "30000 (default)"}`);
250
+ print("");
251
+ print(` ${BOLD("Change with flags, e.g.:")}`);
252
+ dim(` agenticmail-claudecode tune --max-wakes-per-thread 100 --max-concurrent 200`);
253
+ print("");
254
+ return 0;
255
+ }
151
256
  async function main() {
152
257
  const args = process.argv.slice(2);
153
258
  if (args.includes("-h") || args.includes("--help") || args[0] === "help") {
@@ -163,6 +268,8 @@ async function main() {
163
268
  return runUninstall(args.includes("--purge-bridge"));
164
269
  case "status":
165
270
  return runStatus(args.includes("--json"));
271
+ case "tune":
272
+ return runTune(args);
166
273
  default:
167
274
  fail(`Unknown command: ${command}`);
168
275
  usage();
@@ -1,17 +1,27 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ resolveDispatcherTuning
4
+ } from "./chunk-B5JDOV32.js";
2
5
  import {
3
6
  Dispatcher
4
- } from "./chunk-JOK76WRC.js";
7
+ } from "./chunk-LVYWYEFG.js";
5
8
  import "./chunk-4VQP57SO.js";
6
9
 
7
10
  // src/dispatcher-bin.ts
8
11
  async function main() {
12
+ const tuning = resolveDispatcherTuning();
13
+ console.error(
14
+ `[dispatcher-bin] tuning: maxConcurrentWorkers=${tuning.maxConcurrentWorkers ?? "(default)"} maxWakesPerThread=${tuning.maxWakesPerThread ?? "(default)"} wakeWindowMs=${tuning.wakeWindowMs ?? "(default)"} wakeCoalesceMs=${tuning.wakeCoalesceMs ?? "(default)"} accountSyncIntervalMs=${tuning.accountSyncIntervalMs ?? "(default)"}`
15
+ );
9
16
  const dispatcher = new Dispatcher({
10
17
  apiUrl: process.env.AGENTICMAIL_API_URL,
11
18
  masterKey: process.env.AGENTICMAIL_MASTER_KEY,
12
19
  agentsDir: process.env.CLAUDE_CODE_AGENTS_DIR,
13
- maxConcurrentWorkers: positiveInt(process.env.AGENTICMAIL_DISPATCHER_MAX),
14
- accountSyncIntervalMs: positiveInt(process.env.AGENTICMAIL_DISPATCHER_SYNC)
20
+ maxConcurrentWorkers: tuning.maxConcurrentWorkers,
21
+ maxWakesPerThread: tuning.maxWakesPerThread,
22
+ wakeWindowMs: tuning.wakeWindowMs,
23
+ wakeCoalesceMs: tuning.wakeCoalesceMs,
24
+ accountSyncIntervalMs: tuning.accountSyncIntervalMs
15
25
  });
16
26
  const shutdown = async (sig) => {
17
27
  console.error(`[dispatcher-bin] received ${sig} \u2014 shutting down`);
@@ -32,11 +42,6 @@ async function main() {
32
42
  });
33
43
  await dispatcher.start();
34
44
  }
35
- function positiveInt(s) {
36
- if (!s) return void 0;
37
- const n = parseInt(s, 10);
38
- return Number.isFinite(n) && n > 0 ? n : void 0;
39
- }
40
45
  main().catch((err) => {
41
46
  console.error(`[dispatcher-bin] fatal: ${err.message}`);
42
47
  process.exit(1);
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Tuning knobs for the dispatcher daemon.
3
+ *
4
+ * # Why this exists
5
+ *
6
+ * The dispatcher has several rate-limit / concurrency knobs that need
7
+ * to be tunable WITHOUT a code change:
8
+ *
9
+ * - `maxConcurrentWorkers` — global worker cap (default 50)
10
+ * - `maxWakesPerThread` — wakes per (agent, thread) per window (default 10)
11
+ * - `wakeWindowMs` — the window itself (default 24h)
12
+ * - `wakeCoalesceMs` — burst-debounce window (default 30s)
13
+ * - `accountSyncIntervalMs` — how often to poll /accounts (default 30s)
14
+ *
15
+ * The DEFAULTS are conservative — protect a fresh install from runaway
16
+ * cost. Power users running active coordination on a single thread
17
+ * routinely hit the 10/24h wake cap and need it raised. Today (before
18
+ * this module) the only way to do that was edit dispatcher.ts and rebuild,
19
+ * which is absurd.
20
+ *
21
+ * # Three input sources, in precedence order
22
+ *
23
+ * 1. Explicit constructor args (programmatic callers, tests)
24
+ * 2. Env vars (PM2 ecosystem.config.cjs lives here)
25
+ * 3. `~/.agenticmail/dispatcher.json` (persistent operator preference,
26
+ * written by the CLI's `agenticmail dispatcher tune` command)
27
+ * 4. Hard-coded defaults
28
+ *
29
+ * Earlier sources win.
30
+ *
31
+ * # File format
32
+ *
33
+ * { "version": 1,
34
+ * "maxConcurrentWorkers": 200,
35
+ * "maxWakesPerThread": 50,
36
+ * "wakeWindowMs": 86400000,
37
+ * "wakeCoalesceMs": 30000,
38
+ * "accountSyncIntervalMs": 30000 }
39
+ *
40
+ * Missing keys fall through to the next precedence level. All values
41
+ * are integers; non-positive / non-finite values fall through (so a
42
+ * broken edit produces a slightly-stale config, not a broken
43
+ * dispatcher).
44
+ */
45
+ interface DispatcherTuning {
46
+ maxConcurrentWorkers?: number;
47
+ maxWakesPerThread?: number;
48
+ wakeWindowMs?: number;
49
+ wakeCoalesceMs?: number;
50
+ accountSyncIntervalMs?: number;
51
+ }
52
+ declare function defaultDispatcherConfigPath(): string;
53
+ /**
54
+ * Resolve final tuning values by merging the three precedence layers:
55
+ * explicit args > env vars > file > defaults (left to the consumer).
56
+ *
57
+ * Returns ONLY the keys that were explicitly set — the caller passes
58
+ * the result through to the Dispatcher constructor, whose defaults
59
+ * fill in anything still undefined.
60
+ */
61
+ declare function resolveDispatcherTuning(opts?: {
62
+ explicit?: DispatcherTuning;
63
+ env?: NodeJS.ProcessEnv;
64
+ configPath?: string;
65
+ }): DispatcherTuning;
66
+ /**
67
+ * Persist the operator's preferences to ~/.agenticmail/dispatcher.json
68
+ * atomically (.tmp + rename) so a power outage mid-write never produces
69
+ * a half-written config. Only writes the keys that are explicitly set
70
+ * in the patch — preserves keys the user already configured.
71
+ *
72
+ * Returns the resulting on-disk shape so the caller can echo it back.
73
+ */
74
+ declare function writeDispatcherTuning(patch: DispatcherTuning, configPath?: string): DispatcherTuning & {
75
+ version: number;
76
+ updatedAtMs: number;
77
+ };
78
+
79
+ export { type DispatcherTuning, defaultDispatcherConfigPath, resolveDispatcherTuning, writeDispatcherTuning };
@@ -0,0 +1,10 @@
1
+ import {
2
+ defaultDispatcherConfigPath,
3
+ resolveDispatcherTuning,
4
+ writeDispatcherTuning
5
+ } from "./chunk-B5JDOV32.js";
6
+ export {
7
+ defaultDispatcherConfigPath,
8
+ resolveDispatcherTuning,
9
+ writeDispatcherTuning
10
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Dispatcher
3
- } from "./chunk-JOK76WRC.js";
3
+ } from "./chunk-LVYWYEFG.js";
4
4
  import "./chunk-4VQP57SO.js";
5
5
  export {
6
6
  Dispatcher
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  createIntegrationRoutes
3
- } from "./chunk-ZFLOFC6P.js";
4
- import "./chunk-JUEHPFKY.js";
5
- import "./chunk-4EHV3I6W.js";
6
- import "./chunk-DKTAW2N5.js";
3
+ } from "./chunk-UARFA4NI.js";
7
4
  import "./chunk-Q5BA2J2C.js";
5
+ import "./chunk-2RVJ2Q3W.js";
6
+ import "./chunk-OUYPF4ER.js";
7
+ import "./chunk-LO5EQSQA.js";
8
8
  import "./chunk-US5FT2UB.js";
9
9
  import "./chunk-4VQP57SO.js";
10
10
  export {
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ import { A as AgenticMailAccount } from './config-CXW3gXFC.js';
5
5
  export { C as ClaudeCodeIntegrationConfig, I as InstallResult, a as InstallStatus, R as ResolveConfigOptions, U as UninstallResult, r as resolveConfig } from './config-CXW3gXFC.js';
6
6
  export { createIntegrationRoutes } from './http-routes.js';
7
7
  export { Dispatcher, DispatcherOptions, QueryFn } from './dispatcher.js';
8
+ export { DispatcherTuning, defaultDispatcherConfigPath, resolveDispatcherTuning, writeDispatcherTuning } from './dispatcher-tuning.js';
8
9
  import 'express';
9
10
 
10
11
  /**
package/dist/index.js CHANGED
@@ -1,20 +1,25 @@
1
+ import {
2
+ defaultDispatcherConfigPath,
3
+ resolveDispatcherTuning,
4
+ writeDispatcherTuning
5
+ } from "./chunk-B5JDOV32.js";
1
6
  import {
2
7
  Dispatcher,
3
8
  loadPersonaForAgent
4
- } from "./chunk-JOK76WRC.js";
9
+ } from "./chunk-LVYWYEFG.js";
5
10
  import {
6
11
  createIntegrationRoutes
7
- } from "./chunk-ZFLOFC6P.js";
12
+ } from "./chunk-UARFA4NI.js";
13
+ import {
14
+ status
15
+ } from "./chunk-Q5BA2J2C.js";
8
16
  import {
9
17
  uninstall
10
- } from "./chunk-JUEHPFKY.js";
18
+ } from "./chunk-2RVJ2Q3W.js";
11
19
  import {
12
20
  install
13
- } from "./chunk-4EHV3I6W.js";
14
- import "./chunk-DKTAW2N5.js";
15
- import {
16
- status
17
- } from "./chunk-Q5BA2J2C.js";
21
+ } from "./chunk-OUYPF4ER.js";
22
+ import "./chunk-LO5EQSQA.js";
18
23
  import "./chunk-US5FT2UB.js";
19
24
  import {
20
25
  AgenticMailApiError,
@@ -34,6 +39,7 @@ export {
34
39
  MANAGED_BY_MARKER,
35
40
  checkApiHealth,
36
41
  createIntegrationRoutes,
42
+ defaultDispatcherConfigPath,
37
43
  deleteAccount,
38
44
  ensureAccount,
39
45
  getAccountByName,
@@ -43,6 +49,8 @@ export {
43
49
  renderPersonaBody,
44
50
  renderSubagentMarkdown,
45
51
  resolveConfig,
52
+ resolveDispatcherTuning,
46
53
  status,
47
- uninstall
54
+ uninstall,
55
+ writeDispatcherTuning
48
56
  };
package/dist/install.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  install,
3
3
  selectExposableAgents
4
- } from "./chunk-4EHV3I6W.js";
5
- import "./chunk-DKTAW2N5.js";
4
+ } from "./chunk-OUYPF4ER.js";
5
+ import "./chunk-LO5EQSQA.js";
6
6
  import "./chunk-US5FT2UB.js";
7
7
  import "./chunk-4VQP57SO.js";
8
8
  export {
package/dist/mail-hook.js CHANGED
@@ -7,6 +7,8 @@ import { join, dirname } from "path";
7
7
  var AGENTICMAIL_DIR = join(homedir(), ".agenticmail");
8
8
  var CONFIG_PATH = join(AGENTICMAIL_DIR, "config.json");
9
9
  var CURSOR_PATH = join(AGENTICMAIL_DIR, "claudecode-hook-cursor.json");
10
+ var SESSIONS_PATH = join(AGENTICMAIL_DIR, "claudecode-hook-sessions.json");
11
+ var SESSIONS_CAP = 100;
10
12
  var HOOK_VERSION = "1";
11
13
  var HTTP_TIMEOUT_MS = 800;
12
14
  var GLOBAL_TIMEOUT_MS = 1500;
@@ -47,17 +49,95 @@ async function readStdinJson() {
47
49
  setTimeout(() => finish(() => null), 200).unref();
48
50
  });
49
51
  }
52
+ function capabilitiesBlurb() {
53
+ return [
54
+ "\u{1F380} AgenticMail is available via MCP (mcp__agenticmail__*).",
55
+ "",
56
+ "When to reach for it:",
57
+ " \xB7 Building anything that benefits from MULTIPLE specialized roles working in parallel \u2014 designer + developer + reviewer, researcher + writer + fact-checker, planner + executor + critic.",
58
+ " \xB7 Coordinating long-running async work that survives across Claude Code restarts (email threads are durable; in-memory state is not).",
59
+ " \xB7 Anywhere you would otherwise spawn the Task tool repeatedly for sub-tasks \u2014 prefer AgenticMail when those sub-tasks need to talk to EACH OTHER, not just back to you.",
60
+ "",
61
+ "High-leverage tools:",
62
+ " \xB7 mcp__agenticmail__create_account({ name, role }) \u2014 spawn a fresh persistent agent identity with its own inbox + API key.",
63
+ ' \xB7 mcp__agenticmail__send_email({ to, cc, subject, text, wake }) \u2014 kick off the work. CC the whole team; use `wake: ["alice"]` to give exactly one agent the next turn while others stay informed.',
64
+ " \xB7 mcp__agenticmail__call_agent({ agent, message }) / mcp__agenticmail__wait_for_email \u2014 blocking RPC when you need a specific answer back before continuing your own turn.",
65
+ "",
66
+ "Coordination pattern: one email thread = the shared workspace. Reply-all keeps the audit trail. Use `wake` to control whose turn it is.",
67
+ "",
68
+ "Other tools cover: inbox/folder management, drafts, templates, tasks, contacts, signatures, SMS, voice. Full list under mcp__agenticmail__* \u2014 discover on demand, don't front-load them all."
69
+ ].join("\n");
70
+ }
71
+ function loadSeenSessions() {
72
+ if (!existsSync(SESSIONS_PATH)) return [];
73
+ try {
74
+ const parsed = JSON.parse(readFileSync(SESSIONS_PATH, "utf-8"));
75
+ const arr = Array.isArray(parsed?.seen) ? parsed.seen : [];
76
+ return arr.filter((s) => typeof s === "string");
77
+ } catch {
78
+ return [];
79
+ }
80
+ }
81
+ function rememberSession(sessionId, seen) {
82
+ const next = seen.filter((s) => s !== sessionId);
83
+ next.push(sessionId);
84
+ while (next.length > SESSIONS_CAP) next.shift();
85
+ try {
86
+ if (!existsSync(dirname(SESSIONS_PATH))) mkdirSync(dirname(SESSIONS_PATH), { recursive: true });
87
+ writeFileSync(SESSIONS_PATH, JSON.stringify({ seen: next, hookVersion: HOOK_VERSION }, null, 2));
88
+ } catch {
89
+ }
90
+ }
50
91
  async function main() {
51
92
  const input = await readStdinJson();
52
93
  const eventName = input?.hook_event_name ?? "UserPromptSubmit";
53
- if (!existsSync(CONFIG_PATH)) return;
94
+ const sessionId = typeof input?.session_id === "string" ? input.session_id : "";
95
+ if (eventName === "SessionStart") {
96
+ process.stdout.write(JSON.stringify({
97
+ hookSpecificOutput: {
98
+ hookEventName: "SessionStart",
99
+ additionalContext: capabilitiesBlurb()
100
+ }
101
+ }));
102
+ return;
103
+ }
104
+ let blurbContext = "";
105
+ if (eventName === "UserPromptSubmit" && sessionId) {
106
+ const seen = loadSeenSessions();
107
+ if (!seen.includes(sessionId)) {
108
+ blurbContext = capabilitiesBlurb();
109
+ rememberSession(sessionId, seen);
110
+ }
111
+ }
112
+ const emitAndExit = (mailContext) => {
113
+ const combined = [blurbContext, mailContext].filter(Boolean).join("\n\n");
114
+ if (!combined) return;
115
+ if (eventName === "Stop") {
116
+ process.stdout.write(JSON.stringify({ decision: "block", reason: combined }));
117
+ } else {
118
+ process.stdout.write(JSON.stringify({
119
+ hookSpecificOutput: {
120
+ hookEventName: eventName,
121
+ additionalContext: combined
122
+ }
123
+ }));
124
+ }
125
+ };
126
+ if (!existsSync(CONFIG_PATH)) {
127
+ emitAndExit("");
128
+ return;
129
+ }
54
130
  let cfg;
55
131
  try {
56
132
  cfg = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
57
133
  } catch {
134
+ emitAndExit("");
135
+ return;
136
+ }
137
+ if (!cfg.masterKey) {
138
+ emitAndExit("");
58
139
  return;
59
140
  }
60
- if (!cfg.masterKey) return;
61
141
  const apiHost = cfg.api?.host ?? "127.0.0.1";
62
142
  const apiPort = cfg.api?.port ?? 3829;
63
143
  const apiUrl = `http://${apiHost}:${apiPort}`;
@@ -67,15 +147,22 @@ async function main() {
67
147
  headers: { Authorization: `Bearer ${cfg.masterKey}` },
68
148
  signal: AbortSignal.timeout(HTTP_TIMEOUT_MS)
69
149
  });
70
- if (!r.ok) return;
150
+ if (!r.ok) {
151
+ emitAndExit("");
152
+ return;
153
+ }
71
154
  const data = await r.json();
72
155
  bridge = (data.agents ?? []).find(
73
156
  (a) => a.name === "claudecode" || a.name === "claude" || a.role === "bridge"
74
157
  );
75
158
  } catch {
159
+ emitAndExit("");
160
+ return;
161
+ }
162
+ if (!bridge?.apiKey) {
163
+ emitAndExit("");
76
164
  return;
77
165
  }
78
- if (!bridge?.apiKey) return;
79
166
  let cursorMs = 0;
80
167
  let lastCheckedMs = 0;
81
168
  if (existsSync(CURSOR_PATH)) {
@@ -96,10 +183,14 @@ async function main() {
96
183
  headers: { Authorization: `Bearer ${bridge.apiKey}` },
97
184
  signal: AbortSignal.timeout(HTTP_TIMEOUT_MS)
98
185
  });
99
- if (!r.ok) return;
186
+ if (!r.ok) {
187
+ emitAndExit("");
188
+ return;
189
+ }
100
190
  const data = await r.json();
101
191
  messages = data.messages ?? [];
102
192
  } catch {
193
+ emitAndExit("");
103
194
  return;
104
195
  }
105
196
  const newOnes = messages.filter((m) => {
@@ -118,6 +209,7 @@ async function main() {
118
209
  } catch {
119
210
  }
120
211
  }
212
+ emitAndExit("");
121
213
  return;
122
214
  }
123
215
  newOnes.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
@@ -149,19 +241,7 @@ async function main() {
149
241
  );
150
242
  } catch {
151
243
  }
152
- if (eventName === "Stop") {
153
- process.stdout.write(JSON.stringify({
154
- decision: "block",
155
- reason: lines.join("\n")
156
- }));
157
- } else {
158
- process.stdout.write(JSON.stringify({
159
- hookSpecificOutput: {
160
- hookEventName: eventName,
161
- additionalContext: lines.join("\n")
162
- }
163
- }));
164
- }
244
+ emitAndExit(lines.join("\n"));
165
245
  }
166
246
  var globalTimeout = new Promise((resolve) => {
167
247
  setTimeout(() => resolve(), GLOBAL_TIMEOUT_MS).unref();
package/dist/uninstall.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  uninstall
3
- } from "./chunk-JUEHPFKY.js";
4
- import "./chunk-DKTAW2N5.js";
3
+ } from "./chunk-2RVJ2Q3W.js";
4
+ import "./chunk-LO5EQSQA.js";
5
5
  import "./chunk-US5FT2UB.js";
6
6
  import "./chunk-4VQP57SO.js";
7
7
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/claudecode",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
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",
@@ -41,7 +41,7 @@
41
41
  "LICENSE"
42
42
  ],
43
43
  "scripts": {
44
- "build": "tsup src/index.ts src/cli.ts src/install.ts src/uninstall.ts src/status.ts src/http-routes.ts src/dispatcher.ts src/dispatcher-bin.ts src/mail-hook.ts --format esm --dts --clean",
44
+ "build": "tsup src/index.ts src/cli.ts src/install.ts src/uninstall.ts src/status.ts src/http-routes.ts src/dispatcher.ts src/dispatcher-bin.ts src/dispatcher-tuning.ts src/mail-hook.ts --format esm --dts --clean",
45
45
  "test": "vitest run --passWithNoTests",
46
46
  "preuninstall": "node scripts/uninstall.mjs",
47
47
  "prepublishOnly": "npm run build"