moltyjacs 0.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/LICENSE +21 -0
- package/README.md +223 -0
- package/dist/cli.d.ts +24 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +593 -0
- package/dist/cli.js.map +1 -0
- package/dist/gateway/wellknown.d.ts +24 -0
- package/dist/gateway/wellknown.d.ts.map +1 -0
- package/dist/gateway/wellknown.js +206 -0
- package/dist/gateway/wellknown.js.map +1 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +150 -0
- package/dist/index.js.map +1 -0
- package/dist/setup.d.ts +25 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +217 -0
- package/dist/setup.js.map +1 -0
- package/dist/tools/documents.d.ts +115 -0
- package/dist/tools/documents.d.ts.map +1 -0
- package/dist/tools/documents.js +885 -0
- package/dist/tools/documents.js.map +1 -0
- package/dist/tools/hai.d.ts +69 -0
- package/dist/tools/hai.d.ts.map +1 -0
- package/dist/tools/hai.js +214 -0
- package/dist/tools/hai.js.map +1 -0
- package/dist/tools/index.d.ts +106 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +1170 -0
- package/dist/tools/index.js.map +1 -0
- package/marketplace.json +80 -0
- package/openclaw.plugin.json +59 -0
- package/package.json +64 -0
- package/src/skills/jacs/SKILL.md +308 -0
|
@@ -0,0 +1,1170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* JACS Agent Tools
|
|
4
|
+
*
|
|
5
|
+
* Tools that AI agents can use to sign and verify documents.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.parseDnsTxt = parseDnsTxt;
|
|
42
|
+
exports.resolveDnsRecord = resolveDnsRecord;
|
|
43
|
+
exports.fetchPublicKey = fetchPublicKey;
|
|
44
|
+
exports.registerTools = registerTools;
|
|
45
|
+
const simple_1 = require("@hai.ai/jacs/simple");
|
|
46
|
+
const dns = __importStar(require("dns"));
|
|
47
|
+
const util_1 = require("util");
|
|
48
|
+
const hai_1 = require("./hai");
|
|
49
|
+
const documents_1 = require("./documents");
|
|
50
|
+
const resolveTxt = (0, util_1.promisify)(dns.resolveTxt);
|
|
51
|
+
const pubkeyCache = new Map();
|
|
52
|
+
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
53
|
+
/**
|
|
54
|
+
* Get the JACS agent instance from the API runtime
|
|
55
|
+
*/
|
|
56
|
+
function getAgent(api) {
|
|
57
|
+
return api.runtime.jacs?.getAgent() || null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Sanitize domain by removing protocol prefix and trailing slash
|
|
61
|
+
*/
|
|
62
|
+
function sanitizeDomain(input) {
|
|
63
|
+
return input.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Create a tool handler that requires JACS to be initialized.
|
|
67
|
+
* Automatically handles the agent null check and error wrapping.
|
|
68
|
+
*/
|
|
69
|
+
function requireAgent(api, handler) {
|
|
70
|
+
return async (params) => {
|
|
71
|
+
const agent = getAgent(api);
|
|
72
|
+
if (!agent) {
|
|
73
|
+
return { error: "JACS not initialized. Run 'openclaw jacs init' first." };
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const result = await handler(agent, params);
|
|
77
|
+
return { result };
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
return { error: err.message };
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Parse JACS DNS TXT record
|
|
86
|
+
* Format: v=hai.ai; jacs_agent_id=UUID; alg=SHA-256; enc=hex; jac_public_key_hash=HASH
|
|
87
|
+
*/
|
|
88
|
+
function parseDnsTxt(txt) {
|
|
89
|
+
const result = {};
|
|
90
|
+
const parts = txt.split(";").map((s) => s.trim());
|
|
91
|
+
for (const part of parts) {
|
|
92
|
+
const equalsIdx = part.indexOf("=");
|
|
93
|
+
if (equalsIdx <= 0) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const key = part.slice(0, equalsIdx).trim();
|
|
97
|
+
const value = part.slice(equalsIdx + 1).trim();
|
|
98
|
+
if (key && value) {
|
|
99
|
+
result[key] = value;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
v: result["v"],
|
|
104
|
+
jacsAgentId: result["jacs_agent_id"],
|
|
105
|
+
alg: result["alg"],
|
|
106
|
+
enc: result["enc"],
|
|
107
|
+
publicKeyHash: result["jac_public_key_hash"],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Resolve DNS TXT record for JACS agent
|
|
112
|
+
*/
|
|
113
|
+
async function resolveDnsRecord(domain) {
|
|
114
|
+
const owner = `_v1.agent.jacs.${domain.replace(/\.$/, "")}`;
|
|
115
|
+
try {
|
|
116
|
+
const records = await resolveTxt(owner);
|
|
117
|
+
// TXT records come as arrays of strings, join them
|
|
118
|
+
const txt = records.map((r) => r.join("")).join("");
|
|
119
|
+
if (!txt)
|
|
120
|
+
return null;
|
|
121
|
+
return { txt, parsed: parseDnsTxt(txt) };
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Fetch public key from domain's well-known endpoint
|
|
129
|
+
*/
|
|
130
|
+
async function fetchPublicKey(domain, skipCache = false) {
|
|
131
|
+
const cacheKey = domain.toLowerCase();
|
|
132
|
+
// Check cache
|
|
133
|
+
if (!skipCache) {
|
|
134
|
+
const cached = pubkeyCache.get(cacheKey);
|
|
135
|
+
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
|
136
|
+
return { data: cached, cached: true };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const url = `https://${domain}/.well-known/jacs-pubkey.json`;
|
|
141
|
+
const response = await fetch(url, {
|
|
142
|
+
headers: { Accept: "application/json" },
|
|
143
|
+
signal: AbortSignal.timeout(10000),
|
|
144
|
+
});
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
return { error: `HTTP ${response.status} from ${domain}` };
|
|
147
|
+
}
|
|
148
|
+
const data = (await response.json());
|
|
149
|
+
if (!data.publicKey) {
|
|
150
|
+
return { error: `Missing publicKey in response from ${domain}` };
|
|
151
|
+
}
|
|
152
|
+
const keyInfo = {
|
|
153
|
+
key: data.publicKey,
|
|
154
|
+
algorithm: data.algorithm || "unknown",
|
|
155
|
+
agentId: data.agentId,
|
|
156
|
+
publicKeyHash: data.publicKeyHash,
|
|
157
|
+
fetchedAt: Date.now(),
|
|
158
|
+
};
|
|
159
|
+
pubkeyCache.set(cacheKey, keyInfo);
|
|
160
|
+
return { data: keyInfo, cached: false };
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
if (err.name === "TimeoutError") {
|
|
164
|
+
return { error: `Timeout fetching from ${domain}` };
|
|
165
|
+
}
|
|
166
|
+
return { error: err.message };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Extract signer domain from a JACS document
|
|
171
|
+
* Looks for jacsAgentDomain in the document or signature metadata
|
|
172
|
+
*/
|
|
173
|
+
function extractSignerDomain(doc) {
|
|
174
|
+
// Check document-level domain
|
|
175
|
+
if (doc.jacsAgentDomain)
|
|
176
|
+
return doc.jacsAgentDomain;
|
|
177
|
+
// Check signature metadata
|
|
178
|
+
if (doc.jacsSignature?.agentDomain)
|
|
179
|
+
return doc.jacsSignature.agentDomain;
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Register JACS tools with OpenClaw
|
|
184
|
+
*/
|
|
185
|
+
function registerTools(api) {
|
|
186
|
+
// Tool: Sign a document
|
|
187
|
+
api.registerTool({
|
|
188
|
+
name: "jacs_sign",
|
|
189
|
+
description: "Sign a document with JACS cryptographic provenance. Use this to create verifiable, tamper-proof documents that can be traced back to this agent.",
|
|
190
|
+
parameters: {
|
|
191
|
+
type: "object",
|
|
192
|
+
properties: {
|
|
193
|
+
document: {
|
|
194
|
+
type: "object",
|
|
195
|
+
description: "The document or data to sign (any JSON object)",
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
required: ["document"],
|
|
199
|
+
},
|
|
200
|
+
handler: async (params) => {
|
|
201
|
+
const agent = getAgent(api);
|
|
202
|
+
if (!agent) {
|
|
203
|
+
return { error: "JACS not initialized. Run 'openclaw jacs init' first." };
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
const signed = agent.signRequest(params.document);
|
|
207
|
+
const parsed = JSON.parse(signed);
|
|
208
|
+
let verification_url;
|
|
209
|
+
try {
|
|
210
|
+
verification_url = (0, simple_1.generateVerifyLink)(signed, "https://hai.ai");
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// Document too large for URL; omit link
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
result: verification_url != null ? { ...parsed, verification_url } : parsed,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
return { error: `Failed to sign: ${err.message}` };
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
// Tool: Get shareable verification link for a signed document
|
|
225
|
+
api.registerTool({
|
|
226
|
+
name: "jacs_verify_link",
|
|
227
|
+
description: "Get a shareable verification URL for a signed JACS document. Recipients can open the link at https://hai.ai/jacs/verify to see signer and validity. Use after jacs_sign when sharing with humans. Fails if the document is too large for a URL (max ~1515 bytes).",
|
|
228
|
+
parameters: {
|
|
229
|
+
type: "object",
|
|
230
|
+
properties: {
|
|
231
|
+
document: {
|
|
232
|
+
type: "object",
|
|
233
|
+
description: "The signed JACS document (object or JSON string)",
|
|
234
|
+
},
|
|
235
|
+
baseUrl: {
|
|
236
|
+
type: "string",
|
|
237
|
+
description: "Base URL for the verifier (default https://hai.ai)",
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
required: ["document"],
|
|
241
|
+
},
|
|
242
|
+
handler: async (params) => {
|
|
243
|
+
try {
|
|
244
|
+
const docStr = typeof params.document === "string"
|
|
245
|
+
? params.document
|
|
246
|
+
: JSON.stringify(params.document);
|
|
247
|
+
const url = (0, simple_1.generateVerifyLink)(docStr, params.baseUrl ?? "https://hai.ai");
|
|
248
|
+
return { result: { verification_url: url } };
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
return {
|
|
252
|
+
error: `Verification link failed (document may exceed URL size limit): ${err.message}`,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
// Tool: Verify a document
|
|
258
|
+
api.registerTool({
|
|
259
|
+
name: "jacs_verify",
|
|
260
|
+
description: "Verify a JACS-signed document. Use this to check if a document was signed by a valid agent and has not been tampered with.",
|
|
261
|
+
parameters: {
|
|
262
|
+
type: "object",
|
|
263
|
+
properties: {
|
|
264
|
+
document: {
|
|
265
|
+
type: "object",
|
|
266
|
+
description: "The signed document to verify",
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
required: ["document"],
|
|
270
|
+
},
|
|
271
|
+
handler: async (params) => {
|
|
272
|
+
const agent = getAgent(api);
|
|
273
|
+
if (!agent) {
|
|
274
|
+
return { error: "JACS not initialized. Run 'openclaw jacs init' first." };
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
const result = agent.verifyResponse(JSON.stringify(params.document));
|
|
278
|
+
return { result };
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
return { error: `Verification failed: ${err.message}` };
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
// Tool: Create agreement
|
|
286
|
+
api.registerTool({
|
|
287
|
+
name: "jacs_create_agreement",
|
|
288
|
+
description: "Create a multi-party agreement that requires signatures from multiple agents. Use this when multiple parties need to sign off on a decision or document.",
|
|
289
|
+
parameters: {
|
|
290
|
+
type: "object",
|
|
291
|
+
properties: {
|
|
292
|
+
document: {
|
|
293
|
+
type: "object",
|
|
294
|
+
description: "The document to create agreement on",
|
|
295
|
+
},
|
|
296
|
+
agentIds: {
|
|
297
|
+
type: "array",
|
|
298
|
+
items: { type: "string" },
|
|
299
|
+
description: "List of agent IDs required to sign",
|
|
300
|
+
},
|
|
301
|
+
question: {
|
|
302
|
+
type: "string",
|
|
303
|
+
description: "The question or purpose of the agreement",
|
|
304
|
+
},
|
|
305
|
+
context: {
|
|
306
|
+
type: "string",
|
|
307
|
+
description: "Additional context for signers",
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
required: ["document", "agentIds"],
|
|
311
|
+
},
|
|
312
|
+
handler: async (params) => {
|
|
313
|
+
const agent = getAgent(api);
|
|
314
|
+
if (!agent) {
|
|
315
|
+
return { error: "JACS not initialized. Run 'openclaw jacs init' first." };
|
|
316
|
+
}
|
|
317
|
+
try {
|
|
318
|
+
const result = agent.createAgreement(JSON.stringify(params.document), params.agentIds, params.question, params.context);
|
|
319
|
+
return { result: JSON.parse(result) };
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
return { error: `Failed to create agreement: ${err.message}` };
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
// Tool: Sign agreement
|
|
327
|
+
api.registerTool({
|
|
328
|
+
name: "jacs_sign_agreement",
|
|
329
|
+
description: "Sign an existing agreement document. Use this when you need to add your signature to a multi-party agreement.",
|
|
330
|
+
parameters: {
|
|
331
|
+
type: "object",
|
|
332
|
+
properties: {
|
|
333
|
+
document: {
|
|
334
|
+
type: "object",
|
|
335
|
+
description: "The agreement document to sign",
|
|
336
|
+
},
|
|
337
|
+
agreementFieldname: {
|
|
338
|
+
type: "string",
|
|
339
|
+
description: "Name of the agreement field (optional)",
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
required: ["document"],
|
|
343
|
+
},
|
|
344
|
+
handler: async (params) => {
|
|
345
|
+
const agent = getAgent(api);
|
|
346
|
+
if (!agent) {
|
|
347
|
+
return { error: "JACS not initialized. Run 'openclaw jacs init' first." };
|
|
348
|
+
}
|
|
349
|
+
try {
|
|
350
|
+
const result = agent.signAgreement(JSON.stringify(params.document), params.agreementFieldname);
|
|
351
|
+
return { result: JSON.parse(result) };
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
return { error: `Failed to sign agreement: ${err.message}` };
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
// Tool: Check agreement status
|
|
359
|
+
api.registerTool({
|
|
360
|
+
name: "jacs_check_agreement",
|
|
361
|
+
description: "Check the status of a multi-party agreement. Use this to see which parties have signed and which are still pending.",
|
|
362
|
+
parameters: {
|
|
363
|
+
type: "object",
|
|
364
|
+
properties: {
|
|
365
|
+
document: {
|
|
366
|
+
type: "object",
|
|
367
|
+
description: "The agreement document to check",
|
|
368
|
+
},
|
|
369
|
+
agreementFieldname: {
|
|
370
|
+
type: "string",
|
|
371
|
+
description: "Name of the agreement field (optional)",
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
required: ["document"],
|
|
375
|
+
},
|
|
376
|
+
handler: async (params) => {
|
|
377
|
+
const agent = getAgent(api);
|
|
378
|
+
if (!agent) {
|
|
379
|
+
return { error: "JACS not initialized. Run 'openclaw jacs init' first." };
|
|
380
|
+
}
|
|
381
|
+
try {
|
|
382
|
+
const result = agent.checkAgreement(JSON.stringify(params.document), params.agreementFieldname);
|
|
383
|
+
return { result: JSON.parse(result) };
|
|
384
|
+
}
|
|
385
|
+
catch (err) {
|
|
386
|
+
return { error: `Failed to check agreement: ${err.message}` };
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
// Tool: Hash content
|
|
391
|
+
api.registerTool({
|
|
392
|
+
name: "jacs_hash",
|
|
393
|
+
description: "Create a cryptographic hash of content. Use this to create a unique fingerprint of data for verification purposes.",
|
|
394
|
+
parameters: {
|
|
395
|
+
type: "object",
|
|
396
|
+
properties: {
|
|
397
|
+
content: {
|
|
398
|
+
type: "string",
|
|
399
|
+
description: "The content to hash",
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
required: ["content"],
|
|
403
|
+
},
|
|
404
|
+
handler: async (params) => {
|
|
405
|
+
try {
|
|
406
|
+
const hash = (0, simple_1.hashString)(params.content);
|
|
407
|
+
return { result: { hash, algorithm: "SHA-256" } };
|
|
408
|
+
}
|
|
409
|
+
catch (err) {
|
|
410
|
+
return { error: `Failed to hash: ${err.message}` };
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
// Tool: Get agent identity
|
|
415
|
+
api.registerTool({
|
|
416
|
+
name: "jacs_identity",
|
|
417
|
+
description: "Get the current agent's JACS identity information including trust level and verification claim. Use this to share your identity with other agents.",
|
|
418
|
+
parameters: {
|
|
419
|
+
type: "object",
|
|
420
|
+
properties: {},
|
|
421
|
+
},
|
|
422
|
+
handler: async () => {
|
|
423
|
+
if (!api.runtime.jacs?.isInitialized()) {
|
|
424
|
+
return { error: "JACS not initialized. Run 'openclaw jacs init' first." };
|
|
425
|
+
}
|
|
426
|
+
const config = api.config;
|
|
427
|
+
const publicKey = api.runtime.jacs.getPublicKey();
|
|
428
|
+
const publicKeyHash = publicKey ? (0, simple_1.hashString)(publicKey) : undefined;
|
|
429
|
+
// Determine trust level
|
|
430
|
+
let haiRegistered = false;
|
|
431
|
+
if (config.agentId && publicKeyHash) {
|
|
432
|
+
try {
|
|
433
|
+
const haiStatus = await (0, hai_1.checkHaiStatus)(config.agentId);
|
|
434
|
+
haiRegistered = haiStatus?.verified ?? false;
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
// HAI.ai check failed, not registered
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// Check DNS verification
|
|
441
|
+
let dnsVerified = false;
|
|
442
|
+
if (config.agentDomain) {
|
|
443
|
+
const dnsResult = await resolveDnsRecord(config.agentDomain);
|
|
444
|
+
if (dnsResult && publicKeyHash) {
|
|
445
|
+
const dnsHash = dnsResult.parsed.publicKeyHash;
|
|
446
|
+
dnsVerified = dnsHash === publicKeyHash;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
const trustLevel = (0, hai_1.determineTrustLevel)(!!config.agentDomain, dnsVerified, haiRegistered);
|
|
450
|
+
return {
|
|
451
|
+
result: {
|
|
452
|
+
agentId: config.agentId,
|
|
453
|
+
agentName: config.agentName,
|
|
454
|
+
agentDescription: config.agentDescription,
|
|
455
|
+
agentDomain: config.agentDomain,
|
|
456
|
+
algorithm: config.keyAlgorithm,
|
|
457
|
+
publicKeyHash,
|
|
458
|
+
verificationClaim: config.verificationClaim || "unverified",
|
|
459
|
+
trustLevel,
|
|
460
|
+
haiRegistered,
|
|
461
|
+
dnsVerified,
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
},
|
|
465
|
+
});
|
|
466
|
+
// Tool: Fetch another agent's public key
|
|
467
|
+
api.registerTool({
|
|
468
|
+
name: "jacs_fetch_pubkey",
|
|
469
|
+
description: "Fetch another agent's public key from their domain. Use this before verifying documents from other agents. Keys are fetched from https://<domain>/.well-known/jacs-pubkey.json",
|
|
470
|
+
parameters: {
|
|
471
|
+
type: "object",
|
|
472
|
+
properties: {
|
|
473
|
+
domain: {
|
|
474
|
+
type: "string",
|
|
475
|
+
description: "The domain of the agent whose public key to fetch (e.g., 'example.com')",
|
|
476
|
+
},
|
|
477
|
+
skipCache: {
|
|
478
|
+
type: "boolean",
|
|
479
|
+
description: "Force fetch even if key is cached (default: false)",
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
required: ["domain"],
|
|
483
|
+
},
|
|
484
|
+
handler: async (params) => {
|
|
485
|
+
const domain = sanitizeDomain(params.domain);
|
|
486
|
+
const cacheKey = domain.toLowerCase();
|
|
487
|
+
// Check cache first
|
|
488
|
+
if (!params.skipCache) {
|
|
489
|
+
const cached = pubkeyCache.get(cacheKey);
|
|
490
|
+
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
|
491
|
+
return {
|
|
492
|
+
result: {
|
|
493
|
+
domain,
|
|
494
|
+
publicKey: cached.key,
|
|
495
|
+
algorithm: cached.algorithm,
|
|
496
|
+
cached: true,
|
|
497
|
+
fetchedAt: new Date(cached.fetchedAt).toISOString(),
|
|
498
|
+
},
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
try {
|
|
503
|
+
const url = `https://${domain}/.well-known/jacs-pubkey.json`;
|
|
504
|
+
const response = await fetch(url, {
|
|
505
|
+
headers: { Accept: "application/json" },
|
|
506
|
+
signal: AbortSignal.timeout(10000), // 10 second timeout
|
|
507
|
+
});
|
|
508
|
+
if (!response.ok) {
|
|
509
|
+
return {
|
|
510
|
+
error: `Failed to fetch public key from ${domain}: HTTP ${response.status}`,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
const data = (await response.json());
|
|
514
|
+
if (!data.publicKey) {
|
|
515
|
+
return { error: `Invalid response from ${domain}: missing publicKey field` };
|
|
516
|
+
}
|
|
517
|
+
// Cache the key
|
|
518
|
+
pubkeyCache.set(cacheKey, {
|
|
519
|
+
key: data.publicKey,
|
|
520
|
+
algorithm: data.algorithm || "unknown",
|
|
521
|
+
fetchedAt: Date.now(),
|
|
522
|
+
});
|
|
523
|
+
return {
|
|
524
|
+
result: {
|
|
525
|
+
domain,
|
|
526
|
+
publicKey: data.publicKey,
|
|
527
|
+
algorithm: data.algorithm || "unknown",
|
|
528
|
+
agentId: data.agentId,
|
|
529
|
+
agentName: data.agentName,
|
|
530
|
+
cached: false,
|
|
531
|
+
fetchedAt: new Date().toISOString(),
|
|
532
|
+
},
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
catch (err) {
|
|
536
|
+
if (err.name === "TimeoutError") {
|
|
537
|
+
return { error: `Timeout fetching public key from ${domain}` };
|
|
538
|
+
}
|
|
539
|
+
return { error: `Failed to fetch public key from ${domain}: ${err.message}` };
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
});
|
|
543
|
+
// Tool: Verify a document with a specific public key
|
|
544
|
+
api.registerTool({
|
|
545
|
+
name: "jacs_verify_with_key",
|
|
546
|
+
description: "Verify a signed document using another agent's public key. Use jacs_fetch_pubkey first to get the key, then use this to verify documents from that agent.",
|
|
547
|
+
parameters: {
|
|
548
|
+
type: "object",
|
|
549
|
+
properties: {
|
|
550
|
+
document: {
|
|
551
|
+
type: "object",
|
|
552
|
+
description: "The signed document to verify",
|
|
553
|
+
},
|
|
554
|
+
publicKey: {
|
|
555
|
+
type: "string",
|
|
556
|
+
description: "The PEM-encoded public key of the signing agent",
|
|
557
|
+
},
|
|
558
|
+
algorithm: {
|
|
559
|
+
type: "string",
|
|
560
|
+
description: "The key algorithm (e.g., 'pq2025', 'ed25519'). Default: 'pq2025'",
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
required: ["document", "publicKey"],
|
|
564
|
+
},
|
|
565
|
+
handler: async (params) => {
|
|
566
|
+
try {
|
|
567
|
+
const doc = params.document;
|
|
568
|
+
const sig = doc.jacsSignature || doc.signature;
|
|
569
|
+
if (!sig) {
|
|
570
|
+
return { error: "Document does not contain a signature field (jacsSignature or signature)" };
|
|
571
|
+
}
|
|
572
|
+
// Get the actual signature string
|
|
573
|
+
const signatureValue = typeof sig === "object" ? sig.signature : sig;
|
|
574
|
+
if (!signatureValue) {
|
|
575
|
+
return { error: "Could not extract signature value from document" };
|
|
576
|
+
}
|
|
577
|
+
// Determine algorithm from signature or parameter
|
|
578
|
+
const algorithm = params.algorithm || sig.signingAlgorithm || "pq2025";
|
|
579
|
+
// Convert public key to Buffer
|
|
580
|
+
const publicKeyBuffer = Buffer.from(params.publicKey, "utf-8");
|
|
581
|
+
// Build the data that was signed (document without signature fields)
|
|
582
|
+
const docWithoutSig = { ...doc };
|
|
583
|
+
delete docWithoutSig.jacsSignature;
|
|
584
|
+
delete docWithoutSig.signature;
|
|
585
|
+
delete docWithoutSig.jacsHash;
|
|
586
|
+
const dataToVerify = JSON.stringify(docWithoutSig);
|
|
587
|
+
// Use JACS verifyString to verify (static function)
|
|
588
|
+
const isValid = (0, simple_1.verifyString)(dataToVerify, signatureValue, publicKeyBuffer, algorithm);
|
|
589
|
+
return {
|
|
590
|
+
result: {
|
|
591
|
+
valid: isValid,
|
|
592
|
+
algorithm,
|
|
593
|
+
agentId: sig.agentID || doc.jacsAgentId,
|
|
594
|
+
agentVersion: sig.agentVersion,
|
|
595
|
+
signedAt: sig.date,
|
|
596
|
+
publicKeyHash: sig.publicKeyHash,
|
|
597
|
+
documentId: doc.jacsId,
|
|
598
|
+
},
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
catch (err) {
|
|
602
|
+
return { error: `Verification failed: ${err.message}` };
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
});
|
|
606
|
+
// Tool: Seamless verification with auto-fetch
|
|
607
|
+
api.registerTool({
|
|
608
|
+
name: "jacs_verify_auto",
|
|
609
|
+
description: "Automatically verify a JACS-signed document by fetching the signer's public key. Supports trust level requirements: 'basic' (signature only), 'domain' (DNS verified), 'attested' (HAI.ai registered).",
|
|
610
|
+
parameters: {
|
|
611
|
+
type: "object",
|
|
612
|
+
properties: {
|
|
613
|
+
document: {
|
|
614
|
+
type: "object",
|
|
615
|
+
description: "The signed document to verify",
|
|
616
|
+
},
|
|
617
|
+
domain: {
|
|
618
|
+
type: "string",
|
|
619
|
+
description: "The domain of the signing agent (e.g., 'agent.example.com'). If not provided, will try to extract from document.",
|
|
620
|
+
},
|
|
621
|
+
verifyDns: {
|
|
622
|
+
type: "boolean",
|
|
623
|
+
description: "Also verify the public key hash against DNS TXT record (default: false). Provides stronger verification.",
|
|
624
|
+
},
|
|
625
|
+
requiredTrustLevel: {
|
|
626
|
+
type: "string",
|
|
627
|
+
enum: ["basic", "domain", "attested"],
|
|
628
|
+
description: "Minimum trust level required for verification to pass. 'basic' = signature only, 'domain' = DNS verified, 'attested' = HAI.ai registered.",
|
|
629
|
+
},
|
|
630
|
+
},
|
|
631
|
+
required: ["document"],
|
|
632
|
+
},
|
|
633
|
+
handler: async (params) => {
|
|
634
|
+
const doc = params.document;
|
|
635
|
+
const sig = doc.jacsSignature || doc.signature;
|
|
636
|
+
if (!sig) {
|
|
637
|
+
return { error: "Document does not contain a signature" };
|
|
638
|
+
}
|
|
639
|
+
// Determine domain
|
|
640
|
+
let domain = params.domain;
|
|
641
|
+
if (!domain) {
|
|
642
|
+
domain = extractSignerDomain(doc);
|
|
643
|
+
}
|
|
644
|
+
if (!domain) {
|
|
645
|
+
return {
|
|
646
|
+
error: "Could not determine signer domain. Please provide the 'domain' parameter or ensure the document contains 'jacsAgentDomain'.",
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
// Fetch public key
|
|
650
|
+
const keyResult = await fetchPublicKey(domain);
|
|
651
|
+
if ("error" in keyResult) {
|
|
652
|
+
return { error: `Failed to fetch public key: ${keyResult.error}` };
|
|
653
|
+
}
|
|
654
|
+
const keyInfo = keyResult.data;
|
|
655
|
+
let dnsVerified = false;
|
|
656
|
+
let dnsError;
|
|
657
|
+
// Optional DNS verification
|
|
658
|
+
if (params.verifyDns) {
|
|
659
|
+
const dnsResult = await resolveDnsRecord(domain);
|
|
660
|
+
if (dnsResult) {
|
|
661
|
+
const dnsHash = dnsResult.parsed.publicKeyHash;
|
|
662
|
+
// Compare public key hash
|
|
663
|
+
const localHash = (0, simple_1.hashString)(keyInfo.key);
|
|
664
|
+
if (dnsHash === localHash || dnsHash === keyInfo.publicKeyHash) {
|
|
665
|
+
dnsVerified = true;
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
dnsError = "DNS public key hash does not match fetched key";
|
|
669
|
+
}
|
|
670
|
+
// Also verify agent ID if present
|
|
671
|
+
if (dnsResult.parsed.jacsAgentId && sig.agentID) {
|
|
672
|
+
if (dnsResult.parsed.jacsAgentId !== sig.agentID) {
|
|
673
|
+
dnsError = "DNS agent ID does not match document signer";
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
dnsError = "DNS TXT record not found";
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
// Get signature value
|
|
682
|
+
const signatureValue = typeof sig === "object" ? sig.signature : sig;
|
|
683
|
+
if (!signatureValue) {
|
|
684
|
+
return { error: "Could not extract signature value" };
|
|
685
|
+
}
|
|
686
|
+
// Determine algorithm
|
|
687
|
+
const algorithm = sig.signingAlgorithm || keyInfo.algorithm || "pq2025";
|
|
688
|
+
// Build data to verify
|
|
689
|
+
const docWithoutSig = { ...doc };
|
|
690
|
+
delete docWithoutSig.jacsSignature;
|
|
691
|
+
delete docWithoutSig.signature;
|
|
692
|
+
delete docWithoutSig.jacsHash;
|
|
693
|
+
const dataToVerify = JSON.stringify(docWithoutSig);
|
|
694
|
+
try {
|
|
695
|
+
const publicKeyBuffer = Buffer.from(keyInfo.key, "utf-8");
|
|
696
|
+
const isValid = (0, simple_1.verifyString)(dataToVerify, signatureValue, publicKeyBuffer, algorithm);
|
|
697
|
+
// Check HAI.ai registration if required trust level is 'attested'
|
|
698
|
+
let haiRegistered = false;
|
|
699
|
+
let haiError;
|
|
700
|
+
const agentId = sig.agentID || keyInfo.agentId;
|
|
701
|
+
const publicKeyHash = keyInfo.publicKeyHash || (0, simple_1.hashString)(keyInfo.key);
|
|
702
|
+
if (params.requiredTrustLevel === "attested" || params.requiredTrustLevel === "domain") {
|
|
703
|
+
// For attested, must check HAI.ai
|
|
704
|
+
if (params.requiredTrustLevel === "attested" && agentId && publicKeyHash) {
|
|
705
|
+
try {
|
|
706
|
+
const haiResult = await (0, hai_1.verifyHaiRegistration)(agentId, publicKeyHash);
|
|
707
|
+
haiRegistered = haiResult.verified;
|
|
708
|
+
}
|
|
709
|
+
catch (err) {
|
|
710
|
+
haiError = err.message;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
// For domain level, verifyDns must be true and pass
|
|
714
|
+
if (params.requiredTrustLevel === "domain" && !params.verifyDns) {
|
|
715
|
+
// Force DNS verification for domain trust level
|
|
716
|
+
const dnsResult = await resolveDnsRecord(domain);
|
|
717
|
+
if (dnsResult) {
|
|
718
|
+
const dnsHash = dnsResult.parsed.publicKeyHash;
|
|
719
|
+
const localHash = (0, simple_1.hashString)(keyInfo.key);
|
|
720
|
+
if (dnsHash === localHash || dnsHash === keyInfo.publicKeyHash) {
|
|
721
|
+
dnsVerified = true;
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
dnsError = "DNS public key hash does not match fetched key";
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
dnsError = "DNS TXT record not found";
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
// Determine actual trust level achieved
|
|
733
|
+
const trustLevel = (0, hai_1.determineTrustLevel)(!!domain, dnsVerified, haiRegistered);
|
|
734
|
+
// Check if required trust level is met
|
|
735
|
+
const trustOrder = ["basic", "domain", "attested"];
|
|
736
|
+
const requiredIndex = trustOrder.indexOf(params.requiredTrustLevel || "basic");
|
|
737
|
+
const actualIndex = trustOrder.indexOf(trustLevel);
|
|
738
|
+
const trustLevelMet = actualIndex >= requiredIndex;
|
|
739
|
+
if (params.requiredTrustLevel && !trustLevelMet) {
|
|
740
|
+
return {
|
|
741
|
+
error: `Agent does not meet required trust level '${params.requiredTrustLevel}'. Actual: '${trustLevel}'`,
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
return {
|
|
745
|
+
result: {
|
|
746
|
+
valid: isValid && trustLevelMet,
|
|
747
|
+
domain,
|
|
748
|
+
algorithm,
|
|
749
|
+
agentId,
|
|
750
|
+
agentVersion: sig.agentVersion,
|
|
751
|
+
signedAt: sig.date,
|
|
752
|
+
keyFromCache: keyResult.cached,
|
|
753
|
+
dnsVerified: (params.verifyDns || params.requiredTrustLevel === "domain" || params.requiredTrustLevel === "attested") ? dnsVerified : undefined,
|
|
754
|
+
dnsError: (params.verifyDns || params.requiredTrustLevel) ? dnsError : undefined,
|
|
755
|
+
trustLevel,
|
|
756
|
+
requiredTrustLevel: params.requiredTrustLevel,
|
|
757
|
+
haiRegistered: params.requiredTrustLevel === "attested" ? haiRegistered : undefined,
|
|
758
|
+
haiError: params.requiredTrustLevel === "attested" ? haiError : undefined,
|
|
759
|
+
documentId: doc.jacsId,
|
|
760
|
+
},
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
catch (err) {
|
|
764
|
+
return { error: `Signature verification failed: ${err.message}` };
|
|
765
|
+
}
|
|
766
|
+
},
|
|
767
|
+
});
|
|
768
|
+
// Tool: DNS lookup for agent verification
|
|
769
|
+
api.registerTool({
|
|
770
|
+
name: "jacs_dns_lookup",
|
|
771
|
+
description: "Look up a JACS agent's DNS TXT record. This provides the public key hash published in DNS for additional verification. The DNS record is at _v1.agent.jacs.<domain>.",
|
|
772
|
+
parameters: {
|
|
773
|
+
type: "object",
|
|
774
|
+
properties: {
|
|
775
|
+
domain: {
|
|
776
|
+
type: "string",
|
|
777
|
+
description: "The domain to look up (e.g., 'agent.example.com')",
|
|
778
|
+
},
|
|
779
|
+
},
|
|
780
|
+
required: ["domain"],
|
|
781
|
+
},
|
|
782
|
+
handler: async (params) => {
|
|
783
|
+
const domain = sanitizeDomain(params.domain);
|
|
784
|
+
const owner = `_v1.agent.jacs.${domain}`;
|
|
785
|
+
const result = await resolveDnsRecord(domain);
|
|
786
|
+
if (!result) {
|
|
787
|
+
return {
|
|
788
|
+
result: {
|
|
789
|
+
found: false,
|
|
790
|
+
domain,
|
|
791
|
+
owner,
|
|
792
|
+
message: `No JACS DNS TXT record found at ${owner}`,
|
|
793
|
+
},
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
return {
|
|
797
|
+
result: {
|
|
798
|
+
found: true,
|
|
799
|
+
domain,
|
|
800
|
+
owner,
|
|
801
|
+
rawTxt: result.txt,
|
|
802
|
+
...result.parsed,
|
|
803
|
+
},
|
|
804
|
+
};
|
|
805
|
+
},
|
|
806
|
+
});
|
|
807
|
+
// Tool: Lookup agent info (combines DNS + well-known + HAI.ai)
|
|
808
|
+
api.registerTool({
|
|
809
|
+
name: "jacs_lookup_agent",
|
|
810
|
+
description: "Look up complete information about a JACS agent by domain. Fetches the public key from /.well-known/jacs-pubkey.json, DNS TXT record, and HAI.ai attestation status.",
|
|
811
|
+
parameters: {
|
|
812
|
+
type: "object",
|
|
813
|
+
properties: {
|
|
814
|
+
domain: {
|
|
815
|
+
type: "string",
|
|
816
|
+
description: "The domain of the agent (e.g., 'agent.example.com')",
|
|
817
|
+
},
|
|
818
|
+
},
|
|
819
|
+
required: ["domain"],
|
|
820
|
+
},
|
|
821
|
+
handler: async (params) => {
|
|
822
|
+
const domain = sanitizeDomain(params.domain);
|
|
823
|
+
// Fetch public key and DNS in parallel
|
|
824
|
+
const [keyResult, dnsResult] = await Promise.all([
|
|
825
|
+
fetchPublicKey(domain, true), // skip cache for fresh lookup
|
|
826
|
+
resolveDnsRecord(domain),
|
|
827
|
+
]);
|
|
828
|
+
const result = {
|
|
829
|
+
domain,
|
|
830
|
+
wellKnown: null,
|
|
831
|
+
dns: null,
|
|
832
|
+
haiAttestation: null,
|
|
833
|
+
verified: false,
|
|
834
|
+
trustLevel: "basic",
|
|
835
|
+
};
|
|
836
|
+
// Process well-known result
|
|
837
|
+
if ("error" in keyResult) {
|
|
838
|
+
result.wellKnown = { error: keyResult.error };
|
|
839
|
+
}
|
|
840
|
+
else {
|
|
841
|
+
result.wellKnown = {
|
|
842
|
+
publicKey: keyResult.data.key.substring(0, 100) + "...", // truncate for display
|
|
843
|
+
publicKeyHash: keyResult.data.publicKeyHash || (0, simple_1.hashString)(keyResult.data.key),
|
|
844
|
+
algorithm: keyResult.data.algorithm,
|
|
845
|
+
agentId: keyResult.data.agentId,
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
// Process DNS result
|
|
849
|
+
let dnsVerified = false;
|
|
850
|
+
if (dnsResult) {
|
|
851
|
+
result.dns = {
|
|
852
|
+
owner: `_v1.agent.jacs.${domain}`,
|
|
853
|
+
agentId: dnsResult.parsed.jacsAgentId,
|
|
854
|
+
publicKeyHash: dnsResult.parsed.publicKeyHash,
|
|
855
|
+
algorithm: dnsResult.parsed.alg,
|
|
856
|
+
encoding: dnsResult.parsed.enc,
|
|
857
|
+
};
|
|
858
|
+
// Verify DNS matches well-known
|
|
859
|
+
if (result.wellKnown && !result.wellKnown.error) {
|
|
860
|
+
const localHash = result.wellKnown.publicKeyHash;
|
|
861
|
+
const dnsHash = dnsResult.parsed.publicKeyHash;
|
|
862
|
+
dnsVerified = localHash === dnsHash;
|
|
863
|
+
result.verified = dnsVerified;
|
|
864
|
+
if (!result.verified) {
|
|
865
|
+
result.verificationError = "Public key hash from well-known endpoint does not match DNS";
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
else {
|
|
870
|
+
result.dns = { error: "No DNS TXT record found" };
|
|
871
|
+
}
|
|
872
|
+
// Check HAI.ai attestation if we have agent ID
|
|
873
|
+
let haiRegistered = false;
|
|
874
|
+
const agentId = result.wellKnown?.agentId || dnsResult?.parsed.jacsAgentId;
|
|
875
|
+
const publicKeyHash = result.wellKnown?.publicKeyHash;
|
|
876
|
+
if (agentId && publicKeyHash) {
|
|
877
|
+
try {
|
|
878
|
+
const haiStatus = await (0, hai_1.verifyHaiRegistration)(agentId, publicKeyHash);
|
|
879
|
+
result.haiAttestation = haiStatus;
|
|
880
|
+
haiRegistered = haiStatus.verified;
|
|
881
|
+
}
|
|
882
|
+
catch (err) {
|
|
883
|
+
result.haiAttestation = { error: err.message };
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
else {
|
|
887
|
+
result.haiAttestation = { error: "No agent ID available to check HAI.ai status" };
|
|
888
|
+
}
|
|
889
|
+
// Determine trust level
|
|
890
|
+
result.trustLevel = (0, hai_1.determineTrustLevel)(true, dnsVerified, haiRegistered);
|
|
891
|
+
return { result };
|
|
892
|
+
},
|
|
893
|
+
});
|
|
894
|
+
// Tool: Verify HAI.ai registration
|
|
895
|
+
api.registerTool({
|
|
896
|
+
name: "jacs_verify_hai_registration",
|
|
897
|
+
description: "Verify that an agent is registered with HAI.ai. Returns verification status including when the agent was verified and the registration type.",
|
|
898
|
+
parameters: {
|
|
899
|
+
type: "object",
|
|
900
|
+
properties: {
|
|
901
|
+
agentId: {
|
|
902
|
+
type: "string",
|
|
903
|
+
description: "The JACS agent ID (UUID) to verify",
|
|
904
|
+
},
|
|
905
|
+
publicKeyHash: {
|
|
906
|
+
type: "string",
|
|
907
|
+
description: "The SHA-256 hash of the agent's public key (hex encoded). If not provided, will attempt to fetch from domain.",
|
|
908
|
+
},
|
|
909
|
+
domain: {
|
|
910
|
+
type: "string",
|
|
911
|
+
description: "Domain to fetch public key hash from if not provided directly",
|
|
912
|
+
},
|
|
913
|
+
},
|
|
914
|
+
required: ["agentId"],
|
|
915
|
+
},
|
|
916
|
+
handler: async (params) => {
|
|
917
|
+
let publicKeyHash = params.publicKeyHash;
|
|
918
|
+
// If no hash provided, try to fetch from domain
|
|
919
|
+
if (!publicKeyHash && params.domain) {
|
|
920
|
+
const keyResult = await fetchPublicKey(params.domain);
|
|
921
|
+
if ("error" in keyResult) {
|
|
922
|
+
return { error: `Could not fetch public key: ${keyResult.error}` };
|
|
923
|
+
}
|
|
924
|
+
publicKeyHash = keyResult.data.publicKeyHash || (0, simple_1.hashString)(keyResult.data.key);
|
|
925
|
+
}
|
|
926
|
+
if (!publicKeyHash) {
|
|
927
|
+
return { error: "Either publicKeyHash or domain must be provided" };
|
|
928
|
+
}
|
|
929
|
+
try {
|
|
930
|
+
const result = await (0, hai_1.verifyHaiRegistration)(params.agentId, publicKeyHash);
|
|
931
|
+
return { result };
|
|
932
|
+
}
|
|
933
|
+
catch (err) {
|
|
934
|
+
return { error: err.message };
|
|
935
|
+
}
|
|
936
|
+
},
|
|
937
|
+
});
|
|
938
|
+
// Tool: Get attestation status
|
|
939
|
+
api.registerTool({
|
|
940
|
+
name: "jacs_get_attestation",
|
|
941
|
+
description: "Get the full attestation status for an agent, including trust level (basic, domain, attested), verification claim, and HAI.ai registration status.",
|
|
942
|
+
parameters: {
|
|
943
|
+
type: "object",
|
|
944
|
+
properties: {
|
|
945
|
+
domain: {
|
|
946
|
+
type: "string",
|
|
947
|
+
description: "Domain of the agent to check attestation for",
|
|
948
|
+
},
|
|
949
|
+
agentId: {
|
|
950
|
+
type: "string",
|
|
951
|
+
description: "Agent ID to check (alternative to domain, for self-check)",
|
|
952
|
+
},
|
|
953
|
+
},
|
|
954
|
+
},
|
|
955
|
+
handler: async (params) => {
|
|
956
|
+
// If no params, check self
|
|
957
|
+
if (!params.domain && !params.agentId) {
|
|
958
|
+
if (!api.runtime.jacs?.isInitialized()) {
|
|
959
|
+
return { error: "JACS not initialized and no domain/agentId provided" };
|
|
960
|
+
}
|
|
961
|
+
const config = api.config;
|
|
962
|
+
const publicKey = api.runtime.jacs.getPublicKey();
|
|
963
|
+
const publicKeyHash = publicKey ? (0, simple_1.hashString)(publicKey) : undefined;
|
|
964
|
+
let haiRegistration = null;
|
|
965
|
+
if (config.agentId && publicKeyHash) {
|
|
966
|
+
try {
|
|
967
|
+
haiRegistration = await (0, hai_1.checkHaiStatus)(config.agentId);
|
|
968
|
+
}
|
|
969
|
+
catch {
|
|
970
|
+
// Not registered
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
let dnsVerified = false;
|
|
974
|
+
if (config.agentDomain) {
|
|
975
|
+
const dnsResult = await resolveDnsRecord(config.agentDomain);
|
|
976
|
+
if (dnsResult && publicKeyHash) {
|
|
977
|
+
dnsVerified = dnsResult.parsed.publicKeyHash === publicKeyHash;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
const status = {
|
|
981
|
+
agentId: config.agentId || "",
|
|
982
|
+
trustLevel: (0, hai_1.determineTrustLevel)(!!config.agentDomain, dnsVerified, haiRegistration?.verified ?? false),
|
|
983
|
+
verificationClaim: config.verificationClaim || "unverified",
|
|
984
|
+
domain: config.agentDomain,
|
|
985
|
+
haiRegistration,
|
|
986
|
+
dnsVerified,
|
|
987
|
+
timestamp: new Date().toISOString(),
|
|
988
|
+
};
|
|
989
|
+
return { result: status };
|
|
990
|
+
}
|
|
991
|
+
// Check external agent by domain
|
|
992
|
+
if (params.domain) {
|
|
993
|
+
const domain = sanitizeDomain(params.domain);
|
|
994
|
+
// Fetch key and DNS
|
|
995
|
+
const [keyResult, dnsResult] = await Promise.all([
|
|
996
|
+
fetchPublicKey(domain),
|
|
997
|
+
resolveDnsRecord(domain),
|
|
998
|
+
]);
|
|
999
|
+
if ("error" in keyResult) {
|
|
1000
|
+
return { error: `Could not fetch public key: ${keyResult.error}` };
|
|
1001
|
+
}
|
|
1002
|
+
const agentId = keyResult.data.agentId || dnsResult?.parsed.jacsAgentId;
|
|
1003
|
+
const publicKeyHash = keyResult.data.publicKeyHash || (0, simple_1.hashString)(keyResult.data.key);
|
|
1004
|
+
if (!agentId) {
|
|
1005
|
+
return { error: "Could not determine agent ID from well-known or DNS" };
|
|
1006
|
+
}
|
|
1007
|
+
// Check DNS verification
|
|
1008
|
+
let dnsVerified = false;
|
|
1009
|
+
if (dnsResult) {
|
|
1010
|
+
dnsVerified = dnsResult.parsed.publicKeyHash === publicKeyHash;
|
|
1011
|
+
}
|
|
1012
|
+
// Check HAI.ai registration
|
|
1013
|
+
let haiRegistration = null;
|
|
1014
|
+
try {
|
|
1015
|
+
haiRegistration = await (0, hai_1.verifyHaiRegistration)(agentId, publicKeyHash);
|
|
1016
|
+
}
|
|
1017
|
+
catch {
|
|
1018
|
+
// Not registered
|
|
1019
|
+
}
|
|
1020
|
+
const status = {
|
|
1021
|
+
agentId,
|
|
1022
|
+
trustLevel: (0, hai_1.determineTrustLevel)(true, dnsVerified, haiRegistration?.verified ?? false),
|
|
1023
|
+
verificationClaim: haiRegistration?.verified ? "verified-hai.ai" : (dnsVerified ? "verified" : "unverified"),
|
|
1024
|
+
domain,
|
|
1025
|
+
haiRegistration,
|
|
1026
|
+
dnsVerified,
|
|
1027
|
+
timestamp: new Date().toISOString(),
|
|
1028
|
+
};
|
|
1029
|
+
return { result: status };
|
|
1030
|
+
}
|
|
1031
|
+
// Check by agent ID only
|
|
1032
|
+
if (params.agentId) {
|
|
1033
|
+
try {
|
|
1034
|
+
const haiRegistration = await (0, hai_1.checkHaiStatus)(params.agentId);
|
|
1035
|
+
const status = {
|
|
1036
|
+
agentId: params.agentId,
|
|
1037
|
+
trustLevel: haiRegistration?.verified ? "attested" : "basic",
|
|
1038
|
+
verificationClaim: haiRegistration?.verified ? "verified-hai.ai" : "unverified",
|
|
1039
|
+
haiRegistration,
|
|
1040
|
+
timestamp: new Date().toISOString(),
|
|
1041
|
+
};
|
|
1042
|
+
return { result: status };
|
|
1043
|
+
}
|
|
1044
|
+
catch (err) {
|
|
1045
|
+
return { error: err.message };
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
return { error: "Either domain or agentId must be provided" };
|
|
1049
|
+
},
|
|
1050
|
+
});
|
|
1051
|
+
// Tool: Set verification claim
|
|
1052
|
+
api.registerTool({
|
|
1053
|
+
name: "jacs_set_verification_claim",
|
|
1054
|
+
description: "Set the verification claim for this agent. Options: 'unverified' (basic), 'verified' (requires domain + DNS hash verification), 'verified-hai.ai' (requires HAI.ai registration). Claims can only be upgraded, never downgraded.",
|
|
1055
|
+
parameters: {
|
|
1056
|
+
type: "object",
|
|
1057
|
+
properties: {
|
|
1058
|
+
claim: {
|
|
1059
|
+
type: "string",
|
|
1060
|
+
enum: ["unverified", "verified", "verified-hai.ai"],
|
|
1061
|
+
description: "The verification claim level to set",
|
|
1062
|
+
},
|
|
1063
|
+
},
|
|
1064
|
+
required: ["claim"],
|
|
1065
|
+
},
|
|
1066
|
+
handler: async (params) => {
|
|
1067
|
+
if (!api.runtime.jacs?.isInitialized()) {
|
|
1068
|
+
return { error: "JACS not initialized. Run 'openclaw jacs init' first." };
|
|
1069
|
+
}
|
|
1070
|
+
const config = api.config;
|
|
1071
|
+
const currentClaim = config.verificationClaim || "unverified";
|
|
1072
|
+
// Check if downgrade
|
|
1073
|
+
if (!(0, hai_1.canUpgradeClaim)(currentClaim, params.claim)) {
|
|
1074
|
+
return {
|
|
1075
|
+
error: `Cannot downgrade verification claim from '${currentClaim}' to '${params.claim}'`,
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
// Validate requirements
|
|
1079
|
+
const publicKey = api.runtime.jacs.getPublicKey();
|
|
1080
|
+
const publicKeyHash = publicKey ? (0, simple_1.hashString)(publicKey) : undefined;
|
|
1081
|
+
let dnsVerified = false;
|
|
1082
|
+
let dnsRecordFound = false;
|
|
1083
|
+
let dnsHash;
|
|
1084
|
+
if (config.agentDomain) {
|
|
1085
|
+
const dnsResult = await resolveDnsRecord(config.agentDomain);
|
|
1086
|
+
if (dnsResult) {
|
|
1087
|
+
dnsRecordFound = true;
|
|
1088
|
+
dnsHash = dnsResult.parsed.publicKeyHash;
|
|
1089
|
+
if (publicKeyHash && dnsHash) {
|
|
1090
|
+
dnsVerified = dnsHash === publicKeyHash;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
let haiRegistered = false;
|
|
1095
|
+
let haiVerifiedAt;
|
|
1096
|
+
const shouldCheckHai = params.claim === "verified-hai.ai";
|
|
1097
|
+
if (config.agentId && shouldCheckHai) {
|
|
1098
|
+
try {
|
|
1099
|
+
const status = await (0, hai_1.checkHaiStatus)(config.agentId);
|
|
1100
|
+
haiRegistered = status?.verified ?? false;
|
|
1101
|
+
haiVerifiedAt = status?.verified_at;
|
|
1102
|
+
}
|
|
1103
|
+
catch {
|
|
1104
|
+
// Not registered
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
const proof = {
|
|
1108
|
+
domain: config.agentDomain,
|
|
1109
|
+
domainConfigured: !!config.agentDomain,
|
|
1110
|
+
dnsRecordFound,
|
|
1111
|
+
dnsVerified,
|
|
1112
|
+
dnsHash,
|
|
1113
|
+
publicKeyHash,
|
|
1114
|
+
haiChecked: shouldCheckHai,
|
|
1115
|
+
haiRegistered,
|
|
1116
|
+
haiVerifiedAt,
|
|
1117
|
+
};
|
|
1118
|
+
const validationError = (0, hai_1.validateClaimRequirements)(params.claim, proof.domainConfigured, proof.dnsVerified, haiRegistered);
|
|
1119
|
+
if (validationError) {
|
|
1120
|
+
return {
|
|
1121
|
+
error: `${validationError} ` +
|
|
1122
|
+
`(domainConfigured=${proof.domainConfigured}, dnsVerified=${proof.dnsVerified}, haiRegistered=${proof.haiRegistered})`,
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
// Update config
|
|
1126
|
+
api.updateConfig({ verificationClaim: params.claim });
|
|
1127
|
+
return {
|
|
1128
|
+
result: {
|
|
1129
|
+
previousClaim: currentClaim,
|
|
1130
|
+
newClaim: params.claim,
|
|
1131
|
+
proof,
|
|
1132
|
+
message: `Verification claim updated to '${params.claim}'`,
|
|
1133
|
+
},
|
|
1134
|
+
};
|
|
1135
|
+
},
|
|
1136
|
+
});
|
|
1137
|
+
// Tool: Security audit (read-only)
|
|
1138
|
+
api.registerTool({
|
|
1139
|
+
name: "jacs_audit",
|
|
1140
|
+
description: "Run a read-only JACS security audit and health checks. Returns risks, health_checks, summary, and overall_status. Does not modify state. Use this to check configuration, directories, keys, trust store, storage, and optionally re-verify recent documents.",
|
|
1141
|
+
parameters: {
|
|
1142
|
+
type: "object",
|
|
1143
|
+
properties: {
|
|
1144
|
+
configPath: {
|
|
1145
|
+
type: "string",
|
|
1146
|
+
description: "Optional path to jacs.config.json",
|
|
1147
|
+
},
|
|
1148
|
+
recentN: {
|
|
1149
|
+
type: "number",
|
|
1150
|
+
description: "Optional number of recent documents to re-verify",
|
|
1151
|
+
},
|
|
1152
|
+
},
|
|
1153
|
+
},
|
|
1154
|
+
handler: async (params) => {
|
|
1155
|
+
try {
|
|
1156
|
+
const result = (0, simple_1.audit)({
|
|
1157
|
+
configPath: params?.configPath,
|
|
1158
|
+
recentN: params?.recentN,
|
|
1159
|
+
});
|
|
1160
|
+
return { result };
|
|
1161
|
+
}
|
|
1162
|
+
catch (err) {
|
|
1163
|
+
return { error: `Audit failed: ${err.message}` };
|
|
1164
|
+
}
|
|
1165
|
+
},
|
|
1166
|
+
});
|
|
1167
|
+
// Register document type tools (agentstate, commitment, todo, conversation)
|
|
1168
|
+
(0, documents_1.registerDocumentTools)(api);
|
|
1169
|
+
}
|
|
1170
|
+
//# sourceMappingURL=index.js.map
|