@clawcrony/claw-crony 1.2.2 → 1.2.4

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.
@@ -1,20 +1,14 @@
1
1
  /**
2
- * Hub Match API client for openclaw-claw-crony.
3
- *
4
- * Provides a typed client for the hub's /api/matches endpoints:
5
- * - POST /api/matches createMatch
6
- * - GET /api/matches/{id} getMatch
7
- * - GET /api/matches/pending getPendingMatches
8
- * - POST /api/matches/{id}/token submitToken
9
- * - POST /api/matches/{id}/complete completeMatch
10
- * - POST /api/matches/{id}/cancel cancelMatch
2
+ * Hub Match API client for claw-crony.
11
3
  */
12
4
  import type { HubRegistrationData } from "./types.js";
13
5
  export interface HubAgentDto {
14
6
  id: number;
15
7
  name: string;
16
- address: string;
17
8
  skills: string[];
9
+ clientId?: string;
10
+ publicKey?: string;
11
+ presenceStatus?: string;
18
12
  }
19
13
  export interface HubMatchResult {
20
14
  id: number;
@@ -22,58 +16,53 @@ export interface HubMatchResult {
22
16
  status: string;
23
17
  requester: HubAgentDto | null;
24
18
  provider: HubAgentDto | null;
25
- yourToken: string | null;
26
- peerToken: string | null;
19
+ yourToken?: string | null;
20
+ peerToken?: string | null;
27
21
  callerRole?: "requester" | "provider" | "observer" | null;
28
22
  requesterTokenSubmitted?: boolean;
29
23
  providerTokenSubmitted?: boolean;
30
24
  readyForComplete?: boolean;
25
+ requesterHandshakeSent?: boolean;
26
+ providerHandshakeSent?: boolean;
27
+ requesterHandshakeConsumed?: boolean;
28
+ providerHandshakeConsumed?: boolean;
29
+ requesterReady?: boolean;
30
+ providerReady?: boolean;
31
+ readyForConnect?: boolean;
32
+ }
33
+ export interface HubHandshakeMessage {
34
+ id: number;
35
+ senderAgentId: number;
36
+ receiverAgentId: number;
37
+ messageType: "offer" | "answer";
38
+ ciphertext: string;
39
+ status: string;
40
+ expiresAt: string;
41
+ createdAt?: string;
42
+ consumedAt?: string | null;
31
43
  }
32
44
  export declare class HubMatchClient {
33
45
  private readonly hubUrl;
34
46
  private readonly registration;
35
47
  constructor(hubUrl: string, registration: HubRegistrationData);
36
48
  get agentId(): number;
37
- get registrationToken(): string;
38
49
  static create(): Promise<HubMatchClient>;
39
50
  private request;
40
- /**
41
- * Create a new match request.
42
- * @param params.skills - Skills to search for in a provider
43
- * @param params.description - Optional description of the match request
44
- * @param params.token - Optional bearer token to include for this agent
45
- */
46
51
  createMatch(params: {
47
52
  skills: string[];
48
53
  description?: string;
49
- token?: string;
50
54
  }): Promise<HubMatchResult>;
51
- /**
52
- * Get a match result by ID.
53
- * @param matchId - The match ID
54
- * @param callerId - Optional agent ID to set callerId query param (affects yourToken)
55
- */
56
55
  getMatch(matchId: number, callerId?: number): Promise<HubMatchResult>;
57
- /**
58
- * Get all pending matches for this agent.
59
- */
60
56
  getPendingMatches(): Promise<HubMatchResult[]>;
61
- /**
62
- * Submit this agent's token for a match.
63
- * @param matchId - The match ID
64
- * @param token - This agent's bearer token
65
- */
66
- submitToken(matchId: number, token: string): Promise<HubMatchResult>;
67
- /**
68
- * Mark a match as completed (both parties have submitted tokens).
69
- * @param matchId - The match ID
70
- * @param token - This agent's bearer token (for authorization)
71
- */
72
- completeMatch(matchId: number, token: string): Promise<HubMatchResult>;
73
- /**
74
- * Cancel a pending or token_exchange match.
75
- * @param matchId - The match ID
76
- * @param token - This agent's bearer token (for authorization)
77
- */
78
- cancelMatch(matchId: number, token: string): Promise<HubMatchResult>;
57
+ updatePresence(presenceStatus: "online" | "offline" | "busy", clientVersion?: string): Promise<HubAgentDto>;
58
+ sendHandshakeMessage(matchId: number, params: {
59
+ messageType: "offer" | "answer";
60
+ ciphertext: string;
61
+ expiresAt: string;
62
+ }): Promise<HubHandshakeMessage>;
63
+ getPendingHandshakeMessages(matchId: number): Promise<HubHandshakeMessage[]>;
64
+ consumeHandshakeMessage(matchId: number, messageId: number): Promise<HubHandshakeMessage>;
65
+ markReady(matchId: number): Promise<HubMatchResult>;
66
+ completeMatch(matchId: number): Promise<HubMatchResult>;
67
+ cancelMatch(matchId: number): Promise<HubMatchResult>;
79
68
  }
@@ -1,18 +1,7 @@
1
1
  /**
2
- * Hub Match API client for openclaw-claw-crony.
3
- *
4
- * Provides a typed client for the hub's /api/matches endpoints:
5
- * - POST /api/matches createMatch
6
- * - GET /api/matches/{id} getMatch
7
- * - GET /api/matches/pending getPendingMatches
8
- * - POST /api/matches/{id}/token submitToken
9
- * - POST /api/matches/{id}/complete completeMatch
10
- * - POST /api/matches/{id}/cancel cancelMatch
2
+ * Hub Match API client for claw-crony.
11
3
  */
12
4
  import { loadRegistration } from "./hub-registration.js";
13
- // ---------------------------------------------------------------------------
14
- // HubMatchClient
15
- // ---------------------------------------------------------------------------
16
5
  export class HubMatchClient {
17
6
  hubUrl;
18
7
  registration;
@@ -23,16 +12,12 @@ export class HubMatchClient {
23
12
  get agentId() {
24
13
  return this.registration.agentId;
25
14
  }
26
- get registrationToken() {
27
- return this.registration.token;
28
- }
29
15
  static async create() {
30
16
  const registration = loadRegistration();
31
17
  if (!registration) {
32
18
  throw new Error("No hub registration found. Run the gateway first to register with the hub.");
33
19
  }
34
- const configUrl = registration.hubUrl;
35
- return new HubMatchClient(configUrl, registration);
20
+ return new HubMatchClient(registration.hubUrl, registration);
36
21
  }
37
22
  async request(path, options = {}) {
38
23
  const url = `${this.hubUrl}${path}`;
@@ -40,7 +25,6 @@ export class HubMatchClient {
40
25
  ...options,
41
26
  headers: {
42
27
  "Content-Type": "application/json",
43
- "Authorization": `Bearer ${this.registration.token}`,
44
28
  ...(options.headers ?? {}),
45
29
  },
46
30
  });
@@ -50,12 +34,6 @@ export class HubMatchClient {
50
34
  }
51
35
  return res.json();
52
36
  }
53
- /**
54
- * Create a new match request.
55
- * @param params.skills - Skills to search for in a provider
56
- * @param params.description - Optional description of the match request
57
- * @param params.token - Optional bearer token to include for this agent
58
- */
59
37
  async createMatch(params) {
60
38
  return this.request("/api/matches", {
61
39
  method: "POST",
@@ -63,45 +41,57 @@ export class HubMatchClient {
63
41
  agentId: this.registration.agentId,
64
42
  requiredSkills: params.skills,
65
43
  description: params.description ?? "",
66
- token: params.token,
67
44
  }),
68
45
  });
69
46
  }
70
- /**
71
- * Get a match result by ID.
72
- * @param matchId - The match ID
73
- * @param callerId - Optional agent ID to set callerId query param (affects yourToken)
74
- */
75
47
  async getMatch(matchId, callerId) {
76
48
  const path = callerId != null ? `/api/matches/${matchId}?callerId=${callerId}` : `/api/matches/${matchId}`;
77
49
  return this.request(path);
78
50
  }
79
- /**
80
- * Get all pending matches for this agent.
81
- */
82
51
  async getPendingMatches() {
83
52
  return this.request(`/api/matches/pending?agentId=${this.registration.agentId}`);
84
53
  }
85
- /**
86
- * Submit this agent's token for a match.
87
- * @param matchId - The match ID
88
- * @param token - This agent's bearer token
89
- */
90
- async submitToken(matchId, token) {
91
- return this.request(`/api/matches/${matchId}/token`, {
54
+ async updatePresence(presenceStatus, clientVersion = "claw-crony/1.2.4") {
55
+ return this.request(`/api/agents/${this.registration.agentId}/presence`, {
56
+ method: "PUT",
57
+ body: JSON.stringify({
58
+ presenceStatus,
59
+ clientVersion,
60
+ }),
61
+ });
62
+ }
63
+ async sendHandshakeMessage(matchId, params) {
64
+ return this.request(`/api/matches/${matchId}/handshake`, {
65
+ method: "POST",
66
+ body: JSON.stringify({
67
+ agentId: this.registration.agentId,
68
+ messageType: params.messageType,
69
+ ciphertext: params.ciphertext,
70
+ expiresAt: params.expiresAt,
71
+ }),
72
+ });
73
+ }
74
+ async getPendingHandshakeMessages(matchId) {
75
+ const result = await this.request(`/api/matches/${matchId}/handshake/pending?agentId=${this.registration.agentId}`);
76
+ return result.messages ?? [];
77
+ }
78
+ async consumeHandshakeMessage(matchId, messageId) {
79
+ return this.request(`/api/matches/${matchId}/handshake/${messageId}/consume`, {
80
+ method: "POST",
81
+ body: JSON.stringify({
82
+ agentId: this.registration.agentId,
83
+ }),
84
+ });
85
+ }
86
+ async markReady(matchId) {
87
+ return this.request(`/api/matches/${matchId}/ready`, {
92
88
  method: "POST",
93
89
  body: JSON.stringify({
94
90
  agentId: this.registration.agentId,
95
- token,
96
91
  }),
97
92
  });
98
93
  }
99
- /**
100
- * Mark a match as completed (both parties have submitted tokens).
101
- * @param matchId - The match ID
102
- * @param token - This agent's bearer token (for authorization)
103
- */
104
- async completeMatch(matchId, token) {
94
+ async completeMatch(matchId) {
105
95
  return this.request(`/api/matches/${matchId}/complete`, {
106
96
  method: "POST",
107
97
  body: JSON.stringify({
@@ -109,12 +99,7 @@ export class HubMatchClient {
109
99
  }),
110
100
  });
111
101
  }
112
- /**
113
- * Cancel a pending or token_exchange match.
114
- * @param matchId - The match ID
115
- * @param token - This agent's bearer token (for authorization)
116
- */
117
- async cancelMatch(matchId, token) {
102
+ async cancelMatch(matchId) {
118
103
  return this.request(`/api/matches/${matchId}/cancel`, {
119
104
  method: "POST",
120
105
  body: JSON.stringify({
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Hub registration module for openclaw-claw-crony.
2
+ * Hub registration module for claw-crony.
3
3
  *
4
- * Handles automatic registration of the gateway with the hub server on first startup,
5
- * token generation, and idempotent re-registration.
4
+ * Registers the local plugin with the hub using client_id + public_key
5
+ * and persists the resulting agent binding locally.
6
6
  */
7
7
  import type { GatewayConfig, HubConfig, HubRegistrationData, OpenClawPluginApi, RegistrationConfig } from "./types.js";
8
8
  export declare function loadRegistration(configDir?: string): HubRegistrationData | null;
@@ -13,11 +13,4 @@ export interface HubRegistration {
13
13
  address: string;
14
14
  name: string;
15
15
  }
16
- /**
17
- * Run the full hub registration flow:
18
- * 1. Load existing registration (if any)
19
- * 2. Validate existing token with hub
20
- * 3. If no valid registration, create new one (handling 409 conflicts)
21
- * 4. Save registration file atomically
22
- */
23
16
  export declare function runHubRegistration(api: OpenClawPluginApi, config: GatewayConfig, hubConfig: HubConfig, registrationConfig: RegistrationConfig): Promise<HubRegistration | null>;
@@ -1,22 +1,13 @@
1
1
  /**
2
- * Hub registration module for openclaw-claw-crony.
2
+ * Hub registration module for claw-crony.
3
3
  *
4
- * Handles automatic registration of the gateway with the hub server on first startup,
5
- * token generation, and idempotent re-registration.
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 lookupAgentByAddress(hubUrl, address) {
77
- const url = `${hubUrl.replace(/\/$/, "")}/api/agents?address=${encodeURIComponent(address)}`;
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
- // Derive address from agentCard.url (the externally reachable address), not server.bind
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
- // If we have a registration with a matching address and token, validate it
142
- if (existing && existing.address === address && existing.hubUrl === hubUrl && existing.token) {
98
+ if (existing &&
99
+ existing.hubUrl === hubUrl &&
100
+ existing.clientId === identity.clientId &&
101
+ existing.publicKey === identity.publicKey) {
143
102
  try {
144
- const agent = await lookupAgentByAddress(hubUrl, address);
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: existing.token,
150
- address: existing.address,
108
+ token: "",
109
+ address: "",
151
110
  name: existing.name,
152
111
  };
153
112
  }
154
113
  }
155
114
  catch {
156
- // Hub unreachable or agent not found try re-registering with existing token first
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
- address,
214
- token,
127
+ clientId: identity.clientId,
128
+ publicKey: identity.publicKey,
129
+ keyVersion: identity.keyVersion,
130
+ clientVersion: "claw-crony/1.2.4",
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 ${err instanceof Error ? err.message : String(err)}`);
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 lookupAgentByAddress(hubUrl, address);
244
- if (existingAgent) {
245
- agentId = existingAgent.id;
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: address conflict and hub lookup failed");
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 ${err instanceof Error ? err.message : String(err)}`);
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: 1,
175
+ version: 2,
276
176
  hubUrl,
277
177
  agentId,
278
- address,
279
- token,
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 ${saveErr instanceof Error ? saveErr.message : String(saveErr)}`);
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
+ }