@agent-native/core 0.77.6 → 0.77.8
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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +29 -0
- package/corpus/core/package.json +3 -3
- package/corpus/core/src/agent/production-agent.ts +18 -0
- package/corpus/core/src/agent/thread-data-builder.ts +21 -2
- package/corpus/core/src/cli/create.ts +35 -1
- package/corpus/core/src/cli/skills.ts +8 -0
- package/corpus/core/src/client/AgentPanel.tsx +57 -25
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +31 -1
- package/corpus/core/src/client/analytics.ts +24 -2
- package/corpus/core/src/client/use-agent-engine-configured.ts +29 -15
- package/corpus/core/src/client/use-chat-threads.ts +104 -55
- package/corpus/core/src/db/ddl-guard.ts +190 -0
- package/corpus/core/src/secrets/storage.ts +36 -2
- package/corpus/core/src/server/analytics.ts +27 -3
- package/corpus/core/src/settings/store.ts +40 -6
- package/corpus/core/src/sharing/access.ts +35 -13
- package/corpus/core/src/sharing/actions/share-resource.ts +32 -9
- package/corpus/core/src/sharing/actions/unshare-resource.ts +25 -2
- package/corpus/core/src/sharing/registry.ts +9 -0
- package/corpus/core/src/vite/client.ts +3 -0
- package/corpus/templates/analytics/actions/data-source-status.ts +25 -14
- package/corpus/templates/analytics/app/components/layout/NewAnalysisDialog.tsx +2 -2
- package/corpus/templates/analytics/app/lib/data-source-status.ts +47 -6
- package/corpus/templates/analytics/app/lib/data-sources.ts +14 -3
- package/corpus/templates/analytics/app/pages/DataSources.tsx +24 -5
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +15 -43
- package/corpus/templates/analytics/changelog/2026-06-25-fixed-hubspot-data-source-status-so-private-app-tokens-legac.md +6 -0
- package/corpus/templates/analytics/netlify.toml +1 -0
- package/corpus/templates/analytics/server/lib/credential-keys.ts +3 -1
- package/corpus/templates/analytics/server/routes/api/test-connection.post.ts +12 -3
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +5 -2
- package/corpus/templates/assets/changelog/2026-06-25-create-now-returns-from-agent-settings-to-the-asset-chat-eve.md +6 -0
- package/corpus/templates/assets/netlify.toml +1 -0
- package/corpus/templates/brain/netlify.toml +1 -0
- package/corpus/templates/calendar/app/i18n-data.ts +12 -0
- package/corpus/templates/calendar/app/lib/clipboard.ts +58 -0
- package/corpus/templates/calendar/app/pages/AvailabilitySettings.tsx +15 -9
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +6 -2
- package/corpus/templates/calendar/changelog/2026-06-25-booking-link-copy-buttons-no-longer-fail-when-clipboard-perm.md +6 -0
- package/corpus/templates/calendar/netlify.toml +1 -0
- package/corpus/templates/chat/netlify.toml +1 -0
- package/corpus/templates/clips/AGENTS.md +9 -7
- package/corpus/templates/clips/actions/add-vocabulary-term.ts +2 -2
- package/corpus/templates/clips/actions/archive-recording.ts +5 -2
- package/corpus/templates/clips/actions/clear-edits.ts +5 -2
- package/corpus/templates/clips/actions/create-folder.ts +7 -2
- package/corpus/templates/clips/actions/delete-folder.ts +7 -4
- package/corpus/templates/clips/actions/delete-recording-permanent.ts +5 -2
- package/corpus/templates/clips/actions/finalize-recording.ts +5 -2
- package/corpus/templates/clips/actions/import-loom-recording.ts +2 -1
- package/corpus/templates/clips/actions/list-notifications.ts +9 -5
- package/corpus/templates/clips/actions/list-organization-state.ts +2 -1
- package/corpus/templates/clips/actions/list-recordings.ts +4 -1
- package/corpus/templates/clips/actions/move-recording.ts +5 -1
- package/corpus/templates/clips/actions/rename-folder.ts +6 -3
- package/corpus/templates/clips/actions/request-transcript.ts +5 -2
- package/corpus/templates/clips/actions/restore-recording.ts +5 -2
- package/corpus/templates/clips/actions/save-browser-diagnostics.ts +21 -8
- package/corpus/templates/clips/actions/set-chapters.ts +5 -2
- package/corpus/templates/clips/actions/set-thumbnail.ts +5 -2
- package/corpus/templates/clips/actions/split-recording.ts +5 -2
- package/corpus/templates/clips/actions/stitch-recordings.ts +6 -2
- package/corpus/templates/clips/actions/trash-recording.ts +5 -2
- package/corpus/templates/clips/actions/undo-edit.ts +5 -2
- package/corpus/templates/clips/actions/view-screen.ts +2 -1
- package/corpus/templates/clips/app/components/library/recording-card.tsx +24 -7
- package/corpus/templates/clips/app/lib/browser-diagnostics-capture.ts +5 -1
- package/corpus/templates/clips/changelog/2026-06-25-chrome-extension-recordings-now-upload-large-capture-bursts.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-25-console-diagnostics-keep-more-useful-non-secret-details-when.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-25-fixed-owner-recognition-so-clips-created-from-desktop-and-we.md +6 -0
- package/corpus/templates/clips/chrome-extension/package.json +3 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/background.ts +85 -9
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +55 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +79 -3
- package/corpus/templates/clips/chrome-extension/src/options.ts +10 -0
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +6 -0
- package/corpus/templates/clips/chrome-extension/src/permission.ts +8 -1
- package/corpus/templates/clips/chrome-extension/src/popup.ts +65 -39
- package/corpus/templates/clips/chrome-extension/src/sentry.ts +206 -0
- package/corpus/templates/clips/chrome-extension/vite.config.ts +47 -0
- package/corpus/templates/clips/netlify.toml +1 -0
- package/corpus/templates/clips/server/db/index.ts +1 -0
- package/corpus/templates/clips/server/lib/google-calendar-oauth.ts +35 -23
- package/corpus/templates/clips/server/lib/public-agent-context.ts +8 -1
- package/corpus/templates/clips/server/lib/recordings.ts +18 -2
- package/corpus/templates/clips/server/lib/slack-oauth.ts +9 -6
- package/corpus/templates/clips/server/plugins/onboarding-calendar.ts +6 -2
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +2 -2
- package/corpus/templates/clips/server/routes/api/recordings/[recordingId]/thumbnail.post.ts +5 -2
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +5 -2
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +5 -2
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +5 -2
- package/corpus/templates/clips/shared/browser-diagnostics.ts +14 -4
- package/corpus/templates/content/netlify.toml +1 -0
- package/corpus/templates/design/netlify.toml +1 -0
- package/corpus/templates/dispatch/netlify.toml +1 -0
- package/corpus/templates/forms/netlify.toml +1 -0
- package/corpus/templates/macros/netlify.toml +1 -0
- package/corpus/templates/mail/app/components/email/EmailList.tsx +7 -3
- package/corpus/templates/mail/app/components/email/EmailThread.tsx +4 -1
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +10 -2
- package/corpus/templates/mail/app/hooks/use-compose-state.ts +15 -5
- package/corpus/templates/mail/app/hooks/use-google-auth.ts +47 -9
- package/corpus/templates/mail/changelog/2026-06-25-account-avatars-stay-stable-while-mail-refreshes-google-acco.md +6 -0
- package/corpus/templates/mail/changelog/2026-06-25-mail-background-draft-saves-and-thread-prefetches-no-longer.md +6 -0
- package/corpus/templates/mail/netlify.toml +1 -0
- package/corpus/templates/plan/.agents/skills/visual-plan/references/canvas.md +8 -0
- package/corpus/templates/plan/netlify.toml +1 -0
- package/corpus/templates/slides/netlify.toml +1 -0
- package/corpus/templates/videos/netlify.toml +1 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +18 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +23 -2
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +28 -1
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/skills.d.ts +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +8 -0
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +8 -2
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +29 -15
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +4 -4
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +22 -2
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
- package/dist/client/use-agent-engine-configured.js +23 -13
- package/dist/client/use-agent-engine-configured.js.map +1 -1
- package/dist/client/use-chat-threads.d.ts +4 -0
- package/dist/client/use-chat-threads.d.ts.map +1 -1
- package/dist/client/use-chat-threads.js +49 -4
- package/dist/client/use-chat-threads.js.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/db/ddl-guard.d.ts +88 -0
- package/dist/db/ddl-guard.d.ts.map +1 -0
- package/dist/db/ddl-guard.js +180 -0
- package/dist/db/ddl-guard.js.map +1 -0
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +26 -2
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +2 -2
- package/dist/server/analytics.d.ts +4 -0
- package/dist/server/analytics.d.ts.map +1 -1
- package/dist/server/analytics.js +19 -3
- package/dist/server/analytics.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/settings/store.d.ts.map +1 -1
- package/dist/settings/store.js +32 -6
- package/dist/settings/store.js.map +1 -1
- package/dist/sharing/access.d.ts.map +1 -1
- package/dist/sharing/access.js +26 -12
- package/dist/sharing/access.js.map +1 -1
- package/dist/sharing/actions/share-resource.js +20 -9
- package/dist/sharing/actions/share-resource.js.map +1 -1
- package/dist/sharing/actions/unshare-resource.js +13 -2
- package/dist/sharing/actions/unshare-resource.js.map +1 -1
- package/dist/sharing/registry.d.ts +9 -0
- package/dist/sharing/registry.d.ts.map +1 -1
- package/dist/sharing/registry.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +1 -0
- package/dist/vite/client.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { and, eq } from "drizzle-orm";
|
|
1
|
+
import { and, eq, sql, type SQL } from "drizzle-orm";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
import { defineAction } from "../../action.js";
|
|
@@ -89,6 +89,25 @@ function nanoid(size = 12): string {
|
|
|
89
89
|
return id;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
function normalizePrincipalId(
|
|
93
|
+
principalType: "user" | "org",
|
|
94
|
+
principalId: string,
|
|
95
|
+
): string {
|
|
96
|
+
return principalType === "user"
|
|
97
|
+
? principalId.trim().toLowerCase()
|
|
98
|
+
: principalId;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function principalIdMatches(
|
|
102
|
+
sharesTable: any,
|
|
103
|
+
principalType: "user" | "org",
|
|
104
|
+
principalId: string,
|
|
105
|
+
): SQL {
|
|
106
|
+
return principalType === "user"
|
|
107
|
+
? sql`lower(${sharesTable.principalId}) = ${principalId}`
|
|
108
|
+
: eq(sharesTable.principalId, principalId);
|
|
109
|
+
}
|
|
110
|
+
|
|
92
111
|
/**
|
|
93
112
|
* Returns true if the given email is either an active member of `orgId` or
|
|
94
113
|
* has a pending invitation to `orgId`. Used by resources whose registration
|
|
@@ -163,6 +182,10 @@ export default defineAction({
|
|
|
163
182
|
);
|
|
164
183
|
const actor = getRequestUserEmail();
|
|
165
184
|
if (!actor) throw new ForbiddenError("Not signed in");
|
|
185
|
+
const principalId = normalizePrincipalId(
|
|
186
|
+
args.principalType,
|
|
187
|
+
args.principalId,
|
|
188
|
+
);
|
|
166
189
|
const beforeExtensionTargets = await getExtensionShareChangeTargets(
|
|
167
190
|
args.resourceType,
|
|
168
191
|
args.resourceId,
|
|
@@ -176,10 +199,10 @@ export default defineAction({
|
|
|
176
199
|
);
|
|
177
200
|
}
|
|
178
201
|
if (args.principalType === "user") {
|
|
179
|
-
const ok = await isOrgMemberOrInvited(resourceOrgId,
|
|
202
|
+
const ok = await isOrgMemberOrInvited(resourceOrgId, principalId);
|
|
180
203
|
if (!ok) {
|
|
181
204
|
throw new ForbiddenError(
|
|
182
|
-
`${
|
|
205
|
+
`${principalId} is not in your organization. Invite them to the organization first, then share.`,
|
|
183
206
|
);
|
|
184
207
|
}
|
|
185
208
|
} else if (args.principalType === "org") {
|
|
@@ -187,7 +210,7 @@ export default defineAction({
|
|
|
187
210
|
// extension code in the viewer's auth context — the same threat
|
|
188
211
|
// model that blocks public + cross-org user shares. Pin org-
|
|
189
212
|
// principal shares to the resource's own org.
|
|
190
|
-
if (
|
|
213
|
+
if (principalId !== resourceOrgId) {
|
|
191
214
|
throw new ForbiddenError(
|
|
192
215
|
`${reg.displayName} can only be shared with its own organization, not a different one.`,
|
|
193
216
|
);
|
|
@@ -203,7 +226,7 @@ export default defineAction({
|
|
|
203
226
|
and(
|
|
204
227
|
eq(reg.sharesTable.resourceId, args.resourceId),
|
|
205
228
|
eq(reg.sharesTable.principalType, args.principalType),
|
|
206
|
-
|
|
229
|
+
principalIdMatches(reg.sharesTable, args.principalType, principalId),
|
|
207
230
|
),
|
|
208
231
|
);
|
|
209
232
|
|
|
@@ -225,7 +248,7 @@ export default defineAction({
|
|
|
225
248
|
id,
|
|
226
249
|
resourceId: args.resourceId,
|
|
227
250
|
principalType: args.principalType,
|
|
228
|
-
principalId
|
|
251
|
+
principalId,
|
|
229
252
|
role: args.role,
|
|
230
253
|
createdBy: actor,
|
|
231
254
|
createdAt: new Date().toISOString(),
|
|
@@ -240,7 +263,7 @@ export default defineAction({
|
|
|
240
263
|
args.notify !== false &&
|
|
241
264
|
args.principalType === "user" &&
|
|
242
265
|
isEmailConfigured() &&
|
|
243
|
-
!isSyntheticQaEmail(
|
|
266
|
+
!isSyntheticQaEmail(principalId)
|
|
244
267
|
) {
|
|
245
268
|
try {
|
|
246
269
|
const titleCol = reg.titleColumn ?? "title";
|
|
@@ -268,12 +291,12 @@ export default defineAction({
|
|
|
268
291
|
heading: "You've been given access",
|
|
269
292
|
paragraphs: [
|
|
270
293
|
`${emailStrong(actor)} has shared the ${reg.displayName} ${emailStrong(resourceTitle)} with you as a ${emailStrong(args.role)}.`,
|
|
271
|
-
`Use the button below to open it. If prompted, sign in with ${emailStrong(
|
|
294
|
+
`Use the button below to open it. If prompted, sign in with ${emailStrong(principalId)}.`,
|
|
272
295
|
],
|
|
273
296
|
cta: { label: `Open ${reg.displayName}`, url: notificationUrl },
|
|
274
297
|
footer: `You received this because ${actor} granted you ${args.role} access.`,
|
|
275
298
|
});
|
|
276
|
-
await sendEmail({ to:
|
|
299
|
+
await sendEmail({ to: principalId, subject, html, text });
|
|
277
300
|
} catch (err) {
|
|
278
301
|
console.error(
|
|
279
302
|
"[share-resource] failed to send share notification:",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { and, eq } from "drizzle-orm";
|
|
1
|
+
import { and, eq, sql, type SQL } from "drizzle-orm";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
import { defineAction } from "../../action.js";
|
|
@@ -9,6 +9,25 @@ import {
|
|
|
9
9
|
notifyExtensionShareChanged,
|
|
10
10
|
} from "./extension-change.js";
|
|
11
11
|
|
|
12
|
+
function normalizePrincipalId(
|
|
13
|
+
principalType: "user" | "org",
|
|
14
|
+
principalId: string,
|
|
15
|
+
): string {
|
|
16
|
+
return principalType === "user"
|
|
17
|
+
? principalId.trim().toLowerCase()
|
|
18
|
+
: principalId;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function principalIdMatches(
|
|
22
|
+
sharesTable: any,
|
|
23
|
+
principalType: "user" | "org",
|
|
24
|
+
principalId: string,
|
|
25
|
+
): SQL {
|
|
26
|
+
return principalType === "user"
|
|
27
|
+
? sql`lower(${sharesTable.principalId}) = ${principalId}`
|
|
28
|
+
: eq(sharesTable.principalId, principalId);
|
|
29
|
+
}
|
|
30
|
+
|
|
12
31
|
export default defineAction({
|
|
13
32
|
description:
|
|
14
33
|
"Revoke a previously granted share. Owner or admin role required.",
|
|
@@ -28,13 +47,17 @@ export default defineAction({
|
|
|
28
47
|
args.resourceId,
|
|
29
48
|
);
|
|
30
49
|
const db = reg.getDb() as any;
|
|
50
|
+
const principalId = normalizePrincipalId(
|
|
51
|
+
args.principalType,
|
|
52
|
+
args.principalId,
|
|
53
|
+
);
|
|
31
54
|
await db
|
|
32
55
|
.delete(reg.sharesTable)
|
|
33
56
|
.where(
|
|
34
57
|
and(
|
|
35
58
|
eq(reg.sharesTable.resourceId, args.resourceId),
|
|
36
59
|
eq(reg.sharesTable.principalType, args.principalType),
|
|
37
|
-
|
|
60
|
+
principalIdMatches(reg.sharesTable, args.principalType, principalId),
|
|
38
61
|
),
|
|
39
62
|
);
|
|
40
63
|
await notifyExtensionShareChanged(
|
|
@@ -78,6 +78,15 @@ export interface ShareableResourceRegistration {
|
|
|
78
78
|
userEmail?: string;
|
|
79
79
|
orgId?: string;
|
|
80
80
|
};
|
|
81
|
+
/**
|
|
82
|
+
* When true, direct ownership is recognized by owner_email regardless of the
|
|
83
|
+
* caller's active org. Use this only for resource types where the template's
|
|
84
|
+
* own actions already treat owner_email as the cross-org authority and list
|
|
85
|
+
* views add their own org filters.
|
|
86
|
+
*
|
|
87
|
+
* Default: `false`.
|
|
88
|
+
*/
|
|
89
|
+
ownerAccessIgnoresOrg?: boolean;
|
|
81
90
|
}
|
|
82
91
|
|
|
83
92
|
// Stash the registry on globalThis so it survives SSR bundle duplication.
|
|
@@ -1843,6 +1843,9 @@ function createAgentNativeConfig(
|
|
|
1843
1843
|
define: {
|
|
1844
1844
|
...(userConfig.define ?? {}),
|
|
1845
1845
|
...(options.define ?? {}),
|
|
1846
|
+
__AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID__: JSON.stringify(
|
|
1847
|
+
process.env.GA_MEASUREMENT_ID?.trim() || "",
|
|
1848
|
+
),
|
|
1846
1849
|
// Framework route warmup controls how SSR `.data` routes are fetched:
|
|
1847
1850
|
// ordinary fetches keep them CDN-cacheable, while native prefetch headers
|
|
1848
1851
|
// can be refused before the CDN/origin sees the request. Keep this value
|
|
@@ -151,32 +151,43 @@ export default defineAction({
|
|
|
151
151
|
);
|
|
152
152
|
const visibleKeys = new Set(results.map((result) => result.key));
|
|
153
153
|
const providers = credentialProviderConfigs
|
|
154
|
-
.filter((provider) =>
|
|
155
|
-
provider.
|
|
156
|
-
|
|
154
|
+
.filter((provider) => {
|
|
155
|
+
const requiredMode = provider.requiredMode ?? "all";
|
|
156
|
+
return requiredMode === "any"
|
|
157
|
+
? provider.requiredKeys.some((key) => visibleKeys.has(key))
|
|
158
|
+
: provider.requiredKeys.every((key) => visibleKeys.has(key));
|
|
159
|
+
})
|
|
157
160
|
.map((provider) => {
|
|
158
161
|
const optionalKeys = provider.optionalKeys ?? [];
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
const requiredMode = provider.requiredMode ?? "all";
|
|
163
|
+
const hasRequiredCredentials =
|
|
164
|
+
requiredMode === "any"
|
|
165
|
+
? provider.requiredKeys.some((key) => configuredKeys.has(key))
|
|
166
|
+
: provider.requiredKeys.every((key) => configuredKeys.has(key));
|
|
167
|
+
const missingRequiredKeys = hasRequiredCredentials
|
|
168
|
+
? []
|
|
169
|
+
: provider.requiredKeys.filter((key) => !configuredKeys.has(key));
|
|
162
170
|
const configuredProviderKeys = [
|
|
163
171
|
...provider.requiredKeys,
|
|
164
172
|
...optionalKeys,
|
|
165
173
|
].filter((key) => configuredKeys.has(key));
|
|
174
|
+
const workspaceConnection =
|
|
175
|
+
workspaceProviderStatusById.get(provider.provider) ??
|
|
176
|
+
summarizeWorkspaceConnections(
|
|
177
|
+
provider.provider,
|
|
178
|
+
workspace.connections,
|
|
179
|
+
workspace.grants,
|
|
180
|
+
);
|
|
166
181
|
return {
|
|
167
182
|
provider: provider.provider,
|
|
168
183
|
label: provider.label,
|
|
169
|
-
configured:
|
|
184
|
+
configured:
|
|
185
|
+
hasRequiredCredentials ||
|
|
186
|
+
workspaceConnection.grantState === "connected",
|
|
170
187
|
configuredKeys: configuredProviderKeys,
|
|
171
188
|
missingRequiredKeys,
|
|
172
189
|
optionalKeys,
|
|
173
|
-
workspaceConnection
|
|
174
|
-
workspaceProviderStatusById.get(provider.provider) ??
|
|
175
|
-
summarizeWorkspaceConnections(
|
|
176
|
-
provider.provider,
|
|
177
|
-
workspace.connections,
|
|
178
|
-
workspace.grants,
|
|
179
|
-
),
|
|
190
|
+
workspaceConnection,
|
|
180
191
|
};
|
|
181
192
|
});
|
|
182
193
|
return {
|
|
@@ -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) =>
|
|
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(
|
|
108
|
+
const statusMap = new Map(
|
|
109
|
+
envStatus.map((s) => [normalizeCredentialKey(s.key), s.configured]),
|
|
110
|
+
);
|
|
103
111
|
const optionalKeys = getOptionalCredentialKeys(source);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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) =>
|
|
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
|
|
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
|
-
|
|
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={
|
|
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={
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "@
|
|
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
|
-
|
|
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:
|
|
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
|
|
445
|
-
<
|
|
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
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
|
|
@@ -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 {
|
|
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
|
|
95
|
-
|
|
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
|
-
|
|
517
|
+
focusAgentChat();
|
|
518
|
+
if (!isCreateRoute || location.pathname !== "/") {
|
|
519
|
+
navigateWithAgentChatViewTransition(navigate, "/");
|
|
520
|
+
}
|
|
518
521
|
}
|
|
519
522
|
}}
|
|
520
523
|
className={cn(
|