@flowselections/mailbox-orders 1.0.47 → 1.0.50

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.
@@ -13,6 +13,7 @@ import { detectLoadCarrierInText, isLoadCarrierAllowed, loadCarrierLabel, normal
13
13
  import { classifyMessage, extractLatestPart } from "./mailbox-filter.js";
14
14
  import { normalizeUnitKind, toPlants, unitPricePerPlant } from "./mailbox-units.js";
15
15
  import { matchLine, rankCandidates } from "./mailbox-match.js";
16
+ import { classifyEventFolder } from "./mailbox-folders.js";
16
17
  /**
17
18
  * Ladingdragers komen live uit de Logistics Engine — nooit cachen, nooit een
18
19
  * eigen lijst bijhouden.
@@ -713,6 +714,7 @@ async function analyzeAndBuild(admin, msg, customerId, text, s) {
713
714
  },
714
715
  };
715
716
  }
717
+ export const NO_AI_AGENT_NOTE = "Automatisch uitlezen staat uit: koppel een AI-agent bij Instellingen › AI-Authenticatie";
716
718
  /**
717
719
  * Maakt exact één voorstel per bericht. Eerst het voorfilter (zonder AI), dan
718
720
  * het vastleggen van de analyse (één tegelijk + limieten), en pas dan de AI.
@@ -757,6 +759,33 @@ export async function createStubProposal(admin, messageId, fromEmail, opts = {})
757
759
  else {
758
760
  await setMsg({ filter_outcome: msg.filter_outcome ?? "analyseren", filter_reason: "Handmatig gestart" });
759
761
  }
762
+ // Geen AI-agent gekoppeld: toch een voorstel (zonder automatisch uitlezen), nooit stil overslaan.
763
+ {
764
+ const { currentAiFingerprint } = await import("./mailbox-ai.server.js");
765
+ const fp = await currentAiFingerprint(admin).catch(() => null);
766
+ if (fp === null) {
767
+ await setMsg({ analysis_note: NO_AI_AGENT_NOTE });
768
+ const { error: stubErr } = await admin.from("mailbox_order_proposals").insert({
769
+ message_id: messageId,
770
+ matched_customer_id: matchResult.customerId,
771
+ status: "pending",
772
+ notes: NO_AI_AGENT_NOTE,
773
+ parsed_payload: {
774
+ auto_matched_customer: matchResult.customerId !== null,
775
+ auto_match_source: matchResult.source,
776
+ auto_match_email: senderEmail || null,
777
+ auto_match_forwarded: !!latest.forwardedFrom,
778
+ auto_match_warning: matchResult.warning,
779
+ auto_match_candidates: matchResult.candidates,
780
+ stub: true,
781
+ reason: "geen_ai_agent",
782
+ },
783
+ });
784
+ if (stubErr && stubErr.code !== "23505")
785
+ throw new Error(stubErr.message);
786
+ return { status: stubErr ? "exists" : "created", reason: "geen_ai_agent" };
787
+ }
788
+ }
760
789
  const claim = await claimAnalysis(admin, messageId, opts.force === true, opts.trigger ?? "auto");
761
790
  if (!claim.ok) {
762
791
  return claim.reason === "busy" ? { status: "busy" } : { status: "waiting", reason: claim.reason };
@@ -1003,9 +1032,16 @@ export async function retryFailedAiProposals(admin, limit = 3, maxRetries = AI_M
1003
1032
  }
1004
1033
  return { retried, recovered, stillFailing };
1005
1034
  }
1006
- async function ingestSingleEvent(admin, connectionId, ev) {
1035
+ /** Een event dat niet uit een gevolgde map komt: alleen metadata vastleggen, geen inhoud. */
1036
+ async function recordIgnoredEvent(admin, eventId, connectionId, reason) {
1037
+ await admin
1038
+ .from("mailbox_ignored_events")
1039
+ .upsert({ event_id: eventId, connection_id: connectionId, reason }, { onConflict: "event_id", ignoreDuplicates: true });
1040
+ }
1041
+ async function ingestSingleEvent(admin, connectionId, ev, opts = {}) {
1007
1042
  const p = ev.payload ?? {};
1008
1043
  const externalId = ev.external_id ?? pickStr(p, "message_id", "messageId", "id") ?? ev.id;
1044
+ const providerMessageId = pickStr(p, "message_id", "messageId") ?? externalId;
1009
1045
  // Idempotent: bestaat er al een message voor deze external_id op deze connectie?
1010
1046
  if (externalId) {
1011
1047
  const { data: existing } = await admin
@@ -1020,6 +1056,17 @@ async function ingestSingleEvent(admin, connectionId, ev) {
1020
1056
  return { inserted: false, messageId: existing.id };
1021
1057
  }
1022
1058
  }
1059
+ // Ontdubbelen over koppelingen heen: dezelfde mail (bericht-id van de provider) via een andere koppeling.
1060
+ if (providerMessageId) {
1061
+ const { data: same } = await admin
1062
+ .from("mailbox_messages")
1063
+ .select("id")
1064
+ .eq("message_id", providerMessageId)
1065
+ .limit(1)
1066
+ .maybeSingle();
1067
+ if (same?.id)
1068
+ return { inserted: false, messageId: same.id };
1069
+ }
1023
1070
  const fromEmail = pickStr(p, "from_email", "fromEmail", "from.address", "from.email", "sender.address") ?? "";
1024
1071
  const fromName = pickStr(p, "from_name", "fromName", "from.name", "sender.name");
1025
1072
  const subject = pickStr(p, "subject", "title") ?? "";
@@ -1028,12 +1075,14 @@ async function ingestSingleEvent(admin, connectionId, ev) {
1028
1075
  const receivedAt = pickStr(p, "received_at", "receivedAt", "date", "internalDate") ??
1029
1076
  ev.received_at ??
1030
1077
  new Date().toISOString();
1078
+ const tooOld = isTooOld(receivedAt, opts.maxAgeDays);
1031
1079
  const { data: inserted, error: insErr } = await admin
1032
1080
  .from("mailbox_messages")
1033
1081
  .insert({
1034
1082
  external_id: externalId,
1035
- message_id: pickStr(p, "message_id", "messageId") ?? externalId,
1083
+ message_id: providerMessageId,
1036
1084
  connection_id: connectionId,
1085
+ account_email: opts.accountEmail ?? null,
1037
1086
  from_email: fromEmail,
1038
1087
  from_name: fromName,
1039
1088
  subject,
@@ -1046,11 +1095,12 @@ async function ingestSingleEvent(admin, connectionId, ev) {
1046
1095
  (Array.isArray(p?.attachments) && p.attachments.length > 0),
1047
1096
  received_at: receivedAt,
1048
1097
  status: "new",
1098
+ ...(tooOld ? { filter_outcome: "te_oud", filter_reason: "te oud bij eerste verwerking" } : {}),
1049
1099
  })
1050
1100
  .select("id")
1051
1101
  .single();
1052
1102
  if (insErr) {
1053
- // Race met cron/realtime — de andere trigger heeft 'm net ingevoegd.
1103
+ // Race met cron/realtime of dezelfde mail via een andere koppeling.
1054
1104
  if (insErr.code === "23505") {
1055
1105
  const { data: dup } = await admin
1056
1106
  .from("mailbox_messages")
@@ -1058,110 +1108,288 @@ async function ingestSingleEvent(admin, connectionId, ev) {
1058
1108
  .eq("connection_id", connectionId)
1059
1109
  .eq("external_id", externalId)
1060
1110
  .maybeSingle();
1061
- if (dup?.id)
1111
+ if (dup?.id) {
1062
1112
  await createStubProposal(admin, dup.id, fromEmail);
1063
- return { inserted: false, messageId: dup?.id ?? null };
1113
+ return { inserted: false, messageId: dup.id };
1114
+ }
1115
+ const { data: same } = await admin
1116
+ .from("mailbox_messages")
1117
+ .select("id")
1118
+ .eq("message_id", providerMessageId)
1119
+ .limit(1)
1120
+ .maybeSingle();
1121
+ return { inserted: false, messageId: same?.id ?? null };
1064
1122
  }
1065
1123
  throw new Error(insErr.message);
1066
1124
  }
1067
- await createStubProposal(admin, inserted.id, fromEmail);
1125
+ if (!tooOld)
1126
+ await createStubProposal(admin, inserted.id, fromEmail);
1068
1127
  return { inserted: true, messageId: inserted.id };
1069
1128
  }
1129
+ /** Ouder dan de ingestelde grens (standaard 14 dagen) bij eerste verwerking. */
1130
+ export function isTooOld(receivedAt, maxAgeDays, now = Date.now()) {
1131
+ const days = Number(maxAgeDays ?? 14);
1132
+ if (!receivedAt || !Number.isFinite(days) || days <= 0)
1133
+ return false;
1134
+ const t = Date.parse(receivedAt);
1135
+ if (!Number.isFinite(t))
1136
+ return false;
1137
+ return now - t > days * 86400000;
1138
+ }
1139
+ /** Nachtelijk (03:00–03:30 Amsterdam) één portie oude rondes opruimen. */
1140
+ function inPruneWindow(now = new Date()) {
1141
+ const parts = new Intl.DateTimeFormat("en-GB", { timeZone: "Europe/Amsterdam", hour: "2-digit", minute: "2-digit", hour12: false })
1142
+ .formatToParts(now);
1143
+ const h = Number(parts.find((x) => x.type === "hour")?.value);
1144
+ const m = Number(parts.find((x) => x.type === "minute")?.value);
1145
+ return h === 3 && m < 30;
1146
+ }
1147
+ async function loadConnections(admin, ids) {
1148
+ const out = new Map();
1149
+ if (ids.length === 0)
1150
+ return out;
1151
+ // select("*") zodat een oudere e-mailmodule zonder watch_scope geen fout geeft.
1152
+ const { data } = await admin.from("email_auth_connections").select("*").in("id", ids);
1153
+ for (const c of (Array.isArray(data) ? data : [])) {
1154
+ out.set(c.id, {
1155
+ id: c.id,
1156
+ provider: c.provider ?? null,
1157
+ account_email: c.account_email ?? null,
1158
+ watch_scope: c.watch_scope ?? null,
1159
+ graph_subscription_id: c.graph_subscription_id ?? null,
1160
+ });
1161
+ }
1162
+ return out;
1163
+ }
1164
+ async function loadWatchFolders(admin, ids) {
1165
+ const out = new Map();
1166
+ if (ids.length === 0)
1167
+ return out;
1168
+ const { data, error } = await admin.from("email_auth_watch_folders").select("*").in("connection_id", ids);
1169
+ if (error)
1170
+ return out;
1171
+ for (const f of (Array.isArray(data) ? data : [])) {
1172
+ const list = out.get(f.connection_id) ?? [];
1173
+ list.push({ folder_id: f.folder_id ?? null, folder_name: f.folder_name ?? null, graph_subscription_id: f.graph_subscription_id ?? null });
1174
+ out.set(f.connection_id, list);
1175
+ }
1176
+ return out;
1177
+ }
1178
+ /**
1179
+ * Ontkoppelde adressen met onverwerkte events: alleen events ontvangen vóór de
1180
+ * laatste ronde waarin die koppeling nog actief was, worden alsnog verwerkt.
1181
+ */
1182
+ async function leftoverConnections(admin, activeIds) {
1183
+ const out = new Map();
1184
+ let q = admin.from("email_auth_events").select("connection_id").is("consumed_at", null).limit(1000);
1185
+ if (activeIds.length > 0)
1186
+ q = q.not("connection_id", "in", `(${activeIds.join(",")})`);
1187
+ const { data, error } = await q;
1188
+ if (error)
1189
+ throw new Error(`select email_auth_events: ${error.message}`);
1190
+ const ids = Array.from(new Set((Array.isArray(data) ? data : []).map((r) => r.connection_id).filter(Boolean)));
1191
+ for (const id of ids) {
1192
+ const { data: last } = await admin
1193
+ .from("mailbox_ingest_runs")
1194
+ .select("started_at")
1195
+ .eq("connection_id", id)
1196
+ .order("started_at", { ascending: false })
1197
+ .limit(1)
1198
+ .maybeSingle();
1199
+ if (last?.started_at)
1200
+ out.set(id, last.started_at);
1201
+ }
1202
+ return out;
1203
+ }
1204
+ function plainReason(errors, sample) {
1205
+ if (errors === 0)
1206
+ return null;
1207
+ return `${errors} mail(s) konden niet worden verwerkt${sample ? `: ${sample}` : ""}`.slice(0, 500);
1208
+ }
1209
+ async function recordFailedRun(admin, trigger, connectionId, reason) {
1210
+ await admin
1211
+ .from("mailbox_ingest_runs")
1212
+ .insert({ trigger, connection_id: connectionId, status: "fout", reason, finished_at: new Date().toISOString(), errors: 1, error_sample: reason.slice(0, 500) });
1213
+ }
1070
1214
  // De volledige pipeline. Alle triggers roepen dit aan.
1071
1215
  export async function runIngest(trigger) {
1072
1216
  const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
1073
- // 1. Actieve orders/receive connectie ophalen (service_role → geen bearer nodig).
1074
- const { data: active, error: activeErr } = await supabaseAdmin.rpc("email_auth_for_purpose", {
1217
+ const admin = supabaseAdmin;
1218
+ // 0. Zelfherstel: ontbrekende standaardinstellingen aanmaken (overschrijft nooit iets).
1219
+ try {
1220
+ await admin.rpc("mailbox_ensure_defaults");
1221
+ }
1222
+ catch {
1223
+ /* functie ontbreekt nog: loadFilterSettings meldt het hieronder */
1224
+ }
1225
+ // 1. Alle actieve orders/receive koppelingen (service_role → geen bearer nodig).
1226
+ const { data: active, error: activeErr } = await admin.rpc("email_auth_for_purpose", {
1075
1227
  _purpose: "orders",
1076
1228
  _direction: "receive",
1077
1229
  });
1078
- if (activeErr)
1230
+ if (activeErr) {
1231
+ await recordFailedRun(admin, trigger, null, `Gekoppelde mailbox kon niet worden opgehaald: ${activeErr.message}`).catch(() => { });
1079
1232
  throw new Error(`email_auth_for_purpose: ${activeErr.message}`);
1080
- const row = Array.isArray(active) ? active[0] : null;
1081
- const connectionId = row?.id ?? null;
1082
- if (!connectionId) {
1083
- return {
1084
- ok: true,
1085
- runId: null,
1086
- processed: 0,
1087
- inserted: 0,
1088
- errors: 0,
1089
- errorSample: null,
1090
- connectionId: null,
1091
- skipped: "no_active_connection",
1092
- };
1093
1233
  }
1094
- // Controleer instellingen vóór writes; zonder koppeling blijft het bestaande antwoord behouden.
1095
- const settings = await loadFilterSettings(supabaseAdmin);
1096
- // 2. Open run-record voor observability.
1097
- const { data: runRow } = await supabaseAdmin
1098
- .from("mailbox_ingest_runs")
1099
- .insert({ trigger, connection_id: connectionId })
1100
- .select("id")
1101
- .single();
1102
- const runId = runRow?.id ?? null;
1234
+ const activeIds = Array.from(new Set((Array.isArray(active) ? active : []).map((r) => r?.id).filter(Boolean)));
1235
+ if (activeIds.length === 0) {
1236
+ const left = await leftoverConnections(admin, []);
1237
+ if (left.size === 0) {
1238
+ return {
1239
+ ok: true,
1240
+ runId: null,
1241
+ processed: 0,
1242
+ inserted: 0,
1243
+ errors: 0,
1244
+ errorSample: null,
1245
+ connectionId: null,
1246
+ skipped: "no_active_connection",
1247
+ };
1248
+ }
1249
+ }
1250
+ // Controleer instellingen vóór writes.
1251
+ let settings;
1252
+ try {
1253
+ settings = await loadFilterSettings(admin);
1254
+ }
1255
+ catch (e) {
1256
+ const reason = e instanceof MailboxSettingsMissingError
1257
+ ? "De instellingen van de mailbox ontbreken en konden niet worden aangemaakt."
1258
+ : String(e?.message ?? e);
1259
+ await recordFailedRun(admin, trigger, activeIds[0] ?? null, reason).catch(() => { });
1260
+ throw e;
1261
+ }
1262
+ if (trigger === "cron" && inPruneWindow()) {
1263
+ await admin.rpc("mailbox_prune_ingest_runs", { p_limit: 5000 }).then(() => { }, () => { });
1264
+ }
1265
+ const leftovers = await leftoverConnections(admin, activeIds);
1266
+ const allIds = [...activeIds, ...leftovers.keys()];
1267
+ const conns = await loadConnections(admin, allIds);
1268
+ const folders = await loadWatchFolders(admin, allIds);
1269
+ const perConn = Math.max(20, Math.ceil(200 / Math.max(1, allIds.length)));
1103
1270
  let processed = 0;
1104
1271
  let inserted = 0;
1105
1272
  let errors = 0;
1273
+ let ignored = 0;
1106
1274
  let errorSample = null;
1107
- const processedEventIds = [];
1108
- try {
1109
- // 3. Onverwerkte events voor deze connectie ophalen (max 200 per run).
1110
- const { data: events, error: evErr } = await supabaseAdmin
1111
- .from("email_auth_events")
1112
- .select("id, connection_id, external_id, payload, received_at")
1113
- .eq("connection_id", connectionId)
1114
- .is("consumed_at", null)
1115
- .order("received_at", { ascending: true })
1116
- .limit(200);
1117
- if (evErr)
1118
- throw new Error(`select email_auth_events: ${evErr.message}`);
1119
- for (const ev of (events ?? [])) {
1120
- processed++;
1121
- try {
1122
- const res = await ingestSingleEvent(supabaseAdmin, connectionId, {
1123
- id: ev.id,
1124
- external_id: ev.external_id ?? null,
1125
- payload: ev.payload ?? {},
1126
- received_at: ev.received_at ?? null,
1127
- });
1128
- if (res.inserted)
1129
- inserted++;
1130
- processedEventIds.push(ev.id);
1275
+ let firstRunId = null;
1276
+ for (const connectionId of allIds) {
1277
+ const conn = conns.get(connectionId) ?? { id: connectionId, provider: null, account_email: null, watch_scope: null, graph_subscription_id: null };
1278
+ const cutoff = leftovers.get(connectionId) ?? null;
1279
+ const { data: runRow } = await admin
1280
+ .from("mailbox_ingest_runs")
1281
+ .insert({ trigger, connection_id: connectionId })
1282
+ .select("id")
1283
+ .single();
1284
+ const runId = runRow?.id ?? null;
1285
+ if (!firstRunId)
1286
+ firstRunId = runId;
1287
+ let cProcessed = 0, cInserted = 0, cErrors = 0, cIgnored = 0;
1288
+ let cSample = null;
1289
+ const done = [];
1290
+ let fatal = null;
1291
+ try {
1292
+ let q = admin
1293
+ .from("email_auth_events")
1294
+ .select("id, connection_id, external_id, payload, received_at")
1295
+ .eq("connection_id", connectionId)
1296
+ .is("consumed_at", null);
1297
+ if (cutoff)
1298
+ q = q.lte("received_at", cutoff);
1299
+ const { data: events, error: evErr } = await q.order("received_at", { ascending: true }).limit(perConn);
1300
+ if (evErr)
1301
+ throw new Error(`select email_auth_events: ${evErr.message}`);
1302
+ const ctx = {
1303
+ provider: conn.provider,
1304
+ watchScope: conn.watch_scope,
1305
+ connectionSubscriptionId: conn.graph_subscription_id,
1306
+ folders: folders.get(connectionId) ?? [],
1307
+ };
1308
+ for (const ev of (Array.isArray(events) ? events : [])) {
1309
+ cProcessed++;
1310
+ try {
1311
+ const verdict = classifyEventFolder(ctx, ev.payload ?? {});
1312
+ if (verdict.action === "negeren") {
1313
+ await recordIgnoredEvent(admin, ev.id, connectionId, verdict.reason);
1314
+ cIgnored++;
1315
+ done.push(ev.id);
1316
+ continue;
1317
+ }
1318
+ const res = await ingestSingleEvent(admin, connectionId, {
1319
+ id: ev.id,
1320
+ external_id: ev.external_id ?? null,
1321
+ payload: ev.payload ?? {},
1322
+ received_at: ev.received_at ?? null,
1323
+ }, { accountEmail: conn.account_email, maxAgeDays: Number(settings.max_age_days ?? 14) });
1324
+ if (res.inserted)
1325
+ cInserted++;
1326
+ done.push(ev.id);
1327
+ }
1328
+ catch (e) {
1329
+ cErrors++;
1330
+ if (!cSample)
1331
+ cSample = String(e?.message ?? e).slice(0, 500);
1332
+ // Event blijft onverwerkt; de volgende ronde probeert het opnieuw.
1333
+ }
1131
1334
  }
1132
- catch (e) {
1133
- errors++;
1134
- if (!errorSample)
1135
- errorSample = String(e?.message ?? e).slice(0, 500);
1136
- // We laten dit event onverwerkt zodat de volgende run 'm opnieuw probeert.
1335
+ if (done.length > 0) {
1336
+ const { error: markErr } = await admin
1337
+ .from("email_auth_events")
1338
+ .update({ consumed_at: new Date().toISOString() })
1339
+ .in("id", done);
1340
+ if (markErr) {
1341
+ cErrors++;
1342
+ if (!cSample)
1343
+ cSample = `mark consumed: ${markErr.message}`.slice(0, 500);
1344
+ }
1137
1345
  }
1138
1346
  }
1139
- // 4. Markeer succesvolle events als consumed via directe update (service_role).
1140
- if (processedEventIds.length > 0) {
1141
- const { error: markErr } = await supabaseAdmin
1142
- .from("email_auth_events")
1143
- .update({ consumed_at: new Date().toISOString() })
1144
- .in("id", processedEventIds);
1145
- if (markErr) {
1146
- errors++;
1147
- if (!errorSample)
1148
- errorSample = `mark consumed: ${markErr.message}`.slice(0, 500);
1347
+ catch (e) {
1348
+ fatal = e;
1349
+ cErrors++;
1350
+ if (!cSample)
1351
+ cSample = String(e?.message ?? e).slice(0, 500);
1352
+ }
1353
+ finally {
1354
+ if (runId) {
1355
+ await admin
1356
+ .from("mailbox_ingest_runs")
1357
+ .update({
1358
+ finished_at: new Date().toISOString(),
1359
+ processed: cProcessed,
1360
+ inserted: cInserted,
1361
+ errors: cErrors,
1362
+ error_sample: cSample,
1363
+ status: cErrors > 0 ? "fout" : "ok",
1364
+ reason: plainReason(cErrors, cSample),
1365
+ })
1366
+ .eq("id", runId);
1149
1367
  }
1150
1368
  }
1151
- // 5. Backfill: alleen berichten die nog nooit door het voorfilter zijn gegaan.
1152
- const { data: orphans } = await supabaseAdmin
1369
+ processed += cProcessed;
1370
+ inserted += cInserted;
1371
+ errors += cErrors;
1372
+ ignored += cIgnored;
1373
+ if (!errorSample)
1374
+ errorSample = cSample;
1375
+ if (fatal)
1376
+ throw fatal;
1377
+ }
1378
+ // Backfill: berichten die nog nooit door het voorfilter zijn gegaan.
1379
+ try {
1380
+ const { data: orphans } = await admin
1153
1381
  .from("mailbox_messages")
1154
1382
  .select("id, from_email, mailbox_order_proposals(id)")
1155
- .eq("connection_id", connectionId)
1383
+ .in("connection_id", allIds)
1156
1384
  .is("filter_outcome", null)
1157
1385
  .order("received_at", { ascending: false })
1158
1386
  .limit(200);
1159
- for (const m of (orphans ?? [])) {
1387
+ for (const m of (Array.isArray(orphans) ? orphans : [])) {
1160
1388
  const props = Array.isArray(m.mailbox_order_proposals) ? m.mailbox_order_proposals : [];
1161
1389
  if (props.length > 0)
1162
1390
  continue;
1163
1391
  try {
1164
- await createStubProposal(supabaseAdmin, m.id, m.from_email ?? null, { trigger });
1392
+ await createStubProposal(admin, m.id, m.from_email ?? null, { trigger });
1165
1393
  }
1166
1394
  catch (e) {
1167
1395
  errors++;
@@ -1169,43 +1397,44 @@ export async function runIngest(trigger) {
1169
1397
  errorSample = `orphan-stub: ${String(e?.message ?? e)}`.slice(0, 500);
1170
1398
  }
1171
1399
  }
1172
- // 6 + 7. Gewijzigde AI-agent en herstelrondes: alleen vanuit cron en begrensd.
1173
- if (trigger === "cron") {
1174
- const s = settings;
1175
- try {
1176
- if (Number(s.max_agent_reparse) > 0)
1177
- await reparseChangedAgentProposals(supabaseAdmin, Number(s.max_agent_reparse));
1178
- }
1179
- catch (e) {
1180
- errors++;
1181
- if (!errorSample)
1182
- errorSample = `reparse: ${String(e?.message ?? e)}`.slice(0, 500);
1183
- }
1184
- try {
1185
- await retryFailedAiProposals(supabaseAdmin, Number(s.max_retries_per_run), Number(s.max_retry_attempts));
1186
- }
1187
- catch (e) {
1188
- errors++;
1189
- if (!errorSample)
1190
- errorSample = `retry: ${String(e?.message ?? e)}`.slice(0, 500);
1191
- }
1192
- }
1193
1400
  }
1194
- finally {
1195
- if (runId) {
1196
- await supabaseAdmin
1197
- .from("mailbox_ingest_runs")
1198
- .update({
1199
- finished_at: new Date().toISOString(),
1200
- processed,
1201
- inserted,
1202
- errors,
1203
- error_sample: errorSample,
1204
- })
1205
- .eq("id", runId);
1401
+ catch (e) {
1402
+ errors++;
1403
+ if (!errorSample)
1404
+ errorSample = `orphan-stub: ${String(e?.message ?? e)}`.slice(0, 500);
1405
+ }
1406
+ // Gewijzigde AI-agent en herstelrondes: alleen vanuit cron en begrensd.
1407
+ if (trigger === "cron") {
1408
+ const s = settings;
1409
+ try {
1410
+ if (Number(s.max_agent_reparse) > 0)
1411
+ await reparseChangedAgentProposals(admin, Number(s.max_agent_reparse));
1412
+ }
1413
+ catch (e) {
1414
+ errors++;
1415
+ if (!errorSample)
1416
+ errorSample = `reparse: ${String(e?.message ?? e)}`.slice(0, 500);
1417
+ }
1418
+ try {
1419
+ await retryFailedAiProposals(admin, Number(s.max_retries_per_run), Number(s.max_retry_attempts));
1420
+ }
1421
+ catch (e) {
1422
+ errors++;
1423
+ if (!errorSample)
1424
+ errorSample = `retry: ${String(e?.message ?? e)}`.slice(0, 500);
1206
1425
  }
1207
1426
  }
1208
- return { ok: true, runId, processed, inserted, errors, errorSample, connectionId };
1427
+ return {
1428
+ ok: true,
1429
+ runId: firstRunId,
1430
+ processed,
1431
+ inserted,
1432
+ errors,
1433
+ errorSample,
1434
+ connectionId: activeIds[0] ?? allIds[0] ?? null,
1435
+ ignored,
1436
+ connections: allIds.length,
1437
+ };
1209
1438
  }
1210
1439
  // ─── Testset: filter + extractie zonder voorstel of order ───────────────────
1211
1440
  export async function dryRunAnalysis(admin, input) {
@@ -476,4 +476,33 @@ export declare const getStickerDefaults: import("@tanstack/start-client-core").O
476
476
  available: true;
477
477
  defaults: Record<string, boolean>;
478
478
  }>>;
479
+ /** Maakt ontbrekende singleton-instellingen aan (overschrijft nooit iets). Faalt nooit hard. */
480
+ export declare function ensureMailboxDefaults(admin: unknown): Promise<void>;
481
+ export declare const ensureMailboxDefaultsFn: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
482
+ supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
483
+ userId: string;
484
+ claims: import("@supabase/auth-js").User;
485
+ }, undefined, undefined, undefined>], undefined, Promise<{
486
+ ok: true;
487
+ }>>;
488
+ export type MailboxStatus = {
489
+ lastOkAt: string | null;
490
+ lastFailAt: string | null;
491
+ lastFailReason: string | null;
492
+ problem: string | null;
493
+ waiting: number;
494
+ aiAgent: boolean;
495
+ addresses: string[];
496
+ ignoredNotFollowed: number;
497
+ ignoredUnknown: number;
498
+ disconnected: {
499
+ email: string;
500
+ count: number;
501
+ }[];
502
+ };
503
+ export declare const getMailboxStatus: import("@tanstack/start-client-core").OptionalFetcher<readonly [import("@tanstack/start-client-core").FunctionMiddlewareAfterServer<{}, unknown, undefined, {
504
+ supabase: import("../integrations/supabase/auth-middleware.js").AuthedSupabaseClient;
505
+ userId: string;
506
+ claims: import("@supabase/auth-js").User;
507
+ }, undefined, undefined, undefined>], undefined, Promise<MailboxStatus>>;
479
508
  //# sourceMappingURL=mailbox.functions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mailbox.functions.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox.functions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mailbox.functions.d.ts","sourceRoot":"","sources":["../../src/lib/mailbox.functions.ts"],"names":[],"mappings":"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"}