@channlize/mcp 0.5.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/CHANGELOG.md +17 -0
- package/README.md +15 -2
- package/dist/agents.d.mts +12 -2
- package/dist/agents.d.ts +12 -2
- package/dist/agents.js +548 -0
- package/dist/agents.js.map +1 -1
- package/dist/agents.mjs +548 -0
- package/dist/agents.mjs.map +1 -1
- package/dist/inbox.js.map +1 -1
- package/dist/inbox.mjs.map +1 -1
- package/dist/index.js +554 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +554 -1
- package/dist/index.mjs.map +1 -1
- package/dist/server.js +566 -13
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -156,6 +156,8 @@ function agentHint(code, body = {}) {
|
|
|
156
156
|
return "This approval needs a person with the required role: an agent cannot answer it.";
|
|
157
157
|
case "AGENT_ADMIN_KEY_REQUIRED":
|
|
158
158
|
return "This needs an unbound agents:admin key (not a key bound to agents): create one under SDK & Integration \u2192 API keys.";
|
|
159
|
+
case "CHANNEL_NOT_VISIBLE":
|
|
160
|
+
return "Your key's agents can't read that channel: leave channelId out, or pick one from list_agent_channels.";
|
|
159
161
|
case "INVALID_ANSWER":
|
|
160
162
|
return "Answer with one of the question's option ids (approve / deny for approvals), or text when free text is allowed.";
|
|
161
163
|
default:
|
|
@@ -752,6 +754,328 @@ var import_core3 = require("@channlize/core");
|
|
|
752
754
|
|
|
753
755
|
// src/agents.ts
|
|
754
756
|
var z2 = __toESM(require("zod/v4"));
|
|
757
|
+
|
|
758
|
+
// src/agents-summary.ts
|
|
759
|
+
var num = (v) => typeof v === "number" ? v : Number(v) || 0;
|
|
760
|
+
var arr = (v) => Array.isArray(v) ? v : [];
|
|
761
|
+
var obj = (v) => v && typeof v === "object" ? v : {};
|
|
762
|
+
var str = (v) => typeof v === "string" ? v : null;
|
|
763
|
+
function money(v) {
|
|
764
|
+
return Math.round(num(v) * 1e4) / 1e4;
|
|
765
|
+
}
|
|
766
|
+
function fmtMoney(v) {
|
|
767
|
+
return money(v).toFixed(4).replace(/\.?0+$/, "") || "0";
|
|
768
|
+
}
|
|
769
|
+
function pct(v) {
|
|
770
|
+
return v === null || v === void 0 ? "n/a" : `${Math.round(num(v) * 1e3) / 10}%`;
|
|
771
|
+
}
|
|
772
|
+
function fmtMs(v) {
|
|
773
|
+
if (v === null || v === void 0) return "n/a";
|
|
774
|
+
const ms = num(v);
|
|
775
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
776
|
+
if (ms < 6e4) return `${Math.round(ms / 100) / 10}s`;
|
|
777
|
+
if (ms < 36e5) return `${Math.round(ms / 6e3) / 10}m`;
|
|
778
|
+
return `${Math.round(ms / 36e4) / 10}h`;
|
|
779
|
+
}
|
|
780
|
+
function highlightText(html) {
|
|
781
|
+
return html.replace(/<mark>/g, "**").replace(/<\/mark>/g, "**").replace(/<[^>]*>/g, "").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
782
|
+
}
|
|
783
|
+
function clip(text, max) {
|
|
784
|
+
const s = typeof text === "string" ? text : "";
|
|
785
|
+
return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
786
|
+
}
|
|
787
|
+
function rangeSummary(range2) {
|
|
788
|
+
const r = obj(range2);
|
|
789
|
+
return {
|
|
790
|
+
from: String(r.from ?? ""),
|
|
791
|
+
to: String(r.to ?? ""),
|
|
792
|
+
days: num(r.days),
|
|
793
|
+
historyStart: str(r.history_start),
|
|
794
|
+
retentionDays: r.retention_days === void 0 ? null : num(r.retention_days),
|
|
795
|
+
clamped: Boolean(r.clamped),
|
|
796
|
+
empty: Boolean(r.empty)
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
function rangeNote(r) {
|
|
800
|
+
const base = `${r.from}..${r.to} (${r.days} day${r.days === 1 ? "" : "s"}, UTC)`;
|
|
801
|
+
return r.clamped ? `${base}; clamped to the plan's history start ${r.historyStart}` : base;
|
|
802
|
+
}
|
|
803
|
+
function latency(v) {
|
|
804
|
+
const l = obj(v);
|
|
805
|
+
return {
|
|
806
|
+
count: num(l.count),
|
|
807
|
+
avgMs: l.avg_ms === null || l.avg_ms === void 0 ? null : num(l.avg_ms),
|
|
808
|
+
p50Ms: l.p50_ms === null || l.p50_ms === void 0 ? null : num(l.p50_ms),
|
|
809
|
+
p90Ms: l.p90_ms === null || l.p90_ms === void 0 ? null : num(l.p90_ms)
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
function summarizeSearch(r) {
|
|
813
|
+
const results = arr(r.results).map((x) => {
|
|
814
|
+
const channel = x.channel ? obj(x.channel) : null;
|
|
815
|
+
const base = {
|
|
816
|
+
kind: String(x.kind),
|
|
817
|
+
highlight: clip(highlightText(String(x.highlight ?? "")), 400),
|
|
818
|
+
rank: Math.round(num(x.rank) * 1e3) / 1e3,
|
|
819
|
+
channel: channel ? str(channel.slug) ?? str(channel.id) : null,
|
|
820
|
+
channelId: channel ? str(channel.id) : null,
|
|
821
|
+
threadId: str(x.thread_id),
|
|
822
|
+
slackPermalink: str(x.slack_permalink)
|
|
823
|
+
};
|
|
824
|
+
if (x.kind === "question") {
|
|
825
|
+
const q = obj(x.question);
|
|
826
|
+
const answer = q.answer ? obj(q.answer) : null;
|
|
827
|
+
return {
|
|
828
|
+
...base,
|
|
829
|
+
id: String(q.id ?? ""),
|
|
830
|
+
type: "question",
|
|
831
|
+
text: clip(q.question, 500),
|
|
832
|
+
status: str(q.status),
|
|
833
|
+
answer: answer ? clip(
|
|
834
|
+
[answer.option_label ?? answer.option_id, answer.text].filter(Boolean).join(" / "),
|
|
835
|
+
200
|
|
836
|
+
) || null : null,
|
|
837
|
+
sender: str(q.agent_id) ? `agent:${String(q.agent_id)}` : null,
|
|
838
|
+
createdAt: str(q.created_at)
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
const m = obj(x.message);
|
|
842
|
+
const sender = obj(m.sender);
|
|
843
|
+
const senderId = sender.agent_id ?? sender.user_id ?? sender.slack_user_id;
|
|
844
|
+
return {
|
|
845
|
+
...base,
|
|
846
|
+
id: String(m.id ?? ""),
|
|
847
|
+
type: String(m.type ?? "message"),
|
|
848
|
+
text: clip(m.text, 500),
|
|
849
|
+
status: str(m.task_status),
|
|
850
|
+
answer: null,
|
|
851
|
+
sender: sender.kind ? `${String(sender.kind)}${senderId ? `:${String(senderId)}` : ""}` : null,
|
|
852
|
+
createdAt: str(m.created_at)
|
|
853
|
+
};
|
|
854
|
+
});
|
|
855
|
+
const window = obj(r.window);
|
|
856
|
+
return {
|
|
857
|
+
results,
|
|
858
|
+
nextCursor: str(r.next_cursor),
|
|
859
|
+
window: { from: String(window.from ?? ""), days: num(window.days) }
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
function summarizeGraph(r, top2) {
|
|
863
|
+
const range2 = rangeSummary(r.range);
|
|
864
|
+
const nodes = arr(r.nodes);
|
|
865
|
+
const labels = new Map(nodes.map((n) => [String(n.id), String(n.label)]));
|
|
866
|
+
const topNodes = [...nodes].sort(
|
|
867
|
+
(a, b) => num(b.sent) + num(b.received) - (num(a.sent) + num(a.received))
|
|
868
|
+
).slice(0, top2).map((n) => ({
|
|
869
|
+
id: String(n.id),
|
|
870
|
+
kind: String(n.kind),
|
|
871
|
+
label: String(n.label),
|
|
872
|
+
slug: str(n.slug),
|
|
873
|
+
sent: num(n.sent),
|
|
874
|
+
received: num(n.received),
|
|
875
|
+
tokens: num(n.tokens),
|
|
876
|
+
cost: money(n.cost)
|
|
877
|
+
}));
|
|
878
|
+
const edges = arr(r.edges);
|
|
879
|
+
const topEdges = [...edges].sort((a, b) => num(b.messages) - num(a.messages)).slice(0, top2).map((e) => {
|
|
880
|
+
const l = latency(e.reply_latency);
|
|
881
|
+
return {
|
|
882
|
+
source: String(e.source),
|
|
883
|
+
target: String(e.target),
|
|
884
|
+
from: labels.get(String(e.source)) ?? String(e.source),
|
|
885
|
+
to: labels.get(String(e.target)) ?? String(e.target),
|
|
886
|
+
messages: num(e.messages),
|
|
887
|
+
replies: num(e.replies),
|
|
888
|
+
cost: money(e.cost),
|
|
889
|
+
replyAvgMs: l.avgMs,
|
|
890
|
+
replyP90Ms: l.p90Ms,
|
|
891
|
+
lastAt: str(e.last_at)
|
|
892
|
+
};
|
|
893
|
+
});
|
|
894
|
+
const totals = obj(r.totals);
|
|
895
|
+
const truncated = obj(r.truncated);
|
|
896
|
+
const t = {
|
|
897
|
+
nodes: num(totals.nodes),
|
|
898
|
+
edges: num(totals.edges),
|
|
899
|
+
messages: num(totals.messages),
|
|
900
|
+
tokens: num(totals.tokens),
|
|
901
|
+
cost: money(totals.cost)
|
|
902
|
+
};
|
|
903
|
+
const busiest = topEdges[0];
|
|
904
|
+
return {
|
|
905
|
+
range: range2,
|
|
906
|
+
totals: t,
|
|
907
|
+
topNodes,
|
|
908
|
+
topEdges,
|
|
909
|
+
/** Not shown here: beyond `top`, or folded / dropped by the API's cap. */
|
|
910
|
+
omitted: {
|
|
911
|
+
nodes: Math.max(0, nodes.length - topNodes.length),
|
|
912
|
+
edges: Math.max(0, edges.length - topEdges.length),
|
|
913
|
+
cappedNodes: num(truncated.nodes),
|
|
914
|
+
cappedEdges: num(truncated.edges)
|
|
915
|
+
},
|
|
916
|
+
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)}).` : "")
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
function summarizeCost(r, top2) {
|
|
920
|
+
const range2 = rangeSummary(r.range);
|
|
921
|
+
const totals = obj(r.totals);
|
|
922
|
+
const t = {
|
|
923
|
+
messages: num(totals.messages),
|
|
924
|
+
pricedMessages: num(totals.priced_messages),
|
|
925
|
+
tokens: num(totals.tokens),
|
|
926
|
+
cost: money(totals.cost)
|
|
927
|
+
};
|
|
928
|
+
const all = arr(r.groups);
|
|
929
|
+
const groups = all.slice(0, top2).map((g) => ({
|
|
930
|
+
key: String(g.key),
|
|
931
|
+
label: clip(g.label, 120),
|
|
932
|
+
messages: num(g.messages),
|
|
933
|
+
tokens: num(g.tokens),
|
|
934
|
+
cost: money(g.cost),
|
|
935
|
+
share: g.share === null || g.share === void 0 ? null : num(g.share)
|
|
936
|
+
}));
|
|
937
|
+
const active = arr(r.series).filter(
|
|
938
|
+
(d) => num(d.messages) > 0 || num(d.cost) > 0
|
|
939
|
+
);
|
|
940
|
+
const daily = active.slice(-31).map((d) => ({
|
|
941
|
+
day: String(d.day),
|
|
942
|
+
messages: num(d.messages),
|
|
943
|
+
tokens: num(d.tokens),
|
|
944
|
+
cost: money(d.cost)
|
|
945
|
+
}));
|
|
946
|
+
const peak = active.reduce(
|
|
947
|
+
(best, d) => !best || num(d.cost) > num(best.cost) ? d : best,
|
|
948
|
+
null
|
|
949
|
+
);
|
|
950
|
+
const groupBy = String(r.group_by ?? "agent");
|
|
951
|
+
const head = groups.slice(0, 3).map((g) => `${g.label} ${fmtMoney(g.cost)} (${pct(g.share)})`).join(", ");
|
|
952
|
+
return {
|
|
953
|
+
range: range2,
|
|
954
|
+
groupBy,
|
|
955
|
+
totals: t,
|
|
956
|
+
groups,
|
|
957
|
+
omittedGroups: Math.max(0, all.length - groups.length),
|
|
958
|
+
/** The API had more groups than it returned. */
|
|
959
|
+
truncated: Boolean(r.truncated),
|
|
960
|
+
daily,
|
|
961
|
+
peakDay: peak ? { day: String(peak.day), cost: money(peak.cost) } : null,
|
|
962
|
+
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}.` : "")
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
function pair(v) {
|
|
966
|
+
const p = obj(v);
|
|
967
|
+
const l = latency(p.latency);
|
|
968
|
+
return {
|
|
969
|
+
messages: num(p.messages),
|
|
970
|
+
replied: num(p.replied),
|
|
971
|
+
replyRate: p.reply_rate === null || p.reply_rate === void 0 ? null : num(p.reply_rate),
|
|
972
|
+
avgMs: l.avgMs,
|
|
973
|
+
p50Ms: l.p50Ms,
|
|
974
|
+
p90Ms: l.p90Ms
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
function summarizeMetrics(r, top2) {
|
|
978
|
+
const range2 = rangeSummary(r.range);
|
|
979
|
+
const replies = obj(r.replies);
|
|
980
|
+
const q = obj(r.questions);
|
|
981
|
+
const qLatency = latency(q.time_to_human_answer);
|
|
982
|
+
const byUrgency = obj(q.by_urgency);
|
|
983
|
+
const d = obj(r.deliveries);
|
|
984
|
+
const byMode = obj(d.by_mode);
|
|
985
|
+
const backlog = obj(r.backlog);
|
|
986
|
+
const rate = (v) => v === null || v === void 0 ? null : num(v);
|
|
987
|
+
const out = {
|
|
988
|
+
range: range2,
|
|
989
|
+
replies: {
|
|
990
|
+
agentToAgent: pair(replies.agent_to_agent),
|
|
991
|
+
humanToAgent: pair(replies.human_to_agent),
|
|
992
|
+
agentToHuman: pair(replies.agent_to_human),
|
|
993
|
+
byAgent: arr(replies.by_agent).slice(0, top2).map((a) => {
|
|
994
|
+
const l = latency(a.latency);
|
|
995
|
+
return {
|
|
996
|
+
agentId: String(a.agent_id),
|
|
997
|
+
name: str(a.name),
|
|
998
|
+
messages: num(a.messages),
|
|
999
|
+
replied: num(a.replied),
|
|
1000
|
+
avgMs: l.avgMs,
|
|
1001
|
+
p90Ms: l.p90Ms
|
|
1002
|
+
};
|
|
1003
|
+
})
|
|
1004
|
+
},
|
|
1005
|
+
questions: {
|
|
1006
|
+
asked: num(q.asked),
|
|
1007
|
+
answered: num(q.answered),
|
|
1008
|
+
answeredByUser: num(q.answered_by_user),
|
|
1009
|
+
answeredByAgent: num(q.answered_by_agent),
|
|
1010
|
+
expired: num(q.expired),
|
|
1011
|
+
expiredDefault: num(q.expired_default),
|
|
1012
|
+
canceled: num(q.canceled),
|
|
1013
|
+
answerRate: rate(q.answer_rate),
|
|
1014
|
+
timeToHumanAnswer: {
|
|
1015
|
+
count: qLatency.count,
|
|
1016
|
+
avgMs: qLatency.avgMs,
|
|
1017
|
+
p50Ms: qLatency.p50Ms,
|
|
1018
|
+
p90Ms: qLatency.p90Ms
|
|
1019
|
+
},
|
|
1020
|
+
byUrgency: Object.fromEntries(
|
|
1021
|
+
["low", "normal", "high", "urgent"].map((u) => {
|
|
1022
|
+
const b = obj(byUrgency[u]);
|
|
1023
|
+
return [
|
|
1024
|
+
u,
|
|
1025
|
+
{
|
|
1026
|
+
asked: num(b.asked),
|
|
1027
|
+
answerRate: rate(b.answer_rate),
|
|
1028
|
+
p50Ms: latency(b.time_to_human_answer).p50Ms
|
|
1029
|
+
}
|
|
1030
|
+
];
|
|
1031
|
+
})
|
|
1032
|
+
)
|
|
1033
|
+
},
|
|
1034
|
+
deliveries: {
|
|
1035
|
+
deliveries: num(d.deliveries),
|
|
1036
|
+
failed: num(d.failed),
|
|
1037
|
+
retried: num(d.retried),
|
|
1038
|
+
failureRate: rate(d.failure_rate),
|
|
1039
|
+
retryRate: rate(d.retry_rate),
|
|
1040
|
+
byMode: Object.fromEntries(
|
|
1041
|
+
["webhook", "inbox", "a2a"].map((m) => {
|
|
1042
|
+
const b = obj(byMode[m]);
|
|
1043
|
+
return [
|
|
1044
|
+
m,
|
|
1045
|
+
{
|
|
1046
|
+
deliveries: num(b.deliveries),
|
|
1047
|
+
failed: num(b.failed),
|
|
1048
|
+
held: num(b.held),
|
|
1049
|
+
failureRate: rate(b.failure_rate),
|
|
1050
|
+
retryRate: rate(b.retry_rate)
|
|
1051
|
+
}
|
|
1052
|
+
];
|
|
1053
|
+
})
|
|
1054
|
+
)
|
|
1055
|
+
},
|
|
1056
|
+
backlog: {
|
|
1057
|
+
asOf: String(backlog.as_of ?? ""),
|
|
1058
|
+
unacked: num(backlog.unacked),
|
|
1059
|
+
held: num(backlog.held),
|
|
1060
|
+
agentsWithBacklog: num(backlog.agents_with_backlog),
|
|
1061
|
+
agents: arr(backlog.agents).slice(0, top2).map((a) => ({
|
|
1062
|
+
agentId: String(a.agent_id),
|
|
1063
|
+
name: str(a.name),
|
|
1064
|
+
delivery: str(a.delivery),
|
|
1065
|
+
unacked: num(a.unacked),
|
|
1066
|
+
held: num(a.held),
|
|
1067
|
+
oldestAgeSeconds: a.oldest_age_seconds === null || a.oldest_age_seconds === void 0 ? null : num(a.oldest_age_seconds)
|
|
1068
|
+
}))
|
|
1069
|
+
},
|
|
1070
|
+
summary: ""
|
|
1071
|
+
};
|
|
1072
|
+
const h2a = out.replies.humanToAgent;
|
|
1073
|
+
const a2a = out.replies.agentToAgent;
|
|
1074
|
+
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.`;
|
|
1075
|
+
return out;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// src/agents.ts
|
|
755
1079
|
function defineTool(tool) {
|
|
756
1080
|
return tool;
|
|
757
1081
|
}
|
|
@@ -874,6 +1198,40 @@ function outcomeMessage(status, q) {
|
|
|
874
1198
|
return `Status: ${status}.`;
|
|
875
1199
|
}
|
|
876
1200
|
}
|
|
1201
|
+
var AGENT_MESSAGE_TYPES = [
|
|
1202
|
+
"message",
|
|
1203
|
+
"task",
|
|
1204
|
+
"question",
|
|
1205
|
+
"escalation",
|
|
1206
|
+
"status"
|
|
1207
|
+
];
|
|
1208
|
+
var insightsDay = (what) => z2.string().regex(/^\d{4}-\d{2}-\d{2}$/, "expected a YYYY-MM-DD date").optional().describe(what);
|
|
1209
|
+
var insightsRange = {
|
|
1210
|
+
from: insightsDay(
|
|
1211
|
+
"First UTC day, YYYY-MM-DD (default: 30 days before to). Clamped to the plan history window."
|
|
1212
|
+
),
|
|
1213
|
+
to: insightsDay("Last UTC day, inclusive, YYYY-MM-DD (default: today)"),
|
|
1214
|
+
channelId: channelId.optional().describe("Only this agent channel")
|
|
1215
|
+
};
|
|
1216
|
+
var insightsAgent = uuid(
|
|
1217
|
+
"Only this agent (from list_agents): pass your own id to see your own numbers"
|
|
1218
|
+
).optional();
|
|
1219
|
+
var mine = z2.boolean().optional().describe(
|
|
1220
|
+
"true: only your own agent (the server's default agent, CHANNLIZE_AGENT_ID), when you don't know your id"
|
|
1221
|
+
);
|
|
1222
|
+
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})`);
|
|
1223
|
+
function filterAgent(api, a) {
|
|
1224
|
+
if (a.agentId) return a.agentId;
|
|
1225
|
+
return a.mine ? requireAgent(api, void 0) : void 0;
|
|
1226
|
+
}
|
|
1227
|
+
var range = z2.looseObject({
|
|
1228
|
+
from: z2.string(),
|
|
1229
|
+
to: z2.string(),
|
|
1230
|
+
days: z2.number(),
|
|
1231
|
+
clamped: z2.boolean(),
|
|
1232
|
+
empty: z2.boolean()
|
|
1233
|
+
});
|
|
1234
|
+
var nullableNumber = z2.number().nullable();
|
|
877
1235
|
var AGENT_TOOLS = [
|
|
878
1236
|
defineTool({
|
|
879
1237
|
name: "send_agent_message",
|
|
@@ -1424,6 +1782,200 @@ var AGENT_TOOLS = [
|
|
|
1424
1782
|
return { acked: Number(r.acked ?? 0) };
|
|
1425
1783
|
}
|
|
1426
1784
|
}),
|
|
1785
|
+
defineTool({
|
|
1786
|
+
name: "search_agent_messages",
|
|
1787
|
+
title: "Search agent messages",
|
|
1788
|
+
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.`,
|
|
1789
|
+
scope: "agents:read",
|
|
1790
|
+
endpoint: "GET /v1/agents/search",
|
|
1791
|
+
inputSchema: z2.object({
|
|
1792
|
+
q: z2.string().trim().min(2).max(200).describe("What to look for (2-200 characters)"),
|
|
1793
|
+
agentId: uuid(
|
|
1794
|
+
"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."
|
|
1795
|
+
).optional(),
|
|
1796
|
+
channelId: channelId.optional().describe("Only this agent channel"),
|
|
1797
|
+
type: z2.enum(AGENT_MESSAGE_TYPES).optional().describe("Only this message type"),
|
|
1798
|
+
include: z2.enum(["messages", "questions", "both"]).default("both").describe("messages, questions, or both (default)"),
|
|
1799
|
+
from: z2.string().max(40).optional().describe("ISO date-time: the earliest to search"),
|
|
1800
|
+
to: z2.string().max(40).optional().describe("ISO date-time: the latest to search"),
|
|
1801
|
+
cursor: z2.string().max(300).optional().describe("nextCursor of the previous call"),
|
|
1802
|
+
limit: z2.number().int().min(1).max(50).default(10).describe("How many results (1-50, default 10)")
|
|
1803
|
+
}),
|
|
1804
|
+
outputSchema: z2.object({
|
|
1805
|
+
results: z2.array(
|
|
1806
|
+
z2.looseObject({
|
|
1807
|
+
kind: z2.string(),
|
|
1808
|
+
id: z2.string(),
|
|
1809
|
+
type: z2.string(),
|
|
1810
|
+
text: z2.string(),
|
|
1811
|
+
highlight: z2.string(),
|
|
1812
|
+
channel: z2.string().nullable(),
|
|
1813
|
+
threadId: z2.string().nullable()
|
|
1814
|
+
})
|
|
1815
|
+
),
|
|
1816
|
+
nextCursor: z2.string().nullable(),
|
|
1817
|
+
window: z2.object({ from: z2.string(), days: z2.number() })
|
|
1818
|
+
}),
|
|
1819
|
+
annotations: READ,
|
|
1820
|
+
handler: async (api, a) => {
|
|
1821
|
+
const r = await api.request("GET", "/v1/agents/search", {
|
|
1822
|
+
query: {
|
|
1823
|
+
q: a.q,
|
|
1824
|
+
agent_id: a.agentId,
|
|
1825
|
+
channel_id: a.channelId,
|
|
1826
|
+
type: a.type,
|
|
1827
|
+
include: a.include === "both" ? void 0 : a.include,
|
|
1828
|
+
from: a.from,
|
|
1829
|
+
to: a.to,
|
|
1830
|
+
cursor: a.cursor,
|
|
1831
|
+
limit: a.limit
|
|
1832
|
+
}
|
|
1833
|
+
});
|
|
1834
|
+
return summarizeSearch(r);
|
|
1835
|
+
}
|
|
1836
|
+
}),
|
|
1837
|
+
defineTool({
|
|
1838
|
+
name: "get_agent_graph",
|
|
1839
|
+
title: "Get agent communication graph",
|
|
1840
|
+
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.",
|
|
1841
|
+
scope: "agents:read",
|
|
1842
|
+
endpoint: "GET /v1/agents/insights/graph",
|
|
1843
|
+
inputSchema: z2.object({
|
|
1844
|
+
...insightsRange,
|
|
1845
|
+
agentId: insightsAgent,
|
|
1846
|
+
mine,
|
|
1847
|
+
teamId: uuid("Only edges touching this team").optional(),
|
|
1848
|
+
top: top(10)
|
|
1849
|
+
}),
|
|
1850
|
+
outputSchema: z2.object({
|
|
1851
|
+
range,
|
|
1852
|
+
totals: z2.object({
|
|
1853
|
+
nodes: z2.number(),
|
|
1854
|
+
edges: z2.number(),
|
|
1855
|
+
messages: z2.number(),
|
|
1856
|
+
tokens: z2.number(),
|
|
1857
|
+
cost: z2.number()
|
|
1858
|
+
}),
|
|
1859
|
+
topNodes: z2.array(
|
|
1860
|
+
z2.looseObject({ id: z2.string(), label: z2.string(), sent: z2.number() })
|
|
1861
|
+
),
|
|
1862
|
+
topEdges: z2.array(
|
|
1863
|
+
z2.looseObject({
|
|
1864
|
+
source: z2.string(),
|
|
1865
|
+
target: z2.string(),
|
|
1866
|
+
messages: z2.number(),
|
|
1867
|
+
replyAvgMs: nullableNumber
|
|
1868
|
+
})
|
|
1869
|
+
),
|
|
1870
|
+
omitted: z2.looseObject({ nodes: z2.number(), edges: z2.number() }),
|
|
1871
|
+
summary: z2.string()
|
|
1872
|
+
}),
|
|
1873
|
+
annotations: READ,
|
|
1874
|
+
handler: async (api, a) => {
|
|
1875
|
+
const r = await api.request("GET", "/v1/agents/insights/graph", {
|
|
1876
|
+
query: {
|
|
1877
|
+
from: a.from,
|
|
1878
|
+
to: a.to,
|
|
1879
|
+
channel_id: a.channelId,
|
|
1880
|
+
agent_id: filterAgent(api, a),
|
|
1881
|
+
team_id: a.teamId
|
|
1882
|
+
}
|
|
1883
|
+
});
|
|
1884
|
+
return summarizeGraph(r, a.top);
|
|
1885
|
+
}
|
|
1886
|
+
}),
|
|
1887
|
+
defineTool({
|
|
1888
|
+
name: "get_agent_cost",
|
|
1889
|
+
title: "Get agent cost",
|
|
1890
|
+
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.",
|
|
1891
|
+
scope: "agents:read",
|
|
1892
|
+
endpoint: "GET /v1/agents/insights/cost",
|
|
1893
|
+
inputSchema: z2.object({
|
|
1894
|
+
...insightsRange,
|
|
1895
|
+
groupBy: z2.enum(["agent", "channel", "team", "task", "day"]).default("agent").describe("How to break the cost down (default agent)"),
|
|
1896
|
+
agentId: insightsAgent,
|
|
1897
|
+
mine,
|
|
1898
|
+
teamId: uuid("Only what this team's agents sent").optional(),
|
|
1899
|
+
top: top(10)
|
|
1900
|
+
}),
|
|
1901
|
+
outputSchema: z2.object({
|
|
1902
|
+
range,
|
|
1903
|
+
groupBy: z2.string(),
|
|
1904
|
+
totals: z2.object({
|
|
1905
|
+
messages: z2.number(),
|
|
1906
|
+
pricedMessages: z2.number(),
|
|
1907
|
+
tokens: z2.number(),
|
|
1908
|
+
cost: z2.number()
|
|
1909
|
+
}),
|
|
1910
|
+
groups: z2.array(
|
|
1911
|
+
z2.looseObject({
|
|
1912
|
+
key: z2.string(),
|
|
1913
|
+
label: z2.string(),
|
|
1914
|
+
cost: z2.number(),
|
|
1915
|
+
share: nullableNumber
|
|
1916
|
+
})
|
|
1917
|
+
),
|
|
1918
|
+
omittedGroups: z2.number(),
|
|
1919
|
+
truncated: z2.boolean(),
|
|
1920
|
+
daily: z2.array(z2.looseObject({ day: z2.string(), cost: z2.number() })),
|
|
1921
|
+
peakDay: z2.object({ day: z2.string(), cost: z2.number() }).nullable(),
|
|
1922
|
+
summary: z2.string()
|
|
1923
|
+
}),
|
|
1924
|
+
annotations: READ,
|
|
1925
|
+
handler: async (api, a) => {
|
|
1926
|
+
const r = await api.request("GET", "/v1/agents/insights/cost", {
|
|
1927
|
+
query: {
|
|
1928
|
+
from: a.from,
|
|
1929
|
+
to: a.to,
|
|
1930
|
+
channel_id: a.channelId,
|
|
1931
|
+
agent_id: filterAgent(api, a),
|
|
1932
|
+
team_id: a.teamId,
|
|
1933
|
+
group_by: a.groupBy,
|
|
1934
|
+
// Day groups come back whole; the others only need the top ones.
|
|
1935
|
+
limit: a.groupBy === "day" ? void 0 : a.top
|
|
1936
|
+
}
|
|
1937
|
+
});
|
|
1938
|
+
return summarizeCost(r, a.top);
|
|
1939
|
+
}
|
|
1940
|
+
}),
|
|
1941
|
+
defineTool({
|
|
1942
|
+
name: "get_agent_metrics",
|
|
1943
|
+
title: "Get agent metrics",
|
|
1944
|
+
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.",
|
|
1945
|
+
scope: "agents:read",
|
|
1946
|
+
endpoint: "GET /v1/agents/insights/metrics",
|
|
1947
|
+
inputSchema: z2.object({
|
|
1948
|
+
...insightsRange,
|
|
1949
|
+
agentId: insightsAgent,
|
|
1950
|
+
mine,
|
|
1951
|
+
top: top(5)
|
|
1952
|
+
}),
|
|
1953
|
+
outputSchema: z2.object({
|
|
1954
|
+
range,
|
|
1955
|
+
replies: z2.looseObject({
|
|
1956
|
+
agentToAgent: z2.looseObject({ avgMs: nullableNumber }),
|
|
1957
|
+
humanToAgent: z2.looseObject({ avgMs: nullableNumber }),
|
|
1958
|
+
agentToHuman: z2.looseObject({ avgMs: nullableNumber }),
|
|
1959
|
+
byAgent: z2.array(z2.looseObject({ agentId: z2.string() }))
|
|
1960
|
+
}),
|
|
1961
|
+
questions: z2.looseObject({ asked: z2.number() }),
|
|
1962
|
+
deliveries: z2.looseObject({ deliveries: z2.number() }),
|
|
1963
|
+
backlog: z2.looseObject({ unacked: z2.number(), held: z2.number() }),
|
|
1964
|
+
summary: z2.string()
|
|
1965
|
+
}),
|
|
1966
|
+
annotations: READ,
|
|
1967
|
+
handler: async (api, a) => {
|
|
1968
|
+
const r = await api.request("GET", "/v1/agents/insights/metrics", {
|
|
1969
|
+
query: {
|
|
1970
|
+
from: a.from,
|
|
1971
|
+
to: a.to,
|
|
1972
|
+
channel_id: a.channelId,
|
|
1973
|
+
agent_id: filterAgent(api, a)
|
|
1974
|
+
}
|
|
1975
|
+
});
|
|
1976
|
+
return summarizeMetrics(r, a.top);
|
|
1977
|
+
}
|
|
1978
|
+
}),
|
|
1427
1979
|
defineTool({
|
|
1428
1980
|
name: "list_agents",
|
|
1429
1981
|
title: "List agents",
|
|
@@ -2549,7 +3101,7 @@ async function startHttpServer(factory, options = {}) {
|
|
|
2549
3101
|
|
|
2550
3102
|
// src/index.ts
|
|
2551
3103
|
var import_core4 = require("@channlize/core");
|
|
2552
|
-
var VERSION = true ? "0.
|
|
3104
|
+
var VERSION = true ? "0.6.0" : "0.0.0";
|
|
2553
3105
|
var INSTRUCTIONS = `Channlize routes website conversations and app events to the team's Slack.
|
|
2554
3106
|
- Inbox: list_conversations / search_conversations, then get_conversation for the thread. reply_to_conversation sends to the customer as this key ("<key name> (agent)"), so confirm the text first; add_internal_note only reaches the team in Slack. assign_conversation, tag_conversation, close_conversation and reopen_conversation change it.
|
|
2555
3107
|
- get_analytics_summary gives KPIs and the inbox right now; list/create/update/delete_routing_rule manage which Slack channel new conversations go to.
|
|
@@ -2558,6 +3110,7 @@ var INSTRUCTIONS = `Channlize routes website conversations and app events to the
|
|
|
2558
3110
|
- Agents (Channlize for Agents): talk to other agents and people in agent channels. list_agents, list_agent_channels and get_org_chart show who and where; send_agent_message posts; read_agent_inbox returns what was sent to your agent, and ack_agent_messages clears what you handled. With a multi-agent key, pass agentId (or set CHANNLIZE_AGENT_ID).
|
|
2559
3111
|
- Tasks: create_task hands work to an agent (assignee, due date); update_task moves it on the board, update_task_status reports progress or the result, handoff_task passes it to a better-placed agent; list_tasks and get_task_board show the work.
|
|
2560
3112
|
- 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).
|
|
3113
|
+
- 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.
|
|
2561
3114
|
- Refusals: a POLICY_* error with a retry time means wait until then, without one the policy forbids it (don't retry). HUMAN_TAKEOVER means a person took over the thread: stay out of it until the time it gives. AGENT_THREAD_PAUSED means stop posting in that thread. get_effective_policy (agents:admin key) shows the rules.
|
|
2562
3115
|
- 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.`;
|
|
2563
3116
|
function buildServer(config, info = {}) {
|