@agent-native/core 0.77.4 → 0.77.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +31 -0
  3. package/corpus/core/package.json +3 -3
  4. package/corpus/core/src/agent/production-agent.ts +35 -0
  5. package/corpus/core/src/agent/run-store.ts +2 -0
  6. package/corpus/core/src/agent/thread-data-builder.ts +21 -2
  7. package/corpus/core/src/cli/create.ts +35 -1
  8. package/corpus/core/src/cli/skills.ts +8 -0
  9. package/corpus/core/src/client/AgentPanel.tsx +57 -25
  10. package/corpus/core/src/client/MultiTabAssistantChat.tsx +31 -1
  11. package/corpus/core/src/client/analytics.ts +24 -2
  12. package/corpus/core/src/client/changelog/Changelog.tsx +22 -10
  13. package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +405 -35
  14. package/corpus/core/src/client/use-agent-engine-configured.ts +29 -15
  15. package/corpus/core/src/client/use-chat-threads.ts +104 -55
  16. package/corpus/core/src/server/analytics.ts +27 -3
  17. package/corpus/core/src/sharing/access.ts +35 -13
  18. package/corpus/core/src/sharing/actions/share-resource.ts +32 -9
  19. package/corpus/core/src/sharing/actions/unshare-resource.ts +25 -2
  20. package/corpus/core/src/sharing/registry.ts +9 -0
  21. package/corpus/core/src/vite/client.ts +3 -0
  22. package/corpus/templates/analytics/actions/data-source-status.ts +25 -14
  23. package/corpus/templates/analytics/app/components/layout/NewAnalysisDialog.tsx +2 -2
  24. package/corpus/templates/analytics/app/lib/data-source-status.ts +47 -6
  25. package/corpus/templates/analytics/app/lib/data-sources.ts +14 -3
  26. package/corpus/templates/analytics/app/pages/DataSources.tsx +24 -5
  27. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +15 -43
  28. package/corpus/templates/analytics/changelog/2026-06-25-fixed-hubspot-data-source-status-so-private-app-tokens-legac.md +6 -0
  29. package/corpus/templates/analytics/netlify.toml +1 -0
  30. package/corpus/templates/analytics/server/lib/credential-keys.ts +3 -1
  31. package/corpus/templates/analytics/server/routes/api/test-connection.post.ts +12 -3
  32. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +5 -2
  33. package/corpus/templates/assets/changelog/2026-06-25-create-now-returns-from-agent-settings-to-the-asset-chat-eve.md +6 -0
  34. package/corpus/templates/assets/netlify.toml +1 -0
  35. package/corpus/templates/brain/netlify.toml +1 -0
  36. package/corpus/templates/calendar/app/i18n-data.ts +12 -0
  37. package/corpus/templates/calendar/app/lib/clipboard.ts +58 -0
  38. package/corpus/templates/calendar/app/pages/AvailabilitySettings.tsx +15 -9
  39. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +6 -2
  40. package/corpus/templates/calendar/changelog/2026-06-25-booking-link-copy-buttons-no-longer-fail-when-clipboard-perm.md +6 -0
  41. package/corpus/templates/calendar/netlify.toml +1 -0
  42. package/corpus/templates/chat/netlify.toml +1 -0
  43. package/corpus/templates/clips/AGENTS.md +9 -7
  44. package/corpus/templates/clips/actions/add-vocabulary-term.ts +2 -2
  45. package/corpus/templates/clips/actions/archive-recording.ts +5 -2
  46. package/corpus/templates/clips/actions/clear-edits.ts +5 -2
  47. package/corpus/templates/clips/actions/create-folder.ts +7 -2
  48. package/corpus/templates/clips/actions/delete-folder.ts +7 -4
  49. package/corpus/templates/clips/actions/delete-recording-permanent.ts +5 -2
  50. package/corpus/templates/clips/actions/finalize-recording.ts +5 -2
  51. package/corpus/templates/clips/actions/import-loom-recording.ts +2 -1
  52. package/corpus/templates/clips/actions/list-notifications.ts +9 -5
  53. package/corpus/templates/clips/actions/list-organization-state.ts +2 -1
  54. package/corpus/templates/clips/actions/list-recordings.ts +4 -1
  55. package/corpus/templates/clips/actions/move-recording.ts +5 -1
  56. package/corpus/templates/clips/actions/rename-folder.ts +6 -3
  57. package/corpus/templates/clips/actions/request-transcript.ts +5 -2
  58. package/corpus/templates/clips/actions/restore-recording.ts +5 -2
  59. package/corpus/templates/clips/actions/save-browser-diagnostics.ts +21 -8
  60. package/corpus/templates/clips/actions/set-chapters.ts +5 -2
  61. package/corpus/templates/clips/actions/set-thumbnail.ts +5 -2
  62. package/corpus/templates/clips/actions/split-recording.ts +5 -2
  63. package/corpus/templates/clips/actions/stitch-recordings.ts +6 -2
  64. package/corpus/templates/clips/actions/trash-recording.ts +5 -2
  65. package/corpus/templates/clips/actions/undo-edit.ts +5 -2
  66. package/corpus/templates/clips/actions/view-screen.ts +2 -1
  67. package/corpus/templates/clips/app/components/library/recording-card.tsx +24 -7
  68. package/corpus/templates/clips/app/i18n/ar-SA.ts +14 -0
  69. package/corpus/templates/clips/app/i18n/de-DE.ts +14 -0
  70. package/corpus/templates/clips/app/i18n/en-US.ts +14 -0
  71. package/corpus/templates/clips/app/i18n/es-ES.ts +14 -0
  72. package/corpus/templates/clips/app/i18n/fr-FR.ts +14 -0
  73. package/corpus/templates/clips/app/i18n/hi-IN.ts +14 -0
  74. package/corpus/templates/clips/app/i18n/ja-JP.ts +14 -0
  75. package/corpus/templates/clips/app/i18n/ko-KR.ts +14 -0
  76. package/corpus/templates/clips/app/i18n/pt-BR.ts +14 -0
  77. package/corpus/templates/clips/app/i18n/zh-CN.ts +14 -0
  78. package/corpus/templates/clips/app/lib/browser-diagnostics-capture.ts +5 -1
  79. package/corpus/templates/clips/app/root.tsx +2 -2
  80. package/corpus/templates/clips/app/routes/_app.extensions.$id.tsx +4 -1
  81. package/corpus/templates/clips/app/routes/_app.extensions._index.tsx +4 -1
  82. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +12 -5
  83. package/corpus/templates/clips/changelog/2026-06-25-chrome-extension-recordings-now-upload-large-capture-bursts.md +6 -0
  84. package/corpus/templates/clips/changelog/2026-06-25-console-diagnostics-keep-more-useful-non-secret-details-when.md +6 -0
  85. package/corpus/templates/clips/changelog/2026-06-25-fixed-owner-recognition-so-clips-created-from-desktop-and-we.md +6 -0
  86. package/corpus/templates/clips/changelog/2026-06-25-localized-clips-settings-changelog-and-extension-sidebar-lab.md +6 -0
  87. package/corpus/templates/clips/chrome-extension/package.json +3 -0
  88. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  89. package/corpus/templates/clips/chrome-extension/src/background.ts +85 -9
  90. package/corpus/templates/clips/chrome-extension/src/content-script.ts +55 -0
  91. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +79 -3
  92. package/corpus/templates/clips/chrome-extension/src/options.ts +10 -0
  93. package/corpus/templates/clips/chrome-extension/src/overlay.ts +6 -0
  94. package/corpus/templates/clips/chrome-extension/src/permission.ts +8 -1
  95. package/corpus/templates/clips/chrome-extension/src/popup.ts +65 -39
  96. package/corpus/templates/clips/chrome-extension/src/sentry.ts +206 -0
  97. package/corpus/templates/clips/chrome-extension/vite.config.ts +47 -0
  98. package/corpus/templates/clips/desktop/src/overlays/countdown.tsx +4 -2
  99. package/corpus/templates/clips/desktop/src/styles.css +15 -1
  100. package/corpus/templates/clips/netlify.toml +1 -0
  101. package/corpus/templates/clips/server/db/index.ts +1 -0
  102. package/corpus/templates/clips/server/lib/google-calendar-oauth.ts +35 -23
  103. package/corpus/templates/clips/server/lib/public-agent-context.ts +8 -1
  104. package/corpus/templates/clips/server/lib/recordings.ts +18 -2
  105. package/corpus/templates/clips/server/lib/slack-oauth.ts +9 -6
  106. package/corpus/templates/clips/server/plugins/onboarding-calendar.ts +6 -2
  107. package/corpus/templates/clips/server/routes/api/public-recording.get.ts +2 -2
  108. package/corpus/templates/clips/server/routes/api/recordings/[recordingId]/thumbnail.post.ts +5 -2
  109. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +5 -2
  110. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +5 -2
  111. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +5 -2
  112. package/corpus/templates/clips/shared/browser-diagnostics.ts +14 -4
  113. package/corpus/templates/content/netlify.toml +1 -0
  114. package/corpus/templates/design/netlify.toml +1 -0
  115. package/corpus/templates/dispatch/netlify.toml +1 -0
  116. package/corpus/templates/forms/netlify.toml +1 -0
  117. package/corpus/templates/macros/netlify.toml +1 -0
  118. package/corpus/templates/mail/app/components/email/EmailList.tsx +7 -3
  119. package/corpus/templates/mail/app/components/email/EmailThread.tsx +4 -1
  120. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +10 -2
  121. package/corpus/templates/mail/app/hooks/use-compose-state.ts +15 -5
  122. package/corpus/templates/mail/app/hooks/use-google-auth.ts +47 -9
  123. package/corpus/templates/mail/changelog/2026-06-25-account-avatars-stay-stable-while-mail-refreshes-google-acco.md +6 -0
  124. package/corpus/templates/mail/changelog/2026-06-25-mail-background-draft-saves-and-thread-prefetches-no-longer.md +6 -0
  125. package/corpus/templates/mail/netlify.toml +1 -0
  126. package/corpus/templates/plan/.agents/skills/visual-plan/references/canvas.md +8 -0
  127. package/corpus/templates/plan/netlify.toml +1 -0
  128. package/corpus/templates/slides/netlify.toml +1 -0
  129. package/corpus/templates/videos/netlify.toml +1 -0
  130. package/dist/agent/production-agent.d.ts.map +1 -1
  131. package/dist/agent/production-agent.js +31 -0
  132. package/dist/agent/production-agent.js.map +1 -1
  133. package/dist/agent/run-store.d.ts +2 -0
  134. package/dist/agent/run-store.d.ts.map +1 -1
  135. package/dist/agent/run-store.js +2 -0
  136. package/dist/agent/run-store.js.map +1 -1
  137. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  138. package/dist/agent/thread-data-builder.js +23 -2
  139. package/dist/agent/thread-data-builder.js.map +1 -1
  140. package/dist/cli/create.d.ts.map +1 -1
  141. package/dist/cli/create.js +28 -1
  142. package/dist/cli/create.js.map +1 -1
  143. package/dist/cli/skills.d.ts +1 -1
  144. package/dist/cli/skills.d.ts.map +1 -1
  145. package/dist/cli/skills.js +8 -0
  146. package/dist/cli/skills.js.map +1 -1
  147. package/dist/client/AgentPanel.d.ts +8 -2
  148. package/dist/client/AgentPanel.d.ts.map +1 -1
  149. package/dist/client/AgentPanel.js +29 -15
  150. package/dist/client/AgentPanel.js.map +1 -1
  151. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  152. package/dist/client/MultiTabAssistantChat.js +4 -4
  153. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  154. package/dist/client/analytics.d.ts.map +1 -1
  155. package/dist/client/analytics.js +22 -2
  156. package/dist/client/analytics.js.map +1 -1
  157. package/dist/client/changelog/Changelog.d.ts +7 -2
  158. package/dist/client/changelog/Changelog.d.ts.map +1 -1
  159. package/dist/client/changelog/Changelog.js +9 -7
  160. package/dist/client/changelog/Changelog.js.map +1 -1
  161. package/dist/client/extensions/ExtensionsSidebarSection.d.ts.map +1 -1
  162. package/dist/client/extensions/ExtensionsSidebarSection.js +339 -11
  163. package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
  164. package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
  165. package/dist/client/use-agent-engine-configured.js +23 -13
  166. package/dist/client/use-agent-engine-configured.js.map +1 -1
  167. package/dist/client/use-chat-threads.d.ts +4 -0
  168. package/dist/client/use-chat-threads.d.ts.map +1 -1
  169. package/dist/client/use-chat-threads.js +49 -4
  170. package/dist/client/use-chat-threads.js.map +1 -1
  171. package/dist/collab/routes.d.ts +1 -1
  172. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  173. package/dist/observability/routes.d.ts +7 -7
  174. package/dist/server/agent-engine-api-key-route.d.ts +2 -2
  175. package/dist/server/analytics.d.ts +4 -0
  176. package/dist/server/analytics.d.ts.map +1 -1
  177. package/dist/server/analytics.js +19 -3
  178. package/dist/server/analytics.js.map +1 -1
  179. package/dist/sharing/access.d.ts.map +1 -1
  180. package/dist/sharing/access.js +26 -12
  181. package/dist/sharing/access.js.map +1 -1
  182. package/dist/sharing/actions/share-resource.js +20 -9
  183. package/dist/sharing/actions/share-resource.js.map +1 -1
  184. package/dist/sharing/actions/unshare-resource.js +13 -2
  185. package/dist/sharing/actions/unshare-resource.js.map +1 -1
  186. package/dist/sharing/registry.d.ts +9 -0
  187. package/dist/sharing/registry.d.ts.map +1 -1
  188. package/dist/sharing/registry.js.map +1 -1
  189. package/dist/vite/client.d.ts.map +1 -1
  190. package/dist/vite/client.js +1 -0
  191. package/dist/vite/client.js.map +1 -1
  192. package/package.json +3 -3
@@ -60,8 +60,8 @@ export function NewAnalysisDialog() {
60
60
  const status = statusData as DataSourceStatusResponse | undefined;
61
61
  const envStatus = credentialRowsFromStatus(status);
62
62
  const configuredSources = useMemo(
63
- () => getConfiguredDataSources(envStatus),
64
- [envStatus],
63
+ () => getConfiguredDataSources(envStatus, status),
64
+ [envStatus, status],
65
65
  );
66
66
  const configuredSourceNames = configuredSources.map((source) => source.name);
67
67
  const statusMessage = status?.message || status?.error;
@@ -79,6 +79,10 @@ const sharedConnectionLabels: Record<SharedConnectionStatusKind, string> = {
79
79
  local_credentials: "Local credentials",
80
80
  };
81
81
 
82
+ function normalizeCredentialKey(key: string): string {
83
+ return key.trim().toUpperCase();
84
+ }
85
+
82
86
  export function credentialRowsFromStatus(
83
87
  data: DataSourceStatusResponse | EnvKeyStatus[] | undefined,
84
88
  ): EnvKeyStatus[] {
@@ -90,7 +94,9 @@ export function getOptionalCredentialKeys(source: DataSource): Set<string> {
90
94
  return new Set(
91
95
  source.walkthroughSteps
92
96
  .filter((step) => step.optional)
93
- .map((step) => step.inputKey)
97
+ .map((step) =>
98
+ step.inputKey ? normalizeCredentialKey(step.inputKey) : undefined,
99
+ )
94
100
  .filter((k): k is string => Boolean(k)),
95
101
  );
96
102
  }
@@ -99,11 +105,21 @@ export function isSourceConfigured(
99
105
  source: DataSource,
100
106
  envStatus: EnvKeyStatus[],
101
107
  ): boolean {
102
- const statusMap = new Map(envStatus.map((s) => [s.key, s.configured]));
108
+ const statusMap = new Map(
109
+ envStatus.map((s) => [normalizeCredentialKey(s.key), s.configured]),
110
+ );
103
111
  const optionalKeys = getOptionalCredentialKeys(source);
104
- return source.envKeys
105
- .filter((key) => !optionalKeys.has(key))
106
- .every((key) => statusMap.get(key) === true);
112
+ const requiredKeys = source.envKeys.filter(
113
+ (key) => !optionalKeys.has(normalizeCredentialKey(key)),
114
+ );
115
+ if (source.credentialRequirementMode === "any") {
116
+ return requiredKeys.some(
117
+ (key) => statusMap.get(normalizeCredentialKey(key)) === true,
118
+ );
119
+ }
120
+ return requiredKeys.every(
121
+ (key) => statusMap.get(normalizeCredentialKey(key)) === true,
122
+ );
107
123
  }
108
124
 
109
125
  export function getWorkspaceProviderIdForSource(
@@ -132,6 +148,14 @@ export function getWorkspaceConnectionForSource(
132
148
  );
133
149
  }
134
150
 
151
+ export function getProviderStatusForSource(
152
+ source: DataSource,
153
+ data: DataSourceStatusResponse | undefined,
154
+ ): DataSourceProviderStatus | undefined {
155
+ const providerId = getWorkspaceProviderIdForSource(source) ?? source.id;
156
+ return data?.providers?.find((provider) => provider.provider === providerId);
157
+ }
158
+
135
159
  export function getSharedConnectionStatus(
136
160
  source: DataSource,
137
161
  data: DataSourceStatusResponse | undefined,
@@ -178,12 +202,29 @@ export function isSourceReady(
178
202
  ): boolean {
179
203
  return (
180
204
  isSourceConfigured(source, envStatus) ||
205
+ getProviderStatusForSource(source, data)?.configured === true ||
181
206
  getSharedConnectionStatus(source, data, envStatus)?.kind === "ready"
182
207
  );
183
208
  }
184
209
 
210
+ export function isSourceLocallyConfigured(
211
+ source: DataSource,
212
+ data: DataSourceStatusResponse | undefined,
213
+ envStatus: EnvKeyStatus[],
214
+ ): boolean {
215
+ if (isSourceConfigured(source, envStatus)) return true;
216
+ const providerStatus = getProviderStatusForSource(source, data);
217
+ if (!providerStatus?.configured) return false;
218
+ return providerStatus.configuredKeys.length > 0;
219
+ }
220
+
185
221
  export function getConfiguredDataSources(
186
222
  envStatus: EnvKeyStatus[],
223
+ data?: DataSourceStatusResponse,
187
224
  ): DataSource[] {
188
- return dataSources.filter((source) => isSourceConfigured(source, envStatus));
225
+ return dataSources.filter((source) =>
226
+ data
227
+ ? isSourceReady(source, data, envStatus)
228
+ : isSourceConfigured(source, envStatus),
229
+ );
189
230
  }
@@ -52,6 +52,7 @@ export interface DataSource {
52
52
  category: DataSourceCategory;
53
53
  icon: ComponentType<Record<string, unknown>>;
54
54
  envKeys: string[];
55
+ credentialRequirementMode?: "all" | "any";
55
56
  walkthroughSteps: WalkthroughStep[];
56
57
  docsUrl: string;
57
58
  }
@@ -380,7 +381,8 @@ export const dataSources: DataSource[] = [
380
381
  description: "CRM deals, contacts, companies, tickets, and pipelines",
381
382
  category: "crm",
382
383
  icon: IconShoppingCart,
383
- envKeys: ["HUBSPOT_ACCESS_TOKEN"],
384
+ envKeys: ["HUBSPOT_PRIVATE_APP_TOKEN", "HUBSPOT_ACCESS_TOKEN"],
385
+ credentialRequirementMode: "any",
384
386
  docsUrl: "https://developers.hubspot.com/docs/api/overview",
385
387
  walkthroughSteps: [
386
388
  {
@@ -391,10 +393,19 @@ export const dataSources: DataSource[] = [
391
393
  linkText: "Private Apps",
392
394
  },
393
395
  {
394
- title: "Enter your Access Token",
396
+ title: "Enter your Private App Token",
395
397
  description: "Copy the access token from your private app.",
398
+ inputKey: "HUBSPOT_PRIVATE_APP_TOKEN",
399
+ inputLabel: "Private App Token",
400
+ inputPlaceholder: "pat-na1-...",
401
+ inputType: "password",
402
+ },
403
+ {
404
+ title: "Legacy access token",
405
+ description:
406
+ "Only use this if you already have an older HubSpot access token saved under the legacy key.",
396
407
  inputKey: "HUBSPOT_ACCESS_TOKEN",
397
- inputLabel: "Access Token",
408
+ inputLabel: "Legacy Access Token",
398
409
  inputPlaceholder: "pat-na1-...",
399
410
  inputType: "password",
400
411
  },
@@ -66,7 +66,7 @@ import {
66
66
  getOptionalCredentialKeys,
67
67
  getSharedConnectionStatus,
68
68
  isSourceReady,
69
- isSourceConfigured,
69
+ isSourceLocallyConfigured,
70
70
  credentialRowsFromStatus,
71
71
  type DataSourceStatusResponse,
72
72
  type EnvKeyStatus,
@@ -674,6 +674,13 @@ function ConnectedView({
674
674
  ),
675
675
  );
676
676
  const optionalKeys = getOptionalCredentialKeys(source);
677
+ const isAnyCredentialMode = source.credentialRequirementMode === "any";
678
+ const configuredCredentialKeys = new Set(
679
+ envStatus.filter((s) => s.configured).map((s) => s.key),
680
+ );
681
+ const hasAlternativeCredential =
682
+ isAnyCredentialMode &&
683
+ source.envKeys.some((key) => configuredCredentialKeys.has(key));
677
684
 
678
685
  const handleDisconnect = () => {
679
686
  if (sharedCredentialKeys.length > 0) {
@@ -852,8 +859,7 @@ function ConnectedView({
852
859
  <div className="flex items-start justify-between gap-4">
853
860
  <div className="flex-1 space-y-2">
854
861
  {source.envKeys.map((key) => {
855
- const configured =
856
- envStatus.find((s) => s.key === key)?.configured ?? false;
862
+ const configured = configuredCredentialKeys.has(key);
857
863
  const optional = optionalKeys.has(key);
858
864
  return (
859
865
  <div
@@ -873,6 +879,11 @@ function ConnectedView({
873
879
  <IconCircle className="h-3 w-3" />
874
880
  {t("dataSources.optional")}
875
881
  </span>
882
+ ) : hasAlternativeCredential ? (
883
+ <span className="flex items-center gap-1 whitespace-nowrap text-muted-foreground">
884
+ <IconCircle className="h-3 w-3" />
885
+ Alternative
886
+ </span>
876
887
  ) : (
877
888
  <span className="flex items-center gap-1 whitespace-nowrap text-rose-400">
878
889
  <IconAlertCircle className="h-3 w-3" />
@@ -1643,7 +1654,11 @@ export default function DataSources() {
1643
1654
  <DataSourceCard
1644
1655
  key={source.id}
1645
1656
  source={source}
1646
- locallyConfigured={isSourceConfigured(source, envStatus)}
1657
+ locallyConfigured={isSourceLocallyConfigured(
1658
+ source,
1659
+ statusData,
1660
+ envStatus,
1661
+ )}
1647
1662
  ready={isSourceReady(source, statusData, envStatus)}
1648
1663
  sharedConnectionStatus={getSharedConnectionStatus(
1649
1664
  source,
@@ -1676,7 +1691,11 @@ export default function DataSources() {
1676
1691
  <DataSourceCard
1677
1692
  key={source.id}
1678
1693
  source={source}
1679
- locallyConfigured={isSourceConfigured(source, envStatus)}
1694
+ locallyConfigured={isSourceLocallyConfigured(
1695
+ source,
1696
+ statusData,
1697
+ envStatus,
1698
+ )}
1680
1699
  ready={isSourceReady(source, statusData, envStatus)}
1681
1700
  sharedConnectionStatus={getSharedConnectionStatus(
1682
1701
  source,
@@ -1,9 +1,9 @@
1
- import { useSendToAgentChat, useT } from "@agent-native/core/client";
2
1
  import {
3
- IconAlertTriangle,
4
- IconAlignLeft,
5
- IconLoader2,
6
- } from "@tabler/icons-react";
2
+ PromptComposer,
3
+ useSendToAgentChat,
4
+ useT,
5
+ } from "@agent-native/core/client";
6
+ import { IconAlertTriangle, IconAlignLeft } from "@tabler/icons-react";
7
7
  import { useEffect, useState, type ReactElement, type ReactNode } from "react";
8
8
  import { toast } from "sonner";
9
9
 
@@ -30,7 +30,6 @@ import {
30
30
  SelectValue,
31
31
  } from "@/components/ui/select";
32
32
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
33
- import { Textarea } from "@/components/ui/textarea";
34
33
  import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
35
34
  import { canFormatPanelSql, formatPanelSql } from "@/lib/format-sql";
36
35
 
@@ -182,7 +181,6 @@ function PanelEditorContent({
182
181
  const [form, setForm] = useState<PanelFormValues>(() => panelToForm(panel));
183
182
  const [saving, setSaving] = useState(false);
184
183
  const [error, setError] = useState<string | null>(null);
185
- const [prompt, setPrompt] = useState("");
186
184
  const [tab, setTab] = useState<"describe" | "manual">("describe");
187
185
  const { send, isGenerating } = useSendToAgentChat();
188
186
 
@@ -192,7 +190,6 @@ function PanelEditorContent({
192
190
  setForm(panelToForm(panel));
193
191
  setError(null);
194
192
  setSaving(false);
195
- setPrompt("");
196
193
  // Editing an existing panel always goes straight to the manual form.
197
194
  setTab(panel ? "manual" : "describe");
198
195
  }
@@ -200,7 +197,6 @@ function PanelEditorContent({
200
197
 
201
198
  const isEdit = !!panel;
202
199
  const canSave = form.title.trim().length > 0 && form.sql.trim().length > 0;
203
- const canGenerate = prompt.trim().length > 0 && !isGenerating;
204
200
  const canFormat = canFormatPanelSql(form.source);
205
201
 
206
202
  const handleSubmit = async () => {
@@ -219,13 +215,14 @@ function PanelEditorContent({
219
215
  }
220
216
  };
221
217
 
222
- const handleDescribe = () => {
223
- if (!canGenerate) return;
218
+ const handleDescribe = (text: string) => {
219
+ const trimmed = text.trim();
220
+ if (!trimmed || isGenerating) return;
224
221
  const titlesLine = existingPanelTitles.length
225
222
  ? `Existing panels on this dashboard: ${existingPanelTitles.join(", ")}.`
226
223
  : "This dashboard has no panels yet.";
227
224
  send({
228
- message: prompt.trim(),
225
+ message: trimmed,
229
226
  context:
230
227
  `The user wants to add a new panel to SQL dashboard "${dashboardId}". ${titlesLine} ` +
231
228
  `REAL_DATA_REQUIRED: before saving or answering, run at least one real data-source query action for this panel; \`data-source-status\`, \`list-data-dictionary\`, \`update-dashboard\`, and dry-run validation do not count as data queries. ` +
@@ -246,7 +243,6 @@ function PanelEditorContent({
246
243
  `After the panel saves, call \`refresh-screen\` so the UI picks up the change.`,
247
244
  submit: true,
248
245
  });
249
- setPrompt("");
250
246
  onOpenChange(false);
251
247
  };
252
248
 
@@ -441,28 +437,14 @@ function PanelEditorContent({
441
437
 
442
438
  <TabsContent value="describe" className="mt-4">
443
439
  <div className="grid gap-3">
444
- <Label htmlFor="panel-prompt">{t("panelEditor.whatToChart")}</Label>
445
- <Textarea
446
- id="panel-prompt"
447
- value={prompt}
448
- onChange={(e) => setPrompt(e.target.value)}
449
- placeholder={t("panelEditor.promptPlaceholder")}
450
- className="min-h-[140px] resize-y text-sm"
440
+ <Label>{t("panelEditor.whatToChart")}</Label>
441
+ <PromptComposer
451
442
  autoFocus
452
- onKeyDown={(e) => {
453
- if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
454
- e.preventDefault();
455
- handleDescribe();
456
- }
457
- }}
443
+ disabled={isGenerating}
444
+ placeholder={t("panelEditor.promptPlaceholder")}
445
+ draftScope="analytics:add-panel"
446
+ onSubmit={handleDescribe}
458
447
  />
459
- <p className="text-xs text-muted-foreground">
460
- {t("panelEditor.describeHelp", {
461
- shortcut: `${
462
- /Mac|iPhone|iPad/.test(navigator.userAgent) ? "⌘" : "Ctrl"
463
- }+Enter`,
464
- })}
465
- </p>
466
448
  </div>
467
449
  <EditorFooter className="mt-4">
468
450
  <Button
@@ -473,16 +455,6 @@ function PanelEditorContent({
473
455
  >
474
456
  {t("panelEditor.cancel")}
475
457
  </Button>
476
- <Button size="sm" onClick={handleDescribe} disabled={!canGenerate}>
477
- {isGenerating ? (
478
- <>
479
- <IconLoader2 className="h-3.5 w-3.5 mr-1.5 animate-spin" />
480
- {t("panelEditor.generating")}
481
- </>
482
- ) : (
483
- t("panelEditor.generate")
484
- )}
485
- </Button>
486
458
  </EditorFooter>
487
459
  </TabsContent>
488
460
 
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-25
4
+ ---
5
+
6
+ Fixed HubSpot data source status so private app tokens, legacy tokens, and shared workspace grants show as ready consistently.
@@ -5,6 +5,7 @@ publish = "templates/analytics/dist"
5
5
  functions = "templates/analytics/.netlify/functions-internal"
6
6
 
7
7
  [build.environment]
8
+ GA_MEASUREMENT_ID = "G-ESF7FYXGN9"
8
9
  NITRO_PRESET = "netlify"
9
10
  NPM_CONFIG_PRODUCTION = "false"
10
11
  AGENT_PROD_CODE_EXECUTION = "sandboxed"
@@ -8,6 +8,7 @@ export interface CredentialProviderConfig {
8
8
  provider: string;
9
9
  label: string;
10
10
  requiredKeys: string[];
11
+ requiredMode?: "all" | "any";
11
12
  optionalKeys?: string[];
12
13
  }
13
14
 
@@ -217,7 +218,8 @@ export const credentialProviderConfigs: CredentialProviderConfig[] = [
217
218
  {
218
219
  provider: "hubspot",
219
220
  label: "HubSpot",
220
- requiredKeys: ["HUBSPOT_ACCESS_TOKEN"],
221
+ requiredKeys: ["HUBSPOT_PRIVATE_APP_TOKEN", "HUBSPOT_ACCESS_TOKEN"],
222
+ requiredMode: "any",
221
223
  },
222
224
  {
223
225
  provider: "gong",
@@ -5,7 +5,11 @@ import {
5
5
  resolveCredential,
6
6
  withRequestContextFromEvent,
7
7
  } from "../../lib/credentials";
8
- import { resolveAnalyticsGongCredentials } from "../../lib/provider-credentials";
8
+ import {
9
+ HUBSPOT_ANALYTICS_CREDENTIAL_KEYS,
10
+ resolveAnalyticsGongCredentials,
11
+ resolveAnalyticsProviderCredential,
12
+ } from "../../lib/provider-credentials";
9
13
 
10
14
  export default defineEventHandler(async (event) => {
11
15
  const body = await readBody(event);
@@ -91,8 +95,13 @@ export default defineEventHandler(async (event) => {
91
95
  }
92
96
 
93
97
  case "hubspot": {
94
- const token = await resolveCredential("HUBSPOT_ACCESS_TOKEN", ctx);
95
- if (!token) return { ok: false, error: "Missing access token" };
98
+ const credential = await resolveAnalyticsProviderCredential({
99
+ provider: "hubspot",
100
+ keys: HUBSPOT_ANALYTICS_CREDENTIAL_KEYS,
101
+ ctx,
102
+ });
103
+ const token = credential?.value;
104
+ if (!token) return { ok: false, error: "Missing HubSpot token" };
96
105
  const res = await fetch(
97
106
  "https://api.hubapi.com/crm/v3/objects/contacts?limit=1",
98
107
  {
@@ -2,6 +2,7 @@ import {
2
2
  DevDatabaseLink,
3
3
  FeedbackButton,
4
4
  appPath,
5
+ focusAgentChat,
5
6
  navigateWithAgentChatViewTransition,
6
7
  useActionQuery,
7
8
  useChatThreads,
@@ -507,14 +508,16 @@ export function Sidebar() {
507
508
  onClick={(event) => {
508
509
  if (
509
510
  item.href === "/" &&
510
- !isCreateRoute &&
511
511
  !event.metaKey &&
512
512
  !event.ctrlKey &&
513
513
  !event.shiftKey &&
514
514
  !event.altKey
515
515
  ) {
516
516
  event.preventDefault();
517
- navigateWithAgentChatViewTransition(navigate, "/");
517
+ focusAgentChat();
518
+ if (!isCreateRoute || location.pathname !== "/") {
519
+ navigateWithAgentChatViewTransition(navigate, "/");
520
+ }
518
521
  }
519
522
  }}
520
523
  className={cn(
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-25
4
+ ---
5
+
6
+ Create no longer shows agent settings in the main asset chat area after reconnecting AI.
@@ -5,6 +5,7 @@ publish = "templates/assets/dist"
5
5
  functions = "templates/assets/.netlify/functions-internal"
6
6
 
7
7
  [build.environment]
8
+ GA_MEASUREMENT_ID = "G-ESF7FYXGN9"
8
9
  NITRO_PRESET = "netlify"
9
10
  NPM_CONFIG_PRODUCTION = "false"
10
11
 
@@ -5,6 +5,7 @@ publish = "templates/brain/dist"
5
5
  functions = "templates/brain/.netlify/functions-internal"
6
6
 
7
7
  [build.environment]
8
+ GA_MEASUREMENT_ID = "G-ESF7FYXGN9"
8
9
  NITRO_PRESET = "netlify"
9
10
  NPM_CONFIG_PRODUCTION = "false"
10
11
 
@@ -5,6 +5,7 @@ const enUS = {
5
5
  connect: "Connect",
6
6
  connected: "Connected",
7
7
  connecting: "Connecting...",
8
+ clipboardUnavailable: "Clipboard access is unavailable",
8
9
  disconnect: "Disconnect",
9
10
  notConnected: "Not connected",
10
11
  saving: "Saving...",
@@ -3626,6 +3627,7 @@ export const messagesByLocale = {
3626
3627
  connect: "连接",
3627
3628
  connected: "已连接",
3628
3629
  connecting: "正在连接...",
3630
+ clipboardUnavailable: "无法访问剪贴板",
3629
3631
  disconnect: "断开连接",
3630
3632
  notConnected: "未连接",
3631
3633
  saving: "正在保存...",
@@ -3681,6 +3683,7 @@ export const messagesByLocale = {
3681
3683
  connect: "Conectar",
3682
3684
  connected: "Conectado",
3683
3685
  connecting: "Conectando...",
3686
+ clipboardUnavailable: "El acceso al portapapeles no está disponible",
3684
3687
  disconnect: "Desconectar",
3685
3688
  notConnected: "No conectado",
3686
3689
  saving: "Guardando...",
@@ -3737,6 +3740,7 @@ export const messagesByLocale = {
3737
3740
  connect: "Connecter",
3738
3741
  connected: "Connecté",
3739
3742
  connecting: "Connexion...",
3743
+ clipboardUnavailable: "L’accès au presse-papiers n’est pas disponible",
3740
3744
  disconnect: "Déconnecter",
3741
3745
  notConnected: "Non connecté",
3742
3746
  saving: "Enregistrement...",
@@ -3794,6 +3798,8 @@ export const messagesByLocale = {
3794
3798
  connect: "Verbinden",
3795
3799
  connected: "Verbunden",
3796
3800
  connecting: "Verbinden...",
3801
+ clipboardUnavailable:
3802
+ "Zugriff auf die Zwischenablage ist nicht verfügbar",
3797
3803
  disconnect: "Trennen",
3798
3804
  notConnected: "Nicht verbunden",
3799
3805
  saving: "Speichern...",
@@ -3851,6 +3857,7 @@ export const messagesByLocale = {
3851
3857
  connect: "接続",
3852
3858
  connected: "接続済み",
3853
3859
  connecting: "接続中...",
3860
+ clipboardUnavailable: "クリップボードへのアクセスは利用できません",
3854
3861
  disconnect: "切断",
3855
3862
  notConnected: "未接続",
3856
3863
  saving: "保存中...",
@@ -3906,6 +3913,7 @@ export const messagesByLocale = {
3906
3913
  connect: "연결",
3907
3914
  connected: "연결됨",
3908
3915
  connecting: "연결 중...",
3916
+ clipboardUnavailable: "클립보드 접근을 사용할 수 없습니다",
3909
3917
  disconnect: "연결 해제",
3910
3918
  notConnected: "연결되지 않음",
3911
3919
  saving: "저장 중...",
@@ -3961,6 +3969,8 @@ export const messagesByLocale = {
3961
3969
  connect: "Conectar",
3962
3970
  connected: "Conectado",
3963
3971
  connecting: "Conectando...",
3972
+ clipboardUnavailable:
3973
+ "O acesso à área de transferência não está disponível",
3964
3974
  disconnect: "Desconectar",
3965
3975
  notConnected: "Não conectado",
3966
3976
  saving: "Salvando...",
@@ -4017,6 +4027,7 @@ export const messagesByLocale = {
4017
4027
  connect: "कनेक्ट करें",
4018
4028
  connected: "कनेक्टेड",
4019
4029
  connecting: "कनेक्ट कर रहे हैं...",
4030
+ clipboardUnavailable: "क्लिपबोर्ड एक्सेस उपलब्ध नहीं है",
4020
4031
  disconnect: "डिस्कनेक्ट करें",
4021
4032
  notConnected: "कनेक्टेड नहीं",
4022
4033
  saving: "सहेज रहे हैं...",
@@ -4071,6 +4082,7 @@ export const messagesByLocale = {
4071
4082
  connect: "اتصال",
4072
4083
  connected: "متصل",
4073
4084
  connecting: "جارٍ الاتصال...",
4085
+ clipboardUnavailable: "الوصول إلى الحافظة غير متاح",
4074
4086
  disconnect: "قطع الاتصال",
4075
4087
  notConnected: "غير متصل",
4076
4088
  saving: "جارٍ الحفظ...",
@@ -0,0 +1,58 @@
1
+ type ElectronClipboardApi = {
2
+ clipboard?: {
3
+ writeText?: (text: string) => boolean | Promise<boolean>;
4
+ };
5
+ };
6
+
7
+ function getElectronClipboard(): ElectronClipboardApi["clipboard"] | null {
8
+ const api = (
9
+ globalThis as typeof globalThis & {
10
+ electronAPI?: ElectronClipboardApi;
11
+ }
12
+ ).electronAPI;
13
+ return api?.clipboard ?? null;
14
+ }
15
+
16
+ function copyWithExecCommand(text: string): boolean {
17
+ if (typeof document === "undefined" || !document.body) return false;
18
+ const textarea = document.createElement("textarea");
19
+ textarea.value = text;
20
+ textarea.setAttribute("readonly", "");
21
+ textarea.style.position = "fixed";
22
+ textarea.style.left = "-9999px";
23
+ textarea.style.top = "0";
24
+ textarea.style.opacity = "0";
25
+ document.body.appendChild(textarea);
26
+ textarea.select();
27
+ textarea.setSelectionRange(0, text.length);
28
+ try {
29
+ return document.execCommand("copy");
30
+ } catch {
31
+ return false;
32
+ } finally {
33
+ document.body.removeChild(textarea);
34
+ }
35
+ }
36
+
37
+ export async function copyTextToClipboard(text: string): Promise<boolean> {
38
+ const electronClipboard = getElectronClipboard();
39
+ if (electronClipboard?.writeText) {
40
+ try {
41
+ const copied = await electronClipboard.writeText(text);
42
+ if (copied !== false) return true;
43
+ } catch {
44
+ // Fall through to browser clipboard options.
45
+ }
46
+ }
47
+
48
+ if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
49
+ try {
50
+ await navigator.clipboard.writeText(text);
51
+ return true;
52
+ } catch {
53
+ // Electron/web embeds can deny clipboard-write even after a click.
54
+ }
55
+ }
56
+
57
+ return copyWithExecCommand(text);
58
+ }
@@ -21,6 +21,7 @@ import {
21
21
  useUpdateAvailability,
22
22
  } from "@/hooks/use-availability";
23
23
  import { useDbStatus } from "@/hooks/use-db-status";
24
+ import { copyTextToClipboard } from "@/lib/clipboard";
24
25
 
25
26
  type DayName = keyof AvailabilityConfig["weeklySchedule"];
26
27
 
@@ -64,6 +65,19 @@ export default function AvailabilitySettings() {
64
65
  const { isLocal } = useDbStatus();
65
66
  const [showCloudUpgrade, setShowCloudUpgrade] = useState(false);
66
67
 
68
+ async function handleCopyBookingLink() {
69
+ if (isLocal) {
70
+ setShowCloudUpgrade(true);
71
+ return;
72
+ }
73
+ const url = `${window.location.origin}/book/${bookingSlug}`;
74
+ if (await copyTextToClipboard(url)) {
75
+ toast.success(t("bookingLinks.bookingLinkCopied"));
76
+ return;
77
+ }
78
+ toast.error(t("common.clipboardUnavailable"));
79
+ }
80
+
67
81
  useEffect(() => {
68
82
  if (availability) {
69
83
  setSchedule(availability.weeklySchedule);
@@ -283,15 +297,7 @@ export default function AvailabilitySettings() {
283
297
  <Button
284
298
  variant="outline"
285
299
  size="sm"
286
- onClick={() => {
287
- if (isLocal) {
288
- setShowCloudUpgrade(true);
289
- return;
290
- }
291
- const url = `${window.location.origin}/book/${bookingSlug}`;
292
- navigator.clipboard.writeText(url);
293
- toast.success(t("bookingLinks.bookingLinkCopied"));
294
- }}
300
+ onClick={() => void handleCopyBookingLink()}
295
301
  >
296
302
  {t("bookingLinks.copyBookingLink")}
297
303
  </Button>
@@ -112,6 +112,7 @@ import {
112
112
  } from "@/hooks/use-booking-links";
113
113
  import { useGoogleAuthStatus } from "@/hooks/use-google-auth";
114
114
  import { useZoomStatus, useConnectZoom } from "@/hooks/use-zoom-auth";
115
+ import { copyTextToClipboard } from "@/lib/clipboard";
115
116
  import { cn } from "@/lib/utils";
116
117
 
117
118
  import BookingsList from "./BookingsList";
@@ -899,8 +900,11 @@ export default function BookingLinksPage({
899
900
  }
900
901
 
901
902
  async function copyPreviewUrl(slug: string) {
902
- await navigator.clipboard.writeText(getBookingUrl(slug));
903
- toast.success(t("bookingLinks.bookingLinkCopied"));
903
+ if (await copyTextToClipboard(getBookingUrl(slug))) {
904
+ toast.success(t("bookingLinks.bookingLinkCopied"));
905
+ return;
906
+ }
907
+ toast.error(t("common.clipboardUnavailable"));
904
908
  }
905
909
 
906
910
  function openPreview(slug: string) {
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-25
4
+ ---
5
+
6
+ Booking link copy buttons no longer fail when clipboard permissions are blocked.