@agenticmail/api 0.7.8 → 0.7.11
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/README.md +2 -2
- package/dist/index.js +66 -16
- package/package.json +1 -1
- package/public/branding/agenticmail-logo.png +0 -0
- package/public/branding/claude-mark.svg +2 -0
- package/public/index.html +52 -1237
- package/public/js/api.js +25 -0
- package/public/js/app.js +228 -0
- package/public/js/avatar.js +43 -0
- package/public/js/compose.js +81 -0
- package/public/js/icons.js +56 -0
- package/public/js/list-view.js +160 -0
- package/public/js/markdown.js +97 -0
- package/public/js/message-view.js +87 -0
- package/public/js/profile.js +54 -0
- package/public/js/search.js +45 -0
- package/public/js/sidebar.js +40 -0
- package/public/js/sse.js +97 -0
- package/public/js/state.js +17 -0
- package/public/js/time.js +33 -0
- package/public/js/utils.js +22 -0
- package/public/styles.css +668 -0
package/README.md
CHANGED
|
@@ -8,9 +8,9 @@ The API server for [AgenticMail](https://github.com/agenticmail/agenticmail) —
|
|
|
8
8
|
|
|
9
9
|
This package runs a web server that handles everything: sending email and SMS, reading inboxes, managing agents, phone number access, real-time notifications, inter-agent messaging, spam filtering, outbound security scanning, and gateway configuration. Every feature in AgenticMail is accessible through this API.
|
|
10
10
|
|
|
11
|
-
## ✨ What's new in 0.7.
|
|
11
|
+
## ✨ What's new in 0.7.9
|
|
12
12
|
|
|
13
|
-
- **🌐
|
|
13
|
+
- **🌐 Gmail-style web UI, fully redesigned** — `packages/api/public/` ships a proper two-column Gmail layout (sidebar with Compose + folders / content pane) served by `express.static` at the API root. Every emoji replaced with an inline 24×24 vector icon library (`public/js/icons.js`). HTML shell + dedicated `styles.css` + 14 modular ES module JS files under `public/js/`. Hash router (`#/inbox`, `#/m/<uid>`), search with `from:` / `subject:` operators, real-time SSE updates, browser notifications. Run via `agenticmail web` from the CLI.
|
|
14
14
|
- **Wake allowlist on `POST /mail/send`** — accept a `wake` parameter (array of agent names or comma-separated string). The API normalises it, sets an `X-AgenticMail-Wake` header on the outgoing SMTP envelope, AND surfaces it as `wakeAllowlist` on the SSE event so the dispatcher can decide which CC'd recipients to actually give a Claude turn.
|
|
15
15
|
- **Shared helpers exported from `routes/mail.ts`** — `normalizeWakeList`, `wakeHeaders`, and `pushLocalRecipientWakes` so every send path (`/mail/send`, `/templates/:id/send`, `/drafts/:id/send`, `/mail/pending/:id/approve`) uses the same primitives.
|
|
16
16
|
- **System events SSE** at `GET /system/events` — master-auth stream that emits `account_created` / `account_deleted` / `worker_started` / `worker_finished` events. Powers the dispatcher's zero-wait wake on newly-created agents and the `check_activity` MCP tool.
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,8 @@ import express from "express";
|
|
|
7
7
|
import cors from "cors";
|
|
8
8
|
import rateLimit from "express-rate-limit";
|
|
9
9
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10
|
-
import { dirname as dirname2, join as
|
|
11
|
-
import { existsSync } from "fs";
|
|
10
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
11
|
+
import { existsSync as existsSync2 } from "fs";
|
|
12
12
|
import {
|
|
13
13
|
resolveConfig,
|
|
14
14
|
getDatabase,
|
|
@@ -4680,15 +4680,15 @@ function createStorageRoutes(rawDb, accountManager2, config, dialect = "sqlite")
|
|
|
4680
4680
|
|
|
4681
4681
|
// src/routes/dispatcher-activity.ts
|
|
4682
4682
|
import { Router as Router13 } from "express";
|
|
4683
|
-
|
|
4683
|
+
import { existsSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
4684
|
+
import { homedir } from "os";
|
|
4685
|
+
import { join as join2 } from "path";
|
|
4686
|
+
var STALE_HEARTBEAT_MS = 90 * 1e3;
|
|
4684
4687
|
var RECENT_TTL_MS = 2 * 60 * 1e3;
|
|
4685
4688
|
var HARD_CAP = 256;
|
|
4686
4689
|
var active = /* @__PURE__ */ new Map();
|
|
4687
4690
|
var recent = /* @__PURE__ */ new Map();
|
|
4688
4691
|
function prune(nowMs) {
|
|
4689
|
-
for (const [id, w] of active) {
|
|
4690
|
-
if (nowMs - w.startedAtMs > ACTIVE_TTL_MS) active.delete(id);
|
|
4691
|
-
}
|
|
4692
4692
|
for (const [id, w] of recent) {
|
|
4693
4693
|
const t = w.endedAtMs ?? w.startedAtMs;
|
|
4694
4694
|
if (nowMs - t > RECENT_TTL_MS) recent.delete(id);
|
|
@@ -4718,7 +4718,9 @@ function createDispatcherActivityRoutes() {
|
|
|
4718
4718
|
agentEmail: typeof body.agentEmail === "string" ? body.agentEmail : void 0,
|
|
4719
4719
|
kind: typeof body.kind === "string" ? body.kind : "unknown",
|
|
4720
4720
|
trigger: body.trigger && typeof body.trigger === "object" ? body.trigger : void 0,
|
|
4721
|
-
startedAtMs: Date.now()
|
|
4721
|
+
startedAtMs: Date.now(),
|
|
4722
|
+
lastHeartbeatMs: Date.now(),
|
|
4723
|
+
turnCount: 0
|
|
4722
4724
|
};
|
|
4723
4725
|
prune(info.startedAtMs);
|
|
4724
4726
|
active.set(info.workerId, info);
|
|
@@ -4748,7 +4750,8 @@ function createDispatcherActivityRoutes() {
|
|
|
4748
4750
|
},
|
|
4749
4751
|
endedAtMs: nowMs,
|
|
4750
4752
|
ok: body.ok === false ? false : true,
|
|
4751
|
-
resultPreview: typeof body.resultPreview === "string" ? body.resultPreview.slice(0, 240) : void 0
|
|
4753
|
+
resultPreview: typeof body.resultPreview === "string" ? body.resultPreview.slice(0, 240) : void 0,
|
|
4754
|
+
turnCount: typeof body.turnCount === "number" ? body.turnCount : existing?.turnCount
|
|
4752
4755
|
};
|
|
4753
4756
|
active.delete(body.workerId);
|
|
4754
4757
|
recent.set(body.workerId, info);
|
|
@@ -4762,6 +4765,22 @@ function createDispatcherActivityRoutes() {
|
|
|
4762
4765
|
}
|
|
4763
4766
|
res.json({ ok: true });
|
|
4764
4767
|
});
|
|
4768
|
+
router.post("/dispatcher/worker-heartbeat", requireMaster, (req, res) => {
|
|
4769
|
+
const body = req.body ?? {};
|
|
4770
|
+
if (typeof body.workerId !== "string") {
|
|
4771
|
+
res.status(400).json({ error: "workerId is required" });
|
|
4772
|
+
return;
|
|
4773
|
+
}
|
|
4774
|
+
const existing = active.get(body.workerId);
|
|
4775
|
+
if (!existing) {
|
|
4776
|
+
res.json({ ok: true, ignored: "unknown worker" });
|
|
4777
|
+
return;
|
|
4778
|
+
}
|
|
4779
|
+
existing.lastHeartbeatMs = Date.now();
|
|
4780
|
+
if (typeof body.lastTool === "string") existing.lastTool = body.lastTool;
|
|
4781
|
+
if (typeof body.turnCount === "number") existing.turnCount = body.turnCount;
|
|
4782
|
+
res.json({ ok: true });
|
|
4783
|
+
});
|
|
4765
4784
|
router.get("/dispatcher/activity", requireMaster, (_req, res) => {
|
|
4766
4785
|
const nowMs = Date.now();
|
|
4767
4786
|
prune(nowMs);
|
|
@@ -4769,7 +4788,9 @@ function createDispatcherActivityRoutes() {
|
|
|
4769
4788
|
now: nowMs,
|
|
4770
4789
|
active: Array.from(active.values()).map((w) => ({
|
|
4771
4790
|
...w,
|
|
4772
|
-
durationMs: nowMs - w.startedAtMs
|
|
4791
|
+
durationMs: nowMs - w.startedAtMs,
|
|
4792
|
+
stale: w.lastHeartbeatMs !== void 0 && nowMs - w.lastHeartbeatMs > STALE_HEARTBEAT_MS,
|
|
4793
|
+
heartbeatAgeMs: w.lastHeartbeatMs !== void 0 ? nowMs - w.lastHeartbeatMs : void 0
|
|
4773
4794
|
})),
|
|
4774
4795
|
recent: Array.from(recent.values()).map((w) => ({
|
|
4775
4796
|
...w,
|
|
@@ -4777,6 +4798,35 @@ function createDispatcherActivityRoutes() {
|
|
|
4777
4798
|
}))
|
|
4778
4799
|
});
|
|
4779
4800
|
});
|
|
4801
|
+
router.get("/dispatcher/worker-log/:workerId", requireMaster, (req, res) => {
|
|
4802
|
+
const rawId = String(req.params.workerId ?? "");
|
|
4803
|
+
if (!rawId) {
|
|
4804
|
+
res.status(400).json({ error: "workerId is required" });
|
|
4805
|
+
return;
|
|
4806
|
+
}
|
|
4807
|
+
const lines = Math.min(Math.max(Number(req.query.lines ?? 80), 1), 1e3);
|
|
4808
|
+
const safe = rawId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
4809
|
+
const path = join2(homedir(), ".agenticmail", "worker-logs", `${safe}.log`);
|
|
4810
|
+
if (!existsSync(path)) {
|
|
4811
|
+
res.status(404).json({ error: "no log file for that workerId" });
|
|
4812
|
+
return;
|
|
4813
|
+
}
|
|
4814
|
+
try {
|
|
4815
|
+
const raw = readFileSync2(path, "utf-8");
|
|
4816
|
+
const stat = statSync(path);
|
|
4817
|
+
const all = raw.split(/\r?\n/);
|
|
4818
|
+
const tail = all.filter(Boolean).slice(-lines);
|
|
4819
|
+
res.json({
|
|
4820
|
+
workerId: rawId,
|
|
4821
|
+
path,
|
|
4822
|
+
bytes: stat.size,
|
|
4823
|
+
lines: tail.length,
|
|
4824
|
+
tail
|
|
4825
|
+
});
|
|
4826
|
+
} catch (err) {
|
|
4827
|
+
res.status(500).json({ error: err.message });
|
|
4828
|
+
}
|
|
4829
|
+
});
|
|
4780
4830
|
return router;
|
|
4781
4831
|
}
|
|
4782
4832
|
|
|
@@ -4852,15 +4902,15 @@ function createApp(configOverrides) {
|
|
|
4852
4902
|
const staticDir = (() => {
|
|
4853
4903
|
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
4854
4904
|
const candidates = [
|
|
4855
|
-
|
|
4856
|
-
|
|
4905
|
+
join3(here, "..", "public"),
|
|
4906
|
+
join3(here, "public")
|
|
4857
4907
|
];
|
|
4858
|
-
for (const c of candidates) if (
|
|
4908
|
+
for (const c of candidates) if (existsSync2(c)) return c;
|
|
4859
4909
|
return null;
|
|
4860
4910
|
})();
|
|
4861
4911
|
if (staticDir) {
|
|
4862
4912
|
app2.use("/", express.static(staticDir, { index: "index.html", extensions: ["html"] }));
|
|
4863
|
-
app2.get("/ui", (_req, res) => res.sendFile(
|
|
4913
|
+
app2.get("/ui", (_req, res) => res.sendFile(join3(staticDir, "index.html")));
|
|
4864
4914
|
}
|
|
4865
4915
|
app2.use("/api/agenticmail", createHealthRoutes(stalwart));
|
|
4866
4916
|
app2.use("/api/agenticmail", createInboundRoutes(accountManager2, config, gatewayManager));
|
|
@@ -4889,9 +4939,9 @@ function createApp(configOverrides) {
|
|
|
4889
4939
|
}
|
|
4890
4940
|
|
|
4891
4941
|
// src/index.ts
|
|
4892
|
-
import { readFileSync as
|
|
4942
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
4893
4943
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4894
|
-
import { dirname as dirname3, join as
|
|
4944
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
4895
4945
|
await prepareIntegrations();
|
|
4896
4946
|
function getLocalIp() {
|
|
4897
4947
|
const nets = networkInterfaces();
|
|
@@ -4906,7 +4956,7 @@ function getLocalIp() {
|
|
|
4906
4956
|
var VERSION = (() => {
|
|
4907
4957
|
try {
|
|
4908
4958
|
const __dirname = dirname3(fileURLToPath3(import.meta.url));
|
|
4909
|
-
const pkg = JSON.parse(
|
|
4959
|
+
const pkg = JSON.parse(readFileSync3(join4(__dirname, "..", "package.json"), "utf-8"));
|
|
4910
4960
|
return pkg.version;
|
|
4911
4961
|
} catch {
|
|
4912
4962
|
return "0.5.31";
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="73 220 158 165" fill="currentColor" aria-hidden="true"><path d="m 105.01,322.07 29.14,-16.35 0.49,-1.42 -0.49,-0.79 h -1.42 l -4.87,-0.3 -16.65,-0.45 -14.44,-0.6 -13.99,-0.75 -3.52,-0.75 -3.3,-4.35 0.34,-2.17 2.96,-1.99 4.24,0.37 9.37,0.64 14.06,0.97 10.2,0.6 15.11,1.57 h 2.4 l 0.34,-0.97 -0.82,-0.6 -0.64,-0.6 -14.55,-9.86 -15.75,-10.42 -8.25,-6 -4.46,-3.04 -2.25,-2.85 -0.97,-6.22 4.05,-4.46 5.44,0.37 1.39,0.37 5.51,4.24 11.77,9.11 15.37,11.32 2.25,1.87 0.9,-0.64 0.11,-0.45 -1.01,-1.69 -8.36,-15.11 -8.92,-15.37 -3.97,-6.37 -1.05,-3.82 c -0.37,-1.57 -0.64,-2.89 -0.64,-4.5 l 4.61,-6.26 2.55,-0.82 6.15,0.82 2.59,2.25 3.82,8.74 6.19,13.76 9.6,18.71 2.81,5.55 1.5,5.14 0.56,1.57 h 0.97 v -0.9 l 0.79,-10.54 1.46,-12.94 1.42,-16.65 0.49,-4.69 2.32,-5.62 4.61,-3.04 3.6,1.72 2.96,4.24 -0.41,2.74 -1.76,11.44 -3.45,17.92 -2.25,12 h 1.31 l 1.5,-1.5 6.07,-8.06 10.2,-12.75 4.5,-5.06 5.25,-5.59 3.37,-2.66 h 6.37 l 4.69,6.97 -2.1,7.2 -6.56,8.32 -5.44,7.05 -7.8,10.5 -4.87,8.4 0.45,0.67 1.16,-0.11 17.62,-3.75 9.52,-1.72 11.36,-1.95 5.14,2.4 0.56,2.44 -2.02,4.99 -12.15,3 -14.25,2.85 -21.22,5.02 -0.26,0.19 0.3,0.37 9.56,0.9 4.09,0.22 h 10.01 l 18.64,1.39 4.87,3.22 2.92,3.94 -0.49,3 -7.5,3.82 -10.12,-2.4 -23.62,-5.62 -8.1,-2.02 h -1.12 v 0.67 l 6.75,6.6 12.37,11.17 15.49,14.4 0.79,3.56 -1.99,2.81 -2.1,-0.3 -13.61,-10.24 -5.25,-4.61 -11.89,-10.01 h -0.79 v 1.05 l 2.74,4.01 14.47,21.75 0.75,6.67 -1.05,2.17 -3.75,1.31 -4.12,-0.75 -8.47,-11.89 -8.74,-13.39 -7.05,-12 -0.86,0.49 -4.16,44.81 -1.95,2.29 -4.5,1.72 -3.75,-2.85 -1.99,-4.61 1.99,-9.11 2.4,-11.89 1.95,-9.45 1.76,-11.74 1.05,-3.9 -0.07,-0.26 -0.86,0.11 -8.85,12.15 -13.46,18.19 -10.65,11.4 -2.55,1.01 -4.42,-2.29 0.41,-4.09 2.47,-3.64 14.74,-18.75 8.89,-11.62 5.74,-6.71 -0.04,-0.97 h -0.34 l -39.15,25.42 -6.97,0.9 -3,-2.81 0.37,-4.61 1.42,-1.5 11.77,-8.1 -0.04,0.04 z"
|
|
2
|
+
shape-rendering="optimizeQuality"/></svg>
|