@atbash/sdk 0.19.1-dev.0 → 0.20.0-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 +1 -0
- package/dist/browser.d.mts +105 -18
- package/dist/browser.mjs +285 -123
- package/dist/index.d.mts +105 -18
- package/dist/index.d.ts +105 -18
- package/dist/index.js +264 -67
- package/dist/index.mjs +273 -81
- package/index.js +58 -54
- package/package.json +8 -6
package/dist/index.js
CHANGED
|
@@ -42,6 +42,7 @@ __export(src_ts_exports, {
|
|
|
42
42
|
EciesDomain: () => EciesDomain,
|
|
43
43
|
HttpClient: () => HttpClient,
|
|
44
44
|
HttpTransportError: () => HttpTransportError,
|
|
45
|
+
JudgeRefusalError: () => JudgeRefusalError,
|
|
45
46
|
KEY_FILENAMES: () => KEY_FILENAMES,
|
|
46
47
|
MemoryGuardManager: () => MemoryGuardManager,
|
|
47
48
|
MemoryIntegrityError: () => MemoryIntegrityError,
|
|
@@ -60,6 +61,7 @@ __export(src_ts_exports, {
|
|
|
60
61
|
columnAad: () => columnAad,
|
|
61
62
|
commitMemoryVersion: () => commitMemoryVersion,
|
|
62
63
|
configureEvents: () => configureEvents,
|
|
64
|
+
configureJailHold: () => configureJailHold,
|
|
63
65
|
containsEvasionCharacters: () => containsEvasionCharacters,
|
|
64
66
|
containsSecret: () => containsSecret,
|
|
65
67
|
createFileLogger: () => createFileLogger,
|
|
@@ -88,12 +90,14 @@ __export(src_ts_exports, {
|
|
|
88
90
|
getRollbackHistory: () => getRollbackHistory,
|
|
89
91
|
guardMemoryWrite: () => guardMemoryWrite,
|
|
90
92
|
isEnvelope: () => isEnvelope,
|
|
93
|
+
isJailHeld: () => isJailHeld,
|
|
91
94
|
isValidPrivateKey: () => isValidPrivateKey,
|
|
92
95
|
keyFingerprintOf: () => keyFingerprintOf,
|
|
93
96
|
keyPathCandidates: () => keyPathCandidates,
|
|
94
97
|
loadAgent: () => loadAgent,
|
|
95
98
|
loadAgentFromFile: () => loadAgentFromFile,
|
|
96
99
|
loadUserConfig: () => loadUserConfig,
|
|
100
|
+
memorySlotOf: () => memorySlotOf,
|
|
97
101
|
normalizeActionForHash: () => normalizeActionForHash,
|
|
98
102
|
normalizeActionType: () => normalizeActionType,
|
|
99
103
|
normalizeForMatching: () => normalizeForMatching,
|
|
@@ -107,6 +111,7 @@ __export(src_ts_exports, {
|
|
|
107
111
|
recordEvent: () => recordEvent,
|
|
108
112
|
redactJsonStrings: () => redactJsonStrings,
|
|
109
113
|
redactSecrets: () => redactSecrets,
|
|
114
|
+
releaseJailHolds: () => releaseJailHolds,
|
|
110
115
|
resolve: () => resolve,
|
|
111
116
|
resolveKeyPath: () => resolveKeyPath,
|
|
112
117
|
rollbackMemory: () => rollbackMemory,
|
|
@@ -293,12 +298,12 @@ var AtbashAPIError = class extends Error {
|
|
|
293
298
|
* ran does not carry. The path is the fact that settles it.
|
|
294
299
|
*/
|
|
295
300
|
path;
|
|
296
|
-
constructor(status, body, statusText = "", endpoint = DEFAULT_ENDPOINT,
|
|
297
|
-
super(enrich(status, body, statusText, endpoint,
|
|
301
|
+
constructor(status, body, statusText = "", endpoint = DEFAULT_ENDPOINT, path4) {
|
|
302
|
+
super(enrich(status, body, statusText, endpoint, path4));
|
|
298
303
|
this.name = "AtbashAPIError";
|
|
299
304
|
this.status = status;
|
|
300
305
|
this.body = body;
|
|
301
|
-
this.path =
|
|
306
|
+
this.path = path4;
|
|
302
307
|
}
|
|
303
308
|
};
|
|
304
309
|
var SignatureVerificationError = class extends Error {
|
|
@@ -307,9 +312,18 @@ var SignatureVerificationError = class extends Error {
|
|
|
307
312
|
this.name = "SignatureVerificationError";
|
|
308
313
|
}
|
|
309
314
|
};
|
|
310
|
-
|
|
315
|
+
var JudgeRefusalError = class extends Error {
|
|
316
|
+
constructor(message) {
|
|
317
|
+
super(message);
|
|
318
|
+
this.name = "JudgeRefusalError";
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
function isRefusalStatus(status) {
|
|
322
|
+
return status >= 400 && status < 500 && status !== 408;
|
|
323
|
+
}
|
|
324
|
+
function enrich(status, body, statusText, endpoint, path4) {
|
|
311
325
|
const dashboard = endpoint.replace(/\/+$/, "") || DEFAULT_ENDPOINT;
|
|
312
|
-
let msg = `API error ${status}${
|
|
326
|
+
let msg = `API error ${status}${path4 ? ` from ${path4}` : ""}: ${body || statusText}`;
|
|
313
327
|
const lowered = body.toLowerCase();
|
|
314
328
|
if (lowered.includes("agent not registered")) {
|
|
315
329
|
msg += `
|
|
@@ -330,6 +344,61 @@ function enrich(status, body, statusText, endpoint, path5) {
|
|
|
330
344
|
return msg;
|
|
331
345
|
}
|
|
332
346
|
|
|
347
|
+
// src-ts/jail-hold.ts
|
|
348
|
+
var DEFAULTS = { ceilingMs: 3e4, pollMs: 1e3 };
|
|
349
|
+
var holds = /* @__PURE__ */ new Map();
|
|
350
|
+
var blockedAt = /* @__PURE__ */ new Map();
|
|
351
|
+
var BLOCK_MEMORY_MS = 10 * 6e4;
|
|
352
|
+
var config = { ...DEFAULTS };
|
|
353
|
+
function isJailHeld(pubkey) {
|
|
354
|
+
return holds.has(pubkey);
|
|
355
|
+
}
|
|
356
|
+
function recordBlock(pubkey) {
|
|
357
|
+
const now = Date.now();
|
|
358
|
+
for (const [key, at] of blockedAt) if (now - at > BLOCK_MEMORY_MS) blockedAt.delete(key);
|
|
359
|
+
blockedAt.set(pubkey, now);
|
|
360
|
+
}
|
|
361
|
+
function blockedSince(pubkey, startedAt) {
|
|
362
|
+
const at = blockedAt.get(pubkey);
|
|
363
|
+
return at !== void 0 && startedAt <= at;
|
|
364
|
+
}
|
|
365
|
+
function holdJail(pubkey, confirmJailed, onGiveUp) {
|
|
366
|
+
if (holds.has(pubkey)) return;
|
|
367
|
+
const hold = { timer: null };
|
|
368
|
+
holds.set(pubkey, hold);
|
|
369
|
+
const deadline = Date.now() + config.ceilingMs;
|
|
370
|
+
const tick = async () => {
|
|
371
|
+
if (holds.get(pubkey) !== hold) return;
|
|
372
|
+
let jailed = false;
|
|
373
|
+
try {
|
|
374
|
+
jailed = await confirmJailed();
|
|
375
|
+
} catch {
|
|
376
|
+
}
|
|
377
|
+
if (holds.get(pubkey) !== hold) return;
|
|
378
|
+
if (jailed) {
|
|
379
|
+
holds.delete(pubkey);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (Date.now() >= deadline) {
|
|
383
|
+
holds.delete(pubkey);
|
|
384
|
+
onGiveUp?.();
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const timer = setTimeout(() => void tick(), config.pollMs);
|
|
388
|
+
timer.unref?.();
|
|
389
|
+
hold.timer = timer;
|
|
390
|
+
};
|
|
391
|
+
void tick();
|
|
392
|
+
}
|
|
393
|
+
function releaseJailHolds() {
|
|
394
|
+
for (const hold of holds.values()) if (hold.timer) clearTimeout(hold.timer);
|
|
395
|
+
holds.clear();
|
|
396
|
+
blockedAt.clear();
|
|
397
|
+
}
|
|
398
|
+
function configureJailHold(next) {
|
|
399
|
+
config = { ...DEFAULTS, ...next };
|
|
400
|
+
}
|
|
401
|
+
|
|
333
402
|
// src-ts/http/client.ts
|
|
334
403
|
var HttpClient = class {
|
|
335
404
|
baseUrl;
|
|
@@ -338,8 +407,8 @@ var HttpClient = class {
|
|
|
338
407
|
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
339
408
|
this.timeoutMs = timeoutMs;
|
|
340
409
|
}
|
|
341
|
-
buildUrl(
|
|
342
|
-
const url = new URL(this.baseUrl +
|
|
410
|
+
buildUrl(path4, query) {
|
|
411
|
+
const url = new URL(this.baseUrl + path4);
|
|
343
412
|
if (query) {
|
|
344
413
|
for (const [k, v] of Object.entries(query)) {
|
|
345
414
|
if (v !== void 0 && v !== null && v !== "") {
|
|
@@ -349,14 +418,14 @@ var HttpClient = class {
|
|
|
349
418
|
}
|
|
350
419
|
return url.toString();
|
|
351
420
|
}
|
|
352
|
-
async get(
|
|
353
|
-
return this.fetch(this.buildUrl(
|
|
421
|
+
async get(path4, query, headers) {
|
|
422
|
+
return this.fetch(this.buildUrl(path4, query), {
|
|
354
423
|
method: "GET",
|
|
355
424
|
...headers && { headers }
|
|
356
425
|
});
|
|
357
426
|
}
|
|
358
|
-
async post(
|
|
359
|
-
return this.fetch(this.buildUrl(
|
|
427
|
+
async post(path4, body, headers) {
|
|
428
|
+
return this.fetch(this.buildUrl(path4), {
|
|
360
429
|
method: "POST",
|
|
361
430
|
headers: { "Content-Type": "application/json", ...headers },
|
|
362
431
|
body: JSON.stringify(body)
|
|
@@ -622,11 +691,12 @@ function eventsOptedOut() {
|
|
|
622
691
|
const flag = process.env.ATBASH_TELEMETRY_EVENTS_DISABLED?.trim().toLowerCase();
|
|
623
692
|
return Boolean(flag && ["1", "true", "yes", "on"].includes(flag));
|
|
624
693
|
}
|
|
694
|
+
var SOURCE_SHAPE = /^(?:sdk|cli|plugin:[a-z0-9][a-z0-9._-]{0,31})$/;
|
|
625
695
|
function configureEvents(next, opts = {}) {
|
|
626
696
|
logger = next;
|
|
627
697
|
eventsEnabled = next !== null && !eventsOptedOut();
|
|
628
698
|
if (opts.version) sdkVersion = opts.version;
|
|
629
|
-
if (opts.source) eventSource = opts.source;
|
|
699
|
+
if (opts.source && SOURCE_SHAPE.test(opts.source)) eventSource = opts.source;
|
|
630
700
|
hashAgentKey = opts.hashAgentKey ?? null;
|
|
631
701
|
}
|
|
632
702
|
function eventsAreOn() {
|
|
@@ -673,6 +743,20 @@ var import_sdk_metrics = require("@opentelemetry/sdk-metrics");
|
|
|
673
743
|
var import_exporter_logs_otlp_http = require("@opentelemetry/exporter-logs-otlp-http");
|
|
674
744
|
var import_sdk_logs = require("@opentelemetry/sdk-logs");
|
|
675
745
|
var import_node_crypto = require("crypto");
|
|
746
|
+
var SDK_VERSION = true ? "0.20.0-dev.0" : "";
|
|
747
|
+
var SDK_LANGUAGE = "node";
|
|
748
|
+
function sdkResourceAttributes() {
|
|
749
|
+
const attrs = {
|
|
750
|
+
"service.name": "atbash-sdk",
|
|
751
|
+
"sdk.language": SDK_LANGUAGE
|
|
752
|
+
};
|
|
753
|
+
if (SDK_VERSION) attrs["sdk.version"] = SDK_VERSION;
|
|
754
|
+
return attrs;
|
|
755
|
+
}
|
|
756
|
+
function sourceOrDefault(candidate) {
|
|
757
|
+
return typeof candidate === "string" && SOURCE_SHAPE.test(candidate) ? candidate : defaultSource;
|
|
758
|
+
}
|
|
759
|
+
var exitHookInstalled = false;
|
|
676
760
|
var meterProvider = null;
|
|
677
761
|
var loggerProvider = null;
|
|
678
762
|
var callCounter = null;
|
|
@@ -690,8 +774,8 @@ function isTelemetryOptedOut() {
|
|
|
690
774
|
const filePath = (0, import_node_path2.join)(home2, ".config", "atbash", "telemetry.json");
|
|
691
775
|
const raw = (0, import_node_fs2.readFileSync)(filePath, "utf-8").trim();
|
|
692
776
|
if (!raw) return false;
|
|
693
|
-
const
|
|
694
|
-
return
|
|
777
|
+
const config2 = JSON.parse(raw);
|
|
778
|
+
return config2.enabled === false;
|
|
695
779
|
} catch {
|
|
696
780
|
return false;
|
|
697
781
|
}
|
|
@@ -736,26 +820,26 @@ function customCollector() {
|
|
|
736
820
|
}
|
|
737
821
|
return { url: url.replace(/\/+$/, ""), headers: () => headers };
|
|
738
822
|
}
|
|
739
|
-
function setupTelemetry(
|
|
740
|
-
if (!
|
|
823
|
+
function setupTelemetry(config2) {
|
|
824
|
+
if (!config2.enabled) return;
|
|
741
825
|
if (isTelemetryOptedOut()) return;
|
|
742
|
-
if (
|
|
743
|
-
defaultSource =
|
|
826
|
+
if (config2.source && !sourceExplicitlySet && SOURCE_SHAPE.test(config2.source)) {
|
|
827
|
+
defaultSource = config2.source;
|
|
744
828
|
sourceExplicitlySet = true;
|
|
745
829
|
}
|
|
746
|
-
if (
|
|
747
|
-
requestedExportIntervalMs =
|
|
830
|
+
if (config2.exportIntervalMs !== void 0 && requestedExportIntervalMs === void 0) {
|
|
831
|
+
requestedExportIntervalMs = config2.exportIntervalMs;
|
|
748
832
|
}
|
|
749
833
|
if (meterProvider) return;
|
|
750
834
|
const collector = customCollector();
|
|
751
|
-
if (!collector && (!
|
|
835
|
+
if (!collector && (!config2.endpoint || !config2.getAuthHeaders)) return;
|
|
752
836
|
if (!collector && /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)(:|\/|$)/i.test(
|
|
753
|
-
|
|
837
|
+
config2.endpoint
|
|
754
838
|
))
|
|
755
839
|
return;
|
|
756
840
|
const destination = collector ?? {
|
|
757
|
-
url: `${
|
|
758
|
-
headers: () =>
|
|
841
|
+
url: `${config2.endpoint.replace(/\/+$/, "")}/api/telemetry`,
|
|
842
|
+
headers: () => config2.getAuthHeaders()
|
|
759
843
|
};
|
|
760
844
|
const proxyUrl = destination.url;
|
|
761
845
|
const exporter = new import_exporter_metrics_otlp_http.OTLPMetricExporter({
|
|
@@ -779,9 +863,7 @@ function setupTelemetry(config) {
|
|
|
779
863
|
exportIntervalMillis: requestedExportIntervalMs ?? 6e4
|
|
780
864
|
});
|
|
781
865
|
meterProvider = new import_sdk_metrics.MeterProvider({
|
|
782
|
-
resource: (0, import_resources.resourceFromAttributes)(
|
|
783
|
-
"service.name": "atbash-sdk"
|
|
784
|
-
}),
|
|
866
|
+
resource: (0, import_resources.resourceFromAttributes)(sdkResourceAttributes()),
|
|
785
867
|
readers: [reader]
|
|
786
868
|
});
|
|
787
869
|
const meter = meterProvider.getMeter("atbash-sdk");
|
|
@@ -792,7 +874,7 @@ function setupTelemetry(config) {
|
|
|
792
874
|
description: "SDK function execution duration",
|
|
793
875
|
unit: "ms"
|
|
794
876
|
});
|
|
795
|
-
counterHasher =
|
|
877
|
+
counterHasher = config2.hashAgentKey ?? defaultAgentKeyHasher;
|
|
796
878
|
const logExporter = new import_exporter_logs_otlp_http.OTLPLogExporter({
|
|
797
879
|
// A real collector takes OTLP at its own signal paths; the Atbash proxy
|
|
798
880
|
// takes a query parameter, because it is one authenticated route.
|
|
@@ -800,12 +882,20 @@ function setupTelemetry(config) {
|
|
|
800
882
|
headers: async () => destination.headers()
|
|
801
883
|
});
|
|
802
884
|
loggerProvider = new import_sdk_logs.LoggerProvider({
|
|
803
|
-
resource: (0, import_resources.resourceFromAttributes)(
|
|
885
|
+
resource: (0, import_resources.resourceFromAttributes)(sdkResourceAttributes()),
|
|
804
886
|
processors: [new import_sdk_logs.BatchLogRecordProcessor({ exporter: logExporter })]
|
|
805
887
|
});
|
|
888
|
+
if (!exitHookInstalled && typeof process?.once === "function") {
|
|
889
|
+
exitHookInstalled = true;
|
|
890
|
+
process.once("beforeExit", () => {
|
|
891
|
+
shutdownTelemetry().catch(() => {
|
|
892
|
+
});
|
|
893
|
+
});
|
|
894
|
+
}
|
|
806
895
|
configureEvents(loggerProvider.getLogger("atbash-sdk"), {
|
|
807
|
-
source:
|
|
808
|
-
hashAgentKey:
|
|
896
|
+
source: sourceOrDefault(config2.source),
|
|
897
|
+
hashAgentKey: config2.hashAgentKey ?? defaultAgentKeyHasher,
|
|
898
|
+
version: SDK_VERSION
|
|
809
899
|
});
|
|
810
900
|
}
|
|
811
901
|
function recordCall(functionName, source, agentPubkey) {
|
|
@@ -813,7 +903,7 @@ function recordCall(functionName, source, agentPubkey) {
|
|
|
813
903
|
if (!callCounter) return;
|
|
814
904
|
callCounter.add(1, {
|
|
815
905
|
"function.name": functionName,
|
|
816
|
-
source: source
|
|
906
|
+
source: sourceOrDefault(source),
|
|
817
907
|
// Hashed on this path too. The counters and the events export to the same
|
|
818
908
|
// place in the same request, so a raw key here would make hashing it on
|
|
819
909
|
// the event beside it pointless.
|
|
@@ -825,7 +915,7 @@ function recordDuration(functionName, durationMs, status, source) {
|
|
|
825
915
|
durationHistogram.record(durationMs, {
|
|
826
916
|
"function.name": functionName,
|
|
827
917
|
status,
|
|
828
|
-
source: source
|
|
918
|
+
source: sourceOrDefault(source)
|
|
829
919
|
});
|
|
830
920
|
}
|
|
831
921
|
async function flushTelemetry() {
|
|
@@ -876,13 +966,13 @@ function loadUserConfig() {
|
|
|
876
966
|
return {};
|
|
877
967
|
}
|
|
878
968
|
}
|
|
879
|
-
function saveUserConfig(
|
|
969
|
+
function saveUserConfig(config2) {
|
|
880
970
|
const dir = getConfigDir();
|
|
881
971
|
if (!(0, import_node_fs3.existsSync)(dir)) {
|
|
882
972
|
(0, import_node_fs3.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
883
973
|
}
|
|
884
974
|
const filePath = getConfigPath();
|
|
885
|
-
(0, import_node_fs3.writeFileSync)(filePath, JSON.stringify(
|
|
975
|
+
(0, import_node_fs3.writeFileSync)(filePath, JSON.stringify(config2, null, 2) + "\n", {
|
|
886
976
|
mode: 384
|
|
887
977
|
});
|
|
888
978
|
(0, import_node_fs3.chmodSync)(filePath, 384);
|
|
@@ -917,6 +1007,7 @@ function forcedChainNetwork(flagValue) {
|
|
|
917
1007
|
}
|
|
918
1008
|
|
|
919
1009
|
// src-ts/client.ts
|
|
1010
|
+
var AGENT_NOT_REGISTERED = "Agent not registered. Onboard the agent at the dashboard before submitting actions.";
|
|
920
1011
|
function generateToolCallId() {
|
|
921
1012
|
return `tc-${Date.now()}-${randomHex(4)}`;
|
|
922
1013
|
}
|
|
@@ -1265,6 +1356,15 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1265
1356
|
* return the signed tx hex. The server broadcasts to chain.
|
|
1266
1357
|
*/
|
|
1267
1358
|
async logToolCall(action, context = "", options = {}) {
|
|
1359
|
+
return this._logToolCall(action, context, options, false);
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* `raisePreflightErrors` (the judge path, like Python's
|
|
1363
|
+
* `raise_preflight_errors`): a failed registration check throws instead of
|
|
1364
|
+
* becoming `{ success: false }`, so the caller can tell Atbash's "no"
|
|
1365
|
+
* (not registered, or a 4xx) from an outage.
|
|
1366
|
+
*/
|
|
1367
|
+
async _logToolCall(action, context, options, raisePreflightErrors) {
|
|
1268
1368
|
const start = performance.now();
|
|
1269
1369
|
recordCall("logToolCall", void 0, this.auth.pubkey);
|
|
1270
1370
|
let exists;
|
|
@@ -1274,18 +1374,20 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1274
1374
|
});
|
|
1275
1375
|
} catch (err) {
|
|
1276
1376
|
recordDuration("logToolCall", performance.now() - start, "error");
|
|
1377
|
+
if (raisePreflightErrors) throw err;
|
|
1277
1378
|
return { success: false, toolCallId: null, error: errorMessage(err) };
|
|
1278
1379
|
}
|
|
1279
1380
|
if (!exists) {
|
|
1280
1381
|
recordDuration("logToolCall", performance.now() - start, "error");
|
|
1382
|
+
if (raisePreflightErrors) throw new JudgeRefusalError(AGENT_NOT_REGISTERED);
|
|
1281
1383
|
return {
|
|
1282
1384
|
success: false,
|
|
1283
1385
|
toolCallId: null,
|
|
1284
|
-
error:
|
|
1386
|
+
error: AGENT_NOT_REGISTERED
|
|
1285
1387
|
};
|
|
1286
1388
|
}
|
|
1287
1389
|
const toolCallId = generateToolCallId();
|
|
1288
|
-
const brid =
|
|
1390
|
+
const brid = await this.callBrid(options.chainOpts);
|
|
1289
1391
|
const orgKey = options.orgEncryptionPubKey ?? this.orgEncryptionPubKey ?? this._orgKeyFromChain;
|
|
1290
1392
|
try {
|
|
1291
1393
|
const signedHex = orgKey ? signEncryptedToolCall(
|
|
@@ -1335,6 +1437,8 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1335
1437
|
if (!action?.trim()) {
|
|
1336
1438
|
throw new Error("action is required and cannot be empty.");
|
|
1337
1439
|
}
|
|
1440
|
+
if (isJailHeld(this.auth.pubkey)) return this.localJailBlock();
|
|
1441
|
+
const startedAt = Date.now();
|
|
1338
1442
|
let chainOpts = options.chainOpts;
|
|
1339
1443
|
if (options.orgName && this._explicitChain) {
|
|
1340
1444
|
chainOpts = options.chainOpts ?? { network: this._defaultChain.network };
|
|
@@ -1359,12 +1463,13 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1359
1463
|
}
|
|
1360
1464
|
}
|
|
1361
1465
|
}
|
|
1362
|
-
const brid = this.
|
|
1363
|
-
const logResult = await this.
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
chainOpts
|
|
1367
|
-
|
|
1466
|
+
const brid = await this.callBrid(chainOpts);
|
|
1467
|
+
const logResult = await this._logToolCall(
|
|
1468
|
+
action,
|
|
1469
|
+
context,
|
|
1470
|
+
{ toolName: options.toolName, toolArgsJson: options.toolArgsJson, chainOpts },
|
|
1471
|
+
true
|
|
1472
|
+
);
|
|
1368
1473
|
if (!logResult.success || !logResult.toolCallId || !logResult.signedHex) {
|
|
1369
1474
|
throw new Error(logResult.error || "Failed to sign log_tool_call");
|
|
1370
1475
|
}
|
|
@@ -1401,10 +1506,16 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1401
1506
|
if (verifyPubKey !== void 0) {
|
|
1402
1507
|
verifyResponseSignature(bodyBytes, resp, verifyPubKey);
|
|
1403
1508
|
}
|
|
1404
|
-
const
|
|
1509
|
+
const parsed = tryParseJson(bodyBytes.toString("utf-8"));
|
|
1510
|
+
if (!isRecord2(parsed)) {
|
|
1511
|
+
throw new JudgeRefusalError(
|
|
1512
|
+
"The judge's reply could not be read, so the action was not approved."
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
const data = parsed;
|
|
1405
1516
|
const rawScore = data.score;
|
|
1406
1517
|
const score = typeof rawScore === "number" && Number.isInteger(rawScore) && rawScore >= 1 && rawScore <= 10 ? rawScore : void 0;
|
|
1407
|
-
|
|
1518
|
+
const result = {
|
|
1408
1519
|
verdict: normalizeVerdict(data.verdict),
|
|
1409
1520
|
allow: this.executable(data),
|
|
1410
1521
|
actionType: normalizeActionType(data.action_type),
|
|
@@ -1420,6 +1531,45 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1420
1531
|
score,
|
|
1421
1532
|
refusal: explicitRefusal(data)
|
|
1422
1533
|
};
|
|
1534
|
+
return this.applyJailHold(result, startedAt);
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Start a local hold on an enforced, on-chain BLOCK (the verdict that jails).
|
|
1538
|
+
* Refuse any other result that lands while the agent is held, or whose
|
|
1539
|
+
* request was already in flight when the block arrived: the server judged it
|
|
1540
|
+
* on pre-jail state.
|
|
1541
|
+
*/
|
|
1542
|
+
applyJailHold(result, startedAt) {
|
|
1543
|
+
const pubkey = this.auth.pubkey;
|
|
1544
|
+
if (result.verdict === "BLOCK" && result.enforced && result.onChain) {
|
|
1545
|
+
recordBlock(pubkey);
|
|
1546
|
+
holdJail(
|
|
1547
|
+
pubkey,
|
|
1548
|
+
() => this.getAgentPolicy(pubkey).then((p) => p.isJailed),
|
|
1549
|
+
() => this.logger.warn?.("[atbash] local jail hold released: the chain did not confirm the jail in time")
|
|
1550
|
+
);
|
|
1551
|
+
return result;
|
|
1552
|
+
}
|
|
1553
|
+
return isJailHeld(pubkey) || blockedSince(pubkey, startedAt) ? this.localJailBlock(result.toolCallId) : result;
|
|
1554
|
+
}
|
|
1555
|
+
/** The server's jailed refusal, produced locally while the agent is held. */
|
|
1556
|
+
localJailBlock(toolCallId = generateToolCallId()) {
|
|
1557
|
+
return {
|
|
1558
|
+
verdict: "BLOCK",
|
|
1559
|
+
allow: false,
|
|
1560
|
+
actionType: "block",
|
|
1561
|
+
reason: "Agent is jailed. All actions are blocked until the organization owner unjails this agent.",
|
|
1562
|
+
confidence: 1,
|
|
1563
|
+
provider: "policy",
|
|
1564
|
+
latencyMs: 0,
|
|
1565
|
+
toolCallId,
|
|
1566
|
+
onChain: false,
|
|
1567
|
+
enforced: true,
|
|
1568
|
+
enforcementMode: "enforce",
|
|
1569
|
+
status: "",
|
|
1570
|
+
score: void 0,
|
|
1571
|
+
refusal: "BLOCK"
|
|
1572
|
+
};
|
|
1423
1573
|
}
|
|
1424
1574
|
/* ── audit_tool_call (redact → judge → decision) ───────────────────────── */
|
|
1425
1575
|
/**
|
|
@@ -1471,6 +1621,9 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1471
1621
|
reason: `The judge's reply failed its signature check (${errorMessage(err)}), so it was not trusted.`
|
|
1472
1622
|
};
|
|
1473
1623
|
}
|
|
1624
|
+
if (err instanceof JudgeRefusalError || err instanceof AtbashAPIError && isRefusalStatus(err.status)) {
|
|
1625
|
+
return { ...this.failJudge(errorMessage(err), err), allow: false };
|
|
1626
|
+
}
|
|
1474
1627
|
return this.failJudge(errorMessage(err), err);
|
|
1475
1628
|
}
|
|
1476
1629
|
}
|
|
@@ -1500,6 +1653,15 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1500
1653
|
* never overrides a refusal.
|
|
1501
1654
|
*/
|
|
1502
1655
|
decide(result) {
|
|
1656
|
+
const inner = this.decideInner(result);
|
|
1657
|
+
if (!result.enforcementMode) return inner;
|
|
1658
|
+
return {
|
|
1659
|
+
...inner,
|
|
1660
|
+
enforced: result.enforced,
|
|
1661
|
+
enforcementMode: result.enforcementMode
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
decideInner(result) {
|
|
1503
1665
|
const toolCallId = result.toolCallId;
|
|
1504
1666
|
const deny = (verdict, reason) => ({
|
|
1505
1667
|
allow: false,
|
|
@@ -1613,17 +1775,17 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1613
1775
|
/**
|
|
1614
1776
|
* Return the current status of a previously submitted judgment.
|
|
1615
1777
|
*
|
|
1616
|
-
* `chainOpts` names which chain the judgment was signed against
|
|
1617
|
-
*
|
|
1618
|
-
*
|
|
1619
|
-
*
|
|
1620
|
-
*
|
|
1621
|
-
*
|
|
1778
|
+
* `chainOpts` names which chain the judgment was signed against; the SDK
|
|
1779
|
+
* sends it as the `brid` query param the server routes by. Without it the
|
|
1780
|
+
* read uses the same chain `judgeAction` signs for (see `callBrid`): the
|
|
1781
|
+
* client's org chain when it has an `orgName`, else the client default.
|
|
1782
|
+
* A judgment signed with an explicit per-call `chainOpts` (or a per-call
|
|
1783
|
+
* `orgName` on another chain) must be polled with that same `chainOpts`.
|
|
1622
1784
|
*/
|
|
1623
1785
|
async getJudgmentStatus(judgmentId, agentPubkey, chainOpts) {
|
|
1624
1786
|
const pk = agentPubkey ?? this.auth.pubkey;
|
|
1625
|
-
const brid = this.bridFromChainOpts(chainOpts);
|
|
1626
1787
|
return this.track("getJudgmentStatus", pk, async () => {
|
|
1788
|
+
const brid = await this.callBrid(chainOpts);
|
|
1627
1789
|
const resp = await this.http.get(
|
|
1628
1790
|
"/api/v1/judge",
|
|
1629
1791
|
{ tool_call_id: judgmentId, agent_pubkey: pk, brid },
|
|
@@ -1961,6 +2123,18 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1961
2123
|
if (chainOpts?.network === "public") return PUBLIC_CHAIN.blockchainRid;
|
|
1962
2124
|
return this.blockchainRid;
|
|
1963
2125
|
}
|
|
2126
|
+
/**
|
|
2127
|
+
* The chain one call's transactions are signed for: the per-call override
|
|
2128
|
+
* when given, otherwise the client's org, otherwise the client default.
|
|
2129
|
+
* log_tool_call, judge_action and the judgment-status read all use this, so
|
|
2130
|
+
* a request never pairs transactions signed for two different chains.
|
|
2131
|
+
*/
|
|
2132
|
+
async callBrid(chainOpts) {
|
|
2133
|
+
if (chainOpts?.blockchainRid || chainOpts?.network) {
|
|
2134
|
+
return this.bridFromChainOpts(chainOpts);
|
|
2135
|
+
}
|
|
2136
|
+
return await this.defaultOrgBrid() ?? this.blockchainRid;
|
|
2137
|
+
}
|
|
1964
2138
|
/**
|
|
1965
2139
|
* Resolve the dashboard chain used by agent metadata/policy reads.
|
|
1966
2140
|
* Explicit per-call network overrides win; otherwise use the supplied org
|
|
@@ -2006,8 +2180,8 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
2006
2180
|
async riskEnginePost(body, network, brid) {
|
|
2007
2181
|
let resp;
|
|
2008
2182
|
try {
|
|
2009
|
-
const
|
|
2010
|
-
resp = await this.http.post(
|
|
2183
|
+
const path4 = network ? `/api/risk-engine?network=${encodeURIComponent(network)}` : "/api/risk-engine";
|
|
2184
|
+
resp = await this.http.post(path4, body, this.authHeaders(brid));
|
|
2011
2185
|
} catch (err) {
|
|
2012
2186
|
throw this.transportError(err);
|
|
2013
2187
|
}
|
|
@@ -2268,10 +2442,6 @@ function tryParseJson(text) {
|
|
|
2268
2442
|
return void 0;
|
|
2269
2443
|
}
|
|
2270
2444
|
}
|
|
2271
|
-
function parseJson(bytes) {
|
|
2272
|
-
const parsed = tryParseJson(bytes.toString("utf-8"));
|
|
2273
|
-
return parsed === void 0 ? {} : parsed;
|
|
2274
|
-
}
|
|
2275
2445
|
function requestPath(url) {
|
|
2276
2446
|
if (!url) return void 0;
|
|
2277
2447
|
try {
|
|
@@ -2550,10 +2720,10 @@ function resolveBlockchainRid(chainOpts) {
|
|
|
2550
2720
|
'chainOpts.nodeUrls no longer selects a chain on its own \u2014 memory reaches the chain through the Atbash service, not a node. Set `blockchainRid`, or `network: "public" | "private"`.'
|
|
2551
2721
|
);
|
|
2552
2722
|
}
|
|
2553
|
-
const
|
|
2723
|
+
const config2 = chainForNetwork(
|
|
2554
2724
|
chainOpts?.network ?? forcedChainNetwork() ?? "private"
|
|
2555
2725
|
);
|
|
2556
|
-
return
|
|
2726
|
+
return config2.blockchainRid;
|
|
2557
2727
|
}
|
|
2558
2728
|
async function commitMemoryVersion(plaintext, auth, opts) {
|
|
2559
2729
|
const key = await deriveMemoryKey(auth.privkey);
|
|
@@ -2779,7 +2949,34 @@ function classifyMemoryWrite(event, ctx, opts = {}) {
|
|
|
2779
2949
|
// src-ts/memory/guard.ts
|
|
2780
2950
|
var import_node_fs4 = require("fs");
|
|
2781
2951
|
var import_node_os4 = __toESM(require("os"));
|
|
2952
|
+
|
|
2953
|
+
// src-ts/memory/slot.ts
|
|
2782
2954
|
var import_node_path4 = __toESM(require("path"));
|
|
2955
|
+
function asciiLower(s) {
|
|
2956
|
+
return s.replace(/[A-Z]/g, (c) => String.fromCharCode(c.charCodeAt(0) | 32));
|
|
2957
|
+
}
|
|
2958
|
+
function spelledAs(target, base, p = import_node_path4.default) {
|
|
2959
|
+
const resolved = p.resolve(base);
|
|
2960
|
+
if (p.sep !== "\\") return resolved;
|
|
2961
|
+
const prefix = resolved.replace(/[\\/]+$/, "");
|
|
2962
|
+
const t = asciiLower(target);
|
|
2963
|
+
const b = asciiLower(prefix);
|
|
2964
|
+
if (t === b || t.startsWith(b + "\\") || t.startsWith(b + "/")) {
|
|
2965
|
+
return target.slice(0, prefix.length);
|
|
2966
|
+
}
|
|
2967
|
+
return resolved;
|
|
2968
|
+
}
|
|
2969
|
+
function memorySlotOf(target, home2, root, extraHomes, p = import_node_path4.default) {
|
|
2970
|
+
const resolved = p.resolve(target);
|
|
2971
|
+
return native.memorySlotForPath(
|
|
2972
|
+
resolved,
|
|
2973
|
+
home2 ? spelledAs(resolved, home2, p) : "",
|
|
2974
|
+
root ? spelledAs(resolved, root, p) : void 0,
|
|
2975
|
+
extraHomes
|
|
2976
|
+
);
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
// src-ts/memory/guard.ts
|
|
2783
2980
|
function emitDebugProbe(event, ctx, memEntry, logger2) {
|
|
2784
2981
|
if (!logger2?.info) return;
|
|
2785
2982
|
const ev = event ?? {};
|
|
@@ -2805,7 +3002,7 @@ function emitDebugProbe(event, ctx, memEntry, logger2) {
|
|
|
2805
3002
|
}
|
|
2806
3003
|
function memorySlotFor(key, root, extraHomes) {
|
|
2807
3004
|
const home2 = process.env.HOME || import_node_os4.default.homedir() || "";
|
|
2808
|
-
return
|
|
3005
|
+
return memorySlotOf(key, home2, root, extraHomes);
|
|
2809
3006
|
}
|
|
2810
3007
|
async function resultingDocument(memEntry, event, ctx) {
|
|
2811
3008
|
const edits = native.extractMemoryEdits(event, ctx);
|
|
@@ -3558,12 +3755,7 @@ var MemoryGuardManager = class {
|
|
|
3558
3755
|
*/
|
|
3559
3756
|
slotFor(target) {
|
|
3560
3757
|
const home2 = process.env.HOME || import_node_os5.default.homedir() || "";
|
|
3561
|
-
return
|
|
3562
|
-
import_node_path7.default.resolve(target),
|
|
3563
|
-
home2,
|
|
3564
|
-
this.opts.memoryRoot,
|
|
3565
|
-
this.opts.agentHomeDirs
|
|
3566
|
-
);
|
|
3758
|
+
return memorySlotOf(target, home2, this.opts.memoryRoot, this.opts.agentHomeDirs);
|
|
3567
3759
|
}
|
|
3568
3760
|
/**
|
|
3569
3761
|
* Verify every memory file this call reads, each against its own chain slot.
|
|
@@ -3853,6 +4045,7 @@ function diffMemorySnapshots(before, after) {
|
|
|
3853
4045
|
EciesDomain,
|
|
3854
4046
|
HttpClient,
|
|
3855
4047
|
HttpTransportError,
|
|
4048
|
+
JudgeRefusalError,
|
|
3856
4049
|
KEY_FILENAMES,
|
|
3857
4050
|
MemoryGuardManager,
|
|
3858
4051
|
MemoryIntegrityError,
|
|
@@ -3871,6 +4064,7 @@ function diffMemorySnapshots(before, after) {
|
|
|
3871
4064
|
columnAad,
|
|
3872
4065
|
commitMemoryVersion,
|
|
3873
4066
|
configureEvents,
|
|
4067
|
+
configureJailHold,
|
|
3874
4068
|
containsEvasionCharacters,
|
|
3875
4069
|
containsSecret,
|
|
3876
4070
|
createFileLogger,
|
|
@@ -3899,12 +4093,14 @@ function diffMemorySnapshots(before, after) {
|
|
|
3899
4093
|
getRollbackHistory,
|
|
3900
4094
|
guardMemoryWrite,
|
|
3901
4095
|
isEnvelope,
|
|
4096
|
+
isJailHeld,
|
|
3902
4097
|
isValidPrivateKey,
|
|
3903
4098
|
keyFingerprintOf,
|
|
3904
4099
|
keyPathCandidates,
|
|
3905
4100
|
loadAgent,
|
|
3906
4101
|
loadAgentFromFile,
|
|
3907
4102
|
loadUserConfig,
|
|
4103
|
+
memorySlotOf,
|
|
3908
4104
|
normalizeActionForHash,
|
|
3909
4105
|
normalizeActionType,
|
|
3910
4106
|
normalizeForMatching,
|
|
@@ -3918,6 +4114,7 @@ function diffMemorySnapshots(before, after) {
|
|
|
3918
4114
|
recordEvent,
|
|
3919
4115
|
redactJsonStrings,
|
|
3920
4116
|
redactSecrets,
|
|
4117
|
+
releaseJailHolds,
|
|
3921
4118
|
resolve,
|
|
3922
4119
|
resolveKeyPath,
|
|
3923
4120
|
rollbackMemory,
|