@atbash/sdk 0.18.0-dev.0 → 0.19.1-dev.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 CHANGED
@@ -59,6 +59,7 @@ __export(src_ts_exports, {
59
59
  classifyMemoryWrite: () => classifyMemoryWrite,
60
60
  columnAad: () => columnAad,
61
61
  commitMemoryVersion: () => commitMemoryVersion,
62
+ configureEvents: () => configureEvents,
62
63
  containsEvasionCharacters: () => containsEvasionCharacters,
63
64
  containsSecret: () => containsSecret,
64
65
  createFileLogger: () => createFileLogger,
@@ -74,6 +75,7 @@ __export(src_ts_exports, {
74
75
  encryptForOrg: () => encryptForOrg,
75
76
  encryptMemoryContent: () => encryptMemoryContent,
76
77
  encryptedLength: () => encryptedLength,
78
+ eventsAreOn: () => eventsAreOn,
77
79
  flushTelemetry: () => flushTelemetry,
78
80
  generateKeypair: () => generateKeypair,
79
81
  getActiveAgentMemory: () => getActiveAgentMemory,
@@ -102,6 +104,7 @@ __export(src_ts_exports, {
102
104
  pubkeyToHex: () => pubkeyToHex,
103
105
  recordCall: () => recordCall,
104
106
  recordDuration: () => recordDuration,
107
+ recordEvent: () => recordEvent,
105
108
  redactJsonStrings: () => redactJsonStrings,
106
109
  redactSecrets: () => redactSecrets,
107
110
  resolve: () => resolve,
@@ -498,9 +501,19 @@ function loadAgentFromFile(keyPath) {
498
501
  }
499
502
 
500
503
  // src-ts/normalize.ts
504
+ function wireText(raw, fallback = "") {
505
+ if (raw === null || raw === void 0) return fallback;
506
+ if (typeof raw === "object" || typeof raw === "function") return fallback;
507
+ return String(raw);
508
+ }
509
+ function wireNumber(raw, fallback = 0) {
510
+ if (raw === null || raw === void 0) return fallback;
511
+ if (typeof raw === "object" || typeof raw === "function") return fallback;
512
+ return Number(raw);
513
+ }
501
514
  function normalizeVerdict(raw) {
502
515
  if (raw === null || raw === void 0) return "No verdict";
503
- const v = String(raw).trim().toUpperCase();
516
+ const v = wireText(raw).trim().toUpperCase();
504
517
  if (v === "ALLOW" || v === "GREEN") return "ALLOW";
505
518
  if (v === "HOLD" || v === "YELLOW") return "HOLD";
506
519
  if (v === "BLOCK" || v === "RED") return "BLOCK";
@@ -510,7 +523,7 @@ function normalizeActionType(raw) {
510
523
  return typeof raw === "string" ? raw.trim().toLowerCase() : "";
511
524
  }
512
525
  function normalizeStatus(raw) {
513
- const s = String(raw ?? "").toLowerCase();
526
+ const s = wireText(raw).toLowerCase();
514
527
  if (s === "pending" || s === "answered" || s === "error") return s;
515
528
  return "error";
516
529
  }
@@ -597,6 +610,59 @@ function canonicalAllow(data) {
597
610
  return verdict === "ALLOW" || verdict === "GREEN";
598
611
  }
599
612
 
613
+ // src-ts/opentel/events.ts
614
+ var SLOW_MS = 1500;
615
+ var SUCCESS_SAMPLE_RATE = 10;
616
+ var logger = null;
617
+ var eventsEnabled = false;
618
+ var sdkVersion = "";
619
+ var eventSource = "sdk";
620
+ var hashAgentKey = null;
621
+ function eventsOptedOut() {
622
+ const flag = process.env.ATBASH_TELEMETRY_EVENTS_DISABLED?.trim().toLowerCase();
623
+ return Boolean(flag && ["1", "true", "yes", "on"].includes(flag));
624
+ }
625
+ function configureEvents(next, opts = {}) {
626
+ logger = next;
627
+ eventsEnabled = next !== null && !eventsOptedOut();
628
+ if (opts.version) sdkVersion = opts.version;
629
+ if (opts.source) eventSource = opts.source;
630
+ hashAgentKey = opts.hashAgentKey ?? null;
631
+ }
632
+ function eventsAreOn() {
633
+ return eventsEnabled && logger !== null;
634
+ }
635
+ function sampleRateFor(e) {
636
+ if (e.status === "error") return 1;
637
+ if (e.durationMs >= SLOW_MS) return 1;
638
+ return Math.random() < 1 / SUCCESS_SAMPLE_RATE ? SUCCESS_SAMPLE_RATE : 0;
639
+ }
640
+ function recordEvent(e) {
641
+ if (!eventsAreOn()) return;
642
+ const sampleRate = sampleRateFor(e);
643
+ if (sampleRate === 0) return;
644
+ try {
645
+ const attributes = {
646
+ "function.name": e.functionName,
647
+ "duration_ms": Math.round(e.durationMs),
648
+ status: e.status,
649
+ source: eventSource,
650
+ // Honeycomb reweights counts and percentiles by this. It is per-event
651
+ // because the sampling is biased by outcome and latency.
652
+ sampleRate
653
+ };
654
+ if (sdkVersion) attributes["sdk.version"] = sdkVersion;
655
+ if (e.agentPubkey && hashAgentKey) attributes["agent.hash"] = hashAgentKey(e.agentPubkey);
656
+ if (e.chain) attributes["chain"] = e.chain;
657
+ if (e.errorClass) attributes["error.class"] = e.errorClass;
658
+ if (e.errorStatus !== void 0) attributes["error.status"] = e.errorStatus;
659
+ if (e.errorPath) attributes["error.path"] = e.errorPath;
660
+ if (e.outcome) attributes["outcome"] = e.outcome;
661
+ logger.emit({ body: e.functionName, attributes });
662
+ } catch {
663
+ }
664
+ }
665
+
600
666
  // src-ts/opentel/telemetry.ts
601
667
  var import_node_fs2 = require("fs");
602
668
  var import_node_os2 = require("os");
@@ -604,7 +670,11 @@ var import_node_path2 = require("path");
604
670
  var import_exporter_metrics_otlp_http = require("@opentelemetry/exporter-metrics-otlp-http");
605
671
  var import_resources = require("@opentelemetry/resources");
606
672
  var import_sdk_metrics = require("@opentelemetry/sdk-metrics");
673
+ var import_exporter_logs_otlp_http = require("@opentelemetry/exporter-logs-otlp-http");
674
+ var import_sdk_logs = require("@opentelemetry/sdk-logs");
675
+ var import_node_crypto = require("crypto");
607
676
  var meterProvider = null;
677
+ var loggerProvider = null;
608
678
  var callCounter = null;
609
679
  var durationHistogram = null;
610
680
  var defaultSource = "sdk";
@@ -631,6 +701,41 @@ function autoInit() {
631
701
  if (isTelemetryOptedOut()) return;
632
702
  setupTelemetry({ enabled: true });
633
703
  }
704
+ function defaultAgentKeyHasher(pubkeyHex) {
705
+ return (0, import_node_crypto.createHash)("sha256").update(pubkeyHex).digest("hex");
706
+ }
707
+ var counterHasher = null;
708
+ function hashAgentKeyOrDrop(pubkeyHex) {
709
+ return counterHasher ? counterHasher(pubkeyHex) : void 0;
710
+ }
711
+ function customCollector() {
712
+ const url = process.env.ATBASH_TELEMETRY_COLLECTOR_URL?.trim();
713
+ if (!url) return null;
714
+ const loopback = /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])(:|\/|$)/i.test(url);
715
+ if (loopback && process.env.ATBASH_TELEMETRY_ALLOW_LOOPBACK !== "1") {
716
+ throw new Error(
717
+ "ATBASH_TELEMETRY_COLLECTOR_URL is a loopback address; set ATBASH_TELEMETRY_ALLOW_LOOPBACK=1 to allow it."
718
+ );
719
+ }
720
+ if (!loopback && !/^https:\/\//i.test(url)) {
721
+ throw new Error("ATBASH_TELEMETRY_COLLECTOR_URL must be https (loopback excepted).");
722
+ }
723
+ let headers = {};
724
+ const raw = process.env.ATBASH_TELEMETRY_COLLECTOR_HEADERS?.trim();
725
+ if (raw) {
726
+ try {
727
+ const parsed = JSON.parse(raw);
728
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
729
+ headers = Object.fromEntries(
730
+ Object.entries(parsed).map(([k, v]) => [k, String(v)])
731
+ );
732
+ }
733
+ } catch {
734
+ throw new Error("ATBASH_TELEMETRY_COLLECTOR_HEADERS must be a JSON object of header names to values.");
735
+ }
736
+ }
737
+ return { url: url.replace(/\/+$/, ""), headers: () => headers };
738
+ }
634
739
  function setupTelemetry(config) {
635
740
  if (!config.enabled) return;
636
741
  if (isTelemetryOptedOut()) return;
@@ -642,16 +747,20 @@ function setupTelemetry(config) {
642
747
  requestedExportIntervalMs = config.exportIntervalMs;
643
748
  }
644
749
  if (meterProvider) return;
645
- if (!config.endpoint || !config.getAuthHeaders) return;
646
- if (/^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)(:|\/|$)/i.test(
750
+ const collector = customCollector();
751
+ if (!collector && (!config.endpoint || !config.getAuthHeaders)) return;
752
+ if (!collector && /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)(:|\/|$)/i.test(
647
753
  config.endpoint
648
754
  ))
649
755
  return;
650
- const proxyUrl = `${config.endpoint.replace(/\/+$/, "")}/api/telemetry`;
651
- const getAuthHeaders = config.getAuthHeaders;
756
+ const destination = collector ?? {
757
+ url: `${config.endpoint.replace(/\/+$/, "")}/api/telemetry`,
758
+ headers: () => config.getAuthHeaders()
759
+ };
760
+ const proxyUrl = destination.url;
652
761
  const exporter = new import_exporter_metrics_otlp_http.OTLPMetricExporter({
653
- url: proxyUrl,
654
- headers: async () => getAuthHeaders(),
762
+ url: collector ? `${proxyUrl}/v1/metrics` : proxyUrl,
763
+ headers: async () => destination.headers(),
655
764
  // DELTA, not the OTel default of CUMULATIVE.
656
765
  //
657
766
  // A cumulative counter reports a running total, and the backend recovers
@@ -683,6 +792,21 @@ function setupTelemetry(config) {
683
792
  description: "SDK function execution duration",
684
793
  unit: "ms"
685
794
  });
795
+ counterHasher = config.hashAgentKey ?? defaultAgentKeyHasher;
796
+ const logExporter = new import_exporter_logs_otlp_http.OTLPLogExporter({
797
+ // A real collector takes OTLP at its own signal paths; the Atbash proxy
798
+ // takes a query parameter, because it is one authenticated route.
799
+ url: collector ? `${proxyUrl}/v1/logs` : `${proxyUrl}?signal=logs`,
800
+ headers: async () => destination.headers()
801
+ });
802
+ loggerProvider = new import_sdk_logs.LoggerProvider({
803
+ resource: (0, import_resources.resourceFromAttributes)({ "service.name": "atbash-sdk" }),
804
+ processors: [new import_sdk_logs.BatchLogRecordProcessor({ exporter: logExporter })]
805
+ });
806
+ configureEvents(loggerProvider.getLogger("atbash-sdk"), {
807
+ source: config.source ?? defaultSource,
808
+ hashAgentKey: config.hashAgentKey ?? defaultAgentKeyHasher
809
+ });
686
810
  }
687
811
  function recordCall(functionName, source, agentPubkey) {
688
812
  autoInit();
@@ -690,7 +814,10 @@ function recordCall(functionName, source, agentPubkey) {
690
814
  callCounter.add(1, {
691
815
  "function.name": functionName,
692
816
  source: source ?? defaultSource,
693
- ...agentPubkey && { "agent.pubkey": agentPubkey }
817
+ // Hashed on this path too. The counters and the events export to the same
818
+ // place in the same request, so a raw key here would make hashing it on
819
+ // the event beside it pointless.
820
+ ...agentPubkey && { "agent.hash": hashAgentKeyOrDrop(agentPubkey) }
694
821
  });
695
822
  }
696
823
  function recordDuration(functionName, durationMs, status, source) {
@@ -702,15 +829,16 @@ function recordDuration(functionName, durationMs, status, source) {
702
829
  });
703
830
  }
704
831
  async function flushTelemetry() {
705
- if (!meterProvider) return;
706
- await meterProvider.forceFlush();
832
+ await Promise.all([meterProvider?.forceFlush(), loggerProvider?.forceFlush()]);
707
833
  }
708
834
  async function shutdownTelemetry() {
709
- if (!meterProvider) return;
710
- await meterProvider.shutdown();
835
+ await Promise.all([meterProvider?.shutdown(), loggerProvider?.shutdown()]);
711
836
  meterProvider = null;
837
+ loggerProvider = null;
712
838
  callCounter = null;
713
839
  durationHistogram = null;
840
+ counterHasher = null;
841
+ configureEvents(null);
714
842
  defaultSource = "sdk";
715
843
  sourceExplicitlySet = false;
716
844
  requestedExportIntervalMs = void 0;
@@ -812,6 +940,13 @@ function resolveConstructorChain(options) {
812
940
  }
813
941
  return chainForNetwork(options.network ?? forcedChainNetwork() ?? "private");
814
942
  }
943
+ function errorDetail(err) {
944
+ if (!(err instanceof AtbashAPIError)) return {};
945
+ return {
946
+ errorStatus: err.status,
947
+ ...err.path ? { errorPath: err.path } : {}
948
+ };
949
+ }
815
950
  var Atbash = class _Atbash {
816
951
  auth;
817
952
  endpoint;
@@ -1022,7 +1157,7 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
1022
1157
  *
1023
1158
  * @param logger - The host logger, when one was supplied.
1024
1159
  */
1025
- static warnOnStrandedKey(logger) {
1160
+ static warnOnStrandedKey(logger2) {
1026
1161
  const stranded = strandedAgentKey();
1027
1162
  if (!stranded) return;
1028
1163
  let identity;
@@ -1031,7 +1166,7 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
1031
1166
  } catch {
1032
1167
  identity = "(unreadable)";
1033
1168
  }
1034
- logger?.warn?.(
1169
+ logger2?.warn?.(
1035
1170
  `[atbash] "agentKey" in config.json is ignored \u2014 settings do not choose the signing key. It holds ${identity}; this client signs as the key file or ATBASH_AGENT_KEY instead. Move it into the key file, or delete the field, to silence this.`
1036
1171
  );
1037
1172
  }
@@ -1273,15 +1408,15 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
1273
1408
  verdict: normalizeVerdict(data.verdict),
1274
1409
  allow: this.executable(data),
1275
1410
  actionType: normalizeActionType(data.action_type),
1276
- reason: String(data.reason ?? ""),
1277
- confidence: Number(data.confidence ?? 0),
1278
- provider: String(data.provider ?? ""),
1279
- latencyMs: Number(data.latency_ms ?? 0),
1280
- toolCallId: String(data.tool_call_id ?? logResult.toolCallId),
1411
+ reason: wireText(data.reason),
1412
+ confidence: wireNumber(data.confidence),
1413
+ provider: wireText(data.provider),
1414
+ latencyMs: wireNumber(data.latency_ms),
1415
+ toolCallId: wireText(data.tool_call_id, logResult.toolCallId),
1281
1416
  onChain: Boolean(data.on_chain),
1282
1417
  enforced: Boolean(data.enforced),
1283
- enforcementMode: String(data.enforcement_mode ?? ""),
1284
- status: String(data.status ?? ""),
1418
+ enforcementMode: wireText(data.enforcement_mode),
1419
+ status: wireText(data.status),
1285
1420
  score,
1286
1421
  refusal: explicitRefusal(data)
1287
1422
  };
@@ -1505,8 +1640,8 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
1505
1640
  status: normalizeStatus(data.status),
1506
1641
  verdict: normalizeVerdict(data.verdict),
1507
1642
  allow: this.executable(data),
1508
- reason: String(data.reason ?? ""),
1509
- judgmentId: String(data.judgmentId ?? judgmentId),
1643
+ reason: wireText(data.reason),
1644
+ judgmentId: wireText(data.judgmentId, judgmentId),
1510
1645
  onChain: optBool(data.onChain),
1511
1646
  cached: optBool(data.cached),
1512
1647
  responseTimeMs: optNumber(data.responseTimeMs)
@@ -1790,10 +1925,28 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
1790
1925
  recordCall(name, void 0, agentPubkey);
1791
1926
  try {
1792
1927
  const result = await fn();
1793
- recordDuration(name, performance.now() - start, "success");
1928
+ const durationMs = performance.now() - start;
1929
+ recordDuration(name, durationMs, "success");
1930
+ recordEvent({
1931
+ functionName: name,
1932
+ durationMs,
1933
+ status: "success",
1934
+ agentPubkey,
1935
+ chain: this.blockchainRid
1936
+ });
1794
1937
  return result;
1795
1938
  } catch (err) {
1796
- recordDuration(name, performance.now() - start, "error");
1939
+ const durationMs = performance.now() - start;
1940
+ recordDuration(name, durationMs, "error");
1941
+ recordEvent({
1942
+ functionName: name,
1943
+ durationMs,
1944
+ status: "error",
1945
+ agentPubkey,
1946
+ chain: this.blockchainRid,
1947
+ errorClass: err instanceof Error ? err.constructor.name : typeof err,
1948
+ ...errorDetail(err)
1949
+ });
1797
1950
  throw err;
1798
1951
  }
1799
1952
  }
@@ -2095,10 +2248,12 @@ function isRecord2(v) {
2095
2248
  }
2096
2249
  function optString(v) {
2097
2250
  if (v === null || v === void 0) return void 0;
2251
+ if (typeof v === "object" || typeof v === "function") return void 0;
2098
2252
  return typeof v === "string" ? v : String(v);
2099
2253
  }
2100
2254
  function optNumber(v) {
2101
2255
  if (v === null || v === void 0) return void 0;
2256
+ if (typeof v === "object" || typeof v === "function") return void 0;
2102
2257
  const n = Number(v);
2103
2258
  return Number.isFinite(n) ? n : void 0;
2104
2259
  }
@@ -2286,6 +2441,47 @@ async function scanMemoryBatch(entries, auth, opts) {
2286
2441
  return results;
2287
2442
  }
2288
2443
 
2444
+ // src-ts/memory/telemetry.ts
2445
+ function metricName(action) {
2446
+ return `memory.${action}`;
2447
+ }
2448
+ function recordMemoryCall(action, startedAt, agentPubkey, status, err) {
2449
+ const name = metricName(action);
2450
+ const durationMs = performance.now() - startedAt;
2451
+ recordCall(name, void 0, agentPubkey);
2452
+ recordDuration(name, durationMs, status);
2453
+ recordEvent({
2454
+ functionName: name,
2455
+ durationMs,
2456
+ status,
2457
+ agentPubkey,
2458
+ ...err !== void 0 && {
2459
+ errorClass: err instanceof Error ? err.constructor.name : typeof err,
2460
+ // The chain transport is where the failures are, so the status and the
2461
+ // route are the whole of what makes one diagnosable.
2462
+ ...err instanceof AtbashAPIError && {
2463
+ errorStatus: err.status,
2464
+ ...err.path ? { errorPath: err.path } : {}
2465
+ }
2466
+ }
2467
+ });
2468
+ }
2469
+ function recordMemoryGuard(verdict, committed, blocked, startedAt, agentPubkey) {
2470
+ const durationMs = performance.now() - startedAt;
2471
+ recordCall("memory.guard", void 0, agentPubkey);
2472
+ recordDuration("memory.guard", durationMs, blocked ? "error" : "success");
2473
+ recordEvent({
2474
+ functionName: "memory.guard",
2475
+ durationMs,
2476
+ // A block is the outcome worth keeping every one of; the sampler keeps
2477
+ // errors whole, and a refused write is an error from the agent's side.
2478
+ status: blocked ? "error" : "success",
2479
+ agentPubkey,
2480
+ errorClass: blocked ? `memory-blocked:${verdict}` : void 0,
2481
+ outcome: `${verdict}/${committed ? "committed" : "uncommitted"}`
2482
+ });
2483
+ }
2484
+
2289
2485
  // src-ts/memory/transport.ts
2290
2486
  var DEFAULT_TIMEOUT_MS = 12e4;
2291
2487
  function transport(opts) {
@@ -2298,24 +2494,39 @@ async function failure(action, resp) {
2298
2494
  );
2299
2495
  }
2300
2496
  async function memoryQuery(action, auth, blockchainRid, opts, filePath, extra = {}) {
2497
+ const start = performance.now();
2301
2498
  const params = new URLSearchParams({ action, agent: auth.pubkey });
2302
2499
  if (filePath !== null) params.set("file_path", filePath);
2303
2500
  for (const [key, value] of Object.entries(extra)) params.set(key, String(value));
2304
- const resp = await transport(opts).get(
2305
- `/api/risk-engine?${params.toString()}`,
2306
- void 0,
2307
- agentAuthHeaders(auth, blockchainRid)
2308
- );
2309
- if (resp.status !== 200) throw await failure(action, resp);
2310
- return resp.json();
2501
+ try {
2502
+ const resp = await transport(opts).get(
2503
+ `/api/risk-engine?${params.toString()}`,
2504
+ void 0,
2505
+ agentAuthHeaders(auth, blockchainRid)
2506
+ );
2507
+ if (resp.status !== 200) throw await failure(action, resp);
2508
+ const body = await resp.json();
2509
+ recordMemoryCall(action, start, auth.pubkey, "success");
2510
+ return body;
2511
+ } catch (err) {
2512
+ recordMemoryCall(action, start, auth.pubkey, "error", err);
2513
+ throw err;
2514
+ }
2311
2515
  }
2312
2516
  async function memorySubmit(action, auth, opts, signedField, signedHex) {
2313
- const resp = await transport(opts).post("/api/risk-engine", {
2314
- action,
2315
- agentPubkey: auth.pubkey,
2316
- [signedField]: signedHex
2317
- });
2318
- if (resp.status !== 200) throw await failure(action, resp);
2517
+ const start = performance.now();
2518
+ try {
2519
+ const resp = await transport(opts).post("/api/risk-engine", {
2520
+ action,
2521
+ agentPubkey: auth.pubkey,
2522
+ [signedField]: signedHex
2523
+ });
2524
+ if (resp.status !== 200) throw await failure(action, resp);
2525
+ recordMemoryCall(action, start, auth.pubkey, "success");
2526
+ } catch (err) {
2527
+ recordMemoryCall(action, start, auth.pubkey, "error", err);
2528
+ throw err;
2529
+ }
2319
2530
  }
2320
2531
 
2321
2532
  // src-ts/memory/chain.ts
@@ -2569,8 +2780,8 @@ function classifyMemoryWrite(event, ctx, opts = {}) {
2569
2780
  var import_node_fs4 = require("fs");
2570
2781
  var import_node_os4 = __toESM(require("os"));
2571
2782
  var import_node_path4 = __toESM(require("path"));
2572
- function emitDebugProbe(event, ctx, memEntry, logger) {
2573
- if (!logger?.info) return;
2783
+ function emitDebugProbe(event, ctx, memEntry, logger2) {
2784
+ if (!logger2?.info) return;
2574
2785
  const ev = event ?? {};
2575
2786
  const c = ctx ?? {};
2576
2787
  const argsCandidate = ev.params ?? c.params ?? ev.args ?? c.args ?? ev.arguments ?? c.arguments;
@@ -2580,7 +2791,7 @@ function emitDebugProbe(event, ctx, memEntry, logger) {
2580
2791
  const editsArr = Array.isArray(argsObj.edits) ? argsObj.edits : null;
2581
2792
  const firstEditKeys = editsArr && editsArr[0] && typeof editsArr[0] === "object" ? Object.keys(editsArr[0]).slice(0, 10) : null;
2582
2793
  const tool = ev.toolName ?? c.tool?.name ?? c.toolName ?? c.name ?? "(none)";
2583
- logger.info("[atbash][probe] before_tool_call", {
2794
+ logger2.info("[atbash][probe] before_tool_call", {
2584
2795
  toolName: tool,
2585
2796
  eventKeys: Object.keys(ev).slice(0, 20),
2586
2797
  ctxKeys: Object.keys(c).slice(0, 20),
@@ -2608,6 +2819,20 @@ async function resultingDocument(memEntry, event, ctx) {
2608
2819
  return native.applyMemoryEdits(current, edits);
2609
2820
  }
2610
2821
  async function guardMemoryWrite(input) {
2822
+ const start = performance.now();
2823
+ const result = await guardMemoryWriteInner(input);
2824
+ if (result.handled) {
2825
+ recordMemoryGuard(
2826
+ result.scanResult?.verdict ?? "unscanned",
2827
+ result.committed === true,
2828
+ result.decision?.block === true,
2829
+ start,
2830
+ input.auth.pubkey
2831
+ );
2832
+ }
2833
+ return result;
2834
+ }
2835
+ async function guardMemoryWriteInner(input) {
2611
2836
  const {
2612
2837
  event,
2613
2838
  ctx,
@@ -2621,7 +2846,7 @@ async function guardMemoryWrite(input) {
2621
2846
  toolNames,
2622
2847
  enforce = true,
2623
2848
  debug = false,
2624
- logger,
2849
+ logger: logger2,
2625
2850
  agentHomeDirs,
2626
2851
  memoryRoot
2627
2852
  } = input;
@@ -2630,7 +2855,7 @@ async function guardMemoryWrite(input) {
2630
2855
  memEntry = classifyMemoryWrite(event, ctx, { patterns, toolNames });
2631
2856
  } catch (err) {
2632
2857
  const why = err instanceof Error ? err.message : String(err);
2633
- logger?.warn?.("[atbash] memory classifier refused this tool call's input", { reason: why });
2858
+ logger2?.warn?.("[atbash] memory classifier refused this tool call's input", { reason: why });
2634
2859
  if (!enforce) return { handled: false };
2635
2860
  return {
2636
2861
  handled: true,
@@ -2641,14 +2866,14 @@ async function guardMemoryWrite(input) {
2641
2866
  }
2642
2867
  };
2643
2868
  }
2644
- if (debug) emitDebugProbe(event, ctx, memEntry, logger);
2869
+ if (debug) emitDebugProbe(event, ctx, memEntry, logger2);
2645
2870
  if (!memEntry) return { handled: false };
2646
2871
  let document;
2647
2872
  try {
2648
2873
  document = await resultingDocument(memEntry, event, ctx);
2649
2874
  } catch (err) {
2650
2875
  const reason = err instanceof Error ? err.message : String(err);
2651
- logger?.warn?.("[atbash] memory edit could not be applied", { path: memEntry.key, reason });
2876
+ logger2?.warn?.("[atbash] memory edit could not be applied", { path: memEntry.key, reason });
2652
2877
  return {
2653
2878
  handled: true,
2654
2879
  decision: enforce ? { allow: false, block: true, reason: `Memory edit refused: ${reason}` } : { allow: true, reason: `Memory edit refused: ${reason}` },
@@ -2666,7 +2891,7 @@ async function guardMemoryWrite(input) {
2666
2891
  });
2667
2892
  } catch (err) {
2668
2893
  const reason = err instanceof Error ? err.message : String(err);
2669
- logger?.warn?.("[atbash] memory scan failed", {
2894
+ logger2?.warn?.("[atbash] memory scan failed", {
2670
2895
  path: memEntry.key,
2671
2896
  reason
2672
2897
  });
@@ -2676,7 +2901,7 @@ async function guardMemoryWrite(input) {
2676
2901
  };
2677
2902
  }
2678
2903
  if (scanResult.verdict === "red") {
2679
- logger?.warn?.("[atbash] memory BLOCK", {
2904
+ logger2?.warn?.("[atbash] memory BLOCK", {
2680
2905
  path: memEntry.key,
2681
2906
  reason: scanResult.reason
2682
2907
  });
@@ -2698,7 +2923,7 @@ async function guardMemoryWrite(input) {
2698
2923
  }
2699
2924
  const filePath = memorySlotFor(memEntry.key, memoryRoot, agentHomeDirs);
2700
2925
  if (filePath === null) {
2701
- logger?.info?.(
2926
+ logger2?.info?.(
2702
2927
  `[atbash] memory scanned but not recorded \u2014 ${memEntry.key} is not inside an agent home (${(agentHomeDirs ?? native.defaultAgentHomeDirs()).join(", ")}) in your home directory, so it has no chain slot of its own.`,
2703
2928
  { path: memEntry.key, verdict: scanResult.verdict, score: scanResult.score }
2704
2929
  );
@@ -2720,7 +2945,7 @@ async function guardMemoryWrite(input) {
2720
2945
  });
2721
2946
  } catch (err) {
2722
2947
  const reason = err instanceof Error ? err.message : String(err);
2723
- logger?.warn?.("[atbash] memory commit to chain failed", {
2948
+ logger2?.warn?.("[atbash] memory commit to chain failed", {
2724
2949
  path: memEntry.key,
2725
2950
  filePath,
2726
2951
  reason
@@ -2732,7 +2957,7 @@ async function guardMemoryWrite(input) {
2732
2957
  committed: false
2733
2958
  };
2734
2959
  }
2735
- logger?.info?.(
2960
+ logger2?.info?.(
2736
2961
  scanResult.verdict === "yellow" ? "[atbash] memory ALLOW (yellow, low score)" : "[atbash] memory ALLOW",
2737
2962
  {
2738
2963
  path: memEntry.key,
@@ -3645,6 +3870,7 @@ function diffMemorySnapshots(before, after) {
3645
3870
  classifyMemoryWrite,
3646
3871
  columnAad,
3647
3872
  commitMemoryVersion,
3873
+ configureEvents,
3648
3874
  containsEvasionCharacters,
3649
3875
  containsSecret,
3650
3876
  createFileLogger,
@@ -3660,6 +3886,7 @@ function diffMemorySnapshots(before, after) {
3660
3886
  encryptForOrg,
3661
3887
  encryptMemoryContent,
3662
3888
  encryptedLength,
3889
+ eventsAreOn,
3663
3890
  flushTelemetry,
3664
3891
  generateKeypair,
3665
3892
  getActiveAgentMemory,
@@ -3688,6 +3915,7 @@ function diffMemorySnapshots(before, after) {
3688
3915
  pubkeyToHex,
3689
3916
  recordCall,
3690
3917
  recordDuration,
3918
+ recordEvent,
3691
3919
  redactJsonStrings,
3692
3920
  redactSecrets,
3693
3921
  resolve,