@agent-native/core 0.131.9 → 0.132.0

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 (292) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +99 -0
  3. package/corpus/core/docs/content/durable-background-runs.mdx +15 -6
  4. package/corpus/core/docs/design/durable-agent-runs.md +15 -11
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/a2a/activity.ts +127 -10
  7. package/corpus/core/src/a2a/index.ts +3 -0
  8. package/corpus/core/src/agent/durable-background.ts +38 -28
  9. package/corpus/core/src/agent/harness/background.ts +12 -2
  10. package/corpus/core/src/agent/production-agent.ts +18 -15
  11. package/corpus/core/src/agent/run-store.ts +45 -3
  12. package/corpus/core/src/application-state/handlers.ts +45 -0
  13. package/corpus/core/src/application-state/index.ts +3 -0
  14. package/corpus/core/src/application-state/store.ts +28 -11
  15. package/corpus/core/src/audit/redact.ts +104 -18
  16. package/corpus/core/src/client/AgentPanel.tsx +2 -7
  17. package/corpus/core/src/client/MultiTabAssistantChat.tsx +3 -0
  18. package/corpus/core/src/client/RunStuckBanner.tsx +29 -6
  19. package/corpus/core/src/client/agent-chat.ts +4 -9
  20. package/corpus/core/src/client/application-state.ts +115 -12
  21. package/corpus/core/src/client/dynamic-suggestions.ts +4 -12
  22. package/corpus/core/src/client/guided-questions.tsx +12 -23
  23. package/corpus/core/src/db/client.ts +148 -28
  24. package/corpus/core/src/db/create-get-db.ts +39 -6
  25. package/corpus/core/src/db/runtime-diagnostics.ts +41 -6
  26. package/corpus/core/src/deploy/build.ts +26 -33
  27. package/corpus/core/src/feature-flags/store.ts +10 -5
  28. package/corpus/core/src/integrations/a2a-continuations-store.ts +11 -0
  29. package/corpus/core/src/integrations/config-store.ts +13 -0
  30. package/corpus/core/src/integrations/google-docs-poller.ts +26 -2
  31. package/corpus/core/src/mcp-client/routes.ts +33 -2
  32. package/corpus/core/src/onboarding/plugin.ts +31 -25
  33. package/corpus/core/src/org/accept-pending.ts +2 -0
  34. package/corpus/core/src/org/auto-join-domain.ts +2 -0
  35. package/corpus/core/src/org/context.ts +35 -10
  36. package/corpus/core/src/org/handlers.ts +6 -0
  37. package/corpus/core/src/org/request-org-cache.ts +71 -0
  38. package/corpus/core/src/resources/store.ts +10 -9
  39. package/corpus/core/src/scripts/runner.ts +6 -3
  40. package/corpus/core/src/secrets/storage.ts +97 -5
  41. package/corpus/core/src/server/action-change.ts +33 -0
  42. package/corpus/core/src/server/action-routes.ts +11 -9
  43. package/corpus/core/src/server/agent-teams.ts +10 -2
  44. package/corpus/core/src/server/better-auth-instance.ts +39 -12
  45. package/corpus/core/src/server/core-routes-plugin.ts +19 -2
  46. package/corpus/core/src/server/credential-provider.ts +57 -14
  47. package/corpus/core/src/server/http-response-telemetry.ts +1 -0
  48. package/corpus/core/src/server/poll.ts +95 -57
  49. package/corpus/core/src/server/voice-providers-status.ts +19 -4
  50. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +38 -0
  51. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1 -1
  52. package/corpus/templates/analytics/actions/ensure-native-v2-dashboards.ts +299 -0
  53. package/corpus/templates/analytics/actions/list-native-v2-dashboard-templates.ts +34 -0
  54. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +82 -0
  55. package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +2 -2
  56. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +40 -34
  57. package/corpus/templates/analytics/app/hooks/use-user-pref.ts +3 -3
  58. package/corpus/templates/analytics/app/i18n-data.ts +40 -0
  59. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +18 -10
  60. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +14 -0
  61. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +1 -0
  62. package/corpus/templates/analytics/app/routes/chart.tsx +3 -0
  63. package/corpus/templates/analytics/changelog/2026-07-30-analytics-dashboards-now-expose-native-funnel-heatmap-callou.md +6 -0
  64. package/corpus/templates/analytics/changelog/2026-07-30-analytics-now-ships-reusable-native-v2-dashboard-manifests-w.md +6 -0
  65. package/corpus/templates/analytics/server/lib/dashboard-report-render.ts +81 -1
  66. package/corpus/templates/analytics/server/lib/native-v2-dashboards.ts +457 -0
  67. package/corpus/templates/analytics/server/lib/report-chart-svg.ts +73 -2
  68. package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -1
  69. package/corpus/templates/analytics/shared/dashboard-funnel.ts +83 -0
  70. package/corpus/templates/content/changelog/2026-07-30-long-running-delegated-requests-now-continue-reliably.md +6 -0
  71. package/corpus/templates/content/netlify.toml +3 -0
  72. package/corpus/templates/content/server/plugins/agent-chat.ts +1 -0
  73. package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +2 -1
  74. package/corpus/templates/slides/actions/patch-deck.ts +25 -1
  75. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +17 -4
  76. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +9 -1
  77. package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
  78. package/corpus/templates/slides/app/context/DeckContext.tsx +11 -3
  79. package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +39 -11
  80. package/corpus/templates/slides/app/pages/Index.tsx +2 -2
  81. package/corpus/templates/slides/changelog/2026-07-30-slide-previews-keep-their-aspect-ratio-isolate-embedded-styl.md +6 -0
  82. package/corpus/templates/slides/changelog/2026-07-30-slides-no-longer-lose-decks-while-organization-access-is-loa.md +6 -0
  83. package/corpus/toolkit/CHANGELOG.md +8 -0
  84. package/corpus/toolkit/package.json +1 -1
  85. package/corpus/toolkit/src/composer/runtime-adapters.tsx +16 -11
  86. package/dist/a2a/activity.d.ts +22 -4
  87. package/dist/a2a/activity.d.ts.map +1 -1
  88. package/dist/a2a/activity.js +97 -11
  89. package/dist/a2a/activity.js.map +1 -1
  90. package/dist/a2a/index.d.ts +1 -1
  91. package/dist/a2a/index.d.ts.map +1 -1
  92. package/dist/a2a/index.js +1 -1
  93. package/dist/a2a/index.js.map +1 -1
  94. package/dist/agent/durable-background.d.ts +13 -8
  95. package/dist/agent/durable-background.d.ts.map +1 -1
  96. package/dist/agent/durable-background.js +36 -28
  97. package/dist/agent/durable-background.js.map +1 -1
  98. package/dist/agent/harness/background.d.ts.map +1 -1
  99. package/dist/agent/harness/background.js +12 -2
  100. package/dist/agent/harness/background.js.map +1 -1
  101. package/dist/agent/production-agent.d.ts.map +1 -1
  102. package/dist/agent/production-agent.js +18 -15
  103. package/dist/agent/production-agent.js.map +1 -1
  104. package/dist/agent/run-store.d.ts +2 -0
  105. package/dist/agent/run-store.d.ts.map +1 -1
  106. package/dist/agent/run-store.js +44 -3
  107. package/dist/agent/run-store.js.map +1 -1
  108. package/dist/application-state/handlers.d.ts +15 -0
  109. package/dist/application-state/handlers.d.ts.map +1 -1
  110. package/dist/application-state/handlers.js +42 -2
  111. package/dist/application-state/handlers.js.map +1 -1
  112. package/dist/application-state/index.d.ts +2 -2
  113. package/dist/application-state/index.d.ts.map +1 -1
  114. package/dist/application-state/index.js +2 -2
  115. package/dist/application-state/index.js.map +1 -1
  116. package/dist/application-state/store.d.ts +11 -0
  117. package/dist/application-state/store.d.ts.map +1 -1
  118. package/dist/application-state/store.js +23 -10
  119. package/dist/application-state/store.js.map +1 -1
  120. package/dist/audit/redact.d.ts +25 -2
  121. package/dist/audit/redact.d.ts.map +1 -1
  122. package/dist/audit/redact.js +85 -21
  123. package/dist/audit/redact.js.map +1 -1
  124. package/dist/client/AgentPanel.d.ts.map +1 -1
  125. package/dist/client/AgentPanel.js +2 -7
  126. package/dist/client/AgentPanel.js.map +1 -1
  127. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  128. package/dist/client/MultiTabAssistantChat.js +1 -1
  129. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  130. package/dist/client/RunStuckBanner.d.ts +11 -1
  131. package/dist/client/RunStuckBanner.d.ts.map +1 -1
  132. package/dist/client/RunStuckBanner.js +20 -7
  133. package/dist/client/RunStuckBanner.js.map +1 -1
  134. package/dist/client/agent-chat.d.ts.map +1 -1
  135. package/dist/client/agent-chat.js +4 -6
  136. package/dist/client/agent-chat.js.map +1 -1
  137. package/dist/client/application-state.d.ts +11 -0
  138. package/dist/client/application-state.d.ts.map +1 -1
  139. package/dist/client/application-state.js +87 -9
  140. package/dist/client/application-state.js.map +1 -1
  141. package/dist/client/dynamic-suggestions.d.ts.map +1 -1
  142. package/dist/client/dynamic-suggestions.js +4 -13
  143. package/dist/client/dynamic-suggestions.js.map +1 -1
  144. package/dist/client/guided-questions.d.ts.map +1 -1
  145. package/dist/client/guided-questions.js +6 -22
  146. package/dist/client/guided-questions.js.map +1 -1
  147. package/dist/collab/awareness.d.ts +2 -2
  148. package/dist/collab/awareness.d.ts.map +1 -1
  149. package/dist/db/client.d.ts +57 -10
  150. package/dist/db/client.d.ts.map +1 -1
  151. package/dist/db/client.js +122 -31
  152. package/dist/db/client.js.map +1 -1
  153. package/dist/db/create-get-db.d.ts.map +1 -1
  154. package/dist/db/create-get-db.js +31 -7
  155. package/dist/db/create-get-db.js.map +1 -1
  156. package/dist/db/runtime-diagnostics.d.ts +1 -0
  157. package/dist/db/runtime-diagnostics.d.ts.map +1 -1
  158. package/dist/db/runtime-diagnostics.js +31 -6
  159. package/dist/db/runtime-diagnostics.js.map +1 -1
  160. package/dist/deploy/build.d.ts +5 -20
  161. package/dist/deploy/build.d.ts.map +1 -1
  162. package/dist/deploy/build.js +18 -30
  163. package/dist/deploy/build.js.map +1 -1
  164. package/dist/feature-flags/store.d.ts.map +1 -1
  165. package/dist/feature-flags/store.js +10 -5
  166. package/dist/feature-flags/store.js.map +1 -1
  167. package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
  168. package/dist/integrations/a2a-continuations-store.js +12 -0
  169. package/dist/integrations/a2a-continuations-store.js.map +1 -1
  170. package/dist/integrations/config-store.d.ts +1 -0
  171. package/dist/integrations/config-store.d.ts.map +1 -1
  172. package/dist/integrations/config-store.js +11 -0
  173. package/dist/integrations/config-store.js.map +1 -1
  174. package/dist/integrations/google-docs-poller.d.ts.map +1 -1
  175. package/dist/integrations/google-docs-poller.js +19 -2
  176. package/dist/integrations/google-docs-poller.js.map +1 -1
  177. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  178. package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
  179. package/dist/mcp-client/routes.d.ts.map +1 -1
  180. package/dist/mcp-client/routes.js +30 -2
  181. package/dist/mcp-client/routes.js.map +1 -1
  182. package/dist/notifications/routes.d.ts +6 -6
  183. package/dist/observability/routes.d.ts +3 -3
  184. package/dist/onboarding/plugin.d.ts.map +1 -1
  185. package/dist/onboarding/plugin.js +12 -8
  186. package/dist/onboarding/plugin.js.map +1 -1
  187. package/dist/org/accept-pending.d.ts.map +1 -1
  188. package/dist/org/accept-pending.js +2 -0
  189. package/dist/org/accept-pending.js.map +1 -1
  190. package/dist/org/auto-join-domain.d.ts.map +1 -1
  191. package/dist/org/auto-join-domain.js +2 -0
  192. package/dist/org/auto-join-domain.js.map +1 -1
  193. package/dist/org/context.d.ts.map +1 -1
  194. package/dist/org/context.js +29 -9
  195. package/dist/org/context.js.map +1 -1
  196. package/dist/org/handlers.d.ts.map +1 -1
  197. package/dist/org/handlers.js +5 -0
  198. package/dist/org/handlers.js.map +1 -1
  199. package/dist/org/request-org-cache.d.ts +16 -0
  200. package/dist/org/request-org-cache.d.ts.map +1 -0
  201. package/dist/org/request-org-cache.js +62 -0
  202. package/dist/org/request-org-cache.js.map +1 -0
  203. package/dist/progress/routes.d.ts +1 -1
  204. package/dist/resources/handlers.d.ts +1 -1
  205. package/dist/resources/store.d.ts.map +1 -1
  206. package/dist/resources/store.js +11 -9
  207. package/dist/resources/store.js.map +1 -1
  208. package/dist/scripts/runner.d.ts.map +1 -1
  209. package/dist/scripts/runner.js +3 -3
  210. package/dist/scripts/runner.js.map +1 -1
  211. package/dist/secrets/storage.d.ts.map +1 -1
  212. package/dist/secrets/storage.js +82 -5
  213. package/dist/secrets/storage.js.map +1 -1
  214. package/dist/server/action-change.d.ts +20 -0
  215. package/dist/server/action-change.d.ts.map +1 -1
  216. package/dist/server/action-change.js +29 -0
  217. package/dist/server/action-change.js.map +1 -1
  218. package/dist/server/action-routes.d.ts.map +1 -1
  219. package/dist/server/action-routes.js +7 -8
  220. package/dist/server/action-routes.js.map +1 -1
  221. package/dist/server/agent-teams.d.ts.map +1 -1
  222. package/dist/server/agent-teams.js +10 -2
  223. package/dist/server/agent-teams.js.map +1 -1
  224. package/dist/server/better-auth-instance.d.ts.map +1 -1
  225. package/dist/server/better-auth-instance.js +31 -14
  226. package/dist/server/better-auth-instance.js.map +1 -1
  227. package/dist/server/core-routes-plugin.d.ts +1 -0
  228. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  229. package/dist/server/core-routes-plugin.js +18 -4
  230. package/dist/server/core-routes-plugin.js.map +1 -1
  231. package/dist/server/credential-provider.d.ts +16 -0
  232. package/dist/server/credential-provider.d.ts.map +1 -1
  233. package/dist/server/credential-provider.js +49 -13
  234. package/dist/server/credential-provider.js.map +1 -1
  235. package/dist/server/http-response-telemetry.d.ts.map +1 -1
  236. package/dist/server/http-response-telemetry.js +1 -0
  237. package/dist/server/http-response-telemetry.js.map +1 -1
  238. package/dist/server/poll.d.ts +6 -0
  239. package/dist/server/poll.d.ts.map +1 -1
  240. package/dist/server/poll.js +84 -47
  241. package/dist/server/poll.js.map +1 -1
  242. package/dist/server/transcribe-voice.d.ts +1 -1
  243. package/dist/server/voice-providers-status.d.ts.map +1 -1
  244. package/dist/server/voice-providers-status.js +12 -3
  245. package/dist/server/voice-providers-status.js.map +1 -1
  246. package/docs/content/durable-background-runs.mdx +15 -6
  247. package/docs/design/durable-agent-runs.md +15 -11
  248. package/package.json +2 -2
  249. package/src/a2a/activity.ts +127 -10
  250. package/src/a2a/index.ts +3 -0
  251. package/src/agent/durable-background.ts +38 -28
  252. package/src/agent/harness/background.ts +12 -2
  253. package/src/agent/production-agent.ts +18 -15
  254. package/src/agent/run-store.ts +45 -3
  255. package/src/application-state/handlers.ts +45 -0
  256. package/src/application-state/index.ts +3 -0
  257. package/src/application-state/store.ts +28 -11
  258. package/src/audit/redact.ts +104 -18
  259. package/src/client/AgentPanel.tsx +2 -7
  260. package/src/client/MultiTabAssistantChat.tsx +3 -0
  261. package/src/client/RunStuckBanner.tsx +29 -6
  262. package/src/client/agent-chat.ts +4 -9
  263. package/src/client/application-state.ts +115 -12
  264. package/src/client/dynamic-suggestions.ts +4 -12
  265. package/src/client/guided-questions.tsx +12 -23
  266. package/src/db/client.ts +148 -28
  267. package/src/db/create-get-db.ts +39 -6
  268. package/src/db/runtime-diagnostics.ts +41 -6
  269. package/src/deploy/build.ts +26 -33
  270. package/src/feature-flags/store.ts +10 -5
  271. package/src/integrations/a2a-continuations-store.ts +11 -0
  272. package/src/integrations/config-store.ts +13 -0
  273. package/src/integrations/google-docs-poller.ts +26 -2
  274. package/src/mcp-client/routes.ts +33 -2
  275. package/src/onboarding/plugin.ts +31 -25
  276. package/src/org/accept-pending.ts +2 -0
  277. package/src/org/auto-join-domain.ts +2 -0
  278. package/src/org/context.ts +35 -10
  279. package/src/org/handlers.ts +6 -0
  280. package/src/org/request-org-cache.ts +71 -0
  281. package/src/resources/store.ts +10 -9
  282. package/src/scripts/runner.ts +6 -3
  283. package/src/secrets/storage.ts +97 -5
  284. package/src/server/action-change.ts +33 -0
  285. package/src/server/action-routes.ts +11 -9
  286. package/src/server/agent-teams.ts +10 -2
  287. package/src/server/better-auth-instance.ts +39 -12
  288. package/src/server/core-routes-plugin.ts +19 -2
  289. package/src/server/credential-provider.ts +57 -14
  290. package/src/server/http-response-telemetry.ts +1 -0
  291. package/src/server/poll.ts +95 -57
  292. package/src/server/voice-providers-status.ts +19 -4
@@ -442,6 +442,12 @@ export class AppSyncState {
442
442
  private lastExtensionsUpdatedAt: string | number | undefined;
443
443
  private lastExtensionMarkerTs = 0;
444
444
  private lastActionMarkerTs = 0;
445
+ /**
446
+ * Set by the seed when a durable action marker predates this process, so the
447
+ * first `doCheckExternalDbChanges` reads the marker rows even though the
448
+ * MAX(updated_at) probe cannot distinguish that row from the seeded max.
449
+ */
450
+ private replayActionMarkerOnce = false;
445
451
 
446
452
  /**
447
453
  * Tracks the latest updated_at seen on the `__screen_refresh__` key.
@@ -1355,6 +1361,7 @@ export class AppSyncState {
1355
1361
  // performed by a separate action process. Do not baseline past an existing
1356
1362
  // marker on cold start, or the first poll after the action will miss it.
1357
1363
  this.lastActionMarkerTs = 0;
1364
+ this.replayActionMarkerOnce = actionMarkerTs > 0;
1358
1365
  this.lastScreenRefreshTs = refreshTs;
1359
1366
  this.lastScreenRefreshTsBySession.clear();
1360
1367
  for (const row of refreshResult.rows) {
@@ -1416,28 +1423,54 @@ export class AppSyncState {
1416
1423
  // concurrently to shave stacked latency; results are still processed in
1417
1424
  // the original sequential order, and conditional follow-up queries stay
1418
1425
  // sequential within their branch.
1426
+ //
1427
+ // `lastAppStateTs` is maintained as MAX(updated_at) over the whole
1428
+ // `application_state` table (seeded in `seedVersionFromDb`, advanced by
1429
+ // the scan below over every key, not just the ones that emit events).
1430
+ // The application-state row scan and screen/extension marker reads only
1431
+ // report rows strictly newer than a watermark that is itself <=
1432
+ // `lastAppStateTs`, so an unadvanced MAX proves those reads are no-ops.
1433
+ // The action marker keeps its own max probe because its watermark must
1434
+ // remain independent under cross-process clock skew.
1419
1435
  const [
1420
- appResult,
1436
+ appStateMaxTs,
1421
1437
  actionMarkerTs,
1422
- refreshResult,
1423
- extensionMarkerTs,
1424
1438
  settingsTs,
1425
1439
  extensionsMaxUpdatedAt,
1426
1440
  ] = await Promise.all([
1427
- db.execute({
1428
- sql: "SELECT session_id, key, updated_at FROM application_state WHERE updated_at > ? ORDER BY updated_at ASC",
1429
- args: [this.lastAppStateTs],
1430
- }),
1441
+ readMaxUpdatedAt(db, "application_state"),
1431
1442
  readActionMarkerMaxUpdatedAt(db),
1432
- db.execute({
1433
- sql: "SELECT session_id, updated_at, value FROM application_state WHERE key = ?",
1434
- args: [SCREEN_REFRESH_KEY],
1435
- }),
1436
- readExtensionMarkerMaxUpdatedAt(db),
1437
1443
  readMaxUpdatedAt(db, "settings"),
1438
1444
  readMaxUpdatedAtRaw(db, "tools"),
1439
1445
  ]);
1440
1446
 
1447
+ // The seed deliberately rewinds `lastActionMarkerTs` to 0 so a marker
1448
+ // written before this process booted still reaches the first poll. That
1449
+ // replay is invisible to the MAX probe (the row is already counted in the
1450
+ // seeded max), so it gets one forced full read.
1451
+ const replayActionMarker = this.replayActionMarkerOnce;
1452
+ this.replayActionMarkerOnce = false;
1453
+ const appStateChanged =
1454
+ appStateMaxTs > this.lastAppStateTs || replayActionMarker;
1455
+
1456
+ const [appResult, refreshResult, extensionMarkerTs] = appStateChanged
1457
+ ? await Promise.all([
1458
+ db.execute({
1459
+ sql: "SELECT session_id, key, updated_at FROM application_state WHERE updated_at > ? ORDER BY updated_at ASC",
1460
+ args: [this.lastAppStateTs],
1461
+ }),
1462
+ db.execute({
1463
+ sql: "SELECT session_id, updated_at, value FROM application_state WHERE key = ?",
1464
+ args: [SCREEN_REFRESH_KEY],
1465
+ }),
1466
+ readExtensionMarkerMaxUpdatedAt(db),
1467
+ ])
1468
+ : ([
1469
+ { rows: [] as Record<string, unknown>[] },
1470
+ null,
1471
+ this.lastExtensionMarkerTs,
1472
+ ] as const);
1473
+
1441
1474
  // Check application_state for external writes. Preserve the changed key so
1442
1475
  // clients can invalidate one-shot command queries (`navigate`, `__set_url__`)
1443
1476
  // only when those command rows actually change; noisy keys such as
@@ -1497,54 +1530,59 @@ export class AppSyncState {
1497
1530
  // tool writes to application_state under a well-known key; when its
1498
1531
  // updated_at bumps, emit a distinct event so the client invalidates
1499
1532
  // all queries (not just the ones matching its default queryKey prefix).
1500
- const refreshTs = refreshResult.rows.reduce(
1501
- (max, row) => Math.max(max, timestampValue(row.updated_at)),
1502
- 0,
1503
- );
1504
- if (!this.screenRefreshInitialized) {
1505
- this.lastScreenRefreshTs = refreshTs;
1506
- for (const row of refreshResult.rows) {
1507
- if (typeof row.session_id === "string") {
1508
- this.lastScreenRefreshTsBySession.set(
1509
- row.session_id,
1510
- timestampValue(row.updated_at),
1511
- );
1512
- }
1513
- }
1514
- this.screenRefreshInitialized = true;
1515
- } else if (refreshTs > this.lastScreenRefreshTs) {
1516
- // Emit a per-user event only for the session(s) whose row actually
1517
- // advanced, scoped with `owner` so canSeeChangeForUser delivers it only
1518
- // to that user — not every authenticated poller.
1519
- for (const row of refreshResult.rows) {
1520
- const owner =
1521
- typeof row.session_id === "string" ? row.session_id : undefined;
1522
- if (!owner) continue;
1523
- const rowTs = timestampValue(row.updated_at);
1524
- if (rowTs <= (this.lastScreenRefreshTsBySession.get(owner) ?? 0)) {
1525
- continue;
1533
+ // `refreshResult` is null exactly when the MAX probe proved no
1534
+ // application_state row moved, which is also exactly when this block
1535
+ // could not emit anything.
1536
+ if (refreshResult) {
1537
+ const refreshTs = refreshResult.rows.reduce(
1538
+ (max, row) => Math.max(max, timestampValue(row.updated_at)),
1539
+ 0,
1540
+ );
1541
+ if (!this.screenRefreshInitialized) {
1542
+ this.lastScreenRefreshTs = refreshTs;
1543
+ for (const row of refreshResult.rows) {
1544
+ if (typeof row.session_id === "string") {
1545
+ this.lastScreenRefreshTsBySession.set(
1546
+ row.session_id,
1547
+ timestampValue(row.updated_at),
1548
+ );
1549
+ }
1526
1550
  }
1527
- let scope: string | undefined;
1528
- try {
1529
- const raw = row.value;
1530
- if (typeof raw === "string") {
1531
- const parsed = JSON.parse(raw);
1532
- if (typeof parsed?.scope === "string") scope = parsed.scope;
1551
+ this.screenRefreshInitialized = true;
1552
+ } else if (refreshTs > this.lastScreenRefreshTs) {
1553
+ // Emit a per-user event only for the session(s) whose row actually
1554
+ // advanced, scoped with `owner` so canSeeChangeForUser delivers it only
1555
+ // to that user — not every authenticated poller.
1556
+ for (const row of refreshResult.rows) {
1557
+ const owner =
1558
+ typeof row.session_id === "string" ? row.session_id : undefined;
1559
+ if (!owner) continue;
1560
+ const rowTs = timestampValue(row.updated_at);
1561
+ if (rowTs <= (this.lastScreenRefreshTsBySession.get(owner) ?? 0)) {
1562
+ continue;
1533
1563
  }
1534
- } catch {}
1535
- this.recordChange(
1536
- {
1537
- source: "screen-refresh",
1538
- type: "change",
1539
- key: SCREEN_REFRESH_KEY,
1540
- owner,
1541
- ...(scope ? { scope } : {}),
1542
- },
1543
- { dedupeKey: `screen-refresh|${rowTs}` },
1544
- );
1545
- this.lastScreenRefreshTsBySession.set(owner, rowTs);
1564
+ let scope: string | undefined;
1565
+ try {
1566
+ const raw = row.value;
1567
+ if (typeof raw === "string") {
1568
+ const parsed = JSON.parse(raw);
1569
+ if (typeof parsed?.scope === "string") scope = parsed.scope;
1570
+ }
1571
+ } catch {}
1572
+ this.recordChange(
1573
+ {
1574
+ source: "screen-refresh",
1575
+ type: "change",
1576
+ key: SCREEN_REFRESH_KEY,
1577
+ owner,
1578
+ ...(scope ? { scope } : {}),
1579
+ },
1580
+ { dedupeKey: `screen-refresh|${rowTs}` },
1581
+ );
1582
+ this.lastScreenRefreshTsBySession.set(owner, rowTs);
1583
+ }
1584
+ this.lastScreenRefreshTs = refreshTs;
1546
1585
  }
1547
- this.lastScreenRefreshTs = refreshTs;
1548
1586
  }
1549
1587
 
1550
1588
  // Extension mutations write a durable marker row so delete and hide/unhide
@@ -22,6 +22,7 @@ import {
22
22
  import { getOrgContext } from "../org/context.js";
23
23
  import { getSession } from "./auth.js";
24
24
  import {
25
+ prefetchSecrets,
25
26
  resolveHasCompleteBuilderConnection,
26
27
  resolveSecret,
27
28
  } from "./credential-provider.js";
@@ -74,10 +75,14 @@ export function createVoiceProvidersStatusHandler() {
74
75
  async function hasKey(key: string): Promise<boolean> {
75
76
  try {
76
77
  if (key === "GOOGLE_APPLICATION_CREDENTIALS") {
77
- const resolved = await resolveGoogleRealtimeCredentials({
78
- userEmail: session?.email,
79
- orgId: orgCtx?.orgId ?? undefined,
80
- });
78
+ // Same identity, passed explicitly — the context is here only so this
79
+ // scope sweep shares the prefetched per-request memo below.
80
+ const resolved = await withRequestContext(() =>
81
+ resolveGoogleRealtimeCredentials({
82
+ userEmail: session?.email,
83
+ orgId: orgCtx?.orgId ?? undefined,
84
+ }),
85
+ );
81
86
  return typeof resolved === "string" && resolved.length > 0;
82
87
  }
83
88
  const resolved = await withRequestContext(() => resolveSecret(key));
@@ -87,6 +92,16 @@ export function createVoiceProvidersStatusHandler() {
87
92
  }
88
93
  }
89
94
 
95
+ // One read per scope for every key below, instead of one per key per scope.
96
+ await withRequestContext(() =>
97
+ prefetchSecrets([
98
+ "GEMINI_API_KEY",
99
+ "OPENAI_API_KEY",
100
+ "GROQ_API_KEY",
101
+ "GOOGLE_APPLICATION_CREDENTIALS",
102
+ ]),
103
+ );
104
+
90
105
  let builder = false;
91
106
  try {
92
107
  builder =