@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.mjs CHANGED
@@ -3046,24 +3046,97 @@ var HttpClient = class {
3046
3046
  });
3047
3047
  }
3048
3048
  async fetch(url, init4) {
3049
- return fetch(url, { ...init4, signal: AbortSignal.timeout(this.timeoutMs) });
3049
+ try {
3050
+ return await fetch(url, {
3051
+ ...init4,
3052
+ signal: AbortSignal.timeout(this.timeoutMs)
3053
+ });
3054
+ } catch (err) {
3055
+ throw classifyTransportError(err, url, init4.method ?? "GET", this.timeoutMs);
3056
+ }
3050
3057
  }
3051
3058
  };
3059
+ var HttpTransportError = class extends Error {
3060
+ kind;
3061
+ constructor(kind, message, options) {
3062
+ super(message, options);
3063
+ this.name = "HttpTransportError";
3064
+ this.kind = kind;
3065
+ }
3066
+ };
3067
+ function classifyTransportError(err, url, method, timeoutMs) {
3068
+ const name2 = err instanceof Error ? err.name : "";
3069
+ const cause = err instanceof Error ? err.cause : void 0;
3070
+ const code2 = cause && typeof cause === "object" && "code" in cause ? String(cause.code) : "";
3071
+ if (name2 === "TimeoutError") {
3072
+ return new HttpTransportError(
3073
+ "timeout",
3074
+ `${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`,
3075
+ { cause: err }
3076
+ );
3077
+ }
3078
+ if (name2 === "AbortError") {
3079
+ return new HttpTransportError(
3080
+ "aborted",
3081
+ `${method} ${url} was cancelled by the caller`,
3082
+ { cause: err }
3083
+ );
3084
+ }
3085
+ if (code2 === "ENOTFOUND" || code2 === "EAI_AGAIN") {
3086
+ return new HttpTransportError(
3087
+ "dns",
3088
+ `could not resolve the judge hostname (${url}) \u2014 check the endpoint and DNS`,
3089
+ { cause: err }
3090
+ );
3091
+ }
3092
+ if (code2 === "ECONNREFUSED") {
3093
+ return new HttpTransportError(
3094
+ "connect_refused",
3095
+ `judge refused the connection (${url}) \u2014 the service may be down or restarting`,
3096
+ { cause: err }
3097
+ );
3098
+ }
3099
+ if (code2 === "ECONNRESET" || code2 === "EPIPE") {
3100
+ return new HttpTransportError(
3101
+ "connection_reset",
3102
+ `judge dropped the connection mid-request (${url}) \u2014 retry once`,
3103
+ { cause: err }
3104
+ );
3105
+ }
3106
+ return new HttpTransportError(
3107
+ "unknown",
3108
+ `${method} ${url} failed: ${err instanceof Error ? err.message : String(err)}`,
3109
+ { cause: err }
3110
+ );
3111
+ }
3052
3112
 
3053
3113
  // src-ts/keyLoader.ts
3054
- import { readFileSync } from "fs";
3114
+ import { existsSync, readFileSync } from "fs";
3115
+
3116
+ // src-ts/key-path.ts
3055
3117
  import { homedir } from "os";
3056
3118
  import { join } from "path";
3057
- var DEFAULT_KEY_PATH_REL = ".config/atbash/guard-client-key";
3058
- function resolveKeyPath(input) {
3059
- if (input) return expandHome(input);
3060
- const home = process.env.HOME || homedir() || "";
3061
- return join(home, DEFAULT_KEY_PATH_REL);
3119
+ var KEY_DIR_REL = ".config/atbash";
3120
+ var KEY_FILENAMES = ["guard-client-key", "atbash-client-key"];
3121
+ function home() {
3122
+ return process.env.HOME || homedir() || "";
3062
3123
  }
3063
3124
  function expandHome(p) {
3064
3125
  if (!p.startsWith("~/")) return p;
3065
- const home = process.env.HOME || homedir() || "";
3066
- return join(home, p.slice(2));
3126
+ return join(home(), p.slice(2));
3127
+ }
3128
+ function keyPathCandidates() {
3129
+ return KEY_FILENAMES.map((name2) => join(home(), KEY_DIR_REL, name2));
3130
+ }
3131
+ function chooseKeyPath(input, exists) {
3132
+ if (input) return expandHome(input);
3133
+ const candidates = keyPathCandidates();
3134
+ return candidates.find(exists) ?? candidates[0];
3135
+ }
3136
+
3137
+ // src-ts/keyLoader.ts
3138
+ function resolveKeyPath(input) {
3139
+ return chooseKeyPath(input, existsSync);
3067
3140
  }
3068
3141
  function readKeyFile(keyPath) {
3069
3142
  const content = String(readFileSync(keyPath, "utf8") || "").trim();
@@ -3093,6 +3166,12 @@ function readKeyFile(keyPath) {
3093
3166
  }
3094
3167
  function loadAgentFromFile(keyPath) {
3095
3168
  const resolved = resolveKeyPath(keyPath);
3169
+ if (!existsSync(resolved)) {
3170
+ const looked = keyPath ? [resolved] : keyPathCandidates();
3171
+ throw new Error(
3172
+ `atbash key file not found. Looked for: ${looked.join(", ")}`
3173
+ );
3174
+ }
3096
3175
  const { privKey } = readKeyFile(resolved);
3097
3176
  return native.loadAgent(privKey);
3098
3177
  }
@@ -3138,8 +3217,8 @@ var durationHistogram = null;
3138
3217
  var defaultSource = "sdk";
3139
3218
  function isTelemetryOptedOut() {
3140
3219
  try {
3141
- const home = process.env.HOME || homedir2() || "";
3142
- const filePath = join2(home, ".config", "atbash", "telemetry.json");
3220
+ const home2 = process.env.HOME || homedir2() || "";
3221
+ const filePath = join2(home2, ".config", "atbash", "telemetry.json");
3143
3222
  const raw2 = readFileSync2(filePath, "utf-8").trim();
3144
3223
  if (!raw2) return false;
3145
3224
  const config2 = JSON.parse(raw2);
@@ -3217,7 +3296,7 @@ async function shutdownTelemetry() {
3217
3296
  // src-ts/userConfig.ts
3218
3297
  import {
3219
3298
  chmodSync,
3220
- existsSync,
3299
+ existsSync as existsSync2,
3221
3300
  mkdirSync,
3222
3301
  readFileSync as readFileSync3,
3223
3302
  writeFileSync
@@ -3230,11 +3309,12 @@ var ENV_MAP = {
3230
3309
  judgeEndpoint: "ATBASH_ENDPOINT",
3231
3310
  blockchainRid: "ATBASH_BLOCKCHAIN_RID",
3232
3311
  provider: "ATBASH_PROVIDER",
3233
- providerModel: "ATBASH_PROVIDER_MODEL"
3312
+ providerModel: "ATBASH_PROVIDER_MODEL",
3313
+ debug: "ATBASH_DEBUG"
3234
3314
  };
3235
3315
  function getConfigDir() {
3236
- const home = process.env.HOME || homedir3() || "";
3237
- return join3(home, ".config", "atbash");
3316
+ const home2 = process.env.HOME || homedir3() || "";
3317
+ return join3(home2, ".config", "atbash");
3238
3318
  }
3239
3319
  function getConfigPath() {
3240
3320
  return join3(getConfigDir(), "config.json");
@@ -3242,7 +3322,7 @@ function getConfigPath() {
3242
3322
  function loadUserConfig() {
3243
3323
  try {
3244
3324
  const p = getConfigPath();
3245
- if (!existsSync(p)) return {};
3325
+ if (!existsSync2(p)) return {};
3246
3326
  const raw2 = readFileSync3(p, "utf-8").trim();
3247
3327
  if (!raw2) return {};
3248
3328
  return JSON.parse(raw2);
@@ -3253,7 +3333,7 @@ function loadUserConfig() {
3253
3333
  }
3254
3334
  function saveUserConfig(config2) {
3255
3335
  const dir = getConfigDir();
3256
- if (!existsSync(dir)) {
3336
+ if (!existsSync2(dir)) {
3257
3337
  mkdirSync(dir, { recursive: true, mode: 448 });
3258
3338
  }
3259
3339
  const filePath = getConfigPath();
@@ -3293,6 +3373,7 @@ var Atbash = class _Atbash {
3293
3373
  failClosed;
3294
3374
  /** Org key learned from the last agent-exists check, for this agent only. */
3295
3375
  _orgKeyFromChain = null;
3376
+ debug;
3296
3377
  logger;
3297
3378
  http;
3298
3379
  /**
@@ -3306,6 +3387,8 @@ var Atbash = class _Atbash {
3306
3387
  * server-side replay protection windows never expire it mid-session.
3307
3388
  */
3308
3389
  _authBearer = null;
3390
+ /** Guards `logEnvironmentOnce` — hosts construct several clients. */
3391
+ static environmentLogged = false;
3309
3392
  constructor(privkey, options = {}) {
3310
3393
  this.auth = native.loadAgent(privkey);
3311
3394
  this.endpoint = (options.endpoint ?? DEFAULT_ENDPOINT).replace(/\/+$/, "") || DEFAULT_ENDPOINT;
@@ -3315,8 +3398,10 @@ var Atbash = class _Atbash {
3315
3398
  this.verifyPubKey = options.verifyPubKey;
3316
3399
  this.orgEncryptionPubKey = options.orgEncryptionPubKey;
3317
3400
  this.failClosed = options.failClosed !== false;
3401
+ this.debug = options.debug === true;
3318
3402
  this.logger = options.logger ?? {};
3319
- this.http = new HttpClient(this.endpoint, options.timeoutMs ?? 3e4);
3403
+ this.http = new HttpClient(this.endpoint, options.timeoutMs ?? 6e4);
3404
+ this.logEnvironmentOnce();
3320
3405
  if (this.endpoint !== DEFAULT_ENDPOINT) {
3321
3406
  this.logger.warn?.("[atbash] running on non-default judge endpoint", {
3322
3407
  endpoint: this.endpoint,
@@ -3324,6 +3409,31 @@ var Atbash = class _Atbash {
3324
3409
  });
3325
3410
  }
3326
3411
  }
3412
+ /**
3413
+ * Say which environment this build talks to, once per process.
3414
+ *
3415
+ * The endpoint and both chain RIDs are compiled in, chosen by the npm tag,
3416
+ * and no configuration repoints a released build. So installing the build
3417
+ * for the wrong environment is invisible: the plugin loads, the hook fires,
3418
+ * and every judge call fails because the agent does not exist on the chain
3419
+ * this build targets. Organisation names are not unique across environments
3420
+ * either, so an org resolving is not evidence the build is right.
3421
+ */
3422
+ logEnvironmentOnce() {
3423
+ if (_Atbash.environmentLogged) return;
3424
+ _Atbash.environmentLogged = true;
3425
+ const brief = (rid) => rid ? `${rid.slice(0, 8)}\u2026` : "(unset)";
3426
+ this.logger.info?.(
3427
+ `[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"}`,
3428
+ {
3429
+ judgeEndpoint: this.endpoint,
3430
+ publicBlockchainRid: native.DEFAULT_BLOCKCHAIN_RID,
3431
+ privateBlockchainRid: native.DEFAULT_PRIVATE_BLOCKCHAIN_RID,
3432
+ activeBlockchainRid: this.blockchainRid,
3433
+ responseSignatureCheck: Boolean(this.verifyPubKey)
3434
+ }
3435
+ );
3436
+ }
3327
3437
  /**
3328
3438
  * Construct from resolved config: explicit overrides → env vars → the
3329
3439
  * `~/.config/atbash/config.json` file (see userConfig.resolve). The private
@@ -3347,6 +3457,9 @@ var Atbash = class _Atbash {
3347
3457
  orgName: options.orgName,
3348
3458
  verifyPubKey: validated.verifyPubKey ?? void 0,
3349
3459
  failClosed: options.failClosed,
3460
+ // ATBASH_DEBUG lets an operator turn diagnostics on without editing a
3461
+ // host's plugin config, which is usually the harder half.
3462
+ debug: options.debug ?? /^(1|true|yes)$/i.test(resolve("debug")),
3350
3463
  logger: options.logger
3351
3464
  });
3352
3465
  }
@@ -3592,8 +3705,9 @@ var Atbash = class _Atbash {
3592
3705
  });
3593
3706
  if (result.verdict === "No verdict") {
3594
3707
  if (result.status !== "logged") {
3595
- return this.fail(
3708
+ return this.failJudge(
3596
3709
  `judge returned no verdict without an audit-tier marker (status: ${result.status || "absent"})`,
3710
+ void 0,
3597
3711
  result.toolCallId
3598
3712
  );
3599
3713
  }
@@ -3645,16 +3759,38 @@ var Atbash = class _Atbash {
3645
3759
  toolCallId: result.toolCallId
3646
3760
  };
3647
3761
  }
3648
- return this.fail(
3762
+ return this.failJudge(
3649
3763
  "unrecognized action_type from judge",
3764
+ void 0,
3650
3765
  result.toolCallId
3651
3766
  );
3652
3767
  } catch (err) {
3653
- const message = errorMessage(err);
3654
- this.logger.warn?.("[atbash] judge API failed", { reason: message });
3655
- return this.fail(message);
3768
+ return this.failJudge(errorMessage(err), err);
3656
3769
  }
3657
3770
  }
3771
+ /**
3772
+ * One exit for every judge failure.
3773
+ *
3774
+ * Status and reason go in the *message*, not only in the meta object: hosts
3775
+ * print the message and drop the meta, which is why this read as a bare
3776
+ * "judge API failed" while the judge was answering with a precise reason.
3777
+ * The response body follows only under `debug`, since it can echo the action.
3778
+ */
3779
+ failJudge(reason, cause, toolCallId) {
3780
+ const api2 = cause instanceof AtbashAPIError ? cause : null;
3781
+ const status = api2 ? ` status=${api2.status || "no-response"}` : "";
3782
+ const body = this.debug && api2?.body ? ` body=${truncate(api2.body, 500)}` : "";
3783
+ this.logger.warn?.(
3784
+ `[atbash] judge API failed \u2014${status} reason=${truncate(reason, 300)}${body}`,
3785
+ {
3786
+ reason,
3787
+ ...api2 ? { status: api2.status, body: api2.body } : {},
3788
+ endpoint: this.endpoint,
3789
+ ...toolCallId ? { toolCallId } : {}
3790
+ }
3791
+ );
3792
+ return this.fail(reason, toolCallId);
3793
+ }
3658
3794
  fail(reason, toolCallId) {
3659
3795
  return { allow: !this.failClosed, verdict: "ERROR", reason, toolCallId };
3660
3796
  }
@@ -3995,8 +4131,27 @@ var Atbash = class _Atbash {
3995
4131
  this.endpoint
3996
4132
  );
3997
4133
  }
3998
- /** Wrap a *transport* failure (fetch threw, no response) as an AtbashAPIError. */
4134
+ /**
4135
+ * Wrap a transport failure (fetch threw, no response) as an AtbashAPIError.
4136
+ *
4137
+ * `HttpTransportError.kind` names the cause; the message is already
4138
+ * human-readable. `debug` echoes the original exception so operators can
4139
+ * cross-reference with node / undici logs when a class doesn't match.
4140
+ */
3999
4141
  transportError(err) {
4142
+ if (err instanceof HttpTransportError) {
4143
+ if (this.debug) {
4144
+ this.logger.warn?.(
4145
+ `[atbash] transport failed \u2014 kind=${err.kind}`,
4146
+ {
4147
+ kind: err.kind,
4148
+ cause: err.cause instanceof Error ? err.cause.message : String(err.cause ?? ""),
4149
+ endpoint: this.endpoint
4150
+ }
4151
+ );
4152
+ }
4153
+ return new AtbashAPIError(0, err.message, "", this.endpoint);
4154
+ }
4000
4155
  return new AtbashAPIError(0, errorMessage(err), "", this.endpoint);
4001
4156
  }
4002
4157
  async json(resp) {
@@ -4142,9 +4297,9 @@ function stringifyArgs(args) {
4142
4297
  }
4143
4298
  }
4144
4299
  var MAX_ACTION_LEN = 4e3;
4145
- function truncate(text) {
4146
- if (text.length <= MAX_ACTION_LEN) return text;
4147
- return text.slice(0, MAX_ACTION_LEN) + "\u2026";
4300
+ function truncate(text, limit = MAX_ACTION_LEN) {
4301
+ if (text.length <= limit) return text;
4302
+ return text.slice(0, limit) + "\u2026";
4148
4303
  }
4149
4304
 
4150
4305
  // src-ts/redact.ts
@@ -4187,6 +4342,17 @@ function verifyJudgeResponseSignature(bodyBytes, signatureHex, pubKeyHex) {
4187
4342
  };
4188
4343
  }
4189
4344
 
4345
+ // src-ts/memory/boot-sync-message.ts
4346
+ var BOOT_SYNC_HINT = "check the chain endpoint and orgName if the cause above does not explain it";
4347
+ function bootSyncFailureLine(cause) {
4348
+ const reason = cause instanceof Error ? cause.message : String(cause);
4349
+ const trimmed = reason.trim();
4350
+ return trimmed ? `[atbash] boot memory sync failed: ${trimmed}` : (
4351
+ // No cause to show: fall back to the advice rather than a bare colon.
4352
+ `[atbash] boot memory sync failed \u2014 ${BOOT_SYNC_HINT}`
4353
+ );
4354
+ }
4355
+
4190
4356
  // src-ts/memory/crypto.ts
4191
4357
  async function deriveMemoryKey(privkey) {
4192
4358
  return native.deriveMemoryKey(privkey);
@@ -4212,6 +4378,19 @@ async function scanMemory(entry, auth, opts) {
4212
4378
  toolArgsJson: opts?.toolArgsJson ?? JSON.stringify({ key: entry.key, source: entry.source }),
4213
4379
  mode: "memory-scan"
4214
4380
  });
4381
+ const knownAction = result.actionType === "allow" || result.actionType === "block" || result.actionType === "hold_for_user_confirm";
4382
+ const missingVerdict = result.verdict === "No verdict" && result.status !== "logged";
4383
+ const unknownAction = result.actionType !== "" && !knownAction;
4384
+ if (missingVerdict || unknownAction) {
4385
+ return {
4386
+ safe: false,
4387
+ verdict: "red",
4388
+ reason: unknownAction ? `judge returned unrecognised action_type "${result.actionType}"` : "judge returned no verdict",
4389
+ confidence: result.confidence,
4390
+ score: native.defaultScoreForVerdict("red"),
4391
+ toolCallId: result.toolCallId
4392
+ };
4393
+ }
4215
4394
  const verdict = native.mapVerdict(
4216
4395
  result.actionType,
4217
4396
  result.confidence,
@@ -42362,6 +42541,10 @@ var index = /* @__PURE__ */ getDefaultExportFromCjs(builtExports);
42362
42541
 
42363
42542
  // src-ts/memory/chain.ts
42364
42543
  var { createClient, encryption: encryption2, newSignatureProvider: newSignatureProvider2, Buffer: PolyBuffer } = index;
42544
+ var FAILOVER_CONFIG = {
42545
+ strategy: "tryNextOnError",
42546
+ attemptsPerEndpoint: 1
42547
+ };
42365
42548
  function toGtxBytes(bytes) {
42366
42549
  return PolyBuffer.from(new Uint8Array(bytes));
42367
42550
  }
@@ -42391,7 +42574,11 @@ function materializeChain(chainOpts) {
42391
42574
  }
42392
42575
  async function buildChainClient(chainOpts) {
42393
42576
  const { nodeUrls, blockchainRid } = materializeChain(chainOpts);
42394
- return createClient({ nodeUrlPool: [...nodeUrls], blockchainRid });
42577
+ return createClient({
42578
+ nodeUrlPool: [...nodeUrls],
42579
+ blockchainRid,
42580
+ failOverConfig: FAILOVER_CONFIG
42581
+ });
42395
42582
  }
42396
42583
  function buildSigner(auth) {
42397
42584
  const privKeyBuf = Buffer.from(auth.privkey, "hex");
@@ -42829,7 +43016,10 @@ var MemoryGuardManager = class {
42829
43016
  await this.pointerStore.set(this.agentPubkeyHex, result.pointer);
42830
43017
  } catch (err) {
42831
43018
  const msg = err instanceof Error ? err.message : String(err);
42832
- this.logger.warn("[atbash] boot memory sync failed \u2014 check chain endpoint / orgName", { error: msg });
43019
+ this.logger.warn(bootSyncFailureLine(err), {
43020
+ error: msg,
43021
+ hint: BOOT_SYNC_HINT
43022
+ });
42833
43023
  }
42834
43024
  }
42835
43025
  /**
@@ -43057,6 +43247,7 @@ function diffMemorySnapshots(before, after) {
43057
43247
  export {
43058
43248
  Atbash,
43059
43249
  AtbashAPIError,
43250
+ BOOT_SYNC_HINT,
43060
43251
  DEFAULT_BLOCKCHAIN_RID,
43061
43252
  DEFAULT_CHROMIA_NODE_URLS,
43062
43253
  DEFAULT_ENDPOINT,
@@ -43064,11 +43255,16 @@ export {
43064
43255
  DEFAULT_MEMORY_READ_TOOL_NAMES,
43065
43256
  DEFAULT_MEMORY_WRITE_TOOL_NAMES,
43066
43257
  EciesDomain,
43258
+ HttpClient,
43259
+ HttpTransportError,
43260
+ KEY_FILENAMES,
43067
43261
  MemoryGuardManager,
43068
43262
  MemoryIntegrityError,
43069
43263
  PointerStore,
43070
43264
  SignatureVerificationError,
43265
+ bootSyncFailureLine,
43071
43266
  buildAllowedJudgeHosts,
43267
+ chooseKeyPath,
43072
43268
  claimHashHex,
43073
43269
  classifyMemoryRead,
43074
43270
  classifyMemoryWrite,
@@ -43103,6 +43299,7 @@ export {
43103
43299
  isEnvelope,
43104
43300
  isValidPrivateKey,
43105
43301
  keyFingerprintOf,
43302
+ keyPathCandidates,
43106
43303
  loadAgent,
43107
43304
  loadAgentFromFile,
43108
43305
  loadUserConfig,