@leadbay/mcp 0.19.2 → 0.20.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 +9 -0
- package/dist/bin.js +327 -50
- package/dist/http-server.js +277 -46
- package/dist/installer-electron.js +1 -1
- package/dist/installer-gui.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog — @leadbay/mcp
|
|
2
2
|
|
|
3
|
+
## 0.20.0 — 2026-06-15
|
|
4
|
+
|
|
5
|
+
- **Proactive update proposal on a fresh session** (product#3742): the auto-update check already ran at boot, but the resulting proposal only reached the user if the agent happened to call `leadbay_account_status` — which a fresh session rarely does, so the "newer version available" prompt was effectively invisible. The cached `update_available` block now also rides along on `_meta.update_available` of the **first ordinary tool result** of a session while an upgrade is pending, gated once-per-version so it surfaces exactly once. `leadbay_account_status` keeps carrying it as a top-level field. The server-instruction paragraph now tells the agent to surface the `ask_user_input_v0` prompt whenever it sees the field on *any* response.
|
|
6
|
+
- **Installer asset is now `.dxt`, not `.mcpb`**: the release-asset picker prefers the `.dxt` bundle (falling back to `.mcpb` only when a release ships no `.dxt`). The field is renamed `mcpb_url` → `install_url` across `update_available`, the `leadbay_acknowledge_update` result, and the persisted update-state — with forward-migration of the legacy `latest_known_mcpb_url` key so existing users don't lose their cache.
|
|
7
|
+
|
|
8
|
+
## 0.19.3 — 2026-06-15
|
|
9
|
+
|
|
10
|
+
- **New tool `leadbay_send_feedback`**: delivers a user-authored message to the same destination as the web app's "Send feedback" form — the team's Sentry feedback inbox (the website form calls `Sentry.captureFeedback`; there is no Leadbay API endpoint, so the MCP reuses its already-initialized `@sentry/node`). User-initiated ("send feedback / report a bug / tell Leadbay…"), or offered on a tool error and sent only on explicit yes. Distinct from the silent, agent-detected, PostHog-only `leadbay_report_friction`: feedback is explicit, user-authored, and reaches the team's inbox. Honest delivery — if the Sentry transport isn't available it returns `sent:false`, never a false success; Sentry is flushed after capture so the event actually ships; identity is attached when it resolves (anonymous fallback rather than dropping the message). Write-gated (`LEADBAY_MCP_WRITE=1`) since it sends data outward.
|
|
11
|
+
|
|
3
12
|
## 0.19.2 — 2026-06-10
|
|
4
13
|
|
|
5
14
|
- **Stop paging Sentry on a missing `_triggered_by`**: a composite tool called without `_triggered_by` is a recoverable agent mistake — the host just re-calls with the field set. The guard used to `throw` an `{error:true, code:"LAST_PROMPT_REQUIRED"}` envelope into the shared catch, where `isLeadbayBusinessError` matched it and fired `captureException`, auto-opening a top-priority Sentry/GitHub bug (product#3718) on every dropped field. The guard now returns the `isError` envelope directly. Behavior toward the LLM is unchanged (same text, same `isError`, same recovery hint), and PostHog visibility is preserved (`captureToolCall` + `captureCompositeCall` still fire `ok:false` / `LAST_PROMPT_REQUIRED`, so the mandate-ignore rate stays observable); only `captureException` is dropped.
|
package/dist/bin.js
CHANGED
|
@@ -277,13 +277,40 @@ var init_client = __esm({
|
|
|
277
277
|
if (next)
|
|
278
278
|
next();
|
|
279
279
|
}
|
|
280
|
-
|
|
280
|
+
// Leadbay tokens don't expire, so a 401 is almost always a transient
|
|
281
|
+
// server-side blip. Retry the request ONCE before surfacing it — a single
|
|
282
|
+
// retry clears the vast majority of these without the agent ever seeing an
|
|
283
|
+
// error. If the retry also 401s, it's a real Leadbay-side problem and the
|
|
284
|
+
// error envelope says so.
|
|
285
|
+
//
|
|
286
|
+
// Arrow-function field so `this` stays bound even when the method is passed
|
|
287
|
+
// as a bare reference (see request()'s ternary). Retries are GET-ONLY: a 401
|
|
288
|
+
// on a write (POST/PUT/DELETE) may arrive AFTER the mutation already committed
|
|
289
|
+
// server-side, so blindly re-sending it would double-execute the write. Reads
|
|
290
|
+
// are idempotent, so retrying them is safe. The 250ms backoff releases the
|
|
291
|
+
// concurrency slot first (release → sleep → re-acquire) so a wave of 401s
|
|
292
|
+
// doesn't pin all MAX_CONCURRENT slots in setTimeout and stall the queue.
|
|
293
|
+
httpsRequestWithRetry = async (method, url, headers, body) => {
|
|
294
|
+
const res = await httpsRequest(method, url, headers, body);
|
|
295
|
+
if (res.status === 401 && method.toUpperCase() === "GET") {
|
|
296
|
+
this.releaseSemaphore();
|
|
297
|
+
try {
|
|
298
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
299
|
+
} finally {
|
|
300
|
+
await this.acquireSemaphore();
|
|
301
|
+
}
|
|
302
|
+
return httpsRequest(method, url, headers, body);
|
|
303
|
+
}
|
|
304
|
+
return res;
|
|
305
|
+
};
|
|
306
|
+
async request(method, path, body, opts) {
|
|
281
307
|
if (process.env.LEADBAY_MOCK === "1") {
|
|
282
308
|
return this.mockRequest(method, path, body);
|
|
283
309
|
}
|
|
284
310
|
if (!this.token) {
|
|
285
311
|
throw this.makeError("NOT_AUTHENTICATED", "Not logged in to Leadbay", "Set LEADBAY_TOKEN in your MCP client config, or run: npx -y @leadbay/mcp install --email <you> --region <us|fr>", path);
|
|
286
312
|
}
|
|
313
|
+
const retryOn401 = opts?.retryOn401 !== false;
|
|
287
314
|
await this.acquireSemaphore();
|
|
288
315
|
try {
|
|
289
316
|
const url = `${this._baseUrl}/1.5${path}`;
|
|
@@ -293,7 +320,7 @@ var init_client = __esm({
|
|
|
293
320
|
if (body) {
|
|
294
321
|
headers["Content-Type"] = "application/json";
|
|
295
322
|
}
|
|
296
|
-
const res = await httpsRequest(method, url, headers, body ? JSON.stringify(body) : void 0);
|
|
323
|
+
const res = await (retryOn401 ? this.httpsRequestWithRetry : httpsRequest)(method, url, headers, body ? JSON.stringify(body) : void 0);
|
|
297
324
|
this._lastMeta = {
|
|
298
325
|
region: this._region,
|
|
299
326
|
endpoint: `${method} ${path}`,
|
|
@@ -328,7 +355,7 @@ var init_client = __esm({
|
|
|
328
355
|
if (body) {
|
|
329
356
|
headers["Content-Type"] = "application/json";
|
|
330
357
|
}
|
|
331
|
-
const res = await
|
|
358
|
+
const res = await this.httpsRequestWithRetry(method, url, headers, body ? JSON.stringify(body) : void 0);
|
|
332
359
|
this._lastMeta = {
|
|
333
360
|
region: this._region,
|
|
334
361
|
endpoint: `${method} ${path}`,
|
|
@@ -361,7 +388,7 @@ var init_client = __esm({
|
|
|
361
388
|
Authorization: `Bearer ${this.token}`,
|
|
362
389
|
"Content-Type": contentType
|
|
363
390
|
};
|
|
364
|
-
const res = await
|
|
391
|
+
const res = await this.httpsRequestWithRetry(method, url, headers, body);
|
|
365
392
|
this._lastMeta = {
|
|
366
393
|
region: this._region,
|
|
367
394
|
endpoint: `${method} ${path}`,
|
|
@@ -444,7 +471,7 @@ var init_client = __esm({
|
|
|
444
471
|
}
|
|
445
472
|
const retryAfter = parseRetryAfter(headers["retry-after"]);
|
|
446
473
|
if (status === 401) {
|
|
447
|
-
return this.makeError("AUTH_EXPIRED", "
|
|
474
|
+
return this.makeError("AUTH_EXPIRED", "Leadbay rejected this request (401)", "Leadbay tokens don't expire on a timer, so this isn't a stale token. A 401 here is usually a Leadbay-side hiccup, but can also mean the user logged out. Try again shortly; if it persists, offer to report it to the team.", endpoint, null, status);
|
|
448
475
|
}
|
|
449
476
|
if (status === 429 || status === 402 || parsed?.error === "quota_exceeded" || parsed?.error?.code === "quota_exceeded") {
|
|
450
477
|
const hintBase = retryAfter ? `Wait ${retryAfter}s before retrying` : "Wait, then retry";
|
|
@@ -5440,7 +5467,7 @@ var init_notifications = __esm({
|
|
|
5440
5467
|
});
|
|
5441
5468
|
|
|
5442
5469
|
// ../core/dist/tool-descriptions.generated.js
|
|
5443
|
-
var leadbay_account_history, leadbay_account_status, leadbay_acknowledge_notification, leadbay_add_contact, 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_pin_contact, 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_contact, 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_scan_portfolio_signals, 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_unpin_contact, leadbay_update_contact, leadbay_update_lens, leadbay_update_lens_filter;
|
|
5470
|
+
var leadbay_account_history, leadbay_account_status, leadbay_acknowledge_notification, leadbay_add_contact, 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_pin_contact, 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_contact, 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_scan_portfolio_signals, leadbay_seed_candidates, leadbay_select_leads, leadbay_send_feedback, leadbay_set_active_lens, leadbay_set_epilogue_status, leadbay_set_pushback, leadbay_set_user_prompt, leadbay_tour_plan, leadbay_unpin_contact, leadbay_update_contact, leadbay_update_lens, leadbay_update_lens_filter;
|
|
5444
5471
|
var init_tool_descriptions_generated = __esm({
|
|
5445
5472
|
"../core/dist/tool-descriptions.generated.js"() {
|
|
5446
5473
|
"use strict";
|
|
@@ -8722,6 +8749,88 @@ WHEN TO USE: low-level. The user's selection is a per-token global state \u2014
|
|
|
8722
8749
|
WHEN NOT TO USE: in normal flow \u2014 leadbay_enrich_titles wraps select \u2192 action \u2192 clear in one call with proper Mutex protection. Calling this directly without acquiring the selection lock can clobber concurrent composite calls.
|
|
8723
8750
|
|
|
8724
8751
|
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\`.
|
|
8752
|
+
`;
|
|
8753
|
+
leadbay_send_feedback = `## WHEN TO USE
|
|
8754
|
+
|
|
8755
|
+
Trigger phrases: "send feedback", "I want to report a bug", "tell the Leadbay team", "let Leadbay know", "give feedback", "report this to support", "I have a feature request".
|
|
8756
|
+
|
|
8757
|
+
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
8758
|
+
|
|
8759
|
+
Do NOT use for: "no, I meant / still nothing / ugh" \u2192 \`leadbay_report_friction\`; "log the email I sent" \u2192 \`leadbay_report_outreach\`.
|
|
8760
|
+
|
|
8761
|
+
Prefer when: the user explicitly wants the Leadbay TEAM to receive a message they authored \u2014 or accepts your offer to report an error. For silent, agent-detected friction signals use leadbay_report_friction instead.
|
|
8762
|
+
|
|
8763
|
+
Examples that SHOULD invoke this tool:
|
|
8764
|
+
- "Send feedback to the team: the lead scores feel off this week."
|
|
8765
|
+
- "Can you report a bug? Pulling leads in Lyon returns nothing."
|
|
8766
|
+
- "Tell Leadbay I'd love a way to schedule my morning check-in."
|
|
8767
|
+
|
|
8768
|
+
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
8769
|
+
- "No, I meant Wisconsin not Wyoming."
|
|
8770
|
+
- "I emailed Acme \u2014 log that outreach."
|
|
8771
|
+
- "Thumbs down on this lead."
|
|
8772
|
+
|
|
8773
|
+
## RENDER (quick)
|
|
8774
|
+
|
|
8775
|
+
Confirm the exact wording with the user BEFORE calling (this is sent to
|
|
8776
|
+
the team). After sending, show a one-line confirmation from the result's
|
|
8777
|
+
\`message\` (e.g. "\u2713 Sent to the Leadbay team"). If \`sent\` is false, tell
|
|
8778
|
+
the user it could NOT be delivered \u2014 never imply it was sent.
|
|
8779
|
+
|
|
8780
|
+
---
|
|
8781
|
+
|
|
8782
|
+
Deliver a user-authored message to the Leadbay team's feedback inbox \u2014 the same
|
|
8783
|
+
destination as the web app's feedback form. **You do not write the feedback;
|
|
8784
|
+
the user does.** Capture their words, confirm the phrasing, then send.
|
|
8785
|
+
|
|
8786
|
+
## Parameters
|
|
8787
|
+
- \`message\` (required) \u2014 the user's feedback, in their own words. Confirm it
|
|
8788
|
+
with the user before sending. Cap 4000 chars.
|
|
8789
|
+
- \`associated_error_id\` (optional) \u2014 a Sentry event id to attach the feedback
|
|
8790
|
+
to (e.g. the id surfaced by an error the user just hit), so the team sees the
|
|
8791
|
+
feedback on that exact issue.
|
|
8792
|
+
|
|
8793
|
+
## When a tool errors \u2014 OFFER, don't auto-send
|
|
8794
|
+
When a Leadbay tool returns an error and the user might want the team to know,
|
|
8795
|
+
you may OFFER: *"Want me to send feedback about this to the Leadbay team?"*
|
|
8796
|
+
- Send ONLY if the user says yes AND gives (or approves) a message.
|
|
8797
|
+
- Never send feedback the user didn't author or approve.
|
|
8798
|
+
- If an error event id is available, pass it as \`associated_error_id\`.
|
|
8799
|
+
|
|
8800
|
+
## Result
|
|
8801
|
+
- \`sent: true\` \u2192 it reached the team. Show the confirmation from \`message\`.
|
|
8802
|
+
- \`sent: false\` \u2192 delivery wasn't possible (feedback not available on this
|
|
8803
|
+
client). Tell the user it was NOT sent. Do not claim success.
|
|
8804
|
+
|
|
8805
|
+
This is the only "talk to the Leadbay team" tool. It does not mutate any
|
|
8806
|
+
Leadbay data. For silent friction signals you detect yourself, use
|
|
8807
|
+
\`leadbay_report_friction\` instead.
|
|
8808
|
+
|
|
8809
|
+
## NEXT STEPS \u2014 after sending feedback
|
|
8810
|
+
|
|
8811
|
+
**ALWAYS render NEXT STEPS via your host's next-step widget.** Use whichever is in your tool set \u2014 the NAME and SCHEMA differ: **\`ask_user_input_v0\`** (Claude chat / ChatGPT) takes plain-string options with \`type:"single_select"\`; **\`AskUserQuestion\`** (Claude cowork / Claude Code) takes object options \`{label, description}\` plus a required short \`header\` (\u226412 chars) and \`multiSelect\`, NO \`type\` field, and never add an "Other" option (the host adds it). Match the schema to the tool you actually have \u2014 the wrong schema fails silently and you fall back to prose. Prose bullets are the fallback ONLY when NEITHER widget exists. Any turn that would end with a choice must be the widget \u2014 the widget IS the question.
|
|
8812
|
+
|
|
8813
|
+
**If the tool result carries a \`next_steps\` object, that is the source of truth \u2014 use it directly.** Each option has a short \`.label\` (\u22645 words) and a full \`.description\`. Map \`next_steps.options[]\` into your host widget VERBATIM and in order: for \`AskUserQuestion\` (cowork / Claude Code) pass each as \`{label, description}\`; for \`ask_user_input_v0\` (Claude chat / ChatGPT, string options only) pass each option's \`.description\` as the string (it's the full sentence). Do NOT reword, reorder, drop, or prose-ify them \u2014 they're built deterministically by the server so the offer (incl. the artifact option at position 0) fires every time. Fall back to the table below only when there is NO \`next_steps\` field.
|
|
8814
|
+
|
|
8815
|
+
**One exception \u2014 skip the widget** when the user's original message contained a complete sequential instruction chain ("show me X and then do Y") AND all stated steps have been completed. In that case, end with STOP directly \u2014 the user stated their full plan and does not need a "what next?" prompt.
|
|
8816
|
+
- Skip example: "Show me today's leads and then research the top one for me." \u2192 after research completes, emit STOP without the widget.
|
|
8817
|
+
- Do NOT skip for: plain requests ("show me today's leads", "run my check-in"), recurring-language requests ("I do this every day"), or requests where only one action was stated.
|
|
8818
|
+
|
|
8819
|
+
Pick 2\u20134 rows from the (Observation, Suggest, Calls) table below most relevant to the response, then call your host's widget with ITS schema (per the schema rules above \u2014 wrong schema fails silently):
|
|
8820
|
+
- \`ask_user_input_v0\`: \`{questions:[{question,type:"single_select",options:["<Suggest 1>","<Suggest 2>"]}]}\`
|
|
8821
|
+
- \`AskUserQuestion\`: \`{questions:[{question,header:"Next step",multiSelect:false,options:[{label:"<\u22645 words>",description:"<Suggest 1>"}]}]}\`
|
|
8822
|
+
|
|
8823
|
+
User picks \u2192 call the matching \`Calls\` tool. Constraints: 2\u20134 mutually-exclusive options, AskUserQuestion labels \u22645 words (full text in \`description\`), max 3 questions. Table stays internal; never recite it.
|
|
8824
|
+
|
|
8825
|
+
---
|
|
8826
|
+
|
|
8827
|
+
|
|
8828
|
+
|
|
8829
|
+
| Observation | Suggest | Calls |
|
|
8830
|
+
|----------------------------------------------|------------------------------------------------------|-----------------------------------------|
|
|
8831
|
+
| \`sent == true\` | "Anything else you'd like to flag to the team?" | leadbay_send_feedback(message) |
|
|
8832
|
+
| \`sent == false\` | "It didn't go through \u2014 want to try sending again?" | leadbay_send_feedback(message) |
|
|
8833
|
+
| Feedback was about an error the user hit | "Want me to retry the action that failed?" | (re-call the tool that errored) |
|
|
8725
8834
|
`;
|
|
8726
8835
|
leadbay_set_active_lens = `Mark a lens as last-used. Subsequent \`/me\` reads return it as \`last_requested_lens\`, so all composite tools default to it.
|
|
8727
8836
|
|
|
@@ -16415,16 +16524,16 @@ var init_account_status = __esm({
|
|
|
16415
16524
|
properties: {
|
|
16416
16525
|
current_version: { type: "string" },
|
|
16417
16526
|
latest_version: { type: "string" },
|
|
16418
|
-
|
|
16527
|
+
install_url: {
|
|
16419
16528
|
type: "string",
|
|
16420
|
-
description: "Direct download URL for the
|
|
16529
|
+
description: "Direct download URL for the installer asset (.dxt, falling back to .mcpb)."
|
|
16421
16530
|
},
|
|
16422
16531
|
release_url: {
|
|
16423
16532
|
type: "string",
|
|
16424
16533
|
description: "GitHub release page (changelog)."
|
|
16425
16534
|
}
|
|
16426
16535
|
},
|
|
16427
|
-
required: ["current_version", "latest_version", "
|
|
16536
|
+
required: ["current_version", "latest_version", "install_url", "release_url"]
|
|
16428
16537
|
}
|
|
16429
16538
|
},
|
|
16430
16539
|
required: ["user", "organization"]
|
|
@@ -21240,6 +21349,90 @@ var init_report_friction = __esm({
|
|
|
21240
21349
|
}
|
|
21241
21350
|
});
|
|
21242
21351
|
|
|
21352
|
+
// ../core/dist/tools/send-feedback.js
|
|
21353
|
+
var MESSAGE_MAX, sendFeedback;
|
|
21354
|
+
var init_send_feedback = __esm({
|
|
21355
|
+
"../core/dist/tools/send-feedback.js"() {
|
|
21356
|
+
"use strict";
|
|
21357
|
+
init_tool_descriptions_generated();
|
|
21358
|
+
MESSAGE_MAX = 4e3;
|
|
21359
|
+
sendFeedback = {
|
|
21360
|
+
name: "leadbay_send_feedback",
|
|
21361
|
+
annotations: {
|
|
21362
|
+
title: "Send feedback to the Leadbay team",
|
|
21363
|
+
readOnlyHint: false,
|
|
21364
|
+
destructiveHint: false,
|
|
21365
|
+
idempotentHint: false,
|
|
21366
|
+
openWorldHint: true
|
|
21367
|
+
},
|
|
21368
|
+
description: leadbay_send_feedback,
|
|
21369
|
+
// Write-gated: it sends data outward to the Leadbay team. Registered in
|
|
21370
|
+
// compositeWriteTools (default-on since 0.3.0).
|
|
21371
|
+
write: true,
|
|
21372
|
+
inputSchema: {
|
|
21373
|
+
type: "object",
|
|
21374
|
+
properties: {
|
|
21375
|
+
message: {
|
|
21376
|
+
type: "string",
|
|
21377
|
+
description: "The user's feedback, in their own words. Confirm the wording with the user BEFORE calling \u2014 this is sent to the Leadbay team. Cap 4000 chars."
|
|
21378
|
+
},
|
|
21379
|
+
associated_error_id: {
|
|
21380
|
+
type: "string",
|
|
21381
|
+
description: "Optional: a Sentry event id to attach this feedback to (e.g. the id from an error the user just hit), so the team sees the feedback on that exact issue."
|
|
21382
|
+
}
|
|
21383
|
+
},
|
|
21384
|
+
required: ["message"],
|
|
21385
|
+
additionalProperties: false
|
|
21386
|
+
},
|
|
21387
|
+
outputSchema: {
|
|
21388
|
+
type: "object",
|
|
21389
|
+
description: "Whether the feedback reached the Leadbay team. `sent: true` means it landed in the team's inbox.",
|
|
21390
|
+
properties: {
|
|
21391
|
+
sent: { type: "boolean" },
|
|
21392
|
+
message: { type: "string" },
|
|
21393
|
+
_meta: {
|
|
21394
|
+
type: "object",
|
|
21395
|
+
properties: { region: { type: "string" } }
|
|
21396
|
+
}
|
|
21397
|
+
}
|
|
21398
|
+
},
|
|
21399
|
+
execute: async (client, params, ctx) => {
|
|
21400
|
+
const text = typeof params.message === "string" ? params.message.trim() : "";
|
|
21401
|
+
if (!text) {
|
|
21402
|
+
return {
|
|
21403
|
+
error: true,
|
|
21404
|
+
code: "BAD_INPUT",
|
|
21405
|
+
message: "message is required \u2014 pass the user's feedback text.",
|
|
21406
|
+
hint: "Ask the user what they'd like to tell the Leadbay team, then call again with their words in `message`."
|
|
21407
|
+
};
|
|
21408
|
+
}
|
|
21409
|
+
const message = text.length > MESSAGE_MAX ? `${text.slice(0, MESSAGE_MAX - 1)}\u2026` : text;
|
|
21410
|
+
if (!ctx?.sendFeedback) {
|
|
21411
|
+
return {
|
|
21412
|
+
sent: false,
|
|
21413
|
+
message: "Feedback could not be sent from this client (feedback delivery isn't available here). Let the user know it wasn't delivered.",
|
|
21414
|
+
_meta: { region: client.region }
|
|
21415
|
+
};
|
|
21416
|
+
}
|
|
21417
|
+
const errorId = typeof params.associated_error_id === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(params.associated_error_id) ? params.associated_error_id : void 0;
|
|
21418
|
+
const sent = await ctx.sendFeedback(message, {
|
|
21419
|
+
...errorId ? { associatedEventId: errorId } : {}
|
|
21420
|
+
});
|
|
21421
|
+
return {
|
|
21422
|
+
sent,
|
|
21423
|
+
message: sent ? "Sent to the Leadbay team \u2014 thanks for the feedback." : (
|
|
21424
|
+
// `sent:false` means the bounded flush didn't confirm within the
|
|
21425
|
+
// window — the envelope may still drain on shutdown. Don't assert it
|
|
21426
|
+
// failed (that trains users to re-send and spam the inbox).
|
|
21427
|
+
"Delivery not confirmed \u2014 it may still reach the Leadbay team. Avoid re-sending unless the user wants to."
|
|
21428
|
+
),
|
|
21429
|
+
_meta: { region: client.region }
|
|
21430
|
+
};
|
|
21431
|
+
}
|
|
21432
|
+
};
|
|
21433
|
+
}
|
|
21434
|
+
});
|
|
21435
|
+
|
|
21243
21436
|
// ../core/dist/index.js
|
|
21244
21437
|
var dist_exports = {};
|
|
21245
21438
|
__export(dist_exports, {
|
|
@@ -21370,6 +21563,7 @@ __export(dist_exports, {
|
|
|
21370
21563
|
scanPortfolioSignals: () => scanPortfolioSignals,
|
|
21371
21564
|
seedCandidates: () => seedCandidates,
|
|
21372
21565
|
selectLeads: () => selectLeads,
|
|
21566
|
+
sendFeedback: () => sendFeedback,
|
|
21373
21567
|
setActiveLens: () => setActiveLens,
|
|
21374
21568
|
setEpilogueStatus: () => setEpilogueStatus,
|
|
21375
21569
|
setPushback: () => setPushback,
|
|
@@ -21481,6 +21675,7 @@ var init_dist = __esm({
|
|
|
21481
21675
|
init_answer_clarification();
|
|
21482
21676
|
init_report_outreach();
|
|
21483
21677
|
init_report_friction();
|
|
21678
|
+
init_send_feedback();
|
|
21484
21679
|
init_bulk_store();
|
|
21485
21680
|
agentMemoryTools = [
|
|
21486
21681
|
agentMemoryRecall,
|
|
@@ -21612,6 +21807,13 @@ var init_dist = __esm({
|
|
|
21612
21807
|
refinePrompt,
|
|
21613
21808
|
answerClarification,
|
|
21614
21809
|
reportOutreach,
|
|
21810
|
+
// sendFeedback is granular-shaped (a single call to the telemetry seam →
|
|
21811
|
+
// Sentry.captureFeedback, same inbox as the web app's feedback form), so it
|
|
21812
|
+
// lives in tools/, NOT composite/. Registered here (not advanced-gated) so
|
|
21813
|
+
// users can send feedback in-conversation without LEADBAY_MCP_ADVANCED.
|
|
21814
|
+
// Write-gated since it sends data outward. Not in COMPOSITE_FILE_TOOL_NAMES,
|
|
21815
|
+
// so _triggered_by is optional (no orchestration / mandatory-intent-trace).
|
|
21816
|
+
sendFeedback,
|
|
21615
21817
|
importLeads,
|
|
21616
21818
|
importAndQualify,
|
|
21617
21819
|
// Contact management (product#3703) — each is a single-call relay, so
|
|
@@ -23081,6 +23283,7 @@ var NOOP_TELEMETRY = {
|
|
|
23081
23283
|
},
|
|
23082
23284
|
captureException: () => {
|
|
23083
23285
|
},
|
|
23286
|
+
captureFeedback: async () => false,
|
|
23084
23287
|
captureUpdateCheck: () => {
|
|
23085
23288
|
},
|
|
23086
23289
|
captureUpdatePrompted: () => {
|
|
@@ -23335,6 +23538,41 @@ function initTelemetry(opts) {
|
|
|
23335
23538
|
logger?.warn?.(`sentry captureException failed: ${e?.message ?? e}`);
|
|
23336
23539
|
}
|
|
23337
23540
|
},
|
|
23541
|
+
async captureFeedback(message, opts2) {
|
|
23542
|
+
if (!sentryReady) return false;
|
|
23543
|
+
const trimmed = (message ?? "").trim();
|
|
23544
|
+
if (!trimmed) return false;
|
|
23545
|
+
if (identityPromise) {
|
|
23546
|
+
let waitTimer;
|
|
23547
|
+
try {
|
|
23548
|
+
await Promise.race([
|
|
23549
|
+
identityPromise,
|
|
23550
|
+
new Promise((resolve) => {
|
|
23551
|
+
waitTimer = setTimeout(resolve, 2e3);
|
|
23552
|
+
})
|
|
23553
|
+
]);
|
|
23554
|
+
} catch {
|
|
23555
|
+
} finally {
|
|
23556
|
+
if (waitTimer) clearTimeout(waitTimer);
|
|
23557
|
+
}
|
|
23558
|
+
}
|
|
23559
|
+
try {
|
|
23560
|
+
Sentry.captureFeedback({
|
|
23561
|
+
message: trimmed,
|
|
23562
|
+
...me?.name ? { name: me.name } : {},
|
|
23563
|
+
...me?.email ? { email: me.email } : {},
|
|
23564
|
+
...opts2?.associatedEventId ? { associatedEventId: opts2.associatedEventId } : {}
|
|
23565
|
+
});
|
|
23566
|
+
const flushed = await Sentry.flush(4e3);
|
|
23567
|
+
if (!flushed) {
|
|
23568
|
+
logger?.warn?.("sentry feedback flush timed out (event may be buffered)");
|
|
23569
|
+
}
|
|
23570
|
+
return flushed;
|
|
23571
|
+
} catch (e) {
|
|
23572
|
+
logger?.warn?.(`sentry captureFeedback failed: ${e?.message ?? e}`);
|
|
23573
|
+
return false;
|
|
23574
|
+
}
|
|
23575
|
+
},
|
|
23338
23576
|
async shutdown() {
|
|
23339
23577
|
const tasks = [];
|
|
23340
23578
|
if (posthog) tasks.push(posthog.shutdown(2e3).catch(() => void 0));
|
|
@@ -23346,10 +23584,13 @@ function initTelemetry(opts) {
|
|
|
23346
23584
|
|
|
23347
23585
|
// src/update-check.ts
|
|
23348
23586
|
var cachedInfo = null;
|
|
23349
|
-
var
|
|
23587
|
+
var inFlightCheck = null;
|
|
23350
23588
|
function getCachedUpdateInfo() {
|
|
23351
23589
|
return cachedInfo;
|
|
23352
23590
|
}
|
|
23591
|
+
function getInFlightCheck() {
|
|
23592
|
+
return inFlightCheck;
|
|
23593
|
+
}
|
|
23353
23594
|
var RELEASES_LATEST_URL = "https://api.github.com/repos/leadbay/leadclaw/releases/latest";
|
|
23354
23595
|
var CHECK_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
23355
23596
|
var FETCH_TIMEOUT_MS = 5e3;
|
|
@@ -23394,25 +23635,24 @@ function compareSemver(a, b) {
|
|
|
23394
23635
|
}
|
|
23395
23636
|
return 0;
|
|
23396
23637
|
}
|
|
23397
|
-
function
|
|
23638
|
+
function pickInstallAsset(rel) {
|
|
23398
23639
|
if (!Array.isArray(rel.assets)) return void 0;
|
|
23399
|
-
const mcpb = rel.assets.find(
|
|
23400
|
-
(a) => typeof a.name === "string" && a.name.endsWith(".mcpb")
|
|
23401
|
-
);
|
|
23402
|
-
if (mcpb?.browser_download_url) return mcpb.browser_download_url;
|
|
23403
23640
|
const dxt = rel.assets.find(
|
|
23404
23641
|
(a) => typeof a.name === "string" && a.name.endsWith(".dxt")
|
|
23405
23642
|
);
|
|
23406
|
-
|
|
23643
|
+
if (dxt?.browser_download_url) return dxt.browser_download_url;
|
|
23644
|
+
const mcpb = rel.assets.find(
|
|
23645
|
+
(a) => typeof a.name === "string" && a.name.endsWith(".mcpb")
|
|
23646
|
+
);
|
|
23647
|
+
return mcpb?.browser_download_url;
|
|
23407
23648
|
}
|
|
23408
|
-
|
|
23409
|
-
if (
|
|
23410
|
-
|
|
23411
|
-
|
|
23412
|
-
|
|
23413
|
-
|
|
23414
|
-
|
|
23415
|
-
}
|
|
23649
|
+
function checkForUpdate(opts) {
|
|
23650
|
+
if (inFlightCheck) return inFlightCheck;
|
|
23651
|
+
const p = doCheck(opts).finally(() => {
|
|
23652
|
+
if (inFlightCheck === p) inFlightCheck = null;
|
|
23653
|
+
});
|
|
23654
|
+
inFlightCheck = p;
|
|
23655
|
+
return p;
|
|
23416
23656
|
}
|
|
23417
23657
|
async function doCheck(opts) {
|
|
23418
23658
|
const now = opts.now ?? Date.now;
|
|
@@ -23421,11 +23661,11 @@ async function doCheck(opts) {
|
|
|
23421
23661
|
const currentVersion = opts.currentVersion;
|
|
23422
23662
|
const state = await opts.stateStore.read();
|
|
23423
23663
|
const within = now() - state.last_check_time < CHECK_THROTTLE_MS;
|
|
23424
|
-
if (!opts.force && within && state.latest_known_version && state.
|
|
23664
|
+
if (!opts.force && within && state.latest_known_version && state.latest_known_install_url && state.latest_known_release_url) {
|
|
23425
23665
|
const cached = buildInfoIfUpgrade(
|
|
23426
23666
|
currentVersion,
|
|
23427
23667
|
state.latest_known_version,
|
|
23428
|
-
state.
|
|
23668
|
+
state.latest_known_install_url,
|
|
23429
23669
|
state.latest_known_release_url,
|
|
23430
23670
|
state.suppressed_versions,
|
|
23431
23671
|
state.remind_until,
|
|
@@ -23479,18 +23719,18 @@ async function doCheck(opts) {
|
|
|
23479
23719
|
return null;
|
|
23480
23720
|
}
|
|
23481
23721
|
let latestVersion;
|
|
23482
|
-
let
|
|
23722
|
+
let installUrl;
|
|
23483
23723
|
let releaseUrl;
|
|
23484
23724
|
if (status === 200 && body) {
|
|
23485
23725
|
const parsed = body.tag_name ? parseTagName(body.tag_name) : null;
|
|
23486
23726
|
if (parsed) {
|
|
23487
23727
|
latestVersion = parsed;
|
|
23488
|
-
|
|
23728
|
+
installUrl = pickInstallAsset(body);
|
|
23489
23729
|
releaseUrl = body.html_url;
|
|
23490
23730
|
}
|
|
23491
23731
|
} else {
|
|
23492
23732
|
latestVersion = state.latest_known_version;
|
|
23493
|
-
|
|
23733
|
+
installUrl = state.latest_known_install_url;
|
|
23494
23734
|
releaseUrl = state.latest_known_release_url;
|
|
23495
23735
|
}
|
|
23496
23736
|
const persisted = await opts.stateStore.update((cur) => ({
|
|
@@ -23498,7 +23738,7 @@ async function doCheck(opts) {
|
|
|
23498
23738
|
last_check_time: now(),
|
|
23499
23739
|
etag: nextEtag,
|
|
23500
23740
|
latest_known_version: latestVersion ?? cur.latest_known_version,
|
|
23501
|
-
|
|
23741
|
+
latest_known_install_url: installUrl ?? cur.latest_known_install_url,
|
|
23502
23742
|
latest_known_release_url: releaseUrl ?? cur.latest_known_release_url
|
|
23503
23743
|
}));
|
|
23504
23744
|
opts.telemetry.captureUpdateCheck?.({
|
|
@@ -23508,7 +23748,7 @@ async function doCheck(opts) {
|
|
|
23508
23748
|
const info = buildInfoIfUpgrade(
|
|
23509
23749
|
currentVersion,
|
|
23510
23750
|
persisted.latest_known_version,
|
|
23511
|
-
persisted.
|
|
23751
|
+
persisted.latest_known_install_url,
|
|
23512
23752
|
persisted.latest_known_release_url,
|
|
23513
23753
|
persisted.suppressed_versions,
|
|
23514
23754
|
persisted.remind_until,
|
|
@@ -23517,15 +23757,15 @@ async function doCheck(opts) {
|
|
|
23517
23757
|
cachedInfo = info;
|
|
23518
23758
|
return info;
|
|
23519
23759
|
}
|
|
23520
|
-
function buildInfoIfUpgrade(currentVersion, latestVersion,
|
|
23521
|
-
if (!latestVersion || !
|
|
23760
|
+
function buildInfoIfUpgrade(currentVersion, latestVersion, installUrl, releaseUrl, suppressed, remindUntil, nowMs) {
|
|
23761
|
+
if (!latestVersion || !installUrl || !releaseUrl) return null;
|
|
23522
23762
|
if (compareSemver(latestVersion, currentVersion) <= 0) return null;
|
|
23523
23763
|
if (suppressed.includes(latestVersion)) return null;
|
|
23524
23764
|
if (remindUntil && remindUntil > nowMs) return null;
|
|
23525
23765
|
return {
|
|
23526
23766
|
current_version: currentVersion,
|
|
23527
23767
|
latest_version: latestVersion,
|
|
23528
|
-
|
|
23768
|
+
install_url: installUrl,
|
|
23529
23769
|
release_url: releaseUrl
|
|
23530
23770
|
};
|
|
23531
23771
|
}
|
|
@@ -23554,7 +23794,7 @@ async function recordRunningVersion(currentVersion, stateStore, telemetry) {
|
|
|
23554
23794
|
|
|
23555
23795
|
// src/update-tool.ts
|
|
23556
23796
|
var TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1e3;
|
|
23557
|
-
var DESCRIPTION = "Record the user's choice on an update prompt surfaced via `update_available` on leadbay_account_status. Pass `action: 'install' | 'remind_tomorrow' | 'skip'` and `version` (the `latest_version` from the prompt). On 'install', the server returns `{
|
|
23797
|
+
var DESCRIPTION = "Record the user's choice on an update prompt surfaced via `update_available` on leadbay_account_status. Pass `action: 'install' | 'remind_tomorrow' | 'skip'` and `version` (the `latest_version` from the prompt). On 'install', the server returns `{ install_url, release_url }` \u2014 show the user a clickable link to install_url (a `.dxt` bundle) so Claude Desktop's native installer opens it. On 'remind_tomorrow' the server suppresses the prompt for 24 hours. On 'skip' the version is suppressed permanently. Call this tool EXACTLY ONCE per prompt \u2014 do not loop, and do not call it speculatively when no update_available block is present.";
|
|
23558
23798
|
function buildAcknowledgeUpdateTool(opts) {
|
|
23559
23799
|
const now = opts.now ?? Date.now;
|
|
23560
23800
|
return {
|
|
@@ -23590,7 +23830,7 @@ function buildAcknowledgeUpdateTool(opts) {
|
|
|
23590
23830
|
action: { type: "string" },
|
|
23591
23831
|
version: { type: "string" },
|
|
23592
23832
|
message: { type: "string" },
|
|
23593
|
-
|
|
23833
|
+
install_url: { type: ["string", "null"] },
|
|
23594
23834
|
release_url: { type: ["string", "null"] }
|
|
23595
23835
|
},
|
|
23596
23836
|
required: ["ok", "action", "version", "message"]
|
|
@@ -23624,9 +23864,9 @@ function buildAcknowledgeUpdateTool(opts) {
|
|
|
23624
23864
|
ok: true,
|
|
23625
23865
|
action,
|
|
23626
23866
|
version,
|
|
23627
|
-
|
|
23867
|
+
install_url: state.latest_known_install_url ?? null,
|
|
23628
23868
|
release_url: state.latest_known_release_url ?? null,
|
|
23629
|
-
message: state.
|
|
23869
|
+
message: state.latest_known_install_url ? "Show the user the install_url (a .dxt bundle) as a clickable link \u2014 opening it in Claude Desktop runs the native installer." : "No installer URL is cached. Direct the user to the release_url to download manually."
|
|
23630
23870
|
};
|
|
23631
23871
|
}
|
|
23632
23872
|
if (action === "remind_tomorrow") {
|
|
@@ -23729,7 +23969,7 @@ function buildStartHereParagraph(has) {
|
|
|
23729
23969
|
}
|
|
23730
23970
|
function buildUpdateAvailableParagraph(has) {
|
|
23731
23971
|
if (!has("leadbay_acknowledge_update")) return null;
|
|
23732
|
-
return "MCP auto-update:
|
|
23972
|
+
return "MCP auto-update: an `update_available` field (`{ current_version, latest_version, install_url, release_url }`) means a newer MCP server release is published and the user has NOT suppressed it. It appears in TWO places: as a top-level field on `leadbay_account_status`, AND on `_meta.update_available` of the FIRST other tool result in a session while an update is pending (so a fresh session surfaces the proposal even without an account_status call). Whenever you see it on ANY response, surface a prompt via `ask_user_input_v0` with EXACTLY these three options: \"Install now\", \"Remind me tomorrow\", \"Skip this version\". Map the user's choice to `leadbay_acknowledge_update({ action: 'install' | 'remind_tomorrow' | 'skip', version: latest_version })`. On 'install', the tool returns `install_url` \u2014 render it as a clickable markdown link the user can open in Claude Desktop (the .dxt extension triggers the native installer). The user does NOT need to restart anything before clicking \u2014 the new server takes effect on the next MCP session. Prompt the user ONCE per session per version \u2014 don't re-prompt within the same chat after they've acknowledged.";
|
|
23733
23973
|
}
|
|
23734
23974
|
function buildRhythmParagraph(has) {
|
|
23735
23975
|
if (has("leadbay_report_outreach")) {
|
|
@@ -24024,16 +24264,47 @@ function buildServer(client, opts = {}) {
|
|
|
24024
24264
|
);
|
|
24025
24265
|
});
|
|
24026
24266
|
};
|
|
24027
|
-
const
|
|
24028
|
-
|
|
24267
|
+
const UPDATE_SURFACE_WAIT_MS = 1500;
|
|
24268
|
+
const maybeAttachUpdate = async (toolName, result) => {
|
|
24029
24269
|
if (!opts.updateStateStore) return;
|
|
24030
24270
|
if (result === null || typeof result !== "object" || Array.isArray(result)) {
|
|
24031
24271
|
return;
|
|
24032
24272
|
}
|
|
24033
|
-
|
|
24273
|
+
if (result.error === true) {
|
|
24274
|
+
return;
|
|
24275
|
+
}
|
|
24276
|
+
let info = getCachedUpdateInfo();
|
|
24277
|
+
if (!info) {
|
|
24278
|
+
const inflight = getInFlightCheck();
|
|
24279
|
+
if (inflight) {
|
|
24280
|
+
const settled = inflight.catch((err) => {
|
|
24281
|
+
opts.logger?.warn?.(
|
|
24282
|
+
`update_check.surface_await_failed ${err?.message ?? err}`
|
|
24283
|
+
);
|
|
24284
|
+
return null;
|
|
24285
|
+
});
|
|
24286
|
+
info = await Promise.race([
|
|
24287
|
+
settled,
|
|
24288
|
+
new Promise(
|
|
24289
|
+
(resolve) => setTimeout(() => resolve(null), UPDATE_SURFACE_WAIT_MS)
|
|
24290
|
+
)
|
|
24291
|
+
]);
|
|
24292
|
+
info = getCachedUpdateInfo() ?? info;
|
|
24293
|
+
}
|
|
24294
|
+
}
|
|
24034
24295
|
if (!info) return;
|
|
24035
|
-
|
|
24036
|
-
|
|
24296
|
+
const isAccountStatus = toolName === "leadbay_account_status";
|
|
24297
|
+
const alreadyPrompted = promptedVersionsThisSession.has(info.latest_version);
|
|
24298
|
+
if (!isAccountStatus && alreadyPrompted) return;
|
|
24299
|
+
if (isAccountStatus) {
|
|
24300
|
+
result.update_available = info;
|
|
24301
|
+
} else {
|
|
24302
|
+
const envelope = result;
|
|
24303
|
+
const target = envelope.__markdown_envelope === true && envelope.structured !== null && typeof envelope.structured === "object" && !Array.isArray(envelope.structured) ? envelope.structured : envelope;
|
|
24304
|
+
const existingMeta = target._meta && typeof target._meta === "object" && !Array.isArray(target._meta) ? target._meta : {};
|
|
24305
|
+
target._meta = { ...existingMeta, update_available: info };
|
|
24306
|
+
}
|
|
24307
|
+
if (!alreadyPrompted) {
|
|
24037
24308
|
promptedVersionsThisSession.add(info.latest_version);
|
|
24038
24309
|
telemetry.captureUpdatePrompted?.({
|
|
24039
24310
|
current_version: serverVersion,
|
|
@@ -24207,9 +24478,13 @@ function buildServer(client, opts = {}) {
|
|
|
24207
24478
|
notificationsInbox: opts.notificationsInbox,
|
|
24208
24479
|
signal: extra.signal,
|
|
24209
24480
|
progress,
|
|
24210
|
-
elicit
|
|
24481
|
+
elicit,
|
|
24482
|
+
// Route leadbay_send_feedback to Sentry's feedback inbox (same place
|
|
24483
|
+
// the web app's form lands). NOOP_TELEMETRY returns false, so the
|
|
24484
|
+
// tool reports honestly when telemetry is off.
|
|
24485
|
+
sendFeedback: (message, fbOpts) => telemetry.captureFeedback(message, fbOpts)
|
|
24211
24486
|
});
|
|
24212
|
-
maybeAttachUpdate(name, result);
|
|
24487
|
+
await maybeAttachUpdate(name, result);
|
|
24213
24488
|
maybeAttachNotifications(result);
|
|
24214
24489
|
if (result && typeof result === "object" && result.error === true) {
|
|
24215
24490
|
const envText = formatErrorForLLM(result);
|
|
@@ -25219,8 +25494,10 @@ var UpdateStateStore = class {
|
|
|
25219
25494
|
if (typeof r.latest_known_version === "string") {
|
|
25220
25495
|
out.latest_known_version = r.latest_known_version;
|
|
25221
25496
|
}
|
|
25222
|
-
if (typeof r.
|
|
25223
|
-
out.
|
|
25497
|
+
if (typeof r.latest_known_install_url === "string") {
|
|
25498
|
+
out.latest_known_install_url = r.latest_known_install_url;
|
|
25499
|
+
} else if (typeof r.latest_known_mcpb_url === "string") {
|
|
25500
|
+
out.latest_known_install_url = r.latest_known_mcpb_url;
|
|
25224
25501
|
}
|
|
25225
25502
|
if (typeof r.latest_known_release_url === "string") {
|
|
25226
25503
|
out.latest_known_release_url = r.latest_known_release_url;
|
|
@@ -25648,7 +25925,7 @@ var OAUTH_BASE_URLS = {
|
|
|
25648
25925
|
fr: "https://staging.api.leadbay.app"
|
|
25649
25926
|
}
|
|
25650
25927
|
};
|
|
25651
|
-
var VERSION = "0.
|
|
25928
|
+
var VERSION = "0.20.0";
|
|
25652
25929
|
var HELP = `
|
|
25653
25930
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
25654
25931
|
|
|
@@ -25923,7 +26200,7 @@ async function resolveClientFromEnv(logger) {
|
|
|
25923
26200
|
logger.info?.("Auto-detecting region via /users/me on us and fr...");
|
|
25924
26201
|
const probe = async (region) => {
|
|
25925
26202
|
const c = createClient({ token, region });
|
|
25926
|
-
await c.request("GET", "/users/me");
|
|
26203
|
+
await c.request("GET", "/users/me", void 0, { retryOn401: false });
|
|
25927
26204
|
return c;
|
|
25928
26205
|
};
|
|
25929
26206
|
try {
|
package/dist/http-server.js
CHANGED
|
@@ -1519,13 +1519,40 @@ var LeadbayClient = class {
|
|
|
1519
1519
|
if (next)
|
|
1520
1520
|
next();
|
|
1521
1521
|
}
|
|
1522
|
-
|
|
1522
|
+
// Leadbay tokens don't expire, so a 401 is almost always a transient
|
|
1523
|
+
// server-side blip. Retry the request ONCE before surfacing it — a single
|
|
1524
|
+
// retry clears the vast majority of these without the agent ever seeing an
|
|
1525
|
+
// error. If the retry also 401s, it's a real Leadbay-side problem and the
|
|
1526
|
+
// error envelope says so.
|
|
1527
|
+
//
|
|
1528
|
+
// Arrow-function field so `this` stays bound even when the method is passed
|
|
1529
|
+
// as a bare reference (see request()'s ternary). Retries are GET-ONLY: a 401
|
|
1530
|
+
// on a write (POST/PUT/DELETE) may arrive AFTER the mutation already committed
|
|
1531
|
+
// server-side, so blindly re-sending it would double-execute the write. Reads
|
|
1532
|
+
// are idempotent, so retrying them is safe. The 250ms backoff releases the
|
|
1533
|
+
// concurrency slot first (release → sleep → re-acquire) so a wave of 401s
|
|
1534
|
+
// doesn't pin all MAX_CONCURRENT slots in setTimeout and stall the queue.
|
|
1535
|
+
httpsRequestWithRetry = async (method, url, headers, body) => {
|
|
1536
|
+
const res = await httpsRequest(method, url, headers, body);
|
|
1537
|
+
if (res.status === 401 && method.toUpperCase() === "GET") {
|
|
1538
|
+
this.releaseSemaphore();
|
|
1539
|
+
try {
|
|
1540
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
1541
|
+
} finally {
|
|
1542
|
+
await this.acquireSemaphore();
|
|
1543
|
+
}
|
|
1544
|
+
return httpsRequest(method, url, headers, body);
|
|
1545
|
+
}
|
|
1546
|
+
return res;
|
|
1547
|
+
};
|
|
1548
|
+
async request(method, path, body, opts) {
|
|
1523
1549
|
if (process.env.LEADBAY_MOCK === "1") {
|
|
1524
1550
|
return this.mockRequest(method, path, body);
|
|
1525
1551
|
}
|
|
1526
1552
|
if (!this.token) {
|
|
1527
1553
|
throw this.makeError("NOT_AUTHENTICATED", "Not logged in to Leadbay", "Set LEADBAY_TOKEN in your MCP client config, or run: npx -y @leadbay/mcp install --email <you> --region <us|fr>", path);
|
|
1528
1554
|
}
|
|
1555
|
+
const retryOn401 = opts?.retryOn401 !== false;
|
|
1529
1556
|
await this.acquireSemaphore();
|
|
1530
1557
|
try {
|
|
1531
1558
|
const url = `${this._baseUrl}/1.5${path}`;
|
|
@@ -1535,7 +1562,7 @@ var LeadbayClient = class {
|
|
|
1535
1562
|
if (body) {
|
|
1536
1563
|
headers["Content-Type"] = "application/json";
|
|
1537
1564
|
}
|
|
1538
|
-
const res = await httpsRequest(method, url, headers, body ? JSON.stringify(body) : void 0);
|
|
1565
|
+
const res = await (retryOn401 ? this.httpsRequestWithRetry : httpsRequest)(method, url, headers, body ? JSON.stringify(body) : void 0);
|
|
1539
1566
|
this._lastMeta = {
|
|
1540
1567
|
region: this._region,
|
|
1541
1568
|
endpoint: `${method} ${path}`,
|
|
@@ -1570,7 +1597,7 @@ var LeadbayClient = class {
|
|
|
1570
1597
|
if (body) {
|
|
1571
1598
|
headers["Content-Type"] = "application/json";
|
|
1572
1599
|
}
|
|
1573
|
-
const res = await
|
|
1600
|
+
const res = await this.httpsRequestWithRetry(method, url, headers, body ? JSON.stringify(body) : void 0);
|
|
1574
1601
|
this._lastMeta = {
|
|
1575
1602
|
region: this._region,
|
|
1576
1603
|
endpoint: `${method} ${path}`,
|
|
@@ -1603,7 +1630,7 @@ var LeadbayClient = class {
|
|
|
1603
1630
|
Authorization: `Bearer ${this.token}`,
|
|
1604
1631
|
"Content-Type": contentType
|
|
1605
1632
|
};
|
|
1606
|
-
const res = await
|
|
1633
|
+
const res = await this.httpsRequestWithRetry(method, url, headers, body);
|
|
1607
1634
|
this._lastMeta = {
|
|
1608
1635
|
region: this._region,
|
|
1609
1636
|
endpoint: `${method} ${path}`,
|
|
@@ -1686,7 +1713,7 @@ var LeadbayClient = class {
|
|
|
1686
1713
|
}
|
|
1687
1714
|
const retryAfter = parseRetryAfter(headers["retry-after"]);
|
|
1688
1715
|
if (status === 401) {
|
|
1689
|
-
return this.makeError("AUTH_EXPIRED", "
|
|
1716
|
+
return this.makeError("AUTH_EXPIRED", "Leadbay rejected this request (401)", "Leadbay tokens don't expire on a timer, so this isn't a stale token. A 401 here is usually a Leadbay-side hiccup, but can also mean the user logged out. Try again shortly; if it persists, offer to report it to the team.", endpoint, null, status);
|
|
1690
1717
|
}
|
|
1691
1718
|
if (status === 429 || status === 402 || parsed?.error === "quota_exceeded" || parsed?.error?.code === "quota_exceeded") {
|
|
1692
1719
|
const hintBase = retryAfter ? `Wait ${retryAfter}s before retrying` : "Wait, then retry";
|
|
@@ -9546,6 +9573,88 @@ WHEN NOT TO USE: in normal flow \u2014 leadbay_enrich_titles wraps select \u2192
|
|
|
9546
9573
|
|
|
9547
9574
|
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\`.
|
|
9548
9575
|
`;
|
|
9576
|
+
var leadbay_send_feedback = `## WHEN TO USE
|
|
9577
|
+
|
|
9578
|
+
Trigger phrases: "send feedback", "I want to report a bug", "tell the Leadbay team", "let Leadbay know", "give feedback", "report this to support", "I have a feature request".
|
|
9579
|
+
|
|
9580
|
+
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
9581
|
+
|
|
9582
|
+
Do NOT use for: "no, I meant / still nothing / ugh" \u2192 \`leadbay_report_friction\`; "log the email I sent" \u2192 \`leadbay_report_outreach\`.
|
|
9583
|
+
|
|
9584
|
+
Prefer when: the user explicitly wants the Leadbay TEAM to receive a message they authored \u2014 or accepts your offer to report an error. For silent, agent-detected friction signals use leadbay_report_friction instead.
|
|
9585
|
+
|
|
9586
|
+
Examples that SHOULD invoke this tool:
|
|
9587
|
+
- "Send feedback to the team: the lead scores feel off this week."
|
|
9588
|
+
- "Can you report a bug? Pulling leads in Lyon returns nothing."
|
|
9589
|
+
- "Tell Leadbay I'd love a way to schedule my morning check-in."
|
|
9590
|
+
|
|
9591
|
+
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
9592
|
+
- "No, I meant Wisconsin not Wyoming."
|
|
9593
|
+
- "I emailed Acme \u2014 log that outreach."
|
|
9594
|
+
- "Thumbs down on this lead."
|
|
9595
|
+
|
|
9596
|
+
## RENDER (quick)
|
|
9597
|
+
|
|
9598
|
+
Confirm the exact wording with the user BEFORE calling (this is sent to
|
|
9599
|
+
the team). After sending, show a one-line confirmation from the result's
|
|
9600
|
+
\`message\` (e.g. "\u2713 Sent to the Leadbay team"). If \`sent\` is false, tell
|
|
9601
|
+
the user it could NOT be delivered \u2014 never imply it was sent.
|
|
9602
|
+
|
|
9603
|
+
---
|
|
9604
|
+
|
|
9605
|
+
Deliver a user-authored message to the Leadbay team's feedback inbox \u2014 the same
|
|
9606
|
+
destination as the web app's feedback form. **You do not write the feedback;
|
|
9607
|
+
the user does.** Capture their words, confirm the phrasing, then send.
|
|
9608
|
+
|
|
9609
|
+
## Parameters
|
|
9610
|
+
- \`message\` (required) \u2014 the user's feedback, in their own words. Confirm it
|
|
9611
|
+
with the user before sending. Cap 4000 chars.
|
|
9612
|
+
- \`associated_error_id\` (optional) \u2014 a Sentry event id to attach the feedback
|
|
9613
|
+
to (e.g. the id surfaced by an error the user just hit), so the team sees the
|
|
9614
|
+
feedback on that exact issue.
|
|
9615
|
+
|
|
9616
|
+
## When a tool errors \u2014 OFFER, don't auto-send
|
|
9617
|
+
When a Leadbay tool returns an error and the user might want the team to know,
|
|
9618
|
+
you may OFFER: *"Want me to send feedback about this to the Leadbay team?"*
|
|
9619
|
+
- Send ONLY if the user says yes AND gives (or approves) a message.
|
|
9620
|
+
- Never send feedback the user didn't author or approve.
|
|
9621
|
+
- If an error event id is available, pass it as \`associated_error_id\`.
|
|
9622
|
+
|
|
9623
|
+
## Result
|
|
9624
|
+
- \`sent: true\` \u2192 it reached the team. Show the confirmation from \`message\`.
|
|
9625
|
+
- \`sent: false\` \u2192 delivery wasn't possible (feedback not available on this
|
|
9626
|
+
client). Tell the user it was NOT sent. Do not claim success.
|
|
9627
|
+
|
|
9628
|
+
This is the only "talk to the Leadbay team" tool. It does not mutate any
|
|
9629
|
+
Leadbay data. For silent friction signals you detect yourself, use
|
|
9630
|
+
\`leadbay_report_friction\` instead.
|
|
9631
|
+
|
|
9632
|
+
## NEXT STEPS \u2014 after sending feedback
|
|
9633
|
+
|
|
9634
|
+
**ALWAYS render NEXT STEPS via your host's next-step widget.** Use whichever is in your tool set \u2014 the NAME and SCHEMA differ: **\`ask_user_input_v0\`** (Claude chat / ChatGPT) takes plain-string options with \`type:"single_select"\`; **\`AskUserQuestion\`** (Claude cowork / Claude Code) takes object options \`{label, description}\` plus a required short \`header\` (\u226412 chars) and \`multiSelect\`, NO \`type\` field, and never add an "Other" option (the host adds it). Match the schema to the tool you actually have \u2014 the wrong schema fails silently and you fall back to prose. Prose bullets are the fallback ONLY when NEITHER widget exists. Any turn that would end with a choice must be the widget \u2014 the widget IS the question.
|
|
9635
|
+
|
|
9636
|
+
**If the tool result carries a \`next_steps\` object, that is the source of truth \u2014 use it directly.** Each option has a short \`.label\` (\u22645 words) and a full \`.description\`. Map \`next_steps.options[]\` into your host widget VERBATIM and in order: for \`AskUserQuestion\` (cowork / Claude Code) pass each as \`{label, description}\`; for \`ask_user_input_v0\` (Claude chat / ChatGPT, string options only) pass each option's \`.description\` as the string (it's the full sentence). Do NOT reword, reorder, drop, or prose-ify them \u2014 they're built deterministically by the server so the offer (incl. the artifact option at position 0) fires every time. Fall back to the table below only when there is NO \`next_steps\` field.
|
|
9637
|
+
|
|
9638
|
+
**One exception \u2014 skip the widget** when the user's original message contained a complete sequential instruction chain ("show me X and then do Y") AND all stated steps have been completed. In that case, end with STOP directly \u2014 the user stated their full plan and does not need a "what next?" prompt.
|
|
9639
|
+
- Skip example: "Show me today's leads and then research the top one for me." \u2192 after research completes, emit STOP without the widget.
|
|
9640
|
+
- Do NOT skip for: plain requests ("show me today's leads", "run my check-in"), recurring-language requests ("I do this every day"), or requests where only one action was stated.
|
|
9641
|
+
|
|
9642
|
+
Pick 2\u20134 rows from the (Observation, Suggest, Calls) table below most relevant to the response, then call your host's widget with ITS schema (per the schema rules above \u2014 wrong schema fails silently):
|
|
9643
|
+
- \`ask_user_input_v0\`: \`{questions:[{question,type:"single_select",options:["<Suggest 1>","<Suggest 2>"]}]}\`
|
|
9644
|
+
- \`AskUserQuestion\`: \`{questions:[{question,header:"Next step",multiSelect:false,options:[{label:"<\u22645 words>",description:"<Suggest 1>"}]}]}\`
|
|
9645
|
+
|
|
9646
|
+
User picks \u2192 call the matching \`Calls\` tool. Constraints: 2\u20134 mutually-exclusive options, AskUserQuestion labels \u22645 words (full text in \`description\`), max 3 questions. Table stays internal; never recite it.
|
|
9647
|
+
|
|
9648
|
+
---
|
|
9649
|
+
|
|
9650
|
+
|
|
9651
|
+
|
|
9652
|
+
| Observation | Suggest | Calls |
|
|
9653
|
+
|----------------------------------------------|------------------------------------------------------|-----------------------------------------|
|
|
9654
|
+
| \`sent == true\` | "Anything else you'd like to flag to the team?" | leadbay_send_feedback(message) |
|
|
9655
|
+
| \`sent == false\` | "It didn't go through \u2014 want to try sending again?" | leadbay_send_feedback(message) |
|
|
9656
|
+
| Feedback was about an error the user hit | "Want me to retry the action that failed?" | (re-call the tool that errored) |
|
|
9657
|
+
`;
|
|
9549
9658
|
var leadbay_set_active_lens = `Mark a lens as last-used. Subsequent \`/me\` reads return it as \`last_requested_lens\`, so all composite tools default to it.
|
|
9550
9659
|
|
|
9551
9660
|
WHEN TO USE: after the user explicitly switched contexts (e.g. created a new lens via leadbay_create_lens).
|
|
@@ -16670,16 +16779,16 @@ var accountStatus = {
|
|
|
16670
16779
|
properties: {
|
|
16671
16780
|
current_version: { type: "string" },
|
|
16672
16781
|
latest_version: { type: "string" },
|
|
16673
|
-
|
|
16782
|
+
install_url: {
|
|
16674
16783
|
type: "string",
|
|
16675
|
-
description: "Direct download URL for the
|
|
16784
|
+
description: "Direct download URL for the installer asset (.dxt, falling back to .mcpb)."
|
|
16676
16785
|
},
|
|
16677
16786
|
release_url: {
|
|
16678
16787
|
type: "string",
|
|
16679
16788
|
description: "GitHub release page (changelog)."
|
|
16680
16789
|
}
|
|
16681
16790
|
},
|
|
16682
|
-
required: ["current_version", "latest_version", "
|
|
16791
|
+
required: ["current_version", "latest_version", "install_url", "release_url"]
|
|
16683
16792
|
}
|
|
16684
16793
|
},
|
|
16685
16794
|
required: ["user", "organization"]
|
|
@@ -20749,6 +20858,83 @@ var reportFriction = {
|
|
|
20749
20858
|
}
|
|
20750
20859
|
};
|
|
20751
20860
|
|
|
20861
|
+
// ../core/dist/tools/send-feedback.js
|
|
20862
|
+
var MESSAGE_MAX = 4e3;
|
|
20863
|
+
var sendFeedback = {
|
|
20864
|
+
name: "leadbay_send_feedback",
|
|
20865
|
+
annotations: {
|
|
20866
|
+
title: "Send feedback to the Leadbay team",
|
|
20867
|
+
readOnlyHint: false,
|
|
20868
|
+
destructiveHint: false,
|
|
20869
|
+
idempotentHint: false,
|
|
20870
|
+
openWorldHint: true
|
|
20871
|
+
},
|
|
20872
|
+
description: leadbay_send_feedback,
|
|
20873
|
+
// Write-gated: it sends data outward to the Leadbay team. Registered in
|
|
20874
|
+
// compositeWriteTools (default-on since 0.3.0).
|
|
20875
|
+
write: true,
|
|
20876
|
+
inputSchema: {
|
|
20877
|
+
type: "object",
|
|
20878
|
+
properties: {
|
|
20879
|
+
message: {
|
|
20880
|
+
type: "string",
|
|
20881
|
+
description: "The user's feedback, in their own words. Confirm the wording with the user BEFORE calling \u2014 this is sent to the Leadbay team. Cap 4000 chars."
|
|
20882
|
+
},
|
|
20883
|
+
associated_error_id: {
|
|
20884
|
+
type: "string",
|
|
20885
|
+
description: "Optional: a Sentry event id to attach this feedback to (e.g. the id from an error the user just hit), so the team sees the feedback on that exact issue."
|
|
20886
|
+
}
|
|
20887
|
+
},
|
|
20888
|
+
required: ["message"],
|
|
20889
|
+
additionalProperties: false
|
|
20890
|
+
},
|
|
20891
|
+
outputSchema: {
|
|
20892
|
+
type: "object",
|
|
20893
|
+
description: "Whether the feedback reached the Leadbay team. `sent: true` means it landed in the team's inbox.",
|
|
20894
|
+
properties: {
|
|
20895
|
+
sent: { type: "boolean" },
|
|
20896
|
+
message: { type: "string" },
|
|
20897
|
+
_meta: {
|
|
20898
|
+
type: "object",
|
|
20899
|
+
properties: { region: { type: "string" } }
|
|
20900
|
+
}
|
|
20901
|
+
}
|
|
20902
|
+
},
|
|
20903
|
+
execute: async (client, params, ctx) => {
|
|
20904
|
+
const text = typeof params.message === "string" ? params.message.trim() : "";
|
|
20905
|
+
if (!text) {
|
|
20906
|
+
return {
|
|
20907
|
+
error: true,
|
|
20908
|
+
code: "BAD_INPUT",
|
|
20909
|
+
message: "message is required \u2014 pass the user's feedback text.",
|
|
20910
|
+
hint: "Ask the user what they'd like to tell the Leadbay team, then call again with their words in `message`."
|
|
20911
|
+
};
|
|
20912
|
+
}
|
|
20913
|
+
const message = text.length > MESSAGE_MAX ? `${text.slice(0, MESSAGE_MAX - 1)}\u2026` : text;
|
|
20914
|
+
if (!ctx?.sendFeedback) {
|
|
20915
|
+
return {
|
|
20916
|
+
sent: false,
|
|
20917
|
+
message: "Feedback could not be sent from this client (feedback delivery isn't available here). Let the user know it wasn't delivered.",
|
|
20918
|
+
_meta: { region: client.region }
|
|
20919
|
+
};
|
|
20920
|
+
}
|
|
20921
|
+
const errorId = typeof params.associated_error_id === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(params.associated_error_id) ? params.associated_error_id : void 0;
|
|
20922
|
+
const sent = await ctx.sendFeedback(message, {
|
|
20923
|
+
...errorId ? { associatedEventId: errorId } : {}
|
|
20924
|
+
});
|
|
20925
|
+
return {
|
|
20926
|
+
sent,
|
|
20927
|
+
message: sent ? "Sent to the Leadbay team \u2014 thanks for the feedback." : (
|
|
20928
|
+
// `sent:false` means the bounded flush didn't confirm within the
|
|
20929
|
+
// window — the envelope may still drain on shutdown. Don't assert it
|
|
20930
|
+
// failed (that trains users to re-send and spam the inbox).
|
|
20931
|
+
"Delivery not confirmed \u2014 it may still reach the Leadbay team. Avoid re-sending unless the user wants to."
|
|
20932
|
+
),
|
|
20933
|
+
_meta: { region: client.region }
|
|
20934
|
+
};
|
|
20935
|
+
}
|
|
20936
|
+
};
|
|
20937
|
+
|
|
20752
20938
|
// ../core/dist/index.js
|
|
20753
20939
|
var agentMemoryTools = [
|
|
20754
20940
|
agentMemoryRecall,
|
|
@@ -20880,6 +21066,13 @@ var compositeWriteTools = [
|
|
|
20880
21066
|
refinePrompt,
|
|
20881
21067
|
answerClarification,
|
|
20882
21068
|
reportOutreach,
|
|
21069
|
+
// sendFeedback is granular-shaped (a single call to the telemetry seam →
|
|
21070
|
+
// Sentry.captureFeedback, same inbox as the web app's feedback form), so it
|
|
21071
|
+
// lives in tools/, NOT composite/. Registered here (not advanced-gated) so
|
|
21072
|
+
// users can send feedback in-conversation without LEADBAY_MCP_ADVANCED.
|
|
21073
|
+
// Write-gated since it sends data outward. Not in COMPOSITE_FILE_TOOL_NAMES,
|
|
21074
|
+
// so _triggered_by is optional (no orchestration / mandatory-intent-trace).
|
|
21075
|
+
sendFeedback,
|
|
20883
21076
|
importLeads,
|
|
20884
21077
|
importAndQualify,
|
|
20885
21078
|
// Contact management (product#3703) — each is a single-call relay, so
|
|
@@ -21052,6 +21245,7 @@ var NOOP_TELEMETRY = {
|
|
|
21052
21245
|
},
|
|
21053
21246
|
captureException: () => {
|
|
21054
21247
|
},
|
|
21248
|
+
captureFeedback: async () => false,
|
|
21055
21249
|
captureUpdateCheck: () => {
|
|
21056
21250
|
},
|
|
21057
21251
|
captureUpdatePrompted: () => {
|
|
@@ -21068,10 +21262,13 @@ var NOOP_TELEMETRY = {
|
|
|
21068
21262
|
|
|
21069
21263
|
// src/update-check.ts
|
|
21070
21264
|
var cachedInfo = null;
|
|
21071
|
-
var
|
|
21265
|
+
var inFlightCheck = null;
|
|
21072
21266
|
function getCachedUpdateInfo() {
|
|
21073
21267
|
return cachedInfo;
|
|
21074
21268
|
}
|
|
21269
|
+
function getInFlightCheck() {
|
|
21270
|
+
return inFlightCheck;
|
|
21271
|
+
}
|
|
21075
21272
|
var RELEASES_LATEST_URL = "https://api.github.com/repos/leadbay/leadclaw/releases/latest";
|
|
21076
21273
|
var CHECK_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
21077
21274
|
var FETCH_TIMEOUT_MS = 5e3;
|
|
@@ -21116,25 +21313,24 @@ function compareSemver(a, b) {
|
|
|
21116
21313
|
}
|
|
21117
21314
|
return 0;
|
|
21118
21315
|
}
|
|
21119
|
-
function
|
|
21316
|
+
function pickInstallAsset(rel) {
|
|
21120
21317
|
if (!Array.isArray(rel.assets)) return void 0;
|
|
21121
|
-
const mcpb = rel.assets.find(
|
|
21122
|
-
(a) => typeof a.name === "string" && a.name.endsWith(".mcpb")
|
|
21123
|
-
);
|
|
21124
|
-
if (mcpb?.browser_download_url) return mcpb.browser_download_url;
|
|
21125
21318
|
const dxt = rel.assets.find(
|
|
21126
21319
|
(a) => typeof a.name === "string" && a.name.endsWith(".dxt")
|
|
21127
21320
|
);
|
|
21128
|
-
|
|
21321
|
+
if (dxt?.browser_download_url) return dxt.browser_download_url;
|
|
21322
|
+
const mcpb = rel.assets.find(
|
|
21323
|
+
(a) => typeof a.name === "string" && a.name.endsWith(".mcpb")
|
|
21324
|
+
);
|
|
21325
|
+
return mcpb?.browser_download_url;
|
|
21129
21326
|
}
|
|
21130
|
-
|
|
21131
|
-
if (
|
|
21132
|
-
|
|
21133
|
-
|
|
21134
|
-
|
|
21135
|
-
|
|
21136
|
-
|
|
21137
|
-
}
|
|
21327
|
+
function checkForUpdate(opts) {
|
|
21328
|
+
if (inFlightCheck) return inFlightCheck;
|
|
21329
|
+
const p = doCheck(opts).finally(() => {
|
|
21330
|
+
if (inFlightCheck === p) inFlightCheck = null;
|
|
21331
|
+
});
|
|
21332
|
+
inFlightCheck = p;
|
|
21333
|
+
return p;
|
|
21138
21334
|
}
|
|
21139
21335
|
async function doCheck(opts) {
|
|
21140
21336
|
const now = opts.now ?? Date.now;
|
|
@@ -21143,11 +21339,11 @@ async function doCheck(opts) {
|
|
|
21143
21339
|
const currentVersion = opts.currentVersion;
|
|
21144
21340
|
const state = await opts.stateStore.read();
|
|
21145
21341
|
const within = now() - state.last_check_time < CHECK_THROTTLE_MS;
|
|
21146
|
-
if (!opts.force && within && state.latest_known_version && state.
|
|
21342
|
+
if (!opts.force && within && state.latest_known_version && state.latest_known_install_url && state.latest_known_release_url) {
|
|
21147
21343
|
const cached = buildInfoIfUpgrade(
|
|
21148
21344
|
currentVersion,
|
|
21149
21345
|
state.latest_known_version,
|
|
21150
|
-
state.
|
|
21346
|
+
state.latest_known_install_url,
|
|
21151
21347
|
state.latest_known_release_url,
|
|
21152
21348
|
state.suppressed_versions,
|
|
21153
21349
|
state.remind_until,
|
|
@@ -21201,18 +21397,18 @@ async function doCheck(opts) {
|
|
|
21201
21397
|
return null;
|
|
21202
21398
|
}
|
|
21203
21399
|
let latestVersion;
|
|
21204
|
-
let
|
|
21400
|
+
let installUrl;
|
|
21205
21401
|
let releaseUrl;
|
|
21206
21402
|
if (status === 200 && body) {
|
|
21207
21403
|
const parsed = body.tag_name ? parseTagName(body.tag_name) : null;
|
|
21208
21404
|
if (parsed) {
|
|
21209
21405
|
latestVersion = parsed;
|
|
21210
|
-
|
|
21406
|
+
installUrl = pickInstallAsset(body);
|
|
21211
21407
|
releaseUrl = body.html_url;
|
|
21212
21408
|
}
|
|
21213
21409
|
} else {
|
|
21214
21410
|
latestVersion = state.latest_known_version;
|
|
21215
|
-
|
|
21411
|
+
installUrl = state.latest_known_install_url;
|
|
21216
21412
|
releaseUrl = state.latest_known_release_url;
|
|
21217
21413
|
}
|
|
21218
21414
|
const persisted = await opts.stateStore.update((cur) => ({
|
|
@@ -21220,7 +21416,7 @@ async function doCheck(opts) {
|
|
|
21220
21416
|
last_check_time: now(),
|
|
21221
21417
|
etag: nextEtag,
|
|
21222
21418
|
latest_known_version: latestVersion ?? cur.latest_known_version,
|
|
21223
|
-
|
|
21419
|
+
latest_known_install_url: installUrl ?? cur.latest_known_install_url,
|
|
21224
21420
|
latest_known_release_url: releaseUrl ?? cur.latest_known_release_url
|
|
21225
21421
|
}));
|
|
21226
21422
|
opts.telemetry.captureUpdateCheck?.({
|
|
@@ -21230,7 +21426,7 @@ async function doCheck(opts) {
|
|
|
21230
21426
|
const info = buildInfoIfUpgrade(
|
|
21231
21427
|
currentVersion,
|
|
21232
21428
|
persisted.latest_known_version,
|
|
21233
|
-
persisted.
|
|
21429
|
+
persisted.latest_known_install_url,
|
|
21234
21430
|
persisted.latest_known_release_url,
|
|
21235
21431
|
persisted.suppressed_versions,
|
|
21236
21432
|
persisted.remind_until,
|
|
@@ -21239,22 +21435,22 @@ async function doCheck(opts) {
|
|
|
21239
21435
|
cachedInfo = info;
|
|
21240
21436
|
return info;
|
|
21241
21437
|
}
|
|
21242
|
-
function buildInfoIfUpgrade(currentVersion, latestVersion,
|
|
21243
|
-
if (!latestVersion || !
|
|
21438
|
+
function buildInfoIfUpgrade(currentVersion, latestVersion, installUrl, releaseUrl, suppressed, remindUntil, nowMs) {
|
|
21439
|
+
if (!latestVersion || !installUrl || !releaseUrl) return null;
|
|
21244
21440
|
if (compareSemver(latestVersion, currentVersion) <= 0) return null;
|
|
21245
21441
|
if (suppressed.includes(latestVersion)) return null;
|
|
21246
21442
|
if (remindUntil && remindUntil > nowMs) return null;
|
|
21247
21443
|
return {
|
|
21248
21444
|
current_version: currentVersion,
|
|
21249
21445
|
latest_version: latestVersion,
|
|
21250
|
-
|
|
21446
|
+
install_url: installUrl,
|
|
21251
21447
|
release_url: releaseUrl
|
|
21252
21448
|
};
|
|
21253
21449
|
}
|
|
21254
21450
|
|
|
21255
21451
|
// src/update-tool.ts
|
|
21256
21452
|
var TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1e3;
|
|
21257
|
-
var DESCRIPTION = "Record the user's choice on an update prompt surfaced via `update_available` on leadbay_account_status. Pass `action: 'install' | 'remind_tomorrow' | 'skip'` and `version` (the `latest_version` from the prompt). On 'install', the server returns `{
|
|
21453
|
+
var DESCRIPTION = "Record the user's choice on an update prompt surfaced via `update_available` on leadbay_account_status. Pass `action: 'install' | 'remind_tomorrow' | 'skip'` and `version` (the `latest_version` from the prompt). On 'install', the server returns `{ install_url, release_url }` \u2014 show the user a clickable link to install_url (a `.dxt` bundle) so Claude Desktop's native installer opens it. On 'remind_tomorrow' the server suppresses the prompt for 24 hours. On 'skip' the version is suppressed permanently. Call this tool EXACTLY ONCE per prompt \u2014 do not loop, and do not call it speculatively when no update_available block is present.";
|
|
21258
21454
|
function buildAcknowledgeUpdateTool(opts) {
|
|
21259
21455
|
const now = opts.now ?? Date.now;
|
|
21260
21456
|
return {
|
|
@@ -21290,7 +21486,7 @@ function buildAcknowledgeUpdateTool(opts) {
|
|
|
21290
21486
|
action: { type: "string" },
|
|
21291
21487
|
version: { type: "string" },
|
|
21292
21488
|
message: { type: "string" },
|
|
21293
|
-
|
|
21489
|
+
install_url: { type: ["string", "null"] },
|
|
21294
21490
|
release_url: { type: ["string", "null"] }
|
|
21295
21491
|
},
|
|
21296
21492
|
required: ["ok", "action", "version", "message"]
|
|
@@ -21324,9 +21520,9 @@ function buildAcknowledgeUpdateTool(opts) {
|
|
|
21324
21520
|
ok: true,
|
|
21325
21521
|
action,
|
|
21326
21522
|
version,
|
|
21327
|
-
|
|
21523
|
+
install_url: state.latest_known_install_url ?? null,
|
|
21328
21524
|
release_url: state.latest_known_release_url ?? null,
|
|
21329
|
-
message: state.
|
|
21525
|
+
message: state.latest_known_install_url ? "Show the user the install_url (a .dxt bundle) as a clickable link \u2014 opening it in Claude Desktop runs the native installer." : "No installer URL is cached. Direct the user to the release_url to download manually."
|
|
21330
21526
|
};
|
|
21331
21527
|
}
|
|
21332
21528
|
if (action === "remind_tomorrow") {
|
|
@@ -21429,7 +21625,7 @@ function buildStartHereParagraph(has) {
|
|
|
21429
21625
|
}
|
|
21430
21626
|
function buildUpdateAvailableParagraph(has) {
|
|
21431
21627
|
if (!has("leadbay_acknowledge_update")) return null;
|
|
21432
|
-
return "MCP auto-update:
|
|
21628
|
+
return "MCP auto-update: an `update_available` field (`{ current_version, latest_version, install_url, release_url }`) means a newer MCP server release is published and the user has NOT suppressed it. It appears in TWO places: as a top-level field on `leadbay_account_status`, AND on `_meta.update_available` of the FIRST other tool result in a session while an update is pending (so a fresh session surfaces the proposal even without an account_status call). Whenever you see it on ANY response, surface a prompt via `ask_user_input_v0` with EXACTLY these three options: \"Install now\", \"Remind me tomorrow\", \"Skip this version\". Map the user's choice to `leadbay_acknowledge_update({ action: 'install' | 'remind_tomorrow' | 'skip', version: latest_version })`. On 'install', the tool returns `install_url` \u2014 render it as a clickable markdown link the user can open in Claude Desktop (the .dxt extension triggers the native installer). The user does NOT need to restart anything before clicking \u2014 the new server takes effect on the next MCP session. Prompt the user ONCE per session per version \u2014 don't re-prompt within the same chat after they've acknowledged.";
|
|
21433
21629
|
}
|
|
21434
21630
|
function buildRhythmParagraph(has) {
|
|
21435
21631
|
if (has("leadbay_report_outreach")) {
|
|
@@ -21724,16 +21920,47 @@ function buildServer(client, opts = {}) {
|
|
|
21724
21920
|
);
|
|
21725
21921
|
});
|
|
21726
21922
|
};
|
|
21727
|
-
const
|
|
21728
|
-
|
|
21923
|
+
const UPDATE_SURFACE_WAIT_MS = 1500;
|
|
21924
|
+
const maybeAttachUpdate = async (toolName, result) => {
|
|
21729
21925
|
if (!opts.updateStateStore) return;
|
|
21730
21926
|
if (result === null || typeof result !== "object" || Array.isArray(result)) {
|
|
21731
21927
|
return;
|
|
21732
21928
|
}
|
|
21733
|
-
|
|
21929
|
+
if (result.error === true) {
|
|
21930
|
+
return;
|
|
21931
|
+
}
|
|
21932
|
+
let info = getCachedUpdateInfo();
|
|
21933
|
+
if (!info) {
|
|
21934
|
+
const inflight = getInFlightCheck();
|
|
21935
|
+
if (inflight) {
|
|
21936
|
+
const settled = inflight.catch((err) => {
|
|
21937
|
+
opts.logger?.warn?.(
|
|
21938
|
+
`update_check.surface_await_failed ${err?.message ?? err}`
|
|
21939
|
+
);
|
|
21940
|
+
return null;
|
|
21941
|
+
});
|
|
21942
|
+
info = await Promise.race([
|
|
21943
|
+
settled,
|
|
21944
|
+
new Promise(
|
|
21945
|
+
(resolve) => setTimeout(() => resolve(null), UPDATE_SURFACE_WAIT_MS)
|
|
21946
|
+
)
|
|
21947
|
+
]);
|
|
21948
|
+
info = getCachedUpdateInfo() ?? info;
|
|
21949
|
+
}
|
|
21950
|
+
}
|
|
21734
21951
|
if (!info) return;
|
|
21735
|
-
|
|
21736
|
-
|
|
21952
|
+
const isAccountStatus = toolName === "leadbay_account_status";
|
|
21953
|
+
const alreadyPrompted = promptedVersionsThisSession.has(info.latest_version);
|
|
21954
|
+
if (!isAccountStatus && alreadyPrompted) return;
|
|
21955
|
+
if (isAccountStatus) {
|
|
21956
|
+
result.update_available = info;
|
|
21957
|
+
} else {
|
|
21958
|
+
const envelope = result;
|
|
21959
|
+
const target = envelope.__markdown_envelope === true && envelope.structured !== null && typeof envelope.structured === "object" && !Array.isArray(envelope.structured) ? envelope.structured : envelope;
|
|
21960
|
+
const existingMeta = target._meta && typeof target._meta === "object" && !Array.isArray(target._meta) ? target._meta : {};
|
|
21961
|
+
target._meta = { ...existingMeta, update_available: info };
|
|
21962
|
+
}
|
|
21963
|
+
if (!alreadyPrompted) {
|
|
21737
21964
|
promptedVersionsThisSession.add(info.latest_version);
|
|
21738
21965
|
telemetry.captureUpdatePrompted?.({
|
|
21739
21966
|
current_version: serverVersion,
|
|
@@ -21907,9 +22134,13 @@ function buildServer(client, opts = {}) {
|
|
|
21907
22134
|
notificationsInbox: opts.notificationsInbox,
|
|
21908
22135
|
signal: extra.signal,
|
|
21909
22136
|
progress,
|
|
21910
|
-
elicit
|
|
22137
|
+
elicit,
|
|
22138
|
+
// Route leadbay_send_feedback to Sentry's feedback inbox (same place
|
|
22139
|
+
// the web app's form lands). NOOP_TELEMETRY returns false, so the
|
|
22140
|
+
// tool reports honestly when telemetry is off.
|
|
22141
|
+
sendFeedback: (message, fbOpts) => telemetry.captureFeedback(message, fbOpts)
|
|
21911
22142
|
});
|
|
21912
|
-
maybeAttachUpdate(name, result);
|
|
22143
|
+
await maybeAttachUpdate(name, result);
|
|
21913
22144
|
maybeAttachNotifications(result);
|
|
21914
22145
|
if (result && typeof result === "object" && result.error === true) {
|
|
21915
22146
|
const envText = formatErrorForLLM(result);
|
|
@@ -22205,7 +22436,7 @@ function parseWriteEnv(env = process.env) {
|
|
|
22205
22436
|
}
|
|
22206
22437
|
|
|
22207
22438
|
// src/http-server.ts
|
|
22208
|
-
var VERSION = true ? "0.
|
|
22439
|
+
var VERSION = true ? "0.20.0" : "0.0.0-dev";
|
|
22209
22440
|
var PORT = Number(process.env.PORT ?? 8080);
|
|
22210
22441
|
var HOST = process.env.HOST ?? "0.0.0.0";
|
|
22211
22442
|
var sseSessions = /* @__PURE__ */ new Map();
|
|
@@ -1466,7 +1466,7 @@ var init_installer_gui = __esm({
|
|
|
1466
1466
|
init_install_dxt();
|
|
1467
1467
|
init_install_shared();
|
|
1468
1468
|
init_oauth();
|
|
1469
|
-
VERSION = "0.
|
|
1469
|
+
VERSION = "0.20.0";
|
|
1470
1470
|
PORT = Number(process.env.LEADBAY_INSTALLER_PORT ?? 0);
|
|
1471
1471
|
sessions = /* @__PURE__ */ new Map();
|
|
1472
1472
|
OAUTH_BASE_URLS = {
|
package/dist/installer-gui.js
CHANGED
|
@@ -873,7 +873,7 @@ async function oauthLogin(opts) {
|
|
|
873
873
|
}
|
|
874
874
|
|
|
875
875
|
// installer/installer-gui.ts
|
|
876
|
-
var VERSION = "0.
|
|
876
|
+
var VERSION = "0.20.0";
|
|
877
877
|
var PORT = Number(process.env.LEADBAY_INSTALLER_PORT ?? 0);
|
|
878
878
|
var sessions = /* @__PURE__ */ new Map();
|
|
879
879
|
var OAUTH_BASE_URLS = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leadbay/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"mcpName": "io.github.leadbay/leadbay-mcp",
|
|
5
5
|
"description": "Model Context Protocol (MCP) server for Leadbay — AI lead discovery, qualification, and enrichment for Claude Desktop, Cursor, and Claude Code.",
|
|
6
6
|
"type": "module",
|