@atbash/sdk 0.10.0-dev.0 → 0.10.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/LICENSE +202 -28
- package/NOTICE +10 -0
- package/README.md +96 -19
- package/THIRD-PARTY-NOTICES +10256 -0
- package/dist/browser.d.mts +846 -93
- package/dist/browser.mjs +1397 -448
- package/dist/index.d.mts +846 -93
- package/dist/index.d.ts +846 -93
- package/dist/index.js +3251 -42446
- package/dist/index.mjs +3147 -42389
- package/index.d.ts +61 -27
- package/index.js +308 -109
- package/package.json +17 -8
- package/dist/browser.mjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Logger } from '@opentelemetry/api-logs';
|
|
2
|
+
|
|
1
3
|
type JudgeEndpointConfig = {
|
|
2
4
|
policy?: "default";
|
|
3
5
|
endpoint?: string;
|
|
@@ -11,8 +13,34 @@ interface ValidatedEndpoint {
|
|
|
11
13
|
policy: "default" | "self-hosted";
|
|
12
14
|
verifyPubKey: string | null;
|
|
13
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Builds the trusted judge host set.
|
|
18
|
+
*
|
|
19
|
+
* The compiled-in default is always trusted: a `prod` build resolves it to
|
|
20
|
+
* atbash.ai, a dev build to whatever DEV_ENDPOINT was set at build time. No
|
|
21
|
+
* dev host is spelled out in source, and dev builds still validate their own
|
|
22
|
+
* default.
|
|
23
|
+
*
|
|
24
|
+
* Exported for tests only. The set is a build-time value and is deliberately
|
|
25
|
+
* never read from the process environment — an env var would let anyone widen
|
|
26
|
+
* the allowlist of an already-shipped artifact, which is the silent-redirection
|
|
27
|
+
* attack the allowlist exists to prevent (F-003). Asserting that requires
|
|
28
|
+
* calling this with the environment set, so it cannot stay module-private.
|
|
29
|
+
*
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
declare function buildAllowedJudgeHosts(): ReadonlySet<string>;
|
|
14
33
|
declare function validateJudgeEndpoint(judge?: JudgeEndpointConfig): ValidatedEndpoint;
|
|
15
34
|
|
|
35
|
+
interface ChainConfig {
|
|
36
|
+
readonly network: Network;
|
|
37
|
+
readonly blockchainRid: string;
|
|
38
|
+
readonly nodeUrls: readonly string[];
|
|
39
|
+
}
|
|
40
|
+
declare const PUBLIC_CHAIN: ChainConfig;
|
|
41
|
+
declare const PRIVATE_CHAIN: ChainConfig;
|
|
42
|
+
declare function chainForNetwork(network: Network): ChainConfig;
|
|
43
|
+
|
|
16
44
|
/**
|
|
17
45
|
* User-facing types. Two groups:
|
|
18
46
|
* - Core types — the exact shapes the Rust core emits across the NAPI
|
|
@@ -110,14 +138,24 @@ interface MemoryScanOptions {
|
|
|
110
138
|
type Network = "public" | "private";
|
|
111
139
|
/**
|
|
112
140
|
* Per-call chain overrides. Pass exactly what you need to override —
|
|
113
|
-
* `network` selects between the SDK's known chains, `blockchainRid`
|
|
114
|
-
*
|
|
115
|
-
* to the SDK's defaults (or to org-resolved
|
|
141
|
+
* `network` selects between the SDK's known chains, `blockchainRid` pins a
|
|
142
|
+
* custom one, and `endpoint` names the Dashboard the calls travel over. Pass
|
|
143
|
+
* none of these to fall back to the SDK's defaults (or to the org-resolved
|
|
144
|
+
* chain when `orgName` is set).
|
|
116
145
|
*/
|
|
117
146
|
interface ChainOpts {
|
|
118
147
|
network?: Network;
|
|
119
148
|
blockchainRid?: string;
|
|
149
|
+
/**
|
|
150
|
+
* No longer dialled: memory reaches the chain through the Dashboard. Kept
|
|
151
|
+
* because callers and the other bindings still carry it as chain config.
|
|
152
|
+
*/
|
|
120
153
|
nodeUrls?: readonly string[];
|
|
154
|
+
/**
|
|
155
|
+
* Dashboard base URL. Memory reaches the chain through it, so this is where
|
|
156
|
+
* those requests go; defaults to the endpoint compiled into the core.
|
|
157
|
+
*/
|
|
158
|
+
endpoint?: string;
|
|
121
159
|
}
|
|
122
160
|
/** Canonical verdict after normalization. */
|
|
123
161
|
type Verdict = "ALLOW" | "HOLD" | "BLOCK" | "No verdict";
|
|
@@ -143,6 +181,11 @@ interface LogToolCallResult {
|
|
|
143
181
|
}
|
|
144
182
|
interface JudgeResult {
|
|
145
183
|
verdict: Verdict;
|
|
184
|
+
/**
|
|
185
|
+
* Canonical executable permission, computed by `canonicalAllow` — see that
|
|
186
|
+
* function for the rule. Never more permissive than `auditToolCall`.
|
|
187
|
+
*/
|
|
188
|
+
allow: boolean;
|
|
146
189
|
actionType: string;
|
|
147
190
|
reason: string;
|
|
148
191
|
confidence: number;
|
|
@@ -167,10 +210,17 @@ interface JudgeResult {
|
|
|
167
210
|
* missing" — never infer the former from a null verdict alone.
|
|
168
211
|
*/
|
|
169
212
|
status: string;
|
|
213
|
+
/**
|
|
214
|
+
* The judge's explicit refusal in the raw body (`explicitRefusal`): "BLOCK",
|
|
215
|
+
* "HOLD", or "" when it neither refused nor is a known refusal shape.
|
|
216
|
+
*/
|
|
217
|
+
refusal?: "" | "BLOCK" | "HOLD";
|
|
170
218
|
}
|
|
171
219
|
interface JudgmentStatus {
|
|
172
220
|
status: JudgmentState;
|
|
173
221
|
verdict: Verdict;
|
|
222
|
+
/** Same canonical executable permission as {@link JudgeResult.allow}. */
|
|
223
|
+
allow: boolean;
|
|
174
224
|
reason: string;
|
|
175
225
|
judgmentId: string;
|
|
176
226
|
onChain?: boolean;
|
|
@@ -257,6 +307,13 @@ interface AgentPolicy {
|
|
|
257
307
|
isCustom: boolean;
|
|
258
308
|
defaultPolicy: string;
|
|
259
309
|
}
|
|
310
|
+
/** Options for agent metadata and policy lookups. */
|
|
311
|
+
interface AgentLookupOptions {
|
|
312
|
+
/** Resolve the agent's network from this organization's active network. */
|
|
313
|
+
orgName?: string;
|
|
314
|
+
/** Explicit per-call chain override. `network` selects the dashboard chain. */
|
|
315
|
+
chainOpts?: ChainOpts;
|
|
316
|
+
}
|
|
260
317
|
/** Optional structured logger. */
|
|
261
318
|
interface AtbashLogger {
|
|
262
319
|
info?(...args: unknown[]): void;
|
|
@@ -265,8 +322,32 @@ interface AtbashLogger {
|
|
|
265
322
|
/** Options accepted by the `Atbash` constructor. */
|
|
266
323
|
interface AtbashOptions {
|
|
267
324
|
endpoint?: string;
|
|
325
|
+
/**
|
|
326
|
+
* Overall deadline for one `auditToolCall` decision, across every request
|
|
327
|
+
* it makes (org lookup, exists check, judge). Default 60 000 ms. When it
|
|
328
|
+
* passes the decision is an ERROR (denied unless `failClosed` is false).
|
|
329
|
+
* `timeoutMs` still bounds each single request.
|
|
330
|
+
*/
|
|
331
|
+
decisionTimeoutMs?: number;
|
|
268
332
|
timeoutMs?: number;
|
|
333
|
+
/**
|
|
334
|
+
* Full chain override — BRID + nodeUrls in one object. Wins over every
|
|
335
|
+
* other chain selector. Prefer this over paired `nodeUrls`/`blockchainRid`
|
|
336
|
+
* for anything but backwards compatibility.
|
|
337
|
+
*/
|
|
338
|
+
chain?: ChainConfig;
|
|
339
|
+
/**
|
|
340
|
+
* Preset chain selector — `"public"` or `"private"`. Resolves to the
|
|
341
|
+
* matching `ChainConfig` via `chainForNetwork()`. Overridden by `chain`,
|
|
342
|
+
* overrides env `ATBASH_DEFAULT_CHAIN_NETWORK` and the config file.
|
|
343
|
+
*/
|
|
344
|
+
network?: Network;
|
|
345
|
+
/**
|
|
346
|
+
* Explicit node URLs. Must be paired with `blockchainRid`. Passing one
|
|
347
|
+
* without the other throws — a BRID/nodes mismatch 404s every request.
|
|
348
|
+
*/
|
|
269
349
|
nodeUrls?: readonly string[];
|
|
350
|
+
/** Explicit BRID. Must be paired with `nodeUrls`. See {@link nodeUrls}. */
|
|
270
351
|
blockchainRid?: string;
|
|
271
352
|
/**
|
|
272
353
|
* Default org name. When set, `judgeAction` / `auditToolCall` resolve
|
|
@@ -293,6 +374,22 @@ interface AtbashOptions {
|
|
|
293
374
|
orgEncryptionPubKey?: string;
|
|
294
375
|
/** When true (default), `auditToolCall` denies on any error. */
|
|
295
376
|
failClosed?: boolean;
|
|
377
|
+
/**
|
|
378
|
+
* When true (the default), an answer that carries no enforcement decision
|
|
379
|
+
* (the audit tier's `{status:"logged"}` with no verdict) is refused:
|
|
380
|
+
* `auditToolCall` returns `{allow:false, verdict:"ERROR"}` whatever
|
|
381
|
+
* `failClosed` says, and `judgeAction().allow` / `getJudgmentStatus().allow`
|
|
382
|
+
* are false. Set it to `false` only to run deliberately in log-only mode
|
|
383
|
+
* (security review 2026-09-30, H6: an unsigned marker switched enforcement
|
|
384
|
+
* off).
|
|
385
|
+
*/
|
|
386
|
+
requireEnforcement?: boolean;
|
|
387
|
+
/**
|
|
388
|
+
* Verbose diagnostics. Off by default. When on, a failed judge call also
|
|
389
|
+
* logs the response body, which is the difference between "judge API failed"
|
|
390
|
+
* and knowing why it failed. Opt-in because that body can echo the action.
|
|
391
|
+
*/
|
|
392
|
+
debug?: boolean;
|
|
296
393
|
logger?: AtbashLogger;
|
|
297
394
|
}
|
|
298
395
|
/** Canonical decision returned by `auditToolCall`. */
|
|
@@ -327,20 +424,28 @@ interface FromConfigOptions {
|
|
|
327
424
|
keyPath?: string;
|
|
328
425
|
/** Judge endpoint config — validated against the allowlist / self-hosted policy. */
|
|
329
426
|
judge?: JudgeEndpointConfig;
|
|
427
|
+
/** See {@link AtbashOptions.chain}. */
|
|
428
|
+
chain?: ChainConfig;
|
|
429
|
+
/** See {@link AtbashOptions.network}. */
|
|
430
|
+
network?: Network;
|
|
330
431
|
blockchainRid?: string;
|
|
331
432
|
timeoutMs?: number;
|
|
332
433
|
nodeUrls?: readonly string[];
|
|
333
434
|
/** Default org name — see {@link AtbashOptions.orgName}. */
|
|
334
435
|
orgName?: string;
|
|
335
436
|
failClosed?: boolean;
|
|
437
|
+
/** See {@link AtbashOptions.requireEnforcement}. Default true. */
|
|
438
|
+
requireEnforcement?: boolean;
|
|
439
|
+
/** See {@link AtbashOptions.decisionTimeoutMs}. Default 60 000 ms. */
|
|
440
|
+
decisionTimeoutMs?: number;
|
|
441
|
+
/** See {@link AtbashOptions.debug}. */
|
|
442
|
+
debug?: boolean;
|
|
336
443
|
logger?: AtbashLogger;
|
|
337
444
|
}
|
|
338
445
|
/** Options accepted by `judgeAction`. */
|
|
339
446
|
interface JudgeOptions {
|
|
340
447
|
toolName?: string;
|
|
341
448
|
toolArgsJson?: string;
|
|
342
|
-
provider?: string;
|
|
343
|
-
model?: string;
|
|
344
449
|
verifyPubKey?: string;
|
|
345
450
|
/** Per-call override of {@link AtbashOptions.orgEncryptionPubKey}. */
|
|
346
451
|
orgEncryptionPubKey?: string;
|
|
@@ -376,12 +481,6 @@ interface LogToolCallOptions {
|
|
|
376
481
|
orgEncryptionPubKey?: string;
|
|
377
482
|
}
|
|
378
483
|
|
|
379
|
-
interface ChainConfig {
|
|
380
|
-
readonly network: Network;
|
|
381
|
-
readonly blockchainRid: string;
|
|
382
|
-
readonly nodeUrls: readonly string[];
|
|
383
|
-
}
|
|
384
|
-
|
|
385
484
|
declare class Atbash {
|
|
386
485
|
readonly auth: AgentAuth;
|
|
387
486
|
readonly endpoint: string;
|
|
@@ -395,6 +494,13 @@ declare class Atbash {
|
|
|
395
494
|
readonly orgEncryptionPubKey?: string;
|
|
396
495
|
/** When true (default), `auditToolCall` denies on any error. */
|
|
397
496
|
readonly failClosed: boolean;
|
|
497
|
+
/** When true (default), an unenforced (audit-tier) answer is refused. */
|
|
498
|
+
readonly requireEnforcement: boolean;
|
|
499
|
+
/** Overall deadline for one auditToolCall decision, in ms. */
|
|
500
|
+
readonly decisionTimeoutMs: number;
|
|
501
|
+
/** Org key learned from the last agent-exists check, for this agent only. */
|
|
502
|
+
private _orgKeyFromChain;
|
|
503
|
+
private readonly debug;
|
|
398
504
|
private readonly logger;
|
|
399
505
|
private readonly http;
|
|
400
506
|
/**
|
|
@@ -403,12 +509,50 @@ declare class Atbash {
|
|
|
403
509
|
*/
|
|
404
510
|
private readonly _chainCache;
|
|
405
511
|
/**
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
* server-side replay protection windows never expire it mid-session.
|
|
512
|
+
* The chain the constructor settled on. Used only where a lookup returns no
|
|
513
|
+
* answer — see {@link resolveChainFromMap}.
|
|
409
514
|
*/
|
|
410
|
-
private
|
|
515
|
+
private readonly _defaultChain;
|
|
516
|
+
/**
|
|
517
|
+
* True when the caller named a chain outright — `chain`, `network`, or the
|
|
518
|
+
* paired `blockchainRid` + `nodeUrls`.
|
|
519
|
+
*
|
|
520
|
+
* Such a client is never re-pointed: not by the migration switch, and not by
|
|
521
|
+
* where an org turns out to live. Naming a chain is the caller saying "talk
|
|
522
|
+
* to this one", and silently routing elsewhere would make the argument a
|
|
523
|
+
* suggestion. A client that names nothing is the one that follows the org.
|
|
524
|
+
*/
|
|
525
|
+
private readonly _explicitChain;
|
|
526
|
+
/**
|
|
527
|
+
* The fleet-wide chain switch, read once at construction. `resolve()` hits
|
|
528
|
+
* the config file on disk, so re-reading it per call would put a file read
|
|
529
|
+
* on every judge.
|
|
530
|
+
*/
|
|
531
|
+
private readonly _forcedNetwork;
|
|
532
|
+
/**
|
|
533
|
+
* Short-TTL cache for `/api/ai/exists`. The `registered` field is
|
|
534
|
+
* monotonic (once true, stays true), so most calls in a burst re-fetch
|
|
535
|
+
* data that hasn't changed. The `org_encryption_pubkey` field CAN change
|
|
536
|
+
* — an org toggling encryption mid-session — so the TTL is deliberately
|
|
537
|
+
* short (see `AGENT_EXISTS_TTL_MS`). Keyed by (pubkey, network) so
|
|
538
|
+
* cross-agent / cross-network calls don't collide.
|
|
539
|
+
*/
|
|
540
|
+
private _agentExistsCache;
|
|
541
|
+
private static readonly AGENT_EXISTS_TTL_MS;
|
|
542
|
+
/** Guards `logEnvironmentOnce` — hosts construct several clients. */
|
|
543
|
+
private static environmentLogged;
|
|
411
544
|
constructor(privkey: string, options?: AtbashOptions);
|
|
545
|
+
/**
|
|
546
|
+
* Say which environment this build talks to, once per process.
|
|
547
|
+
*
|
|
548
|
+
* The endpoint and both chain RIDs are compiled in, chosen by the npm tag,
|
|
549
|
+
* and no configuration repoints a released build. So installing the build
|
|
550
|
+
* for the wrong environment is invisible: the plugin loads, the hook fires,
|
|
551
|
+
* and every judge call fails because the agent does not exist on the chain
|
|
552
|
+
* this build targets. Organisation names are not unique across environments
|
|
553
|
+
* either, so an org resolving is not evidence the build is right.
|
|
554
|
+
*/
|
|
555
|
+
private logEnvironmentOnce;
|
|
412
556
|
/**
|
|
413
557
|
* Construct from resolved config: explicit overrides → env vars → the
|
|
414
558
|
* `~/.config/atbash/config.json` file (see userConfig.resolve). The private
|
|
@@ -417,6 +561,48 @@ declare class Atbash {
|
|
|
417
561
|
* validated against the trusted allowlist / self-hosted policy; a
|
|
418
562
|
* self-hosted endpoint's `verifyPubKey` becomes the client default.
|
|
419
563
|
*/
|
|
564
|
+
/**
|
|
565
|
+
* Decide which key this client signs as.
|
|
566
|
+
*
|
|
567
|
+
* Most specific wins: a key passed in code, then a key file the host named,
|
|
568
|
+
* then the environment, then the default key file. `config.json` is absent
|
|
569
|
+
* from that list on purpose — it is a settings file, writable by anything
|
|
570
|
+
* that can write the user's home and read by every Atbash integration on the
|
|
571
|
+
* machine, so letting it name the key meant one stray value silently
|
|
572
|
+
* re-pointed who each of them signed as.
|
|
573
|
+
*
|
|
574
|
+
* A named `keyPath` that does not exist throws rather than falling through.
|
|
575
|
+
* The caller said which file holds its identity; quietly signing as a
|
|
576
|
+
* different one is the outcome worth refusing.
|
|
577
|
+
*
|
|
578
|
+
* @param options - The `fromConfig` options, read for `agentKey` and `keyPath`.
|
|
579
|
+
* @returns The resolved agent identity.
|
|
580
|
+
* @throws When a named key file is missing, or no identity can be found.
|
|
581
|
+
*/
|
|
582
|
+
private static resolveIdentity;
|
|
583
|
+
/**
|
|
584
|
+
* Add the one thing a machine with no key file needs to hear.
|
|
585
|
+
*
|
|
586
|
+
* This is the only path where the operator cannot see what happened: no key
|
|
587
|
+
* file, and the key they are looking for sits in `config.json`, which no
|
|
588
|
+
* longer selects the identity. A bare "key file not found" sends them hunting
|
|
589
|
+
* for a key they already have. Only this branch gets the hint — a caller who
|
|
590
|
+
* named a `keyPath` is told about that path, where a stranded key is noise.
|
|
591
|
+
*
|
|
592
|
+
* @param err - The "key file not found" error from the default lookup.
|
|
593
|
+
* @returns The same error, or one that also names the key in config.json.
|
|
594
|
+
*/
|
|
595
|
+
private static explainStrandedKey;
|
|
596
|
+
/**
|
|
597
|
+
* Say so when `config.json` still holds an agent key.
|
|
598
|
+
*
|
|
599
|
+
* Nothing signs with it any more, and silence would leave an operator
|
|
600
|
+
* believing one identity is in force while another is. The pubkey is named,
|
|
601
|
+
* not the key, so the line is safe in a log.
|
|
602
|
+
*
|
|
603
|
+
* @param logger - The host logger, when one was supplied.
|
|
604
|
+
*/
|
|
605
|
+
private static warnOnStrandedKey;
|
|
420
606
|
static fromConfig(options?: FromConfigOptions): Atbash;
|
|
421
607
|
get pubkey(): string;
|
|
422
608
|
get privkey(): string;
|
|
@@ -451,8 +637,50 @@ declare class Atbash {
|
|
|
451
637
|
* explicitly false.
|
|
452
638
|
*/
|
|
453
639
|
auditToolCall(input: ToolCallInput): Promise<Decision>;
|
|
640
|
+
/**
|
|
641
|
+
* The verify key for one call (R4, governor tier-2 review). A per-call key
|
|
642
|
+
* may only narrow what the client accepts: on a client pinned to a judge
|
|
643
|
+
* key it must be that same key, never a replacement; on an unpinned client
|
|
644
|
+
* it adds the check.
|
|
645
|
+
*/
|
|
646
|
+
private effectiveVerifyKey;
|
|
647
|
+
/**
|
|
648
|
+
* Collapse a judge result into a {@link Decision}. The most restrictive
|
|
649
|
+
* signal wins (security review 2026-09-30, H1): `action_type`, `verdict`
|
|
650
|
+
* and the canonical `allow` are each able to deny, and only a result where
|
|
651
|
+
* all three agree may execute. A present `allow` / `decision.allow` that is
|
|
652
|
+
* not exactly `true` is the judge declining to grant permission, so it
|
|
653
|
+
* denies even under `failClosed: false` — that option relaxes *errors*, it
|
|
654
|
+
* never overrides a refusal.
|
|
655
|
+
*/
|
|
656
|
+
private decide;
|
|
657
|
+
/**
|
|
658
|
+
* One exit for every judge failure.
|
|
659
|
+
*
|
|
660
|
+
* Status and reason go in the *message*, not only in the meta object: hosts
|
|
661
|
+
* print the message and drop the meta, which is why this read as a bare
|
|
662
|
+
* "judge API failed" while the judge was answering with a precise reason.
|
|
663
|
+
* The response body follows only under `debug`, since it can echo the action.
|
|
664
|
+
*/
|
|
665
|
+
private failJudge;
|
|
666
|
+
/**
|
|
667
|
+
* The canonical permission for a response body under this client's
|
|
668
|
+
* settings: {@link canonicalAllow}, minus the audit tier when
|
|
669
|
+
* `requireEnforcement` is on. Never more permissive than `auditToolCall`.
|
|
670
|
+
*/
|
|
671
|
+
private executable;
|
|
454
672
|
private fail;
|
|
455
|
-
|
|
673
|
+
/**
|
|
674
|
+
* Return the current status of a previously submitted judgment.
|
|
675
|
+
*
|
|
676
|
+
* `chainOpts` names which chain the judgment was signed against. The
|
|
677
|
+
* server's GET /api/v1/judge routes to that chain when the SDK sends
|
|
678
|
+
* a `brid` query param; without it, the server falls back to public.
|
|
679
|
+
* Callers on the private chain must pass a `chainOpts` (or configure
|
|
680
|
+
* the client on the private chain) — otherwise polling a POSTed
|
|
681
|
+
* judgment on the private chain 404s at the server.
|
|
682
|
+
*/
|
|
683
|
+
getJudgmentStatus(judgmentId: string, agentPubkey?: string, chainOpts?: ChainOpts): Promise<JudgmentStatus>;
|
|
456
684
|
getToolCalls(maxCount: number): Promise<ToolCallRecord[]>;
|
|
457
685
|
getOrgToolCalls(orgName: string, maxCount: number): Promise<ToolCallRecord[]>;
|
|
458
686
|
getAgentToolCalls(agentPubkey: string, maxCount: number): Promise<ToolCallRecord[]>;
|
|
@@ -461,8 +689,8 @@ declare class Atbash {
|
|
|
461
689
|
getOrgTierInfo(orgName: string): Promise<TierInfo | null>;
|
|
462
690
|
getPendingHeldActions(orgName: string, maxCount: number): Promise<HeldAction[]>;
|
|
463
691
|
getHeldActionReviews(orgName: string, maxCount: number): Promise<HeldActionReview[]>;
|
|
464
|
-
getAgentDetail(agentPubkey: string): Promise<Record<string, unknown>>;
|
|
465
|
-
getAgentPolicy(agentPubkey: string): Promise<AgentPolicy>;
|
|
692
|
+
getAgentDetail(agentPubkey: string, options?: AgentLookupOptions): Promise<Record<string, unknown>>;
|
|
693
|
+
getAgentPolicy(agentPubkey: string, options?: AgentLookupOptions): Promise<AgentPolicy>;
|
|
466
694
|
getSafetyStats(): Promise<Record<string, unknown>>;
|
|
467
695
|
/**
|
|
468
696
|
* Org's subscription on a specific chain. The `network` arg selects
|
|
@@ -485,7 +713,9 @@ declare class Atbash {
|
|
|
485
713
|
* 2. Per-chain subscription fallback — public + private records
|
|
486
714
|
* are fetched in parallel, with `is_private_blockchain` and
|
|
487
715
|
* `assigned_at` reconciling mixed states.
|
|
488
|
-
*
|
|
716
|
+
* A lookup that names exactly one chain wins outright. Where it names
|
|
717
|
+
* neither (a brand-new org) or cannot choose between them, the client's
|
|
718
|
+
* configured default decides.
|
|
489
719
|
*/
|
|
490
720
|
resolveChainForOrg(orgName: string): Promise<ChainConfig>;
|
|
491
721
|
/**
|
|
@@ -497,6 +727,8 @@ declare class Atbash {
|
|
|
497
727
|
private resolveChainFromMap;
|
|
498
728
|
/** Drop any cached chain resolutions. Useful in tests. */
|
|
499
729
|
clearChainCache(): void;
|
|
730
|
+
/** Drop the short-TTL `/api/ai/exists` cache. Useful in tests. */
|
|
731
|
+
clearAgentExistsCache(): void;
|
|
500
732
|
/**
|
|
501
733
|
* Wrap an SDK method body in telemetry — records the call at start
|
|
502
734
|
* and a success/error duration at end. Re-throws on failure so the
|
|
@@ -511,6 +743,14 @@ declare class Atbash {
|
|
|
511
743
|
* chains; otherwise the client's default.
|
|
512
744
|
*/
|
|
513
745
|
private bridFromChainOpts;
|
|
746
|
+
/**
|
|
747
|
+
* Resolve the dashboard chain used by agent metadata/policy reads.
|
|
748
|
+
* Explicit per-call network overrides win; otherwise use the supplied org
|
|
749
|
+
* or the client's configured default org. A custom BRID is intentionally
|
|
750
|
+
* left untouched because it cannot be represented by the dashboard's
|
|
751
|
+
* public/private query selector.
|
|
752
|
+
*/
|
|
753
|
+
private resolveAgentLookupNetwork;
|
|
514
754
|
/**
|
|
515
755
|
* Get-or-create a Bearer token for dashboard reads. The token is a
|
|
516
756
|
* signed `log_tool_call` op (locally signed, never submitted) — the
|
|
@@ -523,10 +763,46 @@ declare class Atbash {
|
|
|
523
763
|
private riskEngineGet;
|
|
524
764
|
private riskEnginePost;
|
|
525
765
|
private riskEngineRecords;
|
|
766
|
+
/**
|
|
767
|
+
* BRID for an org — one round-trip to the map, honoring the client's chain
|
|
768
|
+
* cache. A "brand-new org" (nothing anywhere names its chain) is not an
|
|
769
|
+
* error — `resolveChainForOrg` returns the client default for that case and
|
|
770
|
+
* this helper returns its BRID. A transport failure or non-200 from
|
|
771
|
+
* `/api/org-network` IS an error and propagates: the caller cannot fall
|
|
772
|
+
* back to the default chain on outage, because with multi-chain live that
|
|
773
|
+
* silently reads from the wrong chain. Matches the Python binding's
|
|
774
|
+
* `_brid_for_org` semantics.
|
|
775
|
+
*/
|
|
776
|
+
private bridForOrg;
|
|
777
|
+
/**
|
|
778
|
+
* BRID for the client's configured default org, if it has one.
|
|
779
|
+
*
|
|
780
|
+
* Calls that carry no `orgName` argument are not chain-less: they still
|
|
781
|
+
* belong to `this.orgName`, and that org lives on exactly one chain. Routing
|
|
782
|
+
* them by the constructor's chain instead means a client configured
|
|
783
|
+
* `network: "private"` reads the private chain for an org that lives on
|
|
784
|
+
* public, and gets an empty answer rather than an error. So where an org is
|
|
785
|
+
* known the org decides the chain, and the constructor's chain is what is
|
|
786
|
+
* left when no org is known at all — the order `resolveAgentLookupNetwork`
|
|
787
|
+
* already applies to agent metadata reads, and the order the dashboard
|
|
788
|
+
* applies in `resolveChainForWallet`.
|
|
789
|
+
*
|
|
790
|
+
* Undefined when there is no default org, so callers keep falling back to
|
|
791
|
+
* the client default.
|
|
792
|
+
*/
|
|
793
|
+
/** The switch's chain, unless this client named one of its own. */
|
|
794
|
+
private forcedNetwork;
|
|
795
|
+
private defaultOrgBrid;
|
|
526
796
|
private raiseIfError;
|
|
527
797
|
/** Wrap a failed HTTP *response* (non-2xx / non-200) as an AtbashAPIError. */
|
|
528
798
|
private httpError;
|
|
529
|
-
/**
|
|
799
|
+
/**
|
|
800
|
+
* Wrap a transport failure (fetch threw, no response) as an AtbashAPIError.
|
|
801
|
+
*
|
|
802
|
+
* `HttpTransportError.kind` names the cause; the message is already
|
|
803
|
+
* human-readable. `debug` echoes the original exception so operators can
|
|
804
|
+
* cross-reference with node / undici logs when a class doesn't match.
|
|
805
|
+
*/
|
|
530
806
|
private transportError;
|
|
531
807
|
private json;
|
|
532
808
|
static generateKeypair(): KeyPair;
|
|
@@ -546,15 +822,79 @@ declare class AtbashAPIError extends Error {
|
|
|
546
822
|
readonly status: number;
|
|
547
823
|
/** Raw response body text (may be empty). */
|
|
548
824
|
readonly body: string;
|
|
549
|
-
|
|
825
|
+
/**
|
|
826
|
+
* Request path this status came from, e.g. `/api/v1/judge` — absent when the
|
|
827
|
+
* request never reached a response (transport failures carry status 0).
|
|
828
|
+
*
|
|
829
|
+
* A status alone does not say what failed. Several routes answer one call, and
|
|
830
|
+
* more than one of them can 404: a caller reading only `404` cannot tell "this
|
|
831
|
+
* agent is not registered" from "the chain-routing lookup did not resolve", and
|
|
832
|
+
* has to guess from the body's prose — which a 404 raised before any handler
|
|
833
|
+
* ran does not carry. The path is the fact that settles it.
|
|
834
|
+
*/
|
|
835
|
+
readonly path?: string;
|
|
836
|
+
constructor(status: number, body: string, statusText?: string, endpoint?: string, path?: string);
|
|
550
837
|
}
|
|
551
838
|
declare class SignatureVerificationError extends Error {
|
|
552
839
|
constructor(message: string);
|
|
553
840
|
}
|
|
554
841
|
|
|
842
|
+
/**
|
|
843
|
+
* Thin typed fetch wrapper.
|
|
844
|
+
*
|
|
845
|
+
* openapi-typescript emits types only (no runtime client), so this is the
|
|
846
|
+
* single hand-written transport — generic `get`/`post` over global `fetch`
|
|
847
|
+
* with a per-request timeout. The endpoint-specific request/response *shapes*
|
|
848
|
+
* are pulled from the generated `schema.ts` at the call sites in client.ts, so
|
|
849
|
+
* the wire contract still lives in spec/openapi.yaml. Methods return the raw
|
|
850
|
+
* `Response` so the caller can read the exact bytes the server signed before
|
|
851
|
+
* any decode (judge signature verification) — mirroring the Python surface's
|
|
852
|
+
* use of raw httpx (DECISIONS 2026-05-22).
|
|
853
|
+
*/
|
|
854
|
+
type QueryValue = string | number | boolean | undefined | null;
|
|
855
|
+
declare class HttpClient {
|
|
856
|
+
readonly baseUrl: string;
|
|
857
|
+
readonly timeoutMs: number;
|
|
858
|
+
constructor(baseUrl: string, timeoutMs: number);
|
|
859
|
+
buildUrl(path: string, query?: Record<string, QueryValue>): string;
|
|
860
|
+
get(path: string, query?: Record<string, QueryValue>, headers?: Record<string, string>): Promise<Response>;
|
|
861
|
+
post(path: string, body: unknown, headers?: Record<string, string>): Promise<Response>;
|
|
862
|
+
private fetch;
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* A transport failure the SDK can act on. Every real cause the platform surfaces
|
|
866
|
+
* lands as one of these — the message names the cause in plain language so a
|
|
867
|
+
* plugin can show it to a user without decoding httpx / fetch internals.
|
|
868
|
+
*
|
|
869
|
+
* `cause` preserves the original error for debug logging; consumers that want
|
|
870
|
+
* the raw exception (e.g. tests) read it there.
|
|
871
|
+
*/
|
|
872
|
+
declare class HttpTransportError extends Error {
|
|
873
|
+
readonly kind: "timeout" | "aborted" | "dns" | "connect_refused" | "connection_reset" | "unknown";
|
|
874
|
+
constructor(kind: HttpTransportError["kind"], message: string, options?: {
|
|
875
|
+
cause?: unknown;
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
declare function canonicalAllow(data: Record<string, unknown>): boolean;
|
|
880
|
+
|
|
555
881
|
/** Normalize wire shapes (verdict casing, status, pubkey) to canonical forms. */
|
|
556
882
|
|
|
557
883
|
declare function normalizeVerdict(raw: unknown): Verdict;
|
|
884
|
+
/**
|
|
885
|
+
* Canonicalize `action_type` at the wire boundary, the way
|
|
886
|
+
* {@link normalizeVerdict} already canonicalizes `verdict`.
|
|
887
|
+
*
|
|
888
|
+
* `verdict` has been normalized here since the beginning and has never
|
|
889
|
+
* drifted between consumers. `action_type` was passed through raw, so every
|
|
890
|
+
* reader invented its own folding policy — `auditToolCall` compared exactly
|
|
891
|
+
* while `memory/scan.ts` trimmed and case-folded, and the same `" ALLOW "`
|
|
892
|
+
* was therefore an error on one path and permission on the other.
|
|
893
|
+
*
|
|
894
|
+
* Trim and case-fold only. Zero-width and homoglyph variants survive
|
|
895
|
+
* untouched, stay outside the known set, and still fail closed.
|
|
896
|
+
*/
|
|
897
|
+
declare function normalizeActionType(raw: unknown): string;
|
|
558
898
|
declare function normalizeStatus(raw: unknown): JudgmentState;
|
|
559
899
|
/** Wire pubkey may be a hex string, a Buffer/Uint8Array, or `{ data: [...] }`. */
|
|
560
900
|
declare function pubkeyToHex(val: unknown): string;
|
|
@@ -563,9 +903,22 @@ interface AtbashUserConfig {
|
|
|
563
903
|
agentKey?: string;
|
|
564
904
|
orgName?: string;
|
|
565
905
|
judgeEndpoint?: string;
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
906
|
+
/**
|
|
907
|
+
* Response-signing pubkey of a self-hosted judge (66 hex). Setting it makes
|
|
908
|
+
* `fromConfig` use the self-hosted policy for `judgeEndpoint`, which is the
|
|
909
|
+
* only way a non-allowlisted judge host is accepted.
|
|
910
|
+
*/
|
|
911
|
+
judgeVerifyPubKey?: string;
|
|
912
|
+
/**
|
|
913
|
+
* `"private"` pins every org to the private chain regardless of where the
|
|
914
|
+
* dashboard says it lives — the migration switch. Leave it unset for the
|
|
915
|
+
* normal mode, where each org's own chain decides. There is no `"public"`
|
|
916
|
+
* value; a caller that wants one specific chain passes `chain` or `network`
|
|
917
|
+
* at construction instead.
|
|
918
|
+
*/
|
|
919
|
+
defaultChainNetwork?: Network;
|
|
920
|
+
/** "1" / "true" turns on verbose diagnostics. See AtbashOptions.debug. */
|
|
921
|
+
debug?: string;
|
|
569
922
|
}
|
|
570
923
|
declare function getConfigDir(): string;
|
|
571
924
|
declare function getConfigPath(): string;
|
|
@@ -576,6 +929,26 @@ declare function resolve(key: keyof AtbashUserConfig, flagValue?: string): strin
|
|
|
576
929
|
declare function resolveKeyPath(input?: string): string;
|
|
577
930
|
declare function loadAgentFromFile(keyPath?: string): AgentAuth;
|
|
578
931
|
|
|
932
|
+
/**
|
|
933
|
+
* Accepted key filenames, in precedence order.
|
|
934
|
+
*
|
|
935
|
+
* `guard-client-key` stays first: it is the name every existing install
|
|
936
|
+
* already has, and changing which file wins would silently switch agent
|
|
937
|
+
* identity for anyone holding both. `atbash-client-key` is accepted because
|
|
938
|
+
* it is the name people actually create — the old one carries retired
|
|
939
|
+
* branding — and hitting ENOENT on a key you just wrote, from a plugin that
|
|
940
|
+
* still reports itself installed, is a miserable first run.
|
|
941
|
+
*/
|
|
942
|
+
declare const KEY_FILENAMES: readonly ["guard-client-key", "atbash-client-key"];
|
|
943
|
+
/** Every path checked when no explicit key path is given, in order. */
|
|
944
|
+
declare function keyPathCandidates(): string[];
|
|
945
|
+
/**
|
|
946
|
+
* Pick the key path: an explicit input wins untouched; otherwise the first
|
|
947
|
+
* accepted filename that exists, falling back to the preferred name so the
|
|
948
|
+
* error names something recognisable when nothing is there.
|
|
949
|
+
*/
|
|
950
|
+
declare function chooseKeyPath(input: string | undefined, exists: (p: string) => boolean): string;
|
|
951
|
+
|
|
579
952
|
/**
|
|
580
953
|
* Canonical secret kinds reported by the Rust core in `SecretMatch.kind`.
|
|
581
954
|
* Wire is permissive (modelled as a free string in {@link SecretMatch})
|
|
@@ -594,6 +967,26 @@ declare function verifyJudgeResponseSignature(bodyBytes: Uint8Array, signatureHe
|
|
|
594
967
|
reason?: string;
|
|
595
968
|
};
|
|
596
969
|
|
|
970
|
+
/**
|
|
971
|
+
* The boot memory-sync failure line.
|
|
972
|
+
*
|
|
973
|
+
* Split out of `guard-manager.ts` so it can be asserted without constructing a
|
|
974
|
+
* guard manager, which needs the native addon. The message is the whole point
|
|
975
|
+
* of AT-304: hosts print the message and drop the structured meta, so a cause
|
|
976
|
+
* that lives only in meta never reaches the operator.
|
|
977
|
+
*/
|
|
978
|
+
/** Advice, not a diagnosis — appended only when the cause is unhelpful. */
|
|
979
|
+
declare const BOOT_SYNC_HINT = "check the chain endpoint and orgName if the cause above does not explain it";
|
|
980
|
+
/**
|
|
981
|
+
* Lead with the real cause.
|
|
982
|
+
*
|
|
983
|
+
* The previous wording named the chain endpoint and orgName as the things to
|
|
984
|
+
* check, which sent operators to verify configuration that was already correct
|
|
985
|
+
* while the actual cause (a node answering `404 Can't find blockchain with
|
|
986
|
+
* blockchainRID: …` for a chain it does not host) stayed hidden.
|
|
987
|
+
*/
|
|
988
|
+
declare function bootSyncFailureLine(cause: unknown): string;
|
|
989
|
+
|
|
597
990
|
/** Derive the 32-byte AES key from an agent's secp256k1 privkey (hex). Deterministic — same privkey always yields the same key. */
|
|
598
991
|
declare function deriveMemoryKey(privkey: string): Promise<Buffer>;
|
|
599
992
|
/** Encrypt UTF-8 plaintext. `ciphertext` has the 16-byte GCM auth tag appended, matching the SDK's on-chain byte layout. */
|
|
@@ -623,15 +1016,27 @@ declare function scanMemoryBatch(entries: MemoryEntry[], auth: AgentAuth, opts?:
|
|
|
623
1016
|
interface CommitMemoryOptions {
|
|
624
1017
|
/** Score in [1, 10]. Defaults to 5 (matches the Rell entity default). */
|
|
625
1018
|
score?: number;
|
|
1019
|
+
/**
|
|
1020
|
+
* Which memory file this commit targets. Defaults to `""` — the
|
|
1021
|
+
* un-pathed slot, matching Rell's `file_path: text = ""` default.
|
|
1022
|
+
* Pass an explicit filename (e.g. `"AGENTS.md"`) to keep files
|
|
1023
|
+
* versioned independently on chain.
|
|
1024
|
+
*/
|
|
1025
|
+
filePath?: string;
|
|
626
1026
|
/** Org name — when set, the SDK resolves which chain the agent lives on. */
|
|
627
1027
|
orgName?: string;
|
|
628
|
-
/** Atbash service endpoint
|
|
1028
|
+
/** Atbash service endpoint — where memory reaches the chain, and the org→chain lookup. */
|
|
629
1029
|
endpoint?: string;
|
|
1030
|
+
/** Self-hosted judge response-signing pubkey; required with a non-allowlisted `endpoint`. */
|
|
1031
|
+
verifyPubKey?: string;
|
|
630
1032
|
chainOpts?: ChainOpts;
|
|
631
1033
|
}
|
|
632
1034
|
interface RollbackMemoryOptions {
|
|
633
1035
|
orgName?: string;
|
|
1036
|
+
/** Atbash service endpoint — where memory reaches the chain, and the org→chain lookup. */
|
|
634
1037
|
endpoint?: string;
|
|
1038
|
+
/** Self-hosted judge response-signing pubkey; required with a non-allowlisted `endpoint`. */
|
|
1039
|
+
verifyPubKey?: string;
|
|
635
1040
|
chainOpts?: ChainOpts;
|
|
636
1041
|
}
|
|
637
1042
|
/**
|
|
@@ -653,6 +1058,7 @@ declare function commitMemoryVersion(plaintext: string, auth: AgentAuth, opts?:
|
|
|
653
1058
|
*/
|
|
654
1059
|
interface AgentMemoryEntry {
|
|
655
1060
|
id: number;
|
|
1061
|
+
filePath: string;
|
|
656
1062
|
content: string;
|
|
657
1063
|
decryptError?: string;
|
|
658
1064
|
score: number;
|
|
@@ -664,6 +1070,7 @@ interface AgentMemoryEntry {
|
|
|
664
1070
|
interface MemoryRollbackEvent {
|
|
665
1071
|
fromId: number;
|
|
666
1072
|
toId: number;
|
|
1073
|
+
filePath: string;
|
|
667
1074
|
reason: string;
|
|
668
1075
|
signer: string;
|
|
669
1076
|
createdAt: number;
|
|
@@ -674,36 +1081,39 @@ interface MemoryRollbackEvent {
|
|
|
674
1081
|
* response is a single integer, so this is safe to call on every
|
|
675
1082
|
* memory-read hot path.
|
|
676
1083
|
*/
|
|
677
|
-
declare function getActiveMemoryId(auth: AgentAuth, chainOpts?: ChainOpts): Promise<number | null>;
|
|
1084
|
+
declare function getActiveMemoryId(auth: AgentAuth, chainOpts?: ChainOpts, filePath?: string | null): Promise<number | null>;
|
|
678
1085
|
/**
|
|
679
1086
|
* Recent active memory entries — subset of active versions filtered
|
|
680
1087
|
* by the chain's `MEMORY_RECENT_WINDOW_MS`. For a time-unbounded view
|
|
681
|
-
* of every currently active version, use `
|
|
1088
|
+
* of every currently active version, use `getActiveAgentMemory`.
|
|
682
1089
|
*/
|
|
683
|
-
declare function
|
|
1090
|
+
declare function getRecentAgentMemory(auth: AgentAuth, chainOpts?: ChainOpts, filePath?: string | null): Promise<AgentMemoryEntry[]>;
|
|
684
1091
|
/**
|
|
685
1092
|
* All currently-active memory entries with no time cutoff. Use this
|
|
686
1093
|
* when you need every active version regardless of age.
|
|
687
1094
|
*/
|
|
688
|
-
declare function
|
|
1095
|
+
declare function getActiveAgentMemory(auth: AgentAuth, chainOpts?: ChainOpts, filePath?: string | null): Promise<AgentMemoryEntry[]>;
|
|
689
1096
|
/**
|
|
690
1097
|
* Full version history — active + inactive, most recent first. Used
|
|
691
1098
|
* by rollback UX to choose a target version.
|
|
692
1099
|
*/
|
|
693
|
-
declare function getMemoryHistory(auth: AgentAuth, chainOpts?: ChainOpts): Promise<AgentMemoryEntry[]>;
|
|
1100
|
+
declare function getMemoryHistory(auth: AgentAuth, chainOpts?: ChainOpts, filePath?: string | null): Promise<AgentMemoryEntry[]>;
|
|
694
1101
|
/**
|
|
695
1102
|
* Fetch a single memory entry by version id, including its current
|
|
696
|
-
* `is_active` state.
|
|
1103
|
+
* `is_active` state. Version ids are agent-unique on chain (not
|
|
1104
|
+
* per-file), so `id` alone resolves the target row.
|
|
697
1105
|
*/
|
|
698
1106
|
declare function getMemoryById(id: number, auth: AgentAuth, chainOpts?: ChainOpts): Promise<AgentMemoryEntry>;
|
|
699
1107
|
/**
|
|
700
1108
|
* Audit trail of rollback events for this agent, most recent first.
|
|
1109
|
+
* Scope by file with `filePath`; omit for a cross-file view.
|
|
701
1110
|
*/
|
|
702
|
-
declare function getRollbackHistory(auth: AgentAuth, chainOpts?: ChainOpts): Promise<MemoryRollbackEvent[]>;
|
|
1111
|
+
declare function getRollbackHistory(auth: AgentAuth, chainOpts?: ChainOpts, filePath?: string | null): Promise<MemoryRollbackEvent[]>;
|
|
703
1112
|
/**
|
|
704
1113
|
* Roll back to a previously-committed memory version. The target
|
|
705
|
-
* `toId` must exist and be currently inactive.
|
|
706
|
-
*
|
|
1114
|
+
* `toId` must exist and be currently inactive. The chain resolves the
|
|
1115
|
+
* target row's `file_path` from `toId` — no file path is passed in.
|
|
1116
|
+
* `reason` is required and is recorded on-chain in `memory_rollback_log`.
|
|
707
1117
|
*/
|
|
708
1118
|
declare function rollbackMemory(toId: number, reason: string, auth: AgentAuth, opts?: RollbackMemoryOptions): Promise<void>;
|
|
709
1119
|
|
|
@@ -755,21 +1165,15 @@ interface ClassifyMemoryWriteOptions {
|
|
|
755
1165
|
*
|
|
756
1166
|
* Empty-content writes return `null`: they can't carry a poisoning
|
|
757
1167
|
* payload, and the regular audit path still sees them.
|
|
758
|
-
*/
|
|
759
|
-
declare function classifyMemoryWrite(event: unknown, ctx: unknown, opts?: ClassifyMemoryWriteOptions): MemoryEntry | null;
|
|
760
|
-
|
|
761
|
-
/**
|
|
762
|
-
* Plugin-agnostic memory-write guard.
|
|
763
|
-
*
|
|
764
|
-
* A single call that replaces the plugin's usual memory-write branch:
|
|
765
|
-
* classify → scan (Layer 1 regex + Layer 2 LLM) → gate on verdict →
|
|
766
|
-
* persist to chain (fire-and-forget when allowed) → return decision.
|
|
767
1168
|
*
|
|
768
|
-
*
|
|
769
|
-
*
|
|
770
|
-
*
|
|
771
|
-
*
|
|
1169
|
+
* THROWS on host input it cannot read without side effects: a proxy, an accessor where a data
|
|
1170
|
+
* property is expected, a value with no safe projection, or one past the size bounds. Refusing is
|
|
1171
|
+
* deliberate - a silent `null` would report "not a memory write" for a call nobody could classify,
|
|
1172
|
+
* and memory protection would be skipped exactly where the input was strange. Callers that sit in a
|
|
1173
|
+
* host hook MUST catch it and turn it into a decision; `guardMemoryWrite` and
|
|
1174
|
+
* `MemoryGuardManager.handleBeforeToolCall` already do.
|
|
772
1175
|
*/
|
|
1176
|
+
declare function classifyMemoryWrite(event: unknown, ctx: unknown, opts?: ClassifyMemoryWriteOptions): MemoryEntry | null;
|
|
773
1177
|
|
|
774
1178
|
/**
|
|
775
1179
|
* Minimal logger accepted by `guardMemoryWrite`. Plugins pass their
|
|
@@ -793,6 +1197,8 @@ interface GuardMemoryWriteInput extends ClassifyMemoryWriteOptions {
|
|
|
793
1197
|
verifyPubKey?: string;
|
|
794
1198
|
/** Org name for chain resolution (public vs org-private chain). */
|
|
795
1199
|
orgName?: string;
|
|
1200
|
+
/** Explicit chain target when the host already resolved and verified it. */
|
|
1201
|
+
chainOpts?: ChainOpts;
|
|
796
1202
|
/** LLM confidence threshold for yellow escalation. Default 0.6. */
|
|
797
1203
|
threshold?: number;
|
|
798
1204
|
/** When false, red verdicts log but don't block. Default true. */
|
|
@@ -801,6 +1207,17 @@ interface GuardMemoryWriteInput extends ClassifyMemoryWriteOptions {
|
|
|
801
1207
|
debug?: boolean;
|
|
802
1208
|
/** Optional logger for debug probe + persist-failure warnings. */
|
|
803
1209
|
logger?: GuardLogger;
|
|
1210
|
+
/** Agent home directory names to accept beyond the built-in list. */
|
|
1211
|
+
agentHomeDirs?: string[];
|
|
1212
|
+
/**
|
|
1213
|
+
* Root the caller keeps memory under, when it is not an agent home.
|
|
1214
|
+
*
|
|
1215
|
+
* Only pass a value the host stated outright. A root defaulted from the
|
|
1216
|
+
* process working directory would make whichever directory the host happened
|
|
1217
|
+
* to start in look like a memory store — which is how a stray MEMORY.md once
|
|
1218
|
+
* ended up inside a plugin checkout.
|
|
1219
|
+
*/
|
|
1220
|
+
memoryRoot?: string;
|
|
804
1221
|
}
|
|
805
1222
|
/** Plugin-facing decision. Shape mirrors what plugins return from `before_tool_call`. */
|
|
806
1223
|
interface GuardMemoryDecision {
|
|
@@ -815,22 +1232,21 @@ interface GuardMemoryWriteResult {
|
|
|
815
1232
|
decision?: GuardMemoryDecision;
|
|
816
1233
|
/** Present when handled and scan ran. Absent when scan threw. */
|
|
817
1234
|
scanResult?: MemoryScanResult;
|
|
818
|
-
/** Present when handled. True
|
|
1235
|
+
/** Present when handled. True only after the chain transaction confirms. */
|
|
819
1236
|
committed?: boolean;
|
|
820
1237
|
}
|
|
821
1238
|
/**
|
|
822
|
-
*
|
|
823
|
-
*
|
|
824
|
-
*
|
|
1239
|
+
* Guard a memory write, and record what was decided.
|
|
1240
|
+
*
|
|
1241
|
+
* Wraps the decision rather than threading telemetry through its eight exits,
|
|
1242
|
+
* so a later return added inside cannot quietly stop being counted.
|
|
825
1243
|
*
|
|
826
|
-
*
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
* - `green` → persisted with LLM score; decision allows
|
|
1244
|
+
* Only a call this guard actually handled is recorded. A tool call that was
|
|
1245
|
+
* never a memory write is not a memory operation, and counting it would put
|
|
1246
|
+
* every unrelated edit into the number a memory migration reads.
|
|
830
1247
|
*
|
|
831
|
-
*
|
|
832
|
-
*
|
|
833
|
-
* existing plugin behaviour — chain is for audit/recovery, not gating.
|
|
1248
|
+
* @param input - The tool call and the agent to judge it for.
|
|
1249
|
+
* @returns What the guard decided; unchanged by the recording.
|
|
834
1250
|
*/
|
|
835
1251
|
declare function guardMemoryWrite(input: GuardMemoryWriteInput): Promise<GuardMemoryWriteResult>;
|
|
836
1252
|
|
|
@@ -860,17 +1276,26 @@ interface SyncMemoryOptions {
|
|
|
860
1276
|
/** Skip the TTL gate and force a chain check this call. Default false. */
|
|
861
1277
|
force?: boolean;
|
|
862
1278
|
chainOpts?: ChainOpts;
|
|
1279
|
+
/** Exact chain slot for the managed file; omission keeps the cross-file API behavior. */
|
|
1280
|
+
filePath?: string;
|
|
863
1281
|
}
|
|
864
1282
|
/**
|
|
865
1283
|
* `drifted: false` — pointer is still valid; caller can keep serving the local copy.
|
|
866
1284
|
* `drifted: true` — active id changed on chain; `current` is the fresh decrypted row
|
|
867
1285
|
* (or `null` if active memory was removed entirely).
|
|
1286
|
+
*
|
|
1287
|
+
* `checked` — whether this call actually queried chain. `false` means the TTL
|
|
1288
|
+
* window was still open and the pointer was trusted without contacting chain, so
|
|
1289
|
+
* `drifted: false` carries no evidence about the current state. Callers that
|
|
1290
|
+
* vouch for content to a third party must not treat an unchecked result as proof.
|
|
868
1291
|
*/
|
|
869
1292
|
type SyncMemoryResult = {
|
|
870
1293
|
drifted: false;
|
|
1294
|
+
checked: boolean;
|
|
871
1295
|
pointer: MemoryPointer;
|
|
872
1296
|
} | {
|
|
873
1297
|
drifted: true;
|
|
1298
|
+
checked: true;
|
|
874
1299
|
current: AgentMemoryEntry | null;
|
|
875
1300
|
pointer: MemoryPointer;
|
|
876
1301
|
};
|
|
@@ -886,10 +1311,19 @@ declare class PointerStore {
|
|
|
886
1311
|
private cache;
|
|
887
1312
|
private loading;
|
|
888
1313
|
constructor(filePath: string);
|
|
889
|
-
/**
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1314
|
+
/**
|
|
1315
|
+
* The pointer for one agent's file, or a zero-pointer forcing a sync.
|
|
1316
|
+
*
|
|
1317
|
+
* @param filePath - The file's chain slot, i.e. its basename.
|
|
1318
|
+
*/
|
|
1319
|
+
get(agentPubkeyHex: string, filePath: string): Promise<MemoryPointer>;
|
|
1320
|
+
/**
|
|
1321
|
+
* Persist one file's pointer, leaving the agent's other files alone.
|
|
1322
|
+
*
|
|
1323
|
+
* Write failures are reported to `onError` rather than thrown: a cache that
|
|
1324
|
+
* cannot be written must not take down the read it was speeding up.
|
|
1325
|
+
*/
|
|
1326
|
+
set(agentPubkeyHex: string, filePath: string, pointer: MemoryPointer, onError?: (err: Error) => void): Promise<void>;
|
|
893
1327
|
private ensureLoaded;
|
|
894
1328
|
private loadOnce;
|
|
895
1329
|
private persist;
|
|
@@ -901,6 +1335,8 @@ declare function defaultPointerPath(workspaceDir?: string): string;
|
|
|
901
1335
|
interface WrappedLogger {
|
|
902
1336
|
info(message: string, meta?: Record<string, unknown>): void;
|
|
903
1337
|
warn(message: string, meta?: Record<string, unknown>): void;
|
|
1338
|
+
/** Wait for queued writes and surface any lost log entry. */
|
|
1339
|
+
flush(): Promise<void>;
|
|
904
1340
|
}
|
|
905
1341
|
interface UpstreamLogger {
|
|
906
1342
|
info: (...args: unknown[]) => void;
|
|
@@ -929,15 +1365,52 @@ interface ClassifyMemoryReadOptions {
|
|
|
929
1365
|
* Caller-supplied `patterns` are MERGED with the defaults (matches
|
|
930
1366
|
* Node's original behavior — extending in one plugin doesn't disable
|
|
931
1367
|
* standard coverage).
|
|
1368
|
+
*
|
|
1369
|
+
* THROWS on host input it cannot read without side effects: a proxy, an accessor where a data
|
|
1370
|
+
* property is expected, a value with no safe projection, or one past the size bounds. Refusing is
|
|
1371
|
+
* deliberate - a silent `null` would report "not a memory write" for a call nobody could classify,
|
|
1372
|
+
* and memory protection would be skipped exactly where the input was strange. Callers that sit in a
|
|
1373
|
+
* host hook MUST catch it and turn it into a decision; `guardMemoryWrite` and
|
|
1374
|
+
* `MemoryGuardManager.handleBeforeToolCall` already do.
|
|
932
1375
|
*/
|
|
933
1376
|
declare function classifyMemoryRead(event: unknown, ctx: unknown, opts?: ClassifyMemoryReadOptions): boolean;
|
|
934
1377
|
|
|
935
|
-
/**
|
|
1378
|
+
/**
|
|
1379
|
+
* Decision the manager returns to the plugin's `before_tool_call` handler.
|
|
1380
|
+
*
|
|
1381
|
+
* `allow: true` alone is NOT evidence that anything was checked. Read `audited`
|
|
1382
|
+
* to tell the two apart, and route un-audited calls to your own judge — see the
|
|
1383
|
+
* field docs below.
|
|
1384
|
+
*/
|
|
936
1385
|
interface HookDecision {
|
|
937
1386
|
allow?: boolean;
|
|
938
1387
|
block?: boolean;
|
|
939
1388
|
blockReason?: string;
|
|
940
1389
|
reason?: string;
|
|
1390
|
+
/**
|
|
1391
|
+
* Whether the guard reached an enforcement decision about *this* call.
|
|
1392
|
+
*
|
|
1393
|
+
* Note this describes whether the guard **decided**, not whether it allowed.
|
|
1394
|
+
* Every `block` is `audited: true` — a blocked call is the most thoroughly
|
|
1395
|
+
* checked outcome the guard produces (a red scan, a ciphertext integrity
|
|
1396
|
+
* failure, a rolled-back version), and a host must never re-judge its way past
|
|
1397
|
+
* one.
|
|
1398
|
+
*
|
|
1399
|
+
* Absent or false means the guard reached no decision — for example, the scan
|
|
1400
|
+
* never ran or this manager does not own the requested file. In enforce mode,
|
|
1401
|
+
* failed chain verification of the managed file is a block, never an unaudited
|
|
1402
|
+
* allow. In observe mode unverified calls remain unaudited.
|
|
1403
|
+
*
|
|
1404
|
+
* So the host rule is:
|
|
1405
|
+
* `if (d.block) deny; else if (d.audited) allow; else judge it yourself;`
|
|
1406
|
+
*
|
|
1407
|
+
* Treating a bare `allow: true` as a completed audit is what this field exists
|
|
1408
|
+
* to prevent. A host that ignores it and returns the decision verbatim will
|
|
1409
|
+
* execute unaudited tool calls.
|
|
1410
|
+
*/
|
|
1411
|
+
audited?: boolean;
|
|
1412
|
+
/** Scan verdict when one was produced (`green` | `yellow` | `red`). Absent when no scan ran. */
|
|
1413
|
+
verdict?: string;
|
|
941
1414
|
}
|
|
942
1415
|
interface MemoryGuardManagerOptions {
|
|
943
1416
|
auth: AgentAuth;
|
|
@@ -945,6 +1418,17 @@ interface MemoryGuardManagerOptions {
|
|
|
945
1418
|
workspaceDir: string;
|
|
946
1419
|
/** Absolute MEMORY.md path. Default `<workspaceDir>/MEMORY.md`. */
|
|
947
1420
|
memoryFilePath?: string;
|
|
1421
|
+
/**
|
|
1422
|
+
* Root this host keeps memory under, when it is not an agent home.
|
|
1423
|
+
*
|
|
1424
|
+
* Only set this from a value the host stated outright — never from
|
|
1425
|
+
* `process.cwd()`. A guessed root makes whichever directory the host started
|
|
1426
|
+
* in look like a memory store, which is how a stray MEMORY.md once ended up
|
|
1427
|
+
* inside a plugin checkout.
|
|
1428
|
+
*/
|
|
1429
|
+
memoryRoot?: string;
|
|
1430
|
+
/** Agent home directory names to accept beyond the built-in list. */
|
|
1431
|
+
agentHomeDirs?: string[];
|
|
948
1432
|
/** Persistent pointer file. Default `<workspaceDir>/.atbash/memory-pointer.json`. */
|
|
949
1433
|
pointerFilePath?: string;
|
|
950
1434
|
/** File-backed log. Default `<workspaceDir>/.atbash/plugin.log`. */
|
|
@@ -960,6 +1444,13 @@ interface MemoryGuardManagerOptions {
|
|
|
960
1444
|
rollbackMinScore?: number;
|
|
961
1445
|
/** True → return `{block:true}` on defense triggers. False → log and return `null` (audit-only). Default true. */
|
|
962
1446
|
enforce?: boolean;
|
|
1447
|
+
/**
|
|
1448
|
+
* Chain targeting for the pointer sync (network, blockchainRid, nodeUrls).
|
|
1449
|
+
* Defaults to the SDK's configured chain. Without this the manager could only
|
|
1450
|
+
* ever talk to the default chain, which left the whole memory-read path
|
|
1451
|
+
* untestable — `syncLocalMemory` already accepted these options.
|
|
1452
|
+
*/
|
|
1453
|
+
chainOpts?: ChainOpts;
|
|
963
1454
|
/** Host-specific tuning of what counts as a memory read. */
|
|
964
1455
|
memoryReadClassifier?: ClassifyMemoryReadOptions;
|
|
965
1456
|
/** Passed through to `guardMemoryWrite`. Host memory-write tool names override. */
|
|
@@ -984,11 +1475,45 @@ declare class MemoryGuardManager {
|
|
|
984
1475
|
private readonly pointerStore;
|
|
985
1476
|
private readonly logger;
|
|
986
1477
|
private readonly memoryFilePath;
|
|
1478
|
+
/** Base for resolving the relative paths a host sends. */
|
|
1479
|
+
private readonly workspaceDir;
|
|
1480
|
+
/** Set by `verifyOneFile` when the file it checked was actually on chain. */
|
|
1481
|
+
private lastVerified;
|
|
987
1482
|
private readonly ttlMs;
|
|
988
1483
|
private readonly rollbackMinScore;
|
|
989
1484
|
private readonly enforce;
|
|
990
1485
|
private readonly agentPubkeyHex;
|
|
1486
|
+
/**
|
|
1487
|
+
* Memoized org→chain resolution. Reads have to hit the SAME chain
|
|
1488
|
+
* writes did, so an org-scoped guard must resolve `orgName` to
|
|
1489
|
+
* network exactly like `commitMemoryVersion` does. Without this
|
|
1490
|
+
* cache the read path would either (a) hit the SDK-default chain
|
|
1491
|
+
* every time — silently returning "no active memory on chain" when
|
|
1492
|
+
* writes landed on the org's actual chain, or (b) hammer
|
|
1493
|
+
* `/api/org-network` on every read. `undefined` means "not yet
|
|
1494
|
+
* resolved"; a resolved `null` means "no org / use raw chainOpts".
|
|
1495
|
+
*/
|
|
1496
|
+
private _resolvedChainOpts;
|
|
1497
|
+
private _resolveChainInflight?;
|
|
991
1498
|
constructor(opts: MemoryGuardManagerOptions);
|
|
1499
|
+
/**
|
|
1500
|
+
* Carry this manager's host into `chainOpts`.
|
|
1501
|
+
*
|
|
1502
|
+
* Memory reads reach the chain through that host, and they are handed only
|
|
1503
|
+
* `chainOpts` — without this a self-hosted deployment would read from the
|
|
1504
|
+
* compiled-in default host while writing to its own.
|
|
1505
|
+
*/
|
|
1506
|
+
private withEndpoint;
|
|
1507
|
+
/**
|
|
1508
|
+
* Resolve `orgName` → chain once, cache forever. `commitMemoryVersion`
|
|
1509
|
+
* already does this for writes; without the same call on the read
|
|
1510
|
+
* path, a client on the SDK's baked default chain reads from the wrong
|
|
1511
|
+
* chain and reports "no active memory" for an agent whose writes did
|
|
1512
|
+
* land — on the org's actual chain. An explicit `chainOpts.blockchainRid`
|
|
1513
|
+
* still wins (caller vouched for it); everything else honors the
|
|
1514
|
+
* dashboard's `org_networks` map.
|
|
1515
|
+
*/
|
|
1516
|
+
private resolveChainOpts;
|
|
992
1517
|
/**
|
|
993
1518
|
* One-shot chain probe at plugin registration. Refreshes MEMORY.md
|
|
994
1519
|
* from chain when drifted and score passes threshold. Fire-and-forget
|
|
@@ -996,27 +1521,125 @@ declare class MemoryGuardManager {
|
|
|
996
1521
|
*/
|
|
997
1522
|
runBootProbe(): Promise<void>;
|
|
998
1523
|
/**
|
|
999
|
-
*
|
|
1000
|
-
*
|
|
1001
|
-
*
|
|
1524
|
+
* Bring one chain file into step with its local counterpart.
|
|
1525
|
+
*
|
|
1526
|
+
* Only a file that already exists locally is touched. Restoring one that is
|
|
1527
|
+
* absent would mean choosing where to create it, and the root is a guess the
|
|
1528
|
+
* moment more than one could apply — the same guess that once wrote a
|
|
1529
|
+
* MEMORY.md into a plugin checkout.
|
|
1530
|
+
*
|
|
1531
|
+
* @returns What happened, for the single summary line the caller logs.
|
|
1532
|
+
*/
|
|
1533
|
+
private syncOneAtBoot;
|
|
1534
|
+
/**
|
|
1535
|
+
* Where a chain slot's file lives locally, or null when we do not have it.
|
|
1536
|
+
*
|
|
1537
|
+
* A stated root is tried first, then each agent home that actually exists in
|
|
1538
|
+
* the user's home directory — the same rule that produced the slot, run
|
|
1539
|
+
* backwards. Only an existing file is returned: creating one would mean
|
|
1540
|
+
* picking a root, and picking is guessing.
|
|
1002
1541
|
*/
|
|
1542
|
+
private locate;
|
|
1543
|
+
/** Roots a chain slot may be joined to, most specific first. */
|
|
1544
|
+
private memoryRoots;
|
|
1003
1545
|
handleBeforeToolCall(event: unknown, ctx: unknown): Promise<HookDecision | null>;
|
|
1004
1546
|
private mapGuardResult;
|
|
1547
|
+
/**
|
|
1548
|
+
* Whether the pointer state this manager tracks actually describes the file
|
|
1549
|
+
* this call is about to read.
|
|
1550
|
+
*
|
|
1551
|
+
* The classifier fires on nine patterns — including the bare tokens
|
|
1552
|
+
* `"memory/"`, `"CLAUDE.md"` and `"AGENTS.md"` — but the sync path only ever
|
|
1553
|
+
* reads, refreshes, or vouches for `this.memoryFilePath`. Without this check a
|
|
1554
|
+
* read of `/repo/CLAUDE.md` (or any path merely containing `memory/`) would
|
|
1555
|
+
* receive an `audited: true` for a file the guard never opened.
|
|
1556
|
+
*
|
|
1557
|
+
* Conservative on purpose: every path-shaped value found must resolve to the
|
|
1558
|
+
* managed file. If none is found, or any one differs, the answer is no. That
|
|
1559
|
+
* also covers events carrying two different path keys, where the classifier
|
|
1560
|
+
* and the host could otherwise disagree about which one is authoritative.
|
|
1561
|
+
*/
|
|
1562
|
+
/**
|
|
1563
|
+
* The memory files this tool call reads, as absolute paths.
|
|
1564
|
+
*
|
|
1565
|
+
* Relative paths resolve against the workspace, not `process.cwd()`. The host
|
|
1566
|
+
* sends paths relative to the agent's workspace; resolving them against the
|
|
1567
|
+
* process's own directory made the guard protect whichever directory the host
|
|
1568
|
+
* happened to start in, which on one machine was a plugin checkout.
|
|
1569
|
+
*
|
|
1570
|
+
* @returns Every named target, de-duplicated; empty when the call names none.
|
|
1571
|
+
*/
|
|
1572
|
+
private readTargets;
|
|
1573
|
+
/**
|
|
1574
|
+
* Say what the read classifier saw and what it concluded.
|
|
1575
|
+
*
|
|
1576
|
+
* The write path has `emitDebugProbe`; this is its counterpart, so a tool
|
|
1577
|
+
* call can be followed across the fork instead of only after it. `false` here
|
|
1578
|
+
* is the normal answer for a write — it means the call goes to the write
|
|
1579
|
+
* classifier next, not that it was ignored.
|
|
1580
|
+
*
|
|
1581
|
+
* @param isMemoryRead - What `classifyMemoryRead` returned.
|
|
1582
|
+
*/
|
|
1583
|
+
private emitReadProbe;
|
|
1584
|
+
/**
|
|
1585
|
+
* The chain slot for a path, or null when the file gets no chain record.
|
|
1586
|
+
*
|
|
1587
|
+
* One helper so the boot probe and every read agree: taking a basename in one
|
|
1588
|
+
* place and a relative path in the other would verify a file against a record
|
|
1589
|
+
* that is not its own.
|
|
1590
|
+
*/
|
|
1591
|
+
private slotFor;
|
|
1592
|
+
/**
|
|
1593
|
+
* Verify every memory file this call reads, each against its own chain slot.
|
|
1594
|
+
*
|
|
1595
|
+
* A call naming several files is only cleared when all of them are, so one
|
|
1596
|
+
* unverifiable file cannot ride along with a verified one.
|
|
1597
|
+
*
|
|
1598
|
+
* @returns A block for the first file that fails, an audited allow when all
|
|
1599
|
+
* were verified, or null when none of them is on chain.
|
|
1600
|
+
*/
|
|
1005
1601
|
private handleMemoryRead;
|
|
1602
|
+
/**
|
|
1603
|
+
* Check one memory file against the chain slot of the same name.
|
|
1604
|
+
*
|
|
1605
|
+
* A file with no chain version is passed through rather than blocked: there
|
|
1606
|
+
* is no claim to protect, so refusing would only stop an agent reading memory
|
|
1607
|
+
* it has never committed. A file that IS on chain must match, or the read is
|
|
1608
|
+
* refused — that is the whole guarantee.
|
|
1609
|
+
*
|
|
1610
|
+
* @returns A decision when the read must not proceed, else null.
|
|
1611
|
+
*/
|
|
1612
|
+
private verifyOneFile;
|
|
1613
|
+
/** Complete queued audit-log writes before a host shuts down or removes its workspace. */
|
|
1614
|
+
flushLogs(): Promise<void>;
|
|
1615
|
+
/**
|
|
1616
|
+
* A read this guard could not verify.
|
|
1617
|
+
*
|
|
1618
|
+
* Enforcing, that is a block. Observing, the host audits it as usual — but
|
|
1619
|
+
* the reason is said out loud either way, or an unverified read looks
|
|
1620
|
+
* identical to a clean one in the log.
|
|
1621
|
+
*/
|
|
1622
|
+
private unverifiedRead;
|
|
1006
1623
|
private writeMemoryAtomic;
|
|
1624
|
+
/** Replace `target` in one step, so a reader never sees a half-written file. */
|
|
1625
|
+
private writeFileAtomic;
|
|
1007
1626
|
}
|
|
1008
1627
|
declare function createMemoryGuardManager(opts: MemoryGuardManagerOptions): MemoryGuardManager;
|
|
1009
1628
|
|
|
1010
1629
|
/**
|
|
1011
1630
|
* Atbash SDK Telemetry — OpenTelemetry metrics for usage tracking.
|
|
1012
1631
|
*
|
|
1013
|
-
*
|
|
1014
|
-
*
|
|
1632
|
+
* Metrics are POSTed to the Atbash-owned `/api/telemetry` proxy, which
|
|
1633
|
+
* verifies the bearer, injects the Honeycomb ingest key server-side, and
|
|
1634
|
+
* forwards to Honeycomb. The ingest credential never enters the SDK.
|
|
1635
|
+
*
|
|
1636
|
+
* Environment opt-out (recommended for air-gapped deployments):
|
|
1637
|
+
* ATBASH_TELEMETRY_DISABLED=1
|
|
1015
1638
|
*
|
|
1016
1639
|
* Opt-out: create ~/.config/atbash/telemetry.json with { "enabled": false }
|
|
1017
1640
|
* The file must be readable by the SDK process. If missing, corrupted, or
|
|
1018
|
-
* unreadable
|
|
1019
|
-
*
|
|
1641
|
+
* unreadable, telemetry remains eligible to start unless the environment
|
|
1642
|
+
* opt-out is set.
|
|
1020
1643
|
*/
|
|
1021
1644
|
type ClientSource = "cli" | "sdk" | "plugin:openclaw" | "plugin:langchain" | "plugin:langgraph" | "plugin:hermes" | "plugin:eliza" | "plugin:crewai" | "plugin:mcp" | "plugin:autogen" | "plugin:jeenai" | (string & {});
|
|
1022
1645
|
interface TelemetryConfig {
|
|
@@ -1026,6 +1649,27 @@ interface TelemetryConfig {
|
|
|
1026
1649
|
source?: ClientSource;
|
|
1027
1650
|
/** Flush interval in ms. Default: 60000 */
|
|
1028
1651
|
exportIntervalMs?: number;
|
|
1652
|
+
/** Atbash endpoint that hosts /api/telemetry. Required to actually export. */
|
|
1653
|
+
endpoint?: string;
|
|
1654
|
+
/**
|
|
1655
|
+
* Called on every export to obtain fresh auth headers (typically
|
|
1656
|
+
* `{ Authorization: "Bearer <hex>" }`). Required to actually export.
|
|
1657
|
+
*/
|
|
1658
|
+
getAuthHeaders?: () => Record<string, string>;
|
|
1659
|
+
/**
|
|
1660
|
+
* Hashes the agent key before it is emitted on a wide event.
|
|
1661
|
+
*
|
|
1662
|
+
* Defaults to an unsalted SHA-256, which is de-identification rather than
|
|
1663
|
+
* anonymisation: agent pubkeys are public on chain and can be enumerated, so
|
|
1664
|
+
* the mapping is recoverable by anyone holding that list. Supply a salted
|
|
1665
|
+
* hasher to make it uncorrelatable, at the cost of joining across
|
|
1666
|
+
* deployments.
|
|
1667
|
+
*
|
|
1668
|
+
* Injected rather than imported so the telemetry modules need nothing from
|
|
1669
|
+
* the native addon. Left unset, the agent field is dropped from events
|
|
1670
|
+
* rather than falling back to the raw key.
|
|
1671
|
+
*/
|
|
1672
|
+
hashAgentKey?: (pubkeyHex: string) => string;
|
|
1029
1673
|
}
|
|
1030
1674
|
declare function setupTelemetry(config: TelemetryConfig): void;
|
|
1031
1675
|
/**
|
|
@@ -1049,35 +1693,121 @@ declare function flushTelemetry(): Promise<void>;
|
|
|
1049
1693
|
declare function shutdownTelemetry(): Promise<void>;
|
|
1050
1694
|
|
|
1051
1695
|
/**
|
|
1052
|
-
*
|
|
1053
|
-
*
|
|
1054
|
-
*
|
|
1055
|
-
*
|
|
1696
|
+
* Wide events: one row per SDK operation, alongside the counters.
|
|
1697
|
+
*
|
|
1698
|
+
* The counters in `telemetry.ts` are aggregated inside this process before
|
|
1699
|
+
* export, so Honeycomb never receives a per-operation row. That is fine for
|
|
1700
|
+
* "is it up" and useless for everything else: a question nobody attached an
|
|
1701
|
+
* attribute for in advance cannot be answered later, because the data to
|
|
1702
|
+
* answer it never existed. "Which org sees the most failures", "what preceded
|
|
1703
|
+
* a fail-closed", "is the new release slower" are all that shape.
|
|
1704
|
+
*
|
|
1705
|
+
* So each operation also emits one event carrying its whole context. The
|
|
1706
|
+
* counters stay — they are the right tool for health boards and alerts, and
|
|
1707
|
+
* they are not sampled.
|
|
1708
|
+
*
|
|
1709
|
+
* TWO RULES SHAPE THIS MODULE.
|
|
1710
|
+
*
|
|
1711
|
+
* The payload is METADATA ONLY, and the allowlist below is the whole of it:
|
|
1712
|
+
* function name, outcome, latency, error class, error status and path, SDK
|
|
1713
|
+
* version, chain, and a hash of the agent key. Action text, tool arguments, policy text, organisation
|
|
1714
|
+
* names and keys never appear — not as a field that is emptied, but as a field
|
|
1715
|
+
* that does not exist, so there is nothing to forget to strip.
|
|
1716
|
+
*
|
|
1717
|
+
* The agent key is hashed, never sent raw, and the hasher is INJECTED rather
|
|
1718
|
+
* than imported: this module needs nothing from the native addon. With no
|
|
1719
|
+
* hasher supplied the field is DROPPED rather than falling back to the raw
|
|
1720
|
+
* key, so the guarantee does not depend on wiring somebody could forget.
|
|
1721
|
+
*
|
|
1722
|
+
* NOTE on what hashing does and does not buy. Agent pubkeys are public on
|
|
1723
|
+
* chain and can be enumerated, so an UNSALTED hash is reversible by anyone who
|
|
1724
|
+
* can read the chain: it is de-identification, not anonymisation. A salted
|
|
1725
|
+
* hasher makes it uncorrelatable and costs cross-deployment joins. Which one
|
|
1726
|
+
* is wanted is the caller's decision; this module only guarantees the raw key
|
|
1727
|
+
* never leaves.
|
|
1728
|
+
*
|
|
1729
|
+
* And the richer payload has its own switch. Counters carry a function name
|
|
1730
|
+
* and a source. These events carry agent and org identity off a customer's
|
|
1731
|
+
* machine, which is a different proposition for a security product, so
|
|
1732
|
+
* `ATBASH_TELEMETRY_EVENTS_DISABLED=1` turns them off while leaving the health
|
|
1733
|
+
* counters on. Both remain subject to the existing global opt-out.
|
|
1056
1734
|
*/
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1735
|
+
|
|
1736
|
+
/** Attributes an event may carry. Anything not listed here cannot be emitted. */
|
|
1737
|
+
interface OperationEvent {
|
|
1738
|
+
/** SDK function, matching the counter's `function.name` so the two join. */
|
|
1739
|
+
functionName: string;
|
|
1740
|
+
durationMs: number;
|
|
1741
|
+
status: "success" | "error";
|
|
1742
|
+
/**
|
|
1743
|
+
* Hex pubkey of the agent the call was made as. Hashed before it is emitted,
|
|
1744
|
+
* and omitted entirely when no hasher is configured.
|
|
1745
|
+
*/
|
|
1746
|
+
agentPubkey?: string;
|
|
1747
|
+
/**
|
|
1748
|
+
* Blockchain RID of the chain this call concerned.
|
|
1749
|
+
*
|
|
1750
|
+
* The RID rather than a network label: it is public infrastructure, it is
|
|
1751
|
+
* unambiguous, and it identifies the deployment without naming a customer.
|
|
1752
|
+
*/
|
|
1753
|
+
chain?: string;
|
|
1754
|
+
/** Constructor name of the thrown error, e.g. `AtbashAPIError`. */
|
|
1755
|
+
errorClass?: string;
|
|
1756
|
+
/** HTTP status, or 0 when the request never completed. */
|
|
1757
|
+
errorStatus?: number;
|
|
1758
|
+
/**
|
|
1759
|
+
* Request path the status came from, e.g. `/api/v1/judge`.
|
|
1760
|
+
*
|
|
1761
|
+
* The class alone does not say what failed: several routes answer one call
|
|
1762
|
+
* and more than one can 404, so `AtbashAPIError` cannot distinguish "this
|
|
1763
|
+
* agent is not registered" from "the chain-routing lookup did not resolve".
|
|
1764
|
+
* The path settles it.
|
|
1765
|
+
*
|
|
1766
|
+
* Carries no query string — `requestPath` strips it at the point the error
|
|
1767
|
+
* is built, because a query string carries org names, tool call ids and the
|
|
1768
|
+
* agent pubkey. Structured and already sanitised, so this stays inside the
|
|
1769
|
+
* metadata rule where an error message would not.
|
|
1770
|
+
*/
|
|
1771
|
+
errorPath?: string;
|
|
1772
|
+
/**
|
|
1773
|
+
* Short categorical result, when "success" is too coarse — e.g. a memory
|
|
1774
|
+
* write that scanned clean but never reached the chain.
|
|
1775
|
+
*
|
|
1776
|
+
* Categorical by contract: a value a caller chooses from a fixed vocabulary,
|
|
1777
|
+
* never free text and never anything derived from user content.
|
|
1778
|
+
*/
|
|
1779
|
+
outcome?: string;
|
|
1063
1780
|
}
|
|
1781
|
+
/** Hashes an agent key for emission. Supplied by `setupTelemetry`. */
|
|
1782
|
+
type AgentKeyHasher = (pubkeyHex: string) => string;
|
|
1783
|
+
declare function configureEvents(next: Logger | null, opts?: {
|
|
1784
|
+
version?: string;
|
|
1785
|
+
source?: string;
|
|
1786
|
+
hashAgentKey?: AgentKeyHasher;
|
|
1787
|
+
}): void;
|
|
1788
|
+
/** True when an event would actually be emitted. Exported for tests and callers that build context lazily. */
|
|
1789
|
+
declare function eventsAreOn(): boolean;
|
|
1064
1790
|
/**
|
|
1065
|
-
*
|
|
1066
|
-
*
|
|
1067
|
-
*
|
|
1791
|
+
* Emit one wide event for a completed operation.
|
|
1792
|
+
*
|
|
1793
|
+
* Never throws and never blocks: telemetry failing must not change what the
|
|
1794
|
+
* caller observes. Does nothing when events are off.
|
|
1795
|
+
*
|
|
1796
|
+
* @param e - The completed operation.
|
|
1068
1797
|
*/
|
|
1069
|
-
declare function
|
|
1798
|
+
declare function recordEvent(e: OperationEvent): void;
|
|
1799
|
+
|
|
1070
1800
|
/**
|
|
1071
|
-
*
|
|
1072
|
-
*
|
|
1073
|
-
* org key; this hash lets it verify body-vs-ciphertext without decrypting.
|
|
1074
|
-
* Byte-identical to the dashboard's `claimHashHex`.
|
|
1801
|
+
* Rust core does the work; `browser/encrypted-toolcall.ts` mirrors it for the
|
|
1802
|
+
* browser and must produce the same columns.
|
|
1075
1803
|
*/
|
|
1804
|
+
/** Must match `column_aad` in the core — the label binds a ciphertext to its column. */
|
|
1805
|
+
declare function columnAad(toolCallId: string, column: string): string;
|
|
1806
|
+
/** Byte-identical to the dashboard's copy — diverging breaks hold-retry resolution. */
|
|
1807
|
+
declare function normalizeActionForHash(action: string): string;
|
|
1808
|
+
/** Lets the judge check the request body against the ciphertext without an org key. */
|
|
1076
1809
|
declare function claimHashHex(toolName: string, action: string, context: string, toolArgsJson: string): string;
|
|
1077
|
-
/**
|
|
1078
|
-
* Build + sign a `log_encrypted_tool_call` transaction. Returns hex-encoded
|
|
1079
|
-
* signed tx, ready to POST as `signed_log_tool_call`.
|
|
1080
|
-
*/
|
|
1810
|
+
/** @returns hex-encoded signed tx, ready to POST as `signed_log_tool_call`. */
|
|
1081
1811
|
declare function signEncryptedToolCall(toolCallId: string, action: string, context: string, toolName: string, toolArgsJson: string, orgEncryptionPubKey: string, privkeyHex: string, blockchainRidHex: string): string;
|
|
1082
1812
|
|
|
1083
1813
|
/**
|
|
@@ -1126,6 +1856,29 @@ declare function decryptForOrg(payload: Uint8Array, orgPrivKeyHex: string, aad:
|
|
|
1126
1856
|
*/
|
|
1127
1857
|
declare function encryptedLength(plaintextByteLength: number): number;
|
|
1128
1858
|
|
|
1859
|
+
/**
|
|
1860
|
+
* atb1.<key-fingerprint>.<claim-hash>.<base64 ciphertext>
|
|
1861
|
+
*
|
|
1862
|
+
* Normative spec: `core/src/crypto_envelope.rs`. This mirrors it for the browser.
|
|
1863
|
+
*/
|
|
1864
|
+
interface Envelope {
|
|
1865
|
+
/** First 8 bytes of the recipient public key, hex. May be empty. */
|
|
1866
|
+
keyFingerprint: string;
|
|
1867
|
+
/** Commitment to the accompanying plaintext claims. May be empty. */
|
|
1868
|
+
claimHash: string;
|
|
1869
|
+
/** Raw ECIES payload. */
|
|
1870
|
+
payload: Uint8Array;
|
|
1871
|
+
}
|
|
1872
|
+
declare function packEnvelope(payload: Uint8Array, keyFingerprint?: string, claimHash?: string): string;
|
|
1873
|
+
/**
|
|
1874
|
+
* Stays true for a truncated envelope that `parseEnvelope` rejects — a severed
|
|
1875
|
+
* ciphertext is not plaintext, so callers must show a placeholder.
|
|
1876
|
+
*/
|
|
1877
|
+
declare function isEnvelope(value: string): boolean;
|
|
1878
|
+
/** Null, not a throw — pre-encryption records are plaintext. */
|
|
1879
|
+
declare function parseEnvelope(value: string): Envelope | null;
|
|
1880
|
+
declare function keyFingerprintOf(pubKeyHex: string): string;
|
|
1881
|
+
|
|
1129
1882
|
declare function isValidPrivateKey(hex: string): boolean;
|
|
1130
1883
|
declare function derivePublicKey(privkey: string): string;
|
|
1131
1884
|
declare function generateKeypair(): KeyPair;
|
|
@@ -1141,4 +1894,4 @@ declare function containsSecret(text: string): boolean;
|
|
|
1141
1894
|
declare function createMemorySnapshot(entries: MemoryEntry[], takenAt: number): MemorySnapshot;
|
|
1142
1895
|
declare function diffMemorySnapshots(before: MemorySnapshot, after: MemorySnapshot): MemoryDiffResult;
|
|
1143
1896
|
|
|
1144
|
-
export { type ActionType, type AgentAuth, type AgentMemoryEntry, type AgentPolicy, type AnomalySeverity, type AnomalyType, Atbash, AtbashAPIError, type AtbashLogger, type AtbashOptions, type AtbashUserConfig, type ChainOpts, type ClassifierToolContext, type ClassifierToolEvent, type ClassifyMemoryReadOptions, type ClassifyMemoryWriteOptions, type ClientSource, type CommitMemoryOptions, DEFAULT_BLOCKCHAIN_RID, DEFAULT_CHROMIA_NODE_URLS, DEFAULT_ENDPOINT, DEFAULT_MEMORY_PATH_PATTERNS, DEFAULT_MEMORY_READ_TOOL_NAMES, DEFAULT_MEMORY_WRITE_TOOL_NAMES, type Decision, type DecisionVerdict, EciesDomain, type EncryptedMemory, type FromConfigOptions, type GuardLogger, type GuardMemoryDecision, type GuardMemoryWriteInput, type GuardMemoryWriteResult, type HeldAction, type HeldActionReview, type HookDecision, type JudgeEndpointConfig, type JudgeOptions, type JudgeResult, type JudgmentState, type JudgmentStatus, type KeyPair, type LogToolCallOptions, type LogToolCallResult, type MemoryAnomaly, type MemoryDiffResult, type MemoryEntry, MemoryGuardManager, type MemoryGuardManagerOptions, MemoryIntegrityError, type MemoryPointer, type MemoryRollbackEvent, type MemoryScanOptions, type MemoryScanResult, type MemoryScanVerdict, type MemorySnapshot, type ModifiedEntry, type Network, type OrgSubscription, PointerStore, type Provider, type PubkeyValue, type RedactResult, type RollbackMemoryOptions, type SecretKind, type SecretMatch, SignatureVerificationError, type Subscription, type SyncMemoryOptions, type SyncMemoryResult, type TelemetryConfig, type TierInfo, type ToolCallFull, type ToolCallInput, type
|
|
1897
|
+
export { type ActionType, type AgentAuth, type AgentLookupOptions, type AgentMemoryEntry, type AgentPolicy, type AnomalySeverity, type AnomalyType, Atbash, AtbashAPIError, type AtbashLogger, type AtbashOptions, type AtbashUserConfig, BOOT_SYNC_HINT, type ChainConfig, type ChainOpts, type ClassifierToolContext, type ClassifierToolEvent, type ClassifyMemoryReadOptions, type ClassifyMemoryWriteOptions, type ClientSource, type CommitMemoryOptions, DEFAULT_BLOCKCHAIN_RID, DEFAULT_CHROMIA_NODE_URLS, DEFAULT_ENDPOINT, DEFAULT_MEMORY_PATH_PATTERNS, DEFAULT_MEMORY_READ_TOOL_NAMES, DEFAULT_MEMORY_WRITE_TOOL_NAMES, type Decision, type DecisionVerdict, EciesDomain, type EncryptedMemory, type Envelope, type FromConfigOptions, type GuardLogger, type GuardMemoryDecision, type GuardMemoryWriteInput, type GuardMemoryWriteResult, type HeldAction, type HeldActionReview, type HookDecision, HttpClient, HttpTransportError, type JudgeEndpointConfig, type JudgeOptions, type JudgeResult, type JudgmentState, type JudgmentStatus, KEY_FILENAMES, type KeyPair, type LogToolCallOptions, type LogToolCallResult, type MemoryAnomaly, type MemoryDiffResult, type MemoryEntry, MemoryGuardManager, type MemoryGuardManagerOptions, MemoryIntegrityError, type MemoryPointer, type MemoryRollbackEvent, type MemoryScanOptions, type MemoryScanResult, type MemoryScanVerdict, type MemorySnapshot, type ModifiedEntry, type Network, type OperationEvent, type OrgSubscription, PRIVATE_CHAIN, PUBLIC_CHAIN, PointerStore, type Provider, type PubkeyValue, type RedactResult, type RollbackMemoryOptions, type SecretKind, type SecretMatch, SignatureVerificationError, type Subscription, type SyncMemoryOptions, type SyncMemoryResult, type TelemetryConfig, type TierInfo, type ToolCallFull, type ToolCallInput, type ToolCallRecord, type ValidatedEndpoint, type Verdict, type WrappedLogger, bootSyncFailureLine, buildAllowedJudgeHosts, canonicalAllow, chainForNetwork, chooseKeyPath, claimHashHex, classifyMemoryRead, classifyMemoryWrite, columnAad, commitMemoryVersion, configureEvents, containsEvasionCharacters, containsSecret, createFileLogger, createMemoryGuardManager, createMemorySnapshot, decryptForOrg, decryptMemoryContent, defaultPluginLogPath, defaultPointerPath, deriveMemoryKey, derivePublicKey, diffMemorySnapshots, encryptForOrg, encryptMemoryContent, encryptedLength, eventsAreOn, flushTelemetry, generateKeypair, getActiveAgentMemory, getActiveMemoryId, getConfigDir, getConfigPath, getMemoryById, getMemoryHistory, getRecentAgentMemory, getRollbackHistory, guardMemoryWrite, isEnvelope, isValidPrivateKey, keyFingerprintOf, keyPathCandidates, loadAgent, loadAgentFromFile, loadUserConfig, normalizeActionForHash, normalizeActionType, normalizeForMatching, normalizeStatus, normalizeVerdict, packEnvelope, parseEnvelope, pubkeyToHex, recordCall, recordDuration, recordEvent, redactJsonStrings, redactSecrets, resolve, resolveKeyPath, rollbackMemory, saveUserConfig, scanMemory, scanMemoryBatch, setupTelemetry, shutdownTelemetry, signEncryptedToolCall, signJudgeAction, signLogToolCall, syncLocalMemory, validateJudgeEndpoint, verifyJudgeResponseSignature, verifySignature };
|