@axlsdk/studio 0.13.8 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -31,22 +31,169 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var server_exports = {};
32
32
  __export(server_exports, {
33
33
  ConnectionManager: () => ConnectionManager,
34
- CostAggregator: () => CostAggregator,
34
+ EvalAggregator: () => EvalAggregator,
35
+ ExecutionAggregator: () => ExecutionAggregator,
36
+ TraceAggregator: () => TraceAggregator,
35
37
  createServer: () => createServer
36
38
  });
37
39
  module.exports = __toCommonJS(server_exports);
38
40
  var import_node_fs = require("fs");
39
41
  var import_node_path = require("path");
40
- var import_hono12 = require("hono");
42
+ var import_hono15 = require("hono");
41
43
  var import_cors = require("hono/cors");
42
44
  var import_serve_static = require("@hono/node-server/serve-static");
43
45
 
46
+ // src/server/redact.ts
47
+ var REDACTED = "[redacted]";
48
+ var SAFE_ERROR_NAMES = /* @__PURE__ */ new Set([
49
+ "QuorumNotMet",
50
+ "NoConsensus",
51
+ "TimeoutError",
52
+ "MaxTurnsError",
53
+ "BudgetExceededError",
54
+ "ToolDenied"
55
+ ]);
56
+ function redactErrorMessage(err, redact) {
57
+ const raw = err instanceof Error ? err.message : String(err);
58
+ if (!redact) return raw;
59
+ const name = err instanceof Error ? err.name : "";
60
+ return SAFE_ERROR_NAMES.has(name) ? raw : REDACTED;
61
+ }
62
+ function redactValue(value, redact) {
63
+ if (!redact) return value;
64
+ return REDACTED;
65
+ }
66
+ function redactExecutionInfo(info, redact) {
67
+ if (!redact) return info;
68
+ return {
69
+ ...info,
70
+ ...info.result !== void 0 ? { result: REDACTED } : {},
71
+ ...info.error !== void 0 ? { error: REDACTED } : {}
72
+ };
73
+ }
74
+ function redactExecutionList(infos, redact) {
75
+ if (!redact) return infos;
76
+ return infos.map((info) => redactExecutionInfo(info, redact));
77
+ }
78
+ function redactMemoryValue(value, redact) {
79
+ if (!redact) return value;
80
+ return REDACTED;
81
+ }
82
+ function redactMemoryList(entries, redact) {
83
+ if (!redact) return entries;
84
+ return entries.map((entry) => ({ key: entry.key, value: REDACTED }));
85
+ }
86
+ function redactChatMessage(msg) {
87
+ const scrubbed = {
88
+ role: msg.role,
89
+ content: REDACTED,
90
+ ...msg.name !== void 0 ? { name: msg.name } : {},
91
+ ...msg.tool_call_id !== void 0 ? { tool_call_id: msg.tool_call_id } : {},
92
+ ...msg.tool_calls !== void 0 ? {
93
+ tool_calls: msg.tool_calls.map((tc) => ({
94
+ id: tc.id,
95
+ type: tc.type,
96
+ function: {
97
+ name: tc.function.name,
98
+ arguments: REDACTED
99
+ }
100
+ }))
101
+ } : {}
102
+ // providerMetadata deliberately omitted — opaque content.
103
+ };
104
+ return scrubbed;
105
+ }
106
+ function redactSessionHistory(history, redact) {
107
+ if (!redact) return history;
108
+ return history.map(redactChatMessage);
109
+ }
110
+ function redactStreamEvent(event, redact) {
111
+ if (!redact) return event;
112
+ switch (event.type) {
113
+ case "token":
114
+ return { type: "token", data: REDACTED };
115
+ case "tool_call":
116
+ return { ...event, args: REDACTED };
117
+ case "tool_result":
118
+ return { ...event, result: REDACTED };
119
+ case "tool_approval":
120
+ return {
121
+ ...event,
122
+ args: REDACTED,
123
+ ...event.reason !== void 0 ? { reason: REDACTED } : {}
124
+ };
125
+ case "done":
126
+ return { type: "done", data: REDACTED };
127
+ case "error":
128
+ return { type: "error", message: REDACTED };
129
+ // Structural events have no user content to scrub.
130
+ case "agent_start":
131
+ case "agent_end":
132
+ case "handoff":
133
+ case "step":
134
+ return event;
135
+ }
136
+ }
137
+ function redactEvalItem(item) {
138
+ const scrubbed = {
139
+ ...item,
140
+ input: REDACTED,
141
+ output: REDACTED,
142
+ ...item.annotations !== void 0 ? { annotations: REDACTED } : {},
143
+ ...item.error !== void 0 ? { error: REDACTED } : {},
144
+ ...item.scorerErrors !== void 0 ? { scorerErrors: item.scorerErrors.map(() => REDACTED) } : {}
145
+ };
146
+ if (item.scoreDetails) {
147
+ const detailsOut = {};
148
+ for (const [name, detail] of Object.entries(item.scoreDetails)) {
149
+ detailsOut[name] = {
150
+ score: detail.score,
151
+ ...detail.duration !== void 0 ? { duration: detail.duration } : {},
152
+ ...detail.cost !== void 0 ? { cost: detail.cost } : {}
153
+ // metadata deliberately omitted — may contain LLM scorer reasoning
154
+ };
155
+ }
156
+ scrubbed.scoreDetails = detailsOut;
157
+ }
158
+ return scrubbed;
159
+ }
160
+ function redactEvalResult(result, redact) {
161
+ if (!redact) return result;
162
+ return {
163
+ ...result,
164
+ items: result.items.map(redactEvalItem)
165
+ };
166
+ }
167
+ function redactEvalHistoryEntry(entry, redact) {
168
+ if (!redact) return entry;
169
+ return {
170
+ ...entry,
171
+ data: redactEvalResult(entry.data, redact)
172
+ };
173
+ }
174
+ function redactEvalHistoryList(entries, redact) {
175
+ if (!redact) return entries;
176
+ return entries.map((e) => redactEvalHistoryEntry(e, redact));
177
+ }
178
+ function redactPendingDecision(decision, redact) {
179
+ if (!redact) return decision;
180
+ return {
181
+ ...decision,
182
+ prompt: REDACTED,
183
+ ...decision.metadata !== void 0 ? { metadata: { redacted: true } } : {}
184
+ };
185
+ }
186
+ function redactPendingDecisionList(decisions, redact) {
187
+ if (!redact) return decisions;
188
+ return decisions.map((d) => redactPendingDecision(d, redact));
189
+ }
190
+
44
191
  // src/server/middleware/error-handler.ts
45
192
  async function errorHandler(c, next) {
46
193
  try {
47
194
  await next();
48
195
  } catch (err) {
49
- const message = err instanceof Error ? err.message : String(err);
196
+ const rawMessage = err instanceof Error ? err.message : String(err);
50
197
  const code = err.code ?? "INTERNAL_ERROR";
51
198
  let status = 500;
52
199
  if ("status" in err) {
@@ -54,46 +201,81 @@ async function errorHandler(c, next) {
54
201
  if (typeof errStatus === "number" && errStatus >= 400 && errStatus < 600) {
55
202
  status = errStatus;
56
203
  }
57
- } else if (code === "NOT_FOUND" || message.includes("not found") || message.includes("not registered")) {
204
+ } else if (code === "NOT_FOUND" || rawMessage.includes("not found") || rawMessage.includes("not registered")) {
58
205
  status = 404;
59
- } else if (code === "VALIDATION_ERROR" || message.includes("Expected") || message.includes("invalid")) {
206
+ } else if (code === "VALIDATION_ERROR" || rawMessage.includes("Expected") || rawMessage.includes("invalid")) {
60
207
  status = 400;
61
208
  }
209
+ const runtime = c.get("runtime");
210
+ const redactOn = runtime?.isRedactEnabled?.() ?? false;
62
211
  const body = {
63
212
  ok: false,
64
- error: { code, message }
213
+ error: { code, message: redactErrorMessage(err, redactOn) }
65
214
  };
66
215
  return c.json(body, status);
67
216
  }
68
217
  }
69
218
 
70
219
  // src/server/ws/connection-manager.ts
71
- function isBufferedChannel(channel) {
72
- return channel.startsWith("execution:");
73
- }
74
220
  var BUFFER_TTL_MS = 3e4;
75
221
  var MAX_BUFFER_EVENTS = 500;
222
+ var MAX_WS_FRAME_BYTES = 65536;
223
+ function isBufferedChannel(channel) {
224
+ return channel.startsWith("execution:") || channel.startsWith("eval:");
225
+ }
226
+ function truncateIfOversized(msg, channel, data) {
227
+ if (msg.length <= MAX_WS_FRAME_BYTES) return msg;
228
+ const event = data ?? {};
229
+ const truncated = {
230
+ type: "event",
231
+ channel,
232
+ data: {
233
+ ...event,
234
+ data: {
235
+ __truncated: true,
236
+ originalBytes: msg.length,
237
+ maxBytes: MAX_WS_FRAME_BYTES,
238
+ hint: "Event exceeded WS frame budget (likely a verbose agent_call with a large messages[] snapshot). Fetch via REST if you need the full payload."
239
+ }
240
+ }
241
+ };
242
+ return JSON.stringify(truncated);
243
+ }
76
244
  var ConnectionManager = class {
77
245
  /** channel -> set of WS connections */
78
246
  channels = /* @__PURE__ */ new Map();
79
- /** ws -> set of subscribed channels (for cleanup) */
247
+ /** ws -> subscribed channels + optional integrator-supplied metadata */
80
248
  connections = /* @__PURE__ */ new Map();
81
249
  /** channel -> replay buffer for execution streams */
82
250
  buffers = /* @__PURE__ */ new Map();
83
251
  maxConnections = 100;
252
+ filter;
253
+ /**
254
+ * Register a broadcast filter. Called once at middleware construction.
255
+ * The filter runs on every outbound event and can drop or deliver based
256
+ * on the destination connection's metadata.
257
+ */
258
+ setFilter(filter) {
259
+ this.filter = filter;
260
+ }
261
+ /** Attach integrator-supplied metadata to an already-added connection. */
262
+ setMetadata(ws, metadata) {
263
+ const entry = this.connections.get(ws);
264
+ if (entry) entry.metadata = metadata;
265
+ }
84
266
  /** Register a new WS connection. */
85
267
  add(ws) {
86
268
  if (this.connections.size >= this.maxConnections) {
87
269
  ws.close?.();
88
270
  return;
89
271
  }
90
- this.connections.set(ws, /* @__PURE__ */ new Set());
272
+ this.connections.set(ws, { channels: /* @__PURE__ */ new Set() });
91
273
  }
92
274
  /** Remove a WS connection and all its subscriptions. */
93
275
  remove(ws) {
94
- const channels = this.connections.get(ws);
95
- if (channels) {
96
- for (const ch of channels) {
276
+ const entry = this.connections.get(ws);
277
+ if (entry) {
278
+ for (const ch of entry.channels) {
97
279
  this.channels.get(ch)?.delete(ws);
98
280
  if (this.channels.get(ch)?.size === 0) {
99
281
  this.channels.delete(ch);
@@ -111,12 +293,20 @@ var ConnectionManager = class {
111
293
  this.channels.set(channel, subs);
112
294
  }
113
295
  subs.add(ws);
114
- this.connections.get(ws).add(channel);
296
+ this.connections.get(ws).channels.add(channel);
115
297
  const buffer = this.buffers.get(channel);
116
298
  if (buffer) {
117
- for (const msg of buffer.events) {
299
+ const metadata = this.connections.get(ws)?.metadata;
300
+ for (const event of buffer.events) {
301
+ if (this.filter) {
302
+ try {
303
+ if (!this.filter(event.data, metadata)) continue;
304
+ } catch {
305
+ continue;
306
+ }
307
+ }
118
308
  try {
119
- ws.send(msg);
309
+ ws.send(event.msg);
120
310
  } catch {
121
311
  this.remove(ws);
122
312
  return;
@@ -130,11 +320,15 @@ var ConnectionManager = class {
130
320
  if (this.channels.get(channel)?.size === 0) {
131
321
  this.channels.delete(channel);
132
322
  }
133
- this.connections.get(ws)?.delete(channel);
323
+ this.connections.get(ws)?.channels.delete(channel);
134
324
  }
135
325
  /** Broadcast data to all subscribers of a channel. Buffers events for execution channels. */
136
326
  broadcast(channel, data) {
137
- const msg = JSON.stringify({ type: "event", channel, data });
327
+ const msg = truncateIfOversized(
328
+ JSON.stringify({ type: "event", channel, data }),
329
+ channel,
330
+ data
331
+ );
138
332
  if (isBufferedChannel(channel)) {
139
333
  let buffer = this.buffers.get(channel);
140
334
  if (!buffer) {
@@ -144,7 +338,7 @@ var ConnectionManager = class {
144
338
  const event = data;
145
339
  const isTerminal = event.type === "done" || event.type === "error";
146
340
  if (buffer.events.length < MAX_BUFFER_EVENTS || isTerminal) {
147
- buffer.events.push(msg);
341
+ buffer.events.push({ msg, data });
148
342
  }
149
343
  if (isTerminal) {
150
344
  buffer.complete = true;
@@ -157,6 +351,14 @@ var ConnectionManager = class {
157
351
  const subs = this.channels.get(channel);
158
352
  if (!subs || subs.size === 0) return;
159
353
  for (const ws of [...subs]) {
354
+ if (this.filter) {
355
+ const metadata = this.connections.get(ws)?.metadata;
356
+ try {
357
+ if (!this.filter(data, metadata)) continue;
358
+ } catch {
359
+ continue;
360
+ }
361
+ }
160
362
  try {
161
363
  ws.send(msg);
162
364
  } catch {
@@ -172,8 +374,20 @@ var ConnectionManager = class {
172
374
  const wildcardChannel = channel.substring(0, colonIdx) + ":*";
173
375
  const subs = this.channels.get(wildcardChannel);
174
376
  if (!subs || subs.size === 0) return;
175
- const msg = JSON.stringify({ type: "event", channel, data });
377
+ const msg = truncateIfOversized(
378
+ JSON.stringify({ type: "event", channel, data }),
379
+ channel,
380
+ data
381
+ );
176
382
  for (const ws of [...subs]) {
383
+ if (this.filter) {
384
+ const metadata = this.connections.get(ws)?.metadata;
385
+ try {
386
+ if (!this.filter(data, metadata)) continue;
387
+ } catch {
388
+ continue;
389
+ }
390
+ }
177
391
  try {
178
392
  ws.send(msg);
179
393
  } catch {
@@ -205,11 +419,11 @@ var ConnectionManager = class {
205
419
  };
206
420
 
207
421
  // src/server/ws/protocol.ts
208
- var VALID_CHANNEL_PREFIXES = ["execution:", "trace:"];
209
- var VALID_EXACT_CHANNELS = ["costs", "decisions"];
422
+ var VALID_CHANNEL_PREFIXES = ["execution:", "trace:", "eval:"];
423
+ var VALID_EXACT_CHANNELS = ["costs", "decisions", "eval-trends", "workflow-stats", "trace-stats"];
210
424
  var MAX_CHANNEL_LENGTH = 256;
211
425
  function handleWsMessage(raw, socket, connMgr) {
212
- if (raw.length > 65536) {
426
+ if (raw.length > MAX_WS_FRAME_BYTES) {
213
427
  return JSON.stringify({ type: "error", message: "Message too large" });
214
428
  }
215
429
  let msg;
@@ -269,92 +483,609 @@ function createWsHandlers(connMgr) {
269
483
  };
270
484
  }
271
485
 
272
- // src/server/cost-aggregator.ts
273
- var CostAggregator = class {
274
- constructor(connMgr) {
486
+ // src/server/aggregates/aggregate-snapshots.ts
487
+ var WINDOW_MS = {
488
+ "24h": 24 * 60 * 60 * 1e3,
489
+ "7d": 7 * 24 * 60 * 60 * 1e3,
490
+ "30d": 30 * 24 * 60 * 60 * 1e3,
491
+ all: Number.POSITIVE_INFINITY
492
+ };
493
+ function withinWindow(ts, window, now) {
494
+ return ts >= now - WINDOW_MS[window];
495
+ }
496
+ var REBUILD_INTERVAL_MS = 5 * 6e4;
497
+ var ALL_WINDOWS = new Set(Object.keys(WINDOW_MS));
498
+ function parseWindowParam(raw, fallback = "7d") {
499
+ return raw && ALL_WINDOWS.has(raw) ? raw : fallback;
500
+ }
501
+ var AggregateSnapshots = class {
502
+ constructor(windows, emptyState, connMgr, channel, broadcastTransform) {
503
+ this.windows = windows;
504
+ this.emptyState = emptyState;
275
505
  this.connMgr = connMgr;
506
+ this.channel = channel;
507
+ this.broadcastTransform = broadcastTransform;
508
+ this.snapshots = new Map(windows.map((w) => [w, emptyState()]));
509
+ }
510
+ snapshots;
511
+ /** Replace all snapshots atomically — used after a full rebuild. */
512
+ replace(fresh) {
513
+ this.snapshots = fresh;
514
+ this.broadcast();
515
+ }
516
+ /** Apply a reducer update to every window where `ts` falls inside the window. */
517
+ fold(ts, update) {
518
+ const now = Date.now();
519
+ let changed = false;
520
+ for (const window of this.windows) {
521
+ if (withinWindow(ts, window, now)) {
522
+ const prev = this.snapshots.get(window);
523
+ this.snapshots.set(window, update(prev));
524
+ changed = true;
525
+ }
526
+ }
527
+ if (changed) this.broadcast();
528
+ }
529
+ get(window) {
530
+ return this.snapshots.get(window) ?? this.emptyState();
531
+ }
532
+ getAll() {
533
+ return Object.fromEntries(this.snapshots);
534
+ }
535
+ broadcast() {
536
+ const snapshots = this.broadcastTransform ? Object.fromEntries(
537
+ this.windows.map((w) => [w, this.broadcastTransform(this.snapshots.get(w))])
538
+ ) : this.getAll();
539
+ this.connMgr.broadcast(this.channel, {
540
+ snapshots,
541
+ updatedAt: Date.now()
542
+ });
543
+ }
544
+ };
545
+
546
+ // src/server/aggregates/trace-aggregator.ts
547
+ var TraceAggregator = class {
548
+ snaps;
549
+ interval;
550
+ listener;
551
+ options;
552
+ constructor(options) {
553
+ this.options = options;
554
+ this.snaps = new AggregateSnapshots(
555
+ options.windows,
556
+ options.emptyState,
557
+ options.connMgr,
558
+ options.channel,
559
+ options.broadcastTransform
560
+ );
561
+ }
562
+ async start() {
563
+ await this.rebuild();
564
+ this.listener = (event) => {
565
+ this.snaps.fold(event.timestamp, (prev) => this.options.reducer(prev, event));
566
+ };
567
+ this.options.runtime.on("trace", this.listener);
568
+ this.interval = setInterval(
569
+ () => this.rebuild().catch((err) => console.error("[axl-studio] rebuild failed:", err)),
570
+ REBUILD_INTERVAL_MS
571
+ );
572
+ }
573
+ async rebuild() {
574
+ const executions = await this.options.runtime.getExecutions();
575
+ const cap = this.options.executionCap ?? 2e3;
576
+ const capped = executions.slice(0, cap);
577
+ const now = Date.now();
578
+ const fresh = new Map(
579
+ this.options.windows.map((w) => [w, this.options.emptyState()])
580
+ );
581
+ for (const exec of capped) {
582
+ for (const event of exec.steps) {
583
+ for (const window of this.options.windows) {
584
+ if (withinWindow(event.timestamp, window, now)) {
585
+ fresh.set(window, this.options.reducer(fresh.get(window), event));
586
+ }
587
+ }
588
+ }
589
+ }
590
+ this.snaps.replace(fresh);
591
+ }
592
+ getSnapshot(window) {
593
+ return this.snaps.get(window);
276
594
  }
277
- data = {
595
+ getAllSnapshots() {
596
+ return this.snaps.getAll();
597
+ }
598
+ close() {
599
+ if (this.listener) this.options.runtime.off("trace", this.listener);
600
+ if (this.interval) clearInterval(this.interval);
601
+ }
602
+ };
603
+
604
+ // src/server/aggregates/reducers.ts
605
+ var finite = (v) => Number.isFinite(v) ? v : 0;
606
+ function isLogEvent(event, eventName) {
607
+ if (event.type === eventName) return true;
608
+ if (event.type === "log" && event.data != null && typeof event.data === "object") {
609
+ return event.data.event === eventName;
610
+ }
611
+ return false;
612
+ }
613
+ function emptyRetry() {
614
+ return {
615
+ primary: 0,
616
+ primaryCalls: 0,
617
+ schema: 0,
618
+ schemaCalls: 0,
619
+ validate: 0,
620
+ validateCalls: 0,
621
+ guardrail: 0,
622
+ guardrailCalls: 0,
623
+ retryCalls: 0
624
+ };
625
+ }
626
+ function emptyCostData() {
627
+ return {
278
628
  totalCost: 0,
279
629
  totalTokens: { input: 0, output: 0, reasoning: 0 },
280
630
  byAgent: {},
281
631
  byModel: {},
282
- byWorkflow: {}
632
+ byWorkflow: {},
633
+ retry: emptyRetry(),
634
+ byEmbedder: {}
283
635
  };
284
- /** Process a trace event and update cost data. */
285
- onTrace(event) {
286
- if (event.cost == null && !event.tokens) return;
287
- const cost = Number.isFinite(event.cost) ? event.cost : 0;
288
- const tokens = event.tokens ?? {};
289
- this.data.totalCost += cost;
290
- this.data.totalTokens.input += tokens.input ?? 0;
291
- this.data.totalTokens.output += tokens.output ?? 0;
292
- this.data.totalTokens.reasoning += tokens.reasoning ?? 0;
293
- if (event.agent) {
294
- const entry = this.data.byAgent[event.agent] ?? { cost: 0, calls: 0 };
295
- entry.cost += cost;
296
- entry.calls += 1;
297
- this.data.byAgent[event.agent] = entry;
636
+ }
637
+ function reduceCost(acc, event) {
638
+ const isWorkflowStart = isLogEvent(event, "workflow_start");
639
+ if (isWorkflowStart && event.workflow) {
640
+ const byWorkflow2 = { ...acc.byWorkflow };
641
+ const prev = byWorkflow2[event.workflow] ?? { cost: 0, executions: 0 };
642
+ byWorkflow2[event.workflow] = { ...prev, executions: prev.executions + 1 };
643
+ return { ...acc, byWorkflow: byWorkflow2 };
644
+ }
645
+ if (event.cost == null && !event.tokens) return acc;
646
+ const cost = finite(event.cost);
647
+ const tokens = event.tokens ?? {};
648
+ const totalTokens = event.type === "agent_call" ? {
649
+ input: acc.totalTokens.input + finite(tokens.input),
650
+ output: acc.totalTokens.output + finite(tokens.output),
651
+ reasoning: acc.totalTokens.reasoning + finite(tokens.reasoning)
652
+ } : acc.totalTokens;
653
+ const byAgent = { ...acc.byAgent };
654
+ if (event.agent) {
655
+ const prev = byAgent[event.agent] ?? { cost: 0, calls: 0 };
656
+ byAgent[event.agent] = { cost: prev.cost + cost, calls: prev.calls + 1 };
657
+ }
658
+ const byModel = { ...acc.byModel };
659
+ if (event.model) {
660
+ const prev = byModel[event.model] ?? { cost: 0, calls: 0, tokens: { input: 0, output: 0 } };
661
+ byModel[event.model] = {
662
+ cost: prev.cost + cost,
663
+ calls: prev.calls + 1,
664
+ tokens: {
665
+ input: prev.tokens.input + finite(tokens.input),
666
+ output: prev.tokens.output + finite(tokens.output)
667
+ }
668
+ };
669
+ }
670
+ const byWorkflow = { ...acc.byWorkflow };
671
+ if (event.workflow) {
672
+ const prev = byWorkflow[event.workflow] ?? { cost: 0, executions: 0 };
673
+ byWorkflow[event.workflow] = {
674
+ cost: prev.cost + cost,
675
+ executions: prev.executions + (isWorkflowStart ? 1 : 0)
676
+ };
677
+ }
678
+ let retry = acc.retry;
679
+ if (event.type === "agent_call") {
680
+ const d = event.data ?? {};
681
+ const reason = d.retryReason;
682
+ retry = { ...acc.retry };
683
+ if (reason === "schema") {
684
+ retry.schema += cost;
685
+ retry.schemaCalls += 1;
686
+ retry.retryCalls += 1;
687
+ } else if (reason === "validate") {
688
+ retry.validate += cost;
689
+ retry.validateCalls += 1;
690
+ retry.retryCalls += 1;
691
+ } else if (reason === "guardrail") {
692
+ retry.guardrail += cost;
693
+ retry.guardrailCalls += 1;
694
+ retry.retryCalls += 1;
695
+ } else {
696
+ retry.primary += cost;
697
+ retry.primaryCalls += 1;
298
698
  }
299
- if (event.model) {
300
- const entry = this.data.byModel[event.model] ?? {
301
- cost: 0,
302
- calls: 0,
303
- tokens: { input: 0, output: 0 }
699
+ }
700
+ let byEmbedder = acc.byEmbedder;
701
+ if (event.type === "log") {
702
+ const d = event.data ?? {};
703
+ if (d.event === "memory_remember" || d.event === "memory_recall") {
704
+ byEmbedder = { ...acc.byEmbedder };
705
+ const modelKey = d.usage?.model ?? "unknown";
706
+ const embedTokens = typeof d.usage?.tokens === "number" ? finite(d.usage.tokens) : 0;
707
+ const prev = byEmbedder[modelKey] ?? { cost: 0, calls: 0, tokens: 0 };
708
+ byEmbedder[modelKey] = {
709
+ cost: prev.cost + cost,
710
+ calls: prev.calls + 1,
711
+ tokens: prev.tokens + embedTokens
304
712
  };
305
- entry.cost += cost;
306
- entry.calls += 1;
307
- entry.tokens.input += tokens.input ?? 0;
308
- entry.tokens.output += tokens.output ?? 0;
309
- this.data.byModel[event.model] = entry;
310
713
  }
311
- if (event.workflow) {
312
- const entry = this.data.byWorkflow[event.workflow] ?? { cost: 0, executions: 0 };
313
- entry.cost += cost;
314
- if (event.type === "workflow_start") entry.executions += 1;
315
- this.data.byWorkflow[event.workflow] = entry;
714
+ }
715
+ return {
716
+ totalCost: acc.totalCost + cost,
717
+ totalTokens,
718
+ byAgent,
719
+ byModel,
720
+ byWorkflow,
721
+ retry,
722
+ byEmbedder
723
+ };
724
+ }
725
+ function emptyEvalTrendData() {
726
+ return { byEval: {}, totalRuns: 0, totalCost: 0 };
727
+ }
728
+ function extractScores(data) {
729
+ if (!data || typeof data !== "object") return {};
730
+ const result = data;
731
+ const summary = result.summary;
732
+ const scorers = summary?.scorers;
733
+ if (!scorers) return {};
734
+ const out = {};
735
+ for (const [name, entry] of Object.entries(scorers)) {
736
+ if (typeof entry === "number" && Number.isFinite(entry)) {
737
+ out[name] = entry;
738
+ } else if (entry && typeof entry === "object" && Number.isFinite(entry.mean)) {
739
+ out[name] = entry.mean;
740
+ }
741
+ }
742
+ return out;
743
+ }
744
+ function extractCost(data) {
745
+ if (!data || typeof data !== "object") return 0;
746
+ const result = data;
747
+ if (Number.isFinite(result.totalCost)) return result.totalCost;
748
+ const summary = result.summary;
749
+ return Number.isFinite(summary?.totalCost) ? summary.totalCost : 0;
750
+ }
751
+ function extractModel(data) {
752
+ if (!data || typeof data !== "object") return void 0;
753
+ const result = data;
754
+ const metadata = result.metadata;
755
+ const counts = metadata?.modelCounts;
756
+ if (counts && typeof counts === "object" && !Array.isArray(counts)) {
757
+ const entries = Object.entries(counts).filter(
758
+ ([, v]) => typeof v === "number"
759
+ );
760
+ if (entries.length > 0) {
761
+ entries.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
762
+ return entries[0][0];
316
763
  }
317
- this.connMgr.broadcast("costs", this.data);
318
- }
319
- /** Get current aggregated cost data. */
320
- getData() {
321
- return this.data;
322
- }
323
- /** Reset all accumulated data. */
324
- reset() {
325
- this.data = {
326
- totalCost: 0,
327
- totalTokens: { input: 0, output: 0, reasoning: 0 },
328
- byAgent: {},
329
- byModel: {},
330
- byWorkflow: {}
764
+ }
765
+ const models = metadata?.models;
766
+ if (Array.isArray(models) && typeof models[0] === "string") return models[0];
767
+ return void 0;
768
+ }
769
+ function extractDuration(data) {
770
+ if (!data || typeof data !== "object") return void 0;
771
+ const result = data;
772
+ return Number.isFinite(result.duration) ? result.duration : void 0;
773
+ }
774
+ function computeScoreStats(runs) {
775
+ const scorerNames = /* @__PURE__ */ new Set();
776
+ for (const run of runs) {
777
+ for (const name of Object.keys(run.scores)) scorerNames.add(name);
778
+ }
779
+ const mean = {};
780
+ const std = {};
781
+ for (const name of scorerNames) {
782
+ const values = runs.map((r) => r.scores[name]).filter((v) => v != null);
783
+ if (values.length === 0) continue;
784
+ const m = values.reduce((a, b) => a + b, 0) / values.length;
785
+ mean[name] = m;
786
+ const variance = values.reduce((sum, v) => sum + (v - m) ** 2, 0) / values.length;
787
+ std[name] = Math.sqrt(variance);
788
+ }
789
+ return { mean, std };
790
+ }
791
+ function reduceEvalTrends(acc, entry) {
792
+ const scores = extractScores(entry.data);
793
+ const cost = extractCost(entry.data);
794
+ const model = extractModel(entry.data);
795
+ const duration = extractDuration(entry.data);
796
+ const run = {
797
+ timestamp: entry.timestamp,
798
+ id: entry.id,
799
+ scores,
800
+ cost,
801
+ ...model !== void 0 ? { model } : {},
802
+ ...duration !== void 0 ? { duration } : {}
803
+ };
804
+ const byEval = { ...acc.byEval };
805
+ const prev = byEval[entry.eval];
806
+ const MAX_EVAL_RUNS = 50;
807
+ const allRuns = prev ? [...prev.runs, run] : [run];
808
+ const runs = allRuns.length > MAX_EVAL_RUNS ? allRuns.slice(-MAX_EVAL_RUNS) : allRuns;
809
+ const { mean, std } = computeScoreStats(runs);
810
+ const latestScores = prev && prev.runs.length > 0 && prev.runs[prev.runs.length - 1].timestamp > run.timestamp ? prev.latestScores : scores;
811
+ byEval[entry.eval] = {
812
+ runs,
813
+ latestScores,
814
+ scoreMean: mean,
815
+ scoreStd: std,
816
+ costTotal: (prev?.costTotal ?? 0) + cost,
817
+ runCount: (prev?.runCount ?? 0) + 1
818
+ };
819
+ return {
820
+ byEval,
821
+ totalRuns: acc.totalRuns + 1,
822
+ totalCost: acc.totalCost + cost
823
+ };
824
+ }
825
+ var MAX_DURATIONS = 200;
826
+ function emptyWorkflowStatsData() {
827
+ return { byWorkflow: {}, totalExecutions: 0, failureRate: 0 };
828
+ }
829
+ function percentile(sorted, p) {
830
+ if (sorted.length === 0) return 0;
831
+ const idx = p / 100 * (sorted.length - 1);
832
+ const lower = Math.floor(idx);
833
+ const upper = Math.ceil(idx);
834
+ if (lower === upper) return sorted[lower];
835
+ return sorted[lower] + (sorted[upper] - sorted[lower]) * (idx - lower);
836
+ }
837
+ function reduceWorkflowStats(acc, execution) {
838
+ const byWorkflow = { ...acc.byWorkflow };
839
+ const prev = byWorkflow[execution.workflow] ?? {
840
+ total: 0,
841
+ completed: 0,
842
+ failed: 0,
843
+ durations: [],
844
+ durationSum: 0,
845
+ avgDuration: 0
846
+ };
847
+ const dur = finite(execution.duration);
848
+ const durations = [...prev.durations];
849
+ const insertIdx = durations.findIndex((d) => d > dur);
850
+ if (insertIdx === -1) durations.push(dur);
851
+ else durations.splice(insertIdx, 0, dur);
852
+ if (durations.length > MAX_DURATIONS) durations.shift();
853
+ const total = prev.total + 1;
854
+ const completed = prev.completed + (execution.status === "completed" ? 1 : 0);
855
+ const failed = prev.failed + (execution.status === "failed" ? 1 : 0);
856
+ const durationSum = prev.durationSum + dur;
857
+ const avgDuration = durationSum / total;
858
+ byWorkflow[execution.workflow] = {
859
+ total,
860
+ completed,
861
+ failed,
862
+ durations,
863
+ durationSum,
864
+ avgDuration
865
+ };
866
+ const totalExecutions = acc.totalExecutions + 1;
867
+ const totalFailed = Object.values(byWorkflow).reduce((sum, w) => sum + w.failed, 0);
868
+ const failureRate = totalExecutions > 0 ? totalFailed / totalExecutions : 0;
869
+ return { byWorkflow, totalExecutions, failureRate };
870
+ }
871
+ function getWorkflowPercentiles(entry) {
872
+ return {
873
+ durationP50: percentile(entry.durations, 50),
874
+ durationP95: percentile(entry.durations, 95)
875
+ };
876
+ }
877
+ function enrichWorkflowStats(data) {
878
+ const byWorkflow = {};
879
+ for (const [name, entry] of Object.entries(data.byWorkflow)) {
880
+ const { durationP50, durationP95 } = getWorkflowPercentiles(entry);
881
+ byWorkflow[name] = {
882
+ total: entry.total,
883
+ completed: entry.completed,
884
+ failed: entry.failed,
885
+ durationP50,
886
+ durationP95,
887
+ avgDuration: entry.avgDuration
888
+ };
889
+ }
890
+ return {
891
+ byWorkflow,
892
+ totalExecutions: data.totalExecutions,
893
+ failureRate: data.failureRate
894
+ };
895
+ }
896
+ function emptyTraceStatsData() {
897
+ return {
898
+ eventTypeCounts: {},
899
+ byTool: {},
900
+ retryByAgent: {},
901
+ totalEvents: 0
902
+ };
903
+ }
904
+ function reduceTraceStats(acc, event) {
905
+ const eventTypeCounts = { ...acc.eventTypeCounts };
906
+ eventTypeCounts[event.type] = (eventTypeCounts[event.type] ?? 0) + 1;
907
+ const byTool = { ...acc.byTool };
908
+ if (event.type === "tool_call" || event.type === "tool_denied" || event.type === "tool_approval") {
909
+ const toolName = event.tool;
910
+ const prev = byTool[toolName] ?? { calls: 0, denied: 0, approved: 0 };
911
+ const isDeniedEvent = event.type === "tool_denied";
912
+ const isApprovalEvent = event.type === "tool_approval";
913
+ const eventData = isDeniedEvent || isApprovalEvent ? event.data : void 0;
914
+ const isApproved = isDeniedEvent && eventData?.approved === true || isApprovalEvent && eventData?.approved === true;
915
+ const isDenied = isDeniedEvent && !eventData?.approved || isApprovalEvent && eventData?.approved === false;
916
+ byTool[toolName] = {
917
+ calls: prev.calls + (event.type === "tool_call" ? 1 : 0),
918
+ denied: prev.denied + (isDenied ? 1 : 0),
919
+ approved: prev.approved + (isApproved ? 1 : 0)
920
+ };
921
+ }
922
+ const retryByAgent = { ...acc.retryByAgent };
923
+ if (event.agent && event.type === "agent_call") {
924
+ const data = event.data;
925
+ if (data?.retryReason) {
926
+ const prev = retryByAgent[event.agent] ?? { schema: 0, validate: 0, guardrail: 0 };
927
+ const reason = data.retryReason;
928
+ if (reason in prev) {
929
+ retryByAgent[event.agent] = { ...prev, [reason]: prev[reason] + 1 };
930
+ }
931
+ }
932
+ }
933
+ return {
934
+ eventTypeCounts,
935
+ byTool,
936
+ retryByAgent,
937
+ totalEvents: acc.totalEvents + 1
938
+ };
939
+ }
940
+
941
+ // src/server/aggregates/execution-aggregator.ts
942
+ var ExecutionAggregator = class {
943
+ snaps;
944
+ interval;
945
+ listener;
946
+ options;
947
+ /** Generation counter to prevent stale async fold after rebuild. */
948
+ generation = 0;
949
+ constructor(options) {
950
+ this.options = options;
951
+ this.snaps = new AggregateSnapshots(
952
+ options.windows,
953
+ options.emptyState,
954
+ options.connMgr,
955
+ options.channel,
956
+ options.broadcastTransform
957
+ );
958
+ }
959
+ async start() {
960
+ await this.rebuild();
961
+ this.listener = (event) => {
962
+ if (!isLogEvent(event, "workflow_end")) return;
963
+ const gen = this.generation;
964
+ this.options.runtime.getExecution(event.executionId).then((exec) => {
965
+ if (this.generation !== gen) return;
966
+ if (exec) {
967
+ this.snaps.fold(exec.startedAt, (prev) => this.options.reducer(prev, exec));
968
+ }
969
+ }).catch((err) => console.error("[axl-studio] execution fold failed:", err));
970
+ };
971
+ this.options.runtime.on("trace", this.listener);
972
+ this.interval = setInterval(
973
+ () => this.rebuild().catch((err) => console.error("[axl-studio] rebuild failed:", err)),
974
+ REBUILD_INTERVAL_MS
975
+ );
976
+ }
977
+ async rebuild() {
978
+ this.generation++;
979
+ const executions = await this.options.runtime.getExecutions();
980
+ const cap = this.options.executionCap ?? 2e3;
981
+ const capped = executions.slice(0, cap);
982
+ const now = Date.now();
983
+ const fresh = new Map(
984
+ this.options.windows.map((w) => [w, this.options.emptyState()])
985
+ );
986
+ for (const exec of capped) {
987
+ for (const window of this.options.windows) {
988
+ if (withinWindow(exec.startedAt, window, now)) {
989
+ fresh.set(window, this.options.reducer(fresh.get(window), exec));
990
+ }
991
+ }
992
+ }
993
+ this.snaps.replace(fresh);
994
+ }
995
+ getSnapshot(window) {
996
+ return this.snaps.get(window);
997
+ }
998
+ getAllSnapshots() {
999
+ return this.snaps.getAll();
1000
+ }
1001
+ close() {
1002
+ if (this.listener) this.options.runtime.off("trace", this.listener);
1003
+ if (this.interval) clearInterval(this.interval);
1004
+ }
1005
+ };
1006
+
1007
+ // src/server/aggregates/eval-aggregator.ts
1008
+ var EvalAggregator = class {
1009
+ snaps;
1010
+ interval;
1011
+ listener;
1012
+ options;
1013
+ constructor(options) {
1014
+ this.options = options;
1015
+ this.snaps = new AggregateSnapshots(
1016
+ options.windows,
1017
+ options.emptyState,
1018
+ options.connMgr,
1019
+ options.channel,
1020
+ options.broadcastTransform
1021
+ );
1022
+ }
1023
+ async start() {
1024
+ await this.rebuild();
1025
+ this.listener = (entry) => {
1026
+ this.snaps.fold(entry.timestamp, (prev) => this.options.reducer(prev, entry));
331
1027
  };
1028
+ this.options.runtime.on("eval_result", this.listener);
1029
+ this.interval = setInterval(
1030
+ () => this.rebuild().catch((err) => console.error("[axl-studio] rebuild failed:", err)),
1031
+ REBUILD_INTERVAL_MS
1032
+ );
1033
+ }
1034
+ async rebuild() {
1035
+ const history = await this.options.runtime.getEvalHistory();
1036
+ const cap = this.options.entryCap ?? 500;
1037
+ const capped = history.slice(0, cap);
1038
+ const now = Date.now();
1039
+ const fresh = new Map(
1040
+ this.options.windows.map((w) => [w, this.options.emptyState()])
1041
+ );
1042
+ for (const entry of capped) {
1043
+ for (const window of this.options.windows) {
1044
+ if (withinWindow(entry.timestamp, window, now)) {
1045
+ fresh.set(window, this.options.reducer(fresh.get(window), entry));
1046
+ }
1047
+ }
1048
+ }
1049
+ this.snaps.replace(fresh);
1050
+ }
1051
+ getSnapshot(window) {
1052
+ return this.snaps.get(window);
1053
+ }
1054
+ getAllSnapshots() {
1055
+ return this.snaps.getAll();
1056
+ }
1057
+ close() {
1058
+ if (this.listener) this.options.runtime.off("eval_result", this.listener);
1059
+ if (this.interval) clearInterval(this.interval);
332
1060
  }
333
1061
  };
334
1062
 
335
1063
  // src/server/routes/health.ts
336
1064
  var import_hono = require("hono");
337
- var app = new import_hono.Hono();
338
- app.get("/health", (c) => {
339
- const runtime = c.get("runtime");
340
- return c.json({
341
- ok: true,
342
- data: {
343
- status: "healthy",
344
- workflows: runtime.getWorkflowNames().length,
345
- agents: runtime.getAgents().length,
346
- tools: runtime.getTools().length
347
- }
1065
+ function createHealthRoutes(readOnly) {
1066
+ const app6 = new import_hono.Hono();
1067
+ app6.get("/health", (c) => {
1068
+ const runtime = c.get("runtime");
1069
+ return c.json({
1070
+ ok: true,
1071
+ data: {
1072
+ status: "healthy",
1073
+ readOnly,
1074
+ workflows: runtime.getWorkflowNames().length,
1075
+ agents: runtime.getAgents().length,
1076
+ tools: runtime.getTools().length
1077
+ }
1078
+ });
348
1079
  });
349
- });
350
- var health_default = app;
1080
+ return app6;
1081
+ }
351
1082
 
352
1083
  // src/server/routes/workflows.ts
353
1084
  var import_hono2 = require("hono");
354
1085
  var import_axl = require("@axlsdk/axl");
355
1086
  function createWorkflowRoutes(connMgr) {
356
- const app7 = new import_hono2.Hono();
357
- app7.get("/workflows", (c) => {
1087
+ const app6 = new import_hono2.Hono();
1088
+ app6.get("/workflows", (c) => {
358
1089
  const runtime = c.get("runtime");
359
1090
  const workflows = runtime.getWorkflows().map((w) => ({
360
1091
  name: w.name,
@@ -363,7 +1094,7 @@ function createWorkflowRoutes(connMgr) {
363
1094
  }));
364
1095
  return c.json({ ok: true, data: workflows });
365
1096
  });
366
- app7.get("/workflows/:name", (c) => {
1097
+ app6.get("/workflows/:name", (c) => {
367
1098
  const runtime = c.get("runtime");
368
1099
  const name = c.req.param("name");
369
1100
  const workflow = runtime.getWorkflow(name);
@@ -382,7 +1113,7 @@ function createWorkflowRoutes(connMgr) {
382
1113
  }
383
1114
  });
384
1115
  });
385
- app7.post("/workflows/:name/execute", async (c) => {
1116
+ app6.post("/workflows/:name/execute", async (c) => {
386
1117
  const runtime = c.get("runtime");
387
1118
  const name = c.req.param("name");
388
1119
  const workflow = runtime.getWorkflow(name);
@@ -396,28 +1127,38 @@ function createWorkflowRoutes(connMgr) {
396
1127
  if (body.stream) {
397
1128
  const stream = runtime.stream(name, body.input ?? {}, { metadata: body.metadata });
398
1129
  const executionId = `stream-${Date.now()}`;
1130
+ const redactOn = runtime.isRedactEnabled();
399
1131
  (async () => {
400
1132
  for await (const event of stream) {
401
- connMgr.broadcastWithWildcard(`execution:${executionId}`, event);
1133
+ connMgr.broadcastWithWildcard(
1134
+ `execution:${executionId}`,
1135
+ redactStreamEvent(event, redactOn)
1136
+ );
402
1137
  }
403
1138
  })();
404
1139
  return c.json({ ok: true, data: { executionId, streaming: true } });
405
1140
  }
406
1141
  const result = await runtime.execute(name, body.input ?? {}, { metadata: body.metadata });
407
- return c.json({ ok: true, data: { result } });
1142
+ return c.json({
1143
+ ok: true,
1144
+ data: { result: redactValue(result, runtime.isRedactEnabled()) }
1145
+ });
408
1146
  });
409
- return app7;
1147
+ return app6;
410
1148
  }
411
1149
 
412
1150
  // src/server/routes/executions.ts
413
1151
  var import_hono3 = require("hono");
414
- var app2 = new import_hono3.Hono();
415
- app2.get("/executions", async (c) => {
1152
+ var app = new import_hono3.Hono();
1153
+ app.get("/executions", async (c) => {
416
1154
  const runtime = c.get("runtime");
417
1155
  const executions = await runtime.getExecutions();
418
- return c.json({ ok: true, data: executions });
1156
+ return c.json({
1157
+ ok: true,
1158
+ data: redactExecutionList(executions, runtime.isRedactEnabled())
1159
+ });
419
1160
  });
420
- app2.get("/executions/:id", async (c) => {
1161
+ app.get("/executions/:id", async (c) => {
421
1162
  const runtime = c.get("runtime");
422
1163
  const id = c.req.param("id");
423
1164
  const execution = await runtime.getExecution(id);
@@ -427,21 +1168,24 @@ app2.get("/executions/:id", async (c) => {
427
1168
  404
428
1169
  );
429
1170
  }
430
- return c.json({ ok: true, data: execution });
1171
+ return c.json({
1172
+ ok: true,
1173
+ data: redactExecutionInfo(execution, runtime.isRedactEnabled())
1174
+ });
431
1175
  });
432
- app2.post("/executions/:id/abort", (c) => {
1176
+ app.post("/executions/:id/abort", (c) => {
433
1177
  const runtime = c.get("runtime");
434
1178
  const id = c.req.param("id");
435
1179
  runtime.abort(id);
436
1180
  return c.json({ ok: true, data: { aborted: true } });
437
1181
  });
438
- var executions_default = app2;
1182
+ var executions_default = app;
439
1183
 
440
1184
  // src/server/routes/sessions.ts
441
1185
  var import_hono4 = require("hono");
442
1186
  function createSessionRoutes(connMgr) {
443
- const app7 = new import_hono4.Hono();
444
- app7.get("/sessions", async (c) => {
1187
+ const app6 = new import_hono4.Hono();
1188
+ app6.get("/sessions", async (c) => {
445
1189
  const runtime = c.get("runtime");
446
1190
  const store = runtime.getStateStore();
447
1191
  if (!store.listSessions) {
@@ -455,15 +1199,24 @@ function createSessionRoutes(connMgr) {
455
1199
  }
456
1200
  return c.json({ ok: true, data: sessions });
457
1201
  });
458
- app7.get("/sessions/:id", async (c) => {
1202
+ app6.get("/sessions/:id", async (c) => {
459
1203
  const runtime = c.get("runtime");
460
1204
  const store = runtime.getStateStore();
461
1205
  const id = c.req.param("id");
462
1206
  const history = await store.getSession(id);
463
1207
  const handoffHistory = await store.getSessionMeta(id, "handoffHistory");
464
- return c.json({ ok: true, data: { id, history, handoffHistory: handoffHistory ?? [] } });
1208
+ return c.json({
1209
+ ok: true,
1210
+ data: {
1211
+ id,
1212
+ history: redactSessionHistory(history, runtime.isRedactEnabled()),
1213
+ // HandoffRecord has no content fields (source/target/mode/
1214
+ // timestamp/duration) — nothing to scrub.
1215
+ handoffHistory: handoffHistory ?? []
1216
+ }
1217
+ });
465
1218
  });
466
- app7.post("/sessions/:id/send", async (c) => {
1219
+ app6.post("/sessions/:id/send", async (c) => {
467
1220
  const runtime = c.get("runtime");
468
1221
  const id = c.req.param("id");
469
1222
  const body = await c.req.json();
@@ -471,7 +1224,7 @@ function createSessionRoutes(connMgr) {
471
1224
  const result = await session.send(body.workflow, body.message);
472
1225
  return c.json({ ok: true, data: { result } });
473
1226
  });
474
- app7.post("/sessions/:id/stream", async (c) => {
1227
+ app6.post("/sessions/:id/stream", async (c) => {
475
1228
  const runtime = c.get("runtime");
476
1229
  const id = c.req.param("id");
477
1230
  const body = await c.req.json();
@@ -485,21 +1238,21 @@ function createSessionRoutes(connMgr) {
485
1238
  })();
486
1239
  return c.json({ ok: true, data: { executionId, streaming: true } });
487
1240
  });
488
- app7.delete("/sessions/:id", async (c) => {
1241
+ app6.delete("/sessions/:id", async (c) => {
489
1242
  const runtime = c.get("runtime");
490
1243
  const store = runtime.getStateStore();
491
1244
  const id = c.req.param("id");
492
1245
  await store.deleteSession(id);
493
1246
  return c.json({ ok: true, data: { deleted: true } });
494
1247
  });
495
- return app7;
1248
+ return app6;
496
1249
  }
497
1250
 
498
1251
  // src/server/routes/agents.ts
499
1252
  var import_hono5 = require("hono");
500
1253
  var import_axl2 = require("@axlsdk/axl");
501
- var app3 = new import_hono5.Hono();
502
- app3.get("/agents", (c) => {
1254
+ var app2 = new import_hono5.Hono();
1255
+ app2.get("/agents", (c) => {
503
1256
  const runtime = c.get("runtime");
504
1257
  const agents = runtime.getAgents().map((a) => ({
505
1258
  name: a._name,
@@ -518,7 +1271,7 @@ app3.get("/agents", (c) => {
518
1271
  }));
519
1272
  return c.json({ ok: true, data: agents });
520
1273
  });
521
- app3.get("/agents/:name", (c) => {
1274
+ app2.get("/agents/:name", (c) => {
522
1275
  const runtime = c.get("runtime");
523
1276
  const name = c.req.param("name");
524
1277
  const agent = runtime.getAgent(name);
@@ -574,13 +1327,13 @@ app3.get("/agents/:name", (c) => {
574
1327
  }
575
1328
  });
576
1329
  });
577
- var agents_default = app3;
1330
+ var agents_default = app2;
578
1331
 
579
1332
  // src/server/routes/tools.ts
580
1333
  var import_hono6 = require("hono");
581
1334
  var import_axl3 = require("@axlsdk/axl");
582
- var app4 = new import_hono6.Hono();
583
- app4.get("/tools", (c) => {
1335
+ var app3 = new import_hono6.Hono();
1336
+ app3.get("/tools", (c) => {
584
1337
  const runtime = c.get("runtime");
585
1338
  const tools = runtime.getTools().map((t) => ({
586
1339
  name: t.name,
@@ -591,7 +1344,7 @@ app4.get("/tools", (c) => {
591
1344
  }));
592
1345
  return c.json({ ok: true, data: tools });
593
1346
  });
594
- app4.get("/tools/:name", (c) => {
1347
+ app3.get("/tools/:name", (c) => {
595
1348
  const runtime = c.get("runtime");
596
1349
  const name = c.req.param("name");
597
1350
  const tool = runtime.getTool(name);
@@ -618,7 +1371,7 @@ app4.get("/tools/:name", (c) => {
618
1371
  }
619
1372
  });
620
1373
  });
621
- app4.post("/tools/:name/test", async (c) => {
1374
+ app3.post("/tools/:name/test", async (c) => {
622
1375
  const runtime = c.get("runtime");
623
1376
  const name = c.req.param("name");
624
1377
  const tool = runtime.getTool(name);
@@ -631,14 +1384,17 @@ app4.post("/tools/:name/test", async (c) => {
631
1384
  const body = await c.req.json();
632
1385
  const ctx = runtime.createContext();
633
1386
  const result = await tool.run(ctx, body.input);
634
- return c.json({ ok: true, data: { result } });
1387
+ return c.json({
1388
+ ok: true,
1389
+ data: { result: redactValue(result, runtime.isRedactEnabled()) }
1390
+ });
635
1391
  });
636
- var tools_default = app4;
1392
+ var tools_default = app3;
637
1393
 
638
1394
  // src/server/routes/memory.ts
639
1395
  var import_hono7 = require("hono");
640
- var app5 = new import_hono7.Hono();
641
- app5.get("/memory/:scope", async (c) => {
1396
+ var app4 = new import_hono7.Hono();
1397
+ app4.get("/memory/:scope", async (c) => {
642
1398
  const runtime = c.get("runtime");
643
1399
  const store = runtime.getStateStore();
644
1400
  const scope = c.req.param("scope");
@@ -646,9 +1402,9 @@ app5.get("/memory/:scope", async (c) => {
646
1402
  return c.json({ ok: true, data: [] });
647
1403
  }
648
1404
  const entries = await store.getAllMemory(scope);
649
- return c.json({ ok: true, data: entries });
1405
+ return c.json({ ok: true, data: redactMemoryList(entries, runtime.isRedactEnabled()) });
650
1406
  });
651
- app5.get("/memory/:scope/:key", async (c) => {
1407
+ app4.get("/memory/:scope/:key", async (c) => {
652
1408
  const runtime = c.get("runtime");
653
1409
  const store = runtime.getStateStore();
654
1410
  const scope = c.req.param("scope");
@@ -666,9 +1422,12 @@ app5.get("/memory/:scope/:key", async (c) => {
666
1422
  404
667
1423
  );
668
1424
  }
669
- return c.json({ ok: true, data: { key, value } });
1425
+ return c.json({
1426
+ ok: true,
1427
+ data: { key, value: redactMemoryValue(value, runtime.isRedactEnabled()) }
1428
+ });
670
1429
  });
671
- app5.put("/memory/:scope/:key", async (c) => {
1430
+ app4.put("/memory/:scope/:key", async (c) => {
672
1431
  const runtime = c.get("runtime");
673
1432
  const store = runtime.getStateStore();
674
1433
  const scope = c.req.param("scope");
@@ -683,7 +1442,7 @@ app5.put("/memory/:scope/:key", async (c) => {
683
1442
  await store.saveMemory(scope, key, body.value);
684
1443
  return c.json({ ok: true, data: { saved: true } });
685
1444
  });
686
- app5.delete("/memory/:scope/:key", async (c) => {
1445
+ app4.delete("/memory/:scope/:key", async (c) => {
687
1446
  const runtime = c.get("runtime");
688
1447
  const store = runtime.getStateStore();
689
1448
  const scope = c.req.param("scope");
@@ -697,64 +1456,100 @@ app5.delete("/memory/:scope/:key", async (c) => {
697
1456
  await store.deleteMemory(scope, key);
698
1457
  return c.json({ ok: true, data: { deleted: true } });
699
1458
  });
700
- app5.post("/memory/search", async (c) => {
1459
+ app4.post("/memory/search", async (c) => {
701
1460
  return c.json({
702
1461
  ok: true,
703
1462
  data: { results: [], message: "Semantic search requires MemoryManager with vector store" }
704
1463
  });
705
1464
  });
706
- var memory_default = app5;
1465
+ var memory_default = app4;
707
1466
 
708
1467
  // src/server/routes/decisions.ts
709
1468
  var import_hono8 = require("hono");
710
- var app6 = new import_hono8.Hono();
711
- app6.get("/decisions", async (c) => {
1469
+ var app5 = new import_hono8.Hono();
1470
+ app5.get("/decisions", async (c) => {
712
1471
  const runtime = c.get("runtime");
713
1472
  const decisions = await runtime.getPendingDecisions();
714
- return c.json({ ok: true, data: decisions });
1473
+ return c.json({
1474
+ ok: true,
1475
+ data: redactPendingDecisionList(decisions, runtime.isRedactEnabled())
1476
+ });
715
1477
  });
716
- app6.post("/decisions/:executionId/resolve", async (c) => {
1478
+ app5.post("/decisions/:executionId/resolve", async (c) => {
717
1479
  const runtime = c.get("runtime");
718
1480
  const executionId = c.req.param("executionId");
719
1481
  const body = await c.req.json();
720
1482
  await runtime.resolveDecision(executionId, body);
721
1483
  return c.json({ ok: true, data: { resolved: true } });
722
1484
  });
723
- var decisions_default = app6;
1485
+ var decisions_default = app5;
724
1486
 
725
1487
  // src/server/routes/costs.ts
726
1488
  var import_hono9 = require("hono");
727
1489
  function createCostRoutes(costAggregator) {
728
- const app7 = new import_hono9.Hono();
729
- app7.get("/costs", (c) => {
730
- return c.json({ ok: true, data: costAggregator.getData() });
1490
+ const app6 = new import_hono9.Hono();
1491
+ app6.get("/costs", (c) => {
1492
+ if (c.req.query("windows") === "all") {
1493
+ return c.json({ ok: true, data: costAggregator.getAllSnapshots() });
1494
+ }
1495
+ const window = parseWindowParam(c.req.query("window"));
1496
+ return c.json({ ok: true, data: costAggregator.getSnapshot(window) });
731
1497
  });
732
- app7.post("/costs/reset", (c) => {
733
- costAggregator.reset();
734
- return c.json({ ok: true, data: { reset: true } });
1498
+ app6.post("/costs/reset", (c) => {
1499
+ return c.json(
1500
+ {
1501
+ ok: false,
1502
+ error: {
1503
+ code: "GONE",
1504
+ message: "POST /api/costs/reset was removed in @axlsdk/studio 0.15. Cost aggregates are now time-windowed and rebuilt from StateStore history. Use GET /api/costs?window=24h|7d|30d|all to narrow the view instead of resetting."
1505
+ }
1506
+ },
1507
+ 410
1508
+ );
735
1509
  });
736
- return app7;
1510
+ return app6;
737
1511
  }
738
1512
 
739
1513
  // src/server/routes/evals.ts
1514
+ var import_node_crypto = require("crypto");
740
1515
  var import_hono10 = require("hono");
741
- function createEvalRoutes(evalLoader) {
742
- const app7 = new import_hono10.Hono();
743
- app7.get("/evals", async (c) => {
1516
+ function createEvalRoutes(connMgr, evalLoader) {
1517
+ const app6 = new import_hono10.Hono();
1518
+ const activeRuns = /* @__PURE__ */ new Map();
1519
+ app6.get("/evals", async (c) => {
744
1520
  if (evalLoader) await evalLoader();
745
1521
  const runtime = c.get("runtime");
746
1522
  const evals = runtime.getRegisteredEvals();
747
1523
  return c.json({ ok: true, data: evals });
748
1524
  });
749
- app7.get("/evals/history", async (c) => {
1525
+ app6.get("/evals/history", async (c) => {
750
1526
  const runtime = c.get("runtime");
751
1527
  const history = await runtime.getEvalHistory();
752
- return c.json({ ok: true, data: history });
1528
+ return c.json({
1529
+ ok: true,
1530
+ data: redactEvalHistoryList(history, runtime.isRedactEnabled())
1531
+ });
753
1532
  });
754
- app7.post("/evals/:name/run", async (c) => {
1533
+ app6.delete("/evals/history/:id", async (c) => {
1534
+ const runtime = c.get("runtime");
1535
+ const id = c.req.param("id");
1536
+ const deleted = await runtime.deleteEvalResult(id);
1537
+ if (!deleted) {
1538
+ return c.json(
1539
+ {
1540
+ ok: false,
1541
+ error: { code: "NOT_FOUND", message: `Eval history entry "${id}" not found` }
1542
+ },
1543
+ 404
1544
+ );
1545
+ }
1546
+ return c.json({ ok: true, data: { id, deleted: true } });
1547
+ });
1548
+ app6.post("/evals/:name/run", async (c) => {
755
1549
  if (evalLoader) await evalLoader();
756
1550
  const runtime = c.get("runtime");
757
1551
  const name = c.req.param("name");
1552
+ const redactOn = runtime.isRedactEnabled();
758
1553
  const entry = runtime.getRegisteredEval(name);
759
1554
  if (!entry) {
760
1555
  return c.json(
@@ -763,42 +1558,143 @@ function createEvalRoutes(evalLoader) {
763
1558
  );
764
1559
  }
765
1560
  let runs = 1;
1561
+ let stream = false;
1562
+ let captureTraces = false;
766
1563
  try {
767
1564
  const body = await c.req.json().catch(() => ({}));
768
1565
  if (typeof body.runs === "number" && Number.isFinite(body.runs) && body.runs > 1) {
769
1566
  runs = Math.min(Math.floor(body.runs), 25);
770
1567
  }
1568
+ if (body.stream === true) {
1569
+ stream = true;
1570
+ }
1571
+ if (body.captureTraces === true) {
1572
+ captureTraces = true;
1573
+ }
771
1574
  } catch {
772
1575
  }
1576
+ if (stream) {
1577
+ const evalRunId = `eval-${(0, import_node_crypto.randomUUID)()}`;
1578
+ const ac = new AbortController();
1579
+ activeRuns.set(evalRunId, ac);
1580
+ (async () => {
1581
+ try {
1582
+ if (runs > 1) {
1583
+ const runGroupId = (0, import_node_crypto.randomUUID)();
1584
+ const results = [];
1585
+ for (let r = 0; r < runs; r++) {
1586
+ if (ac.signal.aborted) break;
1587
+ const result = await runtime.runRegisteredEval(name, {
1588
+ metadata: { runGroupId, runIndex: r },
1589
+ signal: ac.signal,
1590
+ captureTraces,
1591
+ onProgress: (event) => {
1592
+ if (event.type === "run_done") return;
1593
+ connMgr.broadcastWithWildcard(`eval:${evalRunId}`, {
1594
+ ...event,
1595
+ run: r + 1,
1596
+ totalRuns: runs
1597
+ });
1598
+ }
1599
+ });
1600
+ results.push(result);
1601
+ connMgr.broadcastWithWildcard(`eval:${evalRunId}`, {
1602
+ type: "run_done",
1603
+ run: r + 1,
1604
+ totalRuns: runs
1605
+ });
1606
+ }
1607
+ if (results.length > 0) {
1608
+ connMgr.broadcastWithWildcard(`eval:${evalRunId}`, {
1609
+ type: "done",
1610
+ evalResultId: results[0].id,
1611
+ runGroupId
1612
+ });
1613
+ } else {
1614
+ connMgr.broadcastWithWildcard(`eval:${evalRunId}`, {
1615
+ type: "error",
1616
+ message: "All runs were cancelled"
1617
+ });
1618
+ }
1619
+ } else {
1620
+ const result = await runtime.runRegisteredEval(name, {
1621
+ signal: ac.signal,
1622
+ captureTraces,
1623
+ onProgress: (event) => {
1624
+ if (event.type === "run_done") return;
1625
+ connMgr.broadcastWithWildcard(`eval:${evalRunId}`, event);
1626
+ }
1627
+ });
1628
+ connMgr.broadcastWithWildcard(`eval:${evalRunId}`, {
1629
+ type: "done",
1630
+ evalResultId: result.id
1631
+ });
1632
+ }
1633
+ } catch (err) {
1634
+ connMgr.broadcastWithWildcard(`eval:${evalRunId}`, {
1635
+ type: "error",
1636
+ message: redactErrorMessage(err, redactOn)
1637
+ });
1638
+ } finally {
1639
+ activeRuns.delete(evalRunId);
1640
+ }
1641
+ })();
1642
+ return c.json({ ok: true, data: { evalRunId } });
1643
+ }
773
1644
  try {
774
1645
  if (runs > 1) {
775
- const { randomUUID } = await import("crypto");
776
1646
  const { aggregateRuns } = await import("@axlsdk/eval");
777
- const runGroupId = randomUUID();
1647
+ const runGroupId = (0, import_node_crypto.randomUUID)();
778
1648
  const results = [];
779
1649
  for (let r = 0; r < runs; r++) {
780
1650
  const result2 = await runtime.runRegisteredEval(name, {
781
- metadata: { runGroupId, runIndex: r }
1651
+ metadata: { runGroupId, runIndex: r },
1652
+ captureTraces
782
1653
  });
783
1654
  results.push(result2);
784
1655
  }
785
1656
  const typedResults = results;
786
1657
  const aggregate = aggregateRuns(typedResults);
787
1658
  const first = typedResults[0];
788
- const result = { ...first, _multiRun: { aggregate, allRuns: typedResults } };
789
- return c.json({ ok: true, data: result });
1659
+ const result = {
1660
+ ...first,
1661
+ _multiRun: { aggregate, allRuns: typedResults }
1662
+ };
1663
+ return c.json({
1664
+ ok: true,
1665
+ data: redactEvalResult(result, redactOn)
1666
+ });
790
1667
  } else {
791
- const result = await runtime.runRegisteredEval(name);
792
- return c.json({ ok: true, data: result });
1668
+ const result = await runtime.runRegisteredEval(name, { captureTraces });
1669
+ return c.json({
1670
+ ok: true,
1671
+ data: redactEvalResult(result, redactOn)
1672
+ });
793
1673
  }
794
1674
  } catch (err) {
795
- const message = err instanceof Error ? err.message : String(err);
796
- return c.json({ ok: false, error: { code: "EVAL_ERROR", message } }, 400);
1675
+ return c.json(
1676
+ { ok: false, error: { code: "EVAL_ERROR", message: redactErrorMessage(err, redactOn) } },
1677
+ 400
1678
+ );
797
1679
  }
798
1680
  });
799
- app7.post("/evals/:name/rescore", async (c) => {
1681
+ app6.post("/evals/runs/:evalRunId/cancel", (c) => {
1682
+ const evalRunId = c.req.param("evalRunId");
1683
+ const ac = activeRuns.get(evalRunId);
1684
+ if (!ac) {
1685
+ return c.json(
1686
+ { ok: false, error: { code: "NOT_FOUND", message: "No active eval run found" } },
1687
+ 404
1688
+ );
1689
+ }
1690
+ ac.abort();
1691
+ activeRuns.delete(evalRunId);
1692
+ return c.json({ ok: true, data: { cancelled: true } });
1693
+ });
1694
+ app6.post("/evals/:name/rescore", async (c) => {
800
1695
  if (evalLoader) await evalLoader();
801
1696
  const runtime = c.get("runtime");
1697
+ const redactOn = runtime.isRedactEnabled();
802
1698
  const name = c.req.param("name");
803
1699
  const body = await c.req.json();
804
1700
  if (!body.resultId || typeof body.resultId !== "string") {
@@ -836,31 +1732,167 @@ function createEvalRoutes(evalLoader) {
836
1732
  timestamp: Date.now(),
837
1733
  data: result
838
1734
  });
839
- return c.json({ ok: true, data: result });
1735
+ return c.json({
1736
+ ok: true,
1737
+ data: redactEvalResult(result, redactOn)
1738
+ });
840
1739
  } catch (err) {
841
- const message = err instanceof Error ? err.message : String(err);
842
- return c.json({ ok: false, error: { code: "EVAL_ERROR", message } }, 400);
1740
+ return c.json(
1741
+ { ok: false, error: { code: "EVAL_ERROR", message: redactErrorMessage(err, redactOn) } },
1742
+ 400
1743
+ );
843
1744
  }
844
1745
  });
845
- app7.post("/evals/compare", async (c) => {
1746
+ app6.post("/evals/compare", async (c) => {
846
1747
  const runtime = c.get("runtime");
1748
+ const redactOn = runtime.isRedactEnabled();
847
1749
  const body = await c.req.json();
1750
+ const validateIdParam = (v, name) => {
1751
+ if (typeof v === "string") return v === "" ? `${name} must be non-empty` : null;
1752
+ if (Array.isArray(v)) {
1753
+ if (v.length === 0) return `${name} must be a non-empty array`;
1754
+ for (const elem of v) {
1755
+ if (typeof elem !== "string" || elem === "") {
1756
+ return `${name} array must contain only non-empty strings`;
1757
+ }
1758
+ }
1759
+ return null;
1760
+ }
1761
+ return `${name} is required (string or string[])`;
1762
+ };
1763
+ const baselineErr = validateIdParam(body.baselineId, "baselineId");
1764
+ const candidateErr = validateIdParam(body.candidateId, "candidateId");
1765
+ if (baselineErr || candidateErr) {
1766
+ return c.json(
1767
+ {
1768
+ ok: false,
1769
+ error: {
1770
+ code: "BAD_REQUEST",
1771
+ message: [baselineErr, candidateErr].filter(Boolean).join("; ")
1772
+ }
1773
+ },
1774
+ 400
1775
+ );
1776
+ }
1777
+ const history = await runtime.getEvalHistory();
1778
+ const byId = new Map(history.map((h) => [h.id, h.data]));
1779
+ const missing = [];
1780
+ const resolveOne = (id) => {
1781
+ const data = byId.get(id);
1782
+ if (!data) missing.push(id);
1783
+ return data;
1784
+ };
1785
+ const resolveSelection = (idOrIds) => {
1786
+ if (Array.isArray(idOrIds)) {
1787
+ const unique = Array.from(new Set(idOrIds));
1788
+ if (unique.length === 1) return resolveOne(unique[0]);
1789
+ const results = [];
1790
+ for (const id of unique) {
1791
+ const data = resolveOne(id);
1792
+ if (data) results.push(data);
1793
+ }
1794
+ return results;
1795
+ }
1796
+ return resolveOne(idOrIds);
1797
+ };
1798
+ const baseline = resolveSelection(body.baselineId);
1799
+ const candidate = resolveSelection(body.candidateId);
1800
+ if (missing.length > 0) {
1801
+ return c.json(
1802
+ {
1803
+ ok: false,
1804
+ error: {
1805
+ code: "NOT_FOUND",
1806
+ message: `Eval result(s) not found in history: ${missing.join(", ")}`
1807
+ }
1808
+ },
1809
+ 404
1810
+ );
1811
+ }
848
1812
  try {
849
- const result = await runtime.evalCompare(body.baseline, body.candidate, body.options);
1813
+ const result = await runtime.evalCompare(baseline, candidate, body.options);
850
1814
  return c.json({ ok: true, data: result });
851
1815
  } catch (err) {
852
- const message = err instanceof Error ? err.message : String(err);
853
- return c.json({ ok: false, error: { code: "EVAL_ERROR", message } }, 400);
1816
+ return c.json(
1817
+ {
1818
+ ok: false,
1819
+ error: { code: "COMPARE_FAILED", message: redactErrorMessage(err, redactOn) }
1820
+ },
1821
+ 400
1822
+ );
1823
+ }
1824
+ });
1825
+ app6.post("/evals/import", async (c) => {
1826
+ const runtime = c.get("runtime");
1827
+ const body = await c.req.json();
1828
+ const bad = (message) => c.json({ ok: false, error: { code: "BAD_REQUEST", message } }, 400);
1829
+ if (!body.result || typeof body.result !== "object") {
1830
+ return bad("result is required");
1831
+ }
1832
+ const result = body.result;
1833
+ if (!Array.isArray(result.items)) {
1834
+ return bad("result.items must be an array");
1835
+ }
1836
+ if (typeof result.summary !== "object" || result.summary == null) {
1837
+ return bad("result.summary must be an object");
1838
+ }
1839
+ if (typeof result.dataset !== "string" || !result.dataset) {
1840
+ return bad("result.dataset must be a non-empty string (required for compare)");
1841
+ }
1842
+ const summary = result.summary;
1843
+ if (typeof summary.scorers !== "object" || summary.scorers == null) {
1844
+ return bad("result.summary.scorers must be an object");
854
1845
  }
1846
+ const summaryScorerNames = Object.keys(summary.scorers);
1847
+ const items = result.items;
1848
+ const summaryScorerSet = new Set(summaryScorerNames);
1849
+ const uncoveredAcrossItems = /* @__PURE__ */ new Set();
1850
+ for (const item of items) {
1851
+ const itemScores = item?.scores;
1852
+ if (itemScores && typeof itemScores === "object") {
1853
+ for (const name of Object.keys(itemScores)) {
1854
+ if (!summaryScorerSet.has(name)) uncoveredAcrossItems.add(name);
1855
+ }
1856
+ }
1857
+ }
1858
+ if (uncoveredAcrossItems.size > 0) {
1859
+ return bad(
1860
+ `item scores reference scorer(s) not in summary.scorers: ${[...uncoveredAcrossItems].join(", ")}`
1861
+ );
1862
+ }
1863
+ const trim = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
1864
+ const metadataObj = typeof result.metadata === "object" && result.metadata != null ? result.metadata : {};
1865
+ const workflowsFromMeta = Array.isArray(metadataObj.workflows) ? metadataObj.workflows : [];
1866
+ const primaryWorkflow = workflowsFromMeta.find((w) => typeof w === "string");
1867
+ const evalName = trim(body.eval) ?? trim(primaryWorkflow) ?? // Legacy fallback: pre-0.14 CLI artifacts had workflow at the top level.
1868
+ trim(result.workflow) ?? "imported";
1869
+ const id = (0, import_node_crypto.randomUUID)();
1870
+ const timestamp = Date.now();
1871
+ const imported = {
1872
+ ...result,
1873
+ id,
1874
+ metadata: typeof result.metadata === "object" && result.metadata != null ? result.metadata : {}
1875
+ };
1876
+ await runtime.saveEvalResult({
1877
+ id,
1878
+ eval: evalName,
1879
+ timestamp,
1880
+ data: imported
1881
+ });
1882
+ return c.json({ ok: true, data: { id, eval: evalName, timestamp } });
855
1883
  });
856
- return app7;
1884
+ function closeActiveRuns() {
1885
+ for (const ac of activeRuns.values()) ac.abort();
1886
+ activeRuns.clear();
1887
+ }
1888
+ return { app: app6, closeActiveRuns };
857
1889
  }
858
1890
 
859
1891
  // src/server/routes/playground.ts
860
1892
  var import_hono11 = require("hono");
861
1893
  function createPlaygroundRoutes(connMgr) {
862
- const app7 = new import_hono11.Hono();
863
- app7.post("/playground/chat", async (c) => {
1894
+ const app6 = new import_hono11.Hono();
1895
+ app6.post("/playground/chat", async (c) => {
864
1896
  const runtime = c.get("runtime");
865
1897
  const body = await c.req.json();
866
1898
  if (!body.message || typeof body.message !== "string" || !body.message.trim()) {
@@ -891,13 +1923,14 @@ function createPlaygroundRoutes(connMgr) {
891
1923
  const store = runtime.getStateStore();
892
1924
  const history = await store.getSession(sessionId);
893
1925
  history.push({ role: "user", content: body.message });
1926
+ const redactOn = runtime.isRedactEnabled();
1927
+ const broadcast = (event) => {
1928
+ connMgr.broadcastWithWildcard(`execution:${executionId}`, redactStreamEvent(event, redactOn));
1929
+ };
894
1930
  const ctx = runtime.createContext({
895
1931
  sessionHistory: history,
896
1932
  onToken: (token) => {
897
- connMgr.broadcastWithWildcard(`execution:${executionId}`, {
898
- type: "token",
899
- data: token
900
- });
1933
+ broadcast({ type: "token", data: token });
901
1934
  }
902
1935
  });
903
1936
  (async () => {
@@ -906,12 +1939,9 @@ function createPlaygroundRoutes(connMgr) {
906
1939
  const resultText = typeof result === "string" ? result : JSON.stringify(result);
907
1940
  history.push({ role: "assistant", content: resultText });
908
1941
  await store.saveSession(sessionId, history);
909
- connMgr.broadcastWithWildcard(`execution:${executionId}`, {
910
- type: "done",
911
- data: resultText
912
- });
1942
+ broadcast({ type: "done", data: resultText });
913
1943
  } catch (err) {
914
- connMgr.broadcastWithWildcard(`execution:${executionId}`, {
1944
+ broadcast({
915
1945
  type: "error",
916
1946
  message: err instanceof Error ? err.message : String(err)
917
1947
  });
@@ -922,42 +1952,111 @@ function createPlaygroundRoutes(connMgr) {
922
1952
  data: { sessionId, executionId, streaming: true }
923
1953
  });
924
1954
  });
925
- return app7;
1955
+ return app6;
1956
+ }
1957
+
1958
+ // src/server/routes/eval-trends.ts
1959
+ var import_hono12 = require("hono");
1960
+ function createEvalTrendsRoutes(aggregator) {
1961
+ const app6 = new import_hono12.Hono();
1962
+ app6.get("/eval-trends", (c) => {
1963
+ const window = parseWindowParam(c.req.query("window"));
1964
+ return c.json({ ok: true, data: aggregator.getSnapshot(window) });
1965
+ });
1966
+ return app6;
1967
+ }
1968
+
1969
+ // src/server/routes/workflow-stats.ts
1970
+ var import_hono13 = require("hono");
1971
+ function createWorkflowStatsRoutes(aggregator) {
1972
+ const app6 = new import_hono13.Hono();
1973
+ app6.get("/workflow-stats", (c) => {
1974
+ const window = parseWindowParam(c.req.query("window"));
1975
+ return c.json({ ok: true, data: enrichWorkflowStats(aggregator.getSnapshot(window)) });
1976
+ });
1977
+ return app6;
1978
+ }
1979
+
1980
+ // src/server/routes/trace-stats.ts
1981
+ var import_hono14 = require("hono");
1982
+ function createTraceStatsRoutes(aggregator) {
1983
+ const app6 = new import_hono14.Hono();
1984
+ app6.get("/trace-stats", (c) => {
1985
+ const window = parseWindowParam(c.req.query("window"));
1986
+ return c.json({ ok: true, data: aggregator.getSnapshot(window) });
1987
+ });
1988
+ return app6;
926
1989
  }
927
1990
 
928
1991
  // src/server/index.ts
929
1992
  function createServer(options) {
930
1993
  const { runtime, staticRoot, basePath = "", readOnly = false } = options;
931
- const app7 = new import_hono12.Hono();
1994
+ const app6 = new import_hono15.Hono();
932
1995
  const connMgr = new ConnectionManager();
933
- const costAggregator = new CostAggregator(connMgr);
1996
+ const windows = ["24h", "7d", "30d", "all"];
1997
+ const costAggregator = new TraceAggregator({
1998
+ runtime,
1999
+ connMgr,
2000
+ channel: "costs",
2001
+ reducer: reduceCost,
2002
+ emptyState: emptyCostData,
2003
+ windows
2004
+ });
2005
+ const workflowStatsAggregator = new ExecutionAggregator({
2006
+ runtime,
2007
+ connMgr,
2008
+ channel: "workflow-stats",
2009
+ reducer: reduceWorkflowStats,
2010
+ emptyState: emptyWorkflowStatsData,
2011
+ windows,
2012
+ broadcastTransform: enrichWorkflowStats
2013
+ });
2014
+ const traceStatsAggregator = new TraceAggregator({
2015
+ runtime,
2016
+ connMgr,
2017
+ channel: "trace-stats",
2018
+ reducer: reduceTraceStats,
2019
+ emptyState: emptyTraceStatsData,
2020
+ windows
2021
+ });
2022
+ const evalTrendsAggregator = new EvalAggregator({
2023
+ runtime,
2024
+ connMgr,
2025
+ channel: "eval-trends",
2026
+ reducer: reduceEvalTrends,
2027
+ emptyState: emptyEvalTrendData,
2028
+ windows
2029
+ });
934
2030
  if (options.cors !== false) {
935
- app7.use("*", (0, import_cors.cors)());
2031
+ app6.use("*", (0, import_cors.cors)());
936
2032
  }
937
- app7.use("*", errorHandler);
938
- app7.use("*", async (c, next) => {
2033
+ app6.use("*", errorHandler);
2034
+ app6.use("*", async (c, next) => {
939
2035
  c.set("runtime", runtime);
940
2036
  await next();
941
2037
  });
942
2038
  if (readOnly) {
943
2039
  const blocked = [
944
- "POST /api/workflows",
945
- "POST /api/executions",
946
- "POST /api/sessions",
947
- "DELETE /api/sessions",
948
- "PUT /api/memory",
949
- "DELETE /api/memory",
950
- "POST /api/decisions",
951
- "POST /api/costs",
952
- "POST /api/tools",
953
- "POST /api/evals",
954
- "POST /api/playground"
2040
+ /^POST \/api\/workflows(\/|$)/,
2041
+ /^POST \/api\/executions(\/|$)/,
2042
+ /^POST \/api\/sessions(\/|$)/,
2043
+ /^DELETE \/api\/sessions(\/|$)/,
2044
+ /^PUT \/api\/memory(\/|$)/,
2045
+ /^DELETE \/api\/memory(\/|$)/,
2046
+ /^POST \/api\/decisions(\/|$)/,
2047
+ /^POST \/api\/tools(\/|$)/,
2048
+ /^POST \/api\/evals\/import$/,
2049
+ /^POST \/api\/evals\/[^/]+\/run$/,
2050
+ /^POST \/api\/evals\/[^/]+\/rescore$/,
2051
+ /^POST \/api\/evals\/runs\/[^/]+\/cancel$/,
2052
+ /^DELETE \/api\/evals\/history\/[^/]+$/,
2053
+ /^POST \/api\/playground(\/|$)/
955
2054
  ];
956
- app7.use("/api/*", async (c, next) => {
2055
+ app6.use("/api/*", async (c, next) => {
957
2056
  const apiIdx = c.req.path.indexOf("/api/");
958
2057
  const apiPath = apiIdx >= 0 ? c.req.path.slice(apiIdx) : c.req.path;
959
2058
  const key = `${c.req.method} ${apiPath}`;
960
- if (blocked.some((b) => key.startsWith(b))) {
2059
+ if (blocked.some((re) => re.test(key))) {
961
2060
  return c.json(
962
2061
  {
963
2062
  ok: false,
@@ -969,8 +2068,8 @@ function createServer(options) {
969
2068
  await next();
970
2069
  });
971
2070
  }
972
- const api = new import_hono12.Hono();
973
- api.route("/", health_default);
2071
+ const api = new import_hono15.Hono();
2072
+ api.route("/", createHealthRoutes(readOnly));
974
2073
  api.route("/", createWorkflowRoutes(connMgr));
975
2074
  api.route("/", executions_default);
976
2075
  api.route("/", createSessionRoutes(connMgr));
@@ -979,20 +2078,29 @@ function createServer(options) {
979
2078
  api.route("/", memory_default);
980
2079
  api.route("/", decisions_default);
981
2080
  api.route("/", createCostRoutes(costAggregator));
982
- api.route("/", createEvalRoutes(options.evalLoader));
2081
+ api.route("/", createEvalTrendsRoutes(evalTrendsAggregator));
2082
+ api.route("/", createWorkflowStatsRoutes(workflowStatsAggregator));
2083
+ api.route("/", createTraceStatsRoutes(traceStatsAggregator));
2084
+ const { app: evalApp, closeActiveRuns } = createEvalRoutes(connMgr, options.evalLoader);
2085
+ api.route("/", evalApp);
983
2086
  api.route("/", createPlaygroundRoutes(connMgr));
984
- app7.route("/api", api);
2087
+ app6.route("/api", api);
985
2088
  const traceListener = (event) => {
986
2089
  const traceEvent = event;
987
2090
  if (traceEvent.executionId) {
988
2091
  connMgr.broadcastWithWildcard(`trace:${traceEvent.executionId}`, traceEvent);
989
2092
  }
990
- costAggregator.onTrace(traceEvent);
991
2093
  if (traceEvent.type === "await_human") {
992
2094
  connMgr.broadcast("decisions", traceEvent);
993
2095
  }
994
2096
  };
995
2097
  runtime.on("trace", traceListener);
2098
+ const aggregatorStartPromise = Promise.all([
2099
+ costAggregator.start(),
2100
+ workflowStatsAggregator.start(),
2101
+ traceStatsAggregator.start(),
2102
+ evalTrendsAggregator.start()
2103
+ ]).catch((err) => console.error("[axl-studio] aggregator start failed:", err));
996
2104
  if (staticRoot) {
997
2105
  const indexPath = (0, import_node_path.resolve)(staticRoot, "index.html");
998
2106
  let spaHtml;
@@ -1022,7 +2130,7 @@ function createServer(options) {
1022
2130
  root: staticRoot,
1023
2131
  rewriteRequestPath: basePath ? (path) => path.startsWith(basePath) ? path.slice(basePath.length) || "/" : path : void 0
1024
2132
  });
1025
- app7.use("/*", async (c, next) => {
2133
+ app6.use("/*", async (c, next) => {
1026
2134
  const reqPath = c.req.path;
1027
2135
  const resolved = basePath && reqPath.startsWith(basePath) ? reqPath.slice(basePath.length) || "/" : reqPath;
1028
2136
  if (resolved === "/" || resolved === "/index.html" || resolved === "/ws") {
@@ -1031,7 +2139,7 @@ function createServer(options) {
1031
2139
  return staticHandler(c, next);
1032
2140
  });
1033
2141
  if (spaHtml) {
1034
- app7.get("*", async (c, next) => {
2142
+ app6.get("*", async (c, next) => {
1035
2143
  const resolved = basePath && c.req.path.startsWith(basePath) ? c.req.path.slice(basePath.length) || "/" : c.req.path;
1036
2144
  if (resolved === "/ws") return next();
1037
2145
  return c.html(spaHtml);
@@ -1039,18 +2147,33 @@ function createServer(options) {
1039
2147
  }
1040
2148
  }
1041
2149
  return {
1042
- app: app7,
2150
+ app: app6,
1043
2151
  connMgr,
1044
2152
  costAggregator,
2153
+ workflowStatsAggregator,
2154
+ traceStatsAggregator,
2155
+ evalTrendsAggregator,
2156
+ aggregatorStartPromise,
1045
2157
  /** Create WS handlers. Call before registering static/SPA routes are reached. */
1046
2158
  createWsHandlers: () => createWsHandlers(connMgr),
1047
- traceListener
2159
+ traceListener,
2160
+ /** Abort all active streaming eval runs. */
2161
+ closeActiveRuns,
2162
+ /** Close all aggregators (clear intervals and unsubscribe listeners). */
2163
+ closeAggregators: () => {
2164
+ costAggregator.close();
2165
+ workflowStatsAggregator.close();
2166
+ traceStatsAggregator.close();
2167
+ evalTrendsAggregator.close();
2168
+ }
1048
2169
  };
1049
2170
  }
1050
2171
  // Annotate the CommonJS export names for ESM import in node:
1051
2172
  0 && (module.exports = {
1052
2173
  ConnectionManager,
1053
- CostAggregator,
2174
+ EvalAggregator,
2175
+ ExecutionAggregator,
2176
+ TraceAggregator,
1054
2177
  createServer
1055
2178
  });
1056
2179
  //# sourceMappingURL=index.cjs.map