@agifyai/leadify-mcp 9.14.1 → 9.16.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 +30 -7
- package/dist/correlation.js +1 -1
- package/dist/tools/campaigns.js +20 -2
- package/dist/tools/outreach_review.d.ts +4 -4
- package/dist/tools/outreach_review.js +48 -12
- package/dist/tools/views.js +15 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -277,6 +277,7 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
|
|
|
277
277
|
| `get_follow_up_cadence` | Rythme de relance de l'organisation (lecture seule). |
|
|
278
278
|
| `get_prospect_approach` | Avant d'écrire à une personne : son Approche (quotidien, problématique, ce qu'elle veut, ce qu'on lui apporte). |
|
|
279
279
|
| `list_outreach_review_queue` | La file « À valider » de Leadify (page `/to-review`) en un appel, en lecture seule : filtres campagne, canal, type de message et état (`TO_REVIEW` par défaut), `total`, `counts`, `truncated`. Le texte des messages seulement avec `include_text`. |
|
|
280
|
+
| `revise_review_queue_message` | Remplacer le texte d'**un** message en `TO_REVIEW`, à la demande explicite du relecteur, au nom de l'agent nommé par `LEADIFY_EXTERNAL_AGENT_NAME` : relecture complète en réponse, version attendue, clé d'idempotence, limites du canal. Le message reste à valider : rien ne valide, ne programme, n'envoie ni ne refuse. Organisation d'Agify seulement. |
|
|
280
281
|
| `preview_move_leads` | Essai à blanc du déplacement de fiches vers un autre groupe de la même organisation : sélection par identifiants, vue ou filtre (500 fiches au plus par appel, lots ensuite), refus par fiche avec motif, doublons dans le groupe cible, champs de schéma et relations à ajouter, blocages et empreinte du plan. |
|
|
281
282
|
| `move_leads` | Appliquer un déplacement prévisualisé ; exige l'empreinte de la prévisualisation et une clé d'idempotence. La fiche garde son identifiant et tout son historique ; jamais de fusion. |
|
|
282
283
|
| `undo_move_leads` / `get_lead_move` | Annuler ou relire une opération de déplacement par son identifiant. |
|
|
@@ -303,7 +304,7 @@ Leadify et ne doit jamais être envoyée par un agent MCP.
|
|
|
303
304
|
| `delete_campaign_log` | Supprimer une entrée de log de campagne. |
|
|
304
305
|
| `update_campaign_stats` | Mettre à jour les statistiques d'email d'une campagne pour un groupe de leads. |
|
|
305
306
|
| `create_campaign` | Créer une campagne DRAFT mono-canal rattachée à un Lead Group qui possède déjà sa paire Persona–Offre. Aucun `persona_id` ou contexte indépendant n’est accepté au niveau Campagne. `channel` (`LINKEDIN` ou `EMAIL`) est obligatoire ; `start_at` inclusif, `end_at` exclusif et `timezone` IANA configurent la fenêtre métier. À `end_at`, Leadify met la campagne en pause réversible (`WINDOW_END`) sans la clôturer. |
|
|
306
|
-
| `update_campaign` / `update_campaign_configuration` | Modifier le nom, la description
|
|
307
|
+
| `update_campaign` / `update_campaign_configuration` | Modifier le nom, la description, la fenêtre, la limite par jour (`daily_limit`) et les jours d'envoi (`send_days`) de toute campagne encore `OPEN`, y compris en pilote ; l'audience (`audience_view_id`) se change jusqu'au lancement. La réponse rend `settings` et `sequenceWindowWarning` si la fin de campagne coupe la séquence; le canal reste modifiable uniquement en DRAFT. Prolonger `end_at` dans le futur ou le supprimer relance automatiquement une pause `WINDOW_END` après validation du fournisseur, mais jamais une pause manuelle. La fenêtre demandée reste enregistrée si cette validation échoue. La Persona est héritée du Lead Group et n’est pas modifiable au niveau Campagne. |
|
|
307
308
|
| `delete_campaign` | Supprimer irréversiblement une campagne `DRAFT` ou `PAUSED` encore `OPEN`, avec la confirmation explicite `delete_campaign`. Une campagne active ou clôturée est refusée, et aucun envoi n’est déclenché. |
|
|
308
309
|
| `list_campaigns` | Lister compactement les campagnes d'une organisation explicitement sélectionnée, avec état effectif, fenêtre, motif de pause et clôture. |
|
|
309
310
|
| `get_campaign` | Récupérer les détails d'une campagne, son état effectif, sa clôture et son éventuel rapport final figé, ainsi que ses KPIs temps réel. |
|
|
@@ -474,7 +475,8 @@ d'écrire à quelqu'un : `get_contact_state`, puis `get_prospect_approach`. Les
|
|
|
474
475
|
échanges captés à la main (emails et messages LinkedIn, avec texte, auteur et
|
|
475
476
|
origine) arrivent dans `organizationContact.exchanges` de
|
|
476
477
|
`get_outreach_contact_history` et de la première page de
|
|
477
|
-
`get_outreach_context_history`.
|
|
478
|
+
`get_outreach_context_history`. Une vue qu'aucune campagne n'utilise se supprime
|
|
479
|
+
avec `delete_lead_view`. Les vues (`preview_lead_view`,
|
|
478
480
|
`create_lead_view`) acceptent les 12 champs `__contact_*` (dont `__contact_due`,
|
|
479
481
|
le filtre « À relancer »), `within_last_days`, `older_than_days` et
|
|
480
482
|
`includeEmpty`. Les listes fermées des entrées sont recopiées octet pour octet du
|
|
@@ -502,17 +504,38 @@ de son extrait de 500 caractères.
|
|
|
502
504
|
les messages de campagne qui attendent une décision humaine, dans l'ordre de la
|
|
503
505
|
page, au plus 500, sans pagination. Filtres : `campaign_id`, `channel` (`EMAIL`,
|
|
504
506
|
`LINKEDIN`), `message_type_id` et `state` (`TO_REVIEW` par défaut, `VALIDATED`,
|
|
505
|
-
`CORRECTED`, `REFUSED`, ou `ALL`). Les identifiants des filtres viennent de
|
|
507
|
+
`CORRECTED`, `REWRITTEN`, `REFUSED`, ou `ALL`). Les identifiants des filtres viennent de
|
|
506
508
|
`filters.campaigns` et `filters.messageTypes`. Chaque message garde l'essentiel
|
|
507
509
|
(prospect, entreprise, campagne, canal, étape, type, départ prévu, créneau
|
|
508
|
-
manqué, statut, objet)
|
|
509
|
-
|
|
510
|
+
manqué, statut, objet), sa `version`, et l'auteur de sa dernière modification
|
|
511
|
+
(`lastEditedBy` : `none`, `human` ou `agent`, avec `lastEditedByName`) ; la date de
|
|
512
|
+
création et les identifiants de campagne et de type, déjà dans `filters`, sont
|
|
513
|
+
retirés. `decisions` sépare les messages validés : `validatedAsIs`,
|
|
514
|
+
`validatedAfterAgentRewrite`, `correctedByHuman`. Sans le texte des
|
|
510
515
|
messages, la file réelle de 73 messages tient en environ 30 000 caractères, sous
|
|
511
516
|
la limite de 50 000 d'Hermès ; `include_text: true` ajoute `body`, le message
|
|
512
517
|
entier (environ 500 caractères de plus par message) : au-delà d'une quarantaine
|
|
513
518
|
de messages, filtrer d'abord. Si `truncated` vaut `true`, filtrer par campagne.
|
|
514
|
-
|
|
515
|
-
|
|
519
|
+
Route : `GET /api/outreach-review/queue` (permission `read`).
|
|
520
|
+
|
|
521
|
+
`revise_review_queue_message` remplace le texte d'un seul message en `TO_REVIEW`,
|
|
522
|
+
à la demande explicite du relecteur : le corps, et l'objet d'un email. Le message
|
|
523
|
+
reste à valider ; l'état, l'heure de départ, la campagne, le destinataire et le
|
|
524
|
+
canal ne changent pas. Entrées : `organization_id`, `message_id`, `body`,
|
|
525
|
+
`subject` (facultatif), `expected_version`, `idempotency_key`. La réponse est une
|
|
526
|
+
relecture complète : texte enregistré, `version`, auteur `agent`, état et
|
|
527
|
+
`history` (les textes précédents). Chaque écriture crée la version suivante sous
|
|
528
|
+
un **nouvel identifiant** : utiliser `messageId` et `version` de la réponse pour la
|
|
529
|
+
suite. Rejouer la même clé rend le même résultat (`replayed: true`). Refus sans
|
|
530
|
+
écriture : `MESSAGE_NOT_TO_REVIEW`, `MESSAGE_VERSION_STALE`,
|
|
531
|
+
`IDEMPOTENCY_KEY_REUSED`, `EDIT_INVALID` (note d'invitation LinkedIn : 300 caractères
|
|
532
|
+
au plus ; message LinkedIn et email : 1 500 ; objet : 90 ; pas d'objet sur
|
|
533
|
+
LinkedIn), `AGENT_REVISION_ORGANIZATION_FORBIDDEN` (organisation d'Agify seulement).
|
|
534
|
+
Exige `LEADIFY_EXTERNAL_AGENT_NAME`, et une clé avec la permission `write`. Route :
|
|
535
|
+
`POST /api/outreach-review/messages/:id/revise`.
|
|
536
|
+
|
|
537
|
+
Aucun outil ne valide, ne programme, n'envoie ni ne refuse : cela reste humain, dans
|
|
538
|
+
Leadify.
|
|
516
539
|
|
|
517
540
|
### Déplacer des prospects vers un autre groupe (PRD-2478)
|
|
518
541
|
|
package/dist/correlation.js
CHANGED
|
@@ -9,7 +9,7 @@ export const EXTERNAL_MISSION_VERSION = "external-agent.v1";
|
|
|
9
9
|
const PROCESS_SESSION = randomUUID().replace(/-/g, "").slice(0, 16);
|
|
10
10
|
export class ExternalAgentNameMissingError extends Error {
|
|
11
11
|
constructor() {
|
|
12
|
-
super(`${EXTERNAL_AGENT_ENV} n'est pas défini : une écriture de qualification doit être faite au nom d'un agent identifié. ` +
|
|
12
|
+
super(`${EXTERNAL_AGENT_ENV} n'est pas défini : une écriture de qualification ou une réécriture de message doit être faite au nom d'un agent identifié. ` +
|
|
13
13
|
`Lancez le MCP avec ${EXTERNAL_AGENT_ENV}=<Nom> (par exemple Louise). Les lectures restent disponibles sans ce nom.`);
|
|
14
14
|
this.name = "ExternalAgentNameMissingError";
|
|
15
15
|
}
|
package/dist/tools/campaigns.js
CHANGED
|
@@ -142,7 +142,18 @@ export function registerCampaignTools(server, readClient, writeClient, mutationC
|
|
|
142
142
|
timezone: z.string().min(1).optional().describe("IANA timezone used for the business window, for example Europe/Paris."),
|
|
143
143
|
name: z.string().min(1).optional().describe("New campaign name."),
|
|
144
144
|
description: z.string().nullable().optional().describe("New campaign description, or null to clear it."),
|
|
145
|
-
|
|
145
|
+
daily_limit: z.number().int().min(1).optional().describe("Daily sending limit of the campaign channel, as on the campaign page: at most 500 for EMAIL, at most 30 for LINKEDIN (invitations and messages)."),
|
|
146
|
+
send_days: z.object({
|
|
147
|
+
monday: z.boolean().optional(),
|
|
148
|
+
tuesday: z.boolean().optional(),
|
|
149
|
+
wednesday: z.boolean().optional(),
|
|
150
|
+
thursday: z.boolean().optional(),
|
|
151
|
+
friday: z.boolean().optional(),
|
|
152
|
+
saturday: z.boolean().optional(),
|
|
153
|
+
sunday: z.boolean().optional(),
|
|
154
|
+
}).strict().optional().describe("Sending days to change; omitted days stay as they are. At least one day must stay enabled."),
|
|
155
|
+
audience_view_id: z.string().min(1).nullable().optional().describe("Audience segment: a lead view of the campaign group, or null for the whole group. Refused once the campaign is launched (only DRAFT and PILOT)."),
|
|
156
|
+
}, async ({ id, channel, start_at, end_at, timezone, name, description, daily_limit, send_days, audience_view_id }) => {
|
|
146
157
|
try {
|
|
147
158
|
const body = {};
|
|
148
159
|
if (channel !== undefined)
|
|
@@ -157,6 +168,12 @@ export function registerCampaignTools(server, readClient, writeClient, mutationC
|
|
|
157
168
|
body.name = name;
|
|
158
169
|
if (description !== undefined)
|
|
159
170
|
body.description = description;
|
|
171
|
+
if (daily_limit !== undefined)
|
|
172
|
+
body.dailyLimit = daily_limit;
|
|
173
|
+
if (send_days !== undefined)
|
|
174
|
+
body.sendDays = send_days;
|
|
175
|
+
if (audience_view_id !== undefined)
|
|
176
|
+
body.audienceViewId = audience_view_id;
|
|
160
177
|
return toolResult(await (mutationClient ?? getClient()).put(`/api/campaign/${encodeURIComponent(id)}/configuration`, body));
|
|
161
178
|
}
|
|
162
179
|
catch (error) {
|
|
@@ -164,7 +181,8 @@ export function registerCampaignTools(server, readClient, writeClient, mutationC
|
|
|
164
181
|
}
|
|
165
182
|
});
|
|
166
183
|
};
|
|
167
|
-
const updateCampaignDescription = "Edit the name, description,
|
|
184
|
+
const updateCampaignDescription = "Edit the name, description, lifecycle window, daily limit and sending days of any OPEN campaign, including a campaign in PILOT, as the campaign page does; " +
|
|
185
|
+
"the audience segment changes until launch, and changing channel remains limited to DRAFT. The response returns the saved settings and sequenceWindowWarning when the campaign end cuts the longest sequence. " +
|
|
168
186
|
"Extending endAt into the future or removing it automatically resumes only a campaign paused " +
|
|
169
187
|
"because WINDOW_END, after provider validation and immediate replanning. A MANUAL pause never " +
|
|
170
188
|
"auto-resumes. The requested window remains saved when provider validation fails, and a CLOSED " +
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { type LeadifyClient } from "../client.js";
|
|
3
|
-
export declare const OUTREACH_REVIEW_TOOL_NAMES: readonly ["list_outreach_review_queue"];
|
|
4
|
-
export declare const OUTREACH_REVIEW_STATES: readonly ["ALL", "TO_REVIEW", "VALIDATED", "CORRECTED", "REFUSED"];
|
|
3
|
+
export declare const OUTREACH_REVIEW_TOOL_NAMES: readonly ["list_outreach_review_queue", "revise_review_queue_message"];
|
|
4
|
+
export declare const OUTREACH_REVIEW_STATES: readonly ["ALL", "TO_REVIEW", "VALIDATED", "CORRECTED", "REWRITTEN", "REFUSED"];
|
|
5
5
|
export declare const OUTREACH_REVIEW_CHANNELS: readonly ["EMAIL", "LINKEDIN"];
|
|
6
|
-
type Client = Pick<LeadifyClient, "get">;
|
|
6
|
+
type Client = Pick<LeadifyClient, "get" | "post">;
|
|
7
7
|
/**
|
|
8
|
-
* One queue item
|
|
8
|
+
* One queue item, with version (the revision a write must name), without createdAt and
|
|
9
9
|
* without the campaign and message type ids, which filters.campaigns and
|
|
10
10
|
* filters.messageTypes carry once. The body only with include_text.
|
|
11
11
|
*/
|
|
@@ -1,29 +1,32 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { getClient } from "../client.js";
|
|
3
|
+
import { ExternalAgentNameMissingError, externalAgentName } from "../correlation.js";
|
|
3
4
|
import { handleToolError } from "../types.js";
|
|
4
|
-
// PRD-2815 — la file « À valider » de Leadify (page /to-review)
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
export const
|
|
5
|
+
// PRD-2815 — la file « À valider » de Leadify (page /to-review), par la route serveur
|
|
6
|
+
// GET /api/outreach-review/queue. Une seule écriture : un agent nommé remplace le texte
|
|
7
|
+
// d'un message à valider, par POST /api/outreach-review/messages/:id/revise. Valider,
|
|
8
|
+
// programmer, envoyer ou refuser reste humain : aucun outil ici ne le fait.
|
|
9
|
+
export const OUTREACH_REVIEW_TOOL_NAMES = ["list_outreach_review_queue", "revise_review_queue_message"];
|
|
10
|
+
export const OUTREACH_REVIEW_STATES = ["ALL", "TO_REVIEW", "VALIDATED", "CORRECTED", "REWRITTEN", "REFUSED"];
|
|
9
11
|
export const OUTREACH_REVIEW_CHANNELS = ["EMAIL", "LINKEDIN"];
|
|
10
12
|
const id = z.string().trim().min(1).max(200);
|
|
11
13
|
const READ_ONLY = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
|
|
14
|
+
const REVISE = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true };
|
|
12
15
|
const object = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
13
16
|
/** One line of JSON: an agent runtime sets aside tool results above 50,000 characters. */
|
|
14
17
|
const compact = (data) => ({ content: [{ type: "text", text: JSON.stringify(data) }] });
|
|
15
18
|
/**
|
|
16
|
-
* One queue item
|
|
19
|
+
* One queue item, with version (the revision a write must name), without createdAt and
|
|
17
20
|
* without the campaign and message type ids, which filters.campaigns and
|
|
18
21
|
* filters.messageTypes carry once. The body only with include_text.
|
|
19
22
|
*/
|
|
20
23
|
export function projectReviewItem(item, includeText) {
|
|
21
24
|
const i = object(item);
|
|
22
25
|
const result = {
|
|
23
|
-
id: i.id, leadId: i.leadId, leadName: i.leadName, companyName: i.companyName, campaignName: i.campaignName,
|
|
26
|
+
id: i.id, version: i.revision, leadId: i.leadId, leadName: i.leadName, companyName: i.companyName, campaignName: i.campaignName,
|
|
24
27
|
channel: i.channel, step: i.step, stepKind: i.stepKind, messageTypeName: object(i.messageType).name ?? null,
|
|
25
28
|
plannedDepartureAt: i.plannedDepartureAt, missedSlot: i.missedSlot, status: i.status, editedByHuman: i.editedByHuman,
|
|
26
|
-
subject: i.subject,
|
|
29
|
+
lastEditedBy: i.lastEditedBy, lastEditedByName: i.lastEditedByName, subject: i.subject,
|
|
27
30
|
};
|
|
28
31
|
if (includeText)
|
|
29
32
|
result.body = i.body;
|
|
@@ -32,16 +35,19 @@ export function projectReviewItem(item, includeText) {
|
|
|
32
35
|
export function registerOutreachReviewTools(server, client = getClient()) {
|
|
33
36
|
server.tool("list_outreach_review_queue", "The Leadify 'À valider' queue (page /to-review) in ONE call: the campaign messages waiting for a human decision, in " +
|
|
34
37
|
"the page's order. Read-only: validating, correcting or refusing a message stays human, in Leadify. Answer: " +
|
|
35
|
-
"{organizationId, state, limit, total, counts {all, toReview, validated, corrected, refused},
|
|
36
|
-
"
|
|
38
|
+
"{organizationId, state, limit, total, counts {all, toReview, validated, corrected, rewritten, refused}, " +
|
|
39
|
+
"decisions {validatedAsIs, validatedAfterAgentRewrite, correctedByHuman}, truncated, items, " +
|
|
40
|
+
"filters {campaigns [{id, name}], messageTypes [{id, name}]}, notContactedToday {count, reasons}}. decisions splits the " +
|
|
41
|
+
"validated messages by who last wrote their text. Each item: id, version (what revise_review_queue_message expects), " +
|
|
37
42
|
"leadId, leadName, companyName, campaignName, channel, step, stepKind, messageTypeName, plannedDepartureAt, " +
|
|
38
|
-
"missedSlot, status (TO_REVIEW, APPROVED, SCHEDULED, SENT, REJECTED), editedByHuman,
|
|
43
|
+
"missedSlot, status (TO_REVIEW, APPROVED, SCHEDULED, SENT, REJECTED), editedByHuman, lastEditedBy (none, human or agent), " +
|
|
44
|
+
"lastEditedByName (the agent's name), subject. To stay well under " +
|
|
39
45
|
"50,000 characters, the message text is left out by default (about 30,000 characters for 73 messages); " +
|
|
40
46
|
"include_text=true adds body, the whole message, about 500 characters more per message (about 68,000 for those 73): " +
|
|
41
47
|
"above about 40 messages, narrow with campaign_id, channel or message_type_id first. At most 500 messages, no pagination: when truncated " +
|
|
42
48
|
"is true, filter by campaign_id. Pass the ids found in filters.campaigns and filters.messageTypes.", {
|
|
43
49
|
organization_id: id.describe("Organization to read. Must be in the API key's scope."),
|
|
44
|
-
state: z.enum(OUTREACH_REVIEW_STATES).optional().describe("TO_REVIEW (default): waiting for validation. VALIDATED, CORRECTED, REFUSED: decided in the last 7 days. ALL: TO_REVIEW plus the messages validated, scheduled, sent or refused in the last 7 days."),
|
|
50
|
+
state: z.enum(OUTREACH_REVIEW_STATES).optional().describe("TO_REVIEW (default): waiting for validation. VALIDATED, CORRECTED, REFUSED: decided in the last 7 days. CORRECTED: last written by a human. REWRITTEN: last written by an agent. ALL: TO_REVIEW plus the messages validated, scheduled, sent or refused in the last 7 days."),
|
|
45
51
|
campaign_id: id.optional().describe("Only this campaign (filters.campaigns[].id)."),
|
|
46
52
|
channel: z.enum(OUTREACH_REVIEW_CHANNELS).optional().describe("EMAIL or LINKEDIN."),
|
|
47
53
|
message_type_id: id.optional().describe("Only this message type (filters.messageTypes[].id)."),
|
|
@@ -59,4 +65,34 @@ export function registerOutreachReviewTools(server, client = getClient()) {
|
|
|
59
65
|
return handleToolError(error);
|
|
60
66
|
}
|
|
61
67
|
}));
|
|
68
|
+
server.tool("revise_review_queue_message", "Replace the text of ONE campaign message waiting in the Leadify 'À valider' queue, at the reviewer's explicit request. " +
|
|
69
|
+
"Changes only the body, and the subject of an email. The message stays TO_REVIEW: state, planned departure, campaign, " +
|
|
70
|
+
"recipient and channel never change, and the reviewer still validates it by hand in Leadify. Nothing here validates, " +
|
|
71
|
+
"schedules, sends or refuses. Each write creates the next version under a NEW id: use the returned messageId and version " +
|
|
72
|
+
"for anything after it. The previous text stays in history. Answer, read back after writing: {organizationId, messageId, " +
|
|
73
|
+
"previousMessageId, version, status, campaignId, leadId, channel, step, subject, body, lastEditedBy, lastEditedByName, " +
|
|
74
|
+
"unchanged, replayed, latestMessageId, updatedAt, history [{messageId, version, status, subject, body, lastEditedBy, " +
|
|
75
|
+
"lastEditedByName, createdAt}]}. Replaying the same idempotency_key with the same call returns the same message " +
|
|
76
|
+
"(replayed: true). Refusals, nothing written: MESSAGE_NOT_TO_REVIEW (APPROVED, SCHEDULED, SENT, REJECTED...), " +
|
|
77
|
+
"MESSAGE_VERSION_STALE (reread the queue, latestMessageId gives the current one), IDEMPOTENCY_KEY_REUSED, EDIT_INVALID " +
|
|
78
|
+
"(empty, too long: LinkedIn invitation note 300 characters, LinkedIn message 1500, email 1500 and subject 90; or a subject " +
|
|
79
|
+
"on LinkedIn), AGENT_REVISION_ORGANIZATION_FORBIDDEN. Requires LEADIFY_EXTERNAL_AGENT_NAME: the author is noted as that agent.", {
|
|
80
|
+
organization_id: id.describe("Organization of the message. Must be in the API key's scope."),
|
|
81
|
+
message_id: id.describe("id of the message in list_outreach_review_queue."),
|
|
82
|
+
body: z.string().min(1).max(16000).describe("The whole new message text, not a diff."),
|
|
83
|
+
subject: z.string().min(1).max(600).optional().describe("New subject, email only. Omit it to keep the current subject."),
|
|
84
|
+
expected_version: z.number().int().min(1).describe("version of the message as read in list_outreach_review_queue."),
|
|
85
|
+
idempotency_key: z.string().trim().min(8).max(200).describe("A new unique key per new text; reuse it only to replay the same call after an uncertain result."),
|
|
86
|
+
}, REVISE, (async ({ organization_id, message_id, body, subject, expected_version, idempotency_key }) => {
|
|
87
|
+
try {
|
|
88
|
+
if (!externalAgentName())
|
|
89
|
+
throw new ExternalAgentNameMissingError();
|
|
90
|
+
return compact(await client.post(`/api/outreach-review/messages/${encodeURIComponent(message_id)}/revise`, {
|
|
91
|
+
organizationId: organization_id, body, ...(subject === undefined ? {} : { subject }), expectedVersion: expected_version, idempotencyKey: idempotency_key,
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
return handleToolError(error);
|
|
96
|
+
}
|
|
97
|
+
}));
|
|
62
98
|
}
|
package/dist/tools/views.js
CHANGED
|
@@ -65,7 +65,7 @@ export function registerLeadViewTools(server) {
|
|
|
65
65
|
return handleToolError(error);
|
|
66
66
|
}
|
|
67
67
|
});
|
|
68
|
-
server.tool("create_lead_view", "Create an organization-shared saved view after previewing it. Idempotency key is required: retry the same request with the same key; changing the payload with that key conflicts. Update
|
|
68
|
+
server.tool("create_lead_view", "Create an organization-shared saved view after previewing it. Idempotency key is required: retry the same request with the same key; changing the payload with that key conflicts. Update is intentionally unavailable; delete_lead_view removes a view no campaign uses. " +
|
|
69
69
|
"Filters may use the contact fields __contact_*, with within_last_days, older_than_days and includeEmpty (see preview_lead_view).", { ...viewConfigSchema, idempotency_key: z.string().min(8).max(200) }, async ({ organization_id, lead_group_id, name, filters, columns, sort, pagination, idempotency_key }) => {
|
|
70
70
|
try {
|
|
71
71
|
const data = await getClient().post(`/api/lead-group/${encodeURIComponent(lead_group_id)}/views`, { organizationId: organization_id, name, filters, columns, sort, pagination, idempotencyKey: idempotency_key });
|
|
@@ -75,4 +75,18 @@ export function registerLeadViewTools(server) {
|
|
|
75
75
|
return handleToolError(error);
|
|
76
76
|
}
|
|
77
77
|
});
|
|
78
|
+
server.tool("delete_lead_view", "Delete one organization-shared saved view that no campaign uses. No lead is deleted. The default view cannot be deleted, and a view chosen by a campaign is refused with the list of those campaigns. Always confirm with the user first.", {
|
|
79
|
+
organization_id: z.string().min(1),
|
|
80
|
+
lead_group_id: z.string().min(1),
|
|
81
|
+
view_id: z.string().min(1),
|
|
82
|
+
confirmation: z.literal("delete_lead_view").describe("Exact acknowledgement required before deleting the view."),
|
|
83
|
+
}, async ({ organization_id, lead_group_id, view_id }) => {
|
|
84
|
+
try {
|
|
85
|
+
const data = await getClient().delete(`/api/lead-group/${encodeURIComponent(lead_group_id)}/views/${encodeURIComponent(view_id)}?organizationId=${encodeURIComponent(organization_id)}`);
|
|
86
|
+
return toolResult(data);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
return handleToolError(error);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
78
92
|
}
|