@rubytech/create-maxy 1.0.458 → 1.0.459
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/package.json
CHANGED
package/payload/maxy/server.js
CHANGED
|
@@ -2852,8 +2852,8 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
2852
2852
|
|
|
2853
2853
|
// server/index.ts
|
|
2854
2854
|
import { readFileSync as readFileSync19, existsSync as existsSync19, watchFile } from "fs";
|
|
2855
|
-
import { resolve as resolve16, join as
|
|
2856
|
-
import { homedir as
|
|
2855
|
+
import { resolve as resolve16, join as join9, basename as basename2 } from "path";
|
|
2856
|
+
import { homedir as homedir3 } from "os";
|
|
2857
2857
|
|
|
2858
2858
|
// app/api/health/route.ts
|
|
2859
2859
|
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
@@ -3135,7 +3135,8 @@ async function GET() {
|
|
|
3135
3135
|
// app/lib/claude-agent.ts
|
|
3136
3136
|
import Anthropic from "@anthropic-ai/sdk";
|
|
3137
3137
|
import { spawn as spawn2 } from "child_process";
|
|
3138
|
-
import { resolve as resolve4 } from "path";
|
|
3138
|
+
import { resolve as resolve4, join as join3 } from "path";
|
|
3139
|
+
import { homedir as homedir2 } from "os";
|
|
3139
3140
|
import { readFileSync as readFileSync5, readdirSync, existsSync as existsSync5, mkdirSync as mkdirSync2, createWriteStream, statSync as statSync2, unlinkSync } from "fs";
|
|
3140
3141
|
|
|
3141
3142
|
// app/lib/vnc.ts
|
|
@@ -4679,7 +4680,8 @@ function fetchMcpToolsList(pluginDir) {
|
|
|
4679
4680
|
env: {
|
|
4680
4681
|
...process.env,
|
|
4681
4682
|
PLATFORM_ROOT: PLATFORM_ROOT3,
|
|
4682
|
-
ACCOUNT_ID: "__toolslist__"
|
|
4683
|
+
ACCOUNT_ID: "__toolslist__",
|
|
4684
|
+
PLATFORM_PORT: process.env.PORT ?? "19200"
|
|
4683
4685
|
}
|
|
4684
4686
|
});
|
|
4685
4687
|
let buffer = "";
|
|
@@ -5067,11 +5069,6 @@ function getMcpServers(accountId, enabledPlugins) {
|
|
|
5067
5069
|
args: [resolve4(PLATFORM_ROOT3, "plugins/contacts/mcp/dist/index.js")],
|
|
5068
5070
|
env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT3 }
|
|
5069
5071
|
},
|
|
5070
|
-
"telegram": {
|
|
5071
|
-
command: "node",
|
|
5072
|
-
args: [resolve4(PLATFORM_ROOT3, "plugins/telegram/mcp/dist/index.js")],
|
|
5073
|
-
env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT3 }
|
|
5074
|
-
},
|
|
5075
5072
|
"whatsapp": {
|
|
5076
5073
|
command: "node",
|
|
5077
5074
|
args: [resolve4(PLATFORM_ROOT3, "plugins/whatsapp/mcp/dist/index.js")],
|
|
@@ -5082,11 +5079,6 @@ function getMcpServers(accountId, enabledPlugins) {
|
|
|
5082
5079
|
args: [resolve4(PLATFORM_ROOT3, "plugins/admin/mcp/dist/index.js")],
|
|
5083
5080
|
env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT3 }
|
|
5084
5081
|
},
|
|
5085
|
-
"cloudflare": {
|
|
5086
|
-
command: "node",
|
|
5087
|
-
args: [resolve4(PLATFORM_ROOT3, "plugins/cloudflare/mcp/dist/index.js")],
|
|
5088
|
-
env: { PLATFORM_ROOT: PLATFORM_ROOT3 }
|
|
5089
|
-
},
|
|
5090
5082
|
"scheduling": {
|
|
5091
5083
|
command: "node",
|
|
5092
5084
|
args: [resolve4(PLATFORM_ROOT3, "plugins/scheduling/mcp/dist/index.js")],
|
|
@@ -5113,6 +5105,33 @@ function getMcpServers(accountId, enabledPlugins) {
|
|
|
5113
5105
|
args: ["-y", "@playwright/mcp@latest", "--cdp-endpoint", "http://127.0.0.1:9222"]
|
|
5114
5106
|
}
|
|
5115
5107
|
};
|
|
5108
|
+
if (process.env.TELEGRAM_PUBLIC_BOT_TOKEN) {
|
|
5109
|
+
servers["telegram"] = {
|
|
5110
|
+
command: "node",
|
|
5111
|
+
args: [resolve4(PLATFORM_ROOT3, "plugins/telegram/mcp/dist/index.js")],
|
|
5112
|
+
env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT3 }
|
|
5113
|
+
};
|
|
5114
|
+
} else {
|
|
5115
|
+
console.error("[plugins] telegram MCP: skipped (no TELEGRAM_PUBLIC_BOT_TOKEN)");
|
|
5116
|
+
}
|
|
5117
|
+
let tunnelConfigured = false;
|
|
5118
|
+
try {
|
|
5119
|
+
const stateFile = join3(homedir2(), ".cloudflared", "tunnel.state");
|
|
5120
|
+
if (existsSync5(stateFile)) {
|
|
5121
|
+
const state = JSON.parse(readFileSync5(stateFile, "utf-8"));
|
|
5122
|
+
tunnelConfigured = !!state?.tunnelId;
|
|
5123
|
+
}
|
|
5124
|
+
} catch {
|
|
5125
|
+
}
|
|
5126
|
+
if (!tunnelConfigured) {
|
|
5127
|
+
servers["cloudflare"] = {
|
|
5128
|
+
command: "node",
|
|
5129
|
+
args: [resolve4(PLATFORM_ROOT3, "plugins/cloudflare/mcp/dist/index.js")],
|
|
5130
|
+
env: { PLATFORM_ROOT: PLATFORM_ROOT3 }
|
|
5131
|
+
};
|
|
5132
|
+
} else {
|
|
5133
|
+
console.error("[plugins] cloudflare MCP: skipped (tunnel already configured)");
|
|
5134
|
+
}
|
|
5116
5135
|
if (Array.isArray(enabledPlugins) && enabledPlugins.length > 0) {
|
|
5117
5136
|
const pluginsDir = resolve4(PLATFORM_ROOT3, "plugins");
|
|
5118
5137
|
let dirs;
|
|
@@ -9249,7 +9268,7 @@ async function POST8(req) {
|
|
|
9249
9268
|
}
|
|
9250
9269
|
|
|
9251
9270
|
// app/api/whatsapp/login/start/route.ts
|
|
9252
|
-
import { join as
|
|
9271
|
+
import { join as join4 } from "path";
|
|
9253
9272
|
|
|
9254
9273
|
// app/lib/whatsapp/login.ts
|
|
9255
9274
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
@@ -9835,7 +9854,7 @@ async function POST9(req) {
|
|
|
9835
9854
|
const body = await req.json().catch(() => ({}));
|
|
9836
9855
|
const accountId = validateAccountId(body.accountId);
|
|
9837
9856
|
const force = body.force ?? false;
|
|
9838
|
-
const authDir =
|
|
9857
|
+
const authDir = join4(MAXY_DIR, "credentials", "whatsapp", accountId);
|
|
9839
9858
|
const result = await startLogin({ accountId, authDir, force });
|
|
9840
9859
|
console.error(`[whatsapp:api] login/start result account=${accountId} hasQr=${!!result.qrRaw}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
|
|
9841
9860
|
return Response.json(result);
|
|
@@ -23996,7 +24015,7 @@ async function sendReadReceipt(sock, chatJid, messageIds, participant) {
|
|
|
23996
24015
|
// app/lib/whatsapp/inbound/media.ts
|
|
23997
24016
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
23998
24017
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
23999
|
-
import { join as
|
|
24018
|
+
import { join as join5 } from "path";
|
|
24000
24019
|
import {
|
|
24001
24020
|
downloadMediaMessage,
|
|
24002
24021
|
downloadContentFromMessage,
|
|
@@ -24082,7 +24101,7 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
24082
24101
|
await mkdir2(MEDIA_DIR, { recursive: true });
|
|
24083
24102
|
const ext = mimeToExt(mimetype ?? "application/octet-stream");
|
|
24084
24103
|
const filename = `${randomUUID6()}.${ext}`;
|
|
24085
|
-
const filePath =
|
|
24104
|
+
const filePath = join5(MEDIA_DIR, filename);
|
|
24086
24105
|
await writeFile2(filePath, buffer);
|
|
24087
24106
|
const sizeKB = (buffer.length / 1024).toFixed(0);
|
|
24088
24107
|
console.error(`${TAG5} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
|
|
@@ -24580,7 +24599,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
24580
24599
|
|
|
24581
24600
|
// app/lib/whatsapp/config-persist.ts
|
|
24582
24601
|
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, existsSync as existsSync10 } from "fs";
|
|
24583
|
-
import { resolve as resolve9, join as
|
|
24602
|
+
import { resolve as resolve9, join as join6 } from "path";
|
|
24584
24603
|
var TAG8 = "[whatsapp:config]";
|
|
24585
24604
|
function configPath(accountDir) {
|
|
24586
24605
|
return resolve9(accountDir, "account.json");
|
|
@@ -24731,7 +24750,7 @@ function setPublicAgent(accountDir, slug) {
|
|
|
24731
24750
|
if (!trimmed) {
|
|
24732
24751
|
return { ok: false, error: "Agent slug cannot be empty." };
|
|
24733
24752
|
}
|
|
24734
|
-
const agentConfigPath =
|
|
24753
|
+
const agentConfigPath = join6(accountDir, "agents", trimmed, "config.json");
|
|
24735
24754
|
if (!existsSync10(agentConfigPath)) {
|
|
24736
24755
|
return { ok: false, error: `Agent "${trimmed}" not found \u2014 no config.json at ${agentConfigPath}. Check the agent slug and try again.` };
|
|
24737
24756
|
}
|
|
@@ -25800,11 +25819,11 @@ async function GET7() {
|
|
|
25800
25819
|
|
|
25801
25820
|
// app/api/admin/version/route.ts
|
|
25802
25821
|
import { readFileSync as readFileSync17, existsSync as existsSync17 } from "fs";
|
|
25803
|
-
import { resolve as resolve14, join as
|
|
25822
|
+
import { resolve as resolve14, join as join7 } from "path";
|
|
25804
25823
|
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve14(process.cwd(), "..");
|
|
25805
25824
|
var brandHostname = "maxy";
|
|
25806
25825
|
var brandNpmPackage = "@rubytech/create-maxy";
|
|
25807
|
-
var brandJsonPath =
|
|
25826
|
+
var brandJsonPath = join7(PLATFORM_ROOT6, "config", "brand.json");
|
|
25808
25827
|
if (existsSync17(brandJsonPath)) {
|
|
25809
25828
|
try {
|
|
25810
25829
|
const brand = JSON.parse(readFileSync17(brandJsonPath, "utf-8"));
|
|
@@ -25876,11 +25895,11 @@ async function GET8() {
|
|
|
25876
25895
|
// app/api/admin/version/upgrade/route.ts
|
|
25877
25896
|
import { spawn as spawn4 } from "child_process";
|
|
25878
25897
|
import { existsSync as existsSync18, statSync as statSync4, writeFileSync as writeFileSync11, readFileSync as readFileSync18, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
25879
|
-
import { resolve as resolve15, join as
|
|
25898
|
+
import { resolve as resolve15, join as join8 } from "path";
|
|
25880
25899
|
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve15(process.cwd(), "..");
|
|
25881
25900
|
var upgradePkg = "@rubytech/create-maxy";
|
|
25882
25901
|
var upgradeHostname = "maxy";
|
|
25883
|
-
var brandPath =
|
|
25902
|
+
var brandPath = join8(PLATFORM_ROOT7, "config", "brand.json");
|
|
25884
25903
|
if (existsSync18(brandPath)) {
|
|
25885
25904
|
try {
|
|
25886
25905
|
const brand = JSON.parse(readFileSync18(brandPath, "utf-8"));
|
|
@@ -26047,7 +26066,7 @@ async function POST21() {
|
|
|
26047
26066
|
|
|
26048
26067
|
// server/index.ts
|
|
26049
26068
|
var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
26050
|
-
var BRAND_JSON_PATH = PLATFORM_ROOT8 ?
|
|
26069
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT8 ? join9(PLATFORM_ROOT8, "config", "brand.json") : "";
|
|
26051
26070
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", domain: "getmaxy.com" };
|
|
26052
26071
|
if (BRAND_JSON_PATH && !existsSync19(BRAND_JSON_PATH)) {
|
|
26053
26072
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
@@ -26066,7 +26085,7 @@ var brandLoginOpts = {
|
|
|
26066
26085
|
primaryHoverColor: BRAND.defaultColors?.primaryHover,
|
|
26067
26086
|
primarySubtle: BRAND.defaultColors?.primarySubtle
|
|
26068
26087
|
};
|
|
26069
|
-
var ALIAS_DOMAINS_PATH =
|
|
26088
|
+
var ALIAS_DOMAINS_PATH = join9(homedir3(), BRAND.configDir, "alias-domains.json");
|
|
26070
26089
|
function loadAliasDomains() {
|
|
26071
26090
|
try {
|
|
26072
26091
|
if (!existsSync19(ALIAS_DOMAINS_PATH)) return null;
|
|
@@ -26436,14 +26455,14 @@ function cachedHtml(file2) {
|
|
|
26436
26455
|
}
|
|
26437
26456
|
var brandedHtmlCache = /* @__PURE__ */ new Map();
|
|
26438
26457
|
function loadBrandingCache(agentSlug) {
|
|
26439
|
-
const configDir2 =
|
|
26458
|
+
const configDir2 = join9(homedir3(), BRAND.configDir);
|
|
26440
26459
|
try {
|
|
26441
|
-
const accountJsonPath =
|
|
26460
|
+
const accountJsonPath = join9(configDir2, "account.json");
|
|
26442
26461
|
if (!existsSync19(accountJsonPath)) return null;
|
|
26443
26462
|
const account = JSON.parse(readFileSync19(accountJsonPath, "utf-8"));
|
|
26444
26463
|
const accountId = account.accountId;
|
|
26445
26464
|
if (!accountId) return null;
|
|
26446
|
-
const cachePath =
|
|
26465
|
+
const cachePath = join9(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
|
|
26447
26466
|
if (!existsSync19(cachePath)) return null;
|
|
26448
26467
|
return JSON.parse(readFileSync19(cachePath, "utf-8"));
|
|
26449
26468
|
} catch {
|
|
@@ -26452,8 +26471,8 @@ function loadBrandingCache(agentSlug) {
|
|
|
26452
26471
|
}
|
|
26453
26472
|
function resolveDefaultSlug() {
|
|
26454
26473
|
try {
|
|
26455
|
-
const configDir2 =
|
|
26456
|
-
const accountJsonPath =
|
|
26474
|
+
const configDir2 = join9(homedir3(), BRAND.configDir);
|
|
26475
|
+
const accountJsonPath = join9(configDir2, "account.json");
|
|
26457
26476
|
if (!existsSync19(accountJsonPath)) return null;
|
|
26458
26477
|
const account = JSON.parse(readFileSync19(accountJsonPath, "utf-8"));
|
|
26459
26478
|
return account.defaultAgent || null;
|
|
@@ -26532,7 +26551,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
26532
26551
|
}
|
|
26533
26552
|
init({
|
|
26534
26553
|
configDir: configDirForWhatsApp,
|
|
26535
|
-
platformRoot: resolve16(process.env.MAXY_PLATFORM_ROOT ??
|
|
26554
|
+
platformRoot: resolve16(process.env.MAXY_PLATFORM_ROOT ?? join9(__dirname, "..")),
|
|
26536
26555
|
accountConfig: bootAccountConfig,
|
|
26537
26556
|
onMessage: async (msg) => {
|
|
26538
26557
|
try {
|
|
@@ -20,202 +20,28 @@ metadata: {"platform":{"optional":true,"recommended":true}}
|
|
|
20
20
|
|
|
21
21
|
Manages the agent's own dedicated email account — IMAP for reading, SMTP for sending. Admin agent only.
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## Capabilities
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
- **Setup:** `email-setup` — collect credentials via rendered `form`, connect IMAP/SMTP. Supports alias addresses (`agentAddress`).
|
|
26
|
+
- **Read inbox:** `email-read` — metadata only (UID, sender, subject, date). Supports pagination (`before_uid`), folders (`inbox`/`sent`), filtering by sender/date/subject.
|
|
27
|
+
- **Search inbox:** `email-search` — live IMAP search by sender, subject, body keyword, date range.
|
|
28
|
+
- **Send:** `email-send` — new outbound email.
|
|
29
|
+
- **Reply:** `email-reply` — threaded reply to an existing email by `messageId`. Supports `replyAll`.
|
|
30
|
+
- **Recall/history:** `email-graph-query` — search stored emails in Neo4j by natural language, sender, date, direction.
|
|
31
|
+
- **Status:** `email-status` — connection health and configuration state.
|
|
32
|
+
- **Auto-respond:** `email-auto-respond-config` — enable/disable automated public agent replies to inbound email.
|
|
33
|
+
- **OTP:** `email-otp-extract` — poll for verification codes during service authentication.
|
|
26
34
|
|
|
27
|
-
|
|
35
|
+
## Retrieval paths
|
|
28
36
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
| `password` | Password | text | yes | App password or account password |
|
|
33
|
-
| `imapHost` | IMAP host | text | yes | imap.example.com |
|
|
34
|
-
| `imapPort` | IMAP port | number | yes | 993 |
|
|
35
|
-
| `imapSecurity` | IMAP security | select | yes | Options: `tls` (default), `starttls` |
|
|
36
|
-
| `smtpHost` | SMTP host | text | yes | smtp.example.com |
|
|
37
|
-
| `smtpPort` | SMTP port | number | yes | 587 |
|
|
38
|
-
| `smtpSecurity` | SMTP security | select | yes | Options: `tls`, `starttls` (default) |
|
|
39
|
-
| `agentAddress` | Agent email address (if different from login) | text | no | agent@yourdomain.com |
|
|
37
|
+
- **Live inbox** (`email-read`, `email-search`) — real-time IMAP queries. Use for checking the inbox or reading recent messages.
|
|
38
|
+
- **Graph history** (`email-graph-query`) — stored Email nodes in Neo4j. Use for recall, semantic search, email history questions.
|
|
39
|
+
- **General knowledge** (`memory-search`) — cross-type knowledge graph. Use when the question spans all memory, not just emails.
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
## References
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
| Reference | Topics |
|
|
44
|
+
|-----------|--------|
|
|
45
|
+
| [email-reference.md](references/email-reference.md) | Setup form fields, pagination, filtering, replying, alias support, persistence, threading, screening, auto-respond, rate limiting, OTP integration |
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
## Reading and Searching
|
|
48
|
-
|
|
49
|
-
`email-read` and `email-search` return message metadata only: UID, sender, subject, and date. Body content is not included — present results as a message list without commenting on missing bodies.
|
|
50
|
-
|
|
51
|
-
### Pagination
|
|
52
|
-
|
|
53
|
-
Both tools return up to 50 messages per request (default 20). When more messages exist beyond the returned set, the response includes a `next_before_uid` value. Pass this value as the `before_uid` parameter on the next call to retrieve the next page of older messages. When `next_before_uid` is absent, all matching messages have been returned.
|
|
54
|
-
|
|
55
|
-
### Folders
|
|
56
|
-
|
|
57
|
-
Both tools accept a `folder` parameter: `"inbox"` (default) or `"sent"`. The Sent folder is discovered automatically via the IMAP server's special-use flags — no folder name guessing required.
|
|
58
|
-
|
|
59
|
-
### Filtering
|
|
60
|
-
|
|
61
|
-
Both tools support filtering by:
|
|
62
|
-
- `sender` — sender address or domain
|
|
63
|
-
- `to` — recipient address
|
|
64
|
-
- `since` / `before` — ISO date range
|
|
65
|
-
- `email-read` additionally supports `subjectPattern` (regex)
|
|
66
|
-
- `email-search` additionally supports `subject` (text) and `body` (keyword)
|
|
67
|
-
|
|
68
|
-
## Replying
|
|
69
|
-
|
|
70
|
-
`email-reply` sends a reply to an existing email, threaded correctly in the recipient's mail client.
|
|
71
|
-
|
|
72
|
-
The tool accepts the `messageId` of the email being replied to (visible in `email-read` output as the `Message-ID` field). It looks up the original email in the graph, sets `In-Reply-To` and `References` headers, derives the recipient from the original sender, and prepends `Re:` to the subject (stripping duplicate prefixes).
|
|
73
|
-
|
|
74
|
-
- **Reply to sender:** Default behaviour — replies to the original sender only.
|
|
75
|
-
- **Reply all:** Set `replyAll: true` to include all original recipients (TO + CC). The agent's own address is automatically excluded from the recipient list.
|
|
76
|
-
- **Threading:** The reply appears in the same conversation thread in Gmail, Apple Mail, Outlook, and other RFC 2822-compliant clients.
|
|
77
|
-
- **Prerequisite:** The original email must exist in the graph (stored by the background polling job). If the email hasn't been fetched yet, the tool returns a clear error.
|
|
78
|
-
|
|
79
|
-
Use `email-reply` when responding to a received email. Use `email-send` for new outbound messages.
|
|
80
|
-
|
|
81
|
-
## Alias Support
|
|
82
|
-
|
|
83
|
-
When `agentAddress` is set and differs from the auth email:
|
|
84
|
-
|
|
85
|
-
- **Sending:** emails are sent with the agent address as the From header. SMTP authentication still uses the auth email.
|
|
86
|
-
- **Reading/searching:** only emails whose TO header contains the agent address are returned. The agent ignores emails to other addresses on the same mailbox.
|
|
87
|
-
- **OTP extraction:** only OTPs addressed to the agent address are found.
|
|
88
|
-
- **Status:** reports both the agent address and the auth email.
|
|
89
|
-
|
|
90
|
-
When `agentAddress` is not set or matches the auth email, all tools behave as before — no filtering, From header uses the auth email.
|
|
91
|
-
|
|
92
|
-
## Email Persistence
|
|
93
|
-
|
|
94
|
-
Emails are automatically polled from IMAP and stored as `Email` nodes in the graph. This happens via a background cron job — no manual triggering needed.
|
|
95
|
-
|
|
96
|
-
- **Polling:** After `email-setup` completes, the platform polls IMAP at the configured interval (default: every 5 minutes). Only emails addressed TO the agent's `agentAddress` are stored.
|
|
97
|
-
- **Deduplication:** Each email is identified by its Message-ID header. Re-polling the same messages does not create duplicates, even if the agent's email address changes between poll cycles. A composite unique constraint on `(messageId, accountId)` provides database-level enforcement.
|
|
98
|
-
- **Semantic search:** Stored emails are vector-indexed (subject + body preview), enabling natural-language search over email history via `email-graph-query`.
|
|
99
|
-
- **Isolation:** Each agent sees only emails addressed to its own bound email address, even when sharing a catchall mailbox.
|
|
100
|
-
|
|
101
|
-
## Email Threading
|
|
102
|
-
|
|
103
|
-
Emails are linked into conversation threads via `REPLY_TO` graph edges. When an email has an `In-Reply-To` header, the platform looks up the parent email by `Message-ID` within the same account and creates an edge. Thread linking happens automatically during the polling cron job.
|
|
104
|
-
|
|
105
|
-
- **Out-of-order delivery:** If a reply arrives before its parent, the edge is created later when the parent is stored (orphan back-fill).
|
|
106
|
-
- **Thread context:** `email-read` and `email-search` include `Thread-Depth` (number of hops to the thread root) and `Thread-ID` (emailId of the root message) for any email that is part of a thread. Root emails (no parent) have no thread fields.
|
|
107
|
-
- **Scope:** Thread edges never cross account boundaries — parent lookups are always scoped to the same account.
|
|
108
|
-
|
|
109
|
-
## Auto-Respond Audit Trail
|
|
110
|
-
|
|
111
|
-
When auto-respond sends a reply, the outbound message is stored as an `Email` node with `direction: "outbound"` and a `REPLY_TO` edge to the original inbound email. This creates a queryable audit trail of every automated reply — what the agent said, when, and to whom.
|
|
112
|
-
|
|
113
|
-
Outbound email nodes have the same properties as inbound emails (subject, bodyPreview, fromAddress, toAddresses, timestamps) plus the `direction` property. They appear in thread traversals alongside inbound messages.
|
|
114
|
-
|
|
115
|
-
### Agent-Email Binding
|
|
116
|
-
|
|
117
|
-
Each email address is bound to exactly one agent. Each agent can have at most one email address. This is enforced during `email-setup`:
|
|
118
|
-
|
|
119
|
-
- The `agentSlug` parameter identifies which agent owns this email address (defaults to `"admin"`)
|
|
120
|
-
- If the address is already bound to a different agent, setup fails with a clear error naming the conflicting agent
|
|
121
|
-
- The binding is on `agentAddress` (the identity the agent presents), not the auth email
|
|
122
|
-
|
|
123
|
-
### Email Retrieval Paths
|
|
124
|
-
|
|
125
|
-
Three retrieval paths exist for email data — each scoped to a different purpose:
|
|
126
|
-
|
|
127
|
-
- **Live inbox** (`email-read`, `email-search`) — queries IMAP directly. Use for checking the inbox, reading recent messages, browsing folders, and real-time inbox state. Requires IMAP connectivity.
|
|
128
|
-
- **Graph list/search** (`email-graph-query`) — queries stored Email nodes in Neo4j. Use for email history, recall, and semantic search ("what emails are in memory?", "find emails about cabbages", "what did Sarah send about the contract?"). Works without IMAP. Supports date/sender/direction filters and natural-language semantic matching.
|
|
129
|
-
- **General knowledge** (`memory-search`) — searches the entire knowledge graph across all node types. Use when the user's question spans all memory, not just emails ("what do you know about contract renewals?").
|
|
130
|
-
|
|
131
|
-
When the user asks about stored emails, email history, or email recall — use `email-graph-query`. When they ask to check the inbox or read specific recent messages — use the IMAP tools. When the question is broader than emails — use `memory-search`.
|
|
132
|
-
|
|
133
|
-
## Inbound Email Screening
|
|
134
|
-
|
|
135
|
-
Every inbound email is classified by Claude Haiku before entering Neo4j. The classifier examines `fromAddress`, `fromName`, `subject`, and the first 1024 characters of `bodyPreview`.
|
|
136
|
-
|
|
137
|
-
### Verdicts
|
|
138
|
-
|
|
139
|
-
| Verdict | Stored? | Embedding? | Auto-respond? | Graph query? |
|
|
140
|
-
|---------|---------|------------|---------------|--------------|
|
|
141
|
-
| `clean` | Yes | Yes | Yes | Yes |
|
|
142
|
-
| `suspicious` | Yes (with `screeningReason`) | Yes | No | Yes |
|
|
143
|
-
| `discarded` | Yes (with `screeningReason`) | No | No | No (list mode) |
|
|
144
|
-
|
|
145
|
-
- **clean** — legitimate personal or business correspondence
|
|
146
|
-
- **suspicious** — phishing indicators (urgency + credential requests, mismatched sender), prompt injection patterns, or classification failure (safe default)
|
|
147
|
-
- **discarded** — obvious spam, marketing bulk mail, auto-generated notifications with no actionable content
|
|
148
|
-
|
|
149
|
-
### Prompt injection flag
|
|
150
|
-
|
|
151
|
-
When `promptInjectionFlag` is true (body contains instruction-like patterns targeting an AI agent), auto-respond skips the email entirely regardless of verdict. No reply is generated. The email is visible in Neo4j for admin review.
|
|
152
|
-
|
|
153
|
-
### Failure handling
|
|
154
|
-
|
|
155
|
-
If the Haiku API is unavailable (network, rate limit, auth failure), the email defaults to `suspicious`. The entire fetch batch is never blocked — each email is classified independently. API key absence causes all emails in the batch to default to `suspicious`.
|
|
156
|
-
|
|
157
|
-
### Logs
|
|
158
|
-
|
|
159
|
-
Classification verdicts are logged to `{accountDir}/logs/email-fetch.log` with per-email detail (fromAddress, verdict, reason, promptInjectionRisk) and batch summaries.
|
|
160
|
-
|
|
161
|
-
## Security
|
|
162
|
-
|
|
163
|
-
- Credentials stored as a file with restricted permissions — never in conversation
|
|
164
|
-
- IMAP and SMTP connections require TLS or STARTTLS — plaintext is rejected
|
|
165
|
-
- Only the agent's own dedicated account is accessed — never the user's personal email
|
|
166
|
-
- Email nodes have `scope: "admin"` — never visible to public agents
|
|
167
|
-
- Inbound emails are classified by Haiku before storage — prompt injection and phishing are flagged
|
|
168
|
-
|
|
169
|
-
## Auto-Respond
|
|
170
|
-
|
|
171
|
-
When enabled, a public agent automatically replies to incoming emails. A cron job polls the inbox for new messages and generates replies via the assigned agent.
|
|
172
|
-
|
|
173
|
-
### Setup
|
|
174
|
-
|
|
175
|
-
To enable auto-respond, call `email-auto-respond-config` with `enabled: true`. The tool requires an `agentSlug` — render a `single-select` component with available public agents so the admin can choose which agent handles responses. The tool returns the agent list when called without a slug.
|
|
176
|
-
|
|
177
|
-
When called without an `agentSlug`, the tool returns available agents. Present these as a `single-select` component for the admin to choose from, then call the tool again with the selected slug.
|
|
178
|
-
|
|
179
|
-
### Configuration
|
|
180
|
-
|
|
181
|
-
- `enabled` — `true` to enable, `false` to disable
|
|
182
|
-
- `agentSlug` — slug of the public agent that responds (required when enabling)
|
|
183
|
-
- `pollIntervalMinutes` — how often to check for new emails (default: 5, range: 1–60)
|
|
184
|
-
- `hourlyLimit` — maximum auto-replies per clock hour UTC (default: 20, range: 1–1000)
|
|
185
|
-
- `dailyLimit` — maximum auto-replies per calendar day UTC (default: 100, range: 1–10000)
|
|
186
|
-
|
|
187
|
-
### Behaviour
|
|
188
|
-
|
|
189
|
-
- The cron job runs every minute. Each account's poll is skipped if the configured interval hasn't elapsed since the last poll.
|
|
190
|
-
- Only emails addressed TO the agent's email address are processed (alias filtering applies).
|
|
191
|
-
- Auto-replies, mailing list messages, and emails from the agent's own address are automatically skipped (RFC 3834 loop prevention).
|
|
192
|
-
- Outgoing replies include `In-Reply-To` and `References` headers for correct threading, and `Auto-Submitted: auto-replied` to prevent loops with other auto-responders.
|
|
193
|
-
- The subject line carries a single `Re:` prefix (no doubling).
|
|
194
|
-
|
|
195
|
-
### Rate Limiting
|
|
196
|
-
|
|
197
|
-
Per-account send caps prevent runaway auto-replies from spam waves, mailing list explosions, or deliberate attacks. Two independent limits:
|
|
198
|
-
|
|
199
|
-
- **Hourly limit** (default 20) — resets at each UTC clock-hour boundary
|
|
200
|
-
- **Daily limit** (default 100) — resets at midnight UTC
|
|
201
|
-
|
|
202
|
-
When either limit is reached, remaining emails in the poll cycle are skipped (not replied to). Auto-respond is **not** disabled — counters reset naturally at the next boundary. A medium-priority admin Task is created to notify the admin.
|
|
203
|
-
|
|
204
|
-
Within a single poll cycle, only one auto-reply is sent per unique sender address. This prevents reply storms when an automated sender delivers multiple messages. Sender deduplication is case-insensitive.
|
|
205
|
-
|
|
206
|
-
### Circuit Breaker
|
|
207
|
-
|
|
208
|
-
After 3 consecutive failures (IMAP errors, API failures, SMTP rejections), auto-respond is automatically disabled and a high-priority Task is created for the admin. The admin sees this task at the start of their next session and can re-enable auto-respond after resolving the issue.
|
|
209
|
-
|
|
210
|
-
### Disabling
|
|
211
|
-
|
|
212
|
-
Call `email-auto-respond-config` with `enabled: false`. This clears the agent assignment and resets the failure counter.
|
|
213
|
-
|
|
214
|
-
## OTP Integration
|
|
215
|
-
|
|
216
|
-
Other skills call `email-otp-extract` during service authentication (Anthropic OAuth, Cloudflare setup) with:
|
|
217
|
-
- `sender` — expected sender domain
|
|
218
|
-
- `subject_pattern` — optional regex for subject line
|
|
219
|
-
- `timeout` — how long to poll (default 60s)
|
|
220
|
-
|
|
221
|
-
The tool polls at short intervals, extracts the verification code, and returns it. On timeout, it returns a clear failure so the calling skill can ask the user to check manually.
|
|
47
|
+
Load the reference via `plugin-read` when detailed configuration or behaviour information is needed.
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Email Reference
|
|
2
|
+
|
|
3
|
+
Full reference for the email plugin. Load with `plugin-read` when detailed configuration or behaviour reference is needed.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
Collect email credentials via a form. Render this `form` component with `render-component`:
|
|
8
|
+
|
|
9
|
+
**Fields:**
|
|
10
|
+
|
|
11
|
+
| name | label | type | required | placeholder |
|
|
12
|
+
|------|-------|------|----------|-------------|
|
|
13
|
+
| `email` | Email address | text | yes | user@example.com |
|
|
14
|
+
| `password` | Password | text | yes | App password or account password |
|
|
15
|
+
| `imapHost` | IMAP host | text | yes | imap.example.com |
|
|
16
|
+
| `imapPort` | IMAP port | number | yes | 993 |
|
|
17
|
+
| `imapSecurity` | IMAP security | select | yes | Options: `tls` (default), `starttls` |
|
|
18
|
+
| `smtpHost` | SMTP host | text | yes | smtp.example.com |
|
|
19
|
+
| `smtpPort` | SMTP port | number | yes | 587 |
|
|
20
|
+
| `smtpSecurity` | SMTP security | select | yes | Options: `tls`, `starttls` (default) |
|
|
21
|
+
| `agentAddress` | Agent email address (if different from login) | text | no | agent@yourdomain.com |
|
|
22
|
+
|
|
23
|
+
The `agentAddress` field is for catchall/alias setups where the authentication email differs from the address the agent operates as. Leave empty when they're the same.
|
|
24
|
+
|
|
25
|
+
After form submission, pass all fields to `email-setup`. On success, confirm "credentials stored" and the inbox count. On failure, relay the diagnostic — the tool returns specific guidance (wrong password, connection refused, TLS mismatch).
|
|
26
|
+
|
|
27
|
+
The agent never displays stored passwords. Never name or recommend specific email providers — the form collects the standard fields every provider gives its users.
|
|
28
|
+
|
|
29
|
+
## Reading and Searching
|
|
30
|
+
|
|
31
|
+
`email-read` and `email-search` return message metadata only: UID, sender, subject, and date. Body content is not included — present results as a message list without commenting on missing bodies.
|
|
32
|
+
|
|
33
|
+
### Pagination
|
|
34
|
+
|
|
35
|
+
Both tools return up to 50 messages per request (default 20). When more messages exist beyond the returned set, the response includes a `next_before_uid` value. Pass this value as the `before_uid` parameter on the next call to retrieve the next page of older messages. When `next_before_uid` is absent, all matching messages have been returned.
|
|
36
|
+
|
|
37
|
+
### Folders
|
|
38
|
+
|
|
39
|
+
Both tools accept a `folder` parameter: `"inbox"` (default) or `"sent"`. The Sent folder is discovered automatically via the IMAP server's special-use flags — no folder name guessing required.
|
|
40
|
+
|
|
41
|
+
### Filtering
|
|
42
|
+
|
|
43
|
+
Both tools support filtering by:
|
|
44
|
+
- `sender` — sender address or domain
|
|
45
|
+
- `to` — recipient address
|
|
46
|
+
- `since` / `before` — ISO date range
|
|
47
|
+
- `email-read` additionally supports `subjectPattern` (regex)
|
|
48
|
+
- `email-search` additionally supports `subject` (text) and `body` (keyword)
|
|
49
|
+
|
|
50
|
+
## Replying
|
|
51
|
+
|
|
52
|
+
`email-reply` sends a reply to an existing email, threaded correctly in the recipient's mail client.
|
|
53
|
+
|
|
54
|
+
The tool accepts the `messageId` of the email being replied to (visible in `email-read` output as the `Message-ID` field). It looks up the original email in the graph, sets `In-Reply-To` and `References` headers, derives the recipient from the original sender, and prepends `Re:` to the subject (stripping duplicate prefixes).
|
|
55
|
+
|
|
56
|
+
- **Reply to sender:** Default behaviour — replies to the original sender only.
|
|
57
|
+
- **Reply all:** Set `replyAll: true` to include all original recipients (TO + CC). The agent's own address is automatically excluded from the recipient list.
|
|
58
|
+
- **Threading:** The reply appears in the same conversation thread in Gmail, Apple Mail, Outlook, and other RFC 2822-compliant clients.
|
|
59
|
+
- **Prerequisite:** The original email must exist in the graph (stored by the background polling job). If the email hasn't been fetched yet, the tool returns a clear error.
|
|
60
|
+
|
|
61
|
+
Use `email-reply` when responding to a received email. Use `email-send` for new outbound messages.
|
|
62
|
+
|
|
63
|
+
## Alias Support
|
|
64
|
+
|
|
65
|
+
When `agentAddress` is set and differs from the auth email:
|
|
66
|
+
|
|
67
|
+
- **Sending:** emails are sent with the agent address as the From header. SMTP authentication still uses the auth email.
|
|
68
|
+
- **Reading/searching:** only emails whose TO header contains the agent address are returned. The agent ignores emails to other addresses on the same mailbox.
|
|
69
|
+
- **OTP extraction:** only OTPs addressed to the agent address are found.
|
|
70
|
+
- **Status:** reports both the agent address and the auth email.
|
|
71
|
+
|
|
72
|
+
When `agentAddress` is not set or matches the auth email, all tools behave as before — no filtering, From header uses the auth email.
|
|
73
|
+
|
|
74
|
+
## Email Persistence
|
|
75
|
+
|
|
76
|
+
Emails are automatically polled from IMAP and stored as `Email` nodes in the graph. This happens via a background cron job — no manual triggering needed.
|
|
77
|
+
|
|
78
|
+
- **Polling:** After `email-setup` completes, the platform polls IMAP at the configured interval (default: every 5 minutes). Only emails addressed TO the agent's `agentAddress` are stored.
|
|
79
|
+
- **Deduplication:** Each email is identified by its Message-ID header. Re-polling the same messages does not create duplicates, even if the agent's email address changes between poll cycles. A composite unique constraint on `(messageId, accountId)` provides database-level enforcement.
|
|
80
|
+
- **Semantic search:** Stored emails are vector-indexed (subject + body preview), enabling natural-language search over email history via `email-graph-query`.
|
|
81
|
+
- **Isolation:** Each agent sees only emails addressed to its own bound email address, even when sharing a catchall mailbox.
|
|
82
|
+
|
|
83
|
+
## Email Threading
|
|
84
|
+
|
|
85
|
+
Emails are linked into conversation threads via `REPLY_TO` graph edges. When an email has an `In-Reply-To` header, the platform looks up the parent email by `Message-ID` within the same account and creates an edge. Thread linking happens automatically during the polling cron job.
|
|
86
|
+
|
|
87
|
+
- **Out-of-order delivery:** If a reply arrives before its parent, the edge is created later when the parent is stored (orphan back-fill).
|
|
88
|
+
- **Thread context:** `email-read` and `email-search` include `Thread-Depth` (number of hops to the thread root) and `Thread-ID` (emailId of the root message) for any email that is part of a thread. Root emails (no parent) have no thread fields.
|
|
89
|
+
- **Scope:** Thread edges never cross account boundaries — parent lookups are always scoped to the same account.
|
|
90
|
+
|
|
91
|
+
## Auto-Respond Audit Trail
|
|
92
|
+
|
|
93
|
+
When auto-respond sends a reply, the outbound message is stored as an `Email` node with `direction: "outbound"` and a `REPLY_TO` edge to the original inbound email. This creates a queryable audit trail of every automated reply — what the agent said, when, and to whom.
|
|
94
|
+
|
|
95
|
+
Outbound email nodes have the same properties as inbound emails (subject, bodyPreview, fromAddress, toAddresses, timestamps) plus the `direction` property. They appear in thread traversals alongside inbound messages.
|
|
96
|
+
|
|
97
|
+
### Agent-Email Binding
|
|
98
|
+
|
|
99
|
+
Each email address is bound to exactly one agent. Each agent can have at most one email address. This is enforced during `email-setup`:
|
|
100
|
+
|
|
101
|
+
- The `agentSlug` parameter identifies which agent owns this email address (defaults to `"admin"`)
|
|
102
|
+
- If the address is already bound to a different agent, setup fails with a clear error naming the conflicting agent
|
|
103
|
+
- The binding is on `agentAddress` (the identity the agent presents), not the auth email
|
|
104
|
+
|
|
105
|
+
### Email Retrieval Paths
|
|
106
|
+
|
|
107
|
+
Three retrieval paths exist for email data — each scoped to a different purpose:
|
|
108
|
+
|
|
109
|
+
- **Live inbox** (`email-read`, `email-search`) — queries IMAP directly. Use for checking the inbox, reading recent messages, browsing folders, and real-time inbox state. Requires IMAP connectivity.
|
|
110
|
+
- **Graph list/search** (`email-graph-query`) — queries stored Email nodes in Neo4j. Use for email history, recall, and semantic search ("what emails are in memory?", "find emails about cabbages", "what did Sarah send about the contract?"). Works without IMAP. Supports date/sender/direction filters and natural-language semantic matching.
|
|
111
|
+
- **General knowledge** (`memory-search`) — searches the entire knowledge graph across all node types. Use when the user's question spans all memory, not just emails ("what do you know about contract renewals?").
|
|
112
|
+
|
|
113
|
+
When the user asks about stored emails, email history, or email recall — use `email-graph-query`. When they ask to check the inbox or read specific recent messages — use the IMAP tools. When the question is broader than emails — use `memory-search`.
|
|
114
|
+
|
|
115
|
+
## Inbound Email Screening
|
|
116
|
+
|
|
117
|
+
Every inbound email is classified by Claude Haiku before entering Neo4j. The classifier examines `fromAddress`, `fromName`, `subject`, and the first 1024 characters of `bodyPreview`.
|
|
118
|
+
|
|
119
|
+
### Verdicts
|
|
120
|
+
|
|
121
|
+
| Verdict | Stored? | Embedding? | Auto-respond? | Graph query? |
|
|
122
|
+
|---------|---------|------------|---------------|--------------|
|
|
123
|
+
| `clean` | Yes | Yes | Yes | Yes |
|
|
124
|
+
| `suspicious` | Yes (with `screeningReason`) | Yes | No | Yes |
|
|
125
|
+
| `discarded` | Yes (with `screeningReason`) | No | No | No (list mode) |
|
|
126
|
+
|
|
127
|
+
- **clean** — legitimate personal or business correspondence
|
|
128
|
+
- **suspicious** — phishing indicators (urgency + credential requests, mismatched sender), prompt injection patterns, or classification failure (safe default)
|
|
129
|
+
- **discarded** — obvious spam, marketing bulk mail, auto-generated notifications with no actionable content
|
|
130
|
+
|
|
131
|
+
### Prompt injection flag
|
|
132
|
+
|
|
133
|
+
When `promptInjectionFlag` is true (body contains instruction-like patterns targeting an AI agent), auto-respond skips the email entirely regardless of verdict. No reply is generated. The email is visible in Neo4j for admin review.
|
|
134
|
+
|
|
135
|
+
### Failure handling
|
|
136
|
+
|
|
137
|
+
If the Haiku API is unavailable (network, rate limit, auth failure), the email defaults to `suspicious`. The entire fetch batch is never blocked — each email is classified independently. API key absence causes all emails in the batch to default to `suspicious`.
|
|
138
|
+
|
|
139
|
+
### Logs
|
|
140
|
+
|
|
141
|
+
Classification verdicts are logged to `{accountDir}/logs/email-fetch.log` with per-email detail (fromAddress, verdict, reason, promptInjectionRisk) and batch summaries.
|
|
142
|
+
|
|
143
|
+
## Security
|
|
144
|
+
|
|
145
|
+
- Credentials stored as a file with restricted permissions — never in conversation
|
|
146
|
+
- IMAP and SMTP connections require TLS or STARTTLS — plaintext is rejected
|
|
147
|
+
- Only the agent's own dedicated account is accessed — never the user's personal email
|
|
148
|
+
- Email nodes have `scope: "admin"` — never visible to public agents
|
|
149
|
+
- Inbound emails are classified by Haiku before storage — prompt injection and phishing are flagged
|
|
150
|
+
|
|
151
|
+
## Auto-Respond
|
|
152
|
+
|
|
153
|
+
When enabled, a public agent automatically replies to incoming emails. A cron job polls the inbox for new messages and generates replies via the assigned agent.
|
|
154
|
+
|
|
155
|
+
### Setup
|
|
156
|
+
|
|
157
|
+
To enable auto-respond, call `email-auto-respond-config` with `enabled: true`. The tool requires an `agentSlug` — render a `single-select` component with available public agents so the admin can choose which agent handles responses. The tool returns the agent list when called without a slug.
|
|
158
|
+
|
|
159
|
+
When called without an `agentSlug`, the tool returns available agents. Present these as a `single-select` component for the admin to choose from, then call the tool again with the selected slug.
|
|
160
|
+
|
|
161
|
+
### Configuration
|
|
162
|
+
|
|
163
|
+
- `enabled` — `true` to enable, `false` to disable
|
|
164
|
+
- `agentSlug` — slug of the public agent that responds (required when enabling)
|
|
165
|
+
- `pollIntervalMinutes` — how often to check for new emails (default: 5, range: 1–60)
|
|
166
|
+
- `hourlyLimit` — maximum auto-replies per clock hour UTC (default: 20, range: 1–1000)
|
|
167
|
+
- `dailyLimit` — maximum auto-replies per calendar day UTC (default: 100, range: 1–10000)
|
|
168
|
+
|
|
169
|
+
### Behaviour
|
|
170
|
+
|
|
171
|
+
- The cron job runs every minute. Each account's poll is skipped if the configured interval hasn't elapsed since the last poll.
|
|
172
|
+
- Only emails addressed TO the agent's email address are processed (alias filtering applies).
|
|
173
|
+
- Auto-replies, mailing list messages, and emails from the agent's own address are automatically skipped (RFC 3834 loop prevention).
|
|
174
|
+
- Outgoing replies include `In-Reply-To` and `References` headers for correct threading, and `Auto-Submitted: auto-replied` to prevent loops with other auto-responders.
|
|
175
|
+
- The subject line carries a single `Re:` prefix (no doubling).
|
|
176
|
+
|
|
177
|
+
### Rate Limiting
|
|
178
|
+
|
|
179
|
+
Per-account send caps prevent runaway auto-replies from spam waves, mailing list explosions, or deliberate attacks. Two independent limits:
|
|
180
|
+
|
|
181
|
+
- **Hourly limit** (default 20) — resets at each UTC clock-hour boundary
|
|
182
|
+
- **Daily limit** (default 100) — resets at midnight UTC
|
|
183
|
+
|
|
184
|
+
When either limit is reached, remaining emails in the poll cycle are skipped (not replied to). Auto-respond is **not** disabled — counters reset naturally at the next boundary. A medium-priority admin Task is created to notify the admin.
|
|
185
|
+
|
|
186
|
+
Within a single poll cycle, only one auto-reply is sent per unique sender address. This prevents reply storms when an automated sender delivers multiple messages. Sender deduplication is case-insensitive.
|
|
187
|
+
|
|
188
|
+
### Circuit Breaker
|
|
189
|
+
|
|
190
|
+
After 3 consecutive failures (IMAP errors, API failures, SMTP rejections), auto-respond is automatically disabled and a high-priority Task is created for the admin. The admin sees this task at the start of their next session and can re-enable auto-respond after resolving the issue.
|
|
191
|
+
|
|
192
|
+
### Disabling
|
|
193
|
+
|
|
194
|
+
Call `email-auto-respond-config` with `enabled: false`. This clears the agent assignment and resets the failure counter.
|
|
195
|
+
|
|
196
|
+
## OTP Integration
|
|
197
|
+
|
|
198
|
+
Other skills call `email-otp-extract` during service authentication (Anthropic OAuth, Cloudflare setup) with:
|
|
199
|
+
- `sender` — expected sender domain
|
|
200
|
+
- `subject_pattern` — optional regex for subject line
|
|
201
|
+
- `timeout` — how long to poll (default 60s)
|
|
202
|
+
|
|
203
|
+
The tool polls at short intervals, extracts the verification code, and returns it. On timeout, it returns a clear failure so the calling skill can ask the user to check manually.
|
|
@@ -75,6 +75,10 @@ Any request to create, edit, clone, list, preview, or delete a public agent must
|
|
|
75
75
|
|
|
76
76
|
Plugin installation, removal, and account settings changes are managed via conversation. Load the relevant skill via `plugin-read` (find its path in the manifest under `admin`).
|
|
77
77
|
|
|
78
|
+
## WhatsApp Configuration
|
|
79
|
+
|
|
80
|
+
When the user asks about WhatsApp settings, config, policies, or operational limits, load the manage-whatsapp-config skill via `plugin-read` (find its path in the manifest under `whatsapp`). The skill guides presenting config as an interactive form, not a text dump.
|
|
81
|
+
|
|
78
82
|
## Session Reset
|
|
79
83
|
|
|
80
84
|
When the user asks to start a new session, clear the conversation, or start fresh, call `session-reset`. This compacts the current conversation to memory and clears the chat. Do not ask for confirmation. After calling the tool, do not say anything further — the UI clears and returns to idle.
|