@atbash/sdk 0.10.6-dev.0 → 0.10.9-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2856,6 +2856,7 @@ var src_ts_exports = {};
2856
2856
  __export(src_ts_exports, {
2857
2857
  Atbash: () => Atbash,
2858
2858
  AtbashAPIError: () => AtbashAPIError,
2859
+ BOOT_SYNC_HINT: () => BOOT_SYNC_HINT,
2859
2860
  DEFAULT_BLOCKCHAIN_RID: () => DEFAULT_BLOCKCHAIN_RID,
2860
2861
  DEFAULT_CHROMIA_NODE_URLS: () => DEFAULT_CHROMIA_NODE_URLS,
2861
2862
  DEFAULT_ENDPOINT: () => DEFAULT_ENDPOINT,
@@ -2863,11 +2864,16 @@ __export(src_ts_exports, {
2863
2864
  DEFAULT_MEMORY_READ_TOOL_NAMES: () => DEFAULT_MEMORY_READ_TOOL_NAMES,
2864
2865
  DEFAULT_MEMORY_WRITE_TOOL_NAMES: () => DEFAULT_MEMORY_WRITE_TOOL_NAMES,
2865
2866
  EciesDomain: () => EciesDomain,
2867
+ HttpClient: () => HttpClient,
2868
+ HttpTransportError: () => HttpTransportError,
2869
+ KEY_FILENAMES: () => KEY_FILENAMES,
2866
2870
  MemoryGuardManager: () => MemoryGuardManager,
2867
2871
  MemoryIntegrityError: () => MemoryIntegrityError,
2868
2872
  PointerStore: () => PointerStore,
2869
2873
  SignatureVerificationError: () => SignatureVerificationError,
2874
+ bootSyncFailureLine: () => bootSyncFailureLine,
2870
2875
  buildAllowedJudgeHosts: () => buildAllowedJudgeHosts,
2876
+ chooseKeyPath: () => chooseKeyPath,
2871
2877
  claimHashHex: () => claimHashHex,
2872
2878
  classifyMemoryRead: () => classifyMemoryRead,
2873
2879
  classifyMemoryWrite: () => classifyMemoryWrite,
@@ -2902,6 +2908,7 @@ __export(src_ts_exports, {
2902
2908
  isEnvelope: () => isEnvelope,
2903
2909
  isValidPrivateKey: () => isValidPrivateKey,
2904
2910
  keyFingerprintOf: () => keyFingerprintOf,
2911
+ keyPathCandidates: () => keyPathCandidates,
2905
2912
  loadAgent: () => loadAgent,
2906
2913
  loadAgentFromFile: () => loadAgentFromFile,
2907
2914
  loadUserConfig: () => loadUserConfig,
@@ -3129,24 +3136,97 @@ var HttpClient = class {
3129
3136
  });
3130
3137
  }
3131
3138
  async fetch(url, init4) {
3132
- return fetch(url, { ...init4, signal: AbortSignal.timeout(this.timeoutMs) });
3139
+ try {
3140
+ return await fetch(url, {
3141
+ ...init4,
3142
+ signal: AbortSignal.timeout(this.timeoutMs)
3143
+ });
3144
+ } catch (err) {
3145
+ throw classifyTransportError(err, url, init4.method ?? "GET", this.timeoutMs);
3146
+ }
3133
3147
  }
3134
3148
  };
3149
+ var HttpTransportError = class extends Error {
3150
+ kind;
3151
+ constructor(kind, message, options) {
3152
+ super(message, options);
3153
+ this.name = "HttpTransportError";
3154
+ this.kind = kind;
3155
+ }
3156
+ };
3157
+ function classifyTransportError(err, url, method, timeoutMs) {
3158
+ const name2 = err instanceof Error ? err.name : "";
3159
+ const cause = err instanceof Error ? err.cause : void 0;
3160
+ const code2 = cause && typeof cause === "object" && "code" in cause ? String(cause.code) : "";
3161
+ if (name2 === "TimeoutError") {
3162
+ return new HttpTransportError(
3163
+ "timeout",
3164
+ `${method} ${url} did not respond within ${timeoutMs} ms \u2014 the judge may be slow to boot or the LLM is under load; retry in a moment`,
3165
+ { cause: err }
3166
+ );
3167
+ }
3168
+ if (name2 === "AbortError") {
3169
+ return new HttpTransportError(
3170
+ "aborted",
3171
+ `${method} ${url} was cancelled by the caller`,
3172
+ { cause: err }
3173
+ );
3174
+ }
3175
+ if (code2 === "ENOTFOUND" || code2 === "EAI_AGAIN") {
3176
+ return new HttpTransportError(
3177
+ "dns",
3178
+ `could not resolve the judge hostname (${url}) \u2014 check the endpoint and DNS`,
3179
+ { cause: err }
3180
+ );
3181
+ }
3182
+ if (code2 === "ECONNREFUSED") {
3183
+ return new HttpTransportError(
3184
+ "connect_refused",
3185
+ `judge refused the connection (${url}) \u2014 the service may be down or restarting`,
3186
+ { cause: err }
3187
+ );
3188
+ }
3189
+ if (code2 === "ECONNRESET" || code2 === "EPIPE") {
3190
+ return new HttpTransportError(
3191
+ "connection_reset",
3192
+ `judge dropped the connection mid-request (${url}) \u2014 retry once`,
3193
+ { cause: err }
3194
+ );
3195
+ }
3196
+ return new HttpTransportError(
3197
+ "unknown",
3198
+ `${method} ${url} failed: ${err instanceof Error ? err.message : String(err)}`,
3199
+ { cause: err }
3200
+ );
3201
+ }
3135
3202
 
3136
3203
  // src-ts/keyLoader.ts
3137
3204
  var import_node_fs = require("fs");
3205
+
3206
+ // src-ts/key-path.ts
3138
3207
  var import_node_os = require("os");
3139
3208
  var import_node_path = require("path");
3140
- var DEFAULT_KEY_PATH_REL = ".config/atbash/guard-client-key";
3141
- function resolveKeyPath(input) {
3142
- if (input) return expandHome(input);
3143
- const home = process.env.HOME || (0, import_node_os.homedir)() || "";
3144
- return (0, import_node_path.join)(home, DEFAULT_KEY_PATH_REL);
3209
+ var KEY_DIR_REL = ".config/atbash";
3210
+ var KEY_FILENAMES = ["guard-client-key", "atbash-client-key"];
3211
+ function home() {
3212
+ return process.env.HOME || (0, import_node_os.homedir)() || "";
3145
3213
  }
3146
3214
  function expandHome(p) {
3147
3215
  if (!p.startsWith("~/")) return p;
3148
- const home = process.env.HOME || (0, import_node_os.homedir)() || "";
3149
- return (0, import_node_path.join)(home, p.slice(2));
3216
+ return (0, import_node_path.join)(home(), p.slice(2));
3217
+ }
3218
+ function keyPathCandidates() {
3219
+ return KEY_FILENAMES.map((name2) => (0, import_node_path.join)(home(), KEY_DIR_REL, name2));
3220
+ }
3221
+ function chooseKeyPath(input, exists) {
3222
+ if (input) return expandHome(input);
3223
+ const candidates = keyPathCandidates();
3224
+ return candidates.find(exists) ?? candidates[0];
3225
+ }
3226
+
3227
+ // src-ts/keyLoader.ts
3228
+ function resolveKeyPath(input) {
3229
+ return chooseKeyPath(input, import_node_fs.existsSync);
3150
3230
  }
3151
3231
  function readKeyFile(keyPath) {
3152
3232
  const content = String((0, import_node_fs.readFileSync)(keyPath, "utf8") || "").trim();
@@ -3176,6 +3256,12 @@ function readKeyFile(keyPath) {
3176
3256
  }
3177
3257
  function loadAgentFromFile(keyPath) {
3178
3258
  const resolved = resolveKeyPath(keyPath);
3259
+ if (!(0, import_node_fs.existsSync)(resolved)) {
3260
+ const looked = keyPath ? [resolved] : keyPathCandidates();
3261
+ throw new Error(
3262
+ `atbash key file not found. Looked for: ${looked.join(", ")}`
3263
+ );
3264
+ }
3179
3265
  const { privKey } = readKeyFile(resolved);
3180
3266
  return native.loadAgent(privKey);
3181
3267
  }
@@ -3218,8 +3304,8 @@ var durationHistogram = null;
3218
3304
  var defaultSource = "sdk";
3219
3305
  function isTelemetryOptedOut() {
3220
3306
  try {
3221
- const home = process.env.HOME || (0, import_node_os2.homedir)() || "";
3222
- const filePath = (0, import_node_path2.join)(home, ".config", "atbash", "telemetry.json");
3307
+ const home2 = process.env.HOME || (0, import_node_os2.homedir)() || "";
3308
+ const filePath = (0, import_node_path2.join)(home2, ".config", "atbash", "telemetry.json");
3223
3309
  const raw2 = (0, import_node_fs2.readFileSync)(filePath, "utf-8").trim();
3224
3310
  if (!raw2) return false;
3225
3311
  const config2 = JSON.parse(raw2);
@@ -3304,11 +3390,12 @@ var ENV_MAP = {
3304
3390
  judgeEndpoint: "ATBASH_ENDPOINT",
3305
3391
  blockchainRid: "ATBASH_BLOCKCHAIN_RID",
3306
3392
  provider: "ATBASH_PROVIDER",
3307
- providerModel: "ATBASH_PROVIDER_MODEL"
3393
+ providerModel: "ATBASH_PROVIDER_MODEL",
3394
+ debug: "ATBASH_DEBUG"
3308
3395
  };
3309
3396
  function getConfigDir() {
3310
- const home = process.env.HOME || (0, import_node_os3.homedir)() || "";
3311
- return (0, import_node_path3.join)(home, ".config", "atbash");
3397
+ const home2 = process.env.HOME || (0, import_node_os3.homedir)() || "";
3398
+ return (0, import_node_path3.join)(home2, ".config", "atbash");
3312
3399
  }
3313
3400
  function getConfigPath() {
3314
3401
  return (0, import_node_path3.join)(getConfigDir(), "config.json");
@@ -3367,6 +3454,7 @@ var Atbash = class _Atbash {
3367
3454
  failClosed;
3368
3455
  /** Org key learned from the last agent-exists check, for this agent only. */
3369
3456
  _orgKeyFromChain = null;
3457
+ debug;
3370
3458
  logger;
3371
3459
  http;
3372
3460
  /**
@@ -3380,6 +3468,8 @@ var Atbash = class _Atbash {
3380
3468
  * server-side replay protection windows never expire it mid-session.
3381
3469
  */
3382
3470
  _authBearer = null;
3471
+ /** Guards `logEnvironmentOnce` — hosts construct several clients. */
3472
+ static environmentLogged = false;
3383
3473
  constructor(privkey, options = {}) {
3384
3474
  this.auth = native.loadAgent(privkey);
3385
3475
  this.endpoint = (options.endpoint ?? DEFAULT_ENDPOINT).replace(/\/+$/, "") || DEFAULT_ENDPOINT;
@@ -3389,8 +3479,10 @@ var Atbash = class _Atbash {
3389
3479
  this.verifyPubKey = options.verifyPubKey;
3390
3480
  this.orgEncryptionPubKey = options.orgEncryptionPubKey;
3391
3481
  this.failClosed = options.failClosed !== false;
3482
+ this.debug = options.debug === true;
3392
3483
  this.logger = options.logger ?? {};
3393
- this.http = new HttpClient(this.endpoint, options.timeoutMs ?? 3e4);
3484
+ this.http = new HttpClient(this.endpoint, options.timeoutMs ?? 6e4);
3485
+ this.logEnvironmentOnce();
3394
3486
  if (this.endpoint !== DEFAULT_ENDPOINT) {
3395
3487
  this.logger.warn?.("[atbash] running on non-default judge endpoint", {
3396
3488
  endpoint: this.endpoint,
@@ -3398,6 +3490,31 @@ var Atbash = class _Atbash {
3398
3490
  });
3399
3491
  }
3400
3492
  }
3493
+ /**
3494
+ * Say which environment this build talks to, once per process.
3495
+ *
3496
+ * The endpoint and both chain RIDs are compiled in, chosen by the npm tag,
3497
+ * and no configuration repoints a released build. So installing the build
3498
+ * for the wrong environment is invisible: the plugin loads, the hook fires,
3499
+ * and every judge call fails because the agent does not exist on the chain
3500
+ * this build targets. Organisation names are not unique across environments
3501
+ * either, so an org resolving is not evidence the build is right.
3502
+ */
3503
+ logEnvironmentOnce() {
3504
+ if (_Atbash.environmentLogged) return;
3505
+ _Atbash.environmentLogged = true;
3506
+ const brief = (rid) => rid ? `${rid.slice(0, 8)}\u2026` : "(unset)";
3507
+ this.logger.info?.(
3508
+ `[atbash] environment \u2014 judge=${this.endpoint} publicChain=${brief(native.DEFAULT_BLOCKCHAIN_RID)} privateChain=${brief(native.DEFAULT_PRIVATE_BLOCKCHAIN_RID)} activeChain=${brief(this.blockchainRid)} responseSignatureCheck=${this.verifyPubKey ? "on" : "off"}`,
3509
+ {
3510
+ judgeEndpoint: this.endpoint,
3511
+ publicBlockchainRid: native.DEFAULT_BLOCKCHAIN_RID,
3512
+ privateBlockchainRid: native.DEFAULT_PRIVATE_BLOCKCHAIN_RID,
3513
+ activeBlockchainRid: this.blockchainRid,
3514
+ responseSignatureCheck: Boolean(this.verifyPubKey)
3515
+ }
3516
+ );
3517
+ }
3401
3518
  /**
3402
3519
  * Construct from resolved config: explicit overrides → env vars → the
3403
3520
  * `~/.config/atbash/config.json` file (see userConfig.resolve). The private
@@ -3421,6 +3538,9 @@ var Atbash = class _Atbash {
3421
3538
  orgName: options.orgName,
3422
3539
  verifyPubKey: validated.verifyPubKey ?? void 0,
3423
3540
  failClosed: options.failClosed,
3541
+ // ATBASH_DEBUG lets an operator turn diagnostics on without editing a
3542
+ // host's plugin config, which is usually the harder half.
3543
+ debug: options.debug ?? /^(1|true|yes)$/i.test(resolve("debug")),
3424
3544
  logger: options.logger
3425
3545
  });
3426
3546
  }
@@ -3666,8 +3786,9 @@ var Atbash = class _Atbash {
3666
3786
  });
3667
3787
  if (result.verdict === "No verdict") {
3668
3788
  if (result.status !== "logged") {
3669
- return this.fail(
3789
+ return this.failJudge(
3670
3790
  `judge returned no verdict without an audit-tier marker (status: ${result.status || "absent"})`,
3791
+ void 0,
3671
3792
  result.toolCallId
3672
3793
  );
3673
3794
  }
@@ -3719,16 +3840,38 @@ var Atbash = class _Atbash {
3719
3840
  toolCallId: result.toolCallId
3720
3841
  };
3721
3842
  }
3722
- return this.fail(
3843
+ return this.failJudge(
3723
3844
  "unrecognized action_type from judge",
3845
+ void 0,
3724
3846
  result.toolCallId
3725
3847
  );
3726
3848
  } catch (err) {
3727
- const message = errorMessage(err);
3728
- this.logger.warn?.("[atbash] judge API failed", { reason: message });
3729
- return this.fail(message);
3849
+ return this.failJudge(errorMessage(err), err);
3730
3850
  }
3731
3851
  }
3852
+ /**
3853
+ * One exit for every judge failure.
3854
+ *
3855
+ * Status and reason go in the *message*, not only in the meta object: hosts
3856
+ * print the message and drop the meta, which is why this read as a bare
3857
+ * "judge API failed" while the judge was answering with a precise reason.
3858
+ * The response body follows only under `debug`, since it can echo the action.
3859
+ */
3860
+ failJudge(reason, cause, toolCallId) {
3861
+ const api2 = cause instanceof AtbashAPIError ? cause : null;
3862
+ const status = api2 ? ` status=${api2.status || "no-response"}` : "";
3863
+ const body = this.debug && api2?.body ? ` body=${truncate(api2.body, 500)}` : "";
3864
+ this.logger.warn?.(
3865
+ `[atbash] judge API failed \u2014${status} reason=${truncate(reason, 300)}${body}`,
3866
+ {
3867
+ reason,
3868
+ ...api2 ? { status: api2.status, body: api2.body } : {},
3869
+ endpoint: this.endpoint,
3870
+ ...toolCallId ? { toolCallId } : {}
3871
+ }
3872
+ );
3873
+ return this.fail(reason, toolCallId);
3874
+ }
3732
3875
  fail(reason, toolCallId) {
3733
3876
  return { allow: !this.failClosed, verdict: "ERROR", reason, toolCallId };
3734
3877
  }
@@ -4069,8 +4212,27 @@ var Atbash = class _Atbash {
4069
4212
  this.endpoint
4070
4213
  );
4071
4214
  }
4072
- /** Wrap a *transport* failure (fetch threw, no response) as an AtbashAPIError. */
4215
+ /**
4216
+ * Wrap a transport failure (fetch threw, no response) as an AtbashAPIError.
4217
+ *
4218
+ * `HttpTransportError.kind` names the cause; the message is already
4219
+ * human-readable. `debug` echoes the original exception so operators can
4220
+ * cross-reference with node / undici logs when a class doesn't match.
4221
+ */
4073
4222
  transportError(err) {
4223
+ if (err instanceof HttpTransportError) {
4224
+ if (this.debug) {
4225
+ this.logger.warn?.(
4226
+ `[atbash] transport failed \u2014 kind=${err.kind}`,
4227
+ {
4228
+ kind: err.kind,
4229
+ cause: err.cause instanceof Error ? err.cause.message : String(err.cause ?? ""),
4230
+ endpoint: this.endpoint
4231
+ }
4232
+ );
4233
+ }
4234
+ return new AtbashAPIError(0, err.message, "", this.endpoint);
4235
+ }
4074
4236
  return new AtbashAPIError(0, errorMessage(err), "", this.endpoint);
4075
4237
  }
4076
4238
  async json(resp) {
@@ -4216,9 +4378,9 @@ function stringifyArgs(args) {
4216
4378
  }
4217
4379
  }
4218
4380
  var MAX_ACTION_LEN = 4e3;
4219
- function truncate(text) {
4220
- if (text.length <= MAX_ACTION_LEN) return text;
4221
- return text.slice(0, MAX_ACTION_LEN) + "\u2026";
4381
+ function truncate(text, limit = MAX_ACTION_LEN) {
4382
+ if (text.length <= limit) return text;
4383
+ return text.slice(0, limit) + "\u2026";
4222
4384
  }
4223
4385
 
4224
4386
  // src-ts/redact.ts
@@ -4261,6 +4423,17 @@ function verifyJudgeResponseSignature(bodyBytes, signatureHex, pubKeyHex) {
4261
4423
  };
4262
4424
  }
4263
4425
 
4426
+ // src-ts/memory/boot-sync-message.ts
4427
+ var BOOT_SYNC_HINT = "check the chain endpoint and orgName if the cause above does not explain it";
4428
+ function bootSyncFailureLine(cause) {
4429
+ const reason = cause instanceof Error ? cause.message : String(cause);
4430
+ const trimmed = reason.trim();
4431
+ return trimmed ? `[atbash] boot memory sync failed: ${trimmed}` : (
4432
+ // No cause to show: fall back to the advice rather than a bare colon.
4433
+ `[atbash] boot memory sync failed \u2014 ${BOOT_SYNC_HINT}`
4434
+ );
4435
+ }
4436
+
4264
4437
  // src-ts/memory/crypto.ts
4265
4438
  async function deriveMemoryKey(privkey) {
4266
4439
  return native.deriveMemoryKey(privkey);
@@ -4286,6 +4459,19 @@ async function scanMemory(entry, auth, opts) {
4286
4459
  toolArgsJson: opts?.toolArgsJson ?? JSON.stringify({ key: entry.key, source: entry.source }),
4287
4460
  mode: "memory-scan"
4288
4461
  });
4462
+ const knownAction = result.actionType === "allow" || result.actionType === "block" || result.actionType === "hold_for_user_confirm";
4463
+ const missingVerdict = result.verdict === "No verdict" && result.status !== "logged";
4464
+ const unknownAction = result.actionType !== "" && !knownAction;
4465
+ if (missingVerdict || unknownAction) {
4466
+ return {
4467
+ safe: false,
4468
+ verdict: "red",
4469
+ reason: unknownAction ? `judge returned unrecognised action_type "${result.actionType}"` : "judge returned no verdict",
4470
+ confidence: result.confidence,
4471
+ score: native.defaultScoreForVerdict("red"),
4472
+ toolCallId: result.toolCallId
4473
+ };
4474
+ }
4289
4475
  const verdict = native.mapVerdict(
4290
4476
  result.actionType,
4291
4477
  result.confidence,
@@ -42436,6 +42622,10 @@ var index = /* @__PURE__ */ getDefaultExportFromCjs(builtExports);
42436
42622
 
42437
42623
  // src-ts/memory/chain.ts
42438
42624
  var { createClient, encryption: encryption2, newSignatureProvider: newSignatureProvider2, Buffer: PolyBuffer } = index;
42625
+ var FAILOVER_CONFIG = {
42626
+ strategy: "tryNextOnError",
42627
+ attemptsPerEndpoint: 1
42628
+ };
42439
42629
  function toGtxBytes(bytes) {
42440
42630
  return PolyBuffer.from(new Uint8Array(bytes));
42441
42631
  }
@@ -42465,7 +42655,11 @@ function materializeChain(chainOpts) {
42465
42655
  }
42466
42656
  async function buildChainClient(chainOpts) {
42467
42657
  const { nodeUrls, blockchainRid } = materializeChain(chainOpts);
42468
- return createClient({ nodeUrlPool: [...nodeUrls], blockchainRid });
42658
+ return createClient({
42659
+ nodeUrlPool: [...nodeUrls],
42660
+ blockchainRid,
42661
+ failOverConfig: FAILOVER_CONFIG
42662
+ });
42469
42663
  }
42470
42664
  function buildSigner(auth) {
42471
42665
  const privKeyBuf = Buffer.from(auth.privkey, "hex");
@@ -42903,7 +43097,10 @@ var MemoryGuardManager = class {
42903
43097
  await this.pointerStore.set(this.agentPubkeyHex, result.pointer);
42904
43098
  } catch (err) {
42905
43099
  const msg = err instanceof Error ? err.message : String(err);
42906
- this.logger.warn("[atbash] boot memory sync failed \u2014 check chain endpoint / orgName", { error: msg });
43100
+ this.logger.warn(bootSyncFailureLine(err), {
43101
+ error: msg,
43102
+ hint: BOOT_SYNC_HINT
43103
+ });
42907
43104
  }
42908
43105
  }
42909
43106
  /**
@@ -43132,6 +43329,7 @@ function diffMemorySnapshots(before, after) {
43132
43329
  0 && (module.exports = {
43133
43330
  Atbash,
43134
43331
  AtbashAPIError,
43332
+ BOOT_SYNC_HINT,
43135
43333
  DEFAULT_BLOCKCHAIN_RID,
43136
43334
  DEFAULT_CHROMIA_NODE_URLS,
43137
43335
  DEFAULT_ENDPOINT,
@@ -43139,11 +43337,16 @@ function diffMemorySnapshots(before, after) {
43139
43337
  DEFAULT_MEMORY_READ_TOOL_NAMES,
43140
43338
  DEFAULT_MEMORY_WRITE_TOOL_NAMES,
43141
43339
  EciesDomain,
43340
+ HttpClient,
43341
+ HttpTransportError,
43342
+ KEY_FILENAMES,
43142
43343
  MemoryGuardManager,
43143
43344
  MemoryIntegrityError,
43144
43345
  PointerStore,
43145
43346
  SignatureVerificationError,
43347
+ bootSyncFailureLine,
43146
43348
  buildAllowedJudgeHosts,
43349
+ chooseKeyPath,
43147
43350
  claimHashHex,
43148
43351
  classifyMemoryRead,
43149
43352
  classifyMemoryWrite,
@@ -43178,6 +43381,7 @@ function diffMemorySnapshots(before, after) {
43178
43381
  isEnvelope,
43179
43382
  isValidPrivateKey,
43180
43383
  keyFingerprintOf,
43384
+ keyPathCandidates,
43181
43385
  loadAgent,
43182
43386
  loadAgentFromFile,
43183
43387
  loadUserConfig,