@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.
- package/dist/{chunk-JUEHPFKY.js → chunk-2RVJ2Q3W.js} +1 -1
- package/dist/chunk-B5JDOV32.js +73 -0
- package/dist/{chunk-DKTAW2N5.js → chunk-LO5EQSQA.js} +2 -2
- package/dist/{chunk-JOK76WRC.js → chunk-LVYWYEFG.js} +4 -1
- package/dist/{chunk-4EHV3I6W.js → chunk-OUYPF4ER.js} +1 -1
- package/dist/{chunk-ZFLOFC6P.js → chunk-UARFA4NI.js} +5 -5
- package/dist/cli.js +119 -12
- package/dist/dispatcher-bin.js +13 -8
- package/dist/dispatcher-tuning.d.ts +79 -0
- package/dist/dispatcher-tuning.js +10 -0
- package/dist/dispatcher.js +1 -1
- package/dist/http-routes.js +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -9
- package/dist/install.js +2 -2
- package/dist/mail-hook.js +98 -18
- package/dist/uninstall.js +2 -2
- package/package.json +2 -2
|
@@ -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(
|
|
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,12 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
status
|
|
3
|
+
} from "./chunk-Q5BA2J2C.js";
|
|
1
4
|
import {
|
|
2
5
|
uninstall
|
|
3
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-2RVJ2Q3W.js";
|
|
4
7
|
import {
|
|
5
8
|
install
|
|
6
|
-
} from "./chunk-
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
53
|
-
print(` AGENTICMAIL_MASTER_KEY
|
|
54
|
-
print(` CLAUDE_CODE_CONFIG_PATH
|
|
55
|
-
print(` CLAUDE_CODE_AGENTS_DIR
|
|
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();
|
package/dist/dispatcher-bin.js
CHANGED
|
@@ -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-
|
|
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:
|
|
14
|
-
|
|
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 };
|
package/dist/dispatcher.js
CHANGED
package/dist/http-routes.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createIntegrationRoutes
|
|
3
|
-
} from "./chunk-
|
|
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-
|
|
9
|
+
} from "./chunk-LVYWYEFG.js";
|
|
5
10
|
import {
|
|
6
11
|
createIntegrationRoutes
|
|
7
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-UARFA4NI.js";
|
|
13
|
+
import {
|
|
14
|
+
status
|
|
15
|
+
} from "./chunk-Q5BA2J2C.js";
|
|
8
16
|
import {
|
|
9
17
|
uninstall
|
|
10
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-2RVJ2Q3W.js";
|
|
11
19
|
import {
|
|
12
20
|
install
|
|
13
|
-
} from "./chunk-
|
|
14
|
-
import "./chunk-
|
|
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
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
|
-
|
|
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)
|
|
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)
|
|
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
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agenticmail/claudecode",
|
|
3
|
-
"version": "0.2.
|
|
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"
|