@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/README.md +75 -12
- package/dist/browser.d.mts +145 -1
- package/dist/browser.mjs +131 -36
- package/dist/index.d.mts +145 -1
- package/dist/index.d.ts +145 -1
- package/dist/index.js +279 -51
- package/dist/index.mjs +276 -51
- package/index.js +54 -54
- package/package.json +9 -6
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 =
|
|
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 =
|
|
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
|
-
|
|
646
|
-
if (
|
|
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
|
|
651
|
-
|
|
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 () =>
|
|
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
|
-
|
|
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
|
-
|
|
706
|
-
await meterProvider.forceFlush();
|
|
832
|
+
await Promise.all([meterProvider?.forceFlush(), loggerProvider?.forceFlush()]);
|
|
707
833
|
}
|
|
708
834
|
async function shutdownTelemetry() {
|
|
709
|
-
|
|
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(
|
|
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
|
-
|
|
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:
|
|
1277
|
-
confidence:
|
|
1278
|
-
provider:
|
|
1279
|
-
latencyMs:
|
|
1280
|
-
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:
|
|
1284
|
-
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:
|
|
1509
|
-
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
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
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
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,
|
|
2573
|
-
if (!
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|