@zanii/blackbox 0.23.1 → 0.23.3

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.
@@ -48,6 +48,11 @@ export const FAULTS = {
48
48
  en: "The ledger rejected an anchor",
49
49
  ar: "رفض دفتر الأستاذ تثبيتًا",
50
50
  },
51
+ GATEWAY_BYPASS: {
52
+ fault: "BBX-7501",
53
+ en: "Network call around the gateway, dropped",
54
+ ar: "اتصال شبكي خارج البوابة، تم حظره",
55
+ },
51
56
  SDK_SILENT: {
52
57
  fault: "BBX-1201",
53
58
  en: "The SDK stopped reporting",
@@ -43,6 +43,7 @@ export const TAXONOMY = {
43
43
  EXTRA_LOCAL: { owasp: ["ASI10"] },
44
44
  FALSE_CLAIM: { owasp: ["ASI09", "ASI10"] },
45
45
  FALSE_SUCCESS: { owasp: ["ASI09", "ASI10"] },
46
+ GATEWAY_BYPASS: { owasp: ["ASI10"] },
46
47
  MISSING_LOCAL: { owasp: ["ASI10"] },
47
48
  PLAN_DEVIATION: { owasp: ["ASI01", "ASI10"] },
48
49
  PLAN_VIOLATION: { owasp: ["ASI02", "ASI10"], atlas: ["AML.T0053"] },
@@ -7,3 +7,46 @@ export interface SealedSegment {
7
7
  }
8
8
  /** The body, when every segment opens, hashes to its own hash, and they join to `bodyHash`; else null. */
9
9
  export declare function openSegments(segments: readonly SealedSegment[], bodyHash: string): Uint8Array | null;
10
+ export type ReleaseRole = "company" | "custodian";
11
+ export interface ReleasedSegment {
12
+ hash: string;
13
+ /** base64: nonce ‖ AES-256-GCM(segment key, plaintext, aad = hash) ‖ tag. */
14
+ sealed: string;
15
+ /** base64 sealed shares (92 bytes each): company XOR custodian = the segment key. */
16
+ shares: {
17
+ company: string;
18
+ custodian: string;
19
+ };
20
+ }
21
+ export type RandomBytes = (n: number) => Uint8Array;
22
+ /** The raw 32-byte X25519 public key of a raw 32-byte private key. */
23
+ export declare function releasePublicKey(privateKey: Uint8Array): Uint8Array;
24
+ /** Seal one 32-byte share to a recipient's X25519 public key: E ‖ nonce ‖ ciphertext ‖ tag, base64. */
25
+ export declare function sealReleaseShare(share: Uint8Array, recipientPublicKey: Uint8Array, hash: string, role: ReleaseRole, random?: RandomBytes): string;
26
+ /** One holder opens their own share with their private key: the 32-byte share, or null. */
27
+ export declare function openReleaseShare(sealed: string, privateKey: Uint8Array, hash: string, role: ReleaseRole): Uint8Array | null;
28
+ /**
29
+ * The gateway's side: one stored segment for a two-key release. A segment stored in the clear
30
+ * (`key` null) is sealed with a fresh key first, so every released segment is sealed.
31
+ */
32
+ export declare function releaseSegment(segment: {
33
+ hash: string;
34
+ bytes: Uint8Array;
35
+ key: Uint8Array | null;
36
+ }, recipients: {
37
+ company: Uint8Array;
38
+ custodian: Uint8Array;
39
+ }, random?: RandomBytes): ReleasedSegment;
40
+ /** Both holders' material: their private keys, or the shares they opened (one per segment, in order). */
41
+ export type ReleaseHolders = {
42
+ company: Uint8Array;
43
+ custodian: Uint8Array;
44
+ } | {
45
+ companyShares: readonly Uint8Array[];
46
+ custodianShares: readonly Uint8Array[];
47
+ };
48
+ /**
49
+ * The body of a two-key release, when both shares of every segment join into a key that opens it,
50
+ * each segment hashes to its own hash, and they join to `bodyHash`; else null.
51
+ */
52
+ export declare function openRelease(segments: readonly ReleasedSegment[], bodyHash: string, holders: ReleaseHolders): Uint8Array | null;
@@ -1,7 +1,7 @@
1
1
  // The auditor's side of spec/encryption.md §4: open one body from its sealed segments and their
2
2
  // keys, and check it against the hash in the chain. Pure; mirrors
3
3
  // sdks/python/src/zanii_blackbox/encryption.py; pinned by spec/vectors/body-encryption.json.
4
- import { createDecipheriv, createHash } from "node:crypto";
4
+ import { createCipheriv, createDecipheriv, createHash, createPrivateKey, createPublicKey, diffieHellman, hkdfSync, randomBytes, } from "node:crypto";
5
5
  const sha256 = (b) => `sha256:${createHash("sha256").update(b).digest("hex")}`;
6
6
  function open(key, hash, sealed) {
7
7
  if (key.length !== 32 || sealed.length < 28)
@@ -29,3 +29,112 @@ export function openSegments(segments, bodyHash) {
29
29
  const body = Buffer.concat(parts);
30
30
  return sha256(body) === bodyHash ? new Uint8Array(body) : null;
31
31
  }
32
+ const PKCS8_X25519 = Buffer.from("302e020100300506032b656e04220420", "hex");
33
+ const SPKI_X25519 = Buffer.from("302a300506032b656e032100", "hex");
34
+ const x25519Private = (raw) => createPrivateKey({ key: Buffer.concat([PKCS8_X25519, raw]), format: "der", type: "pkcs8" });
35
+ const x25519Public = (raw) => createPublicKey({ key: Buffer.concat([SPKI_X25519, raw]), format: "der", type: "spki" });
36
+ /** The raw 32-byte X25519 public key of a raw 32-byte private key. */
37
+ export function releasePublicKey(privateKey) {
38
+ const der = createPublicKey(x25519Private(privateKey)).export({ format: "der", type: "spki" });
39
+ return new Uint8Array(der.subarray(der.length - 32));
40
+ }
41
+ const xor = (a, b) => new Uint8Array(32).map((_, i) => (a[i] ?? 0) ^ (b[i] ?? 0));
42
+ const shareInfo = (role, hash) => Buffer.from(`blackbox-release-share:${role}:${hash}`, "utf8");
43
+ function wrappingKey(z, ephemeral, recipient, info) {
44
+ return Buffer.from(hkdfSync("sha256", z, Buffer.concat([ephemeral, recipient]), info, 32));
45
+ }
46
+ function gcmSeal(key, nonce, plain, aad) {
47
+ const c = createCipheriv("aes-256-gcm", key, nonce);
48
+ c.setAAD(aad);
49
+ return Buffer.concat([nonce, c.update(plain), c.final(), c.getAuthTag()]);
50
+ }
51
+ function gcmOpen(key, sealed, aad) {
52
+ if (sealed.length < 28)
53
+ return null;
54
+ try {
55
+ const d = createDecipheriv("aes-256-gcm", key, sealed.subarray(0, 12));
56
+ d.setAAD(aad);
57
+ d.setAuthTag(sealed.subarray(sealed.length - 16));
58
+ return Buffer.concat([d.update(sealed.subarray(12, sealed.length - 16)), d.final()]);
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ /** Seal one 32-byte share to a recipient's X25519 public key: E ‖ nonce ‖ ciphertext ‖ tag, base64. */
65
+ export function sealReleaseShare(share, recipientPublicKey, hash, role, random = randomBytes) {
66
+ if (share.length !== 32 || recipientPublicKey.length !== 32)
67
+ throw new Error("a share and a recipient key are 32 bytes each");
68
+ const e = random(32);
69
+ const ephemeral = releasePublicKey(e);
70
+ const z = diffieHellman({
71
+ privateKey: x25519Private(e),
72
+ publicKey: x25519Public(recipientPublicKey),
73
+ });
74
+ const info = shareInfo(role, hash);
75
+ const w = wrappingKey(z, ephemeral, recipientPublicKey, info);
76
+ return Buffer.concat([ephemeral, gcmSeal(w, random(12), share, info)]).toString("base64");
77
+ }
78
+ /** One holder opens their own share with their private key: the 32-byte share, or null. */
79
+ export function openReleaseShare(sealed, privateKey, hash, role) {
80
+ try {
81
+ const bytes = Buffer.from(sealed, "base64");
82
+ if (bytes.length !== 92 || privateKey.length !== 32)
83
+ return null;
84
+ const ephemeral = bytes.subarray(0, 32);
85
+ const z = diffieHellman({
86
+ privateKey: x25519Private(privateKey),
87
+ publicKey: x25519Public(ephemeral),
88
+ });
89
+ const info = shareInfo(role, hash);
90
+ const share = gcmOpen(wrappingKey(z, ephemeral, releasePublicKey(privateKey), info), bytes.subarray(32), info);
91
+ return share && share.length === 32 ? new Uint8Array(share) : null;
92
+ }
93
+ catch {
94
+ return null;
95
+ }
96
+ }
97
+ /**
98
+ * The gateway's side: one stored segment for a two-key release. A segment stored in the clear
99
+ * (`key` null) is sealed with a fresh key first, so every released segment is sealed.
100
+ */
101
+ export function releaseSegment(segment, recipients, random = randomBytes) {
102
+ let key = segment.key;
103
+ let sealed = Buffer.from(segment.bytes);
104
+ if (key === null) {
105
+ key = random(32);
106
+ sealed = gcmSeal(key, random(12), segment.bytes, Buffer.from(segment.hash, "utf8"));
107
+ }
108
+ if (key.length !== 32)
109
+ throw new Error("a segment key is 32 bytes");
110
+ const a = random(32);
111
+ const b = xor(key, a);
112
+ return {
113
+ hash: segment.hash,
114
+ sealed: sealed.toString("base64"),
115
+ shares: {
116
+ company: sealReleaseShare(a, recipients.company, segment.hash, "company", random),
117
+ custodian: sealReleaseShare(b, recipients.custodian, segment.hash, "custodian", random),
118
+ },
119
+ };
120
+ }
121
+ /**
122
+ * The body of a two-key release, when both shares of every segment join into a key that opens it,
123
+ * each segment hashes to its own hash, and they join to `bodyHash`; else null.
124
+ */
125
+ export function openRelease(segments, bodyHash, holders) {
126
+ const keys = [];
127
+ for (const [i, s] of segments.entries()) {
128
+ const a = "company" in holders
129
+ ? openReleaseShare(s.shares.company, holders.company, s.hash, "company")
130
+ : (holders.companyShares[i] ?? null);
131
+ const b = "custodian" in holders
132
+ ? openReleaseShare(s.shares.custodian, holders.custodian, s.hash, "custodian")
133
+ : (holders.custodianShares[i] ?? null);
134
+ if (!a || !b || a.length !== 32 || b.length !== 32)
135
+ return null;
136
+ const key = Buffer.from(xor(a, b));
137
+ keys.push({ hash: s.hash, sealed: s.sealed, key: key.toString("base64") });
138
+ }
139
+ return openSegments(keys, bodyHash);
140
+ }
package/dist/index.d.ts CHANGED
@@ -35,7 +35,7 @@ export { type Directive, type DirectiveItem, type DirectiveOptions, directives,
35
35
  export { argumentLeaves, type Disclosure, type DisclosureEnvelope, discloseFields, disclosureEnvelope, verifyDisclosure, verifyEventDisclosure, } from "./disclosure/index.ts";
36
36
  export { checkDrill, drillReport, type Fault as DrillFault } from "./drills/index.ts";
37
37
  export { checkDuty, type DutyFinding, type DutyLimits, dutyFindings } from "./duty/index.ts";
38
- export { openSegments, type SealedSegment } from "./encryption/index.ts";
38
+ export { openRelease, openReleaseShare, openSegments, type ReleasedSegment, type ReleaseHolders, type ReleaseRole, releasePublicKey, releaseSegment, type SealedSegment, sealReleaseShare, } from "./encryption/index.ts";
39
39
  export { type Arm, armStats, assignArm, type CaseScore, casesFromScenarios, casesFromSessions, checkDataset, checkExperiment, checkScorer, compareRuns, datasetHash, type EvalCase, experimentResults, type Observation, parseCasesJsonl, promptResults, promptSha256, type RunSummary, runSummary, SCORERS, type Scorer, scoreCase, toMilli, } from "./evals/index.ts";
40
40
  export { type EvidencePack, type EvidencePackOptions, evidencePack, renderEvidencePack, } from "./evidence/index.ts";
41
41
  export { buildEquivocationProof, compareViews, cosignLedgerSth, cosignLedgerSthWith, verifyEquivocationProof, verifyLedgerCosignature, } from "./federation/index.ts";
@@ -49,6 +49,7 @@ export { type AgentSink, BlackboxStoppedError, blackboxAgentMiddleware, blackbox
49
49
  export { blackboxCheckpointer } from "./hooks/langgraph-checkpoint.ts";
50
50
  export { MANAGED_END, MANAGED_START, managedBlock, protectedEdit } from "./hooks/protect.ts";
51
51
  export { type Investigation, investigation, renderInvestigation, } from "./investigate/index.ts";
52
+ export { type InvestigationVerdict, verifyInvestigationExport, } from "./investigation/index.ts";
52
53
  export { isRpcResponse, rpcIdOf, SERVER_REQUESTS, type ServerRequestKind, secretFields, serverReplyMeta, serverRequestMeta, } from "./mcp-requests/index.ts";
53
54
  export { type McpWrapOptions, mcpWrap } from "./mcp-wrap/index.ts";
54
55
  export { commitContent, memoryChain, memoryEntry, verifyMemoryContent, verifyMemoryEntry, } from "./memory/index.ts";
package/dist/index.js CHANGED
@@ -38,7 +38,7 @@ export { directives, fleetDirectives, } from "./directives/index.js";
38
38
  export { argumentLeaves, discloseFields, disclosureEnvelope, verifyDisclosure, verifyEventDisclosure, } from "./disclosure/index.js";
39
39
  export { checkDrill, drillReport } from "./drills/index.js";
40
40
  export { checkDuty, dutyFindings } from "./duty/index.js";
41
- export { openSegments } from "./encryption/index.js";
41
+ export { openRelease, openReleaseShare, openSegments, releasePublicKey, releaseSegment, sealReleaseShare, } from "./encryption/index.js";
42
42
  export { armStats, assignArm, casesFromScenarios, casesFromSessions, checkDataset, checkExperiment, checkScorer, compareRuns, datasetHash, experimentResults, parseCasesJsonl, promptResults, promptSha256, runSummary, SCORERS, scoreCase, toMilli, } from "./evals/index.js";
43
43
  export { evidencePack, renderEvidencePack, } from "./evidence/index.js";
44
44
  export { buildEquivocationProof, compareViews, cosignLedgerSth, cosignLedgerSthWith, verifyEquivocationProof, verifyLedgerCosignature, } from "./federation/index.js";
@@ -52,6 +52,7 @@ export { BlackboxStoppedError, blackboxAgentMiddleware, blackboxHitl, } from "./
52
52
  export { blackboxCheckpointer } from "./hooks/langgraph-checkpoint.js";
53
53
  export { MANAGED_END, MANAGED_START, managedBlock, protectedEdit } from "./hooks/protect.js";
54
54
  export { investigation, renderInvestigation, } from "./investigate/index.js";
55
+ export { verifyInvestigationExport, } from "./investigation/index.js";
55
56
  export { isRpcResponse, rpcIdOf, SERVER_REQUESTS, secretFields, serverReplyMeta, serverRequestMeta, } from "./mcp-requests/index.js";
56
57
  export { mcpWrap } from "./mcp-wrap/index.js";
57
58
  export { commitContent, memoryChain, memoryEntry, verifyMemoryContent, verifyMemoryEntry, } from "./memory/index.js";
@@ -0,0 +1,13 @@
1
+ export interface InvestigationVerdict {
2
+ ok: boolean;
3
+ /** The signer's DID (the gateway identity), or null when unsigned. */
4
+ signer: string | null;
5
+ sessions: number;
6
+ /** Every problem found, empty when ok. */
7
+ reasons: string[];
8
+ }
9
+ /**
10
+ * spec/data.md §7b: verifies an investigation export. `expectedGateway` pins the signer's DID;
11
+ * without it, check `signer` against the gateway DID you trust.
12
+ */
13
+ export declare function verifyInvestigationExport(doc: unknown, expectedGateway?: string): InvestigationVerdict;
@@ -0,0 +1,73 @@
1
+ // Investigation export verification (spec/data.md §7b): checks a Blackbox investigation export
2
+ // offline: the manifest's gateway signature, and every session's hash chain, bodies and anchors
3
+ // against the head and count the manifest signed. Pure; needs nothing but the export.
4
+ import { verifyCertificate } from "../fleet/index.js";
5
+ import { verifyChain } from "../verify/chain.js";
6
+ const isObj = (x) => x !== null && typeof x === "object" && !Array.isArray(x);
7
+ /**
8
+ * spec/data.md §7b: verifies an investigation export. `expectedGateway` pins the signer's DID;
9
+ * without it, check `signer` against the gateway DID you trust.
10
+ */
11
+ export function verifyInvestigationExport(doc, expectedGateway) {
12
+ const reasons = [];
13
+ const out = (signer, sessions) => ({
14
+ ok: reasons.length === 0,
15
+ signer,
16
+ sessions,
17
+ reasons,
18
+ });
19
+ if (!isObj(doc) || doc.kind !== "blackbox.investigation" || !Array.isArray(doc.sessions)) {
20
+ reasons.push("not an investigation export");
21
+ return out(null, 0);
22
+ }
23
+ const manifest = doc.manifest;
24
+ if (!isObj(manifest) || manifest.kind !== "blackbox.investigation-manifest") {
25
+ reasons.push("no manifest");
26
+ return out(null, 0);
27
+ }
28
+ const sig = verifyCertificate(manifest, expectedGateway);
29
+ if (!sig.ok)
30
+ reasons.push(`manifest signature: ${sig.reason}`);
31
+ else if (manifest.gateway !== sig.by)
32
+ reasons.push("manifest gateway is not its signer");
33
+ const heads = Array.isArray(manifest.sessions) ? manifest.sessions : [];
34
+ if (manifest.session_count !== doc.sessions.length || heads.length !== doc.sessions.length)
35
+ reasons.push("session count does not match the manifest");
36
+ doc.sessions.forEach((s, i) => {
37
+ const want = heads[i];
38
+ if (!isObj(s) || !Array.isArray(s.records) || !isObj(s.bodies)) {
39
+ reasons.push(`session ${i}: malformed`);
40
+ return;
41
+ }
42
+ const id = String(s.session_id);
43
+ if (!want || want.session_id !== id) {
44
+ reasons.push(`session ${id}: not in the manifest at ${i}`);
45
+ return;
46
+ }
47
+ const bodies = s.bodies;
48
+ const v = verifyChain(s.records, {
49
+ sessionId: id,
50
+ count: want.count,
51
+ ...(typeof want.head === "string" ? { head: want.head } : {}),
52
+ anchors: Array.isArray(s.anchors)
53
+ ? s.anchors
54
+ : [],
55
+ body: (h) => {
56
+ const b = bodies[h]?.base64;
57
+ return typeof b === "string" ? new Uint8Array(Buffer.from(b, "base64")) : undefined;
58
+ },
59
+ });
60
+ // the agent each session matched, signed in the manifest (absent in older exports)
61
+ if (want.agent !== undefined) {
62
+ if (s.agent !== want.agent)
63
+ reasons.push(`session ${id}: agent differs from the manifest`);
64
+ if (!Array.isArray(manifest.agents) || !manifest.agents.includes(want.agent))
65
+ reasons.push(`session ${id}: agent is not one the manifest names`);
66
+ }
67
+ if (!v.ok)
68
+ reasons.push(`session ${id}: ${v.code} ${v.message}`);
69
+ else if (want.head === null)
70
+ reasons.push(`session ${id}: the manifest signed no head`);
71
+ });
72
+ return out(sig.by, doc.sessions.length);
73
+ }
@@ -1,3 +1,4 @@
1
1
  export { ANCHOR_TARGET, anchorPayload, anchorPayloadBytes, type Bundle, type BundleAnchor, type BundleReport, verifyBundle, } from "./anchor.ts";
2
2
  export { type FailureCode, type VerifyOptions, type VerifyResult, verifyChain } from "./chain.ts";
3
3
  export { assertSafeIntegers, EncodingError, type Envelope, EVENT_KINDS, type EventKind, encodeEnvelope, GENESIS, hashBody, hashLine, type Json, } from "./envelope.ts";
4
+ export { SEEN_BY_KIND, type Seen, seenOf } from "./seen.ts";
@@ -1,3 +1,4 @@
1
1
  export { ANCHOR_TARGET, anchorPayload, anchorPayloadBytes, verifyBundle, } from "./anchor.js";
2
2
  export { verifyChain } from "./chain.js";
3
3
  export { assertSafeIntegers, EncodingError, EVENT_KINDS, encodeEnvelope, GENESIS, hashBody, hashLine, } from "./envelope.js";
4
+ export { SEEN_BY_KIND, seenOf } from "./seen.js";
@@ -0,0 +1,5 @@
1
+ import type { EventKind } from "./envelope.ts";
2
+ export type Seen = "observed" | "reported" | "inferred";
3
+ export declare const SEEN_BY_KIND: Readonly<Record<EventKind, Seen>>;
4
+ /** How an event was seen, from its kind (and an outcome's `by`); null for a kind this doesn't know. */
5
+ export declare function seenOf(kind: string, meta?: Record<string, unknown> | null): Seen | null;
@@ -0,0 +1,31 @@
1
+ // spec/api.md §4a: how each event was seen. Pure; mirrors sdks/python/src/zanii_blackbox/verify/seen.py;
2
+ // pinned by spec/vectors/seen.json.
3
+ export const SEEN_BY_KIND = {
4
+ "session.open": "observed",
5
+ "session.close": "observed",
6
+ "session.lost_contact": "observed",
7
+ "llm.request": "observed",
8
+ "llm.chunk": "observed",
9
+ "llm.response": "observed",
10
+ "llm.incomplete": "observed",
11
+ "llm.realtime.client": "observed",
12
+ "llm.realtime.server": "observed",
13
+ "tool.call": "observed",
14
+ "tool.result": "observed",
15
+ "tool.notification": "observed",
16
+ "tool.sampling": "observed",
17
+ "tool.elicitation": "observed",
18
+ "tool.roots": "observed",
19
+ "workspace.receipt": "reported",
20
+ "sdk.event": "reported",
21
+ control: "observed",
22
+ outcome: "reported",
23
+ finding: "inferred",
24
+ "capture.gap": "observed",
25
+ };
26
+ /** How an event was seen, from its kind (and an outcome's `by`); null for a kind this doesn't know. */
27
+ export function seenOf(kind, meta) {
28
+ if (kind === "outcome")
29
+ return meta?.by === "server" ? "observed" : "reported";
30
+ return Object.hasOwn(SEEN_BY_KIND, kind) ? SEEN_BY_KIND[kind] : null;
31
+ }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.23.1";
1
+ export declare const VERSION = "0.23.3";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // The SDK version, on its own so the CLI can print it without loading the whole SDK.
2
- export const VERSION = "0.23.1";
2
+ export const VERSION = "0.23.3";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zanii/blackbox",
3
3
  "license": "Apache-2.0",
4
- "version": "0.23.1",
4
+ "version": "0.23.3",
5
5
  "description": "The flight recorder for AI agents: sessions, a zero-loss spool, framework hooks, approvals, and offline verification of the gateway's hash-chained record.",
6
6
  "keywords": [
7
7
  "ai-agents",
@@ -40,19 +40,19 @@
40
40
  "LICENSE"
41
41
  ],
42
42
  "dependencies": {
43
- "@zanii/core": "^0.5.6",
44
- "@zanii/sdk": "^0.5.4"
43
+ "@zanii/core": "^0.5.7",
44
+ "@zanii/sdk": "^0.6.0"
45
45
  },
46
46
  "devDependencies": {
47
- "@anthropic-ai/claude-agent-sdk": "0.3.283",
48
- "@langchain/core": "1.2.13",
49
- "@langchain/langgraph": "1.4.18",
50
- "@mastra/core": "1.78.0",
51
- "@mastra/observability": "1.20.1",
52
- "@openai/agents": "0.18.0",
47
+ "@anthropic-ai/claude-agent-sdk": "0.3.319",
48
+ "@langchain/core": "1.3.5",
49
+ "@langchain/langgraph": "1.7.1",
50
+ "@mastra/core": "1.82.0",
51
+ "@mastra/observability": "1.23.1",
52
+ "@openai/agents": "0.21.0",
53
53
  "ai": "6.0.283",
54
- "langchain": "1.5.14",
55
- "zod": "^4.6.5"
54
+ "langchain": "1.6.2",
55
+ "zod": "^4.6.7"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsc -p tsconfig.build.json",