@channlize/mcp 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -33,21 +33,110 @@ var import_stdio = require("@modelcontextprotocol/server/stdio");
33
33
  // src/api.ts
34
34
  var import_core = require("@channlize/core");
35
35
  var ToolError = class extends Error {
36
- constructor(message, statusCode, code) {
37
- super(message);
36
+ constructor(message2, statusCode, code, retryAt) {
37
+ super(message2);
38
38
  this.statusCode = statusCode;
39
39
  this.code = code;
40
+ this.retryAt = retryAt;
40
41
  this.name = "ToolError";
41
42
  }
42
43
  statusCode;
43
44
  code;
45
+ retryAt;
44
46
  };
45
- function hintFor(status, apiMessage) {
47
+ function scopeFor(apiMessage) {
48
+ const perm = /permission:\s*([\w:*-]+)/i.exec(apiMessage)?.[1] ?? "";
49
+ switch (perm) {
50
+ case "inbox:read":
51
+ return "inbox:read (or read)";
52
+ case "inbox:write":
53
+ return "inbox:write";
54
+ case "routing:read":
55
+ return "routing:write, inbox:read (or read)";
56
+ case "routing:write":
57
+ return "routing:write";
58
+ case "analytics:read":
59
+ return "analytics:read (or read)";
60
+ case "agents:send":
61
+ return "agents:send";
62
+ case "agents:read":
63
+ return "agents:read";
64
+ case "agents:ask_human":
65
+ return "agents:ask_human";
66
+ case "agents:admin":
67
+ return "agents:admin (an unbound key)";
68
+ default:
69
+ return /write|notify/.test(apiMessage) ? "write" : "read";
70
+ }
71
+ }
72
+ function retryText(retryAt) {
73
+ return retryAt ? ` Retry after ${retryAt}, not before.` : "";
74
+ }
75
+ function agentHint(code, body = {}) {
76
+ const retryAt = typeof body.retryAt === "string" ? body.retryAt : void 0;
77
+ switch (code) {
78
+ case "AGENT_KEY_REQUIRED":
79
+ return "Agent tools need an agent key or a multi-agent key: create one under SDK & Integration \u2192 API keys and pick the agents.";
80
+ case "AGENT_NOT_ALLOWED":
81
+ 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.";
82
+ case "NOT_A_CHANNEL_MEMBER":
83
+ return "Only channel members can post (observers only read): use list_agent_channels, or ask an admin to add the agent to the channel.";
84
+ case "AGENT_PAUSED":
85
+ case "AGENT_RETIRED":
86
+ return "Only active agents can act: ask an admin to reactivate it in the dashboard.";
87
+ case "AGENT_RATE_LIMITED":
88
+ return "The agent is sending too fast: wait a minute before sending again.";
89
+ case "AGENT_THREAD_PAUSED": {
90
+ const why = body.reason === "ping_pong" ? " (two agents were going back and forth)" : body.reason === "duplicate_content" ? " (the same message kept being posted)" : "";
91
+ return `The loop guard paused this thread${why}: stop sending in it until a person replies or resumes it. Don't rephrase and retry.`;
92
+ }
93
+ case "HUMAN_TAKEOVER":
94
+ return `A person took over this thread: don't post in it until the takeover ends or they hand it back.${retryText(retryAt)}`;
95
+ case "POLICY_RATE_LIMITED":
96
+ case "POLICY_BUDGET_EXHAUSTED":
97
+ case "POLICY_OUTSIDE_WORKING_HOURS":
98
+ return `The agent's policy refuses this for now: wait, don't retry in a loop.${retryText(retryAt)}`;
99
+ case "POLICY_CHANNEL_NOT_ALLOWED":
100
+ case "POLICY_DM_NOT_ALLOWED":
101
+ case "POLICY_ASK_HUMAN_NOT_ALLOWED":
102
+ case "POLICY_URGENCY_NOT_ALLOWED":
103
+ case "POLICY_MAX_DEPTH":
104
+ 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.";
105
+ case "TASK_CLOSED":
106
+ return "The task is closed (completed, failed, canceled or rejected) and cannot change: create a new task instead.";
107
+ case "ALREADY_ASSIGNED":
108
+ return "The task is already assigned to that agent.";
109
+ case "AGENT_NOT_IN_CHANNEL":
110
+ 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.";
111
+ case "NOT_TASK_PARTICIPANT":
112
+ return "Only the task's assignee or creator can change it or hand it off.";
113
+ case "QUESTION_CLOSED":
114
+ return "The question is already closed (answered, expired or canceled): the first answer wins. Read it with get_answer.";
115
+ case "APPROVAL_ROLE_REQUIRED":
116
+ return "This approval needs a person with the required role: an agent cannot answer it.";
117
+ case "AGENT_ADMIN_KEY_REQUIRED":
118
+ return "This needs an unbound agents:admin key (not a key bound to agents): create one under SDK & Integration \u2192 API keys.";
119
+ case "CHANNEL_NOT_VISIBLE":
120
+ return "Your key's agents can't read that channel: leave channelId out, or pick one from list_agent_channels.";
121
+ case "INVALID_ANSWER":
122
+ return "Answer with one of the question's option ids (approve / deny for approvals), or text when free text is allowed.";
123
+ default:
124
+ return void 0;
125
+ }
126
+ }
127
+ function hintFor(status, apiMessage, code, body) {
128
+ const agent = agentHint(code, body);
129
+ if (agent) return `${apiMessage}. ${agent}`;
130
+ if (status === 400 && /agent_id is required/i.test(apiMessage)) {
131
+ return `${apiMessage}. Pass agentId (from list_agents), or set CHANNLIZE_AGENT_ID / --agent-id on the MCP server.`;
132
+ }
46
133
  switch (status) {
47
134
  case 401:
48
135
  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.`;
49
136
  case 403:
50
- return /permission/i.test(apiMessage) ? `${apiMessage}. This needs a server API key with the ${/write|notify/.test(apiMessage) ? "write" : "read"} scope: create one under SDK & Integration \u2192 API keys.` : apiMessage;
137
+ return /permission/i.test(apiMessage) ? `${apiMessage}. This needs a server API key with the ${scopeFor(
138
+ apiMessage
139
+ )} scope: create one under SDK & Integration \u2192 API keys.` : apiMessage;
51
140
  case 404:
52
141
  return `${apiMessage}. Check the ID (and CHANNLIZE_PROJECT_ID): it must belong to the key's organization.`;
53
142
  case 429:
@@ -60,9 +149,15 @@ function toToolError(err) {
60
149
  if (err instanceof ToolError) return err;
61
150
  if (err instanceof import_core.ChannlizeError) {
62
151
  return new ToolError(
63
- err.statusCode ? hintFor(err.statusCode, err.message.replace(/\.$/, "")) : err.message,
152
+ err.statusCode ? hintFor(
153
+ err.statusCode,
154
+ err.message.replace(/\.$/, ""),
155
+ err.code,
156
+ err.details
157
+ ) : err.message,
64
158
  err.statusCode,
65
- err.code
159
+ err.code,
160
+ err.retryAt
66
161
  );
67
162
  }
68
163
  return new ToolError(err instanceof Error ? err.message : String(err));
@@ -80,6 +175,10 @@ var ChannlizeApi = class {
80
175
  get hasWidgetKey() {
81
176
  return !!this.config.widgetKey;
82
177
  }
178
+ /** The default agent for the agent tools (`CHANNLIZE_AGENT_ID`). */
179
+ get agentId() {
180
+ return this.config.agentId;
181
+ }
83
182
  requireServerKey() {
84
183
  if (!this.config.serverApiKey) {
85
184
  throw new ToolError(
@@ -88,9 +187,10 @@ var ChannlizeApi = class {
88
187
  }
89
188
  return this.config.serverApiKey;
90
189
  }
91
- async request(method, path, options = {}) {
190
+ async request(method, path2, options = {}) {
92
191
  const key = this.requireServerKey();
93
- const url2 = new URL(`${this.config.baseUrl}${path}`);
192
+ const timeout = options.timeout ?? this.config.timeout;
193
+ const url2 = new URL(`${this.config.baseUrl}${path2}`);
94
194
  for (const [k, v] of Object.entries(options.query ?? {})) {
95
195
  if (v !== void 0 && v !== "") url2.searchParams.set(k, String(v));
96
196
  }
@@ -106,13 +206,13 @@ var ChannlizeApi = class {
106
206
  },
107
207
  // oxlint-disable-next-line unicorn/no-invalid-fetch-options -- `method` is dynamic; GET calls never pass a body.
108
208
  body: options.body !== void 0 ? JSON.stringify(options.body) : void 0,
109
- signal: AbortSignal.timeout(this.config.timeout)
209
+ signal: AbortSignal.timeout(timeout)
110
210
  });
111
211
  } catch (err) {
112
212
  const e = err;
113
213
  if (e.name === "TimeoutError" || e.name === "AbortError") {
114
214
  throw new ToolError(
115
- `Channlize API timed out after ${this.config.timeout} ms (${method} ${url2.pathname}). Retry, or raise CHANNLIZE_TIMEOUT.`,
215
+ `Channlize API timed out after ${timeout} ms (${method} ${url2.pathname}). Retry, or raise CHANNLIZE_TIMEOUT.`,
116
216
  408,
117
217
  "TIMEOUT"
118
218
  );
@@ -122,28 +222,34 @@ var ChannlizeApi = class {
122
222
  );
123
223
  }
124
224
  const text = await res.text();
125
- let data = void 0;
225
+ let data2 = void 0;
126
226
  if (text) {
127
227
  try {
128
- data = JSON.parse(text);
228
+ data2 = JSON.parse(text);
129
229
  } catch {
130
- data = void 0;
230
+ data2 = void 0;
131
231
  }
132
232
  }
133
233
  if (!res.ok) {
134
- const { message, code } = (0, import_core.parseApiError)(res.status, data, res.statusText);
234
+ const { message: message2, code, body } = (0, import_core.parseApiError)(
235
+ res.status,
236
+ data2,
237
+ res.statusText
238
+ );
239
+ const retryAt = typeof body.retryAt === "string" ? body.retryAt : void 0;
135
240
  throw new ToolError(
136
- hintFor(res.status, message.replace(/\.$/, "")),
241
+ hintFor(res.status, message2.replace(/\.$/, ""), code, body),
137
242
  res.status,
138
- code
243
+ code,
244
+ retryAt
139
245
  );
140
246
  }
141
- if (data === void 0) {
247
+ if (data2 === void 0) {
142
248
  throw new ToolError(
143
249
  `Unexpected non-JSON response from ${method} ${url2.pathname}`
144
250
  );
145
251
  }
146
- return data;
252
+ return data2;
147
253
  }
148
254
  /**
149
255
  * `CHANNLIZE_PROJECT_ID`, or the first project of the key's organization
@@ -199,11 +305,12 @@ var ChannlizeApi = class {
199
305
  // src/config.ts
200
306
  var import_core2 = require("@channlize/core");
201
307
  var ConfigError = class extends Error {
202
- constructor(message) {
203
- super(message);
308
+ constructor(message2) {
309
+ super(message2);
204
310
  this.name = "ConfigError";
205
311
  }
206
312
  };
313
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
207
314
  function clean(value) {
208
315
  const v = value?.trim();
209
316
  return v ? v : void 0;
@@ -248,17 +355,33 @@ function resolveConfig(config) {
248
355
  `Invalid base URL "${config.baseUrl}": use e.g. ${import_core2.DEFAULT_BASE_URL}`
249
356
  );
250
357
  }
358
+ const agentId2 = clean(config.agentId);
359
+ if (agentId2 && !UUID.test(agentId2)) {
360
+ throw new ConfigError(
361
+ `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.`
362
+ );
363
+ }
251
364
  return {
252
365
  serverApiKey,
253
366
  widgetKey,
254
367
  projectId: clean(config.projectId),
368
+ agentId: agentId2,
255
369
  baseUrl,
256
370
  timeout
257
371
  };
258
372
  }
259
373
  function configFromEnv(env = process.env) {
260
- const { timeout, ...rest } = (0, import_core2.readChannlizeEnv)(env);
261
- return { ...rest, timeout: timeout ? Number(timeout) : void 0 };
374
+ const { timeout, serverApiKey, widgetKey, apiKey, projectId, baseUrl } = (0, import_core2.readChannlizeEnv)(env);
375
+ return {
376
+ serverApiKey,
377
+ widgetKey,
378
+ apiKey,
379
+ projectId,
380
+ baseUrl,
381
+ timeout: timeout ? Number(timeout) : void 0,
382
+ // Read here rather than from core, so older @channlize/core versions work.
383
+ agentId: clean(env.CHANNLIZE_AGENT_ID)
384
+ };
262
385
  }
263
386
 
264
387
  // src/prompts.ts
@@ -438,12 +561,12 @@ function registerServerPrompts(server) {
438
561
  },
439
562
  ({ status, limit }) => user(`Triage my Channlize support inbox.
440
563
 
441
- 1. Call list_conversations with status "${status || "open"}" and limit ${Number(limit) || 10}.
564
+ 1. Call list_conversations with status ["${status || "open"}"] and limit ${Number(limit) || 10}.
442
565
  2. For each conversation, call get_conversation to read the thread.
443
566
  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.
444
567
  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.
445
568
 
446
- Replies and status or priority changes are made by the team in the Slack thread or the Channlize dashboard, so present the drafts for me to send; don't invent facts about my product.`)
569
+ 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.`)
447
570
  );
448
571
  server.registerPrompt(
449
572
  "summarize-conversation",
@@ -454,8 +577,8 @@ Replies and status or priority changes are made by the team in the Slack thread
454
577
  conversationId: z.string().describe("Conversation ID")
455
578
  })
456
579
  },
457
- ({ conversationId }) => user(
458
- `Call get_conversation with conversationId "${conversationId}". 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.`
580
+ ({ conversationId: conversationId2 }) => user(
581
+ `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.`
459
582
  )
460
583
  );
461
584
  server.registerPrompt(
@@ -586,14 +709,1790 @@ function registerResources(server, api) {
586
709
  }
587
710
 
588
711
  // src/tools.ts
589
- var z2 = __toESM(require("zod/v4"));
712
+ var z4 = __toESM(require("zod/v4"));
590
713
  var import_core3 = require("@channlize/core");
591
- var STATUSES = ["open", "pending", "resolved", "closed"];
592
- var PRIORITIES = ["low", "normal", "high", "urgent"];
593
- function ok(data) {
714
+
715
+ // src/agents.ts
716
+ var z2 = __toESM(require("zod/v4"));
717
+
718
+ // src/agents-summary.ts
719
+ var num = (v) => typeof v === "number" ? v : Number(v) || 0;
720
+ var arr = (v) => Array.isArray(v) ? v : [];
721
+ var obj = (v) => v && typeof v === "object" ? v : {};
722
+ var str = (v) => typeof v === "string" ? v : null;
723
+ function money(v) {
724
+ return Math.round(num(v) * 1e4) / 1e4;
725
+ }
726
+ function fmtMoney(v) {
727
+ return money(v).toFixed(4).replace(/\.?0+$/, "") || "0";
728
+ }
729
+ function pct(v) {
730
+ return v === null || v === void 0 ? "n/a" : `${Math.round(num(v) * 1e3) / 10}%`;
731
+ }
732
+ function fmtMs(v) {
733
+ if (v === null || v === void 0) return "n/a";
734
+ const ms = num(v);
735
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
736
+ if (ms < 6e4) return `${Math.round(ms / 100) / 10}s`;
737
+ if (ms < 36e5) return `${Math.round(ms / 6e3) / 10}m`;
738
+ return `${Math.round(ms / 36e4) / 10}h`;
739
+ }
740
+ function highlightText(html) {
741
+ return html.replace(/<mark>/g, "**").replace(/<\/mark>/g, "**").replace(/<[^>]*>/g, "").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&amp;/g, "&");
742
+ }
743
+ function clip(text, max) {
744
+ const s = typeof text === "string" ? text : "";
745
+ return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
746
+ }
747
+ function rangeSummary(range2) {
748
+ const r = obj(range2);
749
+ return {
750
+ from: String(r.from ?? ""),
751
+ to: String(r.to ?? ""),
752
+ days: num(r.days),
753
+ historyStart: str(r.history_start),
754
+ retentionDays: r.retention_days === void 0 ? null : num(r.retention_days),
755
+ clamped: Boolean(r.clamped),
756
+ empty: Boolean(r.empty)
757
+ };
758
+ }
759
+ function rangeNote(r) {
760
+ const base = `${r.from}..${r.to} (${r.days} day${r.days === 1 ? "" : "s"}, UTC)`;
761
+ return r.clamped ? `${base}; clamped to the plan's history start ${r.historyStart}` : base;
762
+ }
763
+ function latency(v) {
764
+ const l = obj(v);
765
+ return {
766
+ count: num(l.count),
767
+ avgMs: l.avg_ms === null || l.avg_ms === void 0 ? null : num(l.avg_ms),
768
+ p50Ms: l.p50_ms === null || l.p50_ms === void 0 ? null : num(l.p50_ms),
769
+ p90Ms: l.p90_ms === null || l.p90_ms === void 0 ? null : num(l.p90_ms)
770
+ };
771
+ }
772
+ function summarizeSearch(r) {
773
+ const results = arr(r.results).map((x) => {
774
+ const channel = x.channel ? obj(x.channel) : null;
775
+ const base = {
776
+ kind: String(x.kind),
777
+ highlight: clip(highlightText(String(x.highlight ?? "")), 400),
778
+ rank: Math.round(num(x.rank) * 1e3) / 1e3,
779
+ channel: channel ? str(channel.slug) ?? str(channel.id) : null,
780
+ channelId: channel ? str(channel.id) : null,
781
+ threadId: str(x.thread_id),
782
+ slackPermalink: str(x.slack_permalink)
783
+ };
784
+ if (x.kind === "question") {
785
+ const q = obj(x.question);
786
+ const answer = q.answer ? obj(q.answer) : null;
787
+ return {
788
+ ...base,
789
+ id: String(q.id ?? ""),
790
+ type: "question",
791
+ text: clip(q.question, 500),
792
+ status: str(q.status),
793
+ answer: answer ? clip(
794
+ [answer.option_label ?? answer.option_id, answer.text].filter(Boolean).join(" / "),
795
+ 200
796
+ ) || null : null,
797
+ sender: str(q.agent_id) ? `agent:${String(q.agent_id)}` : null,
798
+ createdAt: str(q.created_at)
799
+ };
800
+ }
801
+ const m = obj(x.message);
802
+ const sender = obj(m.sender);
803
+ const senderId = sender.agent_id ?? sender.user_id ?? sender.slack_user_id;
804
+ return {
805
+ ...base,
806
+ id: String(m.id ?? ""),
807
+ type: String(m.type ?? "message"),
808
+ text: clip(m.text, 500),
809
+ status: str(m.task_status),
810
+ answer: null,
811
+ sender: sender.kind ? `${String(sender.kind)}${senderId ? `:${String(senderId)}` : ""}` : null,
812
+ createdAt: str(m.created_at)
813
+ };
814
+ });
815
+ const window = obj(r.window);
816
+ return {
817
+ results,
818
+ nextCursor: str(r.next_cursor),
819
+ window: { from: String(window.from ?? ""), days: num(window.days) }
820
+ };
821
+ }
822
+ function summarizeGraph(r, top2) {
823
+ const range2 = rangeSummary(r.range);
824
+ const nodes = arr(r.nodes);
825
+ const labels = new Map(nodes.map((n) => [String(n.id), String(n.label)]));
826
+ const topNodes = [...nodes].sort(
827
+ (a, b) => num(b.sent) + num(b.received) - (num(a.sent) + num(a.received))
828
+ ).slice(0, top2).map((n) => ({
829
+ id: String(n.id),
830
+ kind: String(n.kind),
831
+ label: String(n.label),
832
+ slug: str(n.slug),
833
+ sent: num(n.sent),
834
+ received: num(n.received),
835
+ tokens: num(n.tokens),
836
+ cost: money(n.cost)
837
+ }));
838
+ const edges = arr(r.edges);
839
+ const topEdges = [...edges].sort((a, b) => num(b.messages) - num(a.messages)).slice(0, top2).map((e) => {
840
+ const l = latency(e.reply_latency);
841
+ return {
842
+ source: String(e.source),
843
+ target: String(e.target),
844
+ from: labels.get(String(e.source)) ?? String(e.source),
845
+ to: labels.get(String(e.target)) ?? String(e.target),
846
+ messages: num(e.messages),
847
+ replies: num(e.replies),
848
+ cost: money(e.cost),
849
+ replyAvgMs: l.avgMs,
850
+ replyP90Ms: l.p90Ms,
851
+ lastAt: str(e.last_at)
852
+ };
853
+ });
854
+ const totals = obj(r.totals);
855
+ const truncated = obj(r.truncated);
856
+ const t = {
857
+ nodes: num(totals.nodes),
858
+ edges: num(totals.edges),
859
+ messages: num(totals.messages),
860
+ tokens: num(totals.tokens),
861
+ cost: money(totals.cost)
862
+ };
863
+ const busiest = topEdges[0];
864
+ return {
865
+ range: range2,
866
+ totals: t,
867
+ topNodes,
868
+ topEdges,
869
+ /** Not shown here: beyond `top`, or folded / dropped by the API's cap. */
870
+ omitted: {
871
+ nodes: Math.max(0, nodes.length - topNodes.length),
872
+ edges: Math.max(0, edges.length - topEdges.length),
873
+ cappedNodes: num(truncated.nodes),
874
+ cappedEdges: num(truncated.edges)
875
+ },
876
+ summary: range2.empty ? `No visible activity in ${rangeNote(range2)}.` : `${t.nodes} participants, ${t.edges} edges, ${t.messages} messages, cost ${fmtMoney(t.cost)} over ${rangeNote(range2)}.` + (busiest ? ` Busiest edge: ${busiest.from} \u2192 ${busiest.to} (${busiest.messages} messages, reply avg ${fmtMs(busiest.replyAvgMs)}).` : "")
877
+ };
878
+ }
879
+ function summarizeCost(r, top2) {
880
+ const range2 = rangeSummary(r.range);
881
+ const totals = obj(r.totals);
882
+ const t = {
883
+ messages: num(totals.messages),
884
+ pricedMessages: num(totals.priced_messages),
885
+ tokens: num(totals.tokens),
886
+ cost: money(totals.cost)
887
+ };
888
+ const all = arr(r.groups);
889
+ const groups = all.slice(0, top2).map((g) => ({
890
+ key: String(g.key),
891
+ label: clip(g.label, 120),
892
+ messages: num(g.messages),
893
+ tokens: num(g.tokens),
894
+ cost: money(g.cost),
895
+ share: g.share === null || g.share === void 0 ? null : num(g.share)
896
+ }));
897
+ const active = arr(r.series).filter(
898
+ (d) => num(d.messages) > 0 || num(d.cost) > 0
899
+ );
900
+ const daily = active.slice(-31).map((d) => ({
901
+ day: String(d.day),
902
+ messages: num(d.messages),
903
+ tokens: num(d.tokens),
904
+ cost: money(d.cost)
905
+ }));
906
+ const peak = active.reduce(
907
+ (best, d) => !best || num(d.cost) > num(best.cost) ? d : best,
908
+ null
909
+ );
910
+ const groupBy = String(r.group_by ?? "agent");
911
+ const head = groups.slice(0, 3).map((g) => `${g.label} ${fmtMoney(g.cost)} (${pct(g.share)})`).join(", ");
912
+ return {
913
+ range: range2,
914
+ groupBy,
915
+ totals: t,
916
+ groups,
917
+ omittedGroups: Math.max(0, all.length - groups.length),
918
+ /** The API had more groups than it returned. */
919
+ truncated: Boolean(r.truncated),
920
+ daily,
921
+ peakDay: peak ? { day: String(peak.day), cost: money(peak.cost) } : null,
922
+ summary: range2.empty ? `No visible activity in ${rangeNote(range2)}.` : `Total cost ${fmtMoney(t.cost)} (as reported by agents), ${t.tokens} tokens, ${t.messages} messages (${t.pricedMessages} priced) over ${rangeNote(range2)}.` + (head ? ` Top by ${groupBy}: ${head}.` : "")
923
+ };
924
+ }
925
+ function pair(v) {
926
+ const p = obj(v);
927
+ const l = latency(p.latency);
928
+ return {
929
+ messages: num(p.messages),
930
+ replied: num(p.replied),
931
+ replyRate: p.reply_rate === null || p.reply_rate === void 0 ? null : num(p.reply_rate),
932
+ avgMs: l.avgMs,
933
+ p50Ms: l.p50Ms,
934
+ p90Ms: l.p90Ms
935
+ };
936
+ }
937
+ function summarizeMetrics(r, top2) {
938
+ const range2 = rangeSummary(r.range);
939
+ const replies = obj(r.replies);
940
+ const q = obj(r.questions);
941
+ const qLatency = latency(q.time_to_human_answer);
942
+ const byUrgency = obj(q.by_urgency);
943
+ const d = obj(r.deliveries);
944
+ const byMode = obj(d.by_mode);
945
+ const backlog = obj(r.backlog);
946
+ const rate = (v) => v === null || v === void 0 ? null : num(v);
947
+ const out = {
948
+ range: range2,
949
+ replies: {
950
+ agentToAgent: pair(replies.agent_to_agent),
951
+ humanToAgent: pair(replies.human_to_agent),
952
+ agentToHuman: pair(replies.agent_to_human),
953
+ byAgent: arr(replies.by_agent).slice(0, top2).map((a) => {
954
+ const l = latency(a.latency);
955
+ return {
956
+ agentId: String(a.agent_id),
957
+ name: str(a.name),
958
+ messages: num(a.messages),
959
+ replied: num(a.replied),
960
+ avgMs: l.avgMs,
961
+ p90Ms: l.p90Ms
962
+ };
963
+ })
964
+ },
965
+ questions: {
966
+ asked: num(q.asked),
967
+ answered: num(q.answered),
968
+ answeredByUser: num(q.answered_by_user),
969
+ answeredByAgent: num(q.answered_by_agent),
970
+ expired: num(q.expired),
971
+ expiredDefault: num(q.expired_default),
972
+ canceled: num(q.canceled),
973
+ answerRate: rate(q.answer_rate),
974
+ timeToHumanAnswer: {
975
+ count: qLatency.count,
976
+ avgMs: qLatency.avgMs,
977
+ p50Ms: qLatency.p50Ms,
978
+ p90Ms: qLatency.p90Ms
979
+ },
980
+ byUrgency: Object.fromEntries(
981
+ ["low", "normal", "high", "urgent"].map((u) => {
982
+ const b = obj(byUrgency[u]);
983
+ return [
984
+ u,
985
+ {
986
+ asked: num(b.asked),
987
+ answerRate: rate(b.answer_rate),
988
+ p50Ms: latency(b.time_to_human_answer).p50Ms
989
+ }
990
+ ];
991
+ })
992
+ )
993
+ },
994
+ deliveries: {
995
+ deliveries: num(d.deliveries),
996
+ failed: num(d.failed),
997
+ retried: num(d.retried),
998
+ failureRate: rate(d.failure_rate),
999
+ retryRate: rate(d.retry_rate),
1000
+ byMode: Object.fromEntries(
1001
+ ["webhook", "inbox", "a2a"].map((m) => {
1002
+ const b = obj(byMode[m]);
1003
+ return [
1004
+ m,
1005
+ {
1006
+ deliveries: num(b.deliveries),
1007
+ failed: num(b.failed),
1008
+ held: num(b.held),
1009
+ failureRate: rate(b.failure_rate),
1010
+ retryRate: rate(b.retry_rate)
1011
+ }
1012
+ ];
1013
+ })
1014
+ )
1015
+ },
1016
+ backlog: {
1017
+ asOf: String(backlog.as_of ?? ""),
1018
+ unacked: num(backlog.unacked),
1019
+ held: num(backlog.held),
1020
+ agentsWithBacklog: num(backlog.agents_with_backlog),
1021
+ agents: arr(backlog.agents).slice(0, top2).map((a) => ({
1022
+ agentId: String(a.agent_id),
1023
+ name: str(a.name),
1024
+ delivery: str(a.delivery),
1025
+ unacked: num(a.unacked),
1026
+ held: num(a.held),
1027
+ oldestAgeSeconds: a.oldest_age_seconds === null || a.oldest_age_seconds === void 0 ? null : num(a.oldest_age_seconds)
1028
+ }))
1029
+ },
1030
+ summary: ""
1031
+ };
1032
+ const h2a = out.replies.humanToAgent;
1033
+ const a2a = out.replies.agentToAgent;
1034
+ out.summary = `Over ${rangeNote(range2)}: agents answered people in avg ${fmtMs(h2a.avgMs)} (p90 ${fmtMs(h2a.p90Ms)}, ${pct(h2a.replyRate)} replied) and other agents in avg ${fmtMs(a2a.avgMs)} (p90 ${fmtMs(a2a.p90Ms)}). Questions: ${out.questions.asked} asked, ${pct(out.questions.answerRate)} answered, human answer p50 ${fmtMs(out.questions.timeToHumanAnswer.p50Ms)}. Deliveries: ${out.deliveries.deliveries}, ${pct(out.deliveries.failureRate)} failed, ${pct(out.deliveries.retryRate)} retried. Backlog now: ${out.backlog.unacked} unacked, ${out.backlog.held} held.`;
1035
+ return out;
1036
+ }
1037
+
1038
+ // src/agents.ts
1039
+ function defineTool(tool) {
1040
+ return tool;
1041
+ }
1042
+ var AGENT_TASK_STATUSES = [
1043
+ "open",
1044
+ "working",
1045
+ "input_required",
1046
+ "completed",
1047
+ "failed",
1048
+ "canceled",
1049
+ "rejected"
1050
+ ];
1051
+ var uuid = (what) => z2.string().uuid().describe(what);
1052
+ var agentId = uuid(
1053
+ "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."
1054
+ ).optional();
1055
+ var channelId = uuid("Agent channel ID (from list_agent_channels)");
1056
+ var threadId = uuid(
1057
+ "Reply in this thread: the id of any message in it (thread_id or id from read_agent_inbox)"
1058
+ ).optional();
1059
+ var mentions = z2.array(z2.string().uuid()).max(50).optional().describe("Agent IDs to mention (from list_agents)");
1060
+ var data = z2.record(z2.string(), z2.unknown()).optional().describe("Structured data for the recipients (JSON object)");
1061
+ var usage = {
1062
+ tokens: z2.number().int().min(0).optional().describe("Tokens you used for this message (reporting)"),
1063
+ cost: z2.number().min(0).optional().describe("What this message cost you, in USD (reporting)")
1064
+ };
1065
+ var message = z2.looseObject({
1066
+ id: z2.string(),
1067
+ channel_id: z2.string(),
1068
+ thread_id: z2.string().nullable(),
1069
+ type: z2.string(),
1070
+ text: z2.string(),
1071
+ sender: z2.looseObject({ kind: z2.string() }),
1072
+ task_status: z2.string().nullable().optional()
1073
+ });
1074
+ var sendOutput = z2.object({
1075
+ message,
1076
+ deliveries: z2.array(
1077
+ z2.looseObject({ id: z2.string(), agent_id: z2.string(), status: z2.string() })
1078
+ )
1079
+ });
1080
+ var dueAt = z2.string().max(40).optional().describe("Due date: an ISO date (2026-11-30) or date-time");
1081
+ var task = z2.looseObject({
1082
+ id: z2.string(),
1083
+ channel_id: z2.string(),
1084
+ text: z2.string(),
1085
+ board_status: z2.string(),
1086
+ assignee_agent_id: z2.string().nullable(),
1087
+ due_at: z2.string().nullable()
1088
+ });
1089
+ var deliveries = z2.array(
1090
+ z2.looseObject({ id: z2.string(), agent_id: z2.string(), status: z2.string() })
1091
+ );
1092
+ var question = z2.looseObject({
1093
+ id: z2.string(),
1094
+ kind: z2.string(),
1095
+ question: z2.string(),
1096
+ status: z2.string(),
1097
+ answer: z2.looseObject({}).nullable()
1098
+ });
1099
+ var questionId = uuid("The question ID (from ask_human or list_questions)");
1100
+ var BOARD_STATUSES = [
1101
+ "open",
1102
+ "in_progress",
1103
+ "blocked",
1104
+ "done",
1105
+ "failed"
1106
+ ];
1107
+ var QUESTION_WAIT_MAX_SECONDS = 25;
1108
+ var READ = {
1109
+ readOnlyHint: true,
1110
+ destructiveHint: false,
1111
+ idempotentHint: true,
1112
+ openWorldHint: false
1113
+ };
1114
+ var SEND = {
1115
+ readOnlyHint: false,
1116
+ destructiveHint: false,
1117
+ idempotentHint: false,
1118
+ openWorldHint: true
1119
+ };
1120
+ function actingAs(api, id2) {
1121
+ return id2 ?? api.agentId;
1122
+ }
1123
+ function requireAgent(api, id2) {
1124
+ const agent = actingAs(api, id2);
1125
+ if (!agent) {
1126
+ throw new ToolError(
1127
+ "Pass agentId (from list_agents), or set CHANNLIZE_AGENT_ID / --agent-id on the MCP server."
1128
+ );
1129
+ }
1130
+ return agent;
1131
+ }
1132
+ async function send(api, body) {
1133
+ const r = await api.request(
1134
+ "POST",
1135
+ "/v1/agents/messages",
1136
+ { body }
1137
+ );
1138
+ return { message: r.message, deliveries: r.deliveries ?? [] };
1139
+ }
1140
+ var enc = encodeURIComponent;
1141
+ function outcomeMessage(status, q) {
1142
+ const answer = q.answer ?? {};
1143
+ const choice = answer.option_label ?? answer.option_id;
1144
+ const comment = answer.comment ? `: ${String(answer.comment)}` : "";
1145
+ switch (status) {
1146
+ case "answered":
1147
+ if (q.kind === "approval") {
1148
+ return answer.approved ? `Approved${comment}. Go ahead.` : `Denied${comment}. Don't do it.`;
1149
+ }
1150
+ return `Answered: ${[choice, answer.text].filter(Boolean).join(" / ") || "(empty)"}.`;
1151
+ case "expired_default":
1152
+ return `Nobody answered by the deadline: the default option applies (${String(choice ?? "none")}).`;
1153
+ case "expired":
1154
+ 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.";
1155
+ case "canceled":
1156
+ return "The question was canceled.";
1157
+ default:
1158
+ return `Status: ${status}.`;
1159
+ }
1160
+ }
1161
+ var AGENT_MESSAGE_TYPES = [
1162
+ "message",
1163
+ "task",
1164
+ "question",
1165
+ "escalation",
1166
+ "status"
1167
+ ];
1168
+ var insightsDay = (what) => z2.string().regex(/^\d{4}-\d{2}-\d{2}$/, "expected a YYYY-MM-DD date").optional().describe(what);
1169
+ var insightsRange = {
1170
+ from: insightsDay(
1171
+ "First UTC day, YYYY-MM-DD (default: 30 days before to). Clamped to the plan history window."
1172
+ ),
1173
+ to: insightsDay("Last UTC day, inclusive, YYYY-MM-DD (default: today)"),
1174
+ channelId: channelId.optional().describe("Only this agent channel")
1175
+ };
1176
+ var insightsAgent = uuid(
1177
+ "Only this agent (from list_agents): pass your own id to see your own numbers"
1178
+ ).optional();
1179
+ var mine = z2.boolean().optional().describe(
1180
+ "true: only your own agent (the server's default agent, CHANNLIZE_AGENT_ID), when you don't know your id"
1181
+ );
1182
+ var top = (dflt) => z2.number().int().min(1).max(50).default(dflt).describe(`How many rows to return in each list (1-50, default ${dflt})`);
1183
+ function filterAgent(api, a) {
1184
+ if (a.agentId) return a.agentId;
1185
+ return a.mine ? requireAgent(api, void 0) : void 0;
1186
+ }
1187
+ var range = z2.looseObject({
1188
+ from: z2.string(),
1189
+ to: z2.string(),
1190
+ days: z2.number(),
1191
+ clamped: z2.boolean(),
1192
+ empty: z2.boolean()
1193
+ });
1194
+ var nullableNumber = z2.number().nullable();
1195
+ var AGENT_TOOLS = [
1196
+ defineTool({
1197
+ name: "send_agent_message",
1198
+ title: "Send agent message",
1199
+ 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.",
1200
+ scope: "agents:send",
1201
+ endpoint: "POST /v1/agents/messages",
1202
+ inputSchema: z2.object({
1203
+ agentId,
1204
+ channelId,
1205
+ text: z2.string().min(1).max(4e4).describe("Message text"),
1206
+ threadId,
1207
+ mentions,
1208
+ data,
1209
+ ...usage
1210
+ }),
1211
+ outputSchema: sendOutput,
1212
+ annotations: SEND,
1213
+ handler: (api, a) => send(api, {
1214
+ agent_id: actingAs(api, a.agentId),
1215
+ channel_id: a.channelId,
1216
+ type: "message",
1217
+ text: a.text,
1218
+ thread_id: a.threadId,
1219
+ mentions: a.mentions,
1220
+ data: a.data,
1221
+ tokens: a.tokens,
1222
+ cost: a.cost
1223
+ })
1224
+ }),
1225
+ defineTool({
1226
+ name: "create_task",
1227
+ title: "Create task",
1228
+ 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.",
1229
+ scope: "agents:send",
1230
+ endpoint: "POST /v1/agents/tasks",
1231
+ inputSchema: z2.object({
1232
+ agentId,
1233
+ channelId,
1234
+ text: z2.string().min(1).max(4e4).describe("What should be done, with enough context to do it"),
1235
+ assigneeAgentId: uuid(
1236
+ "The agent that should do it: a member of the channel (from list_agents)"
1237
+ ).optional(),
1238
+ dueAt,
1239
+ threadId,
1240
+ mentions,
1241
+ data
1242
+ }),
1243
+ // `message` is the task too (a task is its `task` message plus board fields).
1244
+ outputSchema: z2.object({ taskId: z2.string(), task, message, deliveries }),
1245
+ annotations: SEND,
1246
+ handler: async (api, a) => {
1247
+ const r = await api.request(
1248
+ "POST",
1249
+ "/v1/agents/tasks",
1250
+ {
1251
+ body: {
1252
+ agent_id: actingAs(api, a.agentId),
1253
+ channel_id: a.channelId,
1254
+ text: a.text,
1255
+ assignee_agent_id: a.assigneeAgentId,
1256
+ due_at: a.dueAt,
1257
+ thread_id: a.threadId,
1258
+ mentions: a.mentions,
1259
+ data: a.data
1260
+ }
1261
+ }
1262
+ );
1263
+ return {
1264
+ taskId: String(r.task.id),
1265
+ task: r.task,
1266
+ message: r.task,
1267
+ deliveries: r.deliveries ?? []
1268
+ };
1269
+ }
1270
+ }),
1271
+ defineTool({
1272
+ name: "update_task_status",
1273
+ title: "Update task status",
1274
+ 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.",
1275
+ scope: "agents:send",
1276
+ endpoint: "POST /v1/agents/messages",
1277
+ inputSchema: z2.object({
1278
+ agentId,
1279
+ channelId,
1280
+ taskId: uuid("The task message ID"),
1281
+ status: z2.enum(AGENT_TASK_STATUSES).describe("The new status of the task"),
1282
+ text: z2.string().max(4e4).optional().describe("What changed, e.g. the result or what input is needed"),
1283
+ data,
1284
+ ...usage
1285
+ }),
1286
+ outputSchema: sendOutput,
1287
+ annotations: SEND,
1288
+ handler: (api, a) => send(api, {
1289
+ agent_id: actingAs(api, a.agentId),
1290
+ channel_id: a.channelId,
1291
+ type: "status",
1292
+ text: a.text ?? "",
1293
+ task_id: a.taskId,
1294
+ task_status: a.status,
1295
+ data: a.data,
1296
+ tokens: a.tokens,
1297
+ cost: a.cost
1298
+ })
1299
+ }),
1300
+ defineTool({
1301
+ name: "update_task",
1302
+ title: "Update task",
1303
+ 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.",
1304
+ scope: "agents:send",
1305
+ endpoint: "PATCH /v1/agents/tasks/:taskId",
1306
+ inputSchema: z2.object({
1307
+ agentId,
1308
+ taskId: uuid("The task ID"),
1309
+ status: z2.enum(BOARD_STATUSES).optional().describe("The board column"),
1310
+ dueAt: z2.string().max(40).nullable().optional().describe("New due date (ISO date or date-time); null clears it"),
1311
+ note: z2.string().max(2e3).optional().describe("What changed, shown with the update")
1312
+ }),
1313
+ outputSchema: z2.object({ task, changed: z2.boolean() }),
1314
+ annotations: SEND,
1315
+ handler: async (api, a) => {
1316
+ if (a.status === void 0 && a.dueAt === void 0) {
1317
+ throw new ToolError("Pass status or dueAt.");
1318
+ }
1319
+ const r = await api.request(
1320
+ "PATCH",
1321
+ `/v1/agents/tasks/${enc(a.taskId)}`,
1322
+ {
1323
+ body: {
1324
+ agent_id: actingAs(api, a.agentId),
1325
+ status: a.status,
1326
+ due_at: a.dueAt,
1327
+ note: a.note
1328
+ }
1329
+ }
1330
+ );
1331
+ return { task: r.task, changed: Boolean(r.changed) };
1332
+ }
1333
+ }),
1334
+ defineTool({
1335
+ name: "handoff_task",
1336
+ title: "Hand off task",
1337
+ 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.",
1338
+ scope: "agents:send",
1339
+ endpoint: "POST /v1/agents/tasks/:taskId/handoff",
1340
+ inputSchema: z2.object({
1341
+ agentId,
1342
+ taskId: uuid("The task ID"),
1343
+ toAgentId: uuid("The new assignee (from list_agents or get_org_chart)"),
1344
+ reason: z2.string().max(2e3).optional().describe("Why, and what the new assignee needs to know")
1345
+ }),
1346
+ outputSchema: z2.object({ task, deliveries }),
1347
+ annotations: SEND,
1348
+ handler: async (api, a) => {
1349
+ const r = await api.request(
1350
+ "POST",
1351
+ `/v1/agents/tasks/${enc(a.taskId)}/handoff`,
1352
+ {
1353
+ body: {
1354
+ agent_id: actingAs(api, a.agentId),
1355
+ to_agent_id: a.toAgentId,
1356
+ reason: a.reason
1357
+ }
1358
+ }
1359
+ );
1360
+ return {
1361
+ task: r.task,
1362
+ deliveries: r.deliveries ?? []
1363
+ };
1364
+ }
1365
+ }),
1366
+ defineTool({
1367
+ name: "list_tasks",
1368
+ title: "List tasks",
1369
+ 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.",
1370
+ scope: "agents:read",
1371
+ endpoint: "GET /v1/agents/tasks",
1372
+ inputSchema: z2.object({
1373
+ agentId,
1374
+ channelId: channelId.optional(),
1375
+ assigneeAgentId: uuid("Only tasks assigned to this agent").optional(),
1376
+ status: z2.enum(BOARD_STATUSES).optional(),
1377
+ limit: z2.number().int().min(1).max(500).default(50).describe("How many to return (1-500, default 50)")
1378
+ }),
1379
+ outputSchema: z2.object({ tasks: z2.array(task) }),
1380
+ annotations: READ,
1381
+ handler: async (api, a) => {
1382
+ const r = await api.request(
1383
+ "GET",
1384
+ "/v1/agents/tasks",
1385
+ {
1386
+ query: {
1387
+ agent_id: actingAs(api, a.agentId),
1388
+ channel_id: a.channelId,
1389
+ assignee_agent_id: a.assigneeAgentId,
1390
+ status: a.status,
1391
+ limit: a.limit
1392
+ }
1393
+ }
1394
+ );
1395
+ return { tasks: r.tasks ?? [] };
1396
+ }
1397
+ }),
1398
+ defineTool({
1399
+ name: "get_task_board",
1400
+ title: "Get task board",
1401
+ 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.",
1402
+ scope: "agents:read",
1403
+ endpoint: "GET /v1/agents/tasks/board",
1404
+ inputSchema: z2.object({
1405
+ agentId,
1406
+ channelId,
1407
+ assigneeAgentId: uuid("Only tasks assigned to this agent").optional()
1408
+ }),
1409
+ outputSchema: z2.object({
1410
+ channel_id: z2.string(),
1411
+ columns: z2.record(z2.string(), z2.array(task)),
1412
+ counts: z2.record(z2.string(), z2.number()),
1413
+ agents: z2.array(z2.looseObject({ id: z2.string() })),
1414
+ truncated: z2.boolean()
1415
+ }),
1416
+ annotations: READ,
1417
+ handler: async (api, a) => await api.request("GET", "/v1/agents/tasks/board", {
1418
+ query: {
1419
+ agent_id: actingAs(api, a.agentId),
1420
+ channel_id: a.channelId,
1421
+ assignee_agent_id: a.assigneeAgentId
1422
+ }
1423
+ })
1424
+ }),
1425
+ defineTool({
1426
+ name: "ask_human",
1427
+ title: "Ask a human",
1428
+ 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.",
1429
+ scope: "agents:ask_human",
1430
+ endpoint: "POST /v1/agents/questions",
1431
+ inputSchema: z2.object({
1432
+ agentId,
1433
+ kind: z2.enum(["question", "approval"]).default("question").describe("question (default) or approval (answered Approve / Deny)"),
1434
+ question: z2.string().min(1).max(3e3).describe("The question, with what people need to answer it"),
1435
+ options: z2.array(
1436
+ z2.object({
1437
+ id: z2.string().min(1).max(100),
1438
+ label: z2.string().min(1).max(75)
1439
+ })
1440
+ ).max(10).optional().describe("Up to 10 answers to pick from (ignored for approvals)"),
1441
+ freeText: z2.boolean().optional().describe("Allow a typed answer (always on without options)"),
1442
+ urgency: z2.enum(["low", "normal", "high", "urgent"]).optional().describe("How fast it escalates (default normal)"),
1443
+ deadline: z2.string().max(40).optional().describe("ISO date-time, at most 30 days away"),
1444
+ defaultOption: z2.string().max(100).optional().describe("Option id applied at the deadline when nobody answered"),
1445
+ channelId: channelId.optional().describe("Post it in this agent channel (from list_agent_channels)"),
1446
+ threadId,
1447
+ context: z2.record(z2.string(), z2.unknown()).optional().describe("Facts shown with the question (JSON object)"),
1448
+ amount: z2.number().min(0).optional().describe("Approvals: the amount at stake"),
1449
+ currency: z2.string().regex(/^[A-Za-z]{3}$/).optional().describe("Approvals: ISO 4217 currency code"),
1450
+ resource: z2.record(z2.string(), z2.unknown()).optional().describe("Approvals: what is being approved (JSON object)"),
1451
+ requiredRoles: z2.array(z2.string().min(1).max(40)).max(10).optional().describe('Approvals: roles that may answer, e.g. ["admin"]')
1452
+ }),
1453
+ outputSchema: z2.object({ questionId: z2.string(), question }),
1454
+ annotations: SEND,
1455
+ handler: async (api, a) => {
1456
+ const r = await api.request(
1457
+ "POST",
1458
+ "/v1/agents/questions",
1459
+ {
1460
+ body: {
1461
+ agent_id: actingAs(api, a.agentId),
1462
+ kind: a.kind,
1463
+ question: a.question,
1464
+ options: a.options,
1465
+ free_text: a.freeText,
1466
+ urgency: a.urgency,
1467
+ deadline: a.deadline,
1468
+ default_option: a.defaultOption,
1469
+ channel_id: a.channelId,
1470
+ thread_id: a.threadId,
1471
+ context: a.context,
1472
+ amount: a.amount,
1473
+ currency: a.currency,
1474
+ resource: a.resource,
1475
+ required_roles: a.requiredRoles
1476
+ }
1477
+ }
1478
+ );
1479
+ return {
1480
+ questionId: String(r.question.id),
1481
+ question: r.question
1482
+ };
1483
+ }
1484
+ }),
1485
+ defineTool({
1486
+ name: "get_answer",
1487
+ title: "Get answer",
1488
+ 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).`,
1489
+ scope: "agents:ask_human",
1490
+ endpoint: "GET /v1/agents/questions/:id/wait",
1491
+ inputSchema: z2.object({
1492
+ agentId,
1493
+ questionId,
1494
+ waitSeconds: z2.number().int().min(0).max(QUESTION_WAIT_MAX_SECONDS).default(QUESTION_WAIT_MAX_SECONDS).describe(
1495
+ `Seconds to wait for an answer (0-${QUESTION_WAIT_MAX_SECONDS}, default ${QUESTION_WAIT_MAX_SECONDS}); 0 only checks`
1496
+ )
1497
+ }),
1498
+ outputSchema: z2.object({
1499
+ questionId: z2.string(),
1500
+ status: z2.string(),
1501
+ waiting: z2.boolean(),
1502
+ answer: z2.looseObject({}).nullable(),
1503
+ message: z2.string(),
1504
+ question
1505
+ }),
1506
+ annotations: READ,
1507
+ handler: async (api, a) => {
1508
+ const r = await api.request(
1509
+ "GET",
1510
+ `/v1/agents/questions/${enc(a.questionId)}/wait`,
1511
+ {
1512
+ query: {
1513
+ agent_id: actingAs(api, a.agentId),
1514
+ timeout: a.waitSeconds
1515
+ },
1516
+ // The API holds the request for up to waitSeconds.
1517
+ timeout: (a.waitSeconds + 10) * 1e3
1518
+ }
1519
+ );
1520
+ const status = String(r.question.status);
1521
+ const waiting = status === "open";
1522
+ return {
1523
+ questionId: a.questionId,
1524
+ status,
1525
+ waiting,
1526
+ answer: r.question.answer ?? null,
1527
+ 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),
1528
+ question: r.question
1529
+ };
1530
+ }
1531
+ }),
1532
+ defineTool({
1533
+ name: "list_questions",
1534
+ title: "List questions",
1535
+ 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).",
1536
+ scope: "agents:read",
1537
+ endpoint: "GET /v1/agents/questions",
1538
+ inputSchema: z2.object({
1539
+ agentId,
1540
+ status: z2.enum(["open", "answered", "expired_default", "expired", "canceled"]).optional(),
1541
+ kind: z2.enum(["question", "approval"]).optional(),
1542
+ channelId: channelId.optional(),
1543
+ cursor: z2.string().max(300).optional().describe("nextCursor of the previous call"),
1544
+ limit: z2.number().int().min(1).max(200).default(20).describe("How many to return (1-200, default 20)")
1545
+ }),
1546
+ outputSchema: z2.object({
1547
+ questions: z2.array(question),
1548
+ nextCursor: z2.string().nullable()
1549
+ }),
1550
+ annotations: READ,
1551
+ handler: async (api, a) => {
1552
+ const r = await api.request("GET", "/v1/agents/questions", {
1553
+ query: {
1554
+ agent_id: actingAs(api, a.agentId),
1555
+ status: a.status,
1556
+ kind: a.kind,
1557
+ channel_id: a.channelId,
1558
+ cursor: a.cursor,
1559
+ limit: a.limit
1560
+ }
1561
+ });
1562
+ return {
1563
+ questions: r.questions ?? [],
1564
+ nextCursor: r.next_cursor ?? null
1565
+ };
1566
+ }
1567
+ }),
1568
+ defineTool({
1569
+ name: "cancel_question",
1570
+ title: "Cancel question",
1571
+ 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.",
1572
+ scope: "agents:ask_human",
1573
+ endpoint: "DELETE /v1/agents/questions/:id",
1574
+ inputSchema: z2.object({ agentId, questionId }),
1575
+ outputSchema: z2.object({ question }),
1576
+ annotations: {
1577
+ readOnlyHint: false,
1578
+ destructiveHint: true,
1579
+ idempotentHint: false,
1580
+ openWorldHint: true
1581
+ },
1582
+ handler: async (api, a) => {
1583
+ const r = await api.request(
1584
+ "DELETE",
1585
+ `/v1/agents/questions/${enc(a.questionId)}`,
1586
+ { query: { agent_id: actingAs(api, a.agentId) } }
1587
+ );
1588
+ return { question: r.question };
1589
+ }
1590
+ }),
1591
+ defineTool({
1592
+ name: "answer_escalated_question",
1593
+ title: "Answer escalated question",
1594
+ 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.",
1595
+ scope: "agents:send",
1596
+ endpoint: "POST /v1/agents/questions/:id/answer",
1597
+ inputSchema: z2.object({
1598
+ agentId,
1599
+ questionId,
1600
+ optionId: z2.string().min(1).max(100).optional().describe("One of the question options (approve / deny for approvals)"),
1601
+ text: z2.string().max(3e3).optional().describe("A typed answer, when the question allows free text"),
1602
+ comment: z2.string().max(2e3).optional().describe("Why (shown with the answer; say why on a deny)")
1603
+ }),
1604
+ outputSchema: z2.object({ question }),
1605
+ annotations: SEND,
1606
+ handler: async (api, a) => {
1607
+ if (!a.optionId && !a.text) throw new ToolError("Pass optionId or text.");
1608
+ const r = await api.request(
1609
+ "POST",
1610
+ `/v1/agents/questions/${enc(a.questionId)}/answer`,
1611
+ {
1612
+ body: {
1613
+ agent_id: actingAs(api, a.agentId),
1614
+ option_id: a.optionId,
1615
+ text: a.text,
1616
+ comment: a.comment
1617
+ }
1618
+ }
1619
+ );
1620
+ return { question: r.question };
1621
+ }
1622
+ }),
1623
+ defineTool({
1624
+ name: "get_org_chart",
1625
+ title: "Get org chart",
1626
+ 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.",
1627
+ scope: "agents:read",
1628
+ endpoint: "GET /v1/agents/org-chart",
1629
+ inputSchema: z2.object({
1630
+ teamId: uuid("Only this team").optional(),
1631
+ channelId: uuid("Only the agents of this channel").optional(),
1632
+ includeRetired: z2.boolean().optional().describe("Include retired agents (default false)")
1633
+ }),
1634
+ outputSchema: z2.object({
1635
+ agents: z2.array(z2.looseObject({ id: z2.string() })),
1636
+ users: z2.array(z2.looseObject({ id: z2.string() })),
1637
+ teams: z2.array(z2.looseObject({ id: z2.string() })),
1638
+ tree: z2.array(z2.looseObject({}))
1639
+ }),
1640
+ annotations: READ,
1641
+ handler: async (api, a) => await api.request("GET", "/v1/agents/org-chart", {
1642
+ query: {
1643
+ team_id: a.teamId,
1644
+ channel_id: a.channelId,
1645
+ include_retired: a.includeRetired ? "true" : void 0
1646
+ }
1647
+ })
1648
+ }),
1649
+ defineTool({
1650
+ name: "get_effective_policy",
1651
+ title: "Get effective policy",
1652
+ 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.",
1653
+ scope: "agents:admin",
1654
+ endpoint: "GET /v1/agents/policies/preview",
1655
+ inputSchema: z2.object({
1656
+ agentId: uuid(
1657
+ "The agent (from list_agents). Default: the server agent (CHANNLIZE_AGENT_ID)"
1658
+ ).optional(),
1659
+ channelId: channelId.optional()
1660
+ }),
1661
+ outputSchema: z2.looseObject({
1662
+ agent_id: z2.string(),
1663
+ channel_id: z2.string().nullable(),
1664
+ rules: z2.looseObject({}),
1665
+ working_hours: z2.looseObject({}),
1666
+ usage: z2.looseObject({}),
1667
+ checks: z2.looseObject({}).nullable()
1668
+ }),
1669
+ annotations: READ,
1670
+ handler: async (api, a) => await api.request("GET", "/v1/agents/policies/preview", {
1671
+ query: {
1672
+ agent_id: requireAgent(api, a.agentId),
1673
+ channel_id: a.channelId
1674
+ }
1675
+ })
1676
+ }),
1677
+ defineTool({
1678
+ name: "read_agent_inbox",
1679
+ title: "Read agent inbox",
1680
+ 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.",
1681
+ scope: "agents:read",
1682
+ endpoint: "GET /v1/agents/:id/inbox",
1683
+ inputSchema: z2.object({
1684
+ agentId,
1685
+ cursor: z2.string().max(200).optional().describe("Read after this cursor (nextCursor of the previous call)"),
1686
+ limit: z2.number().int().min(1).max(200).default(20).describe("How many to return (1-200, default 20)")
1687
+ }),
1688
+ outputSchema: z2.object({
1689
+ agentId: z2.string(),
1690
+ messages: z2.array(
1691
+ z2.looseObject({
1692
+ delivery_id: z2.string(),
1693
+ cursor: z2.string(),
1694
+ message
1695
+ })
1696
+ ),
1697
+ nextCursor: z2.string().nullable(),
1698
+ hasMore: z2.boolean()
1699
+ }),
1700
+ annotations: READ,
1701
+ handler: async (api, a) => {
1702
+ const id2 = requireAgent(api, a.agentId);
1703
+ const r = await api.request("GET", `/v1/agents/${enc(id2)}/inbox`, {
1704
+ query: { cursor: a.cursor, limit: a.limit }
1705
+ });
1706
+ return {
1707
+ agentId: id2,
1708
+ messages: r.messages ?? [],
1709
+ nextCursor: r.next_cursor ?? null,
1710
+ hasMore: Boolean(r.has_more)
1711
+ };
1712
+ }
1713
+ }),
1714
+ defineTool({
1715
+ name: "ack_agent_messages",
1716
+ title: "Acknowledge agent messages",
1717
+ 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.",
1718
+ scope: "agents:read",
1719
+ endpoint: "POST /v1/agents/:id/inbox/ack",
1720
+ inputSchema: z2.object({
1721
+ agentId,
1722
+ upTo: z2.string().uuid().optional().describe("Ack everything up to and including this delivery_id"),
1723
+ ids: z2.array(z2.string().uuid()).min(1).max(500).optional().describe("Ack these delivery (or message) IDs")
1724
+ }),
1725
+ outputSchema: z2.object({ acked: z2.number() }),
1726
+ annotations: {
1727
+ readOnlyHint: false,
1728
+ destructiveHint: false,
1729
+ idempotentHint: true,
1730
+ openWorldHint: false
1731
+ },
1732
+ handler: async (api, a) => {
1733
+ if (!a.upTo === !a.ids) {
1734
+ throw new ToolError("Pass either upTo or ids.");
1735
+ }
1736
+ const id2 = requireAgent(api, a.agentId);
1737
+ const r = await api.request(
1738
+ "POST",
1739
+ `/v1/agents/${enc(id2)}/inbox/ack`,
1740
+ { body: a.upTo ? { up_to: a.upTo } : { ids: a.ids } }
1741
+ );
1742
+ return { acked: Number(r.acked ?? 0) };
1743
+ }
1744
+ }),
1745
+ defineTool({
1746
+ name: "search_agent_messages",
1747
+ title: "Search agent messages",
1748
+ description: `Full-text search over agent channel history: messages, tasks, status updates and questions to people (with their answers), best match first. Use it to find what was said or decided before, a past task, or an answer a human already gave, instead of asking again. q takes websearch syntax: "exact phrase", or, -word; codes and ids match as substrings (INV-20 finds INV-2041). Each result has a highlight (hits in **bold**), the channel, threadId and a Slack link when mirrored. Searches the channels your key's agents can read and their own questions (the whole org for an agents:admin key), within the plan's history window. Page with cursor. Needs the agents:read scope.`,
1749
+ scope: "agents:read",
1750
+ endpoint: "GET /v1/agents/search",
1751
+ inputSchema: z2.object({
1752
+ q: z2.string().trim().min(2).max(200).describe("What to look for (2-200 characters)"),
1753
+ agentId: uuid(
1754
+ "Only messages sent by or mentioning this agent, and questions it asked (from list_agents). With a multi-agent key it also picks whose channels are searched."
1755
+ ).optional(),
1756
+ channelId: channelId.optional().describe("Only this agent channel"),
1757
+ type: z2.enum(AGENT_MESSAGE_TYPES).optional().describe("Only this message type"),
1758
+ include: z2.enum(["messages", "questions", "both"]).default("both").describe("messages, questions, or both (default)"),
1759
+ from: z2.string().max(40).optional().describe("ISO date-time: the earliest to search"),
1760
+ to: z2.string().max(40).optional().describe("ISO date-time: the latest to search"),
1761
+ cursor: z2.string().max(300).optional().describe("nextCursor of the previous call"),
1762
+ limit: z2.number().int().min(1).max(50).default(10).describe("How many results (1-50, default 10)")
1763
+ }),
1764
+ outputSchema: z2.object({
1765
+ results: z2.array(
1766
+ z2.looseObject({
1767
+ kind: z2.string(),
1768
+ id: z2.string(),
1769
+ type: z2.string(),
1770
+ text: z2.string(),
1771
+ highlight: z2.string(),
1772
+ channel: z2.string().nullable(),
1773
+ threadId: z2.string().nullable()
1774
+ })
1775
+ ),
1776
+ nextCursor: z2.string().nullable(),
1777
+ window: z2.object({ from: z2.string(), days: z2.number() })
1778
+ }),
1779
+ annotations: READ,
1780
+ handler: async (api, a) => {
1781
+ const r = await api.request("GET", "/v1/agents/search", {
1782
+ query: {
1783
+ q: a.q,
1784
+ agent_id: a.agentId,
1785
+ channel_id: a.channelId,
1786
+ type: a.type,
1787
+ include: a.include === "both" ? void 0 : a.include,
1788
+ from: a.from,
1789
+ to: a.to,
1790
+ cursor: a.cursor,
1791
+ limit: a.limit
1792
+ }
1793
+ });
1794
+ return summarizeSearch(r);
1795
+ }
1796
+ }),
1797
+ defineTool({
1798
+ name: "get_agent_graph",
1799
+ title: "Get agent communication graph",
1800
+ description: "Who talks to whom among agents and people over a range of days: the busiest participants (messages sent and received, tokens, cost) and the busiest sender \u2192 recipient edges (messages, replies, reply latency), with totals and a one-line summary. Rows beyond top are counted in omitted. Filter to one agent (agentId, or mine for your own), a team or a channel. Sees the channels your key's agents can read (the whole org for an agents:admin key). Needs the agents:read scope.",
1801
+ scope: "agents:read",
1802
+ endpoint: "GET /v1/agents/insights/graph",
1803
+ inputSchema: z2.object({
1804
+ ...insightsRange,
1805
+ agentId: insightsAgent,
1806
+ mine,
1807
+ teamId: uuid("Only edges touching this team").optional(),
1808
+ top: top(10)
1809
+ }),
1810
+ outputSchema: z2.object({
1811
+ range,
1812
+ totals: z2.object({
1813
+ nodes: z2.number(),
1814
+ edges: z2.number(),
1815
+ messages: z2.number(),
1816
+ tokens: z2.number(),
1817
+ cost: z2.number()
1818
+ }),
1819
+ topNodes: z2.array(
1820
+ z2.looseObject({ id: z2.string(), label: z2.string(), sent: z2.number() })
1821
+ ),
1822
+ topEdges: z2.array(
1823
+ z2.looseObject({
1824
+ source: z2.string(),
1825
+ target: z2.string(),
1826
+ messages: z2.number(),
1827
+ replyAvgMs: nullableNumber
1828
+ })
1829
+ ),
1830
+ omitted: z2.looseObject({ nodes: z2.number(), edges: z2.number() }),
1831
+ summary: z2.string()
1832
+ }),
1833
+ annotations: READ,
1834
+ handler: async (api, a) => {
1835
+ const r = await api.request("GET", "/v1/agents/insights/graph", {
1836
+ query: {
1837
+ from: a.from,
1838
+ to: a.to,
1839
+ channel_id: a.channelId,
1840
+ agent_id: filterAgent(api, a),
1841
+ team_id: a.teamId
1842
+ }
1843
+ });
1844
+ return summarizeGraph(r, a.top);
1845
+ }
1846
+ }),
1847
+ defineTool({
1848
+ name: "get_agent_cost",
1849
+ title: "Get agent cost",
1850
+ description: "What agents spent, from the tokens and cost they reported on their messages: the range's totals, the top groups by cost (groupBy agent, channel, team, task or day) with their share, the active days and the peak day, and a one-line summary. Check your own spend with mine (or agentId), e.g. before starting expensive work or to report it. Cost is in whatever currency agents report (usually USD). Sees the channels your key's agents can read (the whole org for an agents:admin key). Needs the agents:read scope.",
1851
+ scope: "agents:read",
1852
+ endpoint: "GET /v1/agents/insights/cost",
1853
+ inputSchema: z2.object({
1854
+ ...insightsRange,
1855
+ groupBy: z2.enum(["agent", "channel", "team", "task", "day"]).default("agent").describe("How to break the cost down (default agent)"),
1856
+ agentId: insightsAgent,
1857
+ mine,
1858
+ teamId: uuid("Only what this team's agents sent").optional(),
1859
+ top: top(10)
1860
+ }),
1861
+ outputSchema: z2.object({
1862
+ range,
1863
+ groupBy: z2.string(),
1864
+ totals: z2.object({
1865
+ messages: z2.number(),
1866
+ pricedMessages: z2.number(),
1867
+ tokens: z2.number(),
1868
+ cost: z2.number()
1869
+ }),
1870
+ groups: z2.array(
1871
+ z2.looseObject({
1872
+ key: z2.string(),
1873
+ label: z2.string(),
1874
+ cost: z2.number(),
1875
+ share: nullableNumber
1876
+ })
1877
+ ),
1878
+ omittedGroups: z2.number(),
1879
+ truncated: z2.boolean(),
1880
+ daily: z2.array(z2.looseObject({ day: z2.string(), cost: z2.number() })),
1881
+ peakDay: z2.object({ day: z2.string(), cost: z2.number() }).nullable(),
1882
+ summary: z2.string()
1883
+ }),
1884
+ annotations: READ,
1885
+ handler: async (api, a) => {
1886
+ const r = await api.request("GET", "/v1/agents/insights/cost", {
1887
+ query: {
1888
+ from: a.from,
1889
+ to: a.to,
1890
+ channel_id: a.channelId,
1891
+ agent_id: filterAgent(api, a),
1892
+ team_id: a.teamId,
1893
+ group_by: a.groupBy,
1894
+ // Day groups come back whole; the others only need the top ones.
1895
+ limit: a.groupBy === "day" ? void 0 : a.top
1896
+ }
1897
+ });
1898
+ return summarizeCost(r, a.top);
1899
+ }
1900
+ }),
1901
+ defineTool({
1902
+ name: "get_agent_metrics",
1903
+ title: "Get agent metrics",
1904
+ description: "Response metrics over a range of days: reply latency (agent \u2192 agent, human \u2192 agent, agent \u2192 human: avg, p50, p90 and reply rate) and per responding agent, questions to people (answer rate, time to a human answer, by urgency), delivery failure and retry rates per mode, and the inbox backlog right now, with a one-line summary. Use mine (or agentId) to check how fast you answer and whether your inbox is backing up. Needs the agents:read scope.",
1905
+ scope: "agents:read",
1906
+ endpoint: "GET /v1/agents/insights/metrics",
1907
+ inputSchema: z2.object({
1908
+ ...insightsRange,
1909
+ agentId: insightsAgent,
1910
+ mine,
1911
+ top: top(5)
1912
+ }),
1913
+ outputSchema: z2.object({
1914
+ range,
1915
+ replies: z2.looseObject({
1916
+ agentToAgent: z2.looseObject({ avgMs: nullableNumber }),
1917
+ humanToAgent: z2.looseObject({ avgMs: nullableNumber }),
1918
+ agentToHuman: z2.looseObject({ avgMs: nullableNumber }),
1919
+ byAgent: z2.array(z2.looseObject({ agentId: z2.string() }))
1920
+ }),
1921
+ questions: z2.looseObject({ asked: z2.number() }),
1922
+ deliveries: z2.looseObject({ deliveries: z2.number() }),
1923
+ backlog: z2.looseObject({ unacked: z2.number(), held: z2.number() }),
1924
+ summary: z2.string()
1925
+ }),
1926
+ annotations: READ,
1927
+ handler: async (api, a) => {
1928
+ const r = await api.request("GET", "/v1/agents/insights/metrics", {
1929
+ query: {
1930
+ from: a.from,
1931
+ to: a.to,
1932
+ channel_id: a.channelId,
1933
+ agent_id: filterAgent(api, a)
1934
+ }
1935
+ });
1936
+ return summarizeMetrics(r, a.top);
1937
+ }
1938
+ }),
1939
+ defineTool({
1940
+ name: "list_agents",
1941
+ title: "List agents",
1942
+ 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.",
1943
+ scope: "agents:read",
1944
+ endpoint: "GET /v1/agents",
1945
+ inputSchema: z2.object({
1946
+ includeRetired: z2.boolean().optional().describe("Include retired agents (default false)")
1947
+ }),
1948
+ outputSchema: z2.object({
1949
+ agents: z2.array(
1950
+ z2.looseObject({
1951
+ id: z2.string(),
1952
+ slug: z2.string(),
1953
+ name: z2.string(),
1954
+ status: z2.string()
1955
+ })
1956
+ )
1957
+ }),
1958
+ annotations: READ,
1959
+ handler: async (api, a) => {
1960
+ const r = await api.request("GET", "/v1/agents", {
1961
+ query: { include_retired: a.includeRetired || void 0 }
1962
+ });
1963
+ return { agents: r.agents ?? [] };
1964
+ }
1965
+ }),
1966
+ defineTool({
1967
+ name: "list_agent_channels",
1968
+ title: "List agent channels",
1969
+ 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.",
1970
+ scope: "agents:read",
1971
+ endpoint: "GET /v1/agents/channels",
1972
+ inputSchema: z2.object({
1973
+ includeArchived: z2.boolean().optional().describe("Include archived channels (default false)")
1974
+ }),
1975
+ outputSchema: z2.object({
1976
+ channels: z2.array(
1977
+ z2.looseObject({
1978
+ id: z2.string(),
1979
+ slug: z2.string(),
1980
+ name: z2.string(),
1981
+ archived: z2.boolean()
1982
+ })
1983
+ )
1984
+ }),
1985
+ annotations: READ,
1986
+ handler: async (api, a) => {
1987
+ const r = await api.request(
1988
+ "GET",
1989
+ "/v1/agents/channels",
1990
+ { query: { include_archived: a.includeArchived || void 0 } }
1991
+ );
1992
+ return { channels: r.channels ?? [] };
1993
+ }
1994
+ })
1995
+ ];
1996
+ var AGENT_TOOL_NAMES = AGENT_TOOLS.map((t) => t.name);
1997
+
1998
+ // src/inbox.ts
1999
+ var z3 = __toESM(require("zod/v4"));
2000
+ function defineTool2(tool) {
2001
+ return tool;
2002
+ }
2003
+ var CONVERSATION_STATUSES = [
2004
+ "open",
2005
+ "pending",
2006
+ "resolved",
2007
+ "closed"
2008
+ ];
2009
+ var CONVERSATION_PRIORITIES = [
2010
+ "low",
2011
+ "normal",
2012
+ "high",
2013
+ "urgent"
2014
+ ];
2015
+ var CONVERSATION_SORTS = [
2016
+ "recent",
2017
+ "oldest",
2018
+ "newest",
2019
+ "priority"
2020
+ ];
2021
+ var loose = z3.looseObject({});
2022
+ var id = (what) => z3.string().uuid().describe(`${what} ID`);
2023
+ var conversationId = id(
2024
+ "Conversation (from list_conversations or search_conversations)"
2025
+ );
2026
+ var person = z3.looseObject({
2027
+ id: z3.string(),
2028
+ name: z3.string().nullable().optional(),
2029
+ email: z3.string().nullable().optional()
2030
+ }).nullable();
2031
+ var conversationSummary = z3.looseObject({
2032
+ id: z3.string(),
2033
+ subject: z3.string().nullable().optional(),
2034
+ status: z3.string(),
2035
+ priority: z3.string().optional(),
2036
+ source: z3.string().nullable().optional(),
2037
+ tags: z3.array(z3.string()).optional(),
2038
+ customer: person.optional(),
2039
+ assignee: person.optional(),
2040
+ triage: z3.record(z3.string(), z3.unknown()).nullable().optional(),
2041
+ needsReply: z3.boolean().optional(),
2042
+ lastActivityAt: z3.string().nullable().optional()
2043
+ });
2044
+ var page = {
2045
+ total: z3.number(),
2046
+ limit: z3.number(),
2047
+ offset: z3.number(),
2048
+ hasMore: z3.boolean()
2049
+ };
2050
+ var listOutput = z3.object({
2051
+ conversations: z3.array(conversationSummary),
2052
+ ...page
2053
+ });
2054
+ var changeOutput = z3.object({
2055
+ conversation: z3.looseObject({ id: z3.string(), status: z3.string() }),
2056
+ changes: z3.array(z3.string())
2057
+ });
2058
+ var slackOutcome = z3.string().describe(
2059
+ "Slack mirror: queued, posted, no_thread (not in Slack), not_connected or failed"
2060
+ );
2061
+ var tags = z3.array(z3.string().trim().min(1).max(50)).max(20);
2062
+ var paging = {
2063
+ limit: z3.number().int().min(1).max(100).default(20).describe("How many to return (1-100, default 20)"),
2064
+ offset: z3.number().int().min(0).default(0).describe("Skip this many")
2065
+ };
2066
+ function oneOrMany(values) {
2067
+ return z3.union([z3.enum(values), z3.array(z3.enum(values)).max(10)]).optional();
2068
+ }
2069
+ var asList = (v) => v === void 0 ? void 0 : Array.isArray(v) ? v.join(",") : v;
2070
+ var listFilters = {
2071
+ status: oneOrMany(CONVERSATION_STATUSES).describe(
2072
+ 'Only these statuses: "open" or a list like ["open", "pending"]'
2073
+ ),
2074
+ priority: oneOrMany(CONVERSATION_PRIORITIES).describe(
2075
+ 'Only these priorities: "urgent" or a list'
2076
+ ),
2077
+ assignee: z3.string().max(255).optional().describe('A teammate (user ID or email), "unassigned" or "assigned"'),
2078
+ projectId: id("Project").optional(),
2079
+ updatedSince: z3.iso.datetime({ offset: true }).optional().describe("Updated or with a new message since (ISO 8601)"),
2080
+ tag: z3.string().max(50).optional().describe("Carrying this tag"),
2081
+ source: z3.string().max(32).optional().describe("Entry point, e.g. live_chat, contact_form, api, email"),
2082
+ intent: z3.string().max(64).optional().describe("AI triage intent (triaged conversations only)"),
2083
+ urgency: z3.string().max(64).optional().describe("AI triage urgency (triaged conversations only)"),
2084
+ sort: z3.enum(CONVERSATION_SORTS).optional().describe(
2085
+ "recent (last activity, default), oldest, newest (created) or priority"
2086
+ )
2087
+ };
2088
+ async function list(api, a) {
2089
+ const r = await api.request("GET", "/v1/conversations", {
2090
+ query: {
2091
+ status: asList(a.status),
2092
+ priority: asList(a.priority),
2093
+ assignee: a.assignee,
2094
+ projectId: a.projectId,
2095
+ updatedSince: a.updatedSince,
2096
+ tag: a.tag,
2097
+ source: a.source,
2098
+ intent: a.intent,
2099
+ urgency: a.urgency,
2100
+ sort: a.sort,
2101
+ q: a.q,
2102
+ limit: a.limit,
2103
+ offset: a.offset
2104
+ }
2105
+ });
2106
+ return {
2107
+ conversations: r.conversations ?? [],
2108
+ total: Number(r.total ?? 0),
2109
+ limit: Number(r.limit ?? a.limit),
2110
+ offset: Number(r.offset ?? a.offset),
2111
+ hasMore: Boolean(r.hasMore)
2112
+ };
2113
+ }
2114
+ var path = (cid, rest = "") => `/v1/conversations/${encodeURIComponent(cid)}${rest}`;
2115
+ async function patch(api, cid, body) {
2116
+ const r = await api.request(
2117
+ "PATCH",
2118
+ path(cid),
2119
+ { body }
2120
+ );
2121
+ return {
2122
+ conversation: r.conversation,
2123
+ changes: r.changes ?? []
2124
+ };
2125
+ }
2126
+ var READ2 = {
2127
+ readOnlyHint: true,
2128
+ destructiveHint: false,
2129
+ idempotentHint: true,
2130
+ openWorldHint: false
2131
+ };
2132
+ var CHANGE = {
2133
+ readOnlyHint: false,
2134
+ destructiveHint: false,
2135
+ idempotentHint: true,
2136
+ openWorldHint: true
2137
+ };
2138
+ var ROUTING_FIELDS = [
2139
+ "source",
2140
+ "project",
2141
+ "widget",
2142
+ "connection",
2143
+ "page_url",
2144
+ "page_domain",
2145
+ "customer_email",
2146
+ "customer_email_domain",
2147
+ "message",
2148
+ "subject",
2149
+ "tags",
2150
+ "priority",
2151
+ "language"
2152
+ ];
2153
+ var ROUTING_OPERATORS = [
2154
+ "equals",
2155
+ "not_equals",
2156
+ "contains",
2157
+ "not_contains",
2158
+ "starts_with",
2159
+ "ends_with",
2160
+ "matches"
2161
+ ];
2162
+ var routingConditions = z3.object({
2163
+ match: z3.enum(["all", "any"]).describe("all = every condition holds, any = one is enough"),
2164
+ conditions: z3.array(
2165
+ z3.object({
2166
+ field: z3.enum(ROUTING_FIELDS),
2167
+ operator: z3.enum(ROUTING_OPERATORS),
2168
+ value: z3.union([z3.string(), z3.array(z3.string())]).describe(
2169
+ "A value, or a list (positive operators: any matches; negative: none does). Case-insensitive; matches uses * and ? wildcards."
2170
+ )
2171
+ })
2172
+ ).max(50).describe("Empty = matches every new conversation (catch-all)")
2173
+ }).describe(
2174
+ 'Which new conversations the rule catches, tested on the first message, e.g. { "match": "any", "conditions": [{ "field": "message", "operator": "contains", "value": ["refund", "invoice"] }] }'
2175
+ );
2176
+ var ruleFields = {
2177
+ name: z3.string().trim().min(1).max(255).describe("Rule name"),
2178
+ targetChannelId: z3.string().regex(/^[A-Z0-9]{2,30}$/, "a Slack channel ID like C0123ABCD").describe("Slack channel ID to post matching conversations to"),
2179
+ conditions: routingConditions,
2180
+ projectId: id("Project (default: the oldest project)").optional(),
2181
+ widgetId: id("Widget").nullable().optional(),
2182
+ integrationId: id("Integration (default: the Slack workspace)").optional(),
2183
+ assignToUserId: id("Teammate to assign matching conversations to").nullable().optional(),
2184
+ isActive: z3.boolean().optional().describe("Default true"),
2185
+ priority: z3.number().int().min(-1e6).max(1e6).optional().describe("Higher runs first (default: after every rule)")
2186
+ };
2187
+ var rule = z3.looseObject({
2188
+ id: z3.string(),
2189
+ name: z3.string(),
2190
+ priority: z3.number(),
2191
+ is_active: z3.boolean(),
2192
+ target_channel_id: z3.string()
2193
+ });
2194
+ var INBOX_TOOLS = [
2195
+ defineTool2({
2196
+ name: "list_conversations",
2197
+ title: "List conversations",
2198
+ 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.",
2199
+ scope: "inbox:read",
2200
+ endpoint: "GET /v1/conversations",
2201
+ inputSchema: z3.object({ ...listFilters, ...paging }),
2202
+ outputSchema: listOutput,
2203
+ annotations: READ2,
2204
+ handler: (api, a) => list(api, a)
2205
+ }),
2206
+ defineTool2({
2207
+ name: "search_conversations",
2208
+ title: "Search conversations",
2209
+ 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.",
2210
+ scope: "inbox:read",
2211
+ endpoint: "GET /v1/conversations?q=",
2212
+ inputSchema: z3.object({
2213
+ query: z3.string().trim().min(1).max(200).describe("Text to look for"),
2214
+ ...listFilters,
2215
+ ...paging
2216
+ }),
2217
+ outputSchema: listOutput,
2218
+ annotations: READ2,
2219
+ handler: (api, { query, ...a }) => list(api, { ...a, q: query })
2220
+ }),
2221
+ defineTool2({
2222
+ name: "get_conversation",
2223
+ title: "Get conversation",
2224
+ 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.",
2225
+ scope: "inbox:read",
2226
+ endpoint: "GET /v1/conversations/:id",
2227
+ inputSchema: z3.object({ conversationId }),
2228
+ outputSchema: z3.object({
2229
+ conversation: z3.looseObject({
2230
+ id: z3.string(),
2231
+ status: z3.string(),
2232
+ messages: z3.array(loose)
2233
+ })
2234
+ }),
2235
+ annotations: READ2,
2236
+ handler: async (api, { conversationId: cid }) => {
2237
+ const r = await api.request("GET", path(cid));
2238
+ return { conversation: r.conversation };
2239
+ }
2240
+ }),
2241
+ defineTool2({
2242
+ name: "reply_to_conversation",
2243
+ title: "Reply to conversation",
2244
+ 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.',
2245
+ scope: "inbox:write",
2246
+ endpoint: "POST /v1/conversations/:id/reply",
2247
+ inputSchema: z3.object({
2248
+ conversationId,
2249
+ content: z3.string().trim().min(1).max(1e4).describe("Reply text, as the customer will read it")
2250
+ }),
2251
+ outputSchema: z3.object({
2252
+ message: z3.looseObject({ id: z3.string() }),
2253
+ conversationStatus: z3.string(),
2254
+ slack: slackOutcome
2255
+ }),
2256
+ annotations: {
2257
+ readOnlyHint: false,
2258
+ destructiveHint: false,
2259
+ idempotentHint: false,
2260
+ openWorldHint: true
2261
+ },
2262
+ handler: async (api, { conversationId: cid, content }) => {
2263
+ const r = await api.request("POST", path(cid, "/reply"), {
2264
+ body: { content }
2265
+ });
2266
+ return {
2267
+ message: r.message,
2268
+ conversationStatus: String(r.conversationStatus ?? ""),
2269
+ slack: String(r.slack ?? "")
2270
+ };
2271
+ }
2272
+ }),
2273
+ defineTool2({
2274
+ name: "add_internal_note",
2275
+ title: "Add internal note",
2276
+ 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.",
2277
+ scope: "inbox:write",
2278
+ endpoint: "POST /v1/conversations/:id/notes",
2279
+ inputSchema: z3.object({
2280
+ conversationId,
2281
+ content: z3.string().trim().min(1).max(1e4).describe("Note text")
2282
+ }),
2283
+ outputSchema: z3.object({
2284
+ note: z3.looseObject({ id: z3.string() }),
2285
+ slack: slackOutcome
2286
+ }),
2287
+ annotations: {
2288
+ readOnlyHint: false,
2289
+ destructiveHint: false,
2290
+ idempotentHint: false,
2291
+ openWorldHint: true
2292
+ },
2293
+ handler: async (api, { conversationId: cid, content }) => {
2294
+ const r = await api.request("POST", path(cid, "/notes"), {
2295
+ body: { content }
2296
+ });
2297
+ return { note: r.note, slack: String(r.slack ?? "") };
2298
+ }
2299
+ }),
2300
+ defineTool2({
2301
+ name: "assign_conversation",
2302
+ title: "Assign conversation",
2303
+ 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.",
2304
+ scope: "inbox:write",
2305
+ endpoint: "PATCH /v1/conversations/:id",
2306
+ inputSchema: z3.object({
2307
+ conversationId,
2308
+ assignee: z3.string().min(1).max(255).nullable().describe("User ID or email of the teammate, or null to unassign")
2309
+ }),
2310
+ outputSchema: changeOutput,
2311
+ annotations: CHANGE,
2312
+ handler: (api, { conversationId: cid, assignee }) => patch(
2313
+ api,
2314
+ cid,
2315
+ assignee === null ? { assigneeId: null } : assignee.includes("@") ? { assigneeEmail: assignee } : { assigneeId: assignee }
2316
+ )
2317
+ }),
2318
+ defineTool2({
2319
+ name: "tag_conversation",
2320
+ title: "Tag conversation",
2321
+ 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.",
2322
+ scope: "inbox:write",
2323
+ endpoint: "PATCH /v1/conversations/:id",
2324
+ inputSchema: z3.object({
2325
+ conversationId,
2326
+ add: tags.optional().describe("Tags to add"),
2327
+ remove: tags.optional().describe("Tags to remove"),
2328
+ set: tags.optional().describe("Replace every tag with these ([] clears them)")
2329
+ }),
2330
+ outputSchema: changeOutput,
2331
+ annotations: CHANGE,
2332
+ handler: (api, { conversationId: cid, add, remove, set }) => {
2333
+ if (!add?.length && !remove?.length && !set) {
2334
+ throw new ToolError("Pass tags to add, remove, or set.");
2335
+ }
2336
+ return patch(api, cid, {
2337
+ ...set ? { tags: set } : {},
2338
+ ...add?.length ? { addTags: add } : {},
2339
+ ...remove?.length ? { removeTags: remove } : {}
2340
+ });
2341
+ }
2342
+ }),
2343
+ defineTool2({
2344
+ name: "close_conversation",
2345
+ title: "Close conversation",
2346
+ 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.",
2347
+ scope: "inbox:write",
2348
+ endpoint: "PATCH /v1/conversations/:id",
2349
+ inputSchema: z3.object({
2350
+ conversationId,
2351
+ status: z3.enum(["closed", "resolved"]).default("closed").describe("closed (default) or resolved")
2352
+ }),
2353
+ outputSchema: changeOutput,
2354
+ annotations: CHANGE,
2355
+ handler: (api, { conversationId: cid, status }) => patch(api, cid, { status })
2356
+ }),
2357
+ defineTool2({
2358
+ name: "reopen_conversation",
2359
+ title: "Reopen conversation",
2360
+ description: "Reopen a closed or resolved conversation (status open). Needs the inbox:write scope.",
2361
+ scope: "inbox:write",
2362
+ endpoint: "PATCH /v1/conversations/:id",
2363
+ inputSchema: z3.object({ conversationId }),
2364
+ outputSchema: changeOutput,
2365
+ annotations: CHANGE,
2366
+ handler: (api, { conversationId: cid }) => patch(api, cid, { status: "open" })
2367
+ }),
2368
+ defineTool2({
2369
+ name: "get_analytics_summary",
2370
+ title: "Get analytics summary",
2371
+ 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.",
2372
+ scope: "analytics:read",
2373
+ endpoint: "GET /v1/analytics/summary",
2374
+ inputSchema: z3.object({
2375
+ days: z3.number().int().min(1).max(366).optional().describe("Period length in days, ending today (default 30)"),
2376
+ from: z3.iso.date().optional().describe("First day, YYYY-MM-DD"),
2377
+ to: z3.iso.date().optional().describe("Last day, YYYY-MM-DD"),
2378
+ tz: z3.string().max(64).optional().describe("IANA time zone for day boundaries (default UTC)"),
2379
+ projectId: id("Project").optional()
2380
+ }),
2381
+ outputSchema: z3.looseObject({ range: loose, kpis: loose, inbox: loose }),
2382
+ annotations: READ2,
2383
+ handler: async (api, args) => api.request(
2384
+ "GET",
2385
+ "/v1/analytics/summary",
2386
+ { query: args }
2387
+ )
2388
+ }),
2389
+ defineTool2({
2390
+ name: "list_routing_rules",
2391
+ title: "List routing rules",
2392
+ 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.",
2393
+ scope: "routing:read",
2394
+ endpoint: "GET /v1/routing-rules",
2395
+ inputSchema: z3.object({}),
2396
+ outputSchema: z3.object({ rules: z3.array(rule) }),
2397
+ annotations: READ2,
2398
+ handler: async (api) => {
2399
+ const r = await api.request(
2400
+ "GET",
2401
+ "/v1/routing-rules"
2402
+ );
2403
+ return { rules: r.routing_rules ?? [] };
2404
+ }
2405
+ }),
2406
+ defineTool2({
2407
+ name: "create_routing_rule",
2408
+ title: "Create routing rule",
2409
+ 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.",
2410
+ scope: "routing:write",
2411
+ endpoint: "POST /v1/routing-rules",
2412
+ inputSchema: z3.object(ruleFields),
2413
+ outputSchema: z3.object({ rule }),
2414
+ annotations: {
2415
+ readOnlyHint: false,
2416
+ destructiveHint: false,
2417
+ idempotentHint: false,
2418
+ openWorldHint: false
2419
+ },
2420
+ handler: async (api, body) => {
2421
+ const r = await api.request(
2422
+ "POST",
2423
+ "/v1/routing-rules",
2424
+ { body }
2425
+ );
2426
+ return { rule: r.routing_rule };
2427
+ }
2428
+ }),
2429
+ defineTool2({
2430
+ name: "update_routing_rule",
2431
+ title: "Update routing rule",
2432
+ 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.",
2433
+ scope: "routing:write",
2434
+ endpoint: "PATCH /v1/routing-rules/:id",
2435
+ inputSchema: z3.object({
2436
+ ruleId: id("Routing rule"),
2437
+ name: ruleFields.name.optional(),
2438
+ targetChannelId: ruleFields.targetChannelId.optional(),
2439
+ conditions: routingConditions.optional(),
2440
+ projectId: ruleFields.projectId,
2441
+ widgetId: ruleFields.widgetId,
2442
+ integrationId: ruleFields.integrationId,
2443
+ assignToUserId: ruleFields.assignToUserId,
2444
+ isActive: ruleFields.isActive,
2445
+ priority: ruleFields.priority
2446
+ }),
2447
+ outputSchema: z3.object({ rule }),
2448
+ annotations: {
2449
+ readOnlyHint: false,
2450
+ destructiveHint: false,
2451
+ idempotentHint: true,
2452
+ openWorldHint: false
2453
+ },
2454
+ handler: async (api, { ruleId, ...body }) => {
2455
+ if (Object.values(body).every((v) => v === void 0)) {
2456
+ throw new ToolError("Pass at least one field to change.");
2457
+ }
2458
+ const r = await api.request(
2459
+ "PATCH",
2460
+ `/v1/routing-rules/${encodeURIComponent(ruleId)}`,
2461
+ { body }
2462
+ );
2463
+ return { rule: r.routing_rule };
2464
+ }
2465
+ }),
2466
+ defineTool2({
2467
+ name: "delete_routing_rule",
2468
+ title: "Delete routing rule",
2469
+ 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.",
2470
+ scope: "routing:write",
2471
+ endpoint: "DELETE /v1/routing-rules/:id",
2472
+ inputSchema: z3.object({ ruleId: id("Routing rule") }),
2473
+ outputSchema: z3.object({ deleted: z3.boolean(), ruleId: z3.string() }),
2474
+ annotations: {
2475
+ readOnlyHint: false,
2476
+ destructiveHint: true,
2477
+ idempotentHint: true,
2478
+ openWorldHint: false
2479
+ },
2480
+ handler: async (api, { ruleId }) => {
2481
+ await api.request(
2482
+ "DELETE",
2483
+ `/v1/routing-rules/${encodeURIComponent(ruleId)}`
2484
+ );
2485
+ return { deleted: true, ruleId };
2486
+ }
2487
+ })
2488
+ ];
2489
+ var INBOX_TOOL_NAMES = INBOX_TOOLS.map((t) => t.name);
2490
+
2491
+ // src/tools.ts
2492
+ function ok(data2) {
594
2493
  return {
595
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
596
- structuredContent: data
2494
+ content: [{ type: "text", text: JSON.stringify(data2, null, 2) }],
2495
+ structuredContent: data2
597
2496
  };
598
2497
  }
599
2498
  async function run(fn) {
@@ -607,25 +2506,15 @@ async function run(fn) {
607
2506
  };
608
2507
  }
609
2508
  }
610
- var record2 = z2.record(z2.string(), z2.unknown());
611
- var loose = z2.looseObject({});
612
- var page = {
613
- total: z2.number(),
614
- limit: z2.number(),
615
- offset: z2.number(),
616
- hasMore: z2.boolean()
2509
+ var record4 = z4.record(z4.string(), z4.unknown());
2510
+ var loose2 = z4.looseObject({});
2511
+ var page2 = {
2512
+ total: z4.number(),
2513
+ limit: z4.number(),
2514
+ offset: z4.number(),
2515
+ hasMore: z4.boolean()
617
2516
  };
618
- var conversationSummary = z2.looseObject({
619
- id: z2.string(),
620
- subject: z2.string().nullable().optional(),
621
- status: z2.string(),
622
- priority: z2.string().nullable().optional(),
623
- customerEmail: z2.string().nullable().optional(),
624
- customerName: z2.string().nullable().optional(),
625
- tags: z2.array(z2.string()).nullable().optional(),
626
- createdAt: z2.string().optional()
627
- });
628
- var READ = {
2517
+ var READ3 = {
629
2518
  readOnlyHint: true,
630
2519
  destructiveHint: false,
631
2520
  idempotentHint: true,
@@ -637,9 +2526,9 @@ function registerServerTools(server, api) {
637
2526
  {
638
2527
  title: "Get project",
639
2528
  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.",
640
- inputSchema: z2.object({}),
641
- outputSchema: z2.object({ project: loose }),
642
- annotations: READ
2529
+ inputSchema: z4.object({}),
2530
+ outputSchema: z4.object({ project: loose2 }),
2531
+ annotations: READ3
643
2532
  },
644
2533
  () => run(async () => {
645
2534
  const r = await api.request("GET", "/projects/me");
@@ -651,9 +2540,9 @@ function registerServerTools(server, api) {
651
2540
  {
652
2541
  title: "Get analytics",
653
2542
  description: "Daily support activity for the last 30 days (UTC): conversations created and resolved, customer and agent messages.",
654
- inputSchema: z2.object({}),
655
- outputSchema: z2.object({ analytics: loose }),
656
- annotations: READ
2543
+ inputSchema: z4.object({}),
2544
+ outputSchema: z4.object({ analytics: loose2 }),
2545
+ annotations: READ3
657
2546
  },
658
2547
  () => run(async () => {
659
2548
  const r = await api.request(
@@ -663,75 +2552,20 @@ function registerServerTools(server, api) {
663
2552
  return { analytics: r.analytics };
664
2553
  })
665
2554
  );
666
- server.registerTool(
667
- "list_conversations",
668
- {
669
- title: "List conversations",
670
- 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.",
671
- inputSchema: z2.object({
672
- status: z2.enum(STATUSES).optional().describe("Only conversations with this status"),
673
- priority: z2.enum(PRIORITIES).optional().describe("Only conversations with this priority"),
674
- limit: z2.number().int().min(1).max(50).default(20).describe("How many to return (1-50, default 20)")
675
- }),
676
- outputSchema: z2.object({
677
- conversations: z2.array(conversationSummary),
678
- returned: z2.number(),
679
- matched: z2.number(),
680
- scanned: z2.number()
681
- }),
682
- annotations: READ
683
- },
684
- ({ status, priority, limit }) => run(async () => {
685
- const r = await api.request(
686
- "GET",
687
- "/conversations"
688
- );
689
- const all = r.conversations ?? [];
690
- const matched = all.filter(
691
- (c) => (!status || c.status === status) && (!priority || c.priority === priority)
692
- );
693
- const conversations = matched.slice(0, limit);
694
- return {
695
- conversations,
696
- returned: conversations.length,
697
- matched: matched.length,
698
- scanned: all.length
699
- };
700
- })
701
- );
702
- server.registerTool(
703
- "get_conversation",
704
- {
705
- title: "Get conversation",
706
- description: "Read one conversation with its full message thread (customer, agent and system messages), status, priority and tags.",
707
- inputSchema: z2.object({
708
- conversationId: z2.string().uuid().describe("Conversation ID (from list_conversations)")
709
- }),
710
- outputSchema: z2.object({ conversation: loose }),
711
- annotations: READ
712
- },
713
- ({ conversationId }) => run(async () => {
714
- const conversation = await api.request(
715
- "GET",
716
- `/conversations/${encodeURIComponent(conversationId)}`
717
- );
718
- return { conversation };
719
- })
720
- );
721
2555
  server.registerTool(
722
2556
  "create_conversation",
723
2557
  {
724
2558
  title: "Create conversation",
725
2559
  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.",
726
- inputSchema: z2.object({
727
- customerEmail: z2.email().describe("Customer's email address"),
728
- customerName: z2.string().max(255).optional().describe("Customer's name"),
729
- subject: z2.string().min(1).max(500).describe("Subject line"),
730
- message: z2.string().min(1).describe("First message, from the customer"),
731
- channel: z2.string().optional().describe("Slack channel ID to post to, overriding routing rules"),
732
- tags: z2.array(z2.string()).max(20).optional().describe("Tags")
2560
+ inputSchema: z4.object({
2561
+ customerEmail: z4.email().describe("Customer's email address"),
2562
+ customerName: z4.string().max(255).optional().describe("Customer's name"),
2563
+ subject: z4.string().min(1).max(500).describe("Subject line"),
2564
+ message: z4.string().min(1).describe("First message, from the customer"),
2565
+ channel: z4.string().optional().describe("Slack channel ID to post to, overriding routing rules"),
2566
+ tags: z4.array(z4.string()).max(20).optional().describe("Tags")
733
2567
  }),
734
- outputSchema: z2.object({ conversation: loose }),
2568
+ outputSchema: z4.object({ conversation: loose2 }),
735
2569
  annotations: {
736
2570
  readOnlyHint: false,
737
2571
  destructiveHint: false,
@@ -751,12 +2585,12 @@ function registerServerTools(server, api) {
751
2585
  {
752
2586
  title: "List contacts",
753
2587
  description: "List the customers who contacted the organization, with their conversation count. Paginate with limit/offset.",
754
- inputSchema: z2.object({
755
- limit: z2.number().int().min(1).max(100).default(50),
756
- offset: z2.number().int().min(0).default(0)
2588
+ inputSchema: z4.object({
2589
+ limit: z4.number().int().min(1).max(100).default(50),
2590
+ offset: z4.number().int().min(0).default(0)
757
2591
  }),
758
- outputSchema: z2.object({ contacts: z2.array(loose), ...page }),
759
- annotations: READ
2592
+ outputSchema: z4.object({ contacts: z4.array(loose2), ...page2 }),
2593
+ annotations: READ3
760
2594
  },
761
2595
  ({ limit, offset }) => run(async () => {
762
2596
  const r = await api.request("GET", "/contacts", {
@@ -776,11 +2610,11 @@ function registerServerTools(server, api) {
776
2610
  {
777
2611
  title: "Get contact",
778
2612
  description: "Read one contact (customer) by ID.",
779
- inputSchema: z2.object({
780
- contactId: z2.string().uuid().describe("Contact ID (from list_contacts)")
2613
+ inputSchema: z4.object({
2614
+ contactId: z4.string().uuid().describe("Contact ID (from list_contacts)")
781
2615
  }),
782
- outputSchema: z2.object({ contact: loose }),
783
- annotations: READ
2616
+ outputSchema: z4.object({ contact: loose2 }),
2617
+ annotations: READ3
784
2618
  },
785
2619
  ({ contactId }) => run(async () => {
786
2620
  const contact = await api.request(
@@ -795,14 +2629,14 @@ function registerServerTools(server, api) {
795
2629
  {
796
2630
  title: "Create contact",
797
2631
  description: "Add a customer by email, or return the existing one with that email. Needs the write scope.",
798
- inputSchema: z2.object({
799
- email: z2.email(),
800
- name: z2.string().max(255).optional(),
801
- company: z2.string().optional(),
802
- phone: z2.string().optional(),
803
- tags: z2.array(z2.string()).max(20).optional()
2632
+ inputSchema: z4.object({
2633
+ email: z4.email(),
2634
+ name: z4.string().max(255).optional(),
2635
+ company: z4.string().optional(),
2636
+ phone: z4.string().optional(),
2637
+ tags: z4.array(z4.string()).max(20).optional()
804
2638
  }),
805
- outputSchema: z2.object({ contact: loose }),
2639
+ outputSchema: z4.object({ contact: loose2 }),
806
2640
  annotations: {
807
2641
  readOnlyHint: false,
808
2642
  destructiveHint: false,
@@ -822,23 +2656,23 @@ function registerServerTools(server, api) {
822
2656
  {
823
2657
  title: "Send an Event to Slack",
824
2658
  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.',
825
- inputSchema: z2.object({
826
- event: z2.string().max(100).regex(
2659
+ inputSchema: z4.object({
2660
+ event: z4.string().max(100).regex(
827
2661
  import_core3.EVENT_KEY_PATTERN,
828
2662
  "lowercase letters, digits, dots, underscores or dashes"
829
2663
  ).describe("Event key, e.g. user.subscribed"),
830
- data: record2.optional().describe(
2664
+ data: record4.optional().describe(
831
2665
  'Template fields, e.g. { "username": "ada", "plan": "Pro" }'
832
2666
  ),
833
- idempotencyKey: z2.string().min(1).max(255).optional().describe("Send at most once per key (retry-safe)"),
834
- channel: z2.string().optional().describe("Slack channel ID instead of the event's channel")
2667
+ idempotencyKey: z4.string().min(1).max(255).optional().describe("Send at most once per key (retry-safe)"),
2668
+ channel: z4.string().optional().describe("Slack channel ID instead of the event's channel")
835
2669
  }),
836
- outputSchema: z2.looseObject({
837
- id: z2.string(),
838
- event: z2.string(),
839
- status: z2.string(),
840
- reason: z2.string().optional(),
841
- duplicate: z2.boolean().optional()
2670
+ outputSchema: z4.looseObject({
2671
+ id: z4.string(),
2672
+ event: z4.string(),
2673
+ status: z4.string(),
2674
+ reason: z4.string().optional(),
2675
+ duplicate: z4.boolean().optional()
842
2676
  }),
843
2677
  annotations: {
844
2678
  readOnlyHint: false,
@@ -847,9 +2681,9 @@ function registerServerTools(server, api) {
847
2681
  openWorldHint: true
848
2682
  }
849
2683
  },
850
- ({ event, data, idempotencyKey, channel }) => run(async () => {
2684
+ ({ event, data: data2, idempotencyKey, channel }) => run(async () => {
851
2685
  const client = await api.notifier();
852
- const r = await client.notify(event, data, { idempotencyKey, channel });
2686
+ const r = await client.notify(event, data2, { idempotencyKey, channel });
853
2687
  return {
854
2688
  id: r.id,
855
2689
  event: r.event,
@@ -864,12 +2698,12 @@ function registerServerTools(server, api) {
864
2698
  {
865
2699
  title: "List Events",
866
2700
  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.",
867
- inputSchema: z2.object({}),
868
- outputSchema: z2.object({
869
- projectId: z2.string(),
870
- definitions: z2.array(loose)
2701
+ inputSchema: z4.object({}),
2702
+ outputSchema: z4.object({
2703
+ projectId: z4.string(),
2704
+ definitions: z4.array(loose2)
871
2705
  }),
872
- annotations: READ
2706
+ annotations: READ3
873
2707
  },
874
2708
  () => run(async () => {
875
2709
  const projectId = await api.projectId();
@@ -885,15 +2719,15 @@ function registerServerTools(server, api) {
885
2719
  {
886
2720
  title: "List Event deliveries",
887
2721
  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.",
888
- inputSchema: z2.object({
889
- definitionId: z2.string().uuid().optional().describe("Only this Event (ID from list_event_definitions)"),
890
- limit: z2.number().int().min(1).max(100).default(20)
2722
+ inputSchema: z4.object({
2723
+ definitionId: z4.string().uuid().optional().describe("Only this Event (ID from list_event_definitions)"),
2724
+ limit: z4.number().int().min(1).max(100).default(20)
891
2725
  }),
892
- outputSchema: z2.object({
893
- projectId: z2.string(),
894
- deliveries: z2.array(loose)
2726
+ outputSchema: z4.object({
2727
+ projectId: z4.string(),
2728
+ deliveries: z4.array(loose2)
895
2729
  }),
896
- annotations: READ
2730
+ annotations: READ3
897
2731
  },
898
2732
  ({ definitionId, limit }) => run(async () => {
899
2733
  const projectId = await api.projectId();
@@ -910,20 +2744,20 @@ function registerServerTools(server, api) {
910
2744
  {
911
2745
  title: "Query logs",
912
2746
  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.",
913
- inputSchema: z2.object({
914
- severity: z2.enum(import_core3.EVENT_SEVERITIES).optional(),
915
- source: z2.enum(["client_sdk", "server_api", "system"]).optional().describe(
2747
+ inputSchema: z4.object({
2748
+ severity: z4.enum(import_core3.EVENT_SEVERITIES).optional(),
2749
+ source: z4.enum(["client_sdk", "server_api", "system"]).optional().describe(
916
2750
  "client_sdk = browser (widget key), server_api = server key"
917
2751
  ),
918
- search: z2.string().max(200).optional().describe("Text to look for"),
919
- from: z2.iso.datetime({ offset: true }).optional().describe("ISO 8601 start"),
920
- to: z2.iso.datetime({ offset: true }).optional().describe("ISO 8601 end"),
921
- projectId: z2.string().uuid().optional(),
922
- limit: z2.number().int().min(1).max(100).default(50),
923
- offset: z2.number().int().min(0).default(0)
2752
+ search: z4.string().max(200).optional().describe("Text to look for"),
2753
+ from: z4.iso.datetime({ offset: true }).optional().describe("ISO 8601 start"),
2754
+ to: z4.iso.datetime({ offset: true }).optional().describe("ISO 8601 end"),
2755
+ projectId: z4.string().uuid().optional(),
2756
+ limit: z4.number().int().min(1).max(100).default(50),
2757
+ offset: z4.number().int().min(0).default(0)
924
2758
  }),
925
- outputSchema: z2.object({ events: z2.array(loose), ...page }),
926
- annotations: READ
2759
+ outputSchema: z4.object({ events: z4.array(loose2), ...page2 }),
2760
+ annotations: READ3
927
2761
  },
928
2762
  (args) => run(async () => {
929
2763
  const r = await api.request("GET", "/v1/events", { query: args });
@@ -941,12 +2775,12 @@ function registerServerTools(server, api) {
941
2775
  {
942
2776
  title: "Record a log",
943
2777
  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.",
944
- inputSchema: z2.object({
945
- name: z2.string().min(1).max(255).describe("Log name, e.g. payment.failed"),
946
- severity: z2.enum(import_core3.EVENT_SEVERITIES).default("info"),
947
- properties: record2.optional().describe("Extra fields")
2778
+ inputSchema: z4.object({
2779
+ name: z4.string().min(1).max(255).describe("Log name, e.g. payment.failed"),
2780
+ severity: z4.enum(import_core3.EVENT_SEVERITIES).default("info"),
2781
+ properties: record4.optional().describe("Extra fields")
948
2782
  }),
949
- outputSchema: z2.object({ id: z2.string(), projectId: z2.string() }),
2783
+ outputSchema: z4.object({ id: z4.string(), projectId: z4.string() }),
950
2784
  annotations: {
951
2785
  readOnlyHint: false,
952
2786
  destructiveHint: false,
@@ -975,16 +2809,16 @@ function registerWidgetTools(server, api) {
975
2809
  {
976
2810
  title: "Submit contact form",
977
2811
  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.",
978
- inputSchema: z2.object({
979
- name: z2.string().min(1).describe("Visitor's name"),
980
- email: z2.email().describe("Visitor's email"),
981
- subject: z2.string().optional(),
982
- message: z2.string().min(1),
983
- pageUrl: z2.url().optional().describe("Page the form was sent from")
2812
+ inputSchema: z4.object({
2813
+ name: z4.string().min(1).describe("Visitor's name"),
2814
+ email: z4.email().describe("Visitor's email"),
2815
+ subject: z4.string().optional(),
2816
+ message: z4.string().min(1),
2817
+ pageUrl: z4.url().optional().describe("Page the form was sent from")
984
2818
  }),
985
- outputSchema: z2.looseObject({
986
- conversationId: z2.string().optional(),
987
- message: z2.string().optional()
2819
+ outputSchema: z4.looseObject({
2820
+ conversationId: z4.string().optional(),
2821
+ message: z4.string().optional()
988
2822
  }),
989
2823
  annotations: VISITOR
990
2824
  },
@@ -998,12 +2832,12 @@ function registerWidgetTools(server, api) {
998
2832
  {
999
2833
  title: "Start chat session",
1000
2834
  description: "Start a live chat session as a website visitor (widget key). Returns the session ID for send_message.",
1001
- inputSchema: z2.object({
1002
- metadata: record2.optional().describe("Metadata to attach (e.g. user info)")
2835
+ inputSchema: z4.object({
2836
+ metadata: record4.optional().describe("Metadata to attach (e.g. user info)")
1003
2837
  }),
1004
- outputSchema: z2.looseObject({
1005
- sessionId: z2.string(),
1006
- status: z2.string()
2838
+ outputSchema: z4.looseObject({
2839
+ sessionId: z4.string(),
2840
+ status: z4.string()
1007
2841
  }),
1008
2842
  annotations: VISITOR
1009
2843
  },
@@ -1021,13 +2855,13 @@ function registerWidgetTools(server, api) {
1021
2855
  {
1022
2856
  title: "Send chat message",
1023
2857
  description: "Send a visitor message in a chat session (widget key); it is posted to the Slack thread.",
1024
- inputSchema: z2.object({
1025
- sessionId: z2.string().min(1).describe("From create_chat_session"),
1026
- content: z2.string().min(1).max(5e3)
2858
+ inputSchema: z4.object({
2859
+ sessionId: z4.string().min(1).describe("From create_chat_session"),
2860
+ content: z4.string().min(1).max(5e3)
1027
2861
  }),
1028
- outputSchema: z2.looseObject({
1029
- messageId: z2.string(),
1030
- sessionStatus: z2.string()
2862
+ outputSchema: z4.looseObject({
2863
+ messageId: z4.string(),
2864
+ sessionStatus: z4.string()
1031
2865
  }),
1032
2866
  annotations: VISITOR
1033
2867
  },
@@ -1041,16 +2875,16 @@ function registerWidgetTools(server, api) {
1041
2875
  {
1042
2876
  title: "Read chat messages",
1043
2877
  description: "Read a chat session's messages, including agent replies from Slack (widget key).",
1044
- inputSchema: z2.object({
1045
- sessionId: z2.string().min(1),
1046
- limit: z2.number().int().min(1).max(100).default(50),
1047
- offset: z2.number().int().min(0).default(0)
2878
+ inputSchema: z4.object({
2879
+ sessionId: z4.string().min(1),
2880
+ limit: z4.number().int().min(1).max(100).default(50),
2881
+ offset: z4.number().int().min(0).default(0)
1048
2882
  }),
1049
- outputSchema: z2.object({
1050
- sessionId: z2.string(),
1051
- messages: z2.array(loose)
2883
+ outputSchema: z4.object({
2884
+ sessionId: z4.string(),
2885
+ messages: z4.array(loose2)
1052
2886
  }),
1053
- annotations: { ...READ, openWorldHint: true }
2887
+ annotations: { ...READ3, openWorldHint: true }
1054
2888
  },
1055
2889
  ({ sessionId, limit, offset }) => run(async () => {
1056
2890
  const messages = await api.widget().getChatMessages(sessionId, limit, offset);
@@ -1072,10 +2906,10 @@ function registerWidgetTools(server, api) {
1072
2906
  {
1073
2907
  title: "End chat session",
1074
2908
  description: "End a chat session as the visitor (widget key).",
1075
- inputSchema: z2.object({ sessionId: z2.string().min(1) }),
1076
- outputSchema: z2.looseObject({
1077
- sessionId: z2.string(),
1078
- status: z2.string()
2909
+ inputSchema: z4.object({ sessionId: z4.string().min(1) }),
2910
+ outputSchema: z4.looseObject({
2911
+ sessionId: z4.string(),
2912
+ status: z4.string()
1079
2913
  }),
1080
2914
  annotations: { ...VISITOR, idempotentHint: true }
1081
2915
  },
@@ -1085,6 +2919,27 @@ function registerWidgetTools(server, api) {
1085
2919
  })
1086
2920
  );
1087
2921
  }
2922
+ function registerAll(server, api, tools) {
2923
+ for (const tool of tools) {
2924
+ server.registerTool(
2925
+ tool.name,
2926
+ {
2927
+ title: tool.title,
2928
+ description: tool.description,
2929
+ inputSchema: tool.inputSchema,
2930
+ outputSchema: tool.outputSchema,
2931
+ annotations: tool.annotations
2932
+ },
2933
+ (args) => run(async () => await tool.handler(api, args))
2934
+ );
2935
+ }
2936
+ }
2937
+ function registerInboxTools(server, api) {
2938
+ registerAll(server, api, INBOX_TOOLS);
2939
+ }
2940
+ function registerAgentTools(server, api) {
2941
+ registerAll(server, api, AGENT_TOOLS);
2942
+ }
1088
2943
 
1089
2944
  // src/http.ts
1090
2945
  var import_node_http = require("http");
@@ -1116,7 +2971,7 @@ async function toRequest(req, origin) {
1116
2971
  async function startHttpServer(factory, options = {}) {
1117
2972
  const host = options.host ?? "127.0.0.1";
1118
2973
  const port = options.port ?? 3333;
1119
- const path = options.path ?? "/mcp";
2974
+ const path2 = options.path ?? "/mcp";
1120
2975
  const local = LOCAL_HOSTS.has(host);
1121
2976
  const handler = (0, import_server.createMcpHandler)(() => factory(), {
1122
2977
  onerror: (err) => console.error("[channlize-mcp]", err.message)
@@ -1128,8 +2983,8 @@ async function startHttpServer(factory, options = {}) {
1128
2983
  `http://${req.headers.host ?? `${host}:${port}`}`
1129
2984
  );
1130
2985
  const url2 = new URL(request.url);
1131
- if (url2.pathname !== path) {
1132
- res.writeHead(404, { "Content-Type": "text/plain" }).end(`Not found: use ${path}`);
2986
+ if (url2.pathname !== path2) {
2987
+ res.writeHead(404, { "Content-Type": "text/plain" }).end(`Not found: use ${path2}`);
1133
2988
  return;
1134
2989
  }
1135
2990
  const rejected = local ? (0, import_server.hostHeaderValidationResponse)(request, (0, import_server.localhostAllowedHostnames)()) ?? (0, import_server.originValidationResponse)(request, (0, import_server.localhostAllowedOrigins)()) : void 0;
@@ -1156,7 +3011,7 @@ async function startHttpServer(factory, options = {}) {
1156
3011
  const actualPort = typeof address === "object" && address ? address.port : port;
1157
3012
  const shownHost = host.includes(":") ? `[${host}]` : host;
1158
3013
  return {
1159
- url: `http://${shownHost}:${actualPort}${path}`,
3014
+ url: `http://${shownHost}:${actualPort}${path2}`,
1160
3015
  server,
1161
3016
  close: () => new Promise((r) => server.close(() => r()))
1162
3017
  };
@@ -1164,12 +3019,18 @@ async function startHttpServer(factory, options = {}) {
1164
3019
 
1165
3020
  // src/index.ts
1166
3021
  var import_core4 = require("@channlize/core");
1167
- var VERSION = true ? "0.4.0" : "0.0.0";
3022
+ var VERSION = true ? "0.6.0" : "0.0.0";
1168
3023
  var INSTRUCTIONS = `Channlize routes website conversations and app events to the team's Slack.
1169
- - Inbox: list_conversations / get_conversation, then contacts. Replies and status changes happen in Slack or the dashboard.
3024
+ - 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.
3025
+ - get_analytics_summary gives KPIs and the inbox right now; list/create/update/delete_routing_rule manage which Slack channel new conversations go to.
1170
3026
  - Events (notify_event) post a Slack message per event key; list_event_definitions and list_event_deliveries explain what was sent or skipped.
1171
3027
  - Logs: query_logs searches log tracking; track_log records one.
1172
- - Write tools need a server API key with the write scope; a 403 means the key is read-only.`;
3028
+ - 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).
3029
+ - 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.
3030
+ - 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).
3031
+ - History and numbers: search_agent_messages finds past messages, tasks and answered questions (search before asking a person again). get_agent_cost, get_agent_metrics and get_agent_graph report spend, reply latency, backlog and who talks to whom over a range of UTC days; pass mine: true for your own agent. They return the top rows plus totals and a summary line.
3032
+ - 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.
3033
+ - 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.`;
1173
3034
  function buildServer(config, info = {}) {
1174
3035
  const api = new ChannlizeApi(resolveConfig(config));
1175
3036
  const server = new import_server2.McpServer(
@@ -1177,6 +3038,8 @@ function buildServer(config, info = {}) {
1177
3038
  { instructions: INSTRUCTIONS }
1178
3039
  );
1179
3040
  if (api.hasServerKey) {
3041
+ registerInboxTools(server, api);
3042
+ registerAgentTools(server, api);
1180
3043
  registerServerTools(server, api);
1181
3044
  registerServerPrompts(server);
1182
3045
  }
@@ -1225,6 +3088,8 @@ Keys (flags override environment variables):
1225
3088
  --api-key <key> Either kind, by prefix CHANNLIZE_API_KEY
1226
3089
  --project-id <id> Project (default: the key's first project)
1227
3090
  CHANNLIZE_PROJECT_ID
3091
+ --agent-id <id> Agent the agent tools act as (multi-agent keys)
3092
+ CHANNLIZE_AGENT_ID
1228
3093
  Options:
1229
3094
  --base-url <url> API URL (default https://api.channlize.com)
1230
3095
  CHANNLIZE_BASE_URL
@@ -1238,17 +3103,18 @@ Options:
1238
3103
 
1239
3104
  The server API key enables the inbox, contact, Events and log tools (read
1240
3105
  scope for reads, write scope to send). The widget key adds visitor tools
1241
- (contact form, chat). Create keys in the Channlize dashboard under
1242
- SDK & Integration \u2192 API keys.
3106
+ (contact form, chat). An agent key or a multi-agent key (agents:send,
3107
+ agents:read, agents:ask_human) enables the agent tools. Create keys in the
3108
+ Channlize dashboard under SDK & Integration \u2192 API keys.
1243
3109
 
1244
3110
  Claude Code:
1245
3111
  claude mcp add channlize --env CHANNLIZE_SERVER_API_KEY=ch_live_\u2026 -- npx -y @channlize/mcp
1246
3112
 
1247
3113
  Docs: https://github.com/channlize/sdk/tree/main/packages/mcp
1248
3114
  `;
1249
- function fail(message) {
3115
+ function fail(message2) {
1250
3116
  console.error(
1251
- `channlize-mcp: ${message}
3117
+ `channlize-mcp: ${message2}
1252
3118
 
1253
3119
  Run 'channlize-mcp --help' for usage.`
1254
3120
  );
@@ -1263,6 +3129,7 @@ async function main() {
1263
3129
  "widget-key": { type: "string" },
1264
3130
  "api-key": { type: "string" },
1265
3131
  "project-id": { type: "string" },
3132
+ "agent-id": { type: "string" },
1266
3133
  "base-url": { type: "string" },
1267
3134
  timeout: { type: "string" },
1268
3135
  transport: { type: "string" },
@@ -1284,18 +3151,19 @@ async function main() {
1284
3151
  console.log(VERSION);
1285
3152
  return;
1286
3153
  }
1287
- const str = (k) => values[k];
3154
+ const str2 = (k) => values[k];
1288
3155
  const env = configFromEnv();
1289
- const timeout = str("timeout");
3156
+ const timeout = str2("timeout");
1290
3157
  const config = {
1291
- serverApiKey: str("server-api-key") ?? env.serverApiKey,
1292
- widgetKey: str("widget-key") ?? env.widgetKey,
1293
- apiKey: str("api-key") ?? env.apiKey,
1294
- projectId: str("project-id") ?? env.projectId,
1295
- baseUrl: str("base-url") ?? env.baseUrl,
3158
+ serverApiKey: str2("server-api-key") ?? env.serverApiKey,
3159
+ widgetKey: str2("widget-key") ?? env.widgetKey,
3160
+ apiKey: str2("api-key") ?? env.apiKey,
3161
+ projectId: str2("project-id") ?? env.projectId,
3162
+ agentId: str2("agent-id") ?? env.agentId,
3163
+ baseUrl: str2("base-url") ?? env.baseUrl,
1296
3164
  timeout: timeout !== void 0 ? Number(timeout) : env.timeout
1297
3165
  };
1298
- const transport = str("transport") ?? process.env.CHANNLIZE_MCP_TRANSPORT ?? "stdio";
3166
+ const transport = str2("transport") ?? process.env.CHANNLIZE_MCP_TRANSPORT ?? "stdio";
1299
3167
  if (transport !== "stdio" && transport !== "http") {
1300
3168
  fail(`unknown transport "${transport}": use stdio or http`);
1301
3169
  }
@@ -1307,11 +3175,11 @@ async function main() {
1307
3175
  throw err;
1308
3176
  }
1309
3177
  if (transport === "http") {
1310
- const port = Number(str("port") ?? process.env.CHANNLIZE_MCP_PORT ?? 3333);
3178
+ const port = Number(str2("port") ?? process.env.CHANNLIZE_MCP_PORT ?? 3333);
1311
3179
  if (!Number.isInteger(port) || port < 0 || port > 65535) {
1312
- fail(`invalid port "${str("port") ?? process.env.CHANNLIZE_MCP_PORT}"`);
3180
+ fail(`invalid port "${str2("port") ?? process.env.CHANNLIZE_MCP_PORT}"`);
1313
3181
  }
1314
- const { url: url2, close } = await server.runHttp({ host: str("host"), port });
3182
+ const { url: url2, close } = await server.runHttp({ host: str2("host"), port });
1315
3183
  console.error(`Channlize MCP server listening on ${url2}`);
1316
3184
  const stop2 = async () => {
1317
3185
  await close();