@atbash/sdk 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.d.mts +49 -14
- package/dist/browser.mjs +124 -192
- package/dist/index.d.mts +49 -14
- package/dist/index.d.ts +49 -14
- package/dist/index.js +338 -92
- package/dist/index.mjs +342 -93
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -3026,6 +3026,13 @@ function buildAllowedJudgeHosts() {
|
|
|
3026
3026
|
]);
|
|
3027
3027
|
}
|
|
3028
3028
|
var ALLOWED_JUDGE_HOSTS = buildAllowedJudgeHosts();
|
|
3029
|
+
function isAllowlistedJudgeHost(endpoint) {
|
|
3030
|
+
try {
|
|
3031
|
+
return ALLOWED_JUDGE_HOSTS.has(new URL(endpoint).hostname.toLowerCase());
|
|
3032
|
+
} catch {
|
|
3033
|
+
return false;
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3029
3036
|
function validateJudgeEndpoint(judge) {
|
|
3030
3037
|
const policy = judge?.policy === "self-hosted" ? "self-hosted" : "default";
|
|
3031
3038
|
const candidate = judge?.endpoint?.trim() || DEFAULT_ENDPOINT;
|
|
@@ -3073,11 +3080,23 @@ var AtbashAPIError = class extends Error {
|
|
|
3073
3080
|
status;
|
|
3074
3081
|
/** Raw response body text (may be empty). */
|
|
3075
3082
|
body;
|
|
3076
|
-
|
|
3077
|
-
|
|
3083
|
+
/**
|
|
3084
|
+
* Request path this status came from, e.g. `/api/v1/judge` — absent when the
|
|
3085
|
+
* request never reached a response (transport failures carry status 0).
|
|
3086
|
+
*
|
|
3087
|
+
* A status alone does not say what failed. Several routes answer one call, and
|
|
3088
|
+
* more than one of them can 404: a caller reading only `404` cannot tell "this
|
|
3089
|
+
* agent is not registered" from "the chain-routing lookup did not resolve", and
|
|
3090
|
+
* has to guess from the body's prose — which a 404 raised before any handler
|
|
3091
|
+
* ran does not carry. The path is the fact that settles it.
|
|
3092
|
+
*/
|
|
3093
|
+
path;
|
|
3094
|
+
constructor(status, body, statusText = "", endpoint = DEFAULT_ENDPOINT, path7) {
|
|
3095
|
+
super(enrich(status, body, statusText, endpoint, path7));
|
|
3078
3096
|
this.name = "AtbashAPIError";
|
|
3079
3097
|
this.status = status;
|
|
3080
3098
|
this.body = body;
|
|
3099
|
+
this.path = path7;
|
|
3081
3100
|
}
|
|
3082
3101
|
};
|
|
3083
3102
|
var SignatureVerificationError = class extends Error {
|
|
@@ -3086,9 +3105,9 @@ var SignatureVerificationError = class extends Error {
|
|
|
3086
3105
|
this.name = "SignatureVerificationError";
|
|
3087
3106
|
}
|
|
3088
3107
|
};
|
|
3089
|
-
function enrich(status, body, statusText, endpoint) {
|
|
3108
|
+
function enrich(status, body, statusText, endpoint, path7) {
|
|
3090
3109
|
const dashboard = endpoint.replace(/\/+$/, "") || DEFAULT_ENDPOINT;
|
|
3091
|
-
let msg = `API error ${status}: ${body || statusText}`;
|
|
3110
|
+
let msg = `API error ${status}${path7 ? ` from ${path7}` : ""}: ${body || statusText}`;
|
|
3092
3111
|
const lowered = body.toLowerCase();
|
|
3093
3112
|
if (lowered.includes("agent not registered")) {
|
|
3094
3113
|
msg += `
|
|
@@ -3178,6 +3197,13 @@ function classifyTransportError(err, url, method, timeoutMs) {
|
|
|
3178
3197
|
{ cause: err }
|
|
3179
3198
|
);
|
|
3180
3199
|
}
|
|
3200
|
+
if (code2 === "UND_ERR_CONNECT_TIMEOUT") {
|
|
3201
|
+
return new HttpTransportError(
|
|
3202
|
+
"timeout",
|
|
3203
|
+
`${method} ${url} could not establish a connection before the transport deadline \u2014 check the endpoint and network`,
|
|
3204
|
+
{ cause: err }
|
|
3205
|
+
);
|
|
3206
|
+
}
|
|
3181
3207
|
if (code2 === "ENOTFOUND" || code2 === "EAI_AGAIN") {
|
|
3182
3208
|
return new HttpTransportError(
|
|
3183
3209
|
"dns",
|
|
@@ -3342,6 +3368,8 @@ var meterProvider = null;
|
|
|
3342
3368
|
var callCounter = null;
|
|
3343
3369
|
var durationHistogram = null;
|
|
3344
3370
|
var defaultSource = "sdk";
|
|
3371
|
+
var sourceExplicitlySet = false;
|
|
3372
|
+
var requestedExportIntervalMs;
|
|
3345
3373
|
function isTelemetryOptedOut() {
|
|
3346
3374
|
const disabled = process.env.ATBASH_TELEMETRY_DISABLED?.trim().toLowerCase();
|
|
3347
3375
|
if (disabled && ["1", "true", "yes", "on"].includes(disabled)) {
|
|
@@ -3365,20 +3393,41 @@ function autoInit() {
|
|
|
3365
3393
|
}
|
|
3366
3394
|
function setupTelemetry(config2) {
|
|
3367
3395
|
if (!config2.enabled) return;
|
|
3368
|
-
if (meterProvider) return;
|
|
3369
3396
|
if (isTelemetryOptedOut()) return;
|
|
3397
|
+
if (config2.source && !sourceExplicitlySet) {
|
|
3398
|
+
defaultSource = config2.source;
|
|
3399
|
+
sourceExplicitlySet = true;
|
|
3400
|
+
}
|
|
3401
|
+
if (config2.exportIntervalMs !== void 0 && requestedExportIntervalMs === void 0) {
|
|
3402
|
+
requestedExportIntervalMs = config2.exportIntervalMs;
|
|
3403
|
+
}
|
|
3404
|
+
if (meterProvider) return;
|
|
3370
3405
|
if (!config2.endpoint || !config2.getAuthHeaders) return;
|
|
3371
|
-
if (/^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)(:|\/|$)/i.test(
|
|
3372
|
-
|
|
3406
|
+
if (/^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)(:|\/|$)/i.test(
|
|
3407
|
+
config2.endpoint
|
|
3408
|
+
))
|
|
3409
|
+
return;
|
|
3373
3410
|
const proxyUrl = `${config2.endpoint.replace(/\/+$/, "")}/api/telemetry`;
|
|
3374
3411
|
const getAuthHeaders = config2.getAuthHeaders;
|
|
3375
3412
|
const exporter = new import_exporter_metrics_otlp_http.OTLPMetricExporter({
|
|
3376
3413
|
url: proxyUrl,
|
|
3377
|
-
headers: async () => getAuthHeaders()
|
|
3414
|
+
headers: async () => getAuthHeaders(),
|
|
3415
|
+
// DELTA, not the OTel default of CUMULATIVE.
|
|
3416
|
+
//
|
|
3417
|
+
// A cumulative counter reports a running total, and the backend recovers
|
|
3418
|
+
// the increment by differencing consecutive points of a series. That needs
|
|
3419
|
+
// at least two points. Most Atbash callers are short-lived — a CLI run
|
|
3420
|
+
// exports once and exits — so their series has exactly one point, nothing
|
|
3421
|
+
// to difference against, and the usage board reads them as zero: the calls
|
|
3422
|
+
// arrive, carry the right `source`, and still count for nothing.
|
|
3423
|
+
//
|
|
3424
|
+
// A delta counter reports the increment itself, so a single export is a
|
|
3425
|
+
// whole truthful datapoint and SUM over any window is the real call count.
|
|
3426
|
+
temporalityPreference: import_exporter_metrics_otlp_http.AggregationTemporalityPreference.DELTA
|
|
3378
3427
|
});
|
|
3379
3428
|
const reader = new import_sdk_metrics.PeriodicExportingMetricReader({
|
|
3380
3429
|
exporter,
|
|
3381
|
-
exportIntervalMillis:
|
|
3430
|
+
exportIntervalMillis: requestedExportIntervalMs ?? 6e4
|
|
3382
3431
|
});
|
|
3383
3432
|
meterProvider = new import_sdk_metrics.MeterProvider({
|
|
3384
3433
|
resource: (0, import_resources.resourceFromAttributes)({
|
|
@@ -3422,6 +3471,9 @@ async function shutdownTelemetry() {
|
|
|
3422
3471
|
meterProvider = null;
|
|
3423
3472
|
callCounter = null;
|
|
3424
3473
|
durationHistogram = null;
|
|
3474
|
+
defaultSource = "sdk";
|
|
3475
|
+
sourceExplicitlySet = false;
|
|
3476
|
+
requestedExportIntervalMs = void 0;
|
|
3425
3477
|
}
|
|
3426
3478
|
|
|
3427
3479
|
// src-ts/userConfig.ts
|
|
@@ -3609,17 +3661,23 @@ var Atbash = class _Atbash {
|
|
|
3609
3661
|
verifying: this.verifyPubKey ? "with response-signature pubkey configured" : "without signature verification"
|
|
3610
3662
|
});
|
|
3611
3663
|
}
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3664
|
+
if (isAllowlistedJudgeHost(this.endpoint)) {
|
|
3665
|
+
try {
|
|
3666
|
+
setupTelemetry({
|
|
3667
|
+
enabled: true,
|
|
3668
|
+
endpoint: this.endpoint,
|
|
3669
|
+
getAuthHeaders: () => this.authHeaders()
|
|
3670
|
+
});
|
|
3671
|
+
} catch (err) {
|
|
3672
|
+
this.logger.warn?.(
|
|
3673
|
+
"[atbash] telemetry setup failed \u2014 continuing without metrics",
|
|
3674
|
+
{ error: String(err) }
|
|
3675
|
+
);
|
|
3676
|
+
}
|
|
3677
|
+
} else {
|
|
3678
|
+
this.logger.info?.(
|
|
3679
|
+
"[atbash] telemetry disabled \u2014 the agent bearer is only sent to hosts that already receive it, and this judge endpoint is not one",
|
|
3680
|
+
{ endpoint: this.endpoint }
|
|
3623
3681
|
);
|
|
3624
3682
|
}
|
|
3625
3683
|
}
|
|
@@ -4500,7 +4558,8 @@ var Atbash = class _Atbash {
|
|
|
4500
4558
|
resp.status,
|
|
4501
4559
|
await safeText(resp),
|
|
4502
4560
|
resp.statusText,
|
|
4503
|
-
this.endpoint
|
|
4561
|
+
this.endpoint,
|
|
4562
|
+
requestPath(resp.url)
|
|
4504
4563
|
);
|
|
4505
4564
|
}
|
|
4506
4565
|
/**
|
|
@@ -4642,6 +4701,14 @@ function parseJson(bytes) {
|
|
|
4642
4701
|
const parsed = tryParseJson(bytes.toString("utf-8"));
|
|
4643
4702
|
return parsed === void 0 ? {} : parsed;
|
|
4644
4703
|
}
|
|
4704
|
+
function requestPath(url) {
|
|
4705
|
+
if (!url) return void 0;
|
|
4706
|
+
try {
|
|
4707
|
+
return new URL(url).pathname || void 0;
|
|
4708
|
+
} catch {
|
|
4709
|
+
return void 0;
|
|
4710
|
+
}
|
|
4711
|
+
}
|
|
4645
4712
|
async function safeText(resp) {
|
|
4646
4713
|
try {
|
|
4647
4714
|
return await resp.text();
|
|
@@ -35299,9 +35366,9 @@ var ZodUnion = class extends ZodType {
|
|
|
35299
35366
|
}
|
|
35300
35367
|
};
|
|
35301
35368
|
types.ZodUnion = ZodUnion;
|
|
35302
|
-
ZodUnion.create = (
|
|
35369
|
+
ZodUnion.create = (types3, params) => {
|
|
35303
35370
|
return new ZodUnion({
|
|
35304
|
-
options:
|
|
35371
|
+
options: types3,
|
|
35305
35372
|
typeName: ZodFirstPartyTypeKind.ZodUnion,
|
|
35306
35373
|
...processCreateParams(params)
|
|
35307
35374
|
});
|
|
@@ -38758,9 +38825,9 @@ _nodeUtil.exports;
|
|
|
38758
38825
|
var freeProcess = moduleExports && freeGlobal2.process;
|
|
38759
38826
|
var nodeUtil2 = (function() {
|
|
38760
38827
|
try {
|
|
38761
|
-
var
|
|
38762
|
-
if (
|
|
38763
|
-
return
|
|
38828
|
+
var types3 = freeModule && freeModule.require && freeModule.require("util").types;
|
|
38829
|
+
if (types3) {
|
|
38830
|
+
return types3;
|
|
38764
38831
|
}
|
|
38765
38832
|
return freeProcess && freeProcess.binding && freeProcess.binding("util");
|
|
38766
38833
|
} catch (e) {
|
|
@@ -43008,7 +43075,7 @@ async function commitMemoryVersion(plaintext, auth, opts) {
|
|
|
43008
43075
|
const chainOpts = await resolveChainOptsForOrg(opts, auth);
|
|
43009
43076
|
const client = await buildChainClient(chainOpts);
|
|
43010
43077
|
const { sigProvider } = buildSigner(auth);
|
|
43011
|
-
await client.signAndSendUniqueTransaction(
|
|
43078
|
+
const receipt = await client.signAndSendUniqueTransaction(
|
|
43012
43079
|
{
|
|
43013
43080
|
name: native.OP_ADD_AGENT_MEMORY,
|
|
43014
43081
|
args: [
|
|
@@ -43021,6 +43088,9 @@ async function commitMemoryVersion(plaintext, auth, opts) {
|
|
|
43021
43088
|
},
|
|
43022
43089
|
sigProvider
|
|
43023
43090
|
);
|
|
43091
|
+
if (receipt.status !== index.ResponseStatus.Confirmed) {
|
|
43092
|
+
throw new Error(`Memory transaction was not confirmed (status=${receipt.status})`);
|
|
43093
|
+
}
|
|
43024
43094
|
}
|
|
43025
43095
|
async function decryptRow(row, key3) {
|
|
43026
43096
|
const parsed = native.parseMemoryRow(gtvToFfiSafe(row));
|
|
@@ -43130,22 +43200,109 @@ async function rollbackMemory(toId, reason, auth, opts) {
|
|
|
43130
43200
|
const chainOpts = await resolveChainOptsForOrg(opts, auth);
|
|
43131
43201
|
const client = await buildChainClient(chainOpts);
|
|
43132
43202
|
const { sigProvider } = buildSigner(auth);
|
|
43133
|
-
await client.signAndSendUniqueTransaction(
|
|
43203
|
+
const receipt = await client.signAndSendUniqueTransaction(
|
|
43134
43204
|
{
|
|
43135
43205
|
name: native.OP_ROLLBACK_AGENT_MEMORY,
|
|
43136
43206
|
args: [toGtxBytes(args.agent_pubkey), args.to_id, args.reason]
|
|
43137
43207
|
},
|
|
43138
43208
|
sigProvider
|
|
43139
43209
|
);
|
|
43210
|
+
if (receipt.status !== index.ResponseStatus.Confirmed) {
|
|
43211
|
+
throw new Error(`Memory rollback was not confirmed (status=${receipt.status})`);
|
|
43212
|
+
}
|
|
43140
43213
|
}
|
|
43141
43214
|
|
|
43142
43215
|
// src-ts/memory/_json_safe.ts
|
|
43216
|
+
var import_node_util = require("util");
|
|
43217
|
+
var replacementKeys = [
|
|
43218
|
+
"newText",
|
|
43219
|
+
"new_string",
|
|
43220
|
+
"new_str",
|
|
43221
|
+
"newStr",
|
|
43222
|
+
"replacement"
|
|
43223
|
+
];
|
|
43224
|
+
var argumentKeys = [
|
|
43225
|
+
"file_path",
|
|
43226
|
+
"path",
|
|
43227
|
+
"filename",
|
|
43228
|
+
"target",
|
|
43229
|
+
"file",
|
|
43230
|
+
"filePath",
|
|
43231
|
+
"content",
|
|
43232
|
+
...replacementKeys,
|
|
43233
|
+
"value",
|
|
43234
|
+
"text"
|
|
43235
|
+
];
|
|
43236
|
+
var maxPrototypeDepth = 32;
|
|
43237
|
+
var maxEditSlots = 65536;
|
|
43238
|
+
var maxTextUnits = 16 * 1024 * 1024;
|
|
43239
|
+
function invalid() {
|
|
43240
|
+
throw new TypeError("Unreadable or unsupported memory classifier input");
|
|
43241
|
+
}
|
|
43242
|
+
function dataProperty(value, key3) {
|
|
43243
|
+
let current = value;
|
|
43244
|
+
for (let depth = 0; current !== null; depth++) {
|
|
43245
|
+
if (depth >= maxPrototypeDepth || import_node_util.types.isProxy(current)) invalid();
|
|
43246
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, key3);
|
|
43247
|
+
if (descriptor) {
|
|
43248
|
+
if (!("value" in descriptor)) invalid();
|
|
43249
|
+
return descriptor.value;
|
|
43250
|
+
}
|
|
43251
|
+
current = Object.getPrototypeOf(current);
|
|
43252
|
+
}
|
|
43253
|
+
return void 0;
|
|
43254
|
+
}
|
|
43143
43255
|
function toJsonSafe(value) {
|
|
43144
|
-
|
|
43145
|
-
|
|
43146
|
-
|
|
43147
|
-
|
|
43256
|
+
let textUnits = 0;
|
|
43257
|
+
function scalar(input) {
|
|
43258
|
+
if (input == null) return null;
|
|
43259
|
+
if (typeof input === "string") {
|
|
43260
|
+
textUnits += input.length;
|
|
43261
|
+
if (textUnits > maxTextUnits) invalid();
|
|
43262
|
+
return input;
|
|
43263
|
+
}
|
|
43264
|
+
if (typeof input === "boolean") return input;
|
|
43265
|
+
if (typeof input === "number" && Number.isFinite(input)) return input;
|
|
43266
|
+
return invalid();
|
|
43267
|
+
}
|
|
43268
|
+
function project(input, shape) {
|
|
43269
|
+
if (input == null || typeof input !== "object") return scalar(input);
|
|
43270
|
+
if (import_node_util.types.isProxy(input) || Array.isArray(input)) invalid();
|
|
43271
|
+
const output = /* @__PURE__ */ Object.create(null);
|
|
43272
|
+
const keys2 = shape === "envelope" ? ["toolName", "name"] : shape === "tool" ? ["name"] : shape === "args" ? argumentKeys : replacementKeys;
|
|
43273
|
+
for (const key3 of keys2) {
|
|
43274
|
+
const field = dataProperty(input, key3);
|
|
43275
|
+
if (field !== void 0) output[key3] = scalar(field);
|
|
43276
|
+
}
|
|
43277
|
+
if (shape === "envelope") {
|
|
43278
|
+
for (const key3 of ["params", "args", "arguments", "tool"]) {
|
|
43279
|
+
const field = dataProperty(input, key3);
|
|
43280
|
+
if (field !== void 0)
|
|
43281
|
+
output[key3] = project(field, key3 === "tool" ? "tool" : "args");
|
|
43282
|
+
}
|
|
43283
|
+
}
|
|
43284
|
+
if (shape === "args") {
|
|
43285
|
+
const edits = dataProperty(input, "edits");
|
|
43286
|
+
if (edits !== void 0) {
|
|
43287
|
+
if (edits !== null && typeof edits === "object" && import_node_util.types.isProxy(edits))
|
|
43288
|
+
invalid();
|
|
43289
|
+
if (Array.isArray(edits)) {
|
|
43290
|
+
const length = dataProperty(edits, "length");
|
|
43291
|
+
if (typeof length !== "number" || !Number.isInteger(length) || length < 0 || length > maxEditSlots)
|
|
43292
|
+
invalid();
|
|
43293
|
+
const entries = [];
|
|
43294
|
+
for (let index2 = 0; index2 < length; index2++) {
|
|
43295
|
+
entries.push(project(dataProperty(edits, String(index2)), "edit"));
|
|
43296
|
+
}
|
|
43297
|
+
output.edits = entries;
|
|
43298
|
+
} else {
|
|
43299
|
+
output.edits = scalar(edits);
|
|
43300
|
+
}
|
|
43301
|
+
}
|
|
43302
|
+
}
|
|
43303
|
+
return output;
|
|
43148
43304
|
}
|
|
43305
|
+
return project(value, "envelope");
|
|
43149
43306
|
}
|
|
43150
43307
|
|
|
43151
43308
|
// src-ts/memory/classifier.ts
|
|
@@ -43191,6 +43348,7 @@ async function guardMemoryWrite(input) {
|
|
|
43191
43348
|
endpoint,
|
|
43192
43349
|
verifyPubKey,
|
|
43193
43350
|
orgName,
|
|
43351
|
+
chainOpts,
|
|
43194
43352
|
threshold,
|
|
43195
43353
|
patterns,
|
|
43196
43354
|
toolNames,
|
|
@@ -43198,7 +43356,22 @@ async function guardMemoryWrite(input) {
|
|
|
43198
43356
|
debug: debug2 = false,
|
|
43199
43357
|
logger: logger2
|
|
43200
43358
|
} = input;
|
|
43201
|
-
|
|
43359
|
+
let memEntry;
|
|
43360
|
+
try {
|
|
43361
|
+
memEntry = classifyMemoryWrite(event, ctx, { patterns, toolNames });
|
|
43362
|
+
} catch (err) {
|
|
43363
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
43364
|
+
logger2?.warn?.("[atbash] memory classifier refused this tool call's input", { reason: why });
|
|
43365
|
+
if (!enforce) return { handled: false };
|
|
43366
|
+
return {
|
|
43367
|
+
handled: true,
|
|
43368
|
+
decision: {
|
|
43369
|
+
allow: false,
|
|
43370
|
+
block: true,
|
|
43371
|
+
reason: `Memory protection could not read this tool call (${why}); blocked because it could not be checked.`
|
|
43372
|
+
}
|
|
43373
|
+
};
|
|
43374
|
+
}
|
|
43202
43375
|
if (debug2) emitDebugProbe(event, ctx, memEntry, logger2);
|
|
43203
43376
|
if (!memEntry) return { handled: false };
|
|
43204
43377
|
let scanResult;
|
|
@@ -43232,23 +43405,41 @@ async function guardMemoryWrite(input) {
|
|
|
43232
43405
|
committed: false
|
|
43233
43406
|
};
|
|
43234
43407
|
}
|
|
43408
|
+
if (scanResult.verdict !== "green" && scanResult.verdict !== "yellow") {
|
|
43409
|
+
const reason = "Memory scan returned an unrecognized verdict.";
|
|
43410
|
+
return {
|
|
43411
|
+
handled: true,
|
|
43412
|
+
decision: enforce ? { allow: false, block: true, reason } : { allow: true, reason },
|
|
43413
|
+
scanResult,
|
|
43414
|
+
committed: false
|
|
43415
|
+
};
|
|
43416
|
+
}
|
|
43235
43417
|
const filePath = import_node_path4.default.basename(memEntry.key);
|
|
43236
|
-
|
|
43237
|
-
|
|
43238
|
-
|
|
43239
|
-
|
|
43240
|
-
|
|
43241
|
-
|
|
43242
|
-
|
|
43418
|
+
try {
|
|
43419
|
+
await commitMemoryVersion(memEntry.value, auth, {
|
|
43420
|
+
score: scanResult.score,
|
|
43421
|
+
filePath,
|
|
43422
|
+
orgName,
|
|
43423
|
+
endpoint,
|
|
43424
|
+
verifyPubKey,
|
|
43425
|
+
chainOpts
|
|
43426
|
+
});
|
|
43427
|
+
} catch (err) {
|
|
43243
43428
|
const reason = err instanceof Error ? err.message : String(err);
|
|
43244
43429
|
logger2?.warn?.("[atbash] memory commit to chain failed", {
|
|
43245
43430
|
path: memEntry.key,
|
|
43246
43431
|
filePath,
|
|
43247
43432
|
reason
|
|
43248
43433
|
});
|
|
43249
|
-
|
|
43434
|
+
return {
|
|
43435
|
+
handled: true,
|
|
43436
|
+
decision: enforce ? { allow: false, block: true, reason: `Memory commit failed: ${reason}` } : { allow: true, reason: `Memory commit failed: ${reason}` },
|
|
43437
|
+
scanResult,
|
|
43438
|
+
committed: false
|
|
43439
|
+
};
|
|
43440
|
+
}
|
|
43250
43441
|
logger2?.info?.(
|
|
43251
|
-
scanResult.verdict === "yellow" ? "[atbash] memory
|
|
43442
|
+
scanResult.verdict === "yellow" ? "[atbash] memory ALLOW (yellow, low score)" : "[atbash] memory ALLOW",
|
|
43252
43443
|
{
|
|
43253
43444
|
path: memEntry.key,
|
|
43254
43445
|
filePath,
|
|
@@ -43281,7 +43472,7 @@ async function syncLocalMemory(auth, pointer, opts = {}) {
|
|
|
43281
43472
|
if (withinTtl) {
|
|
43282
43473
|
return { drifted: false, checked: false, pointer };
|
|
43283
43474
|
}
|
|
43284
|
-
const currentId = await getActiveMemoryId(auth, opts.chainOpts);
|
|
43475
|
+
const currentId = await getActiveMemoryId(auth, opts.chainOpts, opts.filePath);
|
|
43285
43476
|
const nextPointer = { activeId: currentId, checkedAt: now };
|
|
43286
43477
|
if (currentId === pointer.activeId) {
|
|
43287
43478
|
return { drifted: false, checked: true, pointer: nextPointer };
|
|
@@ -43363,13 +43554,16 @@ function formatMeta(meta) {
|
|
|
43363
43554
|
}
|
|
43364
43555
|
function createFileLogger(filePath, upstream) {
|
|
43365
43556
|
let queue = Promise.resolve();
|
|
43557
|
+
let writeError;
|
|
43366
43558
|
async function ensureDir() {
|
|
43367
43559
|
await import_node_fs5.promises.mkdir(import_node_path6.default.dirname(filePath), { recursive: true });
|
|
43368
43560
|
}
|
|
43369
43561
|
function append(level, message, meta) {
|
|
43370
43562
|
const line = `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${message}${formatMeta(meta)}
|
|
43371
43563
|
`;
|
|
43372
|
-
queue = queue.then(ensureDir).then(() => import_node_fs5.promises.appendFile(filePath, line, "utf8")).catch(() => {
|
|
43564
|
+
queue = queue.then(ensureDir).then(() => import_node_fs5.promises.appendFile(filePath, line, "utf8")).catch((error4) => {
|
|
43565
|
+
writeError = error4 instanceof Error ? error4 : new Error(String(error4));
|
|
43566
|
+
upstream?.warn("[atbash] plugin log write failed", { error: writeError.message });
|
|
43373
43567
|
});
|
|
43374
43568
|
}
|
|
43375
43569
|
return {
|
|
@@ -43380,6 +43574,10 @@ function createFileLogger(filePath, upstream) {
|
|
|
43380
43574
|
warn(message, meta) {
|
|
43381
43575
|
upstream?.warn(message, meta ?? {});
|
|
43382
43576
|
append("warn", message, meta);
|
|
43577
|
+
},
|
|
43578
|
+
async flush() {
|
|
43579
|
+
await queue;
|
|
43580
|
+
if (writeError) throw writeError;
|
|
43383
43581
|
}
|
|
43384
43582
|
};
|
|
43385
43583
|
}
|
|
@@ -43469,7 +43667,15 @@ var MemoryGuardManager = class {
|
|
|
43469
43667
|
orgName: this.opts.orgName
|
|
43470
43668
|
});
|
|
43471
43669
|
const resolved = await atbash.resolveChainForOrg(this.opts.orgName);
|
|
43472
|
-
|
|
43670
|
+
if (!await atbash.checkAgentExists(this.agentPubkeyHex, { network: resolved.network })) {
|
|
43671
|
+
throw new Error(`agent is not registered on the resolved ${resolved.network} chain`);
|
|
43672
|
+
}
|
|
43673
|
+
const chainOpts = {
|
|
43674
|
+
...this.opts.chainOpts,
|
|
43675
|
+
network: resolved.network,
|
|
43676
|
+
blockchainRid: resolved.blockchainRid,
|
|
43677
|
+
nodeUrls: this.opts.chainOpts?.nodeUrls ?? resolved.nodeUrls
|
|
43678
|
+
};
|
|
43473
43679
|
this._resolvedChainOpts = chainOpts;
|
|
43474
43680
|
this.logger.info(
|
|
43475
43681
|
`[atbash] resolved org \u2192 chain \u2014 org=${this.opts.orgName} network=${resolved.network}`
|
|
@@ -43478,9 +43684,9 @@ var MemoryGuardManager = class {
|
|
|
43478
43684
|
} catch (err) {
|
|
43479
43685
|
const msg = err instanceof Error ? err.message : String(err);
|
|
43480
43686
|
this.logger.warn(
|
|
43481
|
-
`[atbash] org \u2192 chain resolution failed (
|
|
43687
|
+
`[atbash] org \u2192 chain resolution failed (memory read will be blocked): ${msg}`
|
|
43482
43688
|
);
|
|
43483
|
-
|
|
43689
|
+
throw err;
|
|
43484
43690
|
} finally {
|
|
43485
43691
|
this._resolveChainInflight = void 0;
|
|
43486
43692
|
}
|
|
@@ -43499,13 +43705,18 @@ var MemoryGuardManager = class {
|
|
|
43499
43705
|
const result = await syncLocalMemory(this.opts.auth, seed, {
|
|
43500
43706
|
ttlMs: 0,
|
|
43501
43707
|
force: true,
|
|
43502
|
-
chainOpts
|
|
43708
|
+
chainOpts,
|
|
43709
|
+
filePath: import_node_path7.default.basename(this.memoryFilePath)
|
|
43503
43710
|
});
|
|
43504
43711
|
if (!result.drifted && result.pointer.activeId == null) {
|
|
43505
43712
|
this.logger.info(
|
|
43506
43713
|
`[atbash] no active memory on chain for agent=${this.agentPubkeyHex.slice(0, 16)}\u2026 org=${this.opts.orgName ?? "(none)"} \u2014 either the agent isn't registered on this chain or hasn't written any memory. Sync will remain a no-op until a write lands.`
|
|
43507
43714
|
);
|
|
43508
43715
|
} else if (result.drifted && result.current) {
|
|
43716
|
+
if (result.current.filePath !== import_node_path7.default.basename(this.memoryFilePath) || result.current.isActive !== true || result.current.decryptError) {
|
|
43717
|
+
this.logger.warn("[atbash] boot sync REFUSED mismatched memory version");
|
|
43718
|
+
return;
|
|
43719
|
+
}
|
|
43509
43720
|
if (result.current.score < this.rollbackMinScore) {
|
|
43510
43721
|
this.logger.warn(
|
|
43511
43722
|
`[atbash] boot sync REFUSED refresh \u2014 id=${result.current.id} score=${result.current.score} below threshold ${this.rollbackMinScore}. Leaving MEMORY.md and pointer untouched; next memory read will be blocked.`
|
|
@@ -43519,7 +43730,8 @@ var MemoryGuardManager = class {
|
|
|
43519
43730
|
await this.writeMemoryAtomic(result.current.content);
|
|
43520
43731
|
} catch (err) {
|
|
43521
43732
|
const msg = err instanceof Error ? err.message : String(err);
|
|
43522
|
-
this.logger.warn("[atbash] boot sync write failed
|
|
43733
|
+
this.logger.warn("[atbash] boot sync write failed; pointer remains unchanged", { error: msg });
|
|
43734
|
+
return;
|
|
43523
43735
|
}
|
|
43524
43736
|
}
|
|
43525
43737
|
await this.pointerStore.set(this.agentPubkeyHex, result.pointer);
|
|
@@ -43533,16 +43745,30 @@ var MemoryGuardManager = class {
|
|
|
43533
43745
|
}
|
|
43534
43746
|
/**
|
|
43535
43747
|
* Returns a `HookDecision` when the guard reached a decision about this event.
|
|
43536
|
-
* Returns `null`
|
|
43537
|
-
*
|
|
43538
|
-
*
|
|
43748
|
+
* Returns `null` for non-memory events, other files this manager does not
|
|
43749
|
+
* own, or unverified observe-mode reads. An enforced read of the managed
|
|
43750
|
+
* file blocks when verification cannot complete.
|
|
43539
43751
|
*
|
|
43540
43752
|
* A returned decision carries `audited` (see `HookDecision`). Only
|
|
43541
43753
|
* `{ allow: true, audited: true }` means "checked and cleared"; anything else
|
|
43542
43754
|
* that allows is a call the host still needs to judge.
|
|
43543
43755
|
*/
|
|
43544
43756
|
async handleBeforeToolCall(event, ctx) {
|
|
43545
|
-
|
|
43757
|
+
let isMemoryRead;
|
|
43758
|
+
try {
|
|
43759
|
+
isMemoryRead = classifyMemoryRead(event, ctx, this.opts.memoryReadClassifier);
|
|
43760
|
+
} catch (err) {
|
|
43761
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
43762
|
+
this.logger.warn("[atbash] memory classifier refused this tool call's input", { reason: why });
|
|
43763
|
+
if (!this.enforce) return null;
|
|
43764
|
+
return {
|
|
43765
|
+
allow: false,
|
|
43766
|
+
block: true,
|
|
43767
|
+
blockReason: `Memory protection could not read this tool call (${why}); blocked because it could not be checked.`,
|
|
43768
|
+
audited: true
|
|
43769
|
+
};
|
|
43770
|
+
}
|
|
43771
|
+
if (isMemoryRead) {
|
|
43546
43772
|
this.logger.info("[atbash] memory read intercepted \u2014 running sync check");
|
|
43547
43773
|
return await this.handleMemoryRead(event, ctx);
|
|
43548
43774
|
}
|
|
@@ -43557,6 +43783,7 @@ var MemoryGuardManager = class {
|
|
|
43557
43783
|
endpoint: this.opts.judgeEndpoint,
|
|
43558
43784
|
verifyPubKey: this.opts.judgeVerifyPubKey,
|
|
43559
43785
|
orgName: this.opts.orgName,
|
|
43786
|
+
chainOpts: this.opts.chainOpts,
|
|
43560
43787
|
patterns: this.opts.memoryPathPatterns,
|
|
43561
43788
|
toolNames: this.opts.memoryWriteToolNames,
|
|
43562
43789
|
enforce: this.enforce,
|
|
@@ -43601,6 +43828,14 @@ var MemoryGuardManager = class {
|
|
|
43601
43828
|
reason: `memory scan returned ${sr2.verdict} but this guard is not enforcing it`
|
|
43602
43829
|
};
|
|
43603
43830
|
}
|
|
43831
|
+
if (guard.committed !== true) {
|
|
43832
|
+
return {
|
|
43833
|
+
allow: true,
|
|
43834
|
+
audited: false,
|
|
43835
|
+
verdict: sr2.verdict,
|
|
43836
|
+
reason: "memory scan passed but chain commit did not confirm"
|
|
43837
|
+
};
|
|
43838
|
+
}
|
|
43604
43839
|
return { allow: true, audited: true, verdict: sr2.verdict };
|
|
43605
43840
|
}
|
|
43606
43841
|
/**
|
|
@@ -43635,62 +43870,73 @@ var MemoryGuardManager = class {
|
|
|
43635
43870
|
return found.every((p) => import_node_path7.default.resolve(p) === managed);
|
|
43636
43871
|
}
|
|
43637
43872
|
async handleMemoryRead(event, ctx) {
|
|
43873
|
+
if (!this.vouchesForTarget(event, ctx)) return null;
|
|
43638
43874
|
const pointer = await this.pointerStore.get(this.agentPubkeyHex);
|
|
43639
43875
|
let result;
|
|
43876
|
+
let chainOpts;
|
|
43640
43877
|
try {
|
|
43641
|
-
|
|
43878
|
+
chainOpts = await this.resolveChainOpts();
|
|
43642
43879
|
result = await syncLocalMemory(this.opts.auth, pointer, {
|
|
43643
43880
|
ttlMs: this.ttlMs,
|
|
43644
|
-
|
|
43881
|
+
force: this.enforce,
|
|
43882
|
+
chainOpts,
|
|
43883
|
+
filePath: import_node_path7.default.basename(this.memoryFilePath)
|
|
43645
43884
|
});
|
|
43646
43885
|
} catch (err) {
|
|
43647
43886
|
if (err instanceof MemoryIntegrityError) {
|
|
43648
43887
|
const reason = `Memory integrity check failed on id ${err.id} \u2014 read blocked.`;
|
|
43649
43888
|
this.logger.warn("[atbash] MEMORY INTEGRITY FAILURE", { id: err.id, error: err.message });
|
|
43650
|
-
|
|
43651
|
-
return { block: true, blockReason: reason, allow: false, reason, audited: true };
|
|
43889
|
+
return this.unverifiedRead(reason);
|
|
43652
43890
|
}
|
|
43653
43891
|
const msg = err instanceof Error ? err.message : String(err);
|
|
43654
|
-
this.logger.warn("[atbash] memory sync failed
|
|
43655
|
-
return
|
|
43892
|
+
this.logger.warn("[atbash] memory sync failed", { error: msg });
|
|
43893
|
+
return this.unverifiedRead(`Memory chain sync failed: ${msg}`);
|
|
43656
43894
|
}
|
|
43657
|
-
|
|
43658
|
-
|
|
43659
|
-
|
|
43660
|
-
|
|
43661
|
-
if (fresh.score < this.rollbackMinScore) {
|
|
43662
|
-
const reason = `Rolled-back memory version #${fresh.id} scored ${fresh.score} (below threshold ${this.rollbackMinScore}) \u2014 read blocked.`;
|
|
43663
|
-
this.logger.warn("[atbash] blocking read on low-score rollback", { id: fresh.id, score: fresh.score });
|
|
43664
|
-
if (!this.enforce) return null;
|
|
43665
|
-
return { block: true, blockReason: reason, allow: false, reason, audited: true };
|
|
43666
|
-
}
|
|
43667
|
-
this.logger.info("[atbash] memory drift detected \u2014 refreshing local file", {
|
|
43668
|
-
id: fresh.id,
|
|
43669
|
-
score: fresh.score
|
|
43670
|
-
});
|
|
43671
|
-
try {
|
|
43672
|
-
await this.writeMemoryAtomic(fresh.content);
|
|
43673
|
-
} catch (err) {
|
|
43674
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
43675
|
-
this.logger.warn("[atbash] failed to write refreshed memory (serving old)", { error: msg });
|
|
43676
|
-
onDiskIsCurrent = false;
|
|
43677
|
-
}
|
|
43678
|
-
} else {
|
|
43679
|
-
this.logger.info("[atbash] active memory removed on chain", { pubkey: this.agentPubkeyHex });
|
|
43680
|
-
onDiskIsCurrent = false;
|
|
43681
|
-
}
|
|
43895
|
+
if (!result.checked) return this.unverifiedRead("Memory chain state was not checked.");
|
|
43896
|
+
const activeId = result.pointer.activeId;
|
|
43897
|
+
if (activeId === null) {
|
|
43898
|
+
return this.unverifiedRead("No active chain version exists for the managed memory file.");
|
|
43682
43899
|
}
|
|
43683
|
-
|
|
43684
|
-
|
|
43685
|
-
|
|
43686
|
-
|
|
43687
|
-
|
|
43688
|
-
);
|
|
43900
|
+
let active;
|
|
43901
|
+
try {
|
|
43902
|
+
active = result.drifted ? result.current : await getMemoryById(activeId, this.opts.auth, chainOpts);
|
|
43903
|
+
} catch (err) {
|
|
43904
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
43905
|
+
this.logger.warn("[atbash] active memory row could not be verified", { error: msg });
|
|
43906
|
+
return this.unverifiedRead(`Memory version could not be verified: ${msg}`);
|
|
43689
43907
|
}
|
|
43690
|
-
|
|
43691
|
-
if (!
|
|
43908
|
+
const filePath = import_node_path7.default.basename(this.memoryFilePath);
|
|
43909
|
+
if (!active || active.id !== activeId || active.filePath !== filePath || active.isActive !== true || active.decryptError) {
|
|
43910
|
+
return this.unverifiedRead("Active memory version does not match the managed file.");
|
|
43911
|
+
}
|
|
43912
|
+
if (active.score < this.rollbackMinScore) {
|
|
43913
|
+
return this.unverifiedRead(`Memory version #${active.id} is below the minimum safety score.`);
|
|
43914
|
+
}
|
|
43915
|
+
try {
|
|
43916
|
+
if (result.drifted) {
|
|
43917
|
+
this.logger.info("[atbash] memory drift detected \u2014 refreshing local file", { id: active.id });
|
|
43918
|
+
await this.writeMemoryAtomic(active.content);
|
|
43919
|
+
}
|
|
43920
|
+
const onDisk = await import_node_fs6.promises.readFile(this.memoryFilePath, "utf8");
|
|
43921
|
+
if (onDisk !== active.content) {
|
|
43922
|
+
return this.unverifiedRead("Local memory bytes do not match the active chain version.");
|
|
43923
|
+
}
|
|
43924
|
+
} catch (err) {
|
|
43925
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
43926
|
+
this.logger.warn("[atbash] managed memory file could not be verified", { error: msg });
|
|
43927
|
+
return this.unverifiedRead(`Managed memory file could not be verified: ${msg}`);
|
|
43928
|
+
}
|
|
43929
|
+
await this.pointerStore.set(this.agentPubkeyHex, result.pointer);
|
|
43692
43930
|
return { allow: true, audited: true };
|
|
43693
43931
|
}
|
|
43932
|
+
/** Complete queued audit-log writes before a host shuts down or removes its workspace. */
|
|
43933
|
+
async flushLogs() {
|
|
43934
|
+
await this.logger.flush();
|
|
43935
|
+
}
|
|
43936
|
+
unverifiedRead(reason) {
|
|
43937
|
+
if (!this.enforce) return null;
|
|
43938
|
+
return { block: true, blockReason: reason, allow: false, reason, audited: true };
|
|
43939
|
+
}
|
|
43694
43940
|
async writeMemoryAtomic(content) {
|
|
43695
43941
|
await import_node_fs6.promises.mkdir(import_node_path7.default.dirname(this.memoryFilePath), { recursive: true });
|
|
43696
43942
|
const tmp = `${this.memoryFilePath}.${process.pid}.tmp`;
|