@agent-native/core 0.78.9 → 0.79.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 (176) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +11 -0
  3. package/corpus/core/docs/content/actions.md +4 -2
  4. package/corpus/core/docs/content/agent-surfaces.md +9 -0
  5. package/corpus/core/docs/content/extensions.md +10 -0
  6. package/corpus/core/docs/content/generative-ui.md +217 -0
  7. package/corpus/core/docs/content/key-concepts.md +2 -2
  8. package/corpus/core/docs/content/mcp-apps.md +3 -5
  9. package/corpus/core/docs/content/native-chat-ui.md +9 -5
  10. package/corpus/core/docs/content/tracking.md +9 -3
  11. package/corpus/core/package.json +1 -1
  12. package/corpus/core/src/action-ui.ts +1 -0
  13. package/corpus/core/src/client/analytics.ts +195 -12
  14. package/corpus/core/src/client/chat/index.ts +1 -0
  15. package/corpus/core/src/client/chat/tool-call-display.tsx +5 -2
  16. package/corpus/core/src/client/chat/widgets/InlineExtensionWidget.tsx +139 -0
  17. package/corpus/core/src/client/chat/widgets/builtin-tool-renderers.tsx +27 -1
  18. package/corpus/core/src/client/conversation/AgentConversation.tsx +38 -0
  19. package/corpus/core/src/client/conversation/code-agent-transcript.ts +9 -0
  20. package/corpus/core/src/client/conversation/types.ts +4 -0
  21. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +27 -5
  22. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +26 -5
  23. package/corpus/core/src/client/extensions/InlineExtensionFrame.tsx +654 -0
  24. package/corpus/core/src/client/extensions/iframe-bridge.ts +30 -0
  25. package/corpus/core/src/client/extensions/index.ts +5 -0
  26. package/corpus/core/src/client/index.ts +1 -0
  27. package/corpus/core/src/client/session-replay.ts +89 -14
  28. package/corpus/core/src/code-agents/transcript-normalizer.ts +14 -0
  29. package/corpus/core/src/extensions/actions.ts +218 -1
  30. package/corpus/core/src/extensions/html-shell.ts +70 -3
  31. package/corpus/core/src/extensions/routes.ts +12 -4
  32. package/corpus/core/src/index.browser.ts +1 -0
  33. package/corpus/core/src/index.ts +1 -0
  34. package/corpus/core/src/server/agent-chat-plugin.ts +21 -9
  35. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +13 -0
  36. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/references/api.md +2 -0
  37. package/corpus/core/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +115 -0
  38. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +198 -59
  39. package/corpus/templates/analytics/AGENTS.md +12 -0
  40. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1105 -0
  41. package/corpus/templates/analytics/actions/dashboard-panel-order.ts +169 -0
  42. package/corpus/templates/analytics/actions/get-sql-dashboard.ts +89 -6
  43. package/corpus/templates/analytics/actions/mutate-dashboard.ts +333 -0
  44. package/corpus/templates/analytics/actions/reorder-dashboard-panels.ts +214 -0
  45. package/corpus/templates/analytics/actions/send-dashboard-report-now.ts +48 -21
  46. package/corpus/templates/analytics/actions/update-dashboard.ts +193 -100
  47. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +11 -0
  48. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
  49. package/corpus/templates/analytics/app/global.css +81 -10
  50. package/corpus/templates/analytics/app/i18n-data.ts +249 -4
  51. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +3 -3
  52. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +41 -1
  53. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +1 -1
  54. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +4 -4
  55. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +48 -18
  56. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +32 -0
  57. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +204 -70
  58. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +1101 -227
  59. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +160 -133
  60. package/corpus/templates/analytics/changelog/2026-06-26-agents-can-now-edit-dashboards-through-a-typed-mutation-scri.md +6 -0
  61. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-bar-chart-hovers-stay-subtle-in-dark-mode.md +6 -0
  62. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-chart-moves-are-now-handled-by-panel-id-making-sim.md +6 -0
  63. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-charts-drag-smoothly-with-stable-cards-inactive-ch.md +6 -0
  64. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-charts-show-a-refresh-spinner-while-keeping-existi.md +6 -0
  65. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-edits-stay-stable-while-chart-order-and-deletions-.md +6 -0
  66. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-email-reports-queue-immediate-sends.md +6 -0
  67. package/corpus/templates/analytics/changelog/2026-06-26-explorer-dashboards-now-drag-charts-with-a-stable-preview-an.md +6 -0
  68. package/corpus/templates/analytics/changelog/2026-06-26-session-replay-playback-can-now-be-toggled-by-clicking-the-r.md +6 -0
  69. package/corpus/templates/analytics/changelog/2026-06-26-session-replays-now-open-from-the-full-row-and-play-with-tim.md +6 -0
  70. package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +21 -0
  71. package/corpus/templates/analytics/server/lib/dashboard-report.ts +41 -17
  72. package/corpus/templates/analytics/server/plugins/agent-chat.ts +5 -0
  73. package/dist/action-ui.d.ts +1 -0
  74. package/dist/action-ui.d.ts.map +1 -1
  75. package/dist/action-ui.js +1 -0
  76. package/dist/action-ui.js.map +1 -1
  77. package/dist/client/analytics.d.ts.map +1 -1
  78. package/dist/client/analytics.js +173 -12
  79. package/dist/client/analytics.js.map +1 -1
  80. package/dist/client/chat/index.d.ts +1 -1
  81. package/dist/client/chat/index.d.ts.map +1 -1
  82. package/dist/client/chat/index.js +1 -1
  83. package/dist/client/chat/index.js.map +1 -1
  84. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  85. package/dist/client/chat/tool-call-display.js +3 -1
  86. package/dist/client/chat/tool-call-display.js.map +1 -1
  87. package/dist/client/chat/widgets/InlineExtensionWidget.d.ts +17 -0
  88. package/dist/client/chat/widgets/InlineExtensionWidget.d.ts.map +1 -0
  89. package/dist/client/chat/widgets/InlineExtensionWidget.js +85 -0
  90. package/dist/client/chat/widgets/InlineExtensionWidget.js.map +1 -0
  91. package/dist/client/chat/widgets/builtin-tool-renderers.d.ts.map +1 -1
  92. package/dist/client/chat/widgets/builtin-tool-renderers.js +15 -2
  93. package/dist/client/chat/widgets/builtin-tool-renderers.js.map +1 -1
  94. package/dist/client/conversation/AgentConversation.d.ts.map +1 -1
  95. package/dist/client/conversation/AgentConversation.js +33 -0
  96. package/dist/client/conversation/AgentConversation.js.map +1 -1
  97. package/dist/client/conversation/code-agent-transcript.js +8 -0
  98. package/dist/client/conversation/code-agent-transcript.js.map +1 -1
  99. package/dist/client/conversation/types.d.ts +4 -0
  100. package/dist/client/conversation/types.d.ts.map +1 -1
  101. package/dist/client/conversation/types.js.map +1 -1
  102. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  103. package/dist/client/extensions/EmbeddedExtension.js +29 -1
  104. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  105. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  106. package/dist/client/extensions/ExtensionViewer.js +28 -1
  107. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  108. package/dist/client/extensions/InlineExtensionFrame.d.ts +25 -0
  109. package/dist/client/extensions/InlineExtensionFrame.d.ts.map +1 -0
  110. package/dist/client/extensions/InlineExtensionFrame.js +488 -0
  111. package/dist/client/extensions/InlineExtensionFrame.js.map +1 -0
  112. package/dist/client/extensions/iframe-bridge.d.ts +2 -0
  113. package/dist/client/extensions/iframe-bridge.d.ts.map +1 -1
  114. package/dist/client/extensions/iframe-bridge.js +23 -0
  115. package/dist/client/extensions/iframe-bridge.js.map +1 -1
  116. package/dist/client/extensions/index.d.ts +1 -0
  117. package/dist/client/extensions/index.d.ts.map +1 -1
  118. package/dist/client/extensions/index.js +1 -0
  119. package/dist/client/extensions/index.js.map +1 -1
  120. package/dist/client/index.d.ts +1 -1
  121. package/dist/client/index.d.ts.map +1 -1
  122. package/dist/client/index.js +1 -1
  123. package/dist/client/index.js.map +1 -1
  124. package/dist/client/session-replay.d.ts +11 -1
  125. package/dist/client/session-replay.d.ts.map +1 -1
  126. package/dist/client/session-replay.js +74 -14
  127. package/dist/client/session-replay.js.map +1 -1
  128. package/dist/code-agents/transcript-normalizer.d.ts +2 -0
  129. package/dist/code-agents/transcript-normalizer.d.ts.map +1 -1
  130. package/dist/code-agents/transcript-normalizer.js +8 -0
  131. package/dist/code-agents/transcript-normalizer.js.map +1 -1
  132. package/dist/collab/awareness.d.ts +2 -2
  133. package/dist/collab/awareness.d.ts.map +1 -1
  134. package/dist/collab/routes.d.ts +1 -1
  135. package/dist/extensions/actions.d.ts.map +1 -1
  136. package/dist/extensions/actions.js +200 -1
  137. package/dist/extensions/actions.js.map +1 -1
  138. package/dist/extensions/html-shell.d.ts.map +1 -1
  139. package/dist/extensions/html-shell.js +70 -3
  140. package/dist/extensions/html-shell.js.map +1 -1
  141. package/dist/extensions/routes.d.ts.map +1 -1
  142. package/dist/extensions/routes.js +11 -4
  143. package/dist/extensions/routes.js.map +1 -1
  144. package/dist/index.browser.d.ts +1 -1
  145. package/dist/index.browser.d.ts.map +1 -1
  146. package/dist/index.browser.js +1 -1
  147. package/dist/index.browser.js.map +1 -1
  148. package/dist/index.d.ts +1 -1
  149. package/dist/index.d.ts.map +1 -1
  150. package/dist/index.js +1 -1
  151. package/dist/index.js.map +1 -1
  152. package/dist/notifications/routes.d.ts +2 -2
  153. package/dist/observability/routes.d.ts +3 -3
  154. package/dist/progress/routes.d.ts +1 -1
  155. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  156. package/dist/resources/handlers.d.ts +3 -3
  157. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  158. package/dist/server/agent-chat-plugin.js +21 -9
  159. package/dist/server/agent-chat-plugin.js.map +1 -1
  160. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  161. package/dist/server/transcribe-voice.d.ts +1 -1
  162. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +13 -0
  163. package/dist/templates/workspace-core/.agents/skills/extensions/references/api.md +2 -0
  164. package/dist/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +115 -0
  165. package/docs/content/actions.md +4 -2
  166. package/docs/content/agent-surfaces.md +9 -0
  167. package/docs/content/extensions.md +10 -0
  168. package/docs/content/generative-ui.md +217 -0
  169. package/docs/content/key-concepts.md +2 -2
  170. package/docs/content/mcp-apps.md +3 -5
  171. package/docs/content/native-chat-ui.md +9 -5
  172. package/docs/content/tracking.md +9 -3
  173. package/package.json +1 -1
  174. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +13 -0
  175. package/src/templates/workspace-core/.agents/skills/extensions/references/api.md +2 -0
  176. package/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +115 -0
@@ -59,6 +59,13 @@ type SentryUser = {
59
59
  username?: string;
60
60
  };
61
61
 
62
+ type TrackingIdentity = {
63
+ userId?: string;
64
+ userEmail?: string;
65
+ userName?: string;
66
+ orgId?: string | null;
67
+ };
68
+
62
69
  let _getDefaultProps: GetDefaultProps | null = null;
63
70
  let _agentNativeAnalyticsPublicKey: string | null = null;
64
71
  let _agentNativeAnalyticsEndpoint: string | null = null;
@@ -67,6 +74,10 @@ let _sentryInitialized = false;
67
74
  let _llmConnectionStatus: LlmConnectionStatus | null = null;
68
75
  let _llmConnectionRefresh: Promise<void> | null = null;
69
76
  let _llmConnectionRefreshInstalled = false;
77
+ let _trackingIdentity: TrackingIdentity | null = null;
78
+ let _trackingIdentityResolved = false;
79
+ let _trackingSessionRefresh: Promise<void> | null = null;
80
+ let _trackingSessionRefreshInstalled = false;
70
81
  // Buffer for setSentryUser calls made before Sentry has initialized.
71
82
  // `undefined` means "no pending update"; `null` means "pending clear".
72
83
  let _pendingSentryUser: SentryUser | null | undefined = undefined;
@@ -249,6 +260,83 @@ function installLlmConnectionRefresh(): void {
249
260
  });
250
261
  }
251
262
 
263
+ function readTrackingString(value: unknown): string | undefined {
264
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
265
+ }
266
+
267
+ function setTrackingIdentityFromSession(data: unknown): void {
268
+ const session = data as Record<string, unknown> | null;
269
+ if (!session || typeof session !== "object" || session.error) {
270
+ _trackingIdentity = null;
271
+ return;
272
+ }
273
+ const email = readTrackingString(session.email);
274
+ const authUserId = readTrackingString(session.userId);
275
+ const userId = email || authUserId;
276
+ if (!userId) {
277
+ _trackingIdentity = null;
278
+ return;
279
+ }
280
+ const userName = readTrackingString(session.name);
281
+ _trackingIdentity = {
282
+ userId,
283
+ ...(email ? { userEmail: email } : {}),
284
+ ...(userName ? { userName } : {}),
285
+ orgId: readTrackingString(session.orgId) ?? null,
286
+ };
287
+ }
288
+
289
+ function refreshTrackingAuthSession(): Promise<void> {
290
+ if (typeof window === "undefined" || typeof fetch !== "function") {
291
+ _trackingIdentityResolved = true;
292
+ return Promise.resolve();
293
+ }
294
+ if (_trackingSessionRefresh) return _trackingSessionRefresh;
295
+ _trackingSessionRefresh = fetch(
296
+ agentNativePath("/_agent-native/auth/session"),
297
+ )
298
+ .then((res) => (res.ok ? res.json() : null))
299
+ .then((data) => {
300
+ setTrackingIdentityFromSession(data);
301
+ })
302
+ .catch(() => {
303
+ _trackingIdentity = null;
304
+ })
305
+ .finally(() => {
306
+ _trackingIdentityResolved = true;
307
+ _trackingSessionRefresh = null;
308
+ });
309
+ return _trackingSessionRefresh;
310
+ }
311
+
312
+ function installTrackingAuthSessionRefresh(): void {
313
+ if (typeof window === "undefined" || _trackingSessionRefreshInstalled) return;
314
+ _trackingSessionRefreshInstalled = true;
315
+ void refreshTrackingAuthSession();
316
+ window.addEventListener("focus", () => {
317
+ void refreshTrackingAuthSession();
318
+ });
319
+ }
320
+
321
+ function applyTrackingIdentity(
322
+ properties: Record<string, unknown>,
323
+ ): Record<string, unknown> {
324
+ const identity = _trackingIdentity;
325
+ if (!identity) return properties;
326
+ let next = properties;
327
+ const assign = (key: string, value: unknown) => {
328
+ if (value !== undefined && value !== null && next[key] === undefined) {
329
+ if (next === properties) next = { ...properties };
330
+ next[key] = value;
331
+ }
332
+ };
333
+ assign("userId", identity.userId);
334
+ assign("userEmail", identity.userEmail);
335
+ assign("userName", identity.userName);
336
+ assign("orgId", identity.orgId);
337
+ return next;
338
+ }
339
+
252
340
  function getOrCreateAnonymousId(): string | undefined {
253
341
  if (typeof window === "undefined") return undefined;
254
342
  let id = safeStorageGet(ANONYMOUS_ID_STORAGE_KEY);
@@ -665,6 +753,20 @@ export function setSentryUser(
665
753
  user: SentryUser | null,
666
754
  orgId?: string | null,
667
755
  ): void {
756
+ if (user) {
757
+ const userId = user.email || user.id;
758
+ _trackingIdentity = userId
759
+ ? {
760
+ userId,
761
+ ...(user.email ? { userEmail: user.email } : {}),
762
+ ...(user.username ? { userName: user.username } : {}),
763
+ orgId: orgId ?? null,
764
+ }
765
+ : null;
766
+ } else {
767
+ _trackingIdentity = null;
768
+ }
769
+ _trackingIdentityResolved = true;
668
770
  if (_sentryInitialized) {
669
771
  Sentry.setUser(user);
670
772
  if (orgId !== undefined) {
@@ -775,6 +877,7 @@ export function configureTracking(options: ConfigureTrackingOptions): void {
775
877
  ensureAmplitude();
776
878
  captureFirstTouchAttribution();
777
879
  installLlmConnectionRefresh();
880
+ installTrackingAuthSessionRefresh();
778
881
  installPageviewTracking();
779
882
  maybeInstallSessionReplay(options.sessionReplay, {
780
883
  endpoint: options.endpoint,
@@ -791,6 +894,14 @@ function sessionReplayEnabledFromEnv(): boolean {
791
894
  return /^(1|true|yes|on)$/i.test((value ?? "").trim());
792
895
  }
793
896
 
897
+ function sessionReplayRequiresSignedInUserFromEnv(): boolean {
898
+ const env = (import.meta.env as Record<string, string | undefined>) ?? {};
899
+ const value =
900
+ env.VITE_AGENT_NATIVE_SESSION_REPLAY_REQUIRE_AUTH ||
901
+ env.VITE_SESSION_REPLAY_REQUIRE_AUTH;
902
+ return /^(1|true|yes|on)$/i.test((value ?? "").trim());
903
+ }
904
+
794
905
  function configuredSessionReplayOptions(
795
906
  config: boolean | SessionReplayOptions | undefined,
796
907
  tracking: { endpoint?: string; publicKey?: string } = {},
@@ -802,12 +913,22 @@ function configuredSessionReplayOptions(
802
913
  : undefined;
803
914
  const withTrackingDefaults = (
804
915
  options: SessionReplayOptions,
805
- ): SessionReplayOptions => ({
806
- ...(publicKey && !options.publicKey ? { publicKey } : {}),
807
- ...(endpoint && !options.endpoint ? { endpoint } : {}),
808
- ...options,
809
- });
916
+ ): SessionReplayOptions => {
917
+ const extraProperties = replayExtraPropertiesWithDefaults(
918
+ options.extraProperties,
919
+ );
920
+ return {
921
+ ...(publicKey && !options.publicKey ? { publicKey } : {}),
922
+ ...(endpoint && !options.endpoint ? { endpoint } : {}),
923
+ ...options,
924
+ requireSignedInUser:
925
+ options.requireSignedInUser ??
926
+ sessionReplayRequiresSignedInUserFromEnv(),
927
+ ...(extraProperties ? { extraProperties } : {}),
928
+ };
929
+ };
810
930
 
931
+ if (config === false) return null;
811
932
  if (config === true) return withTrackingDefaults({});
812
933
  if (config && typeof config === "object") {
813
934
  if (config.enabled === false) return null;
@@ -816,6 +937,22 @@ function configuredSessionReplayOptions(
816
937
  return sessionReplayEnabledFromEnv() ? withTrackingDefaults({}) : null;
817
938
  }
818
939
 
940
+ function replayExtraPropertiesWithDefaults(
941
+ source: SessionReplayOptions["extraProperties"],
942
+ ): SessionReplayOptions["extraProperties"] {
943
+ return () => {
944
+ const rawProps =
945
+ typeof source === "function"
946
+ ? source()
947
+ : source && typeof source === "object"
948
+ ? source
949
+ : {};
950
+ const props = rawProps && typeof rawProps === "object" ? rawProps : {};
951
+ const withDefaults = _getDefaultProps?.("session_replay", props) ?? props;
952
+ return applyTrackingIdentity(withDefaults);
953
+ };
954
+ }
955
+
819
956
  function replayEndpointFromTrackingEndpoint(value: string): string | undefined {
820
957
  try {
821
958
  const url = new URL(value);
@@ -849,23 +986,60 @@ function maybeInstallSessionReplay(
849
986
  if (typeof window === "undefined") return;
850
987
  const options = configuredSessionReplayOptions(config, tracking);
851
988
  if (!options) return;
852
- import("./session-replay.js")
853
- .then((mod) => mod.startSessionReplay(options))
854
- .catch(() => {});
989
+ void startConfiguredSessionReplay(options);
990
+ }
991
+
992
+ async function waitForSessionReplayAuthIfRequired(
993
+ options: SessionReplayOptions,
994
+ ): Promise<boolean> {
995
+ if (!options.requireSignedInUser) return true;
996
+ if (_trackingIdentity?.userId) return true;
997
+ try {
998
+ if (_trackingSessionRefresh) {
999
+ await _trackingSessionRefresh;
1000
+ } else if (!_trackingIdentityResolved) {
1001
+ await refreshTrackingAuthSession();
1002
+ }
1003
+ } catch {
1004
+ // best-effort; missing identity below keeps replay off
1005
+ }
1006
+ return !!_trackingIdentity?.userId;
1007
+ }
1008
+
1009
+ async function startConfiguredSessionReplay(
1010
+ options: SessionReplayOptions,
1011
+ ): Promise<SessionReplayStartResult | null> {
1012
+ if (!(await waitForSessionReplayAuthIfRequired(options))) {
1013
+ return { started: false, reason: "missing-user-id" };
1014
+ }
1015
+ try {
1016
+ const mod = await import("./session-replay.js");
1017
+ return mod.startSessionReplay(options);
1018
+ } catch {
1019
+ return { started: false, reason: "import-failed" };
1020
+ }
855
1021
  }
856
1022
 
857
1023
  export async function startSessionReplay(
858
1024
  options: SessionReplayOptions = {},
859
1025
  ): Promise<SessionReplayStartResult> {
1026
+ const configured = configuredSessionReplayOptions(options) ?? options;
1027
+ if (!(await waitForSessionReplayAuthIfRequired(configured))) {
1028
+ return { started: false, reason: "missing-user-id" };
1029
+ }
860
1030
  const mod = await import("./session-replay.js");
861
- return mod.startSessionReplay(options);
1031
+ return mod.startSessionReplay(configured);
862
1032
  }
863
1033
 
864
1034
  export async function maybeStartSessionReplay(
865
1035
  options: SessionReplayOptions = {},
866
1036
  ): Promise<SessionReplayStartResult> {
1037
+ const configured = configuredSessionReplayOptions(options) ?? options;
1038
+ if (!(await waitForSessionReplayAuthIfRequired(configured))) {
1039
+ return { started: false, reason: "missing-user-id" };
1040
+ }
867
1041
  const mod = await import("./session-replay.js");
868
- return mod.maybeStartSessionReplay(options);
1042
+ return mod.maybeStartSessionReplay(configured);
869
1043
  }
870
1044
 
871
1045
  export async function stopSessionReplay(reason = "manual"): Promise<void> {
@@ -911,7 +1085,7 @@ function resolveProps(
911
1085
  for (const [key, value] of Object.entries(llmProps)) {
912
1086
  if (enriched[key] === undefined) enriched[key] = value;
913
1087
  }
914
- return enriched;
1088
+ return applyTrackingIdentity(enriched);
915
1089
  }
916
1090
 
917
1091
  function pageviewKey(): string {
@@ -950,11 +1124,20 @@ function emitPageview(reason: string): void {
950
1124
 
951
1125
  function schedulePageview(reason: string): void {
952
1126
  const run = () => emitPageview(reason);
1127
+ const pendingStartupContext: Array<Promise<void>> = [];
953
1128
  if (_llmConnectionRefresh && !_llmConnectionStatus) {
1129
+ pendingStartupContext.push(_llmConnectionRefresh);
1130
+ }
1131
+ if (_trackingSessionRefresh && !_trackingIdentityResolved) {
1132
+ pendingStartupContext.push(_trackingSessionRefresh);
1133
+ }
1134
+ if (pendingStartupContext.length > 0) {
954
1135
  const timeout = new Promise<void>((resolve) =>
955
1136
  window.setTimeout(resolve, 250),
956
1137
  );
957
- Promise.race([_llmConnectionRefresh, timeout]).finally(run);
1138
+ Promise.race([Promise.allSettled(pendingStartupContext), timeout]).finally(
1139
+ run,
1140
+ );
958
1141
  return;
959
1142
  }
960
1143
  if (typeof queueMicrotask === "function") {
@@ -91,6 +91,7 @@ export {
91
91
  ACTION_CHAT_UI_DATA_INSIGHTS_RENDERER,
92
92
  ACTION_CHAT_UI_DATA_TABLE_RENDERER,
93
93
  ACTION_CHAT_UI_DATA_WIDGET_RENDERER,
94
+ ACTION_CHAT_UI_INLINE_EXTENSION_RENDERER,
94
95
  type ActionChatUIConfig,
95
96
  } from "../../action-ui.js";
96
97
  export {
@@ -727,19 +727,22 @@ export function ReconnectStreamMessage({
727
727
  content: ContentPart[];
728
728
  }) {
729
729
  const chatRunning = React.useContext(ChatRunningContext);
730
+ const streamingTextPartIndex =
731
+ content.at(-1)?.type === "text" ? content.length - 1 : -1;
730
732
 
731
733
  return (
732
734
  <div className="flex justify-start">
733
735
  <div className="max-w-[95%] text-sm leading-relaxed text-foreground space-y-1">
734
736
  {content.map((part, i) => {
735
737
  if (part.type === "text") {
738
+ const partStreaming = chatRunning && i === streamingTextPartIndex;
736
739
  return (
737
740
  <SmoothMarkdownText
738
741
  key={`reconnect-text-${i}`}
739
742
  text={part.text}
740
- streaming={chatRunning}
743
+ streaming={partStreaming}
741
744
  resetKey={`reconnect-text-${i}`}
742
- statusType={chatRunning ? "running" : "complete"}
745
+ statusType={partStreaming ? "running" : "complete"}
743
746
  />
744
747
  );
745
748
  }
@@ -0,0 +1,139 @@
1
+ import { IconExternalLink, IconSparkles } from "@tabler/icons-react";
2
+
3
+ import { extensionPath } from "../../../extensions/path.js";
4
+ import { appPath } from "../../api-path.js";
5
+ import { InlineExtensionFrame } from "../../extensions/InlineExtensionFrame.js";
6
+ import type { ToolRendererContext } from "../tool-render-registry.js";
7
+
8
+ export interface InlineExtensionToolResult {
9
+ mode: "transient" | "persisted";
10
+ id: string;
11
+ name: string;
12
+ description?: string;
13
+ content?: string;
14
+ path?: string;
15
+ updatedAt?: string;
16
+ context?: Record<string, unknown>;
17
+ initialHeight?: number;
18
+ }
19
+
20
+ function isRecord(value: unknown): value is Record<string, unknown> {
21
+ return !!value && typeof value === "object" && !Array.isArray(value);
22
+ }
23
+
24
+ function stringValue(value: unknown): string | undefined {
25
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
26
+ }
27
+
28
+ function recordValue(value: unknown): Record<string, unknown> | undefined {
29
+ return isRecord(value) ? value : undefined;
30
+ }
31
+
32
+ function initialHeightValue(value: unknown): number | undefined {
33
+ const height = Number(value);
34
+ if (!Number.isFinite(height) || height <= 0) return undefined;
35
+ return Math.min(Math.max(Math.round(height), 120), 1000);
36
+ }
37
+
38
+ function normalizeInlineExtension(
39
+ value: unknown,
40
+ ): InlineExtensionToolResult | null {
41
+ if (!isRecord(value)) return null;
42
+ const id = stringValue(value.id);
43
+ const name = stringValue(value.name);
44
+ if (!id || !name) return null;
45
+ const mode = value.mode === "transient" ? "transient" : "persisted";
46
+ const content = stringValue(value.content);
47
+ if (mode === "transient" && !content) return null;
48
+ return {
49
+ mode,
50
+ id,
51
+ name,
52
+ description: stringValue(value.description),
53
+ content,
54
+ path: stringValue(value.path),
55
+ updatedAt: stringValue(value.updatedAt),
56
+ context: recordValue(value.context),
57
+ initialHeight: initialHeightValue(value.initialHeight),
58
+ };
59
+ }
60
+
61
+ export function normalizeInlineExtensionToolResult(
62
+ context: ToolRendererContext,
63
+ ): InlineExtensionToolResult | null {
64
+ const result = context.resultJson;
65
+ if (!isRecord(result)) return null;
66
+
67
+ const inline = normalizeInlineExtension(result.inlineExtension);
68
+ if (inline) return inline;
69
+
70
+ if (isRecord(result.extension)) {
71
+ const id = stringValue(result.extension.id);
72
+ const name = stringValue(result.extension.name);
73
+ if (!id || !name) return null;
74
+ return {
75
+ mode: "persisted",
76
+ id,
77
+ name,
78
+ description: stringValue(result.extension.description),
79
+ path:
80
+ stringValue(result.extension.path) ??
81
+ stringValue(result.path) ??
82
+ extensionPath(id, name),
83
+ updatedAt: stringValue(result.extension.updatedAt),
84
+ context: recordValue(result.context),
85
+ initialHeight: initialHeightValue(result.initialHeight),
86
+ };
87
+ }
88
+
89
+ return null;
90
+ }
91
+
92
+ export function InlineExtensionWidget({
93
+ context,
94
+ }: {
95
+ context: ToolRendererContext;
96
+ }) {
97
+ const result = normalizeInlineExtensionToolResult(context);
98
+ if (!result) return null;
99
+
100
+ const href = result.path ? appPath(result.path) : undefined;
101
+
102
+ return (
103
+ <div className="my-1.5 overflow-hidden rounded-lg border border-border bg-background text-foreground shadow-sm">
104
+ <div className="flex items-center gap-2 border-b border-border px-3 py-2">
105
+ <IconSparkles className="h-4 w-4 shrink-0 text-muted-foreground" />
106
+ <div className="min-w-0 flex-1">
107
+ <div className="truncate text-sm font-medium">{result.name}</div>
108
+ {result.description ? (
109
+ <div className="truncate text-[11px] text-muted-foreground">
110
+ {result.description}
111
+ </div>
112
+ ) : null}
113
+ </div>
114
+ {href ? (
115
+ <a
116
+ href={href}
117
+ className="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground"
118
+ aria-label="Open extension"
119
+ >
120
+ <IconExternalLink className="h-4 w-4" />
121
+ </a>
122
+ ) : null}
123
+ </div>
124
+ <InlineExtensionFrame
125
+ extensionId={result.mode === "persisted" ? result.id : undefined}
126
+ extension={{
127
+ id: result.id,
128
+ name: result.name,
129
+ description: result.description,
130
+ content: result.mode === "transient" ? result.content : undefined,
131
+ updatedAt: result.updatedAt,
132
+ mode: result.mode,
133
+ }}
134
+ context={result.context}
135
+ initialHeight={result.initialHeight ?? 260}
136
+ />
137
+ </div>
138
+ );
139
+ }
@@ -3,6 +3,7 @@ import {
3
3
  ACTION_CHAT_UI_DATA_INSIGHTS_RENDERER,
4
4
  ACTION_CHAT_UI_DATA_TABLE_RENDERER,
5
5
  ACTION_CHAT_UI_DATA_WIDGET_RENDERER,
6
+ ACTION_CHAT_UI_INLINE_EXTENSION_RENDERER,
6
7
  } from "../../../action-ui.js";
7
8
  import {
8
9
  registerReservedActionChatRenderer,
@@ -21,6 +22,10 @@ import {
21
22
  import { DataChartWidget } from "./DataChartWidget.js";
22
23
  import { DataInsightsWidget } from "./DataInsightsWidget.js";
23
24
  import { DataTableWidget } from "./DataTableWidget.js";
25
+ import {
26
+ InlineExtensionWidget,
27
+ normalizeInlineExtensionToolResult,
28
+ } from "./InlineExtensionWidget.js";
24
29
 
25
30
  function isRecord(value: unknown): value is Record<string, unknown> {
26
31
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -101,6 +106,11 @@ function renderDataWidget(context: ToolRendererContext) {
101
106
  const BuiltinDataWidgetRenderer: ToolRendererComponent = ({ context }) =>
102
107
  renderDataWidget(context);
103
108
 
109
+ const BuiltinInlineExtensionRenderer: ToolRendererComponent = ({ context }) =>
110
+ normalizeInlineExtensionToolResult(context) ? (
111
+ <InlineExtensionWidget context={context} />
112
+ ) : null;
113
+
104
114
  export function isBuiltinDataWidgetActionRenderer(
105
115
  context: ToolRendererContext,
106
116
  ): boolean {
@@ -116,6 +126,12 @@ export function isBuiltinDataWidgetActionRenderer(
116
126
  export function resolveBuiltinActionChatRenderer(
117
127
  context: ToolRendererContext,
118
128
  ): ToolRendererComponent | null {
129
+ if (
130
+ context.chatUI?.renderer === ACTION_CHAT_UI_INLINE_EXTENSION_RENDERER &&
131
+ normalizeInlineExtensionToolResult(context)
132
+ ) {
133
+ return BuiltinInlineExtensionRenderer;
134
+ }
119
135
  if (
120
136
  isBuiltinDataWidgetActionRenderer(context) &&
121
137
  normalizeActionDataWidgetResult(context)
@@ -128,6 +144,12 @@ export function resolveBuiltinActionChatRenderer(
128
144
  export function resolveBuiltinFallbackToolRenderer(
129
145
  context: ToolRendererContext,
130
146
  ): ToolRendererComponent | null {
147
+ if (
148
+ context.chatUI?.renderer === ACTION_CHAT_UI_INLINE_EXTENSION_RENDERER &&
149
+ normalizeInlineExtensionToolResult(context)
150
+ ) {
151
+ return BuiltinInlineExtensionRenderer;
152
+ }
131
153
  return normalizeActionDataWidgetResult(context) !== null
132
154
  ? BuiltinDataWidgetRenderer
133
155
  : null;
@@ -138,11 +160,15 @@ for (const [id, renderer] of [
138
160
  ["core.data-chart", ACTION_CHAT_UI_DATA_CHART_RENDERER],
139
161
  ["core.data-insights", ACTION_CHAT_UI_DATA_INSIGHTS_RENDERER],
140
162
  ["core.data-widget", ACTION_CHAT_UI_DATA_WIDGET_RENDERER],
163
+ ["core.inline-extension", ACTION_CHAT_UI_INLINE_EXTENSION_RENDERER],
141
164
  ] as const) {
142
165
  registerReservedActionChatRenderer({
143
166
  id,
144
167
  renderer,
145
- Component: BuiltinDataWidgetRenderer,
168
+ Component:
169
+ renderer === ACTION_CHAT_UI_INLINE_EXTENSION_RENDERER
170
+ ? BuiltinInlineExtensionRenderer
171
+ : BuiltinDataWidgetRenderer,
146
172
  });
147
173
  }
148
174
 
@@ -13,6 +13,11 @@ import React from "react";
13
13
  import ReactMarkdown, { defaultUrlTransform } from "react-markdown";
14
14
  import remarkGfm from "remark-gfm";
15
15
 
16
+ import { resolveToolRenderer } from "../chat/tool-render-registry.js";
17
+ import {
18
+ resolveBuiltinActionChatRenderer,
19
+ resolveBuiltinFallbackToolRenderer,
20
+ } from "../chat/widgets/builtin-tool-renderers.js";
16
21
  import { HighlightedCodeBlock as SharedHighlightedCodeBlock } from "../HighlightedCodeBlock.js";
17
22
  import { McpAppRenderer } from "../mcp-apps/McpAppRenderer.js";
18
23
  import { humanizeToolName } from "../tool-display.js";
@@ -356,7 +361,40 @@ function openMarkdownLink(
356
361
  window.open(url.href, "_blank", "noopener,noreferrer");
357
362
  }
358
363
 
364
+ function parseJsonText(value: string | undefined): unknown {
365
+ if (!value) return null;
366
+ try {
367
+ return JSON.parse(value);
368
+ } catch {
369
+ return null;
370
+ }
371
+ }
372
+
373
+ function parseJsonObject(value: string | undefined): Record<string, unknown> {
374
+ const parsed = parseJsonText(value);
375
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
376
+ ? (parsed as Record<string, unknown>)
377
+ : {};
378
+ }
379
+
359
380
  function ConversationToolCall({ tool }: { tool: AgentConversationToolCall }) {
381
+ const resultJson = parseJsonText(tool.result);
382
+ const nativeToolContext = {
383
+ toolName: tool.name,
384
+ args: tool.args ?? parseJsonObject(tool.input),
385
+ resultText: tool.result,
386
+ resultJson: tool.resultJson ?? resultJson,
387
+ isRunning: tool.state === "running" || tool.state === "activity",
388
+ chatUI: tool.chatUI,
389
+ };
390
+ const NativeToolRenderer =
391
+ resolveBuiltinActionChatRenderer(nativeToolContext) ??
392
+ resolveToolRenderer(nativeToolContext) ??
393
+ resolveBuiltinFallbackToolRenderer(nativeToolContext);
394
+ if (NativeToolRenderer) {
395
+ return <NativeToolRenderer context={nativeToolContext} />;
396
+ }
397
+
360
398
  const hasDetails = Boolean(tool.input || tool.result || tool.mcpApp);
361
399
  const icon =
362
400
  tool.state === "running" || tool.state === "activity" ? (
@@ -175,9 +175,12 @@ function toConversationTool(
175
175
  : item.state === "activity"
176
176
  ? "activity"
177
177
  : "running",
178
+ args: recordValue(item.input),
178
179
  input: preview(item.input),
180
+ resultJson: item.result,
179
181
  result: preview(item.result),
180
182
  ...(item.mcpApp ? { mcpApp: item.mcpApp } : {}),
183
+ ...(item.chatUI ? { chatUI: item.chatUI } : {}),
181
184
  summary:
182
185
  item.state === "completed"
183
186
  ? "finished"
@@ -229,6 +232,12 @@ function toConversationArtifact(
229
232
  };
230
233
  }
231
234
 
235
+ function recordValue(value: unknown): Record<string, unknown> | undefined {
236
+ return value && typeof value === "object" && !Array.isArray(value)
237
+ ? (value as Record<string, unknown>)
238
+ : undefined;
239
+ }
240
+
232
241
  function preview(value: unknown): string | undefined {
233
242
  if (value === undefined || value === null) return undefined;
234
243
  const text =
@@ -1,5 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
2
 
3
+ import type { ActionChatUIConfig } from "../../action-ui.js";
3
4
  import type { AgentMcpAppPayload } from "../../mcp-client/app-result.js";
4
5
 
5
6
  export interface AgentConversationAttachment {
@@ -23,8 +24,11 @@ export interface AgentConversationToolCall {
23
24
  name: string;
24
25
  state: AgentConversationToolState;
25
26
  input?: string;
27
+ args?: Record<string, unknown>;
26
28
  result?: string;
29
+ resultJson?: unknown;
27
30
  mcpApp?: AgentMcpAppPayload;
31
+ chatUI?: ActionChatUIConfig;
28
32
  summary?: string;
29
33
  }
30
34