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.
@@ -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