@agent-native/core 0.164.3 → 0.164.11

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 (234) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +36 -47
  3. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +3 -0
  4. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +18 -1
  5. package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +256 -38
  6. package/corpus/templates/calendar/app/hooks/use-google-auth.ts +43 -3
  7. package/corpus/templates/calendar/app/i18n-data.ts +120 -0
  8. package/corpus/templates/calendar/app/lib/event-form-utils.ts +113 -0
  9. package/corpus/templates/calendar/server/handlers/google-auth.ts +119 -3
  10. package/corpus/templates/calendar/server/routes/[base]/_agent-native/google/add-account/auth-url.post.ts +1 -0
  11. package/corpus/templates/calendar/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
  12. package/corpus/templates/calendar/server/routes/_agent-native/google/add-account/auth-url.post.ts +1 -0
  13. package/corpus/templates/calendar/server/routes/_agent-native/google/auth-url.post.ts +1 -0
  14. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +1 -1
  15. package/corpus/templates/clips/app/components/player/video-player.tsx +12 -0
  16. package/corpus/templates/clips/app/routes/_app.dictate.tsx +18 -4
  17. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +114 -20
  18. package/corpus/templates/clips/app/routes/share.$shareId.tsx +86 -78
  19. package/corpus/templates/clips/changelog/2026-08-19-clips-nightly-builds-are-available-for-trying-the-latest-upd.md +6 -0
  20. package/corpus/templates/clips/changelog/2026-08-19-social-preview-video-frames.md +6 -0
  21. package/corpus/templates/clips/desktop/README.md +19 -4
  22. package/corpus/templates/clips/desktop/src/app.tsx +78 -16
  23. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +3 -2
  24. package/corpus/templates/clips/desktop/src-tauri/tauri.conf.json +1 -0
  25. package/corpus/templates/clips/server/lib/slack-unfurls.ts +7 -13
  26. package/corpus/templates/clips/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
  27. package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.get.ts +41 -3
  28. package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.post.ts +1 -0
  29. package/corpus/templates/clips/server/routes/_agent-native/google/callback.get.ts +22 -1
  30. package/corpus/templates/clips/server/routes/api/clips-updater.json.get.ts +41 -22
  31. package/corpus/templates/clips/shared/share-meta.ts +67 -3
  32. package/corpus/templates/content/actions/list-content-databases.ts +95 -1
  33. package/corpus/templates/content/changelog/2026-08-11-content-mutations-delegated-from-verified-slack-dms-now-use-.md +6 -0
  34. package/corpus/templates/content/shared/api.ts +11 -0
  35. package/corpus/templates/factory/AGENTS.md +1 -1
  36. package/corpus/templates/factory/actions/list-factory-automations.ts +4 -0
  37. package/corpus/templates/factory/actions/start-builder-for-item.ts +19 -17
  38. package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +100 -36
  39. package/corpus/templates/factory/app/i18n/en-US.ts +4 -0
  40. package/corpus/templates/factory/app/routes/factory.tsx +209 -46
  41. package/corpus/templates/factory/server/connectors/slack.ts +44 -2
  42. package/corpus/templates/factory/server/plugins/agent-chat.ts +9 -7
  43. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +13 -0
  44. package/corpus/templates/factory/server/triage/slack-client.ts +5 -0
  45. package/corpus/templates/factory/server/triage/slack-poller.ts +72 -4
  46. package/corpus/templates/mail/actions/view-screen.ts +1 -1
  47. package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +100 -18
  48. package/corpus/templates/mail/server/handlers/google-auth.ts +125 -24
  49. package/corpus/templates/mail/server/lib/gmail-query.ts +5 -2
  50. package/corpus/templates/mail/server/routes/[base]/_agent-native/google/add-account/auth-url.post.ts +1 -0
  51. package/corpus/templates/mail/server/routes/[base]/_agent-native/google/auth-url.post.ts +1 -0
  52. package/corpus/templates/mail/server/routes/_agent-native/google/add-account/auth-url.post.ts +1 -0
  53. package/corpus/templates/mail/server/routes/_agent-native/google/auth-url.post.ts +1 -0
  54. package/corpus/templates/plan/app/pages/PlansPage.tsx +0 -12
  55. package/corpus/templates/plan/changelog/2026-08-19-plan-prompts-now-guide-you-to-connect-ai-before-sending-and-.md +6 -0
  56. package/corpus/templates/slides/actions/approve-deck-access-request.ts +234 -0
  57. package/corpus/templates/slides/actions/get-deck-access-status.ts +35 -2
  58. package/corpus/templates/slides/actions/request-deck-access.ts +263 -64
  59. package/corpus/templates/slides/app/components/editor/MissingDeckAccessPane.tsx +196 -0
  60. package/corpus/templates/slides/app/components/editor/RequestDeckAccessDialog.tsx +101 -0
  61. package/corpus/templates/slides/app/hooks/use-deck-access.ts +7 -4
  62. package/corpus/templates/slides/app/i18n/en-US.ts +29 -6
  63. package/corpus/templates/slides/app/pages/DeckEditor.tsx +147 -169
  64. package/corpus/templates/slides/app/routes/access-request.approve.tsx +250 -0
  65. package/corpus/templates/slides/server/db/schema.ts +8 -0
  66. package/corpus/templates/slides/server/lib/access-request-email.ts +36 -0
  67. package/corpus/templates/slides/server/lib/emails.ts +26 -0
  68. package/corpus/templates/slides/server/plugins/agent-chat.ts +7 -0
  69. package/corpus/templates/slides/server/plugins/db.ts +50 -0
  70. package/corpus/templates/slides/shared/deck-access.ts +31 -0
  71. package/dist/a2a/artifact-response.d.ts +50 -0
  72. package/dist/a2a/artifact-response.js +266 -39
  73. package/dist/a2a/index.d.ts +2 -0
  74. package/dist/a2a/index.js +1 -0
  75. package/dist/a2a/task-store.d.ts +1 -0
  76. package/dist/a2a/task-store.js +1 -1
  77. package/dist/agent/observational-memory/store.d.ts +1 -0
  78. package/dist/agent/observational-memory/store.js +1 -1
  79. package/dist/agent/run-store.d.ts +1 -0
  80. package/dist/agent/run-store.js +1 -1
  81. package/dist/agent/tool-approval-store.d.ts +1 -0
  82. package/dist/agent/tool-approval-store.js +1 -1
  83. package/dist/application-state/store.d.ts +1 -0
  84. package/dist/application-state/store.js +1 -1
  85. package/dist/browser-sessions/store.d.ts +1 -0
  86. package/dist/browser-sessions/store.js +1 -1
  87. package/dist/checkpoints/store.d.ts +1 -0
  88. package/dist/checkpoints/store.js +1 -1
  89. package/dist/cli/code-agent-schedules.d.ts +11 -0
  90. package/dist/cli/code-agent-schedules.js +44 -5
  91. package/dist/client/AgentPanel.d.ts +5 -1
  92. package/dist/client/AgentPanel.js +2 -2
  93. package/dist/client/AssistantChat.d.ts +5 -1
  94. package/dist/client/AssistantChat.js +40 -6
  95. package/dist/client/EnvironmentBadge.d.ts +7 -13
  96. package/dist/client/EnvironmentBadge.js +43 -92
  97. package/dist/client/agent-chat/index.d.ts +1 -0
  98. package/dist/client/agent-chat/index.js +1 -0
  99. package/dist/client/agent-chat-adapter.js +76 -50
  100. package/dist/client/app-providers.js +4 -4
  101. package/dist/client/chat/message-components.d.ts +4 -0
  102. package/dist/client/chat/message-components.js +28 -1
  103. package/dist/client/chat/run-recovery.js +26 -0
  104. package/dist/client/chat-first/apps-rail.d.ts +1 -0
  105. package/dist/client/chat-first/apps-rail.js +28 -2
  106. package/dist/client/sse-event-processor.js +13 -8
  107. package/dist/client/use-active-agent-chat-run.d.ts +2 -0
  108. package/dist/client/use-active-agent-chat-run.js +22 -0
  109. package/dist/coding-tools/sandbox/executions-store.d.ts +1 -0
  110. package/dist/coding-tools/sandbox/executions-store.js +1 -1
  111. package/dist/collab/awareness-store.d.ts +1 -0
  112. package/dist/collab/awareness-store.js +1 -1
  113. package/dist/collab/routes.d.ts +1 -1
  114. package/dist/collab/storage.d.ts +1 -0
  115. package/dist/collab/storage.js +1 -1
  116. package/dist/db/client.js +3 -3
  117. package/dist/db/ddl-guard.js +8 -0
  118. package/dist/db/migration-runtime.d.ts +23 -0
  119. package/dist/db/migration-runtime.js +41 -0
  120. package/dist/db/migrations.d.ts +1 -2
  121. package/dist/db/migrations.js +2 -24
  122. package/dist/email-catalog/log.d.ts +1 -0
  123. package/dist/email-catalog/log.js +1 -1
  124. package/dist/guards/index.d.ts +1 -0
  125. package/dist/guards/index.js +1 -0
  126. package/dist/guards/release-schema-complete.d.ts +22 -0
  127. package/dist/guards/release-schema-complete.js +144 -0
  128. package/dist/integrations/a2a-continuations-store.d.ts +1 -0
  129. package/dist/integrations/a2a-continuations-store.js +1 -1
  130. package/dist/integrations/adapters/slack.js +58 -7
  131. package/dist/integrations/awaiting-input-store.d.ts +1 -0
  132. package/dist/integrations/awaiting-input-store.js +1 -1
  133. package/dist/integrations/config-store.d.ts +1 -0
  134. package/dist/integrations/config-store.js +1 -1
  135. package/dist/integrations/controls-store.d.ts +1 -0
  136. package/dist/integrations/controls-store.js +1 -1
  137. package/dist/integrations/identity-links-store.d.ts +1 -0
  138. package/dist/integrations/identity-links-store.js +1 -1
  139. package/dist/integrations/installations-store.d.ts +1 -0
  140. package/dist/integrations/installations-store.js +1 -1
  141. package/dist/integrations/remote-commands-store.d.ts +1 -0
  142. package/dist/integrations/remote-commands-store.js +1 -1
  143. package/dist/integrations/remote-devices-store.d.ts +1 -0
  144. package/dist/integrations/remote-devices-store.js +1 -1
  145. package/dist/integrations/remote-push-store.d.ts +1 -0
  146. package/dist/integrations/remote-push-store.js +1 -1
  147. package/dist/integrations/remote-run-events-store.d.ts +1 -0
  148. package/dist/integrations/remote-run-events-store.js +1 -1
  149. package/dist/integrations/scope-store.d.ts +1 -0
  150. package/dist/integrations/scope-store.js +1 -1
  151. package/dist/integrations/thread-mapping-store.d.ts +1 -0
  152. package/dist/integrations/thread-mapping-store.js +1 -1
  153. package/dist/integrations/usage-budget-store.d.ts +1 -0
  154. package/dist/integrations/usage-budget-store.js +1 -1
  155. package/dist/jobs/background-automation-runner.js +111 -9
  156. package/dist/jobs/run-history.d.ts +1 -0
  157. package/dist/jobs/run-history.js +1 -1
  158. package/dist/jobs/scheduler-health.d.ts +1 -0
  159. package/dist/jobs/scheduler-health.js +1 -1
  160. package/dist/mcp/approval-store.d.ts +1 -0
  161. package/dist/mcp/approval-store.js +1 -1
  162. package/dist/mcp/connect-store.d.ts +1 -0
  163. package/dist/mcp/connect-store.js +1 -1
  164. package/dist/mcp/oauth-store.d.ts +1 -0
  165. package/dist/mcp/oauth-store.js +1 -1
  166. package/dist/notifications/routes.d.ts +3 -3
  167. package/dist/notifications/store.d.ts +1 -0
  168. package/dist/notifications/store.js +1 -1
  169. package/dist/oauth-tokens/store.d.ts +1 -0
  170. package/dist/oauth-tokens/store.js +1 -1
  171. package/dist/observability/routes.d.ts +3 -3
  172. package/dist/progress/routes.d.ts +1 -1
  173. package/dist/progress/store.d.ts +1 -0
  174. package/dist/progress/store.js +1 -1
  175. package/dist/provider-api/actions/custom-provider-registration.d.ts +5 -5
  176. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  177. package/dist/provider-api/corpus-jobs-store.d.ts +1 -0
  178. package/dist/provider-api/corpus-jobs-store.js +1 -1
  179. package/dist/provider-api/custom-registry.d.ts +1 -0
  180. package/dist/provider-api/custom-registry.js +1 -1
  181. package/dist/provider-api/quota-governor.d.ts +1 -0
  182. package/dist/provider-api/quota-governor.js +1 -1
  183. package/dist/provider-api/staged-datasets-store.d.ts +1 -0
  184. package/dist/provider-api/staged-datasets-store.js +1 -1
  185. package/dist/resources/handlers.d.ts +3 -3
  186. package/dist/resources/store.d.ts +1 -0
  187. package/dist/resources/store.js +1 -1
  188. package/dist/scripts/dev/index.js +15 -1
  189. package/dist/search/index.js +3 -0
  190. package/dist/secrets/storage.d.ts +1 -0
  191. package/dist/secrets/storage.js +1 -1
  192. package/dist/server/agent-chat/action-filters-a2a.d.ts +2 -1
  193. package/dist/server/agent-chat/action-filters-a2a.js +10 -2
  194. package/dist/server/agent-chat-plugin.js +71 -20
  195. package/dist/server/agent-teams-run-queue.d.ts +1 -0
  196. package/dist/server/agent-teams-run-queue.js +1 -1
  197. package/dist/server/auth-marketing.js +3 -1
  198. package/dist/server/auth.d.ts +18 -1
  199. package/dist/server/auth.js +370 -154
  200. package/dist/server/embed-session.d.ts +1 -0
  201. package/dist/server/embed-session.js +1 -1
  202. package/dist/server/google-auth-plugin.js +3 -1
  203. package/dist/server/google-oauth.d.ts +10 -0
  204. package/dist/server/google-oauth.js +25 -1
  205. package/dist/server/identity-sso-store.d.ts +1 -0
  206. package/dist/server/identity-sso-store.js +1 -1
  207. package/dist/server/index.d.ts +1 -1
  208. package/dist/server/index.js +1 -1
  209. package/dist/server/onboarding-html.js +200 -31
  210. package/dist/server/poll.d.ts +2 -0
  211. package/dist/server/poll.js +19 -1
  212. package/dist/server/realtime-token.d.ts +1 -1
  213. package/dist/server/release-migrations.js +5 -0
  214. package/dist/server/release-schema.d.ts +46 -0
  215. package/dist/server/release-schema.js +307 -0
  216. package/dist/settings/store.d.ts +1 -0
  217. package/dist/settings/store.js +1 -1
  218. package/dist/shared/environment-lanes.d.ts +28 -0
  219. package/dist/shared/environment-lanes.js +61 -0
  220. package/dist/sharing/actions/share-resource.js +22 -2
  221. package/dist/styles/agent-native.css +17 -15
  222. package/dist/triggers/routes.d.ts +11 -0
  223. package/dist/triggers/routes.js +10 -0
  224. package/dist/usage/alerts-store.d.ts +1 -0
  225. package/dist/usage/alerts-store.js +1 -1
  226. package/dist/usage/store.d.ts +1 -0
  227. package/dist/usage/store.js +1 -1
  228. package/docs/content/drop-in-agent.mdx +4 -4
  229. package/docs/content/faq.mdx +15 -15
  230. package/docs/content/getting-started.mdx +173 -41
  231. package/docs/content/key-concepts.mdx +10 -10
  232. package/docs/content/using-your-agent.mdx +2 -2
  233. package/docs/content/what-is-agent-native.mdx +79 -228
  234. package/package.json +2 -2
@@ -70,7 +70,7 @@ async function fetchEmailList(
70
70
 
71
71
  const gmailQuery = buildGmailEmailSearchQuery({ view, q: search });
72
72
  const effectiveQuery =
73
- view === "all" && !search ? "" : gmailQuery || "in:inbox -in:sent";
73
+ view === "all" && !search ? "" : gmailQuery || "in:inbox";
74
74
  const { messages } = await listGmailMessages(
75
75
  effectiveQuery,
76
76
  50,
@@ -76,6 +76,25 @@ const ADD_ACCOUNT_POLL_ABORT_MS = Math.max(
76
76
  ADD_ACCOUNT_POLL_INTERVAL_MS * 4,
77
77
  );
78
78
 
79
+ function newDesktopOAuthVerifier(): string | null {
80
+ const cryptoApi = globalThis.crypto;
81
+ const randomUuid = cryptoApi?.randomUUID;
82
+ if (typeof randomUuid === "function") {
83
+ return `${randomUuid.call(cryptoApi)}${randomUuid.call(cryptoApi)}`;
84
+ }
85
+ if (typeof cryptoApi?.getRandomValues === "function") {
86
+ const bytes = new Uint8Array(32);
87
+ cryptoApi.getRandomValues(bytes);
88
+ let binary = "";
89
+ for (const byte of bytes) binary += String.fromCharCode(byte);
90
+ return btoa(binary)
91
+ .replace(/\+/g, "-")
92
+ .replace(/\//g, "_")
93
+ .replace(/=+$/, "");
94
+ }
95
+ return null;
96
+ }
97
+
79
98
  interface GoogleConnectBannerProps {
80
99
  variant?: "banner" | "hero";
81
100
  }
@@ -128,20 +147,81 @@ export function GoogleConnectBanner({
128
147
  const flowId =
129
148
  crypto.randomUUID?.() ||
130
149
  Math.random().toString(36).slice(2) + Date.now().toString(36);
150
+ const verifier = newDesktopOAuthVerifier();
151
+ if (!verifier) {
152
+ setDesktopAuthIssue({
153
+ code: "desktop_auth_start_failed",
154
+ message: t("mail.error.failedToConnect"),
155
+ });
156
+ return;
157
+ }
131
158
  const origin = window.location.origin;
132
159
  const endpoint = addAccount
133
160
  ? "/_agent-native/google/add-account/auth-url"
134
161
  : "/_agent-native/google/auth-url";
135
- const redirectUri = encodeURIComponent(
136
- oauthRedirectUri("/_agent-native/google/callback"),
137
- );
138
- window.open(
139
- `${origin}${agentNativePath(endpoint)}?redirect_uri=${redirectUri}&desktop=1&flow_id=${flowId}&redirect=1`,
140
- "_blank",
141
- );
162
+ const params = new URLSearchParams({
163
+ redirect_uri: oauthRedirectUri("/_agent-native/google/callback"),
164
+ desktop: "1",
165
+ flow_id: flowId,
166
+ });
167
+ const popup = window.open("", "_blank");
168
+ if (!popup) {
169
+ setDesktopAuthIssue({
170
+ code: "popup_blocked",
171
+ message: t("mail.error.failedToConnect"),
172
+ });
173
+ return;
174
+ }
175
+ let pollHandle: ReturnType<typeof setInterval> | null = null;
176
+ const stopPoll = () => {
177
+ if (!pollHandle) return;
178
+ clearInterval(pollHandle);
179
+ if (desktopPollRef.current === pollHandle) {
180
+ desktopPollRef.current = null;
181
+ }
182
+ pollHandle = null;
183
+ };
184
+ void fetch(`${origin}${agentNativePath(endpoint)}?${params.toString()}`, {
185
+ method: "POST",
186
+ credentials: "include",
187
+ headers: { "X-Agent-Native-Desktop-Verifier": verifier },
188
+ })
189
+ .then(async (response) => {
190
+ let data: {
191
+ url?: unknown;
192
+ message?: unknown;
193
+ error?: unknown;
194
+ };
195
+ try {
196
+ data = (await response.json()) as typeof data;
197
+ } catch {
198
+ throw new Error(t("mail.googleConnect.connectionFailed"));
199
+ }
200
+ if (!response.ok || typeof data?.url !== "string") {
201
+ const message =
202
+ typeof data.message === "string"
203
+ ? data.message
204
+ : typeof data.error === "string"
205
+ ? data.error
206
+ : t("mail.googleConnect.connectionFailed");
207
+ throw new Error(message);
208
+ }
209
+ popup.location.href = data.url;
210
+ })
211
+ .catch((error) => {
212
+ stopPoll();
213
+ popup.close();
214
+ setDesktopAuthIssue({
215
+ code: "desktop_auth_start_failed",
216
+ message:
217
+ error instanceof Error
218
+ ? error.message
219
+ : t("mail.googleConnect.connectionFailed"),
220
+ });
221
+ });
142
222
  const start = Date.now();
143
223
  if (desktopPollRef.current) clearInterval(desktopPollRef.current);
144
- desktopPollRef.current = setInterval(async () => {
224
+ pollHandle = setInterval(async () => {
145
225
  if (document.hidden || desktopPollInFlightRef.current) return;
146
226
  desktopPollInFlightRef.current = true;
147
227
  const controller = new AbortController();
@@ -153,18 +233,21 @@ export function GoogleConnectBanner({
153
233
  try {
154
234
  const res = await fetch(
155
235
  agentNativePath(
156
- `/_agent-native/auth/desktop-exchange?flow_id=${flowId}`,
236
+ `/_agent-native/auth/desktop-exchange?flow_id=${encodeURIComponent(flowId)}`,
157
237
  ),
158
- { signal: controller.signal },
238
+ {
239
+ headers: {
240
+ "X-Agent-Native-Desktop-Verifier": verifier,
241
+ },
242
+ signal: controller.signal,
243
+ },
159
244
  );
160
245
  const data = await res.json();
161
246
  if (data?.error) {
162
- clearInterval(desktopPollRef.current!);
163
- desktopPollRef.current = null;
247
+ stopPoll();
164
248
  setDesktopAuthIssue(data);
165
249
  } else if (data?.token) {
166
- clearInterval(desktopPollRef.current!);
167
- desktopPollRef.current = null;
250
+ stopPoll();
168
251
  await fetch(
169
252
  agentNativePath(
170
253
  `/_agent-native/auth/session?_session=${data.token}`,
@@ -176,13 +259,11 @@ export function GoogleConnectBanner({
176
259
  );
177
260
  window.location.reload();
178
261
  } else if (Date.now() - start > 120_000) {
179
- clearInterval(desktopPollRef.current!);
180
- desktopPollRef.current = null;
262
+ stopPoll();
181
263
  }
182
264
  } catch {
183
265
  if (Date.now() - start > 120_000) {
184
- clearInterval(desktopPollRef.current!);
185
- desktopPollRef.current = null;
266
+ stopPoll();
186
267
  }
187
268
  }
188
269
  } finally {
@@ -190,6 +271,7 @@ export function GoogleConnectBanner({
190
271
  desktopPollInFlightRef.current = false;
191
272
  }
192
273
  }, DESKTOP_POLL_INTERVAL_MS);
274
+ desktopPollRef.current = pollHandle;
193
275
  }
194
276
 
195
277
  const [authError, setAuthError] = useState<string | null>(null);
@@ -15,6 +15,9 @@ import {
15
15
  oauthCallbackResponse,
16
16
  oauthDesktopExchangePage,
17
17
  oauthErrorPage,
18
+ registerDesktopExchange,
19
+ prepareDesktopOAuthBrowserBinding,
20
+ matchesDesktopOAuthBrowserBinding,
18
21
  safeReturnPath,
19
22
  setDesktopExchange,
20
23
  setDesktopExchangeError,
@@ -22,6 +25,8 @@ import {
22
25
  import { getUserSetting, putUserSetting } from "@agent-native/core/settings";
23
26
  import {
24
27
  defineEventHandler,
28
+ getHeader,
29
+ getMethod,
25
30
  getQuery,
26
31
  setResponseStatus,
27
32
  type H3Event,
@@ -64,17 +69,25 @@ function googleOAuthErrorPayload(
64
69
  error?.name === "OAuthAccountOwnedByOtherUserError"
65
70
  ) {
66
71
  const account = error.accountId || "This Google account";
72
+ const message = `${account} is connected to another login. Sign out, then sign in with ${account}.`;
67
73
  return {
68
- message: `${account} is already connected to another login. Sign out, then sign in with the login that originally connected it.`,
74
+ message,
69
75
  code: "account_owner_mismatch",
70
76
  accountId: error.accountId,
71
77
  };
72
78
  }
73
79
 
74
80
  const msg = error?.message || "Unknown error";
81
+ const statusCode = Number(error?.statusCode || error?.status || 0);
75
82
  const isPermission =
83
+ error?.oauthErrorCode === "access_denied" ||
84
+ error?.oauthErrorCode === "forbidden" ||
85
+ error?.oauthErrorCode === "insufficient_scope" ||
76
86
  msg.includes("Insufficient Permission") ||
77
- msg.includes("insufficient_scope");
87
+ msg.includes("insufficient_scope") ||
88
+ /insufficient authentication scopes/i.test(msg) ||
89
+ (statusCode === 403 &&
90
+ /\b(?:scope|permission|forbidden|access denied)\b/i.test(msg));
78
91
  return {
79
92
  message: isPermission
80
93
  ? "This account wasn't granted the required permissions. Make sure you check all the permission boxes on the consent screen. If the app is in testing mode, add this email as a test user in Google Cloud Console."
@@ -99,6 +112,7 @@ function googleOAuthErrorResponse(
99
112
  export const getGoogleAuthUrl = defineEventHandler(async (event: H3Event) => {
100
113
  try {
101
114
  const q = getQuery(event);
115
+ const method = getMethod(event);
102
116
  const redirectUri = resolveOAuthRedirectUri(event);
103
117
  if (!redirectUri) {
104
118
  setResponseStatus(event, 400);
@@ -113,6 +127,34 @@ export const getGoogleAuthUrl = defineEventHandler(async (event: H3Event) => {
113
127
  const desktop =
114
128
  isElectron(event) || q.desktop === "1" || q.desktop === "true";
115
129
  const flowId = desktop ? (q.flow_id as string) || undefined : undefined;
130
+ if (method === "POST" && (!desktop || !flowId)) {
131
+ setResponseStatus(event, 400);
132
+ return { error: "Invalid desktop exchange challenge." };
133
+ }
134
+ let desktopVerifierHash: string | undefined;
135
+ let desktopBrowserBindingHash: string | undefined;
136
+ if (flowId) {
137
+ if (method !== "POST" || q.redirect !== undefined) {
138
+ setResponseStatus(event, 400);
139
+ return { error: "Invalid desktop exchange challenge." };
140
+ }
141
+ const verifier = getHeader(event, "x-agent-native-desktop-verifier");
142
+ if (!verifier || q.verifier !== undefined) {
143
+ setResponseStatus(event, 400);
144
+ return { error: "Invalid desktop exchange challenge." };
145
+ }
146
+ try {
147
+ desktopBrowserBindingHash = prepareDesktopOAuthBrowserBinding(event);
148
+ desktopVerifierHash = await registerDesktopExchange(
149
+ flowId,
150
+ verifier,
151
+ desktopBrowserBindingHash,
152
+ );
153
+ } catch {
154
+ setResponseStatus(event, 400);
155
+ return { error: "Invalid desktop exchange challenge." };
156
+ }
157
+ }
116
158
  const requestedReturn =
117
159
  typeof q.return === "string" ? safeReturnPath(q.return) : "/";
118
160
  const returnUrl = requestedReturn !== "/" ? requestedReturn : undefined;
@@ -127,6 +169,8 @@ export const getGoogleAuthUrl = defineEventHandler(async (event: H3Event) => {
127
169
  app: OAUTH_STATE_APP_ID,
128
170
  returnUrl,
129
171
  flowId,
172
+ desktopVerifierHash,
173
+ desktopBrowserBindingHash,
130
174
  });
131
175
  const url = await getAuthUrl(undefined, redirectUri, state, owner);
132
176
  if (q.redirect === "1") {
@@ -159,22 +203,28 @@ export const handleGoogleCallback = defineEventHandler(
159
203
  );
160
204
  desktop = state.desktop ?? false;
161
205
  flowId = state.flowId;
206
+ if (
207
+ flowId &&
208
+ (!state.desktopVerifierHash ||
209
+ !state.desktopBrowserBindingHash ||
210
+ !matchesDesktopOAuthBrowserBinding(
211
+ event,
212
+ state.desktopBrowserBindingHash,
213
+ ))
214
+ ) {
215
+ throw new Error("Desktop OAuth browser binding is invalid.");
216
+ }
162
217
 
163
218
  // Handle Google authorization errors (e.g. user denied access, invalid client)
164
219
  const googleError = query.error as string | undefined;
165
220
  if (googleError) {
166
221
  const errorDesc =
167
222
  (query.error_description as string | undefined) || googleError;
168
- const isPermission =
169
- googleError === "access_denied" ||
170
- errorDesc.includes("Insufficient Permission");
171
- const userMessage = isPermission
172
- ? "Access was denied. Make sure to check all the permission boxes on the consent screen. If the app is in testing mode, add this email as a test user in Google Cloud Console."
173
- : `Connection failed: ${errorDesc}`;
174
- return googleOAuthErrorResponse(event, new Error(userMessage), {
175
- desktop,
176
- flowId,
177
- });
223
+ return googleOAuthErrorResponse(
224
+ event,
225
+ Object.assign(new Error(errorDesc), { oauthErrorCode: googleError }),
226
+ { desktop, flowId },
227
+ );
178
228
  }
179
229
 
180
230
  const code = query.code as string;
@@ -183,7 +233,13 @@ export const handleGoogleCallback = defineEventHandler(
183
233
  return { error: "Missing authorization code" };
184
234
  }
185
235
 
186
- const { redirectUri, owner: stateOwner, addAccount, returnUrl } = state;
236
+ const {
237
+ redirectUri,
238
+ owner: stateOwner,
239
+ addAccount,
240
+ returnUrl,
241
+ desktopVerifierHash,
242
+ } = state;
187
243
 
188
244
  // 1. Resolve owner (needs session context, before exchangeCode)
189
245
  const { owner, hasProductionSession } = await resolveOAuthOwner(
@@ -249,7 +305,15 @@ export const handleGoogleCallback = defineEventHandler(
249
305
  });
250
306
 
251
307
  if (flowId && sessionToken) {
252
- setDesktopExchange(flowId, sessionToken, email);
308
+ if (!desktopVerifierHash) {
309
+ throw new Error("Missing desktop exchange challenge.");
310
+ }
311
+ await setDesktopExchange(
312
+ flowId,
313
+ sessionToken,
314
+ email,
315
+ desktopVerifierHash,
316
+ );
253
317
  }
254
318
 
255
319
  // 4. Return platform-appropriate response
@@ -284,6 +348,7 @@ export const getGoogleAddAccountUrl = defineEventHandler(
284
348
  }
285
349
  try {
286
350
  const q = getQuery(event);
351
+ const method = getMethod(event);
287
352
  const redirectUri = resolveOAuthRedirectUri(event);
288
353
  if (!redirectUri) {
289
354
  setResponseStatus(event, 400);
@@ -295,6 +360,34 @@ export const getGoogleAddAccountUrl = defineEventHandler(
295
360
  const desktop =
296
361
  isElectron(event) || q.desktop === "1" || q.desktop === "true";
297
362
  const flowId = desktop ? (q.flow_id as string) || undefined : undefined;
363
+ if (method === "POST" && (!desktop || !flowId)) {
364
+ setResponseStatus(event, 400);
365
+ return { error: "Invalid desktop exchange challenge." };
366
+ }
367
+ let desktopVerifierHash: string | undefined;
368
+ let desktopBrowserBindingHash: string | undefined;
369
+ if (flowId) {
370
+ if (method !== "POST" || q.redirect !== undefined) {
371
+ setResponseStatus(event, 400);
372
+ return { error: "Invalid desktop exchange challenge." };
373
+ }
374
+ const verifier = getHeader(event, "x-agent-native-desktop-verifier");
375
+ if (!verifier || q.verifier !== undefined) {
376
+ setResponseStatus(event, 400);
377
+ return { error: "Invalid desktop exchange challenge." };
378
+ }
379
+ try {
380
+ desktopBrowserBindingHash = prepareDesktopOAuthBrowserBinding(event);
381
+ desktopVerifierHash = await registerDesktopExchange(
382
+ flowId,
383
+ verifier,
384
+ desktopBrowserBindingHash,
385
+ );
386
+ } catch {
387
+ setResponseStatus(event, 400);
388
+ return { error: "Invalid desktop exchange challenge." };
389
+ }
390
+ }
298
391
  const state = encodeOAuthState({
299
392
  redirectUri,
300
393
  owner: session.email,
@@ -302,6 +395,8 @@ export const getGoogleAddAccountUrl = defineEventHandler(
302
395
  addAccount: true,
303
396
  app: OAUTH_STATE_APP_ID,
304
397
  flowId,
398
+ desktopVerifierHash,
399
+ desktopBrowserBindingHash,
305
400
  });
306
401
  const url = await getAuthUrl(
307
402
  undefined,
@@ -333,22 +428,28 @@ export const handleGoogleAddAccountCallback = defineEventHandler(
333
428
  );
334
429
  desktop = state.desktop ?? false;
335
430
  flowId = state.flowId;
431
+ if (
432
+ flowId &&
433
+ (!state.desktopVerifierHash ||
434
+ !state.desktopBrowserBindingHash ||
435
+ !matchesDesktopOAuthBrowserBinding(
436
+ event,
437
+ state.desktopBrowserBindingHash,
438
+ ))
439
+ ) {
440
+ throw new Error("Desktop OAuth browser binding is invalid.");
441
+ }
336
442
 
337
443
  // Handle Google authorization errors (e.g. user denied access, invalid client)
338
444
  const googleError = query.error as string | undefined;
339
445
  if (googleError) {
340
446
  const errorDesc =
341
447
  (query.error_description as string | undefined) || googleError;
342
- const isPermission =
343
- googleError === "access_denied" ||
344
- errorDesc.includes("Insufficient Permission");
345
- const userMessage = isPermission
346
- ? "Access was denied. Make sure to check all the permission boxes on the consent screen. If the app is in testing mode, add this email as a test user in Google Cloud Console."
347
- : `Connection failed: ${errorDesc}`;
348
- return googleOAuthErrorResponse(event, new Error(userMessage), {
349
- desktop,
350
- flowId,
351
- });
448
+ return googleOAuthErrorResponse(
449
+ event,
450
+ Object.assign(new Error(errorDesc), { oauthErrorCode: googleError }),
451
+ { desktop, flowId },
452
+ );
352
453
  }
353
454
 
354
455
  const { redirectUri, owner: stateOwner } = state;
@@ -6,8 +6,11 @@ import { isSelfAddressedThread } from "@shared/self-notes.js";
6
6
  import type { EmailMessage } from "@shared/types.js";
7
7
 
8
8
  export const VIEW_QUERIES: Record<string, string> = {
9
- inbox: "in:inbox -in:sent",
10
- unread: "is:unread in:inbox -in:sent",
9
+ // Keep sent replies in a matching Gmail thread. The local thread filter
10
+ // below excludes sent-only threads while preserving received inbox threads
11
+ // whose latest message was sent by the user.
12
+ inbox: "in:inbox",
13
+ unread: "is:unread in:inbox",
11
14
  starred: "is:starred",
12
15
  sent: "in:sent",
13
16
  drafts: "in:drafts",
@@ -0,0 +1 @@
1
+ export { default } from "../../../../_agent-native/google/add-account/auth-url.post.js";
@@ -0,0 +1 @@
1
+ export { default } from "../../../_agent-native/google/auth-url.post.js";
@@ -0,0 +1 @@
1
+ export { getGoogleAddAccountUrl as default } from "../../../../handlers/google-auth.js";
@@ -0,0 +1 @@
1
+ export { getGoogleAuthUrl as default } from "../../../handlers/google-auth.js";
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  SIDEBAR_STATE_CHANGE_EVENT,
3
- BuilderSetupCard,
4
3
  sendToAgentChat,
5
4
  setAgentChatContextItem,
6
5
  useAgentEngineConfigured,
@@ -8466,23 +8465,12 @@ function CreatePlanDialog({
8466
8465
  </DialogDescription>
8467
8466
  </DialogHeader>
8468
8467
  <div className="grid gap-3">
8469
- {canCreate && agentMissing ? (
8470
- <BuilderSetupCard
8471
- fullWidth
8472
- onConnected={() =>
8473
- window.dispatchEvent(
8474
- new Event("agent-engine:configured-changed"),
8475
- )
8476
- }
8477
- />
8478
- ) : null}
8479
8468
  <div className="rounded-xl border border-border bg-background p-2 shadow-sm">
8480
8469
  <PromptComposer
8481
8470
  autoFocus
8482
8471
  disabled={composerLocked}
8483
8472
  attachmentsEnabled={false}
8484
8473
  showModelSelector={false}
8485
- modelStatusChecksEnabled={false}
8486
8474
  placeholder={t("plansPage.create.placeholder")}
8487
8475
  draftScope="plans:create-plan"
8488
8476
  initialText={promptSeed}
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-19
4
+ ---
5
+
6
+ Plan prompts now guide you to connect AI before sending and offer a clear retry after setup.