kontext-sdk 0.1.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/dist/index.js ADDED
@@ -0,0 +1,3274 @@
1
+ 'use strict';
2
+
3
+ var crypto$1 = require('crypto');
4
+ var fs = require('fs');
5
+ var path = require('path');
6
+
7
+ function _interopNamespace(e) {
8
+ if (e && e.__esModule) return e;
9
+ var n = Object.create(null);
10
+ if (e) {
11
+ Object.keys(e).forEach(function (k) {
12
+ if (k !== 'default') {
13
+ var d = Object.getOwnPropertyDescriptor(e, k);
14
+ Object.defineProperty(n, k, d.get ? d : {
15
+ enumerable: true,
16
+ get: function () { return e[k]; }
17
+ });
18
+ }
19
+ });
20
+ }
21
+ n.default = e;
22
+ return Object.freeze(n);
23
+ }
24
+
25
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
26
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
27
+
28
+ // src/types.ts
29
+ var KontextErrorCode = /* @__PURE__ */ ((KontextErrorCode2) => {
30
+ KontextErrorCode2["INITIALIZATION_ERROR"] = "INITIALIZATION_ERROR";
31
+ KontextErrorCode2["VALIDATION_ERROR"] = "VALIDATION_ERROR";
32
+ KontextErrorCode2["TASK_NOT_FOUND"] = "TASK_NOT_FOUND";
33
+ KontextErrorCode2["TASK_ALREADY_CONFIRMED"] = "TASK_ALREADY_CONFIRMED";
34
+ KontextErrorCode2["TASK_EXPIRED"] = "TASK_EXPIRED";
35
+ KontextErrorCode2["INSUFFICIENT_EVIDENCE"] = "INSUFFICIENT_EVIDENCE";
36
+ KontextErrorCode2["API_ERROR"] = "API_ERROR";
37
+ KontextErrorCode2["NETWORK_ERROR"] = "NETWORK_ERROR";
38
+ KontextErrorCode2["EXPORT_ERROR"] = "EXPORT_ERROR";
39
+ KontextErrorCode2["ANOMALY_CONFIG_ERROR"] = "ANOMALY_CONFIG_ERROR";
40
+ return KontextErrorCode2;
41
+ })(KontextErrorCode || {});
42
+ var KontextError = class extends Error {
43
+ code;
44
+ details;
45
+ constructor(code, message, details) {
46
+ super(message);
47
+ this.name = "KontextError";
48
+ this.code = code;
49
+ this.details = details;
50
+ }
51
+ };
52
+
53
+ // src/store.ts
54
+ var KontextStore = class {
55
+ actions = [];
56
+ transactions = [];
57
+ tasks = /* @__PURE__ */ new Map();
58
+ anomalies = [];
59
+ // --------------------------------------------------------------------------
60
+ // Actions
61
+ // --------------------------------------------------------------------------
62
+ /** Append an action log entry. */
63
+ addAction(action) {
64
+ this.actions.push(action);
65
+ }
66
+ /** Retrieve all action log entries. */
67
+ getActions() {
68
+ return [...this.actions];
69
+ }
70
+ /** Retrieve actions filtered by a predicate. */
71
+ queryActions(predicate) {
72
+ return this.actions.filter(predicate);
73
+ }
74
+ /** Get actions for a specific agent. */
75
+ getActionsByAgent(agentId) {
76
+ return this.actions.filter((a) => a.agentId === agentId);
77
+ }
78
+ // --------------------------------------------------------------------------
79
+ // Transactions
80
+ // --------------------------------------------------------------------------
81
+ /** Append a transaction record. */
82
+ addTransaction(tx) {
83
+ this.transactions.push(tx);
84
+ }
85
+ /** Retrieve all transaction records. */
86
+ getTransactions() {
87
+ return [...this.transactions];
88
+ }
89
+ /** Retrieve transactions filtered by a predicate. */
90
+ queryTransactions(predicate) {
91
+ return this.transactions.filter(predicate);
92
+ }
93
+ /** Get transactions for a specific agent. */
94
+ getTransactionsByAgent(agentId) {
95
+ return this.transactions.filter((t) => t.agentId === agentId);
96
+ }
97
+ /** Get the most recent N transactions for an agent. */
98
+ getRecentTransactions(agentId, limit) {
99
+ return this.transactions.filter((t) => t.agentId === agentId).slice(-limit);
100
+ }
101
+ // --------------------------------------------------------------------------
102
+ // Tasks
103
+ // --------------------------------------------------------------------------
104
+ /** Store a task. */
105
+ addTask(task) {
106
+ this.tasks.set(task.id, task);
107
+ }
108
+ /** Retrieve a task by ID. */
109
+ getTask(taskId) {
110
+ return this.tasks.get(taskId);
111
+ }
112
+ /** Update a task. */
113
+ updateTask(taskId, updates) {
114
+ const existing = this.tasks.get(taskId);
115
+ if (!existing) return void 0;
116
+ const updated = { ...existing, ...updates };
117
+ this.tasks.set(taskId, updated);
118
+ return updated;
119
+ }
120
+ /** Retrieve all tasks. */
121
+ getTasks() {
122
+ return Array.from(this.tasks.values());
123
+ }
124
+ /** Retrieve tasks filtered by a predicate. */
125
+ queryTasks(predicate) {
126
+ return Array.from(this.tasks.values()).filter(predicate);
127
+ }
128
+ // --------------------------------------------------------------------------
129
+ // Anomalies
130
+ // --------------------------------------------------------------------------
131
+ /** Append an anomaly event. */
132
+ addAnomaly(anomaly) {
133
+ this.anomalies.push(anomaly);
134
+ }
135
+ /** Retrieve all anomaly events. */
136
+ getAnomalies() {
137
+ return [...this.anomalies];
138
+ }
139
+ /** Retrieve anomalies filtered by a predicate. */
140
+ queryAnomalies(predicate) {
141
+ return this.anomalies.filter(predicate);
142
+ }
143
+ // --------------------------------------------------------------------------
144
+ // Utilities
145
+ // --------------------------------------------------------------------------
146
+ /** Get total record counts across all stores. */
147
+ getCounts() {
148
+ return {
149
+ actions: this.actions.length,
150
+ transactions: this.transactions.length,
151
+ tasks: this.tasks.size,
152
+ anomalies: this.anomalies.length
153
+ };
154
+ }
155
+ /** Clear all stored data. Useful for testing. */
156
+ clear() {
157
+ this.actions = [];
158
+ this.transactions = [];
159
+ this.tasks.clear();
160
+ this.anomalies = [];
161
+ }
162
+ };
163
+ var DIGEST_EXCLUDED_FIELDS = /* @__PURE__ */ new Set(["digest", "priorDigest"]);
164
+ function serializeForDigest(action) {
165
+ const keys = Object.keys(action).filter((k) => !DIGEST_EXCLUDED_FIELDS.has(k)).sort();
166
+ return JSON.stringify(action, keys);
167
+ }
168
+ var GENESIS_HASH = "0".repeat(64);
169
+ var DigestChain = class {
170
+ links = [];
171
+ currentDigest = GENESIS_HASH;
172
+ hrtimeBase;
173
+ constructor() {
174
+ this.hrtimeBase = process.hrtime.bigint();
175
+ }
176
+ /**
177
+ * Append an action to the digest chain.
178
+ *
179
+ * Computes: HD = SHA-256(HD-1 || Serialize(ED) || SD)
180
+ *
181
+ * @param action - The action log entry to chain
182
+ * @returns The digest link for this event
183
+ */
184
+ append(action) {
185
+ const timestamp = this.getPrecisionTimestamp();
186
+ const serialized = this.serialize(action);
187
+ const salt = this.deriveSalt(timestamp);
188
+ const priorDigest = this.currentDigest;
189
+ const digest = this.computeDigest(priorDigest, serialized, salt);
190
+ const link = {
191
+ digest,
192
+ priorDigest,
193
+ salt,
194
+ timestamp,
195
+ sequence: this.links.length,
196
+ actionId: action.id
197
+ };
198
+ this.links.push(link);
199
+ this.currentDigest = digest;
200
+ return link;
201
+ }
202
+ /**
203
+ * Get the terminal digest — the latest digest in the chain.
204
+ * This can be embedded in outgoing messages as proof of the entire action history.
205
+ */
206
+ getTerminalDigest() {
207
+ return this.currentDigest;
208
+ }
209
+ /**
210
+ * Get the number of links in the chain.
211
+ */
212
+ getChainLength() {
213
+ return this.links.length;
214
+ }
215
+ /**
216
+ * Get all digest links in the chain.
217
+ */
218
+ getLinks() {
219
+ return this.links;
220
+ }
221
+ /**
222
+ * Get a specific digest link by sequence number.
223
+ */
224
+ getLink(sequence) {
225
+ return this.links[sequence];
226
+ }
227
+ /**
228
+ * Verify the integrity of the entire digest chain.
229
+ *
230
+ * Recomputes every digest from the genesis hash and compares.
231
+ * Any tampering (modified, inserted, deleted, or reordered events)
232
+ * will cause verification to fail.
233
+ *
234
+ * @param actions - The original action logs to verify against
235
+ * @returns Verification result with timing data
236
+ */
237
+ verify(actions) {
238
+ const start = performance.now();
239
+ if (actions.length !== this.links.length) {
240
+ return {
241
+ valid: false,
242
+ linksVerified: 0,
243
+ firstInvalidIndex: 0,
244
+ verificationTimeMs: performance.now() - start,
245
+ terminalDigest: this.currentDigest
246
+ };
247
+ }
248
+ let computedDigest = GENESIS_HASH;
249
+ for (let i = 0; i < this.links.length; i++) {
250
+ const link = this.links[i];
251
+ const action = actions[i];
252
+ const serialized = this.serialize(action);
253
+ const expectedDigest = this.computeDigest(computedDigest, serialized, link.salt);
254
+ if (expectedDigest !== link.digest) {
255
+ return {
256
+ valid: false,
257
+ linksVerified: i,
258
+ firstInvalidIndex: i,
259
+ verificationTimeMs: performance.now() - start,
260
+ terminalDigest: this.currentDigest
261
+ };
262
+ }
263
+ computedDigest = expectedDigest;
264
+ }
265
+ return {
266
+ valid: true,
267
+ linksVerified: this.links.length,
268
+ firstInvalidIndex: -1,
269
+ verificationTimeMs: performance.now() - start,
270
+ terminalDigest: this.currentDigest
271
+ };
272
+ }
273
+ /**
274
+ * Verify a single link in isolation (given the expected prior digest).
275
+ *
276
+ * @param link - The digest link to verify
277
+ * @param action - The action data for this link
278
+ * @param expectedPriorDigest - The expected prior digest
279
+ * @returns Whether the link is valid
280
+ */
281
+ verifyLink(link, action, expectedPriorDigest) {
282
+ const serialized = this.serialize(action);
283
+ const expectedDigest = this.computeDigest(expectedPriorDigest, serialized, link.salt);
284
+ return expectedDigest === link.digest;
285
+ }
286
+ /**
287
+ * Export the chain data for independent verification by a third party.
288
+ * Includes all links and enough data for recomputation.
289
+ */
290
+ exportChain() {
291
+ return {
292
+ genesisHash: GENESIS_HASH,
293
+ links: [...this.links],
294
+ terminalDigest: this.currentDigest
295
+ };
296
+ }
297
+ // --------------------------------------------------------------------------
298
+ // Core cryptographic operations
299
+ // --------------------------------------------------------------------------
300
+ /**
301
+ * Compute: HD = SHA-256(HD-1 || Serialize(ED) || SD)
302
+ */
303
+ computeDigest(priorDigest, serializedEvent, salt) {
304
+ const hash = crypto$1.createHash("sha256");
305
+ hash.update(priorDigest);
306
+ hash.update(serializedEvent);
307
+ hash.update(salt);
308
+ return hash.digest("hex");
309
+ }
310
+ /**
311
+ * Deterministically serialize an action log for digest computation.
312
+ * Uses sorted keys to ensure consistent serialization regardless of
313
+ * property insertion order. Excludes digest/priorDigest fields since
314
+ * those are computed from this serialization.
315
+ */
316
+ serialize(action) {
317
+ return serializeForDigest(action);
318
+ }
319
+ /**
320
+ * Derive a salt from the event's high-precision timestamp.
321
+ * SD = SHA-256(microsecond_timestamp)
322
+ */
323
+ deriveSalt(timestamp) {
324
+ const hash = crypto$1.createHash("sha256");
325
+ hash.update(timestamp.hrtime.toString());
326
+ return hash.digest("hex");
327
+ }
328
+ /**
329
+ * Get a microsecond-precision timestamp.
330
+ * Combines wall clock time with high-resolution timer for sub-millisecond precision.
331
+ */
332
+ getPrecisionTimestamp() {
333
+ const hrtime = process.hrtime.bigint();
334
+ const microseconds = Number((hrtime - this.hrtimeBase) % 1000000n);
335
+ return {
336
+ iso: (/* @__PURE__ */ new Date()).toISOString(),
337
+ hrtime,
338
+ microseconds
339
+ };
340
+ }
341
+ };
342
+ function verifyExportedChain(chain, actions) {
343
+ const start = performance.now();
344
+ if (actions.length !== chain.links.length) {
345
+ return {
346
+ valid: false,
347
+ linksVerified: 0,
348
+ firstInvalidIndex: 0,
349
+ verificationTimeMs: performance.now() - start,
350
+ terminalDigest: chain.terminalDigest
351
+ };
352
+ }
353
+ let computedDigest = chain.genesisHash;
354
+ for (let i = 0; i < chain.links.length; i++) {
355
+ const link = chain.links[i];
356
+ const action = actions[i];
357
+ const serialized = serializeForDigest(action);
358
+ const hash = crypto$1.createHash("sha256");
359
+ hash.update(computedDigest);
360
+ hash.update(serialized);
361
+ hash.update(link.salt);
362
+ const expectedDigest = hash.digest("hex");
363
+ if (expectedDigest !== link.digest) {
364
+ return {
365
+ valid: false,
366
+ linksVerified: i,
367
+ firstInvalidIndex: i,
368
+ verificationTimeMs: performance.now() - start,
369
+ terminalDigest: chain.terminalDigest
370
+ };
371
+ }
372
+ computedDigest = expectedDigest;
373
+ }
374
+ const valid = computedDigest === chain.terminalDigest;
375
+ return {
376
+ valid,
377
+ linksVerified: chain.links.length,
378
+ firstInvalidIndex: valid ? -1 : chain.links.length,
379
+ verificationTimeMs: performance.now() - start,
380
+ terminalDigest: chain.terminalDigest
381
+ };
382
+ }
383
+
384
+ // src/utils.ts
385
+ function generateId() {
386
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
387
+ return crypto.randomUUID();
388
+ }
389
+ const timestamp = Date.now().toString(36);
390
+ const random = Math.random().toString(36).substring(2, 10);
391
+ return `${timestamp}-${random}`;
392
+ }
393
+ function now() {
394
+ return (/* @__PURE__ */ new Date()).toISOString();
395
+ }
396
+ function isWithinDateRange(date, start, end) {
397
+ const d = typeof date === "string" ? new Date(date) : date;
398
+ return d >= start && d <= end;
399
+ }
400
+ function parseAmount(amount) {
401
+ const parsed = parseFloat(amount);
402
+ return parsed;
403
+ }
404
+ function toCsv(records) {
405
+ if (records.length === 0) return "";
406
+ const firstRecord = records[0];
407
+ if (!firstRecord) return "";
408
+ const headers = Object.keys(firstRecord);
409
+ const headerRow = headers.join(",");
410
+ const rows = records.map((record) => {
411
+ return headers.map((header) => {
412
+ const value = record[header];
413
+ if (value === null || value === void 0) return "";
414
+ const str = typeof value === "object" ? JSON.stringify(value) : String(value);
415
+ if (str.includes(",") || str.includes('"') || str.includes("\n")) {
416
+ return `"${str.replace(/"/g, '""')}"`;
417
+ }
418
+ return str;
419
+ }).join(",");
420
+ });
421
+ return [headerRow, ...rows].join("\n");
422
+ }
423
+ function clamp(value, min, max) {
424
+ return Math.min(Math.max(value, min), max);
425
+ }
426
+ var ActionLogger = class {
427
+ config;
428
+ store;
429
+ digestChain;
430
+ batch = [];
431
+ flushTimer = null;
432
+ batchSize;
433
+ flushIntervalMs;
434
+ isCloudMode;
435
+ constructor(config, store) {
436
+ this.config = config;
437
+ this.store = store;
438
+ this.digestChain = new DigestChain();
439
+ this.batchSize = config.batchSize ?? 50;
440
+ this.flushIntervalMs = config.flushIntervalMs ?? 5e3;
441
+ this.isCloudMode = !!config.apiKey;
442
+ this.flushTimer = setInterval(() => {
443
+ void this.flush();
444
+ }, this.flushIntervalMs);
445
+ if (this.flushTimer && typeof this.flushTimer === "object" && "unref" in this.flushTimer) {
446
+ this.flushTimer.unref();
447
+ }
448
+ }
449
+ /**
450
+ * Log a generic agent action.
451
+ *
452
+ * @param input - Action details including type, description, agentId, and metadata
453
+ * @returns The created ActionLog entry
454
+ *
455
+ * @example
456
+ * ```typescript
457
+ * const action = await logger.log({
458
+ * type: 'approval',
459
+ * description: 'Agent approved USDC spending',
460
+ * agentId: 'agent-1',
461
+ * metadata: { spender: '0x...', amount: '1000' },
462
+ * });
463
+ * ```
464
+ */
465
+ async log(input) {
466
+ const action = {
467
+ id: generateId(),
468
+ timestamp: now(),
469
+ projectId: this.config.projectId,
470
+ agentId: input.agentId,
471
+ correlationId: input.correlationId ?? generateId(),
472
+ type: input.type,
473
+ description: input.description,
474
+ metadata: input.metadata ?? {}
475
+ };
476
+ const link = this.digestChain.append(action);
477
+ action.digest = link.digest;
478
+ action.priorDigest = link.priorDigest;
479
+ this.store.addAction(action);
480
+ this.batch.push(action);
481
+ if (this.batch.length >= this.batchSize) {
482
+ await this.flush();
483
+ }
484
+ if (this.config.debug) {
485
+ this.debugLog("Action logged", action);
486
+ }
487
+ return action;
488
+ }
489
+ /**
490
+ * Log a cryptocurrency transaction with full chain details.
491
+ *
492
+ * @param input - Transaction details including txHash, chain, amount, token, from, to
493
+ * @returns The created TransactionRecord
494
+ *
495
+ * @example
496
+ * ```typescript
497
+ * const tx = await logger.logTransaction({
498
+ * txHash: '0xabc123...',
499
+ * chain: 'base',
500
+ * amount: '100.00',
501
+ * token: 'USDC',
502
+ * from: '0xSender...',
503
+ * to: '0xReceiver...',
504
+ * agentId: 'payment-agent-1',
505
+ * });
506
+ * ```
507
+ */
508
+ async logTransaction(input) {
509
+ this.validateTransactionInput(input);
510
+ const correlationId = input.correlationId ?? generateId();
511
+ const record = {
512
+ id: generateId(),
513
+ timestamp: now(),
514
+ projectId: this.config.projectId,
515
+ agentId: input.agentId,
516
+ correlationId,
517
+ type: "transaction",
518
+ description: `${input.token} transfer of ${input.amount} on ${input.chain}`,
519
+ metadata: {
520
+ ...input.metadata
521
+ },
522
+ txHash: input.txHash,
523
+ chain: input.chain,
524
+ amount: input.amount,
525
+ token: input.token,
526
+ from: input.from,
527
+ to: input.to
528
+ };
529
+ const link = this.digestChain.append(record);
530
+ record.digest = link.digest;
531
+ record.priorDigest = link.priorDigest;
532
+ this.store.addTransaction(record);
533
+ this.store.addAction(record);
534
+ this.batch.push(record);
535
+ if (this.batch.length >= this.batchSize) {
536
+ await this.flush();
537
+ }
538
+ if (this.config.debug) {
539
+ this.debugLog("Transaction logged", record);
540
+ }
541
+ return record;
542
+ }
543
+ /**
544
+ * Flush the current batch of logs.
545
+ * In local mode, writes to a JSON file.
546
+ * In cloud mode, sends to the Kontext API.
547
+ */
548
+ async flush() {
549
+ if (this.batch.length === 0) return;
550
+ const toFlush = [...this.batch];
551
+ this.batch = [];
552
+ if (this.isCloudMode) {
553
+ await this.flushToApi(toFlush);
554
+ } else {
555
+ this.flushToFile(toFlush);
556
+ }
557
+ }
558
+ /**
559
+ * Stop the logger and flush any remaining logs.
560
+ */
561
+ async destroy() {
562
+ if (this.flushTimer) {
563
+ clearInterval(this.flushTimer);
564
+ this.flushTimer = null;
565
+ }
566
+ await this.flush();
567
+ }
568
+ // --------------------------------------------------------------------------
569
+ // Digest Chain Access
570
+ // --------------------------------------------------------------------------
571
+ /**
572
+ * Get the terminal digest — the latest SHA-256 digest in the chain.
573
+ * Can be embedded in outgoing messages as tamper-evident proof.
574
+ */
575
+ getTerminalDigest() {
576
+ return this.digestChain.getTerminalDigest();
577
+ }
578
+ /**
579
+ * Get the full digest chain for export or verification.
580
+ */
581
+ getDigestChain() {
582
+ return this.digestChain;
583
+ }
584
+ /**
585
+ * Verify the integrity of the digest chain against stored actions.
586
+ */
587
+ verifyChain(actions) {
588
+ return this.digestChain.verify(actions);
589
+ }
590
+ // --------------------------------------------------------------------------
591
+ // Private helpers
592
+ // --------------------------------------------------------------------------
593
+ validateTransactionInput(input) {
594
+ const amount = parseAmount(input.amount);
595
+ if (isNaN(amount) || amount < 0) {
596
+ throw new KontextError(
597
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
598
+ `Invalid transaction amount: ${input.amount}`,
599
+ { field: "amount", value: input.amount }
600
+ );
601
+ }
602
+ if (!input.txHash || input.txHash.trim() === "") {
603
+ throw new KontextError(
604
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
605
+ "Transaction hash is required",
606
+ { field: "txHash" }
607
+ );
608
+ }
609
+ if (!input.from || input.from.trim() === "") {
610
+ throw new KontextError(
611
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
612
+ "Sender address (from) is required",
613
+ { field: "from" }
614
+ );
615
+ }
616
+ if (!input.to || input.to.trim() === "") {
617
+ throw new KontextError(
618
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
619
+ "Recipient address (to) is required",
620
+ { field: "to" }
621
+ );
622
+ }
623
+ const validChains = ["ethereum", "base", "polygon", "arbitrum", "optimism", "arc"];
624
+ if (!validChains.includes(input.chain)) {
625
+ throw new KontextError(
626
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
627
+ `Invalid chain: ${input.chain}. Must be one of: ${validChains.join(", ")}`,
628
+ { field: "chain", value: input.chain }
629
+ );
630
+ }
631
+ const validTokens = ["USDC", "USDT", "DAI", "EURC"];
632
+ if (!validTokens.includes(input.token)) {
633
+ throw new KontextError(
634
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
635
+ `Invalid token: ${input.token}. Must be one of: ${validTokens.join(", ")}`,
636
+ { field: "token", value: input.token }
637
+ );
638
+ }
639
+ }
640
+ flushToFile(actions) {
641
+ const outputDir = this.config.localOutputDir ?? ".kontext";
642
+ const logDir = path__namespace.join(outputDir, "logs");
643
+ try {
644
+ fs__namespace.mkdirSync(logDir, { recursive: true });
645
+ const filename = `actions-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.jsonl`;
646
+ const filePath = path__namespace.join(logDir, filename);
647
+ const lines = actions.map((a) => JSON.stringify(a)).join("\n") + "\n";
648
+ fs__namespace.appendFileSync(filePath, lines, "utf-8");
649
+ } catch (error) {
650
+ if (this.config.debug) {
651
+ this.debugLog("Failed to write log file", { error });
652
+ }
653
+ }
654
+ }
655
+ async flushToApi(actions) {
656
+ const apiUrl = this.config.apiUrl ?? "https://api.kontext.dev";
657
+ try {
658
+ const response = await fetch(`${apiUrl}/v1/actions`, {
659
+ method: "POST",
660
+ headers: {
661
+ "Content-Type": "application/json",
662
+ Authorization: `Bearer ${this.config.apiKey}`,
663
+ "X-Project-Id": this.config.projectId
664
+ },
665
+ body: JSON.stringify({ actions })
666
+ });
667
+ if (!response.ok) {
668
+ throw new KontextError(
669
+ "API_ERROR" /* API_ERROR */,
670
+ `API request failed with status ${response.status}`,
671
+ { status: response.status }
672
+ );
673
+ }
674
+ } catch (error) {
675
+ if (error instanceof KontextError) throw error;
676
+ if (this.config.debug) {
677
+ this.debugLog("API flush failed, falling back to local file", { error });
678
+ }
679
+ this.flushToFile(actions);
680
+ }
681
+ }
682
+ debugLog(message, data) {
683
+ const timestamp = now();
684
+ console.debug(`[Kontext ${timestamp}] ${message}`, data ? JSON.stringify(data, null, 2) : "");
685
+ }
686
+ };
687
+
688
+ // src/tasks.ts
689
+ var DEFAULT_EXPIRATION_MS = 24 * 60 * 60 * 1e3;
690
+ var TaskManager = class {
691
+ config;
692
+ store;
693
+ constructor(config, store) {
694
+ this.config = config;
695
+ this.store = store;
696
+ }
697
+ /**
698
+ * Create a new tracked task.
699
+ *
700
+ * @param input - Task details including description, agentId, and required evidence types
701
+ * @returns The created Task
702
+ *
703
+ * @example
704
+ * ```typescript
705
+ * const task = await taskManager.createTask({
706
+ * description: 'Transfer 100 USDC to vendor wallet',
707
+ * agentId: 'payment-agent-1',
708
+ * requiredEvidence: ['txHash', 'receipt'],
709
+ * });
710
+ * ```
711
+ */
712
+ async createTask(input) {
713
+ this.validateCreateInput(input);
714
+ const id = generateId();
715
+ const timestamp = now();
716
+ const expiresInMs = input.expiresInMs ?? DEFAULT_EXPIRATION_MS;
717
+ const task = {
718
+ id,
719
+ projectId: this.config.projectId,
720
+ description: input.description,
721
+ agentId: input.agentId,
722
+ status: "pending",
723
+ requiredEvidence: input.requiredEvidence,
724
+ providedEvidence: null,
725
+ correlationId: input.correlationId ?? generateId(),
726
+ createdAt: timestamp,
727
+ updatedAt: timestamp,
728
+ confirmedAt: null,
729
+ expiresAt: new Date(Date.now() + expiresInMs).toISOString(),
730
+ metadata: input.metadata ?? {}
731
+ };
732
+ this.store.addTask(task);
733
+ if (this.config.debug) {
734
+ console.debug(`[Kontext] Task created: ${id} - ${input.description}`);
735
+ }
736
+ return task;
737
+ }
738
+ /**
739
+ * Confirm a task by providing evidence.
740
+ * Validates that all required evidence types are present.
741
+ *
742
+ * @param input - Task ID and evidence data
743
+ * @returns The updated Task
744
+ *
745
+ * @example
746
+ * ```typescript
747
+ * const confirmed = await taskManager.confirmTask({
748
+ * taskId: 'task-123',
749
+ * evidence: {
750
+ * txHash: '0xabc123...',
751
+ * receipt: { status: 'confirmed', blockNumber: 12345 },
752
+ * },
753
+ * });
754
+ * ```
755
+ */
756
+ async confirmTask(input) {
757
+ const task = this.store.getTask(input.taskId);
758
+ if (!task) {
759
+ throw new KontextError(
760
+ "TASK_NOT_FOUND" /* TASK_NOT_FOUND */,
761
+ `Task not found: ${input.taskId}`,
762
+ { taskId: input.taskId }
763
+ );
764
+ }
765
+ if (task.status === "confirmed") {
766
+ throw new KontextError(
767
+ "TASK_ALREADY_CONFIRMED" /* TASK_ALREADY_CONFIRMED */,
768
+ `Task already confirmed: ${input.taskId}`,
769
+ { taskId: input.taskId, confirmedAt: task.confirmedAt }
770
+ );
771
+ }
772
+ if (task.expiresAt && new Date(task.expiresAt) < /* @__PURE__ */ new Date()) {
773
+ this.store.updateTask(input.taskId, {
774
+ status: "expired",
775
+ updatedAt: now()
776
+ });
777
+ throw new KontextError(
778
+ "TASK_EXPIRED" /* TASK_EXPIRED */,
779
+ `Task has expired: ${input.taskId}`,
780
+ { taskId: input.taskId, expiresAt: task.expiresAt }
781
+ );
782
+ }
783
+ const missingEvidence = this.findMissingEvidence(task.requiredEvidence, input.evidence);
784
+ if (missingEvidence.length > 0) {
785
+ throw new KontextError(
786
+ "INSUFFICIENT_EVIDENCE" /* INSUFFICIENT_EVIDENCE */,
787
+ `Missing required evidence: ${missingEvidence.join(", ")}`,
788
+ { taskId: input.taskId, missingEvidence }
789
+ );
790
+ }
791
+ const timestamp = now();
792
+ const updated = this.store.updateTask(input.taskId, {
793
+ status: "confirmed",
794
+ providedEvidence: input.evidence,
795
+ confirmedAt: timestamp,
796
+ updatedAt: timestamp
797
+ });
798
+ if (!updated) {
799
+ throw new KontextError(
800
+ "TASK_NOT_FOUND" /* TASK_NOT_FOUND */,
801
+ `Failed to update task: ${input.taskId}`,
802
+ { taskId: input.taskId }
803
+ );
804
+ }
805
+ if (this.config.debug) {
806
+ console.debug(`[Kontext] Task confirmed: ${input.taskId}`);
807
+ }
808
+ return updated;
809
+ }
810
+ /**
811
+ * Get the current status and details of a task.
812
+ *
813
+ * @param taskId - The task identifier
814
+ * @returns The task, or undefined if not found
815
+ */
816
+ async getTaskStatus(taskId) {
817
+ const task = this.store.getTask(taskId);
818
+ if (!task) return void 0;
819
+ if (task.expiresAt && task.status === "pending" && new Date(task.expiresAt) < /* @__PURE__ */ new Date()) {
820
+ const updated = this.store.updateTask(taskId, {
821
+ status: "expired",
822
+ updatedAt: now()
823
+ });
824
+ return updated;
825
+ }
826
+ return task;
827
+ }
828
+ /**
829
+ * Mark a task as in-progress.
830
+ *
831
+ * @param taskId - The task identifier
832
+ * @returns The updated Task
833
+ */
834
+ async startTask(taskId) {
835
+ const task = this.store.getTask(taskId);
836
+ if (!task) {
837
+ throw new KontextError(
838
+ "TASK_NOT_FOUND" /* TASK_NOT_FOUND */,
839
+ `Task not found: ${taskId}`,
840
+ { taskId }
841
+ );
842
+ }
843
+ if (task.status !== "pending") {
844
+ throw new KontextError(
845
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
846
+ `Task cannot be started from status: ${task.status}`,
847
+ { taskId, currentStatus: task.status }
848
+ );
849
+ }
850
+ const updated = this.store.updateTask(taskId, {
851
+ status: "in_progress",
852
+ updatedAt: now()
853
+ });
854
+ if (!updated) {
855
+ throw new KontextError(
856
+ "TASK_NOT_FOUND" /* TASK_NOT_FOUND */,
857
+ `Failed to update task: ${taskId}`,
858
+ { taskId }
859
+ );
860
+ }
861
+ return updated;
862
+ }
863
+ /**
864
+ * Mark a task as failed.
865
+ *
866
+ * @param taskId - The task identifier
867
+ * @param reason - Reason for failure
868
+ * @returns The updated Task
869
+ */
870
+ async failTask(taskId, reason) {
871
+ const task = this.store.getTask(taskId);
872
+ if (!task) {
873
+ throw new KontextError(
874
+ "TASK_NOT_FOUND" /* TASK_NOT_FOUND */,
875
+ `Task not found: ${taskId}`,
876
+ { taskId }
877
+ );
878
+ }
879
+ const updated = this.store.updateTask(taskId, {
880
+ status: "failed",
881
+ updatedAt: now(),
882
+ metadata: { ...task.metadata, failureReason: reason }
883
+ });
884
+ if (!updated) {
885
+ throw new KontextError(
886
+ "TASK_NOT_FOUND" /* TASK_NOT_FOUND */,
887
+ `Failed to update task: ${taskId}`,
888
+ { taskId }
889
+ );
890
+ }
891
+ return updated;
892
+ }
893
+ /**
894
+ * Get all tasks, optionally filtered by status.
895
+ *
896
+ * @param status - Optional status filter
897
+ * @returns Array of matching tasks
898
+ */
899
+ getTasks(status) {
900
+ if (status) {
901
+ return this.store.queryTasks((t) => t.status === status);
902
+ }
903
+ return this.store.getTasks();
904
+ }
905
+ // --------------------------------------------------------------------------
906
+ // Private helpers
907
+ // --------------------------------------------------------------------------
908
+ validateCreateInput(input) {
909
+ if (!input.description || input.description.trim() === "") {
910
+ throw new KontextError(
911
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
912
+ "Task description is required",
913
+ { field: "description" }
914
+ );
915
+ }
916
+ if (!input.agentId || input.agentId.trim() === "") {
917
+ throw new KontextError(
918
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
919
+ "Agent ID is required",
920
+ { field: "agentId" }
921
+ );
922
+ }
923
+ if (!input.requiredEvidence || input.requiredEvidence.length === 0) {
924
+ throw new KontextError(
925
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
926
+ "At least one required evidence type must be specified",
927
+ { field: "requiredEvidence" }
928
+ );
929
+ }
930
+ }
931
+ findMissingEvidence(required, provided) {
932
+ return required.filter((key) => {
933
+ const value = provided[key];
934
+ return value === void 0 || value === null;
935
+ });
936
+ }
937
+ };
938
+
939
+ // src/audit.ts
940
+ var AuditExporter = class {
941
+ config;
942
+ store;
943
+ constructor(config, store) {
944
+ this.config = config;
945
+ this.store = store;
946
+ }
947
+ /**
948
+ * Export audit data in the specified format.
949
+ *
950
+ * @param options - Export configuration including format, date range, and filters
951
+ * @returns ExportResult containing the formatted data
952
+ *
953
+ * @example
954
+ * ```typescript
955
+ * const result = await exporter.export({
956
+ * format: 'json',
957
+ * dateRange: { start: new Date('2026-01-01'), end: new Date() },
958
+ * agentIds: ['payment-agent-1'],
959
+ * includeTasks: true,
960
+ * includeAnomalies: true,
961
+ * });
962
+ * ```
963
+ */
964
+ async export(options) {
965
+ const actions = this.filterActions(options);
966
+ const transactions = this.filterTransactions(options);
967
+ const tasks = options.includeTasks ? this.filterTasks(options) : [];
968
+ const anomalies = options.includeAnomalies ? this.filterAnomalies(options) : [];
969
+ const records = {
970
+ actions,
971
+ transactions,
972
+ tasks,
973
+ anomalies,
974
+ exportMetadata: {
975
+ projectId: this.config.projectId,
976
+ exportedAt: now(),
977
+ filters: {
978
+ dateRange: options.dateRange ? { start: options.dateRange.start.toISOString(), end: options.dateRange.end.toISOString() } : null,
979
+ agentIds: options.agentIds ?? null,
980
+ types: options.types ?? null,
981
+ chains: options.chains ?? null
982
+ }
983
+ }
984
+ };
985
+ const totalCount = actions.length + transactions.length + tasks.length + anomalies.length;
986
+ let data;
987
+ if (options.format === "csv") {
988
+ data = this.formatAsCsv(actions, transactions, tasks, anomalies);
989
+ } else {
990
+ data = JSON.stringify(records, null, 2);
991
+ }
992
+ return {
993
+ format: options.format,
994
+ exportedAt: now(),
995
+ recordCount: totalCount,
996
+ data
997
+ };
998
+ }
999
+ /**
1000
+ * Generate a compliance report for a given period.
1001
+ *
1002
+ * @param options - Report configuration including type, period, and filters
1003
+ * @returns ComplianceReport with summary statistics and detailed records
1004
+ *
1005
+ * @example
1006
+ * ```typescript
1007
+ * const report = await exporter.generateReport({
1008
+ * type: 'compliance',
1009
+ * period: { start: new Date('2026-01-01'), end: new Date() },
1010
+ * });
1011
+ * ```
1012
+ */
1013
+ async generateReport(options) {
1014
+ const exportOptions = {
1015
+ format: "json",
1016
+ dateRange: options.period,
1017
+ agentIds: options.agentIds,
1018
+ includeTasks: true,
1019
+ includeAnomalies: true
1020
+ };
1021
+ const actions = this.filterActions(exportOptions);
1022
+ const transactions = this.filterTransactions(exportOptions);
1023
+ const tasks = this.filterTasks(exportOptions);
1024
+ const anomalies = this.filterAnomalies(exportOptions);
1025
+ const confirmedTasks = tasks.filter((t) => t.status === "confirmed").length;
1026
+ const failedTasks = tasks.filter((t) => t.status === "failed").length;
1027
+ const taskCompletionRate = tasks.length > 0 ? confirmedTasks / tasks.length : 1;
1028
+ const anomalyRate = actions.length > 0 ? 1 - anomalies.length / actions.length : 1;
1029
+ const averageTrustScore = Math.round(
1030
+ (taskCompletionRate * 50 + anomalyRate * 50) * 100
1031
+ ) / 100;
1032
+ const report = {
1033
+ id: generateId(),
1034
+ type: options.type,
1035
+ generatedAt: now(),
1036
+ period: options.period,
1037
+ projectId: this.config.projectId,
1038
+ summary: {
1039
+ totalActions: actions.length,
1040
+ totalTransactions: transactions.length,
1041
+ totalTasks: tasks.length,
1042
+ confirmedTasks,
1043
+ failedTasks,
1044
+ totalAnomalies: anomalies.length,
1045
+ averageTrustScore
1046
+ },
1047
+ actions,
1048
+ transactions,
1049
+ tasks,
1050
+ anomalies
1051
+ };
1052
+ return report;
1053
+ }
1054
+ // --------------------------------------------------------------------------
1055
+ // SAR Report Generation
1056
+ // --------------------------------------------------------------------------
1057
+ /**
1058
+ * Generate a Suspicious Activity Report (SAR) template.
1059
+ *
1060
+ * This produces a structured SAR template populated with data from the SDK.
1061
+ * It is a template/structure, not an actual regulatory filing. Organizations
1062
+ * should review and supplement this data before formal submission.
1063
+ *
1064
+ * @param options - Report configuration including period and optional filters
1065
+ * @returns SARReport template populated with flagged transactions and anomalies
1066
+ *
1067
+ * @example
1068
+ * ```typescript
1069
+ * const sar = await exporter.generateSARReport({
1070
+ * type: 'sar',
1071
+ * period: { start: new Date('2026-01-01'), end: new Date() },
1072
+ * });
1073
+ * console.log(`SAR contains ${sar.suspiciousTransactions.length} flagged transactions`);
1074
+ * ```
1075
+ */
1076
+ async generateSARReport(options) {
1077
+ const exportOptions = {
1078
+ format: "json",
1079
+ dateRange: options.period,
1080
+ agentIds: options.agentIds,
1081
+ includeTasks: true,
1082
+ includeAnomalies: true
1083
+ };
1084
+ const actions = this.filterActions(exportOptions);
1085
+ const transactions = this.filterTransactions(exportOptions);
1086
+ const anomalies = this.filterAnomalies(exportOptions);
1087
+ const anomalyActionIds = new Set(anomalies.map((a) => a.actionId));
1088
+ const suspiciousTransactions = transactions.filter(
1089
+ (tx) => anomalyActionIds.has(tx.id)
1090
+ );
1091
+ const anomalyAgentIds = new Set(anomalies.map((a) => a.agentId));
1092
+ const additionalSuspicious = suspiciousTransactions.length === 0 ? transactions.filter((tx) => anomalyAgentIds.has(tx.agentId)) : [];
1093
+ const allSuspicious = [...suspiciousTransactions, ...additionalSuspicious];
1094
+ const subjectMap = /* @__PURE__ */ new Map();
1095
+ for (const tx of allSuspicious) {
1096
+ if (!subjectMap.has(tx.agentId)) {
1097
+ const agentTxs = transactions.filter((t) => t.agentId === tx.agentId);
1098
+ const addresses = /* @__PURE__ */ new Set();
1099
+ for (const t of agentTxs) {
1100
+ addresses.add(t.from);
1101
+ addresses.add(t.to);
1102
+ }
1103
+ subjectMap.set(tx.agentId, {
1104
+ name: tx.agentId,
1105
+ agentId: tx.agentId,
1106
+ addresses: Array.from(addresses)
1107
+ });
1108
+ }
1109
+ }
1110
+ const totalAmount = allSuspicious.reduce((sum, tx) => sum + (parseAmount(tx.amount) || 0), 0).toFixed(2);
1111
+ const tokenCounts = /* @__PURE__ */ new Map();
1112
+ for (const tx of allSuspicious) {
1113
+ tokenCounts.set(tx.token, (tokenCounts.get(tx.token) ?? 0) + 1);
1114
+ }
1115
+ const currency = tokenCounts.size > 0 ? Array.from(tokenCounts.entries()).sort((a, b) => b[1] - a[1])[0][0] : "USDC";
1116
+ const activityCategories = Array.from(
1117
+ new Set(anomalies.map((a) => this.anomalyTypeToCategory(a.type)))
1118
+ );
1119
+ const narrative = this.generateSARNarrative(
1120
+ allSuspicious,
1121
+ anomalies,
1122
+ options.period
1123
+ );
1124
+ return {
1125
+ id: generateId(),
1126
+ type: "sar",
1127
+ generatedAt: now(),
1128
+ period: options.period,
1129
+ projectId: this.config.projectId,
1130
+ filingInstitution: this.config.projectId,
1131
+ subjects: Array.from(subjectMap.values()),
1132
+ narrative,
1133
+ activityCategories,
1134
+ totalAmount,
1135
+ currency,
1136
+ suspiciousTransactions: allSuspicious,
1137
+ anomalies,
1138
+ supportingActions: actions.filter(
1139
+ (a) => anomalyAgentIds.has(a.agentId)
1140
+ ),
1141
+ isContinuingActivity: false,
1142
+ priorReportId: null,
1143
+ status: "draft"
1144
+ };
1145
+ }
1146
+ // --------------------------------------------------------------------------
1147
+ // CTR Report Generation
1148
+ // --------------------------------------------------------------------------
1149
+ /**
1150
+ * Generate a Currency Transaction Report (CTR) template.
1151
+ *
1152
+ * This produces a structured CTR template for transactions that meet or
1153
+ * exceed reporting thresholds. It is a template/structure, not an actual
1154
+ * regulatory filing. Organizations should review and supplement this data
1155
+ * before formal submission.
1156
+ *
1157
+ * @param options - Report configuration including period and optional filters
1158
+ * @returns CTRReport template populated with qualifying transactions
1159
+ *
1160
+ * @example
1161
+ * ```typescript
1162
+ * const ctr = await exporter.generateCTRReport({
1163
+ * type: 'ctr',
1164
+ * period: { start: new Date('2026-01-01'), end: new Date() },
1165
+ * });
1166
+ * console.log(`CTR covers ${ctr.transactions.length} reportable transactions`);
1167
+ * ```
1168
+ */
1169
+ async generateCTRReport(options) {
1170
+ const REPORTING_THRESHOLD2 = 1e4;
1171
+ const exportOptions = {
1172
+ format: "json",
1173
+ dateRange: options.period,
1174
+ agentIds: options.agentIds,
1175
+ includeTasks: false,
1176
+ includeAnomalies: false
1177
+ };
1178
+ const actions = this.filterActions(exportOptions);
1179
+ const transactions = this.filterTransactions(exportOptions);
1180
+ const reportableTransactions = transactions.filter((tx) => {
1181
+ const amount = parseAmount(tx.amount);
1182
+ return !isNaN(amount) && amount >= REPORTING_THRESHOLD2;
1183
+ });
1184
+ const agentDailyTotals = /* @__PURE__ */ new Map();
1185
+ for (const tx of transactions) {
1186
+ const day = tx.timestamp.split("T")[0];
1187
+ const key = `${tx.agentId}:${day}`;
1188
+ agentDailyTotals.set(key, (agentDailyTotals.get(key) ?? 0) + (parseAmount(tx.amount) || 0));
1189
+ }
1190
+ const structuringKeys = /* @__PURE__ */ new Set();
1191
+ for (const [key, total] of agentDailyTotals.entries()) {
1192
+ if (total >= REPORTING_THRESHOLD2) {
1193
+ structuringKeys.add(key);
1194
+ }
1195
+ }
1196
+ const reportableIds = new Set(reportableTransactions.map((tx) => tx.id));
1197
+ const additionalStructuring = transactions.filter((tx) => {
1198
+ if (reportableIds.has(tx.id)) return false;
1199
+ const day = tx.timestamp.split("T")[0];
1200
+ const key = `${tx.agentId}:${day}`;
1201
+ return structuringKeys.has(key);
1202
+ });
1203
+ const allReportable = [...reportableTransactions, ...additionalStructuring];
1204
+ const isAggregated = additionalStructuring.length > 0;
1205
+ const conductorMap = /* @__PURE__ */ new Map();
1206
+ for (const tx of allReportable) {
1207
+ if (!conductorMap.has(tx.agentId)) {
1208
+ const agentTxs = allReportable.filter((t) => t.agentId === tx.agentId);
1209
+ const addresses = /* @__PURE__ */ new Set();
1210
+ for (const t of agentTxs) {
1211
+ addresses.add(t.from);
1212
+ addresses.add(t.to);
1213
+ }
1214
+ conductorMap.set(tx.agentId, {
1215
+ name: tx.agentId,
1216
+ agentId: tx.agentId,
1217
+ addresses: Array.from(addresses)
1218
+ });
1219
+ }
1220
+ }
1221
+ let cashIn = 0;
1222
+ let cashOut = 0;
1223
+ for (const tx of allReportable) {
1224
+ const amount = parseAmount(tx.amount) || 0;
1225
+ cashOut += amount;
1226
+ cashIn += amount;
1227
+ }
1228
+ const chainsInvolved = Array.from(
1229
+ new Set(allReportable.map((tx) => tx.chain))
1230
+ );
1231
+ const tokenCounts = /* @__PURE__ */ new Map();
1232
+ for (const tx of allReportable) {
1233
+ tokenCounts.set(tx.token, (tokenCounts.get(tx.token) ?? 0) + 1);
1234
+ }
1235
+ const currency = tokenCounts.size > 0 ? Array.from(tokenCounts.entries()).sort((a, b) => b[1] - a[1])[0][0] : "USDC";
1236
+ return {
1237
+ id: generateId(),
1238
+ type: "ctr",
1239
+ generatedAt: now(),
1240
+ period: options.period,
1241
+ projectId: this.config.projectId,
1242
+ filingInstitution: this.config.projectId,
1243
+ conductors: Array.from(conductorMap.values()),
1244
+ transactions: allReportable,
1245
+ totalCashIn: cashIn.toFixed(2),
1246
+ totalCashOut: cashOut.toFixed(2),
1247
+ currency,
1248
+ isAggregated,
1249
+ chainsInvolved,
1250
+ supportingActions: actions.filter(
1251
+ (a) => conductorMap.has(a.agentId)
1252
+ ),
1253
+ status: "draft"
1254
+ };
1255
+ }
1256
+ // --------------------------------------------------------------------------
1257
+ // SAR/CTR helpers
1258
+ // --------------------------------------------------------------------------
1259
+ anomalyTypeToCategory(type) {
1260
+ const mapping = {
1261
+ unusualAmount: "Unusual transaction amount",
1262
+ frequencySpike: "Unusually high transaction frequency",
1263
+ newDestination: "Transactions to unknown destinations",
1264
+ offHoursActivity: "Activity during unusual hours",
1265
+ rapidSuccession: "Rapid succession of transactions",
1266
+ roundAmount: "Potential structuring (round amounts)"
1267
+ };
1268
+ return mapping[type] ?? `Other: ${type}`;
1269
+ }
1270
+ generateSARNarrative(transactions, anomalies, period) {
1271
+ const startDate = period.start.toISOString().split("T")[0];
1272
+ const endDate = period.end.toISOString().split("T")[0];
1273
+ const parts = [];
1274
+ parts.push(
1275
+ `During the period from ${startDate} to ${endDate}, ${transactions.length} transaction(s) were identified as potentially suspicious.`
1276
+ );
1277
+ if (anomalies.length > 0) {
1278
+ const typeCounts = /* @__PURE__ */ new Map();
1279
+ for (const a of anomalies) {
1280
+ typeCounts.set(a.type, (typeCounts.get(a.type) ?? 0) + 1);
1281
+ }
1282
+ const typeDesc = Array.from(typeCounts.entries()).map(([type, count]) => `${this.anomalyTypeToCategory(type)} (${count} occurrence(s))`).join("; ");
1283
+ parts.push(`Anomaly detection identified the following patterns: ${typeDesc}.`);
1284
+ }
1285
+ const totalAmount = transactions.reduce((sum, tx) => sum + (parseAmount(tx.amount) || 0), 0).toFixed(2);
1286
+ parts.push(`Total amount involved: ${totalAmount}.`);
1287
+ const agents = new Set(transactions.map((t) => t.agentId));
1288
+ parts.push(`Agent(s) involved: ${Array.from(agents).join(", ")}.`);
1289
+ parts.push(
1290
+ "This report is generated as a template for review. Additional investigation and supporting documentation should be attached before filing."
1291
+ );
1292
+ return parts.join(" ");
1293
+ }
1294
+ // --------------------------------------------------------------------------
1295
+ // Private filtering
1296
+ // --------------------------------------------------------------------------
1297
+ filterActions(options) {
1298
+ return this.store.queryActions((action) => {
1299
+ if (options.dateRange && !isWithinDateRange(action.timestamp, options.dateRange.start, options.dateRange.end)) {
1300
+ return false;
1301
+ }
1302
+ if (options.agentIds && !options.agentIds.includes(action.agentId)) {
1303
+ return false;
1304
+ }
1305
+ if (options.types && !options.types.includes(action.type)) {
1306
+ return false;
1307
+ }
1308
+ return true;
1309
+ });
1310
+ }
1311
+ filterTransactions(options) {
1312
+ return this.store.queryTransactions((tx) => {
1313
+ if (options.dateRange && !isWithinDateRange(tx.timestamp, options.dateRange.start, options.dateRange.end)) {
1314
+ return false;
1315
+ }
1316
+ if (options.agentIds && !options.agentIds.includes(tx.agentId)) {
1317
+ return false;
1318
+ }
1319
+ if (options.chains && !options.chains.includes(tx.chain)) {
1320
+ return false;
1321
+ }
1322
+ return true;
1323
+ });
1324
+ }
1325
+ filterTasks(options) {
1326
+ return this.store.queryTasks((task) => {
1327
+ if (options.dateRange && !isWithinDateRange(task.createdAt, options.dateRange.start, options.dateRange.end)) {
1328
+ return false;
1329
+ }
1330
+ if (options.agentIds && !options.agentIds.includes(task.agentId)) {
1331
+ return false;
1332
+ }
1333
+ return true;
1334
+ });
1335
+ }
1336
+ filterAnomalies(options) {
1337
+ return this.store.queryAnomalies((anomaly) => {
1338
+ if (options.dateRange && !isWithinDateRange(anomaly.detectedAt, options.dateRange.start, options.dateRange.end)) {
1339
+ return false;
1340
+ }
1341
+ if (options.agentIds && !options.agentIds.includes(anomaly.agentId)) {
1342
+ return false;
1343
+ }
1344
+ return true;
1345
+ });
1346
+ }
1347
+ // --------------------------------------------------------------------------
1348
+ // CSV formatting
1349
+ // --------------------------------------------------------------------------
1350
+ formatAsCsv(actions, transactions, tasks, anomalies) {
1351
+ const sections = [];
1352
+ if (actions.length > 0) {
1353
+ const actionRecords = actions.map((a) => ({
1354
+ section: "action",
1355
+ id: a.id,
1356
+ timestamp: a.timestamp,
1357
+ projectId: a.projectId,
1358
+ agentId: a.agentId,
1359
+ correlationId: a.correlationId,
1360
+ type: a.type,
1361
+ description: a.description,
1362
+ metadata: JSON.stringify(a.metadata)
1363
+ }));
1364
+ sections.push("# Actions\n" + toCsv(actionRecords));
1365
+ }
1366
+ if (transactions.length > 0) {
1367
+ const txRecords = transactions.map((t) => ({
1368
+ section: "transaction",
1369
+ id: t.id,
1370
+ timestamp: t.timestamp,
1371
+ txHash: t.txHash,
1372
+ chain: t.chain,
1373
+ amount: t.amount,
1374
+ token: t.token,
1375
+ from: t.from,
1376
+ to: t.to,
1377
+ agentId: t.agentId
1378
+ }));
1379
+ sections.push("# Transactions\n" + toCsv(txRecords));
1380
+ }
1381
+ if (tasks.length > 0) {
1382
+ const taskRecords = tasks.map((t) => ({
1383
+ section: "task",
1384
+ id: t.id,
1385
+ description: t.description,
1386
+ agentId: t.agentId,
1387
+ status: t.status,
1388
+ createdAt: t.createdAt,
1389
+ confirmedAt: t.confirmedAt ?? "",
1390
+ requiredEvidence: t.requiredEvidence.join(";")
1391
+ }));
1392
+ sections.push("# Tasks\n" + toCsv(taskRecords));
1393
+ }
1394
+ if (anomalies.length > 0) {
1395
+ const anomalyRecords = anomalies.map((a) => ({
1396
+ section: "anomaly",
1397
+ id: a.id,
1398
+ type: a.type,
1399
+ severity: a.severity,
1400
+ description: a.description,
1401
+ agentId: a.agentId,
1402
+ detectedAt: a.detectedAt,
1403
+ reviewed: String(a.reviewed)
1404
+ }));
1405
+ sections.push("# Anomalies\n" + toCsv(anomalyRecords));
1406
+ }
1407
+ return sections.join("\n\n");
1408
+ }
1409
+ };
1410
+
1411
+ // src/trust.ts
1412
+ var TrustScorer = class {
1413
+ config;
1414
+ store;
1415
+ constructor(config, store) {
1416
+ this.config = config;
1417
+ this.store = store;
1418
+ }
1419
+ /**
1420
+ * Compute the trust score for a given agent.
1421
+ *
1422
+ * @param agentId - The agent identifier
1423
+ * @returns TrustScore with overall score, factor breakdown, and trust level
1424
+ *
1425
+ * @example
1426
+ * ```typescript
1427
+ * const score = await scorer.getTrustScore('payment-agent-1');
1428
+ * console.log(`Trust: ${score.score}/100 (${score.level})`);
1429
+ * ```
1430
+ */
1431
+ async getTrustScore(agentId) {
1432
+ const factors = this.computeAgentFactors(agentId);
1433
+ const weightedScore = factors.reduce((sum, f) => sum + f.score * f.weight, 0);
1434
+ const totalWeight = factors.reduce((sum, f) => sum + f.weight, 0);
1435
+ const score = totalWeight > 0 ? Math.round(weightedScore / totalWeight) : 50;
1436
+ const clampedScore = clamp(score, 0, 100);
1437
+ return {
1438
+ agentId,
1439
+ score: clampedScore,
1440
+ factors,
1441
+ computedAt: now(),
1442
+ level: this.scoreToLevel(clampedScore)
1443
+ };
1444
+ }
1445
+ /**
1446
+ * Evaluate the risk of a specific transaction.
1447
+ *
1448
+ * @param tx - Transaction input to evaluate
1449
+ * @returns TransactionEvaluation with risk score, factors, and recommendation
1450
+ *
1451
+ * @example
1452
+ * ```typescript
1453
+ * const eval = await scorer.evaluateTransaction({
1454
+ * txHash: '0x...',
1455
+ * chain: 'base',
1456
+ * amount: '50000',
1457
+ * token: 'USDC',
1458
+ * from: '0xSender',
1459
+ * to: '0xReceiver',
1460
+ * agentId: 'agent-1',
1461
+ * });
1462
+ * if (eval.flagged) console.log('Transaction flagged for review');
1463
+ * ```
1464
+ */
1465
+ async evaluateTransaction(tx) {
1466
+ const factors = this.computeTransactionRiskFactors(tx);
1467
+ const totalScore = factors.reduce((sum, f) => sum + f.score, 0);
1468
+ const riskScore = clamp(Math.round(totalScore / Math.max(factors.length, 1)), 0, 100);
1469
+ const riskLevel = this.riskScoreToLevel(riskScore);
1470
+ const flagged = riskScore >= 60;
1471
+ const recommendation = riskScore >= 80 ? "block" : riskScore >= 50 ? "review" : "approve";
1472
+ return {
1473
+ txHash: tx.txHash,
1474
+ riskScore,
1475
+ riskLevel,
1476
+ factors,
1477
+ flagged,
1478
+ recommendation,
1479
+ evaluatedAt: now()
1480
+ };
1481
+ }
1482
+ // --------------------------------------------------------------------------
1483
+ // Agent trust factor computation
1484
+ // --------------------------------------------------------------------------
1485
+ computeAgentFactors(agentId) {
1486
+ const factors = [];
1487
+ factors.push(this.computeHistoryDepthFactor(agentId));
1488
+ factors.push(this.computeTaskCompletionFactor(agentId));
1489
+ factors.push(this.computeAnomalyFrequencyFactor(agentId));
1490
+ factors.push(this.computeTransactionConsistencyFactor(agentId));
1491
+ factors.push(this.computeComplianceAdherenceFactor(agentId));
1492
+ return factors;
1493
+ }
1494
+ computeHistoryDepthFactor(agentId) {
1495
+ const actions = this.store.getActionsByAgent(agentId);
1496
+ const count = actions.length;
1497
+ let score;
1498
+ if (count === 0) score = 10;
1499
+ else if (count < 5) score = 30;
1500
+ else if (count < 20) score = 50;
1501
+ else if (count < 50) score = 70;
1502
+ else if (count < 100) score = 85;
1503
+ else score = 95;
1504
+ return {
1505
+ name: "history_depth",
1506
+ score,
1507
+ weight: 0.15,
1508
+ description: `Agent has ${count} recorded actions`
1509
+ };
1510
+ }
1511
+ computeTaskCompletionFactor(agentId) {
1512
+ const tasks = this.store.queryTasks((t) => t.agentId === agentId);
1513
+ const totalTasks = tasks.length;
1514
+ if (totalTasks === 0) {
1515
+ return {
1516
+ name: "task_completion",
1517
+ score: 50,
1518
+ // Neutral if no tasks
1519
+ weight: 0.25,
1520
+ description: "No tasks recorded yet"
1521
+ };
1522
+ }
1523
+ const confirmed = tasks.filter((t) => t.status === "confirmed").length;
1524
+ const failed = tasks.filter((t) => t.status === "failed").length;
1525
+ const completionRate = confirmed / totalTasks;
1526
+ const failureRate = failed / totalTasks;
1527
+ const score = Math.round(completionRate * 100 - failureRate * 30);
1528
+ return {
1529
+ name: "task_completion",
1530
+ score: clamp(score, 0, 100),
1531
+ weight: 0.25,
1532
+ description: `${confirmed}/${totalTasks} tasks confirmed (${Math.round(completionRate * 100)}% rate)`
1533
+ };
1534
+ }
1535
+ computeAnomalyFrequencyFactor(agentId) {
1536
+ const anomalies = this.store.queryAnomalies((a) => a.agentId === agentId);
1537
+ const actions = this.store.getActionsByAgent(agentId);
1538
+ const anomalyCount = anomalies.length;
1539
+ const actionCount = actions.length;
1540
+ if (actionCount === 0) {
1541
+ return {
1542
+ name: "anomaly_frequency",
1543
+ score: 50,
1544
+ weight: 0.25,
1545
+ description: "No actions recorded yet"
1546
+ };
1547
+ }
1548
+ const anomalyRate = anomalyCount / actionCount;
1549
+ let score;
1550
+ if (anomalyRate === 0) score = 100;
1551
+ else if (anomalyRate < 0.01) score = 90;
1552
+ else if (anomalyRate < 0.05) score = 70;
1553
+ else if (anomalyRate < 0.1) score = 50;
1554
+ else if (anomalyRate < 0.25) score = 30;
1555
+ else score = 10;
1556
+ const criticalCount = anomalies.filter((a) => a.severity === "critical").length;
1557
+ const highCount = anomalies.filter((a) => a.severity === "high").length;
1558
+ const penaltyFromSeverity = criticalCount * 15 + highCount * 8;
1559
+ return {
1560
+ name: "anomaly_frequency",
1561
+ score: clamp(score - penaltyFromSeverity, 0, 100),
1562
+ weight: 0.25,
1563
+ description: `${anomalyCount} anomalies across ${actionCount} actions (${Math.round(anomalyRate * 100)}% rate)`
1564
+ };
1565
+ }
1566
+ computeTransactionConsistencyFactor(agentId) {
1567
+ const transactions = this.store.getTransactionsByAgent(agentId);
1568
+ if (transactions.length < 2) {
1569
+ return {
1570
+ name: "transaction_consistency",
1571
+ score: 50,
1572
+ weight: 0.2,
1573
+ description: "Insufficient transaction history for consistency analysis"
1574
+ };
1575
+ }
1576
+ const amounts = transactions.map((t) => parseAmount(t.amount)).filter((a) => !isNaN(a));
1577
+ if (amounts.length < 2) {
1578
+ return {
1579
+ name: "transaction_consistency",
1580
+ score: 50,
1581
+ weight: 0.2,
1582
+ description: "Insufficient valid amounts for consistency analysis"
1583
+ };
1584
+ }
1585
+ const mean = amounts.reduce((sum, a) => sum + a, 0) / amounts.length;
1586
+ const variance = amounts.reduce((sum, a) => sum + Math.pow(a - mean, 2), 0) / amounts.length;
1587
+ const stdDev = Math.sqrt(variance);
1588
+ const cv = mean > 0 ? stdDev / mean : 0;
1589
+ let score;
1590
+ if (cv < 0.1) score = 95;
1591
+ else if (cv < 0.3) score = 80;
1592
+ else if (cv < 0.5) score = 65;
1593
+ else if (cv < 1) score = 45;
1594
+ else if (cv < 2) score = 30;
1595
+ else score = 15;
1596
+ const destinations = new Set(transactions.map((t) => t.to));
1597
+ const destRatio = destinations.size / transactions.length;
1598
+ if (destRatio > 0.8 && transactions.length > 5) {
1599
+ score = Math.max(score - 15, 0);
1600
+ }
1601
+ return {
1602
+ name: "transaction_consistency",
1603
+ score: clamp(score, 0, 100),
1604
+ weight: 0.2,
1605
+ description: `CV=${cv.toFixed(2)}, ${destinations.size} unique destinations across ${transactions.length} transactions`
1606
+ };
1607
+ }
1608
+ computeComplianceAdherenceFactor(agentId) {
1609
+ const tasks = this.store.queryTasks((t) => t.agentId === agentId);
1610
+ const transactions = this.store.getTransactionsByAgent(agentId);
1611
+ const confirmedTasks = tasks.filter((t) => t.status === "confirmed");
1612
+ const tasksWithEvidence = confirmedTasks.filter(
1613
+ (t) => t.providedEvidence !== null && Object.keys(t.providedEvidence).length > 0
1614
+ );
1615
+ let score = 50;
1616
+ if (confirmedTasks.length > 0) {
1617
+ const evidenceRate = tasksWithEvidence.length / confirmedTasks.length;
1618
+ score += Math.round(evidenceRate * 30);
1619
+ }
1620
+ if (transactions.length > 0 && tasks.length > 0) {
1621
+ const coverageRate = Math.min(tasks.length / transactions.length, 1);
1622
+ score += Math.round(coverageRate * 20);
1623
+ }
1624
+ return {
1625
+ name: "compliance_adherence",
1626
+ score: clamp(score, 0, 100),
1627
+ weight: 0.15,
1628
+ description: `${tasksWithEvidence.length} tasks with evidence, ${transactions.length} total transactions`
1629
+ };
1630
+ }
1631
+ // --------------------------------------------------------------------------
1632
+ // Transaction risk factor computation
1633
+ // --------------------------------------------------------------------------
1634
+ computeTransactionRiskFactors(tx) {
1635
+ const factors = [];
1636
+ factors.push(this.computeAmountRisk(tx));
1637
+ factors.push(this.computeNewDestinationRisk(tx));
1638
+ factors.push(this.computeFrequencyRisk(tx));
1639
+ factors.push(this.computeAgentRisk(tx.agentId));
1640
+ factors.push(this.computeRoundAmountRisk(tx));
1641
+ return factors;
1642
+ }
1643
+ computeAmountRisk(tx) {
1644
+ const amount = parseAmount(tx.amount);
1645
+ if (isNaN(amount)) {
1646
+ return { name: "amount_risk", score: 50, description: "Unable to parse transaction amount" };
1647
+ }
1648
+ let score;
1649
+ if (amount < 100) score = 5;
1650
+ else if (amount < 1e3) score = 15;
1651
+ else if (amount < 1e4) score = 30;
1652
+ else if (amount < 5e4) score = 55;
1653
+ else if (amount < 1e5) score = 75;
1654
+ else score = 95;
1655
+ const history = this.store.getTransactionsByAgent(tx.agentId);
1656
+ if (history.length > 0) {
1657
+ const avgAmount = history.reduce((sum, t) => sum + parseAmount(t.amount), 0) / history.length;
1658
+ if (avgAmount > 0 && amount > avgAmount * 5) {
1659
+ score = Math.min(score + 20, 100);
1660
+ }
1661
+ }
1662
+ return {
1663
+ name: "amount_risk",
1664
+ score,
1665
+ description: `Transaction amount ${tx.amount} ${tx.token}`
1666
+ };
1667
+ }
1668
+ computeNewDestinationRisk(tx) {
1669
+ const history = this.store.getTransactionsByAgent(tx.agentId);
1670
+ const knownDestinations = new Set(history.map((t) => t.to.toLowerCase()));
1671
+ const isNew = !knownDestinations.has(tx.to.toLowerCase());
1672
+ if (history.length === 0) {
1673
+ return {
1674
+ name: "new_destination",
1675
+ score: 30,
1676
+ description: "First transaction for this agent -- no destination history"
1677
+ };
1678
+ }
1679
+ return {
1680
+ name: "new_destination",
1681
+ score: isNew ? 45 : 5,
1682
+ description: isNew ? `New destination address: ${tx.to}` : `Known destination address: ${tx.to}`
1683
+ };
1684
+ }
1685
+ computeFrequencyRisk(tx) {
1686
+ const oneHourAgo = new Date(Date.now() - 36e5);
1687
+ const recentTxs = this.store.queryTransactions(
1688
+ (t) => t.agentId === tx.agentId && new Date(t.timestamp) >= oneHourAgo
1689
+ );
1690
+ const count = recentTxs.length;
1691
+ let score;
1692
+ if (count < 5) score = 5;
1693
+ else if (count < 10) score = 20;
1694
+ else if (count < 25) score = 45;
1695
+ else if (count < 50) score = 70;
1696
+ else score = 90;
1697
+ return {
1698
+ name: "frequency_risk",
1699
+ score,
1700
+ description: `${count} transactions in the last hour`
1701
+ };
1702
+ }
1703
+ computeAgentRisk(agentId) {
1704
+ const actions = this.store.getActionsByAgent(agentId);
1705
+ const anomalies = this.store.queryAnomalies((a) => a.agentId === agentId);
1706
+ if (actions.length === 0) {
1707
+ return {
1708
+ name: "agent_reputation",
1709
+ score: 40,
1710
+ description: "New agent with no history"
1711
+ };
1712
+ }
1713
+ const anomalyRate = anomalies.length / actions.length;
1714
+ const score = Math.round(anomalyRate * 100);
1715
+ return {
1716
+ name: "agent_reputation",
1717
+ score: clamp(score, 0, 100),
1718
+ description: `Agent anomaly rate: ${Math.round(anomalyRate * 100)}%`
1719
+ };
1720
+ }
1721
+ computeRoundAmountRisk(tx) {
1722
+ const amount = parseAmount(tx.amount);
1723
+ if (isNaN(amount)) {
1724
+ return { name: "round_amount", score: 10, description: "Unable to parse amount" };
1725
+ }
1726
+ const isRound1000 = amount >= 1e3 && amount % 1e3 === 0;
1727
+ const isRound10000 = amount >= 1e4 && amount % 1e4 === 0;
1728
+ const isJustUnderThreshold = amount >= 9e3 && amount <= 1e4;
1729
+ let score = 5;
1730
+ if (isRound10000) score = 25;
1731
+ else if (isRound1000) score = 15;
1732
+ if (isJustUnderThreshold) score += 20;
1733
+ return {
1734
+ name: "round_amount",
1735
+ score,
1736
+ description: `Amount ${tx.amount} -- ${isRound1000 ? "round amount" : "non-round amount"}`
1737
+ };
1738
+ }
1739
+ // --------------------------------------------------------------------------
1740
+ // Scoring helpers
1741
+ // --------------------------------------------------------------------------
1742
+ scoreToLevel(score) {
1743
+ if (score >= 90) return "verified";
1744
+ if (score >= 70) return "high";
1745
+ if (score >= 50) return "medium";
1746
+ if (score >= 30) return "low";
1747
+ return "untrusted";
1748
+ }
1749
+ riskScoreToLevel(score) {
1750
+ if (score >= 80) return "critical";
1751
+ if (score >= 60) return "high";
1752
+ if (score >= 35) return "medium";
1753
+ return "low";
1754
+ }
1755
+ };
1756
+
1757
+ // src/anomaly.ts
1758
+ var DEFAULT_THRESHOLDS = {
1759
+ maxAmount: "10000",
1760
+ maxFrequency: 30,
1761
+ offHours: [22, 23, 0, 1, 2, 3, 4, 5],
1762
+ minIntervalSeconds: 10
1763
+ };
1764
+ var AnomalyDetector = class {
1765
+ config;
1766
+ store;
1767
+ detectionConfig = null;
1768
+ thresholds = { ...DEFAULT_THRESHOLDS };
1769
+ callbacks = [];
1770
+ enabled = false;
1771
+ constructor(config, store) {
1772
+ this.config = config;
1773
+ this.store = store;
1774
+ }
1775
+ /**
1776
+ * Enable anomaly detection with the specified configuration.
1777
+ *
1778
+ * @param detectionConfig - Rules and thresholds for detection
1779
+ *
1780
+ * @example
1781
+ * ```typescript
1782
+ * detector.enableAnomalyDetection({
1783
+ * rules: ['unusualAmount', 'frequencySpike', 'newDestination'],
1784
+ * thresholds: { maxAmount: '10000', maxFrequency: 50 },
1785
+ * });
1786
+ * ```
1787
+ */
1788
+ enableAnomalyDetection(detectionConfig) {
1789
+ if (!detectionConfig.rules || detectionConfig.rules.length === 0) {
1790
+ throw new KontextError(
1791
+ "ANOMALY_CONFIG_ERROR" /* ANOMALY_CONFIG_ERROR */,
1792
+ "At least one detection rule must be specified"
1793
+ );
1794
+ }
1795
+ this.detectionConfig = detectionConfig;
1796
+ this.thresholds = {
1797
+ ...DEFAULT_THRESHOLDS,
1798
+ ...detectionConfig.thresholds
1799
+ };
1800
+ this.enabled = true;
1801
+ if (this.config.debug) {
1802
+ console.debug(
1803
+ `[Kontext] Anomaly detection enabled with rules: ${detectionConfig.rules.join(", ")}`
1804
+ );
1805
+ }
1806
+ }
1807
+ /**
1808
+ * Disable anomaly detection.
1809
+ */
1810
+ disableAnomalyDetection() {
1811
+ this.enabled = false;
1812
+ this.detectionConfig = null;
1813
+ if (this.config.debug) {
1814
+ console.debug("[Kontext] Anomaly detection disabled");
1815
+ }
1816
+ }
1817
+ /**
1818
+ * Register a callback for anomaly events.
1819
+ *
1820
+ * @param callback - Function to call when an anomaly is detected
1821
+ * @returns Unsubscribe function
1822
+ *
1823
+ * @example
1824
+ * ```typescript
1825
+ * const unsub = detector.onAnomaly((anomaly) => {
1826
+ * console.log(`Anomaly: ${anomaly.type} [${anomaly.severity}]`);
1827
+ * });
1828
+ * // Later: unsub();
1829
+ * ```
1830
+ */
1831
+ onAnomaly(callback) {
1832
+ this.callbacks.push(callback);
1833
+ return () => {
1834
+ const index = this.callbacks.indexOf(callback);
1835
+ if (index !== -1) {
1836
+ this.callbacks.splice(index, 1);
1837
+ }
1838
+ };
1839
+ }
1840
+ /**
1841
+ * Evaluate a transaction against all enabled detection rules.
1842
+ * Called automatically when transactions are logged (via the client).
1843
+ *
1844
+ * @param tx - The transaction record to evaluate
1845
+ * @returns Array of detected anomalies (empty if none)
1846
+ */
1847
+ evaluateTransaction(tx) {
1848
+ if (!this.enabled || !this.detectionConfig) return [];
1849
+ const anomalies = [];
1850
+ for (const rule of this.detectionConfig.rules) {
1851
+ const anomaly = this.runRule(rule, tx);
1852
+ if (anomaly) {
1853
+ anomalies.push(anomaly);
1854
+ this.store.addAnomaly(anomaly);
1855
+ this.notifyCallbacks(anomaly);
1856
+ }
1857
+ }
1858
+ return anomalies;
1859
+ }
1860
+ /**
1861
+ * Evaluate a generic action against all enabled detection rules.
1862
+ *
1863
+ * @param action - The action log to evaluate
1864
+ * @returns Array of detected anomalies (empty if none)
1865
+ */
1866
+ evaluateAction(action) {
1867
+ if (!this.enabled || !this.detectionConfig) return [];
1868
+ const anomalies = [];
1869
+ const applicableRules = ["offHoursActivity", "frequencySpike"];
1870
+ for (const rule of this.detectionConfig.rules) {
1871
+ if (!applicableRules.includes(rule)) continue;
1872
+ const anomaly = this.runActionRule(rule, action);
1873
+ if (anomaly) {
1874
+ anomalies.push(anomaly);
1875
+ this.store.addAnomaly(anomaly);
1876
+ this.notifyCallbacks(anomaly);
1877
+ }
1878
+ }
1879
+ return anomalies;
1880
+ }
1881
+ /**
1882
+ * Check whether anomaly detection is currently enabled.
1883
+ */
1884
+ isEnabled() {
1885
+ return this.enabled;
1886
+ }
1887
+ /**
1888
+ * Get the current detection configuration.
1889
+ */
1890
+ getConfig() {
1891
+ return this.detectionConfig;
1892
+ }
1893
+ // --------------------------------------------------------------------------
1894
+ // Rule execution
1895
+ // --------------------------------------------------------------------------
1896
+ runRule(rule, tx) {
1897
+ switch (rule) {
1898
+ case "unusualAmount":
1899
+ return this.checkUnusualAmount(tx);
1900
+ case "frequencySpike":
1901
+ return this.checkFrequencySpike(tx);
1902
+ case "newDestination":
1903
+ return this.checkNewDestination(tx);
1904
+ case "offHoursActivity":
1905
+ return this.checkOffHours(tx);
1906
+ case "rapidSuccession":
1907
+ return this.checkRapidSuccession(tx);
1908
+ case "roundAmount":
1909
+ return this.checkRoundAmount(tx);
1910
+ default:
1911
+ return null;
1912
+ }
1913
+ }
1914
+ runActionRule(rule, action) {
1915
+ switch (rule) {
1916
+ case "offHoursActivity":
1917
+ return this.checkOffHoursAction(action);
1918
+ case "frequencySpike":
1919
+ return this.checkActionFrequencySpike(action);
1920
+ default:
1921
+ return null;
1922
+ }
1923
+ }
1924
+ // --------------------------------------------------------------------------
1925
+ // Individual rule implementations
1926
+ // --------------------------------------------------------------------------
1927
+ checkUnusualAmount(tx) {
1928
+ const amount = parseAmount(tx.amount);
1929
+ if (isNaN(amount)) return null;
1930
+ const threshold = parseAmount(this.thresholds.maxAmount);
1931
+ if (amount > threshold) {
1932
+ return this.createAnomaly(
1933
+ "unusualAmount",
1934
+ amount > threshold * 5 ? "critical" : amount > threshold * 2 ? "high" : "medium",
1935
+ `Transaction amount ${tx.amount} ${tx.token} exceeds threshold of ${this.thresholds.maxAmount}`,
1936
+ tx.agentId,
1937
+ tx.id,
1938
+ { amount: tx.amount, threshold: this.thresholds.maxAmount, token: tx.token }
1939
+ );
1940
+ }
1941
+ const history = this.store.getTransactionsByAgent(tx.agentId);
1942
+ if (history.length >= 3) {
1943
+ const amounts = history.map((t) => parseAmount(t.amount)).filter((a) => !isNaN(a));
1944
+ if (amounts.length >= 3) {
1945
+ const avg = amounts.reduce((s, a) => s + a, 0) / amounts.length;
1946
+ if (avg > 0 && amount > avg * 5) {
1947
+ return this.createAnomaly(
1948
+ "unusualAmount",
1949
+ amount > avg * 10 ? "high" : "medium",
1950
+ `Transaction amount ${tx.amount} is ${(amount / avg).toFixed(1)}x the agent's average of ${avg.toFixed(2)}`,
1951
+ tx.agentId,
1952
+ tx.id,
1953
+ { amount: tx.amount, average: avg.toFixed(2), multiplier: (amount / avg).toFixed(1) }
1954
+ );
1955
+ }
1956
+ }
1957
+ }
1958
+ return null;
1959
+ }
1960
+ checkFrequencySpike(tx) {
1961
+ const oneHourAgo = new Date(Date.now() - 36e5);
1962
+ const recentTxs = this.store.queryTransactions(
1963
+ (t) => t.agentId === tx.agentId && new Date(t.timestamp) >= oneHourAgo
1964
+ );
1965
+ const count = recentTxs.length;
1966
+ const maxFrequency = this.thresholds.maxFrequency;
1967
+ if (count > maxFrequency) {
1968
+ return this.createAnomaly(
1969
+ "frequencySpike",
1970
+ count > maxFrequency * 3 ? "critical" : count > maxFrequency * 2 ? "high" : "medium",
1971
+ `Agent ${tx.agentId} has ${count} transactions in the last hour (threshold: ${maxFrequency})`,
1972
+ tx.agentId,
1973
+ tx.id,
1974
+ { count, threshold: maxFrequency }
1975
+ );
1976
+ }
1977
+ return null;
1978
+ }
1979
+ checkNewDestination(tx) {
1980
+ const history = this.store.getTransactionsByAgent(tx.agentId);
1981
+ if (history.length < 3) return null;
1982
+ const knownDestinations = new Set(
1983
+ history.filter((t) => t.id !== tx.id).map((t) => t.to.toLowerCase())
1984
+ );
1985
+ if (!knownDestinations.has(tx.to.toLowerCase())) {
1986
+ const amount = parseAmount(tx.amount);
1987
+ const severity = !isNaN(amount) && amount > parseAmount(this.thresholds.maxAmount) * 0.5 ? "high" : "low";
1988
+ return this.createAnomaly(
1989
+ "newDestination",
1990
+ severity,
1991
+ `Transaction to new destination ${tx.to} (agent has ${knownDestinations.size} known destinations)`,
1992
+ tx.agentId,
1993
+ tx.id,
1994
+ { destination: tx.to, knownDestinationCount: knownDestinations.size }
1995
+ );
1996
+ }
1997
+ return null;
1998
+ }
1999
+ checkOffHours(tx) {
2000
+ const txHour = new Date(tx.timestamp).getUTCHours();
2001
+ if (this.thresholds.offHours.includes(txHour)) {
2002
+ return this.createAnomaly(
2003
+ "offHoursActivity",
2004
+ "low",
2005
+ `Transaction at ${txHour}:00 UTC falls within off-hours window`,
2006
+ tx.agentId,
2007
+ tx.id,
2008
+ { hour: txHour, offHours: this.thresholds.offHours }
2009
+ );
2010
+ }
2011
+ return null;
2012
+ }
2013
+ checkRapidSuccession(tx) {
2014
+ const recentTxs = this.store.getTransactionsByAgent(tx.agentId).filter((t) => t.id !== tx.id);
2015
+ if (recentTxs.length === 0) return null;
2016
+ const lastTx = recentTxs[recentTxs.length - 1];
2017
+ if (!lastTx) return null;
2018
+ const timeDiffMs = new Date(tx.timestamp).getTime() - new Date(lastTx.timestamp).getTime();
2019
+ const timeDiffSeconds = timeDiffMs / 1e3;
2020
+ if (timeDiffSeconds >= 0 && timeDiffSeconds < this.thresholds.minIntervalSeconds) {
2021
+ return this.createAnomaly(
2022
+ "rapidSuccession",
2023
+ timeDiffSeconds < 2 ? "high" : "medium",
2024
+ `Transaction occurred ${timeDiffSeconds.toFixed(1)}s after previous transaction (minimum: ${this.thresholds.minIntervalSeconds}s)`,
2025
+ tx.agentId,
2026
+ tx.id,
2027
+ {
2028
+ intervalSeconds: timeDiffSeconds,
2029
+ threshold: this.thresholds.minIntervalSeconds,
2030
+ previousTxId: lastTx.id
2031
+ }
2032
+ );
2033
+ }
2034
+ return null;
2035
+ }
2036
+ checkRoundAmount(tx) {
2037
+ const amount = parseAmount(tx.amount);
2038
+ if (isNaN(amount)) return null;
2039
+ const structuringThresholds = [1e4, 5e3, 3e3, 1e3];
2040
+ for (const threshold of structuringThresholds) {
2041
+ const diff = threshold - amount;
2042
+ if (diff > 0 && diff <= threshold * 0.05) {
2043
+ return this.createAnomaly(
2044
+ "roundAmount",
2045
+ threshold >= 1e4 ? "high" : "medium",
2046
+ `Transaction amount ${tx.amount} is just below the ${threshold} threshold (potential structuring)`,
2047
+ tx.agentId,
2048
+ tx.id,
2049
+ { amount: tx.amount, nearThreshold: threshold, difference: diff }
2050
+ );
2051
+ }
2052
+ }
2053
+ if (amount >= 5e3 && amount % 1e3 === 0) {
2054
+ return this.createAnomaly(
2055
+ "roundAmount",
2056
+ "low",
2057
+ `Transaction amount ${tx.amount} is a round number`,
2058
+ tx.agentId,
2059
+ tx.id,
2060
+ { amount: tx.amount }
2061
+ );
2062
+ }
2063
+ return null;
2064
+ }
2065
+ checkOffHoursAction(action) {
2066
+ const actionHour = new Date(action.timestamp).getUTCHours();
2067
+ if (this.thresholds.offHours.includes(actionHour)) {
2068
+ return this.createAnomaly(
2069
+ "offHoursActivity",
2070
+ "low",
2071
+ `Action at ${actionHour}:00 UTC falls within off-hours window`,
2072
+ action.agentId,
2073
+ action.id,
2074
+ { hour: actionHour, offHours: this.thresholds.offHours }
2075
+ );
2076
+ }
2077
+ return null;
2078
+ }
2079
+ checkActionFrequencySpike(action) {
2080
+ const oneHourAgo = new Date(Date.now() - 36e5);
2081
+ const recentActions = this.store.queryActions(
2082
+ (a) => a.agentId === action.agentId && new Date(a.timestamp) >= oneHourAgo
2083
+ );
2084
+ const count = recentActions.length;
2085
+ const maxFrequency = this.thresholds.maxFrequency * 3;
2086
+ if (count > maxFrequency) {
2087
+ return this.createAnomaly(
2088
+ "frequencySpike",
2089
+ count > maxFrequency * 2 ? "high" : "medium",
2090
+ `Agent ${action.agentId} has ${count} actions in the last hour (threshold: ${maxFrequency})`,
2091
+ action.agentId,
2092
+ action.id,
2093
+ { count, threshold: maxFrequency }
2094
+ );
2095
+ }
2096
+ return null;
2097
+ }
2098
+ // --------------------------------------------------------------------------
2099
+ // Helpers
2100
+ // --------------------------------------------------------------------------
2101
+ createAnomaly(type, severity, description, agentId, actionId, data) {
2102
+ return {
2103
+ id: generateId(),
2104
+ type,
2105
+ severity,
2106
+ description,
2107
+ agentId,
2108
+ actionId,
2109
+ detectedAt: now(),
2110
+ data,
2111
+ reviewed: false
2112
+ };
2113
+ }
2114
+ notifyCallbacks(anomaly) {
2115
+ for (const cb of this.callbacks) {
2116
+ try {
2117
+ cb(anomaly);
2118
+ } catch (error) {
2119
+ if (this.config.debug) {
2120
+ console.debug("[Kontext] Anomaly callback error:", error);
2121
+ }
2122
+ }
2123
+ }
2124
+ }
2125
+ };
2126
+
2127
+ // src/integrations/usdc.ts
2128
+ var USDC_CONTRACTS = {
2129
+ ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
2130
+ base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
2131
+ polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
2132
+ arbitrum: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
2133
+ optimism: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
2134
+ // Arc (Circle's stablecoin-native blockchain) -- placeholder address, update when Arc mainnet launches
2135
+ arc: "0xa0c0000000000000000000000000000000000001"
2136
+ };
2137
+ var BLOCKED_ADDRESS_PREFIXES = [
2138
+ // These are examples. Real implementation would query an OFAC API.
2139
+ ];
2140
+ var ENHANCED_DUE_DILIGENCE_THRESHOLD = 3e3;
2141
+ var REPORTING_THRESHOLD = 1e4;
2142
+ var LARGE_TRANSACTION_THRESHOLD = 5e4;
2143
+ var UsdcCompliance = class _UsdcCompliance {
2144
+ /**
2145
+ * Run a full compliance check on a USDC transaction.
2146
+ *
2147
+ * @param tx - Transaction to evaluate
2148
+ * @returns UsdcComplianceCheck with pass/fail results and recommendations
2149
+ *
2150
+ * @example
2151
+ * ```typescript
2152
+ * const check = UsdcCompliance.checkTransaction({
2153
+ * txHash: '0x...',
2154
+ * chain: 'base',
2155
+ * amount: '5000',
2156
+ * token: 'USDC',
2157
+ * from: '0xSender...',
2158
+ * to: '0xReceiver...',
2159
+ * agentId: 'agent-1',
2160
+ * });
2161
+ * if (!check.compliant) {
2162
+ * console.log('Non-compliant:', check.recommendations);
2163
+ * }
2164
+ * ```
2165
+ */
2166
+ static checkTransaction(tx) {
2167
+ const checks = [];
2168
+ checks.push(_UsdcCompliance.checkTokenType(tx));
2169
+ checks.push(_UsdcCompliance.checkChainSupport(tx.chain));
2170
+ checks.push(_UsdcCompliance.checkAddressFormat(tx.from, "sender"));
2171
+ checks.push(_UsdcCompliance.checkAddressFormat(tx.to, "recipient"));
2172
+ checks.push(_UsdcCompliance.checkAmountValid(tx.amount));
2173
+ checks.push(_UsdcCompliance.checkSanctions(tx.from, "sender"));
2174
+ checks.push(_UsdcCompliance.checkSanctions(tx.to, "recipient"));
2175
+ checks.push(_UsdcCompliance.checkEnhancedDueDiligence(tx.amount));
2176
+ checks.push(_UsdcCompliance.checkReportingThreshold(tx.amount));
2177
+ const failedChecks = checks.filter((c) => !c.passed);
2178
+ const compliant = failedChecks.every((c) => c.severity === "low");
2179
+ const highestSeverity = failedChecks.reduce(
2180
+ (max, c) => {
2181
+ const order = ["low", "medium", "high", "critical"];
2182
+ return order.indexOf(c.severity) > order.indexOf(max) ? c.severity : max;
2183
+ },
2184
+ "low"
2185
+ );
2186
+ const recommendations = _UsdcCompliance.generateRecommendations(checks, tx);
2187
+ return {
2188
+ compliant,
2189
+ checks,
2190
+ riskLevel: highestSeverity,
2191
+ recommendations
2192
+ };
2193
+ }
2194
+ /**
2195
+ * Get the USDC contract address for a given chain.
2196
+ *
2197
+ * @param chain - The blockchain network
2198
+ * @returns The USDC contract address, or undefined for unsupported chains
2199
+ */
2200
+ static getContractAddress(chain) {
2201
+ return USDC_CONTRACTS[chain];
2202
+ }
2203
+ /**
2204
+ * Get the chains supported for USDC compliance monitoring.
2205
+ */
2206
+ static getSupportedChains() {
2207
+ return Object.keys(USDC_CONTRACTS);
2208
+ }
2209
+ // --------------------------------------------------------------------------
2210
+ // Individual compliance checks
2211
+ // --------------------------------------------------------------------------
2212
+ static checkTokenType(tx) {
2213
+ const isUsdc = tx.token === "USDC";
2214
+ return {
2215
+ name: "token_type",
2216
+ passed: isUsdc,
2217
+ description: isUsdc ? "Transaction token is USDC" : `Expected USDC but got ${tx.token}`,
2218
+ severity: isUsdc ? "low" : "high"
2219
+ };
2220
+ }
2221
+ static checkChainSupport(chain) {
2222
+ const supported = chain in USDC_CONTRACTS;
2223
+ return {
2224
+ name: "chain_support",
2225
+ passed: supported,
2226
+ description: supported ? `Chain ${chain} is supported for USDC compliance monitoring` : `Chain ${chain} is not in the supported USDC compliance list`,
2227
+ severity: supported ? "low" : "medium"
2228
+ };
2229
+ }
2230
+ static checkAddressFormat(address, label) {
2231
+ const isValid = /^0x[a-fA-F0-9]{40}$/.test(address);
2232
+ return {
2233
+ name: `address_format_${label}`,
2234
+ passed: isValid,
2235
+ description: isValid ? `${label} address format is valid` : `${label} address format is invalid: ${address}`,
2236
+ severity: isValid ? "low" : "high"
2237
+ };
2238
+ }
2239
+ static checkAmountValid(amount) {
2240
+ const parsed = parseAmount(amount);
2241
+ const isValid = !isNaN(parsed) && parsed > 0;
2242
+ return {
2243
+ name: "amount_valid",
2244
+ passed: isValid,
2245
+ description: isValid ? `Transaction amount ${amount} is valid` : `Transaction amount ${amount} is invalid`,
2246
+ severity: isValid ? "low" : "critical"
2247
+ };
2248
+ }
2249
+ static checkSanctions(address, label) {
2250
+ const isBlocked = BLOCKED_ADDRESS_PREFIXES.some(
2251
+ (prefix) => address.toLowerCase().startsWith(prefix.toLowerCase())
2252
+ );
2253
+ return {
2254
+ name: `sanctions_${label}`,
2255
+ passed: !isBlocked,
2256
+ description: isBlocked ? `${label} address ${address} appears on sanctions list` : `${label} address passed sanctions screening`,
2257
+ severity: isBlocked ? "critical" : "low"
2258
+ };
2259
+ }
2260
+ static checkEnhancedDueDiligence(amount) {
2261
+ const parsed = parseAmount(amount);
2262
+ const requiresEdd = !isNaN(parsed) && parsed >= ENHANCED_DUE_DILIGENCE_THRESHOLD;
2263
+ return {
2264
+ name: "enhanced_due_diligence",
2265
+ passed: true,
2266
+ // This is informational -- it always "passes" but flags the need
2267
+ description: requiresEdd ? `Amount ${amount} USDC requires enhanced due diligence (threshold: ${ENHANCED_DUE_DILIGENCE_THRESHOLD})` : `Amount ${amount} USDC is below enhanced due diligence threshold`,
2268
+ severity: requiresEdd ? "medium" : "low"
2269
+ };
2270
+ }
2271
+ static checkReportingThreshold(amount) {
2272
+ const parsed = parseAmount(amount);
2273
+ const requiresReporting = !isNaN(parsed) && parsed >= REPORTING_THRESHOLD;
2274
+ const isLarge = !isNaN(parsed) && parsed >= LARGE_TRANSACTION_THRESHOLD;
2275
+ let description;
2276
+ let severity;
2277
+ if (isLarge) {
2278
+ description = `Amount ${amount} USDC is a large transaction (>= ${LARGE_TRANSACTION_THRESHOLD}) -- requires enhanced monitoring`;
2279
+ severity = "high";
2280
+ } else if (requiresReporting) {
2281
+ description = `Amount ${amount} USDC meets reporting threshold (>= ${REPORTING_THRESHOLD})`;
2282
+ severity = "medium";
2283
+ } else {
2284
+ description = `Amount ${amount} USDC is below reporting threshold`;
2285
+ severity = "low";
2286
+ }
2287
+ return {
2288
+ name: "reporting_threshold",
2289
+ passed: true,
2290
+ // Informational
2291
+ description,
2292
+ severity
2293
+ };
2294
+ }
2295
+ // --------------------------------------------------------------------------
2296
+ // Recommendations
2297
+ // --------------------------------------------------------------------------
2298
+ static generateRecommendations(checks, tx) {
2299
+ const recommendations = [];
2300
+ const amount = parseAmount(tx.amount);
2301
+ const criticalFailures = checks.filter(
2302
+ (c) => !c.passed && c.severity === "critical"
2303
+ );
2304
+ if (criticalFailures.length > 0) {
2305
+ recommendations.push("BLOCK: Critical compliance check failures detected. Do not proceed.");
2306
+ }
2307
+ if (!isNaN(amount)) {
2308
+ if (amount >= LARGE_TRANSACTION_THRESHOLD) {
2309
+ recommendations.push(
2310
+ "Require manual review for large transaction per GENIUS Act Section 4(b)."
2311
+ );
2312
+ recommendations.push("Verify recipient identity through KYC process.");
2313
+ recommendations.push("Document business purpose for the transfer.");
2314
+ } else if (amount >= REPORTING_THRESHOLD) {
2315
+ recommendations.push(
2316
+ "Generate Currency Transaction Report (CTR) per BSA requirements."
2317
+ );
2318
+ recommendations.push("Retain transaction records for minimum 5 years.");
2319
+ } else if (amount >= ENHANCED_DUE_DILIGENCE_THRESHOLD) {
2320
+ recommendations.push(
2321
+ "Enhanced due diligence recommended -- verify transaction purpose."
2322
+ );
2323
+ }
2324
+ }
2325
+ const addressFailures = checks.filter(
2326
+ (c) => c.name.startsWith("address_format") && !c.passed
2327
+ );
2328
+ if (addressFailures.length > 0) {
2329
+ recommendations.push("Verify address format before proceeding.");
2330
+ }
2331
+ if (recommendations.length === 0) {
2332
+ recommendations.push("Transaction passes all compliance checks. Safe to proceed.");
2333
+ }
2334
+ return recommendations;
2335
+ }
2336
+ };
2337
+
2338
+ // src/client.ts
2339
+ var Kontext = class _Kontext {
2340
+ config;
2341
+ store;
2342
+ logger;
2343
+ taskManager;
2344
+ auditExporter;
2345
+ trustScorer;
2346
+ anomalyDetector;
2347
+ mode;
2348
+ constructor(config) {
2349
+ this.config = config;
2350
+ this.mode = config.apiKey ? "cloud" : "local";
2351
+ this.store = new KontextStore();
2352
+ this.logger = new ActionLogger(config, this.store);
2353
+ this.taskManager = new TaskManager(config, this.store);
2354
+ this.auditExporter = new AuditExporter(config, this.store);
2355
+ this.trustScorer = new TrustScorer(config, this.store);
2356
+ this.anomalyDetector = new AnomalyDetector(config, this.store);
2357
+ }
2358
+ /**
2359
+ * Initialize the Kontext SDK.
2360
+ *
2361
+ * @param config - Configuration options
2362
+ * @returns Initialized Kontext client instance
2363
+ *
2364
+ * @example
2365
+ * ```typescript
2366
+ * // Local/OSS mode (no API key)
2367
+ * const kontext = Kontext.init({
2368
+ * projectId: 'my-project',
2369
+ * environment: 'development',
2370
+ * });
2371
+ *
2372
+ * // Cloud mode (with API key)
2373
+ * const kontext = Kontext.init({
2374
+ * apiKey: 'sk_live_...',
2375
+ * projectId: 'my-project',
2376
+ * environment: 'production',
2377
+ * });
2378
+ * ```
2379
+ */
2380
+ static init(config) {
2381
+ if (!config.projectId || config.projectId.trim() === "") {
2382
+ throw new KontextError(
2383
+ "INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
2384
+ "projectId is required"
2385
+ );
2386
+ }
2387
+ const validEnvironments = ["development", "staging", "production"];
2388
+ if (!validEnvironments.includes(config.environment)) {
2389
+ throw new KontextError(
2390
+ "INITIALIZATION_ERROR" /* INITIALIZATION_ERROR */,
2391
+ `Invalid environment: ${config.environment}. Must be one of: ${validEnvironments.join(", ")}`
2392
+ );
2393
+ }
2394
+ if (config.debug) {
2395
+ const mode = config.apiKey ? "cloud" : "local";
2396
+ console.debug(
2397
+ `[Kontext] Initializing in ${mode} mode for project ${config.projectId} (${config.environment})`
2398
+ );
2399
+ }
2400
+ return new _Kontext(config);
2401
+ }
2402
+ // --------------------------------------------------------------------------
2403
+ // Mode & Config
2404
+ // --------------------------------------------------------------------------
2405
+ /**
2406
+ * Get the current operating mode.
2407
+ */
2408
+ getMode() {
2409
+ return this.mode;
2410
+ }
2411
+ /**
2412
+ * Get the current configuration (API key is masked).
2413
+ */
2414
+ getConfig() {
2415
+ return {
2416
+ ...this.config,
2417
+ apiKey: this.config.apiKey ? `${this.config.apiKey.slice(0, 8)}...` : void 0
2418
+ };
2419
+ }
2420
+ // --------------------------------------------------------------------------
2421
+ // Action Logging
2422
+ // --------------------------------------------------------------------------
2423
+ /**
2424
+ * Log a generic agent action.
2425
+ *
2426
+ * @param input - Action details
2427
+ * @returns The created action log entry
2428
+ */
2429
+ async log(input) {
2430
+ const action = await this.logger.log(input);
2431
+ if (this.anomalyDetector.isEnabled()) {
2432
+ this.anomalyDetector.evaluateAction(action);
2433
+ }
2434
+ return action;
2435
+ }
2436
+ /**
2437
+ * Log a cryptocurrency transaction with full chain details.
2438
+ *
2439
+ * @param input - Transaction details
2440
+ * @returns The created transaction record
2441
+ */
2442
+ async logTransaction(input) {
2443
+ const record = await this.logger.logTransaction(input);
2444
+ if (this.anomalyDetector.isEnabled()) {
2445
+ this.anomalyDetector.evaluateTransaction(record);
2446
+ }
2447
+ return record;
2448
+ }
2449
+ /**
2450
+ * Flush any pending log batches.
2451
+ */
2452
+ async flushLogs() {
2453
+ await this.logger.flush();
2454
+ }
2455
+ // --------------------------------------------------------------------------
2456
+ // Task Confirmation
2457
+ // --------------------------------------------------------------------------
2458
+ /**
2459
+ * Create a new tracked task that requires evidence for confirmation.
2460
+ *
2461
+ * @param input - Task details including required evidence types
2462
+ * @returns The created task
2463
+ */
2464
+ async createTask(input) {
2465
+ return this.taskManager.createTask(input);
2466
+ }
2467
+ /**
2468
+ * Confirm a task by providing evidence.
2469
+ *
2470
+ * @param input - Task ID and evidence data
2471
+ * @returns The confirmed task
2472
+ */
2473
+ async confirmTask(input) {
2474
+ return this.taskManager.confirmTask(input);
2475
+ }
2476
+ /**
2477
+ * Get the current status of a task.
2478
+ *
2479
+ * @param taskId - Task identifier
2480
+ * @returns The task or undefined if not found
2481
+ */
2482
+ async getTaskStatus(taskId) {
2483
+ return this.taskManager.getTaskStatus(taskId);
2484
+ }
2485
+ /**
2486
+ * Mark a task as in-progress.
2487
+ *
2488
+ * @param taskId - Task identifier
2489
+ * @returns The updated task
2490
+ */
2491
+ async startTask(taskId) {
2492
+ return this.taskManager.startTask(taskId);
2493
+ }
2494
+ /**
2495
+ * Mark a task as failed.
2496
+ *
2497
+ * @param taskId - Task identifier
2498
+ * @param reason - Reason for failure
2499
+ * @returns The updated task
2500
+ */
2501
+ async failTask(taskId, reason) {
2502
+ return this.taskManager.failTask(taskId, reason);
2503
+ }
2504
+ /**
2505
+ * Get all tasks, optionally filtered by status.
2506
+ *
2507
+ * @param status - Optional status filter
2508
+ * @returns Array of tasks
2509
+ */
2510
+ getTasks(status) {
2511
+ return this.taskManager.getTasks(status);
2512
+ }
2513
+ // --------------------------------------------------------------------------
2514
+ // Audit Export
2515
+ // --------------------------------------------------------------------------
2516
+ /**
2517
+ * Export audit data in JSON or CSV format.
2518
+ *
2519
+ * @param options - Export configuration
2520
+ * @returns Export result with formatted data
2521
+ */
2522
+ async export(options) {
2523
+ return this.auditExporter.export(options);
2524
+ }
2525
+ /**
2526
+ * Generate a compliance report for a given period.
2527
+ *
2528
+ * @param options - Report configuration
2529
+ * @returns Compliance report with summary and detailed records
2530
+ */
2531
+ async generateReport(options) {
2532
+ return this.auditExporter.generateReport(options);
2533
+ }
2534
+ /**
2535
+ * Generate a Suspicious Activity Report (SAR) template.
2536
+ *
2537
+ * This produces a structured SAR template populated with data from the SDK.
2538
+ * It is a template/structure, not an actual regulatory filing.
2539
+ *
2540
+ * @param options - Report configuration
2541
+ * @returns SAR report template
2542
+ */
2543
+ async generateSARReport(options) {
2544
+ return this.auditExporter.generateSARReport(options);
2545
+ }
2546
+ /**
2547
+ * Generate a Currency Transaction Report (CTR) template.
2548
+ *
2549
+ * This produces a structured CTR template for transactions that meet or
2550
+ * exceed reporting thresholds. It is a template/structure, not an actual
2551
+ * regulatory filing.
2552
+ *
2553
+ * @param options - Report configuration
2554
+ * @returns CTR report template
2555
+ */
2556
+ async generateCTRReport(options) {
2557
+ return this.auditExporter.generateCTRReport(options);
2558
+ }
2559
+ // --------------------------------------------------------------------------
2560
+ // Trust Scoring
2561
+ // --------------------------------------------------------------------------
2562
+ /**
2563
+ * Get the trust score for an agent.
2564
+ *
2565
+ * @param agentId - Agent identifier
2566
+ * @returns Trust score with factor breakdown
2567
+ */
2568
+ async getTrustScore(agentId) {
2569
+ return this.trustScorer.getTrustScore(agentId);
2570
+ }
2571
+ /**
2572
+ * Evaluate the risk of a specific transaction.
2573
+ *
2574
+ * @param tx - Transaction to evaluate
2575
+ * @returns Transaction evaluation with risk score and recommendation
2576
+ */
2577
+ async evaluateTransaction(tx) {
2578
+ return this.trustScorer.evaluateTransaction(tx);
2579
+ }
2580
+ // --------------------------------------------------------------------------
2581
+ // Anomaly Detection
2582
+ // --------------------------------------------------------------------------
2583
+ /**
2584
+ * Enable anomaly detection with the specified rules and thresholds.
2585
+ *
2586
+ * @param config - Detection configuration
2587
+ */
2588
+ enableAnomalyDetection(config) {
2589
+ this.anomalyDetector.enableAnomalyDetection(config);
2590
+ }
2591
+ /**
2592
+ * Disable anomaly detection.
2593
+ */
2594
+ disableAnomalyDetection() {
2595
+ this.anomalyDetector.disableAnomalyDetection();
2596
+ }
2597
+ /**
2598
+ * Register a callback for anomaly events.
2599
+ *
2600
+ * @param callback - Function to call when an anomaly is detected
2601
+ * @returns Unsubscribe function
2602
+ */
2603
+ onAnomaly(callback) {
2604
+ return this.anomalyDetector.onAnomaly(callback);
2605
+ }
2606
+ // --------------------------------------------------------------------------
2607
+ // Digest Chain
2608
+ // --------------------------------------------------------------------------
2609
+ /**
2610
+ * Get the terminal digest — the latest SHA-256 hash in the rolling digest chain.
2611
+ * Embed this in outgoing messages as tamper-evident proof of the entire action history.
2612
+ *
2613
+ * @returns The terminal SHA-256 digest hex string
2614
+ */
2615
+ getTerminalDigest() {
2616
+ return this.logger.getTerminalDigest();
2617
+ }
2618
+ /**
2619
+ * Verify the integrity of the digest chain.
2620
+ * Recomputes every digest from genesis and compares against stored values.
2621
+ * Any tampering will cause verification to fail.
2622
+ *
2623
+ * @returns Verification result with timing and validity data
2624
+ */
2625
+ verifyDigestChain() {
2626
+ const actions = this.store.getActions();
2627
+ return this.logger.verifyChain(actions);
2628
+ }
2629
+ /**
2630
+ * Export the digest chain for independent third-party verification.
2631
+ *
2632
+ * @returns Chain data including genesis hash, all links, and terminal digest
2633
+ */
2634
+ exportDigestChain() {
2635
+ return this.logger.getDigestChain().exportChain();
2636
+ }
2637
+ // --------------------------------------------------------------------------
2638
+ // USDC Integration
2639
+ // --------------------------------------------------------------------------
2640
+ /**
2641
+ * Run USDC-specific compliance checks on a transaction.
2642
+ *
2643
+ * @param tx - Transaction to check
2644
+ * @returns Compliance check result
2645
+ */
2646
+ checkUsdcCompliance(tx) {
2647
+ return UsdcCompliance.checkTransaction(tx);
2648
+ }
2649
+ // --------------------------------------------------------------------------
2650
+ // Lifecycle
2651
+ // --------------------------------------------------------------------------
2652
+ /**
2653
+ * Gracefully shut down the SDK, flushing any pending data.
2654
+ */
2655
+ async destroy() {
2656
+ await this.logger.destroy();
2657
+ }
2658
+ };
2659
+
2660
+ // src/integrations/cctp.ts
2661
+ var CCTP_DOMAINS = {
2662
+ ethereum: 0,
2663
+ arbitrum: 3,
2664
+ optimism: 2,
2665
+ base: 6,
2666
+ polygon: 7,
2667
+ // Arc (Circle's stablecoin-native blockchain) -- placeholder domain ID, update when Arc mainnet launches
2668
+ arc: 10
2669
+ };
2670
+ var CCTPTransferManager = class {
2671
+ transfers = /* @__PURE__ */ new Map();
2672
+ actionLinks = /* @__PURE__ */ new Map();
2673
+ /**
2674
+ * Validate a cross-chain transfer before execution.
2675
+ *
2676
+ * Checks include:
2677
+ * - Source and destination chain support
2678
+ * - Route validity (different chains)
2679
+ * - Token support on both chains
2680
+ * - Amount validation
2681
+ * - Address format validation
2682
+ *
2683
+ * @param input - Transfer details to validate
2684
+ * @returns Validation result with checks and recommendations
2685
+ */
2686
+ validateTransfer(input) {
2687
+ const checks = [];
2688
+ checks.push(this.checkChainSupport(input.sourceChain, "source"));
2689
+ checks.push(this.checkChainSupport(input.destinationChain, "destination"));
2690
+ checks.push(this.checkRouteValidity(input.sourceChain, input.destinationChain));
2691
+ checks.push(this.checkTokenSupport(input.token));
2692
+ checks.push(this.checkAmountValidity(input.amount));
2693
+ checks.push(this.checkAddressFormat(input.sender, "sender"));
2694
+ checks.push(this.checkAddressFormat(input.recipient, "recipient"));
2695
+ const failedChecks = checks.filter((c) => !c.passed);
2696
+ const valid = failedChecks.length === 0;
2697
+ const highestSeverity = failedChecks.reduce(
2698
+ (max, c) => {
2699
+ const order = ["low", "medium", "high", "critical"];
2700
+ return order.indexOf(c.severity) > order.indexOf(max) ? c.severity : max;
2701
+ },
2702
+ "low"
2703
+ );
2704
+ const recommendations = this.generateRecommendations(checks, input);
2705
+ return {
2706
+ valid,
2707
+ checks,
2708
+ riskLevel: valid ? "low" : highestSeverity,
2709
+ recommendations
2710
+ };
2711
+ }
2712
+ /**
2713
+ * Record a new cross-chain transfer initiated via CCTP depositForBurn.
2714
+ *
2715
+ * @param input - Transfer initiation details
2716
+ * @returns The created CrossChainTransfer record
2717
+ */
2718
+ initiateTransfer(input) {
2719
+ const id = generateId();
2720
+ const correlationId = input.correlationId ?? generateId();
2721
+ const transfer = {
2722
+ id,
2723
+ sourceChain: input.sourceChain,
2724
+ destinationChain: input.destinationChain,
2725
+ sourceDomain: CCTP_DOMAINS[input.sourceChain] ?? -1,
2726
+ destinationDomain: CCTP_DOMAINS[input.destinationChain] ?? -1,
2727
+ amount: input.amount,
2728
+ token: input.token,
2729
+ sender: input.sender,
2730
+ recipient: input.recipient,
2731
+ sourceTxHash: input.sourceTxHash,
2732
+ destinationTxHash: null,
2733
+ messageHash: null,
2734
+ status: "pending",
2735
+ nonce: input.nonce ?? null,
2736
+ initiatedAt: now(),
2737
+ attestedAt: null,
2738
+ confirmedAt: null,
2739
+ correlationId,
2740
+ agentId: input.agentId,
2741
+ metadata: input.metadata ?? {}
2742
+ };
2743
+ this.transfers.set(id, transfer);
2744
+ this.actionLinks.set(id, {});
2745
+ return transfer;
2746
+ }
2747
+ /**
2748
+ * Record a CCTP attestation for a pending transfer.
2749
+ * Called after the attestation service has signed the burn message.
2750
+ *
2751
+ * @param input - Attestation details
2752
+ * @returns The updated CrossChainTransfer record
2753
+ * @throws Error if transfer not found or not in pending status
2754
+ */
2755
+ recordAttestation(input) {
2756
+ const transfer = this.transfers.get(input.transferId);
2757
+ if (!transfer) {
2758
+ throw new Error(`Cross-chain transfer not found: ${input.transferId}`);
2759
+ }
2760
+ if (transfer.status !== "pending") {
2761
+ throw new Error(
2762
+ `Transfer ${input.transferId} is not in pending status (current: ${transfer.status})`
2763
+ );
2764
+ }
2765
+ const updated = {
2766
+ ...transfer,
2767
+ messageHash: input.messageHash,
2768
+ status: "attested",
2769
+ attestedAt: now(),
2770
+ metadata: {
2771
+ ...transfer.metadata,
2772
+ ...input.metadata
2773
+ }
2774
+ };
2775
+ this.transfers.set(input.transferId, updated);
2776
+ return updated;
2777
+ }
2778
+ /**
2779
+ * Confirm a cross-chain transfer has been received on the destination chain.
2780
+ * Called after receiveMessage has been executed on the destination.
2781
+ *
2782
+ * @param input - Confirmation details
2783
+ * @returns The updated CrossChainTransfer record
2784
+ * @throws Error if transfer not found or not in attested status
2785
+ */
2786
+ confirmTransfer(input) {
2787
+ const transfer = this.transfers.get(input.transferId);
2788
+ if (!transfer) {
2789
+ throw new Error(`Cross-chain transfer not found: ${input.transferId}`);
2790
+ }
2791
+ if (transfer.status !== "attested") {
2792
+ throw new Error(
2793
+ `Transfer ${input.transferId} is not in attested status (current: ${transfer.status})`
2794
+ );
2795
+ }
2796
+ const updated = {
2797
+ ...transfer,
2798
+ destinationTxHash: input.destinationTxHash,
2799
+ status: "confirmed",
2800
+ confirmedAt: now(),
2801
+ metadata: {
2802
+ ...transfer.metadata,
2803
+ ...input.metadata
2804
+ }
2805
+ };
2806
+ this.transfers.set(input.transferId, updated);
2807
+ return updated;
2808
+ }
2809
+ /**
2810
+ * Mark a transfer as failed.
2811
+ *
2812
+ * @param transferId - The transfer to mark as failed
2813
+ * @param reason - Reason for failure
2814
+ * @returns The updated CrossChainTransfer record
2815
+ */
2816
+ failTransfer(transferId, reason) {
2817
+ const transfer = this.transfers.get(transferId);
2818
+ if (!transfer) {
2819
+ throw new Error(`Cross-chain transfer not found: ${transferId}`);
2820
+ }
2821
+ const updated = {
2822
+ ...transfer,
2823
+ status: "failed",
2824
+ metadata: {
2825
+ ...transfer.metadata,
2826
+ failureReason: reason,
2827
+ failedAt: now()
2828
+ }
2829
+ };
2830
+ this.transfers.set(transferId, updated);
2831
+ return updated;
2832
+ }
2833
+ /**
2834
+ * Link a Kontext action log ID to a cross-chain transfer.
2835
+ * Used to correlate source and destination chain actions in the audit trail.
2836
+ *
2837
+ * @param transferId - The cross-chain transfer ID
2838
+ * @param actionId - The action log ID to link
2839
+ * @param side - Whether this is the source or destination action
2840
+ */
2841
+ linkAction(transferId, actionId, side) {
2842
+ const transfer = this.transfers.get(transferId);
2843
+ if (!transfer) {
2844
+ throw new Error(`Cross-chain transfer not found: ${transferId}`);
2845
+ }
2846
+ const links = this.actionLinks.get(transferId) ?? {};
2847
+ if (side === "source") {
2848
+ links.sourceActionId = actionId;
2849
+ } else {
2850
+ links.destinationActionId = actionId;
2851
+ }
2852
+ this.actionLinks.set(transferId, links);
2853
+ }
2854
+ /**
2855
+ * Get a cross-chain transfer by ID.
2856
+ */
2857
+ getTransfer(transferId) {
2858
+ return this.transfers.get(transferId);
2859
+ }
2860
+ /**
2861
+ * Get all cross-chain transfers, optionally filtered by status.
2862
+ */
2863
+ getTransfers(status) {
2864
+ const all = Array.from(this.transfers.values());
2865
+ if (status) {
2866
+ return all.filter((t) => t.status === status);
2867
+ }
2868
+ return all;
2869
+ }
2870
+ /**
2871
+ * Get transfers by correlation ID.
2872
+ * Useful for finding all transfers related to a single workflow.
2873
+ */
2874
+ getTransfersByCorrelation(correlationId) {
2875
+ return Array.from(this.transfers.values()).filter(
2876
+ (t) => t.correlationId === correlationId
2877
+ );
2878
+ }
2879
+ /**
2880
+ * Build a cross-chain audit trail for a given transfer.
2881
+ * Links source and destination chain actions together.
2882
+ *
2883
+ * @param transferId - The transfer to build an audit trail for
2884
+ * @returns CrossChainAuditEntry with linked action references
2885
+ */
2886
+ getAuditEntry(transferId) {
2887
+ const transfer = this.transfers.get(transferId);
2888
+ if (!transfer) return void 0;
2889
+ const links = this.actionLinks.get(transferId) ?? {};
2890
+ let durationMs = null;
2891
+ if (transfer.confirmedAt && transfer.initiatedAt) {
2892
+ durationMs = new Date(transfer.confirmedAt).getTime() - new Date(transfer.initiatedAt).getTime();
2893
+ }
2894
+ return {
2895
+ transfer,
2896
+ sourceActionId: links.sourceActionId ?? null,
2897
+ destinationActionId: links.destinationActionId ?? null,
2898
+ linked: !!(links.sourceActionId && links.destinationActionId),
2899
+ durationMs
2900
+ };
2901
+ }
2902
+ /**
2903
+ * Build audit trail entries for all transfers, optionally filtered.
2904
+ *
2905
+ * @param agentId - Optional filter by agent
2906
+ * @returns Array of CrossChainAuditEntry records
2907
+ */
2908
+ getAuditTrail(agentId) {
2909
+ let transfers = Array.from(this.transfers.values());
2910
+ if (agentId) {
2911
+ transfers = transfers.filter((t) => t.agentId === agentId);
2912
+ }
2913
+ return transfers.map((t) => this.getAuditEntry(t.id)).filter((entry) => entry !== void 0);
2914
+ }
2915
+ /**
2916
+ * Get the CCTP domain ID for a given chain.
2917
+ *
2918
+ * @param chain - The blockchain network
2919
+ * @returns The CCTP domain ID, or undefined for unsupported chains
2920
+ */
2921
+ static getDomainId(chain) {
2922
+ return CCTP_DOMAINS[chain];
2923
+ }
2924
+ /**
2925
+ * Get the chains supported for CCTP transfers.
2926
+ */
2927
+ static getSupportedChains() {
2928
+ return Object.keys(CCTP_DOMAINS);
2929
+ }
2930
+ // --------------------------------------------------------------------------
2931
+ // Validation checks
2932
+ // --------------------------------------------------------------------------
2933
+ checkChainSupport(chain, label) {
2934
+ const supported = chain in CCTP_DOMAINS;
2935
+ return {
2936
+ name: `cctp_${label}_chain`,
2937
+ passed: supported,
2938
+ description: supported ? `${label} chain ${chain} supports CCTP (domain ${CCTP_DOMAINS[chain]})` : `${label} chain ${chain} does not support CCTP`,
2939
+ severity: supported ? "low" : "high"
2940
+ };
2941
+ }
2942
+ checkRouteValidity(source, destination) {
2943
+ const valid = source !== destination;
2944
+ return {
2945
+ name: "cctp_route_validity",
2946
+ passed: valid,
2947
+ description: valid ? `Valid cross-chain route: ${source} -> ${destination}` : `Invalid route: source and destination chains are the same (${source})`,
2948
+ severity: valid ? "low" : "critical"
2949
+ };
2950
+ }
2951
+ checkTokenSupport(token) {
2952
+ const supported = token === "USDC" || token === "EURC";
2953
+ return {
2954
+ name: "cctp_token_support",
2955
+ passed: supported,
2956
+ description: supported ? `Token ${token} is supported for CCTP transfers` : `Token ${token} is not natively supported by CCTP (only USDC and EURC)`,
2957
+ severity: supported ? "low" : "high"
2958
+ };
2959
+ }
2960
+ checkAmountValidity(amount) {
2961
+ const parsed = parseAmount(amount);
2962
+ const valid = !isNaN(parsed) && parsed > 0;
2963
+ return {
2964
+ name: "cctp_amount_validity",
2965
+ passed: valid,
2966
+ description: valid ? `Transfer amount ${amount} is valid` : `Transfer amount ${amount} is invalid`,
2967
+ severity: valid ? "low" : "critical"
2968
+ };
2969
+ }
2970
+ checkAddressFormat(address, label) {
2971
+ const isValid = /^0x[a-fA-F0-9]{40}$/.test(address);
2972
+ return {
2973
+ name: `cctp_address_${label}`,
2974
+ passed: isValid,
2975
+ description: isValid ? `${label} address format is valid` : `${label} address format is invalid: ${address}`,
2976
+ severity: isValid ? "low" : "high"
2977
+ };
2978
+ }
2979
+ // --------------------------------------------------------------------------
2980
+ // Recommendations
2981
+ // --------------------------------------------------------------------------
2982
+ generateRecommendations(checks, input) {
2983
+ const recommendations = [];
2984
+ const amount = parseAmount(input.amount);
2985
+ const failedChecks = checks.filter((c) => !c.passed);
2986
+ if (failedChecks.some((c) => c.severity === "critical")) {
2987
+ recommendations.push(
2988
+ "Do not proceed with this transfer. Critical validation failures detected."
2989
+ );
2990
+ }
2991
+ if (failedChecks.some((c) => c.name === "cctp_token_support")) {
2992
+ recommendations.push(
2993
+ "Consider using USDC for native CCTP support. Other tokens require bridge protocols."
2994
+ );
2995
+ }
2996
+ if (!isNaN(amount) && amount >= 5e4) {
2997
+ recommendations.push(
2998
+ "Large cross-chain transfer detected. Verify recipient identity and document purpose."
2999
+ );
3000
+ }
3001
+ if (!isNaN(amount) && amount >= 1e4) {
3002
+ recommendations.push(
3003
+ "Cross-chain transfer meets reporting threshold. Ensure CTR filing if applicable."
3004
+ );
3005
+ }
3006
+ if (failedChecks.length === 0) {
3007
+ recommendations.push(
3008
+ "Transfer validation passed. Monitor attestation status for completion."
3009
+ );
3010
+ }
3011
+ return recommendations;
3012
+ }
3013
+ };
3014
+
3015
+ // src/webhooks.ts
3016
+ var DEFAULT_RETRY_CONFIG = {
3017
+ maxRetries: 3,
3018
+ baseDelayMs: 1e3,
3019
+ maxDelayMs: 3e4
3020
+ };
3021
+ var WebhookManager = class {
3022
+ webhooks = /* @__PURE__ */ new Map();
3023
+ deliveryResults = [];
3024
+ retryConfig;
3025
+ fetchFn;
3026
+ constructor(retryConfig, fetchFn) {
3027
+ this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...retryConfig };
3028
+ this.fetchFn = fetchFn ?? globalThis.fetch;
3029
+ }
3030
+ /**
3031
+ * Register a new webhook endpoint.
3032
+ *
3033
+ * @param input - Webhook configuration
3034
+ * @returns The created WebhookConfig
3035
+ */
3036
+ register(input) {
3037
+ if (!input.url || input.url.trim() === "") {
3038
+ throw new Error("Webhook URL is required");
3039
+ }
3040
+ if (!input.events || input.events.length === 0) {
3041
+ throw new Error("At least one event type is required");
3042
+ }
3043
+ const config = {
3044
+ id: generateId(),
3045
+ url: input.url,
3046
+ events: input.events,
3047
+ secret: input.secret,
3048
+ active: true,
3049
+ createdAt: now(),
3050
+ metadata: input.metadata
3051
+ };
3052
+ this.webhooks.set(config.id, config);
3053
+ return config;
3054
+ }
3055
+ /**
3056
+ * Unregister a webhook by ID.
3057
+ *
3058
+ * @param webhookId - The webhook to remove
3059
+ * @returns Whether the webhook was found and removed
3060
+ */
3061
+ unregister(webhookId) {
3062
+ return this.webhooks.delete(webhookId);
3063
+ }
3064
+ /**
3065
+ * Enable or disable a webhook.
3066
+ *
3067
+ * @param webhookId - The webhook to update
3068
+ * @param active - Whether to enable or disable
3069
+ * @returns The updated WebhookConfig, or undefined if not found
3070
+ */
3071
+ setActive(webhookId, active) {
3072
+ const webhook = this.webhooks.get(webhookId);
3073
+ if (!webhook) return void 0;
3074
+ const updated = { ...webhook, active };
3075
+ this.webhooks.set(webhookId, updated);
3076
+ return updated;
3077
+ }
3078
+ /**
3079
+ * Get all registered webhooks.
3080
+ */
3081
+ getWebhooks() {
3082
+ return Array.from(this.webhooks.values());
3083
+ }
3084
+ /**
3085
+ * Get a specific webhook by ID.
3086
+ */
3087
+ getWebhook(webhookId) {
3088
+ return this.webhooks.get(webhookId);
3089
+ }
3090
+ /**
3091
+ * Get delivery results for a specific webhook or all webhooks.
3092
+ */
3093
+ getDeliveryResults(webhookId) {
3094
+ if (webhookId) {
3095
+ return this.deliveryResults.filter((r) => r.webhookId === webhookId);
3096
+ }
3097
+ return [...this.deliveryResults];
3098
+ }
3099
+ /**
3100
+ * Notify all subscribed webhooks of an anomaly detection event.
3101
+ *
3102
+ * @param anomaly - The detected anomaly event
3103
+ * @returns Array of delivery results
3104
+ */
3105
+ async notifyAnomalyDetected(anomaly) {
3106
+ const payload = {
3107
+ id: generateId(),
3108
+ event: "anomaly.detected",
3109
+ timestamp: now(),
3110
+ data: {
3111
+ anomalyId: anomaly.id,
3112
+ type: anomaly.type,
3113
+ severity: anomaly.severity,
3114
+ description: anomaly.description,
3115
+ agentId: anomaly.agentId,
3116
+ actionId: anomaly.actionId,
3117
+ detectedAt: anomaly.detectedAt,
3118
+ data: anomaly.data
3119
+ }
3120
+ };
3121
+ return this.deliver("anomaly.detected", payload);
3122
+ }
3123
+ /**
3124
+ * Notify all subscribed webhooks of a task confirmation.
3125
+ *
3126
+ * @param task - The confirmed task
3127
+ * @returns Array of delivery results
3128
+ */
3129
+ async notifyTaskConfirmed(task) {
3130
+ const payload = {
3131
+ id: generateId(),
3132
+ event: "task.confirmed",
3133
+ timestamp: now(),
3134
+ data: {
3135
+ taskId: task.id,
3136
+ description: task.description,
3137
+ agentId: task.agentId,
3138
+ status: task.status,
3139
+ confirmedAt: task.confirmedAt,
3140
+ correlationId: task.correlationId
3141
+ }
3142
+ };
3143
+ return this.deliver("task.confirmed", payload);
3144
+ }
3145
+ /**
3146
+ * Notify all subscribed webhooks of a task failure.
3147
+ *
3148
+ * @param task - The failed task
3149
+ * @returns Array of delivery results
3150
+ */
3151
+ async notifyTaskFailed(task) {
3152
+ const payload = {
3153
+ id: generateId(),
3154
+ event: "task.failed",
3155
+ timestamp: now(),
3156
+ data: {
3157
+ taskId: task.id,
3158
+ description: task.description,
3159
+ agentId: task.agentId,
3160
+ status: task.status,
3161
+ correlationId: task.correlationId,
3162
+ metadata: task.metadata
3163
+ }
3164
+ };
3165
+ return this.deliver("task.failed", payload);
3166
+ }
3167
+ /**
3168
+ * Notify all subscribed webhooks of a trust score change.
3169
+ *
3170
+ * @param trustScore - The new trust score
3171
+ * @param previousScore - The previous score value (if known)
3172
+ * @returns Array of delivery results
3173
+ */
3174
+ async notifyTrustScoreChanged(trustScore, previousScore) {
3175
+ const payload = {
3176
+ id: generateId(),
3177
+ event: "trust.score_changed",
3178
+ timestamp: now(),
3179
+ data: {
3180
+ agentId: trustScore.agentId,
3181
+ score: trustScore.score,
3182
+ previousScore: previousScore ?? null,
3183
+ level: trustScore.level,
3184
+ factors: trustScore.factors,
3185
+ computedAt: trustScore.computedAt
3186
+ }
3187
+ };
3188
+ return this.deliver("trust.score_changed", payload);
3189
+ }
3190
+ // --------------------------------------------------------------------------
3191
+ // Delivery with retry
3192
+ // --------------------------------------------------------------------------
3193
+ async deliver(eventType, payload) {
3194
+ const subscribers = Array.from(this.webhooks.values()).filter(
3195
+ (w) => w.active && w.events.includes(eventType)
3196
+ );
3197
+ const results = [];
3198
+ for (const webhook of subscribers) {
3199
+ const result = await this.deliverToWebhook(webhook, payload);
3200
+ results.push(result);
3201
+ this.deliveryResults.push(result);
3202
+ }
3203
+ return results;
3204
+ }
3205
+ async deliverToWebhook(webhook, payload) {
3206
+ let lastError = null;
3207
+ let statusCode = null;
3208
+ for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
3209
+ try {
3210
+ if (attempt > 0) {
3211
+ const delay = Math.min(
3212
+ this.retryConfig.baseDelayMs * Math.pow(2, attempt - 1),
3213
+ this.retryConfig.maxDelayMs
3214
+ );
3215
+ await this.sleep(delay);
3216
+ }
3217
+ const response = await this.fetchFn(webhook.url, {
3218
+ method: "POST",
3219
+ headers: {
3220
+ "Content-Type": "application/json",
3221
+ "X-Kontext-Event": payload.event,
3222
+ "X-Kontext-Delivery": payload.id,
3223
+ ...webhook.secret ? { "X-Kontext-Signature": await this.computeSignature(payload, webhook.secret) } : {}
3224
+ },
3225
+ body: JSON.stringify(payload)
3226
+ });
3227
+ statusCode = response.status;
3228
+ if (response.ok) {
3229
+ return {
3230
+ webhookId: webhook.id,
3231
+ payloadId: payload.id,
3232
+ success: true,
3233
+ statusCode,
3234
+ attempts: attempt + 1,
3235
+ error: null,
3236
+ lastAttemptAt: now()
3237
+ };
3238
+ }
3239
+ lastError = `HTTP ${response.status}`;
3240
+ } catch (error) {
3241
+ lastError = error instanceof Error ? error.message : String(error);
3242
+ }
3243
+ }
3244
+ return {
3245
+ webhookId: webhook.id,
3246
+ payloadId: payload.id,
3247
+ success: false,
3248
+ statusCode,
3249
+ attempts: this.retryConfig.maxRetries + 1,
3250
+ error: lastError,
3251
+ lastAttemptAt: now()
3252
+ };
3253
+ }
3254
+ async computeSignature(payload, secret) {
3255
+ const { createHmac } = await import('crypto');
3256
+ const hmac = createHmac("sha256", secret);
3257
+ hmac.update(JSON.stringify(payload));
3258
+ return hmac.digest("hex");
3259
+ }
3260
+ sleep(ms) {
3261
+ return new Promise((resolve) => setTimeout(resolve, ms));
3262
+ }
3263
+ };
3264
+
3265
+ exports.CCTPTransferManager = CCTPTransferManager;
3266
+ exports.DigestChain = DigestChain;
3267
+ exports.Kontext = Kontext;
3268
+ exports.KontextError = KontextError;
3269
+ exports.KontextErrorCode = KontextErrorCode;
3270
+ exports.UsdcCompliance = UsdcCompliance;
3271
+ exports.WebhookManager = WebhookManager;
3272
+ exports.verifyExportedChain = verifyExportedChain;
3273
+ //# sourceMappingURL=index.js.map
3274
+ //# sourceMappingURL=index.js.map