agent-relay-server 0.104.3 → 0.104.5
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/examples/integrations/github-issue.ts +14 -21
- package/examples/integrations/prometheus-alertmanager.ts +15 -22
- package/package.json +3 -2
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/src/db/integrations.ts +19 -7
- package/src/routes/integrations.ts +2 -2
package/docs/openapi.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"openapi": "3.1.0",
|
|
3
3
|
"info": {
|
|
4
4
|
"title": "Agent Relay API",
|
|
5
|
-
"version": "0.104.
|
|
5
|
+
"version": "0.104.5",
|
|
6
6
|
"description": "Real-time message bus for inter-agent communication. Agent-first: this spec is designed for machine consumption — agents can self-discover the full API surface via GET /api/spec.",
|
|
7
7
|
"license": {
|
|
8
8
|
"name": "MIT",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
+
import { RelayHttpClient } from "agent-relay-sdk";
|
|
4
|
+
|
|
3
5
|
type GitHubIssuePayload = {
|
|
4
6
|
action?: string;
|
|
5
7
|
issue?: {
|
|
@@ -20,6 +22,7 @@ if (!token) throw new Error("AGENT_RELAY_INTEGRATION_TOKEN is required");
|
|
|
20
22
|
|
|
21
23
|
const target = process.env.AGENT_RELAY_TARGET || "cap:triage";
|
|
22
24
|
const channel = process.env.AGENT_RELAY_CHANNEL || "support";
|
|
25
|
+
const relay = new RelayHttpClient({ baseUrl: relayUrl, token });
|
|
23
26
|
const payload = JSON.parse(await Bun.stdin.text()) as GitHubIssuePayload;
|
|
24
27
|
const issue = payload.issue;
|
|
25
28
|
const repo = payload.repository?.full_name || "unknown/repo";
|
|
@@ -30,25 +33,15 @@ if (!issue || !["opened", "reopened", "labeled", "edited"].includes(action)) {
|
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
const labels = issue.labels?.map((label) => label.name) ?? [];
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
body:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
body: issue.body || "(no issue body)",
|
|
45
|
-
target,
|
|
46
|
-
channel,
|
|
47
|
-
externalUrl: issue.html_url,
|
|
48
|
-
metadata: { repo, issueNumber: issue.number, action, labels },
|
|
49
|
-
}),
|
|
36
|
+
await relay.raiseAlert({
|
|
37
|
+
source: `github:${repo}`,
|
|
38
|
+
type: "issue",
|
|
39
|
+
severity: labels.includes("P0") || labels.includes("critical") ? "critical" : labels.includes("P1") ? "warning" : "info",
|
|
40
|
+
dedupeKey: `issue:${issue.number}`,
|
|
41
|
+
title: `${repo}#${issue.number}: ${issue.title}`,
|
|
42
|
+
body: issue.body || "(no issue body)",
|
|
43
|
+
payload: { repo, issueNumber: issue.number, action, labels },
|
|
44
|
+
target,
|
|
45
|
+
channel,
|
|
46
|
+
externalUrl: issue.html_url,
|
|
50
47
|
});
|
|
51
|
-
|
|
52
|
-
if (!response.ok) {
|
|
53
|
-
throw new Error(`failed to send GitHub issue ${repo}#${issue.number}: ${response.status} ${await response.text()}`);
|
|
54
|
-
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
+
import { RelayHttpClient } from "agent-relay-sdk";
|
|
4
|
+
|
|
3
5
|
type Alert = {
|
|
4
6
|
status?: string;
|
|
5
7
|
labels?: Record<string, string>;
|
|
@@ -19,6 +21,7 @@ if (!token) throw new Error("AGENT_RELAY_INTEGRATION_TOKEN is required");
|
|
|
19
21
|
|
|
20
22
|
const target = process.env.AGENT_RELAY_TARGET || "cap:ops";
|
|
21
23
|
const channel = process.env.AGENT_RELAY_CHANNEL || "alerts";
|
|
24
|
+
const relay = new RelayHttpClient({ baseUrl: relayUrl, token });
|
|
22
25
|
const payload = JSON.parse(await Bun.stdin.text()) as AlertmanagerPayload;
|
|
23
26
|
const alerts = payload.alerts ?? [];
|
|
24
27
|
|
|
@@ -29,29 +32,19 @@ for (const alert of alerts) {
|
|
|
29
32
|
const service = labels.service || labels.job || labels.instance || "unknown";
|
|
30
33
|
const status = alert.status === "resolved" || payload.status === "resolved" ? "resolved" : undefined;
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
},
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
body: annotations.description || annotations.summary || JSON.stringify(labels),
|
|
45
|
-
target,
|
|
46
|
-
channel,
|
|
47
|
-
externalUrl: alert.generatorURL,
|
|
48
|
-
metadata: { labels, annotations, alertStatus: alert.status },
|
|
49
|
-
}),
|
|
35
|
+
await relay.raiseAlert({
|
|
36
|
+
source: `alertmanager:${service}`,
|
|
37
|
+
type: name,
|
|
38
|
+
severity: alertSeverity(labels.severity),
|
|
39
|
+
status,
|
|
40
|
+
dedupeKey: alert.fingerprint ? `alertmanager:${alert.fingerprint}` : undefined,
|
|
41
|
+
title: `${name} (${service})`,
|
|
42
|
+
body: annotations.description || annotations.summary || JSON.stringify(labels),
|
|
43
|
+
payload: { labels, annotations, alertStatus: alert.status },
|
|
44
|
+
target,
|
|
45
|
+
channel,
|
|
46
|
+
externalUrl: alert.generatorURL,
|
|
50
47
|
});
|
|
51
|
-
|
|
52
|
-
if (!response.ok) {
|
|
53
|
-
throw new Error(`failed to send alert ${name}: ${response.status} ${await response.text()}`);
|
|
54
|
-
}
|
|
55
48
|
}
|
|
56
49
|
|
|
57
50
|
function alertSeverity(value?: string): "info" | "warning" | "critical" {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-relay-server",
|
|
3
|
-
"version": "0.104.
|
|
3
|
+
"version": "0.104.5",
|
|
4
4
|
"description": "Lightweight HTTP message relay for inter-agent communication across machines",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"agent-relay-providers": "0.104.0",
|
|
40
|
-
"agent-relay-sdk": "0.2.
|
|
40
|
+
"agent-relay-sdk": "0.2.90",
|
|
41
41
|
"ajv": "^8.20.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
@@ -101,6 +101,7 @@
|
|
|
101
101
|
"devDependencies": {
|
|
102
102
|
"@types/bun": "latest",
|
|
103
103
|
"@types/madge": "^5.0.3",
|
|
104
|
+
"callmux": "0.23.0",
|
|
104
105
|
"madge": "^8.0.0",
|
|
105
106
|
"typescript": "^5"
|
|
106
107
|
},
|
package/src/db/integrations.ts
CHANGED
|
@@ -90,10 +90,11 @@ export function ingestIntegrationEvent(input: IntegrationEventInput, integration
|
|
|
90
90
|
const targetStatus = input.status === "resolved" ? "done" : input.status;
|
|
91
91
|
const attachmentRefs = input.attachments ?? [];
|
|
92
92
|
validateAttachmentRefs(attachmentRefs);
|
|
93
|
-
updateIntegrationObserved(
|
|
93
|
+
updateIntegrationObserved(integrationName, now);
|
|
94
94
|
|
|
95
95
|
return getDb().transaction(() => {
|
|
96
96
|
const existing = input.dedupeKey ? findOpenTaskByDedupe(source, input.dedupeKey) : null;
|
|
97
|
+
const metadata = integrationTaskMetadata(input.metadata, integrationName, existing?.metadata);
|
|
97
98
|
let taskId: number;
|
|
98
99
|
let created = false;
|
|
99
100
|
|
|
@@ -112,7 +113,7 @@ export function ingestIntegrationEvent(input: IntegrationEventInput, integration
|
|
|
112
113
|
input.target,
|
|
113
114
|
input.channel ?? null,
|
|
114
115
|
input.externalUrl ?? null,
|
|
115
|
-
JSON.stringify(
|
|
116
|
+
JSON.stringify(metadata),
|
|
116
117
|
now,
|
|
117
118
|
now,
|
|
118
119
|
targetStatus ?? null,
|
|
@@ -136,7 +137,7 @@ export function ingestIntegrationEvent(input: IntegrationEventInput, integration
|
|
|
136
137
|
input.channel ?? null,
|
|
137
138
|
input.dedupeKey ?? null,
|
|
138
139
|
input.externalUrl ?? null,
|
|
139
|
-
JSON.stringify(
|
|
140
|
+
JSON.stringify(metadata),
|
|
140
141
|
now,
|
|
141
142
|
now,
|
|
142
143
|
now,
|
|
@@ -153,7 +154,7 @@ export function ingestIntegrationEvent(input: IntegrationEventInput, integration
|
|
|
153
154
|
severity,
|
|
154
155
|
title: input.title,
|
|
155
156
|
body: input.body,
|
|
156
|
-
metadata
|
|
157
|
+
metadata,
|
|
157
158
|
}, now);
|
|
158
159
|
|
|
159
160
|
let message: Message | undefined;
|
|
@@ -185,11 +186,23 @@ export function ingestIntegrationEvent(input: IntegrationEventInput, integration
|
|
|
185
186
|
})();
|
|
186
187
|
}
|
|
187
188
|
|
|
189
|
+
function integrationTaskMetadata(
|
|
190
|
+
metadata: Record<string, unknown> | undefined,
|
|
191
|
+
integrationName: string,
|
|
192
|
+
fallback: Record<string, unknown> | undefined,
|
|
193
|
+
): Record<string, unknown> {
|
|
194
|
+
return {
|
|
195
|
+
...(fallback ?? {}),
|
|
196
|
+
...(metadata ?? {}),
|
|
197
|
+
relayIntegrationName: integrationName,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
188
201
|
|
|
189
202
|
export function listIntegrationTaskStats(): IntegrationTaskStats[] {
|
|
190
203
|
const rows = getDb().query(`
|
|
191
204
|
SELECT
|
|
192
|
-
source,
|
|
205
|
+
coalesce(json_extract(metadata, '$.relayIntegrationName'), source) AS source,
|
|
193
206
|
COUNT(*) AS tasks,
|
|
194
207
|
SUM(CASE WHEN status NOT IN ('done', 'failed', 'canceled') THEN 1 ELSE 0 END) AS open_tasks,
|
|
195
208
|
SUM(CASE WHEN status IN ('open', 'blocked') AND claimed_by IS NULL THEN 1 ELSE 0 END) AS waiting_tasks,
|
|
@@ -197,7 +210,7 @@ export function listIntegrationTaskStats(): IntegrationTaskStats[] {
|
|
|
197
210
|
MAX(last_seen_at) AS last_seen_at,
|
|
198
211
|
MAX(updated_at) AS last_updated_at
|
|
199
212
|
FROM tasks
|
|
200
|
-
GROUP BY source
|
|
213
|
+
GROUP BY coalesce(json_extract(metadata, '$.relayIntegrationName'), source)
|
|
201
214
|
ORDER BY last_seen_at DESC, source ASC
|
|
202
215
|
`).all() as any[];
|
|
203
216
|
|
|
@@ -337,4 +350,3 @@ export function isIntegrationRegistryEnabled(name: string): boolean {
|
|
|
337
350
|
const registry = getIntegrationRegistry(name);
|
|
338
351
|
return registry?.enabled !== false;
|
|
339
352
|
}
|
|
340
|
-
|
|
@@ -606,13 +606,13 @@ export const postIntegrationEvent: Handler = async (req) => {
|
|
|
606
606
|
const parsed = await parseBody<unknown>(req);
|
|
607
607
|
if (!parsed.ok) return error(parsed.error, parsed.status);
|
|
608
608
|
try {
|
|
609
|
-
const normalized =
|
|
609
|
+
const normalized = normalizeIntegrationEvent(parsed.body);
|
|
610
610
|
const requestedChannelId = channelIdFromIntegrationTarget(normalized.target);
|
|
611
611
|
const integrationScope = auth && !hasIntegrationScope(auth, "events:create") ? "task:write" : "integration:write";
|
|
612
612
|
if (!isRequestAuthorizedFor(req, { scope: integrationScope, resource: { integrationName, target: normalized.target, channel: normalized.channel ?? requestedChannelId } })) {
|
|
613
613
|
return error("integration token cannot target this task", 403);
|
|
614
614
|
}
|
|
615
|
-
const input = resolveIntegrationEventTarget(normalized);
|
|
615
|
+
const input = resolveIntegrationEventTarget({ ...normalized, source: normalized.source ?? integrationName });
|
|
616
616
|
const result = ingestIntegrationEvent(input, integrationName);
|
|
617
617
|
if (result.message) emitNewMessage(result.message);
|
|
618
618
|
emitTaskChanged(result.task, result.created ? "task.created" : "task.updated");
|