@agenticmail/api 0.7.21 → 0.9.0
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 +8 -1
- package/dist/index.js +110 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -8,7 +8,14 @@ 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.
|
|
11
|
+
## ✨ What's new in 0.9.0
|
|
12
|
+
|
|
13
|
+
- **🧠 Agent-thread memory + thread-id resolver endpoints.** Agents persist their own per-thread judgment so the dispatcher can pre-load it into the next wake's prompt:
|
|
14
|
+
- `GET / POST / DELETE /agents/me/memory/threads/:t` — agent-key scoped; each agent only ever touches its own memory file.
|
|
15
|
+
- `GET /agents/me/thread-id?uid=42&folder=INBOX` — resolves the stable subject-only thread id for a message UID, looking up the canonical root via the dispatcher's ThreadCache when available.
|
|
16
|
+
- **🎯 `wake` default flipped to "To: only".** `POST /mail/send`, `POST /drafts/:id/send`, `POST /templates/:id/send`, and the pending-outbound persistence path all derive the implicit allowlist from local recipients on the `To:` field when `wake` is omitted. CC'd local agents receive the mail without waking. New helper `deriveDefaultWakeList(to)` exported from `routes/mail.ts`. Opt back into the old behaviour with `wake: 'all'`.
|
|
17
|
+
|
|
18
|
+
## ✨ Earlier — 0.7.16
|
|
12
19
|
|
|
13
20
|
- **📐 Typed task contracts** — `POST /tasks/assign` and the long-poll `POST /tasks/rpc` accept an optional `outputSchema` field (JSON Schema, draft-7 subset). The schema is persisted on the task row via migration `014_task_output_schema.sql` and is rendered into the worker's wake prompt. `POST /tasks/:id/result` validates against the schema before accepting; mismatches return **400** with a flat `schemaErrors: [{ path, message }]` list. Validator lives at `src/lib/schema-validator.ts` (hand-rolled, no `ajv` dep) and supports `type`, `required`, `properties`, `items`, `enum`, `additionalProperties: false`, `minLength`/`maxLength`, `minimum`/`maximum`. Tasks without a schema keep the v0.8.x behaviour — fully back-compat.
|
|
14
21
|
- **⭐ Star endpoint** — `POST /mail/messages/:uid/star` with `{ starred: boolean, folder?: string }`. Maps to IMAP's `\Flagged` flag — same on-disk bit Gmail's star uses.
|
package/dist/index.js
CHANGED
|
@@ -903,7 +903,8 @@ function createFeatureRoutes(db, _accountManager, config, gatewayManager) {
|
|
|
903
903
|
return;
|
|
904
904
|
}
|
|
905
905
|
const agent = req.agent;
|
|
906
|
-
const
|
|
906
|
+
const explicitWake = normalizeWakeList(req.body?.wake);
|
|
907
|
+
const wakeList = req.body?.wake === void 0 ? deriveDefaultWakeList(draft.to_addr) : explicitWake;
|
|
907
908
|
const customHeaders = wakeHeaders(wakeList);
|
|
908
909
|
let persistedAttachments;
|
|
909
910
|
if (draft.attachments) {
|
|
@@ -1189,7 +1190,8 @@ function createFeatureRoutes(db, _accountManager, config, gatewayManager) {
|
|
|
1189
1190
|
return;
|
|
1190
1191
|
}
|
|
1191
1192
|
const applyVars = (text, vars2) => text.replace(/\{\{(\w+)\}\}/g, (m, key) => vars2[key] ?? m);
|
|
1192
|
-
const
|
|
1193
|
+
const explicitWake = normalizeWakeList(wake);
|
|
1194
|
+
const wakeList = wake === void 0 ? deriveDefaultWakeList(to) : explicitWake;
|
|
1193
1195
|
const customHeaders = wakeHeaders(wakeList);
|
|
1194
1196
|
const vars = variables && typeof variables === "object" ? variables : {};
|
|
1195
1197
|
const renderedSubject = applyVars(template.subject || "(no subject)", vars);
|
|
@@ -1799,13 +1801,29 @@ function normalizeMessageId(id) {
|
|
|
1799
1801
|
if (!id) return "";
|
|
1800
1802
|
return id.trim().replace(/^<+|>+$/g, "").toLowerCase();
|
|
1801
1803
|
}
|
|
1804
|
+
var WAKE_ALL_SENTINEL = "__wake_all__";
|
|
1802
1805
|
function normalizeWakeList(value) {
|
|
1803
1806
|
if (value === void 0 || value === null) return void 0;
|
|
1807
|
+
if (value === "all" || value === WAKE_ALL_SENTINEL) return void 0;
|
|
1804
1808
|
const strip = (s) => s.trim().replace(/@localhost$/i, "").toLowerCase();
|
|
1805
1809
|
if (Array.isArray(value)) return value.map((v) => strip(String(v))).filter(Boolean);
|
|
1806
1810
|
if (typeof value === "string") return value.split(",").map(strip).filter(Boolean);
|
|
1807
1811
|
return void 0;
|
|
1808
1812
|
}
|
|
1813
|
+
function deriveDefaultWakeList(toField) {
|
|
1814
|
+
if (!toField) return void 0;
|
|
1815
|
+
const arr = Array.isArray(toField) ? toField : String(toField).split(",");
|
|
1816
|
+
const localNames = [];
|
|
1817
|
+
for (const raw of arr) {
|
|
1818
|
+
const addr = String(raw).trim().toLowerCase();
|
|
1819
|
+
if (!addr.endsWith("@localhost")) continue;
|
|
1820
|
+
const m = addr.match(/<([^>]+)>/);
|
|
1821
|
+
const bare = m ? m[1].trim() : addr;
|
|
1822
|
+
const name = bare.replace(/@localhost$/i, "");
|
|
1823
|
+
if (name) localNames.push(name);
|
|
1824
|
+
}
|
|
1825
|
+
return localNames.length > 0 ? localNames : void 0;
|
|
1826
|
+
}
|
|
1809
1827
|
function wakeHeaders(wakeList) {
|
|
1810
1828
|
if (wakeList === void 0) return {};
|
|
1811
1829
|
return { "X-AgenticMail-Wake": wakeList.join(", ") };
|
|
@@ -1933,7 +1951,8 @@ function createMailRoutes(accountManager2, config, db, gatewayManager) {
|
|
|
1933
1951
|
const pendingId = crypto.randomUUID();
|
|
1934
1952
|
const ownerName2 = agent.metadata?.ownerName;
|
|
1935
1953
|
const fromName2 = ownerName2 ? `${agent.name} from ${ownerName2}` : agent.name;
|
|
1936
|
-
const
|
|
1954
|
+
const explicitWakeForPersist = normalizeWakeList(wake);
|
|
1955
|
+
const wakeListForPersist = wake === void 0 ? deriveDefaultWakeList(to) : explicitWakeForPersist;
|
|
1937
1956
|
const mailOptions = {
|
|
1938
1957
|
to,
|
|
1939
1958
|
subject,
|
|
@@ -2016,7 +2035,8 @@ function createMailRoutes(accountManager2, config, db, gatewayManager) {
|
|
|
2016
2035
|
}
|
|
2017
2036
|
const ownerName = agent.metadata?.ownerName;
|
|
2018
2037
|
const fromName = ownerName ? `${agent.name} from ${ownerName}` : agent.name;
|
|
2019
|
-
const
|
|
2038
|
+
const explicitWake = normalizeWakeList(wake);
|
|
2039
|
+
const wakeList = wake === void 0 ? deriveDefaultWakeList(to) : explicitWake;
|
|
2020
2040
|
const customHeaders = wakeHeaders(wakeList);
|
|
2021
2041
|
const mailOpts = {
|
|
2022
2042
|
to,
|
|
@@ -5196,6 +5216,91 @@ function createDispatcherActivityRoutes() {
|
|
|
5196
5216
|
return router;
|
|
5197
5217
|
}
|
|
5198
5218
|
|
|
5219
|
+
// src/routes/agent-memory.ts
|
|
5220
|
+
import { Router as Router14 } from "express";
|
|
5221
|
+
import {
|
|
5222
|
+
AgentMemoryStore,
|
|
5223
|
+
threadIdFor,
|
|
5224
|
+
ThreadCache
|
|
5225
|
+
} from "@agenticmail/core";
|
|
5226
|
+
function createAgentMemoryRoutes(config) {
|
|
5227
|
+
const router = Router14();
|
|
5228
|
+
const memoryStore = new AgentMemoryStore();
|
|
5229
|
+
const threadCache = new ThreadCache();
|
|
5230
|
+
router.get("/agents/me/memory/threads/:t", requireAgent, async (req, res, next) => {
|
|
5231
|
+
try {
|
|
5232
|
+
const t = String(req.params.t);
|
|
5233
|
+
const memory = memoryStore.read(req.agent.id, t);
|
|
5234
|
+
if (!memory) {
|
|
5235
|
+
res.status(404).json({ error: "No memory for this thread" });
|
|
5236
|
+
return;
|
|
5237
|
+
}
|
|
5238
|
+
res.json(memory);
|
|
5239
|
+
} catch (err) {
|
|
5240
|
+
next(err);
|
|
5241
|
+
}
|
|
5242
|
+
});
|
|
5243
|
+
router.post("/agents/me/memory/threads/:t", requireAgent, async (req, res, next) => {
|
|
5244
|
+
try {
|
|
5245
|
+
const t = String(req.params.t);
|
|
5246
|
+
const { summary, commitments, openQuestions, lastAction, lastUid } = req.body ?? {};
|
|
5247
|
+
if (!summary && !commitments && !openQuestions && !lastAction && lastUid === void 0) {
|
|
5248
|
+
res.status(400).json({ error: "At least one field is required (summary, commitments, openQuestions, lastAction, or lastUid)" });
|
|
5249
|
+
return;
|
|
5250
|
+
}
|
|
5251
|
+
memoryStore.write(req.agent.id, t, {
|
|
5252
|
+
summary: typeof summary === "string" ? summary : void 0,
|
|
5253
|
+
commitments: Array.isArray(commitments) ? commitments.map(String) : void 0,
|
|
5254
|
+
openQuestions: Array.isArray(openQuestions) ? openQuestions.map(String) : void 0,
|
|
5255
|
+
lastAction: typeof lastAction === "string" ? lastAction : void 0,
|
|
5256
|
+
lastUid: typeof lastUid === "number" ? lastUid : void 0
|
|
5257
|
+
});
|
|
5258
|
+
res.json({ ok: true, threadId: t });
|
|
5259
|
+
} catch (err) {
|
|
5260
|
+
next(err);
|
|
5261
|
+
}
|
|
5262
|
+
});
|
|
5263
|
+
router.delete("/agents/me/memory/threads/:t", requireAgent, async (req, res, next) => {
|
|
5264
|
+
try {
|
|
5265
|
+
memoryStore.delete(req.agent.id, String(req.params.t));
|
|
5266
|
+
res.json({ ok: true });
|
|
5267
|
+
} catch (err) {
|
|
5268
|
+
next(err);
|
|
5269
|
+
}
|
|
5270
|
+
});
|
|
5271
|
+
router.get("/agents/me/thread-id", requireAgent, async (req, res, next) => {
|
|
5272
|
+
try {
|
|
5273
|
+
const uid = parseInt(String(req.query.uid ?? ""), 10);
|
|
5274
|
+
if (isNaN(uid) || uid < 1) {
|
|
5275
|
+
res.status(400).json({ error: "uid query param is required" });
|
|
5276
|
+
return;
|
|
5277
|
+
}
|
|
5278
|
+
const folder = req.query.folder || "INBOX";
|
|
5279
|
+
const password = getAgentPassword(req.agent);
|
|
5280
|
+
const receiver = await getReceiver(req.agent.stalwartPrincipal, password, config);
|
|
5281
|
+
const envs = await receiver.listEnvelopes(folder, { limit: 1, offset: 0 });
|
|
5282
|
+
const envelope = envs.find((e) => e.uid === uid) ?? (await receiver.listEnvelopes(folder, { limit: 200, offset: 0 })).find((e) => e.uid === uid);
|
|
5283
|
+
if (!envelope) {
|
|
5284
|
+
res.status(404).json({ error: `No message with UID ${uid} in folder ${folder}` });
|
|
5285
|
+
return;
|
|
5286
|
+
}
|
|
5287
|
+
const subject = envelope.subject ?? "";
|
|
5288
|
+
const senderAddr = envelope.from?.[0]?.address ?? "";
|
|
5289
|
+
const provisional = threadIdFor({ subject, rootFromAddr: senderAddr });
|
|
5290
|
+
const existing = threadCache.read(provisional);
|
|
5291
|
+
if (existing) {
|
|
5292
|
+
const canonical = threadIdFor({ subject, rootFromAddr: existing.rootFromAddr });
|
|
5293
|
+
res.json({ threadId: canonical, rootFromAddr: existing.rootFromAddr, subject: existing.subject });
|
|
5294
|
+
return;
|
|
5295
|
+
}
|
|
5296
|
+
res.json({ threadId: provisional, rootFromAddr: senderAddr, subject });
|
|
5297
|
+
} catch (err) {
|
|
5298
|
+
next(err);
|
|
5299
|
+
}
|
|
5300
|
+
});
|
|
5301
|
+
return router;
|
|
5302
|
+
}
|
|
5303
|
+
|
|
5199
5304
|
// src/app.ts
|
|
5200
5305
|
var integrationRouteFactoryPromise = (async () => {
|
|
5201
5306
|
try {
|
|
@@ -5296,6 +5401,7 @@ function createApp(configOverrides) {
|
|
|
5296
5401
|
app2.use("/api/agenticmail", createStorageRoutes(db, accountManager2, config));
|
|
5297
5402
|
app2.use("/api/agenticmail", createSystemEventRoutes());
|
|
5298
5403
|
app2.use("/api/agenticmail", createDispatcherActivityRoutes());
|
|
5404
|
+
app2.use("/api/agenticmail", createAgentMemoryRoutes(config));
|
|
5299
5405
|
app2.use("/api/agenticmail", (_req, res) => {
|
|
5300
5406
|
res.status(404).json({ error: "Not found" });
|
|
5301
5407
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agenticmail/api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "REST API server for AgenticMail — email and SMS endpoints for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"prepublishOnly": "npm run build"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@agenticmail/core": "^0.
|
|
31
|
+
"@agenticmail/core": "^0.9.0",
|
|
32
32
|
"cors": "^2.8.5",
|
|
33
33
|
"dotenv": "^16.4.7",
|
|
34
34
|
"express": "^4.21.0",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"uuid": "^11.1.0"
|
|
37
37
|
},
|
|
38
38
|
"optionalDependencies": {
|
|
39
|
-
"@agenticmail/claudecode": "^0.
|
|
39
|
+
"@agenticmail/claudecode": "^0.2.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/cors": "^2.8.17",
|