@axlsdk/studio 0.13.8 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -329,27 +329,30 @@ var CostAggregator = class {
329
329
 
330
330
  // src/server/routes/health.ts
331
331
  var import_hono = require("hono");
332
- var app = new import_hono.Hono();
333
- app.get("/health", (c) => {
334
- const runtime = c.get("runtime");
335
- return c.json({
336
- ok: true,
337
- data: {
338
- status: "healthy",
339
- workflows: runtime.getWorkflowNames().length,
340
- agents: runtime.getAgents().length,
341
- tools: runtime.getTools().length
342
- }
332
+ function createHealthRoutes(readOnly) {
333
+ const app6 = new import_hono.Hono();
334
+ app6.get("/health", (c) => {
335
+ const runtime = c.get("runtime");
336
+ return c.json({
337
+ ok: true,
338
+ data: {
339
+ status: "healthy",
340
+ readOnly,
341
+ workflows: runtime.getWorkflowNames().length,
342
+ agents: runtime.getAgents().length,
343
+ tools: runtime.getTools().length
344
+ }
345
+ });
343
346
  });
344
- });
345
- var health_default = app;
347
+ return app6;
348
+ }
346
349
 
347
350
  // src/server/routes/workflows.ts
348
351
  var import_hono2 = require("hono");
349
352
  var import_axl = require("@axlsdk/axl");
350
353
  function createWorkflowRoutes(connMgr) {
351
- const app7 = new import_hono2.Hono();
352
- app7.get("/workflows", (c) => {
354
+ const app6 = new import_hono2.Hono();
355
+ app6.get("/workflows", (c) => {
353
356
  const runtime = c.get("runtime");
354
357
  const workflows = runtime.getWorkflows().map((w) => ({
355
358
  name: w.name,
@@ -358,7 +361,7 @@ function createWorkflowRoutes(connMgr) {
358
361
  }));
359
362
  return c.json({ ok: true, data: workflows });
360
363
  });
361
- app7.get("/workflows/:name", (c) => {
364
+ app6.get("/workflows/:name", (c) => {
362
365
  const runtime = c.get("runtime");
363
366
  const name = c.req.param("name");
364
367
  const workflow = runtime.getWorkflow(name);
@@ -377,7 +380,7 @@ function createWorkflowRoutes(connMgr) {
377
380
  }
378
381
  });
379
382
  });
380
- app7.post("/workflows/:name/execute", async (c) => {
383
+ app6.post("/workflows/:name/execute", async (c) => {
381
384
  const runtime = c.get("runtime");
382
385
  const name = c.req.param("name");
383
386
  const workflow = runtime.getWorkflow(name);
@@ -401,18 +404,18 @@ function createWorkflowRoutes(connMgr) {
401
404
  const result = await runtime.execute(name, body.input ?? {}, { metadata: body.metadata });
402
405
  return c.json({ ok: true, data: { result } });
403
406
  });
404
- return app7;
407
+ return app6;
405
408
  }
406
409
 
407
410
  // src/server/routes/executions.ts
408
411
  var import_hono3 = require("hono");
409
- var app2 = new import_hono3.Hono();
410
- app2.get("/executions", async (c) => {
412
+ var app = new import_hono3.Hono();
413
+ app.get("/executions", async (c) => {
411
414
  const runtime = c.get("runtime");
412
415
  const executions = await runtime.getExecutions();
413
416
  return c.json({ ok: true, data: executions });
414
417
  });
415
- app2.get("/executions/:id", async (c) => {
418
+ app.get("/executions/:id", async (c) => {
416
419
  const runtime = c.get("runtime");
417
420
  const id = c.req.param("id");
418
421
  const execution = await runtime.getExecution(id);
@@ -424,19 +427,19 @@ app2.get("/executions/:id", async (c) => {
424
427
  }
425
428
  return c.json({ ok: true, data: execution });
426
429
  });
427
- app2.post("/executions/:id/abort", (c) => {
430
+ app.post("/executions/:id/abort", (c) => {
428
431
  const runtime = c.get("runtime");
429
432
  const id = c.req.param("id");
430
433
  runtime.abort(id);
431
434
  return c.json({ ok: true, data: { aborted: true } });
432
435
  });
433
- var executions_default = app2;
436
+ var executions_default = app;
434
437
 
435
438
  // src/server/routes/sessions.ts
436
439
  var import_hono4 = require("hono");
437
440
  function createSessionRoutes(connMgr) {
438
- const app7 = new import_hono4.Hono();
439
- app7.get("/sessions", async (c) => {
441
+ const app6 = new import_hono4.Hono();
442
+ app6.get("/sessions", async (c) => {
440
443
  const runtime = c.get("runtime");
441
444
  const store = runtime.getStateStore();
442
445
  if (!store.listSessions) {
@@ -450,7 +453,7 @@ function createSessionRoutes(connMgr) {
450
453
  }
451
454
  return c.json({ ok: true, data: sessions });
452
455
  });
453
- app7.get("/sessions/:id", async (c) => {
456
+ app6.get("/sessions/:id", async (c) => {
454
457
  const runtime = c.get("runtime");
455
458
  const store = runtime.getStateStore();
456
459
  const id = c.req.param("id");
@@ -458,7 +461,7 @@ function createSessionRoutes(connMgr) {
458
461
  const handoffHistory = await store.getSessionMeta(id, "handoffHistory");
459
462
  return c.json({ ok: true, data: { id, history, handoffHistory: handoffHistory ?? [] } });
460
463
  });
461
- app7.post("/sessions/:id/send", async (c) => {
464
+ app6.post("/sessions/:id/send", async (c) => {
462
465
  const runtime = c.get("runtime");
463
466
  const id = c.req.param("id");
464
467
  const body = await c.req.json();
@@ -466,7 +469,7 @@ function createSessionRoutes(connMgr) {
466
469
  const result = await session.send(body.workflow, body.message);
467
470
  return c.json({ ok: true, data: { result } });
468
471
  });
469
- app7.post("/sessions/:id/stream", async (c) => {
472
+ app6.post("/sessions/:id/stream", async (c) => {
470
473
  const runtime = c.get("runtime");
471
474
  const id = c.req.param("id");
472
475
  const body = await c.req.json();
@@ -480,21 +483,21 @@ function createSessionRoutes(connMgr) {
480
483
  })();
481
484
  return c.json({ ok: true, data: { executionId, streaming: true } });
482
485
  });
483
- app7.delete("/sessions/:id", async (c) => {
486
+ app6.delete("/sessions/:id", async (c) => {
484
487
  const runtime = c.get("runtime");
485
488
  const store = runtime.getStateStore();
486
489
  const id = c.req.param("id");
487
490
  await store.deleteSession(id);
488
491
  return c.json({ ok: true, data: { deleted: true } });
489
492
  });
490
- return app7;
493
+ return app6;
491
494
  }
492
495
 
493
496
  // src/server/routes/agents.ts
494
497
  var import_hono5 = require("hono");
495
498
  var import_axl2 = require("@axlsdk/axl");
496
- var app3 = new import_hono5.Hono();
497
- app3.get("/agents", (c) => {
499
+ var app2 = new import_hono5.Hono();
500
+ app2.get("/agents", (c) => {
498
501
  const runtime = c.get("runtime");
499
502
  const agents = runtime.getAgents().map((a) => ({
500
503
  name: a._name,
@@ -513,7 +516,7 @@ app3.get("/agents", (c) => {
513
516
  }));
514
517
  return c.json({ ok: true, data: agents });
515
518
  });
516
- app3.get("/agents/:name", (c) => {
519
+ app2.get("/agents/:name", (c) => {
517
520
  const runtime = c.get("runtime");
518
521
  const name = c.req.param("name");
519
522
  const agent = runtime.getAgent(name);
@@ -569,13 +572,13 @@ app3.get("/agents/:name", (c) => {
569
572
  }
570
573
  });
571
574
  });
572
- var agents_default = app3;
575
+ var agents_default = app2;
573
576
 
574
577
  // src/server/routes/tools.ts
575
578
  var import_hono6 = require("hono");
576
579
  var import_axl3 = require("@axlsdk/axl");
577
- var app4 = new import_hono6.Hono();
578
- app4.get("/tools", (c) => {
580
+ var app3 = new import_hono6.Hono();
581
+ app3.get("/tools", (c) => {
579
582
  const runtime = c.get("runtime");
580
583
  const tools = runtime.getTools().map((t) => ({
581
584
  name: t.name,
@@ -586,7 +589,7 @@ app4.get("/tools", (c) => {
586
589
  }));
587
590
  return c.json({ ok: true, data: tools });
588
591
  });
589
- app4.get("/tools/:name", (c) => {
592
+ app3.get("/tools/:name", (c) => {
590
593
  const runtime = c.get("runtime");
591
594
  const name = c.req.param("name");
592
595
  const tool = runtime.getTool(name);
@@ -613,7 +616,7 @@ app4.get("/tools/:name", (c) => {
613
616
  }
614
617
  });
615
618
  });
616
- app4.post("/tools/:name/test", async (c) => {
619
+ app3.post("/tools/:name/test", async (c) => {
617
620
  const runtime = c.get("runtime");
618
621
  const name = c.req.param("name");
619
622
  const tool = runtime.getTool(name);
@@ -628,12 +631,12 @@ app4.post("/tools/:name/test", async (c) => {
628
631
  const result = await tool.run(ctx, body.input);
629
632
  return c.json({ ok: true, data: { result } });
630
633
  });
631
- var tools_default = app4;
634
+ var tools_default = app3;
632
635
 
633
636
  // src/server/routes/memory.ts
634
637
  var import_hono7 = require("hono");
635
- var app5 = new import_hono7.Hono();
636
- app5.get("/memory/:scope", async (c) => {
638
+ var app4 = new import_hono7.Hono();
639
+ app4.get("/memory/:scope", async (c) => {
637
640
  const runtime = c.get("runtime");
638
641
  const store = runtime.getStateStore();
639
642
  const scope = c.req.param("scope");
@@ -643,7 +646,7 @@ app5.get("/memory/:scope", async (c) => {
643
646
  const entries = await store.getAllMemory(scope);
644
647
  return c.json({ ok: true, data: entries });
645
648
  });
646
- app5.get("/memory/:scope/:key", async (c) => {
649
+ app4.get("/memory/:scope/:key", async (c) => {
647
650
  const runtime = c.get("runtime");
648
651
  const store = runtime.getStateStore();
649
652
  const scope = c.req.param("scope");
@@ -663,7 +666,7 @@ app5.get("/memory/:scope/:key", async (c) => {
663
666
  }
664
667
  return c.json({ ok: true, data: { key, value } });
665
668
  });
666
- app5.put("/memory/:scope/:key", async (c) => {
669
+ app4.put("/memory/:scope/:key", async (c) => {
667
670
  const runtime = c.get("runtime");
668
671
  const store = runtime.getStateStore();
669
672
  const scope = c.req.param("scope");
@@ -678,7 +681,7 @@ app5.put("/memory/:scope/:key", async (c) => {
678
681
  await store.saveMemory(scope, key, body.value);
679
682
  return c.json({ ok: true, data: { saved: true } });
680
683
  });
681
- app5.delete("/memory/:scope/:key", async (c) => {
684
+ app4.delete("/memory/:scope/:key", async (c) => {
682
685
  const runtime = c.get("runtime");
683
686
  const store = runtime.getStateStore();
684
687
  const scope = c.req.param("scope");
@@ -692,61 +695,77 @@ app5.delete("/memory/:scope/:key", async (c) => {
692
695
  await store.deleteMemory(scope, key);
693
696
  return c.json({ ok: true, data: { deleted: true } });
694
697
  });
695
- app5.post("/memory/search", async (c) => {
698
+ app4.post("/memory/search", async (c) => {
696
699
  return c.json({
697
700
  ok: true,
698
701
  data: { results: [], message: "Semantic search requires MemoryManager with vector store" }
699
702
  });
700
703
  });
701
- var memory_default = app5;
704
+ var memory_default = app4;
702
705
 
703
706
  // src/server/routes/decisions.ts
704
707
  var import_hono8 = require("hono");
705
- var app6 = new import_hono8.Hono();
706
- app6.get("/decisions", async (c) => {
708
+ var app5 = new import_hono8.Hono();
709
+ app5.get("/decisions", async (c) => {
707
710
  const runtime = c.get("runtime");
708
711
  const decisions = await runtime.getPendingDecisions();
709
712
  return c.json({ ok: true, data: decisions });
710
713
  });
711
- app6.post("/decisions/:executionId/resolve", async (c) => {
714
+ app5.post("/decisions/:executionId/resolve", async (c) => {
712
715
  const runtime = c.get("runtime");
713
716
  const executionId = c.req.param("executionId");
714
717
  const body = await c.req.json();
715
718
  await runtime.resolveDecision(executionId, body);
716
719
  return c.json({ ok: true, data: { resolved: true } });
717
720
  });
718
- var decisions_default = app6;
721
+ var decisions_default = app5;
719
722
 
720
723
  // src/server/routes/costs.ts
721
724
  var import_hono9 = require("hono");
722
725
  function createCostRoutes(costAggregator) {
723
- const app7 = new import_hono9.Hono();
724
- app7.get("/costs", (c) => {
726
+ const app6 = new import_hono9.Hono();
727
+ app6.get("/costs", (c) => {
725
728
  return c.json({ ok: true, data: costAggregator.getData() });
726
729
  });
727
- app7.post("/costs/reset", (c) => {
730
+ app6.post("/costs/reset", (c) => {
728
731
  costAggregator.reset();
729
732
  return c.json({ ok: true, data: { reset: true } });
730
733
  });
731
- return app7;
734
+ return app6;
732
735
  }
733
736
 
734
737
  // src/server/routes/evals.ts
738
+ var import_node_crypto = require("crypto");
735
739
  var import_hono10 = require("hono");
736
740
  function createEvalRoutes(evalLoader) {
737
- const app7 = new import_hono10.Hono();
738
- app7.get("/evals", async (c) => {
741
+ const app6 = new import_hono10.Hono();
742
+ app6.get("/evals", async (c) => {
739
743
  if (evalLoader) await evalLoader();
740
744
  const runtime = c.get("runtime");
741
745
  const evals = runtime.getRegisteredEvals();
742
746
  return c.json({ ok: true, data: evals });
743
747
  });
744
- app7.get("/evals/history", async (c) => {
748
+ app6.get("/evals/history", async (c) => {
745
749
  const runtime = c.get("runtime");
746
750
  const history = await runtime.getEvalHistory();
747
751
  return c.json({ ok: true, data: history });
748
752
  });
749
- app7.post("/evals/:name/run", async (c) => {
753
+ app6.delete("/evals/history/:id", async (c) => {
754
+ const runtime = c.get("runtime");
755
+ const id = c.req.param("id");
756
+ const deleted = await runtime.deleteEvalResult(id);
757
+ if (!deleted) {
758
+ return c.json(
759
+ {
760
+ ok: false,
761
+ error: { code: "NOT_FOUND", message: `Eval history entry "${id}" not found` }
762
+ },
763
+ 404
764
+ );
765
+ }
766
+ return c.json({ ok: true, data: { id, deleted: true } });
767
+ });
768
+ app6.post("/evals/:name/run", async (c) => {
750
769
  if (evalLoader) await evalLoader();
751
770
  const runtime = c.get("runtime");
752
771
  const name = c.req.param("name");
@@ -767,9 +786,8 @@ function createEvalRoutes(evalLoader) {
767
786
  }
768
787
  try {
769
788
  if (runs > 1) {
770
- const { randomUUID } = await import("crypto");
771
789
  const { aggregateRuns } = await import("@axlsdk/eval");
772
- const runGroupId = randomUUID();
790
+ const runGroupId = (0, import_node_crypto.randomUUID)();
773
791
  const results = [];
774
792
  for (let r = 0; r < runs; r++) {
775
793
  const result2 = await runtime.runRegisteredEval(name, {
@@ -791,7 +809,7 @@ function createEvalRoutes(evalLoader) {
791
809
  return c.json({ ok: false, error: { code: "EVAL_ERROR", message } }, 400);
792
810
  }
793
811
  });
794
- app7.post("/evals/:name/rescore", async (c) => {
812
+ app6.post("/evals/:name/rescore", async (c) => {
795
813
  if (evalLoader) await evalLoader();
796
814
  const runtime = c.get("runtime");
797
815
  const name = c.req.param("name");
@@ -837,25 +855,146 @@ function createEvalRoutes(evalLoader) {
837
855
  return c.json({ ok: false, error: { code: "EVAL_ERROR", message } }, 400);
838
856
  }
839
857
  });
840
- app7.post("/evals/compare", async (c) => {
858
+ app6.post("/evals/compare", async (c) => {
841
859
  const runtime = c.get("runtime");
842
860
  const body = await c.req.json();
861
+ const validateIdParam = (v, name) => {
862
+ if (typeof v === "string") return v === "" ? `${name} must be non-empty` : null;
863
+ if (Array.isArray(v)) {
864
+ if (v.length === 0) return `${name} must be a non-empty array`;
865
+ for (const elem of v) {
866
+ if (typeof elem !== "string" || elem === "") {
867
+ return `${name} array must contain only non-empty strings`;
868
+ }
869
+ }
870
+ return null;
871
+ }
872
+ return `${name} is required (string or string[])`;
873
+ };
874
+ const baselineErr = validateIdParam(body.baselineId, "baselineId");
875
+ const candidateErr = validateIdParam(body.candidateId, "candidateId");
876
+ if (baselineErr || candidateErr) {
877
+ return c.json(
878
+ {
879
+ ok: false,
880
+ error: {
881
+ code: "BAD_REQUEST",
882
+ message: [baselineErr, candidateErr].filter(Boolean).join("; ")
883
+ }
884
+ },
885
+ 400
886
+ );
887
+ }
888
+ const history = await runtime.getEvalHistory();
889
+ const byId = new Map(history.map((h) => [h.id, h.data]));
890
+ const missing = [];
891
+ const resolveOne = (id) => {
892
+ const data = byId.get(id);
893
+ if (!data) missing.push(id);
894
+ return data;
895
+ };
896
+ const resolveSelection = (idOrIds) => {
897
+ if (Array.isArray(idOrIds)) {
898
+ const unique = Array.from(new Set(idOrIds));
899
+ if (unique.length === 1) return resolveOne(unique[0]);
900
+ const results = [];
901
+ for (const id of unique) {
902
+ const data = resolveOne(id);
903
+ if (data) results.push(data);
904
+ }
905
+ return results;
906
+ }
907
+ return resolveOne(idOrIds);
908
+ };
909
+ const baseline = resolveSelection(body.baselineId);
910
+ const candidate = resolveSelection(body.candidateId);
911
+ if (missing.length > 0) {
912
+ return c.json(
913
+ {
914
+ ok: false,
915
+ error: {
916
+ code: "NOT_FOUND",
917
+ message: `Eval result(s) not found in history: ${missing.join(", ")}`
918
+ }
919
+ },
920
+ 404
921
+ );
922
+ }
843
923
  try {
844
- const result = await runtime.evalCompare(body.baseline, body.candidate, body.options);
924
+ const result = await runtime.evalCompare(baseline, candidate, body.options);
845
925
  return c.json({ ok: true, data: result });
846
926
  } catch (err) {
847
927
  const message = err instanceof Error ? err.message : String(err);
848
- return c.json({ ok: false, error: { code: "EVAL_ERROR", message } }, 400);
928
+ return c.json({ ok: false, error: { code: "COMPARE_FAILED", message } }, 400);
849
929
  }
850
930
  });
851
- return app7;
931
+ app6.post("/evals/import", async (c) => {
932
+ const runtime = c.get("runtime");
933
+ const body = await c.req.json();
934
+ const bad = (message) => c.json({ ok: false, error: { code: "BAD_REQUEST", message } }, 400);
935
+ if (!body.result || typeof body.result !== "object") {
936
+ return bad("result is required");
937
+ }
938
+ const result = body.result;
939
+ if (!Array.isArray(result.items)) {
940
+ return bad("result.items must be an array");
941
+ }
942
+ if (typeof result.summary !== "object" || result.summary == null) {
943
+ return bad("result.summary must be an object");
944
+ }
945
+ if (typeof result.dataset !== "string" || !result.dataset) {
946
+ return bad("result.dataset must be a non-empty string (required for compare)");
947
+ }
948
+ const summary = result.summary;
949
+ if (typeof summary.scorers !== "object" || summary.scorers == null) {
950
+ return bad("result.summary.scorers must be an object");
951
+ }
952
+ const summaryScorerNames = Object.keys(summary.scorers);
953
+ const items = result.items;
954
+ const summaryScorerSet = new Set(summaryScorerNames);
955
+ const uncoveredAcrossItems = /* @__PURE__ */ new Set();
956
+ for (const item of items) {
957
+ const itemScores = item?.scores;
958
+ if (itemScores && typeof itemScores === "object") {
959
+ for (const name of Object.keys(itemScores)) {
960
+ if (!summaryScorerSet.has(name)) uncoveredAcrossItems.add(name);
961
+ }
962
+ }
963
+ }
964
+ if (uncoveredAcrossItems.size > 0) {
965
+ return bad(
966
+ `item scores reference scorer(s) not in summary.scorers: ${[...uncoveredAcrossItems].join(", ")}`
967
+ );
968
+ }
969
+ const trim = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
970
+ const metadataObj = typeof result.metadata === "object" && result.metadata != null ? result.metadata : {};
971
+ const workflowsFromMeta = Array.isArray(metadataObj.workflows) ? metadataObj.workflows : [];
972
+ const primaryWorkflow = workflowsFromMeta.find((w) => typeof w === "string");
973
+ const evalName = trim(body.eval) ?? trim(primaryWorkflow) ?? // Legacy fallback: pre-0.14 CLI artifacts had workflow at the top level.
974
+ trim(result.workflow) ?? "imported";
975
+ const id = (0, import_node_crypto.randomUUID)();
976
+ const timestamp = Date.now();
977
+ const imported = {
978
+ ...result,
979
+ id,
980
+ metadata: typeof result.metadata === "object" && result.metadata != null ? result.metadata : {}
981
+ };
982
+ await runtime.saveEvalResult({
983
+ id,
984
+ eval: evalName,
985
+ timestamp,
986
+ data: imported
987
+ });
988
+ return c.json({ ok: true, data: { id, eval: evalName, timestamp } });
989
+ });
990
+ return app6;
852
991
  }
853
992
 
854
993
  // src/server/routes/playground.ts
855
994
  var import_hono11 = require("hono");
856
995
  function createPlaygroundRoutes(connMgr) {
857
- const app7 = new import_hono11.Hono();
858
- app7.post("/playground/chat", async (c) => {
996
+ const app6 = new import_hono11.Hono();
997
+ app6.post("/playground/chat", async (c) => {
859
998
  const runtime = c.get("runtime");
860
999
  const body = await c.req.json();
861
1000
  if (!body.message || typeof body.message !== "string" || !body.message.trim()) {
@@ -917,42 +1056,45 @@ function createPlaygroundRoutes(connMgr) {
917
1056
  data: { sessionId, executionId, streaming: true }
918
1057
  });
919
1058
  });
920
- return app7;
1059
+ return app6;
921
1060
  }
922
1061
 
923
1062
  // src/server/index.ts
924
1063
  function createServer(options) {
925
1064
  const { runtime, staticRoot, basePath = "", readOnly = false } = options;
926
- const app7 = new import_hono12.Hono();
1065
+ const app6 = new import_hono12.Hono();
927
1066
  const connMgr = new ConnectionManager();
928
1067
  const costAggregator = new CostAggregator(connMgr);
929
1068
  if (options.cors !== false) {
930
- app7.use("*", (0, import_cors.cors)());
1069
+ app6.use("*", (0, import_cors.cors)());
931
1070
  }
932
- app7.use("*", errorHandler);
933
- app7.use("*", async (c, next) => {
1071
+ app6.use("*", errorHandler);
1072
+ app6.use("*", async (c, next) => {
934
1073
  c.set("runtime", runtime);
935
1074
  await next();
936
1075
  });
937
1076
  if (readOnly) {
938
1077
  const blocked = [
939
- "POST /api/workflows",
940
- "POST /api/executions",
941
- "POST /api/sessions",
942
- "DELETE /api/sessions",
943
- "PUT /api/memory",
944
- "DELETE /api/memory",
945
- "POST /api/decisions",
946
- "POST /api/costs",
947
- "POST /api/tools",
948
- "POST /api/evals",
949
- "POST /api/playground"
1078
+ /^POST \/api\/workflows(\/|$)/,
1079
+ /^POST \/api\/executions(\/|$)/,
1080
+ /^POST \/api\/sessions(\/|$)/,
1081
+ /^DELETE \/api\/sessions(\/|$)/,
1082
+ /^PUT \/api\/memory(\/|$)/,
1083
+ /^DELETE \/api\/memory(\/|$)/,
1084
+ /^POST \/api\/decisions(\/|$)/,
1085
+ /^POST \/api\/costs(\/|$)/,
1086
+ /^POST \/api\/tools(\/|$)/,
1087
+ /^POST \/api\/evals\/import$/,
1088
+ /^POST \/api\/evals\/[^/]+\/run$/,
1089
+ /^POST \/api\/evals\/[^/]+\/rescore$/,
1090
+ /^DELETE \/api\/evals\/history\/[^/]+$/,
1091
+ /^POST \/api\/playground(\/|$)/
950
1092
  ];
951
- app7.use("/api/*", async (c, next) => {
1093
+ app6.use("/api/*", async (c, next) => {
952
1094
  const apiIdx = c.req.path.indexOf("/api/");
953
1095
  const apiPath = apiIdx >= 0 ? c.req.path.slice(apiIdx) : c.req.path;
954
1096
  const key = `${c.req.method} ${apiPath}`;
955
- if (blocked.some((b) => key.startsWith(b))) {
1097
+ if (blocked.some((re) => re.test(key))) {
956
1098
  return c.json(
957
1099
  {
958
1100
  ok: false,
@@ -965,7 +1107,7 @@ function createServer(options) {
965
1107
  });
966
1108
  }
967
1109
  const api = new import_hono12.Hono();
968
- api.route("/", health_default);
1110
+ api.route("/", createHealthRoutes(readOnly));
969
1111
  api.route("/", createWorkflowRoutes(connMgr));
970
1112
  api.route("/", executions_default);
971
1113
  api.route("/", createSessionRoutes(connMgr));
@@ -976,7 +1118,7 @@ function createServer(options) {
976
1118
  api.route("/", createCostRoutes(costAggregator));
977
1119
  api.route("/", createEvalRoutes(options.evalLoader));
978
1120
  api.route("/", createPlaygroundRoutes(connMgr));
979
- app7.route("/api", api);
1121
+ app6.route("/api", api);
980
1122
  const traceListener = (event) => {
981
1123
  const traceEvent = event;
982
1124
  if (traceEvent.executionId) {
@@ -1017,7 +1159,7 @@ function createServer(options) {
1017
1159
  root: staticRoot,
1018
1160
  rewriteRequestPath: basePath ? (path) => path.startsWith(basePath) ? path.slice(basePath.length) || "/" : path : void 0
1019
1161
  });
1020
- app7.use("/*", async (c, next) => {
1162
+ app6.use("/*", async (c, next) => {
1021
1163
  const reqPath = c.req.path;
1022
1164
  const resolved = basePath && reqPath.startsWith(basePath) ? reqPath.slice(basePath.length) || "/" : reqPath;
1023
1165
  if (resolved === "/" || resolved === "/index.html" || resolved === "/ws") {
@@ -1026,7 +1168,7 @@ function createServer(options) {
1026
1168
  return staticHandler(c, next);
1027
1169
  });
1028
1170
  if (spaHtml) {
1029
- app7.get("*", async (c, next) => {
1171
+ app6.get("*", async (c, next) => {
1030
1172
  const resolved = basePath && c.req.path.startsWith(basePath) ? c.req.path.slice(basePath.length) || "/" : c.req.path;
1031
1173
  if (resolved === "/ws") return next();
1032
1174
  return c.html(spaHtml);
@@ -1034,7 +1176,7 @@ function createServer(options) {
1034
1176
  }
1035
1177
  }
1036
1178
  return {
1037
- app: app7,
1179
+ app: app6,
1038
1180
  connMgr,
1039
1181
  costAggregator,
1040
1182
  /** Create WS handlers. Call before registering static/SPA routes are reached. */
@@ -1072,6 +1214,7 @@ function parseArgs(argv) {
1072
1214
  let open = false;
1073
1215
  let help = false;
1074
1216
  let conditions = [];
1217
+ let readOnly = false;
1075
1218
  for (let i = 2; i < argv.length; i++) {
1076
1219
  const arg = argv[i];
1077
1220
  if (arg === "--port" && argv[i + 1]) {
@@ -1085,11 +1228,13 @@ function parseArgs(argv) {
1085
1228
  i++;
1086
1229
  } else if (arg === "--open") {
1087
1230
  open = true;
1231
+ } else if (arg === "--read-only" || arg === "--readonly") {
1232
+ readOnly = true;
1088
1233
  } else if (arg === "--help" || arg === "-h") {
1089
1234
  help = true;
1090
1235
  }
1091
1236
  }
1092
- const result = { port, config, open, help, conditions };
1237
+ const result = { port, config, open, help, conditions, readOnly };
1093
1238
  if (isNaN(port) || port < 1 || port > 65535) {
1094
1239
  result.portError = `Invalid port: ${port}. Must be between 1 and 65535.`;
1095
1240
  }
@@ -1134,6 +1279,7 @@ Options:
1134
1279
  --port <number> Server port (default: 4400)
1135
1280
  --config <path> Path to config file (default: auto-detect)
1136
1281
  --conditions <list> Comma-separated Node.js import conditions (e.g., development)
1282
+ --read-only Disable all mutating endpoints (runs, imports, rescore, etc)
1137
1283
  --open Auto-open browser
1138
1284
  -h, --help Show this help message
1139
1285
 
@@ -1220,19 +1366,23 @@ Tip: Use .mts for configs with top-level await or in projects without "type": "m
1220
1366
  }
1221
1367
  const staticRoot = (0, import_node_path3.resolve)(import_meta.dirname ?? __dirname, "client");
1222
1368
  const hasStaticAssets = (0, import_node_fs3.existsSync)((0, import_node_path3.resolve)(staticRoot, "index.html"));
1223
- const { app: app7, createWsHandlers: createWsHandlers2 } = createServer({
1369
+ const { app: app6, createWsHandlers: createWsHandlers2 } = createServer({
1224
1370
  runtime,
1225
- staticRoot: hasStaticAssets ? staticRoot : void 0
1371
+ staticRoot: hasStaticAssets ? staticRoot : void 0,
1372
+ readOnly: args.readOnly
1226
1373
  });
1227
- const { injectWebSocket, upgradeWebSocket } = (0, import_node_ws.createNodeWebSocket)({ app: app7 });
1374
+ if (args.readOnly) {
1375
+ console.log("[axl-studio] Read-only mode enabled \u2014 mutating endpoints are disabled.");
1376
+ }
1377
+ const { injectWebSocket, upgradeWebSocket } = (0, import_node_ws.createNodeWebSocket)({ app: app6 });
1228
1378
  const wsHandlers = createWsHandlers2();
1229
- app7.get(
1379
+ app6.get(
1230
1380
  "/ws",
1231
1381
  upgradeWebSocket(() => wsHandlers)
1232
1382
  );
1233
1383
  const server = (0, import_node_server.serve)(
1234
1384
  {
1235
- fetch: app7.fetch,
1385
+ fetch: app6.fetch,
1236
1386
  port: args.port
1237
1387
  },
1238
1388
  (info) => {