@agent-native/core 0.225.2-nightly-20261106152255 → 0.226.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/actions/get-agentkit-capabilities.d.ts +12 -1
- package/dist/agent/actions/get-agentkit-capabilities.js +13 -5
- package/dist/agent/production-agent.js +14 -4
- package/dist/agentkit/capabilities.d.ts +4 -2
- package/dist/agentkit/capabilities.js +71 -18
- package/dist/app-config/experience-profiles.d.ts +4 -0
- package/dist/app-config/experience-profiles.js +7 -0
- package/dist/app-config/onboarding.d.ts +2 -0
- package/dist/app-config/onboarding.js +5 -0
- package/dist/app-config/schema.d.ts +5 -0
- package/dist/app-config/schema.js +2 -0
- package/dist/application-state/handlers.d.ts +2 -2
- package/dist/client/agent-chat.d.ts +3 -1
- package/dist/client/agent-chat.js +8 -0
- package/dist/client/agentkit-chat/transport.d.ts +2 -1
- package/dist/client/agentkit-chat/transport.js +2 -1
- package/dist/client/auth/builder-signup.d.ts +14 -0
- package/dist/client/auth/builder-signup.js +75 -0
- package/dist/client/chat/agentkit-agent-native.d.ts +4 -0
- package/dist/client/chat/agentkit-agent-native.js +25 -0
- package/dist/client/chat/composer-draft.d.ts +1 -7
- package/dist/client/chat/composer-draft.js +5 -1
- package/dist/client/chat/native-uploads.d.ts +11 -0
- package/dist/client/chat/native-uploads.js +97 -0
- package/dist/client/experience-profiles.d.ts +182 -0
- package/dist/client/experience-profiles.js +19 -0
- package/dist/client/uploads/index.d.ts +1 -1
- package/dist/client/uploads/index.js +1 -1
- package/dist/client/use-action.js +3 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/experience-profiles/index.d.ts +13 -0
- package/dist/experience-profiles/index.js +10 -0
- package/dist/experience-profiles/server.d.ts +38 -0
- package/dist/experience-profiles/server.js +76 -0
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/framework-package-versions.json +11 -11
- package/dist/localization/workspace-integration-messages.d.ts +11 -0
- package/dist/localization/workspace-integration-messages.js +132 -0
- package/dist/mcp/screen-memory-stdio.d.ts +3 -3
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/org/index.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +5 -5
- package/dist/provider-api/actions/provider-api.d.ts +3 -3
- package/dist/provider-api/actions/provider-api.js +2 -0
- package/dist/provider-api/index.d.ts +7 -1
- package/dist/provider-api/index.js +42 -2
- package/dist/scripts/resources/read.d.ts +6 -1
- package/dist/scripts/resources/read.js +85 -8
- package/dist/secrets/routes.d.ts +2 -2
- package/dist/server/agent-chat/script-entries.js +27 -6
- package/dist/server/agent-chat-plugin.js +102 -33
- package/dist/server/agent-engine-api-key-route.d.ts +4 -4
- package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
- package/dist/server/auth.js +25 -1
- package/dist/server/better-auth-instance.d.ts +1 -0
- package/dist/server/builder-browser.d.ts +2 -0
- package/dist/server/builder-browser.js +8 -4
- package/dist/server/builder-signup-experiment.d.ts +67 -0
- package/dist/server/builder-signup-experiment.js +345 -0
- package/dist/server/core-routes-plugin.d.ts +2 -0
- package/dist/server/core-routes-plugin.js +77 -25
- package/dist/server/first-touch-route.d.ts +1 -1
- package/dist/server/google-oauth.d.ts +2 -0
- package/dist/server/google-oauth.js +5 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/agent-chat-references.d.ts +2 -0
- package/dist/shared/agent-chat-references.js +8 -0
- package/dist/shared/auth-copy.d.ts +1 -0
- package/dist/shared/auth-copy.js +11 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/templates/builder-code-starter/server/lib/composer-reference-messages.ts.patch +44 -0
- package/dist/templates/builder-code-starter/server/plugins/agent-chat.ts.patch +10 -1
- package/dist/templates/chat/app/components/ChatComposerContext.tsx +31 -0
- package/dist/templates/chat/app/components/chat/ChatRouteContent.tsx +3 -0
- package/dist/templates/chat/app/components/home/HomePage.tsx +4 -2
- package/dist/templates/chat/app/components/layout/AgentInspector.tsx +2 -0
- package/dist/templates/chat/app/i18n/ar-SA.ts +7 -0
- package/dist/templates/chat/app/i18n/de-DE.ts +8 -0
- package/dist/templates/chat/app/i18n/en-US.ts +8 -0
- package/dist/templates/chat/app/i18n/es-ES.ts +8 -0
- package/dist/templates/chat/app/i18n/fr-FR.ts +8 -0
- package/dist/templates/chat/app/i18n/hi-IN.ts +7 -0
- package/dist/templates/chat/app/i18n/ja-JP.ts +8 -0
- package/dist/templates/chat/app/i18n/ko-KR.ts +8 -0
- package/dist/templates/chat/app/i18n/pt-BR.ts +8 -0
- package/dist/templates/chat/app/i18n/zh-CN.ts +7 -0
- package/dist/templates/chat/app/i18n/zh-TW.ts +7 -0
- package/dist/templates/chat/server/lib/composer-reference-messages.ts +34 -0
- package/dist/templates/chat/server/lib/composer-references.ts +60 -0
- package/dist/templates/chat/server/plugins/agent-chat.ts +13 -0
- package/docs/content/components.mdx +7 -2
- package/package.json +9 -4
- package/src/templates/builder-code-starter/server/lib/composer-reference-messages.ts.patch +44 -0
- package/src/templates/builder-code-starter/server/plugins/agent-chat.ts.patch +10 -1
- package/src/templates/chat/app/components/ChatComposerContext.tsx +31 -0
- package/src/templates/chat/app/components/chat/ChatRouteContent.tsx +3 -0
- package/src/templates/chat/app/components/home/HomePage.tsx +4 -2
- package/src/templates/chat/app/components/layout/AgentInspector.tsx +2 -0
- package/src/templates/chat/app/i18n/ar-SA.ts +7 -0
- package/src/templates/chat/app/i18n/de-DE.ts +8 -0
- package/src/templates/chat/app/i18n/en-US.ts +8 -0
- package/src/templates/chat/app/i18n/es-ES.ts +8 -0
- package/src/templates/chat/app/i18n/fr-FR.ts +8 -0
- package/src/templates/chat/app/i18n/hi-IN.ts +7 -0
- package/src/templates/chat/app/i18n/ja-JP.ts +8 -0
- package/src/templates/chat/app/i18n/ko-KR.ts +8 -0
- package/src/templates/chat/app/i18n/pt-BR.ts +8 -0
- package/src/templates/chat/app/i18n/zh-CN.ts +7 -0
- package/src/templates/chat/app/i18n/zh-TW.ts +7 -0
- package/src/templates/chat/server/lib/composer-reference-messages.ts +34 -0
- package/src/templates/chat/server/lib/composer-references.ts +60 -0
- package/src/templates/chat/server/plugins/agent-chat.ts +13 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { createHmac, randomBytes, randomInt, timingSafeEqual, } from "node:crypto";
|
|
2
|
+
import { defineEventHandler, deleteCookie, getCookie, getHeader, getMethod, setCookie, setResponseHeader, setResponseStatus, } from "h3";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { getAppConfig } from "../app-config/index.js";
|
|
5
|
+
import { getSetting, mutateSetting } from "../settings/store.js";
|
|
6
|
+
import { track } from "../tracking/index.js";
|
|
7
|
+
import { getAuthSecret, getBetterAuthInternalAdapter, } from "./better-auth-instance.js";
|
|
8
|
+
import { readBodyWithSizeLimit } from "./h3-helpers.js";
|
|
9
|
+
export const BUILDER_SIGNUP_EXPERIMENT = "design_builder_signup_v1";
|
|
10
|
+
export const BUILDER_SIGNUP_POLICY = "builder-signup-2026-11-06";
|
|
11
|
+
export const BUILDER_SIGNUP_COOKIE = "an_builder_signup";
|
|
12
|
+
const TTL = 7 * 24 * 60 * 60;
|
|
13
|
+
const assignmentSchema = z.object({
|
|
14
|
+
id: z.string().regex(/^[a-f0-9]{32}$/),
|
|
15
|
+
variant: z.enum(["control", "automatic"]),
|
|
16
|
+
issuedAt: z.number().int(),
|
|
17
|
+
acceptedAt: z.number().int().optional(),
|
|
18
|
+
attemptId: z.string().min(16).max(128).optional(),
|
|
19
|
+
method: z.enum(["password", "magic-link", "google"]).optional(),
|
|
20
|
+
email: z.string().email().max(320).optional(),
|
|
21
|
+
policy: z.literal(BUILDER_SIGNUP_POLICY),
|
|
22
|
+
});
|
|
23
|
+
function mac(body) {
|
|
24
|
+
return createHmac("sha256", getAuthSecret())
|
|
25
|
+
.update(`${BUILDER_SIGNUP_EXPERIMENT}:${body}`)
|
|
26
|
+
.digest();
|
|
27
|
+
}
|
|
28
|
+
export function encodeBuilderSignupAssignment(assignment) {
|
|
29
|
+
const body = Buffer.from(JSON.stringify(assignment)).toString("base64url");
|
|
30
|
+
return `${body}.${mac(body).toString("base64url")}`;
|
|
31
|
+
}
|
|
32
|
+
export function decodeBuilderSignupAssignment(value) {
|
|
33
|
+
if (!value || value.length > 2048)
|
|
34
|
+
return null;
|
|
35
|
+
const parts = value.split(".");
|
|
36
|
+
if (parts.length !== 2)
|
|
37
|
+
return null;
|
|
38
|
+
const [body, signature] = parts;
|
|
39
|
+
if (!body || !signature)
|
|
40
|
+
return null;
|
|
41
|
+
const expected = mac(body);
|
|
42
|
+
const actual = Buffer.from(signature, "base64url");
|
|
43
|
+
if (actual.length !== expected.length || !timingSafeEqual(actual, expected))
|
|
44
|
+
return null;
|
|
45
|
+
let json;
|
|
46
|
+
try {
|
|
47
|
+
json = JSON.parse(Buffer.from(body, "base64url").toString());
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// coercion-ok: malformed anonymous cookies are rejected, never used as consent.
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const parsed = assignmentSchema.safeParse(json);
|
|
54
|
+
if (!parsed.success)
|
|
55
|
+
return null;
|
|
56
|
+
const assignment = parsed.data;
|
|
57
|
+
if (assignment.issuedAt > Date.now() ||
|
|
58
|
+
Date.now() - assignment.issuedAt > TTL * 1000)
|
|
59
|
+
return null;
|
|
60
|
+
if (assignment.acceptedAt !== undefined &&
|
|
61
|
+
(assignment.acceptedAt < assignment.issuedAt ||
|
|
62
|
+
assignment.acceptedAt > Date.now()))
|
|
63
|
+
return null;
|
|
64
|
+
return assignment;
|
|
65
|
+
}
|
|
66
|
+
function writeAssignment(event, assignment) {
|
|
67
|
+
setCookie(event, BUILDER_SIGNUP_COOKIE, encodeBuilderSignupAssignment(assignment), {
|
|
68
|
+
httpOnly: true,
|
|
69
|
+
secure: getHeader(event, "x-forwarded-proto") === "https" ||
|
|
70
|
+
getHeader(event, "host")?.includes("localhost") !== true,
|
|
71
|
+
sameSite: "lax",
|
|
72
|
+
path: "/",
|
|
73
|
+
maxAge: TTL,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
export function isBuilderSignupExperimentEnabled() {
|
|
77
|
+
const config = getAppConfig().onboarding;
|
|
78
|
+
return (config.builderSignupExperimentOverride ?? config.builderSignupExperiment);
|
|
79
|
+
}
|
|
80
|
+
export async function bindBuilderSignupAttempt(event, attemptId, method, email) {
|
|
81
|
+
if (!isBuilderSignupExperimentEnabled())
|
|
82
|
+
return false;
|
|
83
|
+
const assignment = decodeBuilderSignupAssignment(getCookie(event, BUILDER_SIGNUP_COOKIE));
|
|
84
|
+
if (!assignment)
|
|
85
|
+
return false;
|
|
86
|
+
if (assignment.variant === "control") {
|
|
87
|
+
const adapter = await getBetterAuthInternalAdapter();
|
|
88
|
+
if (!adapter)
|
|
89
|
+
throw new Error("Signup identity store is unavailable");
|
|
90
|
+
const identity = await adapter.findUserByEmail(email, {
|
|
91
|
+
includeAccounts: false,
|
|
92
|
+
});
|
|
93
|
+
const createdAt = identity?.user.createdAt;
|
|
94
|
+
const createdAtMs = createdAt ? new Date(createdAt).getTime() : Number.NaN;
|
|
95
|
+
if (!identity ||
|
|
96
|
+
!Number.isFinite(createdAtMs) ||
|
|
97
|
+
createdAtMs < assignment.issuedAt)
|
|
98
|
+
return false;
|
|
99
|
+
const key = `${BUILDER_SIGNUP_EXPERIMENT}:${identity.user.id}`;
|
|
100
|
+
if (!(await getSetting(key))) {
|
|
101
|
+
await mutateSetting(key, (current) => current ?? {
|
|
102
|
+
id: assignment.id,
|
|
103
|
+
variant: "control",
|
|
104
|
+
issuedAt: assignment.issuedAt,
|
|
105
|
+
policy: assignment.policy,
|
|
106
|
+
userId: identity.user.id,
|
|
107
|
+
status: "control",
|
|
108
|
+
});
|
|
109
|
+
track("builder_signup_experiment_outcome", {
|
|
110
|
+
experiment_id: BUILDER_SIGNUP_EXPERIMENT,
|
|
111
|
+
experiment_variant: "control",
|
|
112
|
+
assignment_id: assignment.id,
|
|
113
|
+
status: "control",
|
|
114
|
+
}, { authUserId: identity.user.id, userId: email });
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (typeof attemptId !== "string" ||
|
|
119
|
+
attemptId.length > 128 ||
|
|
120
|
+
!assignment.acceptedAt ||
|
|
121
|
+
assignment.attemptId !== attemptId)
|
|
122
|
+
return false;
|
|
123
|
+
const key = `${BUILDER_SIGNUP_EXPERIMENT}:attempt:${attemptId}`;
|
|
124
|
+
const normalizedEmail = email.trim().toLowerCase();
|
|
125
|
+
if (assignment.method !== method ||
|
|
126
|
+
(assignment.email && assignment.email !== normalizedEmail))
|
|
127
|
+
return false;
|
|
128
|
+
const adapter = await getBetterAuthInternalAdapter();
|
|
129
|
+
if (!adapter)
|
|
130
|
+
throw new Error("Signup identity store is unavailable");
|
|
131
|
+
const identity = await adapter.findUserByEmail(normalizedEmail, {
|
|
132
|
+
includeAccounts: false,
|
|
133
|
+
});
|
|
134
|
+
const createdAt = identity?.user.createdAt;
|
|
135
|
+
const createdAtMs = createdAt ? new Date(createdAt).getTime() : Number.NaN;
|
|
136
|
+
if (!identity ||
|
|
137
|
+
!Number.isFinite(createdAtMs) ||
|
|
138
|
+
createdAtMs < assignment.acceptedAt)
|
|
139
|
+
return false;
|
|
140
|
+
const bound = z
|
|
141
|
+
.object({ id: z.string(), boundUserId: z.string() })
|
|
142
|
+
.parse(await mutateSetting(key, (current) => current ?? { id: assignment.id, boundUserId: identity.user.id }));
|
|
143
|
+
if (bound.id !== assignment.id || bound.boundUserId !== identity.user.id)
|
|
144
|
+
return false;
|
|
145
|
+
await mutateSetting(`${BUILDER_SIGNUP_EXPERIMENT}:consent:${identity.user.id}`, (current) => current ?? {
|
|
146
|
+
...assignment,
|
|
147
|
+
userId: identity.user.id,
|
|
148
|
+
email: normalizedEmail,
|
|
149
|
+
});
|
|
150
|
+
setCookie(event, "an_builder_signup_pending", "1", {
|
|
151
|
+
path: "/",
|
|
152
|
+
sameSite: "lax",
|
|
153
|
+
maxAge: TTL,
|
|
154
|
+
secure: getHeader(event, "host")?.includes("localhost") !== true,
|
|
155
|
+
});
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
export async function getBuilderSignupConnectError(userId) {
|
|
159
|
+
if (!userId || !isBuilderSignupExperimentEnabled())
|
|
160
|
+
return undefined;
|
|
161
|
+
const record = await getSetting(`${BUILDER_SIGNUP_EXPERIMENT}:${userId}`);
|
|
162
|
+
if (record?.status !== "fallback" || record.code !== "account_exists")
|
|
163
|
+
return undefined;
|
|
164
|
+
const error = z
|
|
165
|
+
.object({ message: z.string(), completedAt: z.number().finite() })
|
|
166
|
+
.parse(record);
|
|
167
|
+
return {
|
|
168
|
+
code: "account_exists",
|
|
169
|
+
message: error.message,
|
|
170
|
+
at: error.completedAt,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
async function finishSignupActivation(key, attempt, outcome) {
|
|
174
|
+
const committed = await mutateSetting(key, (current) => {
|
|
175
|
+
if (!current || current.attempt !== attempt)
|
|
176
|
+
throw new Error("Signup activation reservation ownership changed");
|
|
177
|
+
const status = z
|
|
178
|
+
.enum(["started", "connected", "fallback"])
|
|
179
|
+
.parse(current.status);
|
|
180
|
+
if (status !== "started")
|
|
181
|
+
return current;
|
|
182
|
+
return {
|
|
183
|
+
...current,
|
|
184
|
+
status: outcome.status,
|
|
185
|
+
code: outcome.code ?? null,
|
|
186
|
+
message: outcome.message ?? null,
|
|
187
|
+
completedAt: Date.now(),
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
return z
|
|
191
|
+
.object({
|
|
192
|
+
status: z.enum(["connected", "fallback"]),
|
|
193
|
+
code: z.string().nullable().optional(),
|
|
194
|
+
})
|
|
195
|
+
.parse(committed);
|
|
196
|
+
}
|
|
197
|
+
export function createBuilderSignupExperimentHandler(dependencies) {
|
|
198
|
+
return defineEventHandler(async (event) => {
|
|
199
|
+
setResponseHeader(event, "Cache-Control", "no-store");
|
|
200
|
+
const refuse = (status, code) => {
|
|
201
|
+
setResponseStatus(event, status);
|
|
202
|
+
return { status: "unavailable", code };
|
|
203
|
+
};
|
|
204
|
+
if (!isBuilderSignupExperimentEnabled())
|
|
205
|
+
return { status: "disabled" };
|
|
206
|
+
const method = getMethod(event);
|
|
207
|
+
if (method !== "GET" && method !== "POST")
|
|
208
|
+
return refuse(405, "method_not_allowed");
|
|
209
|
+
if (!dependencies.sameOrigin(event))
|
|
210
|
+
return refuse(403, "cross_origin");
|
|
211
|
+
const session = await dependencies.getSession(event);
|
|
212
|
+
let assignment = decodeBuilderSignupAssignment(getCookie(event, BUILDER_SIGNUP_COOKIE));
|
|
213
|
+
if (method === "GET") {
|
|
214
|
+
if (session)
|
|
215
|
+
return { status: "disabled" };
|
|
216
|
+
if (!assignment) {
|
|
217
|
+
assignment = {
|
|
218
|
+
id: randomBytes(16).toString("hex"),
|
|
219
|
+
variant: randomInt(2) === 0 ? "control" : "automatic",
|
|
220
|
+
issuedAt: Date.now(),
|
|
221
|
+
policy: BUILDER_SIGNUP_POLICY,
|
|
222
|
+
};
|
|
223
|
+
writeAssignment(event, assignment);
|
|
224
|
+
}
|
|
225
|
+
return { status: "assigned", ...assignment };
|
|
226
|
+
}
|
|
227
|
+
if (!/^application\/json\s*(;|$)/i.test(getHeader(event, "content-type") ?? ""))
|
|
228
|
+
return refuse(415, "json_required");
|
|
229
|
+
const body = z
|
|
230
|
+
.object({
|
|
231
|
+
operation: z.enum(["accept", "activate"]),
|
|
232
|
+
assignmentId: z.string().optional(),
|
|
233
|
+
attemptId: z.string().min(16).max(128).optional(),
|
|
234
|
+
method: z.enum(["password", "magic-link", "google"]).optional(),
|
|
235
|
+
email: z.string().email().max(320).optional(),
|
|
236
|
+
})
|
|
237
|
+
.safeParse(await readBodyWithSizeLimit(event, 4096));
|
|
238
|
+
if (!body.success)
|
|
239
|
+
return refuse(400, "invalid_request");
|
|
240
|
+
if (!assignment) {
|
|
241
|
+
deleteCookie(event, "an_builder_signup_pending", { path: "/" });
|
|
242
|
+
return body.data.operation === "activate"
|
|
243
|
+
? { status: "fallback" }
|
|
244
|
+
: refuse(409, "assignment_missing");
|
|
245
|
+
}
|
|
246
|
+
if (body.data.operation === "accept") {
|
|
247
|
+
if (session || body.data.assignmentId !== assignment.id)
|
|
248
|
+
return refuse(409, "assignment_mismatch");
|
|
249
|
+
if (assignment.variant !== "automatic")
|
|
250
|
+
return { status: "not_required" };
|
|
251
|
+
if (!body.data.attemptId ||
|
|
252
|
+
!body.data.method ||
|
|
253
|
+
(body.data.method !== "google" && !body.data.email))
|
|
254
|
+
return refuse(400, "attempt_required");
|
|
255
|
+
assignment = {
|
|
256
|
+
...assignment,
|
|
257
|
+
acceptedAt: Date.now(),
|
|
258
|
+
attemptId: body.data.attemptId,
|
|
259
|
+
method: body.data.method,
|
|
260
|
+
...(body.data.email
|
|
261
|
+
? { email: body.data.email.trim().toLowerCase() }
|
|
262
|
+
: {}),
|
|
263
|
+
};
|
|
264
|
+
writeAssignment(event, assignment);
|
|
265
|
+
setCookie(event, "an_builder_signup_pending", "1", {
|
|
266
|
+
path: "/",
|
|
267
|
+
sameSite: "lax",
|
|
268
|
+
maxAge: TTL,
|
|
269
|
+
secure: getHeader(event, "host")?.includes("localhost") !== true,
|
|
270
|
+
});
|
|
271
|
+
return { status: "accepted" };
|
|
272
|
+
}
|
|
273
|
+
if (!session?.emailVerified || !session.userId)
|
|
274
|
+
return refuse(401, "verified_session_required");
|
|
275
|
+
const terminal = (status, code) => {
|
|
276
|
+
deleteCookie(event, "an_builder_signup_pending", { path: "/" });
|
|
277
|
+
return { status, ...(code ? { code } : {}) };
|
|
278
|
+
};
|
|
279
|
+
if (assignment.variant !== "automatic")
|
|
280
|
+
return terminal("fallback", "control");
|
|
281
|
+
if (!assignment.acceptedAt || !assignment.attemptId)
|
|
282
|
+
return terminal("fallback");
|
|
283
|
+
const consent = await getSetting(`${BUILDER_SIGNUP_EXPERIMENT}:consent:${session.userId}`);
|
|
284
|
+
if (!consent ||
|
|
285
|
+
consent.attemptId !== assignment.attemptId ||
|
|
286
|
+
consent.id !== assignment.id ||
|
|
287
|
+
consent.userId !== session.userId ||
|
|
288
|
+
consent.email !== session.email.trim().toLowerCase())
|
|
289
|
+
return terminal("fallback", "signup_attempt_unbound");
|
|
290
|
+
const adapter = await getBetterAuthInternalAdapter();
|
|
291
|
+
if (!adapter)
|
|
292
|
+
return refuse(503, "identity_unavailable");
|
|
293
|
+
const identity = await adapter.findUserByEmail(session.email, {
|
|
294
|
+
includeAccounts: false,
|
|
295
|
+
});
|
|
296
|
+
const createdAt = identity?.user.createdAt;
|
|
297
|
+
const createdAtMs = createdAt ? new Date(createdAt).getTime() : Number.NaN;
|
|
298
|
+
if (!identity ||
|
|
299
|
+
identity.user.id !== session.userId ||
|
|
300
|
+
!Number.isFinite(createdAtMs) ||
|
|
301
|
+
createdAtMs < assignment.acceptedAt)
|
|
302
|
+
return terminal("fallback");
|
|
303
|
+
const key = `${BUILDER_SIGNUP_EXPERIMENT}:${session.userId}`;
|
|
304
|
+
const existing = await getSetting(key);
|
|
305
|
+
if (existing) {
|
|
306
|
+
const status = z
|
|
307
|
+
.enum(["started", "connected", "fallback"])
|
|
308
|
+
.parse(existing.status);
|
|
309
|
+
if (status !== "started")
|
|
310
|
+
return terminal(status);
|
|
311
|
+
return { status: "pending" };
|
|
312
|
+
}
|
|
313
|
+
const attempt = randomBytes(16).toString("hex");
|
|
314
|
+
const record = await mutateSetting(key, (current) => current ?? {
|
|
315
|
+
...assignment,
|
|
316
|
+
status: "started",
|
|
317
|
+
startedAt: Date.now(),
|
|
318
|
+
attempt,
|
|
319
|
+
userId: session.userId,
|
|
320
|
+
});
|
|
321
|
+
if (record.attempt !== attempt)
|
|
322
|
+
return { status: "pending" };
|
|
323
|
+
let result;
|
|
324
|
+
try {
|
|
325
|
+
result = await dependencies.activate(event, session);
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
result = { ok: false, code: "activation_interrupted" };
|
|
329
|
+
}
|
|
330
|
+
const committed = await finishSignupActivation(key, attempt, {
|
|
331
|
+
status: result.ok ? "connected" : "fallback",
|
|
332
|
+
...(result.code ? { code: result.code } : {}),
|
|
333
|
+
...(result.message ? { message: result.message } : {}),
|
|
334
|
+
});
|
|
335
|
+
const status = committed.status;
|
|
336
|
+
track("builder_signup_experiment_outcome", {
|
|
337
|
+
experiment_id: BUILDER_SIGNUP_EXPERIMENT,
|
|
338
|
+
experiment_variant: assignment.variant,
|
|
339
|
+
assignment_id: assignment.id,
|
|
340
|
+
status,
|
|
341
|
+
code: committed.code ?? null,
|
|
342
|
+
}, { userId: session.email, authUserId: session.userId });
|
|
343
|
+
return terminal(status, committed.code ?? undefined);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
@@ -610,6 +610,7 @@ type BuilderConnectMember = {
|
|
|
610
610
|
export type BuilderAccountActivationResult = {
|
|
611
611
|
ok: true;
|
|
612
612
|
scope: "user" | "org";
|
|
613
|
+
code?: string;
|
|
613
614
|
} | {
|
|
614
615
|
ok: false;
|
|
615
616
|
status: number;
|
|
@@ -620,6 +621,7 @@ export type BuilderAccountActivationResult = {
|
|
|
620
621
|
code?: string;
|
|
621
622
|
};
|
|
622
623
|
interface BuilderAccountActivationInput {
|
|
624
|
+
newAccountOnly?: boolean;
|
|
623
625
|
ownerEmail: string;
|
|
624
626
|
session: AuthSession | null;
|
|
625
627
|
provisioningToken: string | null | undefined;
|
|
@@ -10,6 +10,7 @@ import { registerBuiltinEngines } from "../agent/engine/builtin.js";
|
|
|
10
10
|
import { OPENAI_BASE_URL_ENV_VAR, PROVIDER_ENV_META, } from "../agent/engine/provider-env-vars.js";
|
|
11
11
|
import { detectEngineFromEnvForRequest, detectEngineFromUserSecrets, isStoredEngineUsableForRequest, } from "../agent/engine/registry.js";
|
|
12
12
|
import { canUpdateAgentLoopSettings, readAgentLoopSettings, resetAgentLoopSettings, validateMaxIterationsInput, writeAgentLoopSettings, } from "../agent/loop-settings.js";
|
|
13
|
+
import { createBuilderSignupExperimentHandler, getBuilderSignupConnectError, } from "./builder-signup-experiment.js";
|
|
13
14
|
export { normalizeAgentEngineStatusModel, resolveAgentEngineStatus, } from "../agent/engine-status.js";
|
|
14
15
|
import { getAppConfig } from "../app-config/index.js";
|
|
15
16
|
import { getState, putState, compareAndSetState, deleteState, listComposeDrafts, getComposeDraft, putComposeDraft, saveComposeDraft, deleteComposeDraft, deleteAllComposeDrafts, getStateMany, putStateMany, APP_STATE_ANONYMOUS_OWNER_CONTEXT_KEY, } from "../application-state/handlers.js";
|
|
@@ -1555,36 +1556,57 @@ export async function activateBuilderAccount(event, input) {
|
|
|
1555
1556
|
}
|
|
1556
1557
|
const activationOrg = activation.write;
|
|
1557
1558
|
const credentials = await provisionBuilderAccount({
|
|
1559
|
+
...(input.newAccountOnly ? { newAccountOnly: true } : {}),
|
|
1558
1560
|
email: ownerEmail,
|
|
1559
1561
|
name: session.name,
|
|
1560
1562
|
agentNativeApp: input.tracking.agentNativeApp,
|
|
1561
1563
|
agentNativeTemplate: input.tracking.agentNativeTemplate,
|
|
1562
1564
|
});
|
|
1563
1565
|
const { writeBuilderCredentials } = await import("./credential-provider.js");
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1566
|
+
let written;
|
|
1567
|
+
try {
|
|
1568
|
+
written = await writeBuilderCredentials(ownerEmail, credentials, activationOrg ?? undefined);
|
|
1569
|
+
}
|
|
1570
|
+
catch (error) {
|
|
1571
|
+
captureError(error, { route: "builder.provision.credential-write" });
|
|
1572
|
+
return fail(409, "A Builder.io account already exists for this email. Sign in to Builder.io to use it.", "credential_write_failed", "account_exists");
|
|
1573
|
+
}
|
|
1574
|
+
try {
|
|
1575
|
+
await Promise.all([
|
|
1576
|
+
deleteSetting("builder-disconnected").catch(() => false),
|
|
1577
|
+
...getBuilderConnectErrorCleanupKeys(ownerEmail, connectAttemptId).map((key) => deleteSetting(key).catch(() => false)),
|
|
1578
|
+
]);
|
|
1579
|
+
await trackBuilderLifecycle(event, "builder connect succeeded", ownerEmail, {
|
|
1580
|
+
...builderConnectTrackingProperties(input.tracking),
|
|
1581
|
+
stage: "provision",
|
|
1582
|
+
credential_scope: written.scope,
|
|
1583
|
+
account_provisioned: true,
|
|
1584
|
+
});
|
|
1585
|
+
track("llm_credential_changed", {
|
|
1586
|
+
change_type: "connected",
|
|
1587
|
+
provider: "builder",
|
|
1588
|
+
scope: written.scope,
|
|
1589
|
+
credential_kind: "builder",
|
|
1590
|
+
}, { userId: ownerEmail });
|
|
1591
|
+
await recordBuilderConnectionAudit({
|
|
1592
|
+
connected: true,
|
|
1593
|
+
ownerEmail,
|
|
1594
|
+
orgId: activationOrg?.orgId ?? null,
|
|
1595
|
+
scope: written.scope,
|
|
1596
|
+
attribution: routeAuditAttribution(event),
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
catch (error) {
|
|
1600
|
+
captureError(error, {
|
|
1601
|
+
route: "builder.provision.post-write",
|
|
1602
|
+
tags: { credential_write: "completed" },
|
|
1603
|
+
});
|
|
1604
|
+
return {
|
|
1605
|
+
ok: true,
|
|
1606
|
+
scope: written.scope,
|
|
1607
|
+
code: "post_write_reporting_failed",
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1588
1610
|
return { ok: true, scope: written.scope };
|
|
1589
1611
|
}
|
|
1590
1612
|
catch (error) {
|
|
@@ -2871,6 +2893,7 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
2871
2893
|
const envStatus = getBuilderBrowserStatusForEvent(event);
|
|
2872
2894
|
const ownerContext = await resolveBuilderOwnerContext(event);
|
|
2873
2895
|
const userEmail = ownerContext.email;
|
|
2896
|
+
const signupConnectError = await getBuilderSignupConnectError(ownerContext.session?.userId);
|
|
2874
2897
|
const provisioningToken = userEmail &&
|
|
2875
2898
|
ownerContext.session?.token &&
|
|
2876
2899
|
isBuilderAccountProvisioningEnabled()
|
|
@@ -2884,7 +2907,14 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
2884
2907
|
// (`effective`) alongside the grants that exist, so the UI can show
|
|
2885
2908
|
// the organization and personal connections as separate rows.
|
|
2886
2909
|
const withConnectToken = (status, effective = null) => {
|
|
2887
|
-
const withConnections = {
|
|
2910
|
+
const withConnections = {
|
|
2911
|
+
...status,
|
|
2912
|
+
...connections,
|
|
2913
|
+
effective,
|
|
2914
|
+
...(signupConnectError && status.configured !== true
|
|
2915
|
+
? { connectError: signupConnectError }
|
|
2916
|
+
: {}),
|
|
2917
|
+
};
|
|
2888
2918
|
if (!userEmail)
|
|
2889
2919
|
return withConnections;
|
|
2890
2920
|
return {
|
|
@@ -3430,6 +3460,28 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
3430
3460
|
setResponseHeader(event, "Location", authorizationUrl);
|
|
3431
3461
|
return "";
|
|
3432
3462
|
}));
|
|
3463
|
+
getH3App(nitroApp).use(`${P}/auth/builder-signup-experiment`, createBuilderSignupExperimentHandler({
|
|
3464
|
+
getSession,
|
|
3465
|
+
sameOrigin: isSameOriginConnect,
|
|
3466
|
+
activate: async (event, session) => {
|
|
3467
|
+
const authorization = await authorizeBuilderConnectScope(event, session.email, null);
|
|
3468
|
+
if ("deny" in authorization)
|
|
3469
|
+
return { ok: false, code: authorization.deny.reason };
|
|
3470
|
+
return activateBuilderAccount(event, {
|
|
3471
|
+
ownerEmail: session.email,
|
|
3472
|
+
session,
|
|
3473
|
+
provisioningToken: signBuilderProvisioningToken(session.email, session.token ?? ""),
|
|
3474
|
+
requestedScope: null,
|
|
3475
|
+
member: authorization.member,
|
|
3476
|
+
connectAttemptId: null,
|
|
3477
|
+
tracking: {
|
|
3478
|
+
agentNativeApp: "design",
|
|
3479
|
+
agentNativeTemplate: "design",
|
|
3480
|
+
},
|
|
3481
|
+
newAccountOnly: true,
|
|
3482
|
+
});
|
|
3483
|
+
},
|
|
3484
|
+
}));
|
|
3433
3485
|
getH3App(nitroApp).use(`${P}/builder/provision`, createBuilderProvisionHandler((event) => resolveBuilderOwnerContext(event)));
|
|
3434
3486
|
// Desktop Code Agents speak Anthropic Messages locally, but Builder
|
|
3435
3487
|
// Gateway credentials live on the server. This same-origin route keeps
|
|
@@ -17,8 +17,8 @@ export type FirstTouchSyncResult = {
|
|
|
17
17
|
* browser's `an_ft` and `an_lt` again, so Safari keeps them their 30 days.
|
|
18
18
|
*/
|
|
19
19
|
export declare function createFirstTouchHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
20
|
-
error: string;
|
|
21
20
|
reason?: undefined;
|
|
21
|
+
error: string;
|
|
22
22
|
stored?: undefined;
|
|
23
23
|
firstTouch?: undefined;
|
|
24
24
|
} | {
|
|
@@ -75,6 +75,7 @@ export interface OAuthStatePayload {
|
|
|
75
75
|
desktopVerifierHash?: string;
|
|
76
76
|
desktopBrowserBindingHash?: string;
|
|
77
77
|
desktopWebview?: boolean;
|
|
78
|
+
builderSignupAttemptId?: string;
|
|
78
79
|
signupAttribution?: Record<string, string | undefined>;
|
|
79
80
|
signupAnonymousId?: string;
|
|
80
81
|
signupSessionId?: string;
|
|
@@ -114,6 +115,7 @@ export interface EncodeOAuthStateOptions {
|
|
|
114
115
|
desktopVerifierHash?: string;
|
|
115
116
|
desktopBrowserBindingHash?: string;
|
|
116
117
|
desktopWebview?: boolean;
|
|
118
|
+
builderSignupAttemptId?: string;
|
|
117
119
|
signupAttribution?: Record<string, string | undefined>;
|
|
118
120
|
signupAnonymousId?: string;
|
|
119
121
|
signupSessionId?: string;
|
|
@@ -479,6 +479,8 @@ export function encodeOAuthState(redirectUriOrOpts, owner, desktop, addAccount,
|
|
|
479
479
|
payload.bh = opts.desktopBrowserBindingHash;
|
|
480
480
|
if (opts.desktopWebview)
|
|
481
481
|
payload.dw = true;
|
|
482
|
+
if (opts.builderSignupAttemptId)
|
|
483
|
+
payload.bs = opts.builderSignupAttemptId;
|
|
482
484
|
if (opts.signupAttribution)
|
|
483
485
|
payload.ft = opts.signupAttribution;
|
|
484
486
|
const signupAnonymousId = normalizeAnalyticsAnonymousId(opts.signupAnonymousId);
|
|
@@ -538,6 +540,9 @@ export function decodeOAuthState(stateParam, fallbackUri) {
|
|
|
538
540
|
desktopVerifierHash: typeof parsed.vh === "string" ? parsed.vh : undefined,
|
|
539
541
|
desktopBrowserBindingHash: typeof parsed.bh === "string" ? parsed.bh : undefined,
|
|
540
542
|
desktopWebview: parsed.dw === true,
|
|
543
|
+
builderSignupAttemptId: typeof parsed.bs === "string" && parsed.bs.length <= 128
|
|
544
|
+
? parsed.bs
|
|
545
|
+
: undefined,
|
|
541
546
|
signupAttribution: sanitizeStateAttribution(parsed.ft),
|
|
542
547
|
signupAnonymousId: sanitizeStateAnonymousId(parsed.ai),
|
|
543
548
|
signupSessionId: normalizeAnalyticsSessionId(parsed.si),
|
package/dist/server/index.d.ts
CHANGED
|
@@ -69,7 +69,7 @@ export { refreshMcpManagerForPrincipal } from "./agent-chat/mcp-glue.js";
|
|
|
69
69
|
export { AGENT_CHAT_STREAM_PATH, AGENT_CHAT_STREAM_TOKEN_SUFFIX, AGENT_CHAT_STREAM_TOKEN_TTL_SECONDS, createAgentChatStreamToken, isAgentChatStreamingRuntime, readAgentChatStreamBearerToken, verifyAgentChatStreamToken, type AgentChatStreamPrincipal, } from "./agent-chat-stream.js";
|
|
70
70
|
export type { AgentChatMcpIcon, AgentChatMcpOptions, } from "./agent-chat/mcp-options.js";
|
|
71
71
|
export { configureAgentNativeEmbeddedEnvironment, createAgentNativeEmbeddedAuthOptions, createAgentNativeEmbeddedPlugin, mountAgentNativeEmbedded, normalizeAgentNativeEmbeddedSession, type AgentNativeEmbeddedAuthOptions, type AgentNativeEmbeddedGetSession, type AgentNativeEmbeddedHostSession, type AgentNativeEmbeddedPluginOptions, } from "./embedded.js";
|
|
72
|
-
export { createThread, getThread, listThreads, updateThreadData, deleteThread, setThreadArchived, setThreadPinned, setThreadScope, type ChatThread, type ChatThreadAccessBasis, type ChatThreadOwnership, type ChatThreadScope, type ChatThreadSummary, type ChatThreadListRow, type ListThreadsOptions, } from "../chat-threads/store.js";
|
|
72
|
+
export { createThread, getThread, listThreads, searchThreads, updateThreadData, deleteThread, setThreadArchived, setThreadPinned, setThreadScope, type ChatThread, type ChatThreadAccessBasis, type ChatThreadOwnership, type ChatThreadScope, type ChatThreadSummary, type ChatThreadListRow, type ListThreadsOptions, } from "../chat-threads/store.js";
|
|
73
73
|
export { createResourcesPlugin, defaultResourcesPlugin, } from "./resources-plugin.js";
|
|
74
74
|
export { createCoreRoutesPlugin, defaultCoreRoutesPlugin, FRAMEWORK_ROUTE_PREFIX, type CoreRoutesPluginOptions, } from "./core-routes-plugin.js";
|
|
75
75
|
export type { CoreRoutesMcpOptions } from "./core-routes/mcp-connect-options.js";
|
package/dist/server/index.js
CHANGED
|
@@ -64,7 +64,7 @@ export { buildFrameworkPrompts, generateActionsPrompt, } from "./agent-chat/fram
|
|
|
64
64
|
export { refreshMcpManagerForPrincipal } from "./agent-chat/mcp-glue.js";
|
|
65
65
|
export { AGENT_CHAT_STREAM_PATH, AGENT_CHAT_STREAM_TOKEN_SUFFIX, AGENT_CHAT_STREAM_TOKEN_TTL_SECONDS, createAgentChatStreamToken, isAgentChatStreamingRuntime, readAgentChatStreamBearerToken, verifyAgentChatStreamToken, } from "./agent-chat-stream.js";
|
|
66
66
|
export { configureAgentNativeEmbeddedEnvironment, createAgentNativeEmbeddedAuthOptions, createAgentNativeEmbeddedPlugin, mountAgentNativeEmbedded, normalizeAgentNativeEmbeddedSession, } from "./embedded.js";
|
|
67
|
-
export { createThread, getThread, listThreads, updateThreadData, deleteThread, setThreadArchived, setThreadPinned, setThreadScope, } from "../chat-threads/store.js";
|
|
67
|
+
export { createThread, getThread, listThreads, searchThreads, updateThreadData, deleteThread, setThreadArchived, setThreadPinned, setThreadScope, } from "../chat-threads/store.js";
|
|
68
68
|
export { createResourcesPlugin, defaultResourcesPlugin, } from "./resources-plugin.js";
|
|
69
69
|
export { createCoreRoutesPlugin, defaultCoreRoutesPlugin, FRAMEWORK_ROUTE_PREFIX, } from "./core-routes-plugin.js";
|
|
70
70
|
export { buildRuntimeConfigPrompt, formatRuntimeConfigReport, getRuntimeConfigReport, parseRuntimeConfigReport, runtimeConfigRequirementsFromSearchParams, } from "../shared/runtime-config.js";
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* typed JSON-in / JSON-out).
|
|
18
18
|
*/
|
|
19
19
|
export declare function createTranscribeVoiceHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
20
|
-
error: string;
|
|
21
20
|
text?: undefined;
|
|
21
|
+
error: string;
|
|
22
22
|
} | {
|
|
23
23
|
error?: undefined;
|
|
24
24
|
text: string;
|