@clawcrony/claw-crony 1.2.3 → 1.3.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 +107 -0
- package/CONFIG.md +341 -0
- package/README.md +154 -23
- package/dist/index.js +610 -112
- package/dist/src/ephemeral-token.d.ts +7 -0
- package/dist/src/ephemeral-token.js +17 -0
- package/dist/src/handshake-crypto.d.ts +3 -0
- package/dist/src/handshake-crypto.js +58 -0
- package/dist/src/history.d.ts +44 -0
- package/dist/src/history.js +119 -0
- package/dist/src/hub-match.d.ts +35 -46
- package/dist/src/hub-match.js +38 -53
- package/dist/src/hub-registration.d.ts +3 -10
- package/dist/src/hub-registration.js +33 -132
- package/dist/src/identity-store.d.ts +4 -0
- package/dist/src/identity-store.js +55 -0
- package/dist/src/types.d.ts +44 -3
- package/openclaw.plugin.json +46 -18
- package/package.json +43 -27
- package/scripts/a2a-diagnose.ps1 +15 -0
- package/scripts/a2a-diagnose.sh +22 -0
- package/scripts/a2a-history.ps1 +21 -0
- package/scripts/a2a-history.sh +9 -0
- package/scripts/a2a-match.ps1 +16 -0
- package/scripts/a2a-match.sh +13 -0
- package/scripts/a2a-peers.ps1 +1 -0
- package/scripts/a2a-peers.sh +4 -0
- package/scripts/a2a-send.ps1 +23 -0
- package/scripts/a2a-send.sh +14 -0
- package/scripts/a2a-update.ps1 +3 -0
- package/scripts/a2a-update.sh +6 -0
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hub registration module for
|
|
2
|
+
* Hub registration module for claw-crony.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Registers the local plugin with the hub using client_id + public_key
|
|
5
|
+
* and persists the resulting agent binding locally.
|
|
6
6
|
*/
|
|
7
|
-
import crypto from "node:crypto";
|
|
8
7
|
import fs from "node:fs";
|
|
9
8
|
import os from "node:os";
|
|
10
9
|
import path from "node:path";
|
|
11
|
-
|
|
12
|
-
// Token generation
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
function generateToken() {
|
|
15
|
-
return crypto.randomBytes(32).toString("hex");
|
|
16
|
-
}
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// Registration file path & I/O
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
10
|
+
import { loadOrCreateIdentity } from "./identity-store.js";
|
|
20
11
|
const REGISTRATION_FILENAME = "a2a-registration.json";
|
|
21
12
|
function getRegistrationPath(configDir) {
|
|
22
13
|
return path.join(configDir, REGISTRATION_FILENAME);
|
|
@@ -48,7 +39,6 @@ async function registerHubUser(hubUrl, agentId, username, password) {
|
|
|
48
39
|
body: JSON.stringify({ agentId, username, password }),
|
|
49
40
|
});
|
|
50
41
|
if (res.status === 409) {
|
|
51
|
-
// Already registered — this is fine, idempotent
|
|
52
42
|
return;
|
|
53
43
|
}
|
|
54
44
|
if (!res.ok) {
|
|
@@ -63,18 +53,14 @@ async function registerWithHub(hubUrl, payload) {
|
|
|
63
53
|
headers: { "Content-Type": "application/json" },
|
|
64
54
|
body: JSON.stringify(payload),
|
|
65
55
|
});
|
|
66
|
-
if (res.status === 409) {
|
|
67
|
-
const err = await res.json().catch(() => ({}));
|
|
68
|
-
throw Object.assign(new Error("Agent address already registered"), { status: 409, body: err });
|
|
69
|
-
}
|
|
70
56
|
if (!res.ok) {
|
|
71
57
|
const err = await res.json().catch(() => ({}));
|
|
72
58
|
throw Object.assign(new Error(`Hub rejected registration: ${JSON.stringify(err)}`), { status: res.status });
|
|
73
59
|
}
|
|
74
60
|
return res.json();
|
|
75
61
|
}
|
|
76
|
-
async function
|
|
77
|
-
const url = `${hubUrl.replace(/\/$/, "")}/api/agents?
|
|
62
|
+
async function lookupAgentByClientId(hubUrl, clientId) {
|
|
63
|
+
const url = `${hubUrl.replace(/\/$/, "")}/api/agents?clientId=${encodeURIComponent(clientId)}`;
|
|
78
64
|
const res = await fetch(url);
|
|
79
65
|
if (!res.ok) {
|
|
80
66
|
throw Object.assign(new Error(`Hub lookup failed: ${res.status}`), { status: res.status });
|
|
@@ -82,9 +68,6 @@ async function lookupAgentByAddress(hubUrl, address) {
|
|
|
82
68
|
const agents = await res.json();
|
|
83
69
|
return agents.length > 0 ? agents[0] : null;
|
|
84
70
|
}
|
|
85
|
-
// ---------------------------------------------------------------------------
|
|
86
|
-
// Retry with exponential backoff
|
|
87
|
-
// ---------------------------------------------------------------------------
|
|
88
71
|
async function retryWithBackoff(fn, maxRetries, baseDelayMs, maxDelayMs) {
|
|
89
72
|
let lastError;
|
|
90
73
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
@@ -101,106 +84,37 @@ async function retryWithBackoff(fn, maxRetries, baseDelayMs, maxDelayMs) {
|
|
|
101
84
|
}
|
|
102
85
|
throw lastError;
|
|
103
86
|
}
|
|
104
|
-
// ---------------------------------------------------------------------------
|
|
105
|
-
// Flatten skills from agent card config
|
|
106
|
-
// ---------------------------------------------------------------------------
|
|
107
87
|
function flattenSkills(skills) {
|
|
108
88
|
return skills.map((s) => (typeof s === "string" ? s : s.name));
|
|
109
89
|
}
|
|
110
|
-
/**
|
|
111
|
-
* Run the full hub registration flow:
|
|
112
|
-
* 1. Load existing registration (if any)
|
|
113
|
-
* 2. Validate existing token with hub
|
|
114
|
-
* 3. If no valid registration, create new one (handling 409 conflicts)
|
|
115
|
-
* 4. Save registration file atomically
|
|
116
|
-
*/
|
|
117
90
|
export async function runHubRegistration(api, config, hubConfig, registrationConfig) {
|
|
118
91
|
const configDir = getConfigDir();
|
|
119
92
|
const hubUrl = hubConfig.url;
|
|
120
|
-
|
|
121
|
-
// e.g. "http://100.10.10.1:18800/a2a/jsonrpc" -> "100.10.10.1:18800"
|
|
122
|
-
let address;
|
|
123
|
-
if (config.agentCard.url) {
|
|
124
|
-
try {
|
|
125
|
-
const urlObj = new URL(config.agentCard.url);
|
|
126
|
-
address = `${urlObj.hostname}:${urlObj.port || (urlObj.protocol === "https:" ? 443 : 80)}`;
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
address = `${config.server.host}:${config.server.port}`;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
address = `${config.server.host}:${config.server.port}`;
|
|
134
|
-
}
|
|
135
|
-
// Ensure config directory exists
|
|
93
|
+
const identity = loadOrCreateIdentity(registrationConfig.clientId);
|
|
136
94
|
if (!fs.existsSync(configDir)) {
|
|
137
95
|
fs.mkdirSync(configDir, { recursive: true });
|
|
138
96
|
}
|
|
139
|
-
// Load existing registration
|
|
140
97
|
const existing = loadRegistration(configDir);
|
|
141
|
-
|
|
142
|
-
|
|
98
|
+
if (existing &&
|
|
99
|
+
existing.hubUrl === hubUrl &&
|
|
100
|
+
existing.clientId === identity.clientId &&
|
|
101
|
+
existing.publicKey === identity.publicKey) {
|
|
143
102
|
try {
|
|
144
|
-
const agent = await
|
|
103
|
+
const agent = await lookupAgentByClientId(hubUrl, identity.clientId);
|
|
145
104
|
if (agent && agent.id === existing.agentId) {
|
|
146
105
|
api.logger.info(`claw-crony: using existing hub registration (agentId=${existing.agentId})`);
|
|
147
106
|
return {
|
|
148
107
|
agentId: existing.agentId,
|
|
149
|
-
token:
|
|
150
|
-
address:
|
|
108
|
+
token: "",
|
|
109
|
+
address: "",
|
|
151
110
|
name: existing.name,
|
|
152
111
|
};
|
|
153
112
|
}
|
|
154
113
|
}
|
|
155
114
|
catch {
|
|
156
|
-
|
|
157
|
-
api.logger.warn("claw-crony: existing registration invalid, trying with existing token");
|
|
158
|
-
const existingToken = existing.token;
|
|
159
|
-
try {
|
|
160
|
-
const existingPayload = {
|
|
161
|
-
name: config.agentCard.name,
|
|
162
|
-
description: config.agentCard.description ?? "",
|
|
163
|
-
skills: flattenSkills(config.agentCard.skills),
|
|
164
|
-
address,
|
|
165
|
-
token: existingToken,
|
|
166
|
-
username: registrationConfig.username ?? config.agentCard.name,
|
|
167
|
-
email: registrationConfig.email ?? "",
|
|
168
|
-
};
|
|
169
|
-
const agent = await retryWithBackoff(() => registerWithHub(hubUrl, existingPayload), 3, 1000, 10000);
|
|
170
|
-
api.logger.info(`claw-crony: re-registered with hub using existing token (agentId=${agent.id})`);
|
|
171
|
-
// Also register hub user if password is configured
|
|
172
|
-
if (registrationConfig.password) {
|
|
173
|
-
try {
|
|
174
|
-
await retryWithBackoff(() => registerHubUser(hubUrl, agent.id, registrationConfig.username ?? config.agentCard.name, registrationConfig.password), 3, 1000, 10000);
|
|
175
|
-
api.logger.info(`claw-crony: registered hub user for web login (agentId=${agent.id})`);
|
|
176
|
-
}
|
|
177
|
-
catch (err) {
|
|
178
|
-
api.logger.warn(`claw-crony: hub user registration failed — ${err instanceof Error ? err.message : String(err)}`);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
// Re-save to update registration file
|
|
182
|
-
const updatedData = {
|
|
183
|
-
...existing,
|
|
184
|
-
registeredAt: new Date().toISOString(),
|
|
185
|
-
};
|
|
186
|
-
saveRegistration(configDir, updatedData);
|
|
187
|
-
return { agentId: agent.id, token: existingToken, address, name: config.agentCard.name };
|
|
188
|
-
}
|
|
189
|
-
catch (err) {
|
|
190
|
-
// Existing token also failed (409 means address conflict from elsewhere)
|
|
191
|
-
const status = typeof err === "object" && err !== null ? err.status : undefined;
|
|
192
|
-
if (status === 409) {
|
|
193
|
-
api.logger.warn("claw-crony: existing token rejected, generating new token");
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
api.logger.warn(`claw-crony: re-registration with existing token failed — ${err instanceof Error ? err.message : String(err)}`);
|
|
197
|
-
}
|
|
198
|
-
// fall through to generate new token
|
|
199
|
-
}
|
|
115
|
+
api.logger.warn("claw-crony: existing registration not confirmed remotely, re-registering");
|
|
200
116
|
}
|
|
201
117
|
}
|
|
202
|
-
// Generate new token
|
|
203
|
-
const token = generateToken();
|
|
204
118
|
const name = config.agentCard.name;
|
|
205
119
|
const description = config.agentCard.description ?? "";
|
|
206
120
|
const skills = flattenSkills(config.agentCard.skills);
|
|
@@ -210,8 +124,10 @@ export async function runHubRegistration(api, config, hubConfig, registrationCon
|
|
|
210
124
|
name,
|
|
211
125
|
description,
|
|
212
126
|
skills,
|
|
213
|
-
|
|
214
|
-
|
|
127
|
+
clientId: identity.clientId,
|
|
128
|
+
publicKey: identity.publicKey,
|
|
129
|
+
keyVersion: identity.keyVersion,
|
|
130
|
+
clientVersion: "claw-crony/1.3.0",
|
|
215
131
|
username,
|
|
216
132
|
email,
|
|
217
133
|
};
|
|
@@ -220,15 +136,13 @@ export async function runHubRegistration(api, config, hubConfig, registrationCon
|
|
|
220
136
|
const agent = await retryWithBackoff(() => registerWithHub(hubUrl, payload), 3, 1000, 10000);
|
|
221
137
|
agentId = agent.id;
|
|
222
138
|
api.logger.info(`claw-crony: registered with hub (agentId=${agentId})`);
|
|
223
|
-
// Also register hub user for web dashboard login (if password provided)
|
|
224
139
|
if (registrationConfig.password) {
|
|
225
140
|
try {
|
|
226
141
|
await retryWithBackoff(() => registerHubUser(hubUrl, agentId, username, registrationConfig.password), 3, 1000, 10000);
|
|
227
142
|
api.logger.info(`claw-crony: registered hub user for web login (agentId=${agentId})`);
|
|
228
143
|
}
|
|
229
144
|
catch (err) {
|
|
230
|
-
api.logger.warn(`claw-crony: hub user registration failed
|
|
231
|
-
// Non-fatal — agent is registered, web login may already exist
|
|
145
|
+
api.logger.warn(`claw-crony: hub user registration failed - ${err instanceof Error ? err.message : String(err)}`);
|
|
232
146
|
}
|
|
233
147
|
}
|
|
234
148
|
else {
|
|
@@ -237,46 +151,33 @@ export async function runHubRegistration(api, config, hubConfig, registrationCon
|
|
|
237
151
|
}
|
|
238
152
|
}
|
|
239
153
|
catch (err) {
|
|
240
|
-
// 409 Conflict: address already registered by someone else — try to find our agentId
|
|
241
154
|
if (typeof err === "object" && err !== null && err.status === 409) {
|
|
242
155
|
try {
|
|
243
|
-
const existingAgent = await
|
|
244
|
-
if (existingAgent) {
|
|
245
|
-
|
|
246
|
-
api.logger.info(`claw-crony: found existing registration (agentId=${agentId})`);
|
|
247
|
-
// Also register hub user if password is configured
|
|
248
|
-
if (registrationConfig.password) {
|
|
249
|
-
try {
|
|
250
|
-
await retryWithBackoff(() => registerHubUser(hubUrl, agentId, registrationConfig.username ?? config.agentCard.name, registrationConfig.password), 3, 1000, 10000);
|
|
251
|
-
api.logger.info(`claw-crony: registered hub user for web login (agentId=${agentId})`);
|
|
252
|
-
}
|
|
253
|
-
catch (err) {
|
|
254
|
-
api.logger.warn(`claw-crony: hub user registration failed — ${err instanceof Error ? err.message : String(err)}`);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
api.logger.error("claw-crony: address conflict but could not find existing agent");
|
|
156
|
+
const existingAgent = await lookupAgentByClientId(hubUrl, identity.clientId);
|
|
157
|
+
if (!existingAgent) {
|
|
158
|
+
api.logger.error("claw-crony: identity conflict but could not find existing agent");
|
|
260
159
|
return null;
|
|
261
160
|
}
|
|
161
|
+
agentId = existingAgent.id;
|
|
162
|
+
api.logger.info(`claw-crony: found existing registration (agentId=${agentId})`);
|
|
262
163
|
}
|
|
263
164
|
catch {
|
|
264
|
-
api.logger.error("claw-crony:
|
|
165
|
+
api.logger.error("claw-crony: identity conflict and hub lookup failed");
|
|
265
166
|
return null;
|
|
266
167
|
}
|
|
267
168
|
}
|
|
268
169
|
else {
|
|
269
|
-
api.logger.warn(`claw-crony: hub registration failed
|
|
170
|
+
api.logger.warn(`claw-crony: hub registration failed - ${err instanceof Error ? err.message : String(err)}`);
|
|
270
171
|
return null;
|
|
271
172
|
}
|
|
272
173
|
}
|
|
273
|
-
// Save registration file
|
|
274
174
|
const registrationData = {
|
|
275
|
-
version:
|
|
175
|
+
version: 2,
|
|
276
176
|
hubUrl,
|
|
277
177
|
agentId,
|
|
278
|
-
|
|
279
|
-
|
|
178
|
+
clientId: identity.clientId,
|
|
179
|
+
publicKey: identity.publicKey,
|
|
180
|
+
keyVersion: identity.keyVersion,
|
|
280
181
|
registeredAt: new Date().toISOString(),
|
|
281
182
|
name,
|
|
282
183
|
description,
|
|
@@ -286,7 +187,7 @@ export async function runHubRegistration(api, config, hubConfig, registrationCon
|
|
|
286
187
|
saveRegistration(configDir, registrationData);
|
|
287
188
|
}
|
|
288
189
|
catch (saveErr) {
|
|
289
|
-
api.logger.warn(`claw-crony: failed to save registration file
|
|
190
|
+
api.logger.warn(`claw-crony: failed to save registration file - ${saveErr instanceof Error ? saveErr.message : String(saveErr)}`);
|
|
290
191
|
}
|
|
291
|
-
return { agentId, token, address, name };
|
|
192
|
+
return { agentId, token: "", address: "", name };
|
|
292
193
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IdentityData } from "./types.js";
|
|
2
|
+
export declare function loadIdentity(configDir?: string): IdentityData | null;
|
|
3
|
+
export declare function saveIdentity(configDir: string, data: IdentityData): void;
|
|
4
|
+
export declare function loadOrCreateIdentity(clientId?: string): IdentityData;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
const IDENTITY_FILENAME = "a2a-identity.json";
|
|
6
|
+
function getConfigDir() {
|
|
7
|
+
return path.join(os.homedir(), ".openclaw");
|
|
8
|
+
}
|
|
9
|
+
function getIdentityPath(configDir) {
|
|
10
|
+
return path.join(configDir, IDENTITY_FILENAME);
|
|
11
|
+
}
|
|
12
|
+
function randomClientId() {
|
|
13
|
+
return crypto.randomUUID();
|
|
14
|
+
}
|
|
15
|
+
export function loadIdentity(configDir) {
|
|
16
|
+
const identityPath = getIdentityPath(configDir ?? getConfigDir());
|
|
17
|
+
try {
|
|
18
|
+
const raw = fs.readFileSync(identityPath, "utf-8");
|
|
19
|
+
return JSON.parse(raw);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function saveIdentity(configDir, data) {
|
|
26
|
+
const identityPath = getIdentityPath(configDir);
|
|
27
|
+
const tmpPath = `${identityPath}.tmp`;
|
|
28
|
+
fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
29
|
+
fs.renameSync(tmpPath, identityPath);
|
|
30
|
+
}
|
|
31
|
+
export function loadOrCreateIdentity(clientId) {
|
|
32
|
+
const configDir = getConfigDir();
|
|
33
|
+
if (!fs.existsSync(configDir)) {
|
|
34
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
const existing = loadIdentity(configDir);
|
|
37
|
+
if (existing) {
|
|
38
|
+
if (clientId && existing.clientId !== clientId) {
|
|
39
|
+
existing.clientId = clientId;
|
|
40
|
+
saveIdentity(configDir, existing);
|
|
41
|
+
}
|
|
42
|
+
return existing;
|
|
43
|
+
}
|
|
44
|
+
const { publicKey, privateKey } = crypto.generateKeyPairSync("x25519");
|
|
45
|
+
const identity = {
|
|
46
|
+
version: 1,
|
|
47
|
+
clientId: clientId?.trim() || randomClientId(),
|
|
48
|
+
publicKey: publicKey.export({ format: "pem", type: "spki" }).toString(),
|
|
49
|
+
privateKey: privateKey.export({ format: "pem", type: "pkcs8" }).toString(),
|
|
50
|
+
keyVersion: 1,
|
|
51
|
+
createdAt: new Date().toISOString(),
|
|
52
|
+
};
|
|
53
|
+
saveIdentity(configDir, identity);
|
|
54
|
+
return identity;
|
|
55
|
+
}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* These types support the A2A v0.3.0 protocol integration via @a2a-js/sdk.
|
|
5
5
|
*/
|
|
6
|
-
export type { OpenClawPluginApi, PluginLogger, OpenClawConfig } from "openclaw/plugin-sdk";
|
|
6
|
+
export type { OpenClawPluginApi, PluginLogger, OpenClawConfig } from "openclaw/plugin-sdk/plugin-entry";
|
|
7
7
|
export type InboundAuth = "none" | "bearer";
|
|
8
8
|
export type PeerAuthType = "bearer" | "apiKey";
|
|
9
9
|
export interface PeerAuthConfig {
|
|
@@ -70,6 +70,9 @@ export interface GatewayConfig {
|
|
|
70
70
|
metricsPath: string;
|
|
71
71
|
metricsAuth: "none" | "bearer";
|
|
72
72
|
auditLogPath: string;
|
|
73
|
+
historyEnabled: boolean;
|
|
74
|
+
historyLogPath: string;
|
|
75
|
+
historyIncludeEncryptedPayloads: boolean;
|
|
73
76
|
};
|
|
74
77
|
timeouts?: {
|
|
75
78
|
/**
|
|
@@ -92,18 +95,56 @@ export interface RegistrationConfig {
|
|
|
92
95
|
email?: string;
|
|
93
96
|
password?: string;
|
|
94
97
|
skills?: string[];
|
|
98
|
+
clientId?: string;
|
|
95
99
|
}
|
|
96
100
|
export interface HubRegistrationData {
|
|
97
101
|
version: number;
|
|
98
102
|
hubUrl: string;
|
|
99
103
|
agentId: number;
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
clientId: string;
|
|
105
|
+
publicKey: string;
|
|
106
|
+
keyVersion: number;
|
|
102
107
|
registeredAt: string;
|
|
103
108
|
name: string;
|
|
104
109
|
description: string;
|
|
105
110
|
skills: string[];
|
|
106
111
|
}
|
|
112
|
+
export interface IdentityData {
|
|
113
|
+
version: number;
|
|
114
|
+
clientId: string;
|
|
115
|
+
publicKey: string;
|
|
116
|
+
privateKey: string;
|
|
117
|
+
keyVersion: number;
|
|
118
|
+
createdAt: string;
|
|
119
|
+
}
|
|
120
|
+
export interface EphemeralTokenRecord {
|
|
121
|
+
token: string;
|
|
122
|
+
matchId: number;
|
|
123
|
+
peerAgentId: number;
|
|
124
|
+
expiresAt: number;
|
|
125
|
+
}
|
|
126
|
+
export interface HandshakePayload {
|
|
127
|
+
version: number;
|
|
128
|
+
matchId: number;
|
|
129
|
+
sessionId: string;
|
|
130
|
+
fromAgentId: number;
|
|
131
|
+
toAgentId: number;
|
|
132
|
+
address: string;
|
|
133
|
+
agentCardPath: string;
|
|
134
|
+
token: string;
|
|
135
|
+
tokenExpiresAt: string;
|
|
136
|
+
protocols: string[];
|
|
137
|
+
createdAt: string;
|
|
138
|
+
nonce: string;
|
|
139
|
+
}
|
|
140
|
+
export interface EncryptedHandshakeMessage {
|
|
141
|
+
version: number;
|
|
142
|
+
algorithm: "x25519-aes-256-gcm";
|
|
143
|
+
senderPublicKey: string;
|
|
144
|
+
iv: string;
|
|
145
|
+
ciphertext: string;
|
|
146
|
+
authTag: string;
|
|
147
|
+
}
|
|
107
148
|
export interface HealthCheckConfig {
|
|
108
149
|
enabled: boolean;
|
|
109
150
|
intervalMs: number;
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "claw-crony",
|
|
3
|
-
"name": "Claw Crony",
|
|
4
|
-
"description": "OpenClaw A2A v0.3.0 gateway with Agent Card, JSON-RPC, REST, routing rules, transport fallback, and Hub matchmaking",
|
|
5
|
-
"version": "1.
|
|
6
|
-
"
|
|
2
|
+
"id": "claw-crony",
|
|
3
|
+
"name": "Claw Crony",
|
|
4
|
+
"description": "OpenClaw A2A v0.3.0 gateway with Agent Card, JSON-RPC, REST, routing rules, transport fallback, and Hub matchmaking",
|
|
5
|
+
"version": "1.3.0",
|
|
6
|
+
"activation": {
|
|
7
|
+
"onStartup": true
|
|
8
|
+
},
|
|
9
|
+
"contracts": {
|
|
10
|
+
"tools": [
|
|
11
|
+
"a2a_send_file",
|
|
12
|
+
"a2a_match_request"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
"defaultConfig": {
|
|
7
16
|
"agentCard": {
|
|
8
17
|
"name": "OpenClaw A2A Gateway",
|
|
9
18
|
"description": "A2A bridge for OpenClaw agents",
|
|
@@ -58,12 +67,16 @@
|
|
|
58
67
|
"registration": {
|
|
59
68
|
"type": "object",
|
|
60
69
|
"additionalProperties": false,
|
|
61
|
-
"properties": {
|
|
62
|
-
"username": { "type": "string" },
|
|
63
|
-
"email": { "type": "string" },
|
|
64
|
-
"password": { "type": "string" }
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
"properties": {
|
|
71
|
+
"username": { "type": "string" },
|
|
72
|
+
"email": { "type": "string" },
|
|
73
|
+
"password": { "type": "string" },
|
|
74
|
+
"clientId": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"description": "Optional stable client id for Hub registration. If omitted, claw-crony generates and persists one locally."
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
67
80
|
"server": {
|
|
68
81
|
"type": "object",
|
|
69
82
|
"additionalProperties": false,
|
|
@@ -206,13 +219,28 @@
|
|
|
206
219
|
"default": "none",
|
|
207
220
|
"description": "Authentication for the metrics endpoint. When set to 'bearer', reuses security.token/tokens."
|
|
208
221
|
},
|
|
209
|
-
"auditLogPath": {
|
|
210
|
-
"type": "string",
|
|
211
|
-
"default": "~/.openclaw/a2a-audit.jsonl",
|
|
212
|
-
"description": "Path for the JSONL audit log file (separate from structured logs)."
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
222
|
+
"auditLogPath": {
|
|
223
|
+
"type": "string",
|
|
224
|
+
"default": "~/.openclaw/a2a-audit.jsonl",
|
|
225
|
+
"description": "Path for the JSONL audit log file (separate from structured logs)."
|
|
226
|
+
},
|
|
227
|
+
"historyEnabled": {
|
|
228
|
+
"type": "boolean",
|
|
229
|
+
"default": true,
|
|
230
|
+
"description": "Enable operator-facing JSONL request history for match, handshake, peer, send, and file-send events."
|
|
231
|
+
},
|
|
232
|
+
"historyLogPath": {
|
|
233
|
+
"type": "string",
|
|
234
|
+
"default": "~/.openclaw/a2a-history.jsonl",
|
|
235
|
+
"description": "Path for the JSONL request history file."
|
|
236
|
+
},
|
|
237
|
+
"historyIncludeEncryptedPayloads": {
|
|
238
|
+
"type": "boolean",
|
|
239
|
+
"default": false,
|
|
240
|
+
"description": "Include encrypted handshake payload fields in request history. Tokens and secrets remain redacted."
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
},
|
|
216
244
|
"timeouts": {
|
|
217
245
|
"type": "object",
|
|
218
246
|
"additionalProperties": false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawcrony/claw-crony",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw A2A gateway plugin implementing the A2A v0.3.0 protocol surface",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,11 +11,16 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
-
"files": [
|
|
15
|
-
"dist",
|
|
16
|
-
"openclaw.plugin.json",
|
|
17
|
-
"skill"
|
|
18
|
-
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"openclaw.plugin.json",
|
|
17
|
+
"skill",
|
|
18
|
+
"scripts",
|
|
19
|
+
"README.md",
|
|
20
|
+
"CONFIG.md",
|
|
21
|
+
"CHANGELOG.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
19
24
|
"scripts": {
|
|
20
25
|
"build": "tsc",
|
|
21
26
|
"test": "node --import tsx --test tests/*.test.ts"
|
|
@@ -29,27 +34,38 @@
|
|
|
29
34
|
],
|
|
30
35
|
"license": "MIT",
|
|
31
36
|
"devDependencies": {
|
|
32
|
-
"@types/express": "
|
|
33
|
-
"@types/node": "
|
|
34
|
-
"@types/supertest": "
|
|
35
|
-
"@types/uuid": "
|
|
36
|
-
"openclaw": "
|
|
37
|
-
"supertest": "
|
|
38
|
-
"tsx": "
|
|
39
|
-
"typescript": "
|
|
40
|
-
},
|
|
41
|
-
"dependencies": {
|
|
42
|
-
"@a2a-js/sdk": "
|
|
43
|
-
"@bufbuild/protobuf": "
|
|
44
|
-
"@grpc/grpc-js": "
|
|
45
|
-
"express": "
|
|
46
|
-
"uuid": "
|
|
47
|
-
},
|
|
48
|
-
"openclaw": {
|
|
49
|
-
"extensions": [
|
|
50
|
-
"./index.
|
|
51
|
-
]
|
|
52
|
-
|
|
37
|
+
"@types/express": "5.0.6",
|
|
38
|
+
"@types/node": "25.5.0",
|
|
39
|
+
"@types/supertest": "2.0.16",
|
|
40
|
+
"@types/uuid": "10.0.0",
|
|
41
|
+
"openclaw": "2026.5.2",
|
|
42
|
+
"supertest": "7.2.2",
|
|
43
|
+
"tsx": "4.21.0",
|
|
44
|
+
"typescript": "5.9.3"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@a2a-js/sdk": "0.3.13",
|
|
48
|
+
"@bufbuild/protobuf": "2.11.0",
|
|
49
|
+
"@grpc/grpc-js": "1.14.3",
|
|
50
|
+
"express": "4.22.1",
|
|
51
|
+
"uuid": "9.0.1"
|
|
52
|
+
},
|
|
53
|
+
"openclaw": {
|
|
54
|
+
"extensions": [
|
|
55
|
+
"./dist/index.js"
|
|
56
|
+
],
|
|
57
|
+
"compat": {
|
|
58
|
+
"pluginApi": ">=2026.5.2",
|
|
59
|
+
"minGatewayVersion": "2026.5.2"
|
|
60
|
+
},
|
|
61
|
+
"build": {
|
|
62
|
+
"openclawVersion": "2026.5.2",
|
|
63
|
+
"pluginSdkVersion": "2026.5.2"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"overrides": {
|
|
67
|
+
"axios": "1.16.0"
|
|
68
|
+
},
|
|
53
69
|
"engines": {
|
|
54
70
|
"node": ">=22"
|
|
55
71
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Write-Host "== OpenClaw Gateway =="
|
|
2
|
+
openclaw gateway status
|
|
3
|
+
|
|
4
|
+
Write-Host "`n== Plugin =="
|
|
5
|
+
openclaw plugins inspect claw-crony
|
|
6
|
+
openclaw plugins inspect claw-crony --runtime
|
|
7
|
+
|
|
8
|
+
Write-Host "`n== Peers =="
|
|
9
|
+
openclaw gateway call a2a.peers --params "{}"
|
|
10
|
+
|
|
11
|
+
Write-Host "`n== Metrics =="
|
|
12
|
+
openclaw gateway call a2a.metrics --params "{}"
|
|
13
|
+
|
|
14
|
+
Write-Host "`n== Recent History =="
|
|
15
|
+
openclaw gateway call a2a.history --params "{`"count`":20}"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
echo "== OpenClaw Gateway =="
|
|
5
|
+
openclaw gateway status
|
|
6
|
+
|
|
7
|
+
echo
|
|
8
|
+
echo "== Plugin =="
|
|
9
|
+
openclaw plugins inspect claw-crony
|
|
10
|
+
openclaw plugins inspect claw-crony --runtime
|
|
11
|
+
|
|
12
|
+
echo
|
|
13
|
+
echo "== Peers =="
|
|
14
|
+
openclaw gateway call a2a.peers --params "{}"
|
|
15
|
+
|
|
16
|
+
echo
|
|
17
|
+
echo "== Metrics =="
|
|
18
|
+
openclaw gateway call a2a.metrics --params "{}"
|
|
19
|
+
|
|
20
|
+
echo
|
|
21
|
+
echo "== Recent History =="
|
|
22
|
+
openclaw gateway call a2a.history --params '{"count":20}'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[int] $Count = 50,
|
|
3
|
+
[string] $Type = "",
|
|
4
|
+
[string] $Status = "",
|
|
5
|
+
[string] $Direction = "",
|
|
6
|
+
[int] $MatchId = 0,
|
|
7
|
+
[string] $Peer = ""
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
$payload = @{
|
|
11
|
+
count = $Count
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if ($Type) { $payload.type = $Type }
|
|
15
|
+
if ($Status) { $payload.status = $Status }
|
|
16
|
+
if ($Direction) { $payload.direction = $Direction }
|
|
17
|
+
if ($MatchId -gt 0) { $payload.matchId = $MatchId }
|
|
18
|
+
if ($Peer) { $payload.peer = $Peer }
|
|
19
|
+
|
|
20
|
+
$json = $payload | ConvertTo-Json -Depth 20 -Compress
|
|
21
|
+
openclaw gateway call a2a.history --params $json
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
count="${1:-50}"
|
|
5
|
+
type="${2:-}"
|
|
6
|
+
match_id="${3:-}"
|
|
7
|
+
|
|
8
|
+
params="$(node -e 'const payload={count:Number(process.argv[1] || 50)}; if (process.argv[2]) payload.type=process.argv[2]; if (process.argv[3]) payload.matchId=Number(process.argv[3]); process.stdout.write(JSON.stringify(payload));' "$count" "$type" "$match_id")"
|
|
9
|
+
openclaw gateway call a2a.history --params "$params"
|