@rubytech/create-maxy 1.0.457 → 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 +1 -1
- package/payload/maxy/server.js +130 -38
- package/payload/platform/plugins/email/PLUGIN.md +19 -193
- package/payload/platform/plugins/email/references/email-reference.md +203 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js +12 -5
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/whatsapp/skills/connect-whatsapp/SKILL.md +4 -4
- package/payload/platform/templates/agents/admin/IDENTITY.md +4 -0
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);
|
|
@@ -23636,7 +23655,8 @@ var WhatsAppAccountSchema = external_exports.object({
|
|
|
23636
23655
|
direct: external_exports.boolean().optional().default(true),
|
|
23637
23656
|
group: external_exports.enum(["always", "mentions", "never"]).optional().default("mentions")
|
|
23638
23657
|
}).strict().optional().describe("React to incoming messages with an emoji to acknowledge receipt before the agent responds."),
|
|
23639
|
-
debounceMs: external_exports.number().int().nonnegative().optional().default(0).describe("Wait this many milliseconds after the last message before processing, to batch rapid multi-message input into a single agent turn. 0 means process each message immediately.")
|
|
23658
|
+
debounceMs: external_exports.number().int().nonnegative().optional().default(0).describe("Wait this many milliseconds after the last message before processing, to batch rapid multi-message input into a single agent turn. 0 means process each message immediately."),
|
|
23659
|
+
publicAgent: external_exports.string().optional().describe("Slug of the public agent that handles WhatsApp inbound from non-admin phones for this account. Overrides the top-level publicAgent when set.")
|
|
23640
23660
|
}).strict().superRefine((value, ctx) => {
|
|
23641
23661
|
if (value.dmPolicy !== "open") return;
|
|
23642
23662
|
const allow = (value.allowFrom ?? []).map((v) => String(v).trim()).filter(Boolean);
|
|
@@ -23656,7 +23676,8 @@ var WhatsAppConfigSchema = external_exports.object({
|
|
|
23656
23676
|
sendReadReceipts: external_exports.boolean().optional().default(true).describe("Whether to send read receipts (blue ticks) when messages are received. Disabling may feel less responsive but preserves privacy."),
|
|
23657
23677
|
mediaMaxMb: external_exports.number().int().positive().optional().default(50).describe("Maximum file size in MB for media the agent will download and process."),
|
|
23658
23678
|
textChunkLimit: external_exports.number().int().positive().optional().default(4e3).describe("Maximum characters per outbound message. Longer replies are split into multiple messages."),
|
|
23659
|
-
debounceMs: external_exports.number().int().nonnegative().optional().default(0).describe("Wait this many milliseconds after the last message before processing, to batch rapid multi-message input into a single agent turn. 0 means process each message immediately.")
|
|
23679
|
+
debounceMs: external_exports.number().int().nonnegative().optional().default(0).describe("Wait this many milliseconds after the last message before processing, to batch rapid multi-message input into a single agent turn. 0 means process each message immediately."),
|
|
23680
|
+
publicAgent: external_exports.string().optional().describe("Slug of the public agent that handles WhatsApp inbound from non-admin phones. Per-account overrides take precedence.")
|
|
23660
23681
|
}).strict().superRefine((value, ctx) => {
|
|
23661
23682
|
if (value.dmPolicy !== "open") return;
|
|
23662
23683
|
const allow = (value.allowFrom ?? []).map((v) => String(v).trim()).filter(Boolean);
|
|
@@ -23994,7 +24015,7 @@ async function sendReadReceipt(sock, chatJid, messageIds, participant) {
|
|
|
23994
24015
|
// app/lib/whatsapp/inbound/media.ts
|
|
23995
24016
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
23996
24017
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
23997
|
-
import { join as
|
|
24018
|
+
import { join as join5 } from "path";
|
|
23998
24019
|
import {
|
|
23999
24020
|
downloadMediaMessage,
|
|
24000
24021
|
downloadContentFromMessage,
|
|
@@ -24080,7 +24101,7 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
24080
24101
|
await mkdir2(MEDIA_DIR, { recursive: true });
|
|
24081
24102
|
const ext = mimeToExt(mimetype ?? "application/octet-stream");
|
|
24082
24103
|
const filename = `${randomUUID6()}.${ext}`;
|
|
24083
|
-
const filePath =
|
|
24104
|
+
const filePath = join5(MEDIA_DIR, filename);
|
|
24084
24105
|
await writeFile2(filePath, buffer);
|
|
24085
24106
|
const sizeKB = (buffer.length / 1024).toFixed(0);
|
|
24086
24107
|
console.error(`${TAG5} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
|
|
@@ -24578,7 +24599,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
24578
24599
|
|
|
24579
24600
|
// app/lib/whatsapp/config-persist.ts
|
|
24580
24601
|
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, existsSync as existsSync10 } from "fs";
|
|
24581
|
-
import { resolve as resolve9 } from "path";
|
|
24602
|
+
import { resolve as resolve9, join as join6 } from "path";
|
|
24582
24603
|
var TAG8 = "[whatsapp:config]";
|
|
24583
24604
|
function configPath(accountDir) {
|
|
24584
24605
|
return resolve9(accountDir, "account.json");
|
|
@@ -24724,6 +24745,52 @@ function readAdminPhones(accountDir) {
|
|
|
24724
24745
|
return [];
|
|
24725
24746
|
}
|
|
24726
24747
|
}
|
|
24748
|
+
function setPublicAgent(accountDir, slug) {
|
|
24749
|
+
const trimmed = slug.trim();
|
|
24750
|
+
if (!trimmed) {
|
|
24751
|
+
return { ok: false, error: "Agent slug cannot be empty." };
|
|
24752
|
+
}
|
|
24753
|
+
const agentConfigPath = join6(accountDir, "agents", trimmed, "config.json");
|
|
24754
|
+
if (!existsSync10(agentConfigPath)) {
|
|
24755
|
+
return { ok: false, error: `Agent "${trimmed}" not found \u2014 no config.json at ${agentConfigPath}. Check the agent slug and try again.` };
|
|
24756
|
+
}
|
|
24757
|
+
try {
|
|
24758
|
+
const config2 = readConfig(accountDir);
|
|
24759
|
+
if (!config2.whatsapp || typeof config2.whatsapp !== "object") {
|
|
24760
|
+
config2.whatsapp = {};
|
|
24761
|
+
}
|
|
24762
|
+
const wa = config2.whatsapp;
|
|
24763
|
+
wa.publicAgent = trimmed;
|
|
24764
|
+
const parsed = WhatsAppConfigSchema.safeParse(wa);
|
|
24765
|
+
if (!parsed.success) {
|
|
24766
|
+
const msg = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
24767
|
+
return { ok: false, error: `Validation failed: ${msg}` };
|
|
24768
|
+
}
|
|
24769
|
+
config2.whatsapp = parsed.data;
|
|
24770
|
+
writeConfig(accountDir, config2);
|
|
24771
|
+
console.error(`${TAG8} publicAgent set to ${trimmed}`);
|
|
24772
|
+
reloadManagerConfig(accountDir);
|
|
24773
|
+
return { ok: true, message: `Public agent set to "${trimmed}". WhatsApp messages from non-admin phones will be handled by this agent.` };
|
|
24774
|
+
} catch (err) {
|
|
24775
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
24776
|
+
console.error(`${TAG8} setPublicAgent failed: ${msg}`);
|
|
24777
|
+
return { ok: false, error: msg };
|
|
24778
|
+
}
|
|
24779
|
+
}
|
|
24780
|
+
function getPublicAgent(accountDir) {
|
|
24781
|
+
try {
|
|
24782
|
+
const config2 = readConfig(accountDir);
|
|
24783
|
+
const wa = config2.whatsapp;
|
|
24784
|
+
if (!wa) return null;
|
|
24785
|
+
const slug = wa.publicAgent;
|
|
24786
|
+
if (typeof slug === "string" && slug.trim()) {
|
|
24787
|
+
return slug.trim();
|
|
24788
|
+
}
|
|
24789
|
+
return null;
|
|
24790
|
+
} catch {
|
|
24791
|
+
return null;
|
|
24792
|
+
}
|
|
24793
|
+
}
|
|
24727
24794
|
|
|
24728
24795
|
// app/api/whatsapp/login/wait/route.ts
|
|
24729
24796
|
async function POST10(req) {
|
|
@@ -24920,7 +24987,7 @@ function serializeWhatsAppSchema() {
|
|
|
24920
24987
|
async function POST14(req) {
|
|
24921
24988
|
try {
|
|
24922
24989
|
const body = await req.json().catch(() => ({}));
|
|
24923
|
-
const { action, phone, accountId } = body;
|
|
24990
|
+
const { action, phone, slug, accountId } = body;
|
|
24924
24991
|
if (!action || typeof action !== "string") {
|
|
24925
24992
|
return Response.json({ ok: false, error: 'Missing required field "action".' }, { status: 400 });
|
|
24926
24993
|
}
|
|
@@ -24950,6 +25017,19 @@ async function POST14(req) {
|
|
|
24950
25017
|
console.error(`[whatsapp:api] config action=list-admin-phones count=${phones.length}`);
|
|
24951
25018
|
return Response.json({ ok: true, phones });
|
|
24952
25019
|
}
|
|
25020
|
+
case "set-public-agent": {
|
|
25021
|
+
if (!slug || typeof slug !== "string") {
|
|
25022
|
+
return Response.json({ ok: false, error: 'Missing required field "slug" (the agent directory name, e.g. "my-agent").' }, { status: 400 });
|
|
25023
|
+
}
|
|
25024
|
+
const result = setPublicAgent(account.accountDir, slug);
|
|
25025
|
+
console.error(`[whatsapp:api] config action=set-public-agent slug=${slug} ok=${result.ok}`);
|
|
25026
|
+
return Response.json(result, { status: result.ok ? 200 : 400 });
|
|
25027
|
+
}
|
|
25028
|
+
case "get-public-agent": {
|
|
25029
|
+
const currentSlug = getPublicAgent(account.accountDir);
|
|
25030
|
+
console.error(`[whatsapp:api] config action=get-public-agent slug=${currentSlug ?? "none"}`);
|
|
25031
|
+
return Response.json({ ok: true, slug: currentSlug });
|
|
25032
|
+
}
|
|
24953
25033
|
case "schema": {
|
|
24954
25034
|
const text = serializeWhatsAppSchema();
|
|
24955
25035
|
console.error(`[whatsapp:api] config action=schema`);
|
|
@@ -24978,7 +25058,7 @@ async function POST14(req) {
|
|
|
24978
25058
|
}
|
|
24979
25059
|
default:
|
|
24980
25060
|
return Response.json(
|
|
24981
|
-
{ ok: false, error: `Unknown action "${action}". Valid actions: add-admin-phone, remove-admin-phone, list-admin-phones, schema, list-groups.` },
|
|
25061
|
+
{ ok: false, error: `Unknown action "${action}". Valid actions: add-admin-phone, remove-admin-phone, list-admin-phones, set-public-agent, get-public-agent, schema, list-groups.` },
|
|
24982
25062
|
{ status: 400 }
|
|
24983
25063
|
);
|
|
24984
25064
|
}
|
|
@@ -25739,11 +25819,11 @@ async function GET7() {
|
|
|
25739
25819
|
|
|
25740
25820
|
// app/api/admin/version/route.ts
|
|
25741
25821
|
import { readFileSync as readFileSync17, existsSync as existsSync17 } from "fs";
|
|
25742
|
-
import { resolve as resolve14, join as
|
|
25822
|
+
import { resolve as resolve14, join as join7 } from "path";
|
|
25743
25823
|
var PLATFORM_ROOT6 = process.env.MAXY_PLATFORM_ROOT ?? resolve14(process.cwd(), "..");
|
|
25744
25824
|
var brandHostname = "maxy";
|
|
25745
25825
|
var brandNpmPackage = "@rubytech/create-maxy";
|
|
25746
|
-
var brandJsonPath =
|
|
25826
|
+
var brandJsonPath = join7(PLATFORM_ROOT6, "config", "brand.json");
|
|
25747
25827
|
if (existsSync17(brandJsonPath)) {
|
|
25748
25828
|
try {
|
|
25749
25829
|
const brand = JSON.parse(readFileSync17(brandJsonPath, "utf-8"));
|
|
@@ -25815,11 +25895,11 @@ async function GET8() {
|
|
|
25815
25895
|
// app/api/admin/version/upgrade/route.ts
|
|
25816
25896
|
import { spawn as spawn4 } from "child_process";
|
|
25817
25897
|
import { existsSync as existsSync18, statSync as statSync4, writeFileSync as writeFileSync11, readFileSync as readFileSync18, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
25818
|
-
import { resolve as resolve15, join as
|
|
25898
|
+
import { resolve as resolve15, join as join8 } from "path";
|
|
25819
25899
|
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve15(process.cwd(), "..");
|
|
25820
25900
|
var upgradePkg = "@rubytech/create-maxy";
|
|
25821
25901
|
var upgradeHostname = "maxy";
|
|
25822
|
-
var brandPath =
|
|
25902
|
+
var brandPath = join8(PLATFORM_ROOT7, "config", "brand.json");
|
|
25823
25903
|
if (existsSync18(brandPath)) {
|
|
25824
25904
|
try {
|
|
25825
25905
|
const brand = JSON.parse(readFileSync18(brandPath, "utf-8"));
|
|
@@ -25986,7 +26066,7 @@ async function POST21() {
|
|
|
25986
26066
|
|
|
25987
26067
|
// server/index.ts
|
|
25988
26068
|
var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
25989
|
-
var BRAND_JSON_PATH = PLATFORM_ROOT8 ?
|
|
26069
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT8 ? join9(PLATFORM_ROOT8, "config", "brand.json") : "";
|
|
25990
26070
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", domain: "getmaxy.com" };
|
|
25991
26071
|
if (BRAND_JSON_PATH && !existsSync19(BRAND_JSON_PATH)) {
|
|
25992
26072
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
@@ -26005,7 +26085,7 @@ var brandLoginOpts = {
|
|
|
26005
26085
|
primaryHoverColor: BRAND.defaultColors?.primaryHover,
|
|
26006
26086
|
primarySubtle: BRAND.defaultColors?.primarySubtle
|
|
26007
26087
|
};
|
|
26008
|
-
var ALIAS_DOMAINS_PATH =
|
|
26088
|
+
var ALIAS_DOMAINS_PATH = join9(homedir3(), BRAND.configDir, "alias-domains.json");
|
|
26009
26089
|
function loadAliasDomains() {
|
|
26010
26090
|
try {
|
|
26011
26091
|
if (!existsSync19(ALIAS_DOMAINS_PATH)) return null;
|
|
@@ -26375,14 +26455,14 @@ function cachedHtml(file2) {
|
|
|
26375
26455
|
}
|
|
26376
26456
|
var brandedHtmlCache = /* @__PURE__ */ new Map();
|
|
26377
26457
|
function loadBrandingCache(agentSlug) {
|
|
26378
|
-
const configDir2 =
|
|
26458
|
+
const configDir2 = join9(homedir3(), BRAND.configDir);
|
|
26379
26459
|
try {
|
|
26380
|
-
const accountJsonPath =
|
|
26460
|
+
const accountJsonPath = join9(configDir2, "account.json");
|
|
26381
26461
|
if (!existsSync19(accountJsonPath)) return null;
|
|
26382
26462
|
const account = JSON.parse(readFileSync19(accountJsonPath, "utf-8"));
|
|
26383
26463
|
const accountId = account.accountId;
|
|
26384
26464
|
if (!accountId) return null;
|
|
26385
|
-
const cachePath =
|
|
26465
|
+
const cachePath = join9(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
|
|
26386
26466
|
if (!existsSync19(cachePath)) return null;
|
|
26387
26467
|
return JSON.parse(readFileSync19(cachePath, "utf-8"));
|
|
26388
26468
|
} catch {
|
|
@@ -26391,8 +26471,8 @@ function loadBrandingCache(agentSlug) {
|
|
|
26391
26471
|
}
|
|
26392
26472
|
function resolveDefaultSlug() {
|
|
26393
26473
|
try {
|
|
26394
|
-
const configDir2 =
|
|
26395
|
-
const accountJsonPath =
|
|
26474
|
+
const configDir2 = join9(homedir3(), BRAND.configDir);
|
|
26475
|
+
const accountJsonPath = join9(configDir2, "account.json");
|
|
26396
26476
|
if (!existsSync19(accountJsonPath)) return null;
|
|
26397
26477
|
const account = JSON.parse(readFileSync19(accountJsonPath, "utf-8"));
|
|
26398
26478
|
return account.defaultAgent || null;
|
|
@@ -26463,17 +26543,29 @@ console.log(`${BRAND.productName} listening on http://${hostname3}:${port}`);
|
|
|
26463
26543
|
var configDirForWhatsApp = basename2(MAXY_DIR) || ".maxy";
|
|
26464
26544
|
var bootAccount = resolveAccount();
|
|
26465
26545
|
var bootAccountConfig = bootAccount?.config;
|
|
26546
|
+
var bootPublicAgent = bootAccount ? getPublicAgent(bootAccount.accountDir) : null;
|
|
26466
26547
|
if (bootAccountConfig?.whatsapp) {
|
|
26467
|
-
console.error(
|
|
26548
|
+
console.error(`[whatsapp:boot] loading whatsapp config from account.json publicAgent=${bootPublicAgent ?? "none"}`);
|
|
26468
26549
|
} else {
|
|
26469
26550
|
console.error("[whatsapp:boot] no whatsapp config in account.json \u2014 routing policies will use defaults");
|
|
26470
26551
|
}
|
|
26471
26552
|
init({
|
|
26472
26553
|
configDir: configDirForWhatsApp,
|
|
26473
|
-
platformRoot: resolve16(process.env.MAXY_PLATFORM_ROOT ??
|
|
26554
|
+
platformRoot: resolve16(process.env.MAXY_PLATFORM_ROOT ?? join9(__dirname, "..")),
|
|
26474
26555
|
accountConfig: bootAccountConfig,
|
|
26475
26556
|
onMessage: async (msg) => {
|
|
26476
26557
|
try {
|
|
26558
|
+
let agentName;
|
|
26559
|
+
if (msg.agentType === "admin") {
|
|
26560
|
+
agentName = "admin";
|
|
26561
|
+
} else {
|
|
26562
|
+
const publicAgentSlug = bootAccount ? getPublicAgent(bootAccount.accountDir) : null;
|
|
26563
|
+
if (!publicAgentSlug) {
|
|
26564
|
+
console.error(`[whatsapp:route] rejected: no publicAgent configured account=${msg.accountId} from=${msg.senderPhone}`);
|
|
26565
|
+
return;
|
|
26566
|
+
}
|
|
26567
|
+
agentName = publicAgentSlug;
|
|
26568
|
+
}
|
|
26477
26569
|
const parts = [];
|
|
26478
26570
|
if (msg.replyContext) {
|
|
26479
26571
|
const quotedText = msg.replyContext.text ? `: "${msg.replyContext.text}"` : "";
|
|
@@ -26492,7 +26584,7 @@ init({
|
|
|
26492
26584
|
if (msg.isOwnerMirror) {
|
|
26493
26585
|
let responseText2 = "";
|
|
26494
26586
|
for await (const event of invokeAgent(
|
|
26495
|
-
{ type: msg.agentType, agentName
|
|
26587
|
+
{ type: msg.agentType, agentName },
|
|
26496
26588
|
enrichedText,
|
|
26497
26589
|
msg.sessionKey
|
|
26498
26590
|
)) {
|
|
@@ -26507,7 +26599,7 @@ init({
|
|
|
26507
26599
|
}
|
|
26508
26600
|
let responseText = "";
|
|
26509
26601
|
for await (const event of invokeAgent(
|
|
26510
|
-
{ type: msg.agentType, agentName
|
|
26602
|
+
{ type: msg.agentType, agentName },
|
|
26511
26603
|
enrichedText,
|
|
26512
26604
|
msg.sessionKey
|
|
26513
26605
|
)) {
|
|
@@ -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.
|
|
@@ -127,13 +127,14 @@ server.tool("whatsapp-send", "Send a WhatsApp message to a phone number or group
|
|
|
127
127
|
}
|
|
128
128
|
});
|
|
129
129
|
// ─── whatsapp-config ──────────────────────────────────────────────────
|
|
130
|
-
server.tool("whatsapp-config", "Manage WhatsApp configuration. Actions: add-admin-phone (register a phone as admin — messages from it route to admin agent), remove-admin-phone (demote to public routing), list-admin-phones (show current admin phones), schema (return all config field definitions — names, types, defaults, descriptions, constraints), list-groups (return WhatsApp groups the account belongs to — names, JIDs, participant counts). Phone numbers must be E.164 format (e.g. +441234567890).", {
|
|
131
|
-
action: z.enum(["add-admin-phone", "remove-admin-phone", "list-admin-phones", "schema", "list-groups"]).describe("The config action to perform"),
|
|
132
|
-
phone: z.string().optional().describe("Phone number in E.164 format (required for add/remove)"),
|
|
130
|
+
server.tool("whatsapp-config", "Manage WhatsApp configuration. Actions: add-admin-phone (register a phone as admin — messages from it route to admin agent), remove-admin-phone (demote to public routing), list-admin-phones (show current admin phones), set-public-agent (set which agent handles WhatsApp messages from non-admin phones — requires the agent slug), get-public-agent (show the currently configured public agent), schema (return all config field definitions — names, types, defaults, descriptions, constraints), list-groups (return WhatsApp groups the account belongs to — names, JIDs, participant counts). Phone numbers must be E.164 format (e.g. +441234567890).", {
|
|
131
|
+
action: z.enum(["add-admin-phone", "remove-admin-phone", "list-admin-phones", "set-public-agent", "get-public-agent", "schema", "list-groups"]).describe("The config action to perform"),
|
|
132
|
+
phone: z.string().optional().describe("Phone number in E.164 format (required for add/remove admin phone)"),
|
|
133
|
+
slug: z.string().optional().describe("Agent slug (required for set-public-agent)"),
|
|
133
134
|
accountId: z.string().optional().describe('Account ID for list-groups (default: "default")'),
|
|
134
|
-
}, async ({ action, phone, accountId }) => {
|
|
135
|
+
}, async ({ action, phone, slug, accountId }) => {
|
|
135
136
|
try {
|
|
136
|
-
const result = await callApi("/api/whatsapp/config", "POST", { action, phone, accountId });
|
|
137
|
+
const result = await callApi("/api/whatsapp/config", "POST", { action, phone, slug, accountId });
|
|
137
138
|
if (!result.ok)
|
|
138
139
|
return textResult(result.error ?? "Config operation failed.", true);
|
|
139
140
|
if (action === "list-admin-phones") {
|
|
@@ -142,6 +143,12 @@ server.tool("whatsapp-config", "Manage WhatsApp configuration. Actions: add-admi
|
|
|
142
143
|
return textResult("No admin phones configured. Use add-admin-phone to register one.");
|
|
143
144
|
return textResult(`Admin phones:\n${phones.map((p) => ` ${p}`).join("\n")}`);
|
|
144
145
|
}
|
|
146
|
+
if (action === "get-public-agent") {
|
|
147
|
+
const agentSlug = result.slug;
|
|
148
|
+
if (!agentSlug)
|
|
149
|
+
return textResult("No public agent configured for WhatsApp. Use set-public-agent to assign one.");
|
|
150
|
+
return textResult(`WhatsApp public agent: ${agentSlug}`);
|
|
151
|
+
}
|
|
145
152
|
if (action === "schema") {
|
|
146
153
|
return textResult(result.text ?? "Schema unavailable.");
|
|
147
154
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -43,17 +43,17 @@ The self phone from Phase 1 is automatically registered as an admin phone. If th
|
|
|
43
43
|
|
|
44
44
|
### Phase 3: Public Agent Selection
|
|
45
45
|
|
|
46
|
-
WhatsApp inbound from non-admin phones routes to a public agent
|
|
46
|
+
WhatsApp inbound from non-admin phones routes to a public agent configured in the WhatsApp config — not the account-level `defaultAgent`. The user must explicitly choose which agent handles WhatsApp. There is no automatic fallback; if no public agent is set, WhatsApp silently rejects non-admin messages.
|
|
47
47
|
|
|
48
|
-
1. **
|
|
48
|
+
1. **Check current state** — Call `whatsapp-config` with `action: "get-public-agent"` to see if one is already set. Also call `account-manage` to list the available public agents.
|
|
49
49
|
|
|
50
50
|
2. **Ask which agent should handle WhatsApp** — "Which of your public agents should respond to WhatsApp messages from customers?"
|
|
51
51
|
|
|
52
|
-
3. **Set the
|
|
52
|
+
3. **Set the public agent** — Call `whatsapp-config` with `action: "set-public-agent"` and `slug: "{chosen-slug}"`.
|
|
53
53
|
|
|
54
54
|
4. **Confirm** — "WhatsApp messages from customers will be handled by {agent name}."
|
|
55
55
|
|
|
56
|
-
If only one public agent exists, confirm it as the
|
|
56
|
+
If only one public agent exists, confirm it as the choice rather than asking. If no public agents exist, tell the user: "You don't have a public agent yet. WhatsApp will be connected but won't respond to customer messages until you create one and set it with `whatsapp-config action: set-public-agent`."
|
|
57
57
|
|
|
58
58
|
## Relinking
|
|
59
59
|
|
|
@@ -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.
|