@channlize/mcp 0.3.0 → 0.5.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 +56 -0
- package/README.md +152 -11
- package/dist/agents.d.mts +84 -0
- package/dist/agents.d.ts +84 -0
- package/dist/agents.js +821 -0
- package/dist/agents.js.map +1 -0
- package/dist/agents.mjs +784 -0
- package/dist/agents.mjs.map +1 -0
- package/dist/inbox-BfpFVoK7.d.mts +194 -0
- package/dist/inbox-BfpFVoK7.d.ts +194 -0
- package/dist/inbox.d.mts +3 -0
- package/dist/inbox.d.ts +3 -0
- package/dist/inbox.js +587 -0
- package/dist/inbox.js.map +1 -0
- package/dist/inbox.mjs +547 -0
- package/dist/inbox.mjs.map +1 -0
- package/dist/index.d.mts +22 -72
- package/dist/index.d.ts +22 -72
- package/dist/index.js +1628 -238
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1610 -231
- package/dist/index.mjs.map +1 -1
- package/dist/server.js +1542 -239
- package/dist/server.js.map +1 -1
- package/package.json +14 -5
package/dist/index.js
CHANGED
|
@@ -30,16 +30,41 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
AGENT_TASK_STATUSES: () => AGENT_TASK_STATUSES,
|
|
34
|
+
AGENT_TOOLS: () => AGENT_TOOLS,
|
|
35
|
+
AGENT_TOOL_NAMES: () => AGENT_TOOL_NAMES,
|
|
36
|
+
AuthenticationError: () => import_core4.AuthenticationError,
|
|
37
|
+
CONVERSATION_PRIORITIES: () => CONVERSATION_PRIORITIES,
|
|
38
|
+
CONVERSATION_SORTS: () => CONVERSATION_SORTS,
|
|
39
|
+
CONVERSATION_STATUSES: () => CONVERSATION_STATUSES,
|
|
40
|
+
ChannlizeApi: () => ChannlizeApi,
|
|
41
|
+
ChannlizeError: () => import_core4.ChannlizeError,
|
|
33
42
|
ChannlizeMCPServer: () => ChannlizeMCPServer,
|
|
34
43
|
ConfigError: () => ConfigError,
|
|
44
|
+
INBOX_TOOLS: () => INBOX_TOOLS,
|
|
45
|
+
INBOX_TOOL_NAMES: () => INBOX_TOOL_NAMES,
|
|
46
|
+
QUESTION_WAIT_MAX_SECONDS: () => QUESTION_WAIT_MAX_SECONDS,
|
|
47
|
+
ROUTING_FIELDS: () => ROUTING_FIELDS,
|
|
48
|
+
ROUTING_OPERATORS: () => ROUTING_OPERATORS,
|
|
49
|
+
RateLimitError: () => import_core4.RateLimitError,
|
|
35
50
|
ToolError: () => ToolError,
|
|
36
51
|
VERSION: () => VERSION,
|
|
52
|
+
ValidationError: () => import_core4.ValidationError,
|
|
53
|
+
agentToolsAsJsonSchema: () => agentToolsAsJsonSchema,
|
|
37
54
|
buildServer: () => buildServer,
|
|
38
55
|
configFromEnv: () => configFromEnv,
|
|
39
56
|
createMCPServer: () => createMCPServer,
|
|
40
57
|
default: () => index_default,
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
getAgentTool: () => getAgentTool,
|
|
59
|
+
getInboxTool: () => getInboxTool,
|
|
60
|
+
inboxToolsAsJsonSchema: () => inboxToolsAsJsonSchema,
|
|
61
|
+
keyKind: () => import_core2.keyKind,
|
|
62
|
+
registerAgentTools: () => registerAgentTools,
|
|
63
|
+
registerInboxTools: () => registerInboxTools,
|
|
64
|
+
resolveConfig: () => resolveConfig,
|
|
65
|
+
runAgentTool: () => runAgentTool,
|
|
66
|
+
runInboxTool: () => runInboxTool,
|
|
67
|
+
toToolError: () => toToolError
|
|
43
68
|
});
|
|
44
69
|
module.exports = __toCommonJS(index_exports);
|
|
45
70
|
var import_server2 = require("@modelcontextprotocol/server");
|
|
@@ -48,21 +73,108 @@ var import_stdio = require("@modelcontextprotocol/server/stdio");
|
|
|
48
73
|
// src/api.ts
|
|
49
74
|
var import_core = require("@channlize/core");
|
|
50
75
|
var ToolError = class extends Error {
|
|
51
|
-
constructor(
|
|
52
|
-
super(
|
|
76
|
+
constructor(message2, statusCode, code, retryAt) {
|
|
77
|
+
super(message2);
|
|
53
78
|
this.statusCode = statusCode;
|
|
54
79
|
this.code = code;
|
|
80
|
+
this.retryAt = retryAt;
|
|
55
81
|
this.name = "ToolError";
|
|
56
82
|
}
|
|
57
83
|
statusCode;
|
|
58
84
|
code;
|
|
85
|
+
retryAt;
|
|
59
86
|
};
|
|
60
|
-
function
|
|
87
|
+
function scopeFor(apiMessage) {
|
|
88
|
+
const perm = /permission:\s*([\w:*-]+)/i.exec(apiMessage)?.[1] ?? "";
|
|
89
|
+
switch (perm) {
|
|
90
|
+
case "inbox:read":
|
|
91
|
+
return "inbox:read (or read)";
|
|
92
|
+
case "inbox:write":
|
|
93
|
+
return "inbox:write";
|
|
94
|
+
case "routing:read":
|
|
95
|
+
return "routing:write, inbox:read (or read)";
|
|
96
|
+
case "routing:write":
|
|
97
|
+
return "routing:write";
|
|
98
|
+
case "analytics:read":
|
|
99
|
+
return "analytics:read (or read)";
|
|
100
|
+
case "agents:send":
|
|
101
|
+
return "agents:send";
|
|
102
|
+
case "agents:read":
|
|
103
|
+
return "agents:read";
|
|
104
|
+
case "agents:ask_human":
|
|
105
|
+
return "agents:ask_human";
|
|
106
|
+
case "agents:admin":
|
|
107
|
+
return "agents:admin (an unbound key)";
|
|
108
|
+
default:
|
|
109
|
+
return /write|notify/.test(apiMessage) ? "write" : "read";
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function retryText(retryAt) {
|
|
113
|
+
return retryAt ? ` Retry after ${retryAt}, not before.` : "";
|
|
114
|
+
}
|
|
115
|
+
function agentHint(code, body = {}) {
|
|
116
|
+
const retryAt = typeof body.retryAt === "string" ? body.retryAt : void 0;
|
|
117
|
+
switch (code) {
|
|
118
|
+
case "AGENT_KEY_REQUIRED":
|
|
119
|
+
return "Agent tools need an agent key or a multi-agent key: create one under SDK & Integration \u2192 API keys and pick the agents.";
|
|
120
|
+
case "AGENT_NOT_ALLOWED":
|
|
121
|
+
return "The key can't act as that agent: pass one of the key's agents as agentId (list_agents), or fix CHANNLIZE_AGENT_ID.";
|
|
122
|
+
case "NOT_A_CHANNEL_MEMBER":
|
|
123
|
+
return "Only channel members can post (observers only read): use list_agent_channels, or ask an admin to add the agent to the channel.";
|
|
124
|
+
case "AGENT_PAUSED":
|
|
125
|
+
case "AGENT_RETIRED":
|
|
126
|
+
return "Only active agents can act: ask an admin to reactivate it in the dashboard.";
|
|
127
|
+
case "AGENT_RATE_LIMITED":
|
|
128
|
+
return "The agent is sending too fast: wait a minute before sending again.";
|
|
129
|
+
case "AGENT_THREAD_PAUSED": {
|
|
130
|
+
const why = body.reason === "ping_pong" ? " (two agents were going back and forth)" : body.reason === "duplicate_content" ? " (the same message kept being posted)" : "";
|
|
131
|
+
return `The loop guard paused this thread${why}: stop sending in it until a person replies or resumes it. Don't rephrase and retry.`;
|
|
132
|
+
}
|
|
133
|
+
case "HUMAN_TAKEOVER":
|
|
134
|
+
return `A person took over this thread: don't post in it until the takeover ends or they hand it back.${retryText(retryAt)}`;
|
|
135
|
+
case "POLICY_RATE_LIMITED":
|
|
136
|
+
case "POLICY_BUDGET_EXHAUSTED":
|
|
137
|
+
case "POLICY_OUTSIDE_WORKING_HOURS":
|
|
138
|
+
return `The agent's policy refuses this for now: wait, don't retry in a loop.${retryText(retryAt)}`;
|
|
139
|
+
case "POLICY_CHANNEL_NOT_ALLOWED":
|
|
140
|
+
case "POLICY_DM_NOT_ALLOWED":
|
|
141
|
+
case "POLICY_ASK_HUMAN_NOT_ALLOWED":
|
|
142
|
+
case "POLICY_URGENCY_NOT_ALLOWED":
|
|
143
|
+
case "POLICY_MAX_DEPTH":
|
|
144
|
+
return "The agent's policy forbids this: retrying won't help. Pick another channel, agent or urgency, or ask an admin to change the policy.";
|
|
145
|
+
case "TASK_CLOSED":
|
|
146
|
+
return "The task is closed (completed, failed, canceled or rejected) and cannot change: create a new task instead.";
|
|
147
|
+
case "ALREADY_ASSIGNED":
|
|
148
|
+
return "The task is already assigned to that agent.";
|
|
149
|
+
case "AGENT_NOT_IN_CHANNEL":
|
|
150
|
+
return "The other agent isn't a member of the task's channel: pick a member (list_agent_channels), or ask an admin to add it.";
|
|
151
|
+
case "NOT_TASK_PARTICIPANT":
|
|
152
|
+
return "Only the task's assignee or creator can change it or hand it off.";
|
|
153
|
+
case "QUESTION_CLOSED":
|
|
154
|
+
return "The question is already closed (answered, expired or canceled): the first answer wins. Read it with get_answer.";
|
|
155
|
+
case "APPROVAL_ROLE_REQUIRED":
|
|
156
|
+
return "This approval needs a person with the required role: an agent cannot answer it.";
|
|
157
|
+
case "AGENT_ADMIN_KEY_REQUIRED":
|
|
158
|
+
return "This needs an unbound agents:admin key (not a key bound to agents): create one under SDK & Integration \u2192 API keys.";
|
|
159
|
+
case "INVALID_ANSWER":
|
|
160
|
+
return "Answer with one of the question's option ids (approve / deny for approvals), or text when free text is allowed.";
|
|
161
|
+
default:
|
|
162
|
+
return void 0;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function hintFor(status, apiMessage, code, body) {
|
|
166
|
+
const agent = agentHint(code, body);
|
|
167
|
+
if (agent) return `${apiMessage}. ${agent}`;
|
|
168
|
+
if (status === 400 && /agent_id is required/i.test(apiMessage)) {
|
|
169
|
+
return `${apiMessage}. Pass agentId (from list_agents), or set CHANNLIZE_AGENT_ID / --agent-id on the MCP server.`;
|
|
170
|
+
}
|
|
61
171
|
switch (status) {
|
|
62
172
|
case 401:
|
|
63
173
|
return `${apiMessage}. The server API key is invalid, revoked or expired: create a new one under SDK & Integration \u2192 API keys and update CHANNLIZE_SERVER_API_KEY.`;
|
|
64
174
|
case 403:
|
|
65
|
-
return /permission/i.test(apiMessage) ? `${apiMessage}. This needs a server API key with the ${
|
|
175
|
+
return /permission/i.test(apiMessage) ? `${apiMessage}. This needs a server API key with the ${scopeFor(
|
|
176
|
+
apiMessage
|
|
177
|
+
)} scope: create one under SDK & Integration \u2192 API keys.` : apiMessage;
|
|
66
178
|
case 404:
|
|
67
179
|
return `${apiMessage}. Check the ID (and CHANNLIZE_PROJECT_ID): it must belong to the key's organization.`;
|
|
68
180
|
case 429:
|
|
@@ -75,9 +187,15 @@ function toToolError(err) {
|
|
|
75
187
|
if (err instanceof ToolError) return err;
|
|
76
188
|
if (err instanceof import_core.ChannlizeError) {
|
|
77
189
|
return new ToolError(
|
|
78
|
-
err.statusCode ? hintFor(
|
|
190
|
+
err.statusCode ? hintFor(
|
|
191
|
+
err.statusCode,
|
|
192
|
+
err.message.replace(/\.$/, ""),
|
|
193
|
+
err.code,
|
|
194
|
+
err.details
|
|
195
|
+
) : err.message,
|
|
79
196
|
err.statusCode,
|
|
80
|
-
err.code
|
|
197
|
+
err.code,
|
|
198
|
+
err.retryAt
|
|
81
199
|
);
|
|
82
200
|
}
|
|
83
201
|
return new ToolError(err instanceof Error ? err.message : String(err));
|
|
@@ -95,6 +213,10 @@ var ChannlizeApi = class {
|
|
|
95
213
|
get hasWidgetKey() {
|
|
96
214
|
return !!this.config.widgetKey;
|
|
97
215
|
}
|
|
216
|
+
/** The default agent for the agent tools (`CHANNLIZE_AGENT_ID`). */
|
|
217
|
+
get agentId() {
|
|
218
|
+
return this.config.agentId;
|
|
219
|
+
}
|
|
98
220
|
requireServerKey() {
|
|
99
221
|
if (!this.config.serverApiKey) {
|
|
100
222
|
throw new ToolError(
|
|
@@ -103,9 +225,10 @@ var ChannlizeApi = class {
|
|
|
103
225
|
}
|
|
104
226
|
return this.config.serverApiKey;
|
|
105
227
|
}
|
|
106
|
-
async request(method,
|
|
228
|
+
async request(method, path2, options = {}) {
|
|
107
229
|
const key = this.requireServerKey();
|
|
108
|
-
const
|
|
230
|
+
const timeout = options.timeout ?? this.config.timeout;
|
|
231
|
+
const url2 = new URL(`${this.config.baseUrl}${path2}`);
|
|
109
232
|
for (const [k, v] of Object.entries(options.query ?? {})) {
|
|
110
233
|
if (v !== void 0 && v !== "") url2.searchParams.set(k, String(v));
|
|
111
234
|
}
|
|
@@ -119,14 +242,15 @@ var ChannlizeApi = class {
|
|
|
119
242
|
...options.body !== void 0 ? { "Content-Type": "application/json" } : {},
|
|
120
243
|
...options.headers
|
|
121
244
|
},
|
|
245
|
+
// oxlint-disable-next-line unicorn/no-invalid-fetch-options -- `method` is dynamic; GET calls never pass a body.
|
|
122
246
|
body: options.body !== void 0 ? JSON.stringify(options.body) : void 0,
|
|
123
|
-
signal: AbortSignal.timeout(
|
|
247
|
+
signal: AbortSignal.timeout(timeout)
|
|
124
248
|
});
|
|
125
249
|
} catch (err) {
|
|
126
250
|
const e = err;
|
|
127
251
|
if (e.name === "TimeoutError" || e.name === "AbortError") {
|
|
128
252
|
throw new ToolError(
|
|
129
|
-
`Channlize API timed out after ${
|
|
253
|
+
`Channlize API timed out after ${timeout} ms (${method} ${url2.pathname}). Retry, or raise CHANNLIZE_TIMEOUT.`,
|
|
130
254
|
408,
|
|
131
255
|
"TIMEOUT"
|
|
132
256
|
);
|
|
@@ -136,30 +260,34 @@ var ChannlizeApi = class {
|
|
|
136
260
|
);
|
|
137
261
|
}
|
|
138
262
|
const text = await res.text();
|
|
139
|
-
let
|
|
263
|
+
let data2 = void 0;
|
|
140
264
|
if (text) {
|
|
141
265
|
try {
|
|
142
|
-
|
|
266
|
+
data2 = JSON.parse(text);
|
|
143
267
|
} catch {
|
|
144
|
-
|
|
268
|
+
data2 = void 0;
|
|
145
269
|
}
|
|
146
270
|
}
|
|
147
271
|
if (!res.ok) {
|
|
148
|
-
const body =
|
|
149
|
-
|
|
150
|
-
|
|
272
|
+
const { message: message2, code, body } = (0, import_core.parseApiError)(
|
|
273
|
+
res.status,
|
|
274
|
+
data2,
|
|
275
|
+
res.statusText
|
|
276
|
+
);
|
|
277
|
+
const retryAt = typeof body.retryAt === "string" ? body.retryAt : void 0;
|
|
151
278
|
throw new ToolError(
|
|
152
|
-
hintFor(res.status,
|
|
279
|
+
hintFor(res.status, message2.replace(/\.$/, ""), code, body),
|
|
153
280
|
res.status,
|
|
154
|
-
|
|
281
|
+
code,
|
|
282
|
+
retryAt
|
|
155
283
|
);
|
|
156
284
|
}
|
|
157
|
-
if (
|
|
285
|
+
if (data2 === void 0) {
|
|
158
286
|
throw new ToolError(
|
|
159
287
|
`Unexpected non-JSON response from ${method} ${url2.pathname}`
|
|
160
288
|
);
|
|
161
289
|
}
|
|
162
|
-
return
|
|
290
|
+
return data2;
|
|
163
291
|
}
|
|
164
292
|
/**
|
|
165
293
|
* `CHANNLIZE_PROJECT_ID`, or the first project of the key's organization
|
|
@@ -213,17 +341,14 @@ var ChannlizeApi = class {
|
|
|
213
341
|
};
|
|
214
342
|
|
|
215
343
|
// src/config.ts
|
|
216
|
-
var
|
|
217
|
-
var DEFAULT_TIMEOUT = 1e4;
|
|
344
|
+
var import_core2 = require("@channlize/core");
|
|
218
345
|
var ConfigError = class extends Error {
|
|
219
|
-
constructor(
|
|
220
|
-
super(
|
|
346
|
+
constructor(message2) {
|
|
347
|
+
super(message2);
|
|
221
348
|
this.name = "ConfigError";
|
|
222
349
|
}
|
|
223
350
|
};
|
|
224
|
-
|
|
225
|
-
return key.startsWith("wk_") ? "widget" : "server";
|
|
226
|
-
}
|
|
351
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
227
352
|
function clean(value) {
|
|
228
353
|
const v = value?.trim();
|
|
229
354
|
return v ? v : void 0;
|
|
@@ -232,18 +357,18 @@ function resolveConfig(config) {
|
|
|
232
357
|
let serverApiKey = clean(config.serverApiKey);
|
|
233
358
|
let widgetKey = clean(config.widgetKey);
|
|
234
359
|
const apiKey = clean(config.apiKey);
|
|
235
|
-
if (serverApiKey && keyKind(serverApiKey) === "widget") {
|
|
360
|
+
if (serverApiKey && (0, import_core2.keyKind)(serverApiKey) === "widget") {
|
|
236
361
|
throw new ConfigError(
|
|
237
362
|
"CHANNLIZE_SERVER_API_KEY / --server-api-key holds a widget key (wk_\u2026). Widget keys are public and only work for the visitor tools: pass it as CHANNLIZE_WIDGET_KEY / --widget-key, and create a server API key (ch_live_\u2026) under SDK & Integration \u2192 API keys in the Channlize dashboard."
|
|
238
363
|
);
|
|
239
364
|
}
|
|
240
|
-
if (widgetKey && keyKind(widgetKey) === "server") {
|
|
365
|
+
if (widgetKey && (0, import_core2.keyKind)(widgetKey) === "server") {
|
|
241
366
|
throw new ConfigError(
|
|
242
367
|
"CHANNLIZE_WIDGET_KEY / --widget-key holds a server API key. Pass server keys (ch_live_\u2026) as CHANNLIZE_SERVER_API_KEY / --server-api-key; widget keys start with wk_."
|
|
243
368
|
);
|
|
244
369
|
}
|
|
245
370
|
if (apiKey) {
|
|
246
|
-
if (keyKind(apiKey) === "widget") widgetKey ??= apiKey;
|
|
371
|
+
if ((0, import_core2.keyKind)(apiKey) === "widget") widgetKey ??= apiKey;
|
|
247
372
|
else serverApiKey ??= apiKey;
|
|
248
373
|
}
|
|
249
374
|
if (!serverApiKey && !widgetKey) {
|
|
@@ -251,13 +376,13 @@ function resolveConfig(config) {
|
|
|
251
376
|
"A Channlize key is required. Set CHANNLIZE_SERVER_API_KEY (or --server-api-key) to a server API key (ch_live_\u2026) from SDK & Integration \u2192 API keys in the Channlize dashboard. Use a read-scoped key for read-only access, or add the write scope to send events, logs and conversations."
|
|
252
377
|
);
|
|
253
378
|
}
|
|
254
|
-
const timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
379
|
+
const timeout = config.timeout ?? import_core2.DEFAULT_TIMEOUT;
|
|
255
380
|
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
256
381
|
throw new ConfigError(
|
|
257
382
|
`Invalid timeout "${config.timeout}": use milliseconds, e.g. 10000`
|
|
258
383
|
);
|
|
259
384
|
}
|
|
260
|
-
const baseUrl = (clean(config.baseUrl) ?? DEFAULT_BASE_URL).replace(
|
|
385
|
+
const baseUrl = (clean(config.baseUrl) ?? import_core2.DEFAULT_BASE_URL).replace(
|
|
261
386
|
/\/+$/,
|
|
262
387
|
""
|
|
263
388
|
);
|
|
@@ -265,26 +390,35 @@ function resolveConfig(config) {
|
|
|
265
390
|
new URL(baseUrl);
|
|
266
391
|
} catch {
|
|
267
392
|
throw new ConfigError(
|
|
268
|
-
`Invalid base URL "${config.baseUrl}": use e.g. ${DEFAULT_BASE_URL}`
|
|
393
|
+
`Invalid base URL "${config.baseUrl}": use e.g. ${import_core2.DEFAULT_BASE_URL}`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
const agentId2 = clean(config.agentId);
|
|
397
|
+
if (agentId2 && !UUID.test(agentId2)) {
|
|
398
|
+
throw new ConfigError(
|
|
399
|
+
`Invalid agent ID "${agentId2}": CHANNLIZE_AGENT_ID / --agent-id takes the agent's ID (a UUID, shown in the dashboard under Agents), not its slug.`
|
|
269
400
|
);
|
|
270
401
|
}
|
|
271
402
|
return {
|
|
272
403
|
serverApiKey,
|
|
273
404
|
widgetKey,
|
|
274
405
|
projectId: clean(config.projectId),
|
|
406
|
+
agentId: agentId2,
|
|
275
407
|
baseUrl,
|
|
276
408
|
timeout
|
|
277
409
|
};
|
|
278
410
|
}
|
|
279
411
|
function configFromEnv(env = process.env) {
|
|
280
|
-
const timeout =
|
|
412
|
+
const { timeout, serverApiKey, widgetKey, apiKey, projectId, baseUrl } = (0, import_core2.readChannlizeEnv)(env);
|
|
281
413
|
return {
|
|
282
|
-
serverApiKey
|
|
283
|
-
widgetKey
|
|
284
|
-
apiKey
|
|
285
|
-
projectId
|
|
286
|
-
baseUrl
|
|
287
|
-
timeout: timeout ? Number(timeout) : void 0
|
|
414
|
+
serverApiKey,
|
|
415
|
+
widgetKey,
|
|
416
|
+
apiKey,
|
|
417
|
+
projectId,
|
|
418
|
+
baseUrl,
|
|
419
|
+
timeout: timeout ? Number(timeout) : void 0,
|
|
420
|
+
// Read here rather than from core, so older @channlize/core versions work.
|
|
421
|
+
agentId: clean(env.CHANNLIZE_AGENT_ID)
|
|
288
422
|
};
|
|
289
423
|
}
|
|
290
424
|
|
|
@@ -465,12 +599,12 @@ function registerServerPrompts(server) {
|
|
|
465
599
|
},
|
|
466
600
|
({ status, limit }) => user(`Triage my Channlize support inbox.
|
|
467
601
|
|
|
468
|
-
1. Call list_conversations with status "${status || "open"}" and limit ${Number(limit) || 10}.
|
|
602
|
+
1. Call list_conversations with status ["${status || "open"}"] and limit ${Number(limit) || 10}.
|
|
469
603
|
2. For each conversation, call get_conversation to read the thread.
|
|
470
604
|
3. Return a table sorted by urgency: subject, customer, how long it has waited, current priority, a suggested priority (low / normal / high / urgent) and a one-line reason.
|
|
471
605
|
4. Under the table, draft a short, friendly reply for each conversation that needs one, and flag anything that looks like a bug, an outage or a billing problem.
|
|
472
606
|
|
|
473
|
-
|
|
607
|
+
Present the drafts for me to review; don't invent facts about my product. Only if I confirm, and the key has the inbox:write scope, send them with reply_to_conversation, or post them for the team with add_internal_note; tag, assign or close with the matching tools.`)
|
|
474
608
|
);
|
|
475
609
|
server.registerPrompt(
|
|
476
610
|
"summarize-conversation",
|
|
@@ -481,8 +615,8 @@ Replies and status or priority changes are made by the team in the Slack thread
|
|
|
481
615
|
conversationId: z.string().describe("Conversation ID")
|
|
482
616
|
})
|
|
483
617
|
},
|
|
484
|
-
({ conversationId }) => user(
|
|
485
|
-
`Call get_conversation with conversationId "${
|
|
618
|
+
({ conversationId: conversationId2 }) => user(
|
|
619
|
+
`Call get_conversation with conversationId "${conversationId2}". Summarize it in five bullet points or fewer: what the customer wants, what has been answered, what is still open, and the customer's mood. Then suggest the next step and draft a reply if one is needed.`
|
|
486
620
|
)
|
|
487
621
|
);
|
|
488
622
|
server.registerPrompt(
|
|
@@ -613,15 +747,1282 @@ function registerResources(server, api) {
|
|
|
613
747
|
}
|
|
614
748
|
|
|
615
749
|
// src/tools.ts
|
|
750
|
+
var z4 = __toESM(require("zod/v4"));
|
|
751
|
+
var import_core3 = require("@channlize/core");
|
|
752
|
+
|
|
753
|
+
// src/agents.ts
|
|
616
754
|
var z2 = __toESM(require("zod/v4"));
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
var
|
|
621
|
-
|
|
755
|
+
function defineTool(tool) {
|
|
756
|
+
return tool;
|
|
757
|
+
}
|
|
758
|
+
var AGENT_TASK_STATUSES = [
|
|
759
|
+
"open",
|
|
760
|
+
"working",
|
|
761
|
+
"input_required",
|
|
762
|
+
"completed",
|
|
763
|
+
"failed",
|
|
764
|
+
"canceled",
|
|
765
|
+
"rejected"
|
|
766
|
+
];
|
|
767
|
+
var uuid = (what) => z2.string().uuid().describe(what);
|
|
768
|
+
var agentId = uuid(
|
|
769
|
+
"The agent to act as (from list_agents). Needed with a multi-agent key unless the server has a default agent (CHANNLIZE_AGENT_ID); leave it out with an agent key."
|
|
770
|
+
).optional();
|
|
771
|
+
var channelId = uuid("Agent channel ID (from list_agent_channels)");
|
|
772
|
+
var threadId = uuid(
|
|
773
|
+
"Reply in this thread: the id of any message in it (thread_id or id from read_agent_inbox)"
|
|
774
|
+
).optional();
|
|
775
|
+
var mentions = z2.array(z2.string().uuid()).max(50).optional().describe("Agent IDs to mention (from list_agents)");
|
|
776
|
+
var data = z2.record(z2.string(), z2.unknown()).optional().describe("Structured data for the recipients (JSON object)");
|
|
777
|
+
var usage = {
|
|
778
|
+
tokens: z2.number().int().min(0).optional().describe("Tokens you used for this message (reporting)"),
|
|
779
|
+
cost: z2.number().min(0).optional().describe("What this message cost you, in USD (reporting)")
|
|
780
|
+
};
|
|
781
|
+
var message = z2.looseObject({
|
|
782
|
+
id: z2.string(),
|
|
783
|
+
channel_id: z2.string(),
|
|
784
|
+
thread_id: z2.string().nullable(),
|
|
785
|
+
type: z2.string(),
|
|
786
|
+
text: z2.string(),
|
|
787
|
+
sender: z2.looseObject({ kind: z2.string() }),
|
|
788
|
+
task_status: z2.string().nullable().optional()
|
|
789
|
+
});
|
|
790
|
+
var sendOutput = z2.object({
|
|
791
|
+
message,
|
|
792
|
+
deliveries: z2.array(
|
|
793
|
+
z2.looseObject({ id: z2.string(), agent_id: z2.string(), status: z2.string() })
|
|
794
|
+
)
|
|
795
|
+
});
|
|
796
|
+
var dueAt = z2.string().max(40).optional().describe("Due date: an ISO date (2026-11-30) or date-time");
|
|
797
|
+
var task = z2.looseObject({
|
|
798
|
+
id: z2.string(),
|
|
799
|
+
channel_id: z2.string(),
|
|
800
|
+
text: z2.string(),
|
|
801
|
+
board_status: z2.string(),
|
|
802
|
+
assignee_agent_id: z2.string().nullable(),
|
|
803
|
+
due_at: z2.string().nullable()
|
|
804
|
+
});
|
|
805
|
+
var deliveries = z2.array(
|
|
806
|
+
z2.looseObject({ id: z2.string(), agent_id: z2.string(), status: z2.string() })
|
|
807
|
+
);
|
|
808
|
+
var question = z2.looseObject({
|
|
809
|
+
id: z2.string(),
|
|
810
|
+
kind: z2.string(),
|
|
811
|
+
question: z2.string(),
|
|
812
|
+
status: z2.string(),
|
|
813
|
+
answer: z2.looseObject({}).nullable()
|
|
814
|
+
});
|
|
815
|
+
var questionId = uuid("The question ID (from ask_human or list_questions)");
|
|
816
|
+
var BOARD_STATUSES = [
|
|
817
|
+
"open",
|
|
818
|
+
"in_progress",
|
|
819
|
+
"blocked",
|
|
820
|
+
"done",
|
|
821
|
+
"failed"
|
|
822
|
+
];
|
|
823
|
+
var QUESTION_WAIT_MAX_SECONDS = 25;
|
|
824
|
+
var READ = {
|
|
825
|
+
readOnlyHint: true,
|
|
826
|
+
destructiveHint: false,
|
|
827
|
+
idempotentHint: true,
|
|
828
|
+
openWorldHint: false
|
|
829
|
+
};
|
|
830
|
+
var SEND = {
|
|
831
|
+
readOnlyHint: false,
|
|
832
|
+
destructiveHint: false,
|
|
833
|
+
idempotentHint: false,
|
|
834
|
+
openWorldHint: true
|
|
835
|
+
};
|
|
836
|
+
function actingAs(api, id2) {
|
|
837
|
+
return id2 ?? api.agentId;
|
|
838
|
+
}
|
|
839
|
+
function requireAgent(api, id2) {
|
|
840
|
+
const agent = actingAs(api, id2);
|
|
841
|
+
if (!agent) {
|
|
842
|
+
throw new ToolError(
|
|
843
|
+
"Pass agentId (from list_agents), or set CHANNLIZE_AGENT_ID / --agent-id on the MCP server."
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
return agent;
|
|
847
|
+
}
|
|
848
|
+
async function send(api, body) {
|
|
849
|
+
const r = await api.request(
|
|
850
|
+
"POST",
|
|
851
|
+
"/v1/agents/messages",
|
|
852
|
+
{ body }
|
|
853
|
+
);
|
|
854
|
+
return { message: r.message, deliveries: r.deliveries ?? [] };
|
|
855
|
+
}
|
|
856
|
+
var enc = encodeURIComponent;
|
|
857
|
+
function outcomeMessage(status, q) {
|
|
858
|
+
const answer = q.answer ?? {};
|
|
859
|
+
const choice = answer.option_label ?? answer.option_id;
|
|
860
|
+
const comment = answer.comment ? `: ${String(answer.comment)}` : "";
|
|
861
|
+
switch (status) {
|
|
862
|
+
case "answered":
|
|
863
|
+
if (q.kind === "approval") {
|
|
864
|
+
return answer.approved ? `Approved${comment}. Go ahead.` : `Denied${comment}. Don't do it.`;
|
|
865
|
+
}
|
|
866
|
+
return `Answered: ${[choice, answer.text].filter(Boolean).join(" / ") || "(empty)"}.`;
|
|
867
|
+
case "expired_default":
|
|
868
|
+
return `Nobody answered by the deadline: the default option applies (${String(choice ?? "none")}).`;
|
|
869
|
+
case "expired":
|
|
870
|
+
return "Nobody answered by the deadline and there is no default: decide whether to ask again or drop it. For an approval, treat it as not approved.";
|
|
871
|
+
case "canceled":
|
|
872
|
+
return "The question was canceled.";
|
|
873
|
+
default:
|
|
874
|
+
return `Status: ${status}.`;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
var AGENT_TOOLS = [
|
|
878
|
+
defineTool({
|
|
879
|
+
name: "send_agent_message",
|
|
880
|
+
title: "Send agent message",
|
|
881
|
+
description: "Post a message to an agent channel as your agent. Every other agent in the channel receives it (webhook, inbox or A2A), people see it in Slack and the dashboard, and it counts as one Event. Reply in a thread with threadId; mention agents with mentions. Needs the agents:send scope, and the agent must be a member of the channel.",
|
|
882
|
+
scope: "agents:send",
|
|
883
|
+
endpoint: "POST /v1/agents/messages",
|
|
884
|
+
inputSchema: z2.object({
|
|
885
|
+
agentId,
|
|
886
|
+
channelId,
|
|
887
|
+
text: z2.string().min(1).max(4e4).describe("Message text"),
|
|
888
|
+
threadId,
|
|
889
|
+
mentions,
|
|
890
|
+
data,
|
|
891
|
+
...usage
|
|
892
|
+
}),
|
|
893
|
+
outputSchema: sendOutput,
|
|
894
|
+
annotations: SEND,
|
|
895
|
+
handler: (api, a) => send(api, {
|
|
896
|
+
agent_id: actingAs(api, a.agentId),
|
|
897
|
+
channel_id: a.channelId,
|
|
898
|
+
type: "message",
|
|
899
|
+
text: a.text,
|
|
900
|
+
thread_id: a.threadId,
|
|
901
|
+
mentions: a.mentions,
|
|
902
|
+
data: a.data,
|
|
903
|
+
tokens: a.tokens,
|
|
904
|
+
cost: a.cost
|
|
905
|
+
})
|
|
906
|
+
}),
|
|
907
|
+
defineTool({
|
|
908
|
+
name: "create_task",
|
|
909
|
+
title: "Create task",
|
|
910
|
+
description: "Hand a task to an agent of a channel: creates a task (board status open) that starts its own thread, optionally assigned to a member agent (assigneeAgentId, who is mentioned) and with a due date. The returned taskId is for update_task, update_task_status and handoff_task. Your policy may refuse the channel or the assignee (POLICY_* errors). Needs the agents:send scope.",
|
|
911
|
+
scope: "agents:send",
|
|
912
|
+
endpoint: "POST /v1/agents/tasks",
|
|
913
|
+
inputSchema: z2.object({
|
|
914
|
+
agentId,
|
|
915
|
+
channelId,
|
|
916
|
+
text: z2.string().min(1).max(4e4).describe("What should be done, with enough context to do it"),
|
|
917
|
+
assigneeAgentId: uuid(
|
|
918
|
+
"The agent that should do it: a member of the channel (from list_agents)"
|
|
919
|
+
).optional(),
|
|
920
|
+
dueAt,
|
|
921
|
+
threadId,
|
|
922
|
+
mentions,
|
|
923
|
+
data
|
|
924
|
+
}),
|
|
925
|
+
// `message` is the task too (a task is its `task` message plus board fields).
|
|
926
|
+
outputSchema: z2.object({ taskId: z2.string(), task, message, deliveries }),
|
|
927
|
+
annotations: SEND,
|
|
928
|
+
handler: async (api, a) => {
|
|
929
|
+
const r = await api.request(
|
|
930
|
+
"POST",
|
|
931
|
+
"/v1/agents/tasks",
|
|
932
|
+
{
|
|
933
|
+
body: {
|
|
934
|
+
agent_id: actingAs(api, a.agentId),
|
|
935
|
+
channel_id: a.channelId,
|
|
936
|
+
text: a.text,
|
|
937
|
+
assignee_agent_id: a.assigneeAgentId,
|
|
938
|
+
due_at: a.dueAt,
|
|
939
|
+
thread_id: a.threadId,
|
|
940
|
+
mentions: a.mentions,
|
|
941
|
+
data: a.data
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
);
|
|
945
|
+
return {
|
|
946
|
+
taskId: String(r.task.id),
|
|
947
|
+
task: r.task,
|
|
948
|
+
message: r.task,
|
|
949
|
+
deliveries: r.deliveries ?? []
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
}),
|
|
953
|
+
defineTool({
|
|
954
|
+
name: "update_task_status",
|
|
955
|
+
title: "Update task status",
|
|
956
|
+
description: "Report progress on a task (from create_task or a task in your inbox): working, input_required, completed, failed, canceled or rejected, with an optional note and result data. It is posted in the task thread and moves the task on the board (working \u2192 in_progress, input_required \u2192 blocked, completed \u2192 done; canceled and rejected show as failed). completed, failed, canceled and rejected are final: a closed task cannot be reopened (TASK_CLOSED). To change the due date, use update_task. Needs the agents:send scope.",
|
|
957
|
+
scope: "agents:send",
|
|
958
|
+
endpoint: "POST /v1/agents/messages",
|
|
959
|
+
inputSchema: z2.object({
|
|
960
|
+
agentId,
|
|
961
|
+
channelId,
|
|
962
|
+
taskId: uuid("The task message ID"),
|
|
963
|
+
status: z2.enum(AGENT_TASK_STATUSES).describe("The new status of the task"),
|
|
964
|
+
text: z2.string().max(4e4).optional().describe("What changed, e.g. the result or what input is needed"),
|
|
965
|
+
data,
|
|
966
|
+
...usage
|
|
967
|
+
}),
|
|
968
|
+
outputSchema: sendOutput,
|
|
969
|
+
annotations: SEND,
|
|
970
|
+
handler: (api, a) => send(api, {
|
|
971
|
+
agent_id: actingAs(api, a.agentId),
|
|
972
|
+
channel_id: a.channelId,
|
|
973
|
+
type: "status",
|
|
974
|
+
text: a.text ?? "",
|
|
975
|
+
task_id: a.taskId,
|
|
976
|
+
task_status: a.status,
|
|
977
|
+
data: a.data,
|
|
978
|
+
tokens: a.tokens,
|
|
979
|
+
cost: a.cost
|
|
980
|
+
})
|
|
981
|
+
}),
|
|
982
|
+
defineTool({
|
|
983
|
+
name: "update_task",
|
|
984
|
+
title: "Update task",
|
|
985
|
+
description: "Move a task on the board (open, in_progress, blocked, done, failed) and/or change its due date, with a note: posted as a status update in the task thread, recorded in the task's history and shown on its Slack card. Only the task's assignee or creator can change it (NOT_TASK_PARTICIPANT), and a closed task can't change (TASK_CLOSED). To cancel or reject, or to attach result data, use update_task_status. Needs the agents:send scope.",
|
|
986
|
+
scope: "agents:send",
|
|
987
|
+
endpoint: "PATCH /v1/agents/tasks/:taskId",
|
|
988
|
+
inputSchema: z2.object({
|
|
989
|
+
agentId,
|
|
990
|
+
taskId: uuid("The task ID"),
|
|
991
|
+
status: z2.enum(BOARD_STATUSES).optional().describe("The board column"),
|
|
992
|
+
dueAt: z2.string().max(40).nullable().optional().describe("New due date (ISO date or date-time); null clears it"),
|
|
993
|
+
note: z2.string().max(2e3).optional().describe("What changed, shown with the update")
|
|
994
|
+
}),
|
|
995
|
+
outputSchema: z2.object({ task, changed: z2.boolean() }),
|
|
996
|
+
annotations: SEND,
|
|
997
|
+
handler: async (api, a) => {
|
|
998
|
+
if (a.status === void 0 && a.dueAt === void 0) {
|
|
999
|
+
throw new ToolError("Pass status or dueAt.");
|
|
1000
|
+
}
|
|
1001
|
+
const r = await api.request(
|
|
1002
|
+
"PATCH",
|
|
1003
|
+
`/v1/agents/tasks/${enc(a.taskId)}`,
|
|
1004
|
+
{
|
|
1005
|
+
body: {
|
|
1006
|
+
agent_id: actingAs(api, a.agentId),
|
|
1007
|
+
status: a.status,
|
|
1008
|
+
due_at: a.dueAt,
|
|
1009
|
+
note: a.note
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
);
|
|
1013
|
+
return { task: r.task, changed: Boolean(r.changed) };
|
|
1014
|
+
}
|
|
1015
|
+
}),
|
|
1016
|
+
defineTool({
|
|
1017
|
+
name: "handoff_task",
|
|
1018
|
+
title: "Hand off task",
|
|
1019
|
+
description: "Hand a task you are assigned to (or created) to another member agent of its channel, with the reason: the new and the previous assignee get a status message (data.handoff), and it's recorded in the task's history. Use it when another agent is better placed, then stop working on the task. Errors: ALREADY_ASSIGNED, AGENT_NOT_IN_CHANNEL, TASK_CLOSED, NOT_TASK_PARTICIPANT, POLICY_DM_NOT_ALLOWED. Needs the agents:send scope.",
|
|
1020
|
+
scope: "agents:send",
|
|
1021
|
+
endpoint: "POST /v1/agents/tasks/:taskId/handoff",
|
|
1022
|
+
inputSchema: z2.object({
|
|
1023
|
+
agentId,
|
|
1024
|
+
taskId: uuid("The task ID"),
|
|
1025
|
+
toAgentId: uuid("The new assignee (from list_agents or get_org_chart)"),
|
|
1026
|
+
reason: z2.string().max(2e3).optional().describe("Why, and what the new assignee needs to know")
|
|
1027
|
+
}),
|
|
1028
|
+
outputSchema: z2.object({ task, deliveries }),
|
|
1029
|
+
annotations: SEND,
|
|
1030
|
+
handler: async (api, a) => {
|
|
1031
|
+
const r = await api.request(
|
|
1032
|
+
"POST",
|
|
1033
|
+
`/v1/agents/tasks/${enc(a.taskId)}/handoff`,
|
|
1034
|
+
{
|
|
1035
|
+
body: {
|
|
1036
|
+
agent_id: actingAs(api, a.agentId),
|
|
1037
|
+
to_agent_id: a.toAgentId,
|
|
1038
|
+
reason: a.reason
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
);
|
|
1042
|
+
return {
|
|
1043
|
+
task: r.task,
|
|
1044
|
+
deliveries: r.deliveries ?? []
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
}),
|
|
1048
|
+
defineTool({
|
|
1049
|
+
name: "list_tasks",
|
|
1050
|
+
title: "List tasks",
|
|
1051
|
+
description: "List a channel's tasks, newest first, with their board status (open, in_progress, blocked, done, failed), assignee, due date and last handoff. Filter by assignee (your own agent id for your work) or status. channelId is required unless the key is an agents:admin key. Needs the agents:read scope.",
|
|
1052
|
+
scope: "agents:read",
|
|
1053
|
+
endpoint: "GET /v1/agents/tasks",
|
|
1054
|
+
inputSchema: z2.object({
|
|
1055
|
+
agentId,
|
|
1056
|
+
channelId: channelId.optional(),
|
|
1057
|
+
assigneeAgentId: uuid("Only tasks assigned to this agent").optional(),
|
|
1058
|
+
status: z2.enum(BOARD_STATUSES).optional(),
|
|
1059
|
+
limit: z2.number().int().min(1).max(500).default(50).describe("How many to return (1-500, default 50)")
|
|
1060
|
+
}),
|
|
1061
|
+
outputSchema: z2.object({ tasks: z2.array(task) }),
|
|
1062
|
+
annotations: READ,
|
|
1063
|
+
handler: async (api, a) => {
|
|
1064
|
+
const r = await api.request(
|
|
1065
|
+
"GET",
|
|
1066
|
+
"/v1/agents/tasks",
|
|
1067
|
+
{
|
|
1068
|
+
query: {
|
|
1069
|
+
agent_id: actingAs(api, a.agentId),
|
|
1070
|
+
channel_id: a.channelId,
|
|
1071
|
+
assignee_agent_id: a.assigneeAgentId,
|
|
1072
|
+
status: a.status,
|
|
1073
|
+
limit: a.limit
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
);
|
|
1077
|
+
return { tasks: r.tasks ?? [] };
|
|
1078
|
+
}
|
|
1079
|
+
}),
|
|
1080
|
+
defineTool({
|
|
1081
|
+
name: "get_task_board",
|
|
1082
|
+
title: "Get task board",
|
|
1083
|
+
description: "A channel's task board: tasks grouped by column (open, in_progress, blocked, done, failed), counts per column, and the agents on it. Needs the agents:read scope.",
|
|
1084
|
+
scope: "agents:read",
|
|
1085
|
+
endpoint: "GET /v1/agents/tasks/board",
|
|
1086
|
+
inputSchema: z2.object({
|
|
1087
|
+
agentId,
|
|
1088
|
+
channelId,
|
|
1089
|
+
assigneeAgentId: uuid("Only tasks assigned to this agent").optional()
|
|
1090
|
+
}),
|
|
1091
|
+
outputSchema: z2.object({
|
|
1092
|
+
channel_id: z2.string(),
|
|
1093
|
+
columns: z2.record(z2.string(), z2.array(task)),
|
|
1094
|
+
counts: z2.record(z2.string(), z2.number()),
|
|
1095
|
+
agents: z2.array(z2.looseObject({ id: z2.string() })),
|
|
1096
|
+
truncated: z2.boolean()
|
|
1097
|
+
}),
|
|
1098
|
+
annotations: READ,
|
|
1099
|
+
handler: async (api, a) => await api.request("GET", "/v1/agents/tasks/board", {
|
|
1100
|
+
query: {
|
|
1101
|
+
agent_id: actingAs(api, a.agentId),
|
|
1102
|
+
channel_id: a.channelId,
|
|
1103
|
+
assignee_agent_id: a.assigneeAgentId
|
|
1104
|
+
}
|
|
1105
|
+
})
|
|
1106
|
+
}),
|
|
1107
|
+
defineTool({
|
|
1108
|
+
name: "ask_human",
|
|
1109
|
+
title: "Ask a human",
|
|
1110
|
+
description: "Ask people a question, or for an approval (kind approval: Approve / Deny), when you can't or shouldn't decide alone. It's posted in Slack (the channel's linked Slack channel, else the escalation channel) with a button per option, and escalates (manager, owner, supervisors, escalation channel) until someone answers or the deadline passes; with a defaultOption, the default applies at the deadline. Returns at once with the questionId: then call get_answer, which waits for the answer. Don't act on what you asked about before the answer. Counts as one Event. Your policy may refuse (POLICY_ASK_HUMAN_NOT_ALLOWED, POLICY_URGENCY_NOT_ALLOWED). Needs the agents:ask_human scope.",
|
|
1111
|
+
scope: "agents:ask_human",
|
|
1112
|
+
endpoint: "POST /v1/agents/questions",
|
|
1113
|
+
inputSchema: z2.object({
|
|
1114
|
+
agentId,
|
|
1115
|
+
kind: z2.enum(["question", "approval"]).default("question").describe("question (default) or approval (answered Approve / Deny)"),
|
|
1116
|
+
question: z2.string().min(1).max(3e3).describe("The question, with what people need to answer it"),
|
|
1117
|
+
options: z2.array(
|
|
1118
|
+
z2.object({
|
|
1119
|
+
id: z2.string().min(1).max(100),
|
|
1120
|
+
label: z2.string().min(1).max(75)
|
|
1121
|
+
})
|
|
1122
|
+
).max(10).optional().describe("Up to 10 answers to pick from (ignored for approvals)"),
|
|
1123
|
+
freeText: z2.boolean().optional().describe("Allow a typed answer (always on without options)"),
|
|
1124
|
+
urgency: z2.enum(["low", "normal", "high", "urgent"]).optional().describe("How fast it escalates (default normal)"),
|
|
1125
|
+
deadline: z2.string().max(40).optional().describe("ISO date-time, at most 30 days away"),
|
|
1126
|
+
defaultOption: z2.string().max(100).optional().describe("Option id applied at the deadline when nobody answered"),
|
|
1127
|
+
channelId: channelId.optional().describe("Post it in this agent channel (from list_agent_channels)"),
|
|
1128
|
+
threadId,
|
|
1129
|
+
context: z2.record(z2.string(), z2.unknown()).optional().describe("Facts shown with the question (JSON object)"),
|
|
1130
|
+
amount: z2.number().min(0).optional().describe("Approvals: the amount at stake"),
|
|
1131
|
+
currency: z2.string().regex(/^[A-Za-z]{3}$/).optional().describe("Approvals: ISO 4217 currency code"),
|
|
1132
|
+
resource: z2.record(z2.string(), z2.unknown()).optional().describe("Approvals: what is being approved (JSON object)"),
|
|
1133
|
+
requiredRoles: z2.array(z2.string().min(1).max(40)).max(10).optional().describe('Approvals: roles that may answer, e.g. ["admin"]')
|
|
1134
|
+
}),
|
|
1135
|
+
outputSchema: z2.object({ questionId: z2.string(), question }),
|
|
1136
|
+
annotations: SEND,
|
|
1137
|
+
handler: async (api, a) => {
|
|
1138
|
+
const r = await api.request(
|
|
1139
|
+
"POST",
|
|
1140
|
+
"/v1/agents/questions",
|
|
1141
|
+
{
|
|
1142
|
+
body: {
|
|
1143
|
+
agent_id: actingAs(api, a.agentId),
|
|
1144
|
+
kind: a.kind,
|
|
1145
|
+
question: a.question,
|
|
1146
|
+
options: a.options,
|
|
1147
|
+
free_text: a.freeText,
|
|
1148
|
+
urgency: a.urgency,
|
|
1149
|
+
deadline: a.deadline,
|
|
1150
|
+
default_option: a.defaultOption,
|
|
1151
|
+
channel_id: a.channelId,
|
|
1152
|
+
thread_id: a.threadId,
|
|
1153
|
+
context: a.context,
|
|
1154
|
+
amount: a.amount,
|
|
1155
|
+
currency: a.currency,
|
|
1156
|
+
resource: a.resource,
|
|
1157
|
+
required_roles: a.requiredRoles
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
);
|
|
1161
|
+
return {
|
|
1162
|
+
questionId: String(r.question.id),
|
|
1163
|
+
question: r.question
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
}),
|
|
1167
|
+
defineTool({
|
|
1168
|
+
name: "get_answer",
|
|
1169
|
+
title: "Get answer",
|
|
1170
|
+
description: `Wait for the answer to a question from ask_human: holds up to waitSeconds (default and maximum ${QUESTION_WAIT_MAX_SECONDS}) and returns as soon as it closes. status is answered (answer.option_id / answer.text; approvals have answer.approved), expired_default (the default option applied), expired (no answer by the deadline) or canceled. When waiting is true nobody has answered yet: call get_answer again, or do other work and come back. Needs the agents:ask_human scope (or agents:read).`,
|
|
1171
|
+
scope: "agents:ask_human",
|
|
1172
|
+
endpoint: "GET /v1/agents/questions/:id/wait",
|
|
1173
|
+
inputSchema: z2.object({
|
|
1174
|
+
agentId,
|
|
1175
|
+
questionId,
|
|
1176
|
+
waitSeconds: z2.number().int().min(0).max(QUESTION_WAIT_MAX_SECONDS).default(QUESTION_WAIT_MAX_SECONDS).describe(
|
|
1177
|
+
`Seconds to wait for an answer (0-${QUESTION_WAIT_MAX_SECONDS}, default ${QUESTION_WAIT_MAX_SECONDS}); 0 only checks`
|
|
1178
|
+
)
|
|
1179
|
+
}),
|
|
1180
|
+
outputSchema: z2.object({
|
|
1181
|
+
questionId: z2.string(),
|
|
1182
|
+
status: z2.string(),
|
|
1183
|
+
waiting: z2.boolean(),
|
|
1184
|
+
answer: z2.looseObject({}).nullable(),
|
|
1185
|
+
message: z2.string(),
|
|
1186
|
+
question
|
|
1187
|
+
}),
|
|
1188
|
+
annotations: READ,
|
|
1189
|
+
handler: async (api, a) => {
|
|
1190
|
+
const r = await api.request(
|
|
1191
|
+
"GET",
|
|
1192
|
+
`/v1/agents/questions/${enc(a.questionId)}/wait`,
|
|
1193
|
+
{
|
|
1194
|
+
query: {
|
|
1195
|
+
agent_id: actingAs(api, a.agentId),
|
|
1196
|
+
timeout: a.waitSeconds
|
|
1197
|
+
},
|
|
1198
|
+
// The API holds the request for up to waitSeconds.
|
|
1199
|
+
timeout: (a.waitSeconds + 10) * 1e3
|
|
1200
|
+
}
|
|
1201
|
+
);
|
|
1202
|
+
const status = String(r.question.status);
|
|
1203
|
+
const waiting = status === "open";
|
|
1204
|
+
return {
|
|
1205
|
+
questionId: a.questionId,
|
|
1206
|
+
status,
|
|
1207
|
+
waiting,
|
|
1208
|
+
answer: r.question.answer ?? null,
|
|
1209
|
+
message: waiting ? `Still waiting: nobody has answered yet. Call get_answer again with questionId ${a.questionId}, or check back later; don't act on the question meanwhile.` : outcomeMessage(status, r.question),
|
|
1210
|
+
question: r.question
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
}),
|
|
1214
|
+
defineTool({
|
|
1215
|
+
name: "list_questions",
|
|
1216
|
+
title: "List questions",
|
|
1217
|
+
description: "List your questions and the ones escalated to you as a manager agent, newest first. Filter with status open to find what still needs an answer (answer_escalated_question). Needs the agents:read scope (or agents:ask_human).",
|
|
1218
|
+
scope: "agents:read",
|
|
1219
|
+
endpoint: "GET /v1/agents/questions",
|
|
1220
|
+
inputSchema: z2.object({
|
|
1221
|
+
agentId,
|
|
1222
|
+
status: z2.enum(["open", "answered", "expired_default", "expired", "canceled"]).optional(),
|
|
1223
|
+
kind: z2.enum(["question", "approval"]).optional(),
|
|
1224
|
+
channelId: channelId.optional(),
|
|
1225
|
+
cursor: z2.string().max(300).optional().describe("nextCursor of the previous call"),
|
|
1226
|
+
limit: z2.number().int().min(1).max(200).default(20).describe("How many to return (1-200, default 20)")
|
|
1227
|
+
}),
|
|
1228
|
+
outputSchema: z2.object({
|
|
1229
|
+
questions: z2.array(question),
|
|
1230
|
+
nextCursor: z2.string().nullable()
|
|
1231
|
+
}),
|
|
1232
|
+
annotations: READ,
|
|
1233
|
+
handler: async (api, a) => {
|
|
1234
|
+
const r = await api.request("GET", "/v1/agents/questions", {
|
|
1235
|
+
query: {
|
|
1236
|
+
agent_id: actingAs(api, a.agentId),
|
|
1237
|
+
status: a.status,
|
|
1238
|
+
kind: a.kind,
|
|
1239
|
+
channel_id: a.channelId,
|
|
1240
|
+
cursor: a.cursor,
|
|
1241
|
+
limit: a.limit
|
|
1242
|
+
}
|
|
1243
|
+
});
|
|
1244
|
+
return {
|
|
1245
|
+
questions: r.questions ?? [],
|
|
1246
|
+
nextCursor: r.next_cursor ?? null
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
}),
|
|
1250
|
+
defineTool({
|
|
1251
|
+
name: "cancel_question",
|
|
1252
|
+
title: "Cancel question",
|
|
1253
|
+
description: "Cancel one of your open questions when it no longer matters (you found the answer, the work was dropped): its Slack post is updated and escalation stops. Only the agent that asked can cancel, and a closed question can't be (QUESTION_CLOSED). Needs the agents:ask_human scope.",
|
|
1254
|
+
scope: "agents:ask_human",
|
|
1255
|
+
endpoint: "DELETE /v1/agents/questions/:id",
|
|
1256
|
+
inputSchema: z2.object({ agentId, questionId }),
|
|
1257
|
+
outputSchema: z2.object({ question }),
|
|
1258
|
+
annotations: {
|
|
1259
|
+
readOnlyHint: false,
|
|
1260
|
+
destructiveHint: true,
|
|
1261
|
+
idempotentHint: false,
|
|
1262
|
+
openWorldHint: true
|
|
1263
|
+
},
|
|
1264
|
+
handler: async (api, a) => {
|
|
1265
|
+
const r = await api.request(
|
|
1266
|
+
"DELETE",
|
|
1267
|
+
`/v1/agents/questions/${enc(a.questionId)}`,
|
|
1268
|
+
{ query: { agent_id: actingAs(api, a.agentId) } }
|
|
1269
|
+
);
|
|
1270
|
+
return { question: r.question };
|
|
1271
|
+
}
|
|
1272
|
+
}),
|
|
1273
|
+
defineTool({
|
|
1274
|
+
name: "answer_escalated_question",
|
|
1275
|
+
title: "Answer escalated question",
|
|
1276
|
+
description: "Answer a question another agent asked, when it was escalated to you as its manager agent (a question.escalated delivery, or list_questions with status open). Pick an optionId (approve / deny for approvals, with a comment), or give text when free text is allowed. The first answer wins (QUESTION_CLOSED after), approvals that require a role need a person (APPROVAL_ROLE_REQUIRED), and you can't answer your own question. Needs the agents:send scope.",
|
|
1277
|
+
scope: "agents:send",
|
|
1278
|
+
endpoint: "POST /v1/agents/questions/:id/answer",
|
|
1279
|
+
inputSchema: z2.object({
|
|
1280
|
+
agentId,
|
|
1281
|
+
questionId,
|
|
1282
|
+
optionId: z2.string().min(1).max(100).optional().describe("One of the question options (approve / deny for approvals)"),
|
|
1283
|
+
text: z2.string().max(3e3).optional().describe("A typed answer, when the question allows free text"),
|
|
1284
|
+
comment: z2.string().max(2e3).optional().describe("Why (shown with the answer; say why on a deny)")
|
|
1285
|
+
}),
|
|
1286
|
+
outputSchema: z2.object({ question }),
|
|
1287
|
+
annotations: SEND,
|
|
1288
|
+
handler: async (api, a) => {
|
|
1289
|
+
if (!a.optionId && !a.text) throw new ToolError("Pass optionId or text.");
|
|
1290
|
+
const r = await api.request(
|
|
1291
|
+
"POST",
|
|
1292
|
+
`/v1/agents/questions/${enc(a.questionId)}/answer`,
|
|
1293
|
+
{
|
|
1294
|
+
body: {
|
|
1295
|
+
agent_id: actingAs(api, a.agentId),
|
|
1296
|
+
option_id: a.optionId,
|
|
1297
|
+
text: a.text,
|
|
1298
|
+
comment: a.comment
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
);
|
|
1302
|
+
return { question: r.question };
|
|
1303
|
+
}
|
|
1304
|
+
}),
|
|
1305
|
+
defineTool({
|
|
1306
|
+
name: "get_org_chart",
|
|
1307
|
+
title: "Get org chart",
|
|
1308
|
+
description: "The organization's agents with their manager (an agent or a person), owner and teams, the teams, and the reporting tree. Use it to find who to hand a task off to, or who manages an agent. Needs the agents:read scope.",
|
|
1309
|
+
scope: "agents:read",
|
|
1310
|
+
endpoint: "GET /v1/agents/org-chart",
|
|
1311
|
+
inputSchema: z2.object({
|
|
1312
|
+
teamId: uuid("Only this team").optional(),
|
|
1313
|
+
channelId: uuid("Only the agents of this channel").optional(),
|
|
1314
|
+
includeRetired: z2.boolean().optional().describe("Include retired agents (default false)")
|
|
1315
|
+
}),
|
|
1316
|
+
outputSchema: z2.object({
|
|
1317
|
+
agents: z2.array(z2.looseObject({ id: z2.string() })),
|
|
1318
|
+
users: z2.array(z2.looseObject({ id: z2.string() })),
|
|
1319
|
+
teams: z2.array(z2.looseObject({ id: z2.string() })),
|
|
1320
|
+
tree: z2.array(z2.looseObject({}))
|
|
1321
|
+
}),
|
|
1322
|
+
annotations: READ,
|
|
1323
|
+
handler: async (api, a) => await api.request("GET", "/v1/agents/org-chart", {
|
|
1324
|
+
query: {
|
|
1325
|
+
team_id: a.teamId,
|
|
1326
|
+
channel_id: a.channelId,
|
|
1327
|
+
include_retired: a.includeRetired ? "true" : void 0
|
|
1328
|
+
}
|
|
1329
|
+
})
|
|
1330
|
+
}),
|
|
1331
|
+
defineTool({
|
|
1332
|
+
name: "get_effective_policy",
|
|
1333
|
+
title: "Get effective policy",
|
|
1334
|
+
description: "The policy in force for an agent (in a channel): the merged rules (allowed channels, direct messages, asking humans, rate, budgets, working hours, thread depth, humans-win minutes), which policy set each, current usage, working hours now, and with a channel whether it may post and read there now. Read-only. Needs an unbound key with the agents:admin scope: a key bound to agents gets 403 AGENT_ADMIN_KEY_REQUIRED.",
|
|
1335
|
+
scope: "agents:admin",
|
|
1336
|
+
endpoint: "GET /v1/agents/policies/preview",
|
|
1337
|
+
inputSchema: z2.object({
|
|
1338
|
+
agentId: uuid(
|
|
1339
|
+
"The agent (from list_agents). Default: the server agent (CHANNLIZE_AGENT_ID)"
|
|
1340
|
+
).optional(),
|
|
1341
|
+
channelId: channelId.optional()
|
|
1342
|
+
}),
|
|
1343
|
+
outputSchema: z2.looseObject({
|
|
1344
|
+
agent_id: z2.string(),
|
|
1345
|
+
channel_id: z2.string().nullable(),
|
|
1346
|
+
rules: z2.looseObject({}),
|
|
1347
|
+
working_hours: z2.looseObject({}),
|
|
1348
|
+
usage: z2.looseObject({}),
|
|
1349
|
+
checks: z2.looseObject({}).nullable()
|
|
1350
|
+
}),
|
|
1351
|
+
annotations: READ,
|
|
1352
|
+
handler: async (api, a) => await api.request("GET", "/v1/agents/policies/preview", {
|
|
1353
|
+
query: {
|
|
1354
|
+
agent_id: requireAgent(api, a.agentId),
|
|
1355
|
+
channel_id: a.channelId
|
|
1356
|
+
}
|
|
1357
|
+
})
|
|
1358
|
+
}),
|
|
1359
|
+
defineTool({
|
|
1360
|
+
name: "read_agent_inbox",
|
|
1361
|
+
title: "Read agent inbox",
|
|
1362
|
+
description: "Read your agent's unacknowledged messages, oldest first: messages, tasks and task status updates from other agents and people, each with its channel, thread and sender. Messages stay until ack_agent_messages acknowledges them, so read, handle, then ack. Needs the agents:read scope.",
|
|
1363
|
+
scope: "agents:read",
|
|
1364
|
+
endpoint: "GET /v1/agents/:id/inbox",
|
|
1365
|
+
inputSchema: z2.object({
|
|
1366
|
+
agentId,
|
|
1367
|
+
cursor: z2.string().max(200).optional().describe("Read after this cursor (nextCursor of the previous call)"),
|
|
1368
|
+
limit: z2.number().int().min(1).max(200).default(20).describe("How many to return (1-200, default 20)")
|
|
1369
|
+
}),
|
|
1370
|
+
outputSchema: z2.object({
|
|
1371
|
+
agentId: z2.string(),
|
|
1372
|
+
messages: z2.array(
|
|
1373
|
+
z2.looseObject({
|
|
1374
|
+
delivery_id: z2.string(),
|
|
1375
|
+
cursor: z2.string(),
|
|
1376
|
+
message
|
|
1377
|
+
})
|
|
1378
|
+
),
|
|
1379
|
+
nextCursor: z2.string().nullable(),
|
|
1380
|
+
hasMore: z2.boolean()
|
|
1381
|
+
}),
|
|
1382
|
+
annotations: READ,
|
|
1383
|
+
handler: async (api, a) => {
|
|
1384
|
+
const id2 = requireAgent(api, a.agentId);
|
|
1385
|
+
const r = await api.request("GET", `/v1/agents/${enc(id2)}/inbox`, {
|
|
1386
|
+
query: { cursor: a.cursor, limit: a.limit }
|
|
1387
|
+
});
|
|
1388
|
+
return {
|
|
1389
|
+
agentId: id2,
|
|
1390
|
+
messages: r.messages ?? [],
|
|
1391
|
+
nextCursor: r.next_cursor ?? null,
|
|
1392
|
+
hasMore: Boolean(r.has_more)
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
}),
|
|
1396
|
+
defineTool({
|
|
1397
|
+
name: "ack_agent_messages",
|
|
1398
|
+
title: "Acknowledge agent messages",
|
|
1399
|
+
description: "Acknowledge inbox messages you handled so read_agent_inbox stops returning them: upTo acks everything up to and including that delivery or message ID, ids acks just those. Acking twice is harmless. Needs the agents:read scope.",
|
|
1400
|
+
scope: "agents:read",
|
|
1401
|
+
endpoint: "POST /v1/agents/:id/inbox/ack",
|
|
1402
|
+
inputSchema: z2.object({
|
|
1403
|
+
agentId,
|
|
1404
|
+
upTo: z2.string().uuid().optional().describe("Ack everything up to and including this delivery_id"),
|
|
1405
|
+
ids: z2.array(z2.string().uuid()).min(1).max(500).optional().describe("Ack these delivery (or message) IDs")
|
|
1406
|
+
}),
|
|
1407
|
+
outputSchema: z2.object({ acked: z2.number() }),
|
|
1408
|
+
annotations: {
|
|
1409
|
+
readOnlyHint: false,
|
|
1410
|
+
destructiveHint: false,
|
|
1411
|
+
idempotentHint: true,
|
|
1412
|
+
openWorldHint: false
|
|
1413
|
+
},
|
|
1414
|
+
handler: async (api, a) => {
|
|
1415
|
+
if (!a.upTo === !a.ids) {
|
|
1416
|
+
throw new ToolError("Pass either upTo or ids.");
|
|
1417
|
+
}
|
|
1418
|
+
const id2 = requireAgent(api, a.agentId);
|
|
1419
|
+
const r = await api.request(
|
|
1420
|
+
"POST",
|
|
1421
|
+
`/v1/agents/${enc(id2)}/inbox/ack`,
|
|
1422
|
+
{ body: a.upTo ? { up_to: a.upTo } : { ids: a.ids } }
|
|
1423
|
+
);
|
|
1424
|
+
return { acked: Number(r.acked ?? 0) };
|
|
1425
|
+
}
|
|
1426
|
+
}),
|
|
1427
|
+
defineTool({
|
|
1428
|
+
name: "list_agents",
|
|
1429
|
+
title: "List agents",
|
|
1430
|
+
description: "List the organization's agents: id, slug, name, role, description, status (active, paused, retired) and how they receive messages. Use the ids for mentions and agentId. Needs the agents:read scope.",
|
|
1431
|
+
scope: "agents:read",
|
|
1432
|
+
endpoint: "GET /v1/agents",
|
|
1433
|
+
inputSchema: z2.object({
|
|
1434
|
+
includeRetired: z2.boolean().optional().describe("Include retired agents (default false)")
|
|
1435
|
+
}),
|
|
1436
|
+
outputSchema: z2.object({
|
|
1437
|
+
agents: z2.array(
|
|
1438
|
+
z2.looseObject({
|
|
1439
|
+
id: z2.string(),
|
|
1440
|
+
slug: z2.string(),
|
|
1441
|
+
name: z2.string(),
|
|
1442
|
+
status: z2.string()
|
|
1443
|
+
})
|
|
1444
|
+
)
|
|
1445
|
+
}),
|
|
1446
|
+
annotations: READ,
|
|
1447
|
+
handler: async (api, a) => {
|
|
1448
|
+
const r = await api.request("GET", "/v1/agents", {
|
|
1449
|
+
query: { include_retired: a.includeRetired || void 0 }
|
|
1450
|
+
});
|
|
1451
|
+
return { agents: r.agents ?? [] };
|
|
1452
|
+
}
|
|
1453
|
+
}),
|
|
1454
|
+
defineTool({
|
|
1455
|
+
name: "list_agent_channels",
|
|
1456
|
+
title: "List agent channels",
|
|
1457
|
+
description: "List the agent channels your key's agents are members or observers of: id, slug, name, description and whether they are archived. Post with send_agent_message (members only). Needs the agents:read scope.",
|
|
1458
|
+
scope: "agents:read",
|
|
1459
|
+
endpoint: "GET /v1/agents/channels",
|
|
1460
|
+
inputSchema: z2.object({
|
|
1461
|
+
includeArchived: z2.boolean().optional().describe("Include archived channels (default false)")
|
|
1462
|
+
}),
|
|
1463
|
+
outputSchema: z2.object({
|
|
1464
|
+
channels: z2.array(
|
|
1465
|
+
z2.looseObject({
|
|
1466
|
+
id: z2.string(),
|
|
1467
|
+
slug: z2.string(),
|
|
1468
|
+
name: z2.string(),
|
|
1469
|
+
archived: z2.boolean()
|
|
1470
|
+
})
|
|
1471
|
+
)
|
|
1472
|
+
}),
|
|
1473
|
+
annotations: READ,
|
|
1474
|
+
handler: async (api, a) => {
|
|
1475
|
+
const r = await api.request(
|
|
1476
|
+
"GET",
|
|
1477
|
+
"/v1/agents/channels",
|
|
1478
|
+
{ query: { include_archived: a.includeArchived || void 0 } }
|
|
1479
|
+
);
|
|
1480
|
+
return { channels: r.channels ?? [] };
|
|
1481
|
+
}
|
|
1482
|
+
})
|
|
1483
|
+
];
|
|
1484
|
+
var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
|
|
1485
|
+
function getAgentTool(name) {
|
|
1486
|
+
return AGENT_TOOLS.find((t) => t.name === name);
|
|
1487
|
+
}
|
|
1488
|
+
async function runAgentTool(api, name, args) {
|
|
1489
|
+
const tool = getAgentTool(name);
|
|
1490
|
+
if (!tool) throw new ToolError(`Unknown agent tool "${name}"`);
|
|
1491
|
+
const parsed = tool.inputSchema.safeParse(args ?? {});
|
|
1492
|
+
if (!parsed.success) {
|
|
1493
|
+
throw new ToolError(
|
|
1494
|
+
`Invalid arguments for ${name}: ${parsed.error.issues.map((i) => `${i.path.join(".") || "input"}: ${i.message}`).join("; ")}`
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
return await tool.handler(api, parsed.data);
|
|
1498
|
+
}
|
|
1499
|
+
function agentToolsAsJsonSchema(filter = () => true) {
|
|
1500
|
+
return AGENT_TOOLS.filter(filter).map((t) => ({
|
|
1501
|
+
name: t.name,
|
|
1502
|
+
description: t.description,
|
|
1503
|
+
parameters: z2.toJSONSchema(t.inputSchema, { io: "input" })
|
|
1504
|
+
}));
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// src/inbox.ts
|
|
1508
|
+
var z3 = __toESM(require("zod/v4"));
|
|
1509
|
+
function defineTool2(tool) {
|
|
1510
|
+
return tool;
|
|
1511
|
+
}
|
|
1512
|
+
var CONVERSATION_STATUSES = [
|
|
1513
|
+
"open",
|
|
1514
|
+
"pending",
|
|
1515
|
+
"resolved",
|
|
1516
|
+
"closed"
|
|
1517
|
+
];
|
|
1518
|
+
var CONVERSATION_PRIORITIES = [
|
|
1519
|
+
"low",
|
|
1520
|
+
"normal",
|
|
1521
|
+
"high",
|
|
1522
|
+
"urgent"
|
|
1523
|
+
];
|
|
1524
|
+
var CONVERSATION_SORTS = [
|
|
1525
|
+
"recent",
|
|
1526
|
+
"oldest",
|
|
1527
|
+
"newest",
|
|
1528
|
+
"priority"
|
|
1529
|
+
];
|
|
1530
|
+
var loose = z3.looseObject({});
|
|
1531
|
+
var id = (what) => z3.string().uuid().describe(`${what} ID`);
|
|
1532
|
+
var conversationId = id(
|
|
1533
|
+
"Conversation (from list_conversations or search_conversations)"
|
|
1534
|
+
);
|
|
1535
|
+
var person = z3.looseObject({
|
|
1536
|
+
id: z3.string(),
|
|
1537
|
+
name: z3.string().nullable().optional(),
|
|
1538
|
+
email: z3.string().nullable().optional()
|
|
1539
|
+
}).nullable();
|
|
1540
|
+
var conversationSummary = z3.looseObject({
|
|
1541
|
+
id: z3.string(),
|
|
1542
|
+
subject: z3.string().nullable().optional(),
|
|
1543
|
+
status: z3.string(),
|
|
1544
|
+
priority: z3.string().optional(),
|
|
1545
|
+
source: z3.string().nullable().optional(),
|
|
1546
|
+
tags: z3.array(z3.string()).optional(),
|
|
1547
|
+
customer: person.optional(),
|
|
1548
|
+
assignee: person.optional(),
|
|
1549
|
+
triage: z3.record(z3.string(), z3.unknown()).nullable().optional(),
|
|
1550
|
+
needsReply: z3.boolean().optional(),
|
|
1551
|
+
lastActivityAt: z3.string().nullable().optional()
|
|
1552
|
+
});
|
|
1553
|
+
var page = {
|
|
1554
|
+
total: z3.number(),
|
|
1555
|
+
limit: z3.number(),
|
|
1556
|
+
offset: z3.number(),
|
|
1557
|
+
hasMore: z3.boolean()
|
|
1558
|
+
};
|
|
1559
|
+
var listOutput = z3.object({
|
|
1560
|
+
conversations: z3.array(conversationSummary),
|
|
1561
|
+
...page
|
|
1562
|
+
});
|
|
1563
|
+
var changeOutput = z3.object({
|
|
1564
|
+
conversation: z3.looseObject({ id: z3.string(), status: z3.string() }),
|
|
1565
|
+
changes: z3.array(z3.string())
|
|
1566
|
+
});
|
|
1567
|
+
var slackOutcome = z3.string().describe(
|
|
1568
|
+
"Slack mirror: queued, posted, no_thread (not in Slack), not_connected or failed"
|
|
1569
|
+
);
|
|
1570
|
+
var tags = z3.array(z3.string().trim().min(1).max(50)).max(20);
|
|
1571
|
+
var paging = {
|
|
1572
|
+
limit: z3.number().int().min(1).max(100).default(20).describe("How many to return (1-100, default 20)"),
|
|
1573
|
+
offset: z3.number().int().min(0).default(0).describe("Skip this many")
|
|
1574
|
+
};
|
|
1575
|
+
function oneOrMany(values) {
|
|
1576
|
+
return z3.union([z3.enum(values), z3.array(z3.enum(values)).max(10)]).optional();
|
|
1577
|
+
}
|
|
1578
|
+
var asList = (v) => v === void 0 ? void 0 : Array.isArray(v) ? v.join(",") : v;
|
|
1579
|
+
var listFilters = {
|
|
1580
|
+
status: oneOrMany(CONVERSATION_STATUSES).describe(
|
|
1581
|
+
'Only these statuses: "open" or a list like ["open", "pending"]'
|
|
1582
|
+
),
|
|
1583
|
+
priority: oneOrMany(CONVERSATION_PRIORITIES).describe(
|
|
1584
|
+
'Only these priorities: "urgent" or a list'
|
|
1585
|
+
),
|
|
1586
|
+
assignee: z3.string().max(255).optional().describe('A teammate (user ID or email), "unassigned" or "assigned"'),
|
|
1587
|
+
projectId: id("Project").optional(),
|
|
1588
|
+
updatedSince: z3.iso.datetime({ offset: true }).optional().describe("Updated or with a new message since (ISO 8601)"),
|
|
1589
|
+
tag: z3.string().max(50).optional().describe("Carrying this tag"),
|
|
1590
|
+
source: z3.string().max(32).optional().describe("Entry point, e.g. live_chat, contact_form, api, email"),
|
|
1591
|
+
intent: z3.string().max(64).optional().describe("AI triage intent (triaged conversations only)"),
|
|
1592
|
+
urgency: z3.string().max(64).optional().describe("AI triage urgency (triaged conversations only)"),
|
|
1593
|
+
sort: z3.enum(CONVERSATION_SORTS).optional().describe(
|
|
1594
|
+
"recent (last activity, default), oldest, newest (created) or priority"
|
|
1595
|
+
)
|
|
1596
|
+
};
|
|
1597
|
+
async function list(api, a) {
|
|
1598
|
+
const r = await api.request("GET", "/v1/conversations", {
|
|
1599
|
+
query: {
|
|
1600
|
+
status: asList(a.status),
|
|
1601
|
+
priority: asList(a.priority),
|
|
1602
|
+
assignee: a.assignee,
|
|
1603
|
+
projectId: a.projectId,
|
|
1604
|
+
updatedSince: a.updatedSince,
|
|
1605
|
+
tag: a.tag,
|
|
1606
|
+
source: a.source,
|
|
1607
|
+
intent: a.intent,
|
|
1608
|
+
urgency: a.urgency,
|
|
1609
|
+
sort: a.sort,
|
|
1610
|
+
q: a.q,
|
|
1611
|
+
limit: a.limit,
|
|
1612
|
+
offset: a.offset
|
|
1613
|
+
}
|
|
1614
|
+
});
|
|
1615
|
+
return {
|
|
1616
|
+
conversations: r.conversations ?? [],
|
|
1617
|
+
total: Number(r.total ?? 0),
|
|
1618
|
+
limit: Number(r.limit ?? a.limit),
|
|
1619
|
+
offset: Number(r.offset ?? a.offset),
|
|
1620
|
+
hasMore: Boolean(r.hasMore)
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
var path = (cid, rest = "") => `/v1/conversations/${encodeURIComponent(cid)}${rest}`;
|
|
1624
|
+
async function patch(api, cid, body) {
|
|
1625
|
+
const r = await api.request(
|
|
1626
|
+
"PATCH",
|
|
1627
|
+
path(cid),
|
|
1628
|
+
{ body }
|
|
1629
|
+
);
|
|
1630
|
+
return {
|
|
1631
|
+
conversation: r.conversation,
|
|
1632
|
+
changes: r.changes ?? []
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
var READ2 = {
|
|
1636
|
+
readOnlyHint: true,
|
|
1637
|
+
destructiveHint: false,
|
|
1638
|
+
idempotentHint: true,
|
|
1639
|
+
openWorldHint: false
|
|
1640
|
+
};
|
|
1641
|
+
var CHANGE = {
|
|
1642
|
+
readOnlyHint: false,
|
|
1643
|
+
destructiveHint: false,
|
|
1644
|
+
idempotentHint: true,
|
|
1645
|
+
openWorldHint: true
|
|
1646
|
+
};
|
|
1647
|
+
var ROUTING_FIELDS = [
|
|
1648
|
+
"source",
|
|
1649
|
+
"project",
|
|
1650
|
+
"widget",
|
|
1651
|
+
"connection",
|
|
1652
|
+
"page_url",
|
|
1653
|
+
"page_domain",
|
|
1654
|
+
"customer_email",
|
|
1655
|
+
"customer_email_domain",
|
|
1656
|
+
"message",
|
|
1657
|
+
"subject",
|
|
1658
|
+
"tags",
|
|
1659
|
+
"priority",
|
|
1660
|
+
"language"
|
|
1661
|
+
];
|
|
1662
|
+
var ROUTING_OPERATORS = [
|
|
1663
|
+
"equals",
|
|
1664
|
+
"not_equals",
|
|
1665
|
+
"contains",
|
|
1666
|
+
"not_contains",
|
|
1667
|
+
"starts_with",
|
|
1668
|
+
"ends_with",
|
|
1669
|
+
"matches"
|
|
1670
|
+
];
|
|
1671
|
+
var routingConditions = z3.object({
|
|
1672
|
+
match: z3.enum(["all", "any"]).describe("all = every condition holds, any = one is enough"),
|
|
1673
|
+
conditions: z3.array(
|
|
1674
|
+
z3.object({
|
|
1675
|
+
field: z3.enum(ROUTING_FIELDS),
|
|
1676
|
+
operator: z3.enum(ROUTING_OPERATORS),
|
|
1677
|
+
value: z3.union([z3.string(), z3.array(z3.string())]).describe(
|
|
1678
|
+
"A value, or a list (positive operators: any matches; negative: none does). Case-insensitive; matches uses * and ? wildcards."
|
|
1679
|
+
)
|
|
1680
|
+
})
|
|
1681
|
+
).max(50).describe("Empty = matches every new conversation (catch-all)")
|
|
1682
|
+
}).describe(
|
|
1683
|
+
'Which new conversations the rule catches, tested on the first message, e.g. { "match": "any", "conditions": [{ "field": "message", "operator": "contains", "value": ["refund", "invoice"] }] }'
|
|
1684
|
+
);
|
|
1685
|
+
var ruleFields = {
|
|
1686
|
+
name: z3.string().trim().min(1).max(255).describe("Rule name"),
|
|
1687
|
+
targetChannelId: z3.string().regex(/^[A-Z0-9]{2,30}$/, "a Slack channel ID like C0123ABCD").describe("Slack channel ID to post matching conversations to"),
|
|
1688
|
+
conditions: routingConditions,
|
|
1689
|
+
projectId: id("Project (default: the oldest project)").optional(),
|
|
1690
|
+
widgetId: id("Widget").nullable().optional(),
|
|
1691
|
+
integrationId: id("Integration (default: the Slack workspace)").optional(),
|
|
1692
|
+
assignToUserId: id("Teammate to assign matching conversations to").nullable().optional(),
|
|
1693
|
+
isActive: z3.boolean().optional().describe("Default true"),
|
|
1694
|
+
priority: z3.number().int().min(-1e6).max(1e6).optional().describe("Higher runs first (default: after every rule)")
|
|
1695
|
+
};
|
|
1696
|
+
var rule = z3.looseObject({
|
|
1697
|
+
id: z3.string(),
|
|
1698
|
+
name: z3.string(),
|
|
1699
|
+
priority: z3.number(),
|
|
1700
|
+
is_active: z3.boolean(),
|
|
1701
|
+
target_channel_id: z3.string()
|
|
1702
|
+
});
|
|
1703
|
+
var INBOX_TOOLS = [
|
|
1704
|
+
defineTool2({
|
|
1705
|
+
name: "list_conversations",
|
|
1706
|
+
title: "List conversations",
|
|
1707
|
+
description: "List support conversations, most recent activity first, filtered by status, priority, assignee, project, tag, source, last update or AI triage (intent/urgency). Each row has the customer, assignee, needsReply (the customer spoke last) and a preview of the latest message. Use get_conversation for the thread. Needs the inbox:read (or read) scope.",
|
|
1708
|
+
scope: "inbox:read",
|
|
1709
|
+
endpoint: "GET /v1/conversations",
|
|
1710
|
+
inputSchema: z3.object({ ...listFilters, ...paging }),
|
|
1711
|
+
outputSchema: listOutput,
|
|
1712
|
+
annotations: READ2,
|
|
1713
|
+
handler: (api, a) => list(api, a)
|
|
1714
|
+
}),
|
|
1715
|
+
defineTool2({
|
|
1716
|
+
name: "search_conversations",
|
|
1717
|
+
title: "Search conversations",
|
|
1718
|
+
description: "Find conversations whose subject, customer name or email, or any message contains the text (case-insensitive), with the same filters as list_conversations. Needs the inbox:read (or read) scope.",
|
|
1719
|
+
scope: "inbox:read",
|
|
1720
|
+
endpoint: "GET /v1/conversations?q=",
|
|
1721
|
+
inputSchema: z3.object({
|
|
1722
|
+
query: z3.string().trim().min(1).max(200).describe("Text to look for"),
|
|
1723
|
+
...listFilters,
|
|
1724
|
+
...paging
|
|
1725
|
+
}),
|
|
1726
|
+
outputSchema: listOutput,
|
|
1727
|
+
annotations: READ2,
|
|
1728
|
+
handler: (api, { query, ...a }) => list(api, { ...a, q: query })
|
|
1729
|
+
}),
|
|
1730
|
+
defineTool2({
|
|
1731
|
+
name: "get_conversation",
|
|
1732
|
+
title: "Get conversation",
|
|
1733
|
+
description: "Read one conversation with its full thread: customer, agent, system and bot messages in order, internal notes included (isInternal: true, never seen by the customer), plus status, priority, tags, assignee, customer, triage and its Slack thread. Needs the inbox:read (or read) scope.",
|
|
1734
|
+
scope: "inbox:read",
|
|
1735
|
+
endpoint: "GET /v1/conversations/:id",
|
|
1736
|
+
inputSchema: z3.object({ conversationId }),
|
|
1737
|
+
outputSchema: z3.object({
|
|
1738
|
+
conversation: z3.looseObject({
|
|
1739
|
+
id: z3.string(),
|
|
1740
|
+
status: z3.string(),
|
|
1741
|
+
messages: z3.array(loose)
|
|
1742
|
+
})
|
|
1743
|
+
}),
|
|
1744
|
+
annotations: READ2,
|
|
1745
|
+
handler: async (api, { conversationId: cid }) => {
|
|
1746
|
+
const r = await api.request("GET", path(cid));
|
|
1747
|
+
return { conversation: r.conversation };
|
|
1748
|
+
}
|
|
1749
|
+
}),
|
|
1750
|
+
defineTool2({
|
|
1751
|
+
name: "reply_to_conversation",
|
|
1752
|
+
title: "Reply to conversation",
|
|
1753
|
+
description: 'Send a reply to the customer, as this API key (shown as "<key name> (agent)"). It reaches the customer like a dashboard reply (chat widget, email, SMS, ...) and is mirrored to the Slack thread; an open conversation becomes pending. Confirm the text with the user before sending. For a note only the team sees, use add_internal_note. Needs the inbox:write scope.',
|
|
1754
|
+
scope: "inbox:write",
|
|
1755
|
+
endpoint: "POST /v1/conversations/:id/reply",
|
|
1756
|
+
inputSchema: z3.object({
|
|
1757
|
+
conversationId,
|
|
1758
|
+
content: z3.string().trim().min(1).max(1e4).describe("Reply text, as the customer will read it")
|
|
1759
|
+
}),
|
|
1760
|
+
outputSchema: z3.object({
|
|
1761
|
+
message: z3.looseObject({ id: z3.string() }),
|
|
1762
|
+
conversationStatus: z3.string(),
|
|
1763
|
+
slack: slackOutcome
|
|
1764
|
+
}),
|
|
1765
|
+
annotations: {
|
|
1766
|
+
readOnlyHint: false,
|
|
1767
|
+
destructiveHint: false,
|
|
1768
|
+
idempotentHint: false,
|
|
1769
|
+
openWorldHint: true
|
|
1770
|
+
},
|
|
1771
|
+
handler: async (api, { conversationId: cid, content }) => {
|
|
1772
|
+
const r = await api.request("POST", path(cid, "/reply"), {
|
|
1773
|
+
body: { content }
|
|
1774
|
+
});
|
|
1775
|
+
return {
|
|
1776
|
+
message: r.message,
|
|
1777
|
+
conversationStatus: String(r.conversationStatus ?? ""),
|
|
1778
|
+
slack: String(r.slack ?? "")
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
}),
|
|
1782
|
+
defineTool2({
|
|
1783
|
+
name: "add_internal_note",
|
|
1784
|
+
title: "Add internal note",
|
|
1785
|
+
description: "Add an internal note to a conversation: posted in its Slack thread for the team and kept in the conversation, never sent to the customer. Use it for findings, handoffs and suggested replies. Needs the inbox:write scope.",
|
|
1786
|
+
scope: "inbox:write",
|
|
1787
|
+
endpoint: "POST /v1/conversations/:id/notes",
|
|
1788
|
+
inputSchema: z3.object({
|
|
1789
|
+
conversationId,
|
|
1790
|
+
content: z3.string().trim().min(1).max(1e4).describe("Note text")
|
|
1791
|
+
}),
|
|
1792
|
+
outputSchema: z3.object({
|
|
1793
|
+
note: z3.looseObject({ id: z3.string() }),
|
|
1794
|
+
slack: slackOutcome
|
|
1795
|
+
}),
|
|
1796
|
+
annotations: {
|
|
1797
|
+
readOnlyHint: false,
|
|
1798
|
+
destructiveHint: false,
|
|
1799
|
+
idempotentHint: false,
|
|
1800
|
+
openWorldHint: true
|
|
1801
|
+
},
|
|
1802
|
+
handler: async (api, { conversationId: cid, content }) => {
|
|
1803
|
+
const r = await api.request("POST", path(cid, "/notes"), {
|
|
1804
|
+
body: { content }
|
|
1805
|
+
});
|
|
1806
|
+
return { note: r.note, slack: String(r.slack ?? "") };
|
|
1807
|
+
}
|
|
1808
|
+
}),
|
|
1809
|
+
defineTool2({
|
|
1810
|
+
name: "assign_conversation",
|
|
1811
|
+
title: "Assign conversation",
|
|
1812
|
+
description: "Assign a conversation to a teammate (by user ID or email; owners, admins and agents, not viewers), or unassign it with null. The teammate is notified, and the change shows in the timeline and Slack. Needs the inbox:write scope.",
|
|
1813
|
+
scope: "inbox:write",
|
|
1814
|
+
endpoint: "PATCH /v1/conversations/:id",
|
|
1815
|
+
inputSchema: z3.object({
|
|
1816
|
+
conversationId,
|
|
1817
|
+
assignee: z3.string().min(1).max(255).nullable().describe("User ID or email of the teammate, or null to unassign")
|
|
1818
|
+
}),
|
|
1819
|
+
outputSchema: changeOutput,
|
|
1820
|
+
annotations: CHANGE,
|
|
1821
|
+
handler: (api, { conversationId: cid, assignee }) => patch(
|
|
1822
|
+
api,
|
|
1823
|
+
cid,
|
|
1824
|
+
assignee === null ? { assigneeId: null } : assignee.includes("@") ? { assigneeEmail: assignee } : { assigneeId: assignee }
|
|
1825
|
+
)
|
|
1826
|
+
}),
|
|
1827
|
+
defineTool2({
|
|
1828
|
+
name: "tag_conversation",
|
|
1829
|
+
title: "Tag conversation",
|
|
1830
|
+
description: "Add or remove tags on a conversation, or replace them all with `set`. Tags are lowercased; at most 20. Needs the inbox:write scope.",
|
|
1831
|
+
scope: "inbox:write",
|
|
1832
|
+
endpoint: "PATCH /v1/conversations/:id",
|
|
1833
|
+
inputSchema: z3.object({
|
|
1834
|
+
conversationId,
|
|
1835
|
+
add: tags.optional().describe("Tags to add"),
|
|
1836
|
+
remove: tags.optional().describe("Tags to remove"),
|
|
1837
|
+
set: tags.optional().describe("Replace every tag with these ([] clears them)")
|
|
1838
|
+
}),
|
|
1839
|
+
outputSchema: changeOutput,
|
|
1840
|
+
annotations: CHANGE,
|
|
1841
|
+
handler: (api, { conversationId: cid, add, remove, set }) => {
|
|
1842
|
+
if (!add?.length && !remove?.length && !set) {
|
|
1843
|
+
throw new ToolError("Pass tags to add, remove, or set.");
|
|
1844
|
+
}
|
|
1845
|
+
return patch(api, cid, {
|
|
1846
|
+
...set ? { tags: set } : {},
|
|
1847
|
+
...add?.length ? { addTags: add } : {},
|
|
1848
|
+
...remove?.length ? { removeTags: remove } : {}
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1851
|
+
}),
|
|
1852
|
+
defineTool2({
|
|
1853
|
+
name: "close_conversation",
|
|
1854
|
+
title: "Close conversation",
|
|
1855
|
+
description: "Close a conversation (or mark it resolved). Nothing is sent to the customer; the change shows in the timeline and the Slack root post. Reply first if the customer should hear back. Needs the inbox:write scope.",
|
|
1856
|
+
scope: "inbox:write",
|
|
1857
|
+
endpoint: "PATCH /v1/conversations/:id",
|
|
1858
|
+
inputSchema: z3.object({
|
|
1859
|
+
conversationId,
|
|
1860
|
+
status: z3.enum(["closed", "resolved"]).default("closed").describe("closed (default) or resolved")
|
|
1861
|
+
}),
|
|
1862
|
+
outputSchema: changeOutput,
|
|
1863
|
+
annotations: CHANGE,
|
|
1864
|
+
handler: (api, { conversationId: cid, status }) => patch(api, cid, { status })
|
|
1865
|
+
}),
|
|
1866
|
+
defineTool2({
|
|
1867
|
+
name: "reopen_conversation",
|
|
1868
|
+
title: "Reopen conversation",
|
|
1869
|
+
description: "Reopen a closed or resolved conversation (status open). Needs the inbox:write scope.",
|
|
1870
|
+
scope: "inbox:write",
|
|
1871
|
+
endpoint: "PATCH /v1/conversations/:id",
|
|
1872
|
+
inputSchema: z3.object({ conversationId }),
|
|
1873
|
+
outputSchema: changeOutput,
|
|
1874
|
+
annotations: CHANGE,
|
|
1875
|
+
handler: (api, { conversationId: cid }) => patch(api, cid, { status: "open" })
|
|
1876
|
+
}),
|
|
1877
|
+
defineTool2({
|
|
1878
|
+
name: "get_analytics_summary",
|
|
1879
|
+
title: "Get analytics summary",
|
|
1880
|
+
description: "Support KPIs for a period (default the last 30 days): conversations started and resolved, median first-response and resolution times, messages in and out, each with the previous period; plus the inbox right now (open, pending, unassigned, urgent, awaiting a reply). Needs the analytics:read (or read) scope.",
|
|
1881
|
+
scope: "analytics:read",
|
|
1882
|
+
endpoint: "GET /v1/analytics/summary",
|
|
1883
|
+
inputSchema: z3.object({
|
|
1884
|
+
days: z3.number().int().min(1).max(366).optional().describe("Period length in days, ending today (default 30)"),
|
|
1885
|
+
from: z3.iso.date().optional().describe("First day, YYYY-MM-DD"),
|
|
1886
|
+
to: z3.iso.date().optional().describe("Last day, YYYY-MM-DD"),
|
|
1887
|
+
tz: z3.string().max(64).optional().describe("IANA time zone for day boundaries (default UTC)"),
|
|
1888
|
+
projectId: id("Project").optional()
|
|
1889
|
+
}),
|
|
1890
|
+
outputSchema: z3.looseObject({ range: loose, kpis: loose, inbox: loose }),
|
|
1891
|
+
annotations: READ2,
|
|
1892
|
+
handler: async (api, args) => api.request(
|
|
1893
|
+
"GET",
|
|
1894
|
+
"/v1/analytics/summary",
|
|
1895
|
+
{ query: args }
|
|
1896
|
+
)
|
|
1897
|
+
}),
|
|
1898
|
+
defineTool2({
|
|
1899
|
+
name: "list_routing_rules",
|
|
1900
|
+
title: "List routing rules",
|
|
1901
|
+
description: "List the routing rules in evaluation order (highest priority first): which new conversations each one sends to which Slack channel, and who it assigns. Needs the routing:write, inbox:read or read scope.",
|
|
1902
|
+
scope: "routing:read",
|
|
1903
|
+
endpoint: "GET /v1/routing-rules",
|
|
1904
|
+
inputSchema: z3.object({}),
|
|
1905
|
+
outputSchema: z3.object({ rules: z3.array(rule) }),
|
|
1906
|
+
annotations: READ2,
|
|
1907
|
+
handler: async (api) => {
|
|
1908
|
+
const r = await api.request(
|
|
1909
|
+
"GET",
|
|
1910
|
+
"/v1/routing-rules"
|
|
1911
|
+
);
|
|
1912
|
+
return { rules: r.routing_rules ?? [] };
|
|
1913
|
+
}
|
|
1914
|
+
}),
|
|
1915
|
+
defineTool2({
|
|
1916
|
+
name: "create_routing_rule",
|
|
1917
|
+
title: "Create routing rule",
|
|
1918
|
+
description: "Create a routing rule: new conversations whose first message matches `conditions` are posted to `targetChannelId` instead of the default channel (and optionally assigned). It runs after the existing rules unless `priority` is given. Confirm with the user first. Needs the routing:write scope.",
|
|
1919
|
+
scope: "routing:write",
|
|
1920
|
+
endpoint: "POST /v1/routing-rules",
|
|
1921
|
+
inputSchema: z3.object(ruleFields),
|
|
1922
|
+
outputSchema: z3.object({ rule }),
|
|
1923
|
+
annotations: {
|
|
1924
|
+
readOnlyHint: false,
|
|
1925
|
+
destructiveHint: false,
|
|
1926
|
+
idempotentHint: false,
|
|
1927
|
+
openWorldHint: false
|
|
1928
|
+
},
|
|
1929
|
+
handler: async (api, body) => {
|
|
1930
|
+
const r = await api.request(
|
|
1931
|
+
"POST",
|
|
1932
|
+
"/v1/routing-rules",
|
|
1933
|
+
{ body }
|
|
1934
|
+
);
|
|
1935
|
+
return { rule: r.routing_rule };
|
|
1936
|
+
}
|
|
1937
|
+
}),
|
|
1938
|
+
defineTool2({
|
|
1939
|
+
name: "update_routing_rule",
|
|
1940
|
+
title: "Update routing rule",
|
|
1941
|
+
description: "Change a routing rule (from list_routing_rules); omitted fields keep their value, and `conditions` replaces the whole condition group. Use isActive: false to pause a rule. Needs the routing:write scope.",
|
|
1942
|
+
scope: "routing:write",
|
|
1943
|
+
endpoint: "PATCH /v1/routing-rules/:id",
|
|
1944
|
+
inputSchema: z3.object({
|
|
1945
|
+
ruleId: id("Routing rule"),
|
|
1946
|
+
name: ruleFields.name.optional(),
|
|
1947
|
+
targetChannelId: ruleFields.targetChannelId.optional(),
|
|
1948
|
+
conditions: routingConditions.optional(),
|
|
1949
|
+
projectId: ruleFields.projectId,
|
|
1950
|
+
widgetId: ruleFields.widgetId,
|
|
1951
|
+
integrationId: ruleFields.integrationId,
|
|
1952
|
+
assignToUserId: ruleFields.assignToUserId,
|
|
1953
|
+
isActive: ruleFields.isActive,
|
|
1954
|
+
priority: ruleFields.priority
|
|
1955
|
+
}),
|
|
1956
|
+
outputSchema: z3.object({ rule }),
|
|
1957
|
+
annotations: {
|
|
1958
|
+
readOnlyHint: false,
|
|
1959
|
+
destructiveHint: false,
|
|
1960
|
+
idempotentHint: true,
|
|
1961
|
+
openWorldHint: false
|
|
1962
|
+
},
|
|
1963
|
+
handler: async (api, { ruleId, ...body }) => {
|
|
1964
|
+
if (Object.values(body).every((v) => v === void 0)) {
|
|
1965
|
+
throw new ToolError("Pass at least one field to change.");
|
|
1966
|
+
}
|
|
1967
|
+
const r = await api.request(
|
|
1968
|
+
"PATCH",
|
|
1969
|
+
`/v1/routing-rules/${encodeURIComponent(ruleId)}`,
|
|
1970
|
+
{ body }
|
|
1971
|
+
);
|
|
1972
|
+
return { rule: r.routing_rule };
|
|
1973
|
+
}
|
|
1974
|
+
}),
|
|
1975
|
+
defineTool2({
|
|
1976
|
+
name: "delete_routing_rule",
|
|
1977
|
+
title: "Delete routing rule",
|
|
1978
|
+
description: "Delete a routing rule for good. To stop it temporarily, use update_routing_rule with isActive: false instead. Confirm with the user first. Needs the routing:write scope.",
|
|
1979
|
+
scope: "routing:write",
|
|
1980
|
+
endpoint: "DELETE /v1/routing-rules/:id",
|
|
1981
|
+
inputSchema: z3.object({ ruleId: id("Routing rule") }),
|
|
1982
|
+
outputSchema: z3.object({ deleted: z3.boolean(), ruleId: z3.string() }),
|
|
1983
|
+
annotations: {
|
|
1984
|
+
readOnlyHint: false,
|
|
1985
|
+
destructiveHint: true,
|
|
1986
|
+
idempotentHint: true,
|
|
1987
|
+
openWorldHint: false
|
|
1988
|
+
},
|
|
1989
|
+
handler: async (api, { ruleId }) => {
|
|
1990
|
+
await api.request(
|
|
1991
|
+
"DELETE",
|
|
1992
|
+
`/v1/routing-rules/${encodeURIComponent(ruleId)}`
|
|
1993
|
+
);
|
|
1994
|
+
return { deleted: true, ruleId };
|
|
1995
|
+
}
|
|
1996
|
+
})
|
|
1997
|
+
];
|
|
1998
|
+
var INBOX_TOOL_NAMES = INBOX_TOOLS.map((t) => t.name);
|
|
1999
|
+
function getInboxTool(name) {
|
|
2000
|
+
return INBOX_TOOLS.find((t) => t.name === name);
|
|
2001
|
+
}
|
|
2002
|
+
async function runInboxTool(api, name, args) {
|
|
2003
|
+
const tool = getInboxTool(name);
|
|
2004
|
+
if (!tool) throw new ToolError(`Unknown inbox tool "${name}"`);
|
|
2005
|
+
const parsed = tool.inputSchema.safeParse(args ?? {});
|
|
2006
|
+
if (!parsed.success) {
|
|
2007
|
+
throw new ToolError(
|
|
2008
|
+
`Invalid arguments for ${name}: ${parsed.error.issues.map((i) => `${i.path.join(".") || "input"}: ${i.message}`).join("; ")}`
|
|
2009
|
+
);
|
|
2010
|
+
}
|
|
2011
|
+
return await tool.handler(api, parsed.data);
|
|
2012
|
+
}
|
|
2013
|
+
function inboxToolsAsJsonSchema(filter = () => true) {
|
|
2014
|
+
return INBOX_TOOLS.filter(filter).map((t) => ({
|
|
2015
|
+
name: t.name,
|
|
2016
|
+
description: t.description,
|
|
2017
|
+
parameters: z3.toJSONSchema(t.inputSchema, { io: "input" })
|
|
2018
|
+
}));
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
// src/tools.ts
|
|
2022
|
+
function ok(data2) {
|
|
622
2023
|
return {
|
|
623
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
624
|
-
structuredContent:
|
|
2024
|
+
content: [{ type: "text", text: JSON.stringify(data2, null, 2) }],
|
|
2025
|
+
structuredContent: data2
|
|
625
2026
|
};
|
|
626
2027
|
}
|
|
627
2028
|
async function run(fn) {
|
|
@@ -635,25 +2036,15 @@ async function run(fn) {
|
|
|
635
2036
|
};
|
|
636
2037
|
}
|
|
637
2038
|
}
|
|
638
|
-
var
|
|
639
|
-
var
|
|
640
|
-
var
|
|
641
|
-
total:
|
|
642
|
-
limit:
|
|
643
|
-
offset:
|
|
644
|
-
hasMore:
|
|
2039
|
+
var record4 = z4.record(z4.string(), z4.unknown());
|
|
2040
|
+
var loose2 = z4.looseObject({});
|
|
2041
|
+
var page2 = {
|
|
2042
|
+
total: z4.number(),
|
|
2043
|
+
limit: z4.number(),
|
|
2044
|
+
offset: z4.number(),
|
|
2045
|
+
hasMore: z4.boolean()
|
|
645
2046
|
};
|
|
646
|
-
var
|
|
647
|
-
id: z2.string(),
|
|
648
|
-
subject: z2.string().nullable().optional(),
|
|
649
|
-
status: z2.string(),
|
|
650
|
-
priority: z2.string().nullable().optional(),
|
|
651
|
-
customerEmail: z2.string().nullable().optional(),
|
|
652
|
-
customerName: z2.string().nullable().optional(),
|
|
653
|
-
tags: z2.array(z2.string()).nullable().optional(),
|
|
654
|
-
createdAt: z2.string().optional()
|
|
655
|
-
});
|
|
656
|
-
var READ = {
|
|
2047
|
+
var READ3 = {
|
|
657
2048
|
readOnlyHint: true,
|
|
658
2049
|
destructiveHint: false,
|
|
659
2050
|
idempotentHint: true,
|
|
@@ -665,9 +2056,9 @@ function registerServerTools(server, api) {
|
|
|
665
2056
|
{
|
|
666
2057
|
title: "Get project",
|
|
667
2058
|
description: "Show the Channlize project behind the server API key: name, ID, totals, the key's permissions and whether Slack is connected. Start here to check the setup.",
|
|
668
|
-
inputSchema:
|
|
669
|
-
outputSchema:
|
|
670
|
-
annotations:
|
|
2059
|
+
inputSchema: z4.object({}),
|
|
2060
|
+
outputSchema: z4.object({ project: loose2 }),
|
|
2061
|
+
annotations: READ3
|
|
671
2062
|
},
|
|
672
2063
|
() => run(async () => {
|
|
673
2064
|
const r = await api.request("GET", "/projects/me");
|
|
@@ -679,9 +2070,9 @@ function registerServerTools(server, api) {
|
|
|
679
2070
|
{
|
|
680
2071
|
title: "Get analytics",
|
|
681
2072
|
description: "Daily support activity for the last 30 days (UTC): conversations created and resolved, customer and agent messages.",
|
|
682
|
-
inputSchema:
|
|
683
|
-
outputSchema:
|
|
684
|
-
annotations:
|
|
2073
|
+
inputSchema: z4.object({}),
|
|
2074
|
+
outputSchema: z4.object({ analytics: loose2 }),
|
|
2075
|
+
annotations: READ3
|
|
685
2076
|
},
|
|
686
2077
|
() => run(async () => {
|
|
687
2078
|
const r = await api.request(
|
|
@@ -691,75 +2082,20 @@ function registerServerTools(server, api) {
|
|
|
691
2082
|
return { analytics: r.analytics };
|
|
692
2083
|
})
|
|
693
2084
|
);
|
|
694
|
-
server.registerTool(
|
|
695
|
-
"list_conversations",
|
|
696
|
-
{
|
|
697
|
-
title: "List conversations",
|
|
698
|
-
description: "List the organization's most recent support conversations (newest first, from the latest 50), optionally filtered by status or priority. Use get_conversation for the messages.",
|
|
699
|
-
inputSchema: z2.object({
|
|
700
|
-
status: z2.enum(STATUSES).optional().describe("Only conversations with this status"),
|
|
701
|
-
priority: z2.enum(PRIORITIES).optional().describe("Only conversations with this priority"),
|
|
702
|
-
limit: z2.number().int().min(1).max(50).default(20).describe("How many to return (1-50, default 20)")
|
|
703
|
-
}),
|
|
704
|
-
outputSchema: z2.object({
|
|
705
|
-
conversations: z2.array(conversationSummary),
|
|
706
|
-
returned: z2.number(),
|
|
707
|
-
matched: z2.number(),
|
|
708
|
-
scanned: z2.number()
|
|
709
|
-
}),
|
|
710
|
-
annotations: READ
|
|
711
|
-
},
|
|
712
|
-
({ status, priority, limit }) => run(async () => {
|
|
713
|
-
const r = await api.request(
|
|
714
|
-
"GET",
|
|
715
|
-
"/conversations"
|
|
716
|
-
);
|
|
717
|
-
const all = r.conversations ?? [];
|
|
718
|
-
const matched = all.filter(
|
|
719
|
-
(c) => (!status || c.status === status) && (!priority || c.priority === priority)
|
|
720
|
-
);
|
|
721
|
-
const conversations = matched.slice(0, limit);
|
|
722
|
-
return {
|
|
723
|
-
conversations,
|
|
724
|
-
returned: conversations.length,
|
|
725
|
-
matched: matched.length,
|
|
726
|
-
scanned: all.length
|
|
727
|
-
};
|
|
728
|
-
})
|
|
729
|
-
);
|
|
730
|
-
server.registerTool(
|
|
731
|
-
"get_conversation",
|
|
732
|
-
{
|
|
733
|
-
title: "Get conversation",
|
|
734
|
-
description: "Read one conversation with its full message thread (customer, agent and system messages), status, priority and tags.",
|
|
735
|
-
inputSchema: z2.object({
|
|
736
|
-
conversationId: z2.string().uuid().describe("Conversation ID (from list_conversations)")
|
|
737
|
-
}),
|
|
738
|
-
outputSchema: z2.object({ conversation: loose }),
|
|
739
|
-
annotations: READ
|
|
740
|
-
},
|
|
741
|
-
({ conversationId }) => run(async () => {
|
|
742
|
-
const conversation = await api.request(
|
|
743
|
-
"GET",
|
|
744
|
-
`/conversations/${encodeURIComponent(conversationId)}`
|
|
745
|
-
);
|
|
746
|
-
return { conversation };
|
|
747
|
-
})
|
|
748
|
-
);
|
|
749
2085
|
server.registerTool(
|
|
750
2086
|
"create_conversation",
|
|
751
2087
|
{
|
|
752
2088
|
title: "Create conversation",
|
|
753
2089
|
description: "Open a new support conversation on behalf of a customer. It is posted to Slack (the given channel, else routing rules, else the default channel). Needs the write scope. Repeating the same customer, subject and message within 30 seconds returns the existing conversation.",
|
|
754
|
-
inputSchema:
|
|
755
|
-
customerEmail:
|
|
756
|
-
customerName:
|
|
757
|
-
subject:
|
|
758
|
-
message:
|
|
759
|
-
channel:
|
|
760
|
-
tags:
|
|
2090
|
+
inputSchema: z4.object({
|
|
2091
|
+
customerEmail: z4.email().describe("Customer's email address"),
|
|
2092
|
+
customerName: z4.string().max(255).optional().describe("Customer's name"),
|
|
2093
|
+
subject: z4.string().min(1).max(500).describe("Subject line"),
|
|
2094
|
+
message: z4.string().min(1).describe("First message, from the customer"),
|
|
2095
|
+
channel: z4.string().optional().describe("Slack channel ID to post to, overriding routing rules"),
|
|
2096
|
+
tags: z4.array(z4.string()).max(20).optional().describe("Tags")
|
|
761
2097
|
}),
|
|
762
|
-
outputSchema:
|
|
2098
|
+
outputSchema: z4.object({ conversation: loose2 }),
|
|
763
2099
|
annotations: {
|
|
764
2100
|
readOnlyHint: false,
|
|
765
2101
|
destructiveHint: false,
|
|
@@ -779,12 +2115,12 @@ function registerServerTools(server, api) {
|
|
|
779
2115
|
{
|
|
780
2116
|
title: "List contacts",
|
|
781
2117
|
description: "List the customers who contacted the organization, with their conversation count. Paginate with limit/offset.",
|
|
782
|
-
inputSchema:
|
|
783
|
-
limit:
|
|
784
|
-
offset:
|
|
2118
|
+
inputSchema: z4.object({
|
|
2119
|
+
limit: z4.number().int().min(1).max(100).default(50),
|
|
2120
|
+
offset: z4.number().int().min(0).default(0)
|
|
785
2121
|
}),
|
|
786
|
-
outputSchema:
|
|
787
|
-
annotations:
|
|
2122
|
+
outputSchema: z4.object({ contacts: z4.array(loose2), ...page2 }),
|
|
2123
|
+
annotations: READ3
|
|
788
2124
|
},
|
|
789
2125
|
({ limit, offset }) => run(async () => {
|
|
790
2126
|
const r = await api.request("GET", "/contacts", {
|
|
@@ -804,11 +2140,11 @@ function registerServerTools(server, api) {
|
|
|
804
2140
|
{
|
|
805
2141
|
title: "Get contact",
|
|
806
2142
|
description: "Read one contact (customer) by ID.",
|
|
807
|
-
inputSchema:
|
|
808
|
-
contactId:
|
|
2143
|
+
inputSchema: z4.object({
|
|
2144
|
+
contactId: z4.string().uuid().describe("Contact ID (from list_contacts)")
|
|
809
2145
|
}),
|
|
810
|
-
outputSchema:
|
|
811
|
-
annotations:
|
|
2146
|
+
outputSchema: z4.object({ contact: loose2 }),
|
|
2147
|
+
annotations: READ3
|
|
812
2148
|
},
|
|
813
2149
|
({ contactId }) => run(async () => {
|
|
814
2150
|
const contact = await api.request(
|
|
@@ -823,14 +2159,14 @@ function registerServerTools(server, api) {
|
|
|
823
2159
|
{
|
|
824
2160
|
title: "Create contact",
|
|
825
2161
|
description: "Add a customer by email, or return the existing one with that email. Needs the write scope.",
|
|
826
|
-
inputSchema:
|
|
827
|
-
email:
|
|
828
|
-
name:
|
|
829
|
-
company:
|
|
830
|
-
phone:
|
|
831
|
-
tags:
|
|
2162
|
+
inputSchema: z4.object({
|
|
2163
|
+
email: z4.email(),
|
|
2164
|
+
name: z4.string().max(255).optional(),
|
|
2165
|
+
company: z4.string().optional(),
|
|
2166
|
+
phone: z4.string().optional(),
|
|
2167
|
+
tags: z4.array(z4.string()).max(20).optional()
|
|
832
2168
|
}),
|
|
833
|
-
outputSchema:
|
|
2169
|
+
outputSchema: z4.object({ contact: loose2 }),
|
|
834
2170
|
annotations: {
|
|
835
2171
|
readOnlyHint: false,
|
|
836
2172
|
destructiveHint: false,
|
|
@@ -850,23 +2186,23 @@ function registerServerTools(server, api) {
|
|
|
850
2186
|
{
|
|
851
2187
|
title: "Send an Event to Slack",
|
|
852
2188
|
description: 'Post an Event to Slack: the event key (e.g. "user.subscribed") picks the channel and message template set under Events in the dashboard, and `data` fills its {{placeholders}}. An unknown key is saved as a disabled draft and answered with status "skipped". Needs the write scope. Pass an idempotencyKey so a retry never posts twice.',
|
|
853
|
-
inputSchema:
|
|
854
|
-
event:
|
|
855
|
-
|
|
2189
|
+
inputSchema: z4.object({
|
|
2190
|
+
event: z4.string().max(100).regex(
|
|
2191
|
+
import_core3.EVENT_KEY_PATTERN,
|
|
856
2192
|
"lowercase letters, digits, dots, underscores or dashes"
|
|
857
2193
|
).describe("Event key, e.g. user.subscribed"),
|
|
858
|
-
data:
|
|
2194
|
+
data: record4.optional().describe(
|
|
859
2195
|
'Template fields, e.g. { "username": "ada", "plan": "Pro" }'
|
|
860
2196
|
),
|
|
861
|
-
idempotencyKey:
|
|
862
|
-
channel:
|
|
2197
|
+
idempotencyKey: z4.string().min(1).max(255).optional().describe("Send at most once per key (retry-safe)"),
|
|
2198
|
+
channel: z4.string().optional().describe("Slack channel ID instead of the event's channel")
|
|
863
2199
|
}),
|
|
864
|
-
outputSchema:
|
|
865
|
-
id:
|
|
866
|
-
event:
|
|
867
|
-
status:
|
|
868
|
-
reason:
|
|
869
|
-
duplicate:
|
|
2200
|
+
outputSchema: z4.looseObject({
|
|
2201
|
+
id: z4.string(),
|
|
2202
|
+
event: z4.string(),
|
|
2203
|
+
status: z4.string(),
|
|
2204
|
+
reason: z4.string().optional(),
|
|
2205
|
+
duplicate: z4.boolean().optional()
|
|
870
2206
|
}),
|
|
871
2207
|
annotations: {
|
|
872
2208
|
readOnlyHint: false,
|
|
@@ -875,9 +2211,9 @@ function registerServerTools(server, api) {
|
|
|
875
2211
|
openWorldHint: true
|
|
876
2212
|
}
|
|
877
2213
|
},
|
|
878
|
-
({ event, data, idempotencyKey, channel }) => run(async () => {
|
|
2214
|
+
({ event, data: data2, idempotencyKey, channel }) => run(async () => {
|
|
879
2215
|
const client = await api.notifier();
|
|
880
|
-
const r = await client.notify(event,
|
|
2216
|
+
const r = await client.notify(event, data2, { idempotencyKey, channel });
|
|
881
2217
|
return {
|
|
882
2218
|
id: r.id,
|
|
883
2219
|
event: r.event,
|
|
@@ -892,12 +2228,12 @@ function registerServerTools(server, api) {
|
|
|
892
2228
|
{
|
|
893
2229
|
title: "List Events",
|
|
894
2230
|
description: "List the Events configured on the project (key, name, Slack channel, message template, enabled), including disabled drafts created by unknown keys sent with notify_event.",
|
|
895
|
-
inputSchema:
|
|
896
|
-
outputSchema:
|
|
897
|
-
projectId:
|
|
898
|
-
definitions:
|
|
2231
|
+
inputSchema: z4.object({}),
|
|
2232
|
+
outputSchema: z4.object({
|
|
2233
|
+
projectId: z4.string(),
|
|
2234
|
+
definitions: z4.array(loose2)
|
|
899
2235
|
}),
|
|
900
|
-
annotations:
|
|
2236
|
+
annotations: READ3
|
|
901
2237
|
},
|
|
902
2238
|
() => run(async () => {
|
|
903
2239
|
const projectId = await api.projectId();
|
|
@@ -913,15 +2249,15 @@ function registerServerTools(server, api) {
|
|
|
913
2249
|
{
|
|
914
2250
|
title: "List Event deliveries",
|
|
915
2251
|
description: "Recent notify_event deliveries on the project, newest first, with their status (queued, sent, failed, skipped) and the reason when not sent. Use it to check why an Event did not reach Slack.",
|
|
916
|
-
inputSchema:
|
|
917
|
-
definitionId:
|
|
918
|
-
limit:
|
|
2252
|
+
inputSchema: z4.object({
|
|
2253
|
+
definitionId: z4.string().uuid().optional().describe("Only this Event (ID from list_event_definitions)"),
|
|
2254
|
+
limit: z4.number().int().min(1).max(100).default(20)
|
|
919
2255
|
}),
|
|
920
|
-
outputSchema:
|
|
921
|
-
projectId:
|
|
922
|
-
deliveries:
|
|
2256
|
+
outputSchema: z4.object({
|
|
2257
|
+
projectId: z4.string(),
|
|
2258
|
+
deliveries: z4.array(loose2)
|
|
923
2259
|
}),
|
|
924
|
-
annotations:
|
|
2260
|
+
annotations: READ3
|
|
925
2261
|
},
|
|
926
2262
|
({ definitionId, limit }) => run(async () => {
|
|
927
2263
|
const projectId = await api.projectId();
|
|
@@ -938,20 +2274,20 @@ function registerServerTools(server, api) {
|
|
|
938
2274
|
{
|
|
939
2275
|
title: "Query logs",
|
|
940
2276
|
description: "Search log tracking (trackEvent / trackServerEvent / track_log), newest first. Filter by severity, source, text (matches the name and properties) and time range; paginate with limit/offset. Covers all projects unless projectId is given.",
|
|
941
|
-
inputSchema:
|
|
942
|
-
severity:
|
|
943
|
-
source:
|
|
2277
|
+
inputSchema: z4.object({
|
|
2278
|
+
severity: z4.enum(import_core3.EVENT_SEVERITIES).optional(),
|
|
2279
|
+
source: z4.enum(["client_sdk", "server_api", "system"]).optional().describe(
|
|
944
2280
|
"client_sdk = browser (widget key), server_api = server key"
|
|
945
2281
|
),
|
|
946
|
-
search:
|
|
947
|
-
from:
|
|
948
|
-
to:
|
|
949
|
-
projectId:
|
|
950
|
-
limit:
|
|
951
|
-
offset:
|
|
2282
|
+
search: z4.string().max(200).optional().describe("Text to look for"),
|
|
2283
|
+
from: z4.iso.datetime({ offset: true }).optional().describe("ISO 8601 start"),
|
|
2284
|
+
to: z4.iso.datetime({ offset: true }).optional().describe("ISO 8601 end"),
|
|
2285
|
+
projectId: z4.string().uuid().optional(),
|
|
2286
|
+
limit: z4.number().int().min(1).max(100).default(50),
|
|
2287
|
+
offset: z4.number().int().min(0).default(0)
|
|
952
2288
|
}),
|
|
953
|
-
outputSchema:
|
|
954
|
-
annotations:
|
|
2289
|
+
outputSchema: z4.object({ events: z4.array(loose2), ...page2 }),
|
|
2290
|
+
annotations: READ3
|
|
955
2291
|
},
|
|
956
2292
|
(args) => run(async () => {
|
|
957
2293
|
const r = await api.request("GET", "/v1/events", { query: args });
|
|
@@ -969,12 +2305,12 @@ function registerServerTools(server, api) {
|
|
|
969
2305
|
{
|
|
970
2306
|
title: "Record a log",
|
|
971
2307
|
description: "Record a log line under Log tracking from a trusted server context (source server_api). Needs the write scope. For Slack messages about product events use notify_event instead.",
|
|
972
|
-
inputSchema:
|
|
973
|
-
name:
|
|
974
|
-
severity:
|
|
975
|
-
properties:
|
|
2308
|
+
inputSchema: z4.object({
|
|
2309
|
+
name: z4.string().min(1).max(255).describe("Log name, e.g. payment.failed"),
|
|
2310
|
+
severity: z4.enum(import_core3.EVENT_SEVERITIES).default("info"),
|
|
2311
|
+
properties: record4.optional().describe("Extra fields")
|
|
976
2312
|
}),
|
|
977
|
-
outputSchema:
|
|
2313
|
+
outputSchema: z4.object({ id: z4.string(), projectId: z4.string() }),
|
|
978
2314
|
annotations: {
|
|
979
2315
|
readOnlyHint: false,
|
|
980
2316
|
destructiveHint: false,
|
|
@@ -1003,16 +2339,16 @@ function registerWidgetTools(server, api) {
|
|
|
1003
2339
|
{
|
|
1004
2340
|
title: "Submit contact form",
|
|
1005
2341
|
description: "Submit the website contact form as a visitor (widget key). Creates a conversation that is posted to Slack. Useful to test the setup end to end.",
|
|
1006
|
-
inputSchema:
|
|
1007
|
-
name:
|
|
1008
|
-
email:
|
|
1009
|
-
subject:
|
|
1010
|
-
message:
|
|
1011
|
-
pageUrl:
|
|
2342
|
+
inputSchema: z4.object({
|
|
2343
|
+
name: z4.string().min(1).describe("Visitor's name"),
|
|
2344
|
+
email: z4.email().describe("Visitor's email"),
|
|
2345
|
+
subject: z4.string().optional(),
|
|
2346
|
+
message: z4.string().min(1),
|
|
2347
|
+
pageUrl: z4.url().optional().describe("Page the form was sent from")
|
|
1012
2348
|
}),
|
|
1013
|
-
outputSchema:
|
|
1014
|
-
conversationId:
|
|
1015
|
-
message:
|
|
2349
|
+
outputSchema: z4.looseObject({
|
|
2350
|
+
conversationId: z4.string().optional(),
|
|
2351
|
+
message: z4.string().optional()
|
|
1016
2352
|
}),
|
|
1017
2353
|
annotations: VISITOR
|
|
1018
2354
|
},
|
|
@@ -1026,12 +2362,12 @@ function registerWidgetTools(server, api) {
|
|
|
1026
2362
|
{
|
|
1027
2363
|
title: "Start chat session",
|
|
1028
2364
|
description: "Start a live chat session as a website visitor (widget key). Returns the session ID for send_message.",
|
|
1029
|
-
inputSchema:
|
|
1030
|
-
metadata:
|
|
2365
|
+
inputSchema: z4.object({
|
|
2366
|
+
metadata: record4.optional().describe("Metadata to attach (e.g. user info)")
|
|
1031
2367
|
}),
|
|
1032
|
-
outputSchema:
|
|
1033
|
-
sessionId:
|
|
1034
|
-
status:
|
|
2368
|
+
outputSchema: z4.looseObject({
|
|
2369
|
+
sessionId: z4.string(),
|
|
2370
|
+
status: z4.string()
|
|
1035
2371
|
}),
|
|
1036
2372
|
annotations: VISITOR
|
|
1037
2373
|
},
|
|
@@ -1049,13 +2385,13 @@ function registerWidgetTools(server, api) {
|
|
|
1049
2385
|
{
|
|
1050
2386
|
title: "Send chat message",
|
|
1051
2387
|
description: "Send a visitor message in a chat session (widget key); it is posted to the Slack thread.",
|
|
1052
|
-
inputSchema:
|
|
1053
|
-
sessionId:
|
|
1054
|
-
content:
|
|
2388
|
+
inputSchema: z4.object({
|
|
2389
|
+
sessionId: z4.string().min(1).describe("From create_chat_session"),
|
|
2390
|
+
content: z4.string().min(1).max(5e3)
|
|
1055
2391
|
}),
|
|
1056
|
-
outputSchema:
|
|
1057
|
-
messageId:
|
|
1058
|
-
sessionStatus:
|
|
2392
|
+
outputSchema: z4.looseObject({
|
|
2393
|
+
messageId: z4.string(),
|
|
2394
|
+
sessionStatus: z4.string()
|
|
1059
2395
|
}),
|
|
1060
2396
|
annotations: VISITOR
|
|
1061
2397
|
},
|
|
@@ -1069,16 +2405,16 @@ function registerWidgetTools(server, api) {
|
|
|
1069
2405
|
{
|
|
1070
2406
|
title: "Read chat messages",
|
|
1071
2407
|
description: "Read a chat session's messages, including agent replies from Slack (widget key).",
|
|
1072
|
-
inputSchema:
|
|
1073
|
-
sessionId:
|
|
1074
|
-
limit:
|
|
1075
|
-
offset:
|
|
2408
|
+
inputSchema: z4.object({
|
|
2409
|
+
sessionId: z4.string().min(1),
|
|
2410
|
+
limit: z4.number().int().min(1).max(100).default(50),
|
|
2411
|
+
offset: z4.number().int().min(0).default(0)
|
|
1076
2412
|
}),
|
|
1077
|
-
outputSchema:
|
|
1078
|
-
sessionId:
|
|
1079
|
-
messages:
|
|
2413
|
+
outputSchema: z4.object({
|
|
2414
|
+
sessionId: z4.string(),
|
|
2415
|
+
messages: z4.array(loose2)
|
|
1080
2416
|
}),
|
|
1081
|
-
annotations: { ...
|
|
2417
|
+
annotations: { ...READ3, openWorldHint: true }
|
|
1082
2418
|
},
|
|
1083
2419
|
({ sessionId, limit, offset }) => run(async () => {
|
|
1084
2420
|
const messages = await api.widget().getChatMessages(sessionId, limit, offset);
|
|
@@ -1100,10 +2436,10 @@ function registerWidgetTools(server, api) {
|
|
|
1100
2436
|
{
|
|
1101
2437
|
title: "End chat session",
|
|
1102
2438
|
description: "End a chat session as the visitor (widget key).",
|
|
1103
|
-
inputSchema:
|
|
1104
|
-
outputSchema:
|
|
1105
|
-
sessionId:
|
|
1106
|
-
status:
|
|
2439
|
+
inputSchema: z4.object({ sessionId: z4.string().min(1) }),
|
|
2440
|
+
outputSchema: z4.looseObject({
|
|
2441
|
+
sessionId: z4.string(),
|
|
2442
|
+
status: z4.string()
|
|
1107
2443
|
}),
|
|
1108
2444
|
annotations: { ...VISITOR, idempotentHint: true }
|
|
1109
2445
|
},
|
|
@@ -1113,6 +2449,27 @@ function registerWidgetTools(server, api) {
|
|
|
1113
2449
|
})
|
|
1114
2450
|
);
|
|
1115
2451
|
}
|
|
2452
|
+
function registerAll(server, api, tools) {
|
|
2453
|
+
for (const tool of tools) {
|
|
2454
|
+
server.registerTool(
|
|
2455
|
+
tool.name,
|
|
2456
|
+
{
|
|
2457
|
+
title: tool.title,
|
|
2458
|
+
description: tool.description,
|
|
2459
|
+
inputSchema: tool.inputSchema,
|
|
2460
|
+
outputSchema: tool.outputSchema,
|
|
2461
|
+
annotations: tool.annotations
|
|
2462
|
+
},
|
|
2463
|
+
(args) => run(async () => await tool.handler(api, args))
|
|
2464
|
+
);
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
function registerInboxTools(server, api) {
|
|
2468
|
+
registerAll(server, api, INBOX_TOOLS);
|
|
2469
|
+
}
|
|
2470
|
+
function registerAgentTools(server, api) {
|
|
2471
|
+
registerAll(server, api, AGENT_TOOLS);
|
|
2472
|
+
}
|
|
1116
2473
|
|
|
1117
2474
|
// src/http.ts
|
|
1118
2475
|
var import_node_http = require("http");
|
|
@@ -1144,7 +2501,7 @@ async function toRequest(req, origin) {
|
|
|
1144
2501
|
async function startHttpServer(factory, options = {}) {
|
|
1145
2502
|
const host = options.host ?? "127.0.0.1";
|
|
1146
2503
|
const port = options.port ?? 3333;
|
|
1147
|
-
const
|
|
2504
|
+
const path2 = options.path ?? "/mcp";
|
|
1148
2505
|
const local = LOCAL_HOSTS.has(host);
|
|
1149
2506
|
const handler = (0, import_server.createMcpHandler)(() => factory(), {
|
|
1150
2507
|
onerror: (err) => console.error("[channlize-mcp]", err.message)
|
|
@@ -1156,8 +2513,8 @@ async function startHttpServer(factory, options = {}) {
|
|
|
1156
2513
|
`http://${req.headers.host ?? `${host}:${port}`}`
|
|
1157
2514
|
);
|
|
1158
2515
|
const url2 = new URL(request.url);
|
|
1159
|
-
if (url2.pathname !==
|
|
1160
|
-
res.writeHead(404, { "Content-Type": "text/plain" }).end(`Not found: use ${
|
|
2516
|
+
if (url2.pathname !== path2) {
|
|
2517
|
+
res.writeHead(404, { "Content-Type": "text/plain" }).end(`Not found: use ${path2}`);
|
|
1161
2518
|
return;
|
|
1162
2519
|
}
|
|
1163
2520
|
const rejected = local ? (0, import_server.hostHeaderValidationResponse)(request, (0, import_server.localhostAllowedHostnames)()) ?? (0, import_server.originValidationResponse)(request, (0, import_server.localhostAllowedOrigins)()) : void 0;
|
|
@@ -1184,19 +2541,25 @@ async function startHttpServer(factory, options = {}) {
|
|
|
1184
2541
|
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
1185
2542
|
const shownHost = host.includes(":") ? `[${host}]` : host;
|
|
1186
2543
|
return {
|
|
1187
|
-
url: `http://${shownHost}:${actualPort}${
|
|
2544
|
+
url: `http://${shownHost}:${actualPort}${path2}`,
|
|
1188
2545
|
server,
|
|
1189
2546
|
close: () => new Promise((r) => server.close(() => r()))
|
|
1190
2547
|
};
|
|
1191
2548
|
}
|
|
1192
2549
|
|
|
1193
2550
|
// src/index.ts
|
|
1194
|
-
var
|
|
2551
|
+
var import_core4 = require("@channlize/core");
|
|
2552
|
+
var VERSION = true ? "0.5.0" : "0.0.0";
|
|
1195
2553
|
var INSTRUCTIONS = `Channlize routes website conversations and app events to the team's Slack.
|
|
1196
|
-
- Inbox: list_conversations /
|
|
2554
|
+
- Inbox: list_conversations / search_conversations, then get_conversation for the thread. reply_to_conversation sends to the customer as this key ("<key name> (agent)"), so confirm the text first; add_internal_note only reaches the team in Slack. assign_conversation, tag_conversation, close_conversation and reopen_conversation change it.
|
|
2555
|
+
- get_analytics_summary gives KPIs and the inbox right now; list/create/update/delete_routing_rule manage which Slack channel new conversations go to.
|
|
1197
2556
|
- Events (notify_event) post a Slack message per event key; list_event_definitions and list_event_deliveries explain what was sent or skipped.
|
|
1198
2557
|
- Logs: query_logs searches log tracking; track_log records one.
|
|
1199
|
-
-
|
|
2558
|
+
- Agents (Channlize for Agents): talk to other agents and people in agent channels. list_agents, list_agent_channels and get_org_chart show who and where; send_agent_message posts; read_agent_inbox returns what was sent to your agent, and ack_agent_messages clears what you handled. With a multi-agent key, pass agentId (or set CHANNLIZE_AGENT_ID).
|
|
2559
|
+
- Tasks: create_task hands work to an agent (assignee, due date); update_task moves it on the board, update_task_status reports progress or the result, handoff_task passes it to a better-placed agent; list_tasks and get_task_board show the work.
|
|
2560
|
+
- Humans in the loop: ask_human asks people a question or for an approval, then get_answer waits (up to 25 s per call; call it again while it says still waiting). Don't act on what you asked about until it's answered; an expired approval is not an approval. cancel_question withdraws one; answer_escalated_question answers one escalated to you as a manager agent (list_questions).
|
|
2561
|
+
- Refusals: a POLICY_* error with a retry time means wait until then, without one the policy forbids it (don't retry). HUMAN_TAKEOVER means a person took over the thread: stay out of it until the time it gives. AGENT_THREAD_PAUSED means stop posting in that thread. get_effective_policy (agents:admin key) shows the rules.
|
|
2562
|
+
- Scopes: read / write for contacts, Events and logs; inbox:read, inbox:write, routing:write and analytics:read for the inbox tools; agents:send, agents:read and agents:ask_human for the agent tools. A 403 names the scope the key is missing.`;
|
|
1200
2563
|
function buildServer(config, info = {}) {
|
|
1201
2564
|
const api = new ChannlizeApi(resolveConfig(config));
|
|
1202
2565
|
const server = new import_server2.McpServer(
|
|
@@ -1204,6 +2567,8 @@ function buildServer(config, info = {}) {
|
|
|
1204
2567
|
{ instructions: INSTRUCTIONS }
|
|
1205
2568
|
);
|
|
1206
2569
|
if (api.hasServerKey) {
|
|
2570
|
+
registerInboxTools(server, api);
|
|
2571
|
+
registerAgentTools(server, api);
|
|
1207
2572
|
registerServerTools(server, api);
|
|
1208
2573
|
registerServerPrompts(server);
|
|
1209
2574
|
}
|
|
@@ -1246,14 +2611,39 @@ function createMCPServer(options) {
|
|
|
1246
2611
|
var index_default = ChannlizeMCPServer;
|
|
1247
2612
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1248
2613
|
0 && (module.exports = {
|
|
2614
|
+
AGENT_TASK_STATUSES,
|
|
2615
|
+
AGENT_TOOLS,
|
|
2616
|
+
AGENT_TOOL_NAMES,
|
|
2617
|
+
AuthenticationError,
|
|
2618
|
+
CONVERSATION_PRIORITIES,
|
|
2619
|
+
CONVERSATION_SORTS,
|
|
2620
|
+
CONVERSATION_STATUSES,
|
|
2621
|
+
ChannlizeApi,
|
|
2622
|
+
ChannlizeError,
|
|
1249
2623
|
ChannlizeMCPServer,
|
|
1250
2624
|
ConfigError,
|
|
2625
|
+
INBOX_TOOLS,
|
|
2626
|
+
INBOX_TOOL_NAMES,
|
|
2627
|
+
QUESTION_WAIT_MAX_SECONDS,
|
|
2628
|
+
ROUTING_FIELDS,
|
|
2629
|
+
ROUTING_OPERATORS,
|
|
2630
|
+
RateLimitError,
|
|
1251
2631
|
ToolError,
|
|
1252
2632
|
VERSION,
|
|
2633
|
+
ValidationError,
|
|
2634
|
+
agentToolsAsJsonSchema,
|
|
1253
2635
|
buildServer,
|
|
1254
2636
|
configFromEnv,
|
|
1255
2637
|
createMCPServer,
|
|
2638
|
+
getAgentTool,
|
|
2639
|
+
getInboxTool,
|
|
2640
|
+
inboxToolsAsJsonSchema,
|
|
1256
2641
|
keyKind,
|
|
1257
|
-
|
|
2642
|
+
registerAgentTools,
|
|
2643
|
+
registerInboxTools,
|
|
2644
|
+
resolveConfig,
|
|
2645
|
+
runAgentTool,
|
|
2646
|
+
runInboxTool,
|
|
2647
|
+
toToolError
|
|
1258
2648
|
});
|
|
1259
2649
|
//# sourceMappingURL=index.js.map
|