@perkos/perkos-a2a 0.9.1 → 0.10.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.
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Standalone PerkOS A2A bridge.
4
+ *
5
+ * This is for runtimes that do not load OpenClaw plugins directly, especially
6
+ * Hermes Workspace. It runs the same A2A HTTP/relay server and delivers inbound
7
+ * tasks into a local runtime over that runtime's native wake/send API.
8
+ *
9
+ * Usage:
10
+ * perkos-a2a-agent --config ./a2a.config.json
11
+ * A2A_AGENT_NAME=hermes A2A_RUNTIME=hermes-api HERMES_API_URL=http://127.0.0.1:8642 perkos-a2a-agent
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=bridge-agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-agent.d.ts","sourceRoot":"","sources":["../src/bridge-agent.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG"}
@@ -0,0 +1,396 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Standalone PerkOS A2A bridge.
4
+ *
5
+ * This is for runtimes that do not load OpenClaw plugins directly, especially
6
+ * Hermes Workspace. It runs the same A2A HTTP/relay server and delivers inbound
7
+ * tasks into a local runtime over that runtime's native wake/send API.
8
+ *
9
+ * Usage:
10
+ * perkos-a2a-agent --config ./a2a.config.json
11
+ * A2A_AGENT_NAME=hermes A2A_RUNTIME=hermes-api HERMES_API_URL=http://127.0.0.1:8642 perkos-a2a-agent
12
+ */
13
+ import { readFileSync } from "fs";
14
+ import { randomBytes, randomUUID } from "crypto";
15
+ import { createServer as createHttpServer } from "node:http";
16
+ import { A2AServer } from "./server.js";
17
+ import { ChatClient } from "./chat-client.js";
18
+ import { ConvRegistry } from "./conv-registry.js";
19
+ import { startToolsTokenListener } from "./tools-token-listener.js";
20
+ function argValue(flag) {
21
+ const idx = process.argv.indexOf(flag);
22
+ if (idx === -1)
23
+ return undefined;
24
+ return process.argv[idx + 1];
25
+ }
26
+ function parseJsonEnv(name, fallback) {
27
+ const raw = process.env[name];
28
+ if (!raw)
29
+ return fallback;
30
+ try {
31
+ return JSON.parse(raw);
32
+ }
33
+ catch (err) {
34
+ throw new Error(`${name} must be valid JSON: ${err instanceof Error ? err.message : String(err)}`);
35
+ }
36
+ }
37
+ function loadConfig() {
38
+ const configPath = argValue("--config") || argValue("-c") || process.env.A2A_CONFIG;
39
+ if (configPath) {
40
+ return JSON.parse(readFileSync(configPath, "utf8"));
41
+ }
42
+ const port = Number(process.env.A2A_PORT || "5050");
43
+ const relayEnabled = String(process.env.A2A_RELAY_ENABLED || "").toLowerCase() === "true";
44
+ return {
45
+ agentName: process.env.A2A_AGENT_NAME || "agent",
46
+ port,
47
+ bindHost: process.env.A2A_BIND_HOST || "0.0.0.0",
48
+ publicUrl: process.env.A2A_PUBLIC_URL,
49
+ mode: process.env.A2A_MODE || "auto",
50
+ skills: parseJsonEnv("A2A_SKILLS", []),
51
+ peers: parseJsonEnv("A2A_PEERS", {}),
52
+ peerAuth: parseJsonEnv("A2A_PEER_AUTH", {}),
53
+ auth: process.env.A2A_API_KEYS
54
+ ? { requireApiKey: true, apiKeys: process.env.A2A_API_KEYS.split(",").map((v) => v.trim()).filter(Boolean) }
55
+ : undefined,
56
+ relay: relayEnabled || process.env.A2A_RELAY_URL
57
+ ? {
58
+ enabled: relayEnabled || Boolean(process.env.A2A_RELAY_URL),
59
+ url: process.env.A2A_RELAY_URL || "",
60
+ apiKey: process.env.A2A_RELAY_API_KEY || "",
61
+ hubPort: process.env.A2A_RELAY_HUB_PORT ? Number(process.env.A2A_RELAY_HUB_PORT) : undefined,
62
+ hubApiKeys: process.env.A2A_RELAY_HUB_API_KEYS?.split(",").map((v) => v.trim()).filter(Boolean),
63
+ }
64
+ : undefined,
65
+ runtime: {
66
+ kind: process.env.A2A_RUNTIME || "hermes",
67
+ sessionKey: process.env.A2A_SESSION_KEY,
68
+ hermesUrl: process.env.HERMES_WORKSPACE_URL,
69
+ hermesToken: process.env.HERMES_WORKSPACE_TOKEN || process.env.HERMES_SESSION_TOKEN,
70
+ hermesEndpoint: process.env.HERMES_WORKSPACE_ENDPOINT,
71
+ },
72
+ chat: process.env.A2A_CHAT_ENABLED?.toLowerCase() === "true"
73
+ ? {
74
+ enabled: true,
75
+ url: process.env.A2A_CHAT_URL || "wss://chat.perkos.xyz/chat",
76
+ apiKey: process.env.A2A_CHAT_API_KEY || process.env.A2A_RELAY_API_KEY || "",
77
+ storeRoot: process.env.A2A_CHAT_STORE_ROOT,
78
+ defaultHistoryLimit: process.env.A2A_CHAT_HISTORY_LIMIT
79
+ ? Number(process.env.A2A_CHAT_HISTORY_LIMIT)
80
+ : undefined,
81
+ }
82
+ : undefined,
83
+ };
84
+ }
85
+ /** Port the bridge listens on for local Hermes-side chat-reply callbacks. */
86
+ function chatReplyPort() {
87
+ return Number(process.env.A2A_CHAT_REPLY_PORT || "5060");
88
+ }
89
+ /** Port the bridge listens on for Tools-API JWT mint requests. */
90
+ function toolsTokenPort() {
91
+ return Number(process.env.A2A_TOOLS_TOKEN_PORT || "5070");
92
+ }
93
+ /**
94
+ * Bridge-auth secret used by the Tools-API token listener. Read from
95
+ * env if provided (so multi-process deployments can pre-share it with
96
+ * the Hermes plugin); otherwise generate a random one and surface it
97
+ * via an env var the same container can read. This keeps single-host
98
+ * deployments zero-config.
99
+ */
100
+ function resolveBridgeAuthSecret() {
101
+ const fromEnv = process.env.A2A_BRIDGE_AUTH_SECRET?.trim();
102
+ if (fromEnv && fromEnv.length >= 16)
103
+ return fromEnv;
104
+ const generated = randomBytes(24).toString("hex");
105
+ process.env.A2A_BRIDGE_AUTH_SECRET = generated;
106
+ return generated;
107
+ }
108
+ function taskMarker(task) {
109
+ return `[A2A_RESULT:${task.id}]`;
110
+ }
111
+ function buildRuntimeMessage(task, text) {
112
+ const from = task.metadata?.fromAgent || "unknown";
113
+ const marker = taskMarker(task);
114
+ return [
115
+ marker,
116
+ `From: ${from}`,
117
+ `Task ID: ${task.id}`,
118
+ `Context ID: ${task.contextId}`,
119
+ "",
120
+ "Execute the following request and include the final answer in your assistant reply.",
121
+ `Your final answer must begin with exactly: ${marker}`,
122
+ "Return the useful final answer after that marker.",
123
+ "",
124
+ text,
125
+ ].join("\n");
126
+ }
127
+ async function deliverToHermes(config, message) {
128
+ const runtime = config.runtime || {};
129
+ const baseUrl = (runtime.hermesUrl || process.env.HERMES_API_URL || process.env.HERMES_WORKSPACE_URL || "http://127.0.0.1:8642").replace(/\/+$/, "");
130
+ const endpoint = runtime.hermesEndpoint || process.env.HERMES_API_ENDPOINT || process.env.HERMES_WORKSPACE_ENDPOINT || "/v1/responses";
131
+ const sessionKey = runtime.sessionKey || process.env.HERMES_SESSION_ID || process.env.HERMES_SESSION_KEY || "a2a";
132
+ const token = runtime.hermesToken || process.env.API_SERVER_KEY || process.env.HERMES_API_KEY || process.env.HERMES_WORKSPACE_TOKEN || process.env.HERMES_SESSION_TOKEN;
133
+ const url = `${baseUrl}${endpoint.startsWith("/") ? endpoint : `/${endpoint}`}`;
134
+ const headers = {
135
+ "content-type": "application/json",
136
+ "x-hermes-session-id": sessionKey,
137
+ "x-hermes-session-key": sessionKey,
138
+ };
139
+ if (token)
140
+ headers.authorization = `Bearer ${token}`;
141
+ const normalizedEndpoint = endpoint.replace(/^\/?/, "/");
142
+ const body = normalizedEndpoint.startsWith("/v1/chat/completions")
143
+ ? { model: process.env.HERMES_MODEL || "hermes-agent", messages: [{ role: "user", content: message }], stream: false }
144
+ : normalizedEndpoint.startsWith("/v1/runs")
145
+ ? { input: message, session_id: sessionKey }
146
+ : normalizedEndpoint.startsWith("/v1/responses")
147
+ ? { model: process.env.HERMES_MODEL || "hermes-agent", input: message, store: true }
148
+ : { sessionKey, message };
149
+ const response = await fetch(url, { method: "POST", headers, body: JSON.stringify(body) });
150
+ if (!response.ok) {
151
+ const text = await response.text().catch(() => "");
152
+ throw new Error(`Hermes API delivery failed (${response.status}) at ${url}: ${text || response.statusText}`);
153
+ }
154
+ }
155
+ /** Extract a wallet address from a ChatIdentity ("user:0xabc..." → "0xabc..."). */
156
+ function parseWalletFromIdentity(identity) {
157
+ if (typeof identity !== "string" || !identity.startsWith("user:"))
158
+ return null;
159
+ const addr = identity.slice("user:".length);
160
+ return addr.startsWith("0x") ? addr.toLowerCase() : null;
161
+ }
162
+ function buildChatMessage(frame) {
163
+ const walletAddress = parseWalletFromIdentity(frame.from);
164
+ const marker = `[PERKOS_CHAT:${frame.convId}]`;
165
+ const toolsApiUrl = process.env.A2A_TOOLS_API_URL?.trim();
166
+ const toolsTokenEndpoint = `http://127.0.0.1:${toolsTokenPort()}/v1/tools-token`;
167
+ return [
168
+ marker,
169
+ `From: ${frame.from}`,
170
+ `Conversation: ${frame.convId}`,
171
+ walletAddress ? `Wallet: ${walletAddress}` : "",
172
+ "",
173
+ "A user is messaging this agent in a PerkOS chat conversation.",
174
+ "To respond, POST to http://127.0.0.1:<A2A_CHAT_REPLY_PORT>/chat/reply with:",
175
+ ` { "convId": "${frame.convId}", "walletAddress": "${walletAddress ?? ""}", "text": "<your reply>" }`,
176
+ "",
177
+ toolsApiUrl
178
+ ? [
179
+ "PerkOS Tools API is available for this conversation:",
180
+ ` - Base URL: ${toolsApiUrl}`,
181
+ ` - To call a tool, first POST to ${toolsTokenEndpoint} with`,
182
+ ` { "convId": "${frame.convId}" }`,
183
+ " and X-Bridge-Auth: <A2A_BRIDGE_AUTH_SECRET> to mint a short-lived JWT.",
184
+ " - Then POST to <toolsApiUrl>/v1/tools/<toolName> with Authorization: Bearer <token>.",
185
+ ].join("\n")
186
+ : "",
187
+ "",
188
+ "Message body:",
189
+ frame.text,
190
+ ].filter(Boolean).join("\n");
191
+ }
192
+ /**
193
+ * Local-only HTTP listener for Hermes (or any sidecar) to push chat replies
194
+ * back through the chat client. Bound to 127.0.0.1 — never expose this port
195
+ * publicly. The chat client holds the relay API key; this gives Hermes a
196
+ * controlled callback path without leaking the key to the runtime config.
197
+ *
198
+ * POST /chat/reply
199
+ * { convId, walletAddress, text, replyTo? }
200
+ *
201
+ * → 200 { id, delivered: boolean }
202
+ * → 400 { error: "..." }
203
+ * → 503 { error: "chat client not connected" }
204
+ */
205
+ function startChatReplyListener(chat, port, logger) {
206
+ const server = createHttpServer(async (req, res) => {
207
+ if (req.method !== "POST" || req.url !== "/chat/reply") {
208
+ res.writeHead(404, { "content-type": "application/json" })
209
+ .end(JSON.stringify({ error: "not found" }));
210
+ return;
211
+ }
212
+ let raw = "";
213
+ req.setEncoding("utf8");
214
+ req.on("data", (c) => { raw += c; if (raw.length > 100_000)
215
+ req.destroy(); });
216
+ req.on("end", async () => {
217
+ let body;
218
+ try {
219
+ body = JSON.parse(raw);
220
+ }
221
+ catch {
222
+ res.writeHead(400, { "content-type": "application/json" })
223
+ .end(JSON.stringify({ error: "invalid JSON" }));
224
+ return;
225
+ }
226
+ if (!body.convId || !body.walletAddress || !body.text) {
227
+ res.writeHead(400, { "content-type": "application/json" })
228
+ .end(JSON.stringify({ error: "missing { convId, walletAddress, text }" }));
229
+ return;
230
+ }
231
+ if (!chat.isConnected()) {
232
+ res.writeHead(503, { "content-type": "application/json" })
233
+ .end(JSON.stringify({ error: "chat client not connected; reply will be appended to local store only" }));
234
+ // Continue anyway — sendReply still appends to local jsonl.
235
+ }
236
+ try {
237
+ const result = await chat.sendReply({
238
+ convId: body.convId,
239
+ walletAddress: body.walletAddress,
240
+ text: body.text,
241
+ replyTo: body.replyTo,
242
+ });
243
+ if (!res.headersSent) {
244
+ res.writeHead(200, { "content-type": "application/json" })
245
+ .end(JSON.stringify(result));
246
+ }
247
+ else {
248
+ res.end(JSON.stringify(result));
249
+ }
250
+ }
251
+ catch (err) {
252
+ const msg = err instanceof Error ? err.message : String(err);
253
+ if (!res.headersSent) {
254
+ res.writeHead(500, { "content-type": "application/json" })
255
+ .end(JSON.stringify({ error: msg }));
256
+ }
257
+ }
258
+ });
259
+ });
260
+ server.listen(port, "127.0.0.1", () => {
261
+ logger.info(`[perkos-chat] local reply listener on http://127.0.0.1:${port}/chat/reply`);
262
+ });
263
+ return () => server.close();
264
+ }
265
+ async function main() {
266
+ const config = loadConfig();
267
+ const logger = console;
268
+ const server = new A2AServer(config, logger);
269
+ server.setTaskResultHandler(async (task, text) => {
270
+ const runtimeKind = config.runtime?.kind || "hermes";
271
+ task.status = {
272
+ state: "working",
273
+ timestamp: new Date().toISOString(),
274
+ message: { role: "agent", parts: [{ kind: "text", text: `Task dispatched to ${runtimeKind} runtime.` }] },
275
+ };
276
+ if ((runtimeKind === "hermes" || runtimeKind === "hermes-api")) {
277
+ await deliverToHermes(config, buildRuntimeMessage(task, text));
278
+ task.artifacts.push({
279
+ kind: "artifact",
280
+ artifactId: randomUUID(),
281
+ parts: [{ kind: "text", text: "Delivered to Hermes API Server." }],
282
+ });
283
+ return;
284
+ }
285
+ if (runtimeKind === "none") {
286
+ task.artifacts.push({
287
+ kind: "artifact",
288
+ artifactId: randomUUID(),
289
+ parts: [{ kind: "text", text: "Runtime delivery disabled." }],
290
+ });
291
+ return;
292
+ }
293
+ throw new Error(`Standalone runtime '${runtimeKind}' is not supported yet. Use OpenClaw plugin mode for OpenClaw delivery.`);
294
+ });
295
+ server.setTaskFailureHandler((task, errorText) => {
296
+ task.status = {
297
+ state: "failed",
298
+ timestamp: new Date().toISOString(),
299
+ message: { role: "agent", parts: [{ kind: "text", text: errorText }] },
300
+ };
301
+ });
302
+ await server.start();
303
+ logger.info(`[perkos-a2a] standalone bridge running for ${config.agentName} (${config.runtime?.kind || "hermes"})`);
304
+ // -------------------------------------------------------------------------
305
+ // Optional: PerkOS-Chat client for user-facing conversations.
306
+ //
307
+ // When enabled, inbound chat_deliver frames are forwarded to Hermes via the
308
+ // same /v1/responses path used for A2A tasks, but with a [PERKOS_CHAT:<id>]
309
+ // marker and instructions to POST the reply to the local /chat/reply
310
+ // listener (so the relay API key stays in this bridge).
311
+ // -------------------------------------------------------------------------
312
+ let chat = null;
313
+ let stopChatListener = null;
314
+ let stopToolsTokenListener = null;
315
+ const convRegistry = new ConvRegistry();
316
+ const chatCfg = config.chat;
317
+ if (chatCfg?.enabled && chatCfg.apiKey) {
318
+ chat = new ChatClient({
319
+ agentName: config.agentName,
320
+ config: chatCfg,
321
+ handlers: {
322
+ logger,
323
+ onChatDeliver: async (frame) => {
324
+ try {
325
+ const wallet = parseWalletFromIdentity(frame.from);
326
+ if (wallet)
327
+ convRegistry.touch(frame.convId, wallet, "user");
328
+ await deliverToHermes(config, buildChatMessage(frame));
329
+ }
330
+ catch (err) {
331
+ logger.error(`[perkos-chat] hermes delivery failed: ${err instanceof Error ? err.message : String(err)}`);
332
+ }
333
+ },
334
+ onChannelJoin: async (frame) => {
335
+ logger.info(`[perkos-chat] joined conv ${frame.convId} (host: ${frame.historyHost}, ${frame.participants.length} participants)`);
336
+ },
337
+ },
338
+ });
339
+ chat.start();
340
+ stopChatListener = startChatReplyListener(chat, chatReplyPort(), logger);
341
+ }
342
+ else if (chatCfg?.enabled && !chatCfg.apiKey) {
343
+ logger.error("[perkos-chat] chat.enabled=true but no apiKey (set A2A_CHAT_API_KEY or A2A_RELAY_API_KEY)");
344
+ }
345
+ // Tools-API token listener. Only starts when both env vars are set,
346
+ // since the runtime plugin can't call the API without them anyway.
347
+ const toolsJwtSecret = process.env.A2A_TOOLS_JWT_SECRET?.trim();
348
+ const toolsApiUrl = process.env.A2A_TOOLS_API_URL?.trim();
349
+ if (toolsJwtSecret && toolsApiUrl) {
350
+ if (toolsJwtSecret.length < 32) {
351
+ logger.error("[tools-token] A2A_TOOLS_JWT_SECRET must be >=32 chars; listener disabled");
352
+ }
353
+ else {
354
+ const bridgeAuthSecret = resolveBridgeAuthSecret();
355
+ const ttl = Number(process.env.A2A_TOOLS_TOKEN_TTL_SECONDS || "60");
356
+ convRegistry.startReaper();
357
+ const { stop } = startToolsTokenListener({
358
+ port: toolsTokenPort(),
359
+ bridgeAuthSecret,
360
+ toolsJwtSecret,
361
+ toolsApiUrl,
362
+ registry: convRegistry,
363
+ tokenTtlSeconds: Number.isFinite(ttl) ? ttl : 60,
364
+ logger: {
365
+ info: (m) => logger.info(m),
366
+ error: (m) => logger.error(m),
367
+ },
368
+ });
369
+ stopToolsTokenListener = stop;
370
+ logger.info(`[tools-token] enabled (tools API: ${toolsApiUrl}); plugin must present A2A_BRIDGE_AUTH_SECRET`);
371
+ }
372
+ }
373
+ else if (toolsJwtSecret || toolsApiUrl) {
374
+ logger.warn?.("[tools-token] partial config — both A2A_TOOLS_JWT_SECRET and A2A_TOOLS_API_URL are required; listener disabled");
375
+ }
376
+ const stop = async () => {
377
+ logger.info("[perkos-a2a] standalone bridge shutting down");
378
+ chat?.stop();
379
+ stopChatListener?.();
380
+ convRegistry.stopReaper();
381
+ if (stopToolsTokenListener) {
382
+ try {
383
+ await stopToolsTokenListener();
384
+ }
385
+ catch { /* swallow */ }
386
+ }
387
+ process.exit(0);
388
+ };
389
+ process.on("SIGINT", () => void stop());
390
+ process.on("SIGTERM", () => void stop());
391
+ }
392
+ main().catch((err) => {
393
+ console.error(`[perkos-a2a] standalone bridge failed: ${err instanceof Error ? err.stack || err.message : String(err)}`);
394
+ process.exit(1);
395
+ });
396
+ //# sourceMappingURL=bridge-agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-agent.js","sourceRoot":"","sources":["../src/bridge-agent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Tracks active chat conversations the bridge is currently relaying.
3
+ *
4
+ * Used by `tools-token-listener` to authorize Tools-API token mints:
5
+ * the listener will only mint a token for a convId that the bridge has
6
+ * seen a chat_deliver for in the last `staleAfterMs` window. This binds
7
+ * the wallet to the conv server-side — the runtime cannot ask for a
8
+ * token for a wallet that isn't its current caller.
9
+ */
10
+ export type Role = "user" | "admin";
11
+ export type ConvEntry = {
12
+ wallet: string;
13
+ role: Role;
14
+ lastSeen: number;
15
+ };
16
+ export declare class ConvRegistry {
17
+ private readonly map;
18
+ private readonly staleAfterMs;
19
+ private reaper;
20
+ constructor(opts?: {
21
+ staleAfterMs?: number;
22
+ });
23
+ /** Record that the bridge is currently handling a chat for this wallet/conv. */
24
+ touch(convId: string, wallet: string, role?: Role): void;
25
+ /** Lookup — returns undefined if the conv is unknown or stale. */
26
+ get(convId: string): ConvEntry | undefined;
27
+ size(): number;
28
+ startReaper(intervalMs?: number): void;
29
+ stopReaper(): void;
30
+ private sweep;
31
+ }
32
+ //# sourceMappingURL=conv-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conv-registry.d.ts","sourceRoot":"","sources":["../src/conv-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpC,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAgC;IACpD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,MAAM,CAA+B;gBAEjC,IAAI,GAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAO;IAIhD,gFAAgF;IAChF,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,IAAa,GAAG,IAAI;IAIhE,kEAAkE;IAClE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAU1C,IAAI,IAAI,MAAM;IAId,WAAW,CAAC,UAAU,SAAS,GAAG,IAAI;IAOtC,UAAU,IAAI,IAAI;IAOlB,OAAO,CAAC,KAAK;CAMd"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Tracks active chat conversations the bridge is currently relaying.
3
+ *
4
+ * Used by `tools-token-listener` to authorize Tools-API token mints:
5
+ * the listener will only mint a token for a convId that the bridge has
6
+ * seen a chat_deliver for in the last `staleAfterMs` window. This binds
7
+ * the wallet to the conv server-side — the runtime cannot ask for a
8
+ * token for a wallet that isn't its current caller.
9
+ */
10
+ export class ConvRegistry {
11
+ map = new Map();
12
+ staleAfterMs;
13
+ reaper = null;
14
+ constructor(opts = {}) {
15
+ this.staleAfterMs = opts.staleAfterMs ?? 10 * 60 * 1000;
16
+ }
17
+ /** Record that the bridge is currently handling a chat for this wallet/conv. */
18
+ touch(convId, wallet, role = "user") {
19
+ this.map.set(convId, { wallet: wallet.toLowerCase(), role, lastSeen: Date.now() });
20
+ }
21
+ /** Lookup — returns undefined if the conv is unknown or stale. */
22
+ get(convId) {
23
+ const entry = this.map.get(convId);
24
+ if (!entry)
25
+ return undefined;
26
+ if (Date.now() - entry.lastSeen > this.staleAfterMs) {
27
+ this.map.delete(convId);
28
+ return undefined;
29
+ }
30
+ return entry;
31
+ }
32
+ size() {
33
+ return this.map.size;
34
+ }
35
+ startReaper(intervalMs = 60_000) {
36
+ if (this.reaper)
37
+ return;
38
+ this.reaper = setInterval(() => this.sweep(), intervalMs);
39
+ // Reaper is a maintenance task; don't keep the event loop alive for it.
40
+ this.reaper.unref?.();
41
+ }
42
+ stopReaper() {
43
+ if (this.reaper) {
44
+ clearInterval(this.reaper);
45
+ this.reaper = null;
46
+ }
47
+ }
48
+ sweep() {
49
+ const cutoff = Date.now() - this.staleAfterMs;
50
+ for (const [convId, entry] of this.map) {
51
+ if (entry.lastSeen < cutoff)
52
+ this.map.delete(convId);
53
+ }
54
+ }
55
+ }
56
+ //# sourceMappingURL=conv-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conv-registry.js","sourceRoot":"","sources":["../src/conv-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,MAAM,OAAO,YAAY;IACN,GAAG,GAAG,IAAI,GAAG,EAAqB,CAAC;IACnC,YAAY,CAAS;IAC9B,MAAM,GAA0B,IAAI,CAAC;IAE7C,YAAY,OAAkC,EAAE;QAC9C,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC1D,CAAC;IAED,gFAAgF;IAChF,KAAK,CAAC,MAAc,EAAE,MAAc,EAAE,OAAa,MAAM;QACvD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACrF,CAAC;IAED,kEAAkE;IAClE,GAAG,CAAC,MAAc;QAChB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAC7B,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;YACpD,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACxB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI;QACF,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;IACvB,CAAC;IAED,WAAW,CAAC,UAAU,GAAG,MAAM;QAC7B,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,UAAU,CAAC,CAAC;QAC1D,wEAAwE;QACxE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC;IACxB,CAAC;IAED,UAAU;QACR,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC3B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACrB,CAAC;IACH,CAAC;IAEO,KAAK;QACX,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC;QAC9C,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YACvC,IAAI,KAAK,CAAC,QAAQ,GAAG,MAAM;gBAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,12 @@
1
+ export type ToolsClaims = {
2
+ wallet: string;
3
+ convId: string;
4
+ role: "user" | "admin";
5
+ requestId: string;
6
+ };
7
+ export declare function signToolsToken(claims: ToolsClaims, secret: string, ttlSeconds?: number): {
8
+ token: string;
9
+ iat: number;
10
+ exp: number;
11
+ };
12
+ //# sourceMappingURL=tools-jwt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools-jwt.d.ts","sourceRoot":"","sources":["../src/tools-jwt.ts"],"names":[],"mappings":"AAgBA,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AASF,wBAAgB,cAAc,CAC5B,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,MAAM,EACd,UAAU,SAAK,GACd;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAY7C"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Mint HS256 JWTs the PerkOS-Platform-Tools-API will accept.
3
+ *
4
+ * The token shape MUST match what the Tools API verifier expects in
5
+ * `src/auth/jwt.ts` over there:
6
+ *
7
+ * { wallet: "0x...", convId: "assistant-0x...", role: "user"|"admin",
8
+ * requestId: "uuid", iat: <epoch>, exp: <iat + ttl> }
9
+ *
10
+ * The shared secret is set via env (A2A_TOOLS_JWT_SECRET) and never
11
+ * leaves this process — the bridge is the only entity that holds it.
12
+ * Hermes runtimes ask this bridge for a fresh token via the local
13
+ * listener (see tools-token-listener.ts).
14
+ */
15
+ import { createHmac } from "node:crypto";
16
+ function b64url(buf) {
17
+ return buf.toString("base64")
18
+ .replace(/=+$/, "")
19
+ .replace(/\+/g, "-")
20
+ .replace(/\//g, "_");
21
+ }
22
+ export function signToolsToken(claims, secret, ttlSeconds = 60) {
23
+ if (!secret || secret.length < 32) {
24
+ throw new Error("signToolsToken: secret too short (need >=32 chars)");
25
+ }
26
+ const iat = Math.floor(Date.now() / 1000);
27
+ const exp = iat + Math.max(1, Math.min(ttlSeconds, 600));
28
+ const header = b64url(Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })));
29
+ const payload = b64url(Buffer.from(JSON.stringify({ ...claims, iat, exp })));
30
+ const signature = b64url(createHmac("sha256", secret).update(`${header}.${payload}`).digest());
31
+ return { token: `${header}.${payload}.${signature}`, iat, exp };
32
+ }
33
+ //# sourceMappingURL=tools-jwt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools-jwt.js","sourceRoot":"","sources":["../src/tools-jwt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AASzC,SAAS,MAAM,CAAC,GAAW;IACzB,OAAO,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC;SAC1B,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;SAClB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,MAAmB,EACnB,MAAc,EACd,UAAU,GAAG,EAAE;IAEf,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;IACzD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IACjF,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7E,MAAM,SAAS,GAAG,MAAM,CACtB,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CACrE,CAAC;IACF,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,OAAO,IAAI,SAAS,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAClE,CAAC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Local HTTP listener that mints Tools-API JWTs for the active runtime.
3
+ *
4
+ * POST /v1/tools-token
5
+ * headers: X-Bridge-Auth: <shared secret with runtime plugin>
6
+ * body: { convId: string, requestId?: string }
7
+ * → 200 { token, exp, toolsApiUrl }
8
+ * → 400 { error: "..." } (bad body)
9
+ * → 401 { error: "..." } (bad/missing bridge auth)
10
+ * → 404 { error: "unknown or stale conv" } (conv not in registry)
11
+ *
12
+ * GET /healthz → 200 { ok: true, conv_count }
13
+ *
14
+ * Binds 127.0.0.1 only. The bridge is the only entity that knows
15
+ * A2A_TOOLS_JWT_SECRET — Hermes never sees it; it just receives short
16
+ * derived tokens.
17
+ *
18
+ * Authorization model — three layers of defense:
19
+ * 1. Network: listener is bound to 127.0.0.1, so only same-host
20
+ * processes can reach it.
21
+ * 2. Bridge-auth: caller must present X-Bridge-Auth matching the
22
+ * bridge's startup secret. This stops other processes on the
23
+ * same host (e.g. neighboring containers if 127.0.0.1 is shared)
24
+ * from minting tokens.
25
+ * 3. Conv binding: the listener only mints for convIds the bridge
26
+ * is currently handling (touched via chat_deliver in last 10m).
27
+ * Even with valid auth, the runtime can't mint a token for a
28
+ * wallet that isn't the active caller.
29
+ */
30
+ import { type Server } from "node:http";
31
+ import { ConvRegistry } from "./conv-registry.js";
32
+ export type ToolsTokenListenerOptions = {
33
+ port: number;
34
+ bridgeAuthSecret: string;
35
+ toolsJwtSecret: string;
36
+ toolsApiUrl: string;
37
+ registry: ConvRegistry;
38
+ /** TTL of minted tokens, in seconds. Default 60. */
39
+ tokenTtlSeconds?: number;
40
+ logger?: {
41
+ info: (msg: string) => void;
42
+ error: (msg: string) => void;
43
+ warn?: (msg: string) => void;
44
+ };
45
+ };
46
+ export declare function startToolsTokenListener(opts: ToolsTokenListenerOptions): {
47
+ server: Server;
48
+ stop: () => Promise<void>;
49
+ };
50
+ //# sourceMappingURL=tools-token-listener.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools-token-listener.d.ts","sourceRoot":"","sources":["../src/tools-token-listener.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAsC,KAAK,MAAM,EAAuB,MAAM,WAAW,CAAC;AAGjG,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,YAAY,CAAC;IACvB,oDAAoD;IACpD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CACtG,CAAC;AAwCF,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,yBAAyB,GAAG;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B,CAwEA"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Local HTTP listener that mints Tools-API JWTs for the active runtime.
3
+ *
4
+ * POST /v1/tools-token
5
+ * headers: X-Bridge-Auth: <shared secret with runtime plugin>
6
+ * body: { convId: string, requestId?: string }
7
+ * → 200 { token, exp, toolsApiUrl }
8
+ * → 400 { error: "..." } (bad body)
9
+ * → 401 { error: "..." } (bad/missing bridge auth)
10
+ * → 404 { error: "unknown or stale conv" } (conv not in registry)
11
+ *
12
+ * GET /healthz → 200 { ok: true, conv_count }
13
+ *
14
+ * Binds 127.0.0.1 only. The bridge is the only entity that knows
15
+ * A2A_TOOLS_JWT_SECRET — Hermes never sees it; it just receives short
16
+ * derived tokens.
17
+ *
18
+ * Authorization model — three layers of defense:
19
+ * 1. Network: listener is bound to 127.0.0.1, so only same-host
20
+ * processes can reach it.
21
+ * 2. Bridge-auth: caller must present X-Bridge-Auth matching the
22
+ * bridge's startup secret. This stops other processes on the
23
+ * same host (e.g. neighboring containers if 127.0.0.1 is shared)
24
+ * from minting tokens.
25
+ * 3. Conv binding: the listener only mints for convIds the bridge
26
+ * is currently handling (touched via chat_deliver in last 10m).
27
+ * Even with valid auth, the runtime can't mint a token for a
28
+ * wallet that isn't the active caller.
29
+ */
30
+ import { createServer } from "node:http";
31
+ import { timingSafeEqual, randomUUID } from "node:crypto";
32
+ import { signToolsToken } from "./tools-jwt.js";
33
+ function constantTimeEqual(a, b) {
34
+ // Both inputs may differ in length; pad to the same length to keep
35
+ // the comparison length-stable, then bail on length mismatch.
36
+ if (a.length !== b.length) {
37
+ // Still do a dummy compare to avoid early-exit timing signal.
38
+ const pad = Buffer.alloc(Math.max(a.length, b.length, 1), 0);
39
+ timingSafeEqual(pad, pad);
40
+ return false;
41
+ }
42
+ return timingSafeEqual(Buffer.from(a), Buffer.from(b));
43
+ }
44
+ async function readBody(req, maxBytes = 8192) {
45
+ return new Promise((resolve, reject) => {
46
+ let raw = "";
47
+ req.setEncoding("utf8");
48
+ req.on("data", (chunk) => {
49
+ raw += chunk;
50
+ if (raw.length > maxBytes) {
51
+ req.destroy(new Error("body too large"));
52
+ reject(new Error("body too large"));
53
+ }
54
+ });
55
+ req.on("end", () => resolve(raw));
56
+ req.on("error", reject);
57
+ });
58
+ }
59
+ function sendJson(res, code, body) {
60
+ if (res.headersSent) {
61
+ res.end(JSON.stringify(body));
62
+ return;
63
+ }
64
+ res
65
+ .writeHead(code, { "content-type": "application/json" })
66
+ .end(JSON.stringify(body));
67
+ }
68
+ export function startToolsTokenListener(opts) {
69
+ const logger = opts.logger ?? console;
70
+ const ttl = opts.tokenTtlSeconds ?? 60;
71
+ const server = createServer(async (req, res) => {
72
+ try {
73
+ if (req.method === "GET" && req.url === "/healthz") {
74
+ sendJson(res, 200, { ok: true, conv_count: opts.registry.size() });
75
+ return;
76
+ }
77
+ if (req.method !== "POST" || req.url !== "/v1/tools-token") {
78
+ sendJson(res, 404, { error: "not found" });
79
+ return;
80
+ }
81
+ const presented = req.headers["x-bridge-auth"];
82
+ const authHeader = Array.isArray(presented) ? presented[0] : presented;
83
+ if (!authHeader || !constantTimeEqual(authHeader, opts.bridgeAuthSecret)) {
84
+ sendJson(res, 401, { error: "bridge auth missing or invalid" });
85
+ return;
86
+ }
87
+ let body;
88
+ try {
89
+ body = JSON.parse((await readBody(req)) || "{}");
90
+ }
91
+ catch {
92
+ sendJson(res, 400, { error: "invalid JSON" });
93
+ return;
94
+ }
95
+ if (!body.convId || typeof body.convId !== "string") {
96
+ sendJson(res, 400, { error: "missing convId" });
97
+ return;
98
+ }
99
+ const entry = opts.registry.get(body.convId);
100
+ if (!entry) {
101
+ sendJson(res, 404, { error: "unknown or stale conv" });
102
+ return;
103
+ }
104
+ const { token, exp } = signToolsToken({
105
+ wallet: entry.wallet,
106
+ convId: body.convId,
107
+ role: entry.role,
108
+ requestId: body.requestId ?? randomUUID(),
109
+ }, opts.toolsJwtSecret, ttl);
110
+ sendJson(res, 200, { token, exp, toolsApiUrl: opts.toolsApiUrl });
111
+ }
112
+ catch (err) {
113
+ const msg = err instanceof Error ? err.message : String(err);
114
+ logger.error(`[tools-token] ${msg}`);
115
+ sendJson(res, 500, { error: "internal" });
116
+ }
117
+ });
118
+ server.listen(opts.port, "127.0.0.1", () => {
119
+ logger.info(`[tools-token] listener on http://127.0.0.1:${opts.port}/v1/tools-token (TTL ${ttl}s)`);
120
+ });
121
+ const stop = async () => {
122
+ await new Promise((resolve, reject) => {
123
+ server.close((err) => (err ? reject(err) : resolve()));
124
+ });
125
+ };
126
+ return { server, stop };
127
+ }
128
+ //# sourceMappingURL=tools-token-listener.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools-token-listener.js","sourceRoot":"","sources":["../src/tools-token-listener.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@perkos/perkos-a2a",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "A2A Protocol communication plugin for OpenClaw — Agent-to-Agent protocol implementation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
8
  "bin": {
9
9
  "perkos-a2a-agent": "./dist/agent.js",
10
- "perkos-a2a-hermes": "./dist/hermes-cli.js"
10
+ "perkos-a2a-hermes": "./dist/hermes-cli.js",
11
+ "perkos-a2a-bridge": "./dist/bridge-agent.js"
11
12
  },
12
13
  "exports": {
13
14
  ".": {
@@ -39,7 +40,9 @@
39
40
  "scripts": {
40
41
  "build": "tsc && node scripts/bundle.mjs",
41
42
  "dev": "tsc --watch",
42
- "lint": "tsc --noEmit"
43
+ "lint": "tsc --noEmit",
44
+ "test": "vitest run",
45
+ "test:watch": "vitest"
43
46
  },
44
47
  "openclaw": {
45
48
  "extensions": [
@@ -76,7 +79,8 @@
76
79
  "@types/node": "^22.19.19",
77
80
  "@types/ws": "^8.5.0",
78
81
  "esbuild": "^0.28.0",
79
- "typescript": "^5.7.0"
82
+ "typescript": "^5.7.0",
83
+ "vitest": "^3.2.4"
80
84
  },
81
85
  "peerDependencies": {
82
86
  "openclaw": ">=2026.3.0"