@agent-native/core 0.99.1 → 0.99.2

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 (148) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +13 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +12 -38
  5. package/corpus/core/src/audit/config.ts +0 -1
  6. package/corpus/core/src/chat-threads/store.ts +57 -41
  7. package/corpus/core/src/client/AssistantChat.tsx +1 -2
  8. package/corpus/core/src/client/chat/message-components.tsx +15 -3
  9. package/corpus/core/src/client/chat/tool-call-display.tsx +18 -1
  10. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +5 -9
  11. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +0 -133
  12. package/corpus/core/src/client/index.ts +7 -0
  13. package/corpus/core/src/client/settings/DemoModeSection.tsx +9 -65
  14. package/corpus/core/src/client/settings/SettingsPanel.tsx +1 -1
  15. package/corpus/core/src/client/use-db-sync.ts +3 -15
  16. package/corpus/core/src/client/use-demo-mode-status.ts +16 -30
  17. package/corpus/core/src/demo/browser-state.ts +47 -0
  18. package/corpus/core/src/demo/fetch-interceptor.ts +12 -59
  19. package/corpus/core/src/deploy/workspace-deploy.ts +2 -2
  20. package/corpus/core/src/server/action-discovery.ts +0 -1
  21. package/corpus/core/src/server/core-routes-plugin.ts +0 -30
  22. package/corpus/core/src/templates/workspace-core/.agents/skills/native-navigation/SKILL.md +67 -0
  23. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +48 -7
  24. package/corpus/templates/analytics/AGENTS.md +4 -4
  25. package/corpus/templates/analytics/actions/compose-dashboard.ts +12 -1
  26. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +15 -4
  27. package/corpus/templates/analytics/actions/mutate-dashboard.ts +2 -1
  28. package/corpus/templates/analytics/actions/update-dashboard.ts +14 -0
  29. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +21 -27
  30. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +4 -1
  31. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/interpolate.ts +21 -0
  32. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +14 -0
  33. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +31 -12
  34. package/corpus/templates/analytics/changelog/2026-07-13-dashboard-charts-now-honor-the-selected-time-range-by-defaul.md +6 -0
  35. package/corpus/templates/analytics/changelog/2026-07-13-keep-real-authorized-identities-in-analytics-incident-lookup.md +6 -0
  36. package/corpus/templates/analytics/changelog/2026-07-13-the-analytics-agents-tab-is-available-again-from-the-main-na.md +6 -0
  37. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +3 -3
  38. package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +4 -0
  39. package/corpus/templates/analytics/server/lib/dashboard-time-scope.ts +170 -0
  40. package/corpus/templates/analytics/server/lib/error-capture.ts +6 -62
  41. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +20 -5
  42. package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +1 -1
  43. package/corpus/templates/analytics/server/lib/session-replay.ts +5 -111
  44. package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -0
  45. package/corpus/templates/assets/app/routes/library.tsx +3 -4
  46. package/corpus/templates/assets/changelog/2026-07-13-internal-library-links-open-in-new-tabs.md +6 -0
  47. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +43 -40
  48. package/corpus/templates/calendar/app/pages/ManageBookingPage.tsx +6 -8
  49. package/corpus/templates/calendar/changelog/2026-07-13-booking-links-and-previews-open-in-new-tabs.md +6 -0
  50. package/corpus/templates/clips/app/components/player/sign-in-prompt-dialog.tsx +4 -7
  51. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +19 -9
  52. package/corpus/templates/clips/app/routes/share.$shareId.tsx +5 -3
  53. package/corpus/templates/clips/changelog/2026-07-13-recording-links-open-in-new-tabs.md +6 -0
  54. package/corpus/templates/content/app/components/editor/ContentReferencePreview.tsx +6 -8
  55. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +5 -3
  56. package/corpus/templates/content/app/components/editor/database/FormView.tsx +6 -9
  57. package/corpus/templates/content/changelog/2026-07-13-document-links-open-in-new-tabs.md +6 -0
  58. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +33 -16
  59. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +18 -1
  60. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +4 -4
  61. package/corpus/templates/design/app/pages/DesignSystems.tsx +8 -10
  62. package/corpus/templates/design/app/pages/Index.tsx +6 -1
  63. package/corpus/templates/design/app/pages/Present.tsx +12 -6
  64. package/corpus/templates/design/changelog/2026-07-13-design-navigation-links-open-in-new-tabs.md +6 -0
  65. package/corpus/templates/design/changelog/2026-07-13-design-session-replays-include-preview-iframes.md +6 -0
  66. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +11 -11
  67. package/corpus/templates/forms/app/pages/FormsListPage.tsx +6 -8
  68. package/corpus/templates/forms/changelog/2026-07-13-form-navigation-links-open-in-new-tabs.md +6 -0
  69. package/corpus/templates/mail/app/pages/DraftQueuePage.tsx +3 -8
  70. package/corpus/templates/mail/changelog/2026-07-13-settings-links-open-in-new-tabs.md +6 -0
  71. package/dist/agent/production-agent.d.ts.map +1 -1
  72. package/dist/agent/production-agent.js +13 -37
  73. package/dist/agent/production-agent.js.map +1 -1
  74. package/dist/audit/config.d.ts.map +1 -1
  75. package/dist/audit/config.js +0 -1
  76. package/dist/audit/config.js.map +1 -1
  77. package/dist/chat-threads/store.d.ts.map +1 -1
  78. package/dist/chat-threads/store.js +47 -33
  79. package/dist/chat-threads/store.js.map +1 -1
  80. package/dist/client/AssistantChat.d.ts.map +1 -1
  81. package/dist/client/AssistantChat.js +1 -1
  82. package/dist/client/AssistantChat.js.map +1 -1
  83. package/dist/client/chat/message-components.d.ts.map +1 -1
  84. package/dist/client/chat/message-components.js +9 -3
  85. package/dist/client/chat/message-components.js.map +1 -1
  86. package/dist/client/chat/tool-call-display.d.ts +3 -1
  87. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  88. package/dist/client/chat/tool-call-display.js +10 -2
  89. package/dist/client/chat/tool-call-display.js.map +1 -1
  90. package/dist/client/extensions/EmbeddedExtension.js +2 -6
  91. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  92. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  93. package/dist/client/extensions/ExtensionViewer.js +2 -59
  94. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  95. package/dist/client/index.d.ts +2 -0
  96. package/dist/client/index.d.ts.map +1 -1
  97. package/dist/client/index.js +2 -0
  98. package/dist/client/index.js.map +1 -1
  99. package/dist/client/settings/DemoModeSection.d.ts +1 -13
  100. package/dist/client/settings/DemoModeSection.d.ts.map +1 -1
  101. package/dist/client/settings/DemoModeSection.js +5 -52
  102. package/dist/client/settings/DemoModeSection.js.map +1 -1
  103. package/dist/client/settings/SettingsPanel.js +1 -1
  104. package/dist/client/settings/SettingsPanel.js.map +1 -1
  105. package/dist/client/use-db-sync.d.ts.map +1 -1
  106. package/dist/client/use-db-sync.js +3 -7
  107. package/dist/client/use-db-sync.js.map +1 -1
  108. package/dist/client/use-demo-mode-status.d.ts +3 -4
  109. package/dist/client/use-demo-mode-status.d.ts.map +1 -1
  110. package/dist/client/use-demo-mode-status.js +8 -23
  111. package/dist/client/use-demo-mode-status.js.map +1 -1
  112. package/dist/collab/awareness.d.ts +2 -2
  113. package/dist/collab/awareness.d.ts.map +1 -1
  114. package/dist/collab/routes.d.ts +1 -1
  115. package/dist/collab/struct-routes.d.ts +1 -1
  116. package/dist/demo/browser-state.d.ts +10 -0
  117. package/dist/demo/browser-state.d.ts.map +1 -0
  118. package/dist/demo/browser-state.js +49 -0
  119. package/dist/demo/browser-state.js.map +1 -0
  120. package/dist/demo/fetch-interceptor.d.ts +2 -7
  121. package/dist/demo/fetch-interceptor.d.ts.map +1 -1
  122. package/dist/demo/fetch-interceptor.js +12 -56
  123. package/dist/demo/fetch-interceptor.js.map +1 -1
  124. package/dist/deploy/workspace-deploy.js +2 -2
  125. package/dist/deploy/workspace-deploy.js.map +1 -1
  126. package/dist/notifications/routes.d.ts +2 -2
  127. package/dist/progress/routes.d.ts +1 -1
  128. package/dist/resources/handlers.d.ts +3 -3
  129. package/dist/server/action-discovery.d.ts.map +1 -1
  130. package/dist/server/action-discovery.js +0 -1
  131. package/dist/server/action-discovery.js.map +1 -1
  132. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  133. package/dist/server/core-routes-plugin.js +0 -27
  134. package/dist/server/core-routes-plugin.js.map +1 -1
  135. package/dist/server/transcribe-voice.d.ts +1 -1
  136. package/dist/templates/workspace-core/.agents/skills/native-navigation/SKILL.md +67 -0
  137. package/package.json +1 -1
  138. package/src/templates/workspace-core/.agents/skills/native-navigation/SKILL.md +67 -0
  139. package/corpus/core/src/demo/actions/toggle-demo-mode.ts +0 -23
  140. package/corpus/core/src/demo/config.ts +0 -57
  141. package/dist/demo/actions/toggle-demo-mode.d.ts +0 -8
  142. package/dist/demo/actions/toggle-demo-mode.d.ts.map +0 -1
  143. package/dist/demo/actions/toggle-demo-mode.js +0 -21
  144. package/dist/demo/actions/toggle-demo-mode.js.map +0 -1
  145. package/dist/demo/config.d.ts +0 -9
  146. package/dist/demo/config.d.ts.map +0 -1
  147. package/dist/demo/config.js +0 -58
  148. package/dist/demo/config.js.map +0 -1
package/corpus/README.md CHANGED
@@ -28,4 +28,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
  ## Generated Counts
29
29
 
30
30
  - core files: 2241
31
- - template files: 5539
31
+ - template files: 5551
@@ -1,5 +1,18 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.99.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 34b4f29: Keep Demo mode browser-local so backend actions and agent/MCP results retain real access-scoped data.
8
+ - 34b4f29: Fix 404s when client-side navigating from the dispatch shell into a mounted workspace sub-app's root route. React Router's `.data` loader-fetch convention (e.g. `/coach.data`) didn't match either deployment platform's per-app routing rules (`basePath` / `basePath/*`), so the request fell through to a bare 404 before reaching the app's own server, leaving the sub-app stuck retrying its own action/agent-chat calls afterward.
9
+ - 34b4f29: Keep the latest reasoning thought expanded until a newer thought arrives, then animate the previous thought closed.
10
+ - 34b4f29: Preserve native browser link behavior when opening embedded extensions in their full view.
11
+ - 34b4f29: Keep chat composers editable while AI provider readiness is loading.
12
+ - 34b4f29: Record framework-owned Design preview iframes in session replays with the same privacy masking as the host page.
13
+ - 34b4f29: Remove the extension viewer's View / edit source button.
14
+ - 34b4f29: Retry transient chat-thread database failures so completed agent responses are not surfaced as unsaved.
15
+
3
16
  ## 0.99.1
4
17
 
5
18
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.99.1",
3
+ "version": "0.99.2",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -11,8 +11,6 @@ import type { EventHandler as H3EventHandler } from "h3";
11
11
  import { isAgentActionStopError } from "../action.js";
12
12
  import { readAppState } from "../application-state/script-helpers.js";
13
13
  import { isReadOnlyShellCommand } from "../coding-tools/index.js";
14
- import { isDemoModeEnabled } from "../demo/config.js";
15
- import { redactDemoData, redactDemoString } from "../demo/redact.js";
16
14
  import { extensionIdFromPathname } from "../extensions/path.js";
17
15
  import { preUploadAttachments } from "../file-upload/pre-upload-attachments.js";
18
16
  import { isMcpActionResult } from "../mcp-client/app-result.js";
@@ -4456,48 +4454,24 @@ export async function runAgentLoop(opts: {
4456
4454
  isError = true;
4457
4455
  }
4458
4456
  mcpApp = mcpResult?.mcpApp;
4459
- // Demo mode: the agent must see the same anonymized data the UI shows, so
4460
- // it can't read out a real name/email on a live screen share. Redact
4461
- // the structured result (not the JSON string) so IDs/dates/URLs stay
4462
- // intact and follow-up tool calls still work. Gated — the expensive
4463
- // walk only runs when demo mode is on.
4464
- let redacted: unknown = rawForAgent;
4465
- const demoMode = await isDemoModeEnabled();
4466
- if (demoMode) {
4467
- mcpApp = undefined;
4468
- if (typeof rawForAgent === "string") {
4469
- try {
4470
- redacted = JSON.stringify(
4471
- redactDemoData(JSON.parse(rawForAgent)),
4472
- null,
4473
- 2,
4474
- );
4475
- } catch {
4476
- redacted = redactDemoString(rawForAgent);
4477
- }
4478
- } else {
4479
- redacted = redactDemoData(rawForAgent);
4480
- }
4481
- }
4457
+ // Demo mode is browser-local presentation state. The agent and MCP
4458
+ // layers always receive the real, access-scoped tool result.
4459
+ let resultForAgent: unknown = rawForAgent;
4482
4460
  // Vision images for the model: MCP tools return standard `image`
4483
4461
  // content parts; first-party actions opt in via the well-known
4484
4462
  // `_agentImages` result field (stripped from the JSON the model
4485
- // reads, even in demo mode). Demo mode drops the images themselves —
4486
- // text redaction can't scrub pixels, and a screenshot may expose
4487
- // original visual data. The images array
4463
+ // reads). The images array
4488
4464
  // never touches the ledger — only the compact text notes appended
4489
4465
  // below are persisted.
4490
4466
  let imageNotes: string[] = [];
4491
4467
  if (mcpResult) {
4492
- if (!demoMode) {
4493
- const mcpImages = extractMcpToolResultImages(mcpResult.raw);
4494
- if (mcpImages.length > 0) toolResultImages = mcpImages;
4495
- }
4468
+ const mcpImages = extractMcpToolResultImages(mcpResult.raw);
4469
+ if (mcpImages.length > 0) toolResultImages = mcpImages;
4496
4470
  } else {
4497
- const extracted = extractAgentImagesFromActionResult(redacted);
4498
- redacted = extracted.value;
4471
+ const extracted = extractAgentImagesFromActionResult(resultForAgent);
4472
+ resultForAgent = extracted.value;
4499
4473
  imageNotes = extracted.notes;
4500
- if (extracted.images.length > 0 && !demoMode) {
4474
+ if (extracted.images.length > 0) {
4501
4475
  toolResultImages = extracted.images;
4502
4476
  }
4503
4477
  }
@@ -4508,9 +4482,9 @@ export async function runAgentLoop(opts: {
4508
4482
  ];
4509
4483
  }
4510
4484
  let resultStr =
4511
- typeof redacted === "string"
4512
- ? redacted
4513
- : JSON.stringify(redacted, null, 2);
4485
+ typeof resultForAgent === "string"
4486
+ ? resultForAgent
4487
+ : JSON.stringify(resultForAgent, null, 2);
4514
4488
  if (resultStr.length > toolMaxResultChars) {
4515
4489
  const truncated = resultStr.slice(0, toolMaxResultChars);
4516
4490
  resultStr = `${truncated}\n\n...[truncated — full result was ${resultStr.length.toLocaleString()} chars; only first ${toolMaxResultChars.toLocaleString()} shown]`;
@@ -27,7 +27,6 @@ const DEFAULT_SKIP_ACTIONS = new Set<string>([
27
27
  "context-report",
28
28
  "context-manifest-get",
29
29
  "change-appearance",
30
- "toggle-demo-mode",
31
30
  ]);
32
31
 
33
32
  /** Name patterns for high-frequency state-sync actions, skipped by default. */
@@ -886,57 +886,71 @@ export async function updateThreadData(
886
886
  messageCount: number,
887
887
  options: UpdateThreadDataOptions = {},
888
888
  ): Promise<void> {
889
- await ensureTable();
889
+ // getThread() ensures the table exists. Keep that bootstrap inside the
890
+ // retry boundary below so a cold serverless process can recover from a
891
+ // transient initialization/read failure too.
890
892
  const client = getDbExec();
891
- const maxAttempts =
892
- options.maxAttempts ?? DEFAULT_THREAD_DATA_UPDATE_ATTEMPTS;
893
+ const maxAttempts = Math.max(
894
+ 1,
895
+ options.maxAttempts ?? DEFAULT_THREAD_DATA_UPDATE_ATTEMPTS,
896
+ );
893
897
  let lastConflict = false;
898
+ let lastError: unknown = null;
894
899
 
895
900
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
896
- const current = await getThread(id);
897
- if (!current) return;
898
-
899
- let nextThreadData = threadData;
900
- let nextMessageCount = messageCount;
901
901
  try {
902
- const merged = mergeThreadDataForClientSave(
903
- parseThreadData(current.threadData),
904
- parseThreadData(threadData),
905
- {
906
- preserveExistingQueuedMessages:
907
- options.preserveExistingQueuedMessages ?? true,
908
- preserveExistingTopLevelKeys:
909
- options.preserveExistingTopLevelKeys ?? true,
910
- },
911
- );
912
- nextThreadData = JSON.stringify(merged);
913
- if (Array.isArray(merged.messages)) {
914
- nextMessageCount = merged.messages.length;
902
+ const current = await getThread(id);
903
+ if (!current) return;
904
+
905
+ let nextThreadData = threadData;
906
+ let nextMessageCount = messageCount;
907
+ try {
908
+ const merged = mergeThreadDataForClientSave(
909
+ parseThreadData(current.threadData),
910
+ parseThreadData(threadData),
911
+ {
912
+ preserveExistingQueuedMessages:
913
+ options.preserveExistingQueuedMessages ?? true,
914
+ preserveExistingTopLevelKeys:
915
+ options.preserveExistingTopLevelKeys ?? true,
916
+ },
917
+ );
918
+ nextThreadData = JSON.stringify(merged);
919
+ if (Array.isArray(merged.messages)) {
920
+ nextMessageCount = merged.messages.length;
921
+ }
922
+ } catch {
923
+ // Keep the caller's serialized value if either JSON blob is malformed.
915
924
  }
916
- } catch {
917
- // Keep the caller's serialized value if either JSON blob is malformed.
918
- }
919
925
 
920
- const nextUpdatedAt = Math.max(Date.now(), current.updatedAt + 1);
921
- const result = await client.execute({
922
- sql: `UPDATE chat_threads SET thread_data = ?, title = ?, preview = ?, message_count = ?, updated_at = ? WHERE id = ? AND updated_at = ?`,
923
- args: [
924
- nextThreadData,
925
- title,
926
- preview,
927
- nextMessageCount,
928
- nextUpdatedAt,
929
- id,
930
- current.updatedAt,
931
- ],
932
- });
926
+ const nextUpdatedAt = Math.max(Date.now(), current.updatedAt + 1);
927
+ const result = await client.execute({
928
+ sql: `UPDATE chat_threads SET thread_data = ?, title = ?, preview = ?, message_count = ?, updated_at = ? WHERE id = ? AND updated_at = ?`,
929
+ args: [
930
+ nextThreadData,
931
+ title,
932
+ preview,
933
+ nextMessageCount,
934
+ nextUpdatedAt,
935
+ id,
936
+ current.updatedAt,
937
+ ],
938
+ });
939
+
940
+ if (result.rowsAffected > 0) {
941
+ emitChatThreadChange(id);
942
+ return;
943
+ }
933
944
 
934
- if (result.rowsAffected > 0) {
935
- emitChatThreadChange(id);
936
- return;
945
+ lastConflict = true;
946
+ } catch (error) {
947
+ // Completion saves happen after a long model/tool turn, when a
948
+ // transient connection or serverless DB failure is especially costly.
949
+ // Retry the whole read/merge/write attempt like a CAS conflict, while
950
+ // preserving the final error if the database remains unavailable.
951
+ lastError = error;
937
952
  }
938
953
 
939
- lastConflict = true;
940
954
  if (attempt < maxAttempts - 1) {
941
955
  await new Promise((resolve) =>
942
956
  setTimeout(
@@ -947,6 +961,8 @@ export async function updateThreadData(
947
961
  }
948
962
  }
949
963
 
964
+ if (lastError) throw lastError;
965
+
950
966
  if (lastConflict) {
951
967
  if (options.ignoreConflicts) return;
952
968
  throw new Error(
@@ -2307,8 +2307,7 @@ const AssistantChatInner = forwardRef<
2307
2307
  const missingApiKey = agentEngineConfigured.missing;
2308
2308
  const isProviderStatusChecking =
2309
2309
  providerStatusChecksEnabled && agentEngineConfigured.state === "unknown";
2310
- const isComposerDisabled =
2311
- missingApiKey || isProviderStatusChecking || composerDisabled;
2310
+ const isComposerDisabled = missingApiKey || composerDisabled;
2312
2311
  const [missingKeySetupOpen, setMissingKeySetupOpen] = useState(false);
2313
2312
  const requestMissingKeySetup = useCallback(() => {
2314
2313
  setMissingKeySetupOpen(true);
@@ -11,8 +11,10 @@ import {
11
11
  BranchPickerPrimitive,
12
12
  ComposerPrimitive,
13
13
  useMessagePartReasoning,
14
+ useMessagePartRuntime,
14
15
  } from "@assistant-ui/react";
15
16
  import type { Attachment } from "@assistant-ui/react";
17
+ import { useAuiState } from "@assistant-ui/store";
16
18
  import {
17
19
  IconX,
18
20
  IconCheck,
@@ -842,8 +844,18 @@ export function shouldShowAssistantMessageFooter({
842
844
 
843
845
  function ReasoningMessagePart() {
844
846
  const part = useMessagePartReasoning();
845
- const chatRunning = React.useContext(ChatRunningContext);
847
+ const partRuntime = useMessagePartRuntime();
848
+ const messageParts = useAuiState((state) => state.message.parts);
846
849
  const isStreaming = part.status?.type === "running";
850
+ const partIndex =
851
+ partRuntime.path.messagePartSelector.type === "index"
852
+ ? partRuntime.path.messagePartSelector.index
853
+ : -1;
854
+ const latestReasoningPartIndex = messageParts.reduce(
855
+ (latestIndex, messagePart, index) =>
856
+ messagePart.type === "reasoning" ? index : latestIndex,
857
+ -1,
858
+ );
847
859
  // Time thinking client-side: record the moment streaming first starts and
848
860
  // the moment it stops so the cell can show "Thought for Xs". Historical
849
861
  // messages that were never observed streaming in this session never get a
@@ -865,8 +877,8 @@ function ReasoningMessagePart() {
865
877
  text={part.text}
866
878
  isStreaming={isStreaming}
867
879
  durationMs={durationMs}
868
- defaultOpen={isStreaming || !chatRunning}
869
- autoCollapse={chatRunning}
880
+ defaultOpen={partIndex === latestReasoningPartIndex}
881
+ collapseWhenReplaced={partIndex < latestReasoningPartIndex}
870
882
  />
871
883
  );
872
884
  }
@@ -897,6 +897,11 @@ export function ReconnectStreamMessage({
897
897
  content: ContentPart[];
898
898
  }) {
899
899
  const chatRunning = React.useContext(ChatRunningContext);
900
+ const latestReasoningPartIndex = content.reduce(
901
+ (latestIndex, part, index) =>
902
+ part.type === "reasoning" ? index : latestIndex,
903
+ -1,
904
+ );
900
905
  const streamingTextPartIndex =
901
906
  content.at(-1)?.type === "text" ? content.length - 1 : -1;
902
907
  const streamingReasoningPartIndex =
@@ -924,7 +929,8 @@ export function ReconnectStreamMessage({
924
929
  key={`reconnect-reasoning-${i}`}
925
930
  text={part.text}
926
931
  isStreaming={chatRunning && i === streamingReasoningPartIndex}
927
- defaultOpen={chatRunning && i === streamingReasoningPartIndex}
932
+ defaultOpen={i === latestReasoningPartIndex}
933
+ collapseWhenReplaced={i < latestReasoningPartIndex}
928
934
  />
929
935
  );
930
936
  }
@@ -969,6 +975,7 @@ export function ReasoningCell({
969
975
  isStreaming = false,
970
976
  defaultOpen,
971
977
  autoCollapse = false,
978
+ collapseWhenReplaced = false,
972
979
  durationMs,
973
980
  }: {
974
981
  text: string;
@@ -976,6 +983,8 @@ export function ReasoningCell({
976
983
  defaultOpen?: boolean;
977
984
  /** Animate closed when a live reasoning segment finishes during a run. */
978
985
  autoCollapse?: boolean;
986
+ /** Animate closed when a newer reasoning segment replaces this one. */
987
+ collapseWhenReplaced?: boolean;
979
988
  /**
980
989
  * Elapsed thinking time in ms, once known. Only meaningful once streaming
981
990
  * has finished — callers that track live timing (see ReasoningMessagePart)
@@ -987,6 +996,7 @@ export function ReasoningCell({
987
996
  const embeddedInWorkSummary = React.useContext(WorkSummaryContentContext);
988
997
  const [open, setOpen] = useState(defaultOpen ?? true);
989
998
  const wasStreamingRef = useRef(isStreaming);
999
+ const wasReplacedRef = useRef(collapseWhenReplaced);
990
1000
  const trimmed = text.trim();
991
1001
 
992
1002
  useEffect(() => {
@@ -996,6 +1006,13 @@ export function ReasoningCell({
996
1006
  wasStreamingRef.current = isStreaming;
997
1007
  }, [autoCollapse, isStreaming]);
998
1008
 
1009
+ useEffect(() => {
1010
+ if (collapseWhenReplaced && !wasReplacedRef.current) {
1011
+ setOpen(false);
1012
+ }
1013
+ wasReplacedRef.current = collapseWhenReplaced;
1014
+ }, [collapseWhenReplaced]);
1015
+
999
1016
  if (!trimmed && !isStreaming) return null;
1000
1017
 
1001
1018
  if (embeddedInWorkSummary) {
@@ -6,7 +6,7 @@ import {
6
6
  } from "@tabler/icons-react";
7
7
  import { useQuery, useQueryClient } from "@tanstack/react-query";
8
8
  import { useEffect, useMemo, useRef, useState } from "react";
9
- import { useNavigate } from "react-router";
9
+ import { Link } from "react-router";
10
10
 
11
11
  import { extensionPath } from "../../extensions/path.js";
12
12
  import { THEME_VAR_NAMES } from "../../extensions/theme.js";
@@ -482,7 +482,6 @@ function EmbeddedToolMenu({
482
482
  const [open, setOpen] = useState(false);
483
483
  const [confirmingDelete, setConfirmingDelete] = useState(false);
484
484
  const queryClient = useQueryClient();
485
- const navigate = useNavigate();
486
485
 
487
486
  const closeMenu = () => {
488
487
  setOpen(false);
@@ -545,17 +544,14 @@ function EmbeddedToolMenu({
545
544
  <PopoverContent align="end" sideOffset={4} className="w-56 p-1">
546
545
  {!confirmingDelete ? (
547
546
  <div className="flex flex-col">
548
- <button
549
- type="button"
550
- onClick={() => {
551
- closeMenu();
552
- navigate(extensionPath(extensionId, toolName));
553
- }}
547
+ <Link
548
+ to={extensionPath(extensionId, toolName)}
549
+ onClick={closeMenu}
554
550
  className="flex items-center gap-2 rounded-sm px-2 py-1.5 text-[12px] hover:bg-accent cursor-pointer text-left"
555
551
  >
556
552
  <IconExternalLink className="h-3.5 w-3.5" />
557
553
  <span>{t("extensions.openFullView")}</span>
558
- </button>
554
+ </Link>
559
555
  <button
560
556
  type="button"
561
557
  onClick={removeFromSlot}
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  IconArrowBackUp,
3
3
  IconChevronRight,
4
- IconCode,
5
4
  IconDotsVertical,
6
5
  IconHistory,
7
6
  IconLoader2,
@@ -21,7 +20,6 @@ import { SESSION_REPLAY_IFRAME_ATTRIBUTE } from "../../session-replay-iframe-pro
21
20
  import { sendToAgentChat } from "../agent-chat.js";
22
21
  import { AgentToggleButton } from "../AgentPanel.js";
23
22
  import { agentNativePath, appPath } from "../api-path.js";
24
- import { Dialog, DialogContent, DialogTitle } from "../components/ui/dialog.js";
25
23
  import {
26
24
  Popover,
27
25
  PopoverContent,
@@ -366,131 +364,6 @@ function applyCanonicalLink(path: string): () => void {
366
364
  };
367
365
  }
368
366
 
369
- function SourceCodeDialog({
370
- extension,
371
- onSaved,
372
- }: {
373
- extension: Extension;
374
- onSaved?: () => void;
375
- }) {
376
- const queryClient = useQueryClient();
377
- const [open, setOpen] = useState(false);
378
- const [code, setCode] = useState(extension.content ?? "");
379
- const [saving, setSaving] = useState(false);
380
- const [error, setError] = useState<string | null>(null);
381
-
382
- // Sync in when the dialog opens, or when the viewed extension changes
383
- // while the dialog stays mounted (e.g. re-parented to a different id).
384
- useEffect(() => {
385
- if (open) setCode(extension.content ?? "");
386
- }, [open, extension.id]);
387
-
388
- const isDirty = code !== (extension.content ?? "");
389
-
390
- // Block Escape / outside-click from closing while there are unsaved edits.
391
- const handleOpenChange = (next: boolean) => {
392
- if (!next && isDirty) return;
393
- setOpen(next);
394
- if (!next) setError(null);
395
- };
396
-
397
- const handleCancel = () => {
398
- setCode(extension.content ?? "");
399
- setOpen(false);
400
- setError(null);
401
- };
402
-
403
- const handleSave = async () => {
404
- setSaving(true);
405
- setError(null);
406
- try {
407
- const res = await fetch(
408
- agentNativePath(`/_agent-native/extensions/${extension.id}`),
409
- {
410
- method: "PUT",
411
- headers: { "Content-Type": "application/json" },
412
- body: JSON.stringify({ content: code }),
413
- },
414
- );
415
- if (!res.ok) {
416
- const body = await res.json().catch(() => ({}));
417
- throw new Error(body?.error ?? `Save failed (${res.status})`);
418
- }
419
- setOpen(false);
420
- queryClient.setQueryData<Extension>(["extension", extension.id], (old) =>
421
- old ? { ...old, content: code } : old,
422
- );
423
- queryClient.invalidateQueries({
424
- queryKey: ["extension", extension.id],
425
- });
426
- queryClient.invalidateQueries({ queryKey: ["extensions"] });
427
- onSaved?.();
428
- } catch (err: any) {
429
- setError(err?.message ?? "Save failed");
430
- } finally {
431
- setSaving(false);
432
- }
433
- };
434
-
435
- return (
436
- <>
437
- <Tooltip>
438
- <TooltipTrigger asChild>
439
- <button
440
- type="button"
441
- onClick={() => setOpen(true)}
442
- className="inline-flex items-center justify-center rounded-md h-8 w-8 text-muted-foreground hover:bg-accent hover:text-accent-foreground cursor-pointer"
443
- >
444
- <IconCode className="h-4 w-4" />
445
- </button>
446
- </TooltipTrigger>
447
- <TooltipContent>View / edit source</TooltipContent>
448
- </Tooltip>
449
- <Dialog open={open} onOpenChange={handleOpenChange}>
450
- <DialogContent className="flex h-[85vh] w-[90vw] max-w-[900px] flex-col gap-0 overflow-hidden p-0">
451
- <div className="flex shrink-0 items-center border-b border-border px-5 py-3 pr-12">
452
- <DialogTitle className="truncate text-sm font-medium">
453
- {extension.name} — source
454
- </DialogTitle>
455
- </div>
456
- <textarea
457
- className="flex-1 resize-none bg-muted/40 px-5 py-4 font-mono text-xs leading-relaxed text-foreground focus:outline-none"
458
- value={code}
459
- onChange={(e) => setCode(e.target.value)}
460
- spellCheck={false}
461
- />
462
- <div className="flex shrink-0 items-center justify-between border-t border-border px-5 py-3">
463
- {error ? (
464
- <p className="text-xs text-destructive">{error}</p>
465
- ) : (
466
- <span className="text-xs text-muted-foreground">
467
- Alpine.js / HTML &middot; {code.length.toLocaleString()} chars
468
- </span>
469
- )}
470
- <div className="flex items-center gap-2">
471
- <button
472
- type="button"
473
- onClick={handleCancel}
474
- className="inline-flex h-8 cursor-pointer items-center rounded-md border border-input px-3 text-xs hover:bg-accent"
475
- >
476
- Cancel
477
- </button>
478
- <button
479
- type="button"
480
- onClick={handleSave}
481
- disabled={saving}
482
- className="inline-flex h-8 cursor-pointer items-center rounded-md bg-primary px-4 text-xs font-medium text-primary-foreground hover:opacity-90 disabled:opacity-50"
483
- >
484
- {saving ? "Saving…" : "Save"}
485
- </button>
486
- </div>
487
- </div>
488
- </DialogContent>
489
- </Dialog>
490
- </>
491
- );
492
- }
493
-
494
367
  function EditToolPopover({
495
368
  extension,
496
369
  onOpenChange,
@@ -1342,12 +1215,6 @@ export function ExtensionViewer({ extensionId }: ExtensionViewerProps) {
1342
1215
  onRestored={() => setRefreshKey((k) => k + 1)}
1343
1216
  onOpenChange={onPopoverOpenChange}
1344
1217
  />
1345
- {extension.canEdit && (
1346
- <SourceCodeDialog
1347
- extension={extension}
1348
- onSaved={() => setRefreshKey((k) => k + 1)}
1349
- />
1350
- )}
1351
1218
  <EditToolPopover
1352
1219
  extension={extension}
1353
1220
  onOpenChange={onPopoverOpenChange}
@@ -388,6 +388,13 @@ export {
388
388
  type BuildAgentNativeExtensionHtmlOptions,
389
389
  type CreateHttpAgentNativeExtensionStorageOptions,
390
390
  } from "./extensions/portable-extension.js";
391
+ export { injectSessionReplayIframeBootstrap } from "../extensions/session-replay-iframe.js";
392
+ export {
393
+ SESSION_REPLAY_IFRAME_ATTRIBUTE,
394
+ SESSION_REPLAY_IFRAME_PROBE,
395
+ SESSION_REPLAY_IFRAME_START,
396
+ SESSION_REPLAY_IFRAME_STOP,
397
+ } from "../session-replay-iframe-protocol.js";
391
398
  export {
392
399
  AGENT_NATIVE_HOST_BRIDGE_VERSION,
393
400
  AGENT_NATIVE_HOST_MESSAGE_TYPES,