@setluca/mcp 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +316 -0
- package/LICENSE +21 -0
- package/README.md +19 -288
- package/dist/annotations.js +33 -18
- package/dist/client-address.js +64 -0
- package/dist/completions.js +24 -27
- package/dist/config.js +89 -38
- package/dist/errors.js +94 -13
- package/dist/fields.js +68 -0
- package/dist/fixed-window.js +46 -0
- package/dist/generated/openapi-inputs.js +216 -5820
- package/dist/generated/openapi-outputs.js +19014 -14001
- package/dist/generated/route-catalog.js +80 -0
- package/dist/http.js +103 -41
- package/dist/index.js +4 -1
- package/dist/oauth-challenge.js +74 -0
- package/dist/oauth-resolver.js +58 -61
- package/dist/observability.js +30 -15
- package/dist/openapi-schema.js +175 -0
- package/dist/operations/catalog.js +17 -0
- package/dist/operations/groups/analytics.js +20 -10
- package/dist/operations/groups/bookings.js +43 -15
- package/dist/operations/groups/broadcasts.js +29 -58
- package/dist/operations/groups/cadences.js +3 -1
- package/dist/operations/groups/callEvents.js +19 -72
- package/dist/operations/groups/campaigns.js +28 -32
- package/dist/operations/groups/channels.js +23 -0
- package/dist/operations/groups/conversations.js +14 -25
- package/dist/operations/groups/insights.js +4 -0
- package/dist/operations/groups/integrations.js +15 -17
- package/dist/operations/groups/knowledge.js +5 -1
- package/dist/operations/groups/leads.js +22 -23
- package/dist/operations/groups/learning.js +13 -1
- package/dist/operations/groups/memory.js +4 -6
- package/dist/operations/groups/reports.js +1 -1
- package/dist/operations/groups/reviewQueue.js +17 -14
- package/dist/operations/groups/safety.js +1 -1
- package/dist/operations/groups/voice.js +0 -1
- package/dist/operations/groups/webhooks.js +15 -3
- package/dist/operations/manifest.js +7 -6
- package/dist/operations/registry.js +102 -108
- package/dist/operations.js +4 -3
- package/dist/optional-field.js +15 -3
- package/dist/page-contract.js +74 -0
- package/dist/pagination.js +147 -67
- package/dist/prompts.js +122 -97
- package/dist/rate-limit.js +40 -0
- package/dist/remote.js +145 -65
- package/dist/resilience.js +107 -52
- package/dist/resolvers.js +125 -0
- package/dist/scopes.js +70 -17
- package/dist/serialization.js +32 -22
- package/dist/server.js +148 -132
- package/dist/standard-schema.js +215 -0
- package/dist/task-tools/analytics.js +145 -86
- package/dist/task-tools/bookings.js +168 -81
- package/dist/task-tools/cadences.js +30 -45
- package/dist/task-tools/calls.js +131 -124
- package/dist/task-tools/inbox.js +37 -47
- package/dist/task-tools/leads.js +49 -77
- package/dist/task-tools/runtime.js +75 -15
- package/dist/token-verification.js +42 -0
- package/dist/tool-cache.js +3 -2
- package/dist/tool.js +151 -96
- package/dist/version.js +1 -1
- package/package.json +37 -16
- package/dist/openapi-zod.js +0 -335
- package/dist/page-shape.js +0 -62
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -1,131 +1,78 @@
|
|
|
1
|
+
import { isJsonObject } from "../../serialization.js";
|
|
1
2
|
import { op } from "../registry.js";
|
|
3
|
+
function isNoShowReport(body) {
|
|
4
|
+
return isJsonObject(body) && body.outcome === "no_show";
|
|
5
|
+
}
|
|
2
6
|
export const callEventsOperations = [
|
|
3
7
|
op({
|
|
4
8
|
id: "callEvents.needingFeedback",
|
|
5
9
|
title: "List calls still needing a report",
|
|
6
10
|
description: "List calls whose time has passed that still need the coach to say what happened. The post-call work queue.",
|
|
7
|
-
redactedFields: [
|
|
8
|
-
"callEvents[].coachNotes",
|
|
9
|
-
"callEvents[].crmSyncError",
|
|
10
|
-
"callEvents[].feedbackFields.followUpNote",
|
|
11
|
-
"callEvents[].feedbackFields.surprise",
|
|
12
|
-
"callEvents[].leadName",
|
|
13
|
-
"callEvents[].summary",
|
|
14
|
-
"callEvents[].summaryStructured",
|
|
15
|
-
],
|
|
16
11
|
}),
|
|
17
12
|
op({
|
|
18
13
|
id: "callEvents.get",
|
|
19
14
|
title: "Get a call event",
|
|
20
15
|
description: "Read one call event: outcome, rating, summary, and CRM sync state.",
|
|
21
|
-
redactedFields: [
|
|
22
|
-
"callEvent.coachNotes",
|
|
23
|
-
"callEvent.crmSyncError",
|
|
24
|
-
"callEvent.feedbackFields.followUpNote",
|
|
25
|
-
"callEvent.feedbackFields.surprise",
|
|
26
|
-
"callEvent.leadName",
|
|
27
|
-
"callEvent.summary",
|
|
28
|
-
"callEvent.summaryStructured",
|
|
29
|
-
],
|
|
30
16
|
}),
|
|
31
17
|
op({
|
|
32
18
|
id: "callEvents.byBooking",
|
|
33
19
|
title: "Get a booking's call event",
|
|
34
20
|
description: "Find the call event attached to a booking. A booking whose call hasn't happened yet returns 404, which is normal.",
|
|
35
|
-
redactedFields: [
|
|
36
|
-
"callEvent.coachNotes",
|
|
37
|
-
"callEvent.crmSyncError",
|
|
38
|
-
"callEvent.feedbackFields.followUpNote",
|
|
39
|
-
"callEvent.feedbackFields.surprise",
|
|
40
|
-
"callEvent.leadName",
|
|
41
|
-
"callEvent.summary",
|
|
42
|
-
"callEvent.summaryStructured",
|
|
43
|
-
],
|
|
44
21
|
}),
|
|
45
22
|
op({
|
|
46
23
|
id: "callEvents.byLead",
|
|
47
24
|
title: "List a lead's call events",
|
|
48
25
|
description: "Read a lead's full call history, oldest first, so a second or third call reads in sequence.",
|
|
49
|
-
redactedFields: [
|
|
50
|
-
"callEvents[].coachNotes",
|
|
51
|
-
"callEvents[].crmSyncError",
|
|
52
|
-
"callEvents[].feedbackFields.followUpNote",
|
|
53
|
-
"callEvents[].feedbackFields.surprise",
|
|
54
|
-
"callEvents[].leadName",
|
|
55
|
-
"callEvents[].summary",
|
|
56
|
-
"callEvents[].summaryStructured",
|
|
57
|
-
],
|
|
58
26
|
}),
|
|
59
27
|
op({
|
|
60
28
|
id: "callEvents.feedback",
|
|
61
29
|
title: "Report how a call went",
|
|
30
|
+
mutatesExisting: true,
|
|
62
31
|
untrustedContent: true,
|
|
32
|
+
// Only a no_show can start recovery that messages the lead, so only that
|
|
33
|
+
// report needs approval. The other outcomes stay ungated.
|
|
34
|
+
confirm: {
|
|
35
|
+
applies: (input) => isNoShowReport(input.body),
|
|
36
|
+
description: "outcome is no_show (can start recovery messaging)",
|
|
37
|
+
},
|
|
63
38
|
description: "File the coach's report on how a call went. A completed or no_show outcome also records booking attendance, and a completed call queues its summary.",
|
|
64
|
-
redactedFields: [
|
|
65
|
-
"callEvent.coachNotes",
|
|
66
|
-
"callEvent.crmSyncError",
|
|
67
|
-
"callEvent.feedbackFields.followUpNote",
|
|
68
|
-
"callEvent.feedbackFields.surprise",
|
|
69
|
-
"callEvent.leadName",
|
|
70
|
-
"callEvent.summary",
|
|
71
|
-
"callEvent.summaryStructured",
|
|
72
|
-
],
|
|
73
39
|
}),
|
|
74
40
|
op({
|
|
75
41
|
id: "callEvents.summary.generate",
|
|
76
42
|
title: "Generate a call summary",
|
|
43
|
+
mutatesExisting: true,
|
|
77
44
|
untrustedContent: true,
|
|
78
45
|
description: "Queue a fresh summary and return straight away. Poll the call event for the result.",
|
|
79
46
|
}),
|
|
80
47
|
op({
|
|
81
48
|
id: "callEvents.summary.replace",
|
|
82
49
|
title: "Replace a call summary",
|
|
50
|
+
openWorld: true,
|
|
51
|
+
confirm: "always",
|
|
52
|
+
mutatesExisting: true,
|
|
83
53
|
untrustedContent: true,
|
|
84
54
|
description: "Replace the generated summary with the coach's own wording and push it. A summary already in the CRM updates that same note.",
|
|
85
|
-
redactedFields: [
|
|
86
|
-
"callEvent.coachNotes",
|
|
87
|
-
"callEvent.crmSyncError",
|
|
88
|
-
"callEvent.feedbackFields.followUpNote",
|
|
89
|
-
"callEvent.feedbackFields.surprise",
|
|
90
|
-
"callEvent.leadName",
|
|
91
|
-
"callEvent.summary",
|
|
92
|
-
"callEvent.summaryStructured",
|
|
93
|
-
],
|
|
94
55
|
}),
|
|
95
56
|
op({
|
|
96
57
|
id: "callEvents.pushToCrm",
|
|
97
58
|
title: "Push a call to the CRM",
|
|
59
|
+
openWorld: true,
|
|
60
|
+
confirm: "always",
|
|
61
|
+
mutatesExisting: true,
|
|
98
62
|
untrustedContent: true,
|
|
99
63
|
description: "Send the current summary to the CRM now, without waiting out the review window. Re-pushing updates the existing note.",
|
|
100
64
|
}),
|
|
101
65
|
op({
|
|
102
66
|
id: "callEvents.undoOutcome",
|
|
103
67
|
title: "Undo a call's outcome",
|
|
68
|
+
mutatesExisting: true,
|
|
104
69
|
untrustedContent: true,
|
|
105
70
|
description: "Clear a mistaken attendance mark and put the call back in the queue. The window is 48 hours from when the outcome was set.",
|
|
106
|
-
redactedFields: [
|
|
107
|
-
"callEvent.coachNotes",
|
|
108
|
-
"callEvent.crmSyncError",
|
|
109
|
-
"callEvent.feedbackFields.followUpNote",
|
|
110
|
-
"callEvent.feedbackFields.surprise",
|
|
111
|
-
"callEvent.leadName",
|
|
112
|
-
"callEvent.summary",
|
|
113
|
-
"callEvent.summaryStructured",
|
|
114
|
-
],
|
|
115
71
|
}),
|
|
116
72
|
op({
|
|
117
73
|
id: "callEvents.backfill",
|
|
118
74
|
title: "Backfill a booking's call event",
|
|
119
75
|
untrustedContent: true,
|
|
120
76
|
description: "Create a call event for a booking that predates automatic creation, then queue its summary. Safe to call twice: an existing call event comes back as is.",
|
|
121
|
-
redactedFields: [
|
|
122
|
-
"callEvent.coachNotes",
|
|
123
|
-
"callEvent.crmSyncError",
|
|
124
|
-
"callEvent.feedbackFields.followUpNote",
|
|
125
|
-
"callEvent.feedbackFields.surprise",
|
|
126
|
-
"callEvent.leadName",
|
|
127
|
-
"callEvent.summary",
|
|
128
|
-
"callEvent.summaryStructured",
|
|
129
|
-
],
|
|
130
77
|
}),
|
|
131
78
|
];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isJsonObject } from "../../serialization.js";
|
|
1
2
|
import { op } from "../registry.js";
|
|
2
3
|
export const campaignsOperations = [
|
|
3
4
|
op({
|
|
@@ -24,19 +25,20 @@ export const campaignsOperations = [
|
|
|
24
25
|
id: "campaigns.draft.update",
|
|
25
26
|
title: "Edit a campaign draft",
|
|
26
27
|
description: "Autosave a campaign draft.",
|
|
27
|
-
// The route requires an idempotency key; without this the tool never sends
|
|
28
|
-
// one and every call is rejected (the dead-tool the drift guard now flags).
|
|
29
28
|
}),
|
|
30
29
|
op({
|
|
31
30
|
id: "campaigns.enrollments.list",
|
|
32
31
|
title: "List a campaign's enrollments",
|
|
32
|
+
untrustedContent: true,
|
|
33
33
|
description: "List campaign enrollments.",
|
|
34
|
-
redactedFields: ["enrollments[].context"],
|
|
35
34
|
}),
|
|
36
35
|
op({
|
|
37
36
|
id: "campaigns.enroll",
|
|
38
37
|
title: "Enroll leads in a campaign",
|
|
39
|
-
|
|
38
|
+
openWorld: true,
|
|
39
|
+
confirm: "always",
|
|
40
|
+
// The enrollment's context is execution state pulled out of the lead's replies.
|
|
41
|
+
untrustedContent: true,
|
|
40
42
|
description: "Enroll a lead in the latest published campaign version. Triggers the live campaign's automation for that lead.",
|
|
41
43
|
}),
|
|
42
44
|
op({
|
|
@@ -54,19 +56,11 @@ export const campaignsOperations = [
|
|
|
54
56
|
title: "List campaign comment events",
|
|
55
57
|
description: "List matched, suppressed, blocked, and replayed comment automation events.",
|
|
56
58
|
// Each event carries verbatim external-commenter text (`body`,
|
|
57
|
-
// `commenterDisplayName`),
|
|
58
|
-
//
|
|
59
|
+
// `commenterDisplayName`), and the receipt audit alongside it carries the
|
|
60
|
+
// same verbatim text for comments that never became an event, so this
|
|
61
|
+
// read must be framed untrusted even though the campaigns group is not
|
|
62
|
+
// blanket-untrusted.
|
|
59
63
|
untrustedContent: true,
|
|
60
|
-
redactedFields: [
|
|
61
|
-
"events[].aiClassification",
|
|
62
|
-
"events[].body",
|
|
63
|
-
"events[].commenterDisplayName",
|
|
64
|
-
"events[].matchExplanation",
|
|
65
|
-
"events[].matchResult",
|
|
66
|
-
"events[].providerSnapshot",
|
|
67
|
-
"events[].safetyNotes",
|
|
68
|
-
"events[].sourceMetadata",
|
|
69
|
-
],
|
|
70
64
|
}),
|
|
71
65
|
op({
|
|
72
66
|
id: "campaigns.simulations.list",
|
|
@@ -76,41 +70,43 @@ export const campaignsOperations = [
|
|
|
76
70
|
op({
|
|
77
71
|
id: "campaigns.simulations.create",
|
|
78
72
|
title: "Simulate a campaign",
|
|
73
|
+
// A simulation echoes the lead record, event payload and context it ran on, and the drafts a model wrote from them.
|
|
74
|
+
untrustedContent: true,
|
|
79
75
|
description: "Run a persisted dry-run simulation without sending messages.",
|
|
80
|
-
redactedFields: [
|
|
81
|
-
"generatedDrafts[]",
|
|
82
|
-
"input.lead",
|
|
83
|
-
"trace[].generatedDraft",
|
|
84
|
-
],
|
|
85
76
|
}),
|
|
86
77
|
op({
|
|
87
78
|
id: "campaigns.simulations.get",
|
|
88
79
|
title: "Get a campaign simulation",
|
|
80
|
+
// A simulation echoes the lead record, event payload and context it ran on, and the drafts a model wrote from them.
|
|
81
|
+
untrustedContent: true,
|
|
89
82
|
description: "Get a campaign simulation run with trace.",
|
|
90
|
-
redactedFields: [
|
|
91
|
-
"generatedDrafts[]",
|
|
92
|
-
"input.lead",
|
|
93
|
-
"trace[].generatedDraft",
|
|
94
|
-
],
|
|
95
83
|
}),
|
|
96
84
|
op({
|
|
97
85
|
id: "campaigns.commentSimulationSuite.run",
|
|
98
86
|
title: "Run the comment automation suite",
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
"simulations[].trace[].generatedDraft",
|
|
102
|
-
],
|
|
87
|
+
// Each simulation echoes the lead record, event payload and context it ran on, and the drafts a model wrote from them.
|
|
88
|
+
untrustedContent: true,
|
|
103
89
|
description: "Run the Instagram comment-to-DM simulation suite against a campaign. Any campaign that uses comment automation has to pass it before it can publish, so run this before reaching for publish.",
|
|
104
90
|
}),
|
|
105
91
|
op({
|
|
106
92
|
id: "campaigns.publish",
|
|
107
93
|
title: "Publish a campaign",
|
|
108
|
-
|
|
94
|
+
openWorld: true,
|
|
95
|
+
mutatesExisting: true,
|
|
96
|
+
confirm: "always",
|
|
109
97
|
description: "Freeze the draft as a new immutable version and point new enrollments at it. Published versions never change, and enrollments against a live campaign reach real leads. Publish is blocked when the target provider cannot do something the campaign needs, so read the provider capabilities first.",
|
|
110
98
|
}),
|
|
111
99
|
op({
|
|
112
100
|
id: "campaigns.setStatus",
|
|
113
101
|
title: "Pause, resume, or archive a campaign",
|
|
102
|
+
openWorld: true,
|
|
103
|
+
mutatesExisting: true,
|
|
104
|
+
// Resuming restarts the campaign's automatic sends to real leads. Pausing
|
|
105
|
+
// and archiving only stop work, so they need no confirmation.
|
|
106
|
+
confirm: {
|
|
107
|
+
applies: (input) => isJsonObject(input.body) && input.body.status === "published",
|
|
108
|
+
description: "status is published (resuming sends)",
|
|
109
|
+
},
|
|
114
110
|
description: "Pause, resume, or archive a campaign. A transition that does not make sense for the campaign's current state is rejected rather than forced through.",
|
|
115
111
|
}),
|
|
116
112
|
op({
|
|
@@ -126,7 +122,7 @@ export const campaignsOperations = [
|
|
|
126
122
|
op({
|
|
127
123
|
id: "campaigns.enrollments.timeline",
|
|
128
124
|
title: "Read an enrollment timeline",
|
|
129
|
-
|
|
125
|
+
untrustedContent: true,
|
|
130
126
|
description: "Read every step one enrollment has been through: what got sent, when, and how the lead responded.",
|
|
131
127
|
}),
|
|
132
128
|
];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { op } from "../registry.js";
|
|
2
|
+
export const channelsOperations = [
|
|
3
|
+
op({
|
|
4
|
+
id: "channels.list",
|
|
5
|
+
title: "List channel accounts",
|
|
6
|
+
description: "List the coach's current channel accounts, one per provider account, with each one's status, health, and what the composer can send through it. Connecting, pausing, and repairing an account stay in the Luca app.",
|
|
7
|
+
}),
|
|
8
|
+
op({
|
|
9
|
+
id: "channels.catalog",
|
|
10
|
+
title: "List available channels",
|
|
11
|
+
description: "List every channel Luca supports and whether it can be connected yet. Availability is per channel, not per coach.",
|
|
12
|
+
}),
|
|
13
|
+
op({
|
|
14
|
+
id: "channels.features",
|
|
15
|
+
title: "List channel features",
|
|
16
|
+
description: "List what each provider supports, from sending media to interactive replies, and what still blocks a feature: a permission, a webhook field, or a provider approval. Check this before telling the coach a channel can do something.",
|
|
17
|
+
}),
|
|
18
|
+
op({
|
|
19
|
+
id: "channels.get",
|
|
20
|
+
title: "Get a channel account",
|
|
21
|
+
description: "Read one channel account: status, health, cleanup state after a disconnect, and the actions the coach can take on it. Older rows stay readable by ID.",
|
|
22
|
+
}),
|
|
23
|
+
];
|
|
@@ -4,45 +4,34 @@ export const conversationsOperations = [
|
|
|
4
4
|
id: "conversations.list",
|
|
5
5
|
title: "List conversations",
|
|
6
6
|
description: "List recent conversations. Query supports limit, cursor, channel, and q.",
|
|
7
|
-
redactedFields: [
|
|
8
|
-
"conversations[].lastMessagePreview",
|
|
9
|
-
"conversations[].leadDisplayName",
|
|
10
|
-
],
|
|
11
7
|
}),
|
|
12
8
|
op({
|
|
13
9
|
id: "conversations.get",
|
|
14
10
|
title: "Get a conversation",
|
|
15
11
|
description: "Get a conversation with messages and lead context.",
|
|
16
|
-
redactedFields: [
|
|
17
|
-
"conversation.qualificationProgress",
|
|
18
|
-
"copilotContext.leadDisplayName",
|
|
19
|
-
"lead.customFields",
|
|
20
|
-
"lead.displayName",
|
|
21
|
-
"lead.externalUserId",
|
|
22
|
-
"messages[].body",
|
|
23
|
-
"messages[].media",
|
|
24
|
-
"messages[].mediaUrls",
|
|
25
|
-
"messages[].rawPayload",
|
|
26
|
-
"pendingReview.criticIssues",
|
|
27
|
-
"pendingReview.draftBody",
|
|
28
|
-
"pendingReview.qualification",
|
|
29
|
-
],
|
|
30
12
|
}),
|
|
31
13
|
op({
|
|
32
14
|
id: "conversations.messages",
|
|
33
15
|
title: "Read a conversation's messages",
|
|
34
16
|
description: "Page backward through a thread's older messages, for threads longer than the window the conversation read returns.",
|
|
35
|
-
redactedFields: [
|
|
36
|
-
"messages[].body",
|
|
37
|
-
"messages[].media",
|
|
38
|
-
"messages[].mediaUrls",
|
|
39
|
-
"messages[].rawPayload",
|
|
40
|
-
],
|
|
41
17
|
}),
|
|
42
18
|
op({
|
|
43
19
|
id: "conversations.send",
|
|
20
|
+
// Answers with ids and status flags only; no lead-authored text.
|
|
21
|
+
untrustedContent: false,
|
|
44
22
|
title: "Send a message to a lead",
|
|
45
|
-
|
|
23
|
+
openWorld: true,
|
|
24
|
+
confirm: "always",
|
|
46
25
|
description: "Send a message to the lead on their channel right away. This is the one conversation tool that reaches a real person without a reviewer seeing the message first, which is why it needs an explicit confirm. Prefer drafting into the review queue unless the coach has asked for this message to go out now.",
|
|
47
26
|
}),
|
|
27
|
+
op({
|
|
28
|
+
id: "conversations.partial_send_retry",
|
|
29
|
+
// Answers with ids and status flags only; no lead-authored text.
|
|
30
|
+
untrustedContent: false,
|
|
31
|
+
title: "Retry unsent message parts",
|
|
32
|
+
openWorld: true,
|
|
33
|
+
mutatesExisting: true,
|
|
34
|
+
confirm: "always",
|
|
35
|
+
description: "Retry only the parts of a partially sent message that are known not to have reached the provider.",
|
|
36
|
+
}),
|
|
48
37
|
];
|
|
@@ -8,16 +8,19 @@ export const insightsOperations = [
|
|
|
8
8
|
op({
|
|
9
9
|
id: "insights.markRead",
|
|
10
10
|
title: "Mark an insight read",
|
|
11
|
+
mutatesExisting: true,
|
|
11
12
|
description: "Mark one insight read, so it stops coming back at the top of the feed.",
|
|
12
13
|
}),
|
|
13
14
|
op({
|
|
14
15
|
id: "insights.dismiss",
|
|
15
16
|
title: "Dismiss an insight",
|
|
17
|
+
mutatesExisting: true,
|
|
16
18
|
description: "Dismiss one insight the coach does not want to act on. Use this rather than leaving it unread forever.",
|
|
17
19
|
}),
|
|
18
20
|
op({
|
|
19
21
|
id: "insights.markActioned",
|
|
20
22
|
title: "Mark an insight actioned",
|
|
23
|
+
mutatesExisting: true,
|
|
21
24
|
description: "Record that the thing an insight asked for was actually done. Use it after doing the work, not instead of it.",
|
|
22
25
|
}),
|
|
23
26
|
op({
|
|
@@ -28,6 +31,7 @@ export const insightsOperations = [
|
|
|
28
31
|
op({
|
|
29
32
|
id: "insights.settings.update",
|
|
30
33
|
title: "Update alert delivery settings",
|
|
34
|
+
mutatesExisting: true,
|
|
31
35
|
description: "Merge-patch the coach's alert-delivery settings. Send only what you are changing; anything left out keeps its current value.",
|
|
32
36
|
}),
|
|
33
37
|
];
|
|
@@ -10,11 +10,6 @@ export const integrationsOperations = [
|
|
|
10
10
|
title: "Start a CRM connection",
|
|
11
11
|
description: "Create a CRM provider OAuth URL.",
|
|
12
12
|
}),
|
|
13
|
-
op({
|
|
14
|
-
id: "integrations.crm.oauthCallback",
|
|
15
|
-
title: "Finish a CRM connection",
|
|
16
|
-
description: "Exchange a CRM OAuth code and store encrypted credentials.",
|
|
17
|
-
}),
|
|
18
13
|
op({
|
|
19
14
|
id: "integrations.crm.connections.list",
|
|
20
15
|
title: "List CRM connections",
|
|
@@ -23,13 +18,15 @@ export const integrationsOperations = [
|
|
|
23
18
|
op({
|
|
24
19
|
id: "integrations.crm.connections.create",
|
|
25
20
|
title: "Create a CRM connection",
|
|
21
|
+
mutatesExisting: true,
|
|
26
22
|
description: "Create or update a workspace-scoped CRM connection.",
|
|
27
23
|
}),
|
|
28
24
|
op({
|
|
29
25
|
id: "integrations.crm.connections.schema",
|
|
30
26
|
title: "Read a CRM's field schema",
|
|
27
|
+
// sampleRecords are literal contact and deal rows from the coach's CRM, which leads and other people filled in.
|
|
28
|
+
untrustedContent: true,
|
|
31
29
|
description: "Discover CRM provider schema.",
|
|
32
|
-
redactedFields: ["sampleRecords"],
|
|
33
30
|
}),
|
|
34
31
|
op({
|
|
35
32
|
id: "integrations.crm.connections.health",
|
|
@@ -39,13 +36,11 @@ export const integrationsOperations = [
|
|
|
39
36
|
op({
|
|
40
37
|
id: "integrations.crm.connections.webhooksSetup",
|
|
41
38
|
title: "Set up a CRM's webhooks",
|
|
39
|
+
openWorld: true,
|
|
40
|
+
confirm: "always",
|
|
41
|
+
mutatesExisting: true,
|
|
42
42
|
description: "Configure CRM provider webhook status.",
|
|
43
43
|
}),
|
|
44
|
-
op({
|
|
45
|
-
id: "integrations.crm.webhooks.record",
|
|
46
|
-
title: "Record a CRM webhook",
|
|
47
|
-
description: "Record an inbound CRM provider webhook event. Idempotent on the provider's event id, so a redelivery is recorded once.",
|
|
48
|
-
}),
|
|
49
44
|
op({
|
|
50
45
|
id: "integrations.crm.mappings.suggest",
|
|
51
46
|
title: "Suggest CRM field mappings",
|
|
@@ -54,27 +49,30 @@ export const integrationsOperations = [
|
|
|
54
49
|
op({
|
|
55
50
|
id: "integrations.crm.mappings.confirm",
|
|
56
51
|
title: "Confirm CRM field mappings",
|
|
52
|
+
mutatesExisting: true,
|
|
57
53
|
description: "Confirm CRM field mappings.",
|
|
58
54
|
}),
|
|
59
55
|
op({
|
|
60
56
|
id: "integrations.crm.syncRuns.list",
|
|
61
57
|
title: "List CRM sync runs",
|
|
58
|
+
// Mutations carry CRM record values: names, emails, notes.
|
|
59
|
+
untrustedContent: true,
|
|
62
60
|
description: "List CRM sync history.",
|
|
63
|
-
redactedFields: [
|
|
64
|
-
"syncRuns[].appliedMutations[].fields",
|
|
65
|
-
"syncRuns[].plannedMutations[].fields",
|
|
66
|
-
],
|
|
67
61
|
}),
|
|
68
62
|
op({
|
|
69
63
|
id: "integrations.crm.syncRuns.create",
|
|
70
64
|
title: "Start a CRM sync",
|
|
65
|
+
openWorld: true,
|
|
66
|
+
confirm: "always",
|
|
67
|
+
// Mutations carry CRM record values: names, emails, notes.
|
|
68
|
+
untrustedContent: true,
|
|
71
69
|
description: "Create a CRM dry-run or live sync run.",
|
|
72
|
-
redactedFields: ["appliedMutations[].fields", "plannedMutations[].fields"],
|
|
73
70
|
}),
|
|
74
71
|
op({
|
|
75
72
|
id: "integrations.events.list",
|
|
76
73
|
title: "List integration events",
|
|
74
|
+
// Each event holds the raw webhook or CRM body, which can carry contact fields verbatim.
|
|
75
|
+
untrustedContent: true,
|
|
77
76
|
description: "List integration event log.",
|
|
78
|
-
redactedFields: ["events[].payload"],
|
|
79
77
|
}),
|
|
80
78
|
];
|
|
@@ -8,12 +8,14 @@ export const knowledgeOperations = [
|
|
|
8
8
|
op({
|
|
9
9
|
id: "knowledge.approveSuggestion",
|
|
10
10
|
title: "Approve a knowledge suggestion",
|
|
11
|
-
|
|
11
|
+
mutatesExisting: true,
|
|
12
|
+
confirm: "always",
|
|
12
13
|
description: "Approve a profile suggestion and write it into the coach's profile. Every later draft reads the result, so check the excerpt supports the change before approving.",
|
|
13
14
|
}),
|
|
14
15
|
op({
|
|
15
16
|
id: "knowledge.rejectSuggestion",
|
|
16
17
|
title: "Reject a knowledge suggestion",
|
|
18
|
+
mutatesExisting: true,
|
|
17
19
|
description: "Reject a profile suggestion so it stops being offered. The profile is left as it stands.",
|
|
18
20
|
}),
|
|
19
21
|
op({
|
|
@@ -24,11 +26,13 @@ export const knowledgeOperations = [
|
|
|
24
26
|
op({
|
|
25
27
|
id: "knowledge.sources.sync",
|
|
26
28
|
title: "Re-sync a knowledge source",
|
|
29
|
+
mutatesExisting: true,
|
|
27
30
|
description: "Queue a re-read of one knowledge source now rather than waiting for its next scheduled sync. It returns as soon as the job is queued, so any new suggestions appear on a later read.",
|
|
28
31
|
}),
|
|
29
32
|
op({
|
|
30
33
|
id: "knowledge.sources.disconnect",
|
|
31
34
|
title: "Disconnect a knowledge source",
|
|
35
|
+
mutatesExisting: true,
|
|
32
36
|
description: "Delete a knowledge source. Its pending suggestions go with it; values the coach already approved stay in the profile, because approving copies them there.",
|
|
33
37
|
}),
|
|
34
38
|
];
|
|
@@ -4,47 +4,27 @@ export const leadsOperations = [
|
|
|
4
4
|
id: "leads.list",
|
|
5
5
|
title: "List leads",
|
|
6
6
|
description: "List leads. Query supports limit, cursor, channel, and q according to Luca OpenAPI.",
|
|
7
|
-
redactedFields: ["leads[].displayName", "leads[].externalUserId"],
|
|
8
7
|
}),
|
|
9
8
|
op({
|
|
10
9
|
id: "leads.get",
|
|
11
10
|
title: "Get a lead's timeline",
|
|
12
11
|
description: "Get a lead's full timeline and notes.",
|
|
13
|
-
redactedFields: [
|
|
14
|
-
"identities[].displayName",
|
|
15
|
-
"identities[].externalUserId",
|
|
16
|
-
"lead.customFields",
|
|
17
|
-
"lead.displayName",
|
|
18
|
-
"messages[].body",
|
|
19
|
-
"messages[].media",
|
|
20
|
-
"notes[].body",
|
|
21
|
-
],
|
|
22
12
|
}),
|
|
23
13
|
op({
|
|
24
14
|
id: "leads.explain",
|
|
25
15
|
title: "Explain why a lead qualified",
|
|
26
16
|
description: "Explain why a lead qualified: the qualifier's reasoning, signals, and confidence from its most recent drafted turn.",
|
|
27
|
-
redactedFields: ["qualification"],
|
|
28
17
|
}),
|
|
29
18
|
op({
|
|
30
19
|
id: "leads.create",
|
|
31
20
|
title: "Create or upsert a lead",
|
|
21
|
+
mutatesExisting: true,
|
|
32
22
|
description: "Create or upsert a lead identity. Body requires channel and externalUserId.",
|
|
33
|
-
redactedFields: [
|
|
34
|
-
"lead.customFields",
|
|
35
|
-
"lead.displayName",
|
|
36
|
-
"lead.externalUserId",
|
|
37
|
-
],
|
|
38
23
|
}),
|
|
39
24
|
op({
|
|
40
25
|
id: "leads.update",
|
|
41
26
|
title: "Update a lead",
|
|
42
27
|
description: "Update a lead's display name, stage, score, tags, or custom fields.",
|
|
43
|
-
redactedFields: [
|
|
44
|
-
"lead.customFields",
|
|
45
|
-
"lead.displayName",
|
|
46
|
-
"lead.externalUserId",
|
|
47
|
-
],
|
|
48
28
|
}),
|
|
49
29
|
op({
|
|
50
30
|
id: "leads.fieldDefinitions.list",
|
|
@@ -53,7 +33,10 @@ export const leadsOperations = [
|
|
|
53
33
|
}),
|
|
54
34
|
op({
|
|
55
35
|
id: "leads.fieldDefinitions.upsert",
|
|
36
|
+
// Echoes the coach's own field definition back.
|
|
37
|
+
untrustedContent: false,
|
|
56
38
|
title: "Define a custom lead field",
|
|
39
|
+
mutatesExisting: true,
|
|
57
40
|
description: "Create or update a custom lead field definition.",
|
|
58
41
|
}),
|
|
59
42
|
op({
|
|
@@ -64,37 +47,53 @@ export const leadsOperations = [
|
|
|
64
47
|
op({
|
|
65
48
|
id: "leads.importApply",
|
|
66
49
|
title: "Apply a lead import",
|
|
50
|
+
mutatesExisting: true,
|
|
51
|
+
// Writes many leads and their consent records at once.
|
|
52
|
+
confirm: "always",
|
|
67
53
|
description: "Apply a mapped lead import with consent evidence.",
|
|
68
54
|
}),
|
|
69
55
|
op({
|
|
70
56
|
id: "leads.identities.attach",
|
|
71
57
|
title: "Attach a channel identity to a lead",
|
|
58
|
+
mutatesExisting: true,
|
|
72
59
|
description: "Attach a verified or claimed identity to a lead.",
|
|
73
60
|
}),
|
|
74
61
|
op({
|
|
75
62
|
id: "leads.consents.grant",
|
|
63
|
+
// Echoes the consent record the coach filed.
|
|
64
|
+
untrustedContent: false,
|
|
76
65
|
title: "Record a lead's consent",
|
|
66
|
+
mutatesExisting: true,
|
|
67
|
+
// Consent is what lets Luca message the lead on that channel.
|
|
68
|
+
confirm: "always",
|
|
69
|
+
openWorld: true,
|
|
77
70
|
description: "Grant explicit lead consent for a channel purpose.",
|
|
78
71
|
}),
|
|
79
72
|
op({
|
|
80
73
|
id: "leads.consents.revoke",
|
|
74
|
+
// Echoes the consent record the coach revoked.
|
|
75
|
+
untrustedContent: false,
|
|
81
76
|
title: "Revoke a lead's consent",
|
|
77
|
+
mutatesExisting: true,
|
|
82
78
|
description: "Revoke explicit lead consent for a channel purpose.",
|
|
83
79
|
}),
|
|
84
80
|
op({
|
|
85
81
|
id: "leads.notes.add",
|
|
82
|
+
// Echoes the coach's own note back.
|
|
83
|
+
untrustedContent: false,
|
|
86
84
|
title: "Add a note to a lead",
|
|
87
85
|
description: "Append a coach note to a lead. Body requires body.",
|
|
88
|
-
redactedFields: ["note.body"],
|
|
89
86
|
}),
|
|
90
87
|
op({
|
|
91
88
|
id: "leads.draft",
|
|
92
89
|
title: "Draft a reply to a lead",
|
|
90
|
+
untrustedContent: true,
|
|
93
91
|
description: "Draft a voice-matched reply for a lead on demand and queue it for review. Optional guidance steers tone or content.",
|
|
94
|
-
redactedFields: ["draft.body"],
|
|
95
92
|
}),
|
|
96
93
|
op({
|
|
97
94
|
id: "leads.notes.delete",
|
|
95
|
+
// Answers with ids and status flags only; no lead-authored text.
|
|
96
|
+
untrustedContent: false,
|
|
98
97
|
title: "Delete a lead note",
|
|
99
98
|
description: "Remove one coach note from a lead and leave the rest of the timeline alone. This is how a note filed by mistake gets taken back.",
|
|
100
99
|
}),
|
|
@@ -4,13 +4,22 @@ export const learningOperations = [
|
|
|
4
4
|
id: "learning.panel",
|
|
5
5
|
title: "Read what Luca wants to learn",
|
|
6
6
|
description: "Read which of the three profile questions Luca still owes an answer to, with suggested answers inferred from the coach's own words. The worked example pair is blanked for a redacted-tier key.",
|
|
7
|
-
redactedFields: ["exampleDm", "exampleDraft"],
|
|
8
7
|
}),
|
|
9
8
|
op({
|
|
10
9
|
id: "learning.frameworkPerformance",
|
|
11
10
|
title: "Read framework objection performance",
|
|
12
11
|
description: "Win rate by objection bucket for the sales framework the coach has configured right now, over a rolling window. These numbers are correlational \u2014 read them as a signal, not as proof a framework caused the result.",
|
|
13
12
|
}),
|
|
13
|
+
op({
|
|
14
|
+
id: "learning.abTests.list",
|
|
15
|
+
title: "List A/B tests",
|
|
16
|
+
description: "List the coach's A/B tests, newest first, optionally filtered by status. Variant config comes back as stored, since its shape depends on what the test compares. Creating, starting, stopping, and adopting a test stay in the Luca app.",
|
|
17
|
+
}),
|
|
18
|
+
op({
|
|
19
|
+
id: "learning.abTests.get",
|
|
20
|
+
title: "Get an A/B test",
|
|
21
|
+
description: "Read one A/B test with its per-variant numbers: how many leads each arm saw, and how each is doing against the success metric. Small samples swing widely, so say how many leads each arm has before calling a winner.",
|
|
22
|
+
}),
|
|
14
23
|
op({
|
|
15
24
|
id: "learning.proposals.list",
|
|
16
25
|
title: "List learning proposals",
|
|
@@ -18,7 +27,10 @@ export const learningOperations = [
|
|
|
18
27
|
}),
|
|
19
28
|
op({
|
|
20
29
|
id: "learning.proposals.dismiss",
|
|
30
|
+
// Answers with ids and status flags only; no lead-authored text.
|
|
31
|
+
untrustedContent: false,
|
|
21
32
|
title: "Dismiss a learning proposal",
|
|
33
|
+
mutatesExisting: true,
|
|
22
34
|
description: "Dismiss one learning proposal so it drops out of the default list. Use it when the coach has decided against the change, not when you have simply read it.",
|
|
23
35
|
}),
|
|
24
36
|
];
|