@runtypelabs/persona 3.28.0 → 3.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
2
 
3
3
  import { AgentWidgetSession } from "./session";
4
4
  import type {
@@ -80,6 +80,10 @@ describe("AgentWidgetSession — WebMCP resolve", () => {
80
80
  vi.clearAllMocks();
81
81
  });
82
82
 
83
+ afterEach(() => {
84
+ vi.useRealTimers();
85
+ });
86
+
83
87
  it("posts result to /resume on the happy path", async () => {
84
88
  const { session, executeSpy, resumeSpy } = makeSession({
85
89
  executeReturn: {
@@ -97,6 +101,33 @@ describe("AgentWidgetSession — WebMCP resolve", () => {
97
101
  );
98
102
  });
99
103
 
104
+ it("records the elapsed time of the resolved browser WebMCP promise", async () => {
105
+ vi.useFakeTimers();
106
+ vi.setSystemTime(1_000);
107
+ const { session } = makeSession({
108
+ executeImpl: async () => {
109
+ vi.advanceTimersByTime(1_250);
110
+ return { content: [{ type: "text", text: "loaded jade" }] };
111
+ },
112
+ });
113
+
114
+ await session.resolveWebMcpToolCall(
115
+ awaitingMessage("tool-1", "webmcp:get_product_by_url"),
116
+ );
117
+
118
+ const stored = (
119
+ session as unknown as { messages: AgentWidgetMessage[] }
120
+ ).messages.find((m) => m.toolCall?.id === "tool-1");
121
+ expect(stored?.agentMetadata?.awaitingLocalTool).toBe(false);
122
+ expect(stored?.toolCall?.status).toBe("complete");
123
+ expect(stored?.toolCall?.startedAt).toBe(1_000);
124
+ expect(stored?.toolCall?.completedAt).toBe(2_250);
125
+ expect(stored?.toolCall?.durationMs).toBe(1_250);
126
+ expect(stored?.toolCall?.result).toEqual({
127
+ content: [{ type: "text", text: "loaded jade" }],
128
+ });
129
+ });
130
+
100
131
  it("still resumes (with isError) when the bridge is not operational", async () => {
101
132
  // BugBot finding #1: previously, handleEvent skipped resolveWebMcpToolCall
102
133
  // entirely when `isWebMcpOperational()` was false — leaving the dispatch
@@ -152,6 +183,13 @@ describe("AgentWidgetSession — WebMCP resolve", () => {
152
183
 
153
184
  const msg = awaitingMessage("tool-1", "webmcp:search");
154
185
  await session.resolveWebMcpToolCall(msg);
186
+ const afterFailedResume = (
187
+ session as unknown as { messages: AgentWidgetMessage[] }
188
+ ).messages.find((m) => m.toolCall?.id === "tool-1");
189
+ expect(afterFailedResume?.toolCall?.status).toBe("running");
190
+ expect(afterFailedResume?.toolCall?.result).toBeUndefined();
191
+ expect(afterFailedResume?.toolCall?.durationMs).toBeUndefined();
192
+
155
193
  await session.resolveWebMcpToolCall(msg); // retry — must be allowed
156
194
  await session.resolveWebMcpToolCall(msg); // post-success — must be blocked
157
195
 
@@ -159,6 +197,29 @@ describe("AgentWidgetSession — WebMCP resolve", () => {
159
197
  expect(resumeSpy).toHaveBeenCalledTimes(2);
160
198
  });
161
199
 
200
+ it("marks the bubble complete with an error when browser execution throws before /resume", async () => {
201
+ const { session, resumeSpy } = makeSession({
202
+ executeImpl: async () => {
203
+ throw new Error("page tool exploded");
204
+ },
205
+ });
206
+
207
+ await session.resolveWebMcpToolCall(
208
+ awaitingMessage("tool-1", "webmcp:search"),
209
+ );
210
+
211
+ const stored = (
212
+ session as unknown as { messages: AgentWidgetMessage[] }
213
+ ).messages.find((m) => m.toolCall?.id === "tool-1");
214
+ expect(resumeSpy).not.toHaveBeenCalled();
215
+ expect(stored?.toolCall?.status).toBe("complete");
216
+ expect(stored?.toolCall?.durationMs).toBeGreaterThanOrEqual(0);
217
+ expect(stored?.toolCall?.result).toMatchObject({
218
+ isError: true,
219
+ content: [{ type: "text", text: "page tool exploded" }],
220
+ });
221
+ });
222
+
162
223
  it("threads an AbortSignal into resumeFlow", async () => {
163
224
  // BugBot finding #6: cancel() needs to propagate into /resume.
164
225
  const { session, resumeSpy } = makeSession();
@@ -189,6 +250,14 @@ describe("AgentWidgetSession — WebMCP resolve", () => {
189
250
  release();
190
251
  await inflight;
191
252
  expect(resumeSpy).not.toHaveBeenCalled();
253
+ const stored = (
254
+ session as unknown as { messages: AgentWidgetMessage[] }
255
+ ).messages.find((m) => m.toolCall?.id === "tool-1");
256
+ expect(stored?.toolCall?.status).toBe("complete");
257
+ expect(stored?.toolCall?.result).toMatchObject({
258
+ isError: true,
259
+ content: [{ type: "text", text: "Aborted by cancel()" }],
260
+ });
192
261
  });
193
262
 
194
263
  it("does NOT abort the shared session abortController", async () => {
@@ -282,11 +351,12 @@ describe("AgentWidgetSession — WebMCP resolve", () => {
282
351
  expect(executeSpy).not.toHaveBeenCalled();
283
352
  });
284
353
 
285
- it("a stale step_await re-emit does not resurrect awaitingLocalTool once resolved", () => {
354
+ it("a stale step_await re-emit does not resurrect awaitingLocalTool or reset completed tool state once resolved", () => {
286
355
  // BugBot: a duplicate step_await (awaitingLocalTool:true) for an
287
356
  // already-resolved webmcp tool must not flip the message back to awaiting
288
- // and show a stuck local-tool wait. upsertMessage clears it when the
289
- // tool's `${executionId}:${toolCallId}` key is inflight/resolved.
357
+ // or running and show a stuck local-tool wait / lose the measured duration.
358
+ // upsertMessage clears it when the tool's `${executionId}:${toolCallId}`
359
+ // key is inflight/resolved.
290
360
  const session = makeSession().session;
291
361
  const s = session as unknown as {
292
362
  webMcpResolvedKeys: Set<string>;
@@ -298,11 +368,182 @@ describe("AgentWidgetSession — WebMCP resolve", () => {
298
368
  s.upsertMessage({
299
369
  ...awaitingMessage("tool-1", "webmcp:search"),
300
370
  agentMetadata: { executionId: "exec-1", awaitingLocalTool: false },
371
+ streaming: false,
372
+ toolCall: {
373
+ id: "tool-1",
374
+ name: "webmcp:search",
375
+ status: "complete",
376
+ args: { q: "shoes" },
377
+ result: { content: [{ type: "text", text: "ok" }] },
378
+ startedAt: 1_000,
379
+ completedAt: 2_200,
380
+ durationMs: 1_200,
381
+ },
382
+ });
383
+ // Stale re-emit flips awaiting back to true and carries the running state
384
+ // emitted by client.ts for WebMCP step_await events.
385
+ s.upsertMessage({
386
+ ...awaitingMessage("tool-1", "webmcp:search"),
387
+ streaming: false,
388
+ toolCall: {
389
+ id: "tool-1",
390
+ name: "webmcp:search",
391
+ status: "running",
392
+ args: { q: "shoes" },
393
+ startedAt: 3_000,
394
+ },
395
+ });
396
+ const stored = s.messages.find((m) => m.toolCall?.id === "tool-1");
397
+ expect(stored?.agentMetadata?.awaitingLocalTool).toBe(false);
398
+ expect(stored?.streaming).toBe(false);
399
+ expect(stored?.toolCall?.status).toBe("complete");
400
+ expect(stored?.toolCall?.result).toEqual({
401
+ content: [{ type: "text", text: "ok" }],
402
+ });
403
+ expect(stored?.toolCall?.startedAt).toBe(1_000);
404
+ expect(stored?.toolCall?.completedAt).toBe(2_200);
405
+ expect(stored?.toolCall?.durationMs).toBe(1_200);
406
+ });
407
+
408
+ it("a stale step_await re-emit does not reset in-flight WebMCP tool timing", () => {
409
+ const session = makeSession().session;
410
+ const s = session as unknown as {
411
+ webMcpInflightKeys: Set<string>;
412
+ upsertMessage: (m: AgentWidgetMessage) => void;
413
+ messages: AgentWidgetMessage[];
414
+ };
415
+ s.webMcpInflightKeys.add("exec-1:tool-1");
416
+ s.upsertMessage({
417
+ ...awaitingMessage("tool-1", "webmcp:search"),
418
+ agentMetadata: { executionId: "exec-1", awaitingLocalTool: false },
419
+ streaming: true,
420
+ toolCall: {
421
+ id: "tool-1",
422
+ name: "webmcp:search",
423
+ status: "running",
424
+ args: { q: "shoes" },
425
+ startedAt: 1_000,
426
+ },
427
+ });
428
+
429
+ s.upsertMessage({
430
+ ...awaitingMessage("tool-1", "webmcp:search"),
431
+ streaming: false,
432
+ toolCall: {
433
+ id: "tool-1",
434
+ name: "webmcp:search",
435
+ status: "running",
436
+ args: { q: "shoes" },
437
+ startedAt: 3_000,
438
+ },
301
439
  });
302
- // Stale re-emit flips awaiting back to true on the wire.
303
- s.upsertMessage(awaitingMessage("tool-1", "webmcp:search"));
440
+
304
441
  const stored = s.messages.find((m) => m.toolCall?.id === "tool-1");
305
442
  expect(stored?.agentMetadata?.awaitingLocalTool).toBe(false);
443
+ expect(stored?.streaming).toBe(true);
444
+ expect(stored?.toolCall?.status).toBe("running");
445
+ expect(stored?.toolCall?.startedAt).toBe(1_000);
446
+ expect(stored?.toolCall?.durationMs).toBeUndefined();
447
+ });
448
+
449
+ it("a stale step_await re-emit does not reset completed WebMCP local-error state", () => {
450
+ const session = makeSession().session;
451
+ const s = session as unknown as {
452
+ upsertMessage: (m: AgentWidgetMessage) => void;
453
+ messages: AgentWidgetMessage[];
454
+ };
455
+ s.upsertMessage({
456
+ ...awaitingMessage("tool-1", "webmcp:search"),
457
+ agentMetadata: { executionId: "exec-1", awaitingLocalTool: false },
458
+ streaming: false,
459
+ toolCall: {
460
+ id: "tool-1",
461
+ name: "webmcp:search",
462
+ status: "complete",
463
+ args: { q: "shoes" },
464
+ result: {
465
+ isError: true,
466
+ content: [{ type: "text", text: "page tool exploded" }],
467
+ },
468
+ startedAt: 1_000,
469
+ completedAt: 1_500,
470
+ durationMs: 500,
471
+ },
472
+ });
473
+
474
+ s.upsertMessage({
475
+ ...awaitingMessage("tool-1", "webmcp:search"),
476
+ streaming: false,
477
+ toolCall: {
478
+ id: "tool-1",
479
+ name: "webmcp:search",
480
+ status: "running",
481
+ args: { q: "shoes" },
482
+ startedAt: 3_000,
483
+ },
484
+ });
485
+
486
+ const stored = s.messages.find((m) => m.toolCall?.id === "tool-1");
487
+ expect(stored?.agentMetadata?.awaitingLocalTool).toBe(false);
488
+ expect(stored?.streaming).toBe(false);
489
+ expect(stored?.toolCall?.status).toBe("complete");
490
+ expect(stored?.toolCall?.result).toEqual({
491
+ isError: true,
492
+ content: [{ type: "text", text: "page tool exploded" }],
493
+ });
494
+ expect(stored?.toolCall?.startedAt).toBe(1_000);
495
+ expect(stored?.toolCall?.completedAt).toBe(1_500);
496
+ expect(stored?.toolCall?.durationMs).toBe(500);
497
+ });
498
+
499
+ it("a new execution reusing a completed tool id is not treated as stale", () => {
500
+ const session = makeSession().session;
501
+ const s = session as unknown as {
502
+ upsertMessage: (m: AgentWidgetMessage) => void;
503
+ messages: AgentWidgetMessage[];
504
+ };
505
+ s.upsertMessage({
506
+ ...awaitingMessage("tool-1", "webmcp:search"),
507
+ id: "tool-tool-1",
508
+ agentMetadata: { executionId: "exec-1", awaitingLocalTool: false },
509
+ streaming: false,
510
+ toolCall: {
511
+ id: "tool-1",
512
+ name: "webmcp:search",
513
+ status: "complete",
514
+ args: { q: "shoes" },
515
+ result: {
516
+ isError: true,
517
+ content: [{ type: "text", text: "page tool exploded" }],
518
+ },
519
+ startedAt: 1_000,
520
+ completedAt: 1_500,
521
+ durationMs: 500,
522
+ },
523
+ });
524
+
525
+ s.upsertMessage({
526
+ ...awaitingMessage("tool-1", "webmcp:search", "exec-2"),
527
+ id: "tool-tool-1",
528
+ streaming: false,
529
+ toolCall: {
530
+ id: "tool-1",
531
+ name: "webmcp:search",
532
+ status: "running",
533
+ args: { q: "boots" },
534
+ startedAt: 3_000,
535
+ },
536
+ });
537
+
538
+ const stored = s.messages.find((m) => m.id === "tool-tool-1");
539
+ expect(stored?.agentMetadata?.executionId).toBe("exec-2");
540
+ expect(stored?.agentMetadata?.awaitingLocalTool).toBe(true);
541
+ expect(stored?.toolCall?.status).toBe("running");
542
+ expect(stored?.toolCall?.args).toEqual({ q: "boots" });
543
+ expect(stored?.toolCall?.startedAt).toBe(3_000);
544
+ expect(stored?.toolCall?.result).toBeUndefined();
545
+ expect(stored?.toolCall?.completedAt).toBeUndefined();
546
+ expect(stored?.toolCall?.durationMs).toBeUndefined();
306
547
  });
307
548
 
308
549
  it("an error event does not clear streaming while a webmcp resolve is in flight", () => {
@@ -576,6 +817,10 @@ describe("AgentWidgetSession — WebMCP parallel batched resume (core#3878)", ()
576
817
  vi.clearAllMocks();
577
818
  });
578
819
 
820
+ afterEach(() => {
821
+ vi.useRealTimers();
822
+ });
823
+
579
824
  // A `step_await(local_tool_required)` message as client.ts emits it for a
580
825
  // PARALLEL local-tool call: the per-call `toolCallId` is both the toolCall.id
581
826
  // AND `agentMetadata.webMcpToolCallId`. Two of these for one executionId share
@@ -705,6 +950,81 @@ describe("AgentWidgetSession — WebMCP parallel batched resume (core#3878)", ()
705
950
  expect(Object.keys(toolOutputs).sort()).toEqual(["toolu_A", "toolu_B"]);
706
951
  });
707
952
 
953
+ it("records elapsed time for each resolved tool in a batched WebMCP resume", async () => {
954
+ vi.useFakeTimers();
955
+ vi.setSystemTime(1_000);
956
+
957
+ let releaseA: (r: WebMcpToolResult) => void = () => undefined;
958
+ let releaseB: (r: WebMcpToolResult) => void = () => undefined;
959
+ const pA = new Promise<WebMcpToolResult>((r) => (releaseA = r));
960
+ const pB = new Promise<WebMcpToolResult>((r) => (releaseB = r));
961
+ const { session, resumeSpy } = makeSession();
962
+ const client = (session as unknown as { client: Record<string, unknown> })
963
+ .client;
964
+ (client.executeWebMcpToolCall as ReturnType<typeof vi.fn>).mockImplementation(
965
+ (_name: string, args: { sku: string }) =>
966
+ args.sku === "SHOE-001" ? pA : pB,
967
+ );
968
+
969
+ feed(session, parallelAwait("toolu_A", "SHOE-001"));
970
+ feed(session, parallelAwait("toolu_B", "SHOE-007"));
971
+ endStream(session);
972
+ await flushMicrotasks();
973
+
974
+ vi.setSystemTime(1_800);
975
+ releaseA({ content: [{ type: "text", text: "a" }] });
976
+ await flushMicrotasks();
977
+
978
+ let messages = (session as unknown as { messages: AgentWidgetMessage[] })
979
+ .messages;
980
+ const toolA = messages.find((m) => m.toolCall?.id === "toolu_A");
981
+ const toolBWhileRunning = messages.find((m) => m.toolCall?.id === "toolu_B");
982
+ expect(toolA?.toolCall?.status).toBe("running");
983
+ expect(toolA?.toolCall?.durationMs).toBeUndefined();
984
+ expect(toolBWhileRunning?.toolCall?.status).toBe("running");
985
+ expect(resumeSpy).not.toHaveBeenCalled();
986
+
987
+ vi.setSystemTime(2_600);
988
+ releaseB({ content: [{ type: "text", text: "b" }] });
989
+ await flushMicrotasks();
990
+
991
+ messages = (session as unknown as { messages: AgentWidgetMessage[] })
992
+ .messages;
993
+ const completedToolA = messages.find((m) => m.toolCall?.id === "toolu_A");
994
+ const toolB = messages.find((m) => m.toolCall?.id === "toolu_B");
995
+ expect(completedToolA?.toolCall?.status).toBe("complete");
996
+ expect(completedToolA?.toolCall?.durationMs).toBe(800);
997
+ expect(toolB?.toolCall?.status).toBe("complete");
998
+ expect(toolB?.toolCall?.durationMs).toBe(1_600);
999
+ expect(resumeSpy).toHaveBeenCalledTimes(1);
1000
+ });
1001
+
1002
+ it("does not mark batched WebMCP bubbles complete when the shared /resume fails", async () => {
1003
+ const { session, client, resumeSpy } = makeSession();
1004
+ (client.resumeFlow as ReturnType<typeof vi.fn>).mockImplementationOnce(
1005
+ async () => {
1006
+ throw new Error("resume failed");
1007
+ },
1008
+ );
1009
+
1010
+ feed(session, parallelAwait("toolu_A", "SHOE-001"));
1011
+ feed(session, parallelAwait("toolu_B", "SHOE-007"));
1012
+ endStream(session);
1013
+ await flushMicrotasks();
1014
+
1015
+ const messages = (session as unknown as { messages: AgentWidgetMessage[] })
1016
+ .messages;
1017
+ const toolA = messages.find((m) => m.toolCall?.id === "toolu_A");
1018
+ const toolB = messages.find((m) => m.toolCall?.id === "toolu_B");
1019
+ expect(resumeSpy).toHaveBeenCalledTimes(1);
1020
+ expect(toolA?.toolCall?.status).toBe("running");
1021
+ expect(toolA?.toolCall?.result).toBeUndefined();
1022
+ expect(toolA?.toolCall?.durationMs).toBeUndefined();
1023
+ expect(toolB?.toolCall?.status).toBe("running");
1024
+ expect(toolB?.toolCall?.result).toBeUndefined();
1025
+ expect(toolB?.toolCall?.durationMs).toBeUndefined();
1026
+ });
1027
+
708
1028
  it("dedupes a duplicate parallel await within the same batch", async () => {
709
1029
  const { session, executeSpy, resumeSpy } = makeSession();
710
1030
  feed(session, parallelAwait("toolu_A", "SHOE-001"));
@@ -141,6 +141,16 @@ export type CodeGeneratorOptions = {
141
141
  * Only affects script formats (script-installer, script-manual, script-advanced).
142
142
  */
143
143
  windowKey?: string;
144
+
145
+ /**
146
+ * CSS selector for the mount target. When omitted, the widget mounts on
147
+ * `body` (the existing default). When provided, the generated snippet mounts
148
+ * into that element: ESM / React / manual / advanced formats emit it as the
149
+ * `target` argument to `initAgentWidget()`, and `script-installer` serializes
150
+ * it into `data-config` (the installer reads `config.target`).
151
+ * @default "body"
152
+ */
153
+ target?: string;
144
154
  };
145
155
 
146
156
  // Internal type for normalized hooks (always strings)
@@ -540,6 +550,15 @@ function appendSerializableObjectBlock(
540
550
  lines.push(`${indent}},`);
541
551
  }
542
552
 
553
+ /**
554
+ * Resolve the mount-target selector for a single-quoted JS string context.
555
+ * Defaults to `body` and escapes backslashes / single quotes so an arbitrary
556
+ * selector can't break out of the emitted `target: '...'` literal.
557
+ */
558
+ function emitTargetSelector(options?: CodeGeneratorOptions): string {
559
+ return (options?.target ?? "body").replace(/\\/g, "\\\\").replace(/'/g, "\\'");
560
+ }
561
+
543
562
  export function generateCodeSnippet(
544
563
  config: any,
545
564
  format: CodeFormat = "esm",
@@ -580,7 +599,7 @@ function generateESMCode(config: any, options?: CodeGeneratorOptions): string {
580
599
  "import { initAgentWidget, markdownPostprocessor } from '@runtypelabs/persona';",
581
600
  "",
582
601
  "initAgentWidget({",
583
- " target: 'body',",
602
+ ` target: '${emitTargetSelector(options)}',`,
584
603
  " config: {"
585
604
  ];
586
605
 
@@ -726,7 +745,7 @@ function generateReactComponentCode(config: any, options?: CodeGeneratorOptions)
726
745
  " let handle: AgentWidgetInitHandle | null = null;",
727
746
  "",
728
747
  " handle = initAgentWidget({",
729
- " target: 'body',",
748
+ ` target: '${emitTargetSelector(options)}',`,
730
749
  " config: {"
731
750
  ];
732
751
 
@@ -990,7 +1009,7 @@ function generateReactAdvancedCode(config: any, options?: CodeGeneratorOptions):
990
1009
  " };",
991
1010
  "",
992
1011
  " handle = initAgentWidget({",
993
- " target: 'body',",
1012
+ ` target: '${emitTargetSelector(options)}',`,
994
1013
  " config: {"
995
1014
  ];
996
1015
 
@@ -1356,10 +1375,20 @@ function buildSerializableConfig(config: any): Record<string, any> {
1356
1375
  function generateScriptInstallerCode(config: any, options?: CodeGeneratorOptions): string {
1357
1376
  const serializableConfig = buildSerializableConfig(config);
1358
1377
 
1359
- // When windowKey is provided, nest the widget config under `config` so the
1360
- // install script's parsedConfig.config detection picks it up alongside windowKey.
1361
- const payload = options?.windowKey
1362
- ? { config: serializableConfig, windowKey: options.windowKey }
1378
+ // `windowKey` and `target` are INSTALLER options, not widget-config fields.
1379
+ // install.ts reads them from the top level of data-config: the nested-`config`
1380
+ // branch hoists every sibling key (Object.assign(scriptConfig, parsed)), while
1381
+ // a flat payload becomes the widget config wholesale and its siblings are
1382
+ // ignored. So whenever either is set we emit the nested `{ config, ...}` form
1383
+ // with the option as a SIBLING of `config` — nesting `target` inside the
1384
+ // widget config would leave the widget mounted on `body`.
1385
+ const needsWrapper = Boolean(options?.windowKey || options?.target);
1386
+ const payload = needsWrapper
1387
+ ? {
1388
+ config: serializableConfig,
1389
+ ...(options?.windowKey ? { windowKey: options.windowKey } : {}),
1390
+ ...(options?.target ? { target: options.target } : {}),
1391
+ }
1363
1392
  : serializableConfig;
1364
1393
 
1365
1394
  // Escape single quotes in JSON for HTML attribute
@@ -1383,7 +1412,7 @@ function generateScriptManualCode(config: any, options?: CodeGeneratorOptions):
1383
1412
  "<!-- Initialize widget -->",
1384
1413
  "<script>",
1385
1414
  " var handle = window.AgentWidget.initAgentWidget({",
1386
- " target: 'body',",
1415
+ ` target: '${emitTargetSelector(options)}',`,
1387
1416
  ...(options?.windowKey ? [` windowKey: '${options.windowKey}',`] : []),
1388
1417
  " config: {"
1389
1418
  ];
@@ -1735,7 +1764,7 @@ function generateScriptAdvancedCode(config: any, options?: CodeGeneratorOptions)
1735
1764
  "",
1736
1765
  " // Initialize widget",
1737
1766
  " var handle = agentWidget.initAgentWidget({",
1738
- " target: 'body',",
1767
+ ` target: '${emitTargetSelector(options)}',`,
1739
1768
  " useShadowDom: false,",
1740
1769
  ...(options?.windowKey ? [` windowKey: '${options.windowKey}',`] : []),
1741
1770
  " config: widgetConfig",