@leadbay/mcp 0.23.0 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/bin.js +818 -30
- package/dist/http-server.js +701 -6
- package/dist/installer-electron.js +59 -21
- package/dist/installer-gui.js +59 -21
- package/package.json +1 -1
package/dist/http-server.js
CHANGED
|
@@ -6534,9 +6534,13 @@ var COMPOSITE_FILE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
6534
6534
|
"leadbay_campaign_call_sheet",
|
|
6535
6535
|
"leadbay_campaign_progression",
|
|
6536
6536
|
"leadbay_create_campaign",
|
|
6537
|
+
"leadbay_create_custom_field",
|
|
6538
|
+
"leadbay_delete_custom_field",
|
|
6537
6539
|
"leadbay_enrich_titles",
|
|
6538
6540
|
"leadbay_extend_lens",
|
|
6539
6541
|
"leadbay_followups_map",
|
|
6542
|
+
"leadbay_get_lead_custom_fields",
|
|
6543
|
+
"leadbay_get_qualification_questions",
|
|
6540
6544
|
"leadbay_import_and_qualify",
|
|
6541
6545
|
"leadbay_import_leads",
|
|
6542
6546
|
"leadbay_import_status",
|
|
@@ -6557,7 +6561,9 @@ var COMPOSITE_FILE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
6557
6561
|
"leadbay_resolve_import_rows",
|
|
6558
6562
|
"leadbay_scan_portfolio_signals",
|
|
6559
6563
|
"leadbay_seed_candidates",
|
|
6564
|
+
"leadbay_set_qualification_questions",
|
|
6560
6565
|
"leadbay_team_activity",
|
|
6566
|
+
"leadbay_update_custom_field",
|
|
6561
6567
|
"leadbay_tour_plan"
|
|
6562
6568
|
]);
|
|
6563
6569
|
|
|
@@ -7406,6 +7412,23 @@ WHEN TO USE: when a quota error has blocked the user's intent and they want to k
|
|
|
7406
7412
|
|
|
7407
7413
|
WHEN NOT TO USE: pre-flight (the agent is not paying \u2014 the user is); for subscription / plan changes use \`leadbay_open_billing_portal\` instead; for read-only quota diagnostics use \`leadbay_account_status\` / \`leadbay_get_quota\`.
|
|
7408
7414
|
`;
|
|
7415
|
+
var leadbay_delete_custom_field = `Delete an org-level CRM custom field. Use when the user explicitly wants to remove a custom field from their account \u2014 e.g. "delete the old 'Legacy Source' field".
|
|
7416
|
+
|
|
7417
|
+
**This is destructive.** Removing the field drops its stored values from every lead and breaks any import mapping that targets \`CUSTOM.<id>\`. For that reason the tool has a safety gate:
|
|
7418
|
+
|
|
7419
|
+
- Call with \`id\` only \u2192 the tool returns the field that WOULD be deleted (\`{id, name, type, deleted:false, hint}\`) and changes nothing. Surface the hint to the user and ask them to confirm.
|
|
7420
|
+
- Call with \`id\` + \`confirm:true\` \u2192 the field is deleted (\`{id, name, type, deleted:true}\`).
|
|
7421
|
+
|
|
7422
|
+
\`id\` is the numeric custom-field id from \`leadbay_list_mappable_fields\` \u2014 NOT the \`CUSTOM.<id>\` mapping value.
|
|
7423
|
+
|
|
7424
|
+
WHEN TO USE: the user explicitly asks to delete/remove a custom field \u2014 and only fire with confirm:true after they confirm.
|
|
7425
|
+
|
|
7426
|
+
WHEN NOT TO USE: to rename or retype a field (use leadbay_update_custom_field) or to create one (use leadbay_create_custom_field).
|
|
7427
|
+
|
|
7428
|
+
### RENDERING
|
|
7429
|
+
|
|
7430
|
+
Before deleting (no confirm), render the \`hint\` and ask the user to confirm \u2014 do NOT auto-confirm on the user's behalf. After a confirmed delete, acknowledge in one line: **"Deleted custom field #12 'Legacy Source'."**
|
|
7431
|
+
`;
|
|
7409
7432
|
var leadbay_deselect_leads = `Remove leads from the user's transient selection.
|
|
7410
7433
|
|
|
7411
7434
|
WHEN TO USE: when narrowing a previously-built selection without clearing it entirely.
|
|
@@ -7809,6 +7832,68 @@ WHEN TO USE: to avoid redundant outreach and understand where this lead is in th
|
|
|
7809
7832
|
|
|
7810
7833
|
WHEN NOT TO USE: when leadbay_research_lead_by_id has already been called \u2014 it includes recent prospecting actions in its engagement block.
|
|
7811
7834
|
`;
|
|
7835
|
+
var leadbay_get_lead_custom_fields = `## WHEN TO USE
|
|
7836
|
+
|
|
7837
|
+
Trigger phrases: "what custom fields are on this lead", "show the CRM custom field values for <Company>", "what's the <custom field name> on this lead", "get lead <UUID>'s custom fields", "does this lead have any custom field values".
|
|
7838
|
+
|
|
7839
|
+
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
7840
|
+
|
|
7841
|
+
Do NOT use for: "what custom fields exist on my account" \u2192 \`leadbay_list_mappable_fields\`; "give me the full research dossier on this lead" \u2192 \`leadbay_research_lead_by_id\`.
|
|
7842
|
+
|
|
7843
|
+
Prefer when: user wants the custom-field VALUES on ONE lead; pass \`leadId\`
|
|
7844
|
+
|
|
7845
|
+
Examples that SHOULD invoke this tool:
|
|
7846
|
+
- "What custom field values are stored on this lead?"
|
|
7847
|
+
- "Show me the CRM custom fields for that company."
|
|
7848
|
+
|
|
7849
|
+
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
7850
|
+
- "What custom fields are defined on my account?"
|
|
7851
|
+
- "Give me the full research breakdown on Acme Corp."
|
|
7852
|
+
|
|
7853
|
+
## RENDER (quick)
|
|
7854
|
+
|
|
7855
|
+
3-column markdown table: Field | Type | Value, one row per entry. When
|
|
7856
|
+
\`custom_fields\` is empty, render the \`hint\` sentence instead of an empty
|
|
7857
|
+
table.
|
|
7858
|
+
|
|
7859
|
+
---
|
|
7860
|
+
|
|
7861
|
+
Retrieve the CRM custom-field **values** stored on a single lead \u2014 the actual
|
|
7862
|
+
data (\`value\`) per custom field, not the field definitions.
|
|
7863
|
+
|
|
7864
|
+
This is distinct from **leadbay_list_mappable_fields**, which returns the org's
|
|
7865
|
+
custom-field *catalog* (the definitions: id/name/type, used for import
|
|
7866
|
+
mapping). This tool answers "what does *this* lead hold for each custom field".
|
|
7867
|
+
|
|
7868
|
+
Params: \`leadId\` (required UUID); \`lensId\` (optional escape hatch \u2014 normally
|
|
7869
|
+
omit; auto-resolves to the active lens).
|
|
7870
|
+
|
|
7871
|
+
Returns:
|
|
7872
|
+
|
|
7873
|
+
- **\`custom_fields\`** \u2014 one row per value: \`{id, name, type, value}\`. The lead
|
|
7874
|
+
payload embeds each field's definition, so rows are fully named without a
|
|
7875
|
+
separate catalog lookup. Empty array when the org has no custom fields or
|
|
7876
|
+
none are set on this lead.
|
|
7877
|
+
- **\`count\`** \u2014 number of values.
|
|
7878
|
+
- **\`hint\`** \u2014 empty-state guidance (points at \`leadbay_list_mappable_fields\`
|
|
7879
|
+
and the import path), or a degradation note in the rare case a value arrived
|
|
7880
|
+
without an embedded definition and the catalog fallback failed.
|
|
7881
|
+
|
|
7882
|
+
Reading a lead marks it seen (LEAD_SEEN) the same way the research tools do, so
|
|
7883
|
+
it ages out of the 'new' Discover view.
|
|
7884
|
+
|
|
7885
|
+
Companion tools: **leadbay_list_mappable_fields** for the catalog/definitions;
|
|
7886
|
+
**leadbay_research_lead_by_id** for the full lead dossier (signals, contacts,
|
|
7887
|
+
qualification answers).
|
|
7888
|
+
|
|
7889
|
+
### RENDERING
|
|
7890
|
+
|
|
7891
|
+
Render \`custom_fields\` as a 3-column markdown table: **Field** (\`name\`, or the
|
|
7892
|
+
\`id\` when name is null) \xB7 **Type** (\`type\`) \xB7 **Value** (\`value\`, or "\u2014" when
|
|
7893
|
+
null/empty). One row per entry, in the order returned. When \`custom_fields\` is
|
|
7894
|
+
empty, render the \`hint\` sentence instead of an empty table. Don't fabricate
|
|
7895
|
+
fields or values \u2014 render verbatim.
|
|
7896
|
+
`;
|
|
7812
7897
|
var leadbay_get_lead_notes = `Read existing notes on a lead \u2014 context the human team or prior agent runs have already captured.
|
|
7813
7898
|
|
|
7814
7899
|
WHEN TO USE: before adding a note via leadbay_report_outreach, to avoid duplicating or overwriting context the SDR already wrote.
|
|
@@ -7839,6 +7924,70 @@ WHEN TO USE: before contacting the lead, to avoid duplicating outreach the team
|
|
|
7839
7924
|
|
|
7840
7925
|
WHEN NOT TO USE: when the lead summary's \`prospecting_actions_count\` is 0.
|
|
7841
7926
|
`;
|
|
7927
|
+
var leadbay_get_qualification_questions = `## WHEN TO USE
|
|
7928
|
+
|
|
7929
|
+
Trigger phrases: "what are my qualification questions", "what questions does Leadbay ask about each lead", "show me the org qualification questions", "how are my leads being qualified", "what's the qualification criteria".
|
|
7930
|
+
|
|
7931
|
+
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
7932
|
+
|
|
7933
|
+
Do NOT use for: "how did this lead score on the qualification questions" \u2192 \`leadbay_research_lead_by_id\`; "show my ideal buyer profile and intent tags" \u2192 \`leadbay_get_taste_profile\`.
|
|
7934
|
+
|
|
7935
|
+
Prefer when: user wants the ORG-level qualification questions catalog, no lead and no buyer profile
|
|
7936
|
+
|
|
7937
|
+
Examples that SHOULD invoke this tool:
|
|
7938
|
+
- "What qualification questions does Leadbay use to score my leads?"
|
|
7939
|
+
- "Show me my org's qualification questions."
|
|
7940
|
+
|
|
7941
|
+
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
7942
|
+
- "How did Acme Corp answer the qualification questions?"
|
|
7943
|
+
- "What's my ideal buyer profile?"
|
|
7944
|
+
|
|
7945
|
+
## RENDER (quick)
|
|
7946
|
+
|
|
7947
|
+
Numbered list of the questions (chat-native markdown), each one line. When
|
|
7948
|
+
\`is_admin\` is true, append the \`hint\` as a footnote (points at
|
|
7949
|
+
leadbay_set_qualification_questions for editing). When the list is empty,
|
|
7950
|
+
render the \`hint\` instead.
|
|
7951
|
+
|
|
7952
|
+
---
|
|
7953
|
+
|
|
7954
|
+
Retrieve the organization's **qualification questions** \u2014 the AI-agent questions
|
|
7955
|
+
Leadbay scores every lead against. These are the org-level questions that drive
|
|
7956
|
+
each lead's qualification boost; the per-lead ANSWERS to them surface inside
|
|
7957
|
+
\`leadbay_research_lead_by_id\`.
|
|
7958
|
+
|
|
7959
|
+
Returns:
|
|
7960
|
+
|
|
7961
|
+
- **\`qualification_questions\`** \u2014 the catalog. Each: \`{question, created_at,
|
|
7962
|
+
lang}\`. Ordered as the backend returns them.
|
|
7963
|
+
- **\`count\`** \u2014 number of configured questions.
|
|
7964
|
+
- **\`is_admin\`** \u2014 whether the current user is an org admin. Modifying the
|
|
7965
|
+
questions (\`leadbay_set_qualification_questions\`) is an org-admin action; for
|
|
7966
|
+
admins a \`hint\` points there.
|
|
7967
|
+
- **\`hint\`** \u2014 operator note: the modify pointer, or an empty-state message
|
|
7968
|
+
when no questions are configured.
|
|
7969
|
+
|
|
7970
|
+
This tool only READS. To change the questions, use
|
|
7971
|
+
**leadbay_set_qualification_questions** (add / remove / replace). The result is
|
|
7972
|
+
cached on the client (it reuses the same taste-profile fetch as
|
|
7973
|
+
\`leadbay_get_taste_profile\`), so repeated calls in a session are cheap.
|
|
7974
|
+
|
|
7975
|
+
Companion tools: **leadbay_set_qualification_questions** to modify the questions;
|
|
7976
|
+
**leadbay_get_taste_profile** when the user also wants the Ideal Buyer Profile +
|
|
7977
|
+
purchase-intent tags; **leadbay_research_lead_by_id** for how a SPECIFIC lead
|
|
7978
|
+
answered these questions; **leadbay_refine_prompt** to shape the AI agent's
|
|
7979
|
+
behaviour.
|
|
7980
|
+
|
|
7981
|
+
### RENDERING
|
|
7982
|
+
|
|
7983
|
+
Render \`qualification_questions\` as a numbered list \u2014 one question per line, in
|
|
7984
|
+
the order returned. Lead with a short heading like **"Qualification questions
|
|
7985
|
+
(N)"**. When \`qualification_questions\` is empty, render the \`hint\` sentence
|
|
7986
|
+
instead of an empty list. When \`is_admin\` is true and there are questions,
|
|
7987
|
+
append the \`hint\` as a one-line footnote (points at
|
|
7988
|
+
leadbay_set_qualification_questions). Do not invent questions or reword them \u2014
|
|
7989
|
+
render verbatim.
|
|
7990
|
+
`;
|
|
7842
7991
|
var leadbay_get_quota = `Read remaining quota / spend across daily, weekly, and monthly windows for the org's resources (\`llm_completion\`, \`ai_rescore\`, \`web_fetch\`). Each entry shows \`current_units\` vs \`max_units\` and \`resets_at\`.
|
|
7843
7992
|
|
|
7844
7993
|
WHEN TO USE: after a 429 error, to explain to the user which window was hit and when it resets.
|
|
@@ -10001,6 +10150,28 @@ WHEN NOT TO USE: the user is just skipping ONE outreach attempt \u2014 that's a
|
|
|
10001
10150
|
|
|
10002
10151
|
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\`.
|
|
10003
10152
|
`;
|
|
10153
|
+
var leadbay_set_qualification_questions = `Modify the organization's **qualification questions** \u2014 the AI-agent questions Leadbay scores every lead against. Use when the user wants to add, remove, or rewrite their qualification questions \u2014 e.g. "add a question about whether they run install crews", "remove the flooring question", "replace my questions with these three".
|
|
10154
|
+
|
|
10155
|
+
The backend stores the list as a whole, so this tool reads the current questions and applies your change:
|
|
10156
|
+
|
|
10157
|
+
- **\`add\`** \u2014 append questions (deduped against the current list).
|
|
10158
|
+
- **\`remove\`** \u2014 drop the exact question strings you pass.
|
|
10159
|
+
- **\`questions\`** \u2014 replace the ENTIRE list (mutually exclusive with add/remove).
|
|
10160
|
+
|
|
10161
|
+
Leadbay allows **at most 5** qualification questions. If a change would exceed 5, the tool rejects with a clear limit message \u2014 remove some before adding.
|
|
10162
|
+
|
|
10163
|
+
**Dropping any existing question is destructive** \u2014 it changes how every lead is scored. Any change that removes a current question requires \`confirm:true\` \u2014 including a same-count **swap** (remove one + add one) or a \`questions\` replacement that omits a current question, not only when the list gets shorter. Without \`confirm\`, the tool previews what would be removed and applies nothing. Pure additions never need confirm.
|
|
10164
|
+
|
|
10165
|
+
Returns the resulting \`{qualification_questions, count, previous_count, changed}\`. Phrase questions as the yes/no scoring prompts Leadbay uses (e.g. "Is the company likely to \u2026?").
|
|
10166
|
+
|
|
10167
|
+
WHEN TO USE: the user wants to change the org's qualification questions.
|
|
10168
|
+
|
|
10169
|
+
WHEN NOT TO USE: to READ the questions (use leadbay_get_qualification_questions) or to change a single lead's data. This is org-level \u2014 it affects scoring for ALL leads.
|
|
10170
|
+
|
|
10171
|
+
### RENDERING
|
|
10172
|
+
|
|
10173
|
+
After a change, confirm in one line \u2014 e.g. **"Added 1 question \u2014 you now score leads against 4 questions."** or **"Removed 'the flooring question' \u2014 3 questions remain."** Then list the resulting questions as a numbered list. When the result is a non-changing preview (a removal awaiting confirmation), surface the \`hint\` (what would be removed) and ask the user to confirm \u2014 do NOT auto-confirm.
|
|
10174
|
+
`;
|
|
10004
10175
|
var leadbay_set_user_prompt = `Set the org's intelligence-refinement prompt \u2014 free-text instruction that steers Leadbay's lead recommendations beyond firmographics. Admin-only. Setting this clears any pending clarification and triggers a full intelligence regeneration (web search + high-reasoning). \`dry_run:true\` returns the call shape without contacting the backend.
|
|
10005
10176
|
|
|
10006
10177
|
WHEN TO USE: low-level.
|
|
@@ -10227,6 +10398,20 @@ Returns \`{ updated: true, contact_id, contact: { id, first_name, last_name, job
|
|
|
10227
10398
|
|
|
10228
10399
|
Requires: LEADBAY_MCP_WRITE=1 (MCP) or exposeWrite=true (OpenClaw).
|
|
10229
10400
|
`;
|
|
10401
|
+
var leadbay_update_custom_field = `Update an org-level CRM custom field in place. Use when the user wants to rename a custom field or change its type/config \u2014 e.g. "rename the 'Tier' field to 'Account Tier'" or "make the ARR field a PRICE in USD".
|
|
10402
|
+
|
|
10403
|
+
Pass \`id\` (the numeric custom-field id from \`leadbay_list_mappable_fields\` \u2014 NOT the \`CUSTOM.<id>\` mapping value) plus any of \`name\`, \`type\`, \`config\`. The update is a partial merge over the current definition: a rename-only call keeps the existing type; a retype-only call keeps the name. At least one of \`name\` / \`type\` / \`config\` is required.
|
|
10404
|
+
|
|
10405
|
+
Type + config rules mirror creation: \`EXTERNAL_ID\` requires \`config.url_template\` containing \`{value}\`; \`PRICE\` requires \`config.currency\`; \`DATE\`/\`DATETIME\` may set \`config.format\`. Returns the updated \`{id, name, type, config, mapping_value}\`.
|
|
10406
|
+
|
|
10407
|
+
WHEN TO USE: the user wants to change an existing custom field's name or type.
|
|
10408
|
+
|
|
10409
|
+
WHEN NOT TO USE: to CREATE a new field (use leadbay_create_custom_field) or to DELETE one (use leadbay_delete_custom_field). To read a lead's custom-field values use leadbay_get_lead_custom_fields; to list the catalog use leadbay_list_mappable_fields.
|
|
10410
|
+
|
|
10411
|
+
### RENDERING
|
|
10412
|
+
|
|
10413
|
+
Confirm the change in one line naming the field and what changed, e.g. **"Renamed custom field #12 \u2192 'Account Tier' (TEXT)."** or **"Updated #13 'ARR' \u2192 PRICE (USD)."** Don't dump the raw payload.
|
|
10414
|
+
`;
|
|
10230
10415
|
var leadbay_update_lens = `Update lens metadata (name, description, mode flags). Does NOT change the audience filter \u2014 use leadbay_update_lens_filter for that.
|
|
10231
10416
|
|
|
10232
10417
|
WHEN TO USE: rename a lens or toggle \`multi_product_mode\` / \`use_hq_only\`.
|
|
@@ -14132,7 +14317,39 @@ var launchBulkEnrichment = {
|
|
|
14132
14317
|
}
|
|
14133
14318
|
};
|
|
14134
14319
|
|
|
14135
|
-
// ../core/dist/
|
|
14320
|
+
// ../core/dist/composite/_custom-field-config.js
|
|
14321
|
+
function sanitizeConfigForType(type, rawConfig) {
|
|
14322
|
+
if (!rawConfig)
|
|
14323
|
+
return null;
|
|
14324
|
+
let config;
|
|
14325
|
+
if (typeof rawConfig === "string") {
|
|
14326
|
+
try {
|
|
14327
|
+
const parsed = JSON.parse(rawConfig);
|
|
14328
|
+
if (!parsed || typeof parsed !== "object")
|
|
14329
|
+
return null;
|
|
14330
|
+
config = parsed;
|
|
14331
|
+
} catch {
|
|
14332
|
+
return null;
|
|
14333
|
+
}
|
|
14334
|
+
} else {
|
|
14335
|
+
config = rawConfig;
|
|
14336
|
+
}
|
|
14337
|
+
switch (type) {
|
|
14338
|
+
case "PRICE":
|
|
14339
|
+
return config.currency != null ? { currency: config.currency } : null;
|
|
14340
|
+
case "DATE":
|
|
14341
|
+
case "DATETIME":
|
|
14342
|
+
return "format" in config ? { format: config.format ?? null } : null;
|
|
14343
|
+
case "EXTERNAL_ID": {
|
|
14344
|
+
const url = config.url_template ?? config.urlTemplate;
|
|
14345
|
+
return url != null ? { url_template: url } : null;
|
|
14346
|
+
}
|
|
14347
|
+
default:
|
|
14348
|
+
return null;
|
|
14349
|
+
}
|
|
14350
|
+
}
|
|
14351
|
+
|
|
14352
|
+
// ../core/dist/composite/create-custom-field.js
|
|
14136
14353
|
var createCustomField = {
|
|
14137
14354
|
name: "leadbay_create_custom_field",
|
|
14138
14355
|
annotations: {
|
|
@@ -14157,8 +14374,8 @@ var createCustomField = {
|
|
|
14157
14374
|
description: "Custom field type: TEXT, NUMBER, PRICE, DATE, DATETIME, or EXTERNAL_ID. Defaults to TEXT."
|
|
14158
14375
|
},
|
|
14159
14376
|
config: {
|
|
14160
|
-
type: ["object", "null"],
|
|
14161
|
-
description: "Type-specific config. EXTERNAL_ID requires {url_template:'https://.../{value}'}; PRICE requires {currency:'USD'}; DATE/DATETIME may set {format}."
|
|
14377
|
+
type: ["object", "string", "null"],
|
|
14378
|
+
description: "Type-specific config, as an object (preferred) or a JSON string. EXTERNAL_ID requires {url_template:'https://.../{value}'}; PRICE requires {currency:'USD'}; DATE/DATETIME may set {format}."
|
|
14162
14379
|
},
|
|
14163
14380
|
if_not_exists: {
|
|
14164
14381
|
type: "boolean",
|
|
@@ -14192,9 +14409,9 @@ var createCustomField = {
|
|
|
14192
14409
|
throw client.makeError("CUSTOM_FIELD_NAME_REQUIRED", "name must be a non-empty string", "Pass a user-visible custom field name, e.g. 'HubSpot Contact'.", "POST /crm/custom_fields");
|
|
14193
14410
|
}
|
|
14194
14411
|
const type = params.type ?? "TEXT";
|
|
14195
|
-
const config = params.config ?? null;
|
|
14412
|
+
const config = sanitizeConfigForType(type, params.config ?? null);
|
|
14196
14413
|
if (type === "EXTERNAL_ID") {
|
|
14197
|
-
const urlTemplate = config?.url_template
|
|
14414
|
+
const urlTemplate = config?.url_template;
|
|
14198
14415
|
if (!urlTemplate || !urlTemplate.includes("{value}")) {
|
|
14199
14416
|
throw client.makeError("CUSTOM_FIELD_EXTERNAL_ID_TEMPLATE_REQUIRED", "EXTERNAL_ID custom fields require config.url_template containing {value}", "Use a URL template like https://app.hubspot.com/contacts/<portal-id>/record/0-1/{value}.", "POST /crm/custom_fields");
|
|
14200
14417
|
}
|
|
@@ -14224,6 +14441,170 @@ var createCustomField = {
|
|
|
14224
14441
|
}
|
|
14225
14442
|
};
|
|
14226
14443
|
|
|
14444
|
+
// ../core/dist/composite/update-custom-field.js
|
|
14445
|
+
var updateCustomField = {
|
|
14446
|
+
name: "leadbay_update_custom_field",
|
|
14447
|
+
annotations: {
|
|
14448
|
+
title: "Update CRM custom field",
|
|
14449
|
+
readOnlyHint: false,
|
|
14450
|
+
destructiveHint: false,
|
|
14451
|
+
idempotentHint: true,
|
|
14452
|
+
openWorldHint: true
|
|
14453
|
+
},
|
|
14454
|
+
description: leadbay_update_custom_field,
|
|
14455
|
+
write: true,
|
|
14456
|
+
inputSchema: {
|
|
14457
|
+
type: "object",
|
|
14458
|
+
properties: {
|
|
14459
|
+
id: {
|
|
14460
|
+
type: "string",
|
|
14461
|
+
description: "Custom field id to update (the numeric id from leadbay_list_mappable_fields, NOT the 'CUSTOM.<id>' mapping value)."
|
|
14462
|
+
},
|
|
14463
|
+
name: {
|
|
14464
|
+
type: "string",
|
|
14465
|
+
description: "New user-visible name. Omit to keep the current name."
|
|
14466
|
+
},
|
|
14467
|
+
type: {
|
|
14468
|
+
type: "string",
|
|
14469
|
+
description: "New type: TEXT, NUMBER, PRICE, DATE, DATETIME, or EXTERNAL_ID. Omit to keep the current type."
|
|
14470
|
+
},
|
|
14471
|
+
config: {
|
|
14472
|
+
type: ["object", "string", "null"],
|
|
14473
|
+
description: "New type-specific config, as an object (preferred) or a JSON string. EXTERNAL_ID requires {url_template:'https://.../{value}'}; PRICE requires {currency:'USD'}; DATE/DATETIME may set {format}. Omit to keep current config."
|
|
14474
|
+
}
|
|
14475
|
+
},
|
|
14476
|
+
required: ["id"],
|
|
14477
|
+
additionalProperties: false
|
|
14478
|
+
},
|
|
14479
|
+
outputSchema: {
|
|
14480
|
+
type: "object",
|
|
14481
|
+
properties: {
|
|
14482
|
+
id: { type: "string" },
|
|
14483
|
+
name: { type: "string" },
|
|
14484
|
+
type: { type: "string" },
|
|
14485
|
+
config: { type: ["object", "null"] },
|
|
14486
|
+
mapping_value: {
|
|
14487
|
+
type: "string",
|
|
14488
|
+
description: "Wire mapping value for import mappings, e.g. CUSTOM.123."
|
|
14489
|
+
}
|
|
14490
|
+
},
|
|
14491
|
+
required: ["id", "name", "type", "mapping_value"]
|
|
14492
|
+
},
|
|
14493
|
+
execute: async (client, params) => {
|
|
14494
|
+
const id = String(params.id ?? "").trim();
|
|
14495
|
+
if (!id) {
|
|
14496
|
+
throw client.makeError("CUSTOM_FIELD_ID_REQUIRED", "id must be a non-empty string", "Pass the custom field id from leadbay_list_mappable_fields (the numeric id, not 'CUSTOM.<id>').", "POST /crm/custom_fields/{id}");
|
|
14497
|
+
}
|
|
14498
|
+
if (params.name === void 0 && params.type === void 0 && params.config === void 0) {
|
|
14499
|
+
throw client.makeError("CUSTOM_FIELD_NO_CHANGE", "no field to update \u2014 pass at least one of name, type, config", "Provide a new name and/or type (and config when the type needs it).", "POST /crm/custom_fields/{id}");
|
|
14500
|
+
}
|
|
14501
|
+
const catalog = await client.request("GET", "/crm/custom_fields");
|
|
14502
|
+
const current = (catalog ?? []).find((f) => String(f.id) === id);
|
|
14503
|
+
if (!current) {
|
|
14504
|
+
throw client.makeError("CUSTOM_FIELD_NOT_FOUND", `no custom field with id ${id} on this org`, "Call leadbay_list_mappable_fields to see the available custom fields and their ids.", "POST /crm/custom_fields/{id}");
|
|
14505
|
+
}
|
|
14506
|
+
const name = params.name !== void 0 ? params.name.trim() : current.name;
|
|
14507
|
+
if (!name) {
|
|
14508
|
+
throw client.makeError("CUSTOM_FIELD_NAME_REQUIRED", "name must be a non-empty string", "Pass a user-visible custom field name, or omit name to keep the current one.", "POST /crm/custom_fields/{id}");
|
|
14509
|
+
}
|
|
14510
|
+
const type = params.type !== void 0 ? params.type : current.type;
|
|
14511
|
+
const rawConfig = params.config !== void 0 ? params.config : current.config ?? null;
|
|
14512
|
+
const config = sanitizeConfigForType(type, rawConfig);
|
|
14513
|
+
if (type === "EXTERNAL_ID") {
|
|
14514
|
+
const urlTemplate = config?.url_template;
|
|
14515
|
+
if (!urlTemplate || !urlTemplate.includes("{value}")) {
|
|
14516
|
+
throw client.makeError("CUSTOM_FIELD_EXTERNAL_ID_TEMPLATE_REQUIRED", "EXTERNAL_ID custom fields require config.url_template containing {value}", "Use a URL template like https://app.hubspot.com/contacts/<portal-id>/record/0-1/{value}.", "POST /crm/custom_fields/{id}");
|
|
14517
|
+
}
|
|
14518
|
+
}
|
|
14519
|
+
const body = {
|
|
14520
|
+
name,
|
|
14521
|
+
type,
|
|
14522
|
+
...config ? { config } : {}
|
|
14523
|
+
};
|
|
14524
|
+
await client.requestVoid("POST", `/crm/custom_fields/${id}`, body);
|
|
14525
|
+
return {
|
|
14526
|
+
id,
|
|
14527
|
+
name,
|
|
14528
|
+
type,
|
|
14529
|
+
config: config ?? null,
|
|
14530
|
+
mapping_value: `CUSTOM.${id}`
|
|
14531
|
+
};
|
|
14532
|
+
}
|
|
14533
|
+
};
|
|
14534
|
+
|
|
14535
|
+
// ../core/dist/composite/delete-custom-field.js
|
|
14536
|
+
var deleteCustomField = {
|
|
14537
|
+
name: "leadbay_delete_custom_field",
|
|
14538
|
+
annotations: {
|
|
14539
|
+
title: "Delete CRM custom field",
|
|
14540
|
+
readOnlyHint: false,
|
|
14541
|
+
destructiveHint: true,
|
|
14542
|
+
idempotentHint: false,
|
|
14543
|
+
openWorldHint: true
|
|
14544
|
+
},
|
|
14545
|
+
description: leadbay_delete_custom_field,
|
|
14546
|
+
write: true,
|
|
14547
|
+
inputSchema: {
|
|
14548
|
+
type: "object",
|
|
14549
|
+
properties: {
|
|
14550
|
+
id: {
|
|
14551
|
+
type: "string",
|
|
14552
|
+
description: "Custom field id to delete (the numeric id from leadbay_list_mappable_fields, NOT the 'CUSTOM.<id>' mapping value)."
|
|
14553
|
+
},
|
|
14554
|
+
confirm: {
|
|
14555
|
+
type: "boolean",
|
|
14556
|
+
description: "Must be true to actually delete. Without it the tool returns the field that WOULD be deleted and does nothing \u2014 a safety gate, because deletion drops the field's values from every lead."
|
|
14557
|
+
}
|
|
14558
|
+
},
|
|
14559
|
+
required: ["id"],
|
|
14560
|
+
additionalProperties: false
|
|
14561
|
+
},
|
|
14562
|
+
outputSchema: {
|
|
14563
|
+
type: "object",
|
|
14564
|
+
properties: {
|
|
14565
|
+
id: { type: "string" },
|
|
14566
|
+
name: { type: "string" },
|
|
14567
|
+
type: { type: "string" },
|
|
14568
|
+
deleted: {
|
|
14569
|
+
type: "boolean",
|
|
14570
|
+
description: "True when the field was deleted; false when confirm was not set."
|
|
14571
|
+
},
|
|
14572
|
+
hint: {
|
|
14573
|
+
type: "string",
|
|
14574
|
+
description: "Guidance \u2014 present when confirm was missing (re-call with confirm:true)."
|
|
14575
|
+
}
|
|
14576
|
+
},
|
|
14577
|
+
required: ["id", "deleted"]
|
|
14578
|
+
},
|
|
14579
|
+
execute: async (client, params) => {
|
|
14580
|
+
const id = String(params.id ?? "").trim();
|
|
14581
|
+
if (!id) {
|
|
14582
|
+
throw client.makeError("CUSTOM_FIELD_ID_REQUIRED", "id must be a non-empty string", "Pass the custom field id from leadbay_list_mappable_fields (the numeric id, not 'CUSTOM.<id>').", "DELETE /crm/custom_fields/{id}");
|
|
14583
|
+
}
|
|
14584
|
+
const catalog = await client.request("GET", "/crm/custom_fields");
|
|
14585
|
+
const current = (catalog ?? []).find((f) => String(f.id) === id);
|
|
14586
|
+
if (!current) {
|
|
14587
|
+
throw client.makeError("CUSTOM_FIELD_NOT_FOUND", `no custom field with id ${id} on this org`, "Call leadbay_list_mappable_fields to see the available custom fields and their ids.", "DELETE /crm/custom_fields/{id}");
|
|
14588
|
+
}
|
|
14589
|
+
if (params.confirm !== true) {
|
|
14590
|
+
return {
|
|
14591
|
+
id,
|
|
14592
|
+
name: current.name,
|
|
14593
|
+
type: current.type,
|
|
14594
|
+
deleted: false,
|
|
14595
|
+
hint: `Deleting "${current.name}" removes its values from every lead and breaks any import mapping using CUSTOM.${id}. Re-call with confirm:true to proceed.`
|
|
14596
|
+
};
|
|
14597
|
+
}
|
|
14598
|
+
await client.requestVoid("DELETE", `/crm/custom_fields/${id}`);
|
|
14599
|
+
return {
|
|
14600
|
+
id,
|
|
14601
|
+
name: current.name,
|
|
14602
|
+
type: current.type,
|
|
14603
|
+
deleted: true
|
|
14604
|
+
};
|
|
14605
|
+
}
|
|
14606
|
+
};
|
|
14607
|
+
|
|
14227
14608
|
// ../core/dist/tools/like-lead.js
|
|
14228
14609
|
var likeLead = {
|
|
14229
14610
|
name: "leadbay_like_lead",
|
|
@@ -16588,6 +16969,302 @@ var researchLeadByNameFuzzy = {
|
|
|
16588
16969
|
}
|
|
16589
16970
|
};
|
|
16590
16971
|
|
|
16972
|
+
// ../core/dist/composite/get-qualification-questions.js
|
|
16973
|
+
var getQualificationQuestions = {
|
|
16974
|
+
name: "leadbay_get_qualification_questions",
|
|
16975
|
+
annotations: {
|
|
16976
|
+
title: "Read the org's qualification questions",
|
|
16977
|
+
readOnlyHint: true,
|
|
16978
|
+
destructiveHint: false,
|
|
16979
|
+
idempotentHint: true,
|
|
16980
|
+
openWorldHint: true
|
|
16981
|
+
},
|
|
16982
|
+
description: leadbay_get_qualification_questions,
|
|
16983
|
+
inputSchema: {
|
|
16984
|
+
type: "object",
|
|
16985
|
+
properties: {},
|
|
16986
|
+
additionalProperties: false
|
|
16987
|
+
},
|
|
16988
|
+
outputSchema: {
|
|
16989
|
+
type: "object",
|
|
16990
|
+
properties: {
|
|
16991
|
+
qualification_questions: {
|
|
16992
|
+
type: "array",
|
|
16993
|
+
description: "Org-level questions Leadbay scores every lead against. Each: {question, created_at, lang}.",
|
|
16994
|
+
items: { type: "object" }
|
|
16995
|
+
},
|
|
16996
|
+
count: {
|
|
16997
|
+
type: "number",
|
|
16998
|
+
description: "Number of qualification questions configured."
|
|
16999
|
+
},
|
|
17000
|
+
is_admin: {
|
|
17001
|
+
type: "boolean",
|
|
17002
|
+
description: "Whether the current bearer-token holder is an org admin. Admins can modify the questions via leadbay_set_qualification_questions."
|
|
17003
|
+
},
|
|
17004
|
+
region: { type: "string" },
|
|
17005
|
+
hint: {
|
|
17006
|
+
type: "string",
|
|
17007
|
+
description: "Operator note \u2014 admin edit pointer, or the empty-state message when no questions are configured."
|
|
17008
|
+
},
|
|
17009
|
+
_meta: { type: "object" }
|
|
17010
|
+
},
|
|
17011
|
+
required: ["qualification_questions"]
|
|
17012
|
+
},
|
|
17013
|
+
execute: async (client, _params, ctx) => {
|
|
17014
|
+
const me = await client.resolveMe().catch(() => null);
|
|
17015
|
+
const isAdmin = me?.admin ?? false;
|
|
17016
|
+
const orgId = me?.organization?.id ?? await client.resolveOrgId();
|
|
17017
|
+
const questions = await client.request("GET", `/organizations/${orgId}/ai_agent_questions`) ?? [];
|
|
17018
|
+
let hint;
|
|
17019
|
+
if (questions.length === 0) {
|
|
17020
|
+
hint = "No qualification questions configured yet. Use leadbay_set_qualification_questions to add some, or leadbay_refine_prompt to shape the AI agent.";
|
|
17021
|
+
} else if (isAdmin) {
|
|
17022
|
+
hint = "You're an org admin \u2014 use leadbay_set_qualification_questions to add, remove, or replace these questions.";
|
|
17023
|
+
}
|
|
17024
|
+
return withAgentMemoryMeta(client, {
|
|
17025
|
+
qualification_questions: questions.map((q) => ({
|
|
17026
|
+
question: q.question,
|
|
17027
|
+
created_at: q.created_at,
|
|
17028
|
+
lang: q.lang
|
|
17029
|
+
})),
|
|
17030
|
+
count: questions.length,
|
|
17031
|
+
is_admin: isAdmin,
|
|
17032
|
+
region: client.region,
|
|
17033
|
+
...hint ? { hint } : {}
|
|
17034
|
+
}, ctx);
|
|
17035
|
+
}
|
|
17036
|
+
};
|
|
17037
|
+
|
|
17038
|
+
// ../core/dist/composite/set-qualification-questions.js
|
|
17039
|
+
var setQualificationQuestions = {
|
|
17040
|
+
name: "leadbay_set_qualification_questions",
|
|
17041
|
+
annotations: {
|
|
17042
|
+
title: "Modify the org's qualification questions",
|
|
17043
|
+
readOnlyHint: false,
|
|
17044
|
+
destructiveHint: true,
|
|
17045
|
+
idempotentHint: false,
|
|
17046
|
+
openWorldHint: true
|
|
17047
|
+
},
|
|
17048
|
+
description: leadbay_set_qualification_questions,
|
|
17049
|
+
write: true,
|
|
17050
|
+
inputSchema: {
|
|
17051
|
+
type: "object",
|
|
17052
|
+
properties: {
|
|
17053
|
+
questions: {
|
|
17054
|
+
type: "array",
|
|
17055
|
+
items: { type: "string" },
|
|
17056
|
+
description: "Full replacement list of qualification questions (replaces ALL current questions). Mutually exclusive with add/remove."
|
|
17057
|
+
},
|
|
17058
|
+
add: {
|
|
17059
|
+
type: "array",
|
|
17060
|
+
items: { type: "string" },
|
|
17061
|
+
description: "Questions to append to the current list (deduped). Mutually exclusive with `questions`."
|
|
17062
|
+
},
|
|
17063
|
+
remove: {
|
|
17064
|
+
type: "array",
|
|
17065
|
+
items: { type: "string" },
|
|
17066
|
+
description: "Exact question strings to remove from the current list. Mutually exclusive with `questions`. A removal requires confirm:true."
|
|
17067
|
+
},
|
|
17068
|
+
confirm: {
|
|
17069
|
+
type: "boolean",
|
|
17070
|
+
description: "Required whenever the change DROPS ANY existing question \u2014 including a same-count swap or a `questions` replacement that omits a current question, not only when the list gets shorter (removing a question changes how every lead is scored). Without it, such a change is previewed and not applied. Pure additions never need confirm."
|
|
17071
|
+
}
|
|
17072
|
+
},
|
|
17073
|
+
additionalProperties: false
|
|
17074
|
+
},
|
|
17075
|
+
outputSchema: {
|
|
17076
|
+
type: "object",
|
|
17077
|
+
properties: {
|
|
17078
|
+
qualification_questions: {
|
|
17079
|
+
type: "array",
|
|
17080
|
+
description: "The questions AFTER the change. Each: {question}.",
|
|
17081
|
+
items: { type: "object" }
|
|
17082
|
+
},
|
|
17083
|
+
count: { type: "number" },
|
|
17084
|
+
previous_count: { type: "number" },
|
|
17085
|
+
changed: {
|
|
17086
|
+
type: "boolean",
|
|
17087
|
+
description: "True when the list was actually written; false on a no-op or an unconfirmed shrink."
|
|
17088
|
+
},
|
|
17089
|
+
region: { type: "string" },
|
|
17090
|
+
hint: {
|
|
17091
|
+
type: "string",
|
|
17092
|
+
description: "Operator note \u2014 confirm prompt on a shrink, or a no-op explanation."
|
|
17093
|
+
},
|
|
17094
|
+
_meta: { type: "object" }
|
|
17095
|
+
},
|
|
17096
|
+
required: ["qualification_questions", "count", "changed"]
|
|
17097
|
+
},
|
|
17098
|
+
execute: async (client, params, ctx) => {
|
|
17099
|
+
const hasSet = Array.isArray(params.questions);
|
|
17100
|
+
const hasAdd = Array.isArray(params.add) && params.add.length > 0;
|
|
17101
|
+
const hasRemove = Array.isArray(params.remove) && params.remove.length > 0;
|
|
17102
|
+
if (hasSet && (hasAdd || hasRemove)) {
|
|
17103
|
+
throw client.makeError("QUALIFICATION_QUESTIONS_BAD_ARGS", "`questions` (full replace) is mutually exclusive with add/remove", "Pass EITHER `questions` (the full new list) OR `add`/`remove`, not both.", "POST /organizations/{orgId}");
|
|
17104
|
+
}
|
|
17105
|
+
if (!hasSet && !hasAdd && !hasRemove) {
|
|
17106
|
+
throw client.makeError("QUALIFICATION_QUESTIONS_NO_CHANGE", "nothing to change \u2014 pass `questions`, `add`, or `remove`", "Provide a full `questions` list, or `add`/`remove` entries.", "POST /organizations/{orgId}");
|
|
17107
|
+
}
|
|
17108
|
+
const orgId = await client.resolveOrgId();
|
|
17109
|
+
const current = await client.request("GET", `/organizations/${orgId}/ai_agent_questions`);
|
|
17110
|
+
const currentQs = (current ?? []).map((q) => q.question);
|
|
17111
|
+
const norm = (s) => s.trim();
|
|
17112
|
+
let next;
|
|
17113
|
+
if (hasSet) {
|
|
17114
|
+
next = params.questions.map(norm).filter((s) => s.length > 0);
|
|
17115
|
+
} else {
|
|
17116
|
+
next = [...currentQs];
|
|
17117
|
+
if (hasRemove) {
|
|
17118
|
+
const drop = new Set(params.remove.map(norm));
|
|
17119
|
+
next = next.filter((q) => !drop.has(norm(q)));
|
|
17120
|
+
}
|
|
17121
|
+
if (hasAdd) {
|
|
17122
|
+
const seen2 = new Set(next.map(norm));
|
|
17123
|
+
for (const q of params.add.map(norm)) {
|
|
17124
|
+
if (q.length > 0 && !seen2.has(q)) {
|
|
17125
|
+
next.push(q);
|
|
17126
|
+
seen2.add(q);
|
|
17127
|
+
}
|
|
17128
|
+
}
|
|
17129
|
+
}
|
|
17130
|
+
}
|
|
17131
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17132
|
+
next = next.filter((q) => {
|
|
17133
|
+
const k = norm(q);
|
|
17134
|
+
if (seen.has(k))
|
|
17135
|
+
return false;
|
|
17136
|
+
seen.add(k);
|
|
17137
|
+
return true;
|
|
17138
|
+
});
|
|
17139
|
+
const MAX_QUESTIONS = 5;
|
|
17140
|
+
if (next.length > MAX_QUESTIONS) {
|
|
17141
|
+
throw client.makeError("QUALIFICATION_QUESTIONS_LIMIT", `too many questions: ${next.length} (max ${MAX_QUESTIONS})`, `Leadbay allows at most ${MAX_QUESTIONS} qualification questions. Remove some first (pass fewer in \`questions\`, or use \`remove\`), then add.`, "POST /organizations/{orgId}");
|
|
17142
|
+
}
|
|
17143
|
+
const previousCount = currentQs.length;
|
|
17144
|
+
const noChange = next.length === currentQs.length && next.every((q, i) => norm(q) === norm(currentQs[i] ?? ""));
|
|
17145
|
+
if (noChange) {
|
|
17146
|
+
return withAgentMemoryMeta(client, {
|
|
17147
|
+
qualification_questions: currentQs.map((q) => ({ question: q })),
|
|
17148
|
+
count: currentQs.length,
|
|
17149
|
+
previous_count: previousCount,
|
|
17150
|
+
changed: false,
|
|
17151
|
+
region: client.region,
|
|
17152
|
+
hint: "No change \u2014 the resulting list is identical to the current one. Pass different `add`/`remove` entries, or call leadbay_get_qualification_questions to review the current questions."
|
|
17153
|
+
}, ctx);
|
|
17154
|
+
}
|
|
17155
|
+
const removed = currentQs.filter((q) => !next.some((n) => norm(n) === norm(q)));
|
|
17156
|
+
if (removed.length > 0 && params.confirm !== true) {
|
|
17157
|
+
return withAgentMemoryMeta(client, {
|
|
17158
|
+
qualification_questions: currentQs.map((q) => ({ question: q })),
|
|
17159
|
+
count: currentQs.length,
|
|
17160
|
+
previous_count: previousCount,
|
|
17161
|
+
changed: false,
|
|
17162
|
+
region: client.region,
|
|
17163
|
+
hint: `Re-call with confirm:true to apply. This would remove ${removed.length} question(s): ${removed.map((q) => `"${q}"`).join(", ")}. Removing a question changes how every lead is scored.`
|
|
17164
|
+
}, ctx);
|
|
17165
|
+
}
|
|
17166
|
+
await client.requestVoid("POST", `/organizations/${orgId}`, {
|
|
17167
|
+
ai_agent_lead_questions: next
|
|
17168
|
+
});
|
|
17169
|
+
client.invalidateTasteProfile();
|
|
17170
|
+
return withAgentMemoryMeta(client, {
|
|
17171
|
+
qualification_questions: next.map((q) => ({ question: q })),
|
|
17172
|
+
count: next.length,
|
|
17173
|
+
previous_count: previousCount,
|
|
17174
|
+
changed: true,
|
|
17175
|
+
region: client.region
|
|
17176
|
+
}, ctx);
|
|
17177
|
+
}
|
|
17178
|
+
};
|
|
17179
|
+
|
|
17180
|
+
// ../core/dist/composite/get-lead-custom-fields.js
|
|
17181
|
+
var getLeadCustomFields = {
|
|
17182
|
+
name: "leadbay_get_lead_custom_fields",
|
|
17183
|
+
annotations: {
|
|
17184
|
+
title: "Read a lead's custom-field values",
|
|
17185
|
+
readOnlyHint: true,
|
|
17186
|
+
destructiveHint: false,
|
|
17187
|
+
idempotentHint: true,
|
|
17188
|
+
openWorldHint: true
|
|
17189
|
+
},
|
|
17190
|
+
description: leadbay_get_lead_custom_fields,
|
|
17191
|
+
inputSchema: {
|
|
17192
|
+
type: "object",
|
|
17193
|
+
properties: {
|
|
17194
|
+
leadId: { type: "string", description: "Lead UUID (required)" },
|
|
17195
|
+
lensId: {
|
|
17196
|
+
type: "number",
|
|
17197
|
+
description: "Lens id (escape hatch \u2014 normally omit; auto-resolves to the active lens)"
|
|
17198
|
+
}
|
|
17199
|
+
},
|
|
17200
|
+
required: ["leadId"],
|
|
17201
|
+
additionalProperties: false
|
|
17202
|
+
},
|
|
17203
|
+
outputSchema: {
|
|
17204
|
+
type: "object",
|
|
17205
|
+
properties: {
|
|
17206
|
+
lead_id: { type: "string" },
|
|
17207
|
+
custom_fields: {
|
|
17208
|
+
type: "array",
|
|
17209
|
+
description: "Custom-field VALUES on this lead. Each: {id, name, type, value}. Empty when the org has no custom fields or none are set on this lead.",
|
|
17210
|
+
items: { type: "object" }
|
|
17211
|
+
},
|
|
17212
|
+
count: { type: "number" },
|
|
17213
|
+
region: { type: "string" },
|
|
17214
|
+
hint: {
|
|
17215
|
+
type: "string",
|
|
17216
|
+
description: "Operator note \u2014 empty-state guidance, or a degradation note when the lead fetch returned entries without embedded definitions."
|
|
17217
|
+
},
|
|
17218
|
+
_meta: { type: "object" }
|
|
17219
|
+
},
|
|
17220
|
+
required: ["custom_fields", "lead_id"]
|
|
17221
|
+
},
|
|
17222
|
+
execute: async (client, params, ctx) => {
|
|
17223
|
+
const lensId = params.lensId ?? await client.resolveDefaultLens();
|
|
17224
|
+
void client.request("POST", "/interactions", [
|
|
17225
|
+
{ type: "LEAD_SEEN", leadId: params.leadId, lensId: String(lensId) },
|
|
17226
|
+
{ type: "LEAD_CLICKED", leadId: params.leadId, lensId: String(lensId) }
|
|
17227
|
+
]).catch(() => {
|
|
17228
|
+
});
|
|
17229
|
+
const lead = await client.request("GET", `/lenses/${lensId}/leads/${params.leadId}`);
|
|
17230
|
+
const entries = lead.custom_fields ?? [];
|
|
17231
|
+
const needsCatalog = entries.some((e) => !e?.field?.name);
|
|
17232
|
+
let catalog = null;
|
|
17233
|
+
if (needsCatalog) {
|
|
17234
|
+
try {
|
|
17235
|
+
catalog = await client.request("GET", "/crm/custom_fields");
|
|
17236
|
+
} catch (err) {
|
|
17237
|
+
ctx?.logger?.warn?.(`get_lead_custom_fields: catalog fallback failed: ${err?.message ?? err?.code ?? err}`);
|
|
17238
|
+
}
|
|
17239
|
+
}
|
|
17240
|
+
const byId = new Map((catalog ?? []).map((f) => [f.id, f]));
|
|
17241
|
+
const rows = entries.map((e) => {
|
|
17242
|
+
const bareId = e.id;
|
|
17243
|
+
const id = e.field?.id ?? bareId ?? "";
|
|
17244
|
+
const def = e.field ? void 0 : byId.get(String(id));
|
|
17245
|
+
return {
|
|
17246
|
+
id: String(id),
|
|
17247
|
+
name: e.field?.name ?? def?.name ?? null,
|
|
17248
|
+
type: e.field?.type ?? def?.type ?? null,
|
|
17249
|
+
value: e.value ?? null
|
|
17250
|
+
};
|
|
17251
|
+
});
|
|
17252
|
+
let hint;
|
|
17253
|
+
if (rows.length === 0) {
|
|
17254
|
+
hint = "This lead has no custom-field values. The org may have no custom fields defined \u2014 see leadbay_list_mappable_fields for the catalog, or set values via import (map a column to CUSTOM.<id>).";
|
|
17255
|
+
} else if (needsCatalog && rows.some((r) => r.name === null)) {
|
|
17256
|
+
hint = "Some custom fields could not be named (the lead payload omitted definitions and the catalog fetch failed). Retry, or check leadbay_list_mappable_fields.";
|
|
17257
|
+
}
|
|
17258
|
+
return withAgentMemoryMeta(client, {
|
|
17259
|
+
lead_id: params.leadId,
|
|
17260
|
+
custom_fields: rows,
|
|
17261
|
+
count: rows.length,
|
|
17262
|
+
region: client.region,
|
|
17263
|
+
...hint ? { hint } : {}
|
|
17264
|
+
}, ctx);
|
|
17265
|
+
}
|
|
17266
|
+
};
|
|
17267
|
+
|
|
16591
17268
|
// ../core/dist/composite/account-history.js
|
|
16592
17269
|
var accountHistory = {
|
|
16593
17270
|
name: "leadbay_account_history",
|
|
@@ -21538,6 +22215,15 @@ var compositeReadTools = [
|
|
|
21538
22215
|
campaignCallSheet,
|
|
21539
22216
|
researchLeadById,
|
|
21540
22217
|
researchLeadByNameFuzzy,
|
|
22218
|
+
// Org qualification questions — the AI-agent questions every lead is scored
|
|
22219
|
+
// against. ALWAYS exposed (default surface): "how are my leads qualified"
|
|
22220
|
+
// is a first-session question, and the underlying get_taste_profile is
|
|
22221
|
+
// ADVANCED-gated. Read-only; no MCP edit endpoint exists (issue #3768).
|
|
22222
|
+
getQualificationQuestions,
|
|
22223
|
+
// Per-lead custom-field VALUES. ALWAYS exposed: complements the always-on
|
|
22224
|
+
// list_mappable_fields (which returns DEFINITIONS only). The lead payload
|
|
22225
|
+
// embeds each field's definition, so no catalog join is needed (issue #3768).
|
|
22226
|
+
getLeadCustomFields,
|
|
21541
22227
|
// accountHistory layers FULL notes + activity timeline on top of research
|
|
21542
22228
|
// so the agent can write the US4 "why has this dormant account resurfaced"
|
|
21543
22229
|
// narrative in ONE call. ALWAYS exposed (compositeReadTools) — the underlying
|
|
@@ -21631,6 +22317,15 @@ var compositeWriteTools = [
|
|
|
21631
22317
|
// createCustomField is granular-shaped but file-import prompts depend on it
|
|
21632
22318
|
// to preserve source-system links without requiring advanced-tool exposure.
|
|
21633
22319
|
createCustomField,
|
|
22320
|
+
// update/delete custom field — same default-surface rationale as create.
|
|
22321
|
+
// delete is destructive (requires confirm:true). Both gated behind
|
|
22322
|
+
// LEADBAY_MCP_WRITE=1 in MCP.
|
|
22323
|
+
updateCustomField,
|
|
22324
|
+
deleteCustomField,
|
|
22325
|
+
// Modify the org's qualification questions (AI-agent questions). Full-replace
|
|
22326
|
+
// endpoint (POST /organizations/{orgId} {ai_agent_lead_questions:[...]}); the
|
|
22327
|
+
// tool reads current + applies add/remove/set. Shrinking requires confirm.
|
|
22328
|
+
setQualificationQuestions,
|
|
21634
22329
|
// addNote is granular-shaped but file-import prompts depend on it to preserve
|
|
21635
22330
|
// meaningful source-file notes after imports return lead ids.
|
|
21636
22331
|
addNote,
|
|
@@ -23055,7 +23750,7 @@ function parseWriteEnv(env = process.env) {
|
|
|
23055
23750
|
}
|
|
23056
23751
|
|
|
23057
23752
|
// src/http-server.ts
|
|
23058
|
-
var VERSION = true ? "0.23.
|
|
23753
|
+
var VERSION = true ? "0.23.1" : "0.0.0-dev";
|
|
23059
23754
|
var PORT = Number(process.env.PORT ?? 8080);
|
|
23060
23755
|
var HOST = process.env.HOST ?? "0.0.0.0";
|
|
23061
23756
|
var sseSessions = /* @__PURE__ */ new Map();
|