@agent-native/core 0.7.4 → 0.7.7

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 (250) hide show
  1. package/README.md +6 -5
  2. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  3. package/dist/agent/engine/anthropic-engine.js +8 -4
  4. package/dist/agent/engine/anthropic-engine.js.map +1 -1
  5. package/dist/agent/engine/types.d.ts +1 -1
  6. package/dist/agent/engine/types.d.ts.map +1 -1
  7. package/dist/agent/production-agent.d.ts +7 -0
  8. package/dist/agent/production-agent.d.ts.map +1 -1
  9. package/dist/agent/production-agent.js +153 -118
  10. package/dist/agent/production-agent.js.map +1 -1
  11. package/dist/agent/run-manager.d.ts +4 -0
  12. package/dist/agent/run-manager.d.ts.map +1 -1
  13. package/dist/agent/run-manager.js +46 -25
  14. package/dist/agent/run-manager.js.map +1 -1
  15. package/dist/agent/run-store.d.ts +12 -3
  16. package/dist/agent/run-store.d.ts.map +1 -1
  17. package/dist/agent/run-store.js +25 -4
  18. package/dist/agent/run-store.js.map +1 -1
  19. package/dist/chat-threads/store.d.ts +13 -0
  20. package/dist/chat-threads/store.d.ts.map +1 -1
  21. package/dist/chat-threads/store.js +66 -10
  22. package/dist/chat-threads/store.js.map +1 -1
  23. package/dist/cli/create.d.ts.map +1 -1
  24. package/dist/cli/create.js +8 -1
  25. package/dist/cli/create.js.map +1 -1
  26. package/dist/cli/index.js +8 -0
  27. package/dist/cli/index.js.map +1 -1
  28. package/dist/cli/info.d.ts +2 -0
  29. package/dist/cli/info.d.ts.map +1 -0
  30. package/dist/cli/info.js +103 -0
  31. package/dist/cli/info.js.map +1 -0
  32. package/dist/client/AssistantChat.d.ts.map +1 -1
  33. package/dist/client/AssistantChat.js +249 -85
  34. package/dist/client/AssistantChat.js.map +1 -1
  35. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  36. package/dist/client/agent-chat-adapter.js +12 -1
  37. package/dist/client/agent-chat-adapter.js.map +1 -1
  38. package/dist/client/composer/TiptapComposer.d.ts +3 -1
  39. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  40. package/dist/client/composer/TiptapComposer.js +46 -2
  41. package/dist/client/composer/TiptapComposer.js.map +1 -1
  42. package/dist/client/composer/VoiceButton.d.ts +21 -0
  43. package/dist/client/composer/VoiceButton.d.ts.map +1 -0
  44. package/dist/client/composer/VoiceButton.js +51 -0
  45. package/dist/client/composer/VoiceButton.js.map +1 -0
  46. package/dist/client/composer/useVoiceDictation.d.ts +38 -0
  47. package/dist/client/composer/useVoiceDictation.d.ts.map +1 -0
  48. package/dist/client/composer/useVoiceDictation.js +398 -0
  49. package/dist/client/composer/useVoiceDictation.js.map +1 -0
  50. package/dist/client/onboarding/OnboardingPanel.js +2 -2
  51. package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
  52. package/dist/client/org/OrgSwitcher.d.ts +5 -4
  53. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  54. package/dist/client/org/OrgSwitcher.js +90 -24
  55. package/dist/client/org/OrgSwitcher.js.map +1 -1
  56. package/dist/client/resources/McpServerDetail.d.ts +15 -0
  57. package/dist/client/resources/McpServerDetail.d.ts.map +1 -0
  58. package/dist/client/resources/McpServerDetail.js +65 -0
  59. package/dist/client/resources/McpServerDetail.js.map +1 -0
  60. package/dist/client/resources/ResourceEditor.js +1 -1
  61. package/dist/client/resources/ResourceEditor.js.map +1 -1
  62. package/dist/client/resources/ResourceTree.d.ts +6 -1
  63. package/dist/client/resources/ResourceTree.d.ts.map +1 -1
  64. package/dist/client/resources/ResourceTree.js +18 -7
  65. package/dist/client/resources/ResourceTree.js.map +1 -1
  66. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  67. package/dist/client/resources/ResourcesPanel.js +191 -20
  68. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  69. package/dist/client/resources/use-mcp-servers.d.ts +68 -0
  70. package/dist/client/resources/use-mcp-servers.d.ts.map +1 -0
  71. package/dist/client/resources/use-mcp-servers.js +83 -0
  72. package/dist/client/resources/use-mcp-servers.js.map +1 -0
  73. package/dist/client/resources/use-resources.d.ts +39 -1
  74. package/dist/client/resources/use-resources.d.ts.map +1 -1
  75. package/dist/client/resources/use-resources.js +102 -0
  76. package/dist/client/resources/use-resources.js.map +1 -1
  77. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  78. package/dist/client/settings/SettingsPanel.js +3 -2
  79. package/dist/client/settings/SettingsPanel.js.map +1 -1
  80. package/dist/client/settings/VoiceTranscriptionSection.d.ts +14 -0
  81. package/dist/client/settings/VoiceTranscriptionSection.d.ts.map +1 -0
  82. package/dist/client/settings/VoiceTranscriptionSection.js +111 -0
  83. package/dist/client/settings/VoiceTranscriptionSection.js.map +1 -0
  84. package/dist/client/sharing/ShareButton.d.ts +6 -4
  85. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  86. package/dist/client/sharing/ShareButton.js +299 -34
  87. package/dist/client/sharing/ShareButton.js.map +1 -1
  88. package/dist/client/sharing/ShareDialog.d.ts +22 -4
  89. package/dist/client/sharing/ShareDialog.d.ts.map +1 -1
  90. package/dist/client/sharing/ShareDialog.js +170 -148
  91. package/dist/client/sharing/ShareDialog.js.map +1 -1
  92. package/dist/client/sharing/VisibilityBadge.d.ts.map +1 -1
  93. package/dist/client/sharing/VisibilityBadge.js +1 -2
  94. package/dist/client/sharing/VisibilityBadge.js.map +1 -1
  95. package/dist/client/use-action.d.ts.map +1 -1
  96. package/dist/client/use-action.js +20 -1
  97. package/dist/client/use-action.js.map +1 -1
  98. package/dist/db/migrations.d.ts +18 -3
  99. package/dist/db/migrations.d.ts.map +1 -1
  100. package/dist/db/migrations.js +25 -3
  101. package/dist/db/migrations.js.map +1 -1
  102. package/dist/deploy/workspace-core.js +2 -2
  103. package/dist/mcp-client/config.d.ts +20 -1
  104. package/dist/mcp-client/config.d.ts.map +1 -1
  105. package/dist/mcp-client/config.js +28 -11
  106. package/dist/mcp-client/config.js.map +1 -1
  107. package/dist/mcp-client/hub-client.d.ts +38 -0
  108. package/dist/mcp-client/hub-client.d.ts.map +1 -0
  109. package/dist/mcp-client/hub-client.js +147 -0
  110. package/dist/mcp-client/hub-client.js.map +1 -0
  111. package/dist/mcp-client/hub-routes.d.ts +42 -0
  112. package/dist/mcp-client/hub-routes.d.ts.map +1 -0
  113. package/dist/mcp-client/hub-routes.js +114 -0
  114. package/dist/mcp-client/hub-routes.js.map +1 -0
  115. package/dist/mcp-client/index.d.ts +15 -0
  116. package/dist/mcp-client/index.d.ts.map +1 -1
  117. package/dist/mcp-client/index.js +35 -0
  118. package/dist/mcp-client/index.js.map +1 -1
  119. package/dist/mcp-client/manager.d.ts +54 -8
  120. package/dist/mcp-client/manager.d.ts.map +1 -1
  121. package/dist/mcp-client/manager.js +276 -59
  122. package/dist/mcp-client/manager.js.map +1 -1
  123. package/dist/mcp-client/remote-store.d.ts +102 -0
  124. package/dist/mcp-client/remote-store.d.ts.map +1 -0
  125. package/dist/mcp-client/remote-store.js +200 -0
  126. package/dist/mcp-client/remote-store.js.map +1 -0
  127. package/dist/mcp-client/routes.d.ts +55 -0
  128. package/dist/mcp-client/routes.d.ts.map +1 -0
  129. package/dist/mcp-client/routes.js +384 -0
  130. package/dist/mcp-client/routes.js.map +1 -0
  131. package/dist/mcp-client/visibility.d.ts +16 -0
  132. package/dist/mcp-client/visibility.d.ts.map +1 -0
  133. package/dist/mcp-client/visibility.js +45 -0
  134. package/dist/mcp-client/visibility.js.map +1 -0
  135. package/dist/org/context.js +2 -2
  136. package/dist/org/context.js.map +1 -1
  137. package/dist/org/handlers.js +2 -2
  138. package/dist/org/handlers.js.map +1 -1
  139. package/dist/resources/handlers.d.ts.map +1 -1
  140. package/dist/resources/handlers.js +30 -0
  141. package/dist/resources/handlers.js.map +1 -1
  142. package/dist/secrets/register-framework-secrets.d.ts +13 -0
  143. package/dist/secrets/register-framework-secrets.d.ts.map +1 -0
  144. package/dist/secrets/register-framework-secrets.js +59 -0
  145. package/dist/secrets/register-framework-secrets.js.map +1 -0
  146. package/dist/secrets/register.d.ts.map +1 -1
  147. package/dist/secrets/register.js +8 -1
  148. package/dist/secrets/register.js.map +1 -1
  149. package/dist/server/action-routes.d.ts.map +1 -1
  150. package/dist/server/action-routes.js +22 -2
  151. package/dist/server/action-routes.js.map +1 -1
  152. package/dist/server/agent-chat-plugin.d.ts +16 -0
  153. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  154. package/dist/server/agent-chat-plugin.js +237 -70
  155. package/dist/server/agent-chat-plugin.js.map +1 -1
  156. package/dist/server/app-url.d.ts.map +1 -1
  157. package/dist/server/app-url.js +11 -3
  158. package/dist/server/app-url.js.map +1 -1
  159. package/dist/server/auth.d.ts.map +1 -1
  160. package/dist/server/auth.js +50 -0
  161. package/dist/server/auth.js.map +1 -1
  162. package/dist/server/better-auth-instance.d.ts.map +1 -1
  163. package/dist/server/better-auth-instance.js +99 -4
  164. package/dist/server/better-auth-instance.js.map +1 -1
  165. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  166. package/dist/server/core-routes-plugin.js +44 -0
  167. package/dist/server/core-routes-plugin.js.map +1 -1
  168. package/dist/server/create-server.d.ts.map +1 -1
  169. package/dist/server/create-server.js +6 -0
  170. package/dist/server/create-server.js.map +1 -1
  171. package/dist/server/date-utils.d.ts +15 -0
  172. package/dist/server/date-utils.d.ts.map +1 -0
  173. package/dist/server/date-utils.js +41 -0
  174. package/dist/server/date-utils.js.map +1 -0
  175. package/dist/server/index.d.ts +2 -1
  176. package/dist/server/index.d.ts.map +1 -1
  177. package/dist/server/index.js +2 -1
  178. package/dist/server/index.js.map +1 -1
  179. package/dist/server/onboarding-html.d.ts +3 -0
  180. package/dist/server/onboarding-html.d.ts.map +1 -1
  181. package/dist/server/onboarding-html.js +13 -3
  182. package/dist/server/onboarding-html.js.map +1 -1
  183. package/dist/server/request-context.d.ts +9 -0
  184. package/dist/server/request-context.d.ts.map +1 -1
  185. package/dist/server/request-context.js +10 -0
  186. package/dist/server/request-context.js.map +1 -1
  187. package/dist/server/transcribe-voice.d.ts +26 -0
  188. package/dist/server/transcribe-voice.d.ts.map +1 -0
  189. package/dist/server/transcribe-voice.js +143 -0
  190. package/dist/server/transcribe-voice.js.map +1 -0
  191. package/dist/styles/agent-native.css +111 -0
  192. package/dist/tailwind.preset.d.ts +2 -2
  193. package/dist/tailwind.preset.d.ts.map +1 -1
  194. package/dist/tailwind.preset.js +27 -7
  195. package/dist/tailwind.preset.js.map +1 -1
  196. package/dist/templates/default/app/global.css +65 -68
  197. package/dist/templates/default/components.json +1 -1
  198. package/dist/templates/default/package.json +2 -4
  199. package/dist/templates/default/vite.config.ts +3 -0
  200. package/dist/templates/workspace-core/package.json +1 -4
  201. package/dist/templates/workspace-core/src/index.ts +1 -1
  202. package/dist/templates/workspace-core/styles/tokens.css +22 -0
  203. package/dist/templates/workspace-core/tsconfig.json +1 -1
  204. package/dist/vite/client.d.ts +6 -0
  205. package/dist/vite/client.d.ts.map +1 -1
  206. package/dist/vite/client.js +18 -1
  207. package/dist/vite/client.js.map +1 -1
  208. package/docs/content/actions.md +169 -74
  209. package/docs/content/agent-teams.md +139 -0
  210. package/docs/content/cloneable-saas.md +98 -0
  211. package/docs/content/creating-templates.md +9 -11
  212. package/docs/content/deployment.md +2 -9
  213. package/docs/content/drop-in-agent.md +200 -0
  214. package/docs/content/enterprise-workspace.md +22 -10
  215. package/docs/content/getting-started.md +34 -19
  216. package/docs/content/integrations.md +3 -3
  217. package/docs/content/key-concepts.md +50 -23
  218. package/docs/content/mcp-clients.md +71 -0
  219. package/docs/content/pure-agent-apps.md +69 -0
  220. package/docs/content/recurring-jobs.md +123 -0
  221. package/docs/content/skills-guide.md +8 -0
  222. package/docs/content/template-analytics.md +190 -0
  223. package/docs/content/template-calendar.md +151 -0
  224. package/docs/content/template-clips.md +55 -0
  225. package/docs/content/template-content.md +141 -0
  226. package/docs/content/template-dispatch.md +58 -0
  227. package/docs/content/template-forms.md +51 -0
  228. package/docs/content/template-mail.md +169 -0
  229. package/docs/content/template-slides.md +218 -0
  230. package/docs/content/template-starter.md +68 -0
  231. package/docs/content/template-video.md +162 -0
  232. package/docs/content/voice-input.md +59 -0
  233. package/docs/content/what-is-agent-native.md +142 -45
  234. package/docs/content/workspace-management.md +1 -0
  235. package/docs/content/{resources.md → workspace.md} +94 -42
  236. package/package.json +9 -16
  237. package/src/templates/default/app/global.css +65 -68
  238. package/src/templates/default/components.json +1 -1
  239. package/src/templates/default/package.json +2 -4
  240. package/src/templates/default/vite.config.ts +3 -0
  241. package/src/templates/workspace-core/package.json +1 -4
  242. package/src/templates/workspace-core/src/index.ts +1 -1
  243. package/src/templates/workspace-core/styles/tokens.css +22 -0
  244. package/src/templates/workspace-core/tsconfig.json +1 -1
  245. package/dist/templates/default/postcss.config.js +0 -6
  246. package/dist/templates/default/tailwind.config.ts +0 -7
  247. package/dist/templates/workspace-core/tailwind.preset.ts +0 -34
  248. package/src/templates/default/postcss.config.js +0 -6
  249. package/src/templates/default/tailwind.config.ts +0 -7
  250. package/src/templates/workspace-core/tailwind.preset.ts +0 -34
@@ -26,15 +26,22 @@ const markdownStyles = `
26
26
  .agent-markdown h3 { font-size: 1em; font-weight: 600; margin: 0.75em 0 0.25em; }
27
27
  .agent-markdown strong { font-weight: 600; }
28
28
  .agent-markdown em { font-style: italic; }
29
- .agent-markdown code { font-size: 0.875em; padding: 0.15em 0.35em; border-radius: 0.25em; background: var(--color-muted, hsl(0 0% 15%)); }
30
- .agent-markdown pre { margin: 0.5em 0; padding: 0.75em 1em; border-radius: 0.375em; background: var(--color-muted, hsl(0 0% 15%)); overflow-x: auto; }
31
- .agent-markdown pre code { padding: 0; background: transparent; font-size: 0.8125em; }
32
- .agent-markdown hr { border: none; border-top: 1px solid var(--color-border, hsl(0 0% 20%)); margin: 0.75em 0; }
29
+ .agent-markdown code { font-size: 0.875em; padding: 0.15em 0.35em; border-radius: 0.25em; background: hsl(var(--muted, 0 0% 15%)); color: hsl(var(--foreground, 0 0% 90%)); }
30
+ .agent-markdown pre { margin: 0.5em 0; padding: 0.75em 1em; border-radius: 0.375em; background: hsl(var(--muted, 0 0% 15%)); color: hsl(var(--foreground, 0 0% 90%)); overflow-x: auto; }
31
+ .agent-markdown pre code { padding: 0; background: transparent; font-size: 0.8125em; color: inherit; }
32
+ .agent-markdown-shiki { margin: 0.5em 0; border-radius: 0.375em; overflow: hidden; font-size: 0.8125em; }
33
+ .agent-markdown-shiki pre { margin: 0; padding: 0.75em 1em; overflow-x: auto; background: var(--shiki-light-bg); color: var(--shiki-light); }
34
+ .agent-markdown-shiki pre code { background: transparent; padding: 0; font-size: inherit; color: inherit; }
35
+ .agent-markdown-shiki pre span { color: var(--shiki-light); background: var(--shiki-light-bg); }
36
+ .dark .agent-markdown-shiki pre { background: var(--shiki-dark-bg); color: var(--shiki-dark); }
37
+ .dark .agent-markdown-shiki pre span { color: var(--shiki-dark); background: var(--shiki-dark-bg); }
38
+ @media (prefers-color-scheme: dark) { :root:not(.light) .agent-markdown-shiki pre { background: var(--shiki-dark-bg); color: var(--shiki-dark); } :root:not(.light) .agent-markdown-shiki pre span { color: var(--shiki-dark); background: var(--shiki-dark-bg); } }
39
+ .agent-markdown hr { border: none; border-top: 1px solid hsl(var(--border, 0 0% 20%)); margin: 0.75em 0; }
33
40
  .agent-markdown a { text-decoration: underline; text-underline-offset: 2px; }
34
- .agent-markdown blockquote { border-left: 2px solid var(--color-border, hsl(0 0% 20%)); padding-left: 0.75em; margin: 0.5em 0; opacity: 0.8; }
41
+ .agent-markdown blockquote { border-left: 2px solid hsl(var(--border, 0 0% 20%)); padding-left: 0.75em; margin: 0.5em 0; opacity: 0.8; }
35
42
  .agent-markdown table { border-collapse: collapse; margin: 0.5em 0; font-size: 0.875em; }
36
- .agent-markdown th, .agent-markdown td { border: 1px solid var(--color-border, hsl(0 0% 20%)); padding: 0.35em 0.65em; text-align: left; }
37
- .agent-markdown th { font-weight: 600; background: var(--color-muted, hsl(0 0% 15%)); }
43
+ .agent-markdown th, .agent-markdown td { border: 1px solid hsl(var(--border, 0 0% 20%)); padding: 0.35em 0.65em; text-align: left; }
44
+ .agent-markdown th { font-weight: 600; background: hsl(var(--muted, 0 0% 15%)); color: hsl(var(--foreground, 0 0% 90%)); }
38
45
  `;
39
46
  let stylesInjected = false;
40
47
  function injectMarkdownStyles() {
@@ -56,6 +63,92 @@ function extractCodeText(child) {
56
63
  }
57
64
  return "";
58
65
  }
66
+ let highlighterLoader = null;
67
+ function loadHighlighter() {
68
+ if (!highlighterLoader) {
69
+ highlighterLoader = (async () => {
70
+ const [{ createHighlighterCore }, { createOnigurumaEngine }] = await Promise.all([
71
+ import("shiki/core"),
72
+ import("shiki/engine/oniguruma"),
73
+ ]);
74
+ return createHighlighterCore({
75
+ themes: [
76
+ import("shiki/themes/github-light-default.mjs"),
77
+ import("shiki/themes/github-dark-default.mjs"),
78
+ ],
79
+ langs: [
80
+ import("shiki/langs/javascript.mjs"),
81
+ import("shiki/langs/typescript.mjs"),
82
+ import("shiki/langs/jsx.mjs"),
83
+ import("shiki/langs/tsx.mjs"),
84
+ import("shiki/langs/json.mjs"),
85
+ import("shiki/langs/css.mjs"),
86
+ import("shiki/langs/html.mjs"),
87
+ import("shiki/langs/markdown.mjs"),
88
+ import("shiki/langs/bash.mjs"),
89
+ import("shiki/langs/shellscript.mjs"),
90
+ import("shiki/langs/python.mjs"),
91
+ import("shiki/langs/yaml.mjs"),
92
+ ],
93
+ engine: createOnigurumaEngine(import("shiki/wasm")),
94
+ });
95
+ })().catch((error) => {
96
+ // Reset on failure so a future code block can retry instead of
97
+ // silently failing forever on a stale chunk / network blip.
98
+ highlighterLoader = null;
99
+ throw error;
100
+ });
101
+ }
102
+ return highlighterLoader;
103
+ }
104
+ // Map a few common aliases to languages we bundled above.
105
+ const LANG_ALIASES = {
106
+ js: "javascript",
107
+ ts: "typescript",
108
+ sh: "bash",
109
+ shell: "bash",
110
+ zsh: "bash",
111
+ py: "python",
112
+ yml: "yaml",
113
+ md: "markdown",
114
+ };
115
+ function HighlightedCodeBlock({ code, lang }) {
116
+ const [html, setHtml] = useState(null);
117
+ useEffect(() => {
118
+ let cancelled = false;
119
+ loadHighlighter()
120
+ .then((highlighter) => {
121
+ const requested = (lang || "text").toLowerCase();
122
+ const resolved = LANG_ALIASES[requested] ?? requested;
123
+ const loaded = highlighter.getLoadedLanguages();
124
+ const finalLang = loaded.includes(resolved) ? resolved : "text";
125
+ return highlighter.codeToHtml(code, {
126
+ lang: finalLang,
127
+ themes: {
128
+ light: "github-light-default",
129
+ dark: "github-dark-default",
130
+ },
131
+ defaultColor: false,
132
+ });
133
+ })
134
+ .then((out) => {
135
+ if (!cancelled)
136
+ setHtml(out);
137
+ })
138
+ .catch(() => {
139
+ // Unknown language or other shiki failure — fall back to plain pre.
140
+ if (!cancelled)
141
+ setHtml(null);
142
+ });
143
+ return () => {
144
+ cancelled = true;
145
+ };
146
+ }, [code, lang]);
147
+ if (html) {
148
+ return (_jsx("div", { className: "agent-markdown-shiki", dangerouslySetInnerHTML: { __html: html } }));
149
+ }
150
+ return (_jsx("pre", { children: _jsx("code", { className: lang ? `language-${lang}` : undefined, children: code }) }));
151
+ }
59
152
  const markdownComponents = {
60
153
  pre(props) {
61
154
  const { children, ...rest } = props;
@@ -67,6 +160,11 @@ const markdownComponents = {
67
160
  const parsed = parseEmbedBody(body);
68
161
  return (_jsx(IframeEmbed, { ...parsed }));
69
162
  }
163
+ const langMatch = className.match(/\blanguage-([\w+-]+)\b/);
164
+ if (langMatch) {
165
+ const code = extractCodeText(childProps.children).replace(/\n$/, "");
166
+ return _jsx(HighlightedCodeBlock, { code: code, lang: langMatch[1] });
167
+ }
70
168
  }
71
169
  return _jsx("pre", { ...rest, children: children });
72
170
  },
@@ -115,6 +213,10 @@ function ComposerAttachmentPreviewStrip() {
115
213
  return null;
116
214
  return (_jsx("div", { className: "flex flex-wrap gap-2 px-2 pt-2", children: attachments.map((attachment) => (_jsx(ComposerAttachmentPreviewCard, { attachment: attachment, onRemove: handleRemove }, attachment.id))) }));
117
215
  }
216
+ // Provides the parent's combined running state to tool-call renderers so they
217
+ // can stop spinning when the user clicks stop. `thread.isRunning` alone misses
218
+ // the force-stopped case; `part.result === undefined` alone ignores stop.
219
+ const ChatRunningContext = React.createContext(false);
118
220
  // ─── Tool Call Display ──────────────────────────────────────────────────────
119
221
  // Shared presentational component for rendering a tool call pill + result.
120
222
  // Used by both the normal message path (ToolCallFallback) and the reconnect
@@ -191,8 +293,8 @@ function ToolCallDisplay({ toolName, argsText, args, result, isRunning, }) {
191
293
  : JSON.stringify(result, null, 2) }))] }));
192
294
  }
193
295
  function ToolCallFallback({ toolName, args, argsText, result, }) {
194
- const thread = useThread();
195
- const isRunning = result === undefined && thread.isRunning;
296
+ const chatRunning = React.useContext(ChatRunningContext);
297
+ const isRunning = result === undefined && chatRunning;
196
298
  return (_jsx(ToolCallDisplay, { toolName: toolName, args: args, argsText: argsText, result: typeof result === "string"
197
299
  ? result
198
300
  : result !== undefined
@@ -204,6 +306,7 @@ function ToolCallFallback({ toolName, args, argsText, result, }) {
204
306
  // assistant-ui's runtime). Uses the same visual styling as normal messages.
205
307
  function ReconnectStreamMessage({ content }) {
206
308
  const endRef = useRef(null);
309
+ const chatRunning = React.useContext(ChatRunningContext);
207
310
  useEffect(() => {
208
311
  endRef.current?.scrollIntoView({ behavior: "smooth", block: "end" });
209
312
  }, [content]);
@@ -212,7 +315,7 @@ function ReconnectStreamMessage({ content }) {
212
315
  return (_jsx("div", { className: "agent-markdown break-words", children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], components: markdownComponents, children: part.text }) }, `reconnect-text-${i}`));
213
316
  }
214
317
  if (part.type === "tool-call") {
215
- return (_jsx(ToolCallDisplay, { toolName: part.toolName, argsText: part.argsText, args: part.args, result: part.result, isRunning: part.result === undefined }, `reconnect-tool-${i}`));
318
+ return (_jsx(ToolCallDisplay, { toolName: part.toolName, argsText: part.argsText, args: part.args, result: part.result, isRunning: part.result === undefined && chatRunning }, `reconnect-tool-${i}`));
216
319
  }
217
320
  return null;
218
321
  }), _jsx("div", { ref: endRef })] }) }));
@@ -372,7 +475,7 @@ function AssistantMessage() {
372
475
  } }) }), isComplete && (_jsx("div", { className: "mt-1 flex items-center gap-0.5 opacity-0 group-hover:opacity-100", children: _jsx("button", { onClick: handleCopy, className: "flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", children: copied ? (_jsx(IconCheck, { className: "h-3 w-3" })) : (_jsx(IconCopy, { className: "h-3 w-3" })) }) }))] }));
373
476
  }
374
477
  // ─── Thinking Indicator ─────────────────────────────────────────────────────
375
- function ThinkingIndicator() {
478
+ function ThinkingIndicator({ label = "Thinking" } = {}) {
376
479
  const [dots, setDots] = useState(0);
377
480
  useEffect(() => {
378
481
  const interval = setInterval(() => {
@@ -380,7 +483,7 @@ function ThinkingIndicator() {
380
483
  }, 400);
381
484
  return () => clearInterval(interval);
382
485
  }, []);
383
- return (_jsx("div", { className: "flex items-center text-muted-foreground", children: _jsxs("span", { className: "text-xs", children: ["Thinking", ".".repeat(dots)] }) }));
486
+ return (_jsx("div", { className: "flex items-center text-muted-foreground", children: _jsxs("span", { className: "text-xs", children: [label, ".".repeat(dots)] }) }));
384
487
  }
385
488
  // ─── API Key Setup Card ─────────────────────────────────────────────────────
386
489
  function ApiKeySetupCard({ apiUrl }) {
@@ -518,6 +621,10 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
518
621
  const [authError, setAuthError] = useState(null);
519
622
  const [usageLimitReached, setUsageLimitReached] = useState(null);
520
623
  const [queuedMessages, setQueuedMessages] = useState([]);
624
+ // Tracks the JSON of the last queue we successfully persisted so the
625
+ // debounced save effect can skip no-op writes (e.g. restore-from-server
626
+ // on mount, or queue state that hasn't actually changed).
627
+ const lastPersistedQueueRef = useRef("[]");
521
628
  const [showContinue, setShowContinue] = useState(false);
522
629
  const [isReconnecting, setIsReconnecting] = useState(false);
523
630
  const [reconnectContent, setReconnectContent] = useState([]);
@@ -567,6 +674,13 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
567
674
  titleGeneratedRef.current = true; // Don't re-generate for restored threads
568
675
  threadRuntime.import(ensureMessageMetadata(repo));
569
676
  }
677
+ // Restore user-queued messages that were persisted before reload.
678
+ if (Array.isArray(repo?.queuedMessages)) {
679
+ setQueuedMessages(repo.queuedMessages);
680
+ // Mark as restored so the debounced save effect doesn't write
681
+ // the same data back to the server on mount.
682
+ lastPersistedQueueRef.current = JSON.stringify(repo.queuedMessages);
683
+ }
570
684
  }
571
685
  // Also skip title generation if thread already has a title
572
686
  if (data.title) {
@@ -577,9 +691,19 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
577
691
  const runRes = await fetch(`${apiUrl}/runs/active?threadId=${encodeURIComponent(threadId)}`);
578
692
  if (runRes.ok) {
579
693
  const runInfo = await runRes.json();
580
- // If the run already completed, just re-fetch thread data
581
- // (don't enter "Thinking." reconnection mode)
582
- if (runInfo.status !== "running") {
694
+ // Defense in depth: if the server says status="running" but the
695
+ // heartbeat is stale (producer died before the server-side reap
696
+ // sweep noticed), treat it as dead. 5s tolerates normal jitter
697
+ // around the 1.5s heartbeat without false positives.
698
+ const heartbeatAt = typeof runInfo.heartbeatAt === "number"
699
+ ? runInfo.heartbeatAt
700
+ : null;
701
+ const looksStale = runInfo.status === "running" &&
702
+ heartbeatAt != null &&
703
+ Date.now() - heartbeatAt > 5000;
704
+ // If the run already completed or looks stale, just re-fetch
705
+ // thread data (don't enter "Thinking." reconnection mode).
706
+ if (runInfo.status !== "running" || looksStale) {
583
707
  try {
584
708
  const refreshRes = await fetch(`${apiUrl}/threads/${encodeURIComponent(threadId)}`);
585
709
  if (refreshRes.ok) {
@@ -591,6 +715,10 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
591
715
  if (repo?.messages?.length > 0) {
592
716
  threadRuntime.import(ensureMessageMetadata(repo));
593
717
  }
718
+ if (Array.isArray(repo?.queuedMessages)) {
719
+ setQueuedMessages(repo.queuedMessages);
720
+ lastPersistedQueueRef.current = JSON.stringify(repo.queuedMessages);
721
+ }
594
722
  }
595
723
  }
596
724
  }
@@ -610,12 +738,11 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
610
738
  // can abort it even if clicked before the function body runs.
611
739
  const abortCtrl = new AbortController();
612
740
  reconnectAbortRef.current = abortCtrl;
613
- // Watchdog: poll /runs/active to detect when the run is no
614
- // longer running server-side. If the SSE stream hangs (e.g.
615
- // because the agent process died but its SQL run row is still
616
- // marked "running", or the stream just never emits `done`),
617
- // this aborts the fetch so we fall through to thread refresh
618
- // instead of showing "Thinking..." forever.
741
+ // Watchdog: poll /runs/active every 1s to detect when the run
742
+ // is no longer running server-side, or the heartbeat has gone
743
+ // stale (producer died). Aborts the SSE fetch so we fall
744
+ // through to thread refresh instead of showing "Thinking..."
745
+ // forever.
619
746
  const watchdog = setInterval(async () => {
620
747
  try {
621
748
  const res = await fetch(`${apiUrl}/runs/active?threadId=${encodeURIComponent(threadId)}`);
@@ -625,7 +752,13 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
625
752
  return;
626
753
  }
627
754
  const info = await res.json();
628
- if (info.status !== "running") {
755
+ const hb = typeof info.heartbeatAt === "number"
756
+ ? info.heartbeatAt
757
+ : null;
758
+ const stale = info.status === "running" &&
759
+ hb != null &&
760
+ Date.now() - hb > 5000;
761
+ if (info.status !== "running" || stale) {
629
762
  abortCtrl.abort();
630
763
  clearInterval(watchdog);
631
764
  }
@@ -633,15 +766,16 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
633
766
  catch {
634
767
  // Network blip — keep polling
635
768
  }
636
- }, 3000);
769
+ }, 1000);
637
770
  // Hard cap: no single reconnect should wedge the UI for
638
- // more than 2 minutes. Even if the watchdog is fooled and
639
- // the SSE stream never closes, this guarantees "Thinking..."
640
- // eventually clears.
771
+ // more than 20s. With the 1s watchdog + stale-heartbeat
772
+ // detection + startup reap, this only triggers in truly
773
+ // pathological cases. Keeps "Reconnecting…" from feeling
774
+ // infinite.
641
775
  const maxReconnectTimer = setTimeout(() => {
642
776
  abortCtrl.abort();
643
777
  clearInterval(watchdog);
644
- }, 2 * 60 * 1000);
778
+ }, 20_000);
645
779
  const streamReconnect = async () => {
646
780
  try {
647
781
  const sseRes = await fetch(`${apiUrl}/runs/${encodeURIComponent(runInfo.runId)}/events?after=0`, { signal: abortCtrl.signal });
@@ -839,6 +973,36 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
839
973
  useEffect(() => {
840
974
  onMessageCountChange?.(messages.length);
841
975
  }, [messages.length, onMessageCountChange]);
976
+ // Persist queued messages to the server so they survive reloads. Debounced
977
+ // to 300ms so typing-and-queuing-rapidly doesn't hammer the endpoint.
978
+ // Stores them in thread_data.queuedMessages via POST /threads/:id/queued.
979
+ useEffect(() => {
980
+ if (!threadId)
981
+ return;
982
+ if (!hasRestoredRef.current)
983
+ return;
984
+ const serialized = JSON.stringify(queuedMessages);
985
+ if (serialized === lastPersistedQueueRef.current)
986
+ return;
987
+ const timer = setTimeout(() => {
988
+ (async () => {
989
+ try {
990
+ const res = await fetch(`${apiUrl}/threads/${encodeURIComponent(threadId)}/queued`, {
991
+ method: "POST",
992
+ headers: { "Content-Type": "application/json" },
993
+ body: JSON.stringify({ queuedMessages }),
994
+ });
995
+ if (res.ok) {
996
+ lastPersistedQueueRef.current = serialized;
997
+ }
998
+ }
999
+ catch {
1000
+ // Best-effort — next queue change will retry.
1001
+ }
1002
+ })();
1003
+ }, 300);
1004
+ return () => clearTimeout(timer);
1005
+ }, [queuedMessages, threadId, apiUrl]);
842
1006
  // Listen for missing API key events from the adapter
843
1007
  useEffect(() => {
844
1008
  const handler = () => setMissingApiKey(true);
@@ -1027,65 +1191,65 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
1027
1191
  }, 100);
1028
1192
  return () => clearInterval(interval);
1029
1193
  }, [isRunning]);
1030
- return (_jsxs("div", { className: cn("flex flex-1 flex-col h-full min-h-0 text-foreground", className), children: [showHeader && (_jsxs("div", { className: "flex h-11 shrink-0 items-center justify-between border-b border-border px-4", children: [_jsx("span", { className: "text-[13px] font-medium text-muted-foreground", children: "Agent" }), _jsx("div", { className: "flex items-center gap-1", children: onSwitchToCli && (_jsxs("button", { onClick: onSwitchToCli, className: "flex items-center gap-1 text-[12px] text-muted-foreground hover:text-foreground px-2 py-1 rounded-md hover:bg-accent", title: "Switch to CLI", children: [_jsx(IconTerminal, { className: "h-3.5 w-3.5" }), "CLI"] })) })] })), _jsx("div", { ref: scrollRef, className: "flex-1 overflow-y-auto overflow-x-hidden min-h-0", children: authError ? (_jsxs("div", { className: "flex flex-col items-center justify-center h-full px-4 gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-destructive/10", children: _jsx(IconLock, { className: "h-5 w-5 text-destructive" }) }), _jsxs("div", { className: "text-center max-w-[280px]", children: [_jsx("p", { className: "text-sm font-medium text-foreground mb-1", children: authError.sessionExpired
1031
- ? "Session expired"
1032
- : "Authentication required" }), _jsx("p", { className: "text-xs text-muted-foreground leading-relaxed", children: authError.sessionExpired ? ("Your session may have expired. Log out and log back in to reconnect.") : (_jsxs(_Fragment, { children: ["You need to log in to use the agent. If you're running locally, add", " ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded text-[10px]", children: "AUTH_MODE=local" }), " ", "to your", " ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded text-[10px]", children: ".env" }), " ", "file and restart the dev server."] })) })] }), _jsxs("div", { className: "flex gap-2", children: [authError.sessionExpired && (_jsx("button", { onClick: async () => {
1033
- try {
1034
- await fetch("/_agent-native/auth/logout", {
1035
- method: "POST",
1036
- });
1037
- }
1038
- catch { }
1039
- window.location.reload();
1040
- }, className: "text-xs text-destructive hover:text-destructive/80 px-3 py-1.5 rounded-md border border-destructive/30 hover:bg-destructive/10", children: "Log out" })), _jsx("button", { onClick: () => {
1041
- setAuthError(null);
1042
- window.location.reload();
1043
- }, className: "text-xs text-muted-foreground hover:text-foreground px-3 py-1.5 rounded-md border border-border hover:bg-accent", children: "Retry" })] })] })) : missingApiKey ? (_jsx("div", { className: "flex flex-col items-center justify-center h-full px-2", children: _jsx(ApiKeySetupCard, { apiUrl: apiUrl }) })) : usageLimitReached ? (_jsx("div", { className: "flex flex-col items-center justify-center h-full px-2", children: _jsx(BuilderCtaCard, { reason: "usage_limit", usageCents: usageLimitReached.usageCents, limitCents: usageLimitReached.limitCents, apiUrl: apiUrl }) })) : isRestoring ? (_jsxs("div", { className: "flex flex-col gap-3 p-4", children: [_jsx("div", { className: "flex justify-end", children: _jsx("div", { className: "h-8 w-32 rounded-lg bg-muted animate-pulse" }) }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("div", { className: "h-4 w-48 rounded bg-muted animate-pulse" }), _jsx("div", { className: "h-4 w-64 rounded bg-muted animate-pulse" }), _jsx("div", { className: "h-4 w-40 rounded bg-muted animate-pulse" })] })] })) : messages.length === 0 && !isReconnecting ? (_jsxs("div", { className: "flex flex-col items-center justify-center gap-4 py-16 px-4 h-full", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-muted", children: _jsx(IconMessage, { className: "h-5 w-5 text-muted-foreground" }) }), _jsx("p", { className: "text-sm text-muted-foreground text-center max-w-[240px]", children: emptyStateText ?? "How can I help you?" }), suggestions && suggestions.length > 0 && (_jsx("div", { className: "flex flex-col gap-1.5 w-full max-w-[280px]", children: suggestions.map((suggestion) => (_jsx("button", { onClick: () => {
1044
- threadRuntime.append({
1045
- role: "user",
1046
- content: [{ type: "text", text: suggestion }],
1047
- });
1048
- }, className: "w-full rounded-lg border border-border px-3 py-2 text-left text-[13px] text-muted-foreground hover:bg-accent hover:text-foreground", children: suggestion }, suggestion))) }))] })) : (_jsxs("div", { className: "agent-thread-content flex flex-col gap-4 px-4 py-4", children: [_jsx(ThreadPrimitive.Messages, { components: {
1049
- UserMessage,
1050
- AssistantMessage,
1051
- } }), showContinue && !showRunningInUI && (_jsx("div", { className: "flex justify-center py-2", children: _jsx("button", { type: "button", onClick: () => {
1052
- setShowContinue(false);
1053
- addToQueue("Continue from where you left off.");
1054
- }, className: "rounded-lg border border-border bg-background px-4 py-2 text-sm font-medium text-foreground hover:bg-accent", children: "Continue" }) })), (isReconnecting || reconnectFrozen) &&
1055
- reconnectContent.length > 0 && (_jsx(ReconnectStreamMessage, { content: reconnectContent })), showRunningInUI && _jsx(ThinkingIndicator, {}), queuedMessages.map((msg) => {
1056
- const displayText = msg.text
1057
- .replace(/<context>[\s\S]*?<\/context>\n?/g, "")
1058
- .trim();
1059
- return (_jsx("div", { className: "flex justify-end group", children: _jsxs("div", { className: "relative max-w-[85%] rounded-lg bg-accent/50 text-foreground/60 px-3 py-2 text-sm leading-relaxed whitespace-pre-wrap break-words", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-[10px] text-muted-foreground mb-1 font-medium uppercase tracking-wide", children: [_jsx(IconClock, { className: "h-3 w-3" }), "Queued"] }), displayText, msg.images && msg.images.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-1.5 mt-1.5", children: msg.images.map((img, j) => (_jsx("img", { src: img, alt: "", className: "h-12 w-12 rounded object-cover border border-border/50" }, j))) })), _jsx("button", { type: "button", onClick: () => setQueuedMessages((prev) => prev.filter((m) => m.id !== msg.id)), "aria-label": "Remove from queue", className: "absolute -top-2 -right-2 flex h-5 w-5 items-center justify-center rounded-full border border-border bg-background text-muted-foreground opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:text-foreground hover:bg-accent shadow-sm", children: _jsx(IconX, { className: "h-3 w-3" }) })] }) }, msg.id));
1060
- })] })) }), showScrollToBottom && (_jsx("div", { className: "shrink-0 flex justify-center -mb-1", children: _jsx("button", { type: "button", onClick: scrollToBottom, className: "flex h-7 w-7 items-center justify-center rounded-full border border-border bg-background shadow-sm hover:bg-accent", "aria-label": "Scroll to bottom", children: _jsx(IconChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" }) }) })), composerSlot, _jsx("div", { className: "agent-composer-area shrink-0 px-3 py-2", children: _jsxs(ComposerPrimitive.Root, { className: "flex flex-col rounded-lg border border-input bg-background focus-within:ring-1 focus-within:ring-ring", children: [_jsx(ComposerAttachmentPreviewStrip, {}), _jsx(TiptapComposer, { focusRef: tiptapRef, placeholder: isRunning
1061
- ? queuedMessages.length > 0
1062
- ? `${queuedMessages.length} queued — type another...`
1063
- : "Queue a message..."
1064
- : undefined, onSubmit: isRunning
1065
- ? (text, references) => addToQueue(text, undefined, references.length > 0 ? references : undefined)
1066
- : undefined, onSlashCommand: onSlashCommand, execMode: execMode, onExecModeChange: onExecModeChange, extraActionButton: showRunningInUI ? (_jsx("button", { type: "button", onClick: () => {
1067
- // Nuclear stop: flip forceStopped so isRunning is false
1068
- // immediately. This unblocks submission even if the
1069
- // runtime or reconnect state is stuck.
1070
- setForceStopped(true);
1071
- if (isReconnecting) {
1072
- if (reconnectRunIdRef.current) {
1073
- fetch(`${apiUrl}/runs/${encodeURIComponent(reconnectRunIdRef.current)}/abort`, { method: "POST" });
1194
+ return (_jsx(ChatRunningContext.Provider, { value: isRunning, children: _jsxs("div", { className: cn("flex flex-1 flex-col h-full min-h-0 text-foreground", className), children: [showHeader && (_jsxs("div", { className: "flex h-11 shrink-0 items-center justify-between border-b border-border px-4", children: [_jsx("span", { className: "text-[13px] font-medium text-muted-foreground", children: "Agent" }), _jsx("div", { className: "flex items-center gap-1", children: onSwitchToCli && (_jsxs("button", { onClick: onSwitchToCli, className: "flex items-center gap-1 text-[12px] text-muted-foreground hover:text-foreground px-2 py-1 rounded-md hover:bg-accent", title: "Switch to CLI", children: [_jsx(IconTerminal, { className: "h-3.5 w-3.5" }), "CLI"] })) })] })), _jsx("div", { ref: scrollRef, className: "flex-1 overflow-y-auto overflow-x-hidden min-h-0", children: authError ? (_jsxs("div", { className: "flex flex-col items-center justify-center h-full px-4 gap-3", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-destructive/10", children: _jsx(IconLock, { className: "h-5 w-5 text-destructive" }) }), _jsxs("div", { className: "text-center max-w-[280px]", children: [_jsx("p", { className: "text-sm font-medium text-foreground mb-1", children: authError.sessionExpired
1195
+ ? "Session expired"
1196
+ : "Authentication required" }), _jsx("p", { className: "text-xs text-muted-foreground leading-relaxed", children: authError.sessionExpired ? ("Your session may have expired. Log out and log back in to reconnect.") : (_jsxs(_Fragment, { children: ["You need to log in to use the agent. If you're running locally, add", " ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded text-[10px]", children: "AUTH_MODE=local" }), " ", "to your", " ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded text-[10px]", children: ".env" }), " ", "file and restart the dev server."] })) })] }), _jsxs("div", { className: "flex gap-2", children: [authError.sessionExpired && (_jsx("button", { onClick: async () => {
1197
+ try {
1198
+ await fetch("/_agent-native/auth/logout", {
1199
+ method: "POST",
1200
+ });
1201
+ }
1202
+ catch { }
1203
+ window.location.reload();
1204
+ }, className: "text-xs text-destructive hover:text-destructive/80 px-3 py-1.5 rounded-md border border-destructive/30 hover:bg-destructive/10", children: "Log out" })), _jsx("button", { onClick: () => {
1205
+ setAuthError(null);
1206
+ window.location.reload();
1207
+ }, className: "text-xs text-muted-foreground hover:text-foreground px-3 py-1.5 rounded-md border border-border hover:bg-accent", children: "Retry" })] })] })) : missingApiKey ? (_jsx("div", { className: "flex flex-col items-center justify-center h-full px-2", children: _jsx(ApiKeySetupCard, { apiUrl: apiUrl }) })) : usageLimitReached ? (_jsx("div", { className: "flex flex-col items-center justify-center h-full px-2", children: _jsx(BuilderCtaCard, { reason: "usage_limit", usageCents: usageLimitReached.usageCents, limitCents: usageLimitReached.limitCents, apiUrl: apiUrl }) })) : isRestoring ? (_jsxs("div", { className: "flex flex-col gap-3 p-4", children: [_jsx("div", { className: "flex justify-end", children: _jsx("div", { className: "h-8 w-32 rounded-lg bg-muted animate-pulse" }) }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("div", { className: "h-4 w-48 rounded bg-muted animate-pulse" }), _jsx("div", { className: "h-4 w-64 rounded bg-muted animate-pulse" }), _jsx("div", { className: "h-4 w-40 rounded bg-muted animate-pulse" })] })] })) : messages.length === 0 && !isReconnecting ? (_jsxs("div", { className: "flex flex-col items-center justify-center gap-4 py-16 px-4 h-full", children: [_jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-muted", children: _jsx(IconMessage, { className: "h-5 w-5 text-muted-foreground" }) }), _jsx("p", { className: "text-sm text-muted-foreground text-center max-w-[240px]", children: emptyStateText ?? "How can I help you?" }), suggestions && suggestions.length > 0 && (_jsx("div", { className: "flex flex-col gap-1.5 w-full max-w-[280px]", children: suggestions.map((suggestion) => (_jsx("button", { onClick: () => {
1208
+ threadRuntime.append({
1209
+ role: "user",
1210
+ content: [{ type: "text", text: suggestion }],
1211
+ });
1212
+ }, className: "w-full rounded-lg border border-border px-3 py-2 text-left text-[13px] text-muted-foreground hover:bg-accent hover:text-foreground", children: suggestion }, suggestion))) }))] })) : (_jsxs("div", { className: "agent-thread-content flex flex-col gap-4 px-4 py-4", children: [_jsx(ThreadPrimitive.Messages, { components: {
1213
+ UserMessage,
1214
+ AssistantMessage,
1215
+ } }), showContinue && !showRunningInUI && (_jsx("div", { className: "flex justify-center py-2", children: _jsx("button", { type: "button", onClick: () => {
1216
+ setShowContinue(false);
1217
+ addToQueue("Continue from where you left off.");
1218
+ }, className: "rounded-lg border border-border bg-background px-4 py-2 text-sm font-medium text-foreground hover:bg-accent", children: "Continue" }) })), (isReconnecting || reconnectFrozen) &&
1219
+ reconnectContent.length > 0 && (_jsx(ReconnectStreamMessage, { content: reconnectContent })), showRunningInUI && (_jsx(ThinkingIndicator, { label: isReconnecting ? "Reconnecting" : "Thinking" })), queuedMessages.map((msg) => {
1220
+ const displayText = msg.text
1221
+ .replace(/<context>[\s\S]*?<\/context>\n?/g, "")
1222
+ .trim();
1223
+ return (_jsx("div", { className: "flex justify-end group", children: _jsxs("div", { className: "relative max-w-[85%] rounded-lg bg-accent/50 text-foreground/60 px-3 py-2 text-sm leading-relaxed whitespace-pre-wrap break-words", children: [_jsxs("div", { className: "flex items-center gap-1.5 text-[10px] text-muted-foreground mb-1 font-medium uppercase tracking-wide", children: [_jsx(IconClock, { className: "h-3 w-3" }), "Queued"] }), displayText, msg.images && msg.images.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-1.5 mt-1.5", children: msg.images.map((img, j) => (_jsx("img", { src: img, alt: "", className: "h-12 w-12 rounded object-cover border border-border/50" }, j))) })), _jsx("button", { type: "button", onClick: () => setQueuedMessages((prev) => prev.filter((m) => m.id !== msg.id)), "aria-label": "Remove from queue", className: "absolute -top-2 -right-2 flex h-5 w-5 items-center justify-center rounded-full border border-border bg-background text-muted-foreground opacity-0 group-hover:opacity-100 focus-visible:opacity-100 hover:text-foreground hover:bg-accent shadow-sm", children: _jsx(IconX, { className: "h-3 w-3" }) })] }) }, msg.id));
1224
+ })] })) }), showScrollToBottom && (_jsx("div", { className: "shrink-0 flex justify-center -mb-1", children: _jsx("button", { type: "button", onClick: scrollToBottom, className: "flex h-7 w-7 items-center justify-center rounded-full border border-border bg-background shadow-sm hover:bg-accent", "aria-label": "Scroll to bottom", children: _jsx(IconChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" }) }) })), composerSlot, _jsx("div", { className: "agent-composer-area shrink-0 px-3 py-2", children: _jsxs(ComposerPrimitive.Root, { className: "flex flex-col rounded-lg border border-input bg-background focus-within:ring-1 focus-within:ring-ring", children: [_jsx(ComposerAttachmentPreviewStrip, {}), _jsx(TiptapComposer, { focusRef: tiptapRef, placeholder: isRunning
1225
+ ? queuedMessages.length > 0
1226
+ ? `${queuedMessages.length} queued — type another...`
1227
+ : "Queue a message..."
1228
+ : undefined, onSubmit: isRunning
1229
+ ? (text, references) => addToQueue(text, undefined, references.length > 0 ? references : undefined)
1230
+ : undefined, onSlashCommand: onSlashCommand, execMode: execMode, onExecModeChange: onExecModeChange, extraActionButton: showRunningInUI ? (_jsx("button", { type: "button", onClick: () => {
1231
+ // Nuclear stop: flip forceStopped so isRunning is false
1232
+ // immediately. This unblocks submission even if the
1233
+ // runtime or reconnect state is stuck.
1234
+ setForceStopped(true);
1235
+ if (isReconnecting) {
1236
+ if (reconnectRunIdRef.current) {
1237
+ fetch(`${apiUrl}/runs/${encodeURIComponent(reconnectRunIdRef.current)}/abort`, { method: "POST" });
1238
+ }
1239
+ reconnectAbortRef.current?.abort();
1240
+ reconnectAbortRef.current = null;
1241
+ reconnectRunIdRef.current = null;
1242
+ setIsReconnecting(false);
1243
+ setReconnectFrozen(reconnectContent.length > 0);
1074
1244
  }
1075
- reconnectAbortRef.current?.abort();
1076
- reconnectAbortRef.current = null;
1077
- reconnectRunIdRef.current = null;
1078
- setIsReconnecting(false);
1079
- setReconnectFrozen(reconnectContent.length > 0);
1080
- }
1081
- threadRuntime.cancelRun();
1082
- window.dispatchEvent(new CustomEvent("builder.chatRunning", {
1083
- detail: {
1084
- isRunning: false,
1085
- tabId: tabId || threadId,
1086
- },
1087
- }));
1088
- }, className: "shrink-0 flex h-7 w-7 items-center justify-center rounded-md bg-muted text-foreground hover:bg-muted/80", title: "Stop generating", children: _jsx(IconPlayerStop, { className: "h-3.5 w-3.5" }) })) : undefined })] }) })] }));
1245
+ threadRuntime.cancelRun();
1246
+ window.dispatchEvent(new CustomEvent("builder.chatRunning", {
1247
+ detail: {
1248
+ isRunning: false,
1249
+ tabId: tabId || threadId,
1250
+ },
1251
+ }));
1252
+ }, className: "shrink-0 flex h-7 w-7 items-center justify-center rounded-md bg-muted text-foreground hover:bg-muted/80", title: "Stop generating", children: _jsx(IconPlayerStop, { className: "h-3.5 w-3.5" }) })) : undefined })] }) })] }) }));
1089
1253
  });
1090
1254
  export const AssistantChat = forwardRef(function AssistantChat({ apiUrl = "/_agent-native/agent-chat", tabId, threadId, ...props }, ref) {
1091
1255
  const adapter = useMemo(() => createAgentChatAdapter({ apiUrl, tabId, threadId }), [apiUrl, tabId, threadId]);