radmail-mcp 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +148 -0
- package/dist/api/mcp.d.ts +3 -0
- package/dist/api/mcp.js +44 -0
- package/dist/api/mcp.js.map +1 -0
- package/dist/src/engine/importance-score.d.ts +122 -0
- package/dist/src/engine/importance-score.js +352 -0
- package/dist/src/engine/importance-score.js.map +1 -0
- package/dist/src/engine/send-disposition.d.ts +37 -0
- package/dist/src/engine/send-disposition.js +112 -0
- package/dist/src/engine/send-disposition.js.map +1 -0
- package/dist/src/engine/signals.d.ts +116 -0
- package/dist/src/engine/signals.js +287 -0
- package/dist/src/engine/signals.js.map +1 -0
- package/dist/src/engine/types.d.ts +20 -0
- package/dist/src/engine/types.js +52 -0
- package/dist/src/engine/types.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +19 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib/commitment.d.ts +24 -0
- package/dist/src/lib/commitment.js +123 -0
- package/dist/src/lib/commitment.js.map +1 -0
- package/dist/src/lib/connected.d.ts +112 -0
- package/dist/src/lib/connected.js +150 -0
- package/dist/src/lib/connected.js.map +1 -0
- package/dist/src/lib/demand-sink.d.ts +23 -0
- package/dist/src/lib/demand-sink.js +87 -0
- package/dist/src/lib/demand-sink.js.map +1 -0
- package/dist/src/lib/learning.d.ts +35 -0
- package/dist/src/lib/learning.js +103 -0
- package/dist/src/lib/learning.js.map +1 -0
- package/dist/src/lib/taint.d.ts +35 -0
- package/dist/src/lib/taint.js +65 -0
- package/dist/src/lib/taint.js.map +1 -0
- package/dist/src/lib/tenants.d.ts +21 -0
- package/dist/src/lib/tenants.js +55 -0
- package/dist/src/lib/tenants.js.map +1 -0
- package/dist/src/lib/triage.d.ts +83 -0
- package/dist/src/lib/triage.js +278 -0
- package/dist/src/lib/triage.js.map +1 -0
- package/dist/src/server.d.ts +9 -0
- package/dist/src/server.js +40 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/tools.d.ts +302 -0
- package/dist/src/tools.js +737 -0
- package/dist/src/tools.js.map +1 -0
- package/dist/test/connected.test.d.ts +1 -0
- package/dist/test/connected.test.js +514 -0
- package/dist/test/connected.test.js.map +1 -0
- package/dist/test/demand-sink.test.d.ts +1 -0
- package/dist/test/demand-sink.test.js +137 -0
- package/dist/test/demand-sink.test.js.map +1 -0
- package/dist/test/firewall.test.d.ts +1 -0
- package/dist/test/firewall.test.js +210 -0
- package/dist/test/firewall.test.js.map +1 -0
- package/dist/test/taint.test.d.ts +1 -0
- package/dist/test/taint.test.js +90 -0
- package/dist/test/taint.test.js.map +1 -0
- package/package.json +53 -0
- package/src/engine/importance-score.ts +462 -0
- package/src/engine/send-disposition.ts +173 -0
- package/src/engine/signals.ts +403 -0
- package/src/engine/types.ts +73 -0
- package/src/index.ts +21 -0
- package/src/lib/commitment.ts +143 -0
- package/src/lib/connected.ts +291 -0
- package/src/lib/demand-sink.ts +102 -0
- package/src/lib/learning.ts +136 -0
- package/src/lib/taint.ts +87 -0
- package/src/lib/tenants.ts +67 -0
- package/src/lib/triage.ts +358 -0
- package/src/server.ts +50 -0
- package/src/tools.ts +932 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// PORTED VERBATIM (pure, no-DB) from the RadMail main app:
|
|
3
|
+
// /Users/GreenLife/Documents/CODE/RadMail/src/server/commitments/types.ts
|
|
4
|
+
// Recovered into radmail-mcp so the MCP sandbox runs the SAME deterministic
|
|
5
|
+
// commitment enums as production. Do not diverge — keep byte-for-byte with source.
|
|
6
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
// Commitment engine — shared types (Wave 7). The closed enums are load-bearing:
|
|
8
|
+
// they drive the SLA table, the draft style, the send-disposition gate, and the
|
|
9
|
+
// escalation tier. Keep them CLOSED — an unknown value coerces to a safe default.
|
|
10
|
+
//
|
|
11
|
+
// scope: RADMAIL_SCOPE_AUTONOMOUS_FOLLOWUP_2026_06_19.md §1.2, §2.1, §2.2.
|
|
12
|
+
// ─── Direction (who owes the action) ──────────────────────────────────
|
|
13
|
+
export const COMMITMENT_DIRECTIONS = ["owed_by_us", "owed_to_us"];
|
|
14
|
+
// ─── Action type (CLOSED enum — the spine of every downstream decision) ─
|
|
15
|
+
// send_deliverable | follow_up | contact_third_party | answer_question | other
|
|
16
|
+
// Plus the scope's two HARD-STOP classes (payment/decision) carried so the
|
|
17
|
+
// disposition gate can permanently refuse them (BEC defense).
|
|
18
|
+
export const COMMITMENT_ACTION_TYPES = [
|
|
19
|
+
"send_deliverable",
|
|
20
|
+
"follow_up",
|
|
21
|
+
"contact_third_party",
|
|
22
|
+
"answer_question",
|
|
23
|
+
"payment",
|
|
24
|
+
"decision",
|
|
25
|
+
"other",
|
|
26
|
+
];
|
|
27
|
+
export function isCommitmentActionType(v) {
|
|
28
|
+
return COMMITMENT_ACTION_TYPES.includes(v);
|
|
29
|
+
}
|
|
30
|
+
/** Coerce an arbitrary LLM-returned action_type to the closed enum (unknown → 'other'). */
|
|
31
|
+
export function coerceActionType(v) {
|
|
32
|
+
const s = String(v ?? "").toLowerCase().trim();
|
|
33
|
+
return isCommitmentActionType(s) ? s : "other";
|
|
34
|
+
}
|
|
35
|
+
// ─── State machine ────────────────────────────────────────────────────
|
|
36
|
+
export const COMMITMENT_STATES = [
|
|
37
|
+
"detected",
|
|
38
|
+
"confirmed",
|
|
39
|
+
"needs_due_date",
|
|
40
|
+
"scheduled",
|
|
41
|
+
"drafted",
|
|
42
|
+
"sent",
|
|
43
|
+
"done",
|
|
44
|
+
"overdue",
|
|
45
|
+
"escalated",
|
|
46
|
+
"dismissed",
|
|
47
|
+
"snoozed",
|
|
48
|
+
"cancelled",
|
|
49
|
+
];
|
|
50
|
+
// ─── SLA basis — how the due date was determined ──────────────────────
|
|
51
|
+
export const SLA_BASES = ["explicit", "relative", "default_by_type", "manual"];
|
|
52
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/engine/types.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,2DAA2D;AAC3D,4EAA4E;AAC5E,4EAA4E;AAC5E,mFAAmF;AACnF,gFAAgF;AAEhF,gFAAgF;AAChF,gFAAgF;AAChF,kFAAkF;AAClF,EAAE;AACF,2EAA2E;AAE3E,yEAAyE;AACzE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,YAAY,EAAE,YAAY,CAAU,CAAC;AAG3E,2EAA2E;AAC3E,+EAA+E;AAC/E,2EAA2E;AAC3E,8DAA8D;AAC9D,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,kBAAkB;IAClB,WAAW;IACX,qBAAqB;IACrB,iBAAiB;IACjB,SAAS;IACT,UAAU;IACV,OAAO;CACC,CAAC;AAGX,MAAM,UAAU,sBAAsB,CAAC,CAAS;IAC9C,OAAQ,uBAA6C,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,gBAAgB,CAAC,CAAU;IACzC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IAC/C,OAAO,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;AACjD,CAAC;AAED,yEAAyE;AACzE,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,UAAU;IACV,WAAW;IACX,gBAAgB;IAChB,WAAW;IACX,SAAS;IACT,MAAM;IACN,MAAM;IACN,SAAS;IACT,WAAW;IACX,WAAW;IACX,SAAS;IACT,WAAW;CACH,CAAC;AAGX,yEAAyE;AACzE,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,UAAU,EAAE,UAAU,EAAE,iBAAiB,EAAE,QAAQ,CAAU,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Local stdio entry — `npx radmail-mcp` or `node dist/index.js`. Speaks the MCP
|
|
3
|
+
// stdio transport so a desktop agent host (Claude Desktop, etc.) can run RadMail
|
|
4
|
+
// locally. The Vercel deployment uses api/mcp.ts (streamable-HTTP) instead.
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { createServer } from "./server.js";
|
|
7
|
+
async function main() {
|
|
8
|
+
const server = createServer();
|
|
9
|
+
const transport = new StdioServerTransport();
|
|
10
|
+
await server.connect(transport);
|
|
11
|
+
// eslint-disable-next-line no-console
|
|
12
|
+
console.error("radmail-mcp (sandbox engine) running on stdio");
|
|
13
|
+
}
|
|
14
|
+
main().catch((err) => {
|
|
15
|
+
// eslint-disable-next-line no-console
|
|
16
|
+
console.error("radmail-mcp failed to start:", err);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
});
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AACA,gFAAgF;AAChF,iFAAiF;AACjF,4EAA4E;AAE5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;AACjE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,sCAAsC;IACtC,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,GAAG,CAAC,CAAC;IACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Resolve a relative due phrase ("by Thursday", "EOD", "tomorrow", "in 3 days",
|
|
2
|
+
* an explicit date) to an absolute YYYY-MM-DD, relative to `now`. null if none. */
|
|
3
|
+
export declare function resolveDuePhrase(text: string, now: Date): {
|
|
4
|
+
phrase: string;
|
|
5
|
+
date: string;
|
|
6
|
+
} | null;
|
|
7
|
+
export interface ExtractedCommitmentLite {
|
|
8
|
+
/** verbatim sentence from the body — TAINTED at the call site. */
|
|
9
|
+
what: string;
|
|
10
|
+
owedTo: string;
|
|
11
|
+
owedBy: string | null;
|
|
12
|
+
status: "open" | "due" | "overdue";
|
|
13
|
+
duePhrase: string | null;
|
|
14
|
+
/** who is being asked: 'us' (we owe a deliverable/answer) vs 'them'. */
|
|
15
|
+
direction: "owed_by_us" | "owed_to_us";
|
|
16
|
+
isQuestion: boolean;
|
|
17
|
+
hasAsk: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** Pull the single most-salient commitment out of a message. null if none found. */
|
|
20
|
+
export declare function extractCommitment(msg: {
|
|
21
|
+
from: string;
|
|
22
|
+
subject?: string | null;
|
|
23
|
+
body: string;
|
|
24
|
+
}, now: Date): ExtractedCommitmentLite | null;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Lightweight commitment + due-date extraction for the sandbox engine.
|
|
2
|
+
//
|
|
3
|
+
// This is a HEURISTIC extractor (the sandbox is heuristic by design — the
|
|
4
|
+
// production "99%" engine is launch-gated). It pulls a single most-salient
|
|
5
|
+
// commitment sentence out of a message body and resolves a relative due phrase
|
|
6
|
+
// to an absolute YYYY-MM-DD, so the importance scorer + the firewall have a
|
|
7
|
+
// deterministic deadline to reason over.
|
|
8
|
+
//
|
|
9
|
+
// EVERYTHING here reads an attacker-controllable email body — its outputs are
|
|
10
|
+
// tainted by the caller (see lib/taint.ts). It performs NO action; it only
|
|
11
|
+
// extracts text + a date.
|
|
12
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
13
|
+
const WEEKDAYS = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
|
|
14
|
+
function iso(d) {
|
|
15
|
+
return d.toISOString().slice(0, 10);
|
|
16
|
+
}
|
|
17
|
+
/** Resolve a relative due phrase ("by Thursday", "EOD", "tomorrow", "in 3 days",
|
|
18
|
+
* an explicit date) to an absolute YYYY-MM-DD, relative to `now`. null if none. */
|
|
19
|
+
export function resolveDuePhrase(text, now) {
|
|
20
|
+
const t = text.toLowerCase();
|
|
21
|
+
// Explicit ISO / numeric dates first.
|
|
22
|
+
const isoM = t.match(/\b(\d{4}-\d{2}-\d{2})\b/);
|
|
23
|
+
if (isoM) {
|
|
24
|
+
const d = new Date(isoM[1]);
|
|
25
|
+
if (!isNaN(d.getTime()))
|
|
26
|
+
return { phrase: isoM[1], date: iso(d) };
|
|
27
|
+
}
|
|
28
|
+
const mdM = t.match(/\b(\d{1,2})\/(\d{1,2})(?:\/(\d{2,4}))?\b/);
|
|
29
|
+
if (mdM) {
|
|
30
|
+
const month = parseInt(mdM[1], 10) - 1;
|
|
31
|
+
const day = parseInt(mdM[2], 10);
|
|
32
|
+
let year = mdM[3] ? parseInt(mdM[3], 10) : now.getUTCFullYear();
|
|
33
|
+
if (year < 100)
|
|
34
|
+
year += 2000;
|
|
35
|
+
const d = new Date(Date.UTC(year, month, day));
|
|
36
|
+
if (!isNaN(d.getTime()))
|
|
37
|
+
return { phrase: mdM[0], date: iso(d) };
|
|
38
|
+
}
|
|
39
|
+
if (/\b(today|eod|end of day|cob|by close of business)\b/.test(t)) {
|
|
40
|
+
return { phrase: "today", date: iso(now) };
|
|
41
|
+
}
|
|
42
|
+
if (/\btomorrow\b/.test(t)) {
|
|
43
|
+
return { phrase: "tomorrow", date: iso(new Date(now.getTime() + DAY_MS)) };
|
|
44
|
+
}
|
|
45
|
+
const inDays = t.match(/\bin\s+(\d{1,2})\s+(day|business day|week)s?\b/);
|
|
46
|
+
if (inDays) {
|
|
47
|
+
const n = parseInt(inDays[1], 10);
|
|
48
|
+
const mult = inDays[2].startsWith("week") ? 7 : 1;
|
|
49
|
+
return { phrase: inDays[0], date: iso(new Date(now.getTime() + n * mult * DAY_MS)) };
|
|
50
|
+
}
|
|
51
|
+
if (/\bnext week\b/.test(t)) {
|
|
52
|
+
return { phrase: "next week", date: iso(new Date(now.getTime() + 7 * DAY_MS)) };
|
|
53
|
+
}
|
|
54
|
+
if (/\bend of (the )?week\b/.test(t)) {
|
|
55
|
+
// Friday of the current week.
|
|
56
|
+
const dow = now.getUTCDay();
|
|
57
|
+
const delta = (5 - dow + 7) % 7;
|
|
58
|
+
return { phrase: "end of week", date: iso(new Date(now.getTime() + delta * DAY_MS)) };
|
|
59
|
+
}
|
|
60
|
+
// Named weekday → the NEXT occurrence (today counts only if "by today" matched above).
|
|
61
|
+
for (let i = 0; i < WEEKDAYS.length; i++) {
|
|
62
|
+
const re = new RegExp(`\\b(?:by|before|on|this|next|due)?\\s*${WEEKDAYS[i]}\\b`);
|
|
63
|
+
if (re.test(t)) {
|
|
64
|
+
const dow = now.getUTCDay();
|
|
65
|
+
let delta = (i - dow + 7) % 7;
|
|
66
|
+
if (delta === 0)
|
|
67
|
+
delta = 7; // "Thursday" said on a Thursday = next Thursday
|
|
68
|
+
return { phrase: WEEKDAYS[i], date: iso(new Date(now.getTime() + delta * DAY_MS)) };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const ASK_RE = /\b(can you|could you|please|would you|need|let me know|send (?:me|over|us)|get (?:me|us|back)|follow up|circle back|by\s|deadline|due|confirm|review|provide|share|update me|waiting on|expect)\b/i;
|
|
74
|
+
function splitSentences(body) {
|
|
75
|
+
return body
|
|
76
|
+
.replace(/\s+/g, " ")
|
|
77
|
+
.split(/(?<=[.!?])\s+|\n+/)
|
|
78
|
+
.map((s) => s.trim())
|
|
79
|
+
.filter(Boolean);
|
|
80
|
+
}
|
|
81
|
+
/** Pull the single most-salient commitment out of a message. null if none found. */
|
|
82
|
+
export function extractCommitment(msg, now) {
|
|
83
|
+
const sentences = splitSentences(msg.body);
|
|
84
|
+
if (sentences.length === 0)
|
|
85
|
+
return null;
|
|
86
|
+
// Score each sentence: a question or an ask-verb wins; a due phrase boosts.
|
|
87
|
+
let best = null;
|
|
88
|
+
for (const s of sentences) {
|
|
89
|
+
let score = 0;
|
|
90
|
+
const isQ = s.includes("?");
|
|
91
|
+
if (isQ)
|
|
92
|
+
score += 3;
|
|
93
|
+
if (ASK_RE.test(s))
|
|
94
|
+
score += 2;
|
|
95
|
+
if (resolveDuePhrase(s, now))
|
|
96
|
+
score += 2;
|
|
97
|
+
if (score > 0 && (!best || score > best.score))
|
|
98
|
+
best = { s, score };
|
|
99
|
+
}
|
|
100
|
+
if (!best)
|
|
101
|
+
return null;
|
|
102
|
+
const what = best.s.length > 200 ? best.s.slice(0, 197) + "…" : best.s;
|
|
103
|
+
const due = resolveDuePhrase(best.s, now) ?? resolveDuePhrase(msg.body, now);
|
|
104
|
+
let status = "open";
|
|
105
|
+
if (due) {
|
|
106
|
+
const days = Math.floor((new Date(due.date).getTime() - now.getTime()) / DAY_MS);
|
|
107
|
+
status = days < 0 ? "overdue" : days <= 1 ? "due" : "open";
|
|
108
|
+
}
|
|
109
|
+
// Direction: an inbound asking us to do/send/answer = we owe them (owed_by_us);
|
|
110
|
+
// an inbound saying "I'll send you X" = they owe us (owed_to_us).
|
|
111
|
+
const owedToUs = /\b(i'?ll|we'?ll|i will|we will|i'?m going to|let me get|i can send|on my way)\b/i.test(best.s);
|
|
112
|
+
return {
|
|
113
|
+
what,
|
|
114
|
+
owedTo: msg.from,
|
|
115
|
+
owedBy: due?.date ?? null,
|
|
116
|
+
status,
|
|
117
|
+
duePhrase: due?.phrase ?? null,
|
|
118
|
+
direction: owedToUs ? "owed_to_us" : "owed_by_us",
|
|
119
|
+
isQuestion: best.s.includes("?"),
|
|
120
|
+
hasAsk: ASK_RE.test(best.s),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=commitment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commitment.js","sourceRoot":"","sources":["../../../src/lib/commitment.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,EAAE;AACF,0EAA0E;AAC1E,2EAA2E;AAC3E,+EAA+E;AAC/E,4EAA4E;AAC5E,yCAAyC;AACzC,EAAE;AACF,8EAA8E;AAC9E,2EAA2E;AAC3E,0BAA0B;AAE1B,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AACnC,MAAM,QAAQ,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;AAEhG,SAAS,GAAG,CAAC,CAAO;IAClB,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACtC,CAAC;AAED;oFACoF;AACpF,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,GAAS;IACtD,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAE7B,sCAAsC;IACtC,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAChD,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;YAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACpE,CAAC;IACD,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAChE,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;QACvC,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACjC,IAAI,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC;QAChE,IAAI,IAAI,GAAG,GAAG;YAAE,IAAI,IAAI,IAAI,CAAC;QAC7B,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;QAC/C,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;YAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACnE,CAAC;IAED,IAAI,qDAAqD,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAClE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;IAC7C,CAAC;IACD,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC;IAC7E,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACzE,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAClC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClD,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,IAAI,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC;IACvF,CAAC;IACD,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC;IAClF,CAAC;IACD,IAAI,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACrC,8BAA8B;QAC9B,MAAM,GAAG,GAAG,GAAG,CAAC,SAAS,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;QAChC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC;IACxF,CAAC;IAED,uFAAuF;IACvF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,yCAAyC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACjF,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACf,MAAM,GAAG,GAAG,GAAG,CAAC,SAAS,EAAE,CAAC;YAC5B,IAAI,KAAK,GAAG,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;YAC9B,IAAI,KAAK,KAAK,CAAC;gBAAE,KAAK,GAAG,CAAC,CAAC,CAAC,gDAAgD;YAC5E,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC;QACtF,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,MAAM,GACV,oMAAoM,CAAC;AAEvM,SAAS,cAAc,CAAC,IAAY;IAClC,OAAO,IAAI;SACR,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,KAAK,CAAC,mBAAmB,CAAC;SAC1B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAeD,oFAAoF;AACpF,MAAM,UAAU,iBAAiB,CAC/B,GAA4D,EAC5D,GAAS;IAET,MAAM,SAAS,GAAG,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC3C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAExC,4EAA4E;IAC5E,IAAI,IAAI,GAAwC,IAAI,CAAC;IACrD,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1B,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,MAAM,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,GAAG;YAAE,KAAK,IAAI,CAAC,CAAC;QACpB,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;YAAE,KAAK,IAAI,CAAC,CAAC;QAC/B,IAAI,gBAAgB,CAAC,CAAC,EAAE,GAAG,CAAC;YAAE,KAAK,IAAI,CAAC,CAAC;QACzC,IAAI,KAAK,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;YAAE,IAAI,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC;IACtE,CAAC;IACD,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAEvB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACvE,MAAM,GAAG,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC7E,IAAI,MAAM,GAA+B,MAAM,CAAC;IAChD,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC;QACjF,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;IAC7D,CAAC;IAED,gFAAgF;IAChF,kEAAkE;IAClE,MAAM,QAAQ,GAAG,kFAAkF,CAAC,IAAI,CACtG,IAAI,CAAC,CAAC,CACP,CAAC;IACF,OAAO;QACL,IAAI;QACJ,MAAM,EAAE,GAAG,CAAC,IAAI;QAChB,MAAM,EAAE,GAAG,EAAE,IAAI,IAAI,IAAI;QACzB,MAAM;QACN,SAAS,EAAE,GAAG,EAAE,MAAM,IAAI,IAAI;QAC9B,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY;QACjD,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;QAChC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;KAC5B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export declare const DEFAULT_API_URL = "https://app.radmail.ai";
|
|
2
|
+
export declare const API_KEYS_URL = "https://app.radmail.ai/settings/api-keys";
|
|
3
|
+
export interface ConnectedConfig {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
apiUrl: string;
|
|
6
|
+
}
|
|
7
|
+
/** Connected mode is ON iff RADMAIL_API_KEY is set. RADMAIL_API_URL overrides the host. */
|
|
8
|
+
export declare function getConnectedConfig(env?: NodeJS.ProcessEnv): ConnectedConfig | null;
|
|
9
|
+
export type ConnectedErrorKind = "auth" | "entitlement" | "timeout" | "network" | "api";
|
|
10
|
+
/** A typed, honest, key-free failure. The message is safe to show the agent verbatim. */
|
|
11
|
+
export declare class ConnectedApiError extends Error {
|
|
12
|
+
readonly kind: ConnectedErrorKind;
|
|
13
|
+
readonly status: number | null;
|
|
14
|
+
constructor(kind: ConnectedErrorKind, message: string, status?: number | null);
|
|
15
|
+
}
|
|
16
|
+
type FetchLike = (url: string, init: RequestInit) => Promise<Response>;
|
|
17
|
+
/** Test-only: inject a mock fetch (pass null to restore global fetch). */
|
|
18
|
+
export declare function __setFetchForTests(f: FetchLike | null): void;
|
|
19
|
+
/**
|
|
20
|
+
* GET a v1 API path with query params. Throws ConnectedApiError on any failure —
|
|
21
|
+
* never returns partial/fabricated data. Error messages never contain the key.
|
|
22
|
+
*/
|
|
23
|
+
export declare function apiGet(path: string, params: Record<string, string | number | undefined>, cfg: ConnectedConfig): Promise<Record<string, unknown>>;
|
|
24
|
+
/** One hit from GET /api/v1/search. Fields are UNTRUSTED real-inbox content until tainted. */
|
|
25
|
+
export interface RemoteSearchHit {
|
|
26
|
+
id?: string;
|
|
27
|
+
receivedAt?: string;
|
|
28
|
+
from?: string;
|
|
29
|
+
fromName?: string | null;
|
|
30
|
+
subject?: string | null;
|
|
31
|
+
classification?: string | null;
|
|
32
|
+
classificationSource?: string | null;
|
|
33
|
+
isSpam?: boolean;
|
|
34
|
+
needsOwnerEyes?: boolean;
|
|
35
|
+
counterparty?: string | null;
|
|
36
|
+
threadId?: string | null;
|
|
37
|
+
snippet?: string | null;
|
|
38
|
+
matchedIn?: string | string[] | null;
|
|
39
|
+
}
|
|
40
|
+
export interface RemotePagination {
|
|
41
|
+
limit?: number;
|
|
42
|
+
offset?: number;
|
|
43
|
+
total?: number;
|
|
44
|
+
hasMore?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface RemoteSearchParams {
|
|
47
|
+
query: string;
|
|
48
|
+
limit?: number;
|
|
49
|
+
from?: string;
|
|
50
|
+
after?: string;
|
|
51
|
+
before?: string;
|
|
52
|
+
}
|
|
53
|
+
/** GET /api/v1/search — search the user's real inbox. */
|
|
54
|
+
export declare function searchInbox(p: RemoteSearchParams, cfg: ConnectedConfig): Promise<{
|
|
55
|
+
hits: RemoteSearchHit[];
|
|
56
|
+
pagination: RemotePagination | null;
|
|
57
|
+
}>;
|
|
58
|
+
/** Full email detail from GET /api/v1/emails/{id} — includes textBody. */
|
|
59
|
+
export interface RemoteEmailDetail extends RemoteSearchHit {
|
|
60
|
+
textBody?: string | null;
|
|
61
|
+
}
|
|
62
|
+
/** GET /api/v1/emails/{id} — fetch one full email (incl. textBody). */
|
|
63
|
+
export declare function getEmail(id: string, cfg: ConnectedConfig): Promise<RemoteEmailDetail | null>;
|
|
64
|
+
/** Shared pagination params for the list endpoints. */
|
|
65
|
+
export interface RemotePageParams {
|
|
66
|
+
limit?: number;
|
|
67
|
+
offset?: number;
|
|
68
|
+
}
|
|
69
|
+
/** One item from GET /api/v1/right-now — the user's REAL "can't-miss" lane.
|
|
70
|
+
* Fields are UNTRUSTED real-inbox content until tainted at the tool boundary. */
|
|
71
|
+
export interface RemoteRightNowItem {
|
|
72
|
+
id?: string;
|
|
73
|
+
receivedAt?: string;
|
|
74
|
+
from?: string;
|
|
75
|
+
fromName?: string | null;
|
|
76
|
+
subject?: string | null;
|
|
77
|
+
classification?: string | null;
|
|
78
|
+
classificationSource?: string | null;
|
|
79
|
+
isSpam?: boolean;
|
|
80
|
+
needsOwnerEyes?: boolean;
|
|
81
|
+
counterparty?: string | null;
|
|
82
|
+
threadId?: string | null;
|
|
83
|
+
importance?: number;
|
|
84
|
+
urgency?: number;
|
|
85
|
+
band?: string;
|
|
86
|
+
reasons?: string[];
|
|
87
|
+
}
|
|
88
|
+
/** GET /api/v1/right-now — the ranked Right Now lane of the user's real inbox. */
|
|
89
|
+
export declare function getRightNow(p: RemotePageParams, cfg: ConnectedConfig): Promise<{
|
|
90
|
+
items: RemoteRightNowItem[];
|
|
91
|
+
pagination: RemotePagination | null;
|
|
92
|
+
}>;
|
|
93
|
+
/** One commitment from GET /api/v1/commitments. party / action / duePhrase are
|
|
94
|
+
* UNTRUSTED real-mail-derived text until tainted at the tool boundary. */
|
|
95
|
+
export interface RemoteCommitment {
|
|
96
|
+
id?: string;
|
|
97
|
+
direction?: "owed_by_us" | "owed_to_us" | string;
|
|
98
|
+
party?: string | null;
|
|
99
|
+
action?: string | null;
|
|
100
|
+
actionType?: string | null;
|
|
101
|
+
dueDate?: string | null;
|
|
102
|
+
duePhrase?: string | null;
|
|
103
|
+
state?: string | null;
|
|
104
|
+
confidence?: number | null;
|
|
105
|
+
counterpartyEmail?: string | null;
|
|
106
|
+
}
|
|
107
|
+
/** GET /api/v1/commitments — the user's real open promises (both directions). */
|
|
108
|
+
export declare function getCommitments(p: RemotePageParams, cfg: ConnectedConfig): Promise<{
|
|
109
|
+
items: RemoteCommitment[];
|
|
110
|
+
pagination: RemotePagination | null;
|
|
111
|
+
}>;
|
|
112
|
+
export {};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Connected-mode client — the READ-ONLY bridge to the user's REAL RadMail inbox
|
|
2
|
+
// (the app.radmail.ai v1 REST API, Bearer `tmk_...` API key).
|
|
3
|
+
//
|
|
4
|
+
// Presence of RADMAIL_API_KEY switches `search` / `list_right_now` /
|
|
5
|
+
// `list_commitments` (when `messages` is omitted) and `read_email` from the
|
|
6
|
+
// in-memory sandbox to the live inbox. Deliberately tiny:
|
|
7
|
+
//
|
|
8
|
+
// · global fetch, 10s timeout, at most ONE retry (plain network errors only —
|
|
9
|
+
// never on a timeout, never on an HTTP status)
|
|
10
|
+
// · the API key is NEVER logged, echoed, or included in any response/error
|
|
11
|
+
// · fail-closed: any auth / entitlement / timeout / parse problem surfaces as
|
|
12
|
+
// a typed ConnectedApiError — results are never fabricated
|
|
13
|
+
// · READ-ONLY by construction: this client only issues GETs. There is no
|
|
14
|
+
// connected send / draft / mutate surface, so the BEC hard-stop contract
|
|
15
|
+
// (money / changed-banking / first-contact / decision / injection =
|
|
16
|
+
// human-only forever) is preserved exactly as in the sandbox.
|
|
17
|
+
//
|
|
18
|
+
// Taint discipline lives at the tool boundary (src/tools.ts): every field this
|
|
19
|
+
// client returns that derives from real email content is wrapped with taint()
|
|
20
|
+
// before it reaches the consuming agent.
|
|
21
|
+
export const DEFAULT_API_URL = "https://app.radmail.ai";
|
|
22
|
+
export const API_KEYS_URL = "https://app.radmail.ai/settings/api-keys";
|
|
23
|
+
const TIMEOUT_MS = 10_000;
|
|
24
|
+
/** Connected mode is ON iff RADMAIL_API_KEY is set. RADMAIL_API_URL overrides the host. */
|
|
25
|
+
export function getConnectedConfig(env = process.env) {
|
|
26
|
+
const apiKey = env.RADMAIL_API_KEY?.trim();
|
|
27
|
+
if (!apiKey)
|
|
28
|
+
return null;
|
|
29
|
+
const apiUrl = (env.RADMAIL_API_URL?.trim() || DEFAULT_API_URL).replace(/\/+$/, "");
|
|
30
|
+
return { apiKey, apiUrl };
|
|
31
|
+
}
|
|
32
|
+
/** A typed, honest, key-free failure. The message is safe to show the agent verbatim. */
|
|
33
|
+
export class ConnectedApiError extends Error {
|
|
34
|
+
kind;
|
|
35
|
+
status;
|
|
36
|
+
constructor(kind, message, status = null) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.name = "ConnectedApiError";
|
|
39
|
+
this.kind = kind;
|
|
40
|
+
this.status = status;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
let fetchImpl = (url, init) => globalThis.fetch(url, init);
|
|
44
|
+
/** Test-only: inject a mock fetch (pass null to restore global fetch). */
|
|
45
|
+
export function __setFetchForTests(f) {
|
|
46
|
+
fetchImpl = f ?? ((url, init) => globalThis.fetch(url, init));
|
|
47
|
+
}
|
|
48
|
+
function isAbortError(e) {
|
|
49
|
+
return e instanceof Error && e.name === "AbortError";
|
|
50
|
+
}
|
|
51
|
+
async function fetchOnce(url, apiKey) {
|
|
52
|
+
const ctrl = new AbortController();
|
|
53
|
+
const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
|
|
54
|
+
try {
|
|
55
|
+
return await fetchImpl(url, {
|
|
56
|
+
method: "GET",
|
|
57
|
+
headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
|
|
58
|
+
signal: ctrl.signal,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* GET a v1 API path with query params. Throws ConnectedApiError on any failure —
|
|
67
|
+
* never returns partial/fabricated data. Error messages never contain the key.
|
|
68
|
+
*/
|
|
69
|
+
export async function apiGet(path, params, cfg) {
|
|
70
|
+
const url = new URL(`${cfg.apiUrl}${path}`);
|
|
71
|
+
for (const [k, v] of Object.entries(params)) {
|
|
72
|
+
if (v !== undefined && v !== "")
|
|
73
|
+
url.searchParams.set(k, String(v));
|
|
74
|
+
}
|
|
75
|
+
const timeoutError = () => new ConnectedApiError("timeout", `The RadMail API did not respond within ${TIMEOUT_MS / 1000}s (GET ${path}). ` +
|
|
76
|
+
`Fail-closed: no results. Check connectivity to ${cfg.apiUrl} and retry.`);
|
|
77
|
+
let res;
|
|
78
|
+
try {
|
|
79
|
+
res = await fetchOnce(url.toString(), cfg.apiKey);
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
if (isAbortError(e))
|
|
83
|
+
throw timeoutError();
|
|
84
|
+
// One retry, network errors only.
|
|
85
|
+
try {
|
|
86
|
+
res = await fetchOnce(url.toString(), cfg.apiKey);
|
|
87
|
+
}
|
|
88
|
+
catch (e2) {
|
|
89
|
+
if (isAbortError(e2))
|
|
90
|
+
throw timeoutError();
|
|
91
|
+
const detail = e2 instanceof Error ? e2.message : String(e2);
|
|
92
|
+
throw new ConnectedApiError("network", `Could not reach the RadMail API at ${cfg.apiUrl} (${detail}). Fail-closed: no results.`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (res.status === 401) {
|
|
96
|
+
throw new ConnectedApiError("auth", "RadMail API key rejected (401): the key is invalid, revoked, or mistyped. " +
|
|
97
|
+
`Check the RADMAIL_API_KEY env var (keys start with tmk_) — manage keys at ${API_KEYS_URL}. ` +
|
|
98
|
+
"Fail-closed: no results.", 401);
|
|
99
|
+
}
|
|
100
|
+
if (res.status === 403) {
|
|
101
|
+
throw new ConnectedApiError("entitlement", "RadMail API key not entitled (403): the key is valid but the account's plan or the key's scope " +
|
|
102
|
+
`does not allow this operation. Review the plan/key at ${API_KEYS_URL}. Fail-closed: no results.`, 403);
|
|
103
|
+
}
|
|
104
|
+
if (!res.ok) {
|
|
105
|
+
throw new ConnectedApiError("api", `RadMail API error (HTTP ${res.status}) on GET ${path}. Fail-closed: no results fabricated. Retry, or contact security@radmail.ai if it persists.`, res.status);
|
|
106
|
+
}
|
|
107
|
+
let body;
|
|
108
|
+
try {
|
|
109
|
+
body = await res.json();
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
throw new ConnectedApiError("api", `RadMail API returned a non-JSON response on GET ${path} (HTTP ${res.status}). Fail-closed: no results.`, res.status);
|
|
113
|
+
}
|
|
114
|
+
const obj = body;
|
|
115
|
+
if (!obj || obj.ok !== true) {
|
|
116
|
+
const apiMsg = obj && typeof obj.error === "string" ? obj.error : "unknown error";
|
|
117
|
+
throw new ConnectedApiError("api", `RadMail API reported an error on GET ${path}: ${apiMsg}. Fail-closed: no results fabricated.`, res.status);
|
|
118
|
+
}
|
|
119
|
+
return obj;
|
|
120
|
+
}
|
|
121
|
+
/** GET /api/v1/search — search the user's real inbox. */
|
|
122
|
+
export async function searchInbox(p, cfg) {
|
|
123
|
+
const body = await apiGet("/api/v1/search", { q: p.query, limit: p.limit, from: p.from, after: p.after, before: p.before }, cfg);
|
|
124
|
+
return {
|
|
125
|
+
hits: Array.isArray(body.data) ? body.data : [],
|
|
126
|
+
pagination: body.pagination ?? null,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/** GET /api/v1/emails/{id} — fetch one full email (incl. textBody). */
|
|
130
|
+
export async function getEmail(id, cfg) {
|
|
131
|
+
const body = await apiGet(`/api/v1/emails/${encodeURIComponent(id)}`, {}, cfg);
|
|
132
|
+
return body.data ?? null;
|
|
133
|
+
}
|
|
134
|
+
/** GET /api/v1/right-now — the ranked Right Now lane of the user's real inbox. */
|
|
135
|
+
export async function getRightNow(p, cfg) {
|
|
136
|
+
const body = await apiGet("/api/v1/right-now", { limit: p.limit, offset: p.offset }, cfg);
|
|
137
|
+
return {
|
|
138
|
+
items: Array.isArray(body.data) ? body.data : [],
|
|
139
|
+
pagination: body.pagination ?? null,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** GET /api/v1/commitments — the user's real open promises (both directions). */
|
|
143
|
+
export async function getCommitments(p, cfg) {
|
|
144
|
+
const body = await apiGet("/api/v1/commitments", { limit: p.limit, offset: p.offset }, cfg);
|
|
145
|
+
return {
|
|
146
|
+
items: Array.isArray(body.data) ? body.data : [],
|
|
147
|
+
pagination: body.pagination ?? null,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=connected.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connected.js","sourceRoot":"","sources":["../../../src/lib/connected.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const DEFAULT_SINK_URL = "https://app.radmail.ai/api/mcp-demand";
|
|
2
|
+
export type DemandEventType = "call" | "need" | "capability";
|
|
3
|
+
export interface DemandEvent {
|
|
4
|
+
event: DemandEventType;
|
|
5
|
+
tool?: string;
|
|
6
|
+
agentId?: string;
|
|
7
|
+
note?: string;
|
|
8
|
+
}
|
|
9
|
+
type FetchLike = (url: string, init: RequestInit) => Promise<Response>;
|
|
10
|
+
/** Test-only: inject a mock fetch (pass null to restore global fetch). */
|
|
11
|
+
export declare function __setDemandFetchForTests(f: FetchLike | null): void;
|
|
12
|
+
/** Telemetry is ON unless RADMAIL_TELEMETRY=off (case-insensitive). */
|
|
13
|
+
export declare function telemetryEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
14
|
+
/** The safe api-key prefix (`tmk_live_` + 4 chars) — or null. NEVER the key. */
|
|
15
|
+
export declare function safeKeyPrefix(env?: NodeJS.ProcessEnv): string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Fire-and-forget: mirror one demand event to the durable sink. Returns void
|
|
18
|
+
* immediately — the POST runs as a detached promise with a 3s abort and every
|
|
19
|
+
* failure (network, HTTP status, sync throw) swallowed. Source is
|
|
20
|
+
* `sandbox-package` (zero-auth sandbox engine) or `package` (connected mode).
|
|
21
|
+
*/
|
|
22
|
+
export declare function emitDemandEvent(ev: DemandEvent, env?: NodeJS.ProcessEnv): void;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Durable demand-signal emitter — mirrors each in-memory learning record
|
|
2
|
+
// (src/lib/learning.ts) to RadMail's public sink (POST /api/mcp-demand) so
|
|
3
|
+
// demand survives cold starts and the roadmap can actually see it.
|
|
4
|
+
//
|
|
5
|
+
// CONTRACT (telemetry must never break a tool call):
|
|
6
|
+
// · fire-and-forget — a detached promise; the hot path never awaits it
|
|
7
|
+
// · 3s timeout, ALL errors swallowed silently (offline, DNS, 4xx/5xx, throw)
|
|
8
|
+
// · OPT-OUT: RADMAIL_TELEMETRY=off disables it entirely
|
|
9
|
+
// · WHAT'S SENT: tool name, event type ('call' | 'need' | 'capability'),
|
|
10
|
+
// the need/capability text the agent explicitly submitted, optional agent
|
|
11
|
+
// id — call STRUCTURE only.
|
|
12
|
+
// · WHAT'S NEVER SENT: email content, message batches, queries, results —
|
|
13
|
+
// and NEVER the API key. In connected mode we transmit only the safe
|
|
14
|
+
// display prefix (`tmk_live_` + first 4 chars of the random part) so the
|
|
15
|
+
// sink can distinguish connected-mode adoption; the key itself never
|
|
16
|
+
// leaves the process.
|
|
17
|
+
import { getConnectedConfig } from "./connected.js";
|
|
18
|
+
export const DEFAULT_SINK_URL = "https://app.radmail.ai/api/mcp-demand";
|
|
19
|
+
const TIMEOUT_MS = 3_000;
|
|
20
|
+
// Server-side caps (see radmail src/lib/mcp-demand/validate.ts) — clamp here
|
|
21
|
+
// too so an oversized note degrades to a truncated signal, not a silent 400.
|
|
22
|
+
const TOOL_MAX = 60;
|
|
23
|
+
const AGENT_ID_MAX = 80;
|
|
24
|
+
const NOTE_MAX = 500;
|
|
25
|
+
let fetchImpl = (url, init) => globalThis.fetch(url, init);
|
|
26
|
+
/** Test-only: inject a mock fetch (pass null to restore global fetch). */
|
|
27
|
+
export function __setDemandFetchForTests(f) {
|
|
28
|
+
fetchImpl = f ?? ((url, init) => globalThis.fetch(url, init));
|
|
29
|
+
}
|
|
30
|
+
/** Telemetry is ON unless RADMAIL_TELEMETRY=off (case-insensitive). */
|
|
31
|
+
export function telemetryEnabled(env = process.env) {
|
|
32
|
+
return (env.RADMAIL_TELEMETRY ?? "").trim().toLowerCase() !== "off";
|
|
33
|
+
}
|
|
34
|
+
/** The safe api-key prefix (`tmk_live_` + 4 chars) — or null. NEVER the key. */
|
|
35
|
+
export function safeKeyPrefix(env = process.env) {
|
|
36
|
+
const cfg = getConnectedConfig(env);
|
|
37
|
+
if (!cfg)
|
|
38
|
+
return null;
|
|
39
|
+
const m = /^(tmk_live_)([0-9a-f]{4})/i.exec(cfg.apiKey);
|
|
40
|
+
return m ? `${m[1]}${m[2]}` : null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Fire-and-forget: mirror one demand event to the durable sink. Returns void
|
|
44
|
+
* immediately — the POST runs as a detached promise with a 3s abort and every
|
|
45
|
+
* failure (network, HTTP status, sync throw) swallowed. Source is
|
|
46
|
+
* `sandbox-package` (zero-auth sandbox engine) or `package` (connected mode).
|
|
47
|
+
*/
|
|
48
|
+
export function emitDemandEvent(ev, env = process.env) {
|
|
49
|
+
try {
|
|
50
|
+
if (!telemetryEnabled(env))
|
|
51
|
+
return;
|
|
52
|
+
const connected = getConnectedConfig(env) !== null;
|
|
53
|
+
const body = {
|
|
54
|
+
source: connected ? "package" : "sandbox-package",
|
|
55
|
+
event: ev.event,
|
|
56
|
+
};
|
|
57
|
+
if (ev.tool)
|
|
58
|
+
body.tool = ev.tool.slice(0, TOOL_MAX);
|
|
59
|
+
if (ev.agentId)
|
|
60
|
+
body.agent_id = ev.agentId.slice(0, AGENT_ID_MAX);
|
|
61
|
+
if (ev.note)
|
|
62
|
+
body.note = ev.note.slice(0, NOTE_MAX);
|
|
63
|
+
const headers = { "content-type": "application/json" };
|
|
64
|
+
if (connected) {
|
|
65
|
+
// Only ever the truncated display prefix — the server stores it as
|
|
66
|
+
// api_key_prefix. The real key is NEVER put on the wire by telemetry
|
|
67
|
+
// (the sink URL is env-overridable, so sending the key would leak it).
|
|
68
|
+
const prefix = safeKeyPrefix(env);
|
|
69
|
+
if (prefix)
|
|
70
|
+
headers.authorization = `Bearer ${prefix}`;
|
|
71
|
+
}
|
|
72
|
+
const url = env.RADMAIL_DEMAND_SINK_URL?.trim() || DEFAULT_SINK_URL;
|
|
73
|
+
// Detached promise — deliberately not awaited by any caller.
|
|
74
|
+
void fetchImpl(url, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
headers,
|
|
77
|
+
body: JSON.stringify(body),
|
|
78
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
79
|
+
}).catch(() => {
|
|
80
|
+
/* telemetry is best-effort — silence, always */
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
/* even a sync throw (bad env, no fetch) must never reach a tool call */
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=demand-sink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"demand-sink.js","sourceRoot":"","sources":["../../../src/lib/demand-sink.ts"],"names":[],"mappings":"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"}
|