@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.mjs
CHANGED
|
@@ -166,12 +166,12 @@ var AtbashAPIError = class extends Error {
|
|
|
166
166
|
* ran does not carry. The path is the fact that settles it.
|
|
167
167
|
*/
|
|
168
168
|
path;
|
|
169
|
-
constructor(status, body, statusText = "", endpoint = DEFAULT_ENDPOINT,
|
|
170
|
-
super(enrich(status, body, statusText, endpoint,
|
|
169
|
+
constructor(status, body, statusText = "", endpoint = DEFAULT_ENDPOINT, path4) {
|
|
170
|
+
super(enrich(status, body, statusText, endpoint, path4));
|
|
171
171
|
this.name = "AtbashAPIError";
|
|
172
172
|
this.status = status;
|
|
173
173
|
this.body = body;
|
|
174
|
-
this.path =
|
|
174
|
+
this.path = path4;
|
|
175
175
|
}
|
|
176
176
|
};
|
|
177
177
|
var SignatureVerificationError = class extends Error {
|
|
@@ -180,9 +180,18 @@ var SignatureVerificationError = class extends Error {
|
|
|
180
180
|
this.name = "SignatureVerificationError";
|
|
181
181
|
}
|
|
182
182
|
};
|
|
183
|
-
|
|
183
|
+
var JudgeRefusalError = class extends Error {
|
|
184
|
+
constructor(message) {
|
|
185
|
+
super(message);
|
|
186
|
+
this.name = "JudgeRefusalError";
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
function isRefusalStatus(status) {
|
|
190
|
+
return status >= 400 && status < 500 && status !== 408;
|
|
191
|
+
}
|
|
192
|
+
function enrich(status, body, statusText, endpoint, path4) {
|
|
184
193
|
const dashboard = endpoint.replace(/\/+$/, "") || DEFAULT_ENDPOINT;
|
|
185
|
-
let msg = `API error ${status}${
|
|
194
|
+
let msg = `API error ${status}${path4 ? ` from ${path4}` : ""}: ${body || statusText}`;
|
|
186
195
|
const lowered = body.toLowerCase();
|
|
187
196
|
if (lowered.includes("agent not registered")) {
|
|
188
197
|
msg += `
|
|
@@ -203,6 +212,61 @@ function enrich(status, body, statusText, endpoint, path5) {
|
|
|
203
212
|
return msg;
|
|
204
213
|
}
|
|
205
214
|
|
|
215
|
+
// src-ts/jail-hold.ts
|
|
216
|
+
var DEFAULTS = { ceilingMs: 3e4, pollMs: 1e3 };
|
|
217
|
+
var holds = /* @__PURE__ */ new Map();
|
|
218
|
+
var blockedAt = /* @__PURE__ */ new Map();
|
|
219
|
+
var BLOCK_MEMORY_MS = 10 * 6e4;
|
|
220
|
+
var config = { ...DEFAULTS };
|
|
221
|
+
function isJailHeld(pubkey) {
|
|
222
|
+
return holds.has(pubkey);
|
|
223
|
+
}
|
|
224
|
+
function recordBlock(pubkey) {
|
|
225
|
+
const now = Date.now();
|
|
226
|
+
for (const [key, at] of blockedAt) if (now - at > BLOCK_MEMORY_MS) blockedAt.delete(key);
|
|
227
|
+
blockedAt.set(pubkey, now);
|
|
228
|
+
}
|
|
229
|
+
function blockedSince(pubkey, startedAt) {
|
|
230
|
+
const at = blockedAt.get(pubkey);
|
|
231
|
+
return at !== void 0 && startedAt <= at;
|
|
232
|
+
}
|
|
233
|
+
function holdJail(pubkey, confirmJailed, onGiveUp) {
|
|
234
|
+
if (holds.has(pubkey)) return;
|
|
235
|
+
const hold = { timer: null };
|
|
236
|
+
holds.set(pubkey, hold);
|
|
237
|
+
const deadline = Date.now() + config.ceilingMs;
|
|
238
|
+
const tick = async () => {
|
|
239
|
+
if (holds.get(pubkey) !== hold) return;
|
|
240
|
+
let jailed = false;
|
|
241
|
+
try {
|
|
242
|
+
jailed = await confirmJailed();
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
if (holds.get(pubkey) !== hold) return;
|
|
246
|
+
if (jailed) {
|
|
247
|
+
holds.delete(pubkey);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (Date.now() >= deadline) {
|
|
251
|
+
holds.delete(pubkey);
|
|
252
|
+
onGiveUp?.();
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const timer = setTimeout(() => void tick(), config.pollMs);
|
|
256
|
+
timer.unref?.();
|
|
257
|
+
hold.timer = timer;
|
|
258
|
+
};
|
|
259
|
+
void tick();
|
|
260
|
+
}
|
|
261
|
+
function releaseJailHolds() {
|
|
262
|
+
for (const hold of holds.values()) if (hold.timer) clearTimeout(hold.timer);
|
|
263
|
+
holds.clear();
|
|
264
|
+
blockedAt.clear();
|
|
265
|
+
}
|
|
266
|
+
function configureJailHold(next) {
|
|
267
|
+
config = { ...DEFAULTS, ...next };
|
|
268
|
+
}
|
|
269
|
+
|
|
206
270
|
// src-ts/http/client.ts
|
|
207
271
|
var HttpClient = class {
|
|
208
272
|
baseUrl;
|
|
@@ -211,8 +275,8 @@ var HttpClient = class {
|
|
|
211
275
|
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
212
276
|
this.timeoutMs = timeoutMs;
|
|
213
277
|
}
|
|
214
|
-
buildUrl(
|
|
215
|
-
const url = new URL(this.baseUrl +
|
|
278
|
+
buildUrl(path4, query) {
|
|
279
|
+
const url = new URL(this.baseUrl + path4);
|
|
216
280
|
if (query) {
|
|
217
281
|
for (const [k, v] of Object.entries(query)) {
|
|
218
282
|
if (v !== void 0 && v !== null && v !== "") {
|
|
@@ -222,14 +286,14 @@ var HttpClient = class {
|
|
|
222
286
|
}
|
|
223
287
|
return url.toString();
|
|
224
288
|
}
|
|
225
|
-
async get(
|
|
226
|
-
return this.fetch(this.buildUrl(
|
|
289
|
+
async get(path4, query, headers) {
|
|
290
|
+
return this.fetch(this.buildUrl(path4, query), {
|
|
227
291
|
method: "GET",
|
|
228
292
|
...headers && { headers }
|
|
229
293
|
});
|
|
230
294
|
}
|
|
231
|
-
async post(
|
|
232
|
-
return this.fetch(this.buildUrl(
|
|
295
|
+
async post(path4, body, headers) {
|
|
296
|
+
return this.fetch(this.buildUrl(path4), {
|
|
233
297
|
method: "POST",
|
|
234
298
|
headers: { "Content-Type": "application/json", ...headers },
|
|
235
299
|
body: JSON.stringify(body)
|
|
@@ -495,11 +559,12 @@ function eventsOptedOut() {
|
|
|
495
559
|
const flag = process.env.ATBASH_TELEMETRY_EVENTS_DISABLED?.trim().toLowerCase();
|
|
496
560
|
return Boolean(flag && ["1", "true", "yes", "on"].includes(flag));
|
|
497
561
|
}
|
|
562
|
+
var SOURCE_SHAPE = /^(?:sdk|cli|plugin:[a-z0-9][a-z0-9._-]{0,31})$/;
|
|
498
563
|
function configureEvents(next, opts = {}) {
|
|
499
564
|
logger = next;
|
|
500
565
|
eventsEnabled = next !== null && !eventsOptedOut();
|
|
501
566
|
if (opts.version) sdkVersion = opts.version;
|
|
502
|
-
if (opts.source) eventSource = opts.source;
|
|
567
|
+
if (opts.source && SOURCE_SHAPE.test(opts.source)) eventSource = opts.source;
|
|
503
568
|
hashAgentKey = opts.hashAgentKey ?? null;
|
|
504
569
|
}
|
|
505
570
|
function eventsAreOn() {
|
|
@@ -552,6 +617,20 @@ import {
|
|
|
552
617
|
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
|
553
618
|
import { BatchLogRecordProcessor, LoggerProvider } from "@opentelemetry/sdk-logs";
|
|
554
619
|
import { createHash } from "crypto";
|
|
620
|
+
var SDK_VERSION = true ? "0.20.0-dev.0" : "";
|
|
621
|
+
var SDK_LANGUAGE = "node";
|
|
622
|
+
function sdkResourceAttributes() {
|
|
623
|
+
const attrs = {
|
|
624
|
+
"service.name": "atbash-sdk",
|
|
625
|
+
"sdk.language": SDK_LANGUAGE
|
|
626
|
+
};
|
|
627
|
+
if (SDK_VERSION) attrs["sdk.version"] = SDK_VERSION;
|
|
628
|
+
return attrs;
|
|
629
|
+
}
|
|
630
|
+
function sourceOrDefault(candidate) {
|
|
631
|
+
return typeof candidate === "string" && SOURCE_SHAPE.test(candidate) ? candidate : defaultSource;
|
|
632
|
+
}
|
|
633
|
+
var exitHookInstalled = false;
|
|
555
634
|
var meterProvider = null;
|
|
556
635
|
var loggerProvider = null;
|
|
557
636
|
var callCounter = null;
|
|
@@ -569,8 +648,8 @@ function isTelemetryOptedOut() {
|
|
|
569
648
|
const filePath = join2(home2, ".config", "atbash", "telemetry.json");
|
|
570
649
|
const raw = readFileSync2(filePath, "utf-8").trim();
|
|
571
650
|
if (!raw) return false;
|
|
572
|
-
const
|
|
573
|
-
return
|
|
651
|
+
const config2 = JSON.parse(raw);
|
|
652
|
+
return config2.enabled === false;
|
|
574
653
|
} catch {
|
|
575
654
|
return false;
|
|
576
655
|
}
|
|
@@ -615,26 +694,26 @@ function customCollector() {
|
|
|
615
694
|
}
|
|
616
695
|
return { url: url.replace(/\/+$/, ""), headers: () => headers };
|
|
617
696
|
}
|
|
618
|
-
function setupTelemetry(
|
|
619
|
-
if (!
|
|
697
|
+
function setupTelemetry(config2) {
|
|
698
|
+
if (!config2.enabled) return;
|
|
620
699
|
if (isTelemetryOptedOut()) return;
|
|
621
|
-
if (
|
|
622
|
-
defaultSource =
|
|
700
|
+
if (config2.source && !sourceExplicitlySet && SOURCE_SHAPE.test(config2.source)) {
|
|
701
|
+
defaultSource = config2.source;
|
|
623
702
|
sourceExplicitlySet = true;
|
|
624
703
|
}
|
|
625
|
-
if (
|
|
626
|
-
requestedExportIntervalMs =
|
|
704
|
+
if (config2.exportIntervalMs !== void 0 && requestedExportIntervalMs === void 0) {
|
|
705
|
+
requestedExportIntervalMs = config2.exportIntervalMs;
|
|
627
706
|
}
|
|
628
707
|
if (meterProvider) return;
|
|
629
708
|
const collector = customCollector();
|
|
630
|
-
if (!collector && (!
|
|
709
|
+
if (!collector && (!config2.endpoint || !config2.getAuthHeaders)) return;
|
|
631
710
|
if (!collector && /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)(:|\/|$)/i.test(
|
|
632
|
-
|
|
711
|
+
config2.endpoint
|
|
633
712
|
))
|
|
634
713
|
return;
|
|
635
714
|
const destination = collector ?? {
|
|
636
|
-
url: `${
|
|
637
|
-
headers: () =>
|
|
715
|
+
url: `${config2.endpoint.replace(/\/+$/, "")}/api/telemetry`,
|
|
716
|
+
headers: () => config2.getAuthHeaders()
|
|
638
717
|
};
|
|
639
718
|
const proxyUrl = destination.url;
|
|
640
719
|
const exporter = new OTLPMetricExporter({
|
|
@@ -658,9 +737,7 @@ function setupTelemetry(config) {
|
|
|
658
737
|
exportIntervalMillis: requestedExportIntervalMs ?? 6e4
|
|
659
738
|
});
|
|
660
739
|
meterProvider = new MeterProvider({
|
|
661
|
-
resource: resourceFromAttributes(
|
|
662
|
-
"service.name": "atbash-sdk"
|
|
663
|
-
}),
|
|
740
|
+
resource: resourceFromAttributes(sdkResourceAttributes()),
|
|
664
741
|
readers: [reader]
|
|
665
742
|
});
|
|
666
743
|
const meter = meterProvider.getMeter("atbash-sdk");
|
|
@@ -671,7 +748,7 @@ function setupTelemetry(config) {
|
|
|
671
748
|
description: "SDK function execution duration",
|
|
672
749
|
unit: "ms"
|
|
673
750
|
});
|
|
674
|
-
counterHasher =
|
|
751
|
+
counterHasher = config2.hashAgentKey ?? defaultAgentKeyHasher;
|
|
675
752
|
const logExporter = new OTLPLogExporter({
|
|
676
753
|
// A real collector takes OTLP at its own signal paths; the Atbash proxy
|
|
677
754
|
// takes a query parameter, because it is one authenticated route.
|
|
@@ -679,12 +756,20 @@ function setupTelemetry(config) {
|
|
|
679
756
|
headers: async () => destination.headers()
|
|
680
757
|
});
|
|
681
758
|
loggerProvider = new LoggerProvider({
|
|
682
|
-
resource: resourceFromAttributes(
|
|
759
|
+
resource: resourceFromAttributes(sdkResourceAttributes()),
|
|
683
760
|
processors: [new BatchLogRecordProcessor({ exporter: logExporter })]
|
|
684
761
|
});
|
|
762
|
+
if (!exitHookInstalled && typeof process?.once === "function") {
|
|
763
|
+
exitHookInstalled = true;
|
|
764
|
+
process.once("beforeExit", () => {
|
|
765
|
+
shutdownTelemetry().catch(() => {
|
|
766
|
+
});
|
|
767
|
+
});
|
|
768
|
+
}
|
|
685
769
|
configureEvents(loggerProvider.getLogger("atbash-sdk"), {
|
|
686
|
-
source:
|
|
687
|
-
hashAgentKey:
|
|
770
|
+
source: sourceOrDefault(config2.source),
|
|
771
|
+
hashAgentKey: config2.hashAgentKey ?? defaultAgentKeyHasher,
|
|
772
|
+
version: SDK_VERSION
|
|
688
773
|
});
|
|
689
774
|
}
|
|
690
775
|
function recordCall(functionName, source, agentPubkey) {
|
|
@@ -692,7 +777,7 @@ function recordCall(functionName, source, agentPubkey) {
|
|
|
692
777
|
if (!callCounter) return;
|
|
693
778
|
callCounter.add(1, {
|
|
694
779
|
"function.name": functionName,
|
|
695
|
-
source: source
|
|
780
|
+
source: sourceOrDefault(source),
|
|
696
781
|
// Hashed on this path too. The counters and the events export to the same
|
|
697
782
|
// place in the same request, so a raw key here would make hashing it on
|
|
698
783
|
// the event beside it pointless.
|
|
@@ -704,7 +789,7 @@ function recordDuration(functionName, durationMs, status, source) {
|
|
|
704
789
|
durationHistogram.record(durationMs, {
|
|
705
790
|
"function.name": functionName,
|
|
706
791
|
status,
|
|
707
|
-
source: source
|
|
792
|
+
source: sourceOrDefault(source)
|
|
708
793
|
});
|
|
709
794
|
}
|
|
710
795
|
async function flushTelemetry() {
|
|
@@ -761,13 +846,13 @@ function loadUserConfig() {
|
|
|
761
846
|
return {};
|
|
762
847
|
}
|
|
763
848
|
}
|
|
764
|
-
function saveUserConfig(
|
|
849
|
+
function saveUserConfig(config2) {
|
|
765
850
|
const dir = getConfigDir();
|
|
766
851
|
if (!existsSync2(dir)) {
|
|
767
852
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
768
853
|
}
|
|
769
854
|
const filePath = getConfigPath();
|
|
770
|
-
writeFileSync(filePath, JSON.stringify(
|
|
855
|
+
writeFileSync(filePath, JSON.stringify(config2, null, 2) + "\n", {
|
|
771
856
|
mode: 384
|
|
772
857
|
});
|
|
773
858
|
chmodSync(filePath, 384);
|
|
@@ -802,6 +887,7 @@ function forcedChainNetwork(flagValue) {
|
|
|
802
887
|
}
|
|
803
888
|
|
|
804
889
|
// src-ts/client.ts
|
|
890
|
+
var AGENT_NOT_REGISTERED = "Agent not registered. Onboard the agent at the dashboard before submitting actions.";
|
|
805
891
|
function generateToolCallId() {
|
|
806
892
|
return `tc-${Date.now()}-${randomHex(4)}`;
|
|
807
893
|
}
|
|
@@ -1150,6 +1236,15 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1150
1236
|
* return the signed tx hex. The server broadcasts to chain.
|
|
1151
1237
|
*/
|
|
1152
1238
|
async logToolCall(action, context = "", options = {}) {
|
|
1239
|
+
return this._logToolCall(action, context, options, false);
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* `raisePreflightErrors` (the judge path, like Python's
|
|
1243
|
+
* `raise_preflight_errors`): a failed registration check throws instead of
|
|
1244
|
+
* becoming `{ success: false }`, so the caller can tell Atbash's "no"
|
|
1245
|
+
* (not registered, or a 4xx) from an outage.
|
|
1246
|
+
*/
|
|
1247
|
+
async _logToolCall(action, context, options, raisePreflightErrors) {
|
|
1153
1248
|
const start = performance.now();
|
|
1154
1249
|
recordCall("logToolCall", void 0, this.auth.pubkey);
|
|
1155
1250
|
let exists;
|
|
@@ -1159,18 +1254,20 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1159
1254
|
});
|
|
1160
1255
|
} catch (err) {
|
|
1161
1256
|
recordDuration("logToolCall", performance.now() - start, "error");
|
|
1257
|
+
if (raisePreflightErrors) throw err;
|
|
1162
1258
|
return { success: false, toolCallId: null, error: errorMessage(err) };
|
|
1163
1259
|
}
|
|
1164
1260
|
if (!exists) {
|
|
1165
1261
|
recordDuration("logToolCall", performance.now() - start, "error");
|
|
1262
|
+
if (raisePreflightErrors) throw new JudgeRefusalError(AGENT_NOT_REGISTERED);
|
|
1166
1263
|
return {
|
|
1167
1264
|
success: false,
|
|
1168
1265
|
toolCallId: null,
|
|
1169
|
-
error:
|
|
1266
|
+
error: AGENT_NOT_REGISTERED
|
|
1170
1267
|
};
|
|
1171
1268
|
}
|
|
1172
1269
|
const toolCallId = generateToolCallId();
|
|
1173
|
-
const brid =
|
|
1270
|
+
const brid = await this.callBrid(options.chainOpts);
|
|
1174
1271
|
const orgKey = options.orgEncryptionPubKey ?? this.orgEncryptionPubKey ?? this._orgKeyFromChain;
|
|
1175
1272
|
try {
|
|
1176
1273
|
const signedHex = orgKey ? signEncryptedToolCall(
|
|
@@ -1220,6 +1317,8 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1220
1317
|
if (!action?.trim()) {
|
|
1221
1318
|
throw new Error("action is required and cannot be empty.");
|
|
1222
1319
|
}
|
|
1320
|
+
if (isJailHeld(this.auth.pubkey)) return this.localJailBlock();
|
|
1321
|
+
const startedAt = Date.now();
|
|
1223
1322
|
let chainOpts = options.chainOpts;
|
|
1224
1323
|
if (options.orgName && this._explicitChain) {
|
|
1225
1324
|
chainOpts = options.chainOpts ?? { network: this._defaultChain.network };
|
|
@@ -1244,12 +1343,13 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1244
1343
|
}
|
|
1245
1344
|
}
|
|
1246
1345
|
}
|
|
1247
|
-
const brid = this.
|
|
1248
|
-
const logResult = await this.
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
chainOpts
|
|
1252
|
-
|
|
1346
|
+
const brid = await this.callBrid(chainOpts);
|
|
1347
|
+
const logResult = await this._logToolCall(
|
|
1348
|
+
action,
|
|
1349
|
+
context,
|
|
1350
|
+
{ toolName: options.toolName, toolArgsJson: options.toolArgsJson, chainOpts },
|
|
1351
|
+
true
|
|
1352
|
+
);
|
|
1253
1353
|
if (!logResult.success || !logResult.toolCallId || !logResult.signedHex) {
|
|
1254
1354
|
throw new Error(logResult.error || "Failed to sign log_tool_call");
|
|
1255
1355
|
}
|
|
@@ -1286,10 +1386,16 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1286
1386
|
if (verifyPubKey !== void 0) {
|
|
1287
1387
|
verifyResponseSignature(bodyBytes, resp, verifyPubKey);
|
|
1288
1388
|
}
|
|
1289
|
-
const
|
|
1389
|
+
const parsed = tryParseJson(bodyBytes.toString("utf-8"));
|
|
1390
|
+
if (!isRecord2(parsed)) {
|
|
1391
|
+
throw new JudgeRefusalError(
|
|
1392
|
+
"The judge's reply could not be read, so the action was not approved."
|
|
1393
|
+
);
|
|
1394
|
+
}
|
|
1395
|
+
const data = parsed;
|
|
1290
1396
|
const rawScore = data.score;
|
|
1291
1397
|
const score = typeof rawScore === "number" && Number.isInteger(rawScore) && rawScore >= 1 && rawScore <= 10 ? rawScore : void 0;
|
|
1292
|
-
|
|
1398
|
+
const result = {
|
|
1293
1399
|
verdict: normalizeVerdict(data.verdict),
|
|
1294
1400
|
allow: this.executable(data),
|
|
1295
1401
|
actionType: normalizeActionType(data.action_type),
|
|
@@ -1305,6 +1411,45 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1305
1411
|
score,
|
|
1306
1412
|
refusal: explicitRefusal(data)
|
|
1307
1413
|
};
|
|
1414
|
+
return this.applyJailHold(result, startedAt);
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Start a local hold on an enforced, on-chain BLOCK (the verdict that jails).
|
|
1418
|
+
* Refuse any other result that lands while the agent is held, or whose
|
|
1419
|
+
* request was already in flight when the block arrived: the server judged it
|
|
1420
|
+
* on pre-jail state.
|
|
1421
|
+
*/
|
|
1422
|
+
applyJailHold(result, startedAt) {
|
|
1423
|
+
const pubkey = this.auth.pubkey;
|
|
1424
|
+
if (result.verdict === "BLOCK" && result.enforced && result.onChain) {
|
|
1425
|
+
recordBlock(pubkey);
|
|
1426
|
+
holdJail(
|
|
1427
|
+
pubkey,
|
|
1428
|
+
() => this.getAgentPolicy(pubkey).then((p) => p.isJailed),
|
|
1429
|
+
() => this.logger.warn?.("[atbash] local jail hold released: the chain did not confirm the jail in time")
|
|
1430
|
+
);
|
|
1431
|
+
return result;
|
|
1432
|
+
}
|
|
1433
|
+
return isJailHeld(pubkey) || blockedSince(pubkey, startedAt) ? this.localJailBlock(result.toolCallId) : result;
|
|
1434
|
+
}
|
|
1435
|
+
/** The server's jailed refusal, produced locally while the agent is held. */
|
|
1436
|
+
localJailBlock(toolCallId = generateToolCallId()) {
|
|
1437
|
+
return {
|
|
1438
|
+
verdict: "BLOCK",
|
|
1439
|
+
allow: false,
|
|
1440
|
+
actionType: "block",
|
|
1441
|
+
reason: "Agent is jailed. All actions are blocked until the organization owner unjails this agent.",
|
|
1442
|
+
confidence: 1,
|
|
1443
|
+
provider: "policy",
|
|
1444
|
+
latencyMs: 0,
|
|
1445
|
+
toolCallId,
|
|
1446
|
+
onChain: false,
|
|
1447
|
+
enforced: true,
|
|
1448
|
+
enforcementMode: "enforce",
|
|
1449
|
+
status: "",
|
|
1450
|
+
score: void 0,
|
|
1451
|
+
refusal: "BLOCK"
|
|
1452
|
+
};
|
|
1308
1453
|
}
|
|
1309
1454
|
/* ── audit_tool_call (redact → judge → decision) ───────────────────────── */
|
|
1310
1455
|
/**
|
|
@@ -1356,6 +1501,9 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1356
1501
|
reason: `The judge's reply failed its signature check (${errorMessage(err)}), so it was not trusted.`
|
|
1357
1502
|
};
|
|
1358
1503
|
}
|
|
1504
|
+
if (err instanceof JudgeRefusalError || err instanceof AtbashAPIError && isRefusalStatus(err.status)) {
|
|
1505
|
+
return { ...this.failJudge(errorMessage(err), err), allow: false };
|
|
1506
|
+
}
|
|
1359
1507
|
return this.failJudge(errorMessage(err), err);
|
|
1360
1508
|
}
|
|
1361
1509
|
}
|
|
@@ -1385,6 +1533,15 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1385
1533
|
* never overrides a refusal.
|
|
1386
1534
|
*/
|
|
1387
1535
|
decide(result) {
|
|
1536
|
+
const inner = this.decideInner(result);
|
|
1537
|
+
if (!result.enforcementMode) return inner;
|
|
1538
|
+
return {
|
|
1539
|
+
...inner,
|
|
1540
|
+
enforced: result.enforced,
|
|
1541
|
+
enforcementMode: result.enforcementMode
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
decideInner(result) {
|
|
1388
1545
|
const toolCallId = result.toolCallId;
|
|
1389
1546
|
const deny = (verdict, reason) => ({
|
|
1390
1547
|
allow: false,
|
|
@@ -1498,17 +1655,17 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1498
1655
|
/**
|
|
1499
1656
|
* Return the current status of a previously submitted judgment.
|
|
1500
1657
|
*
|
|
1501
|
-
* `chainOpts` names which chain the judgment was signed against
|
|
1502
|
-
*
|
|
1503
|
-
*
|
|
1504
|
-
*
|
|
1505
|
-
*
|
|
1506
|
-
*
|
|
1658
|
+
* `chainOpts` names which chain the judgment was signed against; the SDK
|
|
1659
|
+
* sends it as the `brid` query param the server routes by. Without it the
|
|
1660
|
+
* read uses the same chain `judgeAction` signs for (see `callBrid`): the
|
|
1661
|
+
* client's org chain when it has an `orgName`, else the client default.
|
|
1662
|
+
* A judgment signed with an explicit per-call `chainOpts` (or a per-call
|
|
1663
|
+
* `orgName` on another chain) must be polled with that same `chainOpts`.
|
|
1507
1664
|
*/
|
|
1508
1665
|
async getJudgmentStatus(judgmentId, agentPubkey, chainOpts) {
|
|
1509
1666
|
const pk = agentPubkey ?? this.auth.pubkey;
|
|
1510
|
-
const brid = this.bridFromChainOpts(chainOpts);
|
|
1511
1667
|
return this.track("getJudgmentStatus", pk, async () => {
|
|
1668
|
+
const brid = await this.callBrid(chainOpts);
|
|
1512
1669
|
const resp = await this.http.get(
|
|
1513
1670
|
"/api/v1/judge",
|
|
1514
1671
|
{ tool_call_id: judgmentId, agent_pubkey: pk, brid },
|
|
@@ -1846,6 +2003,18 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1846
2003
|
if (chainOpts?.network === "public") return PUBLIC_CHAIN.blockchainRid;
|
|
1847
2004
|
return this.blockchainRid;
|
|
1848
2005
|
}
|
|
2006
|
+
/**
|
|
2007
|
+
* The chain one call's transactions are signed for: the per-call override
|
|
2008
|
+
* when given, otherwise the client's org, otherwise the client default.
|
|
2009
|
+
* log_tool_call, judge_action and the judgment-status read all use this, so
|
|
2010
|
+
* a request never pairs transactions signed for two different chains.
|
|
2011
|
+
*/
|
|
2012
|
+
async callBrid(chainOpts) {
|
|
2013
|
+
if (chainOpts?.blockchainRid || chainOpts?.network) {
|
|
2014
|
+
return this.bridFromChainOpts(chainOpts);
|
|
2015
|
+
}
|
|
2016
|
+
return await this.defaultOrgBrid() ?? this.blockchainRid;
|
|
2017
|
+
}
|
|
1849
2018
|
/**
|
|
1850
2019
|
* Resolve the dashboard chain used by agent metadata/policy reads.
|
|
1851
2020
|
* Explicit per-call network overrides win; otherwise use the supplied org
|
|
@@ -1891,8 +2060,8 @@ config.json still holds an "agentKey" (${identity}), but a settings file no long
|
|
|
1891
2060
|
async riskEnginePost(body, network, brid) {
|
|
1892
2061
|
let resp;
|
|
1893
2062
|
try {
|
|
1894
|
-
const
|
|
1895
|
-
resp = await this.http.post(
|
|
2063
|
+
const path4 = network ? `/api/risk-engine?network=${encodeURIComponent(network)}` : "/api/risk-engine";
|
|
2064
|
+
resp = await this.http.post(path4, body, this.authHeaders(brid));
|
|
1896
2065
|
} catch (err) {
|
|
1897
2066
|
throw this.transportError(err);
|
|
1898
2067
|
}
|
|
@@ -2153,10 +2322,6 @@ function tryParseJson(text) {
|
|
|
2153
2322
|
return void 0;
|
|
2154
2323
|
}
|
|
2155
2324
|
}
|
|
2156
|
-
function parseJson(bytes) {
|
|
2157
|
-
const parsed = tryParseJson(bytes.toString("utf-8"));
|
|
2158
|
-
return parsed === void 0 ? {} : parsed;
|
|
2159
|
-
}
|
|
2160
2325
|
function requestPath(url) {
|
|
2161
2326
|
if (!url) return void 0;
|
|
2162
2327
|
try {
|
|
@@ -2435,10 +2600,10 @@ function resolveBlockchainRid(chainOpts) {
|
|
|
2435
2600
|
'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"`.'
|
|
2436
2601
|
);
|
|
2437
2602
|
}
|
|
2438
|
-
const
|
|
2603
|
+
const config2 = chainForNetwork(
|
|
2439
2604
|
chainOpts?.network ?? forcedChainNetwork() ?? "private"
|
|
2440
2605
|
);
|
|
2441
|
-
return
|
|
2606
|
+
return config2.blockchainRid;
|
|
2442
2607
|
}
|
|
2443
2608
|
async function commitMemoryVersion(plaintext, auth, opts) {
|
|
2444
2609
|
const key = await deriveMemoryKey(auth.privkey);
|
|
@@ -2664,7 +2829,34 @@ function classifyMemoryWrite(event, ctx, opts = {}) {
|
|
|
2664
2829
|
// src-ts/memory/guard.ts
|
|
2665
2830
|
import { promises as fs } from "fs";
|
|
2666
2831
|
import os from "os";
|
|
2667
|
-
|
|
2832
|
+
|
|
2833
|
+
// src-ts/memory/slot.ts
|
|
2834
|
+
import nodePath from "path";
|
|
2835
|
+
function asciiLower(s) {
|
|
2836
|
+
return s.replace(/[A-Z]/g, (c) => String.fromCharCode(c.charCodeAt(0) | 32));
|
|
2837
|
+
}
|
|
2838
|
+
function spelledAs(target, base, p = nodePath) {
|
|
2839
|
+
const resolved = p.resolve(base);
|
|
2840
|
+
if (p.sep !== "\\") return resolved;
|
|
2841
|
+
const prefix = resolved.replace(/[\\/]+$/, "");
|
|
2842
|
+
const t = asciiLower(target);
|
|
2843
|
+
const b = asciiLower(prefix);
|
|
2844
|
+
if (t === b || t.startsWith(b + "\\") || t.startsWith(b + "/")) {
|
|
2845
|
+
return target.slice(0, prefix.length);
|
|
2846
|
+
}
|
|
2847
|
+
return resolved;
|
|
2848
|
+
}
|
|
2849
|
+
function memorySlotOf(target, home2, root, extraHomes, p = nodePath) {
|
|
2850
|
+
const resolved = p.resolve(target);
|
|
2851
|
+
return native.memorySlotForPath(
|
|
2852
|
+
resolved,
|
|
2853
|
+
home2 ? spelledAs(resolved, home2, p) : "",
|
|
2854
|
+
root ? spelledAs(resolved, root, p) : void 0,
|
|
2855
|
+
extraHomes
|
|
2856
|
+
);
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
// src-ts/memory/guard.ts
|
|
2668
2860
|
function emitDebugProbe(event, ctx, memEntry, logger2) {
|
|
2669
2861
|
if (!logger2?.info) return;
|
|
2670
2862
|
const ev = event ?? {};
|
|
@@ -2690,7 +2882,7 @@ function emitDebugProbe(event, ctx, memEntry, logger2) {
|
|
|
2690
2882
|
}
|
|
2691
2883
|
function memorySlotFor(key, root, extraHomes) {
|
|
2692
2884
|
const home2 = process.env.HOME || os.homedir() || "";
|
|
2693
|
-
return
|
|
2885
|
+
return memorySlotOf(key, home2, root, extraHomes);
|
|
2694
2886
|
}
|
|
2695
2887
|
async function resultingDocument(memEntry, event, ctx) {
|
|
2696
2888
|
const edits = native.extractMemoryEdits(event, ctx);
|
|
@@ -2893,7 +3085,7 @@ async function syncLocalMemory(auth, pointer, opts = {}) {
|
|
|
2893
3085
|
|
|
2894
3086
|
// src-ts/memory/pointer-store.ts
|
|
2895
3087
|
import { promises as fs2 } from "fs";
|
|
2896
|
-
import
|
|
3088
|
+
import path from "path";
|
|
2897
3089
|
var VERSION = 2;
|
|
2898
3090
|
var ZERO = { activeId: null, checkedAt: 0 };
|
|
2899
3091
|
var PointerStore = class {
|
|
@@ -2946,19 +3138,19 @@ var PointerStore = class {
|
|
|
2946
3138
|
this.cache = { version: VERSION, agents: {} };
|
|
2947
3139
|
}
|
|
2948
3140
|
async persist(file) {
|
|
2949
|
-
await fs2.mkdir(
|
|
3141
|
+
await fs2.mkdir(path.dirname(this.filePath), { recursive: true });
|
|
2950
3142
|
const tmp = `${this.filePath}.${process.pid}.tmp`;
|
|
2951
3143
|
await fs2.writeFile(tmp, JSON.stringify(file, null, 2), "utf8");
|
|
2952
3144
|
await fs2.rename(tmp, this.filePath);
|
|
2953
3145
|
}
|
|
2954
3146
|
};
|
|
2955
3147
|
function defaultPointerPath(workspaceDir = process.cwd()) {
|
|
2956
|
-
return
|
|
3148
|
+
return path.join(workspaceDir, ".atbash", "memory-pointer.json");
|
|
2957
3149
|
}
|
|
2958
3150
|
|
|
2959
3151
|
// src-ts/memory/file-logger.ts
|
|
2960
3152
|
import { promises as fs3 } from "fs";
|
|
2961
|
-
import
|
|
3153
|
+
import path2 from "path";
|
|
2962
3154
|
function formatMeta(meta) {
|
|
2963
3155
|
if (!meta || Object.keys(meta).length === 0) return "";
|
|
2964
3156
|
try {
|
|
@@ -2971,7 +3163,7 @@ function createFileLogger(filePath, upstream) {
|
|
|
2971
3163
|
let queue = Promise.resolve();
|
|
2972
3164
|
let writeError;
|
|
2973
3165
|
async function ensureDir() {
|
|
2974
|
-
await fs3.mkdir(
|
|
3166
|
+
await fs3.mkdir(path2.dirname(filePath), { recursive: true });
|
|
2975
3167
|
}
|
|
2976
3168
|
function append(level, message, meta) {
|
|
2977
3169
|
const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${message}${formatMeta(meta)}
|
|
@@ -2997,7 +3189,7 @@ function createFileLogger(filePath, upstream) {
|
|
|
2997
3189
|
};
|
|
2998
3190
|
}
|
|
2999
3191
|
function defaultPluginLogPath(workspaceDir = process.cwd()) {
|
|
3000
|
-
return
|
|
3192
|
+
return path2.join(workspaceDir, ".atbash", "plugin.log");
|
|
3001
3193
|
}
|
|
3002
3194
|
|
|
3003
3195
|
// src-ts/memory/read-classifier.ts
|
|
@@ -3013,14 +3205,14 @@ function classifyMemoryRead(event, ctx, opts = {}) {
|
|
|
3013
3205
|
// src-ts/memory/guard-manager.ts
|
|
3014
3206
|
import { promises as fs4 } from "fs";
|
|
3015
3207
|
import os2 from "os";
|
|
3016
|
-
import
|
|
3208
|
+
import path3 from "path";
|
|
3017
3209
|
var DEFAULT_SYNC_TTL_MS = 3e4;
|
|
3018
3210
|
var MemoryGuardManager = class {
|
|
3019
3211
|
constructor(opts) {
|
|
3020
3212
|
this.opts = opts;
|
|
3021
3213
|
const workspaceDir = opts.workspaceDir;
|
|
3022
3214
|
this.workspaceDir = workspaceDir;
|
|
3023
|
-
this.memoryFilePath = opts.memoryFilePath ??
|
|
3215
|
+
this.memoryFilePath = opts.memoryFilePath ?? path3.join(workspaceDir, "MEMORY.md");
|
|
3024
3216
|
this.pointerStore = new PointerStore(opts.pointerFilePath ?? defaultPointerPath(workspaceDir));
|
|
3025
3217
|
this.logger = createFileLogger(
|
|
3026
3218
|
opts.logFilePath ?? defaultPluginLogPath(workspaceDir),
|
|
@@ -3235,7 +3427,7 @@ var MemoryGuardManager = class {
|
|
|
3235
3427
|
*/
|
|
3236
3428
|
async locate(slot, roots) {
|
|
3237
3429
|
for (const root of roots) {
|
|
3238
|
-
const candidate =
|
|
3430
|
+
const candidate = path3.join(root, slot);
|
|
3239
3431
|
try {
|
|
3240
3432
|
await fs4.access(candidate);
|
|
3241
3433
|
return candidate;
|
|
@@ -3250,7 +3442,7 @@ var MemoryGuardManager = class {
|
|
|
3250
3442
|
/** Roots a chain slot may be joined to, most specific first. */
|
|
3251
3443
|
async memoryRoots() {
|
|
3252
3444
|
const roots = [];
|
|
3253
|
-
if (this.opts.memoryRoot) roots.push(
|
|
3445
|
+
if (this.opts.memoryRoot) roots.push(path3.resolve(this.opts.memoryRoot));
|
|
3254
3446
|
const home2 = process.env.HOME || os2.homedir() || "";
|
|
3255
3447
|
if (home2) {
|
|
3256
3448
|
const names = [
|
|
@@ -3258,7 +3450,7 @@ var MemoryGuardManager = class {
|
|
|
3258
3450
|
...this.opts.agentHomeDirs ?? []
|
|
3259
3451
|
];
|
|
3260
3452
|
for (const name of names) {
|
|
3261
|
-
const candidate =
|
|
3453
|
+
const candidate = path3.join(home2, name);
|
|
3262
3454
|
try {
|
|
3263
3455
|
if ((await fs4.stat(candidate)).isDirectory()) roots.push(candidate);
|
|
3264
3456
|
} catch {
|
|
@@ -3396,7 +3588,7 @@ var MemoryGuardManager = class {
|
|
|
3396
3588
|
const rec = bag;
|
|
3397
3589
|
for (const k of KEYS) {
|
|
3398
3590
|
const v = rec[k];
|
|
3399
|
-
if (typeof v === "string" && v) found.add(
|
|
3591
|
+
if (typeof v === "string" && v) found.add(path3.resolve(this.workspaceDir, v));
|
|
3400
3592
|
}
|
|
3401
3593
|
}
|
|
3402
3594
|
}
|
|
@@ -3443,12 +3635,7 @@ var MemoryGuardManager = class {
|
|
|
3443
3635
|
*/
|
|
3444
3636
|
slotFor(target) {
|
|
3445
3637
|
const home2 = process.env.HOME || os2.homedir() || "";
|
|
3446
|
-
return
|
|
3447
|
-
path4.resolve(target),
|
|
3448
|
-
home2,
|
|
3449
|
-
this.opts.memoryRoot,
|
|
3450
|
-
this.opts.agentHomeDirs
|
|
3451
|
-
);
|
|
3638
|
+
return memorySlotOf(target, home2, this.opts.memoryRoot, this.opts.agentHomeDirs);
|
|
3452
3639
|
}
|
|
3453
3640
|
/**
|
|
3454
3641
|
* Verify every memory file this call reads, each against its own chain slot.
|
|
@@ -3597,7 +3784,7 @@ var MemoryGuardManager = class {
|
|
|
3597
3784
|
}
|
|
3598
3785
|
/** Replace `target` in one step, so a reader never sees a half-written file. */
|
|
3599
3786
|
async writeFileAtomic(target, content) {
|
|
3600
|
-
await fs4.mkdir(
|
|
3787
|
+
await fs4.mkdir(path3.dirname(target), { recursive: true });
|
|
3601
3788
|
const tmp = `${target}.${process.pid}.tmp`;
|
|
3602
3789
|
await fs4.writeFile(tmp, content, "utf8");
|
|
3603
3790
|
await fs4.rename(tmp, target);
|
|
@@ -3737,6 +3924,7 @@ export {
|
|
|
3737
3924
|
EciesDomain,
|
|
3738
3925
|
HttpClient,
|
|
3739
3926
|
HttpTransportError,
|
|
3927
|
+
JudgeRefusalError,
|
|
3740
3928
|
KEY_FILENAMES,
|
|
3741
3929
|
MemoryGuardManager,
|
|
3742
3930
|
MemoryIntegrityError,
|
|
@@ -3755,6 +3943,7 @@ export {
|
|
|
3755
3943
|
columnAad,
|
|
3756
3944
|
commitMemoryVersion,
|
|
3757
3945
|
configureEvents,
|
|
3946
|
+
configureJailHold,
|
|
3758
3947
|
containsEvasionCharacters,
|
|
3759
3948
|
containsSecret,
|
|
3760
3949
|
createFileLogger,
|
|
@@ -3783,12 +3972,14 @@ export {
|
|
|
3783
3972
|
getRollbackHistory,
|
|
3784
3973
|
guardMemoryWrite,
|
|
3785
3974
|
isEnvelope,
|
|
3975
|
+
isJailHeld,
|
|
3786
3976
|
isValidPrivateKey,
|
|
3787
3977
|
keyFingerprintOf,
|
|
3788
3978
|
keyPathCandidates,
|
|
3789
3979
|
loadAgent,
|
|
3790
3980
|
loadAgentFromFile,
|
|
3791
3981
|
loadUserConfig,
|
|
3982
|
+
memorySlotOf,
|
|
3792
3983
|
normalizeActionForHash,
|
|
3793
3984
|
normalizeActionType,
|
|
3794
3985
|
normalizeForMatching,
|
|
@@ -3802,6 +3993,7 @@ export {
|
|
|
3802
3993
|
recordEvent,
|
|
3803
3994
|
redactJsonStrings,
|
|
3804
3995
|
redactSecrets,
|
|
3996
|
+
releaseJailHolds,
|
|
3805
3997
|
resolve,
|
|
3806
3998
|
resolveKeyPath,
|
|
3807
3999
|
rollbackMemory,
|