agent-relay-server 0.101.1 → 0.102.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/docs/openapi.json +1 -1
- package/package.json +3 -2
- package/public/assets/{MessageBubble-BEjK1-bi.js → MessageBubble-Dppo7pil.js} +2 -2
- package/public/assets/{MessageBubble-BEjK1-bi.js.map → MessageBubble-Dppo7pil.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-AqFEg7Yg.js → PlanGraphPanel-DO5OY2qa.js} +2 -2
- package/public/assets/{PlanGraphPanel-AqFEg7Yg.js.map → PlanGraphPanel-DO5OY2qa.js.map} +1 -1
- package/public/assets/{activity-D5EZw4bj.js → activity-BhuOs5ha.js} +2 -2
- package/public/assets/{activity-D5EZw4bj.js.map → activity-BhuOs5ha.js.map} +1 -1
- package/public/assets/{agent-profiles-CSISRm_L.js → agent-profiles-CT-vbfzd.js} +2 -2
- package/public/assets/{agent-profiles-CSISRm_L.js.map → agent-profiles-CT-vbfzd.js.map} +1 -1
- package/public/assets/{agents-szyYnkgD.js → agents-CLcgi8AD.js} +2 -2
- package/public/assets/{agents-szyYnkgD.js.map → agents-CLcgi8AD.js.map} +1 -1
- package/public/assets/{analytics-KzLQfGMO.js → analytics-BxGRljQc.js} +2 -2
- package/public/assets/{analytics-KzLQfGMO.js.map → analytics-BxGRljQc.js.map} +1 -1
- package/public/assets/{automation-DVSq4A1b.js → automation-CE1i1zpw.js} +2 -2
- package/public/assets/{automation-DVSq4A1b.js.map → automation-CE1i1zpw.js.map} +1 -1
- package/public/assets/{branch-state-badge-DgwkJ2rN.js → branch-state-badge-olhanssg.js} +2 -2
- package/public/assets/{branch-state-badge-DgwkJ2rN.js.map → branch-state-badge-olhanssg.js.map} +1 -1
- package/public/assets/channels-yHkm1dux.js +2 -0
- package/public/assets/channels-yHkm1dux.js.map +1 -0
- package/public/assets/{chat-patMKjsi.js → chat-DHdFZvAg.js} +2 -2
- package/public/assets/chat-DHdFZvAg.js.map +1 -0
- package/public/assets/{connectors-DVKBHNRV.js → connectors-DpR0V2hy.js} +2 -2
- package/public/assets/{connectors-DVKBHNRV.js.map → connectors-DpR0V2hy.js.map} +1 -1
- package/public/assets/{formatted-body-impl-Csr2DuEk.js → formatted-body-impl-BTeCfK4I.js} +2 -2
- package/public/assets/{formatted-body-impl-Csr2DuEk.js.map → formatted-body-impl-BTeCfK4I.js.map} +1 -1
- package/public/assets/index-CKhh9XRZ.css +2 -0
- package/public/assets/{index-D3woTUwp.js → index-DeDosfVE.js} +6 -6
- package/public/assets/{index-D3woTUwp.js.map → index-DeDosfVE.js.map} +1 -1
- package/public/assets/{insights-D2_Qry06.js → insights-C4aYlgsY.js} +2 -2
- package/public/assets/{insights-D2_Qry06.js.map → insights-C4aYlgsY.js.map} +1 -1
- package/public/assets/integrations-CEmYa5mW.js +2 -0
- package/public/assets/integrations-CEmYa5mW.js.map +1 -0
- package/public/assets/{maintenance-Dm56u8F9.js → maintenance-DVx3SiC_.js} +2 -2
- package/public/assets/{maintenance-Dm56u8F9.js.map → maintenance-DVx3SiC_.js.map} +1 -1
- package/public/assets/{managed-agents-DpX-cHsm.js → managed-agents-DUyOTdz1.js} +2 -2
- package/public/assets/{managed-agents-DpX-cHsm.js.map → managed-agents-DUyOTdz1.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-Df6EWwbg.js → markdown-preview-impl-D3hYfzGD.js} +2 -2
- package/public/assets/{markdown-preview-impl-Df6EWwbg.js.map → markdown-preview-impl-D3hYfzGD.js.map} +1 -1
- package/public/assets/{memory-b2AgvV7H.js → memory-2IfPgcnN.js} +2 -2
- package/public/assets/{memory-b2AgvV7H.js.map → memory-2IfPgcnN.js.map} +1 -1
- package/public/assets/{messages-C5yDD_oq.js → messages-CkOwABhw.js} +2 -2
- package/public/assets/{messages-C5yDD_oq.js.map → messages-CkOwABhw.js.map} +1 -1
- package/public/assets/{orchestrators-D3LecmEw.js → orchestrators-CEKO3eQ1.js} +2 -2
- package/public/assets/{orchestrators-D3LecmEw.js.map → orchestrators-CEKO3eQ1.js.map} +1 -1
- package/public/assets/{overview-CnGkrBNe.js → overview-D9BxevAq.js} +2 -2
- package/public/assets/{overview-CnGkrBNe.js.map → overview-D9BxevAq.js.map} +1 -1
- package/public/assets/{pairs-DXUdZOqn.js → pairs-CqT4D1uL.js} +2 -2
- package/public/assets/{pairs-DXUdZOqn.js.map → pairs-CqT4D1uL.js.map} +1 -1
- package/public/assets/{projects-1h41atqJ.js → projects-C_zwR-Fn.js} +2 -2
- package/public/assets/{projects-1h41atqJ.js.map → projects-C_zwR-Fn.js.map} +1 -1
- package/public/assets/{registry-BeGP37ot.js → registry-BLjwVXCW.js} +2 -2
- package/public/assets/{registry-BeGP37ot.js.map → registry-BLjwVXCW.js.map} +1 -1
- package/public/assets/{security-BMh_y43L.js → security-8a5_4U3S.js} +2 -2
- package/public/assets/{security-BMh_y43L.js.map → security-8a5_4U3S.js.map} +1 -1
- package/public/assets/{select-Cvi5OHz0.js → select-s4BE-T62.js} +2 -2
- package/public/assets/{select-Cvi5OHz0.js.map → select-s4BE-T62.js.map} +1 -1
- package/public/assets/{settings-DKSEwIVG.js → settings-BR30UpbJ.js} +2 -2
- package/public/assets/{settings-DKSEwIVG.js.map → settings-BR30UpbJ.js.map} +1 -1
- package/public/assets/{store-Ch-QzxPq.js → store-DNbkrqbU.js} +2 -2
- package/public/assets/store-DNbkrqbU.js.map +1 -0
- package/public/assets/{tasks-BybIAX-y.js → tasks-Bcr17zR3.js} +2 -2
- package/public/assets/{tasks-BybIAX-y.js.map → tasks-Bcr17zR3.js.map} +1 -1
- package/public/assets/{teams-CMteOToR.js → teams-B_HSkU9T.js} +2 -2
- package/public/assets/{teams-CMteOToR.js.map → teams-B_HSkU9T.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-DanOi7lh.js → terminal-viewer-impl-Ctp4csJ3.js} +2 -2
- package/public/assets/{terminal-viewer-impl-DanOi7lh.js.map → terminal-viewer-impl-Ctp4csJ3.js.map} +1 -1
- package/public/assets/{user-avatar-DG629icI.js → user-avatar-RliaJcaG.js} +2 -2
- package/public/assets/{user-avatar-DG629icI.js.map → user-avatar-RliaJcaG.js.map} +1 -1
- package/public/assets/{work-queue-DqW80DIy.js → work-queue-C0hpmlv8.js} +2 -2
- package/public/assets/{work-queue-DqW80DIy.js.map → work-queue-C0hpmlv8.js.map} +1 -1
- package/public/assets/{workspaces-CccVI6lA.js → workspaces-QXbQx_AG.js} +2 -2
- package/public/assets/{workspaces-CccVI6lA.js.map → workspaces-QXbQx_AG.js.map} +1 -1
- package/public/index.html +3 -3
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/runner/src/adapter.ts +5 -0
- package/src/db/channels.ts +18 -2
- package/src/mcp/index.ts +5 -3
- package/src/mcp-egress-tools.ts +101 -0
- package/src/routes/integrations.ts +47 -13
- package/public/assets/channels-Dh_r7SnM.js +0 -2
- package/public/assets/channels-Dh_r7SnM.js.map +0 -1
- package/public/assets/chat-patMKjsi.js.map +0 -1
- package/public/assets/index-B95vV-dd.css +0 -2
- package/public/assets/integrations-AW8b4h7T.js +0 -2
- package/public/assets/integrations-AW8b4h7T.js.map +0 -1
- package/public/assets/store-Ch-QzxPq.js.map +0 -1
- package/src/mcp-ntfy-tools.ts +0 -35
- package/src/ntfy.ts +0 -222
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Generic egress MCP tool + relay_notify_ntfy shim (#777).
|
|
2
|
+
//
|
|
3
|
+
// relay_send_to_channel — resolves a registered channel agent by ID, builds a
|
|
4
|
+
// channel.v1 OUTBOUND envelope, and sends a relay message to the channel agent
|
|
5
|
+
// (which the external adapter picks up over SSE and delivers).
|
|
6
|
+
//
|
|
7
|
+
// relay_notify_ntfy — thin shim that emits to the ntfy:default channel agent with
|
|
8
|
+
// the ntfy event payload { title, body, priority?, tags?, click? }. Same tool name
|
|
9
|
+
// and args as before; async ("accepted for delivery"). Zero ntfy HTTP transport in core.
|
|
10
|
+
//
|
|
11
|
+
// Both tools use notifications:write scope (aliased; DECISION LOCKED — #773).
|
|
12
|
+
|
|
13
|
+
import { MAX_BODY_BYTES } from "./config";
|
|
14
|
+
import { getChannel, sendMessage, ValidationError } from "./db";
|
|
15
|
+
import { validateChannelEnvelope } from "./routes/integrations";
|
|
16
|
+
import { optionalEnum } from "./validation";
|
|
17
|
+
import type { ToolDefinition } from "./mcp/types";
|
|
18
|
+
import { optionalString, optionalStringArray, recordField, stringField } from "./mcp/validation";
|
|
19
|
+
|
|
20
|
+
export const NTFY_PRIORITIES = ["min", "low", "default", "high", "urgent"] as const;
|
|
21
|
+
export type NtfyPriority = (typeof NTFY_PRIORITIES)[number];
|
|
22
|
+
|
|
23
|
+
const NTFY_CHANNEL_ID = "ntfy:default";
|
|
24
|
+
|
|
25
|
+
export const EGRESS_TOOLS: ToolDefinition[] = [
|
|
26
|
+
{
|
|
27
|
+
name: "relay_send_to_channel",
|
|
28
|
+
description: "Send an outbound message to a registered channel agent (e.g. ntfy, webhook). channelId is the channel agent ID; event is an opaque payload the adapter interprets.",
|
|
29
|
+
requiredScopes: ["notifications:write"],
|
|
30
|
+
inputSchema: {
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: {
|
|
33
|
+
channelId: { type: "string", description: "Registered channel agent ID (e.g. 'ntfy:default')." },
|
|
34
|
+
event: { type: "object", description: "Adapter-specific event payload.", additionalProperties: true },
|
|
35
|
+
},
|
|
36
|
+
required: ["channelId", "event"],
|
|
37
|
+
additionalProperties: false,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "relay_notify_ntfy",
|
|
42
|
+
description: "Send an egress-only ntfy notification to the configured operator topic. Supports title, body, priority min→urgent, optional tags, and click-through link.",
|
|
43
|
+
requiredScopes: ["notifications:write"],
|
|
44
|
+
inputSchema: {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
title: { type: "string" },
|
|
48
|
+
body: { type: "string" },
|
|
49
|
+
priority: { type: "string", enum: [...NTFY_PRIORITIES] },
|
|
50
|
+
tags: { type: "array", items: { type: "string" } },
|
|
51
|
+
click: { type: "string" },
|
|
52
|
+
},
|
|
53
|
+
required: ["title", "body"],
|
|
54
|
+
additionalProperties: false,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
function emitChannelEgress(channelId: string, event: Record<string, unknown>): { accepted: boolean; channelId: string; to: string } {
|
|
60
|
+
const channel = getChannel(channelId);
|
|
61
|
+
if (!channel) throw new ValidationError(`channel ${channelId} not registered`);
|
|
62
|
+
if (channel.direction === "inbound") {
|
|
63
|
+
throw new ValidationError(`channel ${channelId} is inbound-only; egress requires outbound or bidirectional`);
|
|
64
|
+
}
|
|
65
|
+
const payload: Record<string, unknown> = {
|
|
66
|
+
schema: "agent-relay.channel.v1",
|
|
67
|
+
direction: "outbound",
|
|
68
|
+
channel: { provider: channel.type, accountId: channel.accountId },
|
|
69
|
+
event,
|
|
70
|
+
};
|
|
71
|
+
validateChannelEnvelope(payload, channel);
|
|
72
|
+
sendMessage({
|
|
73
|
+
from: "system",
|
|
74
|
+
to: channel.agentId,
|
|
75
|
+
kind: "channel.event",
|
|
76
|
+
body: "channel egress",
|
|
77
|
+
payload,
|
|
78
|
+
replyExpected: false,
|
|
79
|
+
});
|
|
80
|
+
return { accepted: true, channelId, to: channel.agentId };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function relayEgressToChannel(args: Record<string, unknown>): { accepted: boolean; channelId: string; to: string } {
|
|
84
|
+
const channelId = stringField(args.channelId, "channelId", { required: true, max: 200 });
|
|
85
|
+
const event = recordField(args.event, "event");
|
|
86
|
+
return emitChannelEgress(channelId, event);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function relayNotifyNtfy(args: Record<string, unknown>): { accepted: boolean; channelId: string } {
|
|
90
|
+
const title = stringField(args.title, "title", { required: true, max: 200 });
|
|
91
|
+
const body = stringField(args.body, "body", { required: true, maxBytes: MAX_BODY_BYTES });
|
|
92
|
+
const priority = optionalEnum(args.priority, "priority", NTFY_PRIORITIES) as NtfyPriority | undefined;
|
|
93
|
+
const tags = optionalStringArray(args.tags, "tags");
|
|
94
|
+
const click = optionalString(args.click, "click", 1000);
|
|
95
|
+
const event: Record<string, unknown> = { title, body };
|
|
96
|
+
if (priority !== undefined) event.priority = priority;
|
|
97
|
+
if (tags?.length) event.tags = tags;
|
|
98
|
+
if (click !== undefined) event.click = click;
|
|
99
|
+
const result = emitChannelEgress(NTFY_CHANNEL_ID, event);
|
|
100
|
+
return { accepted: result.accepted, channelId: result.channelId };
|
|
101
|
+
}
|
|
@@ -93,33 +93,67 @@ function channelPayloadEventType(payload: Record<string, unknown>): string {
|
|
|
93
93
|
return cleanString(event.type, "payload.event.type", { required: true, max: 80 })!;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
// Single validator for the `agent-relay.channel.v1` envelope in BOTH directions
|
|
97
|
+
// (issue #776). Direction decides how strict the shape is:
|
|
98
|
+
//
|
|
99
|
+
// INBOUND (adapter → relay; e.g. a received Telegram message) — fully addressed.
|
|
100
|
+
// Requires payload.channel.agentId, a `conversation` { id, type }, an `actor`
|
|
101
|
+
// { id, kind }, and a typed `event`, because the message is about to be routed to
|
|
102
|
+
// a backend agent and threaded into a conversation.
|
|
103
|
+
//
|
|
104
|
+
// OUTBOUND (relay → adapter egress; e.g. an ntfy notification — issues #774/#777) —
|
|
105
|
+
// a fire-and-forget push with no inbound conversation/actor. The canonical egress
|
|
106
|
+
// envelope is:
|
|
107
|
+
// { schema: "agent-relay.channel.v1", direction: "outbound",
|
|
108
|
+
// channel: { provider, accountId }, event: { … } }
|
|
109
|
+
// For ntfy the channel id is `ntfy:default` and the event carries
|
|
110
|
+
// { title, body, priority?, tags?, click? } (mirrors the old relay_notify_ntfy args).
|
|
111
|
+
// So for outbound: channel.agentId / conversation / actor are OPTIONAL (validated
|
|
112
|
+
// only when present), and the event need NOT carry a `type`. The ntfy adapter (#774)
|
|
113
|
+
// and the egress shim (#777) produce/consume this exact shape — keep it STABLE.
|
|
114
|
+
export function validateChannelEnvelope(payload: Record<string, unknown>, channel: ChannelSummary): void {
|
|
97
115
|
const schema = cleanString(payload.schema, "payload.schema", { required: true, max: 80 });
|
|
98
116
|
if (schema !== "agent-relay.channel.v1") throw new ValidationError("payload.schema must be agent-relay.channel.v1");
|
|
99
117
|
|
|
118
|
+
const direction = cleanString(payload.direction, "payload.direction", { required: true, max: 20 });
|
|
119
|
+
if (direction !== "inbound" && direction !== "outbound") throw new ValidationError("payload.direction must be inbound or outbound");
|
|
120
|
+
const outbound = direction === "outbound";
|
|
121
|
+
|
|
100
122
|
const payloadChannel = payload.channel;
|
|
101
123
|
if (!isRecord(payloadChannel)) throw new ValidationError("payload.channel must be an object");
|
|
102
124
|
const provider = cleanString(payloadChannel.provider, "payload.channel.provider", { required: true, max: 80 });
|
|
103
125
|
const accountId = cleanString(payloadChannel.accountId, "payload.channel.accountId", { required: true, max: 200 });
|
|
104
|
-
|
|
105
|
-
|
|
126
|
+
// agentId is required inbound (the routing target); the outbound egress envelope
|
|
127
|
+
// addresses the channel by (provider, accountId) only — but if present it must match.
|
|
128
|
+
const agentId = cleanString(payloadChannel.agentId, "payload.channel.agentId", { required: !outbound, max: 200 });
|
|
129
|
+
if (provider !== channel.type || accountId !== channel.accountId || (agentId !== undefined && agentId !== channel.agentId)) {
|
|
106
130
|
throw new ValidationError("payload.channel does not match channel identity");
|
|
107
131
|
}
|
|
108
132
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
133
|
+
// Inbound carries a conversation + actor for threading/attribution; outbound egress
|
|
134
|
+
// omits them. Validate whenever present so a malformed object is still rejected.
|
|
112
135
|
const conversation = payload.conversation;
|
|
113
|
-
if (!
|
|
114
|
-
|
|
115
|
-
|
|
136
|
+
if (!outbound || conversation !== undefined) {
|
|
137
|
+
if (!isRecord(conversation)) throw new ValidationError("payload.conversation must be an object");
|
|
138
|
+
cleanString(conversation.id, "payload.conversation.id", { required: true, max: 300 });
|
|
139
|
+
cleanString(conversation.type, "payload.conversation.type", { required: true, max: 80 });
|
|
140
|
+
}
|
|
116
141
|
|
|
117
142
|
const actor = payload.actor;
|
|
118
|
-
if (!
|
|
119
|
-
|
|
120
|
-
|
|
143
|
+
if (!outbound || actor !== undefined) {
|
|
144
|
+
if (!isRecord(actor)) throw new ValidationError("payload.actor must be an object");
|
|
145
|
+
cleanString(actor.id, "payload.actor.id", { required: true, max: 240 });
|
|
146
|
+
cleanString(actor.kind, "payload.actor.kind", { required: true, max: 80 });
|
|
147
|
+
}
|
|
121
148
|
|
|
122
|
-
|
|
149
|
+
if (outbound) {
|
|
150
|
+
// The egress event (e.g. ntfy { title, body, priority?, tags?, click? }) is an
|
|
151
|
+
// opaque object the adapter renders; a `type`, if present, is validated only for size.
|
|
152
|
+
if (!isRecord(payload.event)) throw new ValidationError("payload.event must be an object");
|
|
153
|
+
if (payload.event.type !== undefined) cleanString(payload.event.type, "payload.event.type", { max: 80 });
|
|
154
|
+
} else {
|
|
155
|
+
channelPayloadEventType(payload);
|
|
156
|
+
}
|
|
123
157
|
validateChannelAttachmentRefs(payload);
|
|
124
158
|
}
|
|
125
159
|
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{Hn as e,Jt as t,Qn as n,Un as r,Wn as i,et as a}from"./lucide-react-BYZuAhQs.js";import{d as o,t as s}from"./store-Ch-QzxPq.js";import{J as c,K as l,Y as u,c as d,s as f}from"./display-DRiGi1zj.js";import{t as p}from"./badge-BLar6FjP.js";import{t as m}from"./button-BrIB69qd.js";import{a as h,o as g,r as _,t as v}from"./card-lhnFnBoz.js";var y=n();function b({direction:t}){return t===`inbound`?(0,y.jsx)(r,{className:`w-3 h-3`}):t===`outbound`?(0,y.jsx)(e,{className:`w-3 h-3`}):(0,y.jsx)(i,{className:`w-3 h-3`})}function x({channel:e}){let n=o(),r=s(e=>e.openChannelDetail),i=d(e),f=e.status!==`offline`;return(0,y.jsxs)(v,{className:`${f?``:`opacity-60`} hover:border-zinc-600 transition-colors`,children:[(0,y.jsx)(h,{className:`pb-2 pt-4 px-4`,children:(0,y.jsxs)(`div`,{className:`flex items-start gap-2`,children:[(0,y.jsx)(`div`,{className:`p-1.5 rounded-md ${f?`bg-blue-500/10`:`bg-zinc-800`}`,children:(0,y.jsx)(a,{className:`w-4 h-4 ${f?`text-blue-400`:`text-zinc-500`}`})}),(0,y.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,y.jsx)(g,{className:`text-sm font-medium truncate`,children:e.name||e.id}),(0,y.jsxs)(`div`,{className:`text-xs text-muted-foreground mt-0.5`,children:[e.type,` · `,e.transport]})]}),(0,y.jsx)(p,{className:`text-[10px] shrink-0 ${c(i.tone)}`,children:i.label})]})}),(0,y.jsxs)(_,{className:`px-4 pb-4 space-y-2.5`,children:[(0,y.jsxs)(`div`,{className:`flex items-center gap-1.5 text-xs text-muted-foreground`,children:[(0,y.jsx)(b,{direction:e.direction}),(0,y.jsx)(`span`,{className:`capitalize`,children:e.direction}),e.target&&(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)(`span`,{children:`·`}),(0,y.jsx)(`span`,{className:`truncate text-foreground`,children:e.target})]})]}),e.targetHealth&&e.targetHealth.status!==`ok`&&(0,y.jsx)(`div`,{className:`text-xs px-2 py-1 rounded ${e.targetHealth.status===`error`?`bg-red-500/10 text-red-400`:`bg-yellow-500/10 text-yellow-400`}`,children:e.targetHealth.detail}),u(e.capabilities).length>0&&(0,y.jsx)(`div`,{className:`flex flex-wrap gap-1`,children:u(e.capabilities).map(e=>(0,y.jsx)(p,{variant:`secondary`,className:`text-[9px] px-1 py-0 h-4`,children:e},e))}),e.tags.length>0&&(0,y.jsx)(`div`,{className:`flex flex-wrap gap-1`,children:e.tags.map(e=>(0,y.jsx)(p,{variant:`outline`,className:`text-[9px] px-1 py-0 h-4`,children:e},e))}),(0,y.jsxs)(`div`,{className:`flex items-center justify-between pt-0.5`,children:[(0,y.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:l(n,e.lastSeen)}),(0,y.jsxs)(m,{size:`sm`,variant:`outline`,className:`h-7 text-xs gap-1`,onClick:()=>r(e),children:[(0,y.jsx)(t,{className:`w-3 h-3`}),`Details`]})]})]})]})}function S(){let e=s(e=>e.channels),t=e.filter(f).length,n=e.filter(e=>e.targetHealth?.status===`error`).length,r=e.filter(e=>e.targetHealth?.status===`warning`).length;return(0,y.jsxs)(`div`,{className:`space-y-4`,children:[(0,y.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,y.jsxs)(`div`,{children:[(0,y.jsx)(`h2`,{className:`text-base font-semibold`,children:`Channels`}),(0,y.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:`Inbound / outbound message integrations`})]}),(0,y.jsxs)(`div`,{className:`flex items-center gap-2 ml-auto`,children:[t>0&&(0,y.jsxs)(p,{className:`text-xs bg-emerald-500/15 text-emerald-400 border-emerald-500/30`,children:[t,` ready`]}),r>0&&(0,y.jsxs)(p,{className:`text-xs bg-yellow-500/15 text-yellow-400 border-yellow-500/30`,children:[r,` warning`]}),n>0&&(0,y.jsxs)(p,{className:`text-xs bg-red-500/15 text-red-400 border-red-500/30`,children:[n,` error`]})]})]}),e.length===0?(0,y.jsx)(`div`,{className:`text-center py-16 text-muted-foreground text-sm`,children:`No channels registered.`}):(0,y.jsx)(`div`,{className:`grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4`,children:e.map(e=>(0,y.jsx)(x,{channel:e},e.id))})]})}export{S as ChannelsView};
|
|
2
|
-
//# sourceMappingURL=channels-Dh_r7SnM.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"channels-Dh_r7SnM.js","names":[],"sources":["../../dashboard/src/components/views/channels.tsx"],"sourcesContent":["import { useRelayStore, useNow } from '@/store'\nimport { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { channelIsReady, channelPresence, timeAgo, toneToColor, userFacingCapabilities } from '@/lib/display'\nimport { MessagesSquare, ArrowRight, ArrowLeft, ArrowLeftRight, ExternalLink } from 'lucide-react'\nimport type { ChannelSummary, ChannelDirection } from '@/types'\n\nfunction DirectionIcon({ direction }: { direction: ChannelDirection }) {\n if (direction === 'inbound') return <ArrowLeft className=\"w-3 h-3\" />\n if (direction === 'outbound') return <ArrowRight className=\"w-3 h-3\" />\n return <ArrowLeftRight className=\"w-3 h-3\" />\n}\n\nfunction ChannelCard({ channel }: { channel: ChannelSummary }) {\n const now = useNow()\n const openChannelDetail = useRelayStore((s) => s.openChannelDetail)\n\n const presence = channelPresence(channel)\n const online = channel.status !== 'offline'\n\n return (\n <Card className={`${!online ? 'opacity-60' : ''} hover:border-zinc-600 transition-colors`}>\n <CardHeader className=\"pb-2 pt-4 px-4\">\n <div className=\"flex items-start gap-2\">\n <div className={`p-1.5 rounded-md ${online ? 'bg-blue-500/10' : 'bg-zinc-800'}`}>\n <MessagesSquare className={`w-4 h-4 ${online ? 'text-blue-400' : 'text-zinc-500'}`} />\n </div>\n <div className=\"flex-1 min-w-0\">\n <CardTitle className=\"text-sm font-medium truncate\">{channel.name || channel.id}</CardTitle>\n <div className=\"text-xs text-muted-foreground mt-0.5\">{channel.type} · {channel.transport}</div>\n </div>\n <Badge className={`text-[10px] shrink-0 ${toneToColor(presence.tone)}`}>\n {presence.label}\n </Badge>\n </div>\n </CardHeader>\n\n <CardContent className=\"px-4 pb-4 space-y-2.5\">\n {/* Direction + target */}\n <div className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n <DirectionIcon direction={channel.direction} />\n <span className=\"capitalize\">{channel.direction}</span>\n {channel.target && (\n <>\n <span>·</span>\n <span className=\"truncate text-foreground\">{channel.target}</span>\n </>\n )}\n </div>\n\n {/* Target health */}\n {channel.targetHealth && channel.targetHealth.status !== 'ok' && (\n <div className={`text-xs px-2 py-1 rounded ${channel.targetHealth.status === 'error' ? 'bg-red-500/10 text-red-400' : 'bg-yellow-500/10 text-yellow-400'}`}>\n {channel.targetHealth.detail}\n </div>\n )}\n\n {/* Capabilities */}\n {userFacingCapabilities(channel.capabilities).length > 0 && (\n <div className=\"flex flex-wrap gap-1\">\n {userFacingCapabilities(channel.capabilities).map((c) => (\n <Badge key={c} variant=\"secondary\" className=\"text-[9px] px-1 py-0 h-4\">{c}</Badge>\n ))}\n </div>\n )}\n\n {/* Tags */}\n {channel.tags.length > 0 && (\n <div className=\"flex flex-wrap gap-1\">\n {channel.tags.map((t) => (\n <Badge key={t} variant=\"outline\" className=\"text-[9px] px-1 py-0 h-4\">{t}</Badge>\n ))}\n </div>\n )}\n\n {/* Last seen + action */}\n <div className=\"flex items-center justify-between pt-0.5\">\n <span className=\"text-xs text-muted-foreground\">{timeAgo(now, channel.lastSeen)}</span>\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"h-7 text-xs gap-1\"\n onClick={() => openChannelDetail(channel)}\n >\n <ExternalLink className=\"w-3 h-3\" />\n Details\n </Button>\n </div>\n </CardContent>\n </Card>\n )\n}\n\nexport function ChannelsView() {\n const channels = useRelayStore((s) => s.channels)\n\n const readyCount = channels.filter(channelIsReady).length\n const errorCount = channels.filter((c) => c.targetHealth?.status === 'error').length\n const warningCount = channels.filter((c) => c.targetHealth?.status === 'warning').length\n\n return (\n <div className=\"space-y-4\">\n {/* Header */}\n <div className=\"flex items-center gap-3\">\n <div>\n <h2 className=\"text-base font-semibold\">Channels</h2>\n <p className=\"text-xs text-muted-foreground\">Inbound / outbound message integrations</p>\n </div>\n <div className=\"flex items-center gap-2 ml-auto\">\n {readyCount > 0 && (\n <Badge className=\"text-xs bg-emerald-500/15 text-emerald-400 border-emerald-500/30\">\n {readyCount} ready\n </Badge>\n )}\n {warningCount > 0 && (\n <Badge className=\"text-xs bg-yellow-500/15 text-yellow-400 border-yellow-500/30\">\n {warningCount} warning\n </Badge>\n )}\n {errorCount > 0 && (\n <Badge className=\"text-xs bg-red-500/15 text-red-400 border-red-500/30\">\n {errorCount} error\n </Badge>\n )}\n </div>\n </div>\n\n {/* Cards */}\n {channels.length === 0 ? (\n <div className=\"text-center py-16 text-muted-foreground text-sm\">\n No channels registered.\n </div>\n ) : (\n <div className=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4\">\n {channels.map((channel) => (\n <ChannelCard key={channel.id} channel={channel} />\n ))}\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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"}
|