@runtypelabs/persona 4.21.0 → 4.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/{approval-ui-RNNBR5R3.js → approval-ui-K7R5Q5OC.js} +1 -1
  2. package/dist/approval-ui.cjs +2 -2
  3. package/dist/approval-ui.d.cts +73 -2
  4. package/dist/approval-ui.d.ts +73 -2
  5. package/dist/approval-ui.js +2 -2
  6. package/dist/artifacts-ui-F4N6VMKF.js +7 -0
  7. package/dist/artifacts-ui.cjs +1 -1
  8. package/dist/artifacts-ui.d.cts +73 -2
  9. package/dist/artifacts-ui.d.ts +73 -2
  10. package/dist/artifacts-ui.js +1 -1
  11. package/dist/chunk-NQLDCJAP.js +4 -0
  12. package/dist/codegen.cjs +1 -1
  13. package/dist/codegen.js +1 -1
  14. package/dist/context-mentions.d.cts +73 -2
  15. package/dist/context-mentions.d.ts +73 -2
  16. package/dist/event-stream-view-FKWDY3UB.js +1 -0
  17. package/dist/event-stream-view.cjs +1 -1
  18. package/dist/event-stream-view.d.cts +73 -2
  19. package/dist/event-stream-view.d.ts +73 -2
  20. package/dist/event-stream-view.js +1 -1
  21. package/dist/forms-ui.d.cts +73 -2
  22. package/dist/forms-ui.d.ts +73 -2
  23. package/dist/index.cjs +66 -66
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +136 -5
  26. package/dist/index.d.ts +136 -5
  27. package/dist/index.global.js +50 -50
  28. package/dist/index.global.js.map +1 -1
  29. package/dist/index.js +67 -67
  30. package/dist/index.js.map +1 -1
  31. package/dist/launcher.global.js +2 -2
  32. package/dist/launcher.global.js.map +1 -1
  33. package/dist/session-reconnect.d.cts +73 -2
  34. package/dist/session-reconnect.d.ts +73 -2
  35. package/dist/smart-dom-reader.d.cts +73 -2
  36. package/dist/smart-dom-reader.d.ts +73 -2
  37. package/dist/theme-editor-preview.cjs +65 -65
  38. package/dist/theme-editor-preview.d.cts +73 -2
  39. package/dist/theme-editor-preview.d.ts +73 -2
  40. package/dist/theme-editor-preview.js +49 -49
  41. package/dist/theme-editor.cjs +4 -4
  42. package/dist/theme-editor.d.cts +73 -2
  43. package/dist/theme-editor.d.ts +73 -2
  44. package/dist/theme-editor.js +2 -2
  45. package/dist/theme-reference.cjs +1 -1
  46. package/dist/theme-reference.d.cts +65 -1
  47. package/dist/theme-reference.d.ts +65 -1
  48. package/dist/theme-reference.js +1 -1
  49. package/dist/webmcp-polyfill.js +1 -4
  50. package/dist/webmcp-runtime-JI7T4QYF.js +6 -0
  51. package/dist/webmcp-runtime.cjs +2 -2
  52. package/dist/webmcp-runtime.d.cts +34 -2
  53. package/dist/webmcp-runtime.d.ts +34 -2
  54. package/dist/webmcp-runtime.js +2 -2
  55. package/package.json +2 -2
  56. package/src/client-visitor-history.test.ts +131 -0
  57. package/src/components/artifact-preview.ts +5 -0
  58. package/src/components/event-stream-badge-theming.test.ts +159 -0
  59. package/src/components/event-stream-view.ts +50 -20
  60. package/src/generated/runtype-openapi-contract.ts +8 -0
  61. package/src/index-core.ts +2 -0
  62. package/src/runtime/init.registry.test.ts +56 -0
  63. package/src/runtime/init.ts +17 -0
  64. package/src/types/theme.ts +41 -1
  65. package/src/types.ts +38 -1
  66. package/src/ui.reasoning-display.test.ts +136 -2
  67. package/src/ui.ts +69 -21
  68. package/src/utils/__snapshots__/tokens-parity.test.ts.snap +1740 -0
  69. package/src/utils/formatting.ts +22 -0
  70. package/src/utils/theme.ts +16 -0
  71. package/src/utils/tokens.ts +59 -0
  72. package/src/webmcp-bridge.test.ts +132 -23
  73. package/src/webmcp-bridge.ts +26 -2
  74. package/src/webmcp-runtime-entry.ts +71 -11
  75. package/dist/artifacts-ui-J6AKUGTS.js +0 -7
  76. package/dist/chunk-HBE7LFNT.js +0 -4
  77. package/dist/event-stream-view-73NWKWVQ.js +0 -1
  78. package/dist/webmcp-runtime-P3CHZRHF.js +0 -6
@@ -25,7 +25,8 @@ const injectReasoning = (
25
25
  controller: ReturnType<typeof createAgentExperience>,
26
26
  id: string,
27
27
  status: "streaming" | "complete",
28
- chunks: string[] = ["Weighing the options"]
28
+ chunks: string[] = ["Weighing the options"],
29
+ durationMs?: number
29
30
  ) => {
30
31
  controller.injectTestMessage({
31
32
  type: "message",
@@ -36,7 +37,26 @@ const injectReasoning = (
36
37
  createdAt: new Date().toISOString(),
37
38
  streaming: status !== "complete",
38
39
  variant: "reasoning",
39
- reasoning: { id, status, chunks },
40
+ reasoning: { id, status, chunks, durationMs },
41
+ },
42
+ });
43
+ };
44
+
45
+ const injectToolCall = (
46
+ controller: ReturnType<typeof createAgentExperience>,
47
+ id: string,
48
+ status: "pending" | "running" | "complete"
49
+ ) => {
50
+ controller.injectTestMessage({
51
+ type: "message",
52
+ message: {
53
+ id,
54
+ role: "assistant",
55
+ content: "",
56
+ createdAt: new Date().toISOString(),
57
+ streaming: status !== "complete",
58
+ variant: "tool",
59
+ toolCall: { id, name: "search", status, chunks: [] },
40
60
  },
41
61
  });
42
62
  };
@@ -161,4 +181,118 @@ describe("features.reasoningDisplay.completedVisibility", () => {
161
181
  ?.chunks
162
182
  ).toEqual(["Weighing the options"]);
163
183
  });
184
+
185
+ it('"removed-when-short" drops a completed trace with a short reported duration', () => {
186
+ const { mount, controller } = makeController({
187
+ features: { reasoningDisplay: { completedVisibility: "removed-when-short" } },
188
+ });
189
+ injectReasoning(controller, "r1", "complete", ["Weighing the options"], 5_000);
190
+ expect(rowOf(mount, "r1")).toBeNull();
191
+ });
192
+
193
+ it('"removed-when-short" keeps a completed trace with a long reported duration', () => {
194
+ const { mount, controller } = makeController({
195
+ features: { reasoningDisplay: { completedVisibility: "removed-when-short" } },
196
+ });
197
+ injectReasoning(controller, "r1", "complete", ["Weighing the options"], 20_000);
198
+ expect(rowOf(mount, "r1")).not.toBeNull();
199
+ });
200
+
201
+ it('"removed-when-short" drops a completed trace with no duration and few chars', () => {
202
+ const { mount, controller } = makeController({
203
+ features: { reasoningDisplay: { completedVisibility: "removed-when-short" } },
204
+ });
205
+ injectReasoning(controller, "r1", "complete", ["Short thought"]);
206
+ expect(rowOf(mount, "r1")).toBeNull();
207
+ });
208
+
209
+ it('"removed-when-short" keeps a completed trace with no duration and many chars', () => {
210
+ const { mount, controller } = makeController({
211
+ features: { reasoningDisplay: { completedVisibility: "removed-when-short" } },
212
+ });
213
+ injectReasoning(controller, "r1", "complete", ["x".repeat(1_500)]);
214
+ expect(rowOf(mount, "r1")).not.toBeNull();
215
+ });
216
+
217
+ it('"removed-when-short" honors custom duration and char thresholds', () => {
218
+ const { mount, controller } = makeController({
219
+ features: {
220
+ reasoningDisplay: {
221
+ completedVisibility: "removed-when-short",
222
+ shortThinkThreshold: { durationMs: 1_000, chars: 50 },
223
+ },
224
+ },
225
+ });
226
+ // 5s exceeds the custom 1s threshold, so this long-by-default trace is kept.
227
+ injectReasoning(controller, "r1", "complete", ["Weighing the options"], 5_000);
228
+ expect(rowOf(mount, "r1")).not.toBeNull();
229
+
230
+ // 60 chars exceeds the custom 50-char threshold, so this trace is kept too.
231
+ injectReasoning(controller, "r2", "complete", ["y".repeat(60)]);
232
+ expect(rowOf(mount, "r2")).not.toBeNull();
233
+
234
+ // Under both custom thresholds, so this trace is dropped.
235
+ injectReasoning(controller, "r3", "complete", ["z".repeat(10)], 500);
236
+ expect(rowOf(mount, "r3")).toBeNull();
237
+ });
238
+ });
239
+
240
+ describe("features.toolCallDisplay.completedVisibility", () => {
241
+ beforeEach(() => {
242
+ window.localStorage.clear();
243
+ vi.stubGlobal("requestAnimationFrame", (cb: (time: number) => void) => {
244
+ cb(0);
245
+ return 1;
246
+ });
247
+ vi.stubGlobal("cancelAnimationFrame", () => {});
248
+ window.scrollTo = vi.fn();
249
+ });
250
+
251
+ afterEach(() => {
252
+ controllers.splice(0).forEach((controller) => controller.destroy());
253
+ mounts.splice(0).forEach((mount) => mount.remove());
254
+ document.body.innerHTML = "";
255
+ vi.restoreAllMocks();
256
+ });
257
+
258
+ it("keeps a finished tool call row by default", () => {
259
+ const { mount, controller } = makeController();
260
+ injectToolCall(controller, "t1", "complete");
261
+ expect(rowOf(mount, "t1")).not.toBeNull();
262
+ });
263
+
264
+ it('removes the whole row under "removed", leaving no transcript gap', () => {
265
+ const { mount, controller } = makeController({
266
+ features: { toolCallDisplay: { completedVisibility: "removed" } },
267
+ });
268
+ injectToolCall(controller, "t1", "complete");
269
+ injectText(controller, "a1", "Here is the answer");
270
+
271
+ expect(rowOf(mount, "t1")).toBeNull();
272
+ const rows = mount.querySelectorAll("[data-wrapper-id]");
273
+ expect(Array.from(rows).map((row) => row.getAttribute("data-wrapper-id")))
274
+ .toEqual(["a1"]);
275
+ });
276
+
277
+ it('shows a running tool call under "removed" and drops it once it completes', () => {
278
+ const { mount, controller } = makeController({
279
+ features: { toolCallDisplay: { completedVisibility: "removed" } },
280
+ });
281
+ injectToolCall(controller, "t1", "running");
282
+ expect(rowOf(mount, "t1")).not.toBeNull();
283
+
284
+ injectToolCall(controller, "t1", "complete");
285
+ expect(rowOf(mount, "t1")).toBeNull();
286
+ });
287
+
288
+ it('keeps the tool call message in the session under "removed"', () => {
289
+ const { controller } = makeController({
290
+ features: { toolCallDisplay: { completedVisibility: "removed" } },
291
+ });
292
+ injectToolCall(controller, "t1", "complete");
293
+ expect(
294
+ controller.getMessages().find((message) => message.id === "t1")?.toolCall
295
+ ?.status
296
+ ).toEqual("complete");
297
+ });
164
298
  });
package/src/ui.ts CHANGED
@@ -61,7 +61,8 @@ import {
61
61
  ComposerQuote,
62
62
  ComposerState,
63
63
  ComposerStreamingSubmitBehavior,
64
- ComposerSubmissionSnapshot
64
+ ComposerSubmissionSnapshot,
65
+ AgentWidgetReasoning
65
66
  } from "./types";
66
67
  import { AttachmentManager, type PendingAttachment } from "./utils/attachment-manager";
67
68
  import { createComposerStore } from "./utils/composer-store";
@@ -282,7 +283,7 @@ import {
282
283
  latestAgentSuggestions,
283
284
  resolveFollowUpsFeature,
284
285
  } from "./suggest-replies-tool";
285
- import { formatElapsedMs } from "./utils/formatting";
286
+ import { formatElapsedMs, isShortReasoning } from "./utils/formatting";
286
287
  import { loadApprovalUi, getApprovalUiSync, type ApprovalUiModule } from "./approval-ui-loader";
287
288
  import { getWebMcpToolDisplayTitle } from "./webmcp-bridge";
288
289
  import type { AgentWidgetPlugin } from "./plugins/types";
@@ -1054,6 +1055,8 @@ export const createAgentExperience = (
1054
1055
  messagePersistenceDisabled
1055
1056
  ? null
1056
1057
  : (config.storageAdapter ?? createLocalStorageAdapter());
1058
+ const personaOwnsClientTokenRecovery =
1059
+ Boolean(config.clientToken) && typeof config.reconnectStream !== "function";
1057
1060
  let persistentMetadata: Record<string, unknown> = {};
1058
1061
  let pendingStoredState: Promise<AgentWidgetStoredState | null> | null = null;
1059
1062
  // Resolves once stored state has been applied (or its load failed). Every
@@ -5313,6 +5316,17 @@ export const createAgentExperience = (
5313
5316
  ? getMessagesForPersistence()
5314
5317
  : [];
5315
5318
 
5319
+ const durableHandle =
5320
+ personaOwnsClientTokenRecovery && session?.getResumableHandle();
5321
+ if (durableHandle) {
5322
+ persistentMetadata = {
5323
+ ...persistentMetadata,
5324
+ durableResume: {
5325
+ executionId: durableHandle.executionId,
5326
+ after: durableHandle.lastEventId,
5327
+ },
5328
+ };
5329
+ }
5316
5330
  const payload: AgentWidgetStoredState = {
5317
5331
  messages,
5318
5332
  metadata: persistentMetadata,
@@ -6284,17 +6298,40 @@ export const createAgentExperience = (
6284
6298
  /** The live editor, re-grafted into its morphed wrapper after the pass. */
6285
6299
  let editHydrate: { messageId: string; element: HTMLElement } | null = null;
6286
6300
 
6287
- // `reasoningDisplay.completedVisibility: "removed"`: no row for a finished
6288
- // trace, so the transcript gap closes with it. Checked before the row is
6289
- // registered, so the cached wrapper is pruned too.
6290
- const dropCompletedReasoning =
6291
- config.features?.reasoningDisplay?.completedVisibility === "removed";
6301
+ // `reasoningDisplay.completedVisibility: "removed"` (or "removed-when-short"
6302
+ // for a short completed trace): no row for the finished trace, so the
6303
+ // transcript gap closes with it. Checked before the row is registered, so
6304
+ // the cached wrapper is pruned too.
6305
+ const reasoningCompletedVisibility =
6306
+ config.features?.reasoningDisplay?.completedVisibility;
6307
+ const dropReasoningRow = (reasoning: AgentWidgetReasoning): boolean => {
6308
+ if (reasoningCompletedVisibility === "removed") return true;
6309
+ if (reasoningCompletedVisibility === "removed-when-short") {
6310
+ return isShortReasoning(
6311
+ reasoning,
6312
+ config.features?.reasoningDisplay?.shortThinkThreshold
6313
+ );
6314
+ }
6315
+ return false;
6316
+ };
6317
+
6318
+ // `toolCallDisplay.completedVisibility: "removed"`: same mechanism as
6319
+ // reasoning removal, no threshold variant.
6320
+ const dropCompletedToolCalls =
6321
+ config.features?.toolCallDisplay?.completedVisibility === "removed";
6292
6322
 
6293
6323
  messages.forEach((message) => {
6294
6324
  if (
6295
- dropCompletedReasoning &&
6296
6325
  message.variant === "reasoning" &&
6297
- message.reasoning?.status === "complete"
6326
+ message.reasoning?.status === "complete" &&
6327
+ dropReasoningRow(message.reasoning)
6328
+ ) {
6329
+ return;
6330
+ }
6331
+ if (
6332
+ dropCompletedToolCalls &&
6333
+ message.variant === "tool" &&
6334
+ message.toolCall?.status === "complete"
6298
6335
  ) {
6299
6336
  return;
6300
6337
  }
@@ -6849,6 +6886,12 @@ export const createAgentExperience = (
6849
6886
 
6850
6887
  messages.forEach((message) => {
6851
6888
  if (message.variant === "tool" && message.toolCall && showToolCalls) {
6889
+ // A tool call hidden by `completedVisibility: "removed"` does not
6890
+ // create a visible break in the transcript, so it should not split
6891
+ // an otherwise contiguous group.
6892
+ if (dropCompletedToolCalls && message.toolCall.status === "complete") {
6893
+ return;
6894
+ }
6852
6895
  currentGroup.push(message);
6853
6896
  return;
6854
6897
  }
@@ -6858,7 +6901,8 @@ export const createAgentExperience = (
6858
6901
  if (
6859
6902
  message.variant === "reasoning" &&
6860
6903
  (!showReasoning ||
6861
- (dropCompletedReasoning && message.reasoning?.status === "complete"))
6904
+ (message.reasoning?.status === "complete" &&
6905
+ dropReasoningRow(message.reasoning)))
6862
6906
  ) {
6863
6907
  return;
6864
6908
  }
@@ -8384,8 +8428,6 @@ export const createAgentExperience = (
8384
8428
  ? { executionId: candidate.executionId, after: candidate.after }
8385
8429
  : null;
8386
8430
  };
8387
- const personaOwnsClientTokenRecovery =
8388
- Boolean(config.clientToken) && typeof config.reconnectStream !== "function";
8389
8431
  if (config.clientToken) {
8390
8432
  config = {
8391
8433
  ...config,
@@ -8498,17 +8540,11 @@ export const createAgentExperience = (
8498
8540
  const hostExecutionState = config.onExecutionState;
8499
8541
  const recoveryWindow = mount.ownerDocument.defaultView;
8500
8542
  let pageExitInProgress = false;
8501
- const markPageExit = () => {
8502
- pageExitInProgress = true;
8503
- };
8504
- const markPageActive = () => {
8505
- pageExitInProgress = false;
8506
- };
8507
- recoveryWindow?.addEventListener("beforeunload", markPageExit);
8543
+ const markPageExit = () => (pageExitInProgress = true);
8544
+ const markPageActive = () => (pageExitInProgress = false);
8508
8545
  recoveryWindow?.addEventListener("pagehide", markPageExit);
8509
8546
  recoveryWindow?.addEventListener("pageshow", markPageActive);
8510
8547
  destroyCallbacks.push(() => {
8511
- recoveryWindow?.removeEventListener("beforeunload", markPageExit);
8512
8548
  recoveryWindow?.removeEventListener("pagehide", markPageExit);
8513
8549
  recoveryWindow?.removeEventListener("pageshow", markPageActive);
8514
8550
  });
@@ -8638,6 +8674,18 @@ export const createAgentExperience = (
8638
8674
  let activeStreamingTextCandidate: { index: number; id: string } | null = null;
8639
8675
  let pendingStreamingTextMessages: AgentWidgetMessage[] | null = null;
8640
8676
  let streamingTextRAF: number | null = null;
8677
+ let pendingDurablePersistenceMessages: AgentWidgetMessage[] | null = null;
8678
+
8679
+ const persistMessages = (messages: AgentWidgetMessage[]) => {
8680
+ if (!personaOwnsClientTokenRecovery) return persistState(messages);
8681
+ pendingDurablePersistenceMessages = messages;
8682
+ queueMicrotask(() => {
8683
+ if (pendingDurablePersistenceMessages === messages) {
8684
+ pendingDurablePersistenceMessages = null;
8685
+ persistState(messages);
8686
+ }
8687
+ });
8688
+ };
8641
8689
 
8642
8690
  const applyMessagesChanged = (messages: AgentWidgetMessage[]) => {
8643
8691
  lastAppliedMessages = messages;
@@ -8698,7 +8746,7 @@ export const createAgentExperience = (
8698
8746
  }
8699
8747
 
8700
8748
  voiceState.lastUserMessageWasVoice = Boolean(lastUserMessage?.viaVoice);
8701
- persistState(messages);
8749
+ persistMessages(messages);
8702
8750
  syncComposerBarPeek();
8703
8751
  };
8704
8752