@leadbay/mcp 0.17.3 → 0.18.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.
- package/CHANGELOG.md +14 -0
- package/dist/bin.js +683 -67
- package/dist/http-server.js +338 -58
- package/dist/installer-electron.js +1 -1
- package/dist/installer-gui.js +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -553,6 +553,23 @@ var init_client = __esm({
|
|
|
553
553
|
await this.resolveOrgId();
|
|
554
554
|
await this.resolveTasteProfile();
|
|
555
555
|
}
|
|
556
|
+
// ─── Notifications helpers ────────────────────────────────────────────
|
|
557
|
+
// Backend exposes `GET /notifications`, `POST /notifications/{id}/seen`,
|
|
558
|
+
// `POST /notifications/{id}/archive`, plus `GET /ws/ticket?v=1.0` to mint
|
|
559
|
+
// a one-shot WS URL. See backend/docs/adr/notifications.md for shape.
|
|
560
|
+
async listNotifications(args = {}) {
|
|
561
|
+
const params = new URLSearchParams();
|
|
562
|
+
params.set("archived", String(args.archived ?? false));
|
|
563
|
+
params.set("page", String(args.page ?? 0));
|
|
564
|
+
params.set("count", String(args.count ?? 50));
|
|
565
|
+
return this.request("GET", `/notifications?${params.toString()}`);
|
|
566
|
+
}
|
|
567
|
+
async acknowledgeNotification(notificationId, action = "seen") {
|
|
568
|
+
await this.requestVoid("POST", `/notifications/${notificationId}/${action}`);
|
|
569
|
+
}
|
|
570
|
+
async getWsTicket() {
|
|
571
|
+
return this.request("GET", "/auth/ws?v=1.0");
|
|
572
|
+
}
|
|
556
573
|
makeError(code, message, hint, endpoint, retry_after, http_status) {
|
|
557
574
|
const out = { error: true, code, message, hint };
|
|
558
575
|
if (endpoint || this._region) {
|
|
@@ -5122,8 +5139,306 @@ var init_composite_file_names = __esm({
|
|
|
5122
5139
|
}
|
|
5123
5140
|
});
|
|
5124
5141
|
|
|
5142
|
+
// ../core/dist/notifications/revise-hint.js
|
|
5143
|
+
function reviseHintFor(kind) {
|
|
5144
|
+
switch (kind) {
|
|
5145
|
+
case "bulk_enrich":
|
|
5146
|
+
return HINT_BULK_ENRICH;
|
|
5147
|
+
case "bulk_qualify":
|
|
5148
|
+
return HINT_BULK_QUALIFY;
|
|
5149
|
+
case "import":
|
|
5150
|
+
return HINT_IMPORT;
|
|
5151
|
+
default:
|
|
5152
|
+
return HINT_OTHER;
|
|
5153
|
+
}
|
|
5154
|
+
}
|
|
5155
|
+
function inferKind(n) {
|
|
5156
|
+
if (n.links.some((l) => l.type === "bulk_enrichment"))
|
|
5157
|
+
return "bulk_enrich";
|
|
5158
|
+
if (n.file_import_id)
|
|
5159
|
+
return "import";
|
|
5160
|
+
if (n.bulk_progress)
|
|
5161
|
+
return "bulk_qualify";
|
|
5162
|
+
return "other";
|
|
5163
|
+
}
|
|
5164
|
+
function anchorIdFor(n, kind) {
|
|
5165
|
+
if (kind === "bulk_enrich") {
|
|
5166
|
+
const link = n.links.find((l) => l.type === "bulk_enrichment");
|
|
5167
|
+
return link ? String(link.id) : null;
|
|
5168
|
+
}
|
|
5169
|
+
if (kind === "import")
|
|
5170
|
+
return n.file_import_id;
|
|
5171
|
+
return null;
|
|
5172
|
+
}
|
|
5173
|
+
function toInboxEntry(n) {
|
|
5174
|
+
const kind = inferKind(n);
|
|
5175
|
+
return {
|
|
5176
|
+
notification_id: n.id,
|
|
5177
|
+
kind,
|
|
5178
|
+
anchor_id: anchorIdFor(n, kind),
|
|
5179
|
+
title: n.title,
|
|
5180
|
+
bulk_progress: n.bulk_progress,
|
|
5181
|
+
completed_at: n.updated_at,
|
|
5182
|
+
revise_hint: reviseHintFor(kind)
|
|
5183
|
+
};
|
|
5184
|
+
}
|
|
5185
|
+
var HINT_BULK_ENRICH, HINT_BULK_QUALIFY, HINT_IMPORT, HINT_OTHER;
|
|
5186
|
+
var init_revise_hint = __esm({
|
|
5187
|
+
"../core/dist/notifications/revise-hint.js"() {
|
|
5188
|
+
"use strict";
|
|
5189
|
+
HINT_BULK_ENRICH = "Contact enrichment just finished. Revise any prior output that named these leads' contacts (outreach drafts, contact lists, recommended-lead lists with contact_count, NEXT STEPS asking the user to wait for emails / phones). Re-fetch contacts via leadbay_get_contacts for the affected leads.";
|
|
5190
|
+
HINT_BULK_QUALIFY = "Lead qualification just finished. Revise any prior lead list / ranking / outreach shortlist that depended on ai_agent_lead_score for these leads \u2014 today's leads, top-of-inbox, followups maps, prepare-outreach shortlists. Re-pull qualification answers via leadbay_research_lead_by_id or re-rank via leadbay_pull_leads.";
|
|
5191
|
+
HINT_IMPORT = "CSV / CRM import just finished. Revise any prior output that referenced 'leads available' before the import landed \u2014 lead lists pulled from the affected lens, 'what's new today', followup planning. Re-pull the affected lens via leadbay_pull_leads / leadbay_pull_followups.";
|
|
5192
|
+
HINT_OTHER = "Background work just completed. If you referenced its subject in prior output, re-fetch the affected data and revise.";
|
|
5193
|
+
}
|
|
5194
|
+
});
|
|
5195
|
+
|
|
5196
|
+
// ../core/dist/notifications/inbox.js
|
|
5197
|
+
var DEFAULT_TTL_MS, NotificationsInbox;
|
|
5198
|
+
var init_inbox = __esm({
|
|
5199
|
+
"../core/dist/notifications/inbox.js"() {
|
|
5200
|
+
"use strict";
|
|
5201
|
+
init_revise_hint();
|
|
5202
|
+
DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
5203
|
+
NotificationsInbox = class {
|
|
5204
|
+
entries = /* @__PURE__ */ new Map();
|
|
5205
|
+
ttl_ms;
|
|
5206
|
+
now;
|
|
5207
|
+
constructor(opts = {}) {
|
|
5208
|
+
this.ttl_ms = opts.ttl_ms ?? DEFAULT_TTL_MS;
|
|
5209
|
+
this.now = opts.now ?? Date.now;
|
|
5210
|
+
}
|
|
5211
|
+
// Upsert by notification id. Latest write wins so duplicate arrivals
|
|
5212
|
+
// (WS event + REST catch-up landing the same row) collapse cleanly.
|
|
5213
|
+
record(n) {
|
|
5214
|
+
if (!n.bulk_progress)
|
|
5215
|
+
return;
|
|
5216
|
+
if (n.in_progress)
|
|
5217
|
+
return;
|
|
5218
|
+
const entry = toInboxEntry(n);
|
|
5219
|
+
this.entries.set(entry.notification_id, {
|
|
5220
|
+
entry,
|
|
5221
|
+
recordedAt: this.now()
|
|
5222
|
+
});
|
|
5223
|
+
}
|
|
5224
|
+
list() {
|
|
5225
|
+
this.expireStale();
|
|
5226
|
+
return [...this.entries.values()].map((e) => e.entry);
|
|
5227
|
+
}
|
|
5228
|
+
markSeen(notification_id) {
|
|
5229
|
+
this.entries.delete(notification_id);
|
|
5230
|
+
}
|
|
5231
|
+
size() {
|
|
5232
|
+
this.expireStale();
|
|
5233
|
+
return this.entries.size;
|
|
5234
|
+
}
|
|
5235
|
+
expireStale() {
|
|
5236
|
+
const cutoff = this.now() - this.ttl_ms;
|
|
5237
|
+
for (const [id, e] of this.entries) {
|
|
5238
|
+
if (e.recordedAt < cutoff)
|
|
5239
|
+
this.entries.delete(id);
|
|
5240
|
+
}
|
|
5241
|
+
}
|
|
5242
|
+
};
|
|
5243
|
+
}
|
|
5244
|
+
});
|
|
5245
|
+
|
|
5246
|
+
// ../core/dist/notifications/catch-up.js
|
|
5247
|
+
async function catchUpNotifications(client, inbox, opts = {}) {
|
|
5248
|
+
const count = opts.count ?? DEFAULT_COUNT;
|
|
5249
|
+
let added = 0;
|
|
5250
|
+
try {
|
|
5251
|
+
const page = await client.listNotifications({
|
|
5252
|
+
archived: false,
|
|
5253
|
+
page: 0,
|
|
5254
|
+
count
|
|
5255
|
+
});
|
|
5256
|
+
for (const n of page.items) {
|
|
5257
|
+
if (!n.bulk_progress)
|
|
5258
|
+
continue;
|
|
5259
|
+
if (n.in_progress)
|
|
5260
|
+
continue;
|
|
5261
|
+
if (n.first_seen_at)
|
|
5262
|
+
continue;
|
|
5263
|
+
const sizeBefore = inbox.size();
|
|
5264
|
+
inbox.record(n);
|
|
5265
|
+
if (inbox.size() > sizeBefore)
|
|
5266
|
+
added += 1;
|
|
5267
|
+
}
|
|
5268
|
+
opts.logger?.info?.(`notifications.catch_up scanned=${page.items.length} seeded=${added}`);
|
|
5269
|
+
} catch (err) {
|
|
5270
|
+
opts.logger?.warn?.(`notifications.catch_up failed: ${err?.message ?? err?.code ?? err}`);
|
|
5271
|
+
}
|
|
5272
|
+
return added;
|
|
5273
|
+
}
|
|
5274
|
+
var DEFAULT_COUNT;
|
|
5275
|
+
var init_catch_up = __esm({
|
|
5276
|
+
"../core/dist/notifications/catch-up.js"() {
|
|
5277
|
+
"use strict";
|
|
5278
|
+
DEFAULT_COUNT = 50;
|
|
5279
|
+
}
|
|
5280
|
+
});
|
|
5281
|
+
|
|
5282
|
+
// ../core/dist/notifications/ws-client.js
|
|
5283
|
+
var PING_INTERVAL_MS, RECONNECT_INITIAL_MS, RECONNECT_MAX_MS, NotificationsWsClient;
|
|
5284
|
+
var init_ws_client = __esm({
|
|
5285
|
+
"../core/dist/notifications/ws-client.js"() {
|
|
5286
|
+
"use strict";
|
|
5287
|
+
init_catch_up();
|
|
5288
|
+
PING_INTERVAL_MS = 3e4;
|
|
5289
|
+
RECONNECT_INITIAL_MS = 1e3;
|
|
5290
|
+
RECONNECT_MAX_MS = 3e4;
|
|
5291
|
+
NotificationsWsClient = class {
|
|
5292
|
+
client;
|
|
5293
|
+
inbox;
|
|
5294
|
+
logger;
|
|
5295
|
+
ws = null;
|
|
5296
|
+
pingTimer = null;
|
|
5297
|
+
reconnectTimer = null;
|
|
5298
|
+
reconnectDelay = RECONNECT_INITIAL_MS;
|
|
5299
|
+
stopped = false;
|
|
5300
|
+
constructor(opts) {
|
|
5301
|
+
this.client = opts.client;
|
|
5302
|
+
this.inbox = opts.inbox;
|
|
5303
|
+
this.logger = opts.logger;
|
|
5304
|
+
}
|
|
5305
|
+
async start() {
|
|
5306
|
+
this.stopped = false;
|
|
5307
|
+
await catchUpNotifications(this.client, this.inbox, { logger: this.logger });
|
|
5308
|
+
void this.connect();
|
|
5309
|
+
}
|
|
5310
|
+
stop() {
|
|
5311
|
+
this.stopped = true;
|
|
5312
|
+
if (this.pingTimer)
|
|
5313
|
+
clearInterval(this.pingTimer);
|
|
5314
|
+
if (this.reconnectTimer)
|
|
5315
|
+
clearTimeout(this.reconnectTimer);
|
|
5316
|
+
this.pingTimer = null;
|
|
5317
|
+
this.reconnectTimer = null;
|
|
5318
|
+
if (this.ws) {
|
|
5319
|
+
try {
|
|
5320
|
+
this.ws.close(1e3, "shutdown");
|
|
5321
|
+
} catch {
|
|
5322
|
+
}
|
|
5323
|
+
this.ws = null;
|
|
5324
|
+
}
|
|
5325
|
+
}
|
|
5326
|
+
async connect() {
|
|
5327
|
+
if (this.stopped)
|
|
5328
|
+
return;
|
|
5329
|
+
let url;
|
|
5330
|
+
try {
|
|
5331
|
+
const ticket = await this.client.getWsTicket();
|
|
5332
|
+
url = ticket.url;
|
|
5333
|
+
} catch (err) {
|
|
5334
|
+
this.logger?.warn?.(`notifications.ws ticket_fetch_failed: ${err?.message ?? err?.code ?? err}`);
|
|
5335
|
+
this.scheduleReconnect();
|
|
5336
|
+
return;
|
|
5337
|
+
}
|
|
5338
|
+
let ws;
|
|
5339
|
+
try {
|
|
5340
|
+
ws = new WebSocket(url);
|
|
5341
|
+
} catch (err) {
|
|
5342
|
+
this.logger?.warn?.(`notifications.ws construct_failed: ${err?.message ?? err}`);
|
|
5343
|
+
this.scheduleReconnect();
|
|
5344
|
+
return;
|
|
5345
|
+
}
|
|
5346
|
+
this.ws = ws;
|
|
5347
|
+
ws.addEventListener("open", () => {
|
|
5348
|
+
this.logger?.info?.("notifications.ws connected");
|
|
5349
|
+
this.reconnectDelay = RECONNECT_INITIAL_MS;
|
|
5350
|
+
void catchUpNotifications(this.client, this.inbox, {
|
|
5351
|
+
logger: this.logger
|
|
5352
|
+
});
|
|
5353
|
+
this.pingTimer = setInterval(() => this.sendPing(), PING_INTERVAL_MS);
|
|
5354
|
+
});
|
|
5355
|
+
ws.addEventListener("message", (ev) => {
|
|
5356
|
+
const text = typeof ev.data === "string" ? ev.data : String(ev.data ?? "");
|
|
5357
|
+
if (!text)
|
|
5358
|
+
return;
|
|
5359
|
+
let msg;
|
|
5360
|
+
try {
|
|
5361
|
+
msg = JSON.parse(text);
|
|
5362
|
+
} catch {
|
|
5363
|
+
this.logger?.warn?.("notifications.ws non_json_frame");
|
|
5364
|
+
return;
|
|
5365
|
+
}
|
|
5366
|
+
this.handleMessage(msg);
|
|
5367
|
+
});
|
|
5368
|
+
ws.addEventListener("error", (ev) => {
|
|
5369
|
+
this.logger?.warn?.(`notifications.ws error: ${ev?.message ?? "(no detail)"}`);
|
|
5370
|
+
});
|
|
5371
|
+
ws.addEventListener("close", (ev) => {
|
|
5372
|
+
this.logger?.info?.(`notifications.ws closed code=${ev.code} reason=${ev.reason || "(none)"}`);
|
|
5373
|
+
if (this.pingTimer)
|
|
5374
|
+
clearInterval(this.pingTimer);
|
|
5375
|
+
this.pingTimer = null;
|
|
5376
|
+
this.ws = null;
|
|
5377
|
+
this.scheduleReconnect();
|
|
5378
|
+
});
|
|
5379
|
+
}
|
|
5380
|
+
handleMessage(msg) {
|
|
5381
|
+
if (msg.type === "pong")
|
|
5382
|
+
return;
|
|
5383
|
+
if (msg.type === "ping") {
|
|
5384
|
+
this.sendRaw({ type: "pong" });
|
|
5385
|
+
return;
|
|
5386
|
+
}
|
|
5387
|
+
if (msg.type !== "notification")
|
|
5388
|
+
return;
|
|
5389
|
+
const { type: _t, ...rest } = msg;
|
|
5390
|
+
void _t;
|
|
5391
|
+
const n = rest;
|
|
5392
|
+
if (n.bulk_progress == null || n.in_progress) {
|
|
5393
|
+
return;
|
|
5394
|
+
}
|
|
5395
|
+
this.inbox.record(n);
|
|
5396
|
+
this.logger?.info?.(`notifications.ws terminal id=${n.id} kind=${n.file_import_id ? "import" : n.links.some((l) => l.type === "bulk_enrichment") ? "bulk_enrich" : "bulk_qualify"}`);
|
|
5397
|
+
}
|
|
5398
|
+
sendPing() {
|
|
5399
|
+
this.sendRaw({ type: "ping" });
|
|
5400
|
+
}
|
|
5401
|
+
sendRaw(obj) {
|
|
5402
|
+
if (!this.ws)
|
|
5403
|
+
return;
|
|
5404
|
+
if (this.ws.readyState !== WebSocket.OPEN)
|
|
5405
|
+
return;
|
|
5406
|
+
try {
|
|
5407
|
+
this.ws.send(JSON.stringify(obj));
|
|
5408
|
+
} catch (err) {
|
|
5409
|
+
this.logger?.warn?.(`notifications.ws send_failed: ${err?.message ?? err}`);
|
|
5410
|
+
}
|
|
5411
|
+
}
|
|
5412
|
+
scheduleReconnect() {
|
|
5413
|
+
if (this.stopped)
|
|
5414
|
+
return;
|
|
5415
|
+
if (this.reconnectTimer)
|
|
5416
|
+
return;
|
|
5417
|
+
const delay = this.reconnectDelay;
|
|
5418
|
+
this.reconnectDelay = Math.min(this.reconnectDelay * 2, RECONNECT_MAX_MS);
|
|
5419
|
+
this.reconnectTimer = setTimeout(() => {
|
|
5420
|
+
this.reconnectTimer = null;
|
|
5421
|
+
void this.connect();
|
|
5422
|
+
}, delay);
|
|
5423
|
+
this.logger?.info?.(`notifications.ws reconnect_in_${delay}ms`);
|
|
5424
|
+
}
|
|
5425
|
+
};
|
|
5426
|
+
}
|
|
5427
|
+
});
|
|
5428
|
+
|
|
5429
|
+
// ../core/dist/notifications/index.js
|
|
5430
|
+
var init_notifications = __esm({
|
|
5431
|
+
"../core/dist/notifications/index.js"() {
|
|
5432
|
+
"use strict";
|
|
5433
|
+
init_inbox();
|
|
5434
|
+
init_ws_client();
|
|
5435
|
+
init_catch_up();
|
|
5436
|
+
init_revise_hint();
|
|
5437
|
+
}
|
|
5438
|
+
});
|
|
5439
|
+
|
|
5125
5440
|
// ../core/dist/tool-descriptions.generated.js
|
|
5126
|
-
var leadbay_account_status, leadbay_add_leads_to_campaign, leadbay_add_note, leadbay_adjust_audience, leadbay_agent_memory_capture, leadbay_agent_memory_recall, leadbay_agent_memory_review, leadbay_answer_clarification, leadbay_bulk_enrich_status, leadbay_bulk_qualify_leads, leadbay_campaign_call_sheet, leadbay_campaign_progression, leadbay_clear_selection, leadbay_clear_user_prompt, leadbay_create_campaign, leadbay_create_custom_field, leadbay_create_lens, leadbay_create_lens_draft, leadbay_create_topup_link, leadbay_deselect_leads, leadbay_discover_leads, leadbay_dislike_lead, leadbay_dismiss_clarification, leadbay_enrich_contacts, leadbay_enrich_titles, leadbay_extend_lens, leadbay_followups_map, leadbay_get_clarification, leadbay_get_contacts, leadbay_get_enrichment_job_titles, leadbay_get_epilogue_responses, leadbay_get_lead_activities, leadbay_get_lead_notes, leadbay_get_lead_profile, leadbay_get_lens_filter, leadbay_get_lens_scoring, leadbay_get_prospecting_actions, leadbay_get_quota, leadbay_get_selection_ids, leadbay_get_taste_profile, leadbay_get_user_prompt, leadbay_get_web_fetch, leadbay_import_and_qualify, leadbay_import_leads, leadbay_import_status, leadbay_launch_bulk_enrichment, leadbay_like_lead, leadbay_list_campaigns, leadbay_list_lenses, leadbay_list_locations, leadbay_list_mappable_fields, leadbay_list_sectors, leadbay_login, leadbay_my_lenses, leadbay_new_lens, leadbay_open_billing_portal, leadbay_pick_clarification, leadbay_prepare_outreach, leadbay_preview_bulk_enrichment, leadbay_promote_lens, leadbay_pull_followups, leadbay_pull_leads, leadbay_qualify_lead, leadbay_qualify_status, leadbay_recall_ordered_titles, leadbay_refine_prompt, leadbay_remove_epilogue, leadbay_remove_leads_from_campaign, leadbay_remove_pushback, leadbay_report_friction, leadbay_report_outreach, leadbay_research_lead_by_id, leadbay_research_lead_by_name_fuzzy, leadbay_resolve_import_rows, leadbay_seed_candidates, leadbay_select_leads, leadbay_set_active_lens, leadbay_set_epilogue_status, leadbay_set_pushback, leadbay_set_user_prompt, leadbay_tour_plan, leadbay_update_lens, leadbay_update_lens_filter;
|
|
5441
|
+
var leadbay_account_status, leadbay_acknowledge_notification, leadbay_add_leads_to_campaign, leadbay_add_note, leadbay_adjust_audience, leadbay_agent_memory_capture, leadbay_agent_memory_recall, leadbay_agent_memory_review, leadbay_answer_clarification, leadbay_bulk_enrich_status, leadbay_bulk_qualify_leads, leadbay_campaign_call_sheet, leadbay_campaign_progression, leadbay_clear_selection, leadbay_clear_user_prompt, leadbay_create_campaign, leadbay_create_custom_field, leadbay_create_lens, leadbay_create_lens_draft, leadbay_create_topup_link, leadbay_deselect_leads, leadbay_discover_leads, leadbay_dislike_lead, leadbay_dismiss_clarification, leadbay_enrich_contacts, leadbay_enrich_titles, leadbay_extend_lens, leadbay_followups_map, leadbay_get_clarification, leadbay_get_contacts, leadbay_get_enrichment_job_titles, leadbay_get_epilogue_responses, leadbay_get_lead_activities, leadbay_get_lead_notes, leadbay_get_lead_profile, leadbay_get_lens_filter, leadbay_get_lens_scoring, leadbay_get_prospecting_actions, leadbay_get_quota, leadbay_get_selection_ids, leadbay_get_taste_profile, leadbay_get_user_prompt, leadbay_get_web_fetch, leadbay_import_and_qualify, leadbay_import_leads, leadbay_import_status, leadbay_launch_bulk_enrichment, leadbay_like_lead, leadbay_list_campaigns, leadbay_list_lenses, leadbay_list_locations, leadbay_list_mappable_fields, leadbay_list_sectors, leadbay_login, leadbay_my_lenses, leadbay_new_lens, leadbay_open_billing_portal, leadbay_pick_clarification, leadbay_prepare_outreach, leadbay_preview_bulk_enrichment, leadbay_promote_lens, leadbay_pull_followups, leadbay_pull_leads, leadbay_qualify_lead, leadbay_qualify_status, leadbay_recall_ordered_titles, leadbay_refine_prompt, leadbay_remove_epilogue, leadbay_remove_leads_from_campaign, leadbay_remove_pushback, leadbay_report_friction, leadbay_report_outreach, leadbay_research_lead_by_id, leadbay_research_lead_by_name_fuzzy, leadbay_resolve_import_rows, leadbay_seed_candidates, leadbay_select_leads, leadbay_set_active_lens, leadbay_set_epilogue_status, leadbay_set_pushback, leadbay_set_user_prompt, leadbay_tour_plan, leadbay_update_lens, leadbay_update_lens_filter;
|
|
5127
5442
|
var init_tool_descriptions_generated = __esm({
|
|
5128
5443
|
"../core/dist/tool-descriptions.generated.js"() {
|
|
5129
5444
|
"use strict";
|
|
@@ -5162,9 +5477,52 @@ Show the user's account state \u2014 admin rights, language, last-active lens, c
|
|
|
5162
5477
|
|
|
5163
5478
|
**After a user tops up, do NOT keep refusing \u2014 RETRY.** If the user signals they topped up / bought credits / added credits, the previous QUOTA_EXCEEDED is invalidated the moment the Stripe webhook lands. RE-CALL \`leadbay_account_status\` to pick up the new state AND retry the originally failed call. The retry itself does not require a successful account_status check first \u2014 a topped-up user has cleared the throttle whether or not your cached snapshot reflects it yet. If the retry hits the wall again, only then re-offer top-up / wait. **A stale quota snapshot is never a reason to gate-keep a topped-up user.**
|
|
5164
5479
|
|
|
5480
|
+
**\`notifications\` block.** The response now includes a top-level \`notifications\` array listing background work the user (or agent) initiated that has since completed (\`bulk_enrich\`, \`bulk_qualify\`, \`import\`). These are signals to revise prior agent outputs the just-finished work might have made stale \u2014 they're NOT a pending-task list for the user. After revising (or confirming nothing is affected), call \`leadbay_acknowledge_notification(notification_id)\`. Full handling protocol below.
|
|
5481
|
+
|
|
5482
|
+
## GATE \u2014 INSPECT \`_meta.notifications\` ON EVERY RESPONSE
|
|
5483
|
+
|
|
5484
|
+
Some Leadbay tool responses include a \`_meta.notifications\` array listing **background work the user (or you) initiated that has since completed**. The three relevant kinds:
|
|
5485
|
+
|
|
5486
|
+
| Kind | What just finished |
|
|
5487
|
+
|---|---|
|
|
5488
|
+
| \`bulk_enrich\` | A bulk contact-enrichment job (emails / phone numbers attached to leads' contacts). |
|
|
5489
|
+
| \`bulk_qualify\` | A bulk lead-qualification job (web-fetch + AI rescore, producing \`ai_agent_lead_score\` and qualification answers). |
|
|
5490
|
+
| \`import\` | A CSV / CRM file import (rows resolved to leads in the user's pipeline). |
|
|
5491
|
+
|
|
5492
|
+
**Your job when you see an entry: revise prior outputs the just-finished work might have made stale.** This is NOT a pending-task list. It's a "your earlier answer used data that has now changed" signal.
|
|
5493
|
+
|
|
5494
|
+
| Kind | Outputs you've produced that may now be stale \u2014 refresh them |
|
|
5495
|
+
|---|---|
|
|
5496
|
+
| \`bulk_enrich\` | Outreach drafts mentioning these leads' contacts; contact lists; recommended-lead lists citing \`contact_count\`; NEXT STEPS that asked the user to wait for emails / phones. Re-fetch via \`leadbay_get_contacts(leadId)\` for the affected leads. |
|
|
5497
|
+
| \`bulk_qualify\` | Lead rankings / shortlists you produced without \`ai_agent_lead_score\`; "today's leads"; followup maps; prepare-outreach picks. Re-pull via \`leadbay_pull_leads\` / \`leadbay_research_lead_by_id\`. |
|
|
5498
|
+
| \`import\` | "Available leads" claims; pulls from the affected lens that ran before the import landed; followup planning that needed the imported set. Re-pull via \`leadbay_pull_leads\` / \`leadbay_pull_followups\`. |
|
|
5499
|
+
|
|
5500
|
+
**After revising (or after confirming no prior output is affected):** call \`leadbay_acknowledge_notification(notification_id)\` so the entry stops resurfacing on every tool response. Ack-and-move-on is correct even when nothing was stale \u2014 that's how the inbox stays focused on what's actually pending.
|
|
5501
|
+
|
|
5502
|
+
**Do NOT** interpret these entries as "things waiting for the user." The user expects you to handle them silently. They are signals to YOU \u2014 agent \u2014 that prior outputs need a refresh.
|
|
5503
|
+
|
|
5504
|
+
**Don't poll.** When you launch a long operation (enrichment / qualification / import), simply continue the conversation \u2014 the next time you call any tool, the completed-work entry will appear in \`_meta.notifications\`. Status tools (\`leadbay_bulk_enrich_status\`, \`leadbay_qualify_status\`, \`leadbay_import_status\`) exist for the case where you want the answer NOW (mid-conversation), not for ambient polling.
|
|
5505
|
+
|
|
5506
|
+
Also surfaced as a top-level \`notifications\` array on \`leadbay_account_status\` \u2014 same shape, same handling.
|
|
5507
|
+
|
|
5508
|
+
|
|
5165
5509
|
WHEN TO USE: at the start of a session to know what the agent can/can't do, after a 429 to explain to the user which resource window was exhausted and when it resets (and to offer the top-up alternative), and after the user signals a top-up so the agent can resume the interrupted workflow.
|
|
5166
5510
|
|
|
5167
5511
|
WHEN NOT TO USE: as a pre-flight gate before bulk ops \u2014 operations themselves return 429; this tool is for context, not gating. And: a recent quota snapshot showing "exhausted" is NOT a reason to refuse a write call when the user has just topped up \u2014 re-call this tool first, then proceed.
|
|
5512
|
+
`;
|
|
5513
|
+
leadbay_acknowledge_notification = `Acknowledge a Leadbay notification \u2014 i.e. tell the MCP and the backend "I've seen this and acted on it." Wraps \`POST /1.5/notifications/{id}/seen\` (default) or \`/archive\` (when \`archive:true\`) and drops the entry from the local inbox so subsequent \`_meta.notifications\` payloads stop carrying it.
|
|
5514
|
+
|
|
5515
|
+
**When to call.** After you read an entry from \`_meta.notifications\` or \`account_status.notifications\` and have revised whatever prior output the just-finished background work might have made stale (outreach drafts, lead lists, "available leads" claims, followup plans). Mark-seen tells the human team's pipeline you handled this and prevents the notification from re-surfacing on every subsequent tool response.
|
|
5516
|
+
|
|
5517
|
+
If nothing you produced for the user is affected, ack anyway with \`archive:false\` \u2014 the entry should still clear so the inbox stays focused on what's actually pending.
|
|
5518
|
+
|
|
5519
|
+
Use \`archive:true\` only when you want the row gone from the FE notification dropdown too (e.g. a non-actionable system notification that's already handled). Default behaviour is \`seen\` \u2014 same as the FE dropdown's "click to read" semantics.
|
|
5520
|
+
|
|
5521
|
+
WHEN TO USE: immediately after you finish reviewing / revising in response to a \`_meta.notifications[]\` entry. Idempotent \u2014 calling twice with the same id is safe.
|
|
5522
|
+
|
|
5523
|
+
WHEN NOT TO USE: before doing the revision work; for general "mark all read" sweeps (call once per notification id you've actually consumed).
|
|
5524
|
+
|
|
5525
|
+
This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
|
|
5168
5526
|
`;
|
|
5169
5527
|
leadbay_add_leads_to_campaign = `## WHEN TO USE
|
|
5170
5528
|
|
|
@@ -10199,9 +10557,23 @@ async function completeUploadedChunk(client, upload, mappings, dryRun, perPhaseB
|
|
|
10199
10557
|
await pollPreprocess(client, importId, phaseBudget, ctx, signal);
|
|
10200
10558
|
ctx?.logger?.info?.(`import-leads: preprocess done for importId=${importId}`);
|
|
10201
10559
|
if (dryRun) {
|
|
10202
|
-
return { importId, records: [] };
|
|
10560
|
+
return { importId, records: [], notification_id: null };
|
|
10561
|
+
}
|
|
10562
|
+
let updateMappingsResp = null;
|
|
10563
|
+
try {
|
|
10564
|
+
updateMappingsResp = await client.request("POST", `/imports/${importId}/update_mappings`, mappings);
|
|
10565
|
+
} catch (err) {
|
|
10566
|
+
if (err?.code === "API_ERROR" || err?.code === "NOT_FOUND") {
|
|
10567
|
+
ctx?.logger?.warn?.(`import-leads: update_mappings raw error (${err?.code}); retrying void`);
|
|
10568
|
+
await client.requestVoid("POST", `/imports/${importId}/update_mappings`, mappings);
|
|
10569
|
+
} else {
|
|
10570
|
+
throw err;
|
|
10571
|
+
}
|
|
10572
|
+
}
|
|
10573
|
+
const importNotificationId = updateMappingsResp?.notification_id ?? null;
|
|
10574
|
+
if (importNotificationId) {
|
|
10575
|
+
ctx?.logger?.info?.(`import-leads: notification_id=${importNotificationId} importId=${importId}`);
|
|
10203
10576
|
}
|
|
10204
|
-
await client.requestVoid("POST", `/imports/${importId}/update_mappings`, mappings);
|
|
10205
10577
|
ctx?.logger?.info?.(`import-leads: mappings committed for importId=${importId}`);
|
|
10206
10578
|
const phaseBudget2 = Math.min(perPhaseBudgetMs, Math.max(1, totalDeadline - Date.now()));
|
|
10207
10579
|
await pollProcess(client, importId, phaseBudget2, ctx, signal);
|
|
@@ -10209,7 +10581,7 @@ async function completeUploadedChunk(client, upload, mappings, dryRun, perPhaseB
|
|
|
10209
10581
|
const phaseBudget3 = Math.min(perPhaseBudgetMs, Math.max(1, totalDeadline - Date.now()));
|
|
10210
10582
|
const records = await pollRecordsToTerminal(client, importId, phaseBudget3, chunk.length, ctx, signal);
|
|
10211
10583
|
ctx?.logger?.info?.(`import-leads: ${records.length} records terminal for importId=${importId}`);
|
|
10212
|
-
return { importId, records };
|
|
10584
|
+
return { importId, records, notification_id: importNotificationId };
|
|
10213
10585
|
}
|
|
10214
10586
|
function reconcileOneChunk(prep, chunk, matched, notImported) {
|
|
10215
10587
|
const seenInputIndex = /* @__PURE__ */ new Set();
|
|
@@ -10265,7 +10637,7 @@ function reconcileOneChunk(prep, chunk, matched, notImported) {
|
|
|
10265
10637
|
}
|
|
10266
10638
|
}
|
|
10267
10639
|
}
|
|
10268
|
-
function buildImportLeadsResult(client, prep, importIds, matched, notImported, dryRun, cancelled) {
|
|
10640
|
+
function buildImportLeadsResult(client, prep, importIds, matched, notImported, dryRun, cancelled, notificationIds) {
|
|
10269
10641
|
const leads = [];
|
|
10270
10642
|
const not_imported = [];
|
|
10271
10643
|
if (dryRun) {
|
|
@@ -10330,6 +10702,7 @@ function buildImportLeadsResult(client, prep, importIds, matched, notImported, d
|
|
|
10330
10702
|
leads,
|
|
10331
10703
|
not_imported,
|
|
10332
10704
|
importIds,
|
|
10705
|
+
notification_ids: notificationIds,
|
|
10333
10706
|
region: client.region,
|
|
10334
10707
|
cancelled: cancelled || void 0,
|
|
10335
10708
|
dry_run: dryRun || void 0,
|
|
@@ -10355,17 +10728,21 @@ async function runImportInBackground(client, prep, uploadedChunks, opts, ctx, ha
|
|
|
10355
10728
|
void (async () => {
|
|
10356
10729
|
const bgCtx = { logger: ctx.logger, bulkTracker: tracker };
|
|
10357
10730
|
const importIds = uploadedChunks.map((chunk) => chunk.importId);
|
|
10731
|
+
const notificationIds = [];
|
|
10358
10732
|
const matched = /* @__PURE__ */ new Map();
|
|
10359
10733
|
const notImported = /* @__PURE__ */ new Map();
|
|
10360
10734
|
try {
|
|
10361
10735
|
const totalDeadline = Date.now() + opts.totalBudget;
|
|
10362
10736
|
for (const upload of uploadedChunks) {
|
|
10363
10737
|
const out = await completeUploadedChunk(client, upload, prep.mappings, opts.dryRun, opts.perPhaseBudget, totalDeadline, bgCtx, void 0);
|
|
10738
|
+
if (out.notification_id && !notificationIds.includes(out.notification_id)) {
|
|
10739
|
+
notificationIds.push(out.notification_id);
|
|
10740
|
+
}
|
|
10364
10741
|
if (!opts.dryRun) {
|
|
10365
10742
|
reconcileOneChunk(prep, out, matched, notImported);
|
|
10366
10743
|
}
|
|
10367
10744
|
}
|
|
10368
|
-
const result = buildImportLeadsResult(client, prep, importIds, matched, notImported, opts.dryRun, false);
|
|
10745
|
+
const result = buildImportLeadsResult(client, prep, importIds, matched, notImported, opts.dryRun, false, notificationIds);
|
|
10369
10746
|
await tracker.markImportComplete(handleId, {
|
|
10370
10747
|
leads: result.leads,
|
|
10371
10748
|
not_imported: result.not_imported,
|
|
@@ -10598,6 +10975,7 @@ var init_import_leads = __esm({
|
|
|
10598
10975
|
leads: [],
|
|
10599
10976
|
not_imported,
|
|
10600
10977
|
importIds: [],
|
|
10978
|
+
notification_ids: [],
|
|
10601
10979
|
region: client.region,
|
|
10602
10980
|
dry_run: dryRun || void 0,
|
|
10603
10981
|
_meta: client.lastMeta ?? {
|
|
@@ -10652,6 +11030,10 @@ var init_import_leads = __esm({
|
|
|
10652
11030
|
status: "running",
|
|
10653
11031
|
handle_id: reservation.record.bulk_id,
|
|
10654
11032
|
importIds: importIds2,
|
|
11033
|
+
// Notifications fire from update_mappings, which the background
|
|
11034
|
+
// task hasn't called yet at this point. They surface via the WS
|
|
11035
|
+
// listener / catch-up REST on subsequent agent turns.
|
|
11036
|
+
notification_ids: [],
|
|
10655
11037
|
progress: {
|
|
10656
11038
|
phase: reservation.record.status === "complete" ? "complete" : importIds2.length > 0 ? "preprocess" : "queued",
|
|
10657
11039
|
records_processed: reservation.record.status === "complete" ? reservation.record.records_total : 0,
|
|
@@ -10672,6 +11054,7 @@ var init_import_leads = __esm({
|
|
|
10672
11054
|
}
|
|
10673
11055
|
ctx?.logger?.info?.(`import-leads(${prep.mode}): ${prep.validInputs.length} rows \u2192 ${chunks.length} chunk(s); dry_run=${dryRun}, totalBudgetMs=${totalBudget}`);
|
|
10674
11056
|
const importIds = [];
|
|
11057
|
+
const notificationIds = [];
|
|
10675
11058
|
const matched = /* @__PURE__ */ new Map();
|
|
10676
11059
|
const notImported = /* @__PURE__ */ new Map();
|
|
10677
11060
|
let cancelled = false;
|
|
@@ -10683,6 +11066,9 @@ var init_import_leads = __esm({
|
|
|
10683
11066
|
for (let i = 0; i < chunks.length; i++) {
|
|
10684
11067
|
const chunk = chunks[i];
|
|
10685
11068
|
const out = await runOneChunk(client, chunk, i, chunks.length, prep.header, prep.mappings, dryRun, perPhaseBudget, totalDeadline, ctx, signal, recordImportId);
|
|
11069
|
+
if (out.notification_id && !notificationIds.includes(out.notification_id)) {
|
|
11070
|
+
notificationIds.push(out.notification_id);
|
|
11071
|
+
}
|
|
10686
11072
|
if (!dryRun) {
|
|
10687
11073
|
reconcileOneChunk(prep, out, matched, notImported);
|
|
10688
11074
|
}
|
|
@@ -10703,7 +11089,7 @@ var init_import_leads = __esm({
|
|
|
10703
11089
|
throw err;
|
|
10704
11090
|
}
|
|
10705
11091
|
}
|
|
10706
|
-
return buildImportLeadsResult(client, prep, importIds, matched, notImported, dryRun, cancelled);
|
|
11092
|
+
return buildImportLeadsResult(client, prep, importIds, matched, notImported, dryRun, cancelled, notificationIds);
|
|
10707
11093
|
}
|
|
10708
11094
|
};
|
|
10709
11095
|
}
|
|
@@ -11411,6 +11797,70 @@ var init_agent_memory_review = __esm({
|
|
|
11411
11797
|
}
|
|
11412
11798
|
});
|
|
11413
11799
|
|
|
11800
|
+
// ../core/dist/tools/acknowledge-notification.js
|
|
11801
|
+
var UUID_RE, acknowledgeNotification;
|
|
11802
|
+
var init_acknowledge_notification = __esm({
|
|
11803
|
+
"../core/dist/tools/acknowledge-notification.js"() {
|
|
11804
|
+
"use strict";
|
|
11805
|
+
init_tool_descriptions_generated();
|
|
11806
|
+
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
11807
|
+
acknowledgeNotification = {
|
|
11808
|
+
name: "leadbay_acknowledge_notification",
|
|
11809
|
+
annotations: {
|
|
11810
|
+
title: "Acknowledge a Leadbay notification",
|
|
11811
|
+
readOnlyHint: false,
|
|
11812
|
+
destructiveHint: false,
|
|
11813
|
+
idempotentHint: true,
|
|
11814
|
+
openWorldHint: true
|
|
11815
|
+
},
|
|
11816
|
+
description: leadbay_acknowledge_notification,
|
|
11817
|
+
write: true,
|
|
11818
|
+
inputSchema: {
|
|
11819
|
+
type: "object",
|
|
11820
|
+
properties: {
|
|
11821
|
+
notification_id: {
|
|
11822
|
+
type: "string",
|
|
11823
|
+
description: "UUID of the notification to acknowledge. Use the notification_id from `_meta.notifications[]` or `account_status.notifications[]`."
|
|
11824
|
+
},
|
|
11825
|
+
archive: {
|
|
11826
|
+
type: "boolean",
|
|
11827
|
+
description: "If true, archive the notification (won't appear in `archived=false` listings). If false / omitted, mark seen (resets firstSeenAt)."
|
|
11828
|
+
}
|
|
11829
|
+
},
|
|
11830
|
+
required: ["notification_id"],
|
|
11831
|
+
additionalProperties: false
|
|
11832
|
+
},
|
|
11833
|
+
outputSchema: {
|
|
11834
|
+
type: "object",
|
|
11835
|
+
properties: {
|
|
11836
|
+
acknowledged: { type: "boolean" },
|
|
11837
|
+
notification_id: { type: "string" },
|
|
11838
|
+
action: { type: "string", enum: ["seen", "archive"] }
|
|
11839
|
+
},
|
|
11840
|
+
required: ["acknowledged", "notification_id", "action"]
|
|
11841
|
+
},
|
|
11842
|
+
execute: async (client, params, ctx) => {
|
|
11843
|
+
if (!UUID_RE.test(params.notification_id)) {
|
|
11844
|
+
return {
|
|
11845
|
+
error: true,
|
|
11846
|
+
code: "BAD_INPUT",
|
|
11847
|
+
message: "notification_id must be a UUID",
|
|
11848
|
+
hint: "Pass the notification_id verbatim from _meta.notifications[].notification_id or account_status.notifications[].notification_id."
|
|
11849
|
+
};
|
|
11850
|
+
}
|
|
11851
|
+
const action = params.archive ? "archive" : "seen";
|
|
11852
|
+
await client.acknowledgeNotification(params.notification_id, action);
|
|
11853
|
+
ctx?.notificationsInbox?.markSeen(params.notification_id);
|
|
11854
|
+
return {
|
|
11855
|
+
acknowledged: true,
|
|
11856
|
+
notification_id: params.notification_id,
|
|
11857
|
+
action
|
|
11858
|
+
};
|
|
11859
|
+
}
|
|
11860
|
+
};
|
|
11861
|
+
}
|
|
11862
|
+
});
|
|
11863
|
+
|
|
11414
11864
|
// ../core/dist/tools/select-leads.js
|
|
11415
11865
|
var selectLeads;
|
|
11416
11866
|
var init_select_leads = __esm({
|
|
@@ -14760,6 +15210,11 @@ var init_account_status = __esm({
|
|
|
14760
15210
|
type: ["object", "null"],
|
|
14761
15211
|
description: "Per-resource quota state (llm_completion, ai_rescore, web_fetch, LENS_EXTRA_REFILL) across daily/weekly/monthly windows. Null if /quota_status failed (logged in stderr). Pre-check the LENS_EXTRA_REFILL entry before calling leadbay_extend_lens."
|
|
14762
15212
|
},
|
|
15213
|
+
notifications: {
|
|
15214
|
+
type: "array",
|
|
15215
|
+
description: "Terminal bulk-progress notifications the MCP knows about (background work the user or agent started that has since completed). Each entry carries notification_id, kind (bulk_enrich | bulk_qualify | import | other), bulk_progress counters, and a revise_hint pointing at prior agent outputs the just-finished work might have made stale. After revising affected outputs, call leadbay_acknowledge_notification(notification_id) to clear the entry. Empty array when nothing has completed.",
|
|
15216
|
+
items: { type: "object" }
|
|
15217
|
+
},
|
|
14763
15218
|
_meta: {
|
|
14764
15219
|
type: "object",
|
|
14765
15220
|
properties: {
|
|
@@ -14819,6 +15274,13 @@ var init_account_status = __esm({
|
|
|
14819
15274
|
// on /me are intentionally NOT surfaced — they're defunct (see
|
|
14820
15275
|
// SHAPE-DRIFT.md probe round 4).
|
|
14821
15276
|
quota,
|
|
15277
|
+
// Inbox of terminal bulk-progress notifications. Same shape the MCP
|
|
15278
|
+
// server attaches to `_meta.notifications` on every tool response —
|
|
15279
|
+
// duplicated here as a top-level field so the agent's daily-rhythm
|
|
15280
|
+
// check-in (this composite) sees them without having to read _meta.
|
|
15281
|
+
// Empty array when the WS listener isn't wired (OpenClaw, tests) OR
|
|
15282
|
+
// when nothing has completed since the last ack.
|
|
15283
|
+
notifications: ctx?.notificationsInbox?.list() ?? [],
|
|
14822
15284
|
_meta: {
|
|
14823
15285
|
region: client.region
|
|
14824
15286
|
}
|
|
@@ -14829,13 +15291,40 @@ var init_account_status = __esm({
|
|
|
14829
15291
|
});
|
|
14830
15292
|
|
|
14831
15293
|
// ../core/dist/composite/bulk-qualify-leads.js
|
|
14832
|
-
|
|
15294
|
+
async function launchBulkQualify(client, leadIds, ctx) {
|
|
15295
|
+
await client.acquireSelectionLock();
|
|
15296
|
+
try {
|
|
15297
|
+
try {
|
|
15298
|
+
const qs = leadIds.map((id) => `leadIds=${encodeURIComponent(id)}`).join("&");
|
|
15299
|
+
await client.requestVoid("POST", `/leads/selection/select?${qs}`);
|
|
15300
|
+
try {
|
|
15301
|
+
const resp = await client.request("POST", "/leads/selection/web_fetch?force_fetch=false", {});
|
|
15302
|
+
return { resp, quotaExceeded: false };
|
|
15303
|
+
} catch (err) {
|
|
15304
|
+
if (err?.code === "QUOTA_EXCEEDED") {
|
|
15305
|
+
ctx?.logger?.warn?.("bulk_qualify_leads: 429 on bulk /leads/selection/web_fetch \u2014 no leads queued");
|
|
15306
|
+
return { resp: null, quotaExceeded: true };
|
|
15307
|
+
}
|
|
15308
|
+
throw err;
|
|
15309
|
+
}
|
|
15310
|
+
} finally {
|
|
15311
|
+
try {
|
|
15312
|
+
await client.requestVoid("POST", "/leads/selection/clear");
|
|
15313
|
+
} catch (e) {
|
|
15314
|
+
ctx?.logger?.warn?.(`bulk_qualify_leads: selection.clear failed: ${e?.message ?? e?.code}`);
|
|
15315
|
+
}
|
|
15316
|
+
}
|
|
15317
|
+
} finally {
|
|
15318
|
+
client.releaseSelectionLock();
|
|
15319
|
+
}
|
|
15320
|
+
}
|
|
15321
|
+
var PAGE_SIZE, DEFAULT_COUNT2, MAX_COUNT, DEFAULT_PER_LEAD_BUDGET_MS, DEFAULT_TOTAL_BUDGET_MS2, bulkQualifyLeads;
|
|
14833
15322
|
var init_bulk_qualify_leads = __esm({
|
|
14834
15323
|
"../core/dist/composite/bulk-qualify-leads.js"() {
|
|
14835
15324
|
"use strict";
|
|
14836
15325
|
init_tool_descriptions_generated();
|
|
14837
15326
|
PAGE_SIZE = 50;
|
|
14838
|
-
|
|
15327
|
+
DEFAULT_COUNT2 = 10;
|
|
14839
15328
|
MAX_COUNT = 25;
|
|
14840
15329
|
DEFAULT_PER_LEAD_BUDGET_MS = 9e4;
|
|
14841
15330
|
DEFAULT_TOTAL_BUDGET_MS2 = 5 * 6e4;
|
|
@@ -14856,7 +15345,7 @@ var init_bulk_qualify_leads = __esm({
|
|
|
14856
15345
|
properties: {
|
|
14857
15346
|
count: {
|
|
14858
15347
|
type: "number",
|
|
14859
|
-
description: `How many fresh leads to qualify (default ${
|
|
15348
|
+
description: `How many fresh leads to qualify (default ${DEFAULT_COUNT2}, max ${MAX_COUNT})`
|
|
14860
15349
|
},
|
|
14861
15350
|
leadIds: {
|
|
14862
15351
|
type: "array",
|
|
@@ -14947,7 +15436,7 @@ var init_bulk_qualify_leads = __esm({
|
|
|
14947
15436
|
]
|
|
14948
15437
|
},
|
|
14949
15438
|
execute: async (client, params, ctx) => {
|
|
14950
|
-
const wantCount = Math.min(params.count ??
|
|
15439
|
+
const wantCount = Math.min(params.count ?? DEFAULT_COUNT2, MAX_COUNT);
|
|
14951
15440
|
const perLeadBudget = params.per_lead_budget_ms ?? DEFAULT_PER_LEAD_BUDGET_MS;
|
|
14952
15441
|
const totalBudget = params.total_budget_ms ?? DEFAULT_TOTAL_BUDGET_MS2;
|
|
14953
15442
|
const totalDeadline = Date.now() + totalBudget;
|
|
@@ -15006,69 +15495,50 @@ var init_bulk_qualify_leads = __esm({
|
|
|
15006
15495
|
per_lead_budget_ms: perLeadBudget,
|
|
15007
15496
|
total_budget_ms: totalBudget
|
|
15008
15497
|
});
|
|
15009
|
-
|
|
15010
|
-
|
|
15498
|
+
let launchedCount = 0;
|
|
15499
|
+
let notificationId = null;
|
|
15011
15500
|
let quotaExceeded2 = false;
|
|
15501
|
+
let failed2 = [];
|
|
15012
15502
|
if (!reservation.reused) {
|
|
15013
|
-
|
|
15014
|
-
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
15020
|
-
|
|
15021
|
-
|
|
15022
|
-
|
|
15023
|
-
failed2.push({ lead_id: leadId, error: "lead not found" });
|
|
15024
|
-
} else {
|
|
15025
|
-
failed2.push({
|
|
15026
|
-
lead_id: leadId,
|
|
15027
|
-
error: err?.message ?? err?.code ?? "unknown"
|
|
15028
|
-
});
|
|
15029
|
-
}
|
|
15030
|
-
}
|
|
15031
|
-
}
|
|
15032
|
-
if (failed2.length === candidates.length || launched2.length > 0 || quotaExceeded2) {
|
|
15033
|
-
await ctx.bulkTracker.markLaunched(reservation.record.bulk_id);
|
|
15503
|
+
const launch = await launchBulkQualify(client, candidates, ctx);
|
|
15504
|
+
quotaExceeded2 = launch.quotaExceeded;
|
|
15505
|
+
notificationId = launch.resp?.notification_id ?? null;
|
|
15506
|
+
const queuedIds = launch.resp?.queued_ids ?? [];
|
|
15507
|
+
const skippedIds = launch.resp?.skipped_ids ?? [];
|
|
15508
|
+
launchedCount = queuedIds.length;
|
|
15509
|
+
const seen = /* @__PURE__ */ new Set([...queuedIds, ...skippedIds]);
|
|
15510
|
+
failed2 = candidates.filter((id) => !seen.has(id)).map((id) => ({ lead_id: id, error: "not_queued" }));
|
|
15511
|
+
if (queuedIds.length > 0 || quotaExceeded2 || skippedIds.length > 0 || failed2.length === candidates.length) {
|
|
15512
|
+
await ctx.bulkTracker.markLaunched(reservation.record.bulk_id, notificationId);
|
|
15034
15513
|
}
|
|
15514
|
+
} else {
|
|
15515
|
+
notificationId = reservation.record.notification_id ?? null;
|
|
15516
|
+
launchedCount = reservation.record.lead_ids.length;
|
|
15035
15517
|
}
|
|
15036
15518
|
const out = {
|
|
15037
15519
|
status: "running",
|
|
15038
15520
|
handle_id: reservation.record.bulk_id,
|
|
15039
15521
|
qualify_id: reservation.record.bulk_id,
|
|
15040
15522
|
lead_ids: candidates,
|
|
15041
|
-
launched_count:
|
|
15523
|
+
launched_count: launchedCount,
|
|
15042
15524
|
failed: failed2,
|
|
15043
15525
|
quota_exceeded: quotaExceeded2,
|
|
15044
15526
|
lens_id: lensId,
|
|
15527
|
+
notification_id: notificationId,
|
|
15045
15528
|
_meta: { region: client.region }
|
|
15046
15529
|
};
|
|
15047
15530
|
return out;
|
|
15048
15531
|
}
|
|
15049
|
-
const
|
|
15050
|
-
const
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
await client.requestVoid("POST", `/leads/${leadId}/web_fetch?force_fetch=false`);
|
|
15057
|
-
launched.push(leadId);
|
|
15058
|
-
} catch (err) {
|
|
15059
|
-
if (err?.code === "QUOTA_EXCEEDED") {
|
|
15060
|
-
quotaExceeded = true;
|
|
15061
|
-
ctx?.logger?.warn?.(`bulk_qualify_leads: 429 mid-fanout after launching ${launched.length}/${candidates.length} \u2014 stopping further launches but polling those already in flight`);
|
|
15062
|
-
} else if (err?.code === "NOT_FOUND") {
|
|
15063
|
-
failed.push({ lead_id: leadId, error: "lead not found" });
|
|
15064
|
-
} else {
|
|
15065
|
-
failed.push({
|
|
15066
|
-
lead_id: leadId,
|
|
15067
|
-
error: err?.message ?? err?.code ?? "unknown"
|
|
15068
|
-
});
|
|
15069
|
-
}
|
|
15070
|
-
}
|
|
15532
|
+
const inlineLaunch = await launchBulkQualify(client, candidates, ctx);
|
|
15533
|
+
const quotaExceeded = inlineLaunch.quotaExceeded;
|
|
15534
|
+
const launched = inlineLaunch.resp?.queued_ids ?? [];
|
|
15535
|
+
const inlineSkipped = inlineLaunch.resp?.skipped_ids ?? [];
|
|
15536
|
+
const inlineNotificationId = inlineLaunch.resp?.notification_id ?? null;
|
|
15537
|
+
if (inlineNotificationId) {
|
|
15538
|
+
ctx?.logger?.info?.(`bulk_qualify_leads: launched bulk progress_notification_id=${inlineNotificationId} queued=${launched.length} skipped=${inlineSkipped.length}`);
|
|
15071
15539
|
}
|
|
15540
|
+
const inlineFailedSeen = /* @__PURE__ */ new Set([...launched, ...inlineSkipped]);
|
|
15541
|
+
const failed = candidates.filter((id) => !inlineFailedSeen.has(id)).map((id) => ({ lead_id: id, error: "not_queued" }));
|
|
15072
15542
|
let progressDone = 0;
|
|
15073
15543
|
const progressTotal = launched.length;
|
|
15074
15544
|
if (progressTotal > 0) {
|
|
@@ -15904,6 +16374,7 @@ var init_import_and_qualify = __esm({
|
|
|
15904
16374
|
...chosenBudgets ? { chosen_budgets: chosenBudgets } : {},
|
|
15905
16375
|
qualify_id: null,
|
|
15906
16376
|
import_ids: queued.importIds,
|
|
16377
|
+
notification_ids: queued.notification_ids ?? [],
|
|
15907
16378
|
imported: queued.leads.map((l) => ({
|
|
15908
16379
|
leadId: l.leadId,
|
|
15909
16380
|
...l.domain ? { domain: l.domain } : {},
|
|
@@ -15928,6 +16399,7 @@ var init_import_and_qualify = __esm({
|
|
|
15928
16399
|
...chosenBudgets ? { chosen_budgets: chosenBudgets } : {},
|
|
15929
16400
|
qualify_id: null,
|
|
15930
16401
|
import_ids: queued.importIds,
|
|
16402
|
+
notification_ids: queued.notification_ids ?? [],
|
|
15931
16403
|
imported: [],
|
|
15932
16404
|
not_imported: [],
|
|
15933
16405
|
qualified: [],
|
|
@@ -15965,6 +16437,7 @@ var init_import_and_qualify = __esm({
|
|
|
15965
16437
|
...chosenBudgets ? { chosen_budgets: chosenBudgets } : {},
|
|
15966
16438
|
qualify_id: null,
|
|
15967
16439
|
import_ids: importResult.importIds,
|
|
16440
|
+
notification_ids: importResult.notification_ids ?? [],
|
|
15968
16441
|
imported: [],
|
|
15969
16442
|
not_imported: importResult.not_imported.map(toNotImportedEntry),
|
|
15970
16443
|
qualified: [],
|
|
@@ -16002,6 +16475,7 @@ var init_import_and_qualify = __esm({
|
|
|
16002
16475
|
...chosenBudgets ? { chosen_budgets: chosenBudgets } : {},
|
|
16003
16476
|
qualify_id: null,
|
|
16004
16477
|
import_ids: importResult.importIds,
|
|
16478
|
+
notification_ids: importResult.notification_ids ?? [],
|
|
16005
16479
|
imported,
|
|
16006
16480
|
not_imported,
|
|
16007
16481
|
qualified: [],
|
|
@@ -16112,6 +16586,7 @@ var init_import_and_qualify = __esm({
|
|
|
16112
16586
|
...chosenBudgets ? { chosen_budgets: chosenBudgets } : {},
|
|
16113
16587
|
qualify_id: reservation.record.bulk_id,
|
|
16114
16588
|
import_ids: importResult.importIds,
|
|
16589
|
+
notification_ids: importResult.notification_ids ?? [],
|
|
16115
16590
|
imported,
|
|
16116
16591
|
not_imported,
|
|
16117
16592
|
qualified,
|
|
@@ -16707,16 +17182,20 @@ var init_bulk_store = __esm({
|
|
|
16707
17182
|
this.logger?.info?.(`bulk.import_failed bulk_id=${bulk_id}`);
|
|
16708
17183
|
});
|
|
16709
17184
|
}
|
|
16710
|
-
async markLaunched(bulk_id) {
|
|
17185
|
+
async markLaunched(bulk_id, notification_id) {
|
|
16711
17186
|
return this.mutex.run(async () => {
|
|
16712
17187
|
const all = this.prune(await this.readAll());
|
|
16713
17188
|
const idx = all.findIndex((r) => r.bulk_id === bulk_id);
|
|
16714
17189
|
if (idx < 0) {
|
|
16715
17190
|
throw new Error(`bulk_id not found: ${bulk_id}`);
|
|
16716
17191
|
}
|
|
16717
|
-
all[idx] = {
|
|
17192
|
+
all[idx] = {
|
|
17193
|
+
...all[idx],
|
|
17194
|
+
status: "launched",
|
|
17195
|
+
...notification_id ? { notification_id } : {}
|
|
17196
|
+
};
|
|
16718
17197
|
await this.writeAll(all);
|
|
16719
|
-
this.logger?.info?.(`bulk.launched bulk_id=${bulk_id}`);
|
|
17198
|
+
this.logger?.info?.(`bulk.launched bulk_id=${bulk_id}${notification_id ? ` notification_id=${notification_id}` : ""}`);
|
|
16720
17199
|
return all[idx];
|
|
16721
17200
|
});
|
|
16722
17201
|
}
|
|
@@ -16968,6 +17447,14 @@ var init_import_status = __esm({
|
|
|
16968
17447
|
});
|
|
16969
17448
|
|
|
16970
17449
|
// ../core/dist/composite/qualify-status.js
|
|
17450
|
+
async function readNotification(client, notificationId) {
|
|
17451
|
+
try {
|
|
17452
|
+
const page = await client.listNotifications({ archived: false, count: 50 });
|
|
17453
|
+
return page.items.find((n) => n.id === notificationId) ?? null;
|
|
17454
|
+
} catch {
|
|
17455
|
+
return null;
|
|
17456
|
+
}
|
|
17457
|
+
}
|
|
16971
17458
|
var qualifyStatus;
|
|
16972
17459
|
var init_qualify_status = __esm({
|
|
16973
17460
|
"../core/dist/composite/qualify-status.js"() {
|
|
@@ -17129,6 +17616,16 @@ var init_qualify_status = __esm({
|
|
|
17129
17616
|
const { _stillRunning, _failedCode, ...rest } = r;
|
|
17130
17617
|
qualified.push(rest);
|
|
17131
17618
|
}
|
|
17619
|
+
let bulkProgress = null;
|
|
17620
|
+
let inProgressFlag = null;
|
|
17621
|
+
const notifId = record.notification_id ?? null;
|
|
17622
|
+
if (notifId) {
|
|
17623
|
+
const n = await readNotification(client, notifId);
|
|
17624
|
+
if (n) {
|
|
17625
|
+
bulkProgress = n.bulk_progress;
|
|
17626
|
+
inProgressFlag = n.in_progress;
|
|
17627
|
+
}
|
|
17628
|
+
}
|
|
17132
17629
|
const out = {
|
|
17133
17630
|
qualify_id: record.bulk_id,
|
|
17134
17631
|
launched_at: record.launched_at,
|
|
@@ -17140,6 +17637,9 @@ var init_qualify_status = __esm({
|
|
|
17140
17637
|
still_running,
|
|
17141
17638
|
failed,
|
|
17142
17639
|
not_in_lens: [...notInLensSet],
|
|
17640
|
+
notification_id: notifId,
|
|
17641
|
+
bulk_progress: bulkProgress,
|
|
17642
|
+
in_progress: inProgressFlag,
|
|
17143
17643
|
region: client.region,
|
|
17144
17644
|
_meta: client.lastMeta ?? {
|
|
17145
17645
|
region: client.region,
|
|
@@ -17152,6 +17652,9 @@ var init_qualify_status = __esm({
|
|
|
17152
17652
|
out.per_lead_budget_ms = record.per_lead_budget_ms;
|
|
17153
17653
|
if (record.total_budget_ms !== void 0)
|
|
17154
17654
|
out.total_budget_ms = record.total_budget_ms;
|
|
17655
|
+
if (bulkProgress && bulkProgress.quota_hit_count > 0) {
|
|
17656
|
+
out.quota_hit_hint = "Some leads hit the AI-credits quota during qualification. Top up via leadbay_create_topup_link to clear the throttle immediately, or wait until the daily/weekly window resets.";
|
|
17657
|
+
}
|
|
17155
17658
|
return out;
|
|
17156
17659
|
}
|
|
17157
17660
|
};
|
|
@@ -17429,6 +17932,7 @@ var init_enrich_titles = __esm({
|
|
|
17429
17932
|
bulk_id: res.record.bulk_id,
|
|
17430
17933
|
launched_at: res.record.launched_at,
|
|
17431
17934
|
durability: res.record.durability,
|
|
17935
|
+
notification_id: res.record.notification_id ?? null,
|
|
17432
17936
|
seconds_since_original_launch: bulkSecondsSinceOriginal ?? 0,
|
|
17433
17937
|
titles: params.titles,
|
|
17434
17938
|
email,
|
|
@@ -17444,8 +17948,9 @@ var init_enrich_titles = __esm({
|
|
|
17444
17948
|
total: 3,
|
|
17445
17949
|
message: `Launching enrichment for ${params.titles.length} title${params.titles.length === 1 ? "" : "s"}\u2026`
|
|
17446
17950
|
});
|
|
17951
|
+
let launchResp = null;
|
|
17447
17952
|
try {
|
|
17448
|
-
await client.
|
|
17953
|
+
launchResp = await client.request("POST", "/leads/selection/enrichment/launch", { titles: params.titles, email, phone });
|
|
17449
17954
|
} catch (err) {
|
|
17450
17955
|
const aborted = err?.name === "AbortError" || ctx?.signal?.aborted === true;
|
|
17451
17956
|
if (bulkRecord && tracker) {
|
|
@@ -17469,9 +17974,10 @@ var init_enrich_titles = __esm({
|
|
|
17469
17974
|
}
|
|
17470
17975
|
throw err;
|
|
17471
17976
|
}
|
|
17977
|
+
const notificationId = launchResp?.notification_id ?? null;
|
|
17472
17978
|
if (bulkRecord && tracker) {
|
|
17473
17979
|
try {
|
|
17474
|
-
await tracker.markLaunched(bulkRecord.bulk_id);
|
|
17980
|
+
await tracker.markLaunched(bulkRecord.bulk_id, notificationId);
|
|
17475
17981
|
} catch (e) {
|
|
17476
17982
|
ctx?.logger?.warn?.(`enrich_titles: tracker.markLaunched failed: ${e?.message ?? e}`);
|
|
17477
17983
|
return {
|
|
@@ -17499,8 +18005,9 @@ var init_enrich_titles = __esm({
|
|
|
17499
18005
|
bulk_id: bulkRecord?.bulk_id,
|
|
17500
18006
|
launched_at: bulkRecord?.launched_at,
|
|
17501
18007
|
durability: bulkRecord?.durability,
|
|
17502
|
-
|
|
17503
|
-
|
|
18008
|
+
notification_id: notificationId,
|
|
18009
|
+
message: notificationId ? "Enrichment job launched. The MCP is now listening for the backend notification \u2014 when enrichment finishes, a `_meta.notifications` entry will surface on your next tool response (also visible in `leadbay_account_status.notifications`)." : bulkRecord ? "Enrichment job launched. Backend did not return a notification id this time; poll via leadbay_bulk_enrich_status with the bulk_id." : "Enrichment job launched. No bulk_id tracker configured \u2014 poll leadbay_get_contacts per lead after ~60s; contact.enrichment.done flips to true.",
|
|
18010
|
+
next_action: notificationId ? "Wait for the next `_meta.notifications` entry (typically <2 min for a small batch). If you want progress sooner, call leadbay_bulk_enrich_status({bulk_id})." : bulkRecord ? "Call leadbay_bulk_enrich_status({bulk_id}) after ~60s; pass include_contacts=true for the final read." : "Wait ~60s, then call leadbay_research_lead_by_id or leadbay_get_contacts on the leads you care about."
|
|
17504
18011
|
};
|
|
17505
18012
|
} finally {
|
|
17506
18013
|
try {
|
|
@@ -17518,6 +18025,14 @@ var init_enrich_titles = __esm({
|
|
|
17518
18025
|
});
|
|
17519
18026
|
|
|
17520
18027
|
// ../core/dist/composite/bulk-enrich-status.js
|
|
18028
|
+
async function readNotification2(client, notificationId) {
|
|
18029
|
+
try {
|
|
18030
|
+
const page = await client.listNotifications({ archived: false, count: 50 });
|
|
18031
|
+
return page.items.find((n) => n.id === notificationId) ?? null;
|
|
18032
|
+
} catch {
|
|
18033
|
+
return null;
|
|
18034
|
+
}
|
|
18035
|
+
}
|
|
17521
18036
|
async function pMap(items, fn, concurrency) {
|
|
17522
18037
|
const out = new Array(items.length);
|
|
17523
18038
|
let next = 0;
|
|
@@ -17689,6 +18204,53 @@ var init_bulk_enrich_status = __esm({
|
|
|
17689
18204
|
launched_at: record.launched_at
|
|
17690
18205
|
};
|
|
17691
18206
|
}
|
|
18207
|
+
const notifId = record.notification_id ?? null;
|
|
18208
|
+
if (notifId) {
|
|
18209
|
+
const n = await readNotification2(client, notifId);
|
|
18210
|
+
if (n && n.bulk_progress) {
|
|
18211
|
+
const bp = n.bulk_progress;
|
|
18212
|
+
const inProgress = n.in_progress;
|
|
18213
|
+
let leads2 = [];
|
|
18214
|
+
if (!inProgress && includeContacts) {
|
|
18215
|
+
leads2 = await pMap(record.lead_ids, async (leadId) => {
|
|
18216
|
+
try {
|
|
18217
|
+
const out = await getContacts.execute(client, { leadId });
|
|
18218
|
+
const contacts = Array.isArray(out?.contacts) ? out.contacts : [];
|
|
18219
|
+
return { lead_id: leadId, contacts };
|
|
18220
|
+
} catch {
|
|
18221
|
+
return { lead_id: leadId };
|
|
18222
|
+
}
|
|
18223
|
+
}, STATUS_FETCH_CONCURRENCY);
|
|
18224
|
+
} else {
|
|
18225
|
+
leads2 = record.lead_ids.map((id) => ({ lead_id: id }));
|
|
18226
|
+
}
|
|
18227
|
+
ctx?.logger?.info?.(`bulk.status_checked_via_notification bulk_id=${record.bulk_id} notification_id=${notifId} done=${bp.success_count}/${bp.total_count} in_progress=${inProgress} wall_ms=${Date.now() - startMs}`);
|
|
18228
|
+
return {
|
|
18229
|
+
bulk_id: record.bulk_id,
|
|
18230
|
+
notification_id: notifId,
|
|
18231
|
+
launched_at: record.launched_at,
|
|
18232
|
+
status: record.status,
|
|
18233
|
+
durability: record.durability,
|
|
18234
|
+
titles: record.titles,
|
|
18235
|
+
email: record.email,
|
|
18236
|
+
phone: record.phone,
|
|
18237
|
+
lens_id: record.lens_id,
|
|
18238
|
+
leads: leads2,
|
|
18239
|
+
overall_progress: {
|
|
18240
|
+
done: bp.success_count + bp.failure_count + bp.quota_hit_count,
|
|
18241
|
+
total: bp.total_count,
|
|
18242
|
+
done_ratio: bp.total_count === 0 ? 0 : (bp.success_count + bp.failure_count + bp.quota_hit_count) / bp.total_count
|
|
18243
|
+
},
|
|
18244
|
+
bulk_progress: bp,
|
|
18245
|
+
in_progress: inProgress,
|
|
18246
|
+
all_done: !inProgress,
|
|
18247
|
+
...bp.quota_hit_count > 0 ? {
|
|
18248
|
+
quota_hit_hint: "Some contacts could not be enriched because the AI-credits quota was hit. Top up via leadbay_create_topup_link or wait for the window reset."
|
|
18249
|
+
} : {}
|
|
18250
|
+
};
|
|
18251
|
+
}
|
|
18252
|
+
ctx?.logger?.info?.(`bulk_enrich_status: notification ${notifId} not yet visible; falling back to per-lead fan-out`);
|
|
18253
|
+
}
|
|
17692
18254
|
let doneSoFar = 0;
|
|
17693
18255
|
const totalLeads = record.lead_ids.length;
|
|
17694
18256
|
const results = await pMap(record.lead_ids, async (leadId) => {
|
|
@@ -19457,8 +20019,11 @@ __export(dist_exports, {
|
|
|
19457
20019
|
InMemoryBulkStore: () => InMemoryBulkStore,
|
|
19458
20020
|
LeadbayClient: () => LeadbayClient,
|
|
19459
20021
|
LocalBulkStore: () => LocalBulkStore,
|
|
20022
|
+
NotificationsInbox: () => NotificationsInbox,
|
|
20023
|
+
NotificationsWsClient: () => NotificationsWsClient,
|
|
19460
20024
|
REGIONS: () => REGIONS,
|
|
19461
20025
|
accountStatus: () => accountStatus,
|
|
20026
|
+
acknowledgeNotification: () => acknowledgeNotification,
|
|
19462
20027
|
addLeadsToCampaign: () => addLeadsToCampaign,
|
|
19463
20028
|
addNote: () => addNote,
|
|
19464
20029
|
adjustAudience: () => adjustAudience,
|
|
@@ -19466,6 +20031,7 @@ __export(dist_exports, {
|
|
|
19466
20031
|
agentMemoryRecall: () => agentMemoryRecall,
|
|
19467
20032
|
agentMemoryReview: () => agentMemoryReview,
|
|
19468
20033
|
agentMemoryTools: () => agentMemoryTools,
|
|
20034
|
+
anchorIdFor: () => anchorIdFor,
|
|
19469
20035
|
answerClarification: () => answerClarification,
|
|
19470
20036
|
appendEntry: () => appendEntry,
|
|
19471
20037
|
appendTombstone: () => appendTombstone,
|
|
@@ -19474,6 +20040,7 @@ __export(dist_exports, {
|
|
|
19474
20040
|
bulkQualifyLeads: () => bulkQualifyLeads,
|
|
19475
20041
|
campaignCallSheet: () => campaignCallSheet,
|
|
19476
20042
|
campaignProgression: () => campaignProgression,
|
|
20043
|
+
catchUpNotifications: () => catchUpNotifications,
|
|
19477
20044
|
clearAgentMemoryCache: () => clearAgentMemoryCache,
|
|
19478
20045
|
clearMockJournal: () => clearMockJournal,
|
|
19479
20046
|
clearSelection: () => clearSelection,
|
|
@@ -19523,6 +20090,7 @@ __export(dist_exports, {
|
|
|
19523
20090
|
importAndQualify: () => importAndQualify,
|
|
19524
20091
|
importLeads: () => importLeads,
|
|
19525
20092
|
importStatus: () => importStatus,
|
|
20093
|
+
inferKind: () => inferKind,
|
|
19526
20094
|
invalidateAgentMemoryCache: () => invalidateAgentMemoryCache,
|
|
19527
20095
|
isAgentMemoryEnabled: () => isAgentMemoryEnabled,
|
|
19528
20096
|
isValidBulkId: () => isValidBulkId,
|
|
@@ -19563,12 +20131,14 @@ __export(dist_exports, {
|
|
|
19563
20131
|
resolveAgentMemorySummary: () => resolveAgentMemorySummary,
|
|
19564
20132
|
resolveImportRows: () => resolveImportRows,
|
|
19565
20133
|
resolveRegion: () => resolveRegion,
|
|
20134
|
+
reviseHintFor: () => reviseHintFor,
|
|
19566
20135
|
seedCandidates: () => seedCandidates,
|
|
19567
20136
|
selectLeads: () => selectLeads,
|
|
19568
20137
|
setActiveLens: () => setActiveLens,
|
|
19569
20138
|
setEpilogueStatus: () => setEpilogueStatus,
|
|
19570
20139
|
setPushback: () => setPushback,
|
|
19571
20140
|
setUserPrompt: () => setUserPrompt,
|
|
20141
|
+
toInboxEntry: () => toInboxEntry,
|
|
19572
20142
|
tools: () => tools,
|
|
19573
20143
|
tourPlan: () => tourPlan,
|
|
19574
20144
|
updateLens: () => updateLens,
|
|
@@ -19583,6 +20153,7 @@ var init_dist = __esm({
|
|
|
19583
20153
|
init_types();
|
|
19584
20154
|
init_agent_memory();
|
|
19585
20155
|
init_composite_file_names();
|
|
20156
|
+
init_notifications();
|
|
19586
20157
|
init_login();
|
|
19587
20158
|
init_list_lenses();
|
|
19588
20159
|
init_discover_leads();
|
|
@@ -19612,6 +20183,7 @@ var init_dist = __esm({
|
|
|
19612
20183
|
init_agent_memory_recall();
|
|
19613
20184
|
init_agent_memory_capture();
|
|
19614
20185
|
init_agent_memory_review();
|
|
20186
|
+
init_acknowledge_notification();
|
|
19615
20187
|
init_select_leads();
|
|
19616
20188
|
init_deselect_leads();
|
|
19617
20189
|
init_clear_selection();
|
|
@@ -19771,7 +20343,13 @@ var init_dist = __esm({
|
|
|
19771
20343
|
// didn't deliver"). Does not mutate Leadbay state; emits a PostHog
|
|
19772
20344
|
// event only. Companion to leadbay_report_outreach (which DOES write
|
|
19773
20345
|
// to the backend and stays gated behind LEADBAY_MCP_WRITE).
|
|
19774
|
-
reportFriction
|
|
20346
|
+
reportFriction,
|
|
20347
|
+
// Notification ack — ALWAYS exposed even though it POSTs to /seen.
|
|
20348
|
+
// _meta.notifications surfaces terminal bulk-progress notifications on
|
|
20349
|
+
// every tool response regardless of write gating; without ack the agent
|
|
20350
|
+
// sees the same entries on every call forever. Pairing the surfacing
|
|
20351
|
+
// channel with the clearing tool is non-optional.
|
|
20352
|
+
acknowledgeNotification
|
|
19775
20353
|
];
|
|
19776
20354
|
compositeWriteTools = [
|
|
19777
20355
|
bulkQualifyLeads,
|
|
@@ -22119,6 +22697,22 @@ function buildServer(client, opts = {}) {
|
|
|
22119
22697
|
});
|
|
22120
22698
|
}
|
|
22121
22699
|
};
|
|
22700
|
+
const maybeAttachNotifications = (result) => {
|
|
22701
|
+
const inbox = opts.notificationsInbox;
|
|
22702
|
+
if (!inbox) return;
|
|
22703
|
+
if (result === null || typeof result !== "object" || Array.isArray(result)) {
|
|
22704
|
+
return;
|
|
22705
|
+
}
|
|
22706
|
+
const entries = inbox.list();
|
|
22707
|
+
if (entries.length === 0) return;
|
|
22708
|
+
const envelope = result;
|
|
22709
|
+
const target = envelope.__markdown_envelope === true && envelope.structured !== null && typeof envelope.structured === "object" && !Array.isArray(envelope.structured) ? envelope.structured : envelope;
|
|
22710
|
+
const existingMeta = target._meta && typeof target._meta === "object" && !Array.isArray(target._meta) ? target._meta : {};
|
|
22711
|
+
target._meta = {
|
|
22712
|
+
...existingMeta,
|
|
22713
|
+
notifications: entries
|
|
22714
|
+
};
|
|
22715
|
+
};
|
|
22122
22716
|
const isLeadbayBusinessError = (err) => err != null && typeof err === "object" && err.error === true && typeof err.code === "string";
|
|
22123
22717
|
const buildBusinessCtx = (toolName, envelope, triggered_by) => {
|
|
22124
22718
|
const meta = envelope._meta ?? {};
|
|
@@ -22238,11 +22832,13 @@ function buildServer(client, opts = {}) {
|
|
|
22238
22832
|
const result = await tool.execute(client, args, {
|
|
22239
22833
|
logger: opts.logger,
|
|
22240
22834
|
bulkTracker: opts.bulkTracker,
|
|
22835
|
+
notificationsInbox: opts.notificationsInbox,
|
|
22241
22836
|
signal: extra.signal,
|
|
22242
22837
|
progress,
|
|
22243
22838
|
elicit
|
|
22244
22839
|
});
|
|
22245
22840
|
maybeAttachUpdate(name, result);
|
|
22841
|
+
maybeAttachNotifications(result);
|
|
22246
22842
|
if (result && typeof result === "object" && result.error === true) {
|
|
22247
22843
|
const envText = formatErrorForLLM(result);
|
|
22248
22844
|
const envDur = Date.now() - callStart;
|
|
@@ -23680,7 +24276,7 @@ var OAUTH_BASE_URLS = {
|
|
|
23680
24276
|
fr: "https://staging.api.leadbay.app"
|
|
23681
24277
|
}
|
|
23682
24278
|
};
|
|
23683
|
-
var VERSION = "0.
|
|
24279
|
+
var VERSION = "0.18.0";
|
|
23684
24280
|
var HELP = `
|
|
23685
24281
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
23686
24282
|
|
|
@@ -24777,21 +25373,41 @@ async function main() {
|
|
|
24777
25373
|
logger.warn?.(`update_check.unexpected ${err?.message ?? err}`);
|
|
24778
25374
|
});
|
|
24779
25375
|
}
|
|
25376
|
+
const notificationsInbox = new NotificationsInbox();
|
|
25377
|
+
let notificationsWs = null;
|
|
25378
|
+
const WS_DISABLED = process.env.LEADBAY_NOTIFICATIONS_WS_DISABLED === "1" || authState !== "ok";
|
|
25379
|
+
if (!WS_DISABLED) {
|
|
25380
|
+
notificationsWs = new NotificationsWsClient({
|
|
25381
|
+
client,
|
|
25382
|
+
inbox: notificationsInbox,
|
|
25383
|
+
logger
|
|
25384
|
+
});
|
|
25385
|
+
void notificationsWs.start().catch((err) => {
|
|
25386
|
+
logger.warn?.(
|
|
25387
|
+
`notifications.ws start_failed: ${err?.message ?? err}`
|
|
25388
|
+
);
|
|
25389
|
+
});
|
|
25390
|
+
}
|
|
24780
25391
|
const server = buildServer(client, {
|
|
24781
25392
|
includeAdvanced,
|
|
24782
25393
|
includeWrite,
|
|
24783
25394
|
logger,
|
|
24784
25395
|
bulkTracker,
|
|
25396
|
+
notificationsInbox,
|
|
24785
25397
|
version: VERSION,
|
|
24786
25398
|
telemetry,
|
|
24787
25399
|
updateStateStore
|
|
24788
25400
|
});
|
|
24789
25401
|
const transport = new StdioServerTransport();
|
|
24790
25402
|
logger.info?.(
|
|
24791
|
-
`Starting MCP server v${VERSION} (advanced=${includeAdvanced}, write=${includeWrite}, baseUrl=${client.baseUrl}, bulk_store=${bulkTracker.durability}, auth_state=${authState})`
|
|
25403
|
+
`Starting MCP server v${VERSION} (advanced=${includeAdvanced}, write=${includeWrite}, baseUrl=${client.baseUrl}, bulk_store=${bulkTracker.durability}, notifications_ws=${WS_DISABLED ? "disabled" : "enabled"}, auth_state=${authState})`
|
|
24792
25404
|
);
|
|
24793
25405
|
await server.connect(transport);
|
|
24794
25406
|
const shutdown = async (code) => {
|
|
25407
|
+
try {
|
|
25408
|
+
notificationsWs?.stop();
|
|
25409
|
+
} catch {
|
|
25410
|
+
}
|
|
24795
25411
|
try {
|
|
24796
25412
|
await telemetry.shutdown();
|
|
24797
25413
|
} finally {
|