@axlsdk/studio 0.19.1 → 0.20.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.
package/README.md CHANGED
@@ -89,7 +89,9 @@ The `--conditions` flag is useful in monorepos where workspace packages use the
89
89
 
90
90
  ## Panels
91
91
 
92
- Eight panels, all live over WebSocket. The screenshots below are placeholders — see [`docs/assets/CAPTURE.md`](../../docs/assets/CAPTURE.md) for how to record the real thing.
92
+ Eight panels, all live over WebSocket. The captures below use representative seeded
93
+ runs; see [`docs/assets/CAPTURE.md`](../../docs/assets/CAPTURE.md) for how to regenerate
94
+ them.
93
95
 
94
96
  ### Agent Playground
95
97
 
@@ -101,7 +103,12 @@ Chat with any registered agent in real time. Streaming tokens, tool calls with e
101
103
 
102
104
  ### Trace Explorer
103
105
 
104
- Waterfall visualization of execution traces. Filter by type, agent, or tool; see token counts, cost per step, and duration. The Stats view shows the event-type distribution, top tools, and retry stacks.
106
+ Waterfall visualization of execution traces. Filter by type, agent, or tool; see token
107
+ counts, cost per step, and duration. Tool rows distinguish calls that succeeded,
108
+ failed, were denied, were cancelled, or were rejected before they started. Studio also
109
+ keeps older trace history readable and identifies incomplete runs instead of presenting
110
+ missing events as a complete story. The Stats view shows the event-type distribution,
111
+ top tools, and retry stacks.
105
112
 
106
113
  > **Studio vs `ctx.events`.** Studio consumes the same `AxlEvent` firehose via `runtime.on('trace', …)` — every event from every execution. Inside a workflow handler, `ctx.events` is the in-handler counterpart (per-context, scoped to the current workflow). The two coexist: Studio is for cross-execution observability and replay; `ctx.events` is for in-handler streaming UIs. See [`docs/observability.md`](../../docs/observability.md#observation-paths).
107
114
 
@@ -115,7 +122,14 @@ Track spending across agents, models, workflows, and embedders with time-window
115
122
 
116
123
  ### Eval Runner
117
124
 
118
- Run evaluations from the UI, watch items stream in, and drill into per-item scores, timing, cost, and LLM-judge reasoning. Compare two runs (baseline vs candidate) with bootstrap-CI significance, a score-distribution chart, and an item-level diff table. The History tab groups multi-run results and tracks mean scores across runs. Toggle **Capture traces** to render each item's events inline. Requires `@axlsdk/eval` as an optional peer dependency.
125
+ Run evaluations from the UI, watch items stream in, and drill into per-item scores,
126
+ timing, cost, and LLM-judge reasoning. Compare two runs (baseline vs candidate) with
127
+ bootstrap-CI significance, a score-distribution chart, and an item-level diff table.
128
+ The History tab groups multi-run results and tracks mean scores across runs. Toggle
129
+ **Capture traces** to render each item's events inline. For tool-using workflows, those
130
+ events show whether a call failed, was denied, was cancelled, or never started, giving
131
+ you better evidence for debugging and custom scorers. Studio does not turn those states
132
+ into scores automatically. Requires `@axlsdk/eval` as an optional peer dependency.
119
133
 
120
134
  Amber banners flag runs you shouldn't fully trust — a scorer that failed on too many items, a subset run, or annotations dropped by the dataset schema — so a thinned or misconfigured run can't quietly look clean.
121
135
 
@@ -12,15 +12,14 @@ import { cors } from "hono/cors";
12
12
  import { serveStatic } from "@hono/node-server/serve-static";
13
13
 
14
14
  // src/server/redact.ts
15
- import { redactEvent } from "@axlsdk/axl";
15
+ import { redactHistoricalEvent } from "@axlsdk/axl";
16
16
  var REDACTED = "[redacted]";
17
17
  var SAFE_ERROR_NAMES = /* @__PURE__ */ new Set([
18
18
  "QuorumNotMet",
19
19
  "NoConsensus",
20
20
  "TimeoutError",
21
21
  "MaxTurnsError",
22
- "BudgetExceededError",
23
- "ToolDenied"
22
+ "BudgetExceededError"
24
23
  ]);
25
24
  function redactErrorMessage(err, redact) {
26
25
  const raw = err instanceof Error ? err.message : String(err);
@@ -88,7 +87,7 @@ function redactSessionHistory(history, redact) {
88
87
  }
89
88
  function redactStreamEvent(event, redact) {
90
89
  if (!redact) return event;
91
- return redactEvent(event);
90
+ return redactHistoricalEvent(event);
92
91
  }
93
92
  function redactEvalItem(item) {
94
93
  const scrubbed = {
@@ -151,31 +150,29 @@ function redactPendingDecisionList(decisions, redact) {
151
150
  }
152
151
 
153
152
  // src/server/middleware/error-handler.ts
154
- async function errorHandler(c, next) {
155
- try {
156
- await next();
157
- } catch (err) {
158
- const rawMessage = err instanceof Error ? err.message : String(err);
159
- const code = err.code ?? "INTERNAL_ERROR";
160
- let status = 500;
161
- if ("status" in err) {
162
- const errStatus = err.status;
163
- if (typeof errStatus === "number" && errStatus >= 400 && errStatus < 600) {
164
- status = errStatus;
165
- }
166
- } else if (code === "NOT_FOUND" || rawMessage.includes("not found") || rawMessage.includes("not registered")) {
167
- status = 404;
168
- } else if (code === "VALIDATION_ERROR" || rawMessage.includes("Expected") || rawMessage.includes("invalid")) {
169
- status = 400;
170
- }
171
- const runtime = c.get("runtime");
172
- const redactOn = runtime?.isRedactEnabled?.() ?? false;
173
- const body = {
174
- ok: false,
175
- error: { code, message: redactErrorMessage(err, redactOn) }
176
- };
177
- return c.json(body, status);
153
+ function errorHandler(err, c) {
154
+ const rawMessage = err.message;
155
+ const code = err.code ?? "INTERNAL_ERROR";
156
+ let status = 500;
157
+ if ("status" in err) {
158
+ const errStatus = err.status;
159
+ if (typeof errStatus === "number" && errStatus >= 400 && errStatus < 600) {
160
+ status = errStatus;
161
+ }
162
+ } else if (code === "CROSS_PROCESS_RESUME_UNSUPPORTED") {
163
+ status = 409;
164
+ } else if (code === "NOT_FOUND" || code === "PENDING_DECISION_NOT_FOUND" || rawMessage.includes("not found") || rawMessage.includes("not registered")) {
165
+ status = 404;
166
+ } else if (code === "VALIDATION_ERROR" || code === "INVALID_HUMAN_DECISION" || rawMessage.includes("Expected") || rawMessage.includes("invalid")) {
167
+ status = 400;
178
168
  }
169
+ const runtime = c.get("runtime");
170
+ const redactOn = runtime?.isRedactEnabled?.() ?? false;
171
+ const body = {
172
+ ok: false,
173
+ error: { code, message: redactErrorMessage(err, redactOn) }
174
+ };
175
+ return c.json(body, status);
179
176
  }
180
177
 
181
178
  // src/server/ws/connection-manager.ts
@@ -1091,19 +1088,58 @@ function reduceTraceStats(acc, event) {
1091
1088
  const eventTypeCounts = { ...acc.eventTypeCounts };
1092
1089
  eventTypeCounts[event.type] = (eventTypeCounts[event.type] ?? 0) + 1;
1093
1090
  const byTool = { ...acc.byTool };
1094
- if (event.type === "tool_call_end" || event.type === "tool_denied" || event.type === "tool_approval") {
1091
+ if (event.type === "tool_call_start" || event.type === "tool_call_end" || event.type === "tool_call_rejected" || event.type === "tool_denied" || event.type === "tool_approval") {
1095
1092
  const toolName = event.tool;
1096
- const prev = byTool[toolName] ?? { calls: 0, denied: 0, approved: 0 };
1097
- const isDeniedEvent = event.type === "tool_denied";
1098
- const isApprovalEvent = event.type === "tool_approval";
1099
- const eventData = isDeniedEvent || isApprovalEvent ? event.data : void 0;
1100
- const isApproved = isDeniedEvent && eventData?.approved === true || isApprovalEvent && eventData?.approved === true;
1101
- const isDenied = isDeniedEvent && !eventData?.approved || isApprovalEvent && eventData?.approved === false;
1102
- byTool[toolName] = {
1103
- calls: prev.calls + (event.type === "tool_call_end" ? 1 : 0),
1104
- denied: prev.denied + (isDenied ? 1 : 0),
1105
- approved: prev.approved + (isApproved ? 1 : 0)
1093
+ const prev = byTool[toolName] ?? {
1094
+ accepted: 0,
1095
+ succeeded: 0,
1096
+ failed: 0,
1097
+ failedByPhase: {},
1098
+ denied: 0,
1099
+ cancelled: 0,
1100
+ rejected: 0,
1101
+ approved: 0,
1102
+ legacy: { calls: 0, denied: 0, approved: 0 }
1103
+ };
1104
+ const next = {
1105
+ ...prev,
1106
+ failedByPhase: { ...prev.failedByPhase },
1107
+ legacy: { ...prev.legacy }
1106
1108
  };
1109
+ if (event.schemaVersion === 2) {
1110
+ if (event.type === "tool_call_start") next.accepted++;
1111
+ if (event.type === "tool_call_rejected") next.rejected++;
1112
+ if (event.type === "tool_approval" && event.data?.approved) next.approved++;
1113
+ if (event.type === "tool_call_end") {
1114
+ switch (event.data.outcome.status) {
1115
+ case "succeeded":
1116
+ next.succeeded++;
1117
+ break;
1118
+ case "failed": {
1119
+ next.failed++;
1120
+ const phase = event.data.outcome.failure.phase;
1121
+ next.failedByPhase[phase] = (next.failedByPhase[phase] ?? 0) + 1;
1122
+ break;
1123
+ }
1124
+ case "denied":
1125
+ next.denied++;
1126
+ break;
1127
+ case "cancelled":
1128
+ next.cancelled++;
1129
+ break;
1130
+ }
1131
+ }
1132
+ } else if (event.type === "tool_call_end") {
1133
+ next.legacy.calls++;
1134
+ } else if (event.type === "tool_denied") {
1135
+ const approved = event.data?.approved === true;
1136
+ if (approved) next.legacy.approved++;
1137
+ else next.legacy.denied++;
1138
+ } else if (event.type === "tool_approval") {
1139
+ if (event.data?.approved === true) next.legacy.approved++;
1140
+ else if (event.data?.approved === false) next.legacy.denied++;
1141
+ }
1142
+ byTool[toolName] = next;
1107
1143
  }
1108
1144
  const retryByAgent = { ...acc.retryByAgent };
1109
1145
  if (event.agent && event.type === "agent_call_end") {
@@ -1583,7 +1619,14 @@ app4.get("/decisions", async (c) => {
1583
1619
  app4.post("/decisions/:executionId/resolve", async (c) => {
1584
1620
  const runtime = c.get("runtime");
1585
1621
  const executionId = c.req.param("executionId");
1586
- const body = await c.req.json();
1622
+ let body;
1623
+ try {
1624
+ body = await c.req.json();
1625
+ } catch {
1626
+ const error48 = new Error("Invalid JSON request body");
1627
+ Object.assign(error48, { code: "INVALID_HUMAN_DECISION", status: 400 });
1628
+ throw error48;
1629
+ }
1587
1630
  await runtime.resolveDecision(executionId, body);
1588
1631
  return c.json({ ok: true, data: { resolved: true } });
1589
1632
  });
@@ -15923,6 +15966,7 @@ function createPlaygroundRoutes(connMgr) {
15923
15966
  (async () => {
15924
15967
  let stepCounter = Number.MAX_SAFE_INTEGER - 1;
15925
15968
  const terminalFields = () => ({
15969
+ schemaVersion: 2,
15926
15970
  executionId,
15927
15971
  step: stepCounter++,
15928
15972
  timestamp: Date.now()
@@ -16039,7 +16083,7 @@ function createServer(options) {
16039
16083
  if (options.cors !== false) {
16040
16084
  app5.use("*", cors());
16041
16085
  }
16042
- app5.use("*", errorHandler);
16086
+ app5.onError(errorHandler);
16043
16087
  app5.use("*", async (c, next) => {
16044
16088
  c.set("runtime", runtime);
16045
16089
  await next();
@@ -16195,4 +16239,4 @@ export {
16195
16239
  EvalAggregator,
16196
16240
  createServer
16197
16241
  };
16198
- //# sourceMappingURL=chunk-AYUKXMYW.js.map
16242
+ //# sourceMappingURL=chunk-KQ3HBRTC.js.map