@tleblancureta/proto 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/core-gateway/auth.d.ts +7 -0
  2. package/dist/core-gateway/auth.d.ts.map +1 -0
  3. package/dist/core-gateway/auth.js +12 -0
  4. package/dist/core-gateway/auth.js.map +1 -0
  5. package/dist/core-gateway/claude-runner.d.ts +10 -0
  6. package/dist/core-gateway/claude-runner.d.ts.map +1 -0
  7. package/dist/core-gateway/claude-runner.js +206 -0
  8. package/dist/core-gateway/claude-runner.js.map +1 -0
  9. package/dist/core-gateway/config.d.ts +33 -0
  10. package/dist/core-gateway/config.d.ts.map +1 -0
  11. package/dist/core-gateway/config.js +84 -0
  12. package/dist/core-gateway/config.js.map +1 -0
  13. package/dist/core-gateway/email-sender.d.ts +36 -0
  14. package/dist/core-gateway/email-sender.d.ts.map +1 -0
  15. package/dist/core-gateway/email-sender.js +121 -0
  16. package/dist/core-gateway/email-sender.js.map +1 -0
  17. package/dist/core-gateway/mail-ingester.d.ts +3 -0
  18. package/dist/core-gateway/mail-ingester.d.ts.map +1 -0
  19. package/dist/core-gateway/mail-ingester.js +294 -0
  20. package/dist/core-gateway/mail-ingester.js.map +1 -0
  21. package/dist/core-gateway/mail-router.d.ts +18 -0
  22. package/dist/core-gateway/mail-router.d.ts.map +1 -0
  23. package/dist/core-gateway/mail-router.js +37 -0
  24. package/dist/core-gateway/mail-router.js.map +1 -0
  25. package/dist/core-gateway/mail-threads.d.ts +78 -0
  26. package/dist/core-gateway/mail-threads.d.ts.map +1 -0
  27. package/dist/core-gateway/mail-threads.js +100 -0
  28. package/dist/core-gateway/mail-threads.js.map +1 -0
  29. package/dist/core-gateway/rate-limiter.d.ts +9 -0
  30. package/dist/core-gateway/rate-limiter.d.ts.map +1 -0
  31. package/dist/core-gateway/rate-limiter.js +13 -0
  32. package/dist/core-gateway/rate-limiter.js.map +1 -0
  33. package/dist/core-gateway/registry.d.ts +14 -0
  34. package/dist/core-gateway/registry.d.ts.map +1 -0
  35. package/dist/core-gateway/registry.js +83 -0
  36. package/dist/core-gateway/registry.js.map +1 -0
  37. package/dist/core-gateway/routes/admin.d.ts +3 -0
  38. package/dist/core-gateway/routes/admin.d.ts.map +1 -0
  39. package/dist/core-gateway/routes/admin.js +91 -0
  40. package/dist/core-gateway/routes/admin.js.map +1 -0
  41. package/dist/core-gateway/routes/chat.d.ts +9 -0
  42. package/dist/core-gateway/routes/chat.d.ts.map +1 -0
  43. package/dist/core-gateway/routes/chat.js +149 -0
  44. package/dist/core-gateway/routes/chat.js.map +1 -0
  45. package/dist/core-gateway/routes/cron.d.ts +13 -0
  46. package/dist/core-gateway/routes/cron.d.ts.map +1 -0
  47. package/dist/core-gateway/routes/cron.js +79 -0
  48. package/dist/core-gateway/routes/cron.js.map +1 -0
  49. package/dist/core-gateway/routes/gmail.d.ts +8 -0
  50. package/dist/core-gateway/routes/gmail.d.ts.map +1 -0
  51. package/dist/core-gateway/routes/gmail.js +57 -0
  52. package/dist/core-gateway/routes/gmail.js.map +1 -0
  53. package/dist/core-gateway/routes/health.d.ts +3 -0
  54. package/dist/core-gateway/routes/health.d.ts.map +1 -0
  55. package/dist/core-gateway/routes/health.js +7 -0
  56. package/dist/core-gateway/routes/health.js.map +1 -0
  57. package/dist/core-gateway/routes/upload.d.ts +7 -0
  58. package/dist/core-gateway/routes/upload.d.ts.map +1 -0
  59. package/dist/core-gateway/routes/upload.js +31 -0
  60. package/dist/core-gateway/routes/upload.js.map +1 -0
  61. package/dist/core-gateway/scheduler.d.ts +68 -0
  62. package/dist/core-gateway/scheduler.d.ts.map +1 -0
  63. package/dist/core-gateway/scheduler.js +252 -0
  64. package/dist/core-gateway/scheduler.js.map +1 -0
  65. package/dist/core-gateway/server.d.ts +17 -0
  66. package/dist/core-gateway/server.d.ts.map +1 -0
  67. package/dist/core-gateway/server.js +45 -0
  68. package/dist/core-gateway/server.js.map +1 -0
  69. package/dist/core-gateway/session.d.ts +18 -0
  70. package/dist/core-gateway/session.d.ts.map +1 -0
  71. package/dist/core-gateway/session.js +178 -0
  72. package/dist/core-gateway/session.js.map +1 -0
  73. package/dist/core-gateway/skills.d.ts +4 -0
  74. package/dist/core-gateway/skills.d.ts.map +1 -0
  75. package/dist/core-gateway/skills.js +100 -0
  76. package/dist/core-gateway/skills.js.map +1 -0
  77. package/dist/core-gateway/supabase.d.ts +3 -0
  78. package/dist/core-gateway/supabase.d.ts.map +1 -0
  79. package/dist/core-gateway/supabase.js +18 -0
  80. package/dist/core-gateway/supabase.js.map +1 -0
  81. package/dist/gateway.d.ts +3 -0
  82. package/dist/gateway.d.ts.map +1 -0
  83. package/dist/gateway.js +3 -0
  84. package/dist/gateway.js.map +1 -0
  85. package/package.json +21 -1
@@ -0,0 +1,294 @@
1
+ import { ImapFlow } from 'imapflow';
2
+ import { simpleParser } from 'mailparser';
3
+ import { runClaude } from './claude-runner.js';
4
+ import { sendSystemMail } from './email-sender.js';
5
+ import { createThread, findThreadByMessageId, isMessageIdProcessed, listThreadMessages, normalizeSubject, recordMessage, } from './mail-threads.js';
6
+ import { resolveCompanyByEmail } from './mail-router.js';
7
+ function loadConfig() {
8
+ const host = process.env.MAIL_IMAP_HOST || process.env.HERMES_IMAP_HOST;
9
+ if (!host)
10
+ return null;
11
+ const port = parseInt(process.env.MAIL_IMAP_PORT || process.env.HERMES_IMAP_PORT || '993', 10);
12
+ const user = process.env.MAIL_IMAP_USER || process.env.HERMES_IMAP_USER || process.env.MAIL_SMTP_USER || process.env.HERMES_SMTP_USER;
13
+ const pass = process.env.MAIL_IMAP_PASS || process.env.HERMES_IMAP_PASS || process.env.MAIL_SMTP_PASS || process.env.HERMES_SMTP_PASS;
14
+ if (!user || !pass) {
15
+ console.warn('[mail-ingester] MAIL_IMAP_HOST set but IMAP/SMTP credentials missing');
16
+ return null;
17
+ }
18
+ return {
19
+ host,
20
+ port,
21
+ secure: port === 993,
22
+ user,
23
+ pass,
24
+ pollIntervalMs: parseInt(process.env.MAIL_IMAP_POLL_MS || process.env.HERMES_IMAP_POLL_MS || '30000', 10),
25
+ };
26
+ }
27
+ let running = false;
28
+ let timer = null;
29
+ export function startMailIngester() {
30
+ const config = loadConfig();
31
+ if (!config) {
32
+ console.log('[mail-ingester] disabled (no MAIL_IMAP_HOST configured)');
33
+ return;
34
+ }
35
+ if (running)
36
+ return;
37
+ running = true;
38
+ console.log(`[mail-ingester] starting — ${config.user}@${config.host}:${config.port} every ${config.pollIntervalMs}ms`);
39
+ // Kick off an immediate first poll, then interval
40
+ const tick = async () => {
41
+ try {
42
+ await pollOnce(config);
43
+ }
44
+ catch (err) {
45
+ console.error('[mail-ingester] poll error:', err);
46
+ }
47
+ };
48
+ tick();
49
+ timer = setInterval(tick, config.pollIntervalMs);
50
+ }
51
+ export function stopMailIngester() {
52
+ if (timer)
53
+ clearInterval(timer);
54
+ timer = null;
55
+ running = false;
56
+ }
57
+ async function pollOnce(config) {
58
+ const client = new ImapFlow({
59
+ host: config.host,
60
+ port: config.port,
61
+ secure: config.secure,
62
+ auth: { user: config.user, pass: config.pass },
63
+ logger: false,
64
+ });
65
+ try {
66
+ await client.connect();
67
+ }
68
+ catch (err) {
69
+ console.error('[mail-ingester] connect failed:', err?.message || err);
70
+ return;
71
+ }
72
+ try {
73
+ const lock = await client.getMailboxLock('INBOX');
74
+ try {
75
+ // Search for unseen messages
76
+ const uids = await client.search({ seen: false }, { uid: true });
77
+ if (!uids || uids.length === 0)
78
+ return;
79
+ for (const uid of uids) {
80
+ try {
81
+ const msg = await client.fetchOne(String(uid), { source: true }, { uid: true });
82
+ if (!msg || !msg.source)
83
+ continue;
84
+ const parsed = await simpleParser(msg.source);
85
+ const processed = await processInboundMail(parsed);
86
+ if (processed) {
87
+ // Mark seen only if we handled it cleanly
88
+ await client.messageFlagsAdd(String(uid), ['\\Seen'], { uid: true });
89
+ }
90
+ }
91
+ catch (err) {
92
+ console.error(`[mail-ingester] failed to process uid=${uid}:`, err?.message || err);
93
+ }
94
+ }
95
+ }
96
+ finally {
97
+ lock.release();
98
+ }
99
+ }
100
+ finally {
101
+ try {
102
+ await client.logout();
103
+ }
104
+ catch { }
105
+ }
106
+ }
107
+ /**
108
+ * Core logic per inbound mail. Returns true if it should be marked as seen.
109
+ */
110
+ async function processInboundMail(parsed) {
111
+ const messageId = parsed.messageId;
112
+ if (!messageId) {
113
+ console.warn('[mail-ingester] skipping message without Message-ID');
114
+ return true; // mark seen anyway, nothing we can do
115
+ }
116
+ // Dedup
117
+ if (await isMessageIdProcessed(messageId)) {
118
+ return true;
119
+ }
120
+ // Skip auto-replies, bounces, mailer-daemon
121
+ const headers = parsed.headers;
122
+ if (headers.get('auto-submitted') && String(headers.get('auto-submitted')) !== 'no') {
123
+ console.log(`[mail-ingester] skipping auto-submitted: ${messageId}`);
124
+ return true;
125
+ }
126
+ const fromAddr = parsed.from?.value?.[0]?.address?.toLowerCase();
127
+ if (!fromAddr || /mailer-daemon|postmaster|no-?reply/i.test(fromAddr)) {
128
+ console.log(`[mail-ingester] skipping system sender: ${fromAddr}`);
129
+ return true;
130
+ }
131
+ // Route to company via allowlist
132
+ const route = await resolveCompanyByEmail(fromAddr);
133
+ if (!route) {
134
+ console.log(`[mail-ingester] no company match for ${fromAddr}, dropping`);
135
+ return true; // treat as spam, mark seen
136
+ }
137
+ // Thread lookup via In-Reply-To
138
+ const inReplyTo = firstHeader(parsed.headers.get('in-reply-to'));
139
+ const references = parsed.references
140
+ ? (Array.isArray(parsed.references) ? parsed.references : [parsed.references])
141
+ : [];
142
+ const threadCandidates = [inReplyTo, ...references].filter(Boolean);
143
+ let thread = null;
144
+ for (const parentId of threadCandidates) {
145
+ thread = await findThreadByMessageId(parentId);
146
+ if (thread)
147
+ break;
148
+ }
149
+ if (thread && thread.company_id !== route.companyId) {
150
+ console.warn(`[mail-ingester] thread ${thread.id} belongs to different company — ignoring parent`);
151
+ thread = null;
152
+ }
153
+ if (!thread) {
154
+ thread = await createThread({
155
+ companyId: route.companyId,
156
+ subject: parsed.subject || null,
157
+ initiatedBy: 'in',
158
+ externalAddress: fromAddr,
159
+ });
160
+ }
161
+ const body = cleanInboundBody(parsed.text || parsed.html || '');
162
+ const subject = parsed.subject || '(sin asunto)';
163
+ const toAddr = parsed.to
164
+ ? (Array.isArray(parsed.to) ? parsed.to[0]?.value?.[0]?.address : parsed.to.value?.[0]?.address)
165
+ : '';
166
+ // Record inbound
167
+ await recordMessage({
168
+ threadId: thread.id,
169
+ companyId: route.companyId,
170
+ direction: 'in',
171
+ messageId,
172
+ inReplyTo: inReplyTo || null,
173
+ fromAddress: fromAddr,
174
+ toAddress: toAddr || 'system',
175
+ subject,
176
+ body,
177
+ });
178
+ // Build the agent prompt: prior thread history + the new inbound
179
+ const history = await listThreadMessages(thread.id, 20);
180
+ const prompt = buildAgentPrompt({
181
+ history,
182
+ newFrom: fromAddr,
183
+ newBody: body,
184
+ newSubject: subject,
185
+ });
186
+ console.log(`[mail-ingester] dispatching to agent: thread=${thread.id} from=${fromAddr}`);
187
+ // Dispatch to Claude
188
+ let agentResponse = '';
189
+ try {
190
+ const result = await runClaude({
191
+ company_id: route.companyId,
192
+ user_id: fromAddr,
193
+ message: prompt,
194
+ channel: 'email',
195
+ session_key: thread.session_key,
196
+ enabled_skills: ALL_SKILLS,
197
+ company_context: `[Mensaje recibido por email desde ${fromAddr} en el thread "${thread.subject || subject}"]`,
198
+ selected_files: [],
199
+ });
200
+ agentResponse = result.response || '(sin respuesta)';
201
+ }
202
+ catch (err) {
203
+ console.error('[mail-ingester] runClaude failed:', err?.message || err);
204
+ agentResponse = `(No se pudo procesar tu mensaje: ${err?.message || 'error interno'})`;
205
+ }
206
+ // Reply via outbound
207
+ const sendResult = await sendSystemMail({
208
+ companyId: route.companyId,
209
+ to: fromAddr,
210
+ subject: normalizeSubject(subject) || subject,
211
+ body: agentResponse,
212
+ inReplyTo: messageId,
213
+ threadId: thread.id,
214
+ sessionKey: thread.session_key,
215
+ });
216
+ if (!sendResult.ok) {
217
+ console.error('[mail-ingester] reply send failed:', sendResult.error);
218
+ }
219
+ return true;
220
+ }
221
+ import { loadSkills } from './registry.js';
222
+ function getAllSkillNames() {
223
+ try {
224
+ return loadSkills().map(s => s.name);
225
+ }
226
+ catch {
227
+ return [];
228
+ }
229
+ }
230
+ const ALL_SKILLS = getAllSkillNames();
231
+ function firstHeader(value) {
232
+ if (!value)
233
+ return null;
234
+ if (typeof value === 'string')
235
+ return value.trim();
236
+ if (Array.isArray(value))
237
+ return String(value[0]).trim();
238
+ return String(value).trim();
239
+ }
240
+ /**
241
+ * Strip quoted replies and signatures heuristically so the agent only
242
+ * sees the user's fresh content.
243
+ */
244
+ function cleanInboundBody(raw) {
245
+ if (!raw)
246
+ return '';
247
+ const lines = raw.split(/\r?\n/);
248
+ const out = [];
249
+ for (const line of lines) {
250
+ // Common quote-reply delimiters (Gmail, Outlook, iOS Mail, es)
251
+ if (/^On .+ wrote:\s*$/i.test(line))
252
+ break;
253
+ if (/^El .+ escribi[oó]:\s*$/i.test(line))
254
+ break;
255
+ if (/^-----Original Message-----/i.test(line))
256
+ break;
257
+ if (/^From:.+$/i.test(line) && out.length > 2)
258
+ break;
259
+ // Drop quote lines
260
+ if (/^>/.test(line))
261
+ continue;
262
+ out.push(line);
263
+ }
264
+ return out.join('\n').trim();
265
+ }
266
+ function buildAgentPrompt(opts) {
267
+ const parts = [];
268
+ parts.push(`Estas recibiendo un mensaje por email. Tu respuesta sera enviada de vuelta al remitente como reply automatico.`);
269
+ parts.push('');
270
+ parts.push(`Remitente: ${opts.newFrom}`);
271
+ parts.push(`Asunto: ${opts.newSubject}`);
272
+ parts.push('');
273
+ // Skip the current inbound message — it's shown separately below
274
+ const prior = opts.history.filter(m => m.body && m.body.trim().length > 0);
275
+ const prevMessages = prior.slice(0, -1);
276
+ if (prevMessages.length > 0) {
277
+ parts.push('--- Historial del thread ---');
278
+ for (const m of prevMessages) {
279
+ const who = m.direction === 'out' ? 'Assistant' : m.from_address;
280
+ parts.push(`[${who}]`);
281
+ parts.push(m.body || '');
282
+ parts.push('');
283
+ }
284
+ parts.push('--- Fin del historial ---');
285
+ parts.push('');
286
+ }
287
+ parts.push('--- Mensaje actual del usuario ---');
288
+ parts.push(opts.newBody);
289
+ parts.push('--- Fin del mensaje ---');
290
+ parts.push('');
291
+ parts.push(`Responde al usuario. Tu respuesta sera enviada como reply al thread. No incluyas headers, From/To, ni firmas — el sistema las agrega. Escribi solo el cuerpo de la respuesta, en texto plano, tono directo y util. Usa las tools MCP que necesites.`);
292
+ return parts.join('\n');
293
+ }
294
+ //# sourceMappingURL=mail-ingester.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mail-ingester.js","sourceRoot":"","sources":["../../core-gateway/mail-ingester.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Resolves an inbound sender email address to a company. This is the
3
+ * allowlist: only mails from addresses associated with a company in the
4
+ * system get processed. Anything else is dropped (treated as spam).
5
+ *
6
+ * Lookup order:
7
+ * 1. `companies.contact_email` (exact match, case-insensitive)
8
+ * 2. `profiles.email` joined through `company_users`
9
+ *
10
+ * Returns the first match or null.
11
+ */
12
+ export interface RouteResult {
13
+ companyId: string;
14
+ userId: string | null;
15
+ via: 'company_contact' | 'profile';
16
+ }
17
+ export declare function resolveCompanyByEmail(email: string): Promise<RouteResult | null>;
18
+ //# sourceMappingURL=mail-router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mail-router.d.ts","sourceRoot":"","sources":["../../core-gateway/mail-router.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,GAAG,EAAE,iBAAiB,GAAG,SAAS,CAAA;CACnC;AAED,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAoCtF"}
@@ -0,0 +1,37 @@
1
+ import { getSupabase } from './supabase.js';
2
+ export async function resolveCompanyByEmail(email) {
3
+ const db = getSupabase();
4
+ const normalized = email.trim().toLowerCase();
5
+ if (!normalized)
6
+ return null;
7
+ // 1. Company contact_email
8
+ const { data: company } = await db
9
+ .from('companies')
10
+ .select('id, contact_email')
11
+ .ilike('contact_email', normalized)
12
+ .limit(1)
13
+ .maybeSingle();
14
+ if (company) {
15
+ return { companyId: company.id, userId: null, via: 'company_contact' };
16
+ }
17
+ // 2. Profile email → company_users
18
+ const { data: profile } = await db
19
+ .from('profiles')
20
+ .select('id, email')
21
+ .ilike('email', normalized)
22
+ .limit(1)
23
+ .maybeSingle();
24
+ if (profile) {
25
+ const { data: cu } = await db
26
+ .from('company_users')
27
+ .select('company_id, user_id')
28
+ .eq('user_id', profile.id)
29
+ .limit(1)
30
+ .maybeSingle();
31
+ if (cu) {
32
+ return { companyId: cu.company_id, userId: cu.user_id, via: 'profile' };
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+ //# sourceMappingURL=mail-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mail-router.js","sourceRoot":"","sources":["../../core-gateway/mail-router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAoB3C,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,KAAa;IACvD,MAAM,EAAE,GAAG,WAAW,EAAE,CAAA;IACxB,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAC7C,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAA;IAE5B,2BAA2B;IAC3B,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE;SAC/B,IAAI,CAAC,WAAW,CAAC;SACjB,MAAM,CAAC,mBAAmB,CAAC;SAC3B,KAAK,CAAC,eAAe,EAAE,UAAU,CAAC;SAClC,KAAK,CAAC,CAAC,CAAC;SACR,WAAW,EAAE,CAAA;IAChB,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,iBAAiB,EAAE,CAAA;IACxE,CAAC;IAED,mCAAmC;IACnC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE;SAC/B,IAAI,CAAC,UAAU,CAAC;SAChB,MAAM,CAAC,WAAW,CAAC;SACnB,KAAK,CAAC,OAAO,EAAE,UAAU,CAAC;SAC1B,KAAK,CAAC,CAAC,CAAC;SACR,WAAW,EAAE,CAAA;IAChB,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,MAAM,EAAE;aAC1B,IAAI,CAAC,eAAe,CAAC;aACrB,MAAM,CAAC,qBAAqB,CAAC;aAC7B,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC;aACzB,KAAK,CAAC,CAAC,CAAC;aACR,WAAW,EAAE,CAAA;QAChB,IAAI,EAAE,EAAE,CAAC;YACP,OAAO,EAAE,SAAS,EAAE,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CAAA;QACzE,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Mail thread + message persistence. The "state" of the email-as-chat
3
+ * channel: threads map to Claude CLI session_keys so back-and-forth replies
4
+ * resume context automatically, and messages let us dedup inbound by
5
+ * Message-ID and find parents via In-Reply-To.
6
+ */
7
+ export interface MailThread {
8
+ id: string;
9
+ company_id: string;
10
+ subject: string | null;
11
+ session_key: string;
12
+ initiated_by: 'in' | 'out';
13
+ source_task_id: string | null;
14
+ external_address: string;
15
+ closed: boolean;
16
+ last_activity_at: string;
17
+ }
18
+ export interface MailMessage {
19
+ id: string;
20
+ thread_id: string;
21
+ company_id: string;
22
+ direction: 'in' | 'out';
23
+ message_id: string;
24
+ in_reply_to: string | null;
25
+ from_address: string;
26
+ to_address: string;
27
+ subject: string | null;
28
+ body: string | null;
29
+ run_id: string | null;
30
+ created_at: string;
31
+ }
32
+ /**
33
+ * RFC 5322 Message-ID generator. Format: <proto-{uuid}@{domain}>.
34
+ * Must be stable and unique so both outbound (we set it) and inbound
35
+ * dedup (we look it up) point at the same row.
36
+ */
37
+ export declare function generateMessageId(domain?: string): string;
38
+ /**
39
+ * Look up a thread by one of its message IDs (for inbound reply routing).
40
+ */
41
+ export declare function findThreadByMessageId(messageId: string): Promise<MailThread | null>;
42
+ /**
43
+ * Dedup check: has this Message-ID already been stored?
44
+ */
45
+ export declare function isMessageIdProcessed(messageId: string): Promise<boolean>;
46
+ interface CreateThreadOpts {
47
+ companyId: string;
48
+ subject: string | null;
49
+ initiatedBy: 'in' | 'out';
50
+ sourceTaskId?: string | null;
51
+ externalAddress: string;
52
+ sessionKey?: string;
53
+ }
54
+ export declare function createThread(opts: CreateThreadOpts): Promise<MailThread>;
55
+ interface RecordMessageOpts {
56
+ threadId: string;
57
+ companyId: string;
58
+ direction: 'in' | 'out';
59
+ messageId: string;
60
+ inReplyTo?: string | null;
61
+ fromAddress: string;
62
+ toAddress: string;
63
+ subject?: string | null;
64
+ body?: string | null;
65
+ runId?: string | null;
66
+ }
67
+ export declare function recordMessage(opts: RecordMessageOpts): Promise<MailMessage>;
68
+ /**
69
+ * Fetch prior messages in a thread ordered chronologically — used to build
70
+ * the conversation history prompt for the agent on inbound replies.
71
+ */
72
+ export declare function listThreadMessages(threadId: string, limit?: number): Promise<MailMessage[]>;
73
+ /**
74
+ * Strip "Re: " / "Fwd: " prefixes so the thread subject stays stable.
75
+ */
76
+ export declare function normalizeSubject(subject: string | null | undefined): string | null;
77
+ export {};
78
+ //# sourceMappingURL=mail-threads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mail-threads.d.ts","sourceRoot":"","sources":["../../core-gateway/mail-threads.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AAEH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,IAAI,GAAG,KAAK,CAAA;IAC1B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,gBAAgB,EAAE,MAAM,CAAA;IACxB,MAAM,EAAE,OAAO,CAAA;IACf,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,IAAI,GAAG,KAAK,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,SAAU,GAAG,MAAM,CAE1D;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAUzF;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ9E;AAED,UAAU,gBAAgB;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,WAAW,EAAE,IAAI,GAAG,KAAK,CAAA;IACzB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAiB9E;AAED,UAAU,iBAAiB;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,IAAI,GAAG,KAAK,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAoBjF;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAS7F;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAGlF"}
@@ -0,0 +1,100 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { getSupabase } from './supabase.js';
3
+ /**
4
+ * RFC 5322 Message-ID generator. Format: <proto-{uuid}@{domain}>.
5
+ * Must be stable and unique so both outbound (we set it) and inbound
6
+ * dedup (we look it up) point at the same row.
7
+ */
8
+ export function generateMessageId(domain = 'proto') {
9
+ return `<proto-${randomUUID()}@${domain}>`;
10
+ }
11
+ /**
12
+ * Look up a thread by one of its message IDs (for inbound reply routing).
13
+ */
14
+ export async function findThreadByMessageId(messageId) {
15
+ const db = getSupabase();
16
+ const { data } = await db
17
+ .from('mail_messages')
18
+ .select('thread_id, mail_threads!inner(*)')
19
+ .eq('message_id', messageId)
20
+ .maybeSingle();
21
+ // Supabase returns nested relation as object
22
+ const thread = data?.mail_threads;
23
+ return thread ?? null;
24
+ }
25
+ /**
26
+ * Dedup check: has this Message-ID already been stored?
27
+ */
28
+ export async function isMessageIdProcessed(messageId) {
29
+ const db = getSupabase();
30
+ const { data } = await db
31
+ .from('mail_messages')
32
+ .select('id')
33
+ .eq('message_id', messageId)
34
+ .maybeSingle();
35
+ return !!data;
36
+ }
37
+ export async function createThread(opts) {
38
+ const db = getSupabase();
39
+ const sessionKey = opts.sessionKey || `mail-thread-${randomUUID()}`;
40
+ const { data, error } = await db
41
+ .from('mail_threads')
42
+ .insert({
43
+ company_id: opts.companyId,
44
+ subject: normalizeSubject(opts.subject),
45
+ session_key: sessionKey,
46
+ initiated_by: opts.initiatedBy,
47
+ source_task_id: opts.sourceTaskId ?? null,
48
+ external_address: opts.externalAddress,
49
+ })
50
+ .select('*')
51
+ .single();
52
+ if (error || !data)
53
+ throw new Error(`createThread failed: ${error?.message}`);
54
+ return data;
55
+ }
56
+ export async function recordMessage(opts) {
57
+ const db = getSupabase();
58
+ const { data, error } = await db
59
+ .from('mail_messages')
60
+ .insert({
61
+ thread_id: opts.threadId,
62
+ company_id: opts.companyId,
63
+ direction: opts.direction,
64
+ message_id: opts.messageId,
65
+ in_reply_to: opts.inReplyTo ?? null,
66
+ from_address: opts.fromAddress,
67
+ to_address: opts.toAddress,
68
+ subject: opts.subject ?? null,
69
+ body: opts.body ?? null,
70
+ run_id: opts.runId ?? null,
71
+ })
72
+ .select('*')
73
+ .single();
74
+ if (error || !data)
75
+ throw new Error(`recordMessage failed: ${error?.message}`);
76
+ return data;
77
+ }
78
+ /**
79
+ * Fetch prior messages in a thread ordered chronologically — used to build
80
+ * the conversation history prompt for the agent on inbound replies.
81
+ */
82
+ export async function listThreadMessages(threadId, limit = 20) {
83
+ const db = getSupabase();
84
+ const { data } = await db
85
+ .from('mail_messages')
86
+ .select('*')
87
+ .eq('thread_id', threadId)
88
+ .order('created_at', { ascending: true })
89
+ .limit(limit);
90
+ return (data || []);
91
+ }
92
+ /**
93
+ * Strip "Re: " / "Fwd: " prefixes so the thread subject stays stable.
94
+ */
95
+ export function normalizeSubject(subject) {
96
+ if (!subject)
97
+ return null;
98
+ return subject.replace(/^\s*(re|fwd|fw):\s*/gi, '').trim() || null;
99
+ }
100
+ //# sourceMappingURL=mail-threads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mail-threads.js","sourceRoot":"","sources":["../../core-gateway/mail-threads.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Simple in-memory rate limiter per company.
3
+ * Max 20 messages per company per hour.
4
+ */
5
+ export declare function checkRateLimit(_companyId: string): {
6
+ allowed: boolean;
7
+ retryAfterMs?: number;
8
+ };
9
+ //# sourceMappingURL=rate-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../core-gateway/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAI9F"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Simple in-memory rate limiter per company.
3
+ * Max 20 messages per company per hour.
4
+ */
5
+ const WINDOW_MS = 60 * 60 * 1000; // 1 hour
6
+ const MAX_REQUESTS = 20;
7
+ const windows = new Map();
8
+ export function checkRateLimit(_companyId) {
9
+ // Disabled — see windows/MAX_REQUESTS above for the original gate.
10
+ // Re-enable when multi-tenant launch needs throttling.
11
+ return { allowed: true };
12
+ }
13
+ //# sourceMappingURL=rate-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../core-gateway/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,SAAS,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA,CAAC,SAAS;AAC1C,MAAM,YAAY,GAAG,EAAE,CAAA;AAEvB,MAAM,OAAO,GAAG,IAAI,GAAG,EAA4C,CAAA;AAEnE,MAAM,UAAU,cAAc,CAAC,UAAkB;IAC/C,mEAAmE;IACnE,uDAAuD;IACvD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;AAC1B,CAAC"}
@@ -0,0 +1,14 @@
1
+ export interface Skill {
2
+ name: string;
3
+ description: string | null;
4
+ mcp_tools: string[];
5
+ depends: string[];
6
+ filePath: string;
7
+ }
8
+ export interface Agent {
9
+ name: string;
10
+ filePath: string;
11
+ }
12
+ export declare function loadSkills(): Skill[];
13
+ export declare function loadAgents(): Agent[];
14
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../core-gateway/registry.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;CACjB;AAgED,wBAAgB,UAAU,IAAI,KAAK,EAAE,CAGpC;AAED,wBAAgB,UAAU,IAAI,KAAK,EAAE,CAGpC"}