kontext-sdk 0.5.0 → 0.6.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.mjs CHANGED
@@ -1,6 +1,13 @@
1
1
  import { createHash } from 'crypto';
2
- import * as fs3 from 'fs';
3
- import * as path3 from 'path';
2
+ import * as fs4 from 'fs';
3
+ import * as path4 from 'path';
4
+
5
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
6
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
7
+ }) : x)(function(x) {
8
+ if (typeof require !== "undefined") return require.apply(this, arguments);
9
+ throw Error('Dynamic require of "' + x + '" is not supported');
10
+ });
4
11
 
5
12
  // src/utils.ts
6
13
  function generateId() {
@@ -119,7 +126,7 @@ var TrustScorer = class {
119
126
  const flagged = riskScore >= RISK_FLAG_THRESHOLD;
120
127
  const recommendation = riskScore >= RISK_BLOCK_THRESHOLD ? "block" : riskScore >= RISK_REVIEW_THRESHOLD ? "review" : "approve";
121
128
  return {
122
- txHash: tx.txHash,
129
+ ...tx.txHash ? { txHash: tx.txHash } : {},
123
130
  riskScore,
124
131
  riskLevel,
125
132
  factors,
@@ -389,7 +396,7 @@ var TrustScorer = class {
389
396
  return {
390
397
  name: "amount_risk",
391
398
  score,
392
- description: `Transaction amount ${tx.amount} ${tx.token}`
399
+ description: `Transaction amount ${tx.amount} ${tx.token ?? tx.currency ?? ""}`
393
400
  };
394
401
  }
395
402
  computeNewDestinationRisk(tx) {
@@ -497,6 +504,9 @@ var TrustScorer = class {
497
504
  };
498
505
 
499
506
  // src/types.ts
507
+ function isCryptoTransaction(input) {
508
+ return !!input.txHash && !!input.chain && !!input.token;
509
+ }
500
510
  var KontextErrorCode = /* @__PURE__ */ ((KontextErrorCode2) => {
501
511
  KontextErrorCode2["INITIALIZATION_ERROR"] = "INITIALIZATION_ERROR";
502
512
  KontextErrorCode2["VALIDATION_ERROR"] = "VALIDATION_ERROR";
@@ -702,10 +712,10 @@ var AnomalyDetector = class {
702
712
  return this.createAnomaly(
703
713
  "unusualAmount",
704
714
  amount > threshold * 5 ? "critical" : amount > threshold * 2 ? "high" : "medium",
705
- `Transaction amount ${tx.amount} ${tx.token} exceeds threshold of ${this.thresholds.maxAmount}`,
715
+ `Transaction amount ${tx.amount} ${tx.token ?? tx.currency ?? ""} exceeds threshold of ${this.thresholds.maxAmount}`,
706
716
  tx.agentId,
707
717
  tx.id,
708
- { amount: tx.amount, threshold: this.thresholds.maxAmount, token: tx.token }
718
+ { amount: tx.amount, threshold: this.thresholds.maxAmount, token: tx.token ?? tx.currency }
709
719
  );
710
720
  }
711
721
  const history = this.store.getTransactionsByAgent(tx.agentId);
@@ -1137,6 +1147,18 @@ var DigestChain = class {
1137
1147
  getTerminalDigest() {
1138
1148
  return this.currentDigest;
1139
1149
  }
1150
+ /**
1151
+ * Restore the terminal digest from persisted state.
1152
+ * Called after loading actions from storage so that new actions
1153
+ * chain correctly from the last stored digest instead of genesis.
1154
+ *
1155
+ * Does NOT reconstruct the links array — in-memory verification
1156
+ * via verify() will not work after restore. Use the stored action
1157
+ * digest/priorDigest fields for cross-process chain verification.
1158
+ */
1159
+ restoreTerminalDigest(digest) {
1160
+ this.currentDigest = digest;
1161
+ }
1140
1162
  /**
1141
1163
  * Get the number of links in the chain.
1142
1164
  */
@@ -1404,6 +1426,7 @@ var ActionLogger = class {
1404
1426
  async logTransaction(input) {
1405
1427
  this.validateTransactionInput(input);
1406
1428
  const correlationId = input.correlationId ?? generateId();
1429
+ const description = input.token && input.chain ? `${input.token} transfer of ${input.amount} on ${input.chain}` : `${input.currency ?? "USD"} payment of ${input.amount}${input.paymentMethod ? ` via ${input.paymentMethod}` : ""}`;
1407
1430
  const record = {
1408
1431
  id: generateId(),
1409
1432
  timestamp: now(),
@@ -1412,16 +1435,19 @@ var ActionLogger = class {
1412
1435
  ...input.sessionId ? { sessionId: input.sessionId } : {},
1413
1436
  correlationId,
1414
1437
  type: "transaction",
1415
- description: `${input.token} transfer of ${input.amount} on ${input.chain}`,
1438
+ description,
1416
1439
  metadata: {
1417
1440
  ...input.metadata
1418
1441
  },
1419
- txHash: input.txHash,
1420
- chain: input.chain,
1421
1442
  amount: input.amount,
1422
- token: input.token,
1423
1443
  from: input.from,
1424
- to: input.to
1444
+ to: input.to,
1445
+ ...input.txHash ? { txHash: input.txHash } : {},
1446
+ ...input.chain ? { chain: input.chain } : {},
1447
+ ...input.token ? { token: input.token } : {},
1448
+ ...input.currency ? { currency: input.currency } : {},
1449
+ ...input.paymentMethod ? { paymentMethod: input.paymentMethod } : {},
1450
+ ...input.paymentReference ? { paymentReference: input.paymentReference } : {}
1425
1451
  };
1426
1452
  const link = this.digestChain.append(record);
1427
1453
  record.digest = link.digest;
@@ -1482,6 +1508,13 @@ var ActionLogger = class {
1482
1508
  verifyChain(actions) {
1483
1509
  return this.digestChain.verify(actions);
1484
1510
  }
1511
+ /**
1512
+ * Restore chain state from persisted actions so that new actions
1513
+ * chain correctly across process boundaries.
1514
+ */
1515
+ restoreChainState(terminalDigest) {
1516
+ this.digestChain.restoreTerminalDigest(terminalDigest);
1517
+ }
1485
1518
  // --------------------------------------------------------------------------
1486
1519
  // Private helpers
1487
1520
  // --------------------------------------------------------------------------
@@ -1494,53 +1527,56 @@ var ActionLogger = class {
1494
1527
  { field: "amount", value: input.amount }
1495
1528
  );
1496
1529
  }
1497
- if (!input.txHash || input.txHash.trim() === "") {
1498
- throw new KontextError(
1499
- "VALIDATION_ERROR" /* VALIDATION_ERROR */,
1500
- "Transaction hash is required",
1501
- { field: "txHash" }
1502
- );
1503
- }
1504
1530
  if (!input.from || input.from.trim() === "") {
1505
1531
  throw new KontextError(
1506
1532
  "VALIDATION_ERROR" /* VALIDATION_ERROR */,
1507
- "Sender address (from) is required",
1533
+ "Sender (from) is required",
1508
1534
  { field: "from" }
1509
1535
  );
1510
1536
  }
1511
1537
  if (!input.to || input.to.trim() === "") {
1512
1538
  throw new KontextError(
1513
1539
  "VALIDATION_ERROR" /* VALIDATION_ERROR */,
1514
- "Recipient address (to) is required",
1540
+ "Recipient (to) is required",
1515
1541
  { field: "to" }
1516
1542
  );
1517
1543
  }
1518
- const validChains = ["ethereum", "base", "polygon", "arbitrum", "optimism", "arc", "avalanche", "solana"];
1519
- if (!validChains.includes(input.chain)) {
1520
- throw new KontextError(
1521
- "VALIDATION_ERROR" /* VALIDATION_ERROR */,
1522
- `Invalid chain: ${input.chain}. Must be one of: ${validChains.join(", ")}`,
1523
- { field: "chain", value: input.chain }
1524
- );
1525
- }
1526
- const validTokens = ["USDC", "USDT", "DAI", "EURC"];
1527
- if (!validTokens.includes(input.token)) {
1528
- throw new KontextError(
1529
- "VALIDATION_ERROR" /* VALIDATION_ERROR */,
1530
- `Invalid token: ${input.token}. Must be one of: ${validTokens.join(", ")}`,
1531
- { field: "token", value: input.token }
1532
- );
1544
+ const hasCryptoFields = input.txHash !== void 0 || input.chain !== void 0 || input.token !== void 0;
1545
+ if (hasCryptoFields) {
1546
+ if (!input.txHash || input.txHash.trim() === "") {
1547
+ throw new KontextError(
1548
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
1549
+ "Transaction hash is required for crypto transactions",
1550
+ { field: "txHash" }
1551
+ );
1552
+ }
1553
+ const validChains = ["ethereum", "base", "polygon", "arbitrum", "optimism", "arc", "avalanche", "solana"];
1554
+ if (!input.chain || !validChains.includes(input.chain)) {
1555
+ throw new KontextError(
1556
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
1557
+ `Invalid chain: ${input.chain}. Must be one of: ${validChains.join(", ")}`,
1558
+ { field: "chain", value: input.chain }
1559
+ );
1560
+ }
1561
+ const validTokens = ["USDC", "USDT", "DAI", "EURC", "USDP", "USDG"];
1562
+ if (!input.token || !validTokens.includes(input.token)) {
1563
+ throw new KontextError(
1564
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
1565
+ `Invalid token: ${input.token}. Must be one of: ${validTokens.join(", ")}`,
1566
+ { field: "token", value: input.token }
1567
+ );
1568
+ }
1533
1569
  }
1534
1570
  }
1535
1571
  flushToFile(actions) {
1536
1572
  const outputDir = this.config.localOutputDir ?? ".kontext";
1537
- const logDir = path3.join(outputDir, "logs");
1573
+ const logDir = path4.join(outputDir, "logs");
1538
1574
  try {
1539
- fs3.mkdirSync(logDir, { recursive: true });
1575
+ fs4.mkdirSync(logDir, { recursive: true });
1540
1576
  const filename = `actions-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.jsonl`;
1541
- const filePath = path3.join(logDir, filename);
1577
+ const filePath = path4.join(logDir, filename);
1542
1578
  const lines = actions.map((a) => JSON.stringify(a)).join("\n") + "\n";
1543
- fs3.appendFileSync(filePath, lines, "utf-8");
1579
+ fs4.appendFileSync(filePath, lines, "utf-8");
1544
1580
  } catch (error) {
1545
1581
  this.emitLog("warn", "Failed to write log file", { error });
1546
1582
  }
@@ -1989,7 +2025,7 @@ var AuditExporter = class {
1989
2025
  if (options.agentIds && !options.agentIds.includes(tx.agentId)) {
1990
2026
  return false;
1991
2027
  }
1992
- if (options.chains && !options.chains.includes(tx.chain)) {
2028
+ if (options.chains && (!tx.chain || !options.chains.includes(tx.chain))) {
1993
2029
  return false;
1994
2030
  }
1995
2031
  return true;
@@ -2080,8 +2116,6 @@ var AuditExporter = class {
2080
2116
  return sections.join("\n\n");
2081
2117
  }
2082
2118
  };
2083
-
2084
- // src/integrations/usdc.ts
2085
2119
  var USDC_CONTRACTS = {
2086
2120
  ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
2087
2121
  base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -2142,6 +2176,22 @@ var SANCTIONED_ADDRESSES = [
2142
2176
  var SANCTIONED_SET = new Set(
2143
2177
  SANCTIONED_ADDRESSES.map((addr) => addr.toLowerCase())
2144
2178
  );
2179
+ function loadCachedSDN() {
2180
+ try {
2181
+ const dataDir = process.env["KONTEXT_DATA_DIR"] || ".kontext";
2182
+ const cachePath = path4.join(dataDir, "ofac-sdn-cache.json");
2183
+ if (fs4.existsSync(cachePath)) {
2184
+ const cache = JSON.parse(fs4.readFileSync(cachePath, "utf-8"));
2185
+ if (Array.isArray(cache.addresses)) {
2186
+ for (const addr of cache.addresses) {
2187
+ SANCTIONED_SET.add(String(addr).toLowerCase());
2188
+ }
2189
+ }
2190
+ }
2191
+ } catch {
2192
+ }
2193
+ }
2194
+ loadCachedSDN();
2145
2195
  var ENHANCED_DUE_DILIGENCE_THRESHOLD = 3e3;
2146
2196
  var REPORTING_THRESHOLD = 1e4;
2147
2197
  var LARGE_TRANSACTION_THRESHOLD = 5e4;
@@ -2441,6 +2491,183 @@ var UsdcCompliance = class _UsdcCompliance {
2441
2491
  }
2442
2492
  };
2443
2493
 
2494
+ // src/integrations/payment-compliance.ts
2495
+ var ENHANCED_DUE_DILIGENCE_THRESHOLD2 = 3e3;
2496
+ var REPORTING_THRESHOLD2 = 1e4;
2497
+ var LARGE_TRANSACTION_THRESHOLD2 = 5e4;
2498
+ var NAME_MATCH_THRESHOLD = 0.85;
2499
+ var _screener = null;
2500
+ var _screenerLoaded = false;
2501
+ function getScreener() {
2502
+ if (!_screenerLoaded) {
2503
+ _screenerLoaded = true;
2504
+ try {
2505
+ const mod = __require("./ofac-sanctions.js");
2506
+ if (mod.OFACSanctionsScreener) {
2507
+ _screener = new mod.OFACSanctionsScreener();
2508
+ }
2509
+ } catch {
2510
+ }
2511
+ }
2512
+ return _screener;
2513
+ }
2514
+ var PaymentCompliance = class _PaymentCompliance {
2515
+ /**
2516
+ * Run compliance checks on a general payment.
2517
+ *
2518
+ * @param input - Payment to evaluate
2519
+ * @returns UsdcComplianceCheck with pass/fail results and recommendations
2520
+ */
2521
+ static checkPayment(input) {
2522
+ const checks = [];
2523
+ checks.push(_PaymentCompliance.checkAmountValid(input.amount));
2524
+ checks.push(_PaymentCompliance.checkEntityScreening(input.from, "sender"));
2525
+ checks.push(_PaymentCompliance.checkEntityScreening(input.to, "recipient"));
2526
+ checks.push(_PaymentCompliance.checkEnhancedDueDiligence(input.amount));
2527
+ checks.push(_PaymentCompliance.checkReportingThreshold(input.amount));
2528
+ const failedChecks = checks.filter((c) => !c.passed);
2529
+ const compliant = failedChecks.every((c) => c.severity === "low");
2530
+ const highestSeverity = failedChecks.reduce(
2531
+ (max, c) => {
2532
+ const order = ["low", "medium", "high", "critical"];
2533
+ return order.indexOf(c.severity) > order.indexOf(max) ? c.severity : max;
2534
+ },
2535
+ "low"
2536
+ );
2537
+ const recommendations = _PaymentCompliance.generateRecommendations(checks, input);
2538
+ return {
2539
+ compliant,
2540
+ checks,
2541
+ riskLevel: highestSeverity,
2542
+ recommendations
2543
+ };
2544
+ }
2545
+ // --------------------------------------------------------------------------
2546
+ // Individual checks
2547
+ // --------------------------------------------------------------------------
2548
+ static checkAmountValid(amount) {
2549
+ const parsed = parseAmount(amount);
2550
+ const isValid = !isNaN(parsed) && parsed > 0;
2551
+ return {
2552
+ name: "amount_valid",
2553
+ passed: isValid,
2554
+ description: isValid ? `Payment amount ${amount} is valid` : `Payment amount ${amount} is invalid`,
2555
+ severity: isValid ? "low" : "critical"
2556
+ };
2557
+ }
2558
+ static checkEntityScreening(entity, label) {
2559
+ if (/^0x[a-fA-F0-9]{40}$/.test(entity)) {
2560
+ const sanctioned = UsdcCompliance.isSanctioned(entity);
2561
+ return {
2562
+ name: `entity_screening_${label}`,
2563
+ passed: !sanctioned,
2564
+ description: sanctioned ? `${label} address ${entity} matches OFAC SDN sanctioned address` : `${label} address passed sanctions screening`,
2565
+ severity: sanctioned ? "critical" : "low"
2566
+ };
2567
+ }
2568
+ const screener = getScreener();
2569
+ if (!screener) {
2570
+ return {
2571
+ name: `entity_screening_${label}`,
2572
+ passed: true,
2573
+ description: `${label} "${entity}" \u2014 name-based OFAC screening not available (address screening active)`,
2574
+ severity: "low"
2575
+ };
2576
+ }
2577
+ const rawMatches = screener.searchEntityName(entity, NAME_MATCH_THRESHOLD);
2578
+ const matches = rawMatches.filter(
2579
+ (m) => m.similarity >= NAME_MATCH_THRESHOLD && m.matchedOn.length >= 4
2580
+ );
2581
+ if (matches.length > 0) {
2582
+ const topMatch = matches[0];
2583
+ const isActive = topMatch.entity.list !== "DELISTED";
2584
+ if (isActive) {
2585
+ return {
2586
+ name: `entity_screening_${label}`,
2587
+ passed: false,
2588
+ description: `${label} "${entity}" matches OFAC SDN entity "${topMatch.matchedOn}" (${Math.round(topMatch.similarity * 100)}% match)`,
2589
+ severity: "critical"
2590
+ };
2591
+ }
2592
+ return {
2593
+ name: `entity_screening_${label}`,
2594
+ passed: true,
2595
+ description: `${label} "${entity}" matches delisted entity "${topMatch.matchedOn}" \u2014 enhanced due diligence recommended`,
2596
+ severity: "medium"
2597
+ };
2598
+ }
2599
+ return {
2600
+ name: `entity_screening_${label}`,
2601
+ passed: true,
2602
+ description: `${label} "${entity}" passed OFAC entity screening`,
2603
+ severity: "low"
2604
+ };
2605
+ }
2606
+ static checkEnhancedDueDiligence(amount) {
2607
+ const parsed = parseAmount(amount);
2608
+ const requiresEdd = !isNaN(parsed) && parsed >= ENHANCED_DUE_DILIGENCE_THRESHOLD2;
2609
+ return {
2610
+ name: "enhanced_due_diligence",
2611
+ passed: true,
2612
+ description: requiresEdd ? `Amount ${amount} requires enhanced due diligence (threshold: ${ENHANCED_DUE_DILIGENCE_THRESHOLD2})` : `Amount ${amount} is below enhanced due diligence threshold`,
2613
+ severity: requiresEdd ? "medium" : "low"
2614
+ };
2615
+ }
2616
+ static checkReportingThreshold(amount) {
2617
+ const parsed = parseAmount(amount);
2618
+ const requiresReporting = !isNaN(parsed) && parsed >= REPORTING_THRESHOLD2;
2619
+ const isLarge = !isNaN(parsed) && parsed >= LARGE_TRANSACTION_THRESHOLD2;
2620
+ let description;
2621
+ let severity;
2622
+ if (isLarge) {
2623
+ description = `Amount ${amount} is a large payment (>= ${LARGE_TRANSACTION_THRESHOLD2}) \u2014 requires enhanced monitoring`;
2624
+ severity = "high";
2625
+ } else if (requiresReporting) {
2626
+ description = `Amount ${amount} meets reporting threshold (>= ${REPORTING_THRESHOLD2})`;
2627
+ severity = "medium";
2628
+ } else {
2629
+ description = `Amount ${amount} is below reporting threshold`;
2630
+ severity = "low";
2631
+ }
2632
+ return {
2633
+ name: "reporting_threshold",
2634
+ passed: true,
2635
+ description,
2636
+ severity
2637
+ };
2638
+ }
2639
+ // --------------------------------------------------------------------------
2640
+ // Recommendations
2641
+ // --------------------------------------------------------------------------
2642
+ static generateRecommendations(checks, input) {
2643
+ const recommendations = [];
2644
+ const amount = parseAmount(input.amount);
2645
+ for (const check of checks) {
2646
+ if (!check.passed && check.name.startsWith("entity_screening_")) {
2647
+ recommendations.push(
2648
+ `BLOCK: ${check.description}. Payment is prohibited under OFAC regulations.`
2649
+ );
2650
+ }
2651
+ }
2652
+ if (!isNaN(amount) && amount >= LARGE_TRANSACTION_THRESHOLD2) {
2653
+ recommendations.push(
2654
+ `Enhanced monitoring required for payment of ${input.amount} ${input.currency ?? "USD"} (above ${LARGE_TRANSACTION_THRESHOLD2} threshold).`
2655
+ );
2656
+ }
2657
+ if (!isNaN(amount) && amount >= REPORTING_THRESHOLD2) {
2658
+ recommendations.push(
2659
+ `CTR reporting may be required for payment of ${input.amount} ${input.currency ?? "USD"} (meets ${REPORTING_THRESHOLD2} reporting threshold).`
2660
+ );
2661
+ }
2662
+ if (!isNaN(amount) && amount >= ENHANCED_DUE_DILIGENCE_THRESHOLD2) {
2663
+ recommendations.push(
2664
+ `Enhanced due diligence recommended for payment of ${input.amount} ${input.currency ?? "USD"} (Travel Rule threshold: ${ENHANCED_DUE_DILIGENCE_THRESHOLD2}).`
2665
+ );
2666
+ }
2667
+ return recommendations;
2668
+ }
2669
+ };
2670
+
2444
2671
  // src/plans.ts
2445
2672
  var PLAN_LIMITS = {
2446
2673
  free: 2e4,
@@ -2793,7 +3020,7 @@ var JsonFileExporter = class {
2793
3020
  buffer = [];
2794
3021
  bufferSize;
2795
3022
  constructor(options) {
2796
- this.outputDir = path3.resolve(options?.outputDir ?? ".kontext/exports");
3023
+ this.outputDir = path4.resolve(options?.outputDir ?? ".kontext/exports");
2797
3024
  this.bufferSize = options?.bufferSize ?? 1;
2798
3025
  }
2799
3026
  async export(events) {
@@ -2808,11 +3035,11 @@ var JsonFileExporter = class {
2808
3035
  const toWrite = [...this.buffer];
2809
3036
  this.buffer = [];
2810
3037
  try {
2811
- fs3.mkdirSync(this.outputDir, { recursive: true });
3038
+ fs4.mkdirSync(this.outputDir, { recursive: true });
2812
3039
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2813
- const filePath = path3.join(this.outputDir, `events-${date}.jsonl`);
3040
+ const filePath = path4.join(this.outputDir, `events-${date}.jsonl`);
2814
3041
  const lines = toWrite.map((e) => JSON.stringify(e)).join("\n") + "\n";
2815
- fs3.appendFileSync(filePath, lines, "utf-8");
3042
+ fs4.appendFileSync(filePath, lines, "utf-8");
2816
3043
  } catch (error) {
2817
3044
  console.warn("[Kontext JsonFileExporter] Failed to write events:", error);
2818
3045
  }
@@ -3151,7 +3378,7 @@ var Kontext = class _Kontext {
3151
3378
  * @returns The created transaction record
3152
3379
  */
3153
3380
  async logTransaction(input) {
3154
- if (input.chain !== "base") {
3381
+ if (input.chain && input.chain !== "base") {
3155
3382
  requirePlan("multi-chain", this.planManager.getTier());
3156
3383
  }
3157
3384
  this.validateMetadata(input.metadata);
@@ -3188,10 +3415,22 @@ var Kontext = class _Kontext {
3188
3415
  /**
3189
3416
  * Restore state from the attached storage adapter.
3190
3417
  * Loads previously persisted actions, transactions, tasks, and anomalies.
3418
+ * Also restores the digest chain's terminal digest so that new actions
3419
+ * chain correctly across process boundaries.
3191
3420
  * No-op if no storage adapter is configured.
3192
3421
  */
3193
3422
  async restore() {
3194
3423
  await this.store.restore();
3424
+ const actions = this.store.getActions();
3425
+ if (actions.length > 0) {
3426
+ const sorted = [...actions].sort(
3427
+ (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
3428
+ );
3429
+ const lastAction = sorted[sorted.length - 1];
3430
+ if (lastAction.digest) {
3431
+ this.logger.restoreChainState(lastAction.digest);
3432
+ }
3433
+ }
3195
3434
  }
3196
3435
  // --------------------------------------------------------------------------
3197
3436
  // Task Confirmation
@@ -3494,7 +3733,7 @@ var Kontext = class _Kontext {
3494
3733
  */
3495
3734
  async verify(input) {
3496
3735
  const transaction = await this.logTransaction(input);
3497
- const compliance = UsdcCompliance.checkTransaction(input);
3736
+ const compliance = isCryptoTransaction(input) ? UsdcCompliance.checkTransaction(input) : PaymentCompliance.checkPayment(input);
3498
3737
  let reasoningId;
3499
3738
  if (input.reasoning) {
3500
3739
  const entry = await this.logReasoning({
@@ -3523,18 +3762,22 @@ var Kontext = class _Kontext {
3523
3762
  const threshold = parseAmount(this.config.approvalThreshold);
3524
3763
  if (amount > threshold) {
3525
3764
  requiresApproval = true;
3765
+ const label = input.token ? `${input.token} ${input.amount} transfer` : `${input.currency ?? "USD"} ${input.amount} payment`;
3526
3766
  task = await this.createTask({
3527
- description: `Approve ${input.token} ${input.amount} transfer from ${input.from} to ${input.to}`,
3767
+ description: `Approve ${label} from ${input.from} to ${input.to}`,
3528
3768
  agentId: input.agentId,
3529
- requiredEvidence: ["txHash"],
3769
+ requiredEvidence: input.txHash ? ["txHash"] : ["paymentReference"],
3530
3770
  metadata: {
3531
- txHash: input.txHash,
3532
- chain: input.chain,
3533
3771
  amount: input.amount,
3534
- token: input.token,
3535
3772
  from: input.from,
3536
3773
  to: input.to,
3537
- approvalThreshold: this.config.approvalThreshold
3774
+ approvalThreshold: this.config.approvalThreshold,
3775
+ ...input.txHash ? { txHash: input.txHash } : {},
3776
+ ...input.chain ? { chain: input.chain } : {},
3777
+ ...input.token ? { token: input.token } : {},
3778
+ ...input.currency ? { currency: input.currency } : {},
3779
+ ...input.paymentMethod ? { paymentMethod: input.paymentMethod } : {},
3780
+ ...input.paymentReference ? { paymentReference: input.paymentReference } : {}
3538
3781
  }
3539
3782
  });
3540
3783
  }
@@ -3633,7 +3876,7 @@ var Kontext = class _Kontext {
3633
3876
  * and cryptographically verifies the digest chain integrity.
3634
3877
  *
3635
3878
  * The certificate includes: action/transaction/reasoning counts, digest chain
3636
- * verification status (Patent US 12,463,819 B1), the agent's current trust
3879
+ * verification status (patented), the agent's current trust
3637
3880
  * score, and an overall compliance status. A SHA-256 content hash of the
3638
3881
  * certificate itself is included for tamper-evidence.
3639
3882
  *
@@ -3871,20 +4114,20 @@ var MemoryStorage = class {
3871
4114
  var FileStorage = class {
3872
4115
  baseDir;
3873
4116
  constructor(baseDir) {
3874
- this.baseDir = path3.resolve(baseDir);
4117
+ this.baseDir = path4.resolve(baseDir);
3875
4118
  }
3876
4119
  async save(key, data) {
3877
- fs3.mkdirSync(this.baseDir, { recursive: true });
4120
+ fs4.mkdirSync(this.baseDir, { recursive: true });
3878
4121
  const filePath = this.keyToPath(key);
3879
- const dir = path3.dirname(filePath);
3880
- fs3.mkdirSync(dir, { recursive: true });
3881
- fs3.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
4122
+ const dir = path4.dirname(filePath);
4123
+ fs4.mkdirSync(dir, { recursive: true });
4124
+ fs4.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
3882
4125
  }
3883
4126
  async load(key) {
3884
4127
  const filePath = this.keyToPath(key);
3885
- if (!fs3.existsSync(filePath)) return null;
4128
+ if (!fs4.existsSync(filePath)) return null;
3886
4129
  try {
3887
- const raw = fs3.readFileSync(filePath, "utf-8");
4130
+ const raw = fs4.readFileSync(filePath, "utf-8");
3888
4131
  return JSON.parse(raw);
3889
4132
  } catch {
3890
4133
  return null;
@@ -3892,12 +4135,12 @@ var FileStorage = class {
3892
4135
  }
3893
4136
  async delete(key) {
3894
4137
  const filePath = this.keyToPath(key);
3895
- if (fs3.existsSync(filePath)) {
3896
- fs3.unlinkSync(filePath);
4138
+ if (fs4.existsSync(filePath)) {
4139
+ fs4.unlinkSync(filePath);
3897
4140
  }
3898
4141
  }
3899
4142
  async list(prefix) {
3900
- if (!fs3.existsSync(this.baseDir)) return [];
4143
+ if (!fs4.existsSync(this.baseDir)) return [];
3901
4144
  return this.listRecursive(this.baseDir, prefix);
3902
4145
  }
3903
4146
  /** Get the base directory path. */
@@ -3909,18 +4152,18 @@ var FileStorage = class {
3909
4152
  // --------------------------------------------------------------------------
3910
4153
  keyToPath(key) {
3911
4154
  const safeName = key.replace(/[<>"|?*]/g, "_");
3912
- return path3.join(this.baseDir, `${safeName}.json`);
4155
+ return path4.join(this.baseDir, `${safeName}.json`);
3913
4156
  }
3914
4157
  pathToKey(filePath) {
3915
- const relative2 = path3.relative(this.baseDir, filePath);
4158
+ const relative2 = path4.relative(this.baseDir, filePath);
3916
4159
  return relative2.replace(/\.json$/, "");
3917
4160
  }
3918
4161
  listRecursive(dir, prefix) {
3919
4162
  const keys = [];
3920
- if (!fs3.existsSync(dir)) return keys;
3921
- const entries = fs3.readdirSync(dir, { withFileTypes: true });
4163
+ if (!fs4.existsSync(dir)) return keys;
4164
+ const entries = fs4.readdirSync(dir, { withFileTypes: true });
3922
4165
  for (const entry of entries) {
3923
- const fullPath = path3.join(dir, entry.name);
4166
+ const fullPath = path4.join(dir, entry.name);
3924
4167
  if (entry.isDirectory()) {
3925
4168
  keys.push(...this.listRecursive(fullPath, prefix));
3926
4169
  } else if (entry.isFile() && entry.name.endsWith(".json")) {
@@ -3934,387 +4177,6 @@ var FileStorage = class {
3934
4177
  }
3935
4178
  };
3936
4179
 
3937
- // src/storage-firestore.ts
3938
- var FirestoreStorageAdapter = class {
3939
- config;
3940
- /** In-memory cache for load() — avoids re-fetching on every store.restore() */
3941
- cache = /* @__PURE__ */ new Map();
3942
- /** Token cache: avoid metadata server round-trips on every write */
3943
- cachedToken = null;
3944
- tokenExpiresAt = 0;
3945
- constructor(config) {
3946
- this.config = {
3947
- gcpProjectId: config.gcpProjectId,
3948
- userId: config.userId,
3949
- accessToken: config.accessToken,
3950
- databaseId: config.databaseId ?? "(default)",
3951
- firestoreBaseUrl: config.firestoreBaseUrl ?? "https://firestore.googleapis.com",
3952
- writeDocumentsIndividually: config.writeDocumentsIndividually ?? true
3953
- };
3954
- }
3955
- // --------------------------------------------------------------------------
3956
- // StorageAdapter interface
3957
- // --------------------------------------------------------------------------
3958
- /**
3959
- * Save data under a Kontext storage key.
3960
- *
3961
- * The flat key space (kontext:actions, kontext:transactions, etc.) is mapped
3962
- * to structured Firestore paths. List data (actions, transactions) is written
3963
- * as individual documents under sub-collections for queryability.
3964
- */
3965
- async save(key, data) {
3966
- this.cache.set(key, data);
3967
- if (key === "kontext:actions" && Array.isArray(data)) {
3968
- await this.saveActionList(data);
3969
- } else if (key === "kontext:transactions" && Array.isArray(data)) {
3970
- await this.saveTransactionList(data);
3971
- } else if (key === "kontext:tasks" && Array.isArray(data)) {
3972
- await this.saveTaskList(data);
3973
- } else if (key === "kontext:anomalies" && Array.isArray(data)) {
3974
- await this.saveAnomalyList(data);
3975
- } else {
3976
- await this.saveDocument(this.keyToPath(key), data);
3977
- }
3978
- }
3979
- /**
3980
- * Load data for a Kontext storage key.
3981
- * Returns cached data if available (populated by save()).
3982
- * Falls back to Firestore fetch for cold starts.
3983
- */
3984
- async load(key) {
3985
- if (this.cache.has(key)) {
3986
- return this.cache.get(key) ?? null;
3987
- }
3988
- if (key === "kontext:actions") {
3989
- const actions = await this.loadActionList();
3990
- this.cache.set(key, actions);
3991
- return actions;
3992
- } else if (key === "kontext:transactions") {
3993
- const txs = await this.loadTransactionList();
3994
- this.cache.set(key, txs);
3995
- return txs;
3996
- } else if (key === "kontext:tasks") {
3997
- const tasks = await this.loadTaskList();
3998
- this.cache.set(key, tasks);
3999
- return tasks;
4000
- } else if (key === "kontext:anomalies") {
4001
- const anomalies = await this.loadAnomalyList();
4002
- this.cache.set(key, anomalies);
4003
- return anomalies;
4004
- } else {
4005
- return this.loadDocument(this.keyToPath(key));
4006
- }
4007
- }
4008
- async delete(key) {
4009
- this.cache.delete(key);
4010
- await this.deleteDocument(this.keyToPath(key));
4011
- }
4012
- async list(prefix) {
4013
- const all = Array.from(this.cache.keys());
4014
- if (!prefix) return all;
4015
- return all.filter((k) => k.startsWith(prefix));
4016
- }
4017
- // --------------------------------------------------------------------------
4018
- // Structured write methods (per-record documents)
4019
- // --------------------------------------------------------------------------
4020
- /**
4021
- * Write a single action log to its canonical Firestore path.
4022
- * Called by the SDK when `writeDocumentsIndividually` is true.
4023
- *
4024
- * Path: users/{userId}/projects/{projectId}/agents/{agentId}/sessions/{sessionId}/actions/{actionId}
4025
- */
4026
- async writeAction(action) {
4027
- if (!this.config.writeDocumentsIndividually) return;
4028
- const path4 = this.actionPath(action);
4029
- await this.saveDocument(path4, action);
4030
- }
4031
- /**
4032
- * Write a single transaction record to its canonical Firestore path.
4033
- *
4034
- * Path: users/{userId}/projects/{projectId}/agents/{agentId}/sessions/{sessionId}/transactions/{txId}
4035
- */
4036
- async writeTransaction(tx) {
4037
- if (!this.config.writeDocumentsIndividually) return;
4038
- const path4 = this.transactionPath(tx);
4039
- await this.saveDocument(path4, tx);
4040
- }
4041
- /**
4042
- * Write a single task to its canonical Firestore path.
4043
- *
4044
- * Path: users/{userId}/projects/{projectId}/tasks/{taskId}
4045
- */
4046
- async writeTask(task) {
4047
- if (!this.config.writeDocumentsIndividually) return;
4048
- const path4 = this.taskPath(task.id);
4049
- await this.saveDocument(path4, task);
4050
- }
4051
- // --------------------------------------------------------------------------
4052
- // Path builders
4053
- // --------------------------------------------------------------------------
4054
- get basePath() {
4055
- return `users/${this.config.userId}/projects`;
4056
- }
4057
- actionPath(action) {
4058
- const sessionId = action.sessionId ?? "_default";
4059
- return `${this.basePath}/${action.projectId}/agents/${sanitize(action.agentId)}/sessions/${sanitize(sessionId)}/actions/${action.id}`;
4060
- }
4061
- transactionPath(tx) {
4062
- const sessionId = tx.sessionId ?? "_default";
4063
- return `${this.basePath}/${tx.projectId}/agents/${sanitize(tx.agentId)}/sessions/${sanitize(sessionId)}/transactions/${tx.id}`;
4064
- }
4065
- taskPath(taskId) {
4066
- return `${this.basePath}/_tasks/${taskId}`;
4067
- }
4068
- anomalyPath(anomalyId, agentId) {
4069
- return `${this.basePath}/_anomalies/agents/${sanitize(agentId)}/${anomalyId}`;
4070
- }
4071
- keyToPath(key) {
4072
- const safe = key.replace(/:/g, "/").replace(/[^a-zA-Z0-9/_-]/g, "_");
4073
- return `${this.basePath}/_meta/${safe}`;
4074
- }
4075
- // --------------------------------------------------------------------------
4076
- // Bulk save/load (called by KontextStore.flush / restore)
4077
- // --------------------------------------------------------------------------
4078
- async saveActionList(actions) {
4079
- if (!this.config.writeDocumentsIndividually) {
4080
- await this.saveDocument(`${this.basePath}/_snapshots/actions`, actions);
4081
- return;
4082
- }
4083
- await Promise.allSettled(actions.map((a) => this.saveDocument(this.actionPath(a), a)));
4084
- }
4085
- async saveTransactionList(txs) {
4086
- if (!this.config.writeDocumentsIndividually) {
4087
- await this.saveDocument(`${this.basePath}/_snapshots/transactions`, txs);
4088
- return;
4089
- }
4090
- await Promise.allSettled(txs.map((tx) => this.saveDocument(this.transactionPath(tx), tx)));
4091
- }
4092
- async saveTaskList(entries) {
4093
- await Promise.allSettled(
4094
- entries.map(([_id, task]) => this.saveDocument(this.taskPath(task.id), task))
4095
- );
4096
- }
4097
- async saveAnomalyList(anomalies) {
4098
- if (!this.config.writeDocumentsIndividually) {
4099
- await this.saveDocument(`${this.basePath}/_snapshots/anomalies`, anomalies);
4100
- return;
4101
- }
4102
- await Promise.allSettled(
4103
- anomalies.map((a) => this.saveDocument(this.anomalyPath(a.id, a.agentId), a))
4104
- );
4105
- }
4106
- async loadActionList() {
4107
- if (!this.config.writeDocumentsIndividually) {
4108
- const snap = await this.loadDocument(`${this.basePath}/_snapshots/actions`);
4109
- return Array.isArray(snap) ? snap : [];
4110
- }
4111
- return this.queryCollectionGroup("actions");
4112
- }
4113
- async loadTransactionList() {
4114
- if (!this.config.writeDocumentsIndividually) {
4115
- const snap = await this.loadDocument(`${this.basePath}/_snapshots/transactions`);
4116
- return Array.isArray(snap) ? snap : [];
4117
- }
4118
- return this.queryCollectionGroup("transactions");
4119
- }
4120
- async loadTaskList() {
4121
- const tasks = await this.queryCollection(`${this.basePath}/_tasks`);
4122
- return tasks.map((t) => [t.id, t]);
4123
- }
4124
- async loadAnomalyList() {
4125
- if (!this.config.writeDocumentsIndividually) {
4126
- const snap = await this.loadDocument(`${this.basePath}/_snapshots/anomalies`);
4127
- return Array.isArray(snap) ? snap : [];
4128
- }
4129
- return this.queryCollectionGroup("anomalies");
4130
- }
4131
- // --------------------------------------------------------------------------
4132
- // Firestore REST API helpers
4133
- // --------------------------------------------------------------------------
4134
- firestoreBase() {
4135
- return `${this.config.firestoreBaseUrl}/v1/projects/${this.config.gcpProjectId}/databases/${this.config.databaseId}/documents`;
4136
- }
4137
- /** Save an arbitrary JS object as a Firestore document at the given path. */
4138
- async saveDocument(fsPath, data) {
4139
- const url = `${this.firestoreBase()}/${fsPath}`;
4140
- const token = await this.getToken();
4141
- const body = JSON.stringify({ fields: toFirestoreFields(data) });
4142
- const res = await fetch(url, {
4143
- method: "PATCH",
4144
- headers: {
4145
- "Content-Type": "application/json",
4146
- Authorization: `Bearer ${token}`
4147
- },
4148
- body
4149
- });
4150
- if (!res.ok) {
4151
- const text = await res.text().catch(() => "");
4152
- throw new Error(`Firestore write failed [${res.status}] ${fsPath}: ${text}`);
4153
- }
4154
- }
4155
- /** Load a document at the given Firestore path. Returns null if not found. */
4156
- async loadDocument(fsPath) {
4157
- const url = `${this.firestoreBase()}/${fsPath}`;
4158
- const token = await this.getToken();
4159
- const res = await fetch(url, {
4160
- headers: { Authorization: `Bearer ${token}` }
4161
- });
4162
- if (res.status === 404) return null;
4163
- if (!res.ok) return null;
4164
- const doc = await res.json();
4165
- return fromFirestoreFields(doc.fields);
4166
- }
4167
- /** Delete a document at the given Firestore path. */
4168
- async deleteDocument(fsPath) {
4169
- const url = `${this.firestoreBase()}/${fsPath}`;
4170
- const token = await this.getToken();
4171
- await fetch(url, {
4172
- method: "DELETE",
4173
- headers: { Authorization: `Bearer ${token}` }
4174
- });
4175
- }
4176
- /**
4177
- * List all documents in a collection and deserialize them.
4178
- * Used for tasks (simple flat collection).
4179
- */
4180
- async queryCollection(collectionPath) {
4181
- const url = `${this.firestoreBase()}/${collectionPath}`;
4182
- const token = await this.getToken();
4183
- const res = await fetch(url, {
4184
- headers: { Authorization: `Bearer ${token}` }
4185
- });
4186
- if (!res.ok) return [];
4187
- const body = await res.json();
4188
- if (!body.documents) return [];
4189
- return body.documents.map((doc) => fromFirestoreFields(doc.fields));
4190
- }
4191
- /**
4192
- * Run a Firestore collection group query to fetch documents across all
4193
- * nested sub-collections with the given name (e.g., 'actions' across all
4194
- * agents and sessions).
4195
- *
4196
- * Scoped to the user's project root to prevent cross-tenant reads.
4197
- */
4198
- async queryCollectionGroup(collectionId) {
4199
- const parent = `projects/${this.config.gcpProjectId}/databases/${this.config.databaseId}/documents`;
4200
- `${this.config.firestoreBaseUrl}/v1/${parent}:runQuery`;
4201
- const token = await this.getToken();
4202
- const scopedUrl = `${this.config.firestoreBaseUrl}/v1/projects/${this.config.gcpProjectId}/databases/${this.config.databaseId}/documents/users/${this.config.userId}:runQuery`;
4203
- const body = {
4204
- structuredQuery: {
4205
- from: [{ collectionId, allDescendants: true }],
4206
- orderBy: [{ field: { fieldPath: "timestamp" }, direction: "ASCENDING" }]
4207
- }
4208
- };
4209
- const res = await fetch(scopedUrl, {
4210
- method: "POST",
4211
- headers: {
4212
- "Content-Type": "application/json",
4213
- Authorization: `Bearer ${token}`
4214
- },
4215
- body: JSON.stringify(body)
4216
- });
4217
- if (!res.ok) return [];
4218
- const results = await res.json();
4219
- return results.filter((r) => r.document?.fields).map((r) => fromFirestoreFields(r.document.fields));
4220
- }
4221
- // --------------------------------------------------------------------------
4222
- // Auth: GCP metadata server + token caching
4223
- // --------------------------------------------------------------------------
4224
- async getToken() {
4225
- if (this.config.accessToken) {
4226
- return this.config.accessToken;
4227
- }
4228
- const now2 = Date.now();
4229
- if (this.cachedToken && now2 < this.tokenExpiresAt - 3e5) {
4230
- return this.cachedToken;
4231
- }
4232
- const metadataUrl = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
4233
- const res = await fetch(metadataUrl, {
4234
- headers: { "Metadata-Flavor": "Google" }
4235
- });
4236
- if (!res.ok) {
4237
- throw new Error(
4238
- "FirestoreStorageAdapter: Could not fetch GCP access token. On GCP, ensure the service account has Firestore write access. For local dev, pass accessToken in FirestoreStorageConfig."
4239
- );
4240
- }
4241
- const data = await res.json();
4242
- this.cachedToken = data.access_token;
4243
- this.tokenExpiresAt = now2 + data.expires_in * 1e3;
4244
- return this.cachedToken;
4245
- }
4246
- };
4247
- function toFirestoreValue(value) {
4248
- if (value === null || value === void 0) {
4249
- return { nullValue: null };
4250
- }
4251
- if (typeof value === "boolean") {
4252
- return { booleanValue: value };
4253
- }
4254
- if (typeof value === "number") {
4255
- if (Number.isInteger(value)) {
4256
- return { integerValue: String(value) };
4257
- }
4258
- return { doubleValue: value };
4259
- }
4260
- if (typeof value === "string") {
4261
- if (/^\d{4}-\d{2}-\d{2}T/.test(value)) {
4262
- return { timestampValue: value };
4263
- }
4264
- return { stringValue: value };
4265
- }
4266
- if (Array.isArray(value)) {
4267
- return {
4268
- arrayValue: {
4269
- values: value.map(toFirestoreValue)
4270
- }
4271
- };
4272
- }
4273
- if (typeof value === "object") {
4274
- return {
4275
- mapValue: {
4276
- fields: toFirestoreFields(value)
4277
- }
4278
- };
4279
- }
4280
- return { stringValue: String(value) };
4281
- }
4282
- function toFirestoreFields(obj) {
4283
- if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
4284
- return {};
4285
- }
4286
- const result = {};
4287
- for (const [k, v] of Object.entries(obj)) {
4288
- result[k] = toFirestoreValue(v);
4289
- }
4290
- return result;
4291
- }
4292
- function fromFirestoreValue(value) {
4293
- if ("nullValue" in value) return null;
4294
- if ("booleanValue" in value) return value.booleanValue;
4295
- if ("integerValue" in value) return parseInt(value.integerValue, 10);
4296
- if ("doubleValue" in value) return value.doubleValue;
4297
- if ("stringValue" in value) return value.stringValue;
4298
- if ("timestampValue" in value) return value.timestampValue;
4299
- if ("arrayValue" in value) {
4300
- return (value.arrayValue.values ?? []).map(fromFirestoreValue);
4301
- }
4302
- if ("mapValue" in value) {
4303
- return fromFirestoreFields(value.mapValue.fields);
4304
- }
4305
- return null;
4306
- }
4307
- function fromFirestoreFields(fields) {
4308
- const result = {};
4309
- for (const [k, v] of Object.entries(fields)) {
4310
- result[k] = fromFirestoreValue(v);
4311
- }
4312
- return result;
4313
- }
4314
- function sanitize(id) {
4315
- return id.replace(/[/.]/g, "_").slice(0, 1500);
4316
- }
4317
-
4318
- export { AnomalyDetector, ConsoleExporter, DigestChain, FeatureFlagManager, FileStorage, FirestoreStorageAdapter, JsonFileExporter, Kontext, KontextError, KontextErrorCode, MemoryStorage, NoopExporter, PLAN_LIMITS, PlanManager, TrustScorer, UsdcCompliance, isFeatureAvailable, requirePlan, verifyExportedChain };
4180
+ export { AnomalyDetector, ConsoleExporter, DigestChain, FeatureFlagManager, FileStorage, JsonFileExporter, Kontext, KontextError, KontextErrorCode, MemoryStorage, NoopExporter, PLAN_LIMITS, PaymentCompliance, PlanManager, TrustScorer, UsdcCompliance, isCryptoTransaction, isFeatureAvailable, requirePlan, verifyExportedChain };
4319
4181
  //# sourceMappingURL=index.mjs.map
4320
4182
  //# sourceMappingURL=index.mjs.map