@anvia/studio 0.7.33 → 0.7.35

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.
Files changed (59) hide show
  1. package/dist/index.d.ts +131 -6
  2. package/dist/index.js +1152 -85
  3. package/dist/index.js.map +1 -1
  4. package/dist/ui/assets/{PathIcon-DX1w_Nk9.js → PathIcon-B7p7vF2d.js} +1 -1
  5. package/dist/ui/assets/{PathIcon-DX1w_Nk9.js.map → PathIcon-B7p7vF2d.js.map} +1 -1
  6. package/dist/ui/assets/{PlayIcon-Culan3xL.js → PlayIcon-Dt2gGfiW.js} +1 -1
  7. package/dist/ui/assets/{PlayIcon-Culan3xL.js.map → PlayIcon-Dt2gGfiW.js.map} +1 -1
  8. package/dist/ui/assets/{RefreshIcon-B3w42HZJ.js → RefreshIcon-BOrxG7tt.js} +1 -1
  9. package/dist/ui/assets/{RefreshIcon-B3w42HZJ.js.map → RefreshIcon-BOrxG7tt.js.map} +1 -1
  10. package/dist/ui/assets/{agents-page-nyn87qWN.js → agents-page-BdDnlVyJ.js} +2 -2
  11. package/dist/ui/assets/{agents-page-nyn87qWN.js.map → agents-page-BdDnlVyJ.js.map} +1 -1
  12. package/dist/ui/assets/{badge-BH7gz3tB.js → badge-CeTBFuRX.js} +1 -1
  13. package/dist/ui/assets/{badge-BH7gz3tB.js.map → badge-CeTBFuRX.js.map} +1 -1
  14. package/dist/ui/assets/chunk-FE5QL4G3-C1KRmMy_.js +2 -0
  15. package/dist/ui/assets/chunk-FE5QL4G3-C1KRmMy_.js.map +1 -0
  16. package/dist/ui/assets/evals-page-1QJywSGG.js +2 -0
  17. package/dist/ui/assets/{evals-page-DN6aZ9dT.js.map → evals-page-1QJywSGG.js.map} +1 -1
  18. package/dist/ui/assets/{format-C9HPGfkW.js → format-D43vTqrO.js} +2 -2
  19. package/dist/ui/assets/format-D43vTqrO.js.map +1 -0
  20. package/dist/ui/assets/index-BncPUHdf.css +1 -0
  21. package/dist/ui/assets/index-DsFqmLt-.js +55 -0
  22. package/dist/ui/assets/index-DsFqmLt-.js.map +1 -0
  23. package/dist/ui/assets/{knowledge-page-DYwhR8Ly.js → knowledge-page-Bh0HYSnR.js} +2 -2
  24. package/dist/ui/assets/{knowledge-page-DYwhR8Ly.js.map → knowledge-page-Bh0HYSnR.js.map} +1 -1
  25. package/dist/ui/assets/{mcps-page-B3e3YF6D.js → mcps-page-BoMVJjHL.js} +2 -2
  26. package/dist/ui/assets/{mcps-page-B3e3YF6D.js.map → mcps-page-BoMVJjHL.js.map} +1 -1
  27. package/dist/ui/assets/memory-page-B-LtyVhA.js +4 -0
  28. package/dist/ui/assets/memory-page-B-LtyVhA.js.map +1 -0
  29. package/dist/ui/assets/{pipelines-page-DdYD59h4.js → pipelines-page-DeIlIHCP.js} +3 -3
  30. package/dist/ui/assets/{pipelines-page-DdYD59h4.js.map → pipelines-page-DeIlIHCP.js.map} +1 -1
  31. package/dist/ui/assets/{renderers-m-gqGocU.js → renderers-IW2ZAJZw.js} +2 -2
  32. package/dist/ui/assets/{renderers-m-gqGocU.js.map → renderers-IW2ZAJZw.js.map} +1 -1
  33. package/dist/ui/assets/sandboxes-page-B_Lf-UtP.js +2 -0
  34. package/dist/ui/assets/sandboxes-page-B_Lf-UtP.js.map +1 -0
  35. package/dist/ui/assets/{sessions-page-SAmGnFSU.js → sessions-page-DjAuU-RX.js} +2 -2
  36. package/dist/ui/assets/{sessions-page-SAmGnFSU.js.map → sessions-page-DjAuU-RX.js.map} +1 -1
  37. package/dist/ui/assets/{status-page-g__90tnX.js → status-page-BaDIKvef.js} +2 -2
  38. package/dist/ui/assets/{status-page-g__90tnX.js.map → status-page-BaDIKvef.js.map} +1 -1
  39. package/dist/ui/assets/tool-runner-C5vzRzrt.js +2 -0
  40. package/dist/ui/assets/{tool-runner-CcqIP2r3.js.map → tool-runner-C5vzRzrt.js.map} +1 -1
  41. package/dist/ui/assets/{tools-page-By0mx0ua.js → tools-page-ClNVTM2F.js} +2 -2
  42. package/dist/ui/assets/{tools-page-By0mx0ua.js.map → tools-page-ClNVTM2F.js.map} +1 -1
  43. package/dist/ui/assets/trace-browser-BQ_YWG4y.js +10 -0
  44. package/dist/ui/assets/{trace-browser-BDet5qes.js.map → trace-browser-BQ_YWG4y.js.map} +1 -1
  45. package/dist/ui/assets/transcript-item-CdsVcgad.js +3 -0
  46. package/dist/ui/assets/transcript-item-CdsVcgad.js.map +1 -0
  47. package/dist/ui/index.html +4 -3
  48. package/package.json +4 -4
  49. package/dist/ui/assets/evals-page-DN6aZ9dT.js +0 -2
  50. package/dist/ui/assets/format-C9HPGfkW.js.map +0 -1
  51. package/dist/ui/assets/index-D7w3LxHM.js +0 -55
  52. package/dist/ui/assets/index-D7w3LxHM.js.map +0 -1
  53. package/dist/ui/assets/index-Dl647xDC.css +0 -1
  54. package/dist/ui/assets/memory-page-BeR1NNHY.js +0 -2
  55. package/dist/ui/assets/memory-page-BeR1NNHY.js.map +0 -1
  56. package/dist/ui/assets/tool-runner-CcqIP2r3.js +0 -2
  57. package/dist/ui/assets/trace-browser-BDet5qes.js +0 -10
  58. package/dist/ui/assets/transcript-item-3F08pehk.js +0 -3
  59. package/dist/ui/assets/transcript-item-3F08pehk.js.map +0 -1
package/dist/index.js CHANGED
@@ -634,6 +634,11 @@ function registerStudioUi(app, options) {
634
634
  app.get(`${options.path}/tools`, (c) => redirectWithQuery(c, "/tools"));
635
635
  app.get(`${options.path}/mcps`, (c) => redirectWithQuery(c, "/mcps"));
636
636
  app.get(`${options.path}/pipelines`, (c) => redirectWithQuery(c, "/pipelines"));
637
+ app.get(`${options.path}/sandboxes`, (c) => redirectWithQuery(c, "/sandboxes"));
638
+ app.get(
639
+ `${options.path}/sandboxes/:sandboxRef`,
640
+ (c) => redirectWithQuery(c, `/sandboxes/${encodeURIComponent(c.req.param("sandboxRef"))}`)
641
+ );
637
642
  app.get(`${options.path}/evals`, (c) => redirectWithQuery(c, "/evals"));
638
643
  app.get(`${options.path}/memory`, (c) => redirectWithQuery(c, "/memory"));
639
644
  app.get(`${options.path}/status`, (c) => redirectWithQuery(c, "/status"));
@@ -654,6 +659,8 @@ function registerStudioUi(app, options) {
654
659
  app.get("/tools", async (c) => c.html(await renderShell()));
655
660
  app.get("/mcps", async (c) => c.html(await renderShell()));
656
661
  app.get("/pipelines", shellWhenHtml(renderShell));
662
+ app.get("/sandboxes", shellWhenHtml(renderShell));
663
+ app.get("/sandboxes/:sandboxRef", shellWhenHtml(renderShell));
657
664
  app.get("/evals", shellWhenHtml(renderShell));
658
665
  app.get("/memory", async (c) => c.html(await renderShell()));
659
666
  app.get("/status", shellWhenHtml(renderShell));
@@ -764,8 +771,8 @@ function renderLegacyStudioUiShell(props) {
764
771
  "</html>"
765
772
  ].join("");
766
773
  }
767
- function normalizeUiPath(path) {
768
- const trimmed = path.trim();
774
+ function normalizeUiPath(path2) {
775
+ const trimmed = path2.trim();
769
776
  const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
770
777
  const withoutTrailingSlash = withSlash.length > 1 ? withSlash.replace(/\/+$/, "") : withSlash;
771
778
  if (withoutTrailingSlash.length === 0 || withoutTrailingSlash === "/") {
@@ -1808,8 +1815,69 @@ async function* mergeRunAndApprovalEvents(runEvents, approvalEvents) {
1808
1815
  approvalEvents.close();
1809
1816
  }
1810
1817
  }
1811
- function streamAgentRunEvents(_c, events) {
1812
- return streamStudioJsonl(events);
1818
+ function streamAgentRunEvents(_c, events, options = {}) {
1819
+ return streamStudioJsonl(withAgentRunCancellation(events, options.onCancel));
1820
+ }
1821
+ function withAgentRunCancellation(events, onCancel) {
1822
+ const iterator = events[Symbol.asyncIterator]();
1823
+ let done = false;
1824
+ let terminal = false;
1825
+ let cancellationHandled = false;
1826
+ const handleCancellation = async () => {
1827
+ if (cancellationHandled) {
1828
+ return;
1829
+ }
1830
+ cancellationHandled = true;
1831
+ await onCancel?.();
1832
+ };
1833
+ return {
1834
+ [Symbol.asyncIterator]() {
1835
+ return {
1836
+ async next() {
1837
+ if (done) {
1838
+ return { done: true, value: void 0 };
1839
+ }
1840
+ try {
1841
+ const next = await iterator.next();
1842
+ if (next.done === true) {
1843
+ done = true;
1844
+ if (!terminal) {
1845
+ await handleCancellation();
1846
+ }
1847
+ return next;
1848
+ }
1849
+ if (next.value.type === "final" || next.value.type === "error") {
1850
+ terminal = true;
1851
+ }
1852
+ return next;
1853
+ } catch (error) {
1854
+ done = true;
1855
+ terminal = true;
1856
+ throw error;
1857
+ }
1858
+ },
1859
+ async return() {
1860
+ if (done) {
1861
+ return { done: true, value: void 0 };
1862
+ }
1863
+ done = true;
1864
+ let closeError;
1865
+ const closePromise = iterator.return?.().catch((error) => {
1866
+ closeError = error;
1867
+ return { done: true, value: void 0 };
1868
+ });
1869
+ if (!terminal) {
1870
+ await handleCancellation();
1871
+ }
1872
+ await closePromise;
1873
+ if (closeError !== void 0) {
1874
+ throw closeError;
1875
+ }
1876
+ return { done: true, value: void 0 };
1877
+ }
1878
+ };
1879
+ }
1880
+ };
1813
1881
  }
1814
1882
  function traceForRun(trace, agentId, session) {
1815
1883
  const options = {};
@@ -1822,53 +1890,144 @@ function traceForRun(trace, agentId, session) {
1822
1890
  else if (session !== void 0) options.sessionId = session.id;
1823
1891
  return options;
1824
1892
  }
1825
- async function* persistStreamingSessionTranscript(props) {
1893
+ function createPersistedStreamingSessionTranscript(props) {
1826
1894
  const transcript = [messageToTranscriptEntry(props.message, 0)];
1827
1895
  const title = optionalTitle(props.message);
1828
- await props.store.saveSessionRunTranscript({
1829
- id: props.session.id,
1830
- runId: props.runId,
1831
- ...title,
1832
- transcript,
1833
- status: "running"
1834
- });
1835
- try {
1836
- for await (const event of props.stream) {
1837
- acceptTranscriptStreamEvent(transcript, event);
1838
- const saveInput = {
1839
- id: props.session.id,
1840
- runId: props.runId,
1841
- ...title,
1842
- transcript,
1843
- status: event.type === "final" ? "success" : event.type === "error" ? "error" : "running"
1844
- };
1845
- if (event.type === "error") saveInput.error = serializeError(event.error);
1846
- const nextSession = await props.store.saveSessionRunTranscript(saveInput);
1896
+ let status = "running";
1897
+ let saveTail = Promise.resolve();
1898
+ const isCancelled = () => status === "cancelled";
1899
+ const saveTranscript = (input) => {
1900
+ const operation = saveTail.then(async () => {
1901
+ const nextSession = await props.store.saveSessionRunTranscript(input);
1847
1902
  if (nextSession === void 0) {
1848
1903
  throw new Error("Session not found");
1849
1904
  }
1850
- const generatedMessages = event.type === "final" ? event.messages.slice(1) : [];
1851
- if (props.persistGeneratedMessages === true && generatedMessages.length > 0) {
1852
- await props.store.append({
1853
- context: { sessionId: props.session.id },
1854
- runId: props.runId,
1855
- turn: 1,
1856
- messages: generatedMessages
1857
- });
1858
- }
1859
- yield event;
1860
- }
1861
- } catch (error) {
1862
- appendTranscriptAssistantError(transcript, errorText(error));
1863
- await props.store.saveSessionRunTranscript({
1905
+ });
1906
+ saveTail = operation.catch(() => void 0);
1907
+ return operation;
1908
+ };
1909
+ const events = (async function* () {
1910
+ await saveTranscript({
1864
1911
  id: props.session.id,
1865
1912
  runId: props.runId,
1866
1913
  ...title,
1867
1914
  transcript,
1868
- status: "error",
1869
- error: serializeError(error)
1915
+ status: "running"
1870
1916
  });
1871
- throw error;
1917
+ if (isCancelled()) {
1918
+ return;
1919
+ }
1920
+ try {
1921
+ for await (const event of props.stream) {
1922
+ if (isCancelled()) {
1923
+ return;
1924
+ }
1925
+ acceptTranscriptStreamEvent(transcript, event);
1926
+ if (event.type === "final" || event.type === "error") {
1927
+ assignTranscriptRunDuration(transcript, Date.now() - props.startedAt);
1928
+ }
1929
+ const eventStatus = event.type === "final" ? "success" : event.type === "error" ? "error" : "running";
1930
+ const saveInput = {
1931
+ id: props.session.id,
1932
+ runId: props.runId,
1933
+ ...title,
1934
+ transcript,
1935
+ status: eventStatus
1936
+ };
1937
+ if (event.type === "error") saveInput.error = serializeError(event.error);
1938
+ await saveTranscript(saveInput);
1939
+ const generatedMessages = event.type === "final" ? event.messages.slice(1) : [];
1940
+ if (props.persistGeneratedMessages === true && generatedMessages.length > 0) {
1941
+ await props.store.append({
1942
+ context: { sessionId: props.session.id },
1943
+ runId: props.runId,
1944
+ turn: 1,
1945
+ messages: generatedMessages
1946
+ });
1947
+ }
1948
+ if (isCancelled()) {
1949
+ return;
1950
+ }
1951
+ status = eventStatus;
1952
+ yield event;
1953
+ }
1954
+ } catch (error) {
1955
+ if (status !== "running") {
1956
+ throw error;
1957
+ }
1958
+ status = "error";
1959
+ appendTranscriptAssistantError(transcript, errorText(error));
1960
+ assignTranscriptRunDuration(transcript, Date.now() - props.startedAt);
1961
+ await saveTranscript({
1962
+ id: props.session.id,
1963
+ runId: props.runId,
1964
+ ...title,
1965
+ transcript,
1966
+ status: "error",
1967
+ error: serializeError(error)
1968
+ });
1969
+ throw error;
1970
+ }
1971
+ })();
1972
+ return {
1973
+ events,
1974
+ async cancel() {
1975
+ if (status !== "running") {
1976
+ return;
1977
+ }
1978
+ status = "cancelled";
1979
+ cancelPendingTranscriptInputs(transcript, (/* @__PURE__ */ new Date()).toISOString());
1980
+ assignTranscriptRunDuration(transcript, Date.now() - props.startedAt);
1981
+ await saveTranscript({
1982
+ id: props.session.id,
1983
+ runId: props.runId,
1984
+ ...title,
1985
+ transcript,
1986
+ status: "cancelled"
1987
+ });
1988
+ }
1989
+ };
1990
+ }
1991
+ function assignTranscriptRunDuration(transcript, durationMs2) {
1992
+ const normalizedDurationMs = Math.max(0, durationMs2);
1993
+ for (let index = transcript.length - 1; index >= 0; index -= 1) {
1994
+ const entry = transcript[index];
1995
+ if (entry?.kind === "message" && entry.role === "assistant") {
1996
+ entry.durationMs = normalizedDurationMs;
1997
+ return;
1998
+ }
1999
+ if (entry?.kind === "message" && entry.role === "user") {
2000
+ break;
2001
+ }
2002
+ }
2003
+ transcript.push({
2004
+ entryId: transcript.length,
2005
+ kind: "message",
2006
+ role: "assistant",
2007
+ text: "",
2008
+ durationMs: normalizedDurationMs
2009
+ });
2010
+ }
2011
+ function cancelPendingTranscriptInputs(transcript, cancelledAt) {
2012
+ for (const entry of transcript) {
2013
+ if (entry.kind !== "tool") {
2014
+ continue;
2015
+ }
2016
+ if (entry.approval?.status === "pending") {
2017
+ entry.approval = {
2018
+ ...entry.approval,
2019
+ status: "cancelled",
2020
+ resolvedAt: cancelledAt,
2021
+ reason: "Run cancelled in Anvia Studio."
2022
+ };
2023
+ }
2024
+ if (entry.question?.status === "pending") {
2025
+ entry.question = {
2026
+ ...entry.question,
2027
+ status: "cancelled",
2028
+ cancelledAt
2029
+ };
2030
+ }
1872
2031
  }
1873
2032
  }
1874
2033
  function acceptTranscriptStreamEvent(transcript, event) {
@@ -1985,6 +2144,9 @@ function acceptTranscriptStreamEvent(transcript, event) {
1985
2144
  questions: event.question.questions
1986
2145
  };
1987
2146
  if (event.question.answeredAt !== void 0) question.answeredAt = event.question.answeredAt;
2147
+ if (event.question.cancelledAt !== void 0) {
2148
+ question.cancelledAt = event.question.cancelledAt;
2149
+ }
1988
2150
  if (event.question.answers !== void 0) question.answers = event.question.answers;
1989
2151
  matched.question = question;
1990
2152
  }
@@ -2476,6 +2638,19 @@ function runCompletedLog(props) {
2476
2638
  metadata
2477
2639
  };
2478
2640
  }
2641
+ function runCancelledLog(sessionId, runId, startedAt) {
2642
+ return {
2643
+ sessionId,
2644
+ runId,
2645
+ level: "info",
2646
+ category: "run",
2647
+ event: "run.cancelled",
2648
+ message: "Run cancelled",
2649
+ metadata: {
2650
+ durationMs: Date.now() - startedAt
2651
+ }
2652
+ };
2653
+ }
2479
2654
  function memorySavedLog(props) {
2480
2655
  const metadata = {};
2481
2656
  if (props.messageCount !== void 0) metadata.messageCount = props.messageCount;
@@ -2662,6 +2837,7 @@ function logsFromStreamEvent(props) {
2662
2837
  ];
2663
2838
  }
2664
2839
  if (event.type === "tool_question_result") {
2840
+ const cancelled = event.question.status === "cancelled";
2665
2841
  const metadata = {
2666
2842
  questionId: event.question.id,
2667
2843
  toolName: event.question.toolName,
@@ -2677,8 +2853,8 @@ function logsFromStreamEvent(props) {
2677
2853
  runId,
2678
2854
  level: "info",
2679
2855
  category: "question",
2680
- event: "question.answered",
2681
- message: `Question answered for ${event.question.toolName}`,
2856
+ event: cancelled ? "question.cancelled" : "question.answered",
2857
+ message: cancelled ? `Question cancelled for ${event.question.toolName}` : `Question answered for ${event.question.toolName}`,
2682
2858
  metadata
2683
2859
  }
2684
2860
  ];
@@ -3055,21 +3231,45 @@ function handleStreamingAgentRun(c, run, props) {
3055
3231
  run.request.requestHook(effectiveHook);
3056
3232
  }
3057
3233
  const runStream = mergeRunAndApprovalEvents(run.request.stream(), runtimeEvents);
3058
- const stream = run.session === void 0 || run.sessionStore === void 0 ? runStream : persistStreamingSessionTranscript({
3059
- stream: streamSessionRunLogs({
3060
- stream: runStream,
3234
+ let stream = runStream;
3235
+ let persistedRun;
3236
+ if (run.session !== void 0 && run.sessionStore !== void 0) {
3237
+ persistedRun = createPersistedStreamingSessionTranscript({
3238
+ stream: streamSessionRunLogs({
3239
+ stream: runStream,
3240
+ store: run.sessionStore,
3241
+ session: run.session,
3242
+ runId: run.runId,
3243
+ startedAt: run.runStartedAt
3244
+ }),
3061
3245
  store: run.sessionStore,
3062
3246
  session: run.session,
3247
+ message: run.body.message,
3063
3248
  runId: run.runId,
3064
- startedAt: run.runStartedAt
3065
- }),
3066
- store: run.sessionStore,
3067
- session: run.session,
3068
- message: run.body.message,
3069
- runId: run.runId,
3070
- persistGeneratedMessages: run.shouldPersistSessionMessages
3249
+ startedAt: run.runStartedAt,
3250
+ persistGeneratedMessages: run.shouldPersistSessionMessages
3251
+ });
3252
+ stream = persistedRun.events;
3253
+ }
3254
+ return streamAgentRunEvents(c, stream, {
3255
+ onCancel: async () => {
3256
+ props.approvalRuntime.cancelRun(run.runId);
3257
+ props.questionRuntime.cancelRun(run.runId);
3258
+ const persistence = [];
3259
+ if (persistedRun !== void 0) {
3260
+ persistence.push(persistedRun.cancel());
3261
+ }
3262
+ if (run.session !== void 0 && run.sessionStore !== void 0) {
3263
+ persistence.push(
3264
+ appendSessionLog(
3265
+ run.sessionStore,
3266
+ runCancelledLog(run.session.id, run.runId, run.runStartedAt)
3267
+ )
3268
+ );
3269
+ }
3270
+ await Promise.all(persistence);
3271
+ }
3071
3272
  });
3072
- return streamAgentRunEvents(c, stream);
3073
3273
  }
3074
3274
  async function handleBufferedAgentRun(c, run, props) {
3075
3275
  try {
@@ -3123,11 +3323,14 @@ async function completeBufferedSessionRun(run, response) {
3123
3323
  });
3124
3324
  }
3125
3325
  }
3326
+ const durationMs2 = Date.now() - run.runStartedAt;
3327
+ const transcript = transcriptFromMessages(response.messages);
3328
+ assignTranscriptRunDuration(transcript, durationMs2);
3126
3329
  await run.sessionStore.saveSessionRunTranscript({
3127
3330
  id: run.session.id,
3128
3331
  runId: run.runId,
3129
3332
  ...optionalTitle(run.body.message),
3130
- transcript: transcriptFromMessages(response.messages),
3333
+ transcript,
3131
3334
  status: "success"
3132
3335
  });
3133
3336
  await appendSessionLog(
@@ -3135,7 +3338,7 @@ async function completeBufferedSessionRun(run, response) {
3135
3338
  runCompletedLog({
3136
3339
  sessionId: run.session.id,
3137
3340
  runId: run.runId,
3138
- durationMs: Date.now() - run.runStartedAt,
3341
+ durationMs: durationMs2,
3139
3342
  usage: response.usage,
3140
3343
  output: response.output,
3141
3344
  messageCount: response.messages.length
@@ -3158,11 +3361,14 @@ async function failBufferedSessionRun(run, error) {
3158
3361
  sessionId: run.session.id,
3159
3362
  metadata: run.memoryMetadata
3160
3363
  });
3364
+ const durationMs2 = Date.now() - run.runStartedAt;
3365
+ const transcript = transcriptFromMessages(messages.slice(run.session.messageCount));
3366
+ assignTranscriptRunDuration(transcript, durationMs2);
3161
3367
  await run.sessionStore.saveSessionRunTranscript({
3162
3368
  id: run.session.id,
3163
3369
  runId: run.runId,
3164
3370
  ...optionalTitle(run.body.message),
3165
- transcript: transcriptFromMessages(messages.slice(run.session.messageCount)),
3371
+ transcript,
3166
3372
  status: "error",
3167
3373
  error: serializeError(error)
3168
3374
  });
@@ -3322,6 +3528,22 @@ function createApprovalRuntime() {
3322
3528
  return true;
3323
3529
  }).map(publicApproval);
3324
3530
  },
3531
+ cancelRun(runId) {
3532
+ const cancelled = [];
3533
+ for (const approval of approvals.values()) {
3534
+ if (!isPendingApproval(approval) || approval.runId !== runId) {
3535
+ continue;
3536
+ }
3537
+ const resolved = resolveApproval(approval, "cancelled", {
3538
+ reason: "Run cancelled in Anvia Studio."
3539
+ });
3540
+ approvals.set(approval.id, resolved);
3541
+ approval.emit?.({ type: "tool_approval_result", approval: resolved });
3542
+ approval.resolve(approvalDecision(false, resolved.reason));
3543
+ cancelled.push(resolved);
3544
+ }
3545
+ return cancelled;
3546
+ },
3325
3547
  decide(id, decision) {
3326
3548
  const approval = approvals.get(id);
3327
3549
  if (approval === void 0) {
@@ -3581,7 +3803,7 @@ function pipelineConfig(pipeline) {
3581
3803
  if (pipeline.metadata !== void 0) config.metadata = pipeline.metadata;
3582
3804
  return config;
3583
3805
  }
3584
- function buildConfig(options, agents, pipelines, stores) {
3806
+ function buildConfig(options, agents, pipelines, stores, sandboxCount = 0) {
3585
3807
  const models = options.models === void 0 ? void 0 : studioModelsConfig(createStudioModelRegistry(options.models), agents);
3586
3808
  const config = {
3587
3809
  id: runnerId(options),
@@ -3591,7 +3813,7 @@ function buildConfig(options, agents, pipelines, stores) {
3591
3813
  chat: {
3592
3814
  quickPrompts: Object.fromEntries(agents.map((agent) => [agent.id, agent.quickPrompts ?? []]))
3593
3815
  },
3594
- capabilities: capabilityConfig(options, agents, pipelines, stores),
3816
+ capabilities: capabilityConfig(options, agents, pipelines, stores, sandboxCount),
3595
3817
  unsupportedCapabilities: unsupportedCapabilities(stores)
3596
3818
  };
3597
3819
  if (options.name !== void 0) config.name = options.name;
@@ -3600,7 +3822,7 @@ function buildConfig(options, agents, pipelines, stores) {
3600
3822
  if (models !== void 0) config.models = models;
3601
3823
  return config;
3602
3824
  }
3603
- function capabilityConfig(_options, agents, pipelines, stores) {
3825
+ function capabilityConfig(_options, agents, pipelines, stores, sandboxCount = 0) {
3604
3826
  const capabilities = {
3605
3827
  agents: { enabled: true },
3606
3828
  observability: { enabled: true },
@@ -3608,6 +3830,8 @@ function capabilityConfig(_options, agents, pipelines, stores) {
3608
3830
  };
3609
3831
  if (stores.sessions !== void 0) {
3610
3832
  capabilities.sessions = { enabled: true };
3833
+ }
3834
+ if (stores.sessions !== void 0 || agents.some((agent) => agent.agent.memory !== void 0)) {
3611
3835
  capabilities.memory = { enabled: true };
3612
3836
  }
3613
3837
  if (stores.traces !== void 0) {
@@ -3627,6 +3851,9 @@ function capabilityConfig(_options, agents, pipelines, stores) {
3627
3851
  if (agents.some(agentHasMcpTools)) {
3628
3852
  capabilities.mcps = { enabled: true };
3629
3853
  }
3854
+ if (sandboxCount > 0) {
3855
+ capabilities.sandboxes = { enabled: true };
3856
+ }
3630
3857
  if (agents.some(
3631
3858
  (agent) => agent.agent.hook !== void 0 || agent.agent.toolSet.values().some((tool) => tool.approval)
3632
3859
  )) {
@@ -4159,13 +4386,137 @@ async function agentMcpMetadata(agent) {
4159
4386
 
4160
4387
  // src/runtime/memory.ts
4161
4388
  var DEFAULT_USER_ID = "default";
4389
+ var USER_DISCOVERY_LIMIT = 1e3;
4390
+ function createStudioMemorySourceRegistry(agents, sessionStore) {
4391
+ const sources = [];
4392
+ const byStore = /* @__PURE__ */ new Map();
4393
+ const agentsWithoutMemory = [];
4394
+ for (const agent of agents) {
4395
+ const store = agent.agent.memory?.store;
4396
+ if (store === void 0) {
4397
+ agentsWithoutMemory.push(agent);
4398
+ continue;
4399
+ }
4400
+ const existing = byStore.get(store);
4401
+ if (existing !== void 0) {
4402
+ existing.summary.agentIds.push(agent.id);
4403
+ existing.summary.label = "Shared agent memory";
4404
+ continue;
4405
+ }
4406
+ const ref = `agent-memory-${sources.length + 1}`;
4407
+ const label = `${agent.name ?? agent.agent.name ?? agent.id} memory`;
4408
+ const kind = storeKind(store);
4409
+ const source = store.inspector === void 0 ? unavailableAgentSource(ref, label, agent.id, kind) : agentMemorySource(ref, label, agent.id, kind, store.inspector);
4410
+ sources.push(source);
4411
+ byStore.set(store, source);
4412
+ }
4413
+ if (sessionStore !== void 0 && (agentsWithoutMemory.length > 0 || agents.length === 0)) {
4414
+ sources.push(
4415
+ studioSessionSource(
4416
+ sessionStore,
4417
+ agentsWithoutMemory.map((agent) => agent.id)
4418
+ )
4419
+ );
4420
+ }
4421
+ const byRef = new Map(sources.map((source) => [source.summary.ref, source]));
4422
+ return {
4423
+ size: sources.length,
4424
+ list: () => sources.map((source) => ({ ...source.summary, agentIds: [...source.summary.agentIds] })),
4425
+ get: (ref) => byRef.get(ref)
4426
+ };
4427
+ }
4162
4428
  function registerMemoryRoutes(app, props) {
4429
+ registerMemorySourceRoutes(app, props.sources);
4430
+ if (props.sessionStore !== void 0) {
4431
+ registerLegacyMemoryRoutes(app, props.sessionStore);
4432
+ }
4433
+ }
4434
+ function registerMemorySourceRoutes(app, registry) {
4435
+ app.get(
4436
+ "/memory/sources",
4437
+ (c) => c.json({ sources: registry.list() })
4438
+ );
4439
+ app.get("/memory/sources/:sourceRef/users", async (c) => {
4440
+ const limit = parseLimit(c.req.query("limit"));
4441
+ if (limit === void 0) {
4442
+ return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
4443
+ }
4444
+ const source = registry.get(c.req.param("sourceRef"));
4445
+ if (source === void 0) {
4446
+ return errorResponse(c, 404, "not_found", "Memory source not found");
4447
+ }
4448
+ if (!source.summary.available) return unavailableSourceResponse(c, source.summary);
4449
+ const conversations = await source.listConversations({ limit: USER_DISCOVERY_LIMIT });
4450
+ const userPage = memoryUsers(conversations, limit);
4451
+ return c.json({
4452
+ source: source.summary,
4453
+ users: userPage.users,
4454
+ total: userPage.total
4455
+ });
4456
+ });
4457
+ app.get("/memory/sources/:sourceRef/conversations", async (c) => {
4458
+ const limit = parseLimit(c.req.query("limit"));
4459
+ if (limit === void 0) {
4460
+ return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
4461
+ }
4462
+ const source = registry.get(c.req.param("sourceRef"));
4463
+ if (source === void 0) {
4464
+ return errorResponse(c, 404, "not_found", "Memory source not found");
4465
+ }
4466
+ if (!source.summary.available) return unavailableSourceResponse(c, source.summary);
4467
+ const userId = optionalQueryString(c.req.query("userId"));
4468
+ const listOptions = { limit };
4469
+ if (userId !== void 0) listOptions.userId = userId;
4470
+ const conversations = await source.listConversations(listOptions);
4471
+ return c.json({
4472
+ source: source.summary,
4473
+ conversations,
4474
+ total: conversations.length
4475
+ });
4476
+ });
4477
+ app.get("/memory/sources/:sourceRef/conversations/:conversationRef/messages", async (c) => {
4478
+ const source = registry.get(c.req.param("sourceRef"));
4479
+ if (source === void 0) {
4480
+ return errorResponse(c, 404, "not_found", "Memory source not found");
4481
+ }
4482
+ if (!source.summary.available) return unavailableSourceResponse(c, source.summary);
4483
+ const detail = await source.getConversation(c.req.param("conversationRef"));
4484
+ if (detail === void 0) {
4485
+ return errorResponse(c, 404, "not_found", "Conversation not found");
4486
+ }
4487
+ const messages = detail.records.map((record) => record.message);
4488
+ return c.json({
4489
+ source: source.summary,
4490
+ conversation: detail.conversation,
4491
+ messages,
4492
+ records: detail.records,
4493
+ transcript: transcriptFromMessages(messages)
4494
+ });
4495
+ });
4496
+ app.get("/memory/sources/:sourceRef/conversations/:conversationRef/steps", async (c) => {
4497
+ const source = registry.get(c.req.param("sourceRef"));
4498
+ if (source === void 0) {
4499
+ return errorResponse(c, 404, "not_found", "Memory source not found");
4500
+ }
4501
+ if (!source.summary.available) return unavailableSourceResponse(c, source.summary);
4502
+ const detail = await source.getConversation(c.req.param("conversationRef"));
4503
+ if (detail === void 0) {
4504
+ return errorResponse(c, 404, "not_found", "Conversation not found");
4505
+ }
4506
+ return c.json({
4507
+ source: source.summary,
4508
+ conversation: detail.conversation,
4509
+ steps: transcriptFromMessages(detail.records.map((record) => record.message))
4510
+ });
4511
+ });
4512
+ }
4513
+ function registerLegacyMemoryRoutes(app, sessionStore) {
4163
4514
  app.get("/memory/users", async (c) => {
4164
4515
  const limit = parseLimit(c.req.query("limit"));
4165
4516
  if (limit === void 0) {
4166
4517
  return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
4167
4518
  }
4168
- const sessions = await props.sessionStore.listSessions({ limit: 100 });
4519
+ const sessions = await sessionStore.listSessions({ limit: 100 });
4169
4520
  const users = /* @__PURE__ */ new Map();
4170
4521
  for (const session of sessions) {
4171
4522
  const userId = sessionUserId(session);
@@ -4180,9 +4531,7 @@ function registerMemoryRoutes(app, props) {
4180
4531
  continue;
4181
4532
  }
4182
4533
  existing.conversationCount += 1;
4183
- if (!existing.agentIds.includes(session.agentId)) {
4184
- existing.agentIds.push(session.agentId);
4185
- }
4534
+ if (!existing.agentIds.includes(session.agentId)) existing.agentIds.push(session.agentId);
4186
4535
  if (new Date(session.updatedAt).getTime() > new Date(existing.lastInteractionAt).getTime()) {
4187
4536
  existing.lastInteractionAt = session.updatedAt;
4188
4537
  }
@@ -4201,7 +4550,7 @@ function registerMemoryRoutes(app, props) {
4201
4550
  const userId = optionalQueryString(c.req.query("userId"));
4202
4551
  const listInput = { limit: 100 };
4203
4552
  if (agentId !== void 0) listInput.agentId = agentId;
4204
- const sessions = await props.sessionStore.listSessions(listInput);
4553
+ const sessions = await sessionStore.listSessions(listInput);
4205
4554
  const conversations = sessions.map(memoryConversationSummary).filter((session) => userId === void 0 || session.userId === userId).slice(0, limit);
4206
4555
  return c.json({
4207
4556
  conversations,
@@ -4209,7 +4558,7 @@ function registerMemoryRoutes(app, props) {
4209
4558
  });
4210
4559
  });
4211
4560
  app.get("/memory/conversations/:conversationId/messages", async (c) => {
4212
- const session = await props.sessionStore.getSession(c.req.param("conversationId"));
4561
+ const session = await sessionStore.getSession(c.req.param("conversationId"));
4213
4562
  if (session === void 0) {
4214
4563
  return errorResponse(c, 404, "not_found", "Conversation not found");
4215
4564
  }
@@ -4220,7 +4569,7 @@ function registerMemoryRoutes(app, props) {
4220
4569
  });
4221
4570
  });
4222
4571
  app.get("/memory/conversations/:conversationId/steps", async (c) => {
4223
- const session = await props.sessionStore.getSession(c.req.param("conversationId"));
4572
+ const session = await sessionStore.getSession(c.req.param("conversationId"));
4224
4573
  if (session === void 0) {
4225
4574
  return errorResponse(c, 404, "not_found", "Conversation not found");
4226
4575
  }
@@ -4230,6 +4579,149 @@ function registerMemoryRoutes(app, props) {
4230
4579
  });
4231
4580
  });
4232
4581
  }
4582
+ function agentMemorySource(ref, label, agentId, storeKindValue, inspector) {
4583
+ const summary = {
4584
+ ref,
4585
+ kind: "agent",
4586
+ label,
4587
+ agentIds: [agentId],
4588
+ available: true
4589
+ };
4590
+ if (storeKindValue !== void 0) summary.storeKind = storeKindValue;
4591
+ return {
4592
+ summary,
4593
+ async listConversations(options) {
4594
+ const inspectorOptions = {
4595
+ limit: options.userId === DEFAULT_USER_ID ? USER_DISCOVERY_LIMIT : options.limit
4596
+ };
4597
+ if (options.userId !== void 0 && options.userId !== DEFAULT_USER_ID) {
4598
+ inspectorOptions.userId = options.userId;
4599
+ }
4600
+ const conversations = await inspector.listConversations(inspectorOptions);
4601
+ return conversations.map((conversation) => sourceConversationSummary(conversation, summary.agentIds)).filter(
4602
+ (conversation) => options.userId === void 0 || conversation.userId === options.userId
4603
+ ).slice(0, options.limit);
4604
+ },
4605
+ async getConversation(conversationRef) {
4606
+ const conversation = await inspector.getConversation(conversationRef);
4607
+ return conversation === void 0 ? void 0 : inspectorConversationDetail(conversation, summary.agentIds);
4608
+ }
4609
+ };
4610
+ }
4611
+ function unavailableAgentSource(ref, label, agentId, storeKindValue) {
4612
+ const summary = {
4613
+ ref,
4614
+ kind: "agent",
4615
+ label,
4616
+ agentIds: [agentId],
4617
+ available: false,
4618
+ reason: "This memory store does not expose the optional inspector capability."
4619
+ };
4620
+ if (storeKindValue !== void 0) summary.storeKind = storeKindValue;
4621
+ return {
4622
+ summary,
4623
+ listConversations: async () => [],
4624
+ getConversation: async () => void 0
4625
+ };
4626
+ }
4627
+ function studioSessionSource(sessionStore, agentIds) {
4628
+ const summary = {
4629
+ ref: "studio-sessions",
4630
+ kind: "studio",
4631
+ label: "Studio sessions",
4632
+ agentIds: [...agentIds],
4633
+ available: true,
4634
+ storeKind: sessionStore.kind ?? "studio"
4635
+ };
4636
+ const allowedAgents = new Set(agentIds);
4637
+ const restrictAgents = allowedAgents.size > 0;
4638
+ return {
4639
+ summary,
4640
+ async listConversations(options) {
4641
+ const sessions = await sessionStore.listSessions({ limit: USER_DISCOVERY_LIMIT });
4642
+ return sessions.filter((session) => !restrictAgents || allowedAgents.has(session.agentId)).map(studioSourceConversationSummary).filter(
4643
+ (conversation) => options.userId === void 0 || conversation.userId === options.userId
4644
+ ).slice(0, options.limit);
4645
+ },
4646
+ async getConversation(conversationRef) {
4647
+ const session = await sessionStore.getSession(conversationRef);
4648
+ if (session === void 0 || restrictAgents && !allowedAgents.has(session.agentId)) {
4649
+ return void 0;
4650
+ }
4651
+ return {
4652
+ conversation: studioSourceConversationSummary(session),
4653
+ records: session.messages.map((message, position) => ({
4654
+ position,
4655
+ runId: "studio-session",
4656
+ turn: 0,
4657
+ createdAt: session.updatedAt,
4658
+ message
4659
+ }))
4660
+ };
4661
+ }
4662
+ };
4663
+ }
4664
+ function sourceConversationSummary(conversation, agentIds) {
4665
+ const summary = {
4666
+ ref: conversation.ref,
4667
+ sessionId: conversation.sessionId,
4668
+ userId: conversation.userId ?? DEFAULT_USER_ID,
4669
+ agentIds: [...agentIds],
4670
+ createdAt: conversation.createdAt,
4671
+ updatedAt: conversation.updatedAt,
4672
+ messageCount: conversation.messageCount
4673
+ };
4674
+ if (conversation.metadata !== void 0) summary.metadata = conversation.metadata;
4675
+ return summary;
4676
+ }
4677
+ function inspectorConversationDetail(conversation, agentIds) {
4678
+ return {
4679
+ conversation: sourceConversationSummary(conversation, agentIds),
4680
+ records: conversation.messages
4681
+ };
4682
+ }
4683
+ function studioSourceConversationSummary(session) {
4684
+ const summary = {
4685
+ ref: session.id,
4686
+ sessionId: session.id,
4687
+ userId: sessionUserId(session),
4688
+ agentIds: [session.agentId],
4689
+ createdAt: session.createdAt,
4690
+ updatedAt: session.updatedAt,
4691
+ messageCount: session.messageCount
4692
+ };
4693
+ if (session.title !== void 0) summary.title = session.title;
4694
+ if (session.metadata !== void 0) summary.metadata = session.metadata;
4695
+ return summary;
4696
+ }
4697
+ function memoryUsers(conversations, limit) {
4698
+ const users = /* @__PURE__ */ new Map();
4699
+ for (const conversation of conversations) {
4700
+ const existing = users.get(conversation.userId);
4701
+ if (existing === void 0) {
4702
+ users.set(conversation.userId, {
4703
+ userId: conversation.userId,
4704
+ conversationCount: 1,
4705
+ agentIds: [...conversation.agentIds],
4706
+ lastInteractionAt: conversation.updatedAt
4707
+ });
4708
+ continue;
4709
+ }
4710
+ existing.conversationCount += 1;
4711
+ for (const agentId of conversation.agentIds) {
4712
+ if (!existing.agentIds.includes(agentId)) existing.agentIds.push(agentId);
4713
+ }
4714
+ if (new Date(conversation.updatedAt).getTime() > new Date(existing.lastInteractionAt).getTime()) {
4715
+ existing.lastInteractionAt = conversation.updatedAt;
4716
+ }
4717
+ }
4718
+ return {
4719
+ users: [...users.values()].sort(
4720
+ (left, right) => new Date(right.lastInteractionAt).getTime() - new Date(left.lastInteractionAt).getTime()
4721
+ ).slice(0, limit),
4722
+ total: users.size
4723
+ };
4724
+ }
4233
4725
  function memoryConversationSummary(session) {
4234
4726
  const summary = {
4235
4727
  id: session.id,
@@ -4247,6 +4739,19 @@ function sessionUserId(session) {
4247
4739
  const userId = session.metadata?.userId;
4248
4740
  return typeof userId === "string" && userId.trim().length > 0 ? userId : DEFAULT_USER_ID;
4249
4741
  }
4742
+ function storeKind(store) {
4743
+ const kind = store.kind;
4744
+ return typeof kind === "string" && kind.length > 0 ? kind : void 0;
4745
+ }
4746
+ function unavailableSourceResponse(c, source) {
4747
+ return errorResponse(
4748
+ c,
4749
+ 501,
4750
+ "unsupported_capability",
4751
+ source.reason ?? "Memory source is not inspectable",
4752
+ { sourceRef: source.ref }
4753
+ );
4754
+ }
4250
4755
 
4251
4756
  // src/runtime/pipeline-logs.ts
4252
4757
  async function appendPipelineLog(store, input) {
@@ -4910,6 +5415,20 @@ function createQuestionRuntime() {
4910
5415
  return true;
4911
5416
  }).map(publicQuestion);
4912
5417
  },
5418
+ cancelRun(runId) {
5419
+ const cancelled = [];
5420
+ for (const question of questions.values()) {
5421
+ if (!isPendingQuestion(question) || question.runId !== runId) {
5422
+ continue;
5423
+ }
5424
+ const resolved = cancelQuestion(question);
5425
+ questions.set(question.id, resolved);
5426
+ question.emit?.({ type: "tool_question_result", question: resolved });
5427
+ question.resolve([]);
5428
+ cancelled.push(resolved);
5429
+ }
5430
+ return cancelled;
5431
+ },
4913
5432
  answer(id, answers) {
4914
5433
  const question = questions.get(id);
4915
5434
  if (question === void 0) {
@@ -5010,6 +5529,13 @@ function resolveQuestion(question, answers) {
5010
5529
  answers
5011
5530
  });
5012
5531
  }
5532
+ function cancelQuestion(question) {
5533
+ return publicQuestion({
5534
+ ...question,
5535
+ status: "cancelled",
5536
+ cancelledAt: (/* @__PURE__ */ new Date()).toISOString()
5537
+ });
5538
+ }
5013
5539
  function publicQuestion(question) {
5014
5540
  const { emit, resolve: resolve2, ...rest } = question;
5015
5541
  void emit;
@@ -5017,6 +5543,446 @@ function publicQuestion(question) {
5017
5543
  return rest;
5018
5544
  }
5019
5545
 
5546
+ // src/runtime/sandboxes.ts
5547
+ import path from "path";
5548
+ var sandboxToolMetadataKey = /* @__PURE__ */ Symbol.for("anvia.sandbox.tool.metadata");
5549
+ var maxFileResponseBytes = 10 * 1024 * 1024;
5550
+ var defaultProcessLogBytes = 64 * 1024;
5551
+ var maxProcessLogBytes = 1024 * 1024;
5552
+ var StudioSandboxRegistry = class {
5553
+ entries;
5554
+ constructor(entries) {
5555
+ this.entries = new Map(entries.map((entry) => [entry.summary.ref, entry]));
5556
+ }
5557
+ get size() {
5558
+ return this.entries.size;
5559
+ }
5560
+ list() {
5561
+ return [...this.entries.values()].map((entry) => copySummary(entry.summary));
5562
+ }
5563
+ get(ref) {
5564
+ return this.entries.get(ref);
5565
+ }
5566
+ };
5567
+ function createStudioSandboxRegistry(agents) {
5568
+ const drafts = /* @__PURE__ */ new Map();
5569
+ for (const agent of agents) {
5570
+ for (const { tool } of agentToolItems(agent)) {
5571
+ const session = sandboxSessionFromTool(tool);
5572
+ if (session === void 0) {
5573
+ continue;
5574
+ }
5575
+ const draft = drafts.get(session) ?? {
5576
+ session,
5577
+ agentIds: /* @__PURE__ */ new Set(),
5578
+ toolNames: /* @__PURE__ */ new Set()
5579
+ };
5580
+ draft.agentIds.add(agent.id);
5581
+ draft.toolNames.add(tool.name);
5582
+ drafts.set(session, draft);
5583
+ }
5584
+ }
5585
+ const refCounts = /* @__PURE__ */ new Map();
5586
+ const entries = [...drafts.values()].sort(
5587
+ (left, right) => `${left.session.provider}\0${left.session.id}`.localeCompare(
5588
+ `${right.session.provider}\0${right.session.id}`
5589
+ )
5590
+ ).map((draft) => {
5591
+ const baseRef = sandboxRef(draft.session.provider, draft.session.id);
5592
+ const occurrence = (refCounts.get(baseRef) ?? 0) + 1;
5593
+ refCounts.set(baseRef, occurrence);
5594
+ const ref = occurrence === 1 ? baseRef : `${baseRef}.${occurrence}`;
5595
+ return {
5596
+ session: draft.session,
5597
+ summary: {
5598
+ ref,
5599
+ id: draft.session.id,
5600
+ provider: draft.session.provider,
5601
+ workdir: draft.session.workdir,
5602
+ agentIds: [...draft.agentIds].sort(),
5603
+ toolNames: [...draft.toolNames].sort(),
5604
+ capabilities: {
5605
+ files: true,
5606
+ ports: supportsPorts(draft.session),
5607
+ processes: supportsProcesses(draft.session)
5608
+ }
5609
+ }
5610
+ };
5611
+ });
5612
+ return new StudioSandboxRegistry(entries);
5613
+ }
5614
+ function registerSandboxRoutes(app, registry) {
5615
+ app.use("/sandboxes", sandboxNoStore);
5616
+ app.use("/sandboxes/*", sandboxNoStore);
5617
+ app.get("/sandboxes", (c) => c.json({ sandboxes: registry.list() }));
5618
+ app.get("/sandboxes/:sandboxRef", (c) => {
5619
+ const entry = registry.get(c.req.param("sandboxRef"));
5620
+ if (entry === void 0) {
5621
+ return errorResponse(c, 404, "not_found", "Sandbox not found");
5622
+ }
5623
+ return c.json(copySummary(entry.summary));
5624
+ });
5625
+ app.get("/sandboxes/:sandboxRef/files", async (c) => {
5626
+ const entry = registry.get(c.req.param("sandboxRef"));
5627
+ if (entry === void 0) {
5628
+ return errorResponse(c, 404, "not_found", "Sandbox not found");
5629
+ }
5630
+ try {
5631
+ const requestedPath = c.req.query("path") ?? ".";
5632
+ const normalizedPath = normalizeSandboxPath(requestedPath, true);
5633
+ const entries = await listSandboxFiles(entry.session, normalizedPath);
5634
+ return c.json({
5635
+ sandboxRef: entry.summary.ref,
5636
+ path: normalizedPath,
5637
+ entries
5638
+ });
5639
+ } catch (error) {
5640
+ return sandboxErrorResponse(c, error);
5641
+ }
5642
+ });
5643
+ app.get("/sandboxes/:sandboxRef/files/content", async (c) => {
5644
+ const entry = registry.get(c.req.param("sandboxRef"));
5645
+ if (entry === void 0) {
5646
+ return errorResponse(c, 404, "not_found", "Sandbox not found");
5647
+ }
5648
+ try {
5649
+ const requestedPath = c.req.query("path");
5650
+ if (requestedPath === void 0) {
5651
+ throw new SandboxRouteError(400, "bad_request", "path query parameter is required");
5652
+ }
5653
+ const download = c.req.query("download");
5654
+ if (download !== void 0 && download !== "1") {
5655
+ throw new SandboxRouteError(400, "bad_request", "download must be 1 when provided");
5656
+ }
5657
+ const normalizedPath = normalizeSandboxPath(requestedPath, false);
5658
+ const parentPath = path.posix.dirname(normalizedPath);
5659
+ const siblings = await listSandboxFiles(entry.session, parentPath);
5660
+ const file = siblings.find((candidate) => candidate.path === normalizedPath);
5661
+ if (file === void 0) {
5662
+ throw new SandboxRouteError(404, "not_found", "Sandbox file not found");
5663
+ }
5664
+ if (file.type !== "file") {
5665
+ throw new SandboxRouteError(
5666
+ 400,
5667
+ "bad_request",
5668
+ "Sandbox content path must refer to a regular file"
5669
+ );
5670
+ }
5671
+ if (file.size !== void 0 && file.size > maxFileResponseBytes) {
5672
+ throw fileTooLarge(file.size);
5673
+ }
5674
+ const rawBytes = await entry.session.readFile(normalizedPath);
5675
+ if (!(rawBytes instanceof Uint8Array)) {
5676
+ throw new TypeError("Sandbox readFile returned an invalid byte payload");
5677
+ }
5678
+ if (rawBytes.byteLength > maxFileResponseBytes) {
5679
+ throw fileTooLarge(rawBytes.byteLength);
5680
+ }
5681
+ const bytes = new Uint8Array(rawBytes.byteLength);
5682
+ bytes.set(rawBytes);
5683
+ const disposition = download === "1" ? "attachment" : "inline";
5684
+ const filename = encodeURIComponent(path.posix.basename(normalizedPath));
5685
+ return new Response(bytes, {
5686
+ headers: {
5687
+ "cache-control": "no-store",
5688
+ "content-disposition": `${disposition}; filename*=UTF-8''${filename}`,
5689
+ "content-length": String(bytes.byteLength),
5690
+ "content-type": "application/octet-stream",
5691
+ "x-content-type-options": "nosniff"
5692
+ }
5693
+ });
5694
+ } catch (error) {
5695
+ return sandboxErrorResponse(c, error);
5696
+ }
5697
+ });
5698
+ app.get("/sandboxes/:sandboxRef/ports", (c) => {
5699
+ const entry = registry.get(c.req.param("sandboxRef"));
5700
+ if (entry === void 0) {
5701
+ return errorResponse(c, 404, "not_found", "Sandbox not found");
5702
+ }
5703
+ if (!supportsPorts(entry.session)) {
5704
+ return unsupportedSandboxOperation(c, "ports");
5705
+ }
5706
+ try {
5707
+ return c.json({
5708
+ sandboxRef: entry.summary.ref,
5709
+ ports: normalizePorts(entry.session.publishedPorts)
5710
+ });
5711
+ } catch (error) {
5712
+ return sandboxErrorResponse(c, error);
5713
+ }
5714
+ });
5715
+ app.get("/sandboxes/:sandboxRef/processes", async (c) => {
5716
+ const entry = registry.get(c.req.param("sandboxRef"));
5717
+ if (entry === void 0) {
5718
+ return errorResponse(c, 404, "not_found", "Sandbox not found");
5719
+ }
5720
+ if (!supportsProcesses(entry.session)) {
5721
+ return unsupportedSandboxOperation(c, "processes");
5722
+ }
5723
+ try {
5724
+ return c.json({
5725
+ sandboxRef: entry.summary.ref,
5726
+ processes: normalizeProcesses(await entry.session.listProcesses())
5727
+ });
5728
+ } catch (error) {
5729
+ return sandboxErrorResponse(c, error);
5730
+ }
5731
+ });
5732
+ app.get("/sandboxes/:sandboxRef/processes/:processId/logs", async (c) => {
5733
+ const entry = registry.get(c.req.param("sandboxRef"));
5734
+ if (entry === void 0) {
5735
+ return errorResponse(c, 404, "not_found", "Sandbox not found");
5736
+ }
5737
+ if (!supportsProcesses(entry.session)) {
5738
+ return unsupportedSandboxOperation(c, "processes.logs");
5739
+ }
5740
+ try {
5741
+ const tailBytes = parseTailBytes(c.req.query("tailBytes"));
5742
+ const processId = c.req.param("processId");
5743
+ const processes = normalizeProcesses(await entry.session.listProcesses());
5744
+ if (!processes.some((process2) => process2.id === processId)) {
5745
+ throw new SandboxRouteError(404, "not_found", "Sandbox process not found");
5746
+ }
5747
+ const logs = normalizeProcessLogs(
5748
+ await entry.session.readProcessLogs(processId, { tailBytes }),
5749
+ tailBytes
5750
+ );
5751
+ return c.json({
5752
+ sandboxRef: entry.summary.ref,
5753
+ processId,
5754
+ ...logs
5755
+ });
5756
+ } catch (error) {
5757
+ return sandboxErrorResponse(c, error);
5758
+ }
5759
+ });
5760
+ }
5761
+ async function sandboxNoStore(c, next) {
5762
+ c.header("cache-control", "no-store");
5763
+ await next();
5764
+ }
5765
+ function sandboxSessionFromTool(tool) {
5766
+ const metadata = tool[sandboxToolMetadataKey];
5767
+ if (!isRecord4(metadata)) {
5768
+ return void 0;
5769
+ }
5770
+ return isSandboxSession(metadata.session) ? metadata.session : void 0;
5771
+ }
5772
+ function isSandboxSession(value) {
5773
+ return isRecord4(value) && typeof value.id === "string" && value.id.length > 0 && typeof value.provider === "string" && value.provider.length > 0 && typeof value.workdir === "string" && value.workdir.length > 0 && typeof value.listFiles === "function" && typeof value.readFile === "function";
5774
+ }
5775
+ function supportsPorts(session) {
5776
+ return Array.isArray(session.publishedPorts);
5777
+ }
5778
+ function supportsProcesses(session) {
5779
+ return typeof session.listProcesses === "function" && typeof session.readProcessLogs === "function";
5780
+ }
5781
+ function sandboxRef(provider, id) {
5782
+ return Buffer.from(JSON.stringify([provider, id]), "utf8").toString("base64url");
5783
+ }
5784
+ function copySummary(summary) {
5785
+ return {
5786
+ ...summary,
5787
+ agentIds: [...summary.agentIds],
5788
+ toolNames: [...summary.toolNames],
5789
+ capabilities: { ...summary.capabilities }
5790
+ };
5791
+ }
5792
+ async function listSandboxFiles(session, filePath) {
5793
+ return normalizeFileEntries(await session.listFiles(filePath)).sort((left, right) => {
5794
+ if (left.type === "directory" && right.type !== "directory") return -1;
5795
+ if (left.type !== "directory" && right.type === "directory") return 1;
5796
+ return left.path.localeCompare(right.path);
5797
+ });
5798
+ }
5799
+ function normalizeFileEntries(value) {
5800
+ if (!Array.isArray(value)) {
5801
+ throw new TypeError("Sandbox listFiles returned an invalid payload");
5802
+ }
5803
+ return value.map((item) => {
5804
+ if (!isRecord4(item) || typeof item.path !== "string" || !isFileType(item.type)) {
5805
+ throw new TypeError("Sandbox listFiles returned an invalid entry");
5806
+ }
5807
+ let entryPath;
5808
+ try {
5809
+ entryPath = normalizeSandboxPath(item.path, false);
5810
+ } catch {
5811
+ throw new TypeError("Sandbox listFiles returned an invalid entry path");
5812
+ }
5813
+ const entry = { path: entryPath, type: item.type };
5814
+ if (item.size !== void 0) {
5815
+ if (typeof item.size !== "number" || !Number.isFinite(item.size) || item.size < 0) {
5816
+ throw new TypeError("Sandbox listFiles returned an invalid file size");
5817
+ }
5818
+ entry.size = item.size;
5819
+ }
5820
+ return entry;
5821
+ });
5822
+ }
5823
+ function normalizePorts(value) {
5824
+ if (!Array.isArray(value)) {
5825
+ throw new TypeError("Sandbox publishedPorts returned an invalid payload");
5826
+ }
5827
+ return value.map((item) => {
5828
+ if (!isRecord4(item) || !isPort(item.containerPort) || typeof item.host !== "string" || !isPort(item.hostPort) || typeof item.protocol !== "string") {
5829
+ throw new TypeError("Sandbox publishedPorts returned an invalid entry");
5830
+ }
5831
+ return {
5832
+ containerPort: item.containerPort,
5833
+ host: item.host,
5834
+ hostPort: item.hostPort,
5835
+ protocol: item.protocol
5836
+ };
5837
+ });
5838
+ }
5839
+ function normalizeProcesses(value) {
5840
+ if (!Array.isArray(value)) {
5841
+ throw new TypeError("Sandbox listProcesses returned an invalid payload");
5842
+ }
5843
+ return value.map((item) => {
5844
+ if (!isRecord4(item) || typeof item.id !== "string" || typeof item.command !== "string" || !Array.isArray(item.args) || !item.args.every((arg) => typeof arg === "string") || !isProcessStatus(item.status) || typeof item.startedAt !== "string") {
5845
+ throw new TypeError("Sandbox listProcesses returned an invalid entry");
5846
+ }
5847
+ const process2 = {
5848
+ id: item.id,
5849
+ command: item.command,
5850
+ args: item.args,
5851
+ status: item.status,
5852
+ startedAt: item.startedAt
5853
+ };
5854
+ if (typeof item.cwd === "string") process2.cwd = item.cwd;
5855
+ if (typeof item.exitCode === "number") process2.exitCode = item.exitCode;
5856
+ if (typeof item.endedAt === "string") process2.endedAt = item.endedAt;
5857
+ return process2;
5858
+ });
5859
+ }
5860
+ function normalizeProcessLogs(value, maxBytes) {
5861
+ if (!isRecord4(value) || typeof value.stdout !== "string" || typeof value.stderr !== "string" || typeof value.stdoutTruncated !== "boolean" || typeof value.stderrTruncated !== "boolean") {
5862
+ throw new TypeError("Sandbox readProcessLogs returned an invalid payload");
5863
+ }
5864
+ const stdout = boundedLogText(value.stdout, maxBytes);
5865
+ const stderr = boundedLogText(value.stderr, maxBytes);
5866
+ return {
5867
+ stdout: stdout.text,
5868
+ stderr: stderr.text,
5869
+ stdoutTruncated: value.stdoutTruncated || stdout.truncated,
5870
+ stderrTruncated: value.stderrTruncated || stderr.truncated
5871
+ };
5872
+ }
5873
+ function boundedLogText(value, maxBytes) {
5874
+ const bytes = new TextEncoder().encode(value);
5875
+ if (bytes.byteLength <= maxBytes) {
5876
+ return { text: value, truncated: false };
5877
+ }
5878
+ if (maxBytes === 0) {
5879
+ return { text: "", truncated: true };
5880
+ }
5881
+ let start = bytes.byteLength - maxBytes;
5882
+ while (start < bytes.byteLength && (bytes[start] ?? 0) >> 6 === 2) {
5883
+ start += 1;
5884
+ }
5885
+ return {
5886
+ text: new TextDecoder().decode(bytes.subarray(start)),
5887
+ truncated: true
5888
+ };
5889
+ }
5890
+ function normalizeSandboxPath(input, allowRoot) {
5891
+ if (input.length === 0) {
5892
+ throw new SandboxRouteError(400, "bad_request", "Sandbox path cannot be empty");
5893
+ }
5894
+ if (input.includes("\0")) {
5895
+ throw new SandboxRouteError(400, "bad_request", "Sandbox path cannot contain null bytes");
5896
+ }
5897
+ const normalized = path.posix.normalize(input.replaceAll("\\", "/"));
5898
+ if (path.posix.isAbsolute(normalized)) {
5899
+ throw new SandboxRouteError(400, "bad_request", "Sandbox path must be relative");
5900
+ }
5901
+ if (normalized === ".." || normalized.startsWith("../")) {
5902
+ throw new SandboxRouteError(400, "bad_request", "Sandbox path cannot leave the workspace");
5903
+ }
5904
+ if (normalized === "." && !allowRoot) {
5905
+ throw new SandboxRouteError(400, "bad_request", "Sandbox path must refer to a file");
5906
+ }
5907
+ return normalized;
5908
+ }
5909
+ function parseTailBytes(value) {
5910
+ if (value === void 0) {
5911
+ return defaultProcessLogBytes;
5912
+ }
5913
+ const parsed = Number(value);
5914
+ if (!Number.isInteger(parsed) || parsed < 0 || parsed > maxProcessLogBytes) {
5915
+ throw new SandboxRouteError(
5916
+ 400,
5917
+ "bad_request",
5918
+ `tailBytes must be an integer from 0 through ${maxProcessLogBytes}`
5919
+ );
5920
+ }
5921
+ return parsed;
5922
+ }
5923
+ function unsupportedSandboxOperation(c, operation) {
5924
+ return errorResponse(c, 501, "unsupported_capability", `Sandbox does not support ${operation}`, {
5925
+ capability: "sandboxes",
5926
+ operation
5927
+ });
5928
+ }
5929
+ function sandboxErrorResponse(c, error) {
5930
+ if (error instanceof SandboxRouteError) {
5931
+ return errorResponse(c, error.status, error.code, error.message);
5932
+ }
5933
+ const name = errorName(error);
5934
+ if (name === "SandboxPathError") {
5935
+ return errorResponse(c, 400, "bad_request", errorMessage(error));
5936
+ }
5937
+ if (name === "SandboxFileSizeError") {
5938
+ return errorResponse(c, 413, "payload_too_large", errorMessage(error));
5939
+ }
5940
+ if (name === "SandboxSessionDestroyedError") {
5941
+ return errorResponse(c, 409, "conflict", "Sandbox session is no longer available");
5942
+ }
5943
+ return errorResponse(
5944
+ c,
5945
+ 500,
5946
+ "internal_error",
5947
+ "Sandbox inspection failed",
5948
+ serializeError(error)
5949
+ );
5950
+ }
5951
+ function fileTooLarge(size) {
5952
+ return new SandboxRouteError(
5953
+ 413,
5954
+ "payload_too_large",
5955
+ `Sandbox file exceeds the ${maxFileResponseBytes}-byte response limit (${size} bytes)`
5956
+ );
5957
+ }
5958
+ var SandboxRouteError = class extends Error {
5959
+ constructor(status, code, message) {
5960
+ super(message);
5961
+ this.status = status;
5962
+ this.code = code;
5963
+ }
5964
+ status;
5965
+ code;
5966
+ };
5967
+ function isFileType(value) {
5968
+ return value === "file" || value === "directory" || value === "symlink" || value === "other";
5969
+ }
5970
+ function isProcessStatus(value) {
5971
+ return value === "running" || value === "exited" || value === "stopped";
5972
+ }
5973
+ function isPort(value) {
5974
+ return Number.isInteger(value) && Number(value) >= 1 && Number(value) <= 65535;
5975
+ }
5976
+ function isRecord4(value) {
5977
+ return typeof value === "object" && value !== null;
5978
+ }
5979
+ function errorName(error) {
5980
+ return isRecord4(error) && typeof error.name === "string" ? error.name : "";
5981
+ }
5982
+ function errorMessage(error) {
5983
+ return isRecord4(error) && typeof error.message === "string" ? error.message : String(error);
5984
+ }
5985
+
5020
5986
  // src/runtime/sessions.ts
5021
5987
  function registerSessionRoutes(app, props) {
5022
5988
  app.get("/sessions", async (c) => {
@@ -5505,6 +6471,7 @@ function registerStatusRoutes(app, props) {
5505
6471
  agents: props.agents.length,
5506
6472
  pipelines: props.pipelines.length
5507
6473
  };
6474
+ if (props.sandboxCount > 0) counts.sandboxes = props.sandboxCount;
5508
6475
  if (props.stores.sessions !== void 0) {
5509
6476
  counts.sessions = (await props.stores.sessions.listSessions({ limit: 100 })).length;
5510
6477
  }
@@ -5525,7 +6492,13 @@ function registerStatusRoutes(app, props) {
5525
6492
  runner,
5526
6493
  storage,
5527
6494
  counts,
5528
- capabilities: capabilityConfig(props.options, props.agents, props.pipelines, props.stores),
6495
+ capabilities: capabilityConfig(
6496
+ props.options,
6497
+ props.agents,
6498
+ props.pipelines,
6499
+ props.stores,
6500
+ props.sandboxCount
6501
+ ),
5529
6502
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
5530
6503
  };
5531
6504
  return c.json(summary);
@@ -5734,20 +6707,38 @@ var Studio = class {
5734
6707
  const log = serveOptions.log ?? true;
5735
6708
  if (log) {
5736
6709
  const host = serveOptions.hostname ?? "localhost";
5737
- if (isStudioUiEnabled(this.options.ui)) {
5738
- const uiPath = studioUiEntryPath(resolveStudioUiOptions(this.options.ui));
5739
- console.log(`Studio UI: http://${host}:${port}${uiPath}`);
5740
- } else {
5741
- console.log(`Studio API: http://${host}:${port}`);
5742
- }
6710
+ this.logAddress(host, port);
6711
+ }
6712
+ if (serveOptions.handleSignals ?? true) {
6713
+ this.sigintHandler = () => {
6714
+ this.close();
6715
+ process.exit(0);
6716
+ };
6717
+ process.once("SIGINT", this.sigintHandler);
5743
6718
  }
5744
- this.sigintHandler = () => {
5745
- this.close();
5746
- process.exit(0);
5747
- };
5748
- process.once("SIGINT", this.sigintHandler);
5749
6719
  return this;
5750
6720
  }
6721
+ async serve(serveOptions = {}) {
6722
+ const { onShutdown, signal, ...startOptions } = serveOptions;
6723
+ try {
6724
+ this.start({ ...startOptions, log: false, handleSignals: false });
6725
+ const server = this.server;
6726
+ if (server === void 0) {
6727
+ throw new Error("Studio server did not start");
6728
+ }
6729
+ await waitForServerListening(server);
6730
+ if (startOptions.log ?? true) {
6731
+ const requestedPort = startOptions.port ?? Number(process.env.RUNNER_PORT ?? 4021);
6732
+ const address = server.address();
6733
+ const port = typeof address === "object" && address !== null ? address.port : requestedPort;
6734
+ this.logAddress(startOptions.hostname ?? "localhost", port);
6735
+ }
6736
+ await waitForShutdown(signal);
6737
+ } finally {
6738
+ this.close();
6739
+ await onShutdown?.();
6740
+ }
6741
+ }
5751
6742
  close() {
5752
6743
  if (this.sigintHandler !== void 0) {
5753
6744
  process.off("SIGINT", this.sigintHandler);
@@ -5757,7 +6748,68 @@ var Studio = class {
5757
6748
  this.server = void 0;
5758
6749
  this.studio.close();
5759
6750
  }
6751
+ logAddress(host, port) {
6752
+ if (isStudioUiEnabled(this.options.ui)) {
6753
+ const uiPath = studioUiEntryPath(resolveStudioUiOptions(this.options.ui));
6754
+ console.log(`Studio UI: http://${host}:${port}${uiPath}`);
6755
+ } else {
6756
+ console.log(`Studio API: http://${host}:${port}`);
6757
+ }
6758
+ }
5760
6759
  };
6760
+ function waitForServerListening(server) {
6761
+ if (server.listening) return Promise.resolve();
6762
+ return new Promise((resolve2, reject) => {
6763
+ const cleanup = () => {
6764
+ server.off("listening", onListening);
6765
+ server.off("error", onError);
6766
+ };
6767
+ const onListening = () => {
6768
+ cleanup();
6769
+ resolve2();
6770
+ };
6771
+ const onError = (error) => {
6772
+ cleanup();
6773
+ reject(error);
6774
+ };
6775
+ server.once("listening", onListening);
6776
+ server.once("error", onError);
6777
+ });
6778
+ }
6779
+ function waitForShutdown(signal) {
6780
+ if (signal?.aborted) return Promise.resolve();
6781
+ return new Promise((resolve2) => {
6782
+ const useRawInput = process.stdin.isTTY === true;
6783
+ const wasRaw = process.stdin.isRaw;
6784
+ const wasFlowing = process.stdin.readableFlowing;
6785
+ const cleanup = () => {
6786
+ process.off("SIGINT", finish);
6787
+ process.off("SIGTERM", finish);
6788
+ signal?.removeEventListener("abort", finish);
6789
+ if (useRawInput) {
6790
+ process.stdin.off("data", onInput);
6791
+ if (!wasRaw && process.stdin.isRaw) process.stdin.setRawMode(false);
6792
+ if (wasFlowing !== true) process.stdin.pause();
6793
+ }
6794
+ };
6795
+ const finish = () => {
6796
+ cleanup();
6797
+ resolve2();
6798
+ };
6799
+ const onInput = (chunk) => {
6800
+ const interrupted = typeof chunk === "string" ? chunk.includes("") : chunk.includes(3);
6801
+ if (interrupted) finish();
6802
+ };
6803
+ process.once("SIGINT", finish);
6804
+ process.once("SIGTERM", finish);
6805
+ signal?.addEventListener("abort", finish, { once: true });
6806
+ if (useRawInput) {
6807
+ if (!wasRaw) process.stdin.setRawMode(true);
6808
+ process.stdin.resume();
6809
+ process.stdin.on("data", onInput);
6810
+ }
6811
+ });
6812
+ }
5761
6813
  function studioOptionsFromTargets(targets, options) {
5762
6814
  const agents = targets.filter((target) => target instanceof Agent2);
5763
6815
  const pipelines = targets.filter(
@@ -5836,6 +6888,8 @@ function createStudioApp(options) {
5836
6888
  const evalMap = new Map(options.evals.map((suite) => [suite.id ?? suite.name, suite]));
5837
6889
  const approvalRuntime = createApprovalRuntime();
5838
6890
  const questionRuntime = createQuestionRuntime();
6891
+ const sandboxRegistry = createStudioSandboxRegistry(agents);
6892
+ const memorySources = createStudioMemorySourceRegistry(agents, stores.sessions);
5839
6893
  const app = new HonoApp();
5840
6894
  const uiOptions = isStudioUiEnabled(options.ui) ? resolveStudioUiOptions(options.ui) : void 0;
5841
6895
  if (uiOptions !== void 0 && !uiOptions.protectShell) {
@@ -5855,8 +6909,17 @@ function createStudioApp(options) {
5855
6909
  }
5856
6910
  })
5857
6911
  );
5858
- app.get("/config", (c) => c.json(buildConfig(options, agents, pipelines, stores)));
5859
- registerStatusRoutes(app, { options, agents, pipelines, stores });
6912
+ app.get(
6913
+ "/config",
6914
+ (c) => c.json(buildConfig(options, agents, pipelines, stores, sandboxRegistry.size))
6915
+ );
6916
+ registerStatusRoutes(app, {
6917
+ options,
6918
+ agents,
6919
+ pipelines,
6920
+ stores,
6921
+ sandboxCount: sandboxRegistry.size
6922
+ });
5860
6923
  app.get("/agents", (c) => c.json({ agents: agents.map(agentConfig) }));
5861
6924
  app.get("/agents/:agentId", (c) => {
5862
6925
  const agent = agentMap.get(c.req.param("agentId"));
@@ -5875,6 +6938,7 @@ function createStudioApp(options) {
5875
6938
  registerModelRoutes(app, { registry: modelRegistry, agentMap });
5876
6939
  registerMcpRoutes(app, { agentMap });
5877
6940
  registerToolRoutes(app, { agentMap });
6941
+ registerSandboxRoutes(app, sandboxRegistry);
5878
6942
  registerApprovalRoutes(app, approvalRuntime);
5879
6943
  registerQuestionRoutes(app, questionRuntime);
5880
6944
  registerObservabilityRoutes(app, observabilityHub);
@@ -5899,10 +6963,13 @@ function createStudioApp(options) {
5899
6963
  approvalRuntime,
5900
6964
  questionRuntime
5901
6965
  });
5902
- if (stores.sessions !== void 0) {
6966
+ if (memorySources.size > 0 || stores.sessions !== void 0) {
5903
6967
  registerMemoryRoutes(app, {
6968
+ sources: memorySources,
5904
6969
  sessionStore: stores.sessions
5905
6970
  });
6971
+ }
6972
+ if (stores.sessions !== void 0) {
5906
6973
  const sessionOptions = {
5907
6974
  agentMap,
5908
6975
  sessionStore: stores.sessions
@@ -5923,7 +6990,7 @@ function createStudioApp(options) {
5923
6990
  return app.fetch(request);
5924
6991
  },
5925
6992
  config() {
5926
- return buildConfig(options, agents, pipelines, stores);
6993
+ return buildConfig(options, agents, pipelines, stores, sandboxRegistry.size);
5927
6994
  },
5928
6995
  close() {
5929
6996
  }
@@ -5962,8 +7029,8 @@ function createSqliteSessionStore(options = {}) {
5962
7029
  return new SqliteSessionStore(options.path ?? ":memory:");
5963
7030
  }
5964
7031
  var SqliteSessionStore = class {
5965
- constructor(path) {
5966
- this.path = path;
7032
+ constructor(path2) {
7033
+ this.path = path2;
5967
7034
  }
5968
7035
  path;
5969
7036
  kind = "sqlite";