@agent-native/core 0.84.34 → 0.84.36

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 (230) hide show
  1. package/README.md +15 -51
  2. package/corpus/README.md +1 -1
  3. package/corpus/core/CHANGELOG.md +20 -0
  4. package/corpus/core/docs/content/actions.mdx +17 -24
  5. package/corpus/core/docs/content/agent-surfaces.mdx +59 -56
  6. package/corpus/core/docs/content/cloneable-saas.mdx +3 -3
  7. package/corpus/core/docs/content/faq.mdx +4 -4
  8. package/corpus/core/docs/content/getting-started.mdx +246 -204
  9. package/corpus/core/docs/content/key-concepts.mdx +9 -9
  10. package/corpus/core/docs/content/locales/ar-SA/cloneable-saas.mdx +3 -3
  11. package/corpus/core/docs/content/locales/ar-SA/faq.mdx +4 -4
  12. package/corpus/core/docs/content/locales/ar-SA/getting-started.mdx +148 -214
  13. package/corpus/core/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -9
  14. package/corpus/core/docs/content/locales/ar-SA/template-chat.mdx +2 -2
  15. package/corpus/core/docs/content/locales/ar-SA/using-your-agent.mdx +2 -2
  16. package/corpus/core/docs/content/locales/de-DE/cloneable-saas.mdx +3 -3
  17. package/corpus/core/docs/content/locales/de-DE/faq.mdx +4 -4
  18. package/corpus/core/docs/content/locales/de-DE/getting-started.mdx +145 -211
  19. package/corpus/core/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -9
  20. package/corpus/core/docs/content/locales/de-DE/template-chat.mdx +2 -2
  21. package/corpus/core/docs/content/locales/de-DE/using-your-agent.mdx +2 -2
  22. package/corpus/core/docs/content/locales/es-ES/cloneable-saas.mdx +3 -3
  23. package/corpus/core/docs/content/locales/es-ES/faq.mdx +4 -4
  24. package/corpus/core/docs/content/locales/es-ES/getting-started.mdx +148 -214
  25. package/corpus/core/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -9
  26. package/corpus/core/docs/content/locales/es-ES/template-chat.mdx +2 -2
  27. package/corpus/core/docs/content/locales/es-ES/using-your-agent.mdx +2 -2
  28. package/corpus/core/docs/content/locales/fr-FR/cloneable-saas.mdx +2 -2
  29. package/corpus/core/docs/content/locales/fr-FR/faq.mdx +4 -4
  30. package/corpus/core/docs/content/locales/fr-FR/getting-started.mdx +146 -212
  31. package/corpus/core/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -9
  32. package/corpus/core/docs/content/locales/fr-FR/template-chat.mdx +2 -2
  33. package/corpus/core/docs/content/locales/fr-FR/using-your-agent.mdx +2 -2
  34. package/corpus/core/docs/content/locales/hi-IN/cloneable-saas.mdx +3 -3
  35. package/corpus/core/docs/content/locales/hi-IN/faq.mdx +4 -4
  36. package/corpus/core/docs/content/locales/hi-IN/getting-started.mdx +149 -215
  37. package/corpus/core/docs/content/locales/hi-IN/pure-agent-apps.mdx +8 -13
  38. package/corpus/core/docs/content/locales/hi-IN/template-chat.mdx +2 -2
  39. package/corpus/core/docs/content/locales/hi-IN/using-your-agent.mdx +2 -2
  40. package/corpus/core/docs/content/locales/ja-JP/cloneable-saas.mdx +2 -2
  41. package/corpus/core/docs/content/locales/ja-JP/faq.mdx +4 -4
  42. package/corpus/core/docs/content/locales/ja-JP/getting-started.mdx +144 -210
  43. package/corpus/core/docs/content/locales/ja-JP/pure-agent-apps.mdx +8 -13
  44. package/corpus/core/docs/content/locales/ja-JP/template-chat.mdx +2 -2
  45. package/corpus/core/docs/content/locales/ja-JP/using-your-agent.mdx +2 -2
  46. package/corpus/core/docs/content/locales/ko-KR/cloneable-saas.mdx +3 -3
  47. package/corpus/core/docs/content/locales/ko-KR/faq.mdx +4 -4
  48. package/corpus/core/docs/content/locales/ko-KR/getting-started.mdx +146 -212
  49. package/corpus/core/docs/content/locales/ko-KR/pure-agent-apps.mdx +8 -13
  50. package/corpus/core/docs/content/locales/ko-KR/template-chat.mdx +2 -2
  51. package/corpus/core/docs/content/locales/ko-KR/using-your-agent.mdx +2 -2
  52. package/corpus/core/docs/content/locales/pt-BR/cloneable-saas.mdx +3 -3
  53. package/corpus/core/docs/content/locales/pt-BR/faq.mdx +4 -4
  54. package/corpus/core/docs/content/locales/pt-BR/getting-started.mdx +145 -211
  55. package/corpus/core/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -9
  56. package/corpus/core/docs/content/locales/pt-BR/template-chat.mdx +2 -2
  57. package/corpus/core/docs/content/locales/pt-BR/using-your-agent.mdx +2 -2
  58. package/corpus/core/docs/content/locales/zh-CN/cloneable-saas.mdx +3 -3
  59. package/corpus/core/docs/content/locales/zh-CN/faq.mdx +4 -4
  60. package/corpus/core/docs/content/locales/zh-CN/getting-started.mdx +158 -209
  61. package/corpus/core/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -9
  62. package/corpus/core/docs/content/locales/zh-CN/template-chat.mdx +2 -2
  63. package/corpus/core/docs/content/locales/zh-CN/using-your-agent.mdx +2 -2
  64. package/corpus/core/docs/content/locales/zh-TW/cloneable-saas.mdx +3 -3
  65. package/corpus/core/docs/content/locales/zh-TW/faq.mdx +4 -4
  66. package/corpus/core/docs/content/locales/zh-TW/getting-started.mdx +158 -212
  67. package/corpus/core/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -9
  68. package/corpus/core/docs/content/locales/zh-TW/template-chat.mdx +2 -2
  69. package/corpus/core/docs/content/locales/zh-TW/using-your-agent.mdx +2 -2
  70. package/corpus/core/docs/content/native-chat-ui.mdx +17 -32
  71. package/corpus/core/docs/content/pure-agent-apps.mdx +46 -26
  72. package/corpus/core/docs/content/template-chat.mdx +8 -8
  73. package/corpus/core/docs/content/using-your-agent.mdx +2 -2
  74. package/corpus/core/docs/content/what-is-agent-native.mdx +6 -6
  75. package/corpus/core/package.json +1 -1
  76. package/corpus/core/src/a2a/response-text.ts +10 -1
  77. package/corpus/core/src/agent/production-agent.ts +23 -20
  78. package/corpus/core/src/agent/run-manager.ts +46 -1
  79. package/corpus/core/src/agent/thread-data-builder.ts +15 -2
  80. package/corpus/core/src/cli/skills.ts +7 -9
  81. package/corpus/core/src/client/MultiTabAssistantChat.tsx +2 -0
  82. package/corpus/core/src/client/RunStuckBanner.tsx +172 -18
  83. package/corpus/core/src/client/blocks/library/wireframe-kit.tsx +57 -9
  84. package/corpus/core/src/client/blocks/library/wireframe.tsx +3 -18
  85. package/corpus/core/src/client/chat/widgets/builtin-tool-renderers.tsx +28 -32
  86. package/corpus/core/src/client/code-agent-chat-adapter.ts +8 -1
  87. package/corpus/core/src/client/sse-event-processor.ts +21 -3
  88. package/corpus/core/src/code-agents/transcript-normalizer.ts +31 -0
  89. package/corpus/core/src/db/schema.ts +5 -0
  90. package/corpus/core/src/server/agent-chat-plugin.ts +8 -2
  91. package/corpus/core/src/server/agent-teams.ts +24 -2
  92. package/corpus/core/src/styles/blocks.css +27 -6
  93. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -4
  94. package/corpus/templates/analytics/changelog/2026-07-01-smoother-chat-streaming.md +6 -0
  95. package/corpus/templates/content/actions/list-trashed-content-databases.ts +1 -1
  96. package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +7 -9
  97. package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +7 -9
  98. package/dist/a2a/response-text.d.ts +1 -0
  99. package/dist/a2a/response-text.d.ts.map +1 -1
  100. package/dist/a2a/response-text.js +8 -2
  101. package/dist/a2a/response-text.js.map +1 -1
  102. package/dist/agent/production-agent.d.ts.map +1 -1
  103. package/dist/agent/production-agent.js +31 -27
  104. package/dist/agent/production-agent.js.map +1 -1
  105. package/dist/agent/run-manager.d.ts +12 -0
  106. package/dist/agent/run-manager.d.ts.map +1 -1
  107. package/dist/agent/run-manager.js +32 -1
  108. package/dist/agent/run-manager.js.map +1 -1
  109. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  110. package/dist/agent/thread-data-builder.js +15 -2
  111. package/dist/agent/thread-data-builder.js.map +1 -1
  112. package/dist/cli/skills.d.ts +1 -1
  113. package/dist/cli/skills.d.ts.map +1 -1
  114. package/dist/cli/skills.js +7 -9
  115. package/dist/cli/skills.js.map +1 -1
  116. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  117. package/dist/client/MultiTabAssistantChat.js +1 -1
  118. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  119. package/dist/client/RunStuckBanner.d.ts +13 -1
  120. package/dist/client/RunStuckBanner.d.ts.map +1 -1
  121. package/dist/client/RunStuckBanner.js +123 -16
  122. package/dist/client/RunStuckBanner.js.map +1 -1
  123. package/dist/client/blocks/library/wireframe-kit.d.ts +6 -6
  124. package/dist/client/blocks/library/wireframe-kit.d.ts.map +1 -1
  125. package/dist/client/blocks/library/wireframe-kit.js +52 -5
  126. package/dist/client/blocks/library/wireframe-kit.js.map +1 -1
  127. package/dist/client/blocks/library/wireframe.d.ts.map +1 -1
  128. package/dist/client/blocks/library/wireframe.js +1 -11
  129. package/dist/client/blocks/library/wireframe.js.map +1 -1
  130. package/dist/client/chat/widgets/builtin-tool-renderers.d.ts.map +1 -1
  131. package/dist/client/chat/widgets/builtin-tool-renderers.js +27 -31
  132. package/dist/client/chat/widgets/builtin-tool-renderers.js.map +1 -1
  133. package/dist/client/code-agent-chat-adapter.d.ts.map +1 -1
  134. package/dist/client/code-agent-chat-adapter.js +5 -1
  135. package/dist/client/code-agent-chat-adapter.js.map +1 -1
  136. package/dist/client/sse-event-processor.d.ts.map +1 -1
  137. package/dist/client/sse-event-processor.js +21 -3
  138. package/dist/client/sse-event-processor.js.map +1 -1
  139. package/dist/code-agents/transcript-normalizer.d.ts.map +1 -1
  140. package/dist/code-agents/transcript-normalizer.js +24 -0
  141. package/dist/code-agents/transcript-normalizer.js.map +1 -1
  142. package/dist/collab/awareness.d.ts +2 -2
  143. package/dist/collab/awareness.d.ts.map +1 -1
  144. package/dist/collab/routes.d.ts +2 -2
  145. package/dist/db/schema.d.ts +2 -1
  146. package/dist/db/schema.d.ts.map +1 -1
  147. package/dist/db/schema.js +3 -2
  148. package/dist/db/schema.js.map +1 -1
  149. package/dist/notifications/routes.d.ts +3 -3
  150. package/dist/resources/handlers.d.ts +3 -3
  151. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  152. package/dist/server/agent-chat-plugin.js +2 -3
  153. package/dist/server/agent-chat-plugin.js.map +1 -1
  154. package/dist/server/agent-teams.d.ts.map +1 -1
  155. package/dist/server/agent-teams.js +17 -2
  156. package/dist/server/agent-teams.js.map +1 -1
  157. package/dist/server/transcribe-voice.d.ts +1 -1
  158. package/dist/styles/blocks.css +27 -6
  159. package/docs/content/actions.mdx +17 -24
  160. package/docs/content/agent-surfaces.mdx +59 -56
  161. package/docs/content/cloneable-saas.mdx +3 -3
  162. package/docs/content/faq.mdx +4 -4
  163. package/docs/content/getting-started.mdx +246 -204
  164. package/docs/content/key-concepts.mdx +9 -9
  165. package/docs/content/locales/ar-SA/cloneable-saas.mdx +3 -3
  166. package/docs/content/locales/ar-SA/faq.mdx +4 -4
  167. package/docs/content/locales/ar-SA/getting-started.mdx +148 -214
  168. package/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -9
  169. package/docs/content/locales/ar-SA/template-chat.mdx +2 -2
  170. package/docs/content/locales/ar-SA/using-your-agent.mdx +2 -2
  171. package/docs/content/locales/de-DE/cloneable-saas.mdx +3 -3
  172. package/docs/content/locales/de-DE/faq.mdx +4 -4
  173. package/docs/content/locales/de-DE/getting-started.mdx +145 -211
  174. package/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -9
  175. package/docs/content/locales/de-DE/template-chat.mdx +2 -2
  176. package/docs/content/locales/de-DE/using-your-agent.mdx +2 -2
  177. package/docs/content/locales/es-ES/cloneable-saas.mdx +3 -3
  178. package/docs/content/locales/es-ES/faq.mdx +4 -4
  179. package/docs/content/locales/es-ES/getting-started.mdx +148 -214
  180. package/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -9
  181. package/docs/content/locales/es-ES/template-chat.mdx +2 -2
  182. package/docs/content/locales/es-ES/using-your-agent.mdx +2 -2
  183. package/docs/content/locales/fr-FR/cloneable-saas.mdx +2 -2
  184. package/docs/content/locales/fr-FR/faq.mdx +4 -4
  185. package/docs/content/locales/fr-FR/getting-started.mdx +146 -212
  186. package/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -9
  187. package/docs/content/locales/fr-FR/template-chat.mdx +2 -2
  188. package/docs/content/locales/fr-FR/using-your-agent.mdx +2 -2
  189. package/docs/content/locales/hi-IN/cloneable-saas.mdx +3 -3
  190. package/docs/content/locales/hi-IN/faq.mdx +4 -4
  191. package/docs/content/locales/hi-IN/getting-started.mdx +149 -215
  192. package/docs/content/locales/hi-IN/pure-agent-apps.mdx +8 -13
  193. package/docs/content/locales/hi-IN/template-chat.mdx +2 -2
  194. package/docs/content/locales/hi-IN/using-your-agent.mdx +2 -2
  195. package/docs/content/locales/ja-JP/cloneable-saas.mdx +2 -2
  196. package/docs/content/locales/ja-JP/faq.mdx +4 -4
  197. package/docs/content/locales/ja-JP/getting-started.mdx +144 -210
  198. package/docs/content/locales/ja-JP/pure-agent-apps.mdx +8 -13
  199. package/docs/content/locales/ja-JP/template-chat.mdx +2 -2
  200. package/docs/content/locales/ja-JP/using-your-agent.mdx +2 -2
  201. package/docs/content/locales/ko-KR/cloneable-saas.mdx +3 -3
  202. package/docs/content/locales/ko-KR/faq.mdx +4 -4
  203. package/docs/content/locales/ko-KR/getting-started.mdx +146 -212
  204. package/docs/content/locales/ko-KR/pure-agent-apps.mdx +8 -13
  205. package/docs/content/locales/ko-KR/template-chat.mdx +2 -2
  206. package/docs/content/locales/ko-KR/using-your-agent.mdx +2 -2
  207. package/docs/content/locales/pt-BR/cloneable-saas.mdx +3 -3
  208. package/docs/content/locales/pt-BR/faq.mdx +4 -4
  209. package/docs/content/locales/pt-BR/getting-started.mdx +145 -211
  210. package/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -9
  211. package/docs/content/locales/pt-BR/template-chat.mdx +2 -2
  212. package/docs/content/locales/pt-BR/using-your-agent.mdx +2 -2
  213. package/docs/content/locales/zh-CN/cloneable-saas.mdx +3 -3
  214. package/docs/content/locales/zh-CN/faq.mdx +4 -4
  215. package/docs/content/locales/zh-CN/getting-started.mdx +158 -209
  216. package/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -9
  217. package/docs/content/locales/zh-CN/template-chat.mdx +2 -2
  218. package/docs/content/locales/zh-CN/using-your-agent.mdx +2 -2
  219. package/docs/content/locales/zh-TW/cloneable-saas.mdx +3 -3
  220. package/docs/content/locales/zh-TW/faq.mdx +4 -4
  221. package/docs/content/locales/zh-TW/getting-started.mdx +158 -212
  222. package/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -9
  223. package/docs/content/locales/zh-TW/template-chat.mdx +2 -2
  224. package/docs/content/locales/zh-TW/using-your-agent.mdx +2 -2
  225. package/docs/content/native-chat-ui.mdx +17 -32
  226. package/docs/content/pure-agent-apps.mdx +46 -26
  227. package/docs/content/template-chat.mdx +8 -8
  228. package/docs/content/using-your-agent.mdx +2 -2
  229. package/docs/content/what-is-agent-native.mdx +6 -6
  230. package/package.json +1 -1
@@ -38,21 +38,119 @@ export interface RunStuckBannerProps {
38
38
  * observability events (Sentry, PostHog) at the call site.
39
39
  */
40
40
  onStuckStateChange?: (state: RunStuckState) => void;
41
+ /**
42
+ * Automatically abort and retry once when the server reports a run is
43
+ * alive but has stopped making real progress. Manual controls remain visible
44
+ * as the fallback if the automatic recovery cannot clear the run.
45
+ */
46
+ autoRetry?: boolean;
47
+ /**
48
+ * Stable browser-tab/window id used to coordinate automatic retries across
49
+ * multiple mounted chat views for the same thread. A local id is generated
50
+ * when omitted.
51
+ */
52
+ autoRetryOwnerId?: string;
41
53
  /** Extra class on the outer container. */
42
54
  className?: string;
43
55
  }
44
56
 
57
+ const AUTO_RETRY_CLAIM_TTL_MS = 5 * 60 * 1000;
58
+
59
+ type BusyState = { type: "none" } | { type: "cancel" | "retry"; runId: string };
60
+
61
+ type MaybeLockManager = {
62
+ request<T>(
63
+ name: string,
64
+ options: { mode?: "exclusive" | "shared"; ifAvailable?: boolean },
65
+ callback: (lock: unknown) => T | Promise<T>,
66
+ ): Promise<T>;
67
+ };
68
+
69
+ function createAutoRetryOwnerId() {
70
+ const cryptoApi =
71
+ typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
72
+ ? crypto
73
+ : null;
74
+ return cryptoApi?.randomUUID() ?? `owner-${Math.random().toString(36)}`;
75
+ }
76
+
77
+ function autoRetryClaimKey(threadId: string, runId: string) {
78
+ return `agent-native:stuck-auto-retry:${threadId}:${runId}`;
79
+ }
80
+
81
+ function markAutoRetryClaim(key: string, ownerId: string) {
82
+ if (typeof window === "undefined") return true;
83
+ const now = Date.now();
84
+ try {
85
+ const raw = window.localStorage.getItem(key);
86
+ if (raw) {
87
+ const existing = JSON.parse(raw) as {
88
+ ownerId?: unknown;
89
+ expiresAt?: unknown;
90
+ };
91
+ const expiresAt =
92
+ typeof existing.expiresAt === "number" ? existing.expiresAt : 0;
93
+ if (expiresAt > now && existing.ownerId !== ownerId) return false;
94
+ }
95
+ window.localStorage.setItem(
96
+ key,
97
+ JSON.stringify({ ownerId, expiresAt: now + AUTO_RETRY_CLAIM_TTL_MS }),
98
+ );
99
+ const confirmed = JSON.parse(window.localStorage.getItem(key) ?? "{}") as {
100
+ ownerId?: unknown;
101
+ };
102
+ return confirmed.ownerId === ownerId;
103
+ } catch {
104
+ // Private browsing or disabled storage: fall back to in-tab retry. The
105
+ // server abort is idempotent, and manual controls remain available.
106
+ return true;
107
+ }
108
+ }
109
+
110
+ async function claimAutoRetryAttempt(
111
+ threadId: string | null | undefined,
112
+ runId: string,
113
+ ownerId: string,
114
+ ) {
115
+ if (!threadId) return true;
116
+ const key = autoRetryClaimKey(threadId, runId);
117
+ const locks =
118
+ typeof navigator !== "undefined"
119
+ ? (navigator as Navigator & { locks?: MaybeLockManager }).locks
120
+ : undefined;
121
+ if (locks?.request) {
122
+ try {
123
+ return await locks.request(
124
+ key,
125
+ { mode: "exclusive", ifAvailable: true },
126
+ (lock) => (lock ? markAutoRetryClaim(key, ownerId) : false),
127
+ );
128
+ } catch {
129
+ return markAutoRetryClaim(key, ownerId);
130
+ }
131
+ }
132
+ return markAutoRetryClaim(key, ownerId);
133
+ }
134
+
45
135
  export function RunStuckBanner({
46
136
  threadId,
47
137
  apiUrl,
48
138
  stuckThresholdMs,
49
139
  onRetry,
50
140
  onStuckStateChange,
141
+ autoRetry = false,
142
+ autoRetryOwnerId,
51
143
  className,
52
144
  }: RunStuckBannerProps) {
53
145
  const state = useRunStuckDetection({ threadId, stuckThresholdMs, apiUrl });
54
146
  const abortRun = useAbortRun(apiUrl);
55
- const [busy, setBusy] = useState<"none" | "cancel" | "retry">("none");
147
+ const [busy, setBusy] = useState<BusyState>({ type: "none" });
148
+ const autoRetriedRunIdsRef = useRef<Set<string>>(new Set());
149
+ const generatedOwnerIdRef = useRef<string | null>(null);
150
+ if (!generatedOwnerIdRef.current) {
151
+ generatedOwnerIdRef.current = createAutoRetryOwnerId();
152
+ }
153
+ const ownerId = autoRetryOwnerId ?? generatedOwnerIdRef.current;
56
154
 
57
155
  const lastReportedRef = useRef<{
58
156
  isStuck: boolean;
@@ -77,39 +175,92 @@ export function RunStuckBanner({
77
175
  }
78
176
  }, [state, onStuckStateChange, threadId]);
79
177
 
80
- // Reset the busy spinner once the underlying run has flipped away from
81
- // running no need to keep showing a stale "Retrying…" indicator.
178
+ // Reset the busy spinner once the underlying run is no longer the one we
179
+ // acted on. A recovery continuation can start quickly enough that polling
180
+ // never observes an idle state between run ids.
82
181
  useEffect(() => {
83
- if (state.status !== "running") {
84
- setBusy("none");
182
+ setBusy((current) => {
183
+ if (current.type === "none") return current;
184
+ if (state.status !== "running") return { type: "none" };
185
+ if (state.runId && state.runId !== current.runId) return { type: "none" };
186
+ return current;
187
+ });
188
+ }, [state.runId, state.status]);
189
+
190
+ useEffect(() => {
191
+ if (
192
+ !autoRetry ||
193
+ !state.isStuck ||
194
+ !state.runId ||
195
+ busy.type !== "none" ||
196
+ autoRetriedRunIdsRef.current.has(state.runId)
197
+ ) {
198
+ return;
85
199
  }
86
- }, [state.status]);
200
+
201
+ const runId = state.runId;
202
+ void claimAutoRetryAttempt(threadId, runId, ownerId).then((claimed) => {
203
+ autoRetriedRunIdsRef.current.add(runId);
204
+ if (!claimed) return;
205
+ setBusy({ type: "retry", runId });
206
+ trackEvent("agent_chat_stuck_auto_retry", {
207
+ runId,
208
+ threadId: threadId ?? null,
209
+ stuckSinceMs: state.stuckSinceMs ?? null,
210
+ });
211
+ void abortRun(runId, "auto_stuck_retry").then((aborted) => {
212
+ if (aborted) {
213
+ onRetry?.(aborted);
214
+ return;
215
+ }
216
+ setBusy((current) =>
217
+ current.type !== "none" && current.runId === runId
218
+ ? { type: "none" }
219
+ : current,
220
+ );
221
+ });
222
+ });
223
+ }, [
224
+ abortRun,
225
+ autoRetry,
226
+ busy,
227
+ onRetry,
228
+ ownerId,
229
+ state.isStuck,
230
+ state.runId,
231
+ state.stuckSinceMs,
232
+ threadId,
233
+ ]);
87
234
 
88
235
  if (!state.isStuck || !state.runId) return null;
89
236
 
90
237
  const handleCancel = async () => {
91
- if (!state.runId || busy !== "none") return;
92
- setBusy("cancel");
238
+ if (!state.runId || busy.type !== "none") return;
239
+ const runId = state.runId;
240
+ setBusy({ type: "cancel", runId });
93
241
  trackEvent("agent_chat_stuck_cancel", {
94
- runId: state.runId,
242
+ runId,
95
243
  threadId: threadId ?? null,
96
244
  stuckSinceMs: state.stuckSinceMs ?? null,
97
245
  });
98
- await abortRun(state.runId, "user_stuck_cancel");
246
+ await abortRun(runId, "user_stuck_cancel");
99
247
  };
100
248
 
101
249
  const handleRetry = async () => {
102
- if (!state.runId || busy !== "none") return;
103
- setBusy("retry");
250
+ if (!state.runId || busy.type !== "none") return;
251
+ const runId = state.runId;
252
+ setBusy({ type: "retry", runId });
104
253
  trackEvent("agent_chat_stuck_retry", {
105
- runId: state.runId,
254
+ runId,
106
255
  threadId: threadId ?? null,
107
256
  stuckSinceMs: state.stuckSinceMs ?? null,
108
257
  });
109
- const aborted = await abortRun(state.runId, "user_stuck_retry");
258
+ const aborted = await abortRun(runId, "user_stuck_retry");
110
259
  if (aborted) onRetry?.(aborted);
111
260
  };
112
261
 
262
+ const busyType = busy.type;
263
+
113
264
  const stuckSeconds =
114
265
  state.stuckSinceMs != null ? Math.floor(state.stuckSinceMs / 1000) : null;
115
266
 
@@ -134,16 +285,19 @@ export function RunStuckBanner({
134
285
  No progress
135
286
  {stuckSeconds != null ? ` for ${stuckSeconds}s` : ""}. The agent may
136
287
  have hit a server timeout or lost its connection.
288
+ {autoRetry && busyType === "retry"
289
+ ? " Retrying automatically now."
290
+ : ""}
137
291
  </span>
138
292
  </div>
139
293
  <div className="flex flex-wrap items-center gap-2">
140
294
  <button
141
295
  type="button"
142
296
  onClick={handleRetry}
143
- disabled={busy !== "none"}
297
+ disabled={busyType !== "none"}
144
298
  className="inline-flex h-7 cursor-pointer items-center gap-1.5 rounded-md bg-foreground px-2.5 text-[11px] font-medium text-background transition-colors hover:bg-foreground/90 disabled:cursor-not-allowed disabled:opacity-60"
145
299
  >
146
- {busy === "retry" ? (
300
+ {busyType === "retry" ? (
147
301
  <IconLoader2
148
302
  size={12}
149
303
  className="animate-spin"
@@ -155,10 +309,10 @@ export function RunStuckBanner({
155
309
  <button
156
310
  type="button"
157
311
  onClick={handleCancel}
158
- disabled={busy !== "none"}
312
+ disabled={busyType !== "none"}
159
313
  className="inline-flex h-7 cursor-pointer items-center gap-1.5 rounded-md border border-border bg-background px-2.5 text-[11px] font-medium text-foreground transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-60"
160
314
  >
161
- {busy === "cancel" ? (
315
+ {busyType === "cancel" ? (
162
316
  <IconLoader2
163
317
  size={12}
164
318
  className="animate-spin"
@@ -26,10 +26,10 @@ import type {
26
26
  * DECOUPLING: the only behavioral change from the plan copy is theme detection —
27
27
  * core blocks read `document.documentElement.classList.contains("dark")` (the
28
28
  * MermaidBlock precedent) instead of importing `next-themes`. Everything else
29
- * (the `.plan-wf` / `[data-rough]` class contract the rough overlay measures,
30
- * the `--wf-*` / `--ink` / `--paper` token names every primitive reads) is
31
- * preserved exactly, so the kit looks identical in plan and renders correctly in
32
- * any app once the matching tokens exist in `core/styles/blocks.css`.
29
+ * (the `.plan-wf` / `.wf-*` / `[data-rough]` class contract the rough overlay
30
+ * measures, the `--wf-*` / `--ink` / `--paper` token names every primitive
31
+ * reads) is preserved exactly, so the kit looks identical in plan and renders
32
+ * correctly in any app once the matching tokens exist in `core/styles/blocks.css`.
33
33
  */
34
34
 
35
35
  /* ========================================================================== */
@@ -211,7 +211,7 @@ export function Screen({
211
211
  // clipping. The frame shell (`ArtboardFrame`) owns the height policy; a
212
212
  // caller can still override via `style.height` for a fixed canvas.
213
213
  minHeight: "100%",
214
- background: V.paper,
214
+ background: "transparent",
215
215
  color: V.ink,
216
216
  fontFamily: V.hand,
217
217
  fontSize: V.fs,
@@ -1425,9 +1425,9 @@ const gen = rough.generator();
1425
1425
 
1426
1426
  type RoughPath = { d: string; stroke: string; strokeWidth: number };
1427
1427
 
1428
- /** The default selector used for HTML mockups: controls plus explicit opt-ins. */
1428
+ /** The default selector used for HTML mockups: standard wireframe primitives plus explicit opt-ins. */
1429
1429
  export const HTML_ROUGH_SELECTOR =
1430
- "[data-rough],button,input,textarea,select,hr";
1430
+ "[data-rough],button,input,textarea,select,hr,.wf-btn,.wf-card,.wf-box,.wf-pill,.wf-chip,.wf-icon-fallback,[style*='border:'],[style*='border-top:'],[style*='border-right:'],[style*='border-bottom:'],[style*='border-left:']";
1431
1431
 
1432
1432
  /** Stable per-element seed so a frame doesn't re-wobble on every measure. */
1433
1433
  function seedFrom(...parts: Array<string | number>): number {
@@ -1528,6 +1528,50 @@ function elementStroke(node: Element, fallback: string): string {
1528
1528
  return fallback;
1529
1529
  }
1530
1530
 
1531
+ function visibleBorderSides(
1532
+ node: Element,
1533
+ ): Array<"top" | "right" | "bottom" | "left"> {
1534
+ const cs = getComputedStyle(node);
1535
+ const sides: Array<"top" | "right" | "bottom" | "left"> = [];
1536
+ for (const [side, widthProp, colorProp] of [
1537
+ ["top", "borderTopWidth", "borderTopColor"],
1538
+ ["right", "borderRightWidth", "borderRightColor"],
1539
+ ["bottom", "borderBottomWidth", "borderBottomColor"],
1540
+ ["left", "borderLeftWidth", "borderLeftColor"],
1541
+ ] as const) {
1542
+ const width = parseFloat(cs[widthProp]);
1543
+ const color = cs[colorProp];
1544
+ if (
1545
+ width > 0 &&
1546
+ color &&
1547
+ color !== "rgba(0, 0, 0, 0)" &&
1548
+ color !== "transparent"
1549
+ ) {
1550
+ sides.push(side);
1551
+ }
1552
+ }
1553
+ return sides;
1554
+ }
1555
+
1556
+ function inferRoughKind(node: HTMLElement): string {
1557
+ const explicit = node.getAttribute("data-rough");
1558
+ if (explicit) return explicit;
1559
+ if (node.tagName === "HR") return "line:middle";
1560
+ const sides = visibleBorderSides(node);
1561
+ if (sides.length !== 1) return "rect";
1562
+ switch (sides[0]) {
1563
+ case "top":
1564
+ return "line:top";
1565
+ case "right":
1566
+ return "line:right";
1567
+ case "bottom":
1568
+ return "line:bottom";
1569
+ case "left":
1570
+ return "line:left";
1571
+ }
1572
+ return "rect";
1573
+ }
1574
+
1531
1575
  function build(
1532
1576
  scope: HTMLElement,
1533
1577
  opts: {
@@ -1602,7 +1646,7 @@ function build(
1602
1646
  const w = r.width / zoom;
1603
1647
  const h = r.height / zoom;
1604
1648
  if (w < 2 || h < 2) return;
1605
- const kind = node.getAttribute("data-rough") || "rect";
1649
+ const kind = inferRoughKind(node);
1606
1650
  const rawStroke = elementStroke(node, sketch);
1607
1651
  const stroke =
1608
1652
  sameColor(rawStroke, ink) || (line !== "" && sameColor(rawStroke, line))
@@ -1621,11 +1665,15 @@ function build(
1621
1665
  let drawable: unknown;
1622
1666
  if (kind === "ellipse") {
1623
1667
  drawable = gen.ellipse(x + w / 2, y + h / 2, w, h, o);
1668
+ } else if (kind === "line:left") {
1669
+ drawable = gen.line(x, y, x, y + h, o);
1624
1670
  } else if (kind === "line:right") {
1625
1671
  drawable = gen.line(x + w, y, x + w, y + h, o);
1626
1672
  } else if (kind === "line:bottom") {
1627
1673
  drawable = gen.line(x, y + h, x + w, y + h, o);
1628
- } else if (kind === "line:top" || node.tagName === "HR") {
1674
+ } else if (kind === "line:top") {
1675
+ drawable = gen.line(x, y, x + w, y, o);
1676
+ } else if (kind === "line:middle") {
1629
1677
  drawable = gen.line(x, y + h / 2, x + w, y + h / 2, o);
1630
1678
  } else {
1631
1679
  const cr = parseFloat(getComputedStyle(node).borderTopLeftRadius) || 0;
@@ -56,8 +56,8 @@ import {
56
56
  * - The plan-only prototype runtime, design-element selection, and legacy region
57
57
  * fallback are intentionally NOT ported; those are plan-canvas features, not
58
58
  * part of the document-block render. The kit element vocabulary, the `--wf-*`
59
- * token contract, and the `.plan-wf` / `[data-rough]` classes the overlay
60
- * measures are preserved exactly.
59
+ * token contract, and the `.plan-wf` / `.wf-*` / `[data-rough]` classes the
60
+ * overlay measures are preserved exactly.
61
61
  *
62
62
  * The section carries the app-neutral `an-block` class plus the legacy
63
63
  * `plan-block` class so plan renders byte-identically while any other app gets
@@ -164,12 +164,6 @@ function ArtboardFrame({
164
164
  const designMode = renderMode === "design";
165
165
  const sketchy = !designMode && style === "sketchy" && !skeleton;
166
166
  const roughEnabled = sketchy && roughOverlay;
167
- const paper = designMode
168
- ? "hsl(var(--background))"
169
- : "var(--plan-document, hsl(var(--background)))";
170
- const frameBorder = skeleton
171
- ? "var(--plan-placeholder-line, var(--plan-line, hsl(var(--border))))"
172
- : "var(--plan-line, hsl(var(--border)))";
173
167
 
174
168
  useEffect(() => {
175
169
  const element = fitRef.current;
@@ -243,7 +237,6 @@ function ArtboardFrame({
243
237
  // a fixed `canvasSize` locks the height for canvas artboards.
244
238
  ...(fixedHeight != null ? { height: fixedHeight } : { minHeight }),
245
239
  borderRadius: preset.radius,
246
- background: paper,
247
240
  ...(fitScale !== 1
248
241
  ? {
249
242
  transform: `scale(${fitScale})`,
@@ -266,18 +259,10 @@ function ArtboardFrame({
266
259
  >
267
260
  {render({ theme, style })}
268
261
  </div>
269
- {!roughEnabled && (
270
- <div
271
- className="pointer-events-none absolute inset-0"
272
- style={{
273
- borderRadius: preset.radius,
274
- border: `1.5px solid ${frameBorder}`,
275
- }}
276
- />
277
- )}
278
262
  <RoughOverlay
279
263
  scopeRef={ref}
280
264
  enabled={roughEnabled}
265
+ drawFrame={false}
281
266
  frameRadius={preset.radius}
282
267
  selector={selector}
283
268
  />
@@ -34,10 +34,37 @@ function isRecord(value: unknown): value is Record<string, unknown> {
34
34
  function normalizeActionDataWidgetResult(
35
35
  context: ToolRendererContext,
36
36
  ): DataWidgetResult | null {
37
+ const renderer = context.chatUI?.renderer;
38
+ if (isRecord(context.resultJson)) {
39
+ if (renderer === ACTION_CHAT_UI_DATA_TABLE_RENDERER) {
40
+ return normalizeDataWidgetResult({
41
+ ...context.resultJson,
42
+ widget: DATA_TABLE_WIDGET,
43
+ table: isRecord(context.resultJson.table)
44
+ ? context.resultJson.table
45
+ : context.resultJson,
46
+ });
47
+ }
48
+ if (renderer === ACTION_CHAT_UI_DATA_CHART_RENDERER) {
49
+ return normalizeDataWidgetResult({
50
+ ...context.resultJson,
51
+ widget: DATA_CHART_WIDGET,
52
+ chartSeries: isRecord(context.resultJson.chartSeries)
53
+ ? context.resultJson.chartSeries
54
+ : context.resultJson,
55
+ });
56
+ }
57
+ if (renderer === ACTION_CHAT_UI_DATA_INSIGHTS_RENDERER) {
58
+ return normalizeDataWidgetResult({
59
+ ...context.resultJson,
60
+ widget: DATA_INSIGHTS_WIDGET,
61
+ });
62
+ }
63
+ }
64
+
37
65
  const result = normalizeDataWidgetResult(context.resultJson);
38
66
  if (result) return result;
39
67
 
40
- const renderer = context.chatUI?.renderer;
41
68
  if (
42
69
  renderer === ACTION_CHAT_UI_DATA_WIDGET_RENDERER ||
43
70
  context.toolName === "render-data-widget"
@@ -46,37 +73,6 @@ function normalizeActionDataWidgetResult(
46
73
  if (argsResult) return argsResult;
47
74
  }
48
75
 
49
- if (!isRecord(context.resultJson)) return null;
50
-
51
- if (renderer === ACTION_CHAT_UI_DATA_TABLE_RENDERER) {
52
- return normalizeDataWidgetResult({
53
- widget: DATA_TABLE_WIDGET,
54
- table: isRecord(context.resultJson.table)
55
- ? context.resultJson.table
56
- : context.resultJson,
57
- display: isRecord(context.resultJson.display)
58
- ? context.resultJson.display
59
- : undefined,
60
- });
61
- }
62
- if (renderer === ACTION_CHAT_UI_DATA_CHART_RENDERER) {
63
- return normalizeDataWidgetResult({
64
- widget: DATA_CHART_WIDGET,
65
- chartSeries: isRecord(context.resultJson.chartSeries)
66
- ? context.resultJson.chartSeries
67
- : context.resultJson,
68
- display: isRecord(context.resultJson.display)
69
- ? context.resultJson.display
70
- : undefined,
71
- });
72
- }
73
- if (renderer === ACTION_CHAT_UI_DATA_INSIGHTS_RENDERER) {
74
- return normalizeDataWidgetResult({
75
- ...context.resultJson,
76
- widget: DATA_INSIGHTS_WIDGET,
77
- });
78
- }
79
-
80
76
  return null;
81
77
  }
82
78
 
@@ -180,7 +180,8 @@ export function createCodeAgentChatAdapter(
180
180
  }
181
181
 
182
182
  const content = codeAgentTranscriptEventsToContent(tailedEvents);
183
- if (content.length > 0 && nextEvents.length > 0) {
183
+ const sawClear = nextEvents.some(isAgentChatClearTranscriptEvent);
184
+ if ((content.length > 0 || sawClear) && nextEvents.length > 0) {
184
185
  yieldedContent = true;
185
186
  yield withRunMetadata({ content: [...content] }, runId);
186
187
  }
@@ -249,6 +250,12 @@ export function codeAgentTranscriptEventsToContent(
249
250
  return content;
250
251
  }
251
252
 
253
+ function isAgentChatClearTranscriptEvent(
254
+ event: CodeAgentChatTranscriptEvent | undefined,
255
+ ): boolean {
256
+ return event?.metadata?.agentChatEventType === "clear";
257
+ }
258
+
252
259
  function latestUserMessage(
253
260
  messages: ChatModelRunOptions["messages"],
254
261
  ): ChatModelRunOptions["messages"][number] | undefined {
@@ -698,11 +698,15 @@ export function processEvent(
698
698
  };
699
699
  } {
700
700
  if (ev.type === "clear") {
701
- // Server is retrying — discard partial text/tool output from the failed attempt
702
- content.length = 0;
701
+ // Server is retrying — discard rejected draft text and unfinished tool
702
+ // output while keeping completed tool results visible.
703
+ clearAssistantDraftContent(content);
703
704
  resetProcessEventState(state);
704
705
  dispatchActivityClear(tabId);
705
- return { action: "continue" };
706
+ return {
707
+ action: "yield",
708
+ result: { content: contentSnapshot(content) } as ChatModelRunResult,
709
+ };
706
710
  }
707
711
 
708
712
  if (ev.type === "text") {
@@ -1159,6 +1163,20 @@ export function processEvent(
1159
1163
  return { action: "continue" };
1160
1164
  }
1161
1165
 
1166
+ function clearAssistantDraftContent(content: ContentPart[]): void {
1167
+ for (let index = content.length - 1; index >= 0; index--) {
1168
+ const part = content[index];
1169
+ if (!part) continue;
1170
+ if (part.type === "text") {
1171
+ content.splice(index, 1);
1172
+ continue;
1173
+ }
1174
+ if (part.type === "tool-call" && part.result === undefined) {
1175
+ content.splice(index, 1);
1176
+ }
1177
+ }
1178
+ }
1179
+
1162
1180
  /**
1163
1181
  * Read and process SSE events from a ReadableStream response body.
1164
1182
  * Yields ChatModelRunResult for each meaningful event.
@@ -92,6 +92,12 @@ export function normalizeCodeAgentTranscript(
92
92
 
93
93
  for (const event of events) {
94
94
  const currentTurnIndex = Math.max(turnIndex, 0);
95
+ if (isAgentChatClearEvent(event)) {
96
+ clearNormalizedAgentDraftItems(items, currentTurnIndex);
97
+ hiddenEvents.push(event);
98
+ continue;
99
+ }
100
+
95
101
  if (event.kind === "user") {
96
102
  turnIndex = turnIndex < 0 ? (items.length === 0 ? 0 : 1) : turnIndex + 1;
97
103
  items.push(createUserTurn(event, turnIndex));
@@ -359,6 +365,31 @@ function findOpenToolEvent(
359
365
  return null;
360
366
  }
361
367
 
368
+ function isAgentChatClearEvent(event: CodeAgentTranscriptEvent): boolean {
369
+ return stringMetadata(event.metadata, "agentChatEventType") === "clear";
370
+ }
371
+
372
+ function clearNormalizedAgentDraftItems(
373
+ items: NormalizedCodeAgentTranscriptItem[],
374
+ turnIndex: number,
375
+ ): void {
376
+ for (let index = items.length - 1; index >= 0; index--) {
377
+ const item = items[index];
378
+ if (!item || item.turnIndex !== turnIndex) continue;
379
+ if (
380
+ item.type === "assistant" ||
381
+ item.type === "thinking" ||
382
+ item.type === "status"
383
+ ) {
384
+ items.splice(index, 1);
385
+ continue;
386
+ }
387
+ if (item.type === "tool" && item.state !== "completed") {
388
+ items.splice(index, 1);
389
+ }
390
+ }
391
+ }
392
+
362
393
  function suppressDuplicateFinalAssistantText(
363
394
  items: readonly NormalizedCodeAgentTranscriptItem[],
364
395
  hiddenEvents: CodeAgentTranscriptEvent[],
@@ -19,6 +19,7 @@
19
19
  import { sql } from "drizzle-orm";
20
20
  import {
21
21
  pgTable,
22
+ alias as pgAlias,
22
23
  index as pgIndex,
23
24
  text as pgText,
24
25
  integer as pgInteger,
@@ -28,6 +29,7 @@ import {
28
29
  } from "drizzle-orm/pg-core";
29
30
  import {
30
31
  sqliteTable,
32
+ alias as sqliteAlias,
31
33
  index as sqliteIndex,
32
34
  text as sqliteText,
33
35
  integer as sqliteInteger,
@@ -50,6 +52,9 @@ function pg(): boolean {
50
52
  export const table: typeof sqliteTable = ((...args: any[]) =>
51
53
  pg() ? (pgTable as any)(...args) : (sqliteTable as any)(...args)) as any;
52
54
 
55
+ export const alias: typeof sqliteAlias = ((...args: any[]) =>
56
+ pg() ? (pgAlias as any)(...args) : (sqliteAlias as any)(...args)) as any;
57
+
53
58
  export const index: typeof sqliteIndex = ((...args: any[]) =>
54
59
  pg() ? (pgIndex as any)(...args) : (sqliteIndex as any)(...args)) as any;
55
60
 
@@ -21,7 +21,10 @@ import {
21
21
  hasConfiguredA2ASecret,
22
22
  isA2AProductionRuntime,
23
23
  } from "../a2a/auth-policy.js";
24
- import { collectFinalResponseTextFromAgentEvents } from "../a2a/response-text.js";
24
+ import {
25
+ applyAgentTextEventToBuffer,
26
+ collectFinalResponseTextFromAgentEvents,
27
+ } from "../a2a/response-text.js";
25
28
  import { updateTaskStatusMessage } from "../a2a/task-store.js";
26
29
  import { ACTION_CHAT_UI_DATA_WIDGET_RENDERER } from "../action-ui.js";
27
30
  import type { ActionHttpConfig } from "../action.js";
@@ -5179,7 +5182,10 @@ export function createAgentChatPlugin(
5179
5182
  ],
5180
5183
  actions: mcpActions,
5181
5184
  send: (event) => {
5182
- if (event.type === "text") accumulatedText += event.text;
5185
+ accumulatedText = applyAgentTextEventToBuffer(
5186
+ accumulatedText,
5187
+ event,
5188
+ );
5183
5189
  },
5184
5190
  signal: controller.signal,
5185
5191
  },