@agent-native/core 0.68.0 → 0.68.2
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/README.md +4 -4
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +18 -0
- package/corpus/core/docs/content/authentication.md +16 -2
- package/corpus/core/docs/content/template-analytics.md +14 -9
- package/corpus/core/docs/content/template-clips.md +30 -2
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/builder-engine.ts +22 -3
- package/corpus/core/src/agent/engine/types.ts +1 -0
- package/corpus/core/src/agent/production-agent.ts +2 -0
- package/corpus/core/src/agent/types.ts +1 -0
- package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
- package/corpus/core/src/client/AssistantChat.tsx +45 -6
- package/corpus/core/src/client/CommandMenu.tsx +98 -3
- package/corpus/core/src/client/ConnectBuilderCard.tsx +11 -2
- package/corpus/core/src/client/active-run-state.ts +16 -0
- package/corpus/core/src/client/guided-questions.tsx +73 -17
- package/corpus/core/src/client/index.ts +2 -0
- package/corpus/core/src/client/sse-event-processor.ts +9 -0
- package/corpus/core/src/oauth-tokens/google-refresh.ts +32 -17
- package/corpus/core/src/provider-api/index.ts +37 -17
- package/corpus/core/src/server/auth.ts +6 -8
- package/corpus/core/src/server/better-auth-instance.ts +19 -4
- package/corpus/core/src/server/core-routes-plugin.ts +157 -5
- package/corpus/core/src/server/google-oauth-credentials.ts +51 -0
- package/corpus/core/src/server/index.ts +8 -0
- package/corpus/core/src/server/onboarding-html.ts +2 -1
- package/corpus/core/src/templates/workspace-root/.env.example +4 -0
- package/corpus/templates/analytics/app/routes/$.tsx +9 -0
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +33 -1
- package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +11 -3
- package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +25 -2
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +12 -0
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +7 -2
- package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +37 -78
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +33 -1
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +46 -3
- package/corpus/templates/calendar/app/hooks/use-google-auth.ts +203 -2
- package/corpus/templates/calendar/app/lib/event-colors.ts +1 -4
- package/corpus/templates/calendar/app/lib/rsvp-status.tsx +6 -0
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +9 -4
- package/corpus/templates/calendar/app/pages/NotFound.tsx +1 -1
- package/corpus/templates/calendar/app/pages/Settings.tsx +25 -1
- package/corpus/templates/calendar/app/routes/_app.$.tsx +9 -0
- package/corpus/templates/calendar/server/handlers/google-auth.ts +152 -12
- package/corpus/templates/calendar/server/lib/google-calendar.ts +63 -14
- package/corpus/templates/calendar/server/plugins/auth.ts +4 -20
- package/corpus/templates/calendar/shared/api.ts +4 -0
- package/corpus/templates/clips/.agents/skills/recording/SKILL.md +17 -0
- package/corpus/templates/clips/actions/sync-calendars.ts +6 -7
- package/corpus/templates/clips/app/components/capture-install-options.tsx +157 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +9 -8
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +4 -6
- package/corpus/templates/clips/app/lib/capture-install-options.ts +13 -0
- package/corpus/templates/clips/app/root.tsx +54 -2
- package/corpus/templates/clips/app/routes/_app.dictate.tsx +9 -13
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +5 -7
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +5 -7
- package/corpus/templates/clips/app/routes/download.tsx +49 -0
- package/corpus/templates/clips/app/routes/record.tsx +4 -4
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +5 -6
- package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +26 -0
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +6 -7
- package/corpus/templates/clips/server/lib/google-calendar-client.ts +61 -0
- package/corpus/templates/clips/server/routes/_agent-native/google/auth-url.get.ts +19 -12
- package/corpus/templates/clips/server/routes/_agent-native/google/callback.get.ts +6 -6
- package/corpus/templates/design/actions/show-design-questions.ts +15 -1
- package/corpus/templates/design/app/hooks/use-agent-generating.ts +47 -8
- package/corpus/templates/design/app/pages/DesignEditor.tsx +6 -0
- package/corpus/templates/design/app/pages/NotFound.tsx +1 -1
- package/corpus/templates/design/app/routes/$.tsx +9 -0
- package/corpus/templates/mail/app/pages/NotFound.tsx +1 -1
- package/corpus/templates/mail/app/routes/$.tsx +9 -0
- package/corpus/templates/plan/app/global.css +2 -2
- package/corpus/templates/slides/app/pages/NotFound.tsx +1 -1
- package/corpus/templates/slides/app/routes/$.tsx +9 -0
- package/corpus/templates/slides/server/lib/google-docs-oauth.ts +81 -25
- package/corpus/templates/videos/app/pages/NotFound.tsx +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +21 -3
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/engine/types.d.ts +2 -0
- package/dist/agent/engine/types.d.ts.map +1 -1
- package/dist/agent/engine/types.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +3 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/types.d.ts +2 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +42 -7
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/CommandMenu.d.ts +15 -2
- package/dist/client/CommandMenu.d.ts.map +1 -1
- package/dist/client/CommandMenu.js +46 -3
- package/dist/client/CommandMenu.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
- package/dist/client/ConnectBuilderCard.js +11 -2
- package/dist/client/ConnectBuilderCard.js.map +1 -1
- package/dist/client/active-run-state.d.ts +2 -0
- package/dist/client/active-run-state.d.ts.map +1 -1
- package/dist/client/active-run-state.js +10 -0
- package/dist/client/active-run-state.js.map +1 -1
- package/dist/client/guided-questions.d.ts +2 -0
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +52 -7
- package/dist/client/guided-questions.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +7 -1
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/oauth-tokens/google-refresh.d.ts.map +1 -1
- package/dist/oauth-tokens/google-refresh.js +29 -17
- package/dist/oauth-tokens/google-refresh.js.map +1 -1
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +28 -16
- package/dist/provider-api/index.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +6 -6
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +18 -4
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +5 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +107 -5
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/google-oauth-credentials.d.ts +15 -0
- package/dist/server/google-oauth-credentials.d.ts.map +1 -0
- package/dist/server/google-oauth-credentials.js +34 -0
- package/dist/server/google-oauth-credentials.js.map +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +2 -1
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/templates/workspace-root/.env.example +4 -0
- package/docs/content/authentication.md +16 -2
- package/docs/content/template-analytics.md +14 -9
- package/docs/content/template-clips.md +30 -2
- package/package.json +1 -1
- package/src/templates/workspace-root/.env.example +4 -0
|
@@ -72,6 +72,7 @@ import {
|
|
|
72
72
|
} from "../db/client.js";
|
|
73
73
|
import { getBetterAuth, getBetterAuthSync } from "./better-auth-instance.js";
|
|
74
74
|
import type { BetterAuthConfig } from "./better-auth-instance.js";
|
|
75
|
+
import { resolveGoogleSignInCredentials } from "./google-oauth-credentials.js";
|
|
75
76
|
import {
|
|
76
77
|
getAllowedCorsOrigin,
|
|
77
78
|
readCorsAllowedOrigins,
|
|
@@ -2462,11 +2463,8 @@ async function mountBetterAuthRoutes(
|
|
|
2462
2463
|
// Auto-add Google OAuth routes when credentials are configured. Templates
|
|
2463
2464
|
// that need broader product scopes (mail/calendar) opt out and provide
|
|
2464
2465
|
// their own Nitro routes at these paths.
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
process.env.GOOGLE_CLIENT_SECRET &&
|
|
2468
|
-
options.mountGoogleOAuthRoutes !== false
|
|
2469
|
-
) {
|
|
2466
|
+
const googleSignInCredentials = resolveGoogleSignInCredentials();
|
|
2467
|
+
if (googleSignInCredentials && options.mountGoogleOAuthRoutes !== false) {
|
|
2470
2468
|
setGenericGoogleOAuthRoutesEnabled(app, true);
|
|
2471
2469
|
for (const gp of [
|
|
2472
2470
|
"/_agent-native/google/callback",
|
|
@@ -2535,7 +2533,7 @@ async function mountBetterAuthRoutes(
|
|
|
2535
2533
|
process.env.VITE_AGENT_NATIVE_WORKSPACE === "1",
|
|
2536
2534
|
});
|
|
2537
2535
|
const params = new URLSearchParams({
|
|
2538
|
-
client_id:
|
|
2536
|
+
client_id: googleSignInCredentials.clientId,
|
|
2539
2537
|
redirect_uri: redirectUri,
|
|
2540
2538
|
response_type: "code",
|
|
2541
2539
|
scope: googleScopes,
|
|
@@ -2647,8 +2645,8 @@ async function mountBetterAuthRoutes(
|
|
|
2647
2645
|
},
|
|
2648
2646
|
body: new URLSearchParams({
|
|
2649
2647
|
code,
|
|
2650
|
-
client_id:
|
|
2651
|
-
client_secret:
|
|
2648
|
+
client_id: googleSignInCredentials.clientId,
|
|
2649
|
+
client_secret: googleSignInCredentials.clientSecret,
|
|
2652
2650
|
redirect_uri: redirectUri,
|
|
2653
2651
|
grant_type: "authorization_code",
|
|
2654
2652
|
}),
|
|
@@ -26,6 +26,7 @@ import { flushTracking, identify, track } from "../tracking/index.js";
|
|
|
26
26
|
import { TEMPLATES } from "../cli/templates-meta.js";
|
|
27
27
|
import { resolveAuthCookieNamespace } from "./cookie-namespace.js";
|
|
28
28
|
import { getWorkspaceA2ADerivedSecret } from "./derived-secret.js";
|
|
29
|
+
import { resolveGoogleSignInCredentials } from "./google-oauth-credentials.js";
|
|
29
30
|
import {
|
|
30
31
|
getDialect,
|
|
31
32
|
getDatabaseUrl,
|
|
@@ -802,7 +803,17 @@ async function createBetterAuthInstance(
|
|
|
802
803
|
...config?.socialProviders,
|
|
803
804
|
};
|
|
804
805
|
|
|
805
|
-
|
|
806
|
+
const extraScopes = config?.googleScopes ?? [];
|
|
807
|
+
const googleCredentials =
|
|
808
|
+
extraScopes.length > 0
|
|
809
|
+
? process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET
|
|
810
|
+
? {
|
|
811
|
+
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
812
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
813
|
+
}
|
|
814
|
+
: null
|
|
815
|
+
: resolveGoogleSignInCredentials();
|
|
816
|
+
if (googleCredentials) {
|
|
806
817
|
// When the template requests broader scopes (Gmail, Calendar, etc.)
|
|
807
818
|
// ask for them on the primary sign-in flow so a separate "Connect
|
|
808
819
|
// Google" round-trip isn't needed. `accessType: "offline"` plus
|
|
@@ -810,12 +821,11 @@ async function createBetterAuthInstance(
|
|
|
810
821
|
// Google only re-issues a refresh token on consent, so re-signing in
|
|
811
822
|
// (e.g. after switching machines) would otherwise leave us with an
|
|
812
823
|
// access token that can't be refreshed.
|
|
813
|
-
const extraScopes = config?.googleScopes ?? [];
|
|
814
824
|
const baseScopes = ["openid", "email", "profile"];
|
|
815
825
|
const mergedScopes = Array.from(new Set([...baseScopes, ...extraScopes]));
|
|
816
826
|
socialProviders.google = {
|
|
817
|
-
clientId:
|
|
818
|
-
clientSecret:
|
|
827
|
+
clientId: googleCredentials.clientId,
|
|
828
|
+
clientSecret: googleCredentials.clientSecret,
|
|
819
829
|
...(extraScopes.length > 0
|
|
820
830
|
? {
|
|
821
831
|
scope: mergedScopes,
|
|
@@ -843,6 +853,9 @@ async function createBetterAuthInstance(
|
|
|
843
853
|
const requireEmailVerification =
|
|
844
854
|
isEmailConfigured() && !shouldSkipEmailVerification();
|
|
845
855
|
|
|
856
|
+
const shouldMirrorGoogleAccountTokens =
|
|
857
|
+
(config?.googleScopes?.length ?? 0) > 0;
|
|
858
|
+
|
|
846
859
|
const auth = betterAuth({
|
|
847
860
|
basePath,
|
|
848
861
|
baseURL: appUrl,
|
|
@@ -969,6 +982,7 @@ async function createBetterAuthInstance(
|
|
|
969
982
|
// mirroring work; failures never block sign-in.
|
|
970
983
|
create: {
|
|
971
984
|
after: async (account: any) => {
|
|
985
|
+
if (!shouldMirrorGoogleAccountTokens) return;
|
|
972
986
|
await mirrorGoogleAccountToOAuthTokens(account).catch((err) => {
|
|
973
987
|
console.error(
|
|
974
988
|
"[auth] failed to mirror Google account tokens to oauth_tokens (create)",
|
|
@@ -979,6 +993,7 @@ async function createBetterAuthInstance(
|
|
|
979
993
|
},
|
|
980
994
|
update: {
|
|
981
995
|
after: async (account: any) => {
|
|
996
|
+
if (!shouldMirrorGoogleAccountTokens) return;
|
|
982
997
|
await mirrorGoogleAccountToOAuthTokens(account).catch((err) => {
|
|
983
998
|
console.error(
|
|
984
999
|
"[auth] failed to mirror Google account tokens to oauth_tokens (update)",
|
|
@@ -158,6 +158,24 @@ import {
|
|
|
158
158
|
export const FRAMEWORK_ROUTE_PREFIX = "/_agent-native";
|
|
159
159
|
export const FRAMEWORK_EVENTS_ROUTE = `${FRAMEWORK_ROUTE_PREFIX}/events`;
|
|
160
160
|
export const LEGACY_FRAMEWORK_EVENTS_ROUTE = `${FRAMEWORK_ROUTE_PREFIX}/poll-events`;
|
|
161
|
+
const DEFAULT_BUILDER_WAITLIST_FORM_ID = "DYTHuM0jlV";
|
|
162
|
+
const DEFAULT_BUILDER_WAITLIST_FORMS_ORIGIN = "https://forms.agent-native.com";
|
|
163
|
+
const BUILDER_WAITLIST_FORM_SOURCE = "connect_builder_card";
|
|
164
|
+
const BUILDER_WAITLIST_FORM_TIMEOUT_MS = 8000;
|
|
165
|
+
const BUILDER_WAITLIST_TEXT_LIMIT = 4000;
|
|
166
|
+
|
|
167
|
+
interface BuilderWaitlistFormTarget {
|
|
168
|
+
formId: string;
|
|
169
|
+
formsOrigin: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
interface BuilderWaitlistBody {
|
|
173
|
+
prompt?: unknown;
|
|
174
|
+
orgName?: unknown;
|
|
175
|
+
appUrl?: unknown;
|
|
176
|
+
pageUrl?: unknown;
|
|
177
|
+
source?: unknown;
|
|
178
|
+
}
|
|
161
179
|
|
|
162
180
|
export function resolveFrameworkSseRoutes(sseRoute?: string): string[] {
|
|
163
181
|
return Array.from(
|
|
@@ -171,6 +189,112 @@ export function resolveFrameworkSseRoutes(sseRoute?: string): string[] {
|
|
|
171
189
|
|
|
172
190
|
registerBuiltinEngines();
|
|
173
191
|
|
|
192
|
+
function cleanBuilderWaitlistText(
|
|
193
|
+
value: unknown,
|
|
194
|
+
maxLength = BUILDER_WAITLIST_TEXT_LIMIT,
|
|
195
|
+
): string | undefined {
|
|
196
|
+
if (typeof value !== "string") return undefined;
|
|
197
|
+
const trimmed = value.trim();
|
|
198
|
+
if (!trimmed) return undefined;
|
|
199
|
+
return trimmed.slice(0, maxLength);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function normalizeHttpOrigin(value: string): string | null {
|
|
203
|
+
try {
|
|
204
|
+
const url = new URL(value);
|
|
205
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
206
|
+
return url.origin;
|
|
207
|
+
} catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function isAgentNativeHostedRequest(event: H3Event): boolean {
|
|
213
|
+
const hostname = getRequestURL(event).hostname.toLowerCase();
|
|
214
|
+
return (
|
|
215
|
+
hostname === "agent-native.com" || hostname.endsWith(".agent-native.com")
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function resolveBuilderWaitlistFormTargetForRequest(
|
|
220
|
+
event: H3Event,
|
|
221
|
+
): BuilderWaitlistFormTarget | null {
|
|
222
|
+
if (process.env.AGENT_NATIVE_DISABLE_BUILDER_WAITLIST_FORM === "1") {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const envFormId = process.env.AGENT_NATIVE_BUILDER_WAITLIST_FORM_ID?.trim();
|
|
227
|
+
const envFormsOrigin =
|
|
228
|
+
process.env.AGENT_NATIVE_BUILDER_WAITLIST_FORMS_ORIGIN?.trim();
|
|
229
|
+
const hasExplicitTarget = Boolean(envFormId || envFormsOrigin);
|
|
230
|
+
if (!hasExplicitTarget && !isAgentNativeHostedRequest(event)) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const formId = envFormId || DEFAULT_BUILDER_WAITLIST_FORM_ID;
|
|
235
|
+
const formsOrigin = normalizeHttpOrigin(
|
|
236
|
+
envFormsOrigin || DEFAULT_BUILDER_WAITLIST_FORMS_ORIGIN,
|
|
237
|
+
);
|
|
238
|
+
if (!formsOrigin) {
|
|
239
|
+
throw new Error("Invalid Builder waitlist Forms origin");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return { formId, formsOrigin };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function submitBuilderWaitlistForm(
|
|
246
|
+
event: H3Event,
|
|
247
|
+
sessionEmail: string,
|
|
248
|
+
body: BuilderWaitlistBody,
|
|
249
|
+
): Promise<{ submitted: boolean; formId?: string }> {
|
|
250
|
+
const target = resolveBuilderWaitlistFormTargetForRequest(event);
|
|
251
|
+
if (!target) return { submitted: false };
|
|
252
|
+
|
|
253
|
+
const appUrl =
|
|
254
|
+
cleanBuilderWaitlistText(body.pageUrl ?? body.appUrl, 2000) ??
|
|
255
|
+
cleanBuilderWaitlistText(getHeader(event, "referer"), 2000) ??
|
|
256
|
+
getOrigin(event);
|
|
257
|
+
const source =
|
|
258
|
+
cleanBuilderWaitlistText(body.source, 100) ?? BUILDER_WAITLIST_FORM_SOURCE;
|
|
259
|
+
|
|
260
|
+
const controller = new AbortController();
|
|
261
|
+
const timeout = setTimeout(
|
|
262
|
+
() => controller.abort(),
|
|
263
|
+
BUILDER_WAITLIST_FORM_TIMEOUT_MS,
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const res = await fetch(
|
|
268
|
+
`${target.formsOrigin}/api/submit/${encodeURIComponent(target.formId)}`,
|
|
269
|
+
{
|
|
270
|
+
method: "POST",
|
|
271
|
+
headers: { "Content-Type": "application/json" },
|
|
272
|
+
body: JSON.stringify({
|
|
273
|
+
data: {
|
|
274
|
+
email: sessionEmail,
|
|
275
|
+
orgName: cleanBuilderWaitlistText(body.orgName, 500),
|
|
276
|
+
appUrl,
|
|
277
|
+
prompt: cleanBuilderWaitlistText(body.prompt),
|
|
278
|
+
source,
|
|
279
|
+
},
|
|
280
|
+
_hp: "",
|
|
281
|
+
_meta: {
|
|
282
|
+
submitterEmail: sessionEmail,
|
|
283
|
+
pageUrl: appUrl,
|
|
284
|
+
},
|
|
285
|
+
}),
|
|
286
|
+
signal: controller.signal,
|
|
287
|
+
},
|
|
288
|
+
);
|
|
289
|
+
if (!res.ok) {
|
|
290
|
+
throw new Error(`Forms waitlist submission failed (${res.status})`);
|
|
291
|
+
}
|
|
292
|
+
return { submitted: true, formId: target.formId };
|
|
293
|
+
} finally {
|
|
294
|
+
clearTimeout(timeout);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
174
298
|
function parseBuilderCallbackBoolean(
|
|
175
299
|
value: string | null | undefined,
|
|
176
300
|
): boolean | null {
|
|
@@ -1405,10 +1529,10 @@ export function createCoreRoutesPlugin(
|
|
|
1405
1529
|
);
|
|
1406
1530
|
|
|
1407
1531
|
// Branch-creation waitlist signup. Used by ConnectBuilderCard when the
|
|
1408
|
-
// current request has no Builder branch project configured
|
|
1409
|
-
//
|
|
1410
|
-
//
|
|
1411
|
-
//
|
|
1532
|
+
// current request has no Builder branch project configured. Hosted
|
|
1533
|
+
// Agent Native deployments submit into the Builder-org Forms waitlist;
|
|
1534
|
+
// local/self-hosted deployments keep the analytics signal without
|
|
1535
|
+
// sending private workspace data to Agent Native.
|
|
1412
1536
|
getH3App(nitroApp).use(
|
|
1413
1537
|
`${P}/builder/branch-waitlist`,
|
|
1414
1538
|
defineEventHandler(async (event: H3Event) => {
|
|
@@ -1421,15 +1545,43 @@ export function createCoreRoutesPlugin(
|
|
|
1421
1545
|
setResponseStatus(event, 401);
|
|
1422
1546
|
return { error: "Authentication required" };
|
|
1423
1547
|
}
|
|
1548
|
+
const body = ((await readBody(event).catch(() => ({}))) ??
|
|
1549
|
+
{}) as BuilderWaitlistBody;
|
|
1550
|
+
let formSubmission: { submitted: boolean; formId?: string };
|
|
1551
|
+
try {
|
|
1552
|
+
formSubmission = await submitBuilderWaitlistForm(
|
|
1553
|
+
event,
|
|
1554
|
+
session.email,
|
|
1555
|
+
body,
|
|
1556
|
+
);
|
|
1557
|
+
} catch (err) {
|
|
1558
|
+
await trackBuilderLifecycle(
|
|
1559
|
+
event,
|
|
1560
|
+
"builder branch waitlist form failed",
|
|
1561
|
+
session.email,
|
|
1562
|
+
{
|
|
1563
|
+
reason:
|
|
1564
|
+
err instanceof Error ? err.message : "unknown_waitlist_error",
|
|
1565
|
+
stage: "waitlist",
|
|
1566
|
+
},
|
|
1567
|
+
);
|
|
1568
|
+
setResponseStatus(event, 502);
|
|
1569
|
+
return {
|
|
1570
|
+
error:
|
|
1571
|
+
"Couldn't join the waitlist. Please try again in a moment.",
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1424
1574
|
await trackBuilderLifecycle(
|
|
1425
1575
|
event,
|
|
1426
1576
|
"builder branch waitlist joined",
|
|
1427
1577
|
session.email,
|
|
1428
1578
|
{
|
|
1579
|
+
formId: formSubmission.formId ?? null,
|
|
1580
|
+
formSubmitted: formSubmission.submitted,
|
|
1429
1581
|
stage: "waitlist",
|
|
1430
1582
|
},
|
|
1431
1583
|
);
|
|
1432
|
-
return { ok: true };
|
|
1584
|
+
return { ok: true, formSubmitted: formSubmission.submitted };
|
|
1433
1585
|
}),
|
|
1434
1586
|
);
|
|
1435
1587
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface GoogleOAuthCredentials {
|
|
2
|
+
clientId: string;
|
|
3
|
+
clientSecret: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function readCredentialPair(
|
|
7
|
+
clientIdKey: string,
|
|
8
|
+
clientSecretKey: string,
|
|
9
|
+
): GoogleOAuthCredentials | null {
|
|
10
|
+
const clientId = process.env[clientIdKey];
|
|
11
|
+
const clientSecret = process.env[clientSecretKey];
|
|
12
|
+
if (!clientId || !clientSecret) return null;
|
|
13
|
+
return { clientId, clientSecret };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Credentials for identity-only Google sign-in. Deploys that also use Google
|
|
18
|
+
* product APIs can set these separately from GOOGLE_CLIENT_ID/SECRET, which
|
|
19
|
+
* remain the backwards-compatible provider OAuth credentials.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveGoogleSignInCredentials(): GoogleOAuthCredentials | null {
|
|
22
|
+
return (
|
|
23
|
+
readCredentialPair(
|
|
24
|
+
"GOOGLE_SIGN_IN_CLIENT_ID",
|
|
25
|
+
"GOOGLE_SIGN_IN_CLIENT_SECRET",
|
|
26
|
+
) ?? readCredentialPair("GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET")
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function hasGoogleSignInCredentials(): boolean {
|
|
31
|
+
return resolveGoogleSignInCredentials() !== null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resolveGoogleProviderCredentials(): GoogleOAuthCredentials | null {
|
|
35
|
+
return readCredentialPair("GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function resolveGoogleLegacyProviderCredentials(): GoogleOAuthCredentials | null {
|
|
39
|
+
return readCredentialPair(
|
|
40
|
+
"GOOGLE_LEGACY_CLIENT_ID",
|
|
41
|
+
"GOOGLE_LEGACY_CLIENT_SECRET",
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function resolveGoogleProviderCredentialCandidates(): GoogleOAuthCredentials[] {
|
|
46
|
+
const primary = resolveGoogleProviderCredentials();
|
|
47
|
+
const legacy = resolveGoogleLegacyProviderCredentials();
|
|
48
|
+
if (!primary) return legacy ? [legacy] : [];
|
|
49
|
+
if (!legacy || legacy.clientId === primary.clientId) return [primary];
|
|
50
|
+
return [primary, legacy];
|
|
51
|
+
}
|
|
@@ -264,6 +264,14 @@ export {
|
|
|
264
264
|
type SpawnTaskOptions,
|
|
265
265
|
} from "./agent-teams.js";
|
|
266
266
|
export { isOAuthConnected, getOAuthAccounts } from "./oauth-helpers.js";
|
|
267
|
+
export {
|
|
268
|
+
hasGoogleSignInCredentials,
|
|
269
|
+
resolveGoogleLegacyProviderCredentials,
|
|
270
|
+
resolveGoogleProviderCredentialCandidates,
|
|
271
|
+
resolveGoogleProviderCredentials,
|
|
272
|
+
resolveGoogleSignInCredentials,
|
|
273
|
+
type GoogleOAuthCredentials,
|
|
274
|
+
} from "./google-oauth-credentials.js";
|
|
267
275
|
export { wrapWithAnalytics } from "./analytics.js";
|
|
268
276
|
export {
|
|
269
277
|
getH3App,
|
|
@@ -28,9 +28,10 @@ import {
|
|
|
28
28
|
withAgentNativeSocialImageCacheBuster,
|
|
29
29
|
} from "../shared/social-meta.js";
|
|
30
30
|
import { normalizeAppBasePath } from "./app-base-path.js";
|
|
31
|
+
import { hasGoogleSignInCredentials } from "./google-oauth-credentials.js";
|
|
31
32
|
|
|
32
33
|
function hasGoogleOAuth(): boolean {
|
|
33
|
-
return
|
|
34
|
+
return hasGoogleSignInCredentials();
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
function getConnectionLabel(): string {
|
|
@@ -36,6 +36,10 @@ BUILDER_PRIVATE_KEY=
|
|
|
36
36
|
BUILDER_PUBLIC_KEY=
|
|
37
37
|
BUILDER_USER_ID=
|
|
38
38
|
|
|
39
|
+
# Local ai-services LLM gateway. Leave unset to use the hosted Builder gateway.
|
|
40
|
+
# ai-services listens on API_PORT (default 8080): /agent-native/gateway/v1/messages
|
|
41
|
+
BUILDER_GATEWAY_BASE_URL=
|
|
42
|
+
|
|
39
43
|
# Builder app creation / branching. In local dev these can live in .env.
|
|
40
44
|
# In production, configure deploy env vars instead; production app creation
|
|
41
45
|
# never writes credentials or project IDs to the filesystem.
|
|
@@ -262,7 +262,17 @@ export function DayView({
|
|
|
262
262
|
onDraftDiscard={onDraftDiscard}
|
|
263
263
|
>
|
|
264
264
|
<button
|
|
265
|
-
className=
|
|
265
|
+
className={cn(
|
|
266
|
+
"relative flex w-full items-center gap-1.5 rounded-md px-3 py-1.5 text-left text-sm font-medium text-foreground transition-all hover:brightness-110",
|
|
267
|
+
event.ownerColor && "pr-5",
|
|
268
|
+
)}
|
|
269
|
+
aria-label={
|
|
270
|
+
event.ownerName || event.overlayEmail
|
|
271
|
+
? `${event.title}, ${
|
|
272
|
+
event.ownerName || event.overlayEmail
|
|
273
|
+
}'s calendar`
|
|
274
|
+
: event.title
|
|
275
|
+
}
|
|
266
276
|
style={
|
|
267
277
|
color
|
|
268
278
|
? {
|
|
@@ -283,6 +293,13 @@ export function DayView({
|
|
|
283
293
|
)}
|
|
284
294
|
<EventStatusIcon event={event} className="shrink-0" />
|
|
285
295
|
<span className="truncate">{event.title}</span>
|
|
296
|
+
{event.ownerColor && (
|
|
297
|
+
<span
|
|
298
|
+
aria-hidden="true"
|
|
299
|
+
className="absolute right-2 top-1/2 size-1.5 -translate-y-1/2 rounded-full ring-1 ring-background/70"
|
|
300
|
+
style={{ backgroundColor: event.ownerColor }}
|
|
301
|
+
/>
|
|
302
|
+
)}
|
|
286
303
|
</button>
|
|
287
304
|
</EventDetailPopover>
|
|
288
305
|
);
|
|
@@ -458,7 +475,15 @@ export function DayView({
|
|
|
458
475
|
isBeingDragged && isDragging && "ring-2 ring-primary/40",
|
|
459
476
|
canDrag && isStart && "cursor-grab",
|
|
460
477
|
isBeingDragged && isDragging && "cursor-grabbing",
|
|
478
|
+
event.ownerColor && "pr-4",
|
|
461
479
|
)}
|
|
480
|
+
aria-label={
|
|
481
|
+
event.ownerName || event.overlayEmail
|
|
482
|
+
? `${event.title}, ${
|
|
483
|
+
event.ownerName || event.overlayEmail
|
|
484
|
+
}'s calendar`
|
|
485
|
+
: event.title
|
|
486
|
+
}
|
|
462
487
|
style={{
|
|
463
488
|
...posStyle,
|
|
464
489
|
left: `${li.left}px`,
|
|
@@ -483,6 +508,13 @@ export function DayView({
|
|
|
483
508
|
opacity: isBeingDragged && isDragging ? 0.9 : undefined,
|
|
484
509
|
}}
|
|
485
510
|
>
|
|
511
|
+
{event.ownerColor && (
|
|
512
|
+
<span
|
|
513
|
+
aria-hidden="true"
|
|
514
|
+
className="absolute right-1.5 top-1.5 size-1.5 rounded-full ring-1 ring-background/70"
|
|
515
|
+
style={{ backgroundColor: event.ownerColor }}
|
|
516
|
+
/>
|
|
517
|
+
)}
|
|
486
518
|
{durationMin <= 30 ? (
|
|
487
519
|
<div className="flex items-baseline gap-1.5 truncate">
|
|
488
520
|
{allOthersOut && (
|
|
@@ -16,6 +16,7 @@ import { useAttendeePhotos } from "@/hooks/use-attendee-photos";
|
|
|
16
16
|
import { useRsvpEvent } from "@/hooks/use-events";
|
|
17
17
|
import { cn } from "@/lib/utils";
|
|
18
18
|
import {
|
|
19
|
+
canInlineRsvp,
|
|
19
20
|
getRsvpStatusLabel,
|
|
20
21
|
RsvpStatusIcon,
|
|
21
22
|
type RsvpStatus,
|
|
@@ -406,6 +407,7 @@ export function EventAttendeesSection({
|
|
|
406
407
|
| "id"
|
|
407
408
|
| "accountEmail"
|
|
408
409
|
| "attendees"
|
|
410
|
+
| "overlayEmail"
|
|
409
411
|
| "responseStatus"
|
|
410
412
|
| "source"
|
|
411
413
|
| "recurringEventId"
|
|
@@ -423,8 +425,14 @@ export function EventAttendeesSection({
|
|
|
423
425
|
const { data: photos } = useAttendeePhotos(emails);
|
|
424
426
|
|
|
425
427
|
const sorted = useMemo(() => sortAttendees(attendees), [attendees]);
|
|
426
|
-
const
|
|
427
|
-
const
|
|
428
|
+
const canRsvpInline = canInlineRsvp(event);
|
|
429
|
+
const selfAttendee = canRsvpInline
|
|
430
|
+
? sorted.find((attendee) => attendee.self)
|
|
431
|
+
: undefined;
|
|
432
|
+
const others =
|
|
433
|
+
canRsvpInline && selfAttendee
|
|
434
|
+
? sorted.filter((attendee) => !attendee.self)
|
|
435
|
+
: sorted;
|
|
428
436
|
|
|
429
437
|
useEffect(() => {
|
|
430
438
|
setSelfStatus(event.responseStatus || "needsAction");
|
|
@@ -505,7 +513,7 @@ export function EventAttendeesSection({
|
|
|
505
513
|
attendee={selfAttendee}
|
|
506
514
|
event={event}
|
|
507
515
|
photoUrl={photos?.[selfAttendee.email.toLowerCase()]}
|
|
508
|
-
inlineRsvp={
|
|
516
|
+
inlineRsvp={canRsvpInline}
|
|
509
517
|
currentStatus={selfStatus}
|
|
510
518
|
currentNote={selfNote}
|
|
511
519
|
onResponseChange={handleSelfResponseChange}
|
|
@@ -30,6 +30,7 @@ export function EventCard({
|
|
|
30
30
|
colorPreferences,
|
|
31
31
|
}: EventCardProps) {
|
|
32
32
|
const accentColor = getEventDisplayColor(event, colorPreferences);
|
|
33
|
+
const ownerLabel = event.ownerName || event.overlayEmail;
|
|
33
34
|
|
|
34
35
|
const handleDragStart = (e: React.DragEvent) => {
|
|
35
36
|
e.dataTransfer.setData("text/plain", event.id);
|
|
@@ -47,10 +48,14 @@ export function EventCard({
|
|
|
47
48
|
onDragStart={canDrag ? handleDragStart : undefined}
|
|
48
49
|
onDragEnd={canDrag ? onDragEnd : undefined}
|
|
49
50
|
className={cn(
|
|
50
|
-
"flex w-full items-center gap-1.5 rounded px-1.5 py-0.5 text-left text-xs text-foreground transition-all hover:brightness-110",
|
|
51
|
+
"relative flex w-full items-center gap-1.5 rounded px-1.5 py-0.5 text-left text-xs text-foreground transition-all hover:brightness-110",
|
|
51
52
|
canDrag && "cursor-grab active:cursor-grabbing",
|
|
52
53
|
dimmed && "opacity-40",
|
|
54
|
+
event.ownerColor && "pr-3.5",
|
|
53
55
|
)}
|
|
56
|
+
aria-label={
|
|
57
|
+
ownerLabel ? `${event.title}, ${ownerLabel}'s calendar` : event.title
|
|
58
|
+
}
|
|
54
59
|
style={{
|
|
55
60
|
backgroundColor: `${accentColor}25`,
|
|
56
61
|
}}
|
|
@@ -68,6 +73,13 @@ export function EventCard({
|
|
|
68
73
|
)}
|
|
69
74
|
<EventStatusIcon event={event} />
|
|
70
75
|
<span className="truncate font-medium">{event.title}</span>
|
|
76
|
+
{event.ownerColor && (
|
|
77
|
+
<span
|
|
78
|
+
aria-hidden="true"
|
|
79
|
+
className="absolute right-1 top-1/2 size-1.5 -translate-y-1/2 rounded-full ring-1 ring-background/70"
|
|
80
|
+
style={{ backgroundColor: event.ownerColor }}
|
|
81
|
+
/>
|
|
82
|
+
)}
|
|
71
83
|
</button>
|
|
72
84
|
);
|
|
73
85
|
}
|
|
@@ -79,10 +91,14 @@ export function EventCard({
|
|
|
79
91
|
onDragStart={canDrag ? handleDragStart : undefined}
|
|
80
92
|
onDragEnd={canDrag ? onDragEnd : undefined}
|
|
81
93
|
className={cn(
|
|
82
|
-
"flex w-full flex-col gap-0.5 rounded-md px-2 py-1.5 text-left text-xs text-foreground transition-all hover:brightness-110",
|
|
94
|
+
"relative flex w-full flex-col gap-0.5 rounded-md px-2 py-1.5 text-left text-xs text-foreground transition-all hover:brightness-110",
|
|
83
95
|
canDrag && "cursor-grab active:cursor-grabbing",
|
|
84
96
|
dimmed && "opacity-40",
|
|
97
|
+
event.ownerColor && "pr-4",
|
|
85
98
|
)}
|
|
99
|
+
aria-label={
|
|
100
|
+
ownerLabel ? `${event.title}, ${ownerLabel}'s calendar` : event.title
|
|
101
|
+
}
|
|
86
102
|
style={{
|
|
87
103
|
backgroundColor: `${accentColor}25`,
|
|
88
104
|
borderLeft: `2px solid ${accentColor}`,
|
|
@@ -106,6 +122,13 @@ export function EventCard({
|
|
|
106
122
|
})}
|
|
107
123
|
</span>
|
|
108
124
|
)}
|
|
125
|
+
{event.ownerColor && (
|
|
126
|
+
<span
|
|
127
|
+
aria-hidden="true"
|
|
128
|
+
className="absolute right-1.5 top-1.5 size-1.5 rounded-full ring-1 ring-background/70"
|
|
129
|
+
style={{ backgroundColor: event.ownerColor }}
|
|
130
|
+
/>
|
|
131
|
+
)}
|
|
109
132
|
</button>
|
|
110
133
|
);
|
|
111
134
|
}
|
|
@@ -101,6 +101,7 @@ export function EventDetailPanel({
|
|
|
101
101
|
const isOverlay = !!event?.overlayEmail;
|
|
102
102
|
const lastSavedDescriptionRef = useRef(event?.description || "");
|
|
103
103
|
const meetingLink = event ? extractMeetingLink(event) : null;
|
|
104
|
+
const ownerLabel = event?.ownerName || event?.overlayEmail;
|
|
104
105
|
|
|
105
106
|
// Reset editing state when event changes
|
|
106
107
|
useEffect(() => {
|
|
@@ -310,6 +311,17 @@ export function EventDetailPanel({
|
|
|
310
311
|
</div>
|
|
311
312
|
)}
|
|
312
313
|
|
|
314
|
+
{event.overlayEmail && ownerLabel && (
|
|
315
|
+
<div className="flex items-center gap-2.5 text-sm text-muted-foreground">
|
|
316
|
+
<span
|
|
317
|
+
aria-hidden="true"
|
|
318
|
+
className="ml-0.5 size-2 shrink-0 rounded-full ring-1 ring-border"
|
|
319
|
+
style={{ backgroundColor: event.ownerColor }}
|
|
320
|
+
/>
|
|
321
|
+
<span>Viewing {ownerLabel}'s calendar</span>
|
|
322
|
+
</div>
|
|
323
|
+
)}
|
|
324
|
+
|
|
313
325
|
{meetingLink ? (
|
|
314
326
|
<a
|
|
315
327
|
href={safeUrl(meetingLink)}
|
|
@@ -395,6 +395,7 @@ export function EventDetailPopover({
|
|
|
395
395
|
"meet" | "zoom" | null
|
|
396
396
|
>(null);
|
|
397
397
|
const isOverlay = !!event.overlayEmail;
|
|
398
|
+
const ownerLabel = event.ownerName || event.overlayEmail;
|
|
398
399
|
|
|
399
400
|
const updateEvent = useUpdateEvent();
|
|
400
401
|
const masterEventId =
|
|
@@ -2086,9 +2087,13 @@ Write a short, useful meeting description. If I ask you to apply it, update this
|
|
|
2086
2087
|
<>
|
|
2087
2088
|
<div className="mx-4 my-2 border-t border-border/50" />
|
|
2088
2089
|
<div className="flex items-center gap-3 px-4 py-1.5">
|
|
2089
|
-
<
|
|
2090
|
+
<span
|
|
2091
|
+
aria-hidden="true"
|
|
2092
|
+
className="ml-1 size-2 shrink-0 rounded-full ring-1 ring-border"
|
|
2093
|
+
style={{ backgroundColor: event.ownerColor }}
|
|
2094
|
+
/>
|
|
2090
2095
|
<span className="text-sm text-muted-foreground">
|
|
2091
|
-
{
|
|
2096
|
+
Viewing {ownerLabel}'s calendar
|
|
2092
2097
|
</span>
|
|
2093
2098
|
</div>
|
|
2094
2099
|
</>
|