@agifyai/leadify-mcp 9.10.0 → 9.11.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/README.md
CHANGED
|
@@ -460,6 +460,19 @@ les réponses du serveur sont rendues telles quelles, en JSON compact. Routes :
|
|
|
460
460
|
`POST /api/contact-state`, `GET /api/prospection-tracking/{follow-up-feed,activity,coverage}`,
|
|
461
461
|
`GET /api/campaign-wizard/follow-up-cadence`, `GET /api/prospect-approach/{leadId}`.
|
|
462
462
|
|
|
463
|
+
### Messages complets (PRD-2708)
|
|
464
|
+
|
|
465
|
+
Un message capté (email, LinkedIn, InMail, note d'invitation) est lu en entier.
|
|
466
|
+
`organizationContact.exchanges` rend chaque texte avec ses paragraphes ; les
|
|
467
|
+
textes d'une réponse partagent un budget de 30 000 caractères, les plus récents
|
|
468
|
+
d'abord, et un échange coupé par ce budget porte `textTruncated: true` et sa
|
|
469
|
+
longueur réelle `textLength`. `email_read_thread`, `linkedin_read_conversation`,
|
|
470
|
+
`unipile_read_messages` et `leadify_read_activity_feed` gardent 20 000
|
|
471
|
+
caractères par champ par défaut (`max_content_chars`, jusqu'à 100 000) ; un champ
|
|
472
|
+
plus long se termine par `… [truncated: N characters in total]`. `list_follow_ups`
|
|
473
|
+
accepte `include_text` pour ajouter le message entier (`lastMessage.text`) à côté
|
|
474
|
+
de son extrait de 500 caractères.
|
|
475
|
+
|
|
463
476
|
### Déplacer des prospects vers un autre groupe (PRD-2478)
|
|
464
477
|
|
|
465
478
|
Pour réorganiser une base, **déplacer** les fiches plutôt que les copier
|
|
@@ -74,7 +74,7 @@ const CONTEXT_MODULE_DESCRIPTION = {
|
|
|
74
74
|
playbook: "The published sales PLAYBOOK (GTM) of the organisation.",
|
|
75
75
|
qualification: "What is KNOWN about the person: qualification document, freshness, failures and any refresh requested. Read the company module for the company cards.",
|
|
76
76
|
signals: "WHY NOW: recent signals about the person and her company, newest first. Paged.",
|
|
77
|
-
history: "What was ALREADY SAID: messages exchanged with their text, known threads, the program's own messages, coverage. Newest first. Paged. The first page (no cursor) also carries organizationContact, outside the module version: alreadyInExchange, firstContactAllowed and exchanges[] captured across the whole organization, including emails and LinkedIn messages written by hand (at, channel, direction, origin MANUAL/CAMPAIGN/AGENT, author, subject, text).",
|
|
77
|
+
history: "What was ALREADY SAID: messages exchanged with their text, known threads, the program's own messages, coverage. Newest first. Paged. The first page (no cursor) also carries organizationContact, outside the module version: alreadyInExchange, firstContactAllowed and exchanges[] captured across the whole organization, including emails and LinkedIn messages written by hand (at, channel, direction, origin MANUAL/CAMPAIGN/AGENT, author, subject, text: the whole message with its paragraphs; when the 30,000-character budget of the response is reached, the oldest carry textTruncated and textLength).",
|
|
78
78
|
crm: "CRM situation, exclusion and contact state: commercial status, reception, human takeover, thread-read readiness, last confirmed outbound and inbound. Check it before any first contact.",
|
|
79
79
|
authorization: "What is PERMITTED: program state, caps, senders and the key's rights, with the authorization revision to copy into the decision.",
|
|
80
80
|
};
|
|
@@ -95,7 +95,7 @@ export function registerAgentOutreachTools(server, client = getClient()) {
|
|
|
95
95
|
const personSchema = z.object({ ...scope, lead_id: identifier }).strict();
|
|
96
96
|
register("get_outreach_prospect_summary", "Read a selected person's compact identity, contact counts and selection reasons. This is NOT the writing context: use get_outreach_prospect_context before drafting.", personSchema, v => client.get(`${programPath(v.program_id)}/prospects/${encoded(v.lead_id)}`, params(v.organization_id)));
|
|
97
97
|
register("get_outreach_contact_history", "Read confirmed contact evidence for one person across accessible accounts, including pending, unknown and manual activity. Coverage limits remain explicit." +
|
|
98
|
-
" Also organizationContact (whole organization, every mailbox and LinkedIn account of every member, not only the program senders): alreadyInExchange, firstContactAllowed, lastContactAt, reasonCode and exchanges[], the messages captured by Leadify, including emails and LinkedIn messages written by hand: at, channel, direction, kind, origin (MANUAL, CAMPAIGN, AGENT), author (member, Louise, Inconnu, or the person for a reply), subject and text (readable, quoted history removed, cut
|
|
98
|
+
" Also organizationContact (whole organization, every mailbox and LinkedIn account of every member, not only the program senders): alreadyInExchange, firstContactAllowed, lastContactAt, reasonCode and exchanges[], the messages captured by Leadify, including emails and LinkedIn messages written by hand: at, channel, direction, kind, origin (MANUAL, CAMPAIGN, AGENT), author (member, Louise, Inconnu, or the person for a reply), subject and text (the whole readable message, paragraphs kept, quoted history removed; the texts of one response share a 30,000-character budget, newest first, and an exchange cut by that budget carries textTruncated=true and its real textLength). firstContactAllowed=false forbids a first contact.", personSchema, v => client.get(`${programPath(v.program_id)}/contacts/${encoded(v.lead_id)}`, params(v.organization_id)));
|
|
99
99
|
register("get_outreach_prospect_context", "LEGACY whole writing context, about 90 KB: runtimes that set aside tool results above 50,000 characters never see it. Agents must NOT call it: read get_outreach_context_summary, then the get_outreach_context_<module> tools. Kept for clients that can read very large results. Draft only when readiness.ready=true; WAIT requires resolving the stated reasons. Copy all versions, factsVersion, contextVersion and readAt into the current decision. Never starts qualification. A human takeover can only be resumed by a human in Leadify.", personSchema, async (v) => requireOutreachVersion(await client.get(`${programPath(v.program_id, true)}/people/${encoded(v.lead_id)}/context`, params(v.organization_id)), "agent-writing-context.v1"));
|
|
100
100
|
// PRD-2632 : the same context in pieces. Summary first, then one tool per module.
|
|
101
101
|
const contextPath = (v, tail) => `${programPath(v.program_id, true)}/people/${encoded(v.lead_id)}/context/${tail}`;
|
|
@@ -3,8 +3,10 @@ import { getClient } from "../client.js";
|
|
|
3
3
|
import { handleToolError, toolError, toolResult } from "../types.js";
|
|
4
4
|
const DEFAULT_LIMIT = 25;
|
|
5
5
|
const MAX_LIMIT = 50;
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
// PRD-2708: a message is read whole by default (a real message is a few thousand
|
|
7
|
+
// characters at most); beyond the limit the field says it is cut, never silently.
|
|
8
|
+
const DEFAULT_CONTENT_LIMIT = 20_000;
|
|
9
|
+
const MAX_CONTENT_LIMIT = 100_000;
|
|
8
10
|
const DEFAULT_FRESHNESS_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1_000;
|
|
9
11
|
const MAX_FRESHNESS_MAX_AGE_MS = 90 * 24 * 60 * 60 * 1_000;
|
|
10
12
|
const MAX_TO_ADDRESSES = 10;
|
|
@@ -31,10 +33,10 @@ function stringValue(value, max = 500) {
|
|
|
31
33
|
const trimmed = value.trim();
|
|
32
34
|
if (!trimmed)
|
|
33
35
|
return null;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
if (trimmed.length <= max)
|
|
37
|
+
return trimmed;
|
|
38
|
+
const marker = `… [truncated: ${trimmed.length} characters in total]`;
|
|
39
|
+
return `${trimmed.slice(0, Math.max(0, max - marker.length))}${marker}`;
|
|
38
40
|
}
|
|
39
41
|
function firstString(source, keys, max = 500) {
|
|
40
42
|
return keys.map((key) => stringValue(source[key], max)).find((value) => value !== null) ?? null;
|
|
@@ -1036,7 +1038,7 @@ function readSchema() {
|
|
|
1036
1038
|
page: z.number().int().positive().max(10_000).optional().describe("One-based page number when the source uses page pagination."),
|
|
1037
1039
|
limit: z.number().int().min(1).max(MAX_LIMIT).optional().default(DEFAULT_LIMIT).describe(`Maximum records returned; hard-bounded to ${MAX_LIMIT}.`),
|
|
1038
1040
|
cursor: z.string().trim().min(1).max(500).optional().describe("Opaque cursor returned by the previous page."),
|
|
1039
|
-
max_content_chars: z.number().int().min(100).max(MAX_CONTENT_LIMIT).optional().default(DEFAULT_CONTENT_LIMIT).describe(`Maximum characters kept in each content field;
|
|
1041
|
+
max_content_chars: z.number().int().min(100).max(MAX_CONTENT_LIMIT).optional().default(DEFAULT_CONTENT_LIMIT).describe(`Maximum characters kept in each content field (message body, text, subject). Default ${DEFAULT_CONTENT_LIMIT}: a message is returned whole. A longer field ends with "… [truncated: N characters in total]"; raise this value, up to ${MAX_CONTENT_LIMIT}, to read it whole.`),
|
|
1040
1042
|
freshness_max_age_ms: z.number().int().positive().max(MAX_FRESHNESS_MAX_AGE_MS).optional().default(DEFAULT_FRESHNESS_MAX_AGE_MS).describe("Maximum source age used by the common freshness projection; at most 90 days."),
|
|
1041
1043
|
};
|
|
1042
1044
|
}
|
|
@@ -51,7 +51,8 @@ export function registerProspectionTrackingTools(server, client = getClient()) {
|
|
|
51
51
|
"organization, in the same order as the Suivi page: most overdue first. Use it to pick who to follow up today; " +
|
|
52
52
|
"in the DUE feed, people handled by a campaign or by Louise (AUTOMATED) and excluded people never appear. Each item: personId, " +
|
|
53
53
|
"leadId, groupId, person (name, jobTitle), company, groups, due (channel, step FIRST_MESSAGE or FOLLOW_UP with its " +
|
|
54
|
-
"index and total, dueAt, daysLate, acceptedAt, alsoDue) or reply (at, channel, daysWaiting), lastMessage
|
|
54
|
+
"index and total, dueAt, daysLate, acceptedAt, alsoDue) or reply (at, channel, daysWaiting), lastMessage (a 500-character " +
|
|
55
|
+
"excerpt; include_text=true adds lastMessage.text, the whole message), recentSignal, reason " +
|
|
55
56
|
"(EXCLUSION_ENDED or INVITATION_NOT_ACCEPTED) and href. The page also gives total (same filters) and nextUpcoming " +
|
|
56
57
|
`when empty. Pages of ${FOLLOW_UP_FEED_PAGE_SIZE.default} by default (max ${FOLLOW_UP_FEED_PAGE_SIZE.max}): pass ` +
|
|
57
58
|
"nextCursor unchanged as cursor, with the same filters, until it is null. Read get_contact_state before writing.", {
|
|
@@ -62,8 +63,10 @@ export function registerProspectionTrackingTools(server, client = getClient()) {
|
|
|
62
63
|
group_id: id.optional().describe("Only people present in this lead group."),
|
|
63
64
|
cursor: z.string().min(1).max(500).optional().describe("nextCursor from the previous page, unchanged. Omit for the first page."),
|
|
64
65
|
limit: z.number().int().min(1).max(FOLLOW_UP_FEED_PAGE_SIZE.max).optional(),
|
|
65
|
-
|
|
66
|
+
include_text: z.boolean().optional().describe("Also return lastMessage.text, the whole message next to its 500-character excerpt. Off by default."),
|
|
67
|
+
}, ({ organization_id, kind, member_user_id, channel, group_id, cursor, limit, include_text }) => client.get("/api/prospection-tracking/follow-up-feed", query({
|
|
66
68
|
organizationId: organization_id, kind, memberUserId: member_user_id, channel, groupId: group_id, cursor, limit,
|
|
69
|
+
includeText: include_text === undefined ? undefined : String(include_text),
|
|
67
70
|
})));
|
|
68
71
|
read("get_prospection_activity", "Counters and day-by-day series of the Leadify Suivi page (tab Activité) for a period: messagesSent (emails, " +
|
|
69
72
|
"LinkedIn messages), invitations (sent, accepted, acceptanceRate), peopleContacted (total, new), replies (people, " +
|