@bolt-foundry/gambit 0.8.0 → 0.8.3

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 (61) hide show
  1. package/CHANGELOG.md +82 -2
  2. package/README.md +31 -9
  3. package/esm/gambit/simulator-ui/dist/bundle.js +4744 -4360
  4. package/esm/gambit/simulator-ui/dist/bundle.js.map +4 -4
  5. package/esm/gambit/simulator-ui/dist/favicon.ico +0 -0
  6. package/esm/mod.d.ts +7 -3
  7. package/esm/mod.d.ts.map +1 -1
  8. package/esm/mod.js +5 -1
  9. package/esm/src/cli_utils.d.ts +3 -2
  10. package/esm/src/cli_utils.d.ts.map +1 -1
  11. package/esm/src/cli_utils.js +43 -27
  12. package/esm/src/openai_compat.d.ts +63 -0
  13. package/esm/src/openai_compat.d.ts.map +1 -0
  14. package/esm/src/openai_compat.js +277 -0
  15. package/esm/src/providers/google.d.ts +16 -0
  16. package/esm/src/providers/google.d.ts.map +1 -0
  17. package/esm/src/providers/google.js +352 -0
  18. package/esm/src/providers/ollama.d.ts +17 -0
  19. package/esm/src/providers/ollama.d.ts.map +1 -0
  20. package/esm/src/providers/ollama.js +509 -0
  21. package/esm/src/providers/openrouter.d.ts +14 -1
  22. package/esm/src/providers/openrouter.d.ts.map +1 -1
  23. package/esm/src/providers/openrouter.js +460 -463
  24. package/esm/src/server.d.ts +4 -0
  25. package/esm/src/server.d.ts.map +1 -1
  26. package/esm/src/server.js +623 -164
  27. package/esm/src/trace.d.ts.map +1 -1
  28. package/esm/src/trace.js +3 -6
  29. package/package.json +2 -2
  30. package/script/gambit/simulator-ui/dist/bundle.js +4744 -4360
  31. package/script/gambit/simulator-ui/dist/bundle.js.map +4 -4
  32. package/script/gambit/simulator-ui/dist/favicon.ico +0 -0
  33. package/script/mod.d.ts +7 -3
  34. package/script/mod.d.ts.map +1 -1
  35. package/script/mod.js +9 -3
  36. package/script/src/cli_utils.d.ts +3 -2
  37. package/script/src/cli_utils.d.ts.map +1 -1
  38. package/script/src/cli_utils.js +42 -26
  39. package/script/src/openai_compat.d.ts +63 -0
  40. package/script/src/openai_compat.d.ts.map +1 -0
  41. package/script/src/openai_compat.js +281 -0
  42. package/script/src/providers/google.d.ts +16 -0
  43. package/script/src/providers/google.d.ts.map +1 -0
  44. package/script/src/providers/google.js +359 -0
  45. package/script/src/providers/ollama.d.ts +17 -0
  46. package/script/src/providers/ollama.d.ts.map +1 -0
  47. package/script/src/providers/ollama.js +551 -0
  48. package/script/src/providers/openrouter.d.ts +14 -1
  49. package/script/src/providers/openrouter.d.ts.map +1 -1
  50. package/script/src/providers/openrouter.js +461 -463
  51. package/script/src/server.d.ts +4 -0
  52. package/script/src/server.d.ts.map +1 -1
  53. package/script/src/server.js +623 -164
  54. package/script/src/trace.d.ts.map +1 -1
  55. package/script/src/trace.js +3 -6
  56. package/esm/src/compat/openai.d.ts +0 -2
  57. package/esm/src/compat/openai.d.ts.map +0 -1
  58. package/esm/src/compat/openai.js +0 -1
  59. package/script/src/compat/openai.d.ts +0 -2
  60. package/script/src/compat/openai.d.ts.map +0 -1
  61. package/script/src/compat/openai.js +0 -5
@@ -42,6 +42,7 @@ const trace_js_1 = require("./trace.js");
42
42
  const cli_utils_js_1 = require("./cli_utils.js");
43
43
  const gambit_core_2 = require("@bolt-foundry/gambit-core");
44
44
  const durable_streams_js_1 = require("./durable_streams.js");
45
+ const GAMBIT_TOOL_RESPOND = "gambit_respond";
45
46
  const logger = console;
46
47
  const moduleLocation = (() => {
47
48
  const directoryFromUrl = (url) => {
@@ -83,11 +84,12 @@ const simulatorBundleSourceMapUrl = (() => {
83
84
  let cachedRemoteBundle = null;
84
85
  let cachedRemoteBundleSourceMap = null;
85
86
  const simulatorBundlePath = path.resolve(moduleDir, "..", "simulator-ui", "dist", "bundle.js");
86
- const simulatorUiEntryPath = path.resolve(moduleDir, "..", "simulator-ui", "src", "main.tsx");
87
87
  const simulatorBundleSourceMapPath = path.resolve(moduleDir, "..", "simulator-ui", "dist", "bundle.js.map");
88
+ const simulatorFaviconDistPath = path.resolve(moduleDir, "..", "simulator-ui", "dist", "favicon.ico");
89
+ const simulatorFaviconSrcPath = path.resolve(moduleDir, "..", "simulator-ui", "src", "favicon.ico");
88
90
  const SIMULATOR_STREAM_ID = "gambit-simulator";
89
- const TEST_BOT_STREAM_ID = "gambit-test-bot";
90
- const CALIBRATE_STREAM_ID = "gambit-calibrate";
91
+ const GRADE_STREAM_ID = "gambit-grade";
92
+ const TEST_STREAM_ID = "gambit-test";
91
93
  let availableTestDecks = [];
92
94
  const testDeckByPath = new Map();
93
95
  const testDeckById = new Map();
@@ -98,87 +100,6 @@ function randomId(prefix) {
98
100
  const suffix = crypto.randomUUID().replace(/-/g, "").slice(0, 24);
99
101
  return `${prefix}-${suffix}`;
100
102
  }
101
- async function parseOpenResponseRequest(req) {
102
- try {
103
- return await req.json();
104
- }
105
- catch {
106
- return null;
107
- }
108
- }
109
- function formatOpenResponseSseEvent(event) {
110
- return `event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`;
111
- }
112
- function formatOpenResponseDoneEvent() {
113
- return "data: [DONE]\n\n";
114
- }
115
- function createOpenResponseStream(req, provider, payload) {
116
- const encoder = new TextEncoder();
117
- const stream = new ReadableStream({
118
- start(controller) {
119
- let closed = false;
120
- let completed = false;
121
- const close = () => {
122
- if (closed)
123
- return;
124
- closed = true;
125
- controller.close();
126
- };
127
- const send = (chunk) => {
128
- if (closed)
129
- return;
130
- controller.enqueue(encoder.encode(chunk));
131
- };
132
- const sendDone = () => {
133
- if (closed)
134
- return;
135
- send(formatOpenResponseDoneEvent());
136
- close();
137
- };
138
- const sendEvent = (event) => {
139
- if (closed)
140
- return;
141
- send(formatOpenResponseSseEvent(event));
142
- if (event.type === "response.completed") {
143
- completed = true;
144
- sendDone();
145
- }
146
- };
147
- req.signal.addEventListener("abort", () => {
148
- close();
149
- });
150
- (async () => {
151
- try {
152
- const response = await provider.responses({
153
- ...payload,
154
- stream: true,
155
- onStreamEvent: sendEvent,
156
- });
157
- if (!completed) {
158
- sendEvent({ type: "response.completed", response });
159
- sendDone();
160
- }
161
- }
162
- catch (err) {
163
- const message = err instanceof Error ? err.message : String(err);
164
- sendEvent({
165
- type: "error",
166
- error: { code: "server_error", message },
167
- });
168
- sendDone();
169
- }
170
- })();
171
- },
172
- });
173
- return new Response(stream, {
174
- status: 200,
175
- headers: {
176
- "Content-Type": "text/event-stream",
177
- "Cache-Control": "no-cache",
178
- "Connection": "keep-alive",
179
- },
180
- });
181
- }
182
103
  function resolveDefaultValue(raw) {
183
104
  if (typeof raw === "function") {
184
105
  try {
@@ -424,11 +345,170 @@ function deriveInitialFromSchema(schema) {
424
345
  return undefined;
425
346
  }
426
347
  }
348
+ function getPathValue(value, path) {
349
+ let current = value;
350
+ for (const segment of path) {
351
+ if (!current || typeof current !== "object" ||
352
+ !(segment in current)) {
353
+ return undefined;
354
+ }
355
+ current = current[segment];
356
+ }
357
+ return current;
358
+ }
359
+ function setPathValue(value, path, nextValue) {
360
+ if (path.length === 0)
361
+ return nextValue;
362
+ const root = value && typeof value === "object"
363
+ ? cloneValue(value)
364
+ : {};
365
+ let cursor = root;
366
+ for (let i = 0; i < path.length - 1; i++) {
367
+ const segment = path[i];
368
+ const existing = cursor[segment];
369
+ const next = existing && typeof existing === "object"
370
+ ? cloneValue(existing)
371
+ : {};
372
+ cursor[segment] = next;
373
+ cursor = next;
374
+ }
375
+ const last = path[path.length - 1];
376
+ if (nextValue === undefined) {
377
+ delete cursor[last];
378
+ }
379
+ else {
380
+ cursor[last] = nextValue;
381
+ }
382
+ return root;
383
+ }
384
+ function findMissingRequiredFields(schema, value, prefix = []) {
385
+ if (!schema)
386
+ return [];
387
+ if (schema.optional)
388
+ return [];
389
+ if (schema.kind === "object" && schema.fields) {
390
+ if (value !== undefined && value !== null &&
391
+ (typeof value !== "object" || Array.isArray(value))) {
392
+ return [];
393
+ }
394
+ const asObj = value && typeof value === "object"
395
+ ? value
396
+ : undefined;
397
+ const missing = [];
398
+ for (const [key, child] of Object.entries(schema.fields)) {
399
+ missing.push(...findMissingRequiredFields(child, asObj ? asObj[key] : undefined, [...prefix, key]));
400
+ }
401
+ return missing;
402
+ }
403
+ const key = prefix.join(".") || "(root)";
404
+ if (value === undefined || value === null) {
405
+ return schema.defaultValue !== undefined ? [] : [key];
406
+ }
407
+ if (schema.kind === "string" || schema.kind === "enum") {
408
+ return typeof value === "string" && value.trim() === "" ? [key] : [];
409
+ }
410
+ if (schema.kind === "array") {
411
+ return Array.isArray(value) && value.length === 0 ? [key] : [];
412
+ }
413
+ if (schema.kind === "number") {
414
+ return typeof value === "number" && Number.isFinite(value) ? [] : [key];
415
+ }
416
+ if (schema.kind === "boolean") {
417
+ return typeof value === "boolean" ? [] : [key];
418
+ }
419
+ return [];
420
+ }
421
+ function getSchemaAtPath(schema, path) {
422
+ let current = schema;
423
+ for (const segment of path) {
424
+ if (!current || current.kind !== "object" || !current.fields)
425
+ return;
426
+ current = current.fields[segment];
427
+ }
428
+ return current;
429
+ }
430
+ function buildInitFillPrompt(args) {
431
+ const schemaHints = args.missing.map((path) => {
432
+ const segments = path === "(root)" ? [] : path.split(".");
433
+ const leaf = getSchemaAtPath(args.schema, segments);
434
+ return {
435
+ path,
436
+ kind: leaf?.kind,
437
+ description: leaf?.description,
438
+ enumValues: leaf?.enumValues,
439
+ };
440
+ });
441
+ const payload = {
442
+ type: "gambit_test_bot_init_fill",
443
+ missing: args.missing,
444
+ current: args.current ?? null,
445
+ schemaHints,
446
+ };
447
+ return [
448
+ "You are filling missing required init fields for a Gambit Test Bot run.",
449
+ "Return ONLY valid JSON that includes values for the missing fields.",
450
+ "Do not include any fields that are not listed as missing.",
451
+ "If the only missing path is '(root)', return the full init JSON value.",
452
+ "",
453
+ JSON.stringify(payload, null, 2),
454
+ ].join("\n");
455
+ }
456
+ function unwrapRespondPayload(output) {
457
+ if (!output || typeof output !== "object")
458
+ return output;
459
+ const record = output;
460
+ if ("payload" in record) {
461
+ return record.payload;
462
+ }
463
+ return output;
464
+ }
465
+ function parseInitFillOutput(output) {
466
+ if (output === null || output === undefined) {
467
+ return { error: "Persona returned empty init fill output." };
468
+ }
469
+ if (typeof output === "object") {
470
+ return { data: unwrapRespondPayload(output) };
471
+ }
472
+ if (typeof output === "string") {
473
+ const text = output.trim();
474
+ if (!text)
475
+ return { error: "Persona returned empty init fill output." };
476
+ try {
477
+ const parsed = JSON.parse(text);
478
+ return { data: unwrapRespondPayload(parsed) };
479
+ }
480
+ catch (err) {
481
+ return {
482
+ error: `Persona returned invalid JSON for init fill: ${err instanceof Error ? err.message : String(err)}`,
483
+ };
484
+ }
485
+ }
486
+ return { error: "Persona returned unsupported init fill output." };
487
+ }
488
+ function validateInitInput(schema, value) {
489
+ if (!schema)
490
+ return value;
491
+ if (typeof schema.safeParse !== "function") {
492
+ throw new Error("Init schema missing safeParse");
493
+ }
494
+ const result = schema.safeParse(value);
495
+ if (!result.success) {
496
+ const issue = result.error.issues?.[0];
497
+ const message = issue
498
+ ? `${issue.path.join(".") || "(root)"}: ${issue.message}`
499
+ : result.error.message;
500
+ throw new Error(`Schema validation failed: ${message}`);
501
+ }
502
+ return result.data;
503
+ }
427
504
  /**
428
505
  * Start the WebSocket simulator server used by the Gambit debug UI.
429
506
  */
430
507
  function startWebSocketSimulator(opts) {
431
508
  const port = opts.port ?? 8000;
509
+ const initialContext = opts.initialContext;
510
+ const hasInitialContext = opts.contextProvided ??
511
+ (initialContext !== undefined);
432
512
  const consoleTracer = opts.verbose ? (0, trace_js_1.makeConsoleTracer)() : undefined;
433
513
  let resolvedDeckPath = resolveDeckPath(opts.deckPath);
434
514
  const sessionsRoot = (() => {
@@ -459,9 +539,10 @@ function startWebSocketSimulator(opts) {
459
539
  };
460
540
  const testBotRuns = new Map();
461
541
  const broadcastTestBot = (payload) => {
462
- (0, durable_streams_js_1.appendDurableStreamEvent)(TEST_BOT_STREAM_ID, payload);
542
+ (0, durable_streams_js_1.appendDurableStreamEvent)(TEST_STREAM_ID, payload);
463
543
  };
464
544
  let deckSlug = deckSlugFromPath(resolvedDeckPath);
545
+ let deckLabel = undefined;
465
546
  const enrichStateWithSession = (state) => {
466
547
  const meta = { ...(state.meta ?? {}) };
467
548
  const now = new Date();
@@ -646,12 +727,6 @@ function startWebSocketSimulator(opts) {
646
727
  return "";
647
728
  if (typeof value === "string")
648
729
  return value;
649
- if (Array.isArray(value)) {
650
- return value
651
- .map((part) => typeof part === "string" ? part : part.text ??
652
- "")
653
- .join("");
654
- }
655
730
  try {
656
731
  return JSON.stringify(value);
657
732
  }
@@ -659,6 +734,59 @@ function startWebSocketSimulator(opts) {
659
734
  return String(value);
660
735
  }
661
736
  };
737
+ const safeParseJson = (text) => {
738
+ if (typeof text !== "string" || text.trim().length === 0)
739
+ return undefined;
740
+ try {
741
+ return JSON.parse(text);
742
+ }
743
+ catch {
744
+ return undefined;
745
+ }
746
+ };
747
+ const summarizeRespondCall = (message) => {
748
+ if (!message || message.role !== "tool")
749
+ return null;
750
+ const name = typeof message.name === "string" ? message.name : undefined;
751
+ if (name !== GAMBIT_TOOL_RESPOND)
752
+ return null;
753
+ const parsed = safeParseJson(typeof message.content === "string" ? message.content : "");
754
+ const payload = parsed && typeof parsed === "object"
755
+ ? ("payload" in parsed
756
+ ? parsed.payload
757
+ : parsed)
758
+ : undefined;
759
+ const status = typeof parsed?.status === "number"
760
+ ? parsed.status
761
+ : undefined;
762
+ const code = typeof parsed?.code === "string"
763
+ ? parsed.code
764
+ : undefined;
765
+ const respondMessage = typeof parsed?.message === "string"
766
+ ? parsed.message
767
+ : undefined;
768
+ const meta = parsed && typeof parsed.meta === "object"
769
+ ? parsed.meta
770
+ : undefined;
771
+ const summary = {};
772
+ if (status !== undefined)
773
+ summary.status = status;
774
+ if (code !== undefined)
775
+ summary.code = code;
776
+ if (respondMessage !== undefined)
777
+ summary.message = respondMessage;
778
+ if (meta !== undefined)
779
+ summary.meta = meta;
780
+ summary.payload = payload ?? null;
781
+ return {
782
+ status,
783
+ code,
784
+ message: respondMessage,
785
+ meta,
786
+ payload,
787
+ displayText: JSON.stringify(summary, null, 2),
788
+ };
789
+ };
662
790
  const updateTestDeckRegistry = (list) => {
663
791
  testDeckByPath.clear();
664
792
  testDeckById.clear();
@@ -711,12 +839,11 @@ function startWebSocketSimulator(opts) {
711
839
  const fallbackToolInserts = [];
712
840
  for (let i = 0; i < rawMessages.length; i++) {
713
841
  const msg = rawMessages[i];
714
- if (msg?.type === "message" &&
715
- (msg.role === "assistant" || msg.role === "user")) {
842
+ const refId = refs[i]?.id;
843
+ if (msg?.role === "assistant" || msg?.role === "user") {
716
844
  const content = stringifyContent(msg.content).trim();
717
845
  if (!content)
718
846
  continue;
719
- const refId = refs[i]?.id;
720
847
  messages.push({
721
848
  role: msg.role,
722
849
  content,
@@ -725,7 +852,22 @@ function startWebSocketSimulator(opts) {
725
852
  });
726
853
  continue;
727
854
  }
728
- if (msg?.type === "message" && msg.role === "tool") {
855
+ const respondSummary = summarizeRespondCall(msg);
856
+ if (respondSummary) {
857
+ messages.push({
858
+ role: "assistant",
859
+ content: respondSummary.displayText,
860
+ messageRefId: refId,
861
+ feedback: refId ? feedbackByRef.get(refId) : undefined,
862
+ respondStatus: respondSummary.status,
863
+ respondCode: respondSummary.code,
864
+ respondMessage: respondSummary.message,
865
+ respondPayload: respondSummary.payload,
866
+ respondMeta: respondSummary.meta,
867
+ });
868
+ continue;
869
+ }
870
+ if (msg?.role === "tool") {
729
871
  const actionCallId = typeof msg.tool_call_id === "string"
730
872
  ? msg.tool_call_id
731
873
  : undefined;
@@ -745,6 +887,33 @@ function startWebSocketSimulator(opts) {
745
887
  : fallbackToolInserts,
746
888
  };
747
889
  };
890
+ const buildConversationMessages = (state) => {
891
+ const rawMessages = state.messages ?? [];
892
+ const conversation = [];
893
+ for (const msg of rawMessages) {
894
+ if (msg?.role === "assistant" || msg?.role === "user") {
895
+ const content = stringifyContent(msg.content).trim();
896
+ if (!content)
897
+ continue;
898
+ conversation.push({
899
+ role: msg.role,
900
+ content,
901
+ name: msg.name,
902
+ tool_calls: msg.tool_calls,
903
+ });
904
+ continue;
905
+ }
906
+ const respondSummary = summarizeRespondCall(msg);
907
+ if (respondSummary) {
908
+ conversation.push({
909
+ role: "assistant",
910
+ content: respondSummary.displayText,
911
+ name: GAMBIT_TOOL_RESPOND,
912
+ });
913
+ }
914
+ }
915
+ return conversation;
916
+ };
748
917
  const deriveToolInsertsFromTraces = (state, messageCount) => {
749
918
  const traces = Array.isArray(state.traces) ? state.traces : [];
750
919
  if (!traces.length)
@@ -789,6 +958,10 @@ function startWebSocketSimulator(opts) {
789
958
  : undefined;
790
959
  if (sessionId)
791
960
  run.sessionId = sessionId;
961
+ const initFill = state.meta
962
+ ?.testBotInitFill;
963
+ if (initFill)
964
+ run.initFill = initFill;
792
965
  run.traces = Array.isArray(state.traces) ? [...state.traces] : undefined;
793
966
  };
794
967
  const startTestBotRun = (runOpts = {}) => {
@@ -826,9 +999,27 @@ function startWebSocketSimulator(opts) {
826
999
  };
827
1000
  testBotRuns.set(runId, entry);
828
1001
  const run = entry.run;
1002
+ if (runOpts.initFill)
1003
+ run.initFill = runOpts.initFill;
829
1004
  let savedState = undefined;
830
1005
  let lastCount = 0;
831
1006
  const capturedTraces = [];
1007
+ if (runOpts.initFillTrace) {
1008
+ const actionCallId = randomId("initfill");
1009
+ capturedTraces.push({
1010
+ type: "tool.call",
1011
+ runId,
1012
+ actionCallId,
1013
+ name: "gambit_test_bot_init_fill",
1014
+ args: runOpts.initFillTrace.args,
1015
+ }, {
1016
+ type: "tool.result",
1017
+ runId,
1018
+ actionCallId,
1019
+ name: "gambit_test_bot_init_fill",
1020
+ result: runOpts.initFillTrace.result,
1021
+ });
1022
+ }
832
1023
  const setSessionId = (state) => {
833
1024
  const sessionId = typeof state?.meta?.sessionId === "string"
834
1025
  ? state.meta.sessionId
@@ -861,7 +1052,7 @@ function startWebSocketSimulator(opts) {
861
1052
  const getLastAssistantMessage = (history) => {
862
1053
  for (let i = history.length - 1; i >= 0; i--) {
863
1054
  const msg = history[i];
864
- if (msg?.type === "message" && msg.role === "assistant") {
1055
+ if (msg?.role === "assistant") {
865
1056
  return stringifyContent(msg.content);
866
1057
  }
867
1058
  }
@@ -884,6 +1075,7 @@ function startWebSocketSimulator(opts) {
884
1075
  },
885
1076
  stream: Boolean(streamOpts?.onStreamText),
886
1077
  onStreamText: streamOpts?.onStreamText,
1078
+ responsesMode: opts.responsesMode,
887
1079
  });
888
1080
  if ((0, gambit_core_1.isGambitEndSignal)(result)) {
889
1081
  sessionEnded = true;
@@ -907,6 +1099,7 @@ function startWebSocketSimulator(opts) {
907
1099
  state: savedState,
908
1100
  allowRootStringInput: true,
909
1101
  initialUserMessage: initialUserMessage || undefined,
1102
+ responsesMode: opts.responsesMode,
910
1103
  onStateUpdate: (state) => {
911
1104
  const nextMeta = {
912
1105
  ...(savedState?.meta ?? {}),
@@ -915,6 +1108,7 @@ function startWebSocketSimulator(opts) {
915
1108
  testBotRunId: runId,
916
1109
  testBotConfigPath: botConfigPath,
917
1110
  testBotName,
1111
+ ...(run.initFill ? { testBotInitFill: run.initFill } : {}),
918
1112
  };
919
1113
  const enriched = persistSessionState({
920
1114
  ...state,
@@ -966,6 +1160,7 @@ function startWebSocketSimulator(opts) {
966
1160
  state: savedState,
967
1161
  allowRootStringInput: true,
968
1162
  initialUserMessage: userMessage,
1163
+ responsesMode: opts.responsesMode,
969
1164
  onStateUpdate: (state) => {
970
1165
  const nextMeta = {
971
1166
  ...(savedState?.meta ?? {}),
@@ -974,6 +1169,7 @@ function startWebSocketSimulator(opts) {
974
1169
  testBotRunId: runId,
975
1170
  testBotConfigPath: botConfigPath,
976
1171
  testBotName,
1172
+ ...(run.initFill ? { testBotInitFill: run.initFill } : {}),
977
1173
  };
978
1174
  const enriched = persistSessionState({
979
1175
  ...state,
@@ -1032,10 +1228,60 @@ function startWebSocketSimulator(opts) {
1032
1228
  broadcastTestBot({ type: "testBotStatus", run });
1033
1229
  return run;
1034
1230
  };
1231
+ const persistFailedInitFill = (args) => {
1232
+ const failedRunId = randomId("testbot");
1233
+ const testBotName = path.basename(args.botDeckPath).replace(/\.deck\.(md|ts)$/i, "");
1234
+ const actionCallId = randomId("initfill");
1235
+ const traces = [
1236
+ {
1237
+ type: "tool.call",
1238
+ runId: failedRunId,
1239
+ actionCallId,
1240
+ name: "gambit_test_bot_init_fill",
1241
+ args: { missing: args.initFill?.requested ?? [] },
1242
+ },
1243
+ {
1244
+ type: "tool.result",
1245
+ runId: failedRunId,
1246
+ actionCallId,
1247
+ name: "gambit_test_bot_init_fill",
1248
+ result: {
1249
+ error: args.error,
1250
+ provided: args.initFill?.provided,
1251
+ },
1252
+ },
1253
+ ];
1254
+ const failedState = persistSessionState({
1255
+ runId: failedRunId,
1256
+ messages: [],
1257
+ traces,
1258
+ meta: {
1259
+ testBot: true,
1260
+ testBotRunId: failedRunId,
1261
+ testBotConfigPath: args.botDeckPath,
1262
+ testBotName,
1263
+ testBotInitFill: args.initFill,
1264
+ testBotInitFillError: args.error,
1265
+ },
1266
+ });
1267
+ const sessionId = typeof failedState.meta?.sessionId === "string"
1268
+ ? failedState.meta.sessionId
1269
+ : undefined;
1270
+ const sessionPath = typeof failedState.meta?.sessionStatePath === "string"
1271
+ ? failedState.meta.sessionStatePath
1272
+ : undefined;
1273
+ if (sessionPath) {
1274
+ logger.warn(`[sim] init fill failed; session saved to ${sessionPath}`);
1275
+ }
1276
+ return { sessionId, sessionPath };
1277
+ };
1035
1278
  const deckLoadPromise = (0, gambit_core_2.loadDeck)(resolvedDeckPath)
1036
1279
  .then((deck) => {
1037
1280
  resolvedDeckPath = deck.path;
1038
1281
  deckSlug = deckSlugFromPath(resolvedDeckPath);
1282
+ deckLabel = typeof deck.label === "string"
1283
+ ? deck.label
1284
+ : toDeckLabel(deck.path);
1039
1285
  availableTestDecks = (deck.testDecks ?? []).map((testDeck, index) => {
1040
1286
  const label = testDeck.label && typeof testDeck.label === "string"
1041
1287
  ? testDeck.label
@@ -1082,8 +1328,14 @@ function startWebSocketSimulator(opts) {
1082
1328
  return null;
1083
1329
  });
1084
1330
  const schemaPromise = deckLoadPromise
1085
- .then((deck) => deck ? describeZodSchema(deck.inputSchema) : {
1086
- error: "Deck failed to load",
1331
+ .then((deck) => {
1332
+ const desc = deck ? describeZodSchema(deck.inputSchema) : {
1333
+ error: "Deck failed to load",
1334
+ };
1335
+ if (hasInitialContext) {
1336
+ return { ...desc, defaults: initialContext };
1337
+ }
1338
+ return desc;
1087
1339
  })
1088
1340
  .catch((err) => {
1089
1341
  const message = err instanceof Error ? err.message : String(err);
@@ -1093,14 +1345,21 @@ function startWebSocketSimulator(opts) {
1093
1345
  const wantsSourceMap = Boolean(opts.sourceMap);
1094
1346
  const bundlePlatform = opts.bundlePlatform ?? "deno";
1095
1347
  const autoBundle = opts.autoBundle ?? true;
1348
+ const forceBundle = opts.forceBundle ?? false;
1096
1349
  const needsBundle = !hasReactBundle() ||
1097
1350
  (wantsSourceMap && !hasReactBundleSourceMap()) ||
1098
1351
  isReactBundleStale();
1099
- const shouldAutoBundle = autoBundle && moduleLocation.isLocal && needsBundle;
1352
+ const shouldAutoBundle = autoBundle && moduleLocation.isLocal &&
1353
+ (forceBundle || needsBundle);
1100
1354
  if (autoBundle && !moduleLocation.isLocal && opts.verbose) {
1101
1355
  logger.log("[sim] auto-bundle disabled for remote package; using packaged bundle.");
1102
1356
  }
1357
+ if (autoBundle && moduleLocation.isLocal && !shouldAutoBundle) {
1358
+ logger.log("[sim] auto-bundle enabled; bundle already up to date.");
1359
+ }
1103
1360
  if (shouldAutoBundle) {
1361
+ logger.log(`[sim] auto-bundle enabled; rebuilding simulator UI (${forceBundle ? "forced" : "stale"})...`);
1362
+ logger.log(`[sim] bundling simulator UI (${forceBundle ? "forced" : "stale"})...`);
1104
1363
  try {
1105
1364
  const p = new dntShim.Deno.Command("deno", {
1106
1365
  args: [
@@ -1124,46 +1383,31 @@ function startWebSocketSimulator(opts) {
1124
1383
  }
1125
1384
  const server = dntShim.Deno.serve({ port, signal: opts.signal, onListen: () => { } }, async (req) => {
1126
1385
  const url = new URL(req.url);
1127
- if (url.pathname === "/v1/responses") {
1128
- if (req.method !== "POST") {
1386
+ if (url.pathname.startsWith("/api/durable-streams/stream/")) {
1387
+ return (0, durable_streams_js_1.handleDurableStreamRequest)(req);
1388
+ }
1389
+ if (url.pathname === "/favicon.ico") {
1390
+ if (req.method !== "GET" && req.method !== "HEAD") {
1129
1391
  return new Response("Method not allowed", { status: 405 });
1130
1392
  }
1131
- const payload = await parseOpenResponseRequest(req);
1132
- if (!payload) {
1133
- return new Response("Invalid JSON payload", { status: 400 });
1134
- }
1135
- const model = payload.model ?? opts.model;
1136
- if (!model) {
1137
- return new Response("Missing model", { status: 400 });
1138
- }
1139
- const requestPayload = {
1140
- ...payload,
1141
- model,
1142
- input: payload.input ?? null,
1143
- };
1144
- if (payload.stream) {
1145
- return createOpenResponseStream(req, opts.modelProvider, requestPayload);
1146
- }
1147
1393
  try {
1148
- const response = await opts.modelProvider.responses({
1149
- ...requestPayload,
1150
- stream: false,
1151
- });
1152
- return new Response(JSON.stringify(response), {
1153
- headers: { "content-type": "application/json" },
1394
+ const data = await dntShim.Deno.readFile(simulatorFaviconDistPath);
1395
+ return new Response(req.method === "HEAD" ? null : data, {
1396
+ headers: { "content-type": "image/x-icon" },
1154
1397
  });
1155
1398
  }
1156
- catch (err) {
1157
- const message = err instanceof Error ? err.message : String(err);
1158
- return new Response(JSON.stringify({ error: message }), {
1159
- status: 500,
1160
- headers: { "content-type": "application/json" },
1161
- });
1399
+ catch {
1400
+ try {
1401
+ const data = await dntShim.Deno.readFile(simulatorFaviconSrcPath);
1402
+ return new Response(req.method === "HEAD" ? null : data, {
1403
+ headers: { "content-type": "image/x-icon" },
1404
+ });
1405
+ }
1406
+ catch {
1407
+ return new Response("Not found", { status: 404 });
1408
+ }
1162
1409
  }
1163
1410
  }
1164
- if (url.pathname.startsWith("/api/durable-streams/stream/")) {
1165
- return (0, durable_streams_js_1.handleDurableStreamRequest)(req);
1166
- }
1167
1411
  if (url.pathname === "/api/calibrate") {
1168
1412
  if (req.method !== "GET") {
1169
1413
  return new Response("Method not allowed", { status: 405 });
@@ -1209,11 +1453,10 @@ function startWebSocketSimulator(opts) {
1209
1453
  delete next.gradingRuns;
1210
1454
  return next;
1211
1455
  })();
1456
+ const conversationMessages = buildConversationMessages(sessionState);
1212
1457
  const sessionPayload = {
1213
- messages: Array.isArray(sessionState.messages)
1214
- ? sessionState.messages
1215
- .filter((msg) => msg.type === "message")
1216
- .map((msg) => ({
1458
+ messages: conversationMessages.length > 0
1459
+ ? conversationMessages.map((msg) => ({
1217
1460
  role: msg.role,
1218
1461
  content: msg.content,
1219
1462
  name: msg.name,
@@ -1246,7 +1489,7 @@ function startWebSocketSimulator(opts) {
1246
1489
  },
1247
1490
  });
1248
1491
  const sessionMeta = buildSessionMeta(sessionId, nextState);
1249
- (0, durable_streams_js_1.appendDurableStreamEvent)(CALIBRATE_STREAM_ID, {
1492
+ (0, durable_streams_js_1.appendDurableStreamEvent)(GRADE_STREAM_ID, {
1250
1493
  type: "calibrateSession",
1251
1494
  sessionId,
1252
1495
  run: nextEntry,
@@ -1276,6 +1519,7 @@ function startWebSocketSimulator(opts) {
1276
1519
  allowRootStringInput: false,
1277
1520
  initialUserMessage: undefined,
1278
1521
  stream: false,
1522
+ responsesMode: opts.responsesMode,
1279
1523
  });
1280
1524
  }
1281
1525
  const messages = sessionPayload.messages ?? [];
@@ -1315,6 +1559,7 @@ function startWebSocketSimulator(opts) {
1315
1559
  allowRootStringInput: false,
1316
1560
  initialUserMessage: undefined,
1317
1561
  stream: false,
1562
+ responsesMode: opts.responsesMode,
1318
1563
  });
1319
1564
  turns.push({
1320
1565
  index: idx,
@@ -1417,7 +1662,7 @@ function startWebSocketSimulator(opts) {
1417
1662
  },
1418
1663
  });
1419
1664
  const sessionMeta = buildSessionMeta(body.sessionId, updated);
1420
- (0, durable_streams_js_1.appendDurableStreamEvent)(CALIBRATE_STREAM_ID, {
1665
+ (0, durable_streams_js_1.appendDurableStreamEvent)(GRADE_STREAM_ID, {
1421
1666
  type: "calibrateSession",
1422
1667
  sessionId: body.sessionId,
1423
1668
  session: sessionMeta,
@@ -1470,7 +1715,7 @@ function startWebSocketSimulator(opts) {
1470
1715
  },
1471
1716
  });
1472
1717
  const sessionMeta = buildSessionMeta(body.sessionId, updated);
1473
- (0, durable_streams_js_1.appendDurableStreamEvent)(CALIBRATE_STREAM_ID, {
1718
+ (0, durable_streams_js_1.appendDurableStreamEvent)(GRADE_STREAM_ID, {
1474
1719
  type: "calibrateSession",
1475
1720
  sessionId: body.sessionId,
1476
1721
  session: sessionMeta,
@@ -1557,7 +1802,7 @@ function startWebSocketSimulator(opts) {
1557
1802
  },
1558
1803
  });
1559
1804
  const sessionMeta = buildSessionMeta(body.sessionId, nextState);
1560
- (0, durable_streams_js_1.appendDurableStreamEvent)(CALIBRATE_STREAM_ID, {
1805
+ (0, durable_streams_js_1.appendDurableStreamEvent)(GRADE_STREAM_ID, {
1561
1806
  type: "calibrateSession",
1562
1807
  sessionId: body.sessionId,
1563
1808
  run: nextRun,
@@ -1575,7 +1820,7 @@ function startWebSocketSimulator(opts) {
1575
1820
  }), { status: 400, headers: { "content-type": "application/json" } });
1576
1821
  }
1577
1822
  }
1578
- if (url.pathname === "/api/test-bot") {
1823
+ if (url.pathname === "/api/test") {
1579
1824
  if (req.method === "GET") {
1580
1825
  await deckLoadPromise.catch(() => null);
1581
1826
  const requestedDeck = url.searchParams.get("deckPath");
@@ -1616,7 +1861,7 @@ function startWebSocketSimulator(opts) {
1616
1861
  }
1617
1862
  return new Response("Method not allowed", { status: 405 });
1618
1863
  }
1619
- if (url.pathname === "/api/test-bot/run") {
1864
+ if (url.pathname === "/api/test/run") {
1620
1865
  if (req.method !== "POST") {
1621
1866
  return new Response("Method not allowed", { status: 405 });
1622
1867
  }
@@ -1625,17 +1870,28 @@ function startWebSocketSimulator(opts) {
1625
1870
  let botInput = undefined;
1626
1871
  let initialUserMessage = undefined;
1627
1872
  let botDeckSelection;
1873
+ let inheritBotInput = false;
1874
+ let userProvidedDeckInput = false;
1875
+ let initFillRequestMissing = undefined;
1628
1876
  try {
1629
1877
  const body = await req.json();
1630
1878
  if (typeof body.maxTurns === "number" && Number.isFinite(body.maxTurns)) {
1631
1879
  maxTurnsOverride = body.maxTurns;
1632
1880
  }
1633
1881
  deckInput = body.context ?? body.init;
1882
+ if (body.context !== undefined || body.init !== undefined) {
1883
+ userProvidedDeckInput = true;
1884
+ }
1634
1885
  if (body.init !== undefined && body.context === undefined) {
1635
- logger.warn('[gambit] Received deprecated "init" field in test-bot API; use "context" instead.');
1886
+ logger.warn('[gambit] Received deprecated "init" field in test API; use "context" instead.');
1636
1887
  }
1637
1888
  botInput = body.botInput;
1638
- await deckLoadPromise.catch(() => null);
1889
+ if (typeof body.inheritBotInput === "boolean") {
1890
+ inheritBotInput = body.inheritBotInput;
1891
+ }
1892
+ if (body.initFill && Array.isArray(body.initFill.missing)) {
1893
+ initFillRequestMissing = body.initFill.missing.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
1894
+ }
1639
1895
  if (typeof body.botDeckPath === "string") {
1640
1896
  const resolved = resolveTestDeck(body.botDeckPath);
1641
1897
  if (!resolved) {
@@ -1668,19 +1924,175 @@ function startWebSocketSimulator(opts) {
1668
1924
  // ignore; keep undefined
1669
1925
  }
1670
1926
  }
1927
+ if (!userProvidedDeckInput && inheritBotInput && botInput !== undefined) {
1928
+ deckInput = cloneValue(botInput);
1929
+ }
1671
1930
  if (!botDeckSelection) {
1672
1931
  return new Response(JSON.stringify({ error: "No test decks configured" }), { status: 400, headers: { "content-type": "application/json" } });
1673
1932
  }
1933
+ let initFillInfo;
1934
+ let initFillTrace;
1935
+ try {
1936
+ const rootDeck = await deckLoadPromise.catch(() => null);
1937
+ const rootSchema = rootDeck?.contextSchema ?? rootDeck?.inputSchema;
1938
+ const normalizedSchema = rootSchema
1939
+ ? normalizeSchema(rootSchema)
1940
+ : undefined;
1941
+ const missing = normalizedSchema
1942
+ ? findMissingRequiredFields(normalizedSchema, deckInput)
1943
+ : [];
1944
+ const requested = initFillRequestMissing?.length
1945
+ ? missing.filter((entry) => initFillRequestMissing?.includes(entry))
1946
+ : missing;
1947
+ if (requested.length > 0) {
1948
+ const fillPrompt = buildInitFillPrompt({
1949
+ missing: requested,
1950
+ current: deckInput,
1951
+ schema: normalizedSchema,
1952
+ });
1953
+ const fillOutput = await runDeckWithFallback({
1954
+ path: botDeckSelection.path,
1955
+ input: botInput,
1956
+ inputProvided: botInput !== undefined,
1957
+ modelProvider: opts.modelProvider,
1958
+ allowRootStringInput: true,
1959
+ initialUserMessage: fillPrompt,
1960
+ responsesMode: opts.responsesMode,
1961
+ });
1962
+ const parsed = parseInitFillOutput(fillOutput);
1963
+ if (parsed.error) {
1964
+ initFillInfo = {
1965
+ requested,
1966
+ provided: fillOutput,
1967
+ error: parsed.error,
1968
+ };
1969
+ const failure = persistFailedInitFill({
1970
+ error: parsed.error,
1971
+ initFill: initFillInfo,
1972
+ botDeckPath: botDeckSelection.path,
1973
+ });
1974
+ return new Response(JSON.stringify({
1975
+ error: parsed.error,
1976
+ initFill: initFillInfo,
1977
+ sessionId: failure.sessionId,
1978
+ sessionPath: failure.sessionPath,
1979
+ }), {
1980
+ status: 400,
1981
+ headers: { "content-type": "application/json" },
1982
+ });
1983
+ }
1984
+ let appliedObject = {};
1985
+ let appliedRoot = undefined;
1986
+ let nextInput = deckInput;
1987
+ for (const pathKey of requested) {
1988
+ const segments = pathKey === "(root)" ? [] : pathKey.split(".");
1989
+ const leafSchema = getSchemaAtPath(normalizedSchema, segments);
1990
+ const currentValue = getPathValue(nextInput, segments);
1991
+ if (currentValue !== undefined && currentValue !== null &&
1992
+ !(typeof currentValue === "string" &&
1993
+ (leafSchema?.kind === "string" ||
1994
+ leafSchema?.kind === "enum") &&
1995
+ currentValue.trim() === "") &&
1996
+ !(Array.isArray(currentValue) && leafSchema?.kind === "array" &&
1997
+ currentValue.length === 0)) {
1998
+ continue;
1999
+ }
2000
+ const fillValue = getPathValue(parsed.data, segments);
2001
+ if (fillValue === undefined)
2002
+ continue;
2003
+ if (segments.length === 0) {
2004
+ nextInput = fillValue;
2005
+ appliedRoot = fillValue;
2006
+ continue;
2007
+ }
2008
+ nextInput = setPathValue(nextInput, segments, fillValue);
2009
+ const appliedValue = setPathValue(appliedObject, segments, fillValue);
2010
+ if (appliedValue && typeof appliedValue === "object") {
2011
+ appliedObject = appliedValue;
2012
+ }
2013
+ }
2014
+ const validated = validateInitInput(rootSchema, nextInput);
2015
+ deckInput = validated;
2016
+ const remainingMissing = normalizedSchema
2017
+ ? findMissingRequiredFields(normalizedSchema, deckInput)
2018
+ : [];
2019
+ if (remainingMissing.length > 0) {
2020
+ const message = `Init fill incomplete: missing ${remainingMissing.join(", ")}`;
2021
+ initFillInfo = {
2022
+ requested,
2023
+ applied: appliedRoot !== undefined
2024
+ ? appliedRoot
2025
+ : Object.keys(appliedObject).length
2026
+ ? appliedObject
2027
+ : undefined,
2028
+ provided: parsed.data,
2029
+ error: message,
2030
+ };
2031
+ const failure = persistFailedInitFill({
2032
+ error: message,
2033
+ initFill: initFillInfo,
2034
+ botDeckPath: botDeckSelection.path,
2035
+ });
2036
+ return new Response(JSON.stringify({
2037
+ error: message,
2038
+ initFill: initFillInfo,
2039
+ sessionId: failure.sessionId,
2040
+ sessionPath: failure.sessionPath,
2041
+ }), {
2042
+ status: 400,
2043
+ headers: { "content-type": "application/json" },
2044
+ });
2045
+ }
2046
+ initFillInfo = {
2047
+ requested,
2048
+ applied: appliedRoot !== undefined
2049
+ ? appliedRoot
2050
+ : Object.keys(appliedObject).length
2051
+ ? appliedObject
2052
+ : undefined,
2053
+ provided: parsed.data,
2054
+ };
2055
+ initFillTrace = {
2056
+ args: {
2057
+ missing: requested,
2058
+ },
2059
+ result: {
2060
+ applied: initFillInfo.applied,
2061
+ provided: initFillInfo.provided,
2062
+ },
2063
+ };
2064
+ }
2065
+ }
2066
+ catch (err) {
2067
+ const message = err instanceof Error ? err.message : String(err);
2068
+ initFillInfo = initFillInfo ?? {
2069
+ requested: [],
2070
+ };
2071
+ initFillInfo.error = message;
2072
+ const failure = persistFailedInitFill({
2073
+ error: message,
2074
+ initFill: initFillInfo,
2075
+ botDeckPath: botDeckSelection.path,
2076
+ });
2077
+ return new Response(JSON.stringify({
2078
+ error: message,
2079
+ initFill: initFillInfo,
2080
+ sessionId: failure.sessionId,
2081
+ sessionPath: failure.sessionPath,
2082
+ }), { status: 400, headers: { "content-type": "application/json" } });
2083
+ }
1674
2084
  const run = startTestBotRun({
1675
2085
  maxTurnsOverride,
1676
2086
  deckInput,
1677
2087
  botInput,
1678
2088
  initialUserMessage,
1679
2089
  botDeckPath: botDeckSelection.path,
2090
+ initFill: initFillInfo,
2091
+ initFillTrace,
1680
2092
  });
1681
2093
  return new Response(JSON.stringify({ run }), { headers: { "content-type": "application/json" } });
1682
2094
  }
1683
- if (url.pathname === "/api/test-bot/status") {
2095
+ if (url.pathname === "/api/test/status") {
1684
2096
  const runId = url.searchParams.get("runId") ?? undefined;
1685
2097
  const sessionId = url.searchParams.get("sessionId") ?? undefined;
1686
2098
  let entry = runId ? testBotRuns.get(runId) : undefined;
@@ -1753,7 +2165,7 @@ function startWebSocketSimulator(opts) {
1753
2165
  testDecks: availableTestDecks,
1754
2166
  }), { headers: { "content-type": "application/json" } });
1755
2167
  }
1756
- if (url.pathname === "/api/test-bot/stop") {
2168
+ if (url.pathname === "/api/test/stop") {
1757
2169
  if (req.method !== "POST") {
1758
2170
  return new Response("Method not allowed", { status: 405 });
1759
2171
  }
@@ -1854,6 +2266,7 @@ function startWebSocketSimulator(opts) {
1854
2266
  trace: tracer,
1855
2267
  stream,
1856
2268
  state: simulatorSavedState,
2269
+ responsesMode: opts.responsesMode,
1857
2270
  onStateUpdate: (state) => {
1858
2271
  const nextMeta = {
1859
2272
  ...(simulatorSavedState?.meta ?? {}),
@@ -2241,13 +2654,7 @@ function startWebSocketSimulator(opts) {
2241
2654
  Array.isArray(state.messages)) {
2242
2655
  const idx = state.messageRefs.findIndex((ref) => ref?.id === messageRefId);
2243
2656
  if (idx >= 0) {
2244
- const item = state.messages[idx];
2245
- if (item?.type === "message") {
2246
- messageContent = stringifyContent(item.content);
2247
- }
2248
- else {
2249
- messageContent = undefined;
2250
- }
2657
+ messageContent = state.messages[idx]?.content;
2251
2658
  }
2252
2659
  }
2253
2660
  items.push({
@@ -2323,20 +2730,28 @@ function startWebSocketSimulator(opts) {
2323
2730
  url.pathname.startsWith("/debug") ||
2324
2731
  url.pathname.startsWith("/editor") ||
2325
2732
  url.pathname.startsWith("/docs") ||
2326
- url.pathname.startsWith("/test-bot") ||
2327
- url.pathname.startsWith("/calibrate")) {
2733
+ url.pathname.startsWith("/test") ||
2734
+ url.pathname.startsWith("/grade")) {
2328
2735
  const hasBundle = await canServeReactBundle();
2329
2736
  if (!hasBundle) {
2330
2737
  return new Response("Simulator UI bundle missing. Run `deno task bundle:sim` (or start with `--bundle`).", { status: 500 });
2331
2738
  }
2332
- return new Response(simulatorReactHtml(resolvedDeckPath), {
2739
+ await deckLoadPromise.catch(() => null);
2740
+ const resolvedLabel = deckLabel ?? toDeckLabel(resolvedDeckPath);
2741
+ return new Response(simulatorReactHtml(resolvedDeckPath, resolvedLabel), {
2333
2742
  headers: { "content-type": "text/html; charset=utf-8" },
2334
2743
  });
2335
2744
  }
2336
2745
  if (url.pathname === "/schema") {
2337
2746
  const desc = await schemaPromise;
2747
+ const deck = await deckLoadPromise.catch(() => null);
2748
+ const startMode = deck &&
2749
+ (deck.startMode === "assistant" || deck.startMode === "user")
2750
+ ? deck.startMode
2751
+ : undefined;
2338
2752
  return new Response(JSON.stringify({
2339
2753
  deck: resolvedDeckPath,
2754
+ startMode,
2340
2755
  ...desc,
2341
2756
  }), {
2342
2757
  headers: { "content-type": "application/json; charset=utf-8" },
@@ -2430,18 +2845,58 @@ function hasReactBundleSourceMap() {
2430
2845
  return false;
2431
2846
  }
2432
2847
  }
2848
+ function newestMtimeInDir(dirPath) {
2849
+ const stack = [dirPath];
2850
+ let newest = undefined;
2851
+ while (stack.length > 0) {
2852
+ const current = stack.pop();
2853
+ if (!current)
2854
+ continue;
2855
+ let entries;
2856
+ try {
2857
+ entries = Array.from(dntShim.Deno.readDirSync(current));
2858
+ }
2859
+ catch {
2860
+ continue;
2861
+ }
2862
+ for (const entry of entries) {
2863
+ const entryPath = path.join(current, entry.name);
2864
+ if (entry.isDirectory) {
2865
+ stack.push(entryPath);
2866
+ continue;
2867
+ }
2868
+ if (!entry.isFile)
2869
+ continue;
2870
+ try {
2871
+ const stat = dntShim.Deno.statSync(entryPath);
2872
+ if (!stat.isFile)
2873
+ continue;
2874
+ const mtime = stat.mtime?.getTime();
2875
+ if (typeof mtime !== "number")
2876
+ continue;
2877
+ newest = newest === undefined ? mtime : Math.max(newest, mtime);
2878
+ }
2879
+ catch {
2880
+ continue;
2881
+ }
2882
+ }
2883
+ }
2884
+ return newest;
2885
+ }
2433
2886
  function isReactBundleStale() {
2434
2887
  try {
2435
2888
  const bundleStat = dntShim.Deno.statSync(simulatorBundlePath);
2436
- const entryStat = dntShim.Deno.statSync(simulatorUiEntryPath);
2437
- if (!bundleStat.isFile || !entryStat.isFile)
2889
+ if (!bundleStat.isFile)
2438
2890
  return false;
2439
2891
  const bundleTime = bundleStat.mtime?.getTime();
2440
- const entryTime = entryStat.mtime?.getTime();
2441
- if (typeof bundleTime !== "number" || typeof entryTime !== "number") {
2892
+ if (typeof bundleTime !== "number") {
2442
2893
  return false;
2443
2894
  }
2444
- return entryTime > bundleTime;
2895
+ const srcRoot = path.resolve(moduleDir, "..", "simulator-ui", "src");
2896
+ const newestSource = newestMtimeInDir(srcRoot);
2897
+ if (typeof newestSource !== "number")
2898
+ return false;
2899
+ return newestSource > bundleTime;
2445
2900
  }
2446
2901
  catch {
2447
2902
  return false;
@@ -2500,8 +2955,9 @@ async function readRemoteBundle(url, kind) {
2500
2955
  return null;
2501
2956
  }
2502
2957
  }
2503
- function simulatorReactHtml(deckPath) {
2504
- const deckLabel = deckPath.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
2958
+ function simulatorReactHtml(deckPath, deckLabel) {
2959
+ const safeDeckPath = deckPath.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
2960
+ const safeDeckLabel = deckLabel?.replaceAll("<", "&lt;").replaceAll(">", "&gt;") ?? null;
2505
2961
  const bundleStamp = (() => {
2506
2962
  try {
2507
2963
  const stat = dntShim.Deno.statSync(simulatorBundlePath);
@@ -2529,7 +2985,8 @@ function simulatorReactHtml(deckPath) {
2529
2985
  <body>
2530
2986
  <div id="root"></div>
2531
2987
  <script>
2532
- window.__GAMBIT_DECK_PATH__ = ${JSON.stringify(deckLabel)};
2988
+ window.__GAMBIT_DECK_PATH__ = ${JSON.stringify(safeDeckPath)};
2989
+ window.__GAMBIT_DECK_LABEL__ = ${JSON.stringify(safeDeckLabel)};
2533
2990
  </script>
2534
2991
  <script type="module" src="${bundleUrl}"></script>
2535
2992
  </body>
@@ -2573,6 +3030,7 @@ async function runDeckWithFallback(args) {
2573
3030
  onStateUpdate: args.onStateUpdate,
2574
3031
  stream: args.stream,
2575
3032
  onStreamText: args.onStreamText,
3033
+ responsesMode: args.responsesMode,
2576
3034
  });
2577
3035
  }
2578
3036
  catch (error) {
@@ -2588,6 +3046,7 @@ async function runDeckWithFallback(args) {
2588
3046
  onStateUpdate: args.onStateUpdate,
2589
3047
  stream: args.stream,
2590
3048
  onStreamText: args.onStreamText,
3049
+ responsesMode: args.responsesMode,
2591
3050
  });
2592
3051
  }
2593
3052
  throw error;