@openbkn/bkn-sdk 0.1.1 → 0.1.3-rc.1

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.
@@ -20,44 +20,53 @@ var HttpError = class extends Error {
20
20
  this.hint = hint;
21
21
  }
22
22
  };
23
+ var ToolError = class extends Error {
24
+ code;
25
+ constructor(message, code) {
26
+ super(message);
27
+ this.name = "ToolError";
28
+ if (code) this.code = code;
29
+ }
30
+ };
23
31
  var InputError = class extends Error {
24
32
  constructor(message) {
25
33
  super(message);
26
34
  this.name = "InputError";
27
35
  }
28
36
  };
29
- function toExitCode(err) {
30
- if (err instanceof InputError) return 2;
31
- if (err instanceof HttpError) {
32
- if (err.status === 401 || err.status === 403) return 3;
37
+ function toExitCode(err2) {
38
+ if (err2 instanceof InputError) return 2;
39
+ if (err2 instanceof HttpError) {
40
+ if (err2.status === 401 || err2.status === 403) return 3;
33
41
  return 1;
34
42
  }
35
43
  return 1;
36
44
  }
37
- function formatError(err) {
38
- if (err instanceof HttpError) {
39
- const serverMsg = serverError(err.body);
40
- if (err.status === 401) {
41
- const next = err.hint ?? "Run `openbkn auth login` and retry.";
42
- return `Not authorized (HTTP 401)${serverMsg ? `: ${serverMsg}` : ""}. ${next}`;
45
+ function formatError(err2) {
46
+ if (err2 instanceof HttpError) {
47
+ const serverMsg = serverError(err2.body);
48
+ if (err2.status === 401) {
49
+ const next2 = err2.hint ?? "Run `openbkn auth login` and retry.";
50
+ return `Not authorized (HTTP 401)${serverMsg ? `: ${serverMsg}` : ""}. ${next2}`;
43
51
  }
44
- if (err.status === 403) {
52
+ if (err2.status === 403) {
45
53
  return `Forbidden (HTTP 403)${serverMsg ? `: ${serverMsg}` : " \u2014 admin privileges required"}.`;
46
54
  }
47
- const detail = err.body ? `: ${truncate(err.body, 500)}` : "";
48
- return `Request failed (HTTP ${err.status} ${err.statusText})${detail}`;
55
+ const detail = err2.body ? `: ${truncate(err2.body, 500)}` : "";
56
+ const next = err2.hint ? ` ${err2.hint}` : "";
57
+ return `Request failed (HTTP ${err2.status} ${err2.statusText})${detail}${next}`;
49
58
  }
50
- if (err instanceof Error) {
51
- const cause = err.cause;
59
+ if (err2 instanceof Error) {
60
+ const cause = err2.cause;
52
61
  if (cause?.code && isTlsCertError(cause.code)) {
53
62
  return `TLS certificate rejected (${cause.code}). The platform is likely self-signed \u2014 retry with \`-k\`/\`--insecure\`.`;
54
63
  }
55
- if (err.message === "fetch failed" && cause?.message) {
64
+ if (err2.message === "fetch failed" && cause?.message) {
56
65
  return `Request failed: ${cause.message}${cause.code ? ` (${cause.code})` : ""}`;
57
66
  }
58
- return err.message;
67
+ return err2.message;
59
68
  }
60
- return String(err);
69
+ return String(err2);
61
70
  }
62
71
  function serverError(body) {
63
72
  if (!body) return "";
@@ -80,17 +89,44 @@ function truncate(s, n) {
80
89
  import { spawn } from "child_process";
81
90
 
82
91
  // src/api/tls.ts
83
- import { Agent, fetch as undiciFetch } from "undici";
84
- var insecureAgent;
85
- function insecureDispatcher() {
86
- insecureAgent ??= new Agent({ connect: { rejectUnauthorized: false } });
87
- return insecureAgent;
88
- }
89
- function tlsFetch(insecure, url, init) {
90
- if (!insecure) return fetch(url, init);
92
+ import { Agent, FormData as UndiciFormData, fetch as undiciFetch } from "undici";
93
+ var UNDICI_HEADERS_TIMEOUT_MS = 3e5;
94
+ var agents = /* @__PURE__ */ new Map();
95
+ function dispatcherFor(insecure, headersTimeoutMs) {
96
+ const key = `${insecure}|${headersTimeoutMs ?? ""}`;
97
+ let agent = agents.get(key);
98
+ if (!agent) {
99
+ agent = new Agent({
100
+ ...insecure ? { connect: { rejectUnauthorized: false } } : {},
101
+ ...headersTimeoutMs === void 0 ? {} : (
102
+ // undici also enforces a body deadline; a request that waits this
103
+ // long for headers is not going to stream its body any faster.
104
+ { headersTimeout: headersTimeoutMs, bodyTimeout: headersTimeoutMs }
105
+ )
106
+ });
107
+ agents.set(key, agent);
108
+ }
109
+ return agent;
110
+ }
111
+ function isFormData(body) {
112
+ return typeof body === "object" && body !== null && body[Symbol.toStringTag] === "FormData";
113
+ }
114
+ function toUndiciBody(body) {
115
+ if (body instanceof UndiciFormData || !isFormData(body)) return body;
116
+ const form2 = new UndiciFormData();
117
+ for (const [name, value] of body.entries()) {
118
+ if (typeof value === "string") form2.append(name, value);
119
+ else form2.append(name, value, value.name);
120
+ }
121
+ return form2;
122
+ }
123
+ function tlsFetch(insecure, url, init, headersTimeoutMs) {
124
+ const needsAgent = headersTimeoutMs !== void 0 && headersTimeoutMs > UNDICI_HEADERS_TIMEOUT_MS;
125
+ if (!insecure && !needsAgent) return fetch(url, init);
91
126
  return undiciFetch(url, {
92
127
  ...init,
93
- dispatcher: insecureDispatcher()
128
+ ...init?.body === void 0 || init?.body === null ? {} : { body: toUndiciBody(init.body) },
129
+ dispatcher: dispatcherFor(insecure === true, needsAgent ? headersTimeoutMs : void 0)
94
130
  });
95
131
  }
96
132
 
@@ -305,11 +341,96 @@ async function credentialDeviceLogin(baseUrl, username, password, opts = {}) {
305
341
  );
306
342
  }
307
343
 
344
+ // src/trace-context.ts
345
+ import { randomBytes, randomUUID } from "crypto";
346
+ var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/;
347
+ var REQUEST_ID_RE = /^req_[0-9A-Za-z_.-]+$/;
348
+ var CORRELATION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
349
+ var RFC3339_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
350
+ var ALLOWED_BAGGAGE = /* @__PURE__ */ new Set(["bkn.account.type", "bkn.runtime.env"]);
351
+ function isValidRequestId(value) {
352
+ return typeof value === "string" && REQUEST_ID_RE.test(value);
353
+ }
354
+ function isValidCorrelationId(value) {
355
+ return typeof value === "string" && CORRELATION_ID_RE.test(value.trim());
356
+ }
357
+ function isValidTraceparent(value) {
358
+ if (typeof value !== "string") return false;
359
+ const match = TRACEPARENT_RE.exec(value);
360
+ if (!match) return false;
361
+ const [, traceId, spanId] = match;
362
+ return traceId !== "0".repeat(32) && spanId !== "0".repeat(16);
363
+ }
364
+ function createTraceContext(opts = {}) {
365
+ const requestId = isValidRequestId(opts.requestId) ? opts.requestId : `req_${randomUUID()}`;
366
+ const traceparent = isValidTraceparent(opts.traceparent) ? opts.traceparent : newTraceparent();
367
+ const baggage = filterBaggage(opts.baggage);
368
+ const conversationId = isValidCorrelationId(opts.conversationId) ? opts.conversationId.trim() : void 0;
369
+ const interactionId = isValidCorrelationId(opts.interactionId) ? opts.interactionId.trim() : void 0;
370
+ const operationId = isValidCorrelationId(opts.operationId) ? opts.operationId.trim() : void 0;
371
+ const attempt = Number.isInteger(opts.attempt) && (opts.attempt ?? 0) >= 1 && (opts.attempt ?? 0) <= 1e3 ? opts.attempt : void 0;
372
+ const observedAt = isValidObservedAt(opts.observedAt) ? opts.observedAt : void 0;
373
+ return {
374
+ requestId,
375
+ traceparent,
376
+ ...conversationId ? { conversationId } : {},
377
+ ...interactionId ? { interactionId } : {},
378
+ ...operationId ? { operationId } : {},
379
+ ...attempt ? { attempt } : {},
380
+ ...observedAt ? { observedAt } : {},
381
+ ...Object.keys(baggage).length > 0 ? { baggage } : {}
382
+ };
383
+ }
384
+ function createOperationTraceContext(trace2) {
385
+ return {
386
+ ...trace2,
387
+ operationId: trace2.operationId ?? `op_${randomUUID()}`,
388
+ attempt: trace2.attempt ?? 1,
389
+ observedAt: isValidObservedAt(trace2.observedAt) ? trace2.observedAt : (/* @__PURE__ */ new Date()).toISOString()
390
+ };
391
+ }
392
+ function isValidObservedAt(value) {
393
+ return typeof value === "string" && RFC3339_RE.test(value) && !Number.isNaN(Date.parse(value));
394
+ }
395
+ function filterBaggage(baggage) {
396
+ const filtered = {};
397
+ for (const [key, value] of Object.entries(baggage ?? {})) {
398
+ if (ALLOWED_BAGGAGE.has(key) && value !== "") filtered[key] = value;
399
+ }
400
+ return filtered;
401
+ }
402
+ function serializeBaggage(baggage) {
403
+ const filtered = filterBaggage(baggage);
404
+ const entries = Object.entries(filtered);
405
+ if (entries.length === 0) return void 0;
406
+ return entries.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join(",");
407
+ }
408
+ function newTraceparent() {
409
+ return `00-${randomHex(16)}-${randomHex(8)}-01`;
410
+ }
411
+ function randomHex(bytes) {
412
+ let value = randomBytes(bytes).toString("hex");
413
+ while (/^0+$/.test(value)) value = randomBytes(bytes).toString("hex");
414
+ return value;
415
+ }
416
+
308
417
  // src/api/headers.ts
309
418
  function buildHeaders(ctx, extra) {
419
+ const baggage = serializeBaggage(ctx.trace?.baggage);
310
420
  return {
311
421
  authorization: `Bearer ${ctx.token}`,
312
422
  "x-business-domain": ctx.businessDomain,
423
+ ...ctx.trace ? {
424
+ "bkn-request-id": ctx.trace.requestId,
425
+ "x-request-id": ctx.trace.requestId,
426
+ traceparent: ctx.trace.traceparent,
427
+ ...ctx.trace.conversationId ? { "bkn-conversation-id": ctx.trace.conversationId } : {},
428
+ ...ctx.trace.interactionId ? { "bkn-interaction-id": ctx.trace.interactionId } : {},
429
+ ...ctx.trace.operationId ? { "bkn-operation-id": ctx.trace.operationId } : {},
430
+ ...ctx.trace.attempt ? { "bkn-attempt": String(ctx.trace.attempt) } : {},
431
+ ...ctx.trace.observedAt ? { "bkn-event-observed-at": ctx.trace.observedAt } : {}
432
+ } : {},
433
+ ...baggage ? { baggage } : {},
313
434
  ...extra
314
435
  };
315
436
  }
@@ -328,32 +449,59 @@ async function request(ctx, path, init = {}) {
328
449
  const hasBody = init.body !== void 0;
329
450
  const controller = new AbortController();
330
451
  const timer = setTimeout(() => controller.abort(), init.timeoutMs ?? DEFAULT_TIMEOUT_MS);
331
- const send = () => tlsFetch(ctx.insecure, url, {
332
- method: init.method ?? (hasBody ? "POST" : "GET"),
333
- headers: buildHeaders(ctx, {
334
- ...hasBody ? { "content-type": "application/json" } : {},
335
- ...init.headers
336
- }),
337
- body: hasBody ? JSON.stringify(init.body) : void 0,
338
- signal: controller.signal
339
- });
452
+ const send = () => tlsFetch(
453
+ ctx.insecure,
454
+ url,
455
+ {
456
+ method: init.method ?? (hasBody ? "POST" : "GET"),
457
+ headers: buildHeaders(ctx, {
458
+ ...hasBody ? { "content-type": "application/json" } : {},
459
+ ...init.headers
460
+ }),
461
+ body: hasBody ? JSON.stringify(init.body) : void 0,
462
+ redirect: init.redirect,
463
+ signal: controller.signal
464
+ },
465
+ init.headersTimeoutMs
466
+ );
340
467
  try {
341
468
  let res = await send();
342
469
  if (res.status === 401 && ctx.refresh && await tryRefresh(ctx)) {
343
470
  res = await send();
344
471
  }
345
472
  const text = await res.text();
346
- if (!res.ok) throw new HttpError(res.status, res.statusText, text, hintFor(ctx, res.status));
473
+ if (!res.ok) {
474
+ throw new HttpError(res.status, res.statusText, text, hintFor(ctx, res.status, text));
475
+ }
347
476
  return text ? JSON.parse(text) : void 0;
348
477
  } finally {
349
478
  clearTimeout(timer);
350
479
  }
351
480
  }
352
- function hintFor(ctx, status2) {
481
+ function hintFor(ctx, status2, body) {
353
482
  if (status2 === 401 && ctx.token.startsWith("bak_")) {
354
483
  return "AppKey invalid / expired / revoked / owner disabled \u2014 re-issue with `openbkn appkey create` (or `appkey regenerate <id>`). Do not auto-retry.";
355
484
  }
356
- return void 0;
485
+ return lifecycleHint(body);
486
+ }
487
+ var LIFECYCLE_ACTIONS = /* @__PURE__ */ new Set([
488
+ "create_conversation",
489
+ "start_interaction",
490
+ "ensure_operation",
491
+ "bkn_start_interaction"
492
+ ]);
493
+ function lifecycleHint(body) {
494
+ if (!LIFECYCLE_ACTIONS.has(requiredAction(body) ?? "")) return void 0;
495
+ return "This deploy requires a managed lifecycle session: the request needs a `bkn_context` with conversation_id and interaction_id. Easiest fix: use the `openbkn bkn` / `openbkn context` commands, which open and release one for you. To do it by hand, check `openbkn context info` for the deploy's lifecycle tools \u2014 where it lists `bkn_create_conversation`, call that first and pass the conversation_id it returns to `bkn_start_interaction`; where it does not, `bkn_start_interaction` alone returns both ids. Either way: `openbkn context tool-call <kn-id> <tool> --args '{...}'`.";
496
+ }
497
+ function requiredAction(body) {
498
+ try {
499
+ const parsed = JSON.parse(body);
500
+ const action = parsed.error?.required_action;
501
+ return typeof action === "string" ? action : void 0;
502
+ } catch {
503
+ return void 0;
504
+ }
357
505
  }
358
506
  async function tryRefresh(ctx) {
359
507
  if (!ctx.refresh) return false;
@@ -588,6 +736,13 @@ function resolveContext(opts = {}) {
588
736
  token,
589
737
  businessDomain: opts.businessDomain ?? readPlatformConfig(normalized).businessDomain ?? DEFAULT_BUSINESS_DOMAIN,
590
738
  insecure,
739
+ ...opts.evidenceIngestToken ?? process.env.BKN_TRACE_EVIDENCE_INGEST_TOKEN ? {
740
+ evidenceIngestToken: opts.evidenceIngestToken ?? process.env.BKN_TRACE_EVIDENCE_INGEST_TOKEN
741
+ } : {},
742
+ // Correlation ids come from `opts.trace` only. The CLI reads its flags and
743
+ // env vars in `commands/_shared.ts`; a library client must not inherit an
744
+ // ambient interaction id it would then freeze for its whole lifetime.
745
+ trace: createTraceContext(opts.trace),
591
746
  ...refresh ? { refresh } : {}
592
747
  };
593
748
  }
@@ -774,12 +929,12 @@ async function importLicenseSafe(ctx, licenseText, opts = {}) {
774
929
  method: "POST",
775
930
  body: { license: text }
776
931
  });
777
- } catch (err) {
778
- if (err instanceof HttpError) {
779
- const stored = storedImport(err.body);
932
+ } catch (err2) {
933
+ if (err2 instanceof HttpError) {
934
+ const stored = storedImport(err2.body);
780
935
  if (stored) return stored;
781
936
  }
782
- throw err;
937
+ throw err2;
783
938
  }
784
939
  }
785
940
  function storedImport(body) {
@@ -1088,7 +1243,7 @@ function setSkillMembers(agent, members) {
1088
1243
  if (!config.skills || typeof config.skills !== "object") config.skills = {};
1089
1244
  config.skills.skills = members;
1090
1245
  }
1091
- function agents(ctx) {
1246
+ function agents2(ctx) {
1092
1247
  return {
1093
1248
  list: (opts) => listAgents(ctx, opts),
1094
1249
  get: (agentId) => getAgent(ctx, agentId),
@@ -1137,6 +1292,9 @@ function agents(ctx) {
1137
1292
  };
1138
1293
  }
1139
1294
 
1295
+ // src/api/lifecycle.ts
1296
+ import { createHash, randomUUID as randomUUID2 } from "crypto";
1297
+
1140
1298
  // src/api/context-loader.ts
1141
1299
  var MCP_PATH = "/api/agent-retrieval/v1/mcp";
1142
1300
  var PROTOCOL = "2024-11-05";
@@ -1150,19 +1308,20 @@ function nextId() {
1150
1308
  function mcpUrl(ctx) {
1151
1309
  return `${ctx.baseUrl}${MCP_PATH}`;
1152
1310
  }
1311
+ function operationContext(ctx) {
1312
+ return ctx.trace ? { ...ctx, trace: createOperationTraceContext(ctx.trace) } : ctx;
1313
+ }
1153
1314
  function mcpInfo(ctx) {
1154
1315
  return request(ctx, `${MCP_PATH}/info`);
1155
1316
  }
1156
1317
  function headers(ctx, knId, sessionId) {
1157
- const h = {
1318
+ return buildHeaders(ctx, {
1158
1319
  "content-type": "application/json",
1159
1320
  accept: "application/json, text/event-stream",
1160
1321
  "x-kn-id": knId,
1161
1322
  "mcp-protocol-version": PROTOCOL,
1162
- authorization: `Bearer ${ctx.token}`
1163
- };
1164
- if (sessionId) h["mcp-session-id"] = sessionId;
1165
- return h;
1323
+ ...sessionId ? { "mcp-session-id": sessionId } : {}
1324
+ });
1166
1325
  }
1167
1326
  function parseBody(text) {
1168
1327
  try {
@@ -1173,18 +1332,25 @@ function parseBody(text) {
1173
1332
  throw new Error(`Context-loader returned invalid JSON: ${text.slice(0, 200)}`);
1174
1333
  }
1175
1334
  }
1176
- async function post(ctx, knId, sessionId, body) {
1177
- const res = await authFetch(
1178
- ctx,
1179
- () => tlsFetch(ctx.insecure, mcpUrl(ctx), {
1180
- method: "POST",
1181
- headers: headers(ctx, knId, sessionId),
1182
- body: JSON.stringify(body)
1183
- })
1184
- );
1185
- const text = await res.text();
1186
- if (!res.ok) throw new HttpError(res.status, res.statusText, text);
1187
- return { res, text };
1335
+ async function post(ctx, knId, sessionId, body, timeoutMs) {
1336
+ const controller = timeoutMs === void 0 ? void 0 : new AbortController();
1337
+ const timer = controller === void 0 ? void 0 : setTimeout(() => controller.abort(), timeoutMs);
1338
+ try {
1339
+ const res = await authFetch(
1340
+ ctx,
1341
+ () => tlsFetch(ctx.insecure, mcpUrl(ctx), {
1342
+ method: "POST",
1343
+ headers: headers(ctx, knId, sessionId),
1344
+ body: JSON.stringify(body),
1345
+ ...controller ? { signal: controller.signal } : {}
1346
+ })
1347
+ );
1348
+ const text = await res.text();
1349
+ if (!res.ok) throw new HttpError(res.status, res.statusText, text);
1350
+ return { res, text };
1351
+ } finally {
1352
+ if (timer) clearTimeout(timer);
1353
+ }
1188
1354
  }
1189
1355
  async function ensureSession(ctx, knId) {
1190
1356
  const key = `${mcpUrl(ctx)}:${knId}`;
@@ -1207,34 +1373,108 @@ async function ensureSession(ctx, knId) {
1207
1373
  sessions.set(key, { id: sessionId, at: Date.now() });
1208
1374
  return sessionId;
1209
1375
  }
1210
- function unwrap(parsed) {
1376
+ function toolErrorCode(structuredContent) {
1377
+ const code = structuredContent?.error?.code;
1378
+ return typeof code === "string" ? code : void 0;
1379
+ }
1380
+ function unwrapToolResult(parsed) {
1211
1381
  const rpc = parsed;
1212
1382
  if (rpc.error) throw new Error(`Context-loader error: ${rpc.error.message}`);
1213
1383
  const result = rpc.result;
1214
- if (result === void 0) return parsed;
1384
+ if (result === void 0) return { value: parsed };
1385
+ const structuredContent = result.structuredContent;
1386
+ const receipt = structuredContent?.bkn_receipt;
1215
1387
  const content = result.content;
1388
+ if (result.isError === true) {
1389
+ const message = Array.isArray(content) && content[0] && typeof content[0].text === "string" ? content[0].text : "tool call failed";
1390
+ throw new ToolError(`Context-loader error: ${message}`, toolErrorCode(structuredContent));
1391
+ }
1216
1392
  if (Array.isArray(content) && content[0] && typeof content[0].text === "string") {
1217
1393
  try {
1218
- return JSON.parse(content[0].text);
1394
+ return { value: JSON.parse(content[0].text), receipt };
1219
1395
  } catch {
1220
- return { raw: content[0].text };
1396
+ if (structuredContent !== void 0) {
1397
+ return { value: structuredContent, receipt };
1398
+ }
1399
+ return { value: { raw: content[0].text }, receipt };
1221
1400
  }
1222
1401
  }
1223
- return result;
1402
+ return { value: result, receipt };
1403
+ }
1404
+ function toolCallParams(name, args, options) {
1405
+ const meta = {
1406
+ ...options?.hostConversationKey ? { "openbkn.ai/host-conversation-key": options.hostConversationKey } : {},
1407
+ ...options?.clientInvocationId ? { "openbkn.ai/client-invocation-id": options.clientInvocationId } : {}
1408
+ };
1409
+ return {
1410
+ name,
1411
+ arguments: args,
1412
+ ...Object.keys(meta).length > 0 ? { _meta: meta } : {}
1413
+ };
1224
1414
  }
1225
- async function callTool(ctx, knId, name, args) {
1226
- const sessionId = await ensureSession(ctx, knId);
1227
- const { text } = await post(ctx, knId, sessionId, {
1415
+ async function callToolRaw(ctx, knId, name, args, options, timeoutMs) {
1416
+ const operationCtx = operationContext(ctx);
1417
+ const sessionId = await ensureSession(operationCtx, knId);
1418
+ const { text } = await post(
1419
+ operationCtx,
1420
+ knId,
1421
+ sessionId,
1422
+ {
1423
+ jsonrpc: "2.0",
1424
+ method: "tools/call",
1425
+ params: toolCallParams(name, args, options),
1426
+ id: nextId()
1427
+ },
1428
+ timeoutMs
1429
+ );
1430
+ return unwrapToolResult(parseBody(text)).value;
1431
+ }
1432
+ var LIFECYCLE_TOOLS = /* @__PURE__ */ new Set([
1433
+ "bkn_create_conversation",
1434
+ "bkn_resume_conversation",
1435
+ "bkn_start_interaction",
1436
+ "bkn_complete_interaction",
1437
+ "bkn_finish_interaction",
1438
+ "bkn_fail_interaction",
1439
+ "bkn_cancel_interaction",
1440
+ "bkn_handoff_interaction",
1441
+ "bkn_close_conversation",
1442
+ "bkn_get_operation",
1443
+ "bkn_retry_operation",
1444
+ "bkn_get_receipt"
1445
+ ]);
1446
+ function callTool(ctx, knId, name, args, options) {
1447
+ if (LIFECYCLE_TOOLS.has(name)) return callToolRaw(ctx, knId, name, args, options);
1448
+ if (args.bkn_context !== void 0) return callToolRaw(ctx, knId, name, args, options);
1449
+ return withManagedLifecycle(
1450
+ ctx,
1451
+ knId,
1452
+ questionFor(name, args),
1453
+ (bknContext) => callToolRaw(ctx, knId, name, bknContext ? { ...args, bkn_context: bknContext } : args, options)
1454
+ );
1455
+ }
1456
+ function questionFor(name, args) {
1457
+ return typeof args.query === "string" && args.query ? args.query : name;
1458
+ }
1459
+ async function callManagedTool(ctx, knId, name, args, options) {
1460
+ const operationCtx = operationContext(ctx);
1461
+ const sessionId = await ensureSession(operationCtx, knId);
1462
+ const { text } = await post(operationCtx, knId, sessionId, {
1228
1463
  jsonrpc: "2.0",
1229
1464
  method: "tools/call",
1230
- params: { name, arguments: args },
1465
+ params: toolCallParams(name, args, options),
1231
1466
  id: nextId()
1232
1467
  });
1233
- return unwrap(parseBody(text));
1468
+ const result = unwrapToolResult(parseBody(text));
1469
+ if (!result.receipt) {
1470
+ throw new Error("Context-loader managed tool response did not include bkn_receipt");
1471
+ }
1472
+ return { value: result.value, receipt: result.receipt };
1234
1473
  }
1235
1474
  async function callMethod(ctx, knId, method, params = {}) {
1236
- const sessionId = await ensureSession(ctx, knId);
1237
- const { text } = await post(ctx, knId, sessionId, {
1475
+ const operationCtx = operationContext(ctx);
1476
+ const sessionId = await ensureSession(operationCtx, knId);
1477
+ const { text } = await post(operationCtx, knId, sessionId, {
1238
1478
  jsonrpc: "2.0",
1239
1479
  method,
1240
1480
  params: Object.keys(params).length > 0 ? params : void 0,
@@ -1297,6 +1537,225 @@ function getPrompt(ctx, knId, name, args = {}) {
1297
1537
  return callMethod(ctx, knId, "prompts/get", { name, arguments: args });
1298
1538
  }
1299
1539
 
1540
+ // src/api/lifecycle.ts
1541
+ var V1_MARKER = "bkn_create_conversation";
1542
+ var V2_MARKER = "bkn_start_interaction";
1543
+ var STALE_SESSION_CODES = /* @__PURE__ */ new Set([
1544
+ "conversation_required",
1545
+ "interaction_required",
1546
+ "interaction_terminal",
1547
+ "interaction_in_progress",
1548
+ "lease_expired",
1549
+ "lease_invalid",
1550
+ "lease_superseded"
1551
+ ]);
1552
+ var PROCESS_ID = randomUUID2();
1553
+ var generation = 0;
1554
+ var AGENT_NAME = "openbkn-sdk";
1555
+ var RELEASE_TIMEOUT_MS = 3e3;
1556
+ var contracts = /* @__PURE__ */ new Map();
1557
+ var PROBE_FAILURE_TTL_MS = 3e4;
1558
+ var probeFailures = /* @__PURE__ */ new Map();
1559
+ var sessions2 = /* @__PURE__ */ new Map();
1560
+ function lifecycleContract(ctx) {
1561
+ const failureKey = `${ctx.baseUrl}\0${identityOf(ctx)}`;
1562
+ const failedAt = probeFailures.get(failureKey);
1563
+ if (failedAt !== void 0) {
1564
+ if (Date.now() - failedAt < PROBE_FAILURE_TTL_MS) return Promise.resolve("none");
1565
+ probeFailures.delete(failureKey);
1566
+ }
1567
+ let pending = contracts.get(ctx.baseUrl);
1568
+ if (!pending) {
1569
+ pending = mcpInfo(ctx).then((info) => {
1570
+ const names = toolNames(info);
1571
+ if (names.includes(V1_MARKER)) return "managed-v1";
1572
+ return names.includes(V2_MARKER) ? "managed-v2" : "none";
1573
+ });
1574
+ contracts.set(ctx.baseUrl, pending);
1575
+ pending.catch((err2) => {
1576
+ contracts.delete(ctx.baseUrl);
1577
+ if (!isAuthFailure(err2)) probeFailures.set(failureKey, Date.now());
1578
+ });
1579
+ }
1580
+ return pending.catch(() => "none");
1581
+ }
1582
+ function isAuthFailure(err2) {
1583
+ return err2 instanceof HttpError && (err2.status === 401 || err2.status === 403);
1584
+ }
1585
+ function toolNames(info) {
1586
+ const tools = info?.tools;
1587
+ if (!Array.isArray(tools)) return [];
1588
+ return tools.flatMap((tool) => typeof tool?.name === "string" ? [tool.name] : []);
1589
+ }
1590
+ function callerOwnedSession(ctx) {
1591
+ const conversationId = ctx.trace?.conversationId;
1592
+ const interactionId = ctx.trace?.interactionId;
1593
+ return conversationId && interactionId ? { conversationId, interactionId } : void 0;
1594
+ }
1595
+ function readId(result, field, tool) {
1596
+ const value = result?.[field];
1597
+ if (typeof value !== "string" || !value) {
1598
+ throw new Error(`Managed lifecycle: ${tool} returned no ${field}.`);
1599
+ }
1600
+ return value;
1601
+ }
1602
+ function callerNamedConversation(ctx) {
1603
+ return ctx.trace?.interactionId ? void 0 : ctx.trace?.conversationId;
1604
+ }
1605
+ async function openSession(ctx, knId, contract, question) {
1606
+ generation += 1;
1607
+ const named = callerNamedConversation(ctx);
1608
+ if (contract === "managed-v2") {
1609
+ const started2 = await callToolRaw(ctx, knId, V2_MARKER, {
1610
+ question,
1611
+ // The name is fixed when the conversation is created, so it belongs only
1612
+ // on the call that creates one. Joining a conversation the caller named
1613
+ // and relabelling it `openbkn-sdk` would rewrite their attribution — the
1614
+ // v1 join below has always omitted it, and this is the same decision.
1615
+ ...named ? { conversation_id: named } : (
1616
+ // Display-only, but the only thing separating a session the SDK
1617
+ // opened from a real agent's in a Trace listing.
1618
+ { agent_name: AGENT_NAME }
1619
+ )
1620
+ });
1621
+ return {
1622
+ contract,
1623
+ ctx,
1624
+ knId,
1625
+ conversationId: readId(started2, "conversation_id", V2_MARKER),
1626
+ interactionId: readId(started2, "interaction_id", V2_MARKER)
1627
+ };
1628
+ }
1629
+ if (named) {
1630
+ const started2 = await callToolRaw(ctx, knId, V2_MARKER, {
1631
+ conversation_id: named,
1632
+ idempotency_key: `start:${PROCESS_ID}:${generation}`,
1633
+ question
1634
+ });
1635
+ return {
1636
+ contract,
1637
+ ctx,
1638
+ knId,
1639
+ conversationId: named,
1640
+ interactionId: readId(started2, "interaction_id", V2_MARKER)
1641
+ };
1642
+ }
1643
+ const conversation = await callToolRaw(ctx, knId, V1_MARKER, {
1644
+ external_conversation_key: `cli:${PROCESS_ID}:${generation}`,
1645
+ // Nothing closes this conversation: a CLI invocation has no answer to close
1646
+ // over, and v1's closure manifest must enumerate every operation it
1647
+ // produced. one_shot hands it to the server's idle sweeper instead.
1648
+ one_shot: true
1649
+ });
1650
+ const conversationId = readId(conversation, "conversation_id", V1_MARKER);
1651
+ const started = await callToolRaw(ctx, knId, V2_MARKER, {
1652
+ conversation_id: conversationId,
1653
+ idempotency_key: `start:${PROCESS_ID}:${generation}`,
1654
+ question
1655
+ });
1656
+ return {
1657
+ contract,
1658
+ ctx,
1659
+ knId,
1660
+ conversationId,
1661
+ interactionId: readId(started, "interaction_id", V2_MARKER)
1662
+ };
1663
+ }
1664
+ function identityOf(ctx) {
1665
+ return createHash("sha256").update(`${ctx.token}\0${ctx.businessDomain}`).digest("hex").slice(0, 16);
1666
+ }
1667
+ function sessionKey(ctx, knId) {
1668
+ return `${ctx.baseUrl}\0${knId}\0${identityOf(ctx)}\0${ctx.trace?.conversationId ?? ""}`;
1669
+ }
1670
+ function ensureSession2(ctx, knId, contract, question) {
1671
+ const key = sessionKey(ctx, knId);
1672
+ const cached = sessions2.get(key);
1673
+ if (cached) return cached;
1674
+ const opening = openSession(ctx, knId, contract, question);
1675
+ sessions2.set(key, opening);
1676
+ opening.catch(() => sessions2.delete(key));
1677
+ return opening;
1678
+ }
1679
+ function newOperationKey() {
1680
+ return `op:${randomUUID2()}`;
1681
+ }
1682
+ function contextFor(session, contract) {
1683
+ return {
1684
+ conversation_id: session.conversationId,
1685
+ interaction_id: session.interactionId,
1686
+ // v2 validates bkn_context strictly and rejects the field.
1687
+ ...contract === "managed-v1" ? { operation_key: newOperationKey() } : {}
1688
+ };
1689
+ }
1690
+ async function bknContextFor(ctx, knId, question) {
1691
+ const contract = await lifecycleContract(ctx);
1692
+ if (contract === "none") return void 0;
1693
+ const owned = callerOwnedSession(ctx);
1694
+ if (owned) return contextFor(owned, contract);
1695
+ try {
1696
+ return contextFor(await ensureSession2(ctx, knId, contract, question), contract);
1697
+ } catch {
1698
+ return void 0;
1699
+ }
1700
+ }
1701
+ function serverErrorCode(err2) {
1702
+ if (err2 instanceof ToolError) return err2.code;
1703
+ if (!(err2 instanceof HttpError)) return void 0;
1704
+ try {
1705
+ const parsed = JSON.parse(err2.body);
1706
+ return typeof parsed.error?.code === "string" ? parsed.error.code : void 0;
1707
+ } catch {
1708
+ return void 0;
1709
+ }
1710
+ }
1711
+ async function withManagedLifecycle(ctx, knId, question, send) {
1712
+ const first = await bknContextFor(ctx, knId, question);
1713
+ try {
1714
+ return await send(first);
1715
+ } catch (err2) {
1716
+ const code = serverErrorCode(err2);
1717
+ if (!first || callerOwnedSession(ctx) || !code || !STALE_SESSION_CODES.has(code)) throw err2;
1718
+ sessions2.delete(sessionKey(ctx, knId));
1719
+ const reopened = await bknContextFor(ctx, knId, question);
1720
+ if (!reopened) throw err2;
1721
+ return await send(reopened);
1722
+ }
1723
+ }
1724
+ async function releaseLifecycleSessions() {
1725
+ const pending = [...sessions2.values()];
1726
+ sessions2.clear();
1727
+ await Promise.all(pending.map((opening) => withDeadline(releaseOne(opening))));
1728
+ }
1729
+ function withDeadline(work) {
1730
+ return new Promise((resolve7) => {
1731
+ const timer = setTimeout(resolve7, RELEASE_TIMEOUT_MS);
1732
+ timer.unref?.();
1733
+ work.finally(() => {
1734
+ clearTimeout(timer);
1735
+ resolve7();
1736
+ });
1737
+ });
1738
+ }
1739
+ async function releaseOne(opening) {
1740
+ try {
1741
+ const session = await opening;
1742
+ if (session.contract !== "managed-v2") return;
1743
+ await callToolRaw(
1744
+ session.ctx,
1745
+ session.knId,
1746
+ "bkn_finish_interaction",
1747
+ {
1748
+ interaction_id: session.interactionId,
1749
+ outcome: "cancelled",
1750
+ reason: "client session ended"
1751
+ },
1752
+ void 0,
1753
+ RELEASE_TIMEOUT_MS
1754
+ );
1755
+ } catch {
1756
+ }
1757
+ }
1758
+
1300
1759
  // src/resources/context-loader.ts
1301
1760
  function context(ctx) {
1302
1761
  return {
@@ -1309,7 +1768,8 @@ function context(ctx) {
1309
1768
  relationTypes: (knId, ids) => getRelationTypes(ctx, knId, ids),
1310
1769
  info: () => mcpInfo(ctx),
1311
1770
  tools: (knId) => listTools(ctx, knId),
1312
- toolCall: (knId, name, args) => callTool(ctx, knId, name, args),
1771
+ toolCall: (knId, name, args, options) => callTool(ctx, knId, name, args, options),
1772
+ managedToolCall: (knId, name, args, options) => callManagedTool(ctx, knId, name, args, options),
1313
1773
  // Generic MCP method passthrough — covers methods not yet wrapped, so the
1314
1774
  // surface doesn't have to grow every time the server adds one.
1315
1775
  callMethod: (knId, method, params) => callMethod(ctx, knId, method, params),
@@ -1369,7 +1829,11 @@ async function executeDataflow(ctx, body, opts = {}) {
1369
1829
  }
1370
1830
  function listDataflowRuns(ctx, dagId, opts = {}) {
1371
1831
  return request(ctx, `${BASE2}/dag/${encodeURIComponent(dagId)}/results`, {
1372
- query: { since: opts.since || void 0 }
1832
+ query: {
1833
+ since: opts.since || void 0,
1834
+ page: opts.page,
1835
+ limit: opts.limit && opts.limit > 0 ? opts.limit : void 0
1836
+ }
1373
1837
  });
1374
1838
  }
1375
1839
  function runDataflowRemote(ctx, dagId, url, name) {
@@ -1425,9 +1889,11 @@ function deleteKnowledgeNetwork(ctx, knId) {
1425
1889
  function updateKnowledgeNetwork(ctx, knId, body) {
1426
1890
  return request(ctx, `${ONTOLOGY_BASE}/${encodeURIComponent(knId)}`, { method: "PUT", body });
1427
1891
  }
1892
+ var QUERY_OVER_POST = { "X-HTTP-Method-Override": "GET" };
1428
1893
  function querySubgraph(ctx, knId, body) {
1429
1894
  return request(ctx, `${ONTOLOGY_QUERY_BASE}/${encodeURIComponent(knId)}/subgraph`, {
1430
1895
  method: "POST",
1896
+ headers: QUERY_OVER_POST,
1431
1897
  body
1432
1898
  });
1433
1899
  }
@@ -1459,13 +1925,7 @@ function queryObjectTypeInstances(ctx, knId, otId, body) {
1459
1925
  return request(
1460
1926
  ctx,
1461
1927
  `${ONTOLOGY_QUERY_BASE}/${encodeURIComponent(knId)}/object-types/${encodeURIComponent(otId)}`,
1462
- { method: "POST", body }
1463
- );
1464
- }
1465
- function getObjectTypeProperties(ctx, knId, otId) {
1466
- return request(
1467
- ctx,
1468
- `${ONTOLOGY_QUERY_BASE}/${encodeURIComponent(knId)}/object-types/${encodeURIComponent(otId)}/properties`
1928
+ { method: "POST", headers: QUERY_OVER_POST, body }
1469
1929
  );
1470
1930
  }
1471
1931
  function queryActionType(ctx, knId, atId, body) {
@@ -1606,22 +2066,39 @@ function searchMetrics(ctx, knId, body) {
1606
2066
  });
1607
2067
  }
1608
2068
  function validateMetric(ctx, knId, body) {
1609
- return request(ctx, `${ONTOLOGY_BASE}/${encodeURIComponent(knId)}/metrics/validate`, {
2069
+ return request(ctx, `${ONTOLOGY_BASE}/${encodeURIComponent(knId)}/metrics/validation`, {
1610
2070
  method: "POST",
1611
2071
  body
1612
2072
  });
1613
2073
  }
2074
+ function searchBody(knId, query, opts) {
2075
+ return {
2076
+ kn_id: knId,
2077
+ query,
2078
+ mode: opts.mode ?? "keyword_vector_retrieval",
2079
+ max_concepts: opts.maxConcepts ?? 10,
2080
+ return_query_understanding: opts.returnQueryUnderstanding ?? false
2081
+ };
2082
+ }
1614
2083
  function semanticSearch(ctx, knId, query, opts = {}) {
1615
- return request(ctx, `${RETRIEVAL_BASE}/semantic-search`, {
1616
- method: "POST",
1617
- body: {
1618
- kn_id: knId,
1619
- query,
1620
- mode: opts.mode ?? "keyword_vector_retrieval",
1621
- max_concepts: opts.maxConcepts ?? 10,
1622
- return_query_understanding: opts.returnQueryUnderstanding ?? false
1623
- }
1624
- });
2084
+ if (opts.bknContext) {
2085
+ return request(ctx, `${RETRIEVAL_BASE}/semantic-search`, {
2086
+ method: "POST",
2087
+ body: { ...searchBody(knId, query, opts), bkn_context: opts.bknContext }
2088
+ });
2089
+ }
2090
+ return withManagedLifecycle(
2091
+ ctx,
2092
+ knId,
2093
+ query,
2094
+ (bknContext) => request(ctx, `${RETRIEVAL_BASE}/semantic-search`, {
2095
+ method: "POST",
2096
+ body: {
2097
+ ...searchBody(knId, query, opts),
2098
+ ...bknContext ? { bkn_context: bknContext } : {}
2099
+ }
2100
+ })
2101
+ );
1625
2102
  }
1626
2103
 
1627
2104
  // src/api/resources.ts
@@ -1634,7 +2111,10 @@ function listResources2(ctx, opts = {}) {
1634
2111
  category: opts.category || void 0,
1635
2112
  status: opts.status || void 0,
1636
2113
  database: opts.database || void 0,
1637
- limit: opts.limit && opts.limit > 0 ? opts.limit : void 0,
2114
+ // Same `/resources` endpoint as `catalogResources`: limit=-1 (NO_LIMIT)
2115
+ // fetches every row; any other non-positive/invalid value falls back to
2116
+ // the backend default.
2117
+ limit: Number.isFinite(opts.limit) && (opts.limit > 0 || opts.limit === -1) ? opts.limit : void 0,
1638
2118
  offset: opts.offset,
1639
2119
  sort: opts.sort,
1640
2120
  direction: opts.direction,
@@ -1750,18 +2230,31 @@ function deleteResource(ctx, id, opts = {}) {
1750
2230
  });
1751
2231
  }
1752
2232
  async function findResource(ctx, name, opts = {}) {
1753
- const result = await listResources2(ctx, { name, datasourceId: opts.datasourceId });
2233
+ const result = await listResources2(ctx, {
2234
+ name,
2235
+ datasourceId: opts.datasourceId,
2236
+ limit: opts.limit
2237
+ });
1754
2238
  const list = Array.isArray(result) ? result : result.entries ?? [];
1755
2239
  return opts.exact ? list.filter((r) => r.name === name) : list;
1756
2240
  }
1757
2241
  function queryResource(ctx, id, opts = {}) {
1758
- return request(ctx, `${BASE3}/${encodeURIComponent(id)}/data`, {
1759
- method: "POST",
1760
- body: {
2242
+ const body = opts.cursor ? {
2243
+ paging: { cursor: opts.cursor },
2244
+ need_total: opts.needTotal ?? false
2245
+ } : {
2246
+ paging: {
2247
+ mode: opts.pagingMode ?? "single",
1761
2248
  limit: opts.limit ?? 50,
1762
2249
  offset: opts.offset ?? 0,
1763
- need_total: opts.needTotal ?? false
1764
- }
2250
+ ...opts.keepAliveSec !== void 0 ? { keep_alive_sec: opts.keepAliveSec } : {}
2251
+ },
2252
+ need_total: opts.needTotal ?? false
2253
+ };
2254
+ return request(ctx, `${BASE3}/${encodeURIComponent(id)}/data`, {
2255
+ method: "POST",
2256
+ headers: { "X-HTTP-Method-Override": "GET" },
2257
+ body
1765
2258
  });
1766
2259
  }
1767
2260
 
@@ -2999,7 +3492,11 @@ function listBknResources(ctx) {
2999
3492
  return request(ctx, "/api/bkn-backend/v1/resources");
3000
3493
  }
3001
3494
  function relationTypePaths(ctx, knId, body) {
3002
- return request(ctx, knPath(knId, "relation-type-paths"), { method: "POST", body });
3495
+ return request(ctx, knPath(knId, "relation-type-paths"), {
3496
+ method: "POST",
3497
+ headers: { "X-HTTP-Method-Override": "GET" },
3498
+ body
3499
+ });
3003
3500
  }
3004
3501
  function listConceptGroups(ctx, knId) {
3005
3502
  return request(ctx, knPath(knId, "concept-groups"));
@@ -3060,45 +3557,107 @@ function setActionScheduleStatus(ctx, knId, scheduleId, body) {
3060
3557
  function deleteActionSchedules(ctx, knId, ids) {
3061
3558
  return request(ctx, knPath(knId, `action-schedules/${ids}`), { method: "DELETE" });
3062
3559
  }
3063
- function listJobs(ctx, knId) {
3064
- return request(ctx, knPath(knId, "jobs"));
3065
- }
3066
- function getJob(ctx, knId, jobId) {
3067
- return request(ctx, knPath(knId, `jobs/${encodeURIComponent(jobId)}`));
3068
- }
3069
- function getJobTasks(ctx, knId, jobId) {
3070
- return request(ctx, knPath(knId, `jobs/${encodeURIComponent(jobId)}/tasks`));
3071
- }
3072
- function deleteJobs(ctx, knId, ids) {
3073
- return request(ctx, knPath(knId, `jobs/${ids}`), { method: "DELETE" });
3074
- }
3075
3560
 
3076
3561
  // src/api/vega.ts
3077
3562
  import { z } from "zod";
3078
3563
  var VEGA_BASE = "/api/vega-backend/v1";
3079
3564
  var BuildMode = z.enum(["batch", "streaming"]);
3080
- var CreateBuildTaskRequest = z.object({
3081
- resource_id: z.string().min(1),
3082
- mode: BuildMode,
3083
- execute_type: z.enum(["incremental", "full"]).optional()
3084
- });
3565
+ var BuildTaskExecuteType = z.enum(["incremental", "full"]);
3566
+ var BuildTaskStatus = z.enum([
3567
+ "init",
3568
+ "running",
3569
+ "stopping",
3570
+ "stopped",
3571
+ "completed",
3572
+ "failed"
3573
+ ]);
3574
+ var CatalogHealthCheckScheduleMode = z.enum(["inherit", "enabled", "disabled"]);
3575
+ var CatalogHealthCheckStatus = z.enum([
3576
+ "healthy",
3577
+ "degraded",
3578
+ "unhealthy",
3579
+ "offline",
3580
+ "unchecked"
3581
+ ]);
3582
+ var CatalogHealthStatus = z.object({
3583
+ id: z.string(),
3584
+ health_check_status: CatalogHealthCheckStatus,
3585
+ last_check_time: z.number().optional(),
3586
+ health_check_result: z.string().optional()
3587
+ }).passthrough();
3588
+ var CatalogHealthCheckSchedule = z.object({
3589
+ catalog_id: z.string(),
3590
+ mode: CatalogHealthCheckScheduleMode,
3591
+ cron_expr: z.string().optional(),
3592
+ last_run: z.number(),
3593
+ next_run: z.number()
3594
+ }).passthrough();
3595
+ var CatalogConnectionTestResult = z.object({
3596
+ success: z.boolean(),
3597
+ message: z.string().optional()
3598
+ }).passthrough();
3599
+ var CreateBuildTaskRequest = z.discriminatedUnion("mode", [
3600
+ z.object({
3601
+ resource_id: z.string().min(1),
3602
+ mode: z.literal("batch"),
3603
+ execute_type: BuildTaskExecuteType.optional()
3604
+ }),
3605
+ z.object({
3606
+ resource_id: z.string().min(1),
3607
+ mode: z.literal("streaming"),
3608
+ // Streaming tasks do not have an execution type.
3609
+ execute_type: z.never().optional()
3610
+ })
3611
+ ]);
3085
3612
  var BuildTask = z.object({
3086
3613
  id: z.string(),
3087
3614
  resource_id: z.string().optional(),
3088
3615
  mode: BuildMode.optional(),
3089
- status: z.string().optional(),
3616
+ status: BuildTaskStatus.optional(),
3090
3617
  state: z.string().optional(),
3091
3618
  total_count: z.number().optional(),
3092
3619
  synced_count: z.number().optional(),
3093
3620
  vectorized_count: z.number().optional(),
3094
3621
  index_config: z.unknown().optional(),
3095
3622
  catalog_id: z.string().optional(),
3623
+ execute_type: BuildTaskExecuteType.optional(),
3096
3624
  index_health: z.object({
3097
3625
  embedding: z.string(),
3098
3626
  fulltext: z.string(),
3099
3627
  usable: z.boolean()
3100
3628
  }).passthrough().optional()
3101
3629
  }).passthrough();
3630
+ var BuildTaskSummary = z.object({
3631
+ id: z.string(),
3632
+ resource_id: z.string(),
3633
+ resource_name: z.string().optional(),
3634
+ catalog_id: z.string(),
3635
+ catalog_name: z.string().optional(),
3636
+ status: BuildTaskStatus,
3637
+ mode: BuildMode,
3638
+ execute_type: BuildTaskExecuteType.optional(),
3639
+ total_count: z.number(),
3640
+ synced_count: z.number(),
3641
+ vectorized_count: z.number(),
3642
+ synced_mark: z.string(),
3643
+ error_msg: z.string().optional(),
3644
+ creator: z.object({
3645
+ id: z.string(),
3646
+ name: z.string().optional(),
3647
+ type: z.string()
3648
+ }),
3649
+ create_time: z.number(),
3650
+ update_time: z.number(),
3651
+ index_health: z.object({
3652
+ embedding: z.string(),
3653
+ fulltext: z.string(),
3654
+ usable: z.boolean()
3655
+ }).passthrough().optional()
3656
+ }).passthrough();
3657
+ var ListBuildTasksResponse = z.object({
3658
+ entries: z.array(BuildTaskSummary),
3659
+ total_count: z.number()
3660
+ }).passthrough();
3102
3661
  async function createBuildTask(ctx, req) {
3103
3662
  const p = CreateBuildTaskRequest.parse(req);
3104
3663
  const body = {
@@ -3109,8 +3668,13 @@ async function createBuildTask(ctx, req) {
3109
3668
  const res = await request(ctx, `${VEGA_BASE}/build-tasks`, { method: "POST", body });
3110
3669
  return BuildTask.parse(res);
3111
3670
  }
3112
- function listBuildTasks(ctx, opts = {}) {
3113
- return request(ctx, `${VEGA_BASE}/build-tasks`, {
3671
+ async function listBuildTasks(ctx, opts = {}) {
3672
+ if (opts.orderBy === "default") {
3673
+ throw new InputError(
3674
+ 'orderBy "default" is no longer supported; use "created_at" or "updated_at"'
3675
+ );
3676
+ }
3677
+ const res = await request(ctx, `${VEGA_BASE}/build-tasks`, {
3114
3678
  query: {
3115
3679
  limit: opts.limit,
3116
3680
  offset: opts.offset,
@@ -3123,6 +3687,7 @@ function listBuildTasks(ctx, opts = {}) {
3123
3687
  order: opts.order
3124
3688
  }
3125
3689
  });
3690
+ return ListBuildTasksResponse.parse(res);
3126
3691
  }
3127
3692
  async function getBuildTask(ctx, taskId) {
3128
3693
  const res = await request(ctx, `${VEGA_BASE}/build-tasks/${encodeURIComponent(taskId)}`);
@@ -3173,35 +3738,46 @@ async function listCatalogs(ctx, opts = {}) {
3173
3738
  function getCatalog(ctx, id) {
3174
3739
  return request(ctx, `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}`);
3175
3740
  }
3176
- function createCatalog(ctx, req) {
3741
+ function createCatalog(ctx, req, opts = {}) {
3177
3742
  return request(ctx, `${VEGA_BASE}/catalogs`, {
3178
3743
  method: "POST",
3744
+ query: {
3745
+ allow_unhealthy: opts.allowUnhealthy === void 0 ? void 0 : String(opts.allowUnhealthy)
3746
+ },
3179
3747
  body: {
3180
3748
  ...req.id ? { id: req.id } : {},
3181
3749
  name: req.name,
3182
3750
  connector_type: req.connectorType,
3183
3751
  connector_config: req.connectorConfig,
3184
- ...req.tags ? { tags: req.tags } : {},
3185
- ...req.description ? { description: req.description } : {},
3752
+ ...req.tags !== void 0 ? { tags: req.tags } : {},
3753
+ ...req.description !== void 0 ? { description: req.description } : {},
3186
3754
  ...req.enabled !== void 0 ? { enabled: req.enabled } : {},
3187
3755
  ...req.internal !== void 0 ? { internal: req.internal } : {},
3188
- ...req.extensions ? { extensions: req.extensions } : {}
3189
- }
3756
+ ...req.extensions !== void 0 ? { extensions: req.extensions } : {},
3757
+ ...req.healthCheckSchedule !== void 0 ? {
3758
+ health_check_schedule: req.healthCheckSchedule === null ? null : mapCatalogHealthCheckScheduleRequest(req.healthCheckSchedule)
3759
+ } : {}
3760
+ },
3761
+ timeoutMs: 6e4
3190
3762
  });
3191
3763
  }
3192
- function updateCatalog(ctx, id, req) {
3764
+ function updateCatalog(ctx, id, req, opts = {}) {
3193
3765
  return request(ctx, `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}`, {
3194
3766
  method: "PUT",
3767
+ query: {
3768
+ allow_unhealthy: opts.allowUnhealthy === void 0 ? void 0 : String(opts.allowUnhealthy)
3769
+ },
3195
3770
  body: {
3196
- ...req.id ? { id: req.id } : {},
3197
- ...req.name ? { name: req.name } : {},
3198
- ...req.connectorType ? { connector_type: req.connectorType } : {},
3771
+ id,
3772
+ name: req.name,
3773
+ connector_type: req.connectorType,
3774
+ enabled: req.enabled,
3199
3775
  ...req.connectorConfig !== void 0 ? { connector_config: req.connectorConfig } : {},
3200
- ...req.tags ? { tags: req.tags } : {},
3776
+ ...req.tags !== void 0 ? { tags: req.tags } : {},
3201
3777
  ...req.description !== void 0 ? { description: req.description } : {},
3202
- ...req.enabled !== void 0 ? { enabled: req.enabled } : {},
3203
- ...req.extensions ? { extensions: req.extensions } : {}
3204
- }
3778
+ ...req.extensions !== void 0 ? { extensions: req.extensions } : {}
3779
+ },
3780
+ timeoutMs: 6e4
3205
3781
  });
3206
3782
  }
3207
3783
  function enableCatalog(ctx, id) {
@@ -3215,10 +3791,51 @@ function disableCatalog(ctx, id) {
3215
3791
  function deleteCatalog(ctx, id) {
3216
3792
  return request(ctx, `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}`, { method: "DELETE" });
3217
3793
  }
3218
- function testCatalogConnection(ctx, id) {
3219
- return request(ctx, `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}/test-connection`, {
3220
- method: "POST"
3794
+ async function testCatalogConnectionConfig(ctx, req) {
3795
+ const result = await request(ctx, `${VEGA_BASE}/catalogs/test-connection`, {
3796
+ method: "POST",
3797
+ body: {
3798
+ connector_type: req.connectorType,
3799
+ connector_config: req.connectorConfig
3800
+ },
3801
+ timeoutMs: 6e4
3221
3802
  });
3803
+ return CatalogConnectionTestResult.parse(result);
3804
+ }
3805
+ async function testCatalogConnection(ctx, id) {
3806
+ const result = await request(
3807
+ ctx,
3808
+ `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}/test-connection`,
3809
+ {
3810
+ method: "POST",
3811
+ timeoutMs: 6e4
3812
+ }
3813
+ );
3814
+ return CatalogConnectionTestResult.parse(result);
3815
+ }
3816
+ async function getCatalogHealthCheckSchedule(ctx, id) {
3817
+ const result = await request(
3818
+ ctx,
3819
+ `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}/health-check-schedule`
3820
+ );
3821
+ return CatalogHealthCheckSchedule.parse(result);
3822
+ }
3823
+ async function updateCatalogHealthCheckSchedule(ctx, id, req) {
3824
+ const result = await request(
3825
+ ctx,
3826
+ `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}/health-check-schedule`,
3827
+ {
3828
+ method: "PUT",
3829
+ body: mapCatalogHealthCheckScheduleRequest(req)
3830
+ }
3831
+ );
3832
+ return CatalogHealthCheckSchedule.parse(result);
3833
+ }
3834
+ function mapCatalogHealthCheckScheduleRequest(req) {
3835
+ return {
3836
+ mode: req.mode,
3837
+ ...req.mode === "enabled" ? { cron_expr: req.cronExpr } : {}
3838
+ };
3222
3839
  }
3223
3840
  function discoverCatalog(ctx, id, wait = true) {
3224
3841
  return request(ctx, `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}/discover`, {
@@ -3227,16 +3844,23 @@ function discoverCatalog(ctx, id, wait = true) {
3227
3844
  timeoutMs: 12e4
3228
3845
  });
3229
3846
  }
3230
- function listCatalogResources(ctx, id, category) {
3847
+ function listCatalogResources(ctx, id, category, limit, offset) {
3231
3848
  return request(ctx, `${VEGA_BASE}/resources`, {
3232
- query: { catalog_id: id, category: category || void 0 }
3849
+ query: {
3850
+ catalog_id: id,
3851
+ category: category || void 0,
3852
+ // limit=-1 (NO_LIMIT) fetches all; NaN / 0 fall back to the backend default.
3853
+ limit: Number.isFinite(limit) && (limit > 0 || limit === -1) ? limit : void 0,
3854
+ offset: offset || void 0
3855
+ }
3233
3856
  });
3234
3857
  }
3235
- function catalogHealthStatus(ctx, ids) {
3236
- return request(
3858
+ async function catalogHealthStatus(ctx, id) {
3859
+ const result = await request(
3237
3860
  ctx,
3238
- `${VEGA_BASE}/catalogs/${ids.map(encodeURIComponent).join(",")}/health-status`
3861
+ `${VEGA_BASE}/catalogs/${encodeURIComponent(id)}/health-status`
3239
3862
  );
3863
+ return CatalogHealthStatus.parse(result);
3240
3864
  }
3241
3865
  function listConnectorTypes(ctx) {
3242
3866
  return request(ctx, `${VEGA_BASE}/connector-types`, { query: { sort: "name", order: "asc" } });
@@ -3871,7 +4495,6 @@ function kn(ctx) {
3871
4495
  metricValidate: (knId, body) => validateMetric(ctx, knId, body),
3872
4496
  objectTypes: (knId, opts) => listObjectTypes(ctx, knId, opts),
3873
4497
  objectTypeQuery: (knId, otId, body) => queryObjectTypeInstances(ctx, knId, otId, body),
3874
- objectTypeProperties: (knId, otId) => getObjectTypeProperties(ctx, knId, otId),
3875
4498
  objectTypeGet: (knId, id) => getSchemaItem(ctx, knId, "object-types", id),
3876
4499
  objectTypeCreate: (knId, body) => createSchemaItem(ctx, knId, "object-types", body),
3877
4500
  objectTypeUpdate: (knId, id, body) => updateSchemaItem(ctx, knId, "object-types", id, body),
@@ -3899,10 +4522,6 @@ function kn(ctx) {
3899
4522
  actionScheduleUpdate: (knId, scheduleId, body) => updateActionSchedule(ctx, knId, scheduleId, body),
3900
4523
  actionScheduleSetStatus: (knId, scheduleId, body) => setActionScheduleStatus(ctx, knId, scheduleId, body),
3901
4524
  actionScheduleDelete: (knId, ids) => deleteActionSchedules(ctx, knId, ids),
3902
- jobs: (knId) => listJobs(ctx, knId),
3903
- job: (knId, jobId) => getJob(ctx, knId, jobId),
3904
- jobTasks: (knId, jobId) => getJobTasks(ctx, knId, jobId),
3905
- jobDelete: (knId, ids) => deleteJobs(ctx, knId, ids),
3906
4525
  relationTypePaths: (knId, body) => relationTypePaths(ctx, knId, body),
3907
4526
  bknResources: () => listBknResources(ctx),
3908
4527
  createFromCatalog: (opts) => createFromCatalog(ctx, opts),
@@ -4155,16 +4774,45 @@ async function updateSkillPackageZip(ctx, skillId, bytes, filename = "skill.zip"
4155
4774
  if (!res.ok) throw new HttpError(res.status, res.statusText, text);
4156
4775
  return text ? JSON.parse(text) : void 0;
4157
4776
  }
4158
- async function downloadSkill(ctx, skillId) {
4777
+ function skillPath(skillId, view, path) {
4778
+ const seg = view === "draft" ? "management/" : "";
4779
+ return `${BASE5}/skills/${encodeURIComponent(skillId)}/${seg}${path}`;
4780
+ }
4781
+ async function downloadSkill(ctx, skillId, view = "published") {
4159
4782
  const res = await authFetch(
4160
4783
  ctx,
4161
- () => tlsFetch(ctx.insecure, `${ctx.baseUrl}${BASE5}/skills/${encodeURIComponent(skillId)}/download`, {
4784
+ () => tlsFetch(ctx.insecure, `${ctx.baseUrl}${skillPath(skillId, view, "download")}`, {
4162
4785
  headers: buildHeaders(ctx)
4163
4786
  })
4164
4787
  );
4165
4788
  if (!res.ok) throw new HttpError(res.status, res.statusText, await res.text());
4166
4789
  return new Uint8Array(await res.arrayBuffer());
4167
4790
  }
4791
+ function executeSkill(ctx, skillId, opts) {
4792
+ return request(ctx, `${BASE5}/skills/${encodeURIComponent(skillId)}/execute`, {
4793
+ method: "POST",
4794
+ body: {
4795
+ entry_shell: opts.entryShell,
4796
+ ...opts.timeout === void 0 ? {} : { timeout: opts.timeout }
4797
+ },
4798
+ // Outlast the sandbox: the default client timeout is shorter than the run
4799
+ // budget, so without this a long run aborts locally mid-execution and the
4800
+ // caller never learns the exit code. With no stated limit the sandbox
4801
+ // applies its own — 300s by default, 3600s at most — so the budget has to
4802
+ // cover that rather than a number of ours.
4803
+ timeoutMs: executeBudgetMs(opts.timeout),
4804
+ // The abort deadline alone tops out at undici's 300s header deadline,
4805
+ // because `execute-sync` blocks and sends no headers until the run is over.
4806
+ headersTimeoutMs: executeBudgetMs(opts.timeout)
4807
+ });
4808
+ }
4809
+ var SANDBOX_MAX_TIMEOUT_SEC = 3600;
4810
+ function executeBudgetMs(timeoutSec) {
4811
+ return (timeoutSec ?? SANDBOX_MAX_TIMEOUT_SEC) * 1e3 + 15e3;
4812
+ }
4813
+ function getSkillNames(ctx, ids) {
4814
+ return request(ctx, `${BASE5}/skills/names`, { method: "POST", body: { ids } });
4815
+ }
4168
4816
  function updateSkillMetadata(ctx, skillId, body) {
4169
4817
  return request(ctx, `${BASE5}/skills/${encodeURIComponent(skillId)}`, { method: "PUT", body });
4170
4818
  }
@@ -4205,12 +4853,15 @@ function getSkillMarket(ctx, skillId) {
4205
4853
  function deleteSkill(ctx, skillId) {
4206
4854
  return request(ctx, `${BASE5}/skills/${encodeURIComponent(skillId)}`, { method: "DELETE" });
4207
4855
  }
4208
- function getSkillContent(ctx, skillId) {
4209
- return request(ctx, `${BASE5}/skills/${encodeURIComponent(skillId)}/content`);
4856
+ function getSkillContent(ctx, skillId, opts = {}) {
4857
+ return request(ctx, skillPath(skillId, opts.view ?? "published", "content"), {
4858
+ query: { response_mode: opts.responseMode }
4859
+ });
4210
4860
  }
4211
- function readSkillFile(ctx, skillId, relPath) {
4212
- return request(ctx, `${BASE5}/skills/${encodeURIComponent(skillId)}/files/read`, {
4861
+ function readSkillFile(ctx, skillId, relPath, opts = {}) {
4862
+ return request(ctx, skillPath(skillId, opts.view ?? "published", "files/read"), {
4213
4863
  method: "POST",
4864
+ query: { response_mode: opts.responseMode },
4214
4865
  body: { rel_path: relPath }
4215
4866
  });
4216
4867
  }
@@ -4248,6 +4899,15 @@ async function zipDirectory(dir) {
4248
4899
  }
4249
4900
  return new Uint8Array(await zip.generateAsync({ type: "uint8array", compression: "DEFLATE" }));
4250
4901
  }
4902
+ async function unzipToMap(bytes) {
4903
+ const zip = await JSZip.loadAsync(bytes);
4904
+ const out = /* @__PURE__ */ new Map();
4905
+ for (const entry of Object.values(zip.files)) {
4906
+ if (entry.dir) continue;
4907
+ out.set(entry.name, new Uint8Array(await entry.async("uint8array")));
4908
+ }
4909
+ return out;
4910
+ }
4251
4911
  async function unzipToDirectory(bytes, dir) {
4252
4912
  const abs = resolve4(dir);
4253
4913
  mkdirSync3(abs, { recursive: true });
@@ -4264,16 +4924,202 @@ async function unzipToDirectory(bytes, dir) {
4264
4924
  return written;
4265
4925
  }
4266
4926
 
4927
+ // src/utils/skill-tree.ts
4928
+ function normalize(path) {
4929
+ return (path ?? "").replace(/^\/+|\/+$/g, "");
4930
+ }
4931
+ function segments(relPath) {
4932
+ return relPath.split("/").filter(Boolean);
4933
+ }
4934
+ function classifyPath(files, path) {
4935
+ const target = normalize(path);
4936
+ if (!target) return "root";
4937
+ if (files.some((f) => normalize(f.rel_path) === target)) return "file";
4938
+ const prefix = `${target}/`;
4939
+ return files.some((f) => normalize(f.rel_path).startsWith(prefix)) ? "dir" : "missing";
4940
+ }
4941
+ function listChildren(files, path) {
4942
+ const target = normalize(path);
4943
+ const prefix = target ? `${target}/` : "";
4944
+ const dirs = /* @__PURE__ */ new Map();
4945
+ const leaves = [];
4946
+ for (const file of files) {
4947
+ const rel = normalize(file.rel_path);
4948
+ if (prefix && !rel.startsWith(prefix)) continue;
4949
+ const rest = segments(rel.slice(prefix.length));
4950
+ if (rest.length === 0) continue;
4951
+ const [head] = rest;
4952
+ if (!head) continue;
4953
+ if (rest.length === 1) {
4954
+ leaves.push({
4955
+ name: head,
4956
+ type: "file",
4957
+ relPath: rel,
4958
+ fileType: file.file_type,
4959
+ size: file.size,
4960
+ mime: file.mime_type
4961
+ });
4962
+ continue;
4963
+ }
4964
+ const dir = dirs.get(head) ?? { name: head, type: "dir", files: 0, size: 0 };
4965
+ dir.files += 1;
4966
+ dir.size += file.size ?? 0;
4967
+ dirs.set(head, dir);
4968
+ }
4969
+ const byName = (a, b) => a.name.localeCompare(b.name);
4970
+ return [...[...dirs.values()].sort(byName), ...leaves.sort(byName)];
4971
+ }
4972
+ function filesUnder(files, path) {
4973
+ const target = normalize(path);
4974
+ if (!target) return files;
4975
+ const prefix = `${target}/`;
4976
+ return files.filter((f) => normalize(f.rel_path).startsWith(prefix));
4977
+ }
4978
+ function emptyNode() {
4979
+ return { dirs: /* @__PURE__ */ new Map(), files: [] };
4980
+ }
4981
+ function buildTree(files) {
4982
+ const root = emptyNode();
4983
+ for (const file of files) {
4984
+ const parts = segments(normalize(file.rel_path));
4985
+ const name = parts.pop();
4986
+ if (!name) continue;
4987
+ let node = root;
4988
+ for (const part of parts) {
4989
+ const next = node.dirs.get(part) ?? emptyNode();
4990
+ node.dirs.set(part, next);
4991
+ node = next;
4992
+ }
4993
+ node.files.push({
4994
+ name,
4995
+ type: "file",
4996
+ relPath: normalize(file.rel_path),
4997
+ fileType: file.file_type,
4998
+ size: file.size,
4999
+ mime: file.mime_type
5000
+ });
5001
+ }
5002
+ return root;
5003
+ }
5004
+ function renderNode(node, indent, out) {
5005
+ const dirs = [...node.dirs.entries()].sort(([a], [b]) => a.localeCompare(b));
5006
+ const files = [...node.files].sort((a, b) => a.name.localeCompare(b.name));
5007
+ const total = dirs.length + files.length;
5008
+ let index = 0;
5009
+ for (const [name, child] of dirs) {
5010
+ index += 1;
5011
+ const last = index === total;
5012
+ out.push(`${indent}${last ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "}${name}/`);
5013
+ renderNode(child, `${indent}${last ? " " : "\u2502 "}`, out);
5014
+ }
5015
+ for (const file of files) {
5016
+ index += 1;
5017
+ const last = index === total;
5018
+ const meta = [file.fileType, file.size === void 0 ? void 0 : `${file.size} B`].filter(Boolean).join(", ");
5019
+ out.push(`${indent}${last ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "}${file.name}${meta ? ` (${meta})` : ""}`);
5020
+ }
5021
+ }
5022
+ function renderTree(files) {
5023
+ const out = [];
5024
+ renderNode(buildTree(files), "", out);
5025
+ return out.join("\n");
5026
+ }
5027
+
4267
5028
  // src/resources/skills.ts
5029
+ function viewOf(opts) {
5030
+ return opts?.draft ? "draft" : "published";
5031
+ }
5032
+ function cachedArchive(cache, ctx, skillId, view) {
5033
+ const key = `${skillId}|${view}`;
5034
+ const hit = cache.get(key);
5035
+ if (hit) return hit;
5036
+ const pending = downloadSkill(ctx, skillId, view).then(unzipToMap);
5037
+ cache.set(key, pending);
5038
+ pending.catch(() => cache.delete(key));
5039
+ return pending;
5040
+ }
5041
+ function binaryError(relPath) {
5042
+ return new InputError(
5043
+ `'${relPath}' is a binary file \u2014 use \`openbkn skill install\` or \`skill download\` to fetch it.`
5044
+ );
5045
+ }
5046
+ function decodeStrict(bytes, relPath) {
5047
+ let text;
5048
+ try {
5049
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
5050
+ } catch {
5051
+ throw binaryError(relPath);
5052
+ }
5053
+ if (text.includes("\0")) throw binaryError(relPath);
5054
+ return text;
5055
+ }
5056
+ function looksLossy(text) {
5057
+ return text.includes("\0") || text.includes("\uFFFD");
5058
+ }
5059
+ async function readFileText(cache, ctx, skillId, relPath, view) {
5060
+ const res = await readSkillFile(ctx, skillId, normalize(relPath), {
5061
+ view,
5062
+ responseMode: "content"
5063
+ });
5064
+ if (typeof res?.content === "string" && res.content.length > 0) {
5065
+ if (looksLossy(res.content)) throw binaryError(relPath);
5066
+ return res.content;
5067
+ }
5068
+ const archive = await cachedArchive(cache, ctx, skillId, view);
5069
+ const bytes = archive.get(normalize(relPath));
5070
+ if (!bytes) {
5071
+ throw new InputError(`'${relPath}' not found in skill ${skillId}.`);
5072
+ }
5073
+ return decodeStrict(bytes, relPath);
5074
+ }
4268
5075
  function skills(ctx) {
5076
+ const archives = /* @__PURE__ */ new Map();
5077
+ const manifest = async (skillId, opts) => {
5078
+ const res = await getSkillContent(ctx, skillId, { view: viewOf(opts) });
5079
+ return res?.files ?? [];
5080
+ };
4269
5081
  return {
4270
5082
  list: (opts) => listSkills(ctx, opts),
4271
5083
  get: (skillId) => getSkill(ctx, skillId),
4272
5084
  market: (opts) => listSkillMarket(ctx, opts),
4273
5085
  marketGet: (skillId) => getSkillMarket(ctx, skillId),
4274
5086
  delete: (skillId) => deleteSkill(ctx, skillId),
4275
- content: (skillId) => getSkillContent(ctx, skillId),
4276
- readFile: (skillId, relPath) => readSkillFile(ctx, skillId, relPath),
5087
+ content: (skillId, opts) => getSkillContent(ctx, skillId, { view: viewOf(opts) }),
5088
+ // Normalized like every other path entry point, so `/a/b.md` addresses the
5089
+ // same file here as it does under `--raw` and in `skill files`.
5090
+ readFile: (skillId, relPath, opts) => readSkillFile(ctx, skillId, normalize(relPath), { view: viewOf(opts) }),
5091
+ /** SKILL.md's own text, for callers that want the document rather than a link. */
5092
+ contentRaw: (skillId, opts) => readFileText(archives, ctx, skillId, "SKILL.md", viewOf(opts)),
5093
+ /** A bundled file's text. */
5094
+ readFileRaw: (skillId, relPath, opts) => readFileText(archives, ctx, skillId, relPath, viewOf(opts)),
5095
+ /** Run the skill in the platform sandbox. */
5096
+ execute: (skillId, opts) => executeSkill(ctx, skillId, opts),
5097
+ /** Resolve skill ids to names; unknown ids are simply absent from the result. */
5098
+ names: (ids) => getSkillNames(ctx, ids),
5099
+ /**
5100
+ * One level of the skill's file tree. Directories are inferred from the
5101
+ * manifest's paths — see utils/skill-tree.
5102
+ */
5103
+ files: async (skillId, path, opts) => {
5104
+ const files = await manifest(skillId, opts);
5105
+ const kind = classifyPath(files, path);
5106
+ if (kind === "file") {
5107
+ throw new InputError(`'${path}' is a file, not a directory \u2014 use \`skill read-file\`.`);
5108
+ }
5109
+ if (kind === "missing") {
5110
+ throw new InputError(`'${path}' not found in skill ${skillId}.`);
5111
+ }
5112
+ const subtree = filesUnder(files, path);
5113
+ return {
5114
+ skillId,
5115
+ path: path ?? "",
5116
+ entries: listChildren(files, path),
5117
+ totalFiles: subtree.length,
5118
+ totalSize: subtree.reduce((sum, f) => sum + (f.size ?? 0), 0)
5119
+ };
5120
+ },
5121
+ /** The manifest itself, for callers that want to render the whole tree. */
5122
+ fileManifest: manifest,
4277
5123
  history: (skillId) => getSkillHistory(ctx, skillId),
4278
5124
  setStatus: (skillId, status2) => setSkillStatus(ctx, skillId, status2),
4279
5125
  updateMetadata: (skillId, body) => updateSkillMetadata(ctx, skillId, body),
@@ -4287,16 +5133,16 @@ function skills(ctx) {
4287
5133
  /** Replace a skill's package from a local directory. */
4288
5134
  updatePackage: async (skillId, dir) => updateSkillPackageZip(ctx, skillId, await zipDirectory(dir), `${basename2(resolve5(dir))}.zip`),
4289
5135
  /** Download a skill archive to a local .zip file. */
4290
- download: async (skillId, outPath) => {
4291
- const bytes = await downloadSkill(ctx, skillId);
5136
+ download: async (skillId, outPath, opts) => {
5137
+ const bytes = await downloadSkill(ctx, skillId, viewOf(opts));
4292
5138
  const dest = resolve5(outPath ?? `${skillId}.zip`);
4293
5139
  mkdirSync4(dirname3(dest), { recursive: true });
4294
5140
  writeFileSync3(dest, bytes);
4295
5141
  return { skillId, path: dest, bytes: bytes.length };
4296
5142
  },
4297
5143
  /** Download a skill archive and extract it into a directory. */
4298
- install: async (skillId, dir) => {
4299
- const bytes = await downloadSkill(ctx, skillId);
5144
+ install: async (skillId, dir, opts) => {
5145
+ const bytes = await downloadSkill(ctx, skillId, viewOf(opts));
4300
5146
  const target = resolve5(dir ?? skillId);
4301
5147
  const files = await unzipToDirectory(bytes, target);
4302
5148
  return { skillId, dir: target, files: files.length };
@@ -4366,8 +5212,14 @@ function listToolboxes(ctx, opts = {}) {
4366
5212
  query: { keyword: opts.keyword || void 0, limit: opts.limit, offset: opts.offset ?? 0 }
4367
5213
  });
4368
5214
  }
4369
- function listTools2(ctx, boxId) {
4370
- return request(ctx, `${PATH}/${encodeURIComponent(boxId)}/tools/list`);
5215
+ function listTools2(ctx, boxId, opts = {}) {
5216
+ return request(ctx, `${PATH}/${encodeURIComponent(boxId)}/tools/list`, {
5217
+ query: {
5218
+ page: opts.page,
5219
+ page_size: Number.isFinite(opts.pageSize) && opts.pageSize > 0 ? opts.pageSize : void 0,
5220
+ all: opts.all ? "true" : void 0
5221
+ }
5222
+ });
4371
5223
  }
4372
5224
  function createToolbox(ctx, opts) {
4373
5225
  return request(ctx, PATH, {
@@ -4426,7 +5278,7 @@ function setToolStatuses(ctx, boxId, updates) {
4426
5278
  function toolboxes(ctx) {
4427
5279
  return {
4428
5280
  list: (opts) => listToolboxes(ctx, opts),
4429
- tools: (boxId) => listTools2(ctx, boxId),
5281
+ tools: (boxId, opts) => listTools2(ctx, boxId, opts),
4430
5282
  create: (opts) => createToolbox(ctx, opts),
4431
5283
  delete: (boxId) => deleteToolbox(ctx, boxId),
4432
5284
  publish: (boxId) => setToolboxStatus(ctx, boxId, "published"),
@@ -4452,8 +5304,385 @@ function toolboxes(ctx) {
4452
5304
  };
4453
5305
  }
4454
5306
 
5307
+ // src/api/trace-lifecycle.ts
5308
+ var LIFECYCLE = "/api/agent-observability/v1";
5309
+ var FORBIDDEN_INPUT_FIELDS = /* @__PURE__ */ new Set([
5310
+ "generation",
5311
+ "on_behalf_of",
5312
+ "onBehalfOf",
5313
+ "owner",
5314
+ "tenant_id",
5315
+ "application_principal_id",
5316
+ "actor_subject",
5317
+ "actor_subject_type",
5318
+ "actor_subject_id",
5319
+ "effective_subject",
5320
+ "effective_subject_type",
5321
+ "effective_subject_id",
5322
+ "delegation_id"
5323
+ ]);
5324
+ function traceLifecycleApi(ctx) {
5325
+ const post2 = async (path, input) => {
5326
+ assertNoForbiddenInputFields(input);
5327
+ return await request(ctx, `${LIFECYCLE}${path}`, { method: "POST", body: input });
5328
+ };
5329
+ const get = (path) => request(ctx, `${LIFECYCLE}${path}`, { method: "GET" });
5330
+ const interactionTerminal = (interactionId, action, input) => post2(`/interactions/${encodeURIComponent(interactionId)}/${action}`, input);
5331
+ const finishAttempt = (operationId, attempt, action, input) => post2(
5332
+ `/operations/${encodeURIComponent(operationId)}/attempts/${encodeURIComponent(String(attempt))}:${action}`,
5333
+ input
5334
+ );
5335
+ return {
5336
+ listConversations: (query = {}) => {
5337
+ const params = new URLSearchParams();
5338
+ if (query.limit !== void 0 && Number.isFinite(query.limit)) {
5339
+ params.set("limit", String(query.limit));
5340
+ }
5341
+ const suffix = params.size > 0 ? `?${params.toString()}` : "";
5342
+ return get(`/conversations${suffix}`);
5343
+ },
5344
+ ensureConversation: (input) => post2("/conversations:ensure-current", input),
5345
+ createNewConversationGeneration: (input) => post2("/conversations:create-new-generation", input),
5346
+ resumeConversation: (input) => post2("/conversations:resume-by-id", input),
5347
+ getConversation: (conversationId) => get(`/conversations/${encodeURIComponent(conversationId)}`),
5348
+ closeConversation: (conversationId, input) => post2(`/conversations/${encodeURIComponent(conversationId)}/close`, input),
5349
+ startInteraction: (conversationId, input) => post2(`/conversations/${encodeURIComponent(conversationId)}/interactions`, input),
5350
+ getInteraction: (interactionId) => get(`/interactions/${encodeURIComponent(interactionId)}`),
5351
+ completeInteraction: (interactionId, input) => interactionTerminal(interactionId, "complete", input),
5352
+ failInteraction: (interactionId, input) => interactionTerminal(interactionId, "fail", input),
5353
+ cancelInteraction: (interactionId, input) => interactionTerminal(interactionId, "cancel", input),
5354
+ handoffInteraction: (interactionId, input) => interactionTerminal(interactionId, "handoff", input),
5355
+ ensureOperation: (conversationId, interactionId, input) => post2(
5356
+ `/conversations/${encodeURIComponent(conversationId)}/interactions/${encodeURIComponent(interactionId)}/operations:ensure`,
5357
+ input
5358
+ ),
5359
+ getOperation: (operationId) => get(`/operations/${encodeURIComponent(operationId)}`),
5360
+ retryOperationAttempt: (operationId, input) => post2(`/operations/${encodeURIComponent(operationId)}/attempts`, input),
5361
+ completeOperationAttempt: (operationId, attempt, input) => finishAttempt(operationId, attempt, "complete", input),
5362
+ failOperationAttempt: (operationId, attempt, input) => finishAttempt(operationId, attempt, "fail", input),
5363
+ getReceipt: (receiptId) => get(`/receipts/${encodeURIComponent(receiptId)}`)
5364
+ };
5365
+ }
5366
+ function assertNoForbiddenInputFields(input) {
5367
+ const pending = [input];
5368
+ const seen = /* @__PURE__ */ new Set();
5369
+ while (pending.length > 0) {
5370
+ const value = pending.pop();
5371
+ if (typeof value !== "object" || value === null || seen.has(value)) continue;
5372
+ seen.add(value);
5373
+ for (const [field, nested] of Object.entries(value)) {
5374
+ if (FORBIDDEN_INPUT_FIELDS.has(field)) {
5375
+ throw new InputError(`Lifecycle input field "${field}" is not allowed`);
5376
+ }
5377
+ pending.push(nested);
5378
+ }
5379
+ }
5380
+ }
5381
+
5382
+ // src/managed-trace.ts
5383
+ import { randomUUID as randomUUID3 } from "crypto";
5384
+ var FORBIDDEN_INPUT_FIELDS2 = ["generation", "on_behalf_of", "onBehalfOf"];
5385
+ var CompletionMissingError = class extends InputError {
5386
+ };
5387
+ var OperationFailedError = class extends InputError {
5388
+ };
5389
+ var ManagedTrace = class {
5390
+ constructor(api, options = {}) {
5391
+ this.api = api;
5392
+ this.idFactory = options.idFactory ?? randomUUID3;
5393
+ }
5394
+ api;
5395
+ idFactory;
5396
+ pendingConversations = /* @__PURE__ */ new Map();
5397
+ activeConversationIds = /* @__PURE__ */ new Set();
5398
+ async withInteraction(strategy, callback) {
5399
+ assertSafeStrategy(strategy);
5400
+ const conversation = await this.resolveConversation(strategy);
5401
+ if (this.activeConversationIds.has(conversation.conversation_id)) {
5402
+ throw new InputError(
5403
+ `Conversation "${conversation.conversation_id}" already has an active interaction`
5404
+ );
5405
+ }
5406
+ this.activeConversationIds.add(conversation.conversation_id);
5407
+ try {
5408
+ const interaction = await this.api.startInteraction(conversation.conversation_id, {
5409
+ idempotency_key: this.idFactory()
5410
+ });
5411
+ const receipts = /* @__PURE__ */ new Map();
5412
+ let terminal;
5413
+ let terminalAction;
5414
+ const terminalInput = (reason) => ({
5415
+ terminal_idempotency_key: this.idFactory(),
5416
+ lease_token: interaction.lease_token,
5417
+ lease_epoch: interaction.lease_epoch,
5418
+ completion_manifest_version: "3.0.0",
5419
+ completion_reason: reason,
5420
+ claims: [],
5421
+ expected_operations: expectedOperations(receipts.values()),
5422
+ expected_receipts: expectedReceipts(receipts.values())
5423
+ });
5424
+ const scope = {
5425
+ conversation,
5426
+ interaction,
5427
+ bknContext: (operationKey, parentOperationId, causationEventIds) => ({
5428
+ bkn_context: {
5429
+ conversation_id: conversation.conversation_id,
5430
+ interaction_id: interaction.interaction_id,
5431
+ operation_key: operationKey,
5432
+ ...parentOperationId ? { parent_operation_id: parentOperationId } : {},
5433
+ ...causationEventIds?.length ? { causation_event_ids: causationEventIds } : {}
5434
+ }
5435
+ }),
5436
+ recordReceipt: (receipt) => {
5437
+ if (receipt.conversation_id !== conversation.conversation_id || receipt.interaction_id !== interaction.interaction_id) {
5438
+ throw new InputError("Receipt does not belong to the active managed interaction");
5439
+ }
5440
+ receipts.set(receipt.receipt_id, receipt);
5441
+ },
5442
+ supportCandidates: () => [...receipts.values()].flatMap(
5443
+ (receipt) => receipt.observed_evidence_refs.map((ref) => ({
5444
+ ref,
5445
+ state: "observed",
5446
+ adopted: false
5447
+ }))
5448
+ ),
5449
+ runOperation: async (input, execute) => this.runOperation(
5450
+ conversation,
5451
+ interaction,
5452
+ input,
5453
+ execute,
5454
+ scope.bknContext,
5455
+ scope.recordReceipt
5456
+ ),
5457
+ cancel: async (reason) => {
5458
+ terminalAction ??= this.api.cancelInteraction(
5459
+ interaction.interaction_id,
5460
+ terminalInput(reason)
5461
+ );
5462
+ terminal = await terminalAction;
5463
+ return terminal;
5464
+ },
5465
+ handoff: async (reason) => {
5466
+ terminalAction ??= this.api.handoffInteraction(
5467
+ interaction.interaction_id,
5468
+ terminalInput(reason)
5469
+ );
5470
+ terminal = await terminalAction;
5471
+ return terminal;
5472
+ }
5473
+ };
5474
+ let completion;
5475
+ try {
5476
+ completion = await callback(scope);
5477
+ if (terminalAction) return await terminalAction;
5478
+ if (terminal) return terminal;
5479
+ if (!completion || typeof completion !== "object") {
5480
+ throw new CompletionMissingError(
5481
+ "Interaction callback must return a completion manifest"
5482
+ );
5483
+ }
5484
+ } catch (error) {
5485
+ if (!terminalAction && !terminal) {
5486
+ try {
5487
+ terminal = await this.api.failInteraction(
5488
+ interaction.interaction_id,
5489
+ terminalInput(
5490
+ error instanceof CompletionMissingError ? "completion_missing" : "callback_failed"
5491
+ )
5492
+ );
5493
+ } catch {
5494
+ }
5495
+ }
5496
+ throw error;
5497
+ }
5498
+ const completionInput = {
5499
+ ...completion,
5500
+ terminal_idempotency_key: this.idFactory(),
5501
+ lease_token: interaction.lease_token,
5502
+ lease_epoch: interaction.lease_epoch,
5503
+ expected_operations: completion.expected_operations ?? expectedOperations(receipts.values()),
5504
+ expected_receipts: completion.expected_receipts ?? expectedReceipts(receipts.values())
5505
+ };
5506
+ try {
5507
+ return await this.api.completeInteraction(interaction.interaction_id, completionInput);
5508
+ } catch (completeError) {
5509
+ let current;
5510
+ try {
5511
+ current = await this.api.getInteraction(interaction.interaction_id);
5512
+ } catch {
5513
+ throw completeError;
5514
+ }
5515
+ if (current.execution_status === "completed") return current;
5516
+ if (current.execution_status !== "active") {
5517
+ throw new InputError(
5518
+ `Interaction terminal state "${current.execution_status}" conflicts with complete`
5519
+ );
5520
+ }
5521
+ return await this.api.completeInteraction(interaction.interaction_id, completionInput);
5522
+ }
5523
+ } finally {
5524
+ this.activeConversationIds.delete(conversation.conversation_id);
5525
+ }
5526
+ }
5527
+ async resolveConversation(strategy) {
5528
+ switch (strategy.mode) {
5529
+ case "resume_by_id":
5530
+ return await this.api.resumeConversation({ conversation_id: strategy.conversationId });
5531
+ case "create_new_generation":
5532
+ return await this.api.createNewConversationGeneration({
5533
+ external_conversation_key: strategy.externalConversationKey,
5534
+ idempotency_key: this.idFactory()
5535
+ });
5536
+ case "one_shot":
5537
+ return await this.api.ensureConversation({
5538
+ external_conversation_key: strategy.externalConversationKey ?? `one-shot-${this.idFactory()}`,
5539
+ idempotency_key: this.idFactory(),
5540
+ one_shot: true
5541
+ });
5542
+ case "ensure_current":
5543
+ return await this.ensureCurrent(strategy.externalConversationKey);
5544
+ }
5545
+ }
5546
+ async ensureCurrent(externalConversationKey) {
5547
+ const pending = this.pendingConversations.get(externalConversationKey);
5548
+ if (pending) return await pending;
5549
+ const request2 = this.api.ensureConversation({
5550
+ external_conversation_key: externalConversationKey,
5551
+ idempotency_key: this.idFactory()
5552
+ });
5553
+ this.pendingConversations.set(externalConversationKey, request2);
5554
+ try {
5555
+ return await request2;
5556
+ } finally {
5557
+ if (this.pendingConversations.get(externalConversationKey) === request2) {
5558
+ this.pendingConversations.delete(externalConversationKey);
5559
+ }
5560
+ }
5561
+ }
5562
+ async runOperation(conversation, interaction, input, execute, bknContext, recordReceipt) {
5563
+ const operationKey = input.operationKey ?? this.idFactory();
5564
+ let current = await this.api.ensureOperation(
5565
+ conversation.conversation_id,
5566
+ interaction.interaction_id,
5567
+ {
5568
+ operation_key: operationKey,
5569
+ tool_name: input.toolName,
5570
+ normalized_input_hash: input.normalizedInputHash,
5571
+ parent_operation_id: input.parentOperationId,
5572
+ causation_event_ids: input.causationEventIds,
5573
+ required: input.required ?? true,
5574
+ lease_token: interaction.lease_token,
5575
+ lease_epoch: interaction.lease_epoch
5576
+ }
5577
+ );
5578
+ const maxAttempts = input.maxAttempts ?? 2;
5579
+ if (!Number.isInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 10) {
5580
+ throw new InputError("maxAttempts must be an integer between 1 and 10");
5581
+ }
5582
+ while (true) {
5583
+ if (isCompletedReceipt(current.receipt)) {
5584
+ recordReceipt(current.receipt);
5585
+ return { value: void 0, receipt: current.receipt, recovered: true };
5586
+ }
5587
+ if (isFailedReceipt(current.receipt)) {
5588
+ recordReceipt(current.receipt);
5589
+ if (!current.operation.retryable) {
5590
+ throw new OperationFailedError(
5591
+ `Operation "${current.operation.operation_id}" failed and is not retryable`
5592
+ );
5593
+ }
5594
+ if (current.operation.attempt >= maxAttempts) {
5595
+ throw new OperationFailedError(
5596
+ `Operation "${current.operation.operation_id}" reached maximum attempt count ${maxAttempts}`
5597
+ );
5598
+ }
5599
+ current = await this.api.retryOperationAttempt(current.operation.operation_id, {
5600
+ lease_token: interaction.lease_token,
5601
+ lease_epoch: interaction.lease_epoch
5602
+ });
5603
+ continue;
5604
+ }
5605
+ let result;
5606
+ try {
5607
+ result = await execute({
5608
+ context: bknContext(operationKey, input.parentOperationId, input.causationEventIds),
5609
+ operation: current.operation,
5610
+ receipt: current.receipt
5611
+ });
5612
+ } catch (executeError) {
5613
+ const recovered = await this.api.getReceipt(current.receipt.receipt_id);
5614
+ if (!isTerminalReceipt(recovered)) throw executeError;
5615
+ assertSameReceipt(current.receipt, recovered);
5616
+ current = {
5617
+ ...current,
5618
+ operation: isFailedReceipt(recovered) ? await this.api.getOperation(current.operation.operation_id) : current.operation,
5619
+ receipt: recovered
5620
+ };
5621
+ continue;
5622
+ }
5623
+ assertSameReceipt(current.receipt, result.receipt);
5624
+ recordReceipt(result.receipt);
5625
+ if (isCompletedReceipt(result.receipt)) {
5626
+ return { ...result, recovered: false };
5627
+ }
5628
+ current = {
5629
+ ...current,
5630
+ operation: isFailedReceipt(result.receipt) ? await this.api.getOperation(current.operation.operation_id) : current.operation,
5631
+ receipt: result.receipt
5632
+ };
5633
+ if (!isTerminalReceipt(result.receipt)) {
5634
+ throw new InputError(
5635
+ `Operation "${current.operation.operation_id}" returned a pending receipt`
5636
+ );
5637
+ }
5638
+ }
5639
+ }
5640
+ };
5641
+ function assertSafeStrategy(strategy) {
5642
+ if (!strategy || typeof strategy !== "object") {
5643
+ throw new InputError("A conversation strategy is required");
5644
+ }
5645
+ for (const field of FORBIDDEN_INPUT_FIELDS2) {
5646
+ if (field in strategy) throw new InputError(`Lifecycle input field "${field}" is not allowed`);
5647
+ }
5648
+ }
5649
+ function expectedOperations(receipts) {
5650
+ const operations = /* @__PURE__ */ new Map();
5651
+ for (const receipt of receipts) {
5652
+ operations.set(
5653
+ receipt.operation_id,
5654
+ (operations.get(receipt.operation_id) ?? false) || receipt.required
5655
+ );
5656
+ }
5657
+ return [...operations].map(([operation_id, required]) => ({ operation_id, required }));
5658
+ }
5659
+ function expectedReceipts(receipts) {
5660
+ return [...receipts].map((receipt) => ({
5661
+ receipt_id: receipt.receipt_id,
5662
+ required: receipt.required
5663
+ }));
5664
+ }
5665
+ function isTerminalReceipt(receipt) {
5666
+ return receipt.receipt_status !== "pending";
5667
+ }
5668
+ function isCompletedReceipt(receipt) {
5669
+ return receipt.receipt_status === "completed";
5670
+ }
5671
+ function isFailedReceipt(receipt) {
5672
+ return receipt.receipt_status === "failed";
5673
+ }
5674
+ function assertSameReceipt(expected, actual) {
5675
+ if (actual.receipt_id !== expected.receipt_id || actual.operation_id !== expected.operation_id || actual.attempt !== expected.attempt || actual.operation_key !== expected.operation_key || actual.normalized_input_hash !== expected.normalized_input_hash) {
5676
+ throw new InputError("Recovered receipt does not match the registered operation attempt");
5677
+ }
5678
+ }
5679
+
4455
5680
  // src/api/trace.ts
4456
5681
  var SEARCH = "/api/agent-observability/v1/traces/_search";
5682
+ var BUSINESS_PROVENANCE = "/api/agent-observability/v1/business-provenance";
5683
+ var REQUESTS = `${BUSINESS_PROVENANCE}/requests`;
5684
+ var INTERACTIONS = `${BUSINESS_PROVENANCE}/interactions`;
5685
+ var TRACES = "/api/agent-observability/v1/traces";
4457
5686
  function isoToNanos(iso) {
4458
5687
  const ms = Date.parse(iso);
4459
5688
  if (Number.isNaN(ms)) return void 0;
@@ -4495,6 +5724,9 @@ async function getRawSpansByConversation(ctx, conversationId, opts = {}) {
4495
5724
  function traceSearch(ctx, body) {
4496
5725
  return request(ctx, SEARCH, { method: "POST", body });
4497
5726
  }
5727
+ function getTraceGraph(ctx, traceId) {
5728
+ return request(ctx, `${TRACES}/${encodeURIComponent(traceId)}/trace-graph`);
5729
+ }
4498
5730
  async function getSpansByConversation(ctx, conversationId, opts = {}) {
4499
5731
  const agg = await request(ctx, SEARCH, {
4500
5732
  method: "POST",
@@ -4580,10 +5812,10 @@ async function judgeJson(prompt, opts = {}) {
4580
5812
  child.stdout.on("data", (d) => {
4581
5813
  out += d;
4582
5814
  });
4583
- child.on("error", (err) => {
5815
+ child.on("error", (err2) => {
4584
5816
  clearTimeout(killer);
4585
5817
  reject(
4586
- err.code === "ENOENT" ? new ClaudeJudgeError("`claude` not found on PATH", "not_available") : err
5818
+ err2.code === "ENOENT" ? new ClaudeJudgeError("`claude` not found on PATH", "not_available") : err2
4587
5819
  );
4588
5820
  });
4589
5821
  child.on("close", (code) => {
@@ -4593,8 +5825,8 @@ async function judgeJson(prompt, opts = {}) {
4593
5825
  if (code !== 0) return reject(new ClaudeJudgeError(`claude exited ${code}`, "exit"));
4594
5826
  resolve7(out);
4595
5827
  });
4596
- child.stdin.on("error", (err) => {
4597
- if (err.code !== "EPIPE") reject(err);
5828
+ child.stdin.on("error", (err2) => {
5829
+ if (err2.code !== "EPIPE") reject(err2);
4598
5830
  });
4599
5831
  child.stdin.end(prompt);
4600
5832
  });
@@ -5144,6 +6376,747 @@ async function runEvalSet(agentId, cases, deps) {
5144
6376
  };
5145
6377
  }
5146
6378
 
6379
+ // src/bkn-trace/fixture-validate.ts
6380
+ import { readFileSync as readFileSync4, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
6381
+ import { join as join4 } from "path";
6382
+ var CONTRACT_VERSIONS = /* @__PURE__ */ new Set(["1.0.0", "2.0.0", "2.1.0"]);
6383
+ var BUSINESS_CONTRACT_VERSION = "2.1.0";
6384
+ var TRACEPARENT_RE2 = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/;
6385
+ var REQUEST_ID_RE2 = /^req_[0-9A-Za-z_.-]+$/;
6386
+ var RFC3339_NANO_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/;
6387
+ var ALLOWED_BAGGAGE2 = /* @__PURE__ */ new Set(["bkn.account.type", "bkn.runtime.env"]);
6388
+ var REQUIRED_LOG_FIELDS = [
6389
+ "trace_id",
6390
+ "span_id",
6391
+ "bkn.request.id",
6392
+ "bkn.module.name",
6393
+ "bkn.operation.name",
6394
+ "bkn.status",
6395
+ "bkn.timestamp",
6396
+ "bkn.trace.schema.version"
6397
+ ];
6398
+ var REQUIRED_SPAN_FIELDS = [
6399
+ "span_id",
6400
+ "name",
6401
+ "bkn.module.name",
6402
+ "bkn.operation.name",
6403
+ "bkn.status",
6404
+ "bkn.timestamp"
6405
+ ];
6406
+ var REQUIRED_EVENT_FIELDS = [
6407
+ "trace_id",
6408
+ "span_id",
6409
+ "bkn.request.id",
6410
+ "bkn.operation.name",
6411
+ "event_id",
6412
+ "event_type",
6413
+ "bkn.trace.schema.version",
6414
+ "observed_at",
6415
+ "emitted_at",
6416
+ "producer_module",
6417
+ "payload"
6418
+ ];
6419
+ var SENSITIVE_PATTERNS = [
6420
+ /authorization/i,
6421
+ /bearer\s+[A-Za-z0-9._-]+/i,
6422
+ /access[_-]?token/i,
6423
+ /api[_-]?key/i,
6424
+ /cookie/i,
6425
+ /\bselect\s+.+\s+from\b/is,
6426
+ /prompt\s*[:=]/i,
6427
+ /https?:\/\/[^\s"']+/i,
6428
+ /[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}/
6429
+ ];
6430
+ var BUSINESS_EVENT_TYPES = /* @__PURE__ */ new Set([
6431
+ "agent.interaction.started",
6432
+ "retrieval.completed",
6433
+ "knowledge.read.observed",
6434
+ "data.query.observed",
6435
+ "model.call.observed",
6436
+ "tool.called",
6437
+ "tool.result.observed",
6438
+ "claim.created",
6439
+ "evidence.refs.created",
6440
+ "business.refs.resolved",
6441
+ "action.recommended",
6442
+ "action.approval_requested",
6443
+ "action.approved",
6444
+ "action.rejected",
6445
+ "action.executed",
6446
+ "action.result_recorded"
6447
+ ]);
6448
+ var CLAIM_EVENT_TYPES = /* @__PURE__ */ new Set([
6449
+ "claim.created",
6450
+ "evidence.refs.created",
6451
+ "business.refs.resolved",
6452
+ "action.recommended",
6453
+ "action.approval_requested",
6454
+ "action.approved",
6455
+ "action.rejected",
6456
+ "action.executed",
6457
+ "action.result_recorded"
6458
+ ]);
6459
+ var ACTION_TRANSITIONS = {
6460
+ recommended: /* @__PURE__ */ new Set(["approval_requested"]),
6461
+ approval_requested: /* @__PURE__ */ new Set(["approved", "rejected"]),
6462
+ approved: /* @__PURE__ */ new Set(["executed"]),
6463
+ executed: /* @__PURE__ */ new Set(["result_recorded"]),
6464
+ rejected: /* @__PURE__ */ new Set(),
6465
+ result_recorded: /* @__PURE__ */ new Set()
6466
+ };
6467
+ var ACTION_STATE_BY_EVENT = {
6468
+ "action.recommended": "recommended",
6469
+ "action.approval_requested": "approval_requested",
6470
+ "action.approved": "approved",
6471
+ "action.rejected": "rejected",
6472
+ "action.executed": "executed",
6473
+ "action.result_recorded": "result_recorded"
6474
+ };
6475
+ var EVENT_PAYLOAD_FIELDS = {
6476
+ "agent.interaction.started": /* @__PURE__ */ new Set(["intent_hash", "mode", "agent_id", "app_ref"]),
6477
+ "retrieval.completed": /* @__PURE__ */ new Set([
6478
+ "query_hash",
6479
+ "candidate_count",
6480
+ "truncated",
6481
+ "version_status",
6482
+ "source_refs"
6483
+ ]),
6484
+ "knowledge.read.observed": /* @__PURE__ */ new Set([
6485
+ "kn_id",
6486
+ "read_kind",
6487
+ "version_status",
6488
+ "schema_version",
6489
+ "business_refs"
6490
+ ]),
6491
+ "data.query.observed": /* @__PURE__ */ new Set([
6492
+ "query_hash",
6493
+ "query_type",
6494
+ "row_count",
6495
+ "truncated",
6496
+ "as_of",
6497
+ "version_status",
6498
+ "resource_refs",
6499
+ "field_refs"
6500
+ ]),
6501
+ "model.call.observed": /* @__PURE__ */ new Set([
6502
+ "model_name",
6503
+ "model_provider",
6504
+ "status",
6505
+ "input_token_count",
6506
+ "output_token_count",
6507
+ "prompt_hash",
6508
+ "output_hash",
6509
+ "error_category",
6510
+ "error_hash"
6511
+ ]),
6512
+ "tool.called": /* @__PURE__ */ new Set(["tool_id", "tool_name", "args_hash", "visibility", "version_status"]),
6513
+ "tool.result.observed": /* @__PURE__ */ new Set([
6514
+ "tool_id",
6515
+ "tool_name",
6516
+ "status",
6517
+ "result_hash",
6518
+ "result_length",
6519
+ "result_count",
6520
+ "error_hash",
6521
+ "error_category",
6522
+ "visibility",
6523
+ "version_status"
6524
+ ]),
6525
+ "claim.created": /* @__PURE__ */ new Set([
6526
+ "claim_id",
6527
+ "claim_type",
6528
+ "claim_hash",
6529
+ "source_event_ids",
6530
+ "operation_ids",
6531
+ "visibility",
6532
+ "version_status"
6533
+ ]),
6534
+ "evidence.refs.created": /* @__PURE__ */ new Set(["claim_id", "evidence_refs"]),
6535
+ "business.refs.resolved": /* @__PURE__ */ new Set(["claim_id", "resolver_status", "business_refs"]),
6536
+ "action.recommended": /* @__PURE__ */ new Set([
6537
+ "action_instance_id",
6538
+ "action_type",
6539
+ "target_refs",
6540
+ "reason_hash",
6541
+ "status"
6542
+ ]),
6543
+ "action.approval_requested": /* @__PURE__ */ new Set(["action_instance_id", "policy_ref", "status"]),
6544
+ "action.approved": /* @__PURE__ */ new Set(["action_instance_id", "actor_ref", "policy_decision_ref", "status"]),
6545
+ "action.rejected": /* @__PURE__ */ new Set(["action_instance_id", "actor_ref", "policy_decision_ref", "status"]),
6546
+ "action.executed": /* @__PURE__ */ new Set([
6547
+ "action_instance_id",
6548
+ "invocation_ref",
6549
+ "tool_ref",
6550
+ "status",
6551
+ "error_category",
6552
+ "error_hash"
6553
+ ]),
6554
+ "action.result_recorded": /* @__PURE__ */ new Set([
6555
+ "action_instance_id",
6556
+ "result_hash",
6557
+ "artifact_ref",
6558
+ "task_ref",
6559
+ "status"
6560
+ ])
6561
+ };
6562
+ var REFERENCE_FIELDS = /* @__PURE__ */ new Set([
6563
+ "ref_id",
6564
+ "ref_type",
6565
+ "source_system",
6566
+ "validity",
6567
+ "version_status",
6568
+ "visibility",
6569
+ "summary_hash"
6570
+ ]);
6571
+ var FORBIDDEN_RAW_KEYS = /* @__PURE__ */ new Set([
6572
+ "authorization",
6573
+ "cookie",
6574
+ "access_token",
6575
+ "refresh_token",
6576
+ "id_token",
6577
+ "api_key",
6578
+ "password",
6579
+ "private_key",
6580
+ "prompt",
6581
+ "user_question",
6582
+ "approval_comment",
6583
+ "sql",
6584
+ "query_params",
6585
+ "rows"
6586
+ ]);
6587
+ function err(code, path, message) {
6588
+ return { code, path, message };
6589
+ }
6590
+ function asRecord(value) {
6591
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
6592
+ }
6593
+ function jsonFiles(path) {
6594
+ const stat = statSync4(path);
6595
+ if (stat.isFile()) return [path];
6596
+ return readdirSync5(path).filter((name) => name.endsWith(".json")).sort().map((name) => join4(path, name));
6597
+ }
6598
+ function validTraceparent(value) {
6599
+ if (typeof value !== "string") return false;
6600
+ const match = TRACEPARENT_RE2.exec(value);
6601
+ if (!match) return false;
6602
+ const [, traceId, spanId] = match;
6603
+ return traceId !== "0".repeat(32) && spanId !== "0".repeat(16);
6604
+ }
6605
+ function checkRequired(item, fields, basePath, errors) {
6606
+ for (const field of fields) {
6607
+ if (item[field] === void 0 || item[field] === "") {
6608
+ errors.push(
6609
+ err(
6610
+ "BKN_TRACE_REQUIRED_FIELD_MISSING",
6611
+ `${basePath}.${field}`,
6612
+ `missing required field ${field}`
6613
+ )
6614
+ );
6615
+ }
6616
+ }
6617
+ }
6618
+ function checkTimestamp(value, path, errors) {
6619
+ if (typeof value !== "string" || !RFC3339_NANO_RE.test(value)) {
6620
+ errors.push(err("BKN_TRACE_INVALID_TIMESTAMP", path, "timestamp must be UTC RFC3339Nano"));
6621
+ }
6622
+ }
6623
+ function checkSensitive(value, path, errors) {
6624
+ if (Array.isArray(value)) {
6625
+ value.forEach((child, index) => checkSensitive(child, `${path}[${index}]`, errors));
6626
+ return;
6627
+ }
6628
+ if (value && typeof value === "object") {
6629
+ for (const [key, child] of Object.entries(value)) {
6630
+ if (FORBIDDEN_RAW_KEYS.has(key.toLowerCase())) {
6631
+ errors.push(
6632
+ err(
6633
+ "BKN_TRACE_SENSITIVE_VALUE_LEAKED",
6634
+ `${path}.${key}`,
6635
+ "raw sensitive field is forbidden"
6636
+ )
6637
+ );
6638
+ }
6639
+ if (key.endsWith("_hash") && child !== "" && (typeof child !== "string" || !/^sha256:[0-9a-f]{64}$/.test(child))) {
6640
+ errors.push(
6641
+ err("BKN_TRACE_REQUIRED_FIELD_MISSING", `${path}.${key}`, `${key} must be a sha256 hash`)
6642
+ );
6643
+ }
6644
+ checkSensitive(child, `${path}.${key}`, errors);
6645
+ }
6646
+ return;
6647
+ }
6648
+ if (typeof value !== "string") return;
6649
+ if (SENSITIVE_PATTERNS.some((pattern) => pattern.test(value))) {
6650
+ errors.push(
6651
+ err(
6652
+ "BKN_TRACE_SENSITIVE_VALUE_LEAKED",
6653
+ path,
6654
+ "sensitive value must be redacted, hashed, or referenced"
6655
+ )
6656
+ );
6657
+ }
6658
+ }
6659
+ function validateFixture(data) {
6660
+ const root = asRecord(data);
6661
+ const errors = [];
6662
+ const fixtureId = typeof root.fixture_id === "string" ? root.fixture_id : "<unknown>";
6663
+ const contractVersion = typeof root["bkn.trace.schema.version"] === "string" ? root["bkn.trace.schema.version"] : null;
6664
+ if (!contractVersion) {
6665
+ errors.push(
6666
+ err(
6667
+ "BKN_TRACE_SCHEMA_VERSION_MISSING",
6668
+ "$.bkn.trace.schema.version",
6669
+ "missing contract version"
6670
+ )
6671
+ );
6672
+ } else if (!CONTRACT_VERSIONS.has(contractVersion)) {
6673
+ errors.push(
6674
+ err(
6675
+ "BKN_TRACE_SCHEMA_VERSION_UNSUPPORTED",
6676
+ "$.bkn.trace.schema.version",
6677
+ `unsupported contract version ${contractVersion}`
6678
+ )
6679
+ );
6680
+ }
6681
+ const trace2 = asRecord(root.trace);
6682
+ const traceId = trace2.trace_id;
6683
+ const requestId = trace2["bkn.request.id"];
6684
+ if (typeof traceId !== "string" || !/^[0-9a-f]{32}$/.test(traceId)) {
6685
+ errors.push(
6686
+ err("BKN_TRACE_REQUIRED_FIELD_MISSING", "$.trace.trace_id", "missing valid trace id")
6687
+ );
6688
+ }
6689
+ if (typeof requestId !== "string" || !REQUEST_ID_RE2.test(requestId)) {
6690
+ errors.push(
6691
+ err(
6692
+ "BKN_TRACE_REQUIRED_FIELD_MISSING",
6693
+ "$.trace.bkn.request.id",
6694
+ "missing valid bkn.request.id"
6695
+ )
6696
+ );
6697
+ }
6698
+ if (!validTraceparent(trace2.traceparent)) {
6699
+ errors.push(err("BKN_TRACE_INVALID_TRACEPARENT", "$.trace.traceparent", "invalid traceparent"));
6700
+ }
6701
+ const spans = Array.isArray(root.spans) ? root.spans : [];
6702
+ const spanIds = /* @__PURE__ */ new Set();
6703
+ spans.forEach((item, index) => {
6704
+ const span = asRecord(item);
6705
+ checkRequired(span, REQUIRED_SPAN_FIELDS, `$.spans[${index}]`, errors);
6706
+ checkTimestamp(span["bkn.timestamp"], `$.spans[${index}].bkn.timestamp`, errors);
6707
+ if (typeof span.span_id === "string") spanIds.add(span.span_id);
6708
+ const parent = span.parent_span_id;
6709
+ if (parent !== null && parent !== void 0 && !spanIds.has(String(parent))) {
6710
+ errors.push(
6711
+ err(
6712
+ "BKN_TRACE_ORPHAN_SPAN",
6713
+ `$.spans[${index}].parent_span_id`,
6714
+ "parent span must appear before child span or be represented as a link"
6715
+ )
6716
+ );
6717
+ }
6718
+ });
6719
+ if (spans.length === 0) {
6720
+ errors.push(err("BKN_TRACE_REQUIRED_FIELD_MISSING", "$.spans", "at least one span required"));
6721
+ }
6722
+ const logs = Array.isArray(root.logs) ? root.logs : [];
6723
+ logs.forEach((item, index) => {
6724
+ const log = asRecord(item);
6725
+ checkRequired(log, REQUIRED_LOG_FIELDS, `$.logs[${index}]`, errors);
6726
+ checkTimestamp(log["bkn.timestamp"], `$.logs[${index}].bkn.timestamp`, errors);
6727
+ if (log.trace_id !== traceId || log["bkn.request.id"] !== requestId) {
6728
+ errors.push(
6729
+ err("BKN_TRACE_JOIN_FAILED", `$.logs[${index}]`, "log cannot join trace/request")
6730
+ );
6731
+ }
6732
+ if (!spanIds.has(String(log.span_id))) {
6733
+ errors.push(
6734
+ err("BKN_TRACE_JOIN_FAILED", `$.logs[${index}].span_id`, "log span_id not found")
6735
+ );
6736
+ }
6737
+ });
6738
+ const events = Array.isArray(root.events) ? root.events : [];
6739
+ const eventIds = /* @__PURE__ */ new Set();
6740
+ const knownEventIds = /* @__PURE__ */ new Set();
6741
+ const knownOperationIds = /* @__PURE__ */ new Set();
6742
+ const knownClaimIds = /* @__PURE__ */ new Set();
6743
+ const actionStates = /* @__PURE__ */ new Map();
6744
+ events.forEach((item, index) => {
6745
+ const event = asRecord(item);
6746
+ const eventPath = `$.events[${index}]`;
6747
+ checkRequired(event, REQUIRED_EVENT_FIELDS, `$.events[${index}]`, errors);
6748
+ checkTimestamp(event.observed_at, `$.events[${index}].observed_at`, errors);
6749
+ checkTimestamp(event.emitted_at, `$.events[${index}].emitted_at`, errors);
6750
+ if (event.trace_id !== traceId || event["bkn.request.id"] !== requestId) {
6751
+ errors.push(
6752
+ err("BKN_TRACE_JOIN_FAILED", `$.events[${index}]`, "event cannot join trace/request")
6753
+ );
6754
+ }
6755
+ if (!spanIds.has(String(event.span_id))) {
6756
+ errors.push(
6757
+ err("BKN_TRACE_JOIN_FAILED", `$.events[${index}].span_id`, "event span_id not found")
6758
+ );
6759
+ }
6760
+ if (typeof event.event_id === "string") {
6761
+ if (eventIds.has(event.event_id)) {
6762
+ errors.push(
6763
+ err("BKN_TRACE_EVENT_ID_CONFLICT", `${eventPath}.event_id`, "duplicate event_id")
6764
+ );
6765
+ }
6766
+ eventIds.add(event.event_id);
6767
+ }
6768
+ if (contractVersion !== BUSINESS_CONTRACT_VERSION) return;
6769
+ if (event["bkn.trace.schema.version"] !== contractVersion) {
6770
+ errors.push(
6771
+ err(
6772
+ "BKN_TRACE_SCHEMA_VERSION_UNSUPPORTED",
6773
+ `${eventPath}.bkn.trace.schema.version`,
6774
+ "event contract version must match the fixture envelope"
6775
+ )
6776
+ );
6777
+ }
6778
+ validateBusinessEvent(
6779
+ event,
6780
+ eventPath,
6781
+ knownEventIds,
6782
+ knownOperationIds,
6783
+ knownClaimIds,
6784
+ actionStates,
6785
+ errors
6786
+ );
6787
+ if (typeof event.event_id === "string") knownEventIds.add(event.event_id);
6788
+ if (typeof event.operation_id === "string") knownOperationIds.add(event.operation_id);
6789
+ if (event.event_type === "claim.created" && typeof event.claim_id === "string") {
6790
+ knownClaimIds.add(event.claim_id);
6791
+ }
6792
+ });
6793
+ if (contractVersion !== "1.0.0" && events.length === 0) {
6794
+ errors.push(err("BKN_TRACE_REQUIRED_FIELD_MISSING", "$.events", "at least one event required"));
6795
+ }
6796
+ const baggage = asRecord(root.baggage);
6797
+ for (const key of Object.keys(baggage)) {
6798
+ if (!ALLOWED_BAGGAGE2.has(key)) {
6799
+ errors.push(
6800
+ err(
6801
+ "BKN_TRACE_BAGGAGE_FORBIDDEN_FIELD",
6802
+ `$.baggage.${key}`,
6803
+ `baggage field ${key} is forbidden`
6804
+ )
6805
+ );
6806
+ }
6807
+ }
6808
+ checkSensitive(root, "$", errors);
6809
+ const result = errors.length > 0 ? "fail" : "pass";
6810
+ const expectedResult = root.expected_result === "pass" || root.expected_result === "fail" ? root.expected_result : null;
6811
+ return {
6812
+ fixtureId,
6813
+ result,
6814
+ contractVersion,
6815
+ errors,
6816
+ warnings: [],
6817
+ expectedResult,
6818
+ expectationMatched: expectedResult === null ? result === "pass" : expectedResult === result
6819
+ };
6820
+ }
6821
+ function validateBusinessEvent(event, path, knownEventIds, knownOperationIds, knownClaimIds, actionStates, errors) {
6822
+ const eventType = typeof event.event_type === "string" ? event.event_type : "";
6823
+ if (!BUSINESS_EVENT_TYPES.has(eventType)) {
6824
+ errors.push(
6825
+ err(
6826
+ "BKN_TRACE_EVENT_TYPE_UNSUPPORTED",
6827
+ `${path}.event_type`,
6828
+ `unsupported event ${eventType}`
6829
+ )
6830
+ );
6831
+ return;
6832
+ }
6833
+ checkRequired(event, ["interaction_id"], path, errors);
6834
+ if (eventType !== "agent.interaction.started" && eventType !== "claim.created") {
6835
+ checkRequired(event, ["operation_id"], path, errors);
6836
+ }
6837
+ if (eventType !== "agent.interaction.started") {
6838
+ checkRequired(event, ["causation_event_id"], path, errors);
6839
+ if (typeof event.causation_event_id === "string" && !knownEventIds.has(event.causation_event_id)) {
6840
+ errors.push(
6841
+ err(
6842
+ "BKN_TRACE_CAUSATION_INVALID",
6843
+ `${path}.causation_event_id`,
6844
+ "causation_event_id must reference an earlier event"
6845
+ )
6846
+ );
6847
+ }
6848
+ }
6849
+ if (CLAIM_EVENT_TYPES.has(eventType)) {
6850
+ checkRequired(event, ["claim_id"], path, errors);
6851
+ if (eventType !== "claim.created" && typeof event.claim_id === "string" && !knownClaimIds.has(event.claim_id)) {
6852
+ errors.push(
6853
+ err(
6854
+ "BKN_TRACE_UNKNOWN_CLAIM_ID",
6855
+ `${path}.claim_id`,
6856
+ "event must reference an earlier claim"
6857
+ )
6858
+ );
6859
+ }
6860
+ }
6861
+ const payload = asRecord(event.payload);
6862
+ checkAllowedKeys(
6863
+ payload,
6864
+ EVENT_PAYLOAD_FIELDS[eventType] ?? /* @__PURE__ */ new Set(),
6865
+ `${path}.payload`,
6866
+ errors
6867
+ );
6868
+ if (eventType === "agent.interaction.started") {
6869
+ checkRequired(payload, ["intent_hash", "mode"], `${path}.payload`, errors);
6870
+ checkOneOf(payload, ["agent_id", "app_ref"], `${path}.payload`, errors);
6871
+ }
6872
+ if (eventType === "retrieval.completed") {
6873
+ checkRequired(
6874
+ payload,
6875
+ ["query_hash", "candidate_count", "truncated"],
6876
+ `${path}.payload`,
6877
+ errors
6878
+ );
6879
+ }
6880
+ if (eventType === "knowledge.read.observed") {
6881
+ checkRequired(payload, ["kn_id", "read_kind", "version_status"], `${path}.payload`, errors);
6882
+ }
6883
+ if (eventType === "data.query.observed") {
6884
+ checkRequired(payload, ["query_hash", "query_type", "row_count"], `${path}.payload`, errors);
6885
+ }
6886
+ if (eventType === "model.call.observed") {
6887
+ checkRequired(
6888
+ payload,
6889
+ [
6890
+ "model_name",
6891
+ "model_provider",
6892
+ "status",
6893
+ "input_token_count",
6894
+ "output_token_count",
6895
+ "prompt_hash",
6896
+ "output_hash"
6897
+ ],
6898
+ `${path}.payload`,
6899
+ errors
6900
+ );
6901
+ if (payload.status === "error") {
6902
+ checkRequired(payload, ["error_category", "error_hash"], `${path}.payload`, errors);
6903
+ }
6904
+ }
6905
+ if (eventType === "claim.created") {
6906
+ checkRequired(
6907
+ payload,
6908
+ [
6909
+ "claim_id",
6910
+ "claim_type",
6911
+ "claim_hash",
6912
+ "source_event_ids",
6913
+ "operation_ids",
6914
+ "visibility",
6915
+ "version_status"
6916
+ ],
6917
+ `${path}.payload`,
6918
+ errors
6919
+ );
6920
+ checkNonEmptyArray(payload, "source_event_ids", `${path}.payload`, errors);
6921
+ checkNonEmptyArray(payload, "operation_ids", `${path}.payload`, errors);
6922
+ checkKnownArray(payload, "source_event_ids", knownEventIds, `${path}.payload`, errors);
6923
+ checkKnownArray(payload, "operation_ids", knownOperationIds, `${path}.payload`, errors);
6924
+ }
6925
+ if (eventType === "evidence.refs.created") {
6926
+ checkReferenceList(payload, "evidence_refs", `${path}.payload`, errors);
6927
+ }
6928
+ if (eventType === "business.refs.resolved") {
6929
+ checkRequired(payload, ["resolver_status"], `${path}.payload`, errors);
6930
+ checkReferenceList(
6931
+ payload,
6932
+ "business_refs",
6933
+ `${path}.payload`,
6934
+ errors,
6935
+ payload.resolver_status === "unresolved"
6936
+ );
6937
+ }
6938
+ const actionState = ACTION_STATE_BY_EVENT[eventType];
6939
+ if (!actionState) return;
6940
+ checkRequired(payload, ["action_instance_id", "status"], `${path}.payload`, errors);
6941
+ const fixedStatus = {
6942
+ "action.recommended": "recommended",
6943
+ "action.approval_requested": "approval_requested",
6944
+ "action.approved": "approved",
6945
+ "action.rejected": "rejected"
6946
+ };
6947
+ if (fixedStatus[eventType] && payload.status !== fixedStatus[eventType]) {
6948
+ errors.push(
6949
+ err(
6950
+ "BKN_TRACE_ACTION_TRANSITION_INVALID",
6951
+ `${path}.payload.status`,
6952
+ `${eventType} requires status=${fixedStatus[eventType]}`
6953
+ )
6954
+ );
6955
+ }
6956
+ if (eventType === "action.recommended") {
6957
+ checkRequired(
6958
+ payload,
6959
+ ["action_type", "target_refs", "reason_hash"],
6960
+ `${path}.payload`,
6961
+ errors
6962
+ );
6963
+ checkNonEmptyArray(payload, "target_refs", `${path}.payload`, errors);
6964
+ checkQualifiedStringRefs(payload, "target_refs", `${path}.payload`, errors);
6965
+ }
6966
+ if (eventType === "action.approval_requested") {
6967
+ checkRequired(payload, ["policy_ref"], `${path}.payload`, errors);
6968
+ }
6969
+ if (eventType === "action.approved" || eventType === "action.rejected") {
6970
+ checkRequired(payload, ["actor_ref", "policy_decision_ref"], `${path}.payload`, errors);
6971
+ }
6972
+ if (eventType === "action.executed") {
6973
+ checkOneOf(payload, ["invocation_ref", "tool_ref"], `${path}.payload`, errors);
6974
+ if (payload.status === "error") {
6975
+ checkRequired(payload, ["error_category", "error_hash"], `${path}.payload`, errors);
6976
+ }
6977
+ }
6978
+ if (eventType === "action.result_recorded") {
6979
+ checkRequired(payload, ["result_hash"], `${path}.payload`, errors);
6980
+ checkOneOf(payload, ["artifact_ref", "task_ref"], `${path}.payload`, errors);
6981
+ }
6982
+ const actionID = typeof payload.action_instance_id === "string" ? payload.action_instance_id : "";
6983
+ if (!actionID) return;
6984
+ const claimID = typeof event.claim_id === "string" ? event.claim_id : "";
6985
+ const operationID = typeof event.operation_id === "string" ? event.operation_id : "";
6986
+ const previous = actionStates.get(actionID);
6987
+ if (!previous && actionState !== "recommended" || previous && (!ACTION_TRANSITIONS[previous.state]?.has(actionState) || event.causation_event_id !== previous.lastEventID || claimID !== previous.claimID || operationID !== previous.operationID)) {
6988
+ errors.push(
6989
+ err(
6990
+ "BKN_TRACE_ACTION_TRANSITION_INVALID",
6991
+ `${path}.event_type`,
6992
+ `invalid action transition ${previous?.state ?? "<none>"} -> ${actionState}`
6993
+ )
6994
+ );
6995
+ return;
6996
+ }
6997
+ actionStates.set(actionID, {
6998
+ state: actionState,
6999
+ claimID,
7000
+ operationID,
7001
+ lastEventID: String(event.event_id ?? "")
7002
+ });
7003
+ }
7004
+ function checkOneOf(payload, fields, path, errors) {
7005
+ if (fields.some((field) => typeof payload[field] === "string" && payload[field] !== "")) return;
7006
+ errors.push(
7007
+ err(
7008
+ "BKN_TRACE_REQUIRED_FIELD_MISSING",
7009
+ `${path}.${fields[0]}`,
7010
+ `one of ${fields.join(" or ")} is required`
7011
+ )
7012
+ );
7013
+ }
7014
+ function checkNonEmptyArray(payload, field, path, errors) {
7015
+ if (Array.isArray(payload[field]) && payload[field].length > 0) return;
7016
+ errors.push(
7017
+ err(
7018
+ "BKN_TRACE_REQUIRED_FIELD_MISSING",
7019
+ `${path}.${field}`,
7020
+ `${field} must be a non-empty array`
7021
+ )
7022
+ );
7023
+ }
7024
+ function checkReferenceList(payload, field, path, errors, allowEmpty = false) {
7025
+ if (!allowEmpty) checkNonEmptyArray(payload, field, path, errors);
7026
+ const refs = Array.isArray(payload[field]) ? payload[field] : [];
7027
+ refs.forEach((value, index) => {
7028
+ const ref = asRecord(value);
7029
+ checkRequired(
7030
+ ref,
7031
+ ["ref_id", "ref_type", "source_system", "validity", "version_status", "visibility"],
7032
+ `${path}.${field}[${index}]`,
7033
+ errors
7034
+ );
7035
+ checkAllowedKeys(ref, REFERENCE_FIELDS, `${path}.${field}[${index}]`, errors);
7036
+ if (typeof ref.ref_id === "string" && !isQualifiedReference(ref.ref_id)) {
7037
+ errors.push(
7038
+ err(
7039
+ "BKN_TRACE_REFERENCE_ID_INVALID",
7040
+ `${path}.${field}[${index}].ref_id`,
7041
+ "business reference id must include its knowledge-network or resource scope"
7042
+ )
7043
+ );
7044
+ }
7045
+ });
7046
+ }
7047
+ function checkQualifiedStringRefs(payload, field, path, errors) {
7048
+ const refs = Array.isArray(payload[field]) ? payload[field] : [];
7049
+ refs.forEach((value, index) => {
7050
+ if (typeof value !== "string" || isQualifiedReference(value)) return;
7051
+ errors.push(
7052
+ err(
7053
+ "BKN_TRACE_REFERENCE_ID_INVALID",
7054
+ `${path}.${field}[${index}]`,
7055
+ "business reference id must include its knowledge-network or resource scope"
7056
+ )
7057
+ );
7058
+ });
7059
+ }
7060
+ function isQualifiedReference(value) {
7061
+ const parts = value.trim().split(":");
7062
+ if (parts.some((part) => part.length === 0)) return false;
7063
+ if (["kn", "resource"].includes(parts[0] ?? "")) return parts.length === 2;
7064
+ if (["object", "relation", "action_type", "metric", "field"].includes(parts[0] ?? "")) {
7065
+ return parts.length === 3;
7066
+ }
7067
+ if (parts[0] === "property") return parts.length === 4;
7068
+ return true;
7069
+ }
7070
+ function checkKnownArray(payload, field, known, path, errors) {
7071
+ if (!Array.isArray(payload[field])) return;
7072
+ for (const value of payload[field]) {
7073
+ if (typeof value === "string" && known.has(value)) continue;
7074
+ errors.push(
7075
+ err(
7076
+ "BKN_TRACE_CAUSATION_INVALID",
7077
+ `${path}.${field}`,
7078
+ `${field} must reference earlier events or operations`
7079
+ )
7080
+ );
7081
+ }
7082
+ }
7083
+ function checkAllowedKeys(value, allowed, path, errors) {
7084
+ for (const key of Object.keys(value)) {
7085
+ if (allowed.has(key)) continue;
7086
+ errors.push(
7087
+ err(
7088
+ "BKN_TRACE_EVENT_PAYLOAD_FIELD_UNSUPPORTED",
7089
+ `${path}.${key}`,
7090
+ `payload field ${key} is not registered for this event`
7091
+ )
7092
+ );
7093
+ }
7094
+ }
7095
+ function validateFixturePath(path) {
7096
+ const results = jsonFiles(path).map((file) => {
7097
+ try {
7098
+ return validateFixture(JSON.parse(readFileSync4(file, "utf8")));
7099
+ } catch (e) {
7100
+ return {
7101
+ fixtureId: file,
7102
+ result: "fail",
7103
+ contractVersion: null,
7104
+ errors: [
7105
+ err(
7106
+ "BKN_TRACE_FIXTURE_PARSE_FAILED",
7107
+ "$",
7108
+ `failed to parse JSON: ${e instanceof Error ? e.message : String(e)}`
7109
+ )
7110
+ ],
7111
+ warnings: [],
7112
+ expectedResult: null,
7113
+ expectationMatched: false
7114
+ };
7115
+ }
7116
+ });
7117
+ return { ok: results.every((r) => r.expectationMatched), results };
7118
+ }
7119
+
5147
7120
  // src/resources/trace.ts
5148
7121
  async function semanticJudge(question, answer, reference) {
5149
7122
  const prompt = [
@@ -5160,6 +7133,8 @@ async function semanticJudge(question, answer, reference) {
5160
7133
  };
5161
7134
  }
5162
7135
  function trace(ctx) {
7136
+ const lifecycle = traceLifecycleApi(ctx);
7137
+ const managed = new ManagedTrace(lifecycle);
5163
7138
  const diagnoseOne = async (conversationId, opts = {}) => {
5164
7139
  const { spans, traceIds } = await getRawSpansByConversation(ctx, conversationId);
5165
7140
  if (spans.length === 0) throw new Error(`No spans found for conversation: ${conversationId}`);
@@ -5188,8 +7163,14 @@ function trace(ctx) {
5188
7163
  };
5189
7164
  };
5190
7165
  return {
7166
+ /** Low-level BKN Trace 3.0 lifecycle and durable receipt API. */
7167
+ lifecycle,
7168
+ /** Own one complete interaction lifecycle around an application callback. */
7169
+ withInteraction: managed.withInteraction.bind(managed),
5191
7170
  /** Raw trace search (OpenSearch-style body). */
5192
7171
  search: (body) => traceSearch(ctx, body),
7172
+ /** Normalized trace tree/status graph by trace id. */
7173
+ graph: (traceId) => getTraceGraph(ctx, traceId),
5193
7174
  /** All span source docs for a conversation. */
5194
7175
  spans: (conversationId, opts) => getSpansByConversation(ctx, conversationId, opts),
5195
7176
  diagnose: diagnoseOne,
@@ -5227,6 +7208,8 @@ function trace(ctx) {
5227
7208
  },
5228
7209
  /** Build eval cases from a loosely-shaped queries object/array. */
5229
7210
  evalSetBuild: (raw) => buildCasesFromQueries(raw),
7211
+ /** Validate BKN Trace phase-one fixture files or directories. */
7212
+ validateFixture: (path) => validateFixturePath(path),
5230
7213
  /**
5231
7214
  * Run an eval set against an agent: each case's query is sent to the agent,
5232
7215
  * the resulting trace is fetched, and assertions are checked. `llm` enables
@@ -5259,15 +7242,18 @@ function vega(ctx) {
5259
7242
  return {
5260
7243
  catalogs: (opts) => listCatalogs(ctx, opts),
5261
7244
  getCatalog: (id) => getCatalog(ctx, id),
5262
- createCatalog: (req) => createCatalog(ctx, req),
5263
- updateCatalog: (id, req) => updateCatalog(ctx, id, req),
7245
+ createCatalog: (req, opts) => createCatalog(ctx, req, opts),
7246
+ updateCatalog: (id, req, opts) => updateCatalog(ctx, id, req, opts),
5264
7247
  enableCatalog: (id) => enableCatalog(ctx, id),
5265
7248
  disableCatalog: (id) => disableCatalog(ctx, id),
5266
7249
  deleteCatalog: (id) => deleteCatalog(ctx, id),
7250
+ testCatalogConnectionConfig: (req) => testCatalogConnectionConfig(ctx, req),
5267
7251
  testCatalogConnection: (id) => testCatalogConnection(ctx, id),
7252
+ catalogHealthCheckSchedule: (id) => getCatalogHealthCheckSchedule(ctx, id),
7253
+ updateCatalogHealthCheckSchedule: (id, req) => updateCatalogHealthCheckSchedule(ctx, id, req),
5268
7254
  discoverCatalog: (id, wait = false) => discoverCatalog(ctx, id, wait),
5269
- catalogResources: (id, category) => listCatalogResources(ctx, id, category),
5270
- catalogHealth: (ids) => catalogHealthStatus(ctx, ids),
7255
+ catalogResources: (id, category, limit, offset) => listCatalogResources(ctx, id, category, limit, offset),
7256
+ catalogHealth: (id) => catalogHealthStatus(ctx, id),
5271
7257
  connectorTypes: () => listConnectorTypes(ctx),
5272
7258
  connectorType: (type) => getConnectorType(ctx, type),
5273
7259
  /** Run SQL / OpenSearch DSL directly against a data source. */
@@ -5300,7 +7286,7 @@ function sleep(ms) {
5300
7286
  }
5301
7287
 
5302
7288
  // src/api/call.ts
5303
- import { readFileSync as readFileSync4 } from "fs";
7289
+ import { readFileSync as readFileSync5 } from "fs";
5304
7290
  function parseHeader(raw) {
5305
7291
  const idx = raw.indexOf(":");
5306
7292
  if (idx <= 0) return null;
@@ -5329,7 +7315,7 @@ async function rawCall(ctx, path, opts = {}) {
5329
7315
  const fd = new FormData();
5330
7316
  for (const field of opts.form) {
5331
7317
  const [key, value, isFile] = parseFormField(field);
5332
- if (isFile) fd.append(key, new Blob([readFileSync4(value)]), value.split("/").pop());
7318
+ if (isFile) fd.append(key, new Blob([readFileSync5(value)]), value.split("/").pop());
5333
7319
  else fd.append(key, value);
5334
7320
  }
5335
7321
  body = fd;
@@ -5414,7 +7400,7 @@ function createClient(opts = {}) {
5414
7400
  kn: kn(ctx),
5415
7401
  resource: resources(ctx),
5416
7402
  dataflows: dataflows(ctx),
5417
- agents: agents(ctx),
7403
+ agents: agents2(ctx),
5418
7404
  context: context(ctx),
5419
7405
  models: models(ctx),
5420
7406
  skills: skills(ctx),
@@ -5452,7 +7438,7 @@ function hostOf(baseUrl) {
5452
7438
  return baseUrl;
5453
7439
  }
5454
7440
  }
5455
- function normalize(baseUrl) {
7441
+ function normalize2(baseUrl) {
5456
7442
  return baseUrl.replace(/\/+$/, "");
5457
7443
  }
5458
7444
  function usernameOf(token) {
@@ -5461,7 +7447,7 @@ function usernameOf(token) {
5461
7447
  return token.username ?? token.displayName ?? claims?.preferred_username ?? claims?.name ?? claims?.sub;
5462
7448
  }
5463
7449
  function attachToken(baseUrl, accessToken, opts = {}) {
5464
- const url = normalize(baseUrl);
7450
+ const url = normalize2(baseUrl);
5465
7451
  const token = {
5466
7452
  baseUrl: url,
5467
7453
  accessToken,
@@ -5564,7 +7550,7 @@ function listPlatforms2() {
5564
7550
  );
5565
7551
  }
5566
7552
  function use(baseUrl) {
5567
- const url = normalize(baseUrl);
7553
+ const url = normalize2(baseUrl);
5568
7554
  if (!readToken(url)) {
5569
7555
  throw new InputError(`No saved credentials for ${url}. Run \`openbkn auth login\` first.`);
5570
7556
  }
@@ -5575,10 +7561,10 @@ function logout() {
5575
7561
  return baseUrl ? deleteToken(baseUrl) : false;
5576
7562
  }
5577
7563
  function deletePlatform(baseUrl, userId) {
5578
- return deleteToken(normalize(baseUrl), userId);
7564
+ return deleteToken(normalize2(baseUrl), userId);
5579
7565
  }
5580
7566
  function switchUser(baseUrl, userOrName) {
5581
- const url = normalize(baseUrl);
7567
+ const url = normalize2(baseUrl);
5582
7568
  const users = usersOf(url);
5583
7569
  const match = users.find((u) => u.userId === userOrName) ?? users.find((u) => (u.username ?? u.displayName) === userOrName);
5584
7570
  if (!match) {
@@ -5589,7 +7575,7 @@ function switchUser(baseUrl, userOrName) {
5589
7575
  return { baseUrl: url, userId: match.userId, username: match.username ?? match.displayName };
5590
7576
  }
5591
7577
  function usersOf(baseUrl) {
5592
- const url = normalize(baseUrl);
7578
+ const url = normalize2(baseUrl);
5593
7579
  return listPlatforms().find((p) => p.baseUrl === url)?.users ?? [];
5594
7580
  }
5595
7581
  function exportCreds() {
@@ -5607,6 +7593,7 @@ function exportCreds() {
5607
7593
 
5608
7594
  export {
5609
7595
  HttpError,
7596
+ ToolError,
5610
7597
  InputError,
5611
7598
  toExitCode,
5612
7599
  formatError,
@@ -5615,6 +7602,7 @@ export {
5615
7602
  deviceLogin,
5616
7603
  credentialDeviceLogin,
5617
7604
  request,
7605
+ lifecycleHint,
5618
7606
  rawCall,
5619
7607
  DEFAULT_BUSINESS_DOMAIN,
5620
7608
  DEFAULT_LIST_LIMIT,
@@ -5628,7 +7616,8 @@ export {
5628
7616
  getUserSafe,
5629
7617
  changePasswordSafe,
5630
7618
  admin,
5631
- agents,
7619
+ agents2 as agents,
7620
+ releaseLifecycleSessions,
5632
7621
  context,
5633
7622
  dataflows,
5634
7623
  parsePkMap,
@@ -5636,9 +7625,15 @@ export {
5636
7625
  kn,
5637
7626
  models,
5638
7627
  resources,
7628
+ classifyPath,
7629
+ filesUnder,
7630
+ renderTree,
5639
7631
  skills,
5640
7632
  toolboxes,
7633
+ traceLifecycleApi,
5641
7634
  renderReportMarkdown,
7635
+ validateFixturePath,
7636
+ ManagedTrace,
5642
7637
  trace,
5643
7638
  vega,
5644
7639
  createClient,
@@ -5655,4 +7650,4 @@ export {
5655
7650
  exportCreds,
5656
7651
  auth_exports
5657
7652
  };
5658
- //# sourceMappingURL=chunk-NC6DZ2AU.js.map
7653
+ //# sourceMappingURL=chunk-PC2F54XD.js.map