@agent-native/core 0.119.2 → 0.119.4

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 (228) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +19 -0
  3. package/corpus/core/package.json +3 -3
  4. package/corpus/core/src/agent/production-agent.ts +107 -125
  5. package/corpus/core/src/agent/run-manager.ts +45 -2
  6. package/corpus/core/src/agent/thread-data-builder.ts +85 -8
  7. package/corpus/core/src/cli/create.ts +44 -5
  8. package/corpus/core/src/client/AssistantChat.tsx +37 -9
  9. package/corpus/core/src/client/chat/message-components.tsx +9 -1
  10. package/corpus/core/src/client/chat/repo-helpers.ts +77 -0
  11. package/corpus/core/src/client/chat/tool-call-display.tsx +51 -15
  12. package/corpus/core/src/server/agent-chat/framework-prompts.ts +3 -1
  13. package/corpus/core/src/server/agent-chat/plugin-options.ts +9 -0
  14. package/corpus/core/src/server/agent-chat-plugin.ts +12 -3
  15. package/corpus/core/src/templates/chat/.agents/skills/actions/SKILL.md +512 -0
  16. package/corpus/core/src/templates/chat/.agents/skills/adding-a-feature/SKILL.md +189 -0
  17. package/corpus/core/src/templates/chat/.agents/skills/agent-native-docs/SKILL.md +115 -0
  18. package/corpus/core/src/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +177 -0
  19. package/corpus/core/src/templates/chat/.agents/skills/capture-learnings/SKILL.md +89 -0
  20. package/corpus/core/src/templates/chat/.agents/skills/create-skill/SKILL.md +221 -0
  21. package/corpus/core/src/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +220 -0
  22. package/corpus/core/src/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +263 -0
  23. package/corpus/core/src/templates/chat/.agents/skills/feature-flags/SKILL.md +169 -0
  24. package/corpus/core/src/templates/chat/.agents/skills/frontend-design/SKILL.md +160 -0
  25. package/corpus/core/src/templates/chat/.agents/skills/real-time-sync/SKILL.md +232 -0
  26. package/corpus/core/src/templates/chat/.agents/skills/security/SKILL.md +280 -0
  27. package/corpus/core/src/templates/chat/.agents/skills/self-modifying-code/SKILL.md +118 -0
  28. package/corpus/core/src/templates/chat/.agents/skills/shadcn-ui/SKILL.md +119 -0
  29. package/corpus/core/src/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +100 -0
  30. package/corpus/core/src/templates/chat/.env.example +5 -0
  31. package/corpus/core/src/templates/chat/.ignore +0 -0
  32. package/corpus/core/src/templates/chat/.oxfmtrc.json +8 -0
  33. package/corpus/core/src/templates/chat/AGENTS.md +69 -0
  34. package/corpus/core/src/templates/chat/CHANGELOG.md +10 -0
  35. package/corpus/core/src/templates/chat/DEVELOPING.md +185 -0
  36. package/corpus/core/src/templates/chat/README.md +32 -0
  37. package/corpus/core/src/templates/chat/_gitignore +43 -0
  38. package/corpus/core/src/templates/chat/actions/hello.ts +13 -0
  39. package/corpus/core/src/templates/chat/actions/navigate.ts +41 -0
  40. package/corpus/core/src/templates/chat/actions/run.ts +2 -0
  41. package/corpus/core/src/templates/chat/actions/view-screen.ts +31 -0
  42. package/corpus/core/src/templates/chat/app/components/layout/Header.tsx +60 -0
  43. package/corpus/core/src/templates/chat/app/components/layout/Layout.tsx +185 -0
  44. package/corpus/core/src/templates/chat/app/components/layout/Sidebar.tsx +450 -0
  45. package/corpus/core/src/templates/chat/app/components/ui/button.tsx +1 -0
  46. package/corpus/core/src/templates/chat/app/components/ui/card.tsx +1 -0
  47. package/corpus/core/src/templates/chat/app/components/ui/dropdown-menu.tsx +1 -0
  48. package/corpus/core/src/templates/chat/app/components/ui/input.tsx +1 -0
  49. package/corpus/core/src/templates/chat/app/components/ui/label.tsx +1 -0
  50. package/corpus/core/src/templates/chat/app/components/ui/sheet.tsx +1 -0
  51. package/corpus/core/src/templates/chat/app/components/ui/toolkit-provider.tsx +17 -0
  52. package/corpus/core/src/templates/chat/app/components/ui/tooltip.tsx +1 -0
  53. package/corpus/core/src/templates/chat/app/design-system.ts +3 -0
  54. package/corpus/core/src/templates/chat/app/entry.client.tsx +19 -0
  55. package/corpus/core/src/templates/chat/app/entry.server.tsx +10 -0
  56. package/corpus/core/src/templates/chat/app/global.css +93 -0
  57. package/corpus/core/src/templates/chat/app/hooks/use-navigation-state.ts +93 -0
  58. package/corpus/core/src/templates/chat/app/i18n/ar-SA.ts +68 -0
  59. package/corpus/core/src/templates/chat/app/i18n/de-DE.ts +71 -0
  60. package/corpus/core/src/templates/chat/app/i18n/en-US.ts +70 -0
  61. package/corpus/core/src/templates/chat/app/i18n/es-ES.ts +71 -0
  62. package/corpus/core/src/templates/chat/app/i18n/fr-FR.ts +71 -0
  63. package/corpus/core/src/templates/chat/app/i18n/hi-IN.ts +69 -0
  64. package/corpus/core/src/templates/chat/app/i18n/index.ts +34 -0
  65. package/corpus/core/src/templates/chat/app/i18n/ja-JP.ts +70 -0
  66. package/corpus/core/src/templates/chat/app/i18n/ko-KR.ts +70 -0
  67. package/corpus/core/src/templates/chat/app/i18n/pt-BR.ts +71 -0
  68. package/corpus/core/src/templates/chat/app/i18n/zh-CN.ts +66 -0
  69. package/corpus/core/src/templates/chat/app/i18n/zh-TW.ts +66 -0
  70. package/corpus/core/src/templates/chat/app/i18n-data.ts +556 -0
  71. package/corpus/core/src/templates/chat/app/lib/agent-page.tsx +49 -0
  72. package/corpus/core/src/templates/chat/app/lib/app-config.ts +11 -0
  73. package/corpus/core/src/templates/chat/app/lib/tab-id.ts +1 -0
  74. package/corpus/core/src/templates/chat/app/lib/utils.ts +1 -0
  75. package/corpus/core/src/templates/chat/app/root.tsx +173 -0
  76. package/corpus/core/src/templates/chat/app/routes/_index.tsx +94 -0
  77. package/corpus/core/src/templates/chat/app/routes/agent.tsx +24 -0
  78. package/corpus/core/src/templates/chat/app/routes/chat.$threadId.tsx +1 -0
  79. package/corpus/core/src/templates/chat/app/routes/database.tsx +17 -0
  80. package/corpus/core/src/templates/chat/app/routes/extensions.$id.$slug.tsx +2 -0
  81. package/corpus/core/src/templates/chat/app/routes/extensions.$id.tsx +11 -0
  82. package/corpus/core/src/templates/chat/app/routes/extensions._index.tsx +11 -0
  83. package/corpus/core/src/templates/chat/app/routes/extensions.tsx +5 -0
  84. package/corpus/core/src/templates/chat/app/routes/observability.tsx +19 -0
  85. package/corpus/core/src/templates/chat/app/routes/settings.tsx +89 -0
  86. package/corpus/core/src/templates/chat/app/routes/team.tsx +11 -0
  87. package/corpus/core/src/templates/chat/app/routes.ts +4 -0
  88. package/corpus/core/src/templates/chat/app/vite-env.d.ts +6 -0
  89. package/corpus/core/src/templates/chat/changelog/2026-06-24-a-new-settings-page-gives-quick-access-to-language-workspace.md +6 -0
  90. package/corpus/core/src/templates/chat/changelog/2026-06-24-added-a-language-picker-and-localized-app-chrome-for-support.md +6 -0
  91. package/corpus/core/src/templates/chat/changelog/2026-06-27-traditional-chinese-copy-uses-taiwan-terminology.md +6 -0
  92. package/corpus/core/src/templates/chat/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  93. package/corpus/core/src/templates/chat/changelog/2026-06-29-chat-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  94. package/corpus/core/src/templates/chat/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  95. package/corpus/core/src/templates/chat/changelog/2026-07-10-chat-now-makes-ai-connection-setup-clear-without-shifting-th.md +6 -0
  96. package/corpus/core/src/templates/chat/changelog/2026-07-13-a-full-agent-page-now-brings-context-files-connections-jobs-.md +6 -0
  97. package/corpus/core/src/templates/chat/changelog/2026-07-14-chat-opens-reliably-on-hosted-deployments-instead-of-failing.md +6 -0
  98. package/corpus/core/src/templates/chat/changelog/2026-07-14-fixed-chat-template-startup-with-older-core-versions.md +6 -0
  99. package/corpus/core/src/templates/chat/changelog/2026-07-15-chat-navigation-focuses-on-chat-and-agent.md +6 -0
  100. package/corpus/core/src/templates/chat/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  101. package/corpus/core/src/templates/chat/changelog/2026-07-22-compact-sidebar-footer.md +6 -0
  102. package/corpus/core/src/templates/chat/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
  103. package/corpus/core/src/templates/chat/changelog/2026-07-22-manage-agent-navigation-now-uses-the-connected-nodes-icon.md +6 -0
  104. package/corpus/core/src/templates/chat/changelog/2026-07-22-recent-chats-are-easier-to-scan-and-expand.md +6 -0
  105. package/corpus/core/src/templates/chat/changelog/2026-07-23-full-page-chat-is-better-centered-with-quieter-chat-history-.md +6 -0
  106. package/corpus/core/src/templates/chat/components.json +20 -0
  107. package/corpus/core/src/templates/chat/learnings.defaults.md +5 -0
  108. package/corpus/core/src/templates/chat/netlify.toml +10 -0
  109. package/corpus/core/src/templates/chat/package.json +92 -0
  110. package/corpus/core/src/templates/chat/public/agent-native-icon-dark.svg +10 -0
  111. package/corpus/core/src/templates/chat/public/agent-native-icon-light.svg +10 -0
  112. package/corpus/core/src/templates/chat/public/agent-native-logo-dark.svg +21 -0
  113. package/corpus/core/src/templates/chat/public/agent-native-logo-light.svg +21 -0
  114. package/corpus/core/src/templates/chat/public/favicon.svg +1 -0
  115. package/corpus/core/src/templates/chat/public/icon-180.svg +1 -0
  116. package/corpus/core/src/templates/chat/public/icon-192.svg +1 -0
  117. package/corpus/core/src/templates/chat/public/icon-512.svg +1 -0
  118. package/corpus/core/src/templates/chat/public/manifest.json +21 -0
  119. package/corpus/core/src/templates/chat/react-router.config.ts +7 -0
  120. package/corpus/core/src/templates/chat/server/middleware/auth.ts +15 -0
  121. package/corpus/core/src/templates/chat/server/plugins/agent-chat.ts +21 -0
  122. package/corpus/core/src/templates/chat/server/plugins/auth.ts +17 -0
  123. package/corpus/core/src/templates/chat/server/routes/[...page].get.ts +5 -0
  124. package/corpus/core/src/templates/chat/ssr-entry.ts +15 -0
  125. package/corpus/core/src/templates/chat/tsconfig.json +21 -0
  126. package/corpus/core/src/templates/chat/vite.config.ts +19 -0
  127. package/corpus/templates/analytics/AGENTS.md +19 -11
  128. package/corpus/templates/analytics/actions/list-data-dictionary.ts +3 -1
  129. package/corpus/templates/analytics/actions/save-data-dictionary-entry.ts +14 -0
  130. package/corpus/templates/analytics/actions/search-analytics-query-catalog.ts +41 -0
  131. package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +13 -0
  132. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +12 -11
  133. package/corpus/templates/analytics/app/pages/adhoc/DashboardSkeleton.tsx +3 -3
  134. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +7 -1
  135. package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +8 -1
  136. package/corpus/templates/analytics/changelog/2026-07-22-fixed-dashboards-and-analyses-missing-a-copyable-share-link.md +6 -0
  137. package/corpus/templates/analytics/changelog/2026-07-23-dashboard-chart-loading-placeholders-are-easier-to-see-in-da.md +6 -0
  138. package/corpus/templates/analytics/changelog/2026-07-23-routine-analytics-lookups-reuse-existing-query-definitions.md +6 -0
  139. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +1 -12
  140. package/corpus/templates/analytics/server/lib/analytics-query-catalog.ts +405 -0
  141. package/corpus/templates/analytics/server/plugins/agent-chat.ts +11 -16
  142. package/corpus/templates/content/changelog/2026-07-23-database-toolbar-sort-and-filter-controls-open-reliably.md +6 -0
  143. package/corpus/templates/forms/app/lib/public-form-link.ts +14 -0
  144. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +6 -14
  145. package/corpus/templates/forms/changelog/2026-07-23-published-form-links-can-be-copied-without-being-asked-to-re.md +6 -0
  146. package/corpus/templates/mail/changelog/2026-07-23-scheduled-emails-now-send-only-once-when-multiple-requests-r.md +6 -0
  147. package/corpus/templates/mail/server/lib/jobs.ts +13 -4
  148. package/corpus/templates/mail/server/plugins/mail-jobs.ts +1 -1
  149. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +7 -18
  150. package/corpus/templates/slides/app/lib/agent-visible-message.ts +28 -0
  151. package/corpus/templates/slides/app/pages/Index.tsx +6 -34
  152. package/corpus/templates/slides/changelog/2026-07-23-full-prompts-stay-visible-in-chat-when-creating-decks-or-add.md +6 -0
  153. package/corpus/toolkit/CHANGELOG.md +13 -0
  154. package/corpus/toolkit/package.json +1 -1
  155. package/corpus/toolkit/src/editor/SharedRichEditor.tsx +23 -2
  156. package/corpus/toolkit/src/editor/SlashCommandMenu.tsx +68 -2
  157. package/corpus/toolkit/src/editor/index.ts +1 -0
  158. package/corpus/toolkit/src/editor.css +75 -0
  159. package/corpus/toolkit/src/ui/button.tsx +3 -2
  160. package/dist/agent/production-agent.d.ts.map +1 -1
  161. package/dist/agent/production-agent.js +96 -115
  162. package/dist/agent/production-agent.js.map +1 -1
  163. package/dist/agent/run-manager.d.ts +9 -1
  164. package/dist/agent/run-manager.d.ts.map +1 -1
  165. package/dist/agent/run-manager.js +26 -0
  166. package/dist/agent/run-manager.js.map +1 -1
  167. package/dist/agent/thread-data-builder.d.ts +2 -0
  168. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  169. package/dist/agent/thread-data-builder.js +69 -9
  170. package/dist/agent/thread-data-builder.js.map +1 -1
  171. package/dist/cli/create.d.ts +2 -1
  172. package/dist/cli/create.d.ts.map +1 -1
  173. package/dist/cli/create.js +43 -6
  174. package/dist/cli/create.js.map +1 -1
  175. package/dist/client/AssistantChat.d.ts.map +1 -1
  176. package/dist/client/AssistantChat.js +32 -10
  177. package/dist/client/AssistantChat.js.map +1 -1
  178. package/dist/client/chat/message-components.d.ts +2 -1
  179. package/dist/client/chat/message-components.d.ts.map +1 -1
  180. package/dist/client/chat/message-components.js +9 -3
  181. package/dist/client/chat/message-components.js.map +1 -1
  182. package/dist/client/chat/repo-helpers.d.ts +4 -0
  183. package/dist/client/chat/repo-helpers.d.ts.map +1 -1
  184. package/dist/client/chat/repo-helpers.js +61 -0
  185. package/dist/client/chat/repo-helpers.js.map +1 -1
  186. package/dist/client/chat/tool-call-display.d.ts +2 -1
  187. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  188. package/dist/client/chat/tool-call-display.js +10 -9
  189. package/dist/client/chat/tool-call-display.js.map +1 -1
  190. package/dist/collab/awareness.d.ts +2 -2
  191. package/dist/collab/awareness.d.ts.map +1 -1
  192. package/dist/collab/routes.d.ts +1 -1
  193. package/dist/collab/struct-routes.d.ts +1 -1
  194. package/dist/notifications/routes.d.ts +2 -2
  195. package/dist/observability/routes.d.ts +3 -3
  196. package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
  197. package/dist/provider-api/actions/provider-api.d.ts +7 -7
  198. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  199. package/dist/resources/handlers.d.ts +1 -1
  200. package/dist/secrets/routes.d.ts +9 -9
  201. package/dist/server/agent-chat/framework-prompts.d.ts.map +1 -1
  202. package/dist/server/agent-chat/framework-prompts.js +3 -1
  203. package/dist/server/agent-chat/framework-prompts.js.map +1 -1
  204. package/dist/server/agent-chat/plugin-options.d.ts +9 -0
  205. package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
  206. package/dist/server/agent-chat/plugin-options.js.map +1 -1
  207. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  208. package/dist/server/agent-chat-plugin.js +11 -2
  209. package/dist/server/agent-chat-plugin.js.map +1 -1
  210. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  211. package/dist/server/realtime-token.d.ts +1 -1
  212. package/dist/server/transcribe-voice.d.ts +1 -1
  213. package/package.json +4 -4
  214. package/src/agent/production-agent.ts +107 -125
  215. package/src/agent/run-manager.ts +45 -2
  216. package/src/agent/thread-data-builder.ts +85 -8
  217. package/src/cli/create.ts +44 -5
  218. package/src/client/AssistantChat.tsx +37 -9
  219. package/src/client/chat/message-components.tsx +9 -1
  220. package/src/client/chat/repo-helpers.ts +77 -0
  221. package/src/client/chat/tool-call-display.tsx +51 -15
  222. package/src/server/agent-chat/framework-prompts.ts +3 -1
  223. package/src/server/agent-chat/plugin-options.ts +9 -0
  224. package/src/server/agent-chat-plugin.ts +12 -3
  225. package/src/templates/chat/actions/hello.spec.ts +0 -23
  226. package/src/templates/chat/actions/navigate.spec.ts +0 -68
  227. package/src/templates/chat/actions/view-screen.spec.ts +0 -40
  228. package/src/templates/chat/app/lib/agent-page.spec.tsx +0 -61
package/src/cli/create.ts CHANGED
@@ -1384,6 +1384,7 @@ export {
1384
1384
  getCoreDependencyVersion as _getCoreDependencyVersion,
1385
1385
  getDispatchDependencyVersion as _getDispatchDependencyVersion,
1386
1386
  getToolkitDependencyVersion as _getToolkitDependencyVersion,
1387
+ getCorePackageVersion as _getCorePackageVersion,
1387
1388
  getGitHubTemplateRef as _getGitHubTemplateRef,
1388
1389
  getGitHubTemplateRefCandidates as _getGitHubTemplateRefCandidates,
1389
1390
  githubTarballUrl as _githubTarballUrl,
@@ -1753,11 +1754,22 @@ function getCoreDependencyVersion(): string {
1753
1754
  if (localCore) return localPackageTarball(localCore);
1754
1755
  }
1755
1756
 
1756
- // Generated apps must install before the current package version is
1757
- // published. The dist-tag resolves to the newest released core today and to
1758
- // this package version once the release goes live. Local file deps are
1759
- // intentionally opt-in so scaffolded repos remain portable by default.
1760
- return "latest";
1757
+ // Pin to the exact core version running this CLI rather than the npm
1758
+ // `latest` dist-tag. `latest` can drift forward after `create` runs (a
1759
+ // stale/cached CLI invocation, or simply time passing before `npm
1760
+ // install`), installing a newer core release whose internal toolkit
1761
+ // dependency no longer matches the toolkit range this CLI just wrote into
1762
+ // the scaffold via getOwnPackageDependencyVersion() — reintroducing the
1763
+ // exact duplicate/mismatched-toolkit class of bug this pinning exists to
1764
+ // prevent. For the common case — `npx @agent-native/core@<version> create`
1765
+ // against the public registry — this exact version is guaranteed
1766
+ // installable, since npx just fetched it. Private/offline mirrors with a
1767
+ // retention window narrower than "every historical version" are a known
1768
+ // gap; `getCorePackageVersion()` returning undefined (e.g. malformed own
1769
+ // package.json) falls back to `latest` rather than failing scaffolding
1770
+ // outright. Local file deps stay opt-in so scaffolded repos remain
1771
+ // portable by default.
1772
+ return getCorePackageVersion() ?? "latest";
1761
1773
  }
1762
1774
 
1763
1775
  function getDispatchDependencyVersion(): string {
@@ -1766,6 +1778,9 @@ function getDispatchDependencyVersion(): string {
1766
1778
  if (localDispatch) return pathToFileURL(localDispatch).href;
1767
1779
  }
1768
1780
 
1781
+ // Unlike toolkit, core's own package.json does not declare
1782
+ // @agent-native/dispatch as a dependency, so there is no published
1783
+ // compatible range to read here — "latest" is the best available signal.
1769
1784
  return "latest";
1770
1785
  }
1771
1786
 
@@ -1775,6 +1790,30 @@ function getToolkitDependencyVersion(): string {
1775
1790
  if (localToolkit) return localPackageTarball(localToolkit);
1776
1791
  }
1777
1792
 
1793
+ return getOwnPackageDependencyVersion("@agent-native/toolkit");
1794
+ }
1795
+
1796
+ /**
1797
+ * Toolkit is versioned and published independently of core, so its npm
1798
+ * `latest` dist-tag can briefly point to an incompatible release relative to
1799
+ * the core version currently running this CLI. The published core
1800
+ * `package.json` already carries the exact compatible range changesets
1801
+ * resolved at release time — read it from there instead of trusting
1802
+ * `latest`, which is only safe for pinning `core` itself.
1803
+ */
1804
+ function getOwnPackageDependencyVersion(depName: string): string {
1805
+ try {
1806
+ const ownPkgPath = path.join(__dirname, "../../package.json");
1807
+ const ownPkg = JSON.parse(fs.readFileSync(ownPkgPath, "utf-8"));
1808
+ const range = ownPkg.dependencies?.[depName];
1809
+ const isPublishedRange =
1810
+ typeof range === "string" &&
1811
+ range.length > 0 &&
1812
+ !range.startsWith("workspace:") &&
1813
+ range !== "catalog:";
1814
+ if (isPublishedRange) return range;
1815
+ } catch {}
1816
+
1778
1817
  return "latest";
1779
1818
  }
1780
1819
 
@@ -110,6 +110,7 @@ import {
110
110
  shouldImportServerThreadData,
111
111
  dedupeRepoMessagesById,
112
112
  dropEmptyAssistantMessages,
113
+ withLastAssistantRunDuration,
113
114
  } from "./chat/repo-helpers.js";
114
115
  import {
115
116
  BuilderSetupContent,
@@ -2842,6 +2843,26 @@ const AssistantChatInner = forwardRef<
2842
2843
  ensureMessageMetadata(normalizeThreadRepository(threadRuntime.export())),
2843
2844
  [threadRuntime],
2844
2845
  );
2846
+ const exportPersistableThreadRepo = useCallback(
2847
+ () =>
2848
+ withLastAssistantRunDuration(
2849
+ exportCleanThreadRepo(),
2850
+ showRunningInUI ? null : lastChatRunDurationMs,
2851
+ ),
2852
+ [exportCleanThreadRepo, lastChatRunDurationMs, showRunningInUI],
2853
+ );
2854
+ useEffect(() => {
2855
+ if (showRunningInUI || lastChatRunDurationMs == null) return;
2856
+ const repo = exportCleanThreadRepo();
2857
+ const persisted = withLastAssistantRunDuration(repo, lastChatRunDurationMs);
2858
+ if (persisted === repo) return;
2859
+ threadRuntime.import(ensureMessageMetadata(persisted));
2860
+ }, [
2861
+ exportCleanThreadRepo,
2862
+ lastChatRunDurationMs,
2863
+ showRunningInUI,
2864
+ threadRuntime,
2865
+ ]);
2845
2866
 
2846
2867
  const appendRealtimeVoiceTranscript = useCallback(
2847
2868
  (
@@ -2896,7 +2917,7 @@ const AssistantChatInner = forwardRef<
2896
2917
 
2897
2918
  const cacheCurrentThreadSnapshot = useCallback(() => {
2898
2919
  if (!threadId || messages.length === 0) return;
2899
- const repo = exportCleanThreadRepo();
2920
+ const repo = exportPersistableThreadRepo();
2900
2921
  const threadData = JSON.stringify(stripBase64FromRepo(repo));
2901
2922
  const { title, preview } = extractThreadMeta(repo);
2902
2923
  writeCachedThreadSnapshot(apiUrl, threadId, {
@@ -2905,7 +2926,7 @@ const AssistantChatInner = forwardRef<
2905
2926
  preview,
2906
2927
  messageCount: messages.length,
2907
2928
  });
2908
- }, [apiUrl, exportCleanThreadRepo, messages.length, threadId]);
2929
+ }, [apiUrl, exportPersistableThreadRepo, messages.length, threadId]);
2909
2930
 
2910
2931
  useBrowserLayoutEffect(() => {
2911
2932
  if (hasImportedInitialCachedSnapshotRef.current) return;
@@ -3669,7 +3690,7 @@ const AssistantChatInner = forwardRef<
3669
3690
  const timeSinceLastSave = now - lastSaveTimeRef.current;
3670
3691
  if (timeSinceLastSave < 5000) return;
3671
3692
 
3672
- const repo = exportCleanThreadRepo();
3693
+ const repo = exportPersistableThreadRepo();
3673
3694
  const { title, preview } = extractThreadMeta(repo);
3674
3695
  const threadData = JSON.stringify(stripBase64FromRepo(repo));
3675
3696
  const snapshot = {
@@ -3683,7 +3704,7 @@ const AssistantChatInner = forwardRef<
3683
3704
  savedTitleRef.current = title;
3684
3705
  writeCachedThreadSnapshot(apiUrl, threadId, snapshot);
3685
3706
  onSaveThreadRef.current(threadId, snapshot);
3686
- }, [apiUrl, exportCleanThreadRepo, messages, isRunning, threadId]);
3707
+ }, [apiUrl, exportPersistableThreadRepo, messages, isRunning, threadId]);
3687
3708
 
3688
3709
  // Persist full thread data after each completed response
3689
3710
  useEffect(() => {
@@ -3691,7 +3712,7 @@ const AssistantChatInner = forwardRef<
3691
3712
  if (isRunning) return;
3692
3713
  if (messages.length === 0) return;
3693
3714
 
3694
- const repo = exportCleanThreadRepo();
3715
+ const repo = exportPersistableThreadRepo();
3695
3716
 
3696
3717
  if (threadId && onSaveThreadRef.current) {
3697
3718
  // Save to server via the hook callback
@@ -3713,7 +3734,14 @@ const AssistantChatInner = forwardRef<
3713
3734
  sessionStorage.setItem(storageKey, JSON.stringify(repo));
3714
3735
  } catch {}
3715
3736
  }
3716
- }, [apiUrl, exportCleanThreadRepo, messages, isRunning, threadId, tabId]);
3737
+ }, [
3738
+ apiUrl,
3739
+ exportPersistableThreadRepo,
3740
+ messages,
3741
+ isRunning,
3742
+ threadId,
3743
+ tabId,
3744
+ ]);
3717
3745
 
3718
3746
  useEffect(() => {
3719
3747
  onMessageCountChange?.(messages.length);
@@ -4368,8 +4396,8 @@ const AssistantChatInner = forwardRef<
4368
4396
  }
4369
4397
  reconnectTailOnlyRef.current = false;
4370
4398
  }
4371
- threadRuntime.cancelRun();
4372
4399
  settleVisibleInterruptedTools();
4400
+ threadRuntime.cancelRun();
4373
4401
  if (typeof window !== "undefined") {
4374
4402
  window.dispatchEvent(
4375
4403
  new CustomEvent("agentNative.chatRunning", {
@@ -4810,7 +4838,7 @@ const AssistantChatInner = forwardRef<
4810
4838
  },
4811
4839
  exportThreadSnapshot() {
4812
4840
  if (messages.length === 0) return null;
4813
- const repo = exportCleanThreadRepo();
4841
+ const repo = exportPersistableThreadRepo();
4814
4842
  const { title, preview } = extractThreadMeta(repo);
4815
4843
  return {
4816
4844
  threadData: JSON.stringify(repo),
@@ -4822,7 +4850,7 @@ const AssistantChatInner = forwardRef<
4822
4850
  }),
4823
4851
  [
4824
4852
  addToQueue,
4825
- exportCleanThreadRepo,
4853
+ exportPersistableThreadRepo,
4826
4854
  isRunning,
4827
4855
  messages.length,
4828
4856
  stageComposerContextItem,
@@ -73,6 +73,7 @@ import {
73
73
  MarkdownText,
74
74
  renderMarkdownToClipboardHtml,
75
75
  } from "./markdown-renderer.js";
76
+ import { getAssistantRunDurationMs } from "./repo-helpers.js";
76
77
  import {
77
78
  ToolCallFallback,
78
79
  ToolActivityPresentation,
@@ -989,17 +990,22 @@ export function shouldShowAssistantMessageFooter({
989
990
  }
990
991
 
991
992
  export function shouldShowMissingFinalResponse({
993
+ isCurrentTurnRunning,
992
994
  statusIsTerminal,
993
995
  hasAssistantText,
994
996
  hasUnresolvedTool,
995
997
  hasCompletedCustomUi,
996
998
  }: {
999
+ isCurrentTurnRunning: boolean;
997
1000
  statusIsTerminal: boolean;
998
1001
  hasAssistantText: boolean;
999
1002
  hasUnresolvedTool: boolean;
1000
1003
  hasCompletedCustomUi?: boolean;
1001
1004
  }): boolean {
1005
+ // A completed tool can make the latest message look terminal before the
1006
+ // active turn attaches its follow-up text.
1002
1007
  return (
1008
+ !isCurrentTurnRunning &&
1003
1009
  statusIsTerminal &&
1004
1010
  !hasAssistantText &&
1005
1011
  !hasUnresolvedTool &&
@@ -1202,6 +1208,7 @@ export function AssistantMessage() {
1202
1208
  const chatRunning = React.useContext(ChatRunningContext);
1203
1209
  const lastRunDurationMs = React.useContext(ChatRunDurationContext);
1204
1210
  const msg = messageRuntime.getState();
1211
+ const persistedDurationMs = getAssistantRunDurationMs(msg);
1205
1212
  const timestamp = formatMessageTimestamp(msg.createdAt);
1206
1213
  const isLast =
1207
1214
  thread.messages.length > 0 &&
@@ -1215,6 +1222,7 @@ export function AssistantMessage() {
1215
1222
  );
1216
1223
  const hasCustomUi = assistantMessageHasCustomUi(msg.content);
1217
1224
  const showMissingFinalResponse = shouldShowMissingFinalResponse({
1225
+ isCurrentTurnRunning: isLast && chatRunning,
1218
1226
  statusIsTerminal,
1219
1227
  hasAssistantText: responseConnectionText.trim().length > 0,
1220
1228
  hasUnresolvedTool,
@@ -1370,7 +1378,7 @@ export function AssistantMessage() {
1370
1378
  if (!showSummary) return <>{children}</>;
1371
1379
  return (
1372
1380
  <WorkedForSummary
1373
- durationMs={capturedDurationMs}
1381
+ durationMs={capturedDurationMs ?? persistedDurationMs}
1374
1382
  defaultOpen={hasCustomUi}
1375
1383
  autoCollapse={animateCollapse && !hasCustomUi}
1376
1384
  >
@@ -5,6 +5,8 @@
5
5
  // interface covering the shapes both `threadRuntime.export()` and
6
6
  // `normalizeThreadRepository()` produce, and use it everywhere instead of `any`.
7
7
 
8
+ import { ASSISTANT_RUN_DURATION_METADATA_KEY } from "../../agent/thread-data-builder.js";
9
+
8
10
  export interface RepoMessageStatus {
9
11
  type?: string;
10
12
  reason?: string;
@@ -56,6 +58,81 @@ export function getRepoMessage(entry: RepoEntry): RepoMessage | null {
56
58
  return (entry?.message ?? entry) as RepoMessage | null;
57
59
  }
58
60
 
61
+ export function getAssistantRunDurationMs(
62
+ message:
63
+ | {
64
+ metadata?: unknown;
65
+ }
66
+ | null
67
+ | undefined,
68
+ ): number | null {
69
+ const metadata =
70
+ message?.metadata && typeof message.metadata === "object"
71
+ ? (message.metadata as Record<string, unknown>)
72
+ : null;
73
+ const custom =
74
+ metadata?.custom && typeof metadata.custom === "object"
75
+ ? (metadata.custom as Record<string, unknown>)
76
+ : null;
77
+ const durationMs = custom?.[ASSISTANT_RUN_DURATION_METADATA_KEY];
78
+ return typeof durationMs === "number" &&
79
+ Number.isFinite(durationMs) &&
80
+ durationMs >= 0
81
+ ? durationMs
82
+ : null;
83
+ }
84
+
85
+ export function withLastAssistantRunDuration<T extends NormalizedRepo>(
86
+ repo: T,
87
+ durationMs: number | null | undefined,
88
+ ): T {
89
+ if (
90
+ !Array.isArray(repo.messages) ||
91
+ typeof durationMs !== "number" ||
92
+ !Number.isFinite(durationMs) ||
93
+ durationMs < 0
94
+ ) {
95
+ return repo;
96
+ }
97
+
98
+ let messageIndex = -1;
99
+ for (let index = repo.messages.length - 1; index >= 0; index -= 1) {
100
+ if (getRepoMessage(repo.messages[index]!)?.role === "assistant") {
101
+ messageIndex = index;
102
+ break;
103
+ }
104
+ }
105
+ if (messageIndex < 0) return repo;
106
+
107
+ const entry = repo.messages[messageIndex]!;
108
+ const message = getRepoMessage(entry);
109
+ if (!message || getAssistantRunDurationMs(message) === durationMs) {
110
+ return repo;
111
+ }
112
+
113
+ const metadata = message.metadata ?? {};
114
+ const custom =
115
+ metadata.custom && typeof metadata.custom === "object"
116
+ ? (metadata.custom as Record<string, unknown>)
117
+ : {};
118
+ const nextMessage: RepoMessage = {
119
+ ...message,
120
+ metadata: {
121
+ ...metadata,
122
+ custom: {
123
+ ...custom,
124
+ [ASSISTANT_RUN_DURATION_METADATA_KEY]: durationMs,
125
+ },
126
+ },
127
+ };
128
+ const messages = repo.messages.slice();
129
+ messages[messageIndex] =
130
+ entry.message === undefined
131
+ ? (nextMessage as RepoEntry)
132
+ : { ...entry, message: nextMessage };
133
+ return { ...repo, messages };
134
+ }
135
+
59
136
  /**
60
137
  * Collapse duplicate message ids before a repository is handed to
61
138
  * `threadRuntime.import()`. assistant-ui's `MessageRepository` throws
@@ -25,7 +25,10 @@ import React, {
25
25
  useRef,
26
26
  } from "react";
27
27
 
28
- import type { A2AAgentActivitySnapshot } from "../../a2a/activity.js";
28
+ import type {
29
+ A2AAgentActivitySnapshot,
30
+ A2AAgentActivityToolCall,
31
+ } from "../../a2a/activity.js";
29
32
  import type { ActionChatUIConfig } from "../../action-ui.js";
30
33
  import type { AgentMcpAppPayload } from "../../mcp-client/app-result.js";
31
34
  import { AgentTaskCard } from "../AgentTaskCard.js";
@@ -100,11 +103,13 @@ export function ToolActivityPresentation({
100
103
  toolName,
101
104
  isRunning,
102
105
  isActiveTail,
106
+ suppressLongRunningHint = false,
103
107
  children,
104
108
  }: {
105
109
  toolName: string;
106
110
  isRunning: boolean;
107
111
  isActiveTail: boolean;
112
+ suppressLongRunningHint?: boolean;
108
113
  children: React.ReactNode;
109
114
  }) {
110
115
  const [showLongRunningHint, setShowLongRunningHint] = useState(false);
@@ -114,7 +119,7 @@ export function ToolActivityPresentation({
114
119
  const [animateEntry] = useState(isActiveTail);
115
120
 
116
121
  useEffect(() => {
117
- if (!isRunning) {
122
+ if (!isRunning || suppressLongRunningHint) {
118
123
  setShowLongRunningHint(false);
119
124
  return;
120
125
  }
@@ -123,7 +128,7 @@ export function ToolActivityPresentation({
123
128
  setShowLongRunningHint(true);
124
129
  }, TOOL_LONG_RUNNING_HINT_DELAY_MS);
125
130
  return () => window.clearTimeout(timeout);
126
- }, [isRunning, toolName]);
131
+ }, [isRunning, suppressLongRunningHint, toolName]);
127
132
 
128
133
  return (
129
134
  <div
@@ -584,6 +589,9 @@ export function ToolCallDisplay({
584
589
  toolName={toolName}
585
590
  isRunning={isRunning}
586
591
  isActiveTail={showActiveTail}
592
+ suppressLongRunningHint={
593
+ toolName === "call-agent" || toolName.startsWith("agent:")
594
+ }
587
595
  >
588
596
  {children}
589
597
  </ToolActivityPresentation>
@@ -955,18 +963,8 @@ function AgentCallCell({
955
963
  />
956
964
  )}
957
965
  {tool && (
958
- <ToolCallDisplay
959
- toolName={tool.name}
960
- args={{}}
961
- argsText=""
962
- isRunning={tool.status === "running"}
963
- result={
964
- tool.status === "failed"
965
- ? "Failed"
966
- : tool.status === "completed"
967
- ? "Done"
968
- : undefined
969
- }
966
+ <AgentActivityToolCallRow
967
+ tool={tool}
970
968
  isActiveTail={
971
969
  isRunning && index === activity.toolCalls.length - 1
972
970
  }
@@ -1051,6 +1049,44 @@ function AgentCallCell({
1051
1049
  );
1052
1050
  }
1053
1051
 
1052
+ function AgentActivityToolCallRow({
1053
+ tool,
1054
+ isActiveTail,
1055
+ }: {
1056
+ tool: A2AAgentActivityToolCall;
1057
+ isActiveTail: boolean;
1058
+ }) {
1059
+ const isRunning = tool.status === "running";
1060
+ const ToolIcon = resolveToolIcon(tool.name);
1061
+
1062
+ return (
1063
+ <ToolActivityPresentation
1064
+ toolName={tool.name}
1065
+ isRunning={isRunning}
1066
+ isActiveTail={isActiveTail}
1067
+ suppressLongRunningHint
1068
+ >
1069
+ <div className="my-0.5 flex w-full items-center gap-1.5 rounded-md py-0.5 text-left text-[13px] text-muted-foreground">
1070
+ <span className="flex size-4 shrink-0 items-center justify-center">
1071
+ {isRunning ? (
1072
+ <IconLoader2 className="size-3.5 animate-spin" />
1073
+ ) : (
1074
+ <ToolIcon className="size-3.5" />
1075
+ )}
1076
+ </span>
1077
+ <span
1078
+ className={cn(
1079
+ "min-w-0 truncate font-normal",
1080
+ isActiveTail && "agent-running-shimmer",
1081
+ )}
1082
+ >
1083
+ {humanizeToolName(tool.name)}
1084
+ </span>
1085
+ </div>
1086
+ </ToolActivityPresentation>
1087
+ );
1088
+ }
1089
+
1054
1090
  // ─── ToolCallFallback ──────────────────────────────────────────────────────────
1055
1091
 
1056
1092
  export function ToolCallFallback({
@@ -413,7 +413,9 @@ export function generateCorpusToolsPrompt(
413
413
 
414
414
  Available corpus-capable tools: ${available.join(", ")}.
415
415
 
416
- For broad provider searches, raw API access, multi-page cohorts, cross-source joins, classification/counting over records, or absence-sensitive answers, do not stop at a bounded shortcut action. Use the provider's broad API/search/list surface, fetch every relevant page or an explicit bounded cohort, stage/save large responses when needed, and reduce the corpus with durable corpus jobs, staged-dataset queries, or code execution.
416
+ This workflow does not apply to ordinary structured lookups, bounded aggregates, or counts grouped over one known source. For those requests, use the single most directly authoritative source, run one bounded query, and answer as soon as it succeeds. Do not cross-check or expand into a corpus workflow unless the user asks for multiple sources, exhaustive unstructured-record coverage, or an absence claim.
417
+
418
+ For broad provider searches, raw API access, multi-page cohorts, cross-source joins, classification over unstructured records, or absence-sensitive answers, do not stop at a bounded shortcut action. Use the provider's broad API/search/list surface, fetch every relevant page or an explicit bounded cohort, stage/save large responses when needed, and reduce the corpus with durable corpus jobs, staged-dataset queries, or code execution.
417
419
 
418
420
  When \`provider-corpus-job\` is available, prefer it for transcript/message/ticket/issue/document scans that may exceed one turn, need provider-side backoff, or need a defensible "not found" conclusion. Use operation="start" with mode="paginated-search" for any paginated provider endpoint, or mode="batch-search" when a prior cohort of ids/records must feed a second provider endpoint. Continue paused jobs with operation="continue" until status is completed or quota_wait, then read operation="results". In run-code, prefer providerFetchAll() for short cursor/page/offset pagination and providerRequest() when response status, headers, or truncation metadata matters. Report source, filters, row counts, pagination/truncation, failed pages, quota_wait times, and remaining gaps.`;
419
421
  }
@@ -216,6 +216,15 @@ export interface AgentChatPluginOptions {
216
216
  * tool-search unless the app explicitly includes them in this list.
217
217
  */
218
218
  initialToolNames?: string[];
219
+ /**
220
+ * Controls whether broad provider/corpus tools and their workflow prompt are
221
+ * loaded into the first model request. Use `"lazy"` for apps that can answer
222
+ * ordinary lookups from a compact curated tool set; corpus tools remain
223
+ * discoverable through tool-search and the full retry surface.
224
+ *
225
+ * @default "initial"
226
+ */
227
+ corpusTools?: "initial" | "lazy";
219
228
  /**
220
229
  * Use a compact system prompt with on-demand context loading. The system
221
230
  * prompt includes essential behavioral rules and action signatures, but
@@ -1179,8 +1179,10 @@ export function createAgentChatPlugin(
1179
1179
  // that never emit the corpus prompt (no provider/run-code tools
1180
1180
  // registered), so this never silently expands the initial set for
1181
1181
  // apps that don't teach these tools by name.
1182
- const corpusToolNames =
1183
- corpusToolNamesTaughtByPrompt(corpusPromptRegistry);
1182
+ const loadCorpusToolsInitially = options?.corpusTools !== "lazy";
1183
+ const corpusToolNames = loadCorpusToolsInitially
1184
+ ? corpusToolNamesTaughtByPrompt(corpusPromptRegistry)
1185
+ : [];
1184
1186
  const effectiveInitialToolNames =
1185
1187
  corpusToolNames.length > 0
1186
1188
  ? [...new Set([...templateInitialToolNames, ...corpusToolNames])]
@@ -1907,7 +1909,9 @@ export function createAgentChatPlugin(
1907
1909
  // Dev: actions are invoked via bash — emit `pnpm action name --arg <type>`
1908
1910
  // and include discoveredActions too, since those are also missing
1909
1911
  // from the dev tool registry.
1910
- const corpusToolsPrompt = generateCorpusToolsPrompt(corpusPromptRegistry);
1912
+ const corpusToolsPrompt = loadCorpusToolsInitially
1913
+ ? generateCorpusToolsPrompt(corpusPromptRegistry)
1914
+ : "";
1911
1915
  const prodActionsPrompt =
1912
1916
  generateActionsPrompt(
1913
1917
  templateScripts,
@@ -2250,6 +2254,11 @@ export function createAgentChatPlugin(
2250
2254
  typeof run.turnId === "string" && run.turnId
2251
2255
  ? run.turnId
2252
2256
  : undefined,
2257
+ runDurationMs:
2258
+ typeof run.startedAt === "number" &&
2259
+ Number.isFinite(run.startedAt)
2260
+ ? Math.max(0, Date.now() - run.startedAt)
2261
+ : undefined,
2253
2262
  },
2254
2263
  );
2255
2264
  if (!assistantMsg) {
@@ -1,23 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import action from "./hello";
4
-
5
- describe("hello", () => {
6
- it("defaults name to world when omitted", async () => {
7
- const result = await action.run({});
8
-
9
- expect(result).toEqual({ message: "Hello, world!" });
10
- });
11
-
12
- it("greets the provided name", async () => {
13
- const result = await action.run({ name: "Steve" });
14
-
15
- expect(result).toEqual({ message: "Hello, Steve!" });
16
- });
17
-
18
- it("does not apply the default when name is an empty string", async () => {
19
- const result = await action.run({ name: "" });
20
-
21
- expect(result).toEqual({ message: "Hello, !" });
22
- });
23
- });
@@ -1,68 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const mocks = vi.hoisted(() => ({
4
- writeAppState: vi.fn(),
5
- }));
6
-
7
- vi.mock("@agent-native/core/application-state", () => ({
8
- writeAppState: mocks.writeAppState,
9
- }));
10
-
11
- import action from "./navigate";
12
-
13
- describe("navigate", () => {
14
- beforeEach(() => {
15
- vi.clearAllMocks();
16
- mocks.writeAppState.mockResolvedValue(undefined);
17
- });
18
-
19
- it("requires at least a view or a path", async () => {
20
- await expect(action.run({})).rejects.toThrow(
21
- "At least --view or --path is required.",
22
- );
23
- expect(mocks.writeAppState).not.toHaveBeenCalled();
24
- });
25
-
26
- it("navigates by view and only includes provided keys", async () => {
27
- const result = await action.run({ view: "chat" });
28
-
29
- expect(result).toBe("Navigating to chat");
30
- expect(mocks.writeAppState).toHaveBeenCalledWith(
31
- "navigate",
32
- expect.objectContaining({ view: "chat" }),
33
- );
34
- const callArg = mocks.writeAppState.mock.calls[0][1];
35
- expect(callArg.path).toBeUndefined();
36
- expect(callArg.threadId).toBeUndefined();
37
- expect(Object.keys(callArg).sort()).toEqual(["_writeId", "view"]);
38
- });
39
-
40
- it("navigates by path when view is not given", async () => {
41
- const result = await action.run({ path: "/settings" });
42
-
43
- expect(result).toBe("Navigating to /settings");
44
- });
45
-
46
- it("prefers view over path when both are given", async () => {
47
- const result = await action.run({ view: "chat", path: "/settings" });
48
-
49
- expect(result).toBe("Navigating to chat");
50
- });
51
-
52
- it("includes threadId when provided", async () => {
53
- await action.run({ view: "chat", threadId: "t1" });
54
-
55
- expect(mocks.writeAppState).toHaveBeenCalledWith(
56
- "navigate",
57
- expect.objectContaining({ view: "chat", threadId: "t1" }),
58
- );
59
- });
60
-
61
- it("stamps a unique _writeId on every write", async () => {
62
- await action.run({ view: "chat" });
63
-
64
- const callArg = mocks.writeAppState.mock.calls[0][1];
65
- expect(typeof callArg._writeId).toBe("string");
66
- expect(callArg._writeId.includes("-")).toBe(true);
67
- });
68
- });
@@ -1,40 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from "vitest";
2
-
3
- const mocks = vi.hoisted(() => ({
4
- readAppState: vi.fn(),
5
- }));
6
-
7
- vi.mock("@agent-native/core/application-state", () => ({
8
- readAppState: mocks.readAppState,
9
- }));
10
-
11
- import action from "./view-screen";
12
-
13
- describe("view-screen", () => {
14
- beforeEach(() => {
15
- vi.clearAllMocks();
16
- });
17
-
18
- it("returns the current navigation state when present", async () => {
19
- mocks.readAppState.mockResolvedValue({ view: "chat", threadId: "t1" });
20
-
21
- const result = await action.run({});
22
-
23
- expect(mocks.readAppState).toHaveBeenCalledWith("navigation");
24
- expect(result).toEqual({
25
- navigation: { view: "chat", threadId: "t1" },
26
- });
27
- });
28
-
29
- it("returns a fallback message when there is no navigation state", async () => {
30
- mocks.readAppState.mockResolvedValue(null);
31
-
32
- const result = await action.run({});
33
-
34
- expect(result).toBe("No application state found. Is the app running?");
35
- });
36
-
37
- it("is marked read-only", () => {
38
- expect(action.readOnly).toBe(true);
39
- });
40
- });