@agifyai/leadify-mcp 3.6.3 → 5.0.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/dist/tools/dataroom.js +5 -2
- package/dist/tools/outreach_settings.js +596 -2
- package/dist/tools/personas.js +4 -332
- package/package.json +1 -1
package/dist/tools/dataroom.js
CHANGED
|
@@ -278,11 +278,14 @@ export function registerDataRoomTools(server) {
|
|
|
278
278
|
// ── get_outreach_context ──────────────────────────────────────────────
|
|
279
279
|
server.tool("get_outreach_context", "Get a curated outreach context for a lead group: the outreach-relevant slice " +
|
|
280
280
|
"of companyInfo (identity, pitch, constraints, curated products, economics " +
|
|
281
|
-
"triggers/baseline, product ICP roles, proof wording) PLUS the full
|
|
281
|
+
"triggers/baseline, product ICP roles, proof wording) PLUS the full outreach " +
|
|
282
|
+
"settings block (positioning, sequence design, rules, URLs, case studies, " +
|
|
283
|
+
"ICP strategy, pain points, lookalike clients, outreach templates, tone, " +
|
|
284
|
+
"messaging guidelines, signal routing, sender override). " +
|
|
282
285
|
"Takes a lead_group_id (NOT organization_id) — the org is resolved server-side, " +
|
|
283
286
|
"eliminating the cross-org footgun of get_data_room. " +
|
|
284
287
|
"Returns a HARD error if no persona is linked to the group. " +
|
|
285
|
-
"Use this instead of get_data_room +
|
|
288
|
+
"Use this instead of get_data_room + get_outreach_settings in the outreach " +
|
|
286
289
|
"pipeline Phase 0.", {
|
|
287
290
|
lead_group_id: z.string().describe("ID of the lead group to resolve context for."),
|
|
288
291
|
}, async ({ lead_group_id }) => {
|
|
@@ -67,6 +67,51 @@ const urlValue = z
|
|
|
67
67
|
.union([z.literal(""), z.string().url()])
|
|
68
68
|
.nullable()
|
|
69
69
|
.optional();
|
|
70
|
+
// ── Schemas for persona-migrated fields (PRD-737) ────────────────────────
|
|
71
|
+
const outreachTemplate = z
|
|
72
|
+
.object({
|
|
73
|
+
id: z.string().optional(),
|
|
74
|
+
signal: z.string().optional(),
|
|
75
|
+
channel: z.string().nullable().optional(),
|
|
76
|
+
fieldName: z.string().optional(),
|
|
77
|
+
signalContext: z.string().optional(),
|
|
78
|
+
purpose: z.string().optional(),
|
|
79
|
+
example: z.string().optional(),
|
|
80
|
+
maxLength: z.number().optional(),
|
|
81
|
+
subject: z.string().optional(),
|
|
82
|
+
body: z.string().optional(),
|
|
83
|
+
})
|
|
84
|
+
.passthrough();
|
|
85
|
+
const outreachSignalRouting = z
|
|
86
|
+
.object({
|
|
87
|
+
signalSourceField: z.string().optional(),
|
|
88
|
+
defaultSignal: z.string().optional(),
|
|
89
|
+
signals: z.array(z.string()).optional(),
|
|
90
|
+
})
|
|
91
|
+
.passthrough()
|
|
92
|
+
.nullable();
|
|
93
|
+
const extraPromptPhase = z
|
|
94
|
+
.object({
|
|
95
|
+
scope: z.enum(["qualify", "outreach"]),
|
|
96
|
+
insertionPoint: z.enum(["prefix", "phase_1_5", "phase_2_extra", "suffix"]),
|
|
97
|
+
content: z.string(),
|
|
98
|
+
})
|
|
99
|
+
.passthrough();
|
|
100
|
+
const senderOverride = z
|
|
101
|
+
.object({ name: z.string().min(1), title: z.string().optional() })
|
|
102
|
+
.nullable()
|
|
103
|
+
.optional();
|
|
104
|
+
const icpStrategy = z
|
|
105
|
+
.object({
|
|
106
|
+
dealType: z.string().optional(),
|
|
107
|
+
dreamOutcome: z.string().optional(),
|
|
108
|
+
mainPains: z.array(z.string()).optional(),
|
|
109
|
+
keyObjections: z.array(z.string()).optional(),
|
|
110
|
+
decisionDrivers: z.array(z.string()).optional(),
|
|
111
|
+
})
|
|
112
|
+
.passthrough()
|
|
113
|
+
.nullable()
|
|
114
|
+
.optional();
|
|
70
115
|
async function fetchCurrentSettings(leadGroupId) {
|
|
71
116
|
const data = await getClient().get(`/api/lead-group/${encodeURIComponent(leadGroupId)}/outreach-settings`);
|
|
72
117
|
return data;
|
|
@@ -109,7 +154,10 @@ export function registerOutreachSettingsTools(server) {
|
|
|
109
154
|
"provided — if you only want to tweak a single slot or sub-field, prefer " +
|
|
110
155
|
"update_outreach_positioning / update_outreach_sequence_slot / " +
|
|
111
156
|
"update_outreach_rules / update_outreach_case_study / update_outreach_urls / " +
|
|
112
|
-
"set_outreach_connection_request
|
|
157
|
+
"set_outreach_connection_request / update_outreach_agent_identity / " +
|
|
158
|
+
"update_outreach_icp_strategy / update_outreach_pain_point / " +
|
|
159
|
+
"update_outreach_lookalike_client / update_outreach_outreach_template / " +
|
|
160
|
+
"update_outreach_signal_routing, which do safe read-modify-write merges.", {
|
|
113
161
|
lead_group_id: z.string().describe("Lead group ID."),
|
|
114
162
|
connection_request_enabled: z
|
|
115
163
|
.boolean()
|
|
@@ -133,9 +181,69 @@ export function registerOutreachSettingsTools(server) {
|
|
|
133
181
|
.max(5)
|
|
134
182
|
.optional()
|
|
135
183
|
.describe("Up to 5 case studies used as proof (title, summary ≤200 chars, idealFor, proofPoints[])."),
|
|
136
|
-
|
|
184
|
+
// ── Fields migrated from Persona (PRD-737) ───────────────────────────
|
|
185
|
+
description: z
|
|
186
|
+
.string()
|
|
187
|
+
.optional()
|
|
188
|
+
.describe("Description / context for this lead group's outreach."),
|
|
189
|
+
output_language: z
|
|
190
|
+
.string()
|
|
191
|
+
.optional()
|
|
192
|
+
.describe("Output language code (fr, en, de, ...)."),
|
|
193
|
+
agent_domain: z
|
|
194
|
+
.string()
|
|
195
|
+
.optional()
|
|
196
|
+
.describe("Agent identity string (e.g. 'for medtech companies')."),
|
|
197
|
+
icp_strategy: icpStrategy
|
|
198
|
+
.optional()
|
|
199
|
+
.describe("ICP strategy mental map: {dealType, dreamOutcome, mainPains[], keyObjections[], decisionDrivers[]}."),
|
|
200
|
+
pain_points: z
|
|
201
|
+
.array(z.object({ title: z.string().optional(), description: z.string().optional() }).passthrough())
|
|
202
|
+
.optional()
|
|
203
|
+
.describe("Structured business pain points ({title, description})."),
|
|
204
|
+
lookalike_clients: z
|
|
205
|
+
.array(z
|
|
206
|
+
.object({
|
|
207
|
+
name: z.string(),
|
|
208
|
+
sector: z.string().optional(),
|
|
209
|
+
size: z.string().optional(),
|
|
210
|
+
segment: z.string().optional(),
|
|
211
|
+
comparison_criteria: z.string().optional(),
|
|
212
|
+
})
|
|
213
|
+
.passthrough())
|
|
214
|
+
.optional()
|
|
215
|
+
.describe("Lookalike client references ({name, sector?, size?, segment?, comparison_criteria?})."),
|
|
216
|
+
outreach_templates: z
|
|
217
|
+
.array(outreachTemplate)
|
|
218
|
+
.optional()
|
|
219
|
+
.describe("Outreach message templates by channel/signal. Each: {channel, signal, fieldName, purpose, example, subject, body}."),
|
|
220
|
+
tone_instructions: z
|
|
221
|
+
.string()
|
|
222
|
+
.optional()
|
|
223
|
+
.describe("Free-form markdown voice / tone guidance."),
|
|
224
|
+
messaging_guidelines: z
|
|
225
|
+
.any()
|
|
226
|
+
.optional()
|
|
227
|
+
.describe("Messaging approach: {tone, language, can_mention, must_not_mention, value_propositions}."),
|
|
228
|
+
outreach_signal_routing: outreachSignalRouting
|
|
229
|
+
.optional()
|
|
230
|
+
.describe("Signal routing config: {signalSourceField, defaultSignal, signals[]}. defaultSignal must exist in signals[]."),
|
|
231
|
+
sender_override: senderOverride
|
|
232
|
+
.describe("Override SDR identity: {name, title?}. Pass null to clear."),
|
|
233
|
+
outreach_fields: z
|
|
234
|
+
.any()
|
|
235
|
+
.optional()
|
|
236
|
+
.describe("Variables referenceable in templates: [{key, label, source}]."),
|
|
237
|
+
extra_prompt_phases: z
|
|
238
|
+
.array(extraPromptPhase)
|
|
239
|
+
.optional()
|
|
240
|
+
.describe("Custom prompt snippets: [{scope, insertionPoint, content}]. scope: qualify|outreach. insertionPoint: prefix|phase_1_5|phase_2_extra|suffix."),
|
|
241
|
+
}, async ({ lead_group_id, connection_request_enabled, positioning: pos, sequence_design, rules: rls, booking_url, website_url, case_studies,
|
|
242
|
+
// ── PRD-737 fields ──────────────────────────────────────────────────
|
|
243
|
+
description, output_language, agent_domain, icp_strategy: icp, pain_points, lookalike_clients, outreach_templates, tone_instructions, messaging_guidelines, outreach_signal_routing: signal_routing, sender_override: sdr_override, outreach_fields, extra_prompt_phases, }) => {
|
|
137
244
|
try {
|
|
138
245
|
const body = {};
|
|
246
|
+
// Legacy fields
|
|
139
247
|
if (connection_request_enabled !== undefined)
|
|
140
248
|
body.connectionRequestEnabled = connection_request_enabled;
|
|
141
249
|
if (pos !== undefined)
|
|
@@ -150,6 +258,33 @@ export function registerOutreachSettingsTools(server) {
|
|
|
150
258
|
body.websiteUrl = website_url;
|
|
151
259
|
if (case_studies !== undefined)
|
|
152
260
|
body.caseStudies = case_studies;
|
|
261
|
+
// PRD-737 fields
|
|
262
|
+
if (description !== undefined)
|
|
263
|
+
body.description = description;
|
|
264
|
+
if (output_language !== undefined)
|
|
265
|
+
body.outputLanguage = output_language;
|
|
266
|
+
if (agent_domain !== undefined)
|
|
267
|
+
body.agentDomain = agent_domain;
|
|
268
|
+
if (icp !== undefined)
|
|
269
|
+
body.icpStrategy = icp;
|
|
270
|
+
if (pain_points !== undefined)
|
|
271
|
+
body.painPoints = pain_points;
|
|
272
|
+
if (lookalike_clients !== undefined)
|
|
273
|
+
body.lookalikeClients = lookalike_clients;
|
|
274
|
+
if (outreach_templates !== undefined)
|
|
275
|
+
body.outreachTemplates = outreach_templates;
|
|
276
|
+
if (tone_instructions !== undefined)
|
|
277
|
+
body.toneInstructions = tone_instructions;
|
|
278
|
+
if (messaging_guidelines !== undefined)
|
|
279
|
+
body.messagingGuidelines = messaging_guidelines;
|
|
280
|
+
if (signal_routing !== undefined)
|
|
281
|
+
body.outreachSignalRouting = signal_routing;
|
|
282
|
+
if (sdr_override !== undefined)
|
|
283
|
+
body.senderOverride = sdr_override;
|
|
284
|
+
if (outreach_fields !== undefined)
|
|
285
|
+
body.outreachFields = outreach_fields;
|
|
286
|
+
if (extra_prompt_phases !== undefined)
|
|
287
|
+
body.extraPromptPhases = extra_prompt_phases;
|
|
153
288
|
const data = await putPartial(lead_group_id, body);
|
|
154
289
|
return toolResult(data);
|
|
155
290
|
}
|
|
@@ -407,4 +542,463 @@ export function registerOutreachSettingsTools(server) {
|
|
|
407
542
|
return handleToolError(error);
|
|
408
543
|
}
|
|
409
544
|
});
|
|
545
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
546
|
+
// Granular tools for persona-migrated fields (PRD-737)
|
|
547
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
548
|
+
// ── update_outreach_agent_identity ─────────────────────────────────────
|
|
549
|
+
server.tool("update_outreach_agent_identity", "Patch the agent identity fields (description, outputLanguage, " +
|
|
550
|
+
"agentDomain). Reads current settings, merges your patch, writes back. " +
|
|
551
|
+
"Pass only the fields you want to change.", {
|
|
552
|
+
lead_group_id: z.string().describe("Lead group ID."),
|
|
553
|
+
description: z
|
|
554
|
+
.string()
|
|
555
|
+
.optional()
|
|
556
|
+
.describe("Description / context for this lead group's outreach."),
|
|
557
|
+
output_language: z
|
|
558
|
+
.string()
|
|
559
|
+
.optional()
|
|
560
|
+
.describe("Output language code (fr, en, de, ...)."),
|
|
561
|
+
agent_domain: z
|
|
562
|
+
.string()
|
|
563
|
+
.optional()
|
|
564
|
+
.describe("Agent identity string (e.g. 'for medtech companies')."),
|
|
565
|
+
}, async ({ lead_group_id, description, output_language, agent_domain }) => {
|
|
566
|
+
try {
|
|
567
|
+
if (description === undefined &&
|
|
568
|
+
output_language === undefined &&
|
|
569
|
+
agent_domain === undefined) {
|
|
570
|
+
return toolError("Provide at least one of: description, output_language, agent_domain.");
|
|
571
|
+
}
|
|
572
|
+
const current = await fetchCurrentSettings(lead_group_id);
|
|
573
|
+
const body = {};
|
|
574
|
+
if (description !== undefined)
|
|
575
|
+
body.description = description;
|
|
576
|
+
if (output_language !== undefined)
|
|
577
|
+
body.outputLanguage = output_language;
|
|
578
|
+
if (agent_domain !== undefined)
|
|
579
|
+
body.agentDomain = agent_domain;
|
|
580
|
+
const data = await putPartial(lead_group_id, body);
|
|
581
|
+
return toolResult(data);
|
|
582
|
+
}
|
|
583
|
+
catch (error) {
|
|
584
|
+
return handleToolError(error);
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
// ── update_outreach_icp_strategy ──────────────────────────────────────
|
|
588
|
+
server.tool("update_outreach_icp_strategy", "Patch the ICP strategy mental map for the outreach agent. Reads " +
|
|
589
|
+
"current icpStrategy, shallow-merges the keys you pass, writes back. " +
|
|
590
|
+
"Arrays (mainPains/keyObjections/decisionDrivers) are replaced " +
|
|
591
|
+
"wholesale when passed.", {
|
|
592
|
+
lead_group_id: z.string().describe("Lead group ID."),
|
|
593
|
+
deal_type: z
|
|
594
|
+
.string()
|
|
595
|
+
.optional()
|
|
596
|
+
.describe("Type of deal (e.g. 'midmarket', 'enterprise', 'self-serve')."),
|
|
597
|
+
dream_outcome: z
|
|
598
|
+
.string()
|
|
599
|
+
.optional()
|
|
600
|
+
.describe("The aspirational result the lead wants."),
|
|
601
|
+
main_pains: z
|
|
602
|
+
.array(z.string())
|
|
603
|
+
.optional()
|
|
604
|
+
.describe("Replaces mainPains[] wholesale."),
|
|
605
|
+
key_objections: z
|
|
606
|
+
.array(z.string())
|
|
607
|
+
.optional()
|
|
608
|
+
.describe("Replaces keyObjections[] wholesale."),
|
|
609
|
+
decision_drivers: z
|
|
610
|
+
.array(z.string())
|
|
611
|
+
.optional()
|
|
612
|
+
.describe("Replaces decisionDrivers[] wholesale."),
|
|
613
|
+
}, async ({ lead_group_id, deal_type, dream_outcome, main_pains, key_objections, decision_drivers, }) => {
|
|
614
|
+
try {
|
|
615
|
+
if (deal_type === undefined &&
|
|
616
|
+
dream_outcome === undefined &&
|
|
617
|
+
main_pains === undefined &&
|
|
618
|
+
key_objections === undefined &&
|
|
619
|
+
decision_drivers === undefined) {
|
|
620
|
+
return toolError("Provide at least one field to update.");
|
|
621
|
+
}
|
|
622
|
+
const current = await fetchCurrentSettings(lead_group_id);
|
|
623
|
+
const merged = {
|
|
624
|
+
...(current.icpStrategy ?? {}),
|
|
625
|
+
...(deal_type !== undefined && { dealType: deal_type }),
|
|
626
|
+
...(dream_outcome !== undefined && { dreamOutcome: dream_outcome }),
|
|
627
|
+
...(main_pains !== undefined && { mainPains: main_pains }),
|
|
628
|
+
...(key_objections !== undefined && { keyObjections: key_objections }),
|
|
629
|
+
...(decision_drivers !== undefined && { decisionDrivers: decision_drivers }),
|
|
630
|
+
};
|
|
631
|
+
const data = await putPartial(lead_group_id, { icpStrategy: merged });
|
|
632
|
+
return toolResult(data);
|
|
633
|
+
}
|
|
634
|
+
catch (error) {
|
|
635
|
+
return handleToolError(error);
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
// ── update_outreach_pain_point ────────────────────────────────────────
|
|
639
|
+
server.tool("update_outreach_pain_point", "Add, replace, or remove a single pain point without re-sending the " +
|
|
640
|
+
"whole list. Pain points have no stable id — entries are addressed by " +
|
|
641
|
+
"zero-based index. Beware: indices shift after a remove.", {
|
|
642
|
+
lead_group_id: z.string().describe("Lead group ID."),
|
|
643
|
+
action: z
|
|
644
|
+
.enum(["add", "replace", "remove"])
|
|
645
|
+
.describe("add = append; replace = overwrite at index; remove = delete at index."),
|
|
646
|
+
index: z
|
|
647
|
+
.number()
|
|
648
|
+
.int()
|
|
649
|
+
.nonnegative()
|
|
650
|
+
.optional()
|
|
651
|
+
.describe("Zero-based index. Required for 'replace' and 'remove'."),
|
|
652
|
+
pain: z
|
|
653
|
+
.object({
|
|
654
|
+
title: z.string().optional(),
|
|
655
|
+
description: z.string().optional(),
|
|
656
|
+
})
|
|
657
|
+
.optional()
|
|
658
|
+
.describe("Pain point payload {title?, description?}. Required for 'add' and 'replace'."),
|
|
659
|
+
}, async ({ lead_group_id, action, index, pain }) => {
|
|
660
|
+
try {
|
|
661
|
+
if ((action === "add" || action === "replace") && !pain) {
|
|
662
|
+
return toolError(`pain is required for action '${action}'.`);
|
|
663
|
+
}
|
|
664
|
+
if ((action === "replace" || action === "remove") && index === undefined) {
|
|
665
|
+
return toolError(`index is required for action '${action}'.`);
|
|
666
|
+
}
|
|
667
|
+
const current = await fetchCurrentSettings(lead_group_id);
|
|
668
|
+
const list = Array.isArray(current.painPoints)
|
|
669
|
+
? [...current.painPoints]
|
|
670
|
+
: [];
|
|
671
|
+
if (action === "add") {
|
|
672
|
+
list.push(pain);
|
|
673
|
+
}
|
|
674
|
+
else if (action === "replace") {
|
|
675
|
+
if (index < 0 || index >= list.length) {
|
|
676
|
+
return toolError(`Index ${index} out of bounds (list has ${list.length} entries).`);
|
|
677
|
+
}
|
|
678
|
+
list[index] = pain;
|
|
679
|
+
}
|
|
680
|
+
else {
|
|
681
|
+
if (index < 0 || index >= list.length) {
|
|
682
|
+
return toolError(`Index ${index} out of bounds (list has ${list.length} entries).`);
|
|
683
|
+
}
|
|
684
|
+
list.splice(index, 1);
|
|
685
|
+
}
|
|
686
|
+
const data = await putPartial(lead_group_id, { painPoints: list });
|
|
687
|
+
return toolResult(data);
|
|
688
|
+
}
|
|
689
|
+
catch (error) {
|
|
690
|
+
return handleToolError(error);
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
// ── update_outreach_lookalike_client ──────────────────────────────────
|
|
694
|
+
server.tool("update_outreach_lookalike_client", "Add, replace, or remove a single lookalike client without re-sending " +
|
|
695
|
+
"the whole list. Lookups are by client name (case-sensitive exact match).", {
|
|
696
|
+
lead_group_id: z.string().describe("Lead group ID."),
|
|
697
|
+
action: z
|
|
698
|
+
.enum(["add", "replace", "remove"])
|
|
699
|
+
.describe("add = append (fails if name exists); replace = overwrite by name; remove = delete by name."),
|
|
700
|
+
name: z
|
|
701
|
+
.string()
|
|
702
|
+
.optional()
|
|
703
|
+
.describe("Lookup key for replace/remove. Required for those actions."),
|
|
704
|
+
client: z
|
|
705
|
+
.object({
|
|
706
|
+
name: z.string(),
|
|
707
|
+
sector: z.string().optional(),
|
|
708
|
+
size: z.string().optional(),
|
|
709
|
+
segment: z.string().optional(),
|
|
710
|
+
comparison_criteria: z.string().optional(),
|
|
711
|
+
})
|
|
712
|
+
.passthrough()
|
|
713
|
+
.optional()
|
|
714
|
+
.describe("Client payload {name, sector?, size?, segment?, comparison_criteria?}. Required for 'add' and 'replace'."),
|
|
715
|
+
}, async ({ lead_group_id, action, name, client }) => {
|
|
716
|
+
try {
|
|
717
|
+
if ((action === "add" || action === "replace") && !client) {
|
|
718
|
+
return toolError(`client is required for action '${action}'.`);
|
|
719
|
+
}
|
|
720
|
+
if (action !== "add" && !name) {
|
|
721
|
+
return toolError(`name is required for action '${action}'.`);
|
|
722
|
+
}
|
|
723
|
+
const current = await fetchCurrentSettings(lead_group_id);
|
|
724
|
+
const list = Array.isArray(current.lookalikeClients)
|
|
725
|
+
? [...current.lookalikeClients]
|
|
726
|
+
: [];
|
|
727
|
+
if (action === "add") {
|
|
728
|
+
const exists = list.some((c) => c.name === client.name);
|
|
729
|
+
if (exists) {
|
|
730
|
+
return toolError(`Lookalike client '${client.name}' already exists. Use 'replace' to update.`);
|
|
731
|
+
}
|
|
732
|
+
list.push(client);
|
|
733
|
+
}
|
|
734
|
+
else if (action === "replace") {
|
|
735
|
+
const idx = list.findIndex((c) => c.name === name);
|
|
736
|
+
if (idx === -1) {
|
|
737
|
+
return toolError(`Lookalike client '${name}' not found.`);
|
|
738
|
+
}
|
|
739
|
+
list[idx] = client;
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
const idx = list.findIndex((c) => c.name === name);
|
|
743
|
+
if (idx === -1) {
|
|
744
|
+
return toolError(`Lookalike client '${name}' not found.`);
|
|
745
|
+
}
|
|
746
|
+
list.splice(idx, 1);
|
|
747
|
+
}
|
|
748
|
+
const data = await putPartial(lead_group_id, { lookalikeClients: list });
|
|
749
|
+
return toolResult(data);
|
|
750
|
+
}
|
|
751
|
+
catch (error) {
|
|
752
|
+
return handleToolError(error);
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
// ── update_outreach_outreach_template ─────────────────────────────────
|
|
756
|
+
server.tool("update_outreach_outreach_template", "Add, replace, or remove a single outreach template without re-sending " +
|
|
757
|
+
"the whole list. Lookups are by template id (preferred) or by " +
|
|
758
|
+
"composite key (channel + signal + fieldName).", {
|
|
759
|
+
lead_group_id: z.string().describe("Lead group ID."),
|
|
760
|
+
action: z
|
|
761
|
+
.enum(["add", "replace", "remove"])
|
|
762
|
+
.describe("add = append; replace = overwrite by lookup; remove = delete by lookup."),
|
|
763
|
+
template_id: z
|
|
764
|
+
.string()
|
|
765
|
+
.optional()
|
|
766
|
+
.describe("Template id for replace/remove. Preferred lookup key."),
|
|
767
|
+
lookup_channel: z
|
|
768
|
+
.string()
|
|
769
|
+
.optional()
|
|
770
|
+
.describe("Fallback lookup: channel (combined with lookup_signal + lookup_field_name)."),
|
|
771
|
+
lookup_signal: z
|
|
772
|
+
.string()
|
|
773
|
+
.optional()
|
|
774
|
+
.describe("Fallback lookup: signal."),
|
|
775
|
+
lookup_field_name: z
|
|
776
|
+
.string()
|
|
777
|
+
.optional()
|
|
778
|
+
.describe("Fallback lookup: field_name."),
|
|
779
|
+
template: outreachTemplate
|
|
780
|
+
.optional()
|
|
781
|
+
.describe("Template payload. Required for 'add' and 'replace'. " +
|
|
782
|
+
"{channel, signal, fieldName, purpose, example, subject, body}."),
|
|
783
|
+
}, async ({ lead_group_id, action, template_id, lookup_channel, lookup_signal, lookup_field_name, template: tmpl, }) => {
|
|
784
|
+
try {
|
|
785
|
+
if ((action === "add" || action === "replace") && !tmpl) {
|
|
786
|
+
return toolError(`template is required for action '${action}'.`);
|
|
787
|
+
}
|
|
788
|
+
const current = await fetchCurrentSettings(lead_group_id);
|
|
789
|
+
const list = Array.isArray(current.outreachTemplates)
|
|
790
|
+
? [...current.outreachTemplates]
|
|
791
|
+
: [];
|
|
792
|
+
if (action === "add") {
|
|
793
|
+
list.push(tmpl);
|
|
794
|
+
}
|
|
795
|
+
else {
|
|
796
|
+
// Resolve lookup key
|
|
797
|
+
let idx = -1;
|
|
798
|
+
if (template_id) {
|
|
799
|
+
idx = list.findIndex((t) => t.id === template_id);
|
|
800
|
+
}
|
|
801
|
+
else if (lookup_channel && lookup_signal && lookup_field_name) {
|
|
802
|
+
idx = list.findIndex((t) => t.channel === lookup_channel &&
|
|
803
|
+
t.signal === lookup_signal &&
|
|
804
|
+
t.fieldName === lookup_field_name);
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
return toolError("Provide template_id or (lookup_channel + lookup_signal + lookup_field_name) for replace/remove.");
|
|
808
|
+
}
|
|
809
|
+
if (idx === -1) {
|
|
810
|
+
return toolError("Template not found with the given lookup key.");
|
|
811
|
+
}
|
|
812
|
+
if (action === "replace") {
|
|
813
|
+
list[idx] = tmpl;
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
list.splice(idx, 1);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
const data = await putPartial(lead_group_id, { outreachTemplates: list });
|
|
820
|
+
return toolResult(data);
|
|
821
|
+
}
|
|
822
|
+
catch (error) {
|
|
823
|
+
return handleToolError(error);
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
// ── update_outreach_tone_instructions ──────────────────────────────────
|
|
827
|
+
server.tool("update_outreach_tone_instructions", "Replace the tone instructions (free-form markdown for voice / tone " +
|
|
828
|
+
"guidance). Pass null to clear.", {
|
|
829
|
+
lead_group_id: z.string().describe("Lead group ID."),
|
|
830
|
+
tone_instructions: z
|
|
831
|
+
.union([z.string(), z.null()])
|
|
832
|
+
.describe("Markdown tone guidance. Pass null to clear."),
|
|
833
|
+
}, async ({ lead_group_id, tone_instructions }) => {
|
|
834
|
+
try {
|
|
835
|
+
const data = await putPartial(lead_group_id, { toneInstructions: tone_instructions });
|
|
836
|
+
return toolResult(data);
|
|
837
|
+
}
|
|
838
|
+
catch (error) {
|
|
839
|
+
return handleToolError(error);
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
// ── update_outreach_messaging_guidelines ──────────────────────────────
|
|
843
|
+
server.tool("update_outreach_messaging_guidelines", "Patch the messaging guidelines. Reads current messagingGuidelines, " +
|
|
844
|
+
"shallow-merges the keys you pass, writes back. Common keys: tone, " +
|
|
845
|
+
"language, can_mention, must_not_mention, value_propositions.", {
|
|
846
|
+
lead_group_id: z.string().describe("Lead group ID."),
|
|
847
|
+
patch: z
|
|
848
|
+
.any()
|
|
849
|
+
.describe("Object whose keys are merged into existing messagingGuidelines (shallow). " +
|
|
850
|
+
"E.g. {\"tone\": \"professional\", \"can_mention\": [\"AI\", \"ROI\"]}."),
|
|
851
|
+
remove_keys: z
|
|
852
|
+
.array(z.string())
|
|
853
|
+
.optional()
|
|
854
|
+
.describe("Keys to delete from messagingGuidelines."),
|
|
855
|
+
}, async ({ lead_group_id, patch, remove_keys }) => {
|
|
856
|
+
try {
|
|
857
|
+
const current = await fetchCurrentSettings(lead_group_id);
|
|
858
|
+
const existing = (current.messagingGuidelines ?? {});
|
|
859
|
+
const merged = { ...existing, ...patch };
|
|
860
|
+
if (remove_keys) {
|
|
861
|
+
for (const k of remove_keys)
|
|
862
|
+
delete merged[k];
|
|
863
|
+
}
|
|
864
|
+
const data = await putPartial(lead_group_id, { messagingGuidelines: merged });
|
|
865
|
+
return toolResult(data);
|
|
866
|
+
}
|
|
867
|
+
catch (error) {
|
|
868
|
+
return handleToolError(error);
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
// ── update_outreach_signal_routing ────────────────────────────────────
|
|
872
|
+
server.tool("update_outreach_signal_routing", "Patch the outreach signal routing config. signalSourceField and " +
|
|
873
|
+
"defaultSignal are scalar — passing one replaces it. signals[] is " +
|
|
874
|
+
"replaced wholesale when passed; use add_signals / remove_signals to " +
|
|
875
|
+
"splice instead. WARNING: defaultSignal must be present in signals[] " +
|
|
876
|
+
"(API rejects otherwise).", {
|
|
877
|
+
lead_group_id: z.string().describe("Lead group ID."),
|
|
878
|
+
signal_source_field: z
|
|
879
|
+
.string()
|
|
880
|
+
.optional()
|
|
881
|
+
.describe("Card field to read (e.g. card_signals)."),
|
|
882
|
+
default_signal: z
|
|
883
|
+
.string()
|
|
884
|
+
.optional()
|
|
885
|
+
.describe("Default signal — must exist in signals[]."),
|
|
886
|
+
signals: z
|
|
887
|
+
.array(z.string())
|
|
888
|
+
.optional()
|
|
889
|
+
.describe("Full replacement of the signals list. Mutually exclusive with add_signals / remove_signals."),
|
|
890
|
+
add_signals: z
|
|
891
|
+
.array(z.string())
|
|
892
|
+
.optional()
|
|
893
|
+
.describe("Signals to add (deduped). Mutually exclusive with signals."),
|
|
894
|
+
remove_signals: z
|
|
895
|
+
.array(z.string())
|
|
896
|
+
.optional()
|
|
897
|
+
.describe("Signals to remove. Mutually exclusive with signals."),
|
|
898
|
+
}, async ({ lead_group_id, signal_source_field, default_signal, signals, add_signals, remove_signals, }) => {
|
|
899
|
+
try {
|
|
900
|
+
const current = await fetchCurrentSettings(lead_group_id);
|
|
901
|
+
const existing = (current.outreachSignalRouting ?? {});
|
|
902
|
+
const next = { ...existing };
|
|
903
|
+
if (signal_source_field !== undefined)
|
|
904
|
+
next.signalSourceField = signal_source_field;
|
|
905
|
+
if (default_signal !== undefined)
|
|
906
|
+
next.defaultSignal = default_signal;
|
|
907
|
+
if (signals !== undefined) {
|
|
908
|
+
next.signals = signals;
|
|
909
|
+
}
|
|
910
|
+
else {
|
|
911
|
+
const currentSignals = existing.signals ?? [];
|
|
912
|
+
let updatedSignals = [...currentSignals];
|
|
913
|
+
if (add_signals) {
|
|
914
|
+
for (const s of add_signals) {
|
|
915
|
+
if (!updatedSignals.includes(s))
|
|
916
|
+
updatedSignals.push(s);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
if (remove_signals) {
|
|
920
|
+
updatedSignals = updatedSignals.filter((s) => !remove_signals.includes(s));
|
|
921
|
+
}
|
|
922
|
+
if (add_signals || remove_signals)
|
|
923
|
+
next.signals = updatedSignals;
|
|
924
|
+
}
|
|
925
|
+
const data = await putPartial(lead_group_id, { outreachSignalRouting: next });
|
|
926
|
+
return toolResult(data);
|
|
927
|
+
}
|
|
928
|
+
catch (error) {
|
|
929
|
+
return handleToolError(error);
|
|
930
|
+
}
|
|
931
|
+
});
|
|
932
|
+
// ── update_outreach_sender_override ───────────────────────────────────
|
|
933
|
+
server.tool("update_outreach_sender_override", "Set or clear the SDR identity override for this lead group. " +
|
|
934
|
+
"Pass {name, title?} to set. Pass null to clear.", {
|
|
935
|
+
lead_group_id: z.string().describe("Lead group ID."),
|
|
936
|
+
sender_override: z
|
|
937
|
+
.union([
|
|
938
|
+
z.object({ name: z.string().min(1), title: z.string().optional() }),
|
|
939
|
+
z.null(),
|
|
940
|
+
])
|
|
941
|
+
.describe("SDR identity: {name, title?}. Pass null to clear."),
|
|
942
|
+
}, async ({ lead_group_id, sender_override }) => {
|
|
943
|
+
try {
|
|
944
|
+
const data = await putPartial(lead_group_id, { senderOverride: sender_override });
|
|
945
|
+
return toolResult(data);
|
|
946
|
+
}
|
|
947
|
+
catch (error) {
|
|
948
|
+
return handleToolError(error);
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
// ── update_outreach_extra_prompt_phase ────────────────────────────────
|
|
952
|
+
server.tool("update_outreach_extra_prompt_phase", "Add, replace, or remove a single extra prompt phase snippet. " +
|
|
953
|
+
"Entries have no stable id — addressed by zero-based index. " +
|
|
954
|
+
"Beware: indices shift after a remove.", {
|
|
955
|
+
lead_group_id: z.string().describe("Lead group ID."),
|
|
956
|
+
action: z
|
|
957
|
+
.enum(["add", "replace", "remove"])
|
|
958
|
+
.describe("add = append; replace = overwrite at index; remove = delete at index."),
|
|
959
|
+
index: z
|
|
960
|
+
.number()
|
|
961
|
+
.int()
|
|
962
|
+
.nonnegative()
|
|
963
|
+
.optional()
|
|
964
|
+
.describe("Zero-based index. Required for 'replace' and 'remove'."),
|
|
965
|
+
phase: extraPromptPhase
|
|
966
|
+
.optional()
|
|
967
|
+
.describe("Phase payload {scope, insertionPoint, content}. " +
|
|
968
|
+
"scope: qualify|outreach. insertionPoint: prefix|phase_1_5|phase_2_extra|suffix. " +
|
|
969
|
+
"Required for 'add' and 'replace'."),
|
|
970
|
+
}, async ({ lead_group_id, action, index, phase }) => {
|
|
971
|
+
try {
|
|
972
|
+
if ((action === "add" || action === "replace") && !phase) {
|
|
973
|
+
return toolError(`phase is required for action '${action}'.`);
|
|
974
|
+
}
|
|
975
|
+
if ((action === "replace" || action === "remove") && index === undefined) {
|
|
976
|
+
return toolError(`index is required for action '${action}'.`);
|
|
977
|
+
}
|
|
978
|
+
const current = await fetchCurrentSettings(lead_group_id);
|
|
979
|
+
const list = Array.isArray(current.extraPromptPhases)
|
|
980
|
+
? [...current.extraPromptPhases]
|
|
981
|
+
: [];
|
|
982
|
+
if (action === "add") {
|
|
983
|
+
list.push(phase);
|
|
984
|
+
}
|
|
985
|
+
else if (action === "replace") {
|
|
986
|
+
if (index < 0 || index >= list.length) {
|
|
987
|
+
return toolError(`Index ${index} out of bounds (list has ${list.length} entries).`);
|
|
988
|
+
}
|
|
989
|
+
list[index] = phase;
|
|
990
|
+
}
|
|
991
|
+
else {
|
|
992
|
+
if (index < 0 || index >= list.length) {
|
|
993
|
+
return toolError(`Index ${index} out of bounds (list has ${list.length} entries).`);
|
|
994
|
+
}
|
|
995
|
+
list.splice(index, 1);
|
|
996
|
+
}
|
|
997
|
+
const data = await putPartial(lead_group_id, { extraPromptPhases: list });
|
|
998
|
+
return toolResult(data);
|
|
999
|
+
}
|
|
1000
|
+
catch (error) {
|
|
1001
|
+
return handleToolError(error);
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
410
1004
|
}
|
package/dist/tools/personas.js
CHANGED
|
@@ -40,31 +40,6 @@ const signalTierRule = z.object({
|
|
|
40
40
|
signal_id: z.string().optional(),
|
|
41
41
|
tier: z.enum(["hot", "warm", "cold"]).optional(),
|
|
42
42
|
});
|
|
43
|
-
const outreachTemplate = z
|
|
44
|
-
.object({
|
|
45
|
-
id: z.string().optional(),
|
|
46
|
-
channel: z.string().optional(),
|
|
47
|
-
signal: z
|
|
48
|
-
.string()
|
|
49
|
-
.optional()
|
|
50
|
-
.describe("Must exist in outreach_signal_routing.signals."),
|
|
51
|
-
field_name: z.string().optional(),
|
|
52
|
-
signal_context: z.string().optional(),
|
|
53
|
-
purpose: z.string().optional(),
|
|
54
|
-
example: z.string().optional(),
|
|
55
|
-
max_length: z.number().optional(),
|
|
56
|
-
label: z.string().optional(),
|
|
57
|
-
subject: z.string().optional(),
|
|
58
|
-
body: z.string().optional(),
|
|
59
|
-
})
|
|
60
|
-
.passthrough();
|
|
61
|
-
const outreachField = z
|
|
62
|
-
.object({
|
|
63
|
-
key: z.string().optional(),
|
|
64
|
-
label: z.string().optional(),
|
|
65
|
-
source: z.string().optional(),
|
|
66
|
-
})
|
|
67
|
-
.passthrough();
|
|
68
43
|
const extraPromptPhase = z.object({
|
|
69
44
|
scope: z.enum(["qualify", "outreach"]).optional(),
|
|
70
45
|
insertion_point: z
|
|
@@ -72,10 +47,6 @@ const extraPromptPhase = z.object({
|
|
|
72
47
|
.describe("Where in the prompt to inject this snippet."),
|
|
73
48
|
content: z.string().describe("Snippet content."),
|
|
74
49
|
});
|
|
75
|
-
const senderOverride = z.object({
|
|
76
|
-
name: z.string().describe("Sender display name (required)."),
|
|
77
|
-
title: z.string().optional(),
|
|
78
|
-
});
|
|
79
50
|
// ICP strategy — mental map consumed verbatim by the outreach agent preflight.
|
|
80
51
|
const icpStrategyShape = {
|
|
81
52
|
dealType: z.string().optional(),
|
|
@@ -107,32 +78,6 @@ function mapCardSection(s) {
|
|
|
107
78
|
out.instructions = s.instructions;
|
|
108
79
|
return out;
|
|
109
80
|
}
|
|
110
|
-
function mapOutreachTemplate(t) {
|
|
111
|
-
const out = {};
|
|
112
|
-
if (t.id !== undefined)
|
|
113
|
-
out.id = t.id;
|
|
114
|
-
if (t.channel !== undefined)
|
|
115
|
-
out.channel = t.channel;
|
|
116
|
-
if (t.signal !== undefined)
|
|
117
|
-
out.signal = t.signal;
|
|
118
|
-
if (t.field_name !== undefined)
|
|
119
|
-
out.fieldName = t.field_name;
|
|
120
|
-
if (t.signal_context !== undefined)
|
|
121
|
-
out.signalContext = t.signal_context;
|
|
122
|
-
if (t.purpose !== undefined)
|
|
123
|
-
out.purpose = t.purpose;
|
|
124
|
-
if (t.example !== undefined)
|
|
125
|
-
out.example = t.example;
|
|
126
|
-
if (t.max_length !== undefined)
|
|
127
|
-
out.maxLength = t.max_length;
|
|
128
|
-
if (t.label !== undefined)
|
|
129
|
-
out.label = t.label;
|
|
130
|
-
if (t.subject !== undefined)
|
|
131
|
-
out.subject = t.subject;
|
|
132
|
-
if (t.body !== undefined)
|
|
133
|
-
out.body = t.body;
|
|
134
|
-
return out;
|
|
135
|
-
}
|
|
136
81
|
function mapSignalTierRule(r) {
|
|
137
82
|
const out = {};
|
|
138
83
|
if (r.signal_id !== undefined)
|
|
@@ -314,10 +259,6 @@ export function registerPersonaTools(server) {
|
|
|
314
259
|
.record(z.unknown())
|
|
315
260
|
.optional()
|
|
316
261
|
.describe("Exclusion rules. Common keys: reasons (array of strings)."),
|
|
317
|
-
messaging_guidelines: z
|
|
318
|
-
.record(z.unknown())
|
|
319
|
-
.optional()
|
|
320
|
-
.describe("Message approach. Common keys: tone, language, can_mention, must_not_mention, value_propositions."),
|
|
321
262
|
active_tools: z
|
|
322
263
|
.array(z.string())
|
|
323
264
|
.optional()
|
|
@@ -351,11 +292,6 @@ export function registerPersonaTools(server) {
|
|
|
351
292
|
.boolean()
|
|
352
293
|
.optional()
|
|
353
294
|
.describe("If true, reject non-perfectly-aligned leads."),
|
|
354
|
-
agent_domain: z
|
|
355
|
-
.string()
|
|
356
|
-
.nullable()
|
|
357
|
-
.optional()
|
|
358
|
-
.describe("Agent identity string (e.g. 'for medtech companies')."),
|
|
359
295
|
workflow_mode: z
|
|
360
296
|
.enum(["company_first", "person_first"])
|
|
361
297
|
.nullable()
|
|
@@ -398,37 +334,6 @@ export function registerPersonaTools(server) {
|
|
|
398
334
|
})
|
|
399
335
|
.optional()
|
|
400
336
|
.describe("Rules mapping signal → tier (hot/warm/cold)."),
|
|
401
|
-
// outreach
|
|
402
|
-
outreach_signal_routing: z
|
|
403
|
-
.object({
|
|
404
|
-
signal_source_field: z
|
|
405
|
-
.string()
|
|
406
|
-
.optional()
|
|
407
|
-
.describe("Card field to read (e.g. card_signals)."),
|
|
408
|
-
default_signal: z
|
|
409
|
-
.string()
|
|
410
|
-
.optional()
|
|
411
|
-
.describe("Must exist in 'signals' array."),
|
|
412
|
-
signals: z
|
|
413
|
-
.array(z.string())
|
|
414
|
-
.optional()
|
|
415
|
-
.describe("Exhaustive list of recognized signals."),
|
|
416
|
-
})
|
|
417
|
-
.optional()
|
|
418
|
-
.describe("Extract signal source from card lead + default signal. All three sub-fields are optional."),
|
|
419
|
-
outreach_templates: z
|
|
420
|
-
.array(outreachTemplate)
|
|
421
|
-
.optional()
|
|
422
|
-
.describe("Outreach templates by channel. Each template: {id, channel, signal, field_name, signal_context, purpose, example, max_length, label, subject, body}."),
|
|
423
|
-
outreach_fields: z
|
|
424
|
-
.array(outreachField)
|
|
425
|
-
.optional()
|
|
426
|
-
.describe("Variables referenceable in templates. Each: {key, label, source}."),
|
|
427
|
-
tone_instructions: z
|
|
428
|
-
.string()
|
|
429
|
-
.nullable()
|
|
430
|
-
.optional()
|
|
431
|
-
.describe("Free-form markdown for voice / tone guidance."),
|
|
432
337
|
context: z
|
|
433
338
|
.string()
|
|
434
339
|
.nullable()
|
|
@@ -438,38 +343,8 @@ export function registerPersonaTools(server) {
|
|
|
438
343
|
.array(extraPromptPhase)
|
|
439
344
|
.optional()
|
|
440
345
|
.describe("Snippets inserted at named points. scope: 'qualify' | 'outreach'; insertion_point: 'prefix' | 'phase_1_5' | 'phase_2_extra' | 'suffix'."),
|
|
441
|
-
// sender
|
|
442
|
-
sender_override: senderOverride
|
|
443
|
-
.nullable()
|
|
444
|
-
.optional()
|
|
445
|
-
.describe("Override SDR identity at persona level ({name, title})."),
|
|
446
346
|
}, async (params) => {
|
|
447
347
|
try {
|
|
448
|
-
// SILO guard: the upsert escape hatch must NOT be used for outreach
|
|
449
|
-
// data. Outreach is its own concern with its own granular tools
|
|
450
|
-
// (update_persona_outreach_template, update_persona_outreach_signal_routing).
|
|
451
|
-
// Reject early with a clear pointer so the caller knows what to use.
|
|
452
|
-
if (params.outreach_signal_routing !== undefined ||
|
|
453
|
-
params.outreach_templates !== undefined ||
|
|
454
|
-
params.outreach_fields !== undefined) {
|
|
455
|
-
return toolError("SILO: upsert_persona does NOT accept outreach fields. " +
|
|
456
|
-
"Use the dedicated outreach tools instead: " +
|
|
457
|
-
"update_persona_outreach_template (add/replace/remove a template), " +
|
|
458
|
-
"update_persona_outreach_signal_routing (patch the signal routing block). " +
|
|
459
|
-
"For a read of the trio, use get_outreach_templates({ lead_group_id }).", {
|
|
460
|
-
rejectedParams: [
|
|
461
|
-
...(params.outreach_signal_routing !== undefined
|
|
462
|
-
? ["outreach_signal_routing"]
|
|
463
|
-
: []),
|
|
464
|
-
...(params.outreach_templates !== undefined
|
|
465
|
-
? ["outreach_templates"]
|
|
466
|
-
: []),
|
|
467
|
-
...(params.outreach_fields !== undefined
|
|
468
|
-
? ["outreach_fields"]
|
|
469
|
-
: []),
|
|
470
|
-
],
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
348
|
const body = { name: params.name };
|
|
474
349
|
if (params.id !== undefined)
|
|
475
350
|
body.id = params.id;
|
|
@@ -481,8 +356,6 @@ export function registerPersonaTools(server) {
|
|
|
481
356
|
body.targetProfile = params.target_profile;
|
|
482
357
|
if (params.disqualification_criteria !== undefined)
|
|
483
358
|
body.disqualificationCriteria = params.disqualification_criteria;
|
|
484
|
-
if (params.messaging_guidelines !== undefined)
|
|
485
|
-
body.messagingGuidelines = params.messaging_guidelines;
|
|
486
359
|
if (params.active_tools !== undefined)
|
|
487
360
|
body.activeTools = params.active_tools;
|
|
488
361
|
if (params.hot_criteria !== undefined)
|
|
@@ -501,8 +374,6 @@ export function registerPersonaTools(server) {
|
|
|
501
374
|
body.icpStrategy = params.icp_strategy;
|
|
502
375
|
if (params.strict_match !== undefined)
|
|
503
376
|
body.strictMatch = params.strict_match;
|
|
504
|
-
if (params.agent_domain !== undefined)
|
|
505
|
-
body.agentDomain = params.agent_domain;
|
|
506
377
|
if (params.workflow_mode !== undefined)
|
|
507
378
|
body.workflowMode = params.workflow_mode;
|
|
508
379
|
if (params.output_language !== undefined)
|
|
@@ -528,21 +399,11 @@ export function registerPersonaTools(server) {
|
|
|
528
399
|
rules: (params.signal_tier_rules.rules ?? []).map(mapSignalTierRule),
|
|
529
400
|
};
|
|
530
401
|
}
|
|
531
|
-
// SILO: outreach_* params are accepted on the schema (so users discover
|
|
532
|
-
// they exist here) but ALWAYS rejected by the guard at the top of this
|
|
533
|
-
// handler. Outreach data flows through update_persona_outreach_template
|
|
534
|
-
// and update_persona_outreach_signal_routing only. The unreachable
|
|
535
|
-
// mapping code below was removed — TypeScript narrows the params to
|
|
536
|
-
// `never` after the guard, so the old branches would never run anyway.
|
|
537
|
-
if (params.tone_instructions !== undefined)
|
|
538
|
-
body.toneInstructions = params.tone_instructions;
|
|
539
402
|
if (params.context !== undefined)
|
|
540
403
|
body.context = params.context;
|
|
541
404
|
if (params.extra_prompt_phases !== undefined)
|
|
542
405
|
body.extraPromptPhases =
|
|
543
406
|
params.extra_prompt_phases.map(mapExtraPromptPhase);
|
|
544
|
-
if (params.sender_override !== undefined)
|
|
545
|
-
body.senderOverride = params.sender_override;
|
|
546
407
|
const data = await getClient().post("/api/persona", body);
|
|
547
408
|
return toolResult(data);
|
|
548
409
|
}
|
|
@@ -730,17 +591,12 @@ export function registerPersonaTools(server) {
|
|
|
730
591
|
// GRANULAR PATCH TOOLS — read-modify-write, scoped to one section each
|
|
731
592
|
// ════════════════════════════════════════════════════════════════════════
|
|
732
593
|
// ── update_persona_identity ────────────────────────────────────────────
|
|
733
|
-
server.tool("update_persona_identity", "Patch top-level scalar fields of a persona (name, description,
|
|
734
|
-
"output_language, workflow_mode, strict_match, enable_signals
|
|
594
|
+
server.tool("update_persona_identity", "Patch top-level scalar fields of a persona (name, description, " +
|
|
595
|
+
"output_language, workflow_mode, strict_match, enable_signals). " +
|
|
735
596
|
"Pass only the fields you want to change. Other persona sections are untouched.", {
|
|
736
597
|
id: z.string().describe("Persona ID."),
|
|
737
598
|
name: z.string().min(1).optional().describe("New persona name."),
|
|
738
599
|
description: z.string().nullable().optional().describe("Detailed description."),
|
|
739
|
-
agent_domain: z
|
|
740
|
-
.string()
|
|
741
|
-
.nullable()
|
|
742
|
-
.optional()
|
|
743
|
-
.describe("Agent identity string (e.g. 'for medtech companies')."),
|
|
744
600
|
output_language: z
|
|
745
601
|
.string()
|
|
746
602
|
.nullable()
|
|
@@ -759,28 +615,20 @@ export function registerPersonaTools(server) {
|
|
|
759
615
|
.boolean()
|
|
760
616
|
.optional()
|
|
761
617
|
.describe("Enable business signal detection for this persona."),
|
|
762
|
-
|
|
763
|
-
.nullable()
|
|
764
|
-
.optional()
|
|
765
|
-
.describe("Override SDR identity at persona level ({name, title}). Pass null to clear."),
|
|
766
|
-
}, async ({ id, name, description, agent_domain, output_language, workflow_mode, strict_match, enable_signals, sender_override, }) => {
|
|
618
|
+
}, async ({ id, name, description, output_language, workflow_mode, strict_match, enable_signals, }) => {
|
|
767
619
|
try {
|
|
768
620
|
if (name === undefined &&
|
|
769
621
|
description === undefined &&
|
|
770
|
-
agent_domain === undefined &&
|
|
771
622
|
output_language === undefined &&
|
|
772
623
|
workflow_mode === undefined &&
|
|
773
624
|
strict_match === undefined &&
|
|
774
|
-
enable_signals === undefined
|
|
775
|
-
sender_override === undefined) {
|
|
625
|
+
enable_signals === undefined) {
|
|
776
626
|
return toolError("Provide at least one field to update.");
|
|
777
627
|
}
|
|
778
628
|
const current = await fetchPersona(id);
|
|
779
629
|
const body = {};
|
|
780
630
|
if (description !== undefined)
|
|
781
631
|
body.description = description;
|
|
782
|
-
if (agent_domain !== undefined)
|
|
783
|
-
body.agentDomain = agent_domain;
|
|
784
632
|
if (output_language !== undefined)
|
|
785
633
|
body.outputLanguage = output_language;
|
|
786
634
|
if (workflow_mode !== undefined)
|
|
@@ -789,8 +637,6 @@ export function registerPersonaTools(server) {
|
|
|
789
637
|
body.strictMatch = strict_match;
|
|
790
638
|
if (enable_signals !== undefined)
|
|
791
639
|
body.enableSignals = enable_signals;
|
|
792
|
-
if (sender_override !== undefined)
|
|
793
|
-
body.senderOverride = sender_override;
|
|
794
640
|
const data = await postPersonaUpdate(id, name ?? current.name, body);
|
|
795
641
|
return toolResult(data);
|
|
796
642
|
}
|
|
@@ -859,35 +705,6 @@ export function registerPersonaTools(server) {
|
|
|
859
705
|
return handlePersonaToolError(error);
|
|
860
706
|
}
|
|
861
707
|
});
|
|
862
|
-
// ── update_persona_messaging ───────────────────────────────────────────
|
|
863
|
-
server.tool("update_persona_messaging", "Patch the messagingGuidelines JSON column (shallow merge). Common keys: tone, " +
|
|
864
|
-
"language, can_mention, must_not_mention, value_propositions. Other keys remain intact.", {
|
|
865
|
-
id: z.string().describe("Persona ID."),
|
|
866
|
-
patch: z
|
|
867
|
-
.record(z.unknown())
|
|
868
|
-
.optional()
|
|
869
|
-
.describe("Object whose keys are merged into existing messagingGuidelines (shallow)."),
|
|
870
|
-
remove_keys: z
|
|
871
|
-
.array(z.string())
|
|
872
|
-
.optional()
|
|
873
|
-
.describe("Keys to delete from messagingGuidelines."),
|
|
874
|
-
}, async ({ id, patch, remove_keys }) => {
|
|
875
|
-
try {
|
|
876
|
-
if ((patch === undefined || Object.keys(patch).length === 0) &&
|
|
877
|
-
(remove_keys === undefined || remove_keys.length === 0)) {
|
|
878
|
-
return toolError("Provide at least one of: patch (non-empty), remove_keys (non-empty).");
|
|
879
|
-
}
|
|
880
|
-
const current = await fetchPersona(id);
|
|
881
|
-
const next = { ...asObject(current.messagingGuidelines), ...(patch ?? {}) };
|
|
882
|
-
for (const k of remove_keys ?? [])
|
|
883
|
-
delete next[k];
|
|
884
|
-
const data = await postPersonaUpdate(id, current.name, { messagingGuidelines: next });
|
|
885
|
-
return toolResult(data);
|
|
886
|
-
}
|
|
887
|
-
catch (error) {
|
|
888
|
-
return handlePersonaToolError(error);
|
|
889
|
-
}
|
|
890
|
-
});
|
|
891
708
|
// ── update_persona_active_tools ────────────────────────────────────────
|
|
892
709
|
server.tool("update_persona_active_tools", "Replace the activeTools list wholesale, OR add/remove specific entries while " +
|
|
893
710
|
"preserving the rest. Use 'tools' to fully replace; use 'add' / 'remove' for " +
|
|
@@ -1090,151 +907,6 @@ export function registerPersonaTools(server) {
|
|
|
1090
907
|
return handlePersonaToolError(error);
|
|
1091
908
|
}
|
|
1092
909
|
});
|
|
1093
|
-
// ── update_persona_outreach_template ───────────────────────────────────
|
|
1094
|
-
server.tool("update_persona_outreach_template", "Add, replace, or remove a single outreach template without re-sending the whole " +
|
|
1095
|
-
"list. Lookups are by template `id` (recommended) or by composite key " +
|
|
1096
|
-
"(channel + signal + field_name) when no id is set. WARNING: if the persona's " +
|
|
1097
|
-
"outreachSignalRouting.signals[] is configured, every template's `signal` must " +
|
|
1098
|
-
"be in that list — the API rejects with 422 otherwise.", {
|
|
1099
|
-
id: z.string().describe("Persona ID."),
|
|
1100
|
-
action: z
|
|
1101
|
-
.enum(["add", "replace", "remove"])
|
|
1102
|
-
.describe("add = append; replace = overwrite by id (or composite key); remove = delete by id (or composite key)."),
|
|
1103
|
-
template_id: z
|
|
1104
|
-
.string()
|
|
1105
|
-
.optional()
|
|
1106
|
-
.describe("Template id for replace/remove. Preferred lookup key."),
|
|
1107
|
-
lookup_channel: z
|
|
1108
|
-
.string()
|
|
1109
|
-
.optional()
|
|
1110
|
-
.describe("Fallback lookup: channel. Used together with lookup_signal + lookup_field_name when template_id is absent."),
|
|
1111
|
-
lookup_signal: z
|
|
1112
|
-
.string()
|
|
1113
|
-
.optional()
|
|
1114
|
-
.describe("Fallback lookup: signal."),
|
|
1115
|
-
lookup_field_name: z
|
|
1116
|
-
.string()
|
|
1117
|
-
.optional()
|
|
1118
|
-
.describe("Fallback lookup: field_name."),
|
|
1119
|
-
template: outreachTemplate
|
|
1120
|
-
.optional()
|
|
1121
|
-
.describe("Template payload. Required for 'add' and 'replace'. Schema: {id, channel, signal, field_name, signal_context, purpose, example, max_length, label, subject, body}."),
|
|
1122
|
-
}, async ({ id, action, template_id, lookup_channel, lookup_signal, lookup_field_name, template, }) => {
|
|
1123
|
-
try {
|
|
1124
|
-
if ((action === "add" || action === "replace") && !template) {
|
|
1125
|
-
return toolError(`template is required for action '${action}'.`);
|
|
1126
|
-
}
|
|
1127
|
-
const matches = (t) => {
|
|
1128
|
-
if (template_id)
|
|
1129
|
-
return t.id === template_id;
|
|
1130
|
-
if (lookup_channel || lookup_signal || lookup_field_name) {
|
|
1131
|
-
return ((lookup_channel === undefined || t.channel === lookup_channel) &&
|
|
1132
|
-
(lookup_signal === undefined || t.signal === lookup_signal) &&
|
|
1133
|
-
(lookup_field_name === undefined || t.fieldName === lookup_field_name));
|
|
1134
|
-
}
|
|
1135
|
-
return false;
|
|
1136
|
-
};
|
|
1137
|
-
if (action !== "add" && !template_id && !lookup_channel && !lookup_signal && !lookup_field_name) {
|
|
1138
|
-
return toolError("Provide template_id (preferred) or composite lookup (lookup_channel + lookup_signal + lookup_field_name).");
|
|
1139
|
-
}
|
|
1140
|
-
const current = await fetchPersona(id);
|
|
1141
|
-
const list = asArray(current.outreachTemplates).map((t) => ({ ...t }));
|
|
1142
|
-
if (action === "add") {
|
|
1143
|
-
list.push(mapOutreachTemplate(template));
|
|
1144
|
-
}
|
|
1145
|
-
else if (action === "replace") {
|
|
1146
|
-
const idx = list.findIndex(matches);
|
|
1147
|
-
if (idx === -1) {
|
|
1148
|
-
return toolError("No matching template found for the given lookup.");
|
|
1149
|
-
}
|
|
1150
|
-
list[idx] = mapOutreachTemplate(template);
|
|
1151
|
-
}
|
|
1152
|
-
else {
|
|
1153
|
-
const before = list.length;
|
|
1154
|
-
const filtered = list.filter((t) => !matches(t));
|
|
1155
|
-
if (filtered.length === before) {
|
|
1156
|
-
return toolError("No matching template found for the given lookup.");
|
|
1157
|
-
}
|
|
1158
|
-
list.length = 0;
|
|
1159
|
-
list.push(...filtered);
|
|
1160
|
-
}
|
|
1161
|
-
const data = await postPersonaUpdate(id, current.name, {
|
|
1162
|
-
outreachTemplates: list,
|
|
1163
|
-
});
|
|
1164
|
-
return toolResult(data);
|
|
1165
|
-
}
|
|
1166
|
-
catch (error) {
|
|
1167
|
-
return handlePersonaToolError(error);
|
|
1168
|
-
}
|
|
1169
|
-
});
|
|
1170
|
-
// ── update_persona_outreach_signal_routing ─────────────────────────────
|
|
1171
|
-
server.tool("update_persona_outreach_signal_routing", "Patch the outreachSignalRouting block. signal_source_field and default_signal " +
|
|
1172
|
-
"are scalar — passing one replaces it. signals[] is replaced wholesale when " +
|
|
1173
|
-
"passed; use add_signals / remove_signals to splice instead. " +
|
|
1174
|
-
"WARNING: default_signal must be present in signals[] (API rejects otherwise). " +
|
|
1175
|
-
"Removing a signal that's still referenced by an outreachTemplate.signal will " +
|
|
1176
|
-
"also be rejected.", {
|
|
1177
|
-
id: z.string().describe("Persona ID."),
|
|
1178
|
-
signal_source_field: z
|
|
1179
|
-
.string()
|
|
1180
|
-
.optional()
|
|
1181
|
-
.describe("Card field to read (e.g. card_signals)."),
|
|
1182
|
-
default_signal: z
|
|
1183
|
-
.string()
|
|
1184
|
-
.optional()
|
|
1185
|
-
.describe("Default signal — must exist in signals[]."),
|
|
1186
|
-
signals: z
|
|
1187
|
-
.array(z.string())
|
|
1188
|
-
.optional()
|
|
1189
|
-
.describe("Full replacement of the signals list. Mutually exclusive with add_signals / remove_signals."),
|
|
1190
|
-
add_signals: z
|
|
1191
|
-
.array(z.string())
|
|
1192
|
-
.optional()
|
|
1193
|
-
.describe("Signals to add (deduped). Mutually exclusive with signals."),
|
|
1194
|
-
remove_signals: z
|
|
1195
|
-
.array(z.string())
|
|
1196
|
-
.optional()
|
|
1197
|
-
.describe("Signals to remove. Mutually exclusive with signals."),
|
|
1198
|
-
}, async ({ id, signal_source_field, default_signal, signals, add_signals, remove_signals, }) => {
|
|
1199
|
-
try {
|
|
1200
|
-
if (signal_source_field === undefined &&
|
|
1201
|
-
default_signal === undefined &&
|
|
1202
|
-
signals === undefined &&
|
|
1203
|
-
add_signals === undefined &&
|
|
1204
|
-
remove_signals === undefined) {
|
|
1205
|
-
return toolError("Provide at least one field to update.");
|
|
1206
|
-
}
|
|
1207
|
-
if (signals !== undefined &&
|
|
1208
|
-
(add_signals !== undefined || remove_signals !== undefined)) {
|
|
1209
|
-
return toolError("'signals' is mutually exclusive with add_signals / remove_signals.");
|
|
1210
|
-
}
|
|
1211
|
-
const current = await fetchPersona(id);
|
|
1212
|
-
const existing = asObject(current.outreachSignalRouting);
|
|
1213
|
-
const next = { ...existing };
|
|
1214
|
-
if (signal_source_field !== undefined)
|
|
1215
|
-
next.signalSourceField = signal_source_field;
|
|
1216
|
-
if (default_signal !== undefined)
|
|
1217
|
-
next.defaultSignal = default_signal;
|
|
1218
|
-
if (signals !== undefined) {
|
|
1219
|
-
next.signals = signals;
|
|
1220
|
-
}
|
|
1221
|
-
else if (add_signals !== undefined || remove_signals !== undefined) {
|
|
1222
|
-
const set = new Set(asArray(existing.signals));
|
|
1223
|
-
for (const s of add_signals ?? [])
|
|
1224
|
-
set.add(s);
|
|
1225
|
-
for (const s of remove_signals ?? [])
|
|
1226
|
-
set.delete(s);
|
|
1227
|
-
next.signals = Array.from(set);
|
|
1228
|
-
}
|
|
1229
|
-
const data = await postPersonaUpdate(id, current.name, {
|
|
1230
|
-
outreachSignalRouting: next,
|
|
1231
|
-
});
|
|
1232
|
-
return toolResult(data);
|
|
1233
|
-
}
|
|
1234
|
-
catch (error) {
|
|
1235
|
-
return handlePersonaToolError(error);
|
|
1236
|
-
}
|
|
1237
|
-
});
|
|
1238
910
|
// ── update_persona_prompt_engineering ──────────────────────────────────
|
|
1239
911
|
server.tool("update_persona_prompt_engineering", "Patch free-form markdown prompt fields (normalization_rules, signal_guidance, " +
|
|
1240
912
|
"tone_instructions, context). Each is a separate scalar JSON column — passing " +
|