@agent-native/core 0.94.3 → 0.95.1

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 (181) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +13 -0
  4. package/corpus/core/docs/content/locales/de-DE/template-design.mdx +18 -0
  5. package/corpus/core/docs/content/locales/es-ES/template-design.mdx +16 -0
  6. package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +17 -0
  7. package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +16 -0
  8. package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +17 -0
  9. package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +15 -0
  10. package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +16 -0
  11. package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +11 -0
  12. package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +12 -0
  13. package/corpus/core/docs/content/pr-visual-recap.mdx +20 -12
  14. package/corpus/core/docs/content/template-design.mdx +16 -0
  15. package/corpus/core/package.json +2 -2
  16. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +3 -0
  17. package/corpus/core/src/agent/engine/registry.ts +6 -1
  18. package/corpus/core/src/agent/engine/types.ts +2 -0
  19. package/corpus/core/src/agent/production-agent.ts +20 -0
  20. package/corpus/core/src/agent/thread-data-builder.ts +41 -17
  21. package/corpus/core/src/cli/code-agent-executor.ts +61 -1
  22. package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
  23. package/corpus/core/src/cli/recap.ts +262 -25
  24. package/corpus/core/src/client/AssistantChat.tsx +250 -25
  25. package/corpus/core/src/client/chat/tool-call-display.tsx +1 -1
  26. package/corpus/core/src/client/chat/use-reconnect-reader-owner.ts +28 -0
  27. package/corpus/core/src/client/guided-questions.tsx +9 -1
  28. package/corpus/core/src/client/integrations/IntegrationsPanel.tsx +6 -7
  29. package/corpus/core/src/client/settings/UsageSection.tsx +26 -5
  30. package/corpus/core/src/client/sse-event-processor.ts +90 -21
  31. package/corpus/core/src/connections/catalog.ts +2 -2
  32. package/corpus/core/src/deploy/build.ts +3 -0
  33. package/corpus/core/src/index.ts +2 -0
  34. package/corpus/core/src/integrations/catalog.ts +2 -2
  35. package/corpus/core/src/server/auth.ts +1 -1
  36. package/corpus/core/src/server/credential-provider.ts +7 -3
  37. package/corpus/core/src/shared/cache-control.ts +10 -4
  38. package/corpus/core/src/usage/store.ts +82 -20
  39. package/corpus/templates/analytics/app/lib/data-sources.ts +5 -4
  40. package/corpus/templates/analytics/changelog/2026-07-10-slack-token-setup-marked-legacy.md +6 -0
  41. package/corpus/templates/analytics/server/lib/credential-keys.ts +6 -2
  42. package/corpus/templates/brain/changelog/2026-07-10-slack-backfill-token-marked-legacy.md +6 -0
  43. package/corpus/templates/brain/server/register-secrets.ts +2 -2
  44. package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -1
  45. package/corpus/templates/clips/app/components/library/page-header.tsx +19 -3
  46. package/corpus/templates/clips/app/routes/_app.library._index.tsx +16 -12
  47. package/corpus/templates/clips/changelog/2026-07-10-new-recording-appears-once-when-the-library-sidebar-is-open.md +6 -0
  48. package/corpus/templates/clips/changelog/2026-07-10-slack-token-fallback-guidance.md +6 -0
  49. package/corpus/templates/clips/server/register-secrets.ts +1 -1
  50. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +0 -66
  51. package/corpus/templates/content/app/routes/_app.local-files.tsx +7 -47
  52. package/corpus/templates/content/changelog/2026-07-10-local-folders-now-wait-for-an-explicit-pull-before-content-r.md +6 -0
  53. package/corpus/templates/content/package.json +1 -1
  54. package/corpus/templates/design/actions/generate-design.ts +128 -31
  55. package/corpus/templates/design/actions/present-design-variants.ts +37 -9
  56. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +9 -3
  57. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +32 -10
  58. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +2 -2
  59. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +9 -1
  60. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +7 -5
  61. package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
  62. package/corpus/templates/design/app/global.css +10 -0
  63. package/corpus/templates/design/app/hooks/use-question-flow.ts +9 -2
  64. package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
  65. package/corpus/templates/design/app/i18n-data.ts +21 -11
  66. package/corpus/templates/design/changelog/2026-07-10-editor-panels-are-easier-to-distinguish-and-design-intake-qu.md +6 -0
  67. package/corpus/templates/design/changelog/2026-07-10-interact-mode-now-stays-on-the-full-canvas-matches-edit-visu.md +6 -0
  68. package/corpus/templates/design/changelog/2026-07-10-new-web-designs-open-at-desktop-size-and-adapt-across-mobile.md +6 -0
  69. package/corpus/templates/design/changelog/2026-07-10-tweaks-now-link-to-full-documentation.md +6 -0
  70. package/corpus/templates/design/server/plugins/agent-chat.ts +2 -0
  71. package/corpus/templates/mail/app/i18n/ar-SA.ts +3 -3
  72. package/corpus/templates/mail/app/i18n/de-DE.ts +3 -3
  73. package/corpus/templates/mail/app/i18n/en-US.ts +4 -3
  74. package/corpus/templates/mail/app/i18n/es-ES.ts +3 -3
  75. package/corpus/templates/mail/app/i18n/fr-FR.ts +3 -3
  76. package/corpus/templates/mail/app/i18n/hi-IN.ts +3 -3
  77. package/corpus/templates/mail/app/i18n/ja-JP.ts +3 -3
  78. package/corpus/templates/mail/app/i18n/ko-KR.ts +3 -3
  79. package/corpus/templates/mail/app/i18n/pt-BR.ts +3 -3
  80. package/corpus/templates/mail/app/i18n/zh-CN.ts +3 -3
  81. package/corpus/templates/mail/app/i18n/zh-TW.ts +3 -3
  82. package/corpus/templates/mail/changelog/2026-07-10-slack-intake-marked-legacy.md +6 -0
  83. package/corpus/templates/mail/server/lib/env-config.ts +5 -4
  84. package/corpus/templates/plan/actions/record-recap-usage-cost.ts +49 -0
  85. package/corpus/templates/plan/actions/record-recap-usage.ts +15 -22
  86. package/corpus/templates/plan/package.json +1 -1
  87. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  88. package/dist/agent/engine/ai-sdk-engine.js +3 -0
  89. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  90. package/dist/agent/engine/registry.d.ts +1 -1
  91. package/dist/agent/engine/registry.d.ts.map +1 -1
  92. package/dist/agent/engine/registry.js +2 -0
  93. package/dist/agent/engine/registry.js.map +1 -1
  94. package/dist/agent/engine/types.d.ts +2 -0
  95. package/dist/agent/engine/types.d.ts.map +1 -1
  96. package/dist/agent/engine/types.js.map +1 -1
  97. package/dist/agent/production-agent.d.ts.map +1 -1
  98. package/dist/agent/production-agent.js +20 -0
  99. package/dist/agent/production-agent.js.map +1 -1
  100. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  101. package/dist/agent/thread-data-builder.js +36 -18
  102. package/dist/agent/thread-data-builder.js.map +1 -1
  103. package/dist/cli/code-agent-executor.d.ts +2 -0
  104. package/dist/cli/code-agent-executor.d.ts.map +1 -1
  105. package/dist/cli/code-agent-executor.js +52 -3
  106. package/dist/cli/code-agent-executor.js.map +1 -1
  107. package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
  108. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  109. package/dist/cli/pr-visual-recap-workflow.js +1 -1
  110. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  111. package/dist/cli/recap.d.ts +22 -2
  112. package/dist/cli/recap.d.ts.map +1 -1
  113. package/dist/cli/recap.js +199 -22
  114. package/dist/cli/recap.js.map +1 -1
  115. package/dist/client/AssistantChat.d.ts +3 -1
  116. package/dist/client/AssistantChat.d.ts.map +1 -1
  117. package/dist/client/AssistantChat.js +200 -28
  118. package/dist/client/AssistantChat.js.map +1 -1
  119. package/dist/client/chat/tool-call-display.js +1 -1
  120. package/dist/client/chat/tool-call-display.js.map +1 -1
  121. package/dist/client/chat/use-reconnect-reader-owner.d.ts +11 -0
  122. package/dist/client/chat/use-reconnect-reader-owner.d.ts.map +1 -0
  123. package/dist/client/chat/use-reconnect-reader-owner.js +21 -0
  124. package/dist/client/chat/use-reconnect-reader-owner.js.map +1 -0
  125. package/dist/client/guided-questions.d.ts.map +1 -1
  126. package/dist/client/guided-questions.js +5 -1
  127. package/dist/client/guided-questions.js.map +1 -1
  128. package/dist/client/integrations/IntegrationsPanel.d.ts.map +1 -1
  129. package/dist/client/integrations/IntegrationsPanel.js +5 -7
  130. package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
  131. package/dist/client/settings/UsageSection.d.ts.map +1 -1
  132. package/dist/client/settings/UsageSection.js +12 -3
  133. package/dist/client/settings/UsageSection.js.map +1 -1
  134. package/dist/client/sse-event-processor.d.ts.map +1 -1
  135. package/dist/client/sse-event-processor.js +67 -20
  136. package/dist/client/sse-event-processor.js.map +1 -1
  137. package/dist/collab/routes.d.ts +1 -1
  138. package/dist/collab/struct-routes.d.ts +1 -1
  139. package/dist/connections/catalog.d.ts +2 -2
  140. package/dist/connections/catalog.js +2 -2
  141. package/dist/connections/catalog.js.map +1 -1
  142. package/dist/deploy/build.d.ts.map +1 -1
  143. package/dist/deploy/build.js +3 -1
  144. package/dist/deploy/build.js.map +1 -1
  145. package/dist/index.d.ts +1 -1
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js.map +1 -1
  148. package/dist/integrations/catalog.js +2 -2
  149. package/dist/integrations/catalog.js.map +1 -1
  150. package/dist/notifications/routes.d.ts +2 -2
  151. package/dist/observability/routes.d.ts +1 -1
  152. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  153. package/dist/resources/handlers.d.ts +1 -1
  154. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  155. package/dist/server/auth.js +1 -1
  156. package/dist/server/auth.js.map +1 -1
  157. package/dist/server/credential-provider.d.ts +5 -3
  158. package/dist/server/credential-provider.d.ts.map +1 -1
  159. package/dist/server/credential-provider.js +7 -3
  160. package/dist/server/credential-provider.js.map +1 -1
  161. package/dist/shared/cache-control.d.ts +8 -6
  162. package/dist/shared/cache-control.d.ts.map +1 -1
  163. package/dist/shared/cache-control.js +8 -3
  164. package/dist/shared/cache-control.js.map +1 -1
  165. package/dist/usage/store.d.ts +21 -0
  166. package/dist/usage/store.d.ts.map +1 -1
  167. package/dist/usage/store.js +54 -19
  168. package/dist/usage/store.js.map +1 -1
  169. package/docs/content/locales/ar-SA/template-design.mdx +13 -0
  170. package/docs/content/locales/de-DE/template-design.mdx +18 -0
  171. package/docs/content/locales/es-ES/template-design.mdx +16 -0
  172. package/docs/content/locales/fr-FR/template-design.mdx +17 -0
  173. package/docs/content/locales/hi-IN/template-design.mdx +16 -0
  174. package/docs/content/locales/ja-JP/template-design.mdx +17 -0
  175. package/docs/content/locales/ko-KR/template-design.mdx +15 -0
  176. package/docs/content/locales/pt-BR/template-design.mdx +16 -0
  177. package/docs/content/locales/zh-CN/template-design.mdx +11 -0
  178. package/docs/content/locales/zh-TW/template-design.mdx +12 -0
  179. package/docs/content/pr-visual-recap.mdx +20 -12
  180. package/docs/content/template-design.mdx +16 -0
  181. package/package.json +2 -2
@@ -231,12 +231,6 @@ export type PreparingActionState = {
231
231
  toolEntries?: Map<string, PreparingActionEntry>;
232
232
  };
233
233
 
234
- function formatProgressBytes(bytes: number): string {
235
- if (bytes < 1024) return `${bytes} B`;
236
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
237
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
238
- }
239
-
240
234
  function activityProgressBytes(ev: SSEEvent): number | undefined {
241
235
  return typeof ev.progressBytes === "number" &&
242
236
  Number.isFinite(ev.progressBytes) &&
@@ -281,7 +275,7 @@ function preparationActivityLabel(
281
275
  if (progressBytes <= 0) {
282
276
  return `Preparing ${action}...`;
283
277
  }
284
- return `Writing ${action}... (${formatProgressBytes(progressBytes)} prepared)`;
278
+ return `Writing ${action}...`;
285
279
  }
286
280
 
287
281
  function visibleActivityLabel(ev: SSEEvent, tool?: string): string {
@@ -301,16 +295,8 @@ function findPendingToolCallIndex(
301
295
  // calls can be in flight simultaneously, and name-only matching would
302
296
  // attach a result to the wrong call.
303
297
  if (toolCallId) {
304
- for (let i = content.length - 1; i >= 0; i--) {
305
- const part = content[i];
306
- if (
307
- part.type === "tool-call" &&
308
- part.toolCallId === toolCallId &&
309
- part.result === undefined
310
- ) {
311
- return i;
312
- }
313
- }
298
+ const exactIndex = findPendingToolCallIndexById(content, toolCallId);
299
+ if (exactIndex >= 0) return exactIndex;
314
300
  // Fall through to name-matching: the start event may have arrived before
315
301
  // the server started emitting ids (e.g. older server build), so the
316
302
  // stored toolCallId is the locally-generated "tc_N" value rather than the
@@ -329,6 +315,76 @@ function findPendingToolCallIndex(
329
315
  return -1;
330
316
  }
331
317
 
318
+ function findPendingToolCallIndexById(
319
+ content: ContentPart[],
320
+ toolCallId: string,
321
+ ): number {
322
+ for (let i = content.length - 1; i >= 0; i--) {
323
+ const part = content[i];
324
+ if (
325
+ part.type === "tool-call" &&
326
+ part.toolCallId === toolCallId &&
327
+ part.result === undefined
328
+ ) {
329
+ return i;
330
+ }
331
+ }
332
+ return -1;
333
+ }
334
+
335
+ function findOldestPendingActivityToolCallIndex(
336
+ content: ContentPart[],
337
+ toolName: string,
338
+ ): number {
339
+ for (let i = 0; i < content.length; i += 1) {
340
+ const part = content[i];
341
+ if (
342
+ part.type === "tool-call" &&
343
+ part.toolName === toolName &&
344
+ part.activity === true &&
345
+ part.result === undefined
346
+ ) {
347
+ return i;
348
+ }
349
+ }
350
+ return -1;
351
+ }
352
+
353
+ function findPendingActivityToolCallIndex(
354
+ content: ContentPart[],
355
+ toolName: string,
356
+ toolCallId?: string,
357
+ ): number {
358
+ if (!toolCallId) {
359
+ return findPendingToolCallIndex(content, toolName);
360
+ }
361
+
362
+ // Activity events emitted while the model is assembling tool input already
363
+ // carry the engine's stable call id. Match that id exactly so repeated
364
+ // progress for one call updates one placeholder while parallel same-name
365
+ // calls retain separate cards.
366
+ const exactIndex = findPendingToolCallIndexById(content, toolCallId);
367
+ if (exactIndex >= 0) return exactIndex;
368
+
369
+ // Older activity events may omit the id before a later progress event adds
370
+ // it. Upgrade one unambiguous reader-local placeholder instead of painting a
371
+ // second card for the same logical call.
372
+ const readerLocalCandidates: number[] = [];
373
+ for (let i = 0; i < content.length; i += 1) {
374
+ const part = content[i];
375
+ if (
376
+ part.type === "tool-call" &&
377
+ part.toolName === toolName &&
378
+ part.activity === true &&
379
+ part.result === undefined &&
380
+ /^tc_\d+$/.test(part.toolCallId)
381
+ ) {
382
+ readerLocalCandidates.push(i);
383
+ }
384
+ }
385
+ return readerLocalCandidates.length === 1 ? readerLocalCandidates[0] : -1;
386
+ }
387
+
332
388
  function findCompletedToolCallIndex(
333
389
  content: ContentPart[],
334
390
  toolCallId?: string,
@@ -1000,7 +1056,17 @@ export function processEvent(
1000
1056
  }
1001
1057
  if (!tool) return { action: "continue" };
1002
1058
 
1003
- const pendingToolCallIndex = findPendingToolCallIndex(content, tool);
1059
+ const pendingToolCallIndex = findPendingActivityToolCallIndex(
1060
+ content,
1061
+ tool,
1062
+ ev.id,
1063
+ );
1064
+ if (pendingToolCallIndex >= 0 && ev.id) {
1065
+ const pending = content[pendingToolCallIndex];
1066
+ if (pending?.type === "tool-call" && pending.toolCallId !== ev.id) {
1067
+ content[pendingToolCallIndex] = { ...pending, toolCallId: ev.id };
1068
+ }
1069
+ }
1004
1070
  if (pendingToolCallIndex === -1) {
1005
1071
  // Only surface a placeholder spinner when this tool has no card yet. A
1006
1072
  // trailing activity heartbeat that arrives after the matching tool_done
@@ -1011,12 +1077,13 @@ export function processEvent(
1011
1077
  (part) =>
1012
1078
  part.type === "tool-call" &&
1013
1079
  part.toolName === tool &&
1014
- part.result !== undefined,
1080
+ part.result !== undefined &&
1081
+ (!ev.id || part.toolCallId === ev.id),
1015
1082
  );
1016
1083
  if (!hasCompletedSameTool) {
1017
1084
  content.push({
1018
1085
  type: "tool-call",
1019
- toolCallId: `tc_${++toolCallCounter.value}`,
1086
+ toolCallId: ev.id ?? `tc_${++toolCallCounter.value}`,
1020
1087
  toolName: tool,
1021
1088
  argsText: "",
1022
1089
  args: {},
@@ -1054,7 +1121,9 @@ export function processEvent(
1054
1121
  }
1055
1122
  // Pass the server-assigned id so we upgrade the pending activity card
1056
1123
  // using id-match when available (parallel same-name calls stay separate).
1057
- const pendingToolCallIndex = findPendingToolCallIndex(content, tool, ev.id);
1124
+ const pendingToolCallIndex = ev.id
1125
+ ? findPendingActivityToolCallIndex(content, tool, ev.id)
1126
+ : findOldestPendingActivityToolCallIndex(content, tool);
1058
1127
  const pendingToolCall =
1059
1128
  pendingToolCallIndex >= 0 ? content[pendingToolCallIndex] : undefined;
1060
1129
  const pendingIsActivityPlaceholder =
@@ -66,9 +66,9 @@ export const WORKSPACE_CONNECTION_PROVIDERS = [
66
66
  credentialKeys: [
67
67
  {
68
68
  key: "SLACK_BOT_TOKEN",
69
- label: "Slack bot token",
69
+ label: "Slack bot token (legacy)",
70
70
  description:
71
- "Bot token with the smallest channel and history scopes needed by the template.",
71
+ "Legacy single-workspace fallback. For new messaging automations, connect Slack from Settings Messaging instead.",
72
72
  required: true,
73
73
  },
74
74
  ],
@@ -32,6 +32,7 @@ import { normalizeAppBasePath } from "../server/app-base-path.js";
32
32
  import {
33
33
  DEFAULT_SSR_CDN_CACHE_CONTROL,
34
34
  DEFAULT_SSR_NETLIFY_CDN_CACHE_CONTROL,
35
+ DEFAULT_SSR_NETLIFY_VARY,
35
36
  DEFAULT_SPECULATION_RULES_PATH,
36
37
  DEFAULT_SSR_CACHE_CONTROL,
37
38
  } from "../shared/cache-control.js";
@@ -908,6 +909,7 @@ function injectHeadScript(html, script) {
908
909
  const DEFAULT_SSR_CACHE_CONTROL = ${JSON.stringify(DEFAULT_SSR_CACHE_CONTROL)};
909
910
  const DEFAULT_SSR_CDN_CACHE_CONTROL = ${JSON.stringify(DEFAULT_SSR_CDN_CACHE_CONTROL)};
910
911
  const DEFAULT_SSR_NETLIFY_CDN_CACHE_CONTROL = ${JSON.stringify(DEFAULT_SSR_NETLIFY_CDN_CACHE_CONTROL)};
912
+ const DEFAULT_SSR_NETLIFY_VARY = ${JSON.stringify(DEFAULT_SSR_NETLIFY_VARY)};
911
913
  const DEFAULT_SPECULATION_RULES_PATH = ${JSON.stringify(DEFAULT_SPECULATION_RULES_PATH)};
912
914
  const IMMUTABLE_ASSET_CACHE_CONTROL = ${JSON.stringify(IMMUTABLE_ASSET_CACHE_CONTROL)};
913
915
  const IMMUTABLE_ASSET_PATHS = new Set(${JSON.stringify(
@@ -1003,6 +1005,7 @@ function applyDefaultSsrCacheHeader(headers, status, pathname) {
1003
1005
  // Netlify-specific header so SSR HTML/.data are served from the shared
1004
1006
  // durable CDN cache instead of stampeding origin — for every visitor.
1005
1007
  headers.set("netlify-cdn-cache-control", DEFAULT_SSR_NETLIFY_CDN_CACHE_CONTROL);
1008
+ headers.set("netlify-vary", DEFAULT_SSR_NETLIFY_VARY);
1006
1009
  }
1007
1010
 
1008
1011
  function applyDefaultSpeculationRulesHeader(headers, status, basePath) {
@@ -236,6 +236,8 @@ export {
236
236
  BUILDER_CREDIT_USAGE_BILLING,
237
237
  USD_USAGE_BILLING,
238
238
  type UsageRecord,
239
+ type UsageCostSource,
240
+ type UsageCostAggregate,
239
241
  type UsageSummary,
240
242
  type UsageBillingMode,
241
243
  type UsageBillingUnit,
@@ -137,10 +137,10 @@ const BUILT_IN_CHANNEL_CATALOG = [
137
137
  credentialRequirements: [
138
138
  {
139
139
  key: "SLACK_BOT_TOKEN",
140
- label: "Slack Bot Token",
140
+ label: "Slack Bot Token (legacy)",
141
141
  required: false,
142
142
  helpText:
143
- "OAuth & PermissionsBot User OAuth Token (starts with `xoxb-`).",
143
+ "Legacy single-workspace fallback only. For new setups, install Slack from Settings Messaging; managed OAuth stores each workspace token automatically.",
144
144
  },
145
145
  {
146
146
  key: "SLACK_CLIENT_ID",
@@ -1627,7 +1627,7 @@ function loginHtmlResponse(loginHtml: string, event: H3Event): Response {
1627
1627
  headers: {
1628
1628
  "Content-Type": "text/html; charset=utf-8",
1629
1629
  // The sign-in document is part of the public server shell. Keep it on the
1630
- // same short-fresh/long-SWR CDN policy as React Router SSR so hosted
1630
+ // same long-fresh/long-SWR CDN policy as React Router SSR so hosted
1631
1631
  // template roots do not invoke origin just to render anonymous login UI.
1632
1632
  // The login HTML is env-INDEPENDENT (a Google-only app always renders a
1633
1633
  // working button), so a cached copy is never "wrong" — never downgrade
@@ -78,6 +78,8 @@ const APP_PROVIDED_DEPLOY_CREDENTIAL_KEYS = new Set([
78
78
  "OPENAI_API_KEY",
79
79
  "OPENAI_BASE_URL",
80
80
  "OPENROUTER_API_KEY",
81
+ "GOOGLE_CLIENT_ID",
82
+ "GOOGLE_CLIENT_SECRET",
81
83
  "GOOGLE_GENERATIVE_AI_API_KEY",
82
84
  "GROQ_API_KEY",
83
85
  "MISTRAL_API_KEY",
@@ -93,9 +95,11 @@ function isAppProvidedDeployCredentialKey(key: string | undefined): boolean {
93
95
  * single-tenant contexts. In hosted production with a shared database, every
94
96
  * signed-in user needs their own user/org/workspace credential for
95
97
  * identity-bearing provider keys so one deploy key does not silently
96
- * impersonate another tenant. App-provided LLM provider keys are different:
97
- * they intentionally let the app developer pay for model usage for users of
98
- * that deployed app, so key-aware callers may use those env vars.
98
+ * impersonate another tenant. App-provided service credentials are different:
99
+ * they configure the deployed app itself rather than identifying a user. This
100
+ * includes LLM keys that let the app developer pay for model usage and OAuth
101
+ * client credentials whose per-user identity remains in scoped OAuth tokens.
102
+ * Key-aware callers may use those env vars.
99
103
  *
100
104
  * @deprecated Use `canUseDeployCredentialFallbackForRequest()` for generic
101
105
  * provider secrets. This stricter helper remains for legacy call sites with
@@ -1,15 +1,21 @@
1
- export const DEFAULT_SSR_CACHE_CONTROL =
2
- "public, max-age=5, stale-while-revalidate=604800, stale-if-error=3600";
1
+ export const DEFAULT_SSR_CACHE_CONTROL = "public, max-age=0, must-revalidate";
3
2
 
4
- export const DEFAULT_SSR_CDN_CACHE_CONTROL = DEFAULT_SSR_CACHE_CONTROL;
3
+ export const DEFAULT_SSR_CDN_CACHE_CONTROL =
4
+ "public, s-maxage=3600, stale-while-revalidate=604800, stale-if-error=3600";
5
5
 
6
6
  export const DEFAULT_SSR_NETLIFY_CDN_CACHE_CONTROL =
7
- "public, durable, max-age=5, stale-while-revalidate=604800, stale-if-error=3600";
7
+ "public, durable, s-maxage=3600, stale-while-revalidate=604800, stale-if-error=3600";
8
+
9
+ // React Router data requests can select a subset of route loaders with
10
+ // `?_routes=...`. Ignore every other query parameter (UTM tags, click ids,
11
+ // and similar tracking noise) so they cannot fragment Netlify's SSR cache.
12
+ export const DEFAULT_SSR_NETLIFY_VARY = "query=_routes";
8
13
 
9
14
  export const DEFAULT_SSR_CACHE_HEADERS = {
10
15
  "cache-control": DEFAULT_SSR_CACHE_CONTROL,
11
16
  "cdn-cache-control": DEFAULT_SSR_CDN_CACHE_CONTROL,
12
17
  "netlify-cdn-cache-control": DEFAULT_SSR_NETLIFY_CDN_CACHE_CONTROL,
18
+ "netlify-vary": DEFAULT_SSR_NETLIFY_VARY,
13
19
  } as const;
14
20
 
15
21
  export const DEFAULT_SPECULATION_RULES_PATH =
@@ -197,6 +197,8 @@ export interface UsageRecord {
197
197
  * provider-reported dollar cost so two surfaces agree exactly.
198
198
  */
199
199
  costCentsX100?: number;
200
+ /** Whether cost is provider-reported, estimated, or unavailable. */
201
+ costSource?: UsageCostSource;
200
202
  orgId?: string;
201
203
  runId?: string;
202
204
  threadId?: string;
@@ -206,6 +208,8 @@ export interface UsageRecord {
206
208
  sourceId?: string;
207
209
  }
208
210
 
211
+ export type UsageCostSource = "reported" | "estimated" | "unavailable";
212
+
209
213
  let _initPromise: Promise<void> | undefined;
210
214
 
211
215
  async function ensureUsageTable(): Promise<void> {
@@ -221,6 +225,7 @@ async function ensureUsageTable(): Promise<void> {
221
225
  cache_read_tokens ${intType()} NOT NULL DEFAULT 0,
222
226
  cache_write_tokens ${intType()} NOT NULL DEFAULT 0,
223
227
  cost_cents_x100 ${intType()} NOT NULL DEFAULT 0,
228
+ cost_source TEXT NOT NULL DEFAULT 'estimated',
224
229
  model TEXT NOT NULL DEFAULT '',
225
230
  label TEXT NOT NULL DEFAULT 'chat',
226
231
  app TEXT NOT NULL DEFAULT '',
@@ -240,6 +245,7 @@ async function ensureUsageTable(): Promise<void> {
240
245
  const additions: Array<[string, string]> = [
241
246
  ["cache_read_tokens", `${intType()} NOT NULL DEFAULT 0`],
242
247
  ["cache_write_tokens", `${intType()} NOT NULL DEFAULT 0`],
248
+ ["cost_source", `TEXT NOT NULL DEFAULT 'estimated'`],
243
249
  ["label", `TEXT NOT NULL DEFAULT 'chat'`],
244
250
  ["app", `TEXT NOT NULL DEFAULT ''`],
245
251
  ["ref_id", `TEXT NOT NULL DEFAULT ''`],
@@ -379,6 +385,7 @@ export async function recordUsage(
379
385
  app,
380
386
  refId,
381
387
  costCentsX100,
388
+ costSource,
382
389
  orgId,
383
390
  runId,
384
391
  threadId,
@@ -410,14 +417,24 @@ export async function recordUsage(
410
417
 
411
418
  // Prefer an explicit precomputed cost (e.g. a provider-reported dollar cost);
412
419
  // otherwise derive it from tokens via the pricing table.
420
+ const resolvedCostSource =
421
+ costSource ?? (costCentsX100 == null ? "estimated" : "reported");
413
422
  const costX100 =
414
- costCentsX100 ??
415
- calculateCost(inTok, outTok, modelName, cacheReadTokens, cacheWriteTokens);
423
+ resolvedCostSource === "unavailable"
424
+ ? 0
425
+ : (costCentsX100 ??
426
+ calculateCost(
427
+ inTok,
428
+ outTok,
429
+ modelName,
430
+ cacheReadTokens,
431
+ cacheWriteTokens,
432
+ ));
416
433
  const id = Date.now() * 1000 + Math.floor(Math.random() * 1000);
417
434
  await client.execute({
418
435
  sql: `INSERT INTO token_usage
419
- (id, owner_email, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, cost_cents_x100, model, label, app, ref_id, org_id, run_id, thread_id, task_id, integration_scope_id, source_platform, source_id, created_at)
420
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
436
+ (id, owner_email, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, cost_cents_x100, cost_source, model, label, app, ref_id, org_id, run_id, thread_id, task_id, integration_scope_id, source_platform, source_id, created_at)
437
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
421
438
  args: [
422
439
  id,
423
440
  ownerEmail,
@@ -426,6 +443,7 @@ export async function recordUsage(
426
443
  cacheReadTokens,
427
444
  cacheWriteTokens,
428
445
  costX100,
446
+ resolvedCostSource,
429
447
  modelName,
430
448
  resolvedLabel,
431
449
  resolvedApp,
@@ -469,6 +487,7 @@ export interface UsageBucket {
469
487
  cacheReadTokens: number;
470
488
  cacheWriteTokens: number;
471
489
  cents: number;
490
+ cost: UsageCostAggregate;
472
491
  calls: number;
473
492
  }
474
493
 
@@ -476,9 +495,15 @@ export interface DailyBucket {
476
495
  /** YYYY-MM-DD (UTC) */
477
496
  date: string;
478
497
  cents: number;
498
+ cost: UsageCostAggregate;
479
499
  calls: number;
480
500
  }
481
501
 
502
+ export type UsageCostAggregate =
503
+ | { status: "known"; knownCents: number; unavailableCalls: 0 }
504
+ | { status: "partial"; knownCents: number; unavailableCalls: number }
505
+ | { status: "unavailable"; knownCents: 0; unavailableCalls: number };
506
+
482
507
  export interface UsageRecentEntry {
483
508
  id: number;
484
509
  createdAt: number;
@@ -490,11 +515,14 @@ export interface UsageRecentEntry {
490
515
  cacheReadTokens: number;
491
516
  cacheWriteTokens: number;
492
517
  cents: number;
518
+ costSource: UsageCostSource;
493
519
  }
494
520
 
495
521
  export interface UsageSummary {
496
522
  billing?: UsageBillingMode;
523
+ /** Legacy known-cost subtotal. Use totalCost to preserve unavailable spend. */
497
524
  totalCents: number;
525
+ totalCost: UsageCostAggregate;
498
526
  totalCalls: number;
499
527
  totalInputTokens: number;
500
528
  totalOutputTokens: number;
@@ -523,7 +551,8 @@ export async function getUsageSummary(
523
551
 
524
552
  const totalRow = await client.execute({
525
553
  sql: `SELECT
526
- COALESCE(SUM(cost_cents_x100), 0) AS cents,
554
+ COALESCE(SUM(CASE WHEN cost_source = 'unavailable' THEN 0 ELSE cost_cents_x100 END), 0) AS known_cents,
555
+ COALESCE(SUM(CASE WHEN cost_source = 'unavailable' THEN 1 ELSE 0 END), 0) AS unavailable_calls,
527
556
  COUNT(*) AS calls,
528
557
  COALESCE(SUM(input_tokens), 0) AS in_tok,
529
558
  COALESCE(SUM(output_tokens), 0) AS out_tok,
@@ -536,7 +565,8 @@ export async function getUsageSummary(
536
565
 
537
566
  const bucketSql = (col: string) => ({
538
567
  sql: `SELECT ${col} AS k,
539
- COALESCE(SUM(cost_cents_x100), 0) AS cents,
568
+ COALESCE(SUM(CASE WHEN cost_source = 'unavailable' THEN 0 ELSE cost_cents_x100 END), 0) AS known_cents,
569
+ COALESCE(SUM(CASE WHEN cost_source = 'unavailable' THEN 1 ELSE 0 END), 0) AS unavailable_calls,
540
570
  COUNT(*) AS calls,
541
571
  COALESCE(SUM(input_tokens), 0) AS in_tok,
542
572
  COALESCE(SUM(output_tokens), 0) AS out_tok,
@@ -545,16 +575,19 @@ export async function getUsageSummary(
545
575
  FROM token_usage
546
576
  WHERE owner_email = ? AND created_at >= ?
547
577
  GROUP BY ${col}
548
- ORDER BY cents DESC`,
578
+ ORDER BY known_cents DESC`,
549
579
  args: [options.ownerEmail, sinceMs],
550
580
  });
551
581
 
552
582
  const mapBuckets = (rows: unknown[]): UsageBucket[] =>
553
583
  rows.map((r) => {
554
584
  const row = r as Record<string, number | string | null>;
585
+ const knownCents = Number(row.known_cents ?? 0) / 100;
586
+ const unavailableCalls = Number(row.unavailable_calls ?? 0);
555
587
  return {
556
588
  key: String(row.k ?? ""),
557
- cents: Number(row.cents ?? 0) / 100,
589
+ cents: knownCents,
590
+ cost: buildUsageCostAggregate(knownCents, unavailableCalls),
558
591
  calls: Number(row.calls ?? 0),
559
592
  inputTokens: Number(row.in_tok ?? 0),
560
593
  outputTokens: Number(row.out_tok ?? 0),
@@ -573,30 +606,42 @@ export async function getUsageSummary(
573
606
  // date functions (SQLite `strftime`, Postgres `to_char`). Cheap enough
574
607
  // for a 30-day window; if this grows, swap for a dialect-aware query.
575
608
  const dayRows = await client.execute({
576
- sql: `SELECT created_at, cost_cents_x100 FROM token_usage
609
+ sql: `SELECT created_at, cost_cents_x100, cost_source FROM token_usage
577
610
  WHERE owner_email = ? AND created_at >= ?`,
578
611
  args: [options.ownerEmail, sinceMs],
579
612
  });
580
- const dayMap = new Map<string, { cents: number; calls: number }>();
581
- for (const row of dayRows.rows as Array<Record<string, number>>) {
613
+ const dayMap = new Map<
614
+ string,
615
+ { knownCentsX100: number; unavailableCalls: number; calls: number }
616
+ >();
617
+ for (const row of dayRows.rows as Array<Record<string, number | string>>) {
582
618
  const date = new Date(Number(row.created_at)).toISOString().slice(0, 10);
583
- const prev = dayMap.get(date) ?? { cents: 0, calls: 0 };
584
- prev.cents += Number(row.cost_cents_x100 ?? 0);
619
+ const prev = dayMap.get(date) ?? {
620
+ knownCentsX100: 0,
621
+ unavailableCalls: 0,
622
+ calls: 0,
623
+ };
624
+ if (row.cost_source === "unavailable") prev.unavailableCalls += 1;
625
+ else prev.knownCentsX100 += Number(row.cost_cents_x100 ?? 0);
585
626
  prev.calls += 1;
586
627
  dayMap.set(date, prev);
587
628
  }
588
629
  const byDay: DailyBucket[] = [...dayMap.entries()]
589
- .map(([date, v]) => ({
590
- date,
591
- cents: v.cents / 100,
592
- calls: v.calls,
593
- }))
630
+ .map(([date, v]) => {
631
+ const knownCents = v.knownCentsX100 / 100;
632
+ return {
633
+ date,
634
+ cents: knownCents,
635
+ cost: buildUsageCostAggregate(knownCents, v.unavailableCalls),
636
+ calls: v.calls,
637
+ };
638
+ })
594
639
  .sort((a, b) => a.date.localeCompare(b.date));
595
640
 
596
641
  const recentRows = await client.execute({
597
642
  sql: `SELECT id, created_at, label, app, model,
598
643
  input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
599
- cost_cents_x100
644
+ cost_cents_x100, cost_source
600
645
  FROM token_usage
601
646
  WHERE owner_email = ?
602
647
  ORDER BY created_at DESC
@@ -616,11 +661,15 @@ export async function getUsageSummary(
616
661
  cacheReadTokens: Number(row.cache_read_tokens ?? 0),
617
662
  cacheWriteTokens: Number(row.cache_write_tokens ?? 0),
618
663
  cents: Number(row.cost_cents_x100 ?? 0) / 100,
664
+ costSource: String(row.cost_source ?? "estimated") as UsageCostSource,
619
665
  }));
620
666
 
667
+ const knownCents = Number(t.known_cents ?? 0) / 100;
668
+ const unavailableCalls = Number(t.unavailable_calls ?? 0);
621
669
  return {
622
670
  billing: USD_USAGE_BILLING,
623
- totalCents: Number(t.cents ?? 0) / 100,
671
+ totalCents: knownCents,
672
+ totalCost: buildUsageCostAggregate(knownCents, unavailableCalls),
624
673
  totalCalls: Number(t.calls ?? 0),
625
674
  totalInputTokens: Number(t.in_tok ?? 0),
626
675
  totalOutputTokens: Number(t.out_tok ?? 0),
@@ -634,3 +683,16 @@ export async function getUsageSummary(
634
683
  recent,
635
684
  };
636
685
  }
686
+
687
+ function buildUsageCostAggregate(
688
+ knownCents: number,
689
+ unavailableCalls: number,
690
+ ): UsageCostAggregate {
691
+ if (unavailableCalls === 0) {
692
+ return { status: "known", knownCents, unavailableCalls: 0 };
693
+ }
694
+ if (knownCents === 0) {
695
+ return { status: "unavailable", knownCents: 0, unavailableCalls };
696
+ }
697
+ return { status: "partial", knownCents, unavailableCalls };
698
+ }
@@ -748,14 +748,15 @@ export const dataSources: DataSource[] = [
748
748
  {
749
749
  id: "slack",
750
750
  name: "Slack",
751
- description: "Channel messages and workspace search",
751
+ description:
752
+ "Channel history and workspace search. Prefer a Slack workspace integration from Settings > Connections; a local bot token remains available as a legacy fallback.",
752
753
  category: "communication",
753
754
  icon: IconMessage,
754
755
  envKeys: ["SLACK_BOT_TOKEN"],
755
756
  docsUrl: "https://api.slack.com/methods",
756
757
  walkthroughSteps: [
757
758
  {
758
- title: "Create a Slack App",
759
+ title: "Create a Slack App for local fallback access",
759
760
  description:
760
761
  "Go to api.slack.com/apps, create a new app, and add OAuth scopes: channels:read, channels:history, search:read.",
761
762
  url: "https://api.slack.com/apps",
@@ -767,10 +768,10 @@ export const dataSources: DataSource[] = [
767
768
  'Under "OAuth & Permissions", install the app and copy the Bot User OAuth Token.',
768
769
  },
769
770
  {
770
- title: "Enter your Bot Token",
771
+ title: "Enter your local fallback Bot Token",
771
772
  description: 'Paste the Bot User OAuth Token (starts with "xoxb-").',
772
773
  inputKey: "SLACK_BOT_TOKEN",
773
- inputLabel: "Bot Token",
774
+ inputLabel: "Bot Token (legacy local fallback)",
774
775
  inputPlaceholder: "xoxb-...",
775
776
  inputType: "password",
776
777
  },
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-10
4
+ ---
5
+
6
+ Slack data-source setup now favors workspace connections and identifies raw bot tokens as a legacy local fallback.
@@ -107,10 +107,14 @@ export const credentialKeys: CredentialKeyConfig[] = [
107
107
  required: false,
108
108
  },
109
109
  // Slack
110
- { key: "SLACK_BOT_TOKEN", label: "Slack Bot Token", required: false },
110
+ {
111
+ key: "SLACK_BOT_TOKEN",
112
+ label: "Slack Bot Token (legacy local fallback)",
113
+ required: false,
114
+ },
111
115
  {
112
116
  key: "SLACK_BOT_TOKEN_2",
113
- label: "Slack Bot Token (secondary)",
117
+ label: "Slack Bot Token (legacy secondary fallback)",
114
118
  required: false,
115
119
  },
116
120
  // Notion
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-10
4
+ ---
5
+
6
+ Slack backfills now favor workspace connections while clearly marking raw bot tokens as a local fallback.
@@ -2,9 +2,9 @@ import { registerRequiredSecret } from "@agent-native/core/secrets";
2
2
 
3
3
  registerRequiredSecret({
4
4
  key: "SLACK_BOT_TOKEN",
5
- label: "Slack Bot Token",
5
+ label: "Slack Bot Token (local fallback)",
6
6
  description:
7
- "Optional Slack bot token for channel backfills. Brain only scans allow-listed channels and never enumerates DMs.",
7
+ "Optional local fallback for Brain channel backfills. Prefer a Slack workspace integration in Settings > Connections. Brain only scans allow-listed channels and never enumerates DMs.",
8
8
  docsUrl: "https://api.slack.com/authentication/token-types",
9
9
  scope: "workspace",
10
10
  kind: "api-key",
@@ -154,6 +154,9 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
154
154
  );
155
155
  const [headerSlot, setHeaderSlot] = useState<HTMLElement | null>(null);
156
156
  const showCollapsedSidebar = sidebarCollapsed && !isMobile;
157
+ const sidebarHasNewRecordingAction = isMobile
158
+ ? sidebarOpen
159
+ : !sidebarCollapsed;
157
160
 
158
161
  // Routes whose page renders its own h-12 toolbar. Layout still mounts Sidebar
159
162
  // + AgentSidebar, but skips its own header so there's no double-header.
@@ -603,7 +606,10 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
603
606
  </div>
604
607
  )}
605
608
  <main className="agent-native-app-main flex min-h-0 flex-1 flex-col overflow-y-auto">
606
- <PageHeaderSlotProvider slot={headerSlot}>
609
+ <PageHeaderSlotProvider
610
+ slot={headerSlot}
611
+ sidebarHasNewRecordingAction={sidebarHasNewRecordingAction}
612
+ >
607
613
  {children}
608
614
  </PageHeaderSlotProvider>
609
615
  </main>