@agent-native/core 0.222.1-nightly-20261029063606 → 0.222.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/production-agent.d.ts +2 -0
- package/dist/agent/production-agent.js +86 -6
- package/dist/agent/run-store.js +3 -2
- package/dist/app-config/agent.d.ts +9 -0
- package/dist/app-config/agent.js +31 -0
- package/dist/app-config/schema.d.ts +9 -0
- package/dist/app-config/store.js +48 -5
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +4 -4
- package/dist/jobs/run-history.js +3 -2
- package/dist/observability/hosted-model-experiment.d.ts +1 -1
- package/dist/observability/hosted-model-experiment.js +7 -1
- package/dist/observability/question-intake-experiment.d.ts +34 -0
- package/dist/observability/question-intake-experiment.js +114 -0
- package/dist/observability/traces.d.ts +4 -0
- package/dist/observability/traces.js +5 -4
- package/dist/observability/tracing.d.ts +1 -0
- package/dist/observability/tracing.js +14 -4
- package/dist/org/index.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
- package/dist/provider-api/actions/provider-api.d.ts +7 -7
- package/dist/server/agent-engine-api-key-route.d.ts +3 -3
- package/dist/server/better-auth-instance.js +43 -0
- package/dist/server/builder-branch-files.d.ts +3 -1
- package/dist/server/builder-branch-files.js +74 -33
- package/dist/server/builder-branch-git.d.ts +1 -1
- package/dist/server/builder-branch-git.js +2 -0
- package/dist/server/core-routes-plugin.js +31 -4
- package/dist/server/request-context.d.ts +8 -0
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/first-run-builder-credits-experiment.d.ts +1 -0
- package/dist/shared/first-run-builder-credits-experiment.js +19 -0
- package/dist/tracking/index.d.ts +1 -1
- package/dist/tracking/index.js +1 -1
- package/dist/tracking/registry.d.ts +7 -0
- package/dist/tracking/registry.js +84 -4
- package/package.json +4 -4
|
@@ -35,6 +35,7 @@ export interface TrackingEventScope {
|
|
|
35
35
|
pending: Set<Promise<void>>;
|
|
36
36
|
}
|
|
37
37
|
export declare function createTrackingEventScope(): TrackingEventScope;
|
|
38
|
+
export declare function registerTrackingWork(pending: Promise<void>, scope?: TrackingEventScope): void;
|
|
38
39
|
export declare function recordTrackingEvent(name: string, properties?: Record<string, unknown>, origin?: TrackingEventOrigin): Promise<void>;
|
|
39
40
|
export declare function queueTrackingEvent(name: string, properties?: Record<string, unknown>, origin?: TrackingEventOrigin, scope?: TrackingEventScope): void;
|
|
40
41
|
export declare function flushTrackingEvents(scope?: TrackingEventScope): Promise<void>;
|
|
@@ -59,6 +59,11 @@ const pendingTrackingEvents = new Set();
|
|
|
59
59
|
export function createTrackingEventScope() {
|
|
60
60
|
return { pending: new Set() };
|
|
61
61
|
}
|
|
62
|
+
export function registerTrackingWork(pending, scope) {
|
|
63
|
+
const pendingEvents = scope?.pending ?? pendingTrackingEvents;
|
|
64
|
+
pendingEvents.add(pending);
|
|
65
|
+
void pending.then(() => pendingEvents.delete(pending), () => pendingEvents.delete(pending));
|
|
66
|
+
}
|
|
62
67
|
function numericDuration(properties) {
|
|
63
68
|
const duration = properties.duration_ms;
|
|
64
69
|
return typeof duration === "number" &&
|
|
@@ -194,12 +199,17 @@ export async function recordTrackingEvent(name, properties = {}, origin) {
|
|
|
194
199
|
}
|
|
195
200
|
export function queueTrackingEvent(name, properties = {}, origin = "server", scope) {
|
|
196
201
|
const pending = recordTrackingEvent(name, properties, origin);
|
|
197
|
-
|
|
198
|
-
pendingEvents.add(pending);
|
|
199
|
-
void pending.then(() => pendingEvents.delete(pending), () => pendingEvents.delete(pending));
|
|
202
|
+
registerTrackingWork(pending, scope);
|
|
200
203
|
}
|
|
201
204
|
export async function flushTrackingEvents(scope) {
|
|
202
|
-
|
|
205
|
+
const pendingEvents = scope?.pending ?? pendingTrackingEvents;
|
|
206
|
+
if (!scope) {
|
|
207
|
+
await Promise.allSettled([...pendingEvents]);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
while (pendingEvents.size > 0) {
|
|
211
|
+
await Promise.allSettled([...pendingEvents]);
|
|
212
|
+
}
|
|
203
213
|
}
|
|
204
214
|
export async function startAgentSpan(name, attributes = {}, parentSpan = null, startTime) {
|
|
205
215
|
const runtime = await resolveRuntime();
|
package/dist/org/index.d.ts
CHANGED
|
@@ -180,10 +180,10 @@ export declare const getMyOrgHandler: import("h3").EventHandlerWithFetch<import(
|
|
|
180
180
|
soloDeploymentAdmin: boolean | undefined;
|
|
181
181
|
}>>;
|
|
182
182
|
export declare const createOrgHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
183
|
-
id?: undefined;
|
|
184
183
|
name?: undefined;
|
|
185
184
|
success: boolean;
|
|
186
185
|
role?: undefined;
|
|
186
|
+
id?: undefined;
|
|
187
187
|
} | {
|
|
188
188
|
success?: undefined;
|
|
189
189
|
id: string;
|
|
@@ -76,9 +76,9 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
76
76
|
}>>;
|
|
77
77
|
}, z.core.$strip>>, {
|
|
78
78
|
id?: undefined;
|
|
79
|
+
provider?: undefined;
|
|
79
80
|
deleted?: undefined;
|
|
80
81
|
message?: undefined;
|
|
81
|
-
found?: undefined;
|
|
82
82
|
providers: {
|
|
83
83
|
id: string;
|
|
84
84
|
label: string;
|
|
@@ -89,7 +89,7 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
89
89
|
updatedAt: number;
|
|
90
90
|
}[];
|
|
91
91
|
count: number;
|
|
92
|
-
|
|
92
|
+
found?: undefined;
|
|
93
93
|
registered?: undefined;
|
|
94
94
|
label?: undefined;
|
|
95
95
|
} | {
|
|
@@ -103,31 +103,31 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
103
103
|
registered?: undefined;
|
|
104
104
|
label?: undefined;
|
|
105
105
|
} | {
|
|
106
|
+
provider?: undefined;
|
|
106
107
|
count?: undefined;
|
|
107
108
|
deleted?: undefined;
|
|
108
109
|
message?: undefined;
|
|
109
110
|
providers?: undefined;
|
|
110
|
-
provider?: undefined;
|
|
111
111
|
found: boolean;
|
|
112
112
|
id: string;
|
|
113
113
|
registered?: undefined;
|
|
114
114
|
label?: undefined;
|
|
115
115
|
} | {
|
|
116
|
+
provider?: undefined;
|
|
116
117
|
count?: undefined;
|
|
117
118
|
message?: undefined;
|
|
118
|
-
found?: undefined;
|
|
119
119
|
providers?: undefined;
|
|
120
|
-
|
|
120
|
+
found?: undefined;
|
|
121
121
|
deleted: boolean;
|
|
122
122
|
id: string;
|
|
123
123
|
registered?: undefined;
|
|
124
124
|
label?: undefined;
|
|
125
125
|
} | {
|
|
126
|
+
provider?: undefined;
|
|
126
127
|
count?: undefined;
|
|
127
128
|
deleted?: undefined;
|
|
128
|
-
found?: undefined;
|
|
129
129
|
providers?: undefined;
|
|
130
|
-
|
|
130
|
+
found?: undefined;
|
|
131
131
|
registered: boolean;
|
|
132
132
|
id: string;
|
|
133
133
|
label: string;
|
|
@@ -316,9 +316,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
316
316
|
notes?: string | undefined;
|
|
317
317
|
scope?: "org" | "user" | undefined;
|
|
318
318
|
}, {
|
|
319
|
+
provider?: undefined;
|
|
319
320
|
id?: undefined;
|
|
320
321
|
message?: undefined;
|
|
321
|
-
found?: undefined;
|
|
322
322
|
providers: {
|
|
323
323
|
id: string;
|
|
324
324
|
label: string;
|
|
@@ -329,7 +329,7 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
329
329
|
updatedAt: number;
|
|
330
330
|
}[];
|
|
331
331
|
count: number;
|
|
332
|
-
|
|
332
|
+
found?: undefined;
|
|
333
333
|
deleted?: undefined;
|
|
334
334
|
registered?: undefined;
|
|
335
335
|
label?: undefined;
|
|
@@ -344,30 +344,30 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
344
344
|
registered?: undefined;
|
|
345
345
|
label?: undefined;
|
|
346
346
|
} | {
|
|
347
|
+
provider?: undefined;
|
|
347
348
|
count?: undefined;
|
|
348
349
|
message?: undefined;
|
|
349
350
|
providers?: undefined;
|
|
350
|
-
provider?: undefined;
|
|
351
351
|
found: boolean;
|
|
352
352
|
id: string;
|
|
353
353
|
deleted?: undefined;
|
|
354
354
|
registered?: undefined;
|
|
355
355
|
label?: undefined;
|
|
356
356
|
} | {
|
|
357
|
+
provider?: undefined;
|
|
357
358
|
count?: undefined;
|
|
358
359
|
message?: undefined;
|
|
359
|
-
found?: undefined;
|
|
360
360
|
providers?: undefined;
|
|
361
|
-
|
|
361
|
+
found?: undefined;
|
|
362
362
|
deleted: boolean;
|
|
363
363
|
id: string;
|
|
364
364
|
registered?: undefined;
|
|
365
365
|
label?: undefined;
|
|
366
366
|
} | {
|
|
367
|
+
provider?: undefined;
|
|
367
368
|
count?: undefined;
|
|
368
|
-
found?: undefined;
|
|
369
369
|
providers?: undefined;
|
|
370
|
-
|
|
370
|
+
found?: undefined;
|
|
371
371
|
deleted?: undefined;
|
|
372
372
|
registered: boolean;
|
|
373
373
|
id: string;
|
|
@@ -54,21 +54,21 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
|
|
|
54
54
|
error: string;
|
|
55
55
|
}>;
|
|
56
56
|
export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
57
|
+
error: string;
|
|
57
58
|
errorCode?: undefined;
|
|
58
59
|
code?: undefined;
|
|
59
|
-
error: string;
|
|
60
60
|
} | {
|
|
61
|
-
code?: undefined;
|
|
62
61
|
error: string;
|
|
63
62
|
errorCode: string;
|
|
63
|
+
code?: undefined;
|
|
64
64
|
} | {
|
|
65
65
|
errorCode?: undefined;
|
|
66
66
|
error: string;
|
|
67
67
|
code: ProviderKeyCheckCode;
|
|
68
68
|
} | {
|
|
69
|
+
error?: undefined;
|
|
69
70
|
errorCode?: undefined;
|
|
70
71
|
code?: undefined;
|
|
71
|
-
error?: undefined;
|
|
72
72
|
ok: boolean;
|
|
73
73
|
key: string;
|
|
74
74
|
baseUrlKey?: string | undefined;
|
|
@@ -26,6 +26,7 @@ import { CORE_CHANGE_EMAIL_CONFIRMATION_EMAIL_ID, CORE_CHANGE_EMAIL_VERIFICATION
|
|
|
26
26
|
import { renderTransactionalEmail } from "../email-catalog/templates.js";
|
|
27
27
|
import { executeIdentityRekey, rekeyIdentity, resumePendingIdentityRekeys, verifiedEmailChangeFromToken, } from "../identity/rekey.js";
|
|
28
28
|
import { saveOAuthTokens } from "../oauth-tokens/store.js";
|
|
29
|
+
import { assignQuestionIntakeExperimentOnSignup } from "../observability/question-intake-experiment.js";
|
|
29
30
|
import { acceptPendingInvitationsForEmail } from "../org/accept-pending.js";
|
|
30
31
|
import { getAuthEmailForUserId, getRequiredAuthProviderForEmail, } from "../org/auth-policy.js";
|
|
31
32
|
import { autoJoinDomainMatchingOrgs } from "../org/auto-join-domain.js";
|
|
@@ -1849,6 +1850,48 @@ async function createBetterAuthInstance(config) {
|
|
|
1849
1850
|
if (!email)
|
|
1850
1851
|
return;
|
|
1851
1852
|
await emitSignupEventForCreatedUser(user, context);
|
|
1853
|
+
const actingUserId = context?.context?.session?.user?.id;
|
|
1854
|
+
if (user.id && (!actingUserId || actingUserId === user.id)) {
|
|
1855
|
+
let experimentId;
|
|
1856
|
+
try {
|
|
1857
|
+
const questionIntakeConfig = getAppConfig().agent.questionIntake;
|
|
1858
|
+
experimentId = questionIntakeConfig?.experimentId;
|
|
1859
|
+
// raw-sql-ok: the assignment store binds the authenticated user ID as a query argument.
|
|
1860
|
+
await assignQuestionIntakeExperimentOnSignup(questionIntakeConfig, user.id);
|
|
1861
|
+
}
|
|
1862
|
+
catch (error) {
|
|
1863
|
+
console.error("[auth] question-intake assignment failed after retry", {
|
|
1864
|
+
code: "question_intake_assignment_failed",
|
|
1865
|
+
experimentId,
|
|
1866
|
+
});
|
|
1867
|
+
if (experimentId) {
|
|
1868
|
+
try {
|
|
1869
|
+
track("agent_experiment_assignment_failed", {
|
|
1870
|
+
experiment_id: experimentId,
|
|
1871
|
+
failure_stage: "signup",
|
|
1872
|
+
error_code: "question_intake_assignment_failed",
|
|
1873
|
+
}, { authUserId: user.id, userId: email });
|
|
1874
|
+
}
|
|
1875
|
+
catch {
|
|
1876
|
+
console.error("[auth] failed to track question-intake assignment failure", { code: "question_intake_assignment_tracking_failed" });
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
try {
|
|
1880
|
+
const { captureError } = await import("./capture-error.js");
|
|
1881
|
+
captureError(error, {
|
|
1882
|
+
route: "auth.signup",
|
|
1883
|
+
tags: {
|
|
1884
|
+
failureClass: "question-intake-assignment",
|
|
1885
|
+
errorCode: "question_intake_assignment_failed",
|
|
1886
|
+
},
|
|
1887
|
+
extra: { experimentId },
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
catch {
|
|
1891
|
+
console.error("[auth] failed to report question-intake assignment failure", { code: "question_intake_assignment_reporting_failed" });
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1852
1895
|
if (user.emailVerified !== true)
|
|
1853
1896
|
return;
|
|
1854
1897
|
try {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
export type BuilderBranchReadbackStage = "status_request" | "status_shape" | "status_binding" | "status_currentness" | "post_read_status_request" | "post_read_status_shape" | "post_read_status_binding" | "post_read_status_currentness" | "download_url_request" | "download_url_shape" | "download_url_binding" | "bundle_download" | "bundle_read" | "bundle_digest" | "bundle_parse" | "target_subtree_missing" | "post_read_currentness";
|
|
1
2
|
export declare class BuilderBranchFilesError extends Error {
|
|
2
3
|
readonly code: "invalid_request" | "not_connected" | "forbidden" | "unavailable" | "unverified" | "integrity_failed";
|
|
3
|
-
|
|
4
|
+
readonly readbackStage?: BuilderBranchReadbackStage | undefined;
|
|
5
|
+
constructor(code: "invalid_request" | "not_connected" | "forbidden" | "unavailable" | "unverified" | "integrity_failed", message: string, readbackStage?: BuilderBranchReadbackStage | undefined);
|
|
4
6
|
}
|
|
5
7
|
type BranchRef = {
|
|
6
8
|
projectId: string;
|
|
@@ -10,9 +10,11 @@ const MAX_BUNDLE_BYTES = 32 * 1024 * 1024;
|
|
|
10
10
|
const API_TIMEOUT_MS = 30_000;
|
|
11
11
|
export class BuilderBranchFilesError extends Error {
|
|
12
12
|
code;
|
|
13
|
-
|
|
13
|
+
readbackStage;
|
|
14
|
+
constructor(code, message, readbackStage) {
|
|
14
15
|
super(message);
|
|
15
16
|
this.code = code;
|
|
17
|
+
this.readbackStage = readbackStage;
|
|
16
18
|
this.name = "BuilderBranchFilesError";
|
|
17
19
|
}
|
|
18
20
|
}
|
|
@@ -56,6 +58,20 @@ const downloadSchema = z.object({
|
|
|
56
58
|
partial: z.literal(false),
|
|
57
59
|
expiresAt: z.string(),
|
|
58
60
|
});
|
|
61
|
+
const statusFailureStages = {
|
|
62
|
+
before_read: {
|
|
63
|
+
request: "status_request",
|
|
64
|
+
shape: "status_shape",
|
|
65
|
+
binding: "status_binding",
|
|
66
|
+
currentness: "status_currentness",
|
|
67
|
+
},
|
|
68
|
+
after_read: {
|
|
69
|
+
request: "post_read_status_request",
|
|
70
|
+
shape: "post_read_status_shape",
|
|
71
|
+
binding: "post_read_status_binding",
|
|
72
|
+
currentness: "post_read_status_currentness",
|
|
73
|
+
},
|
|
74
|
+
};
|
|
59
75
|
function backupIdentityFingerprint(backup) {
|
|
60
76
|
return JSON.stringify([
|
|
61
77
|
backup.projectId,
|
|
@@ -67,10 +83,10 @@ function backupIdentityFingerprint(backup) {
|
|
|
67
83
|
backup.size,
|
|
68
84
|
]);
|
|
69
85
|
}
|
|
70
|
-
function unverified() {
|
|
71
|
-
throw new BuilderBranchFilesError("unverified", "Builder has no complete, current, identity-bound branch backup to verify.");
|
|
86
|
+
function unverified(stage) {
|
|
87
|
+
throw new BuilderBranchFilesError("unverified", "Builder has no complete, current, identity-bound branch backup to verify.", stage);
|
|
72
88
|
}
|
|
73
|
-
function primaryCommit(snapshot) {
|
|
89
|
+
function primaryCommit(snapshot, failureStage) {
|
|
74
90
|
if (SHA.test(snapshot))
|
|
75
91
|
return snapshot;
|
|
76
92
|
const pairs = snapshot.split(",");
|
|
@@ -78,25 +94,25 @@ function primaryCommit(snapshot) {
|
|
|
78
94
|
for (const pair of pairs) {
|
|
79
95
|
const separator = pair.indexOf(":");
|
|
80
96
|
if (separator < 1)
|
|
81
|
-
unverified();
|
|
97
|
+
unverified(failureStage);
|
|
82
98
|
let folder;
|
|
83
99
|
try {
|
|
84
100
|
folder = decodeURIComponent(pair.slice(0, separator));
|
|
85
101
|
}
|
|
86
102
|
catch {
|
|
87
|
-
return unverified();
|
|
103
|
+
return unverified(failureStage);
|
|
88
104
|
}
|
|
89
105
|
const commit = pair.slice(separator + 1);
|
|
90
106
|
if (!SHA.test(commit))
|
|
91
|
-
unverified();
|
|
107
|
+
unverified(failureStage);
|
|
92
108
|
if (folder === "code")
|
|
93
109
|
entries.push(commit);
|
|
94
110
|
}
|
|
95
111
|
return entries.length === 1 ? entries[0] : undefined;
|
|
96
112
|
}
|
|
97
|
-
async function readBytes(response, maxBytes) {
|
|
113
|
+
async function readBytes(response, maxBytes, stage) {
|
|
98
114
|
if (!response.body) {
|
|
99
|
-
throw new BuilderBranchFilesError("unavailable", "Builder response has no body.");
|
|
115
|
+
throw new BuilderBranchFilesError("unavailable", "Builder response has no body.", stage);
|
|
100
116
|
}
|
|
101
117
|
const reader = response.body.getReader();
|
|
102
118
|
const chunks = [];
|
|
@@ -108,7 +124,7 @@ async function readBytes(response, maxBytes) {
|
|
|
108
124
|
break;
|
|
109
125
|
size += part.value.byteLength;
|
|
110
126
|
if (size > maxBytes) {
|
|
111
|
-
throw new BuilderBranchFilesError("integrity_failed", "Builder readback exceeds its byte limit.");
|
|
127
|
+
throw new BuilderBranchFilesError("integrity_failed", "Builder readback exceeds its byte limit.", stage);
|
|
112
128
|
}
|
|
113
129
|
chunks.push(part.value);
|
|
114
130
|
}
|
|
@@ -116,7 +132,7 @@ async function readBytes(response, maxBytes) {
|
|
|
116
132
|
catch (error) {
|
|
117
133
|
if (error instanceof BuilderBranchFilesError)
|
|
118
134
|
throw error;
|
|
119
|
-
throw new BuilderBranchFilesError("unavailable", "Builder response could not be read completely.");
|
|
135
|
+
throw new BuilderBranchFilesError("unavailable", "Builder response could not be read completely.", stage);
|
|
120
136
|
}
|
|
121
137
|
finally {
|
|
122
138
|
await reader.cancel().catch(() => { });
|
|
@@ -190,31 +206,54 @@ export async function readBuilderBranchFiles(input) {
|
|
|
190
206
|
throw new BuilderBranchFilesError("unavailable", "Builder returned an invalid readback response.");
|
|
191
207
|
}
|
|
192
208
|
}
|
|
193
|
-
async function readStatus() {
|
|
194
|
-
|
|
209
|
+
async function readStatus(phase) {
|
|
210
|
+
let rawStatus;
|
|
211
|
+
try {
|
|
212
|
+
rawStatus = await request("/projects/branch/status");
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
if (error instanceof BuilderBranchFilesError) {
|
|
216
|
+
throw new BuilderBranchFilesError(error.code, error.message, statusFailureStages[phase].request);
|
|
217
|
+
}
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
const result = statusSchema.safeParse(rawStatus);
|
|
195
221
|
if (!result.success)
|
|
196
|
-
return unverified();
|
|
222
|
+
return unverified(statusFailureStages[phase].shape);
|
|
197
223
|
const { branch, backupStatuses } = result.data;
|
|
198
224
|
const backup = backupStatuses.code.backup;
|
|
199
225
|
if (branch.projectId !== input.projectId ||
|
|
200
226
|
branch.name !== input.branchName ||
|
|
201
227
|
backup.projectId !== input.projectId ||
|
|
202
228
|
backup.branchName !== input.branchName ||
|
|
203
|
-
branch.ownerId !== backup.ownerId
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
229
|
+
branch.ownerId !== backup.ownerId) {
|
|
230
|
+
return unverified(statusFailureStages[phase].binding);
|
|
231
|
+
}
|
|
232
|
+
const branchCommit = primaryCommit(branch.lastCommitHash, statusFailureStages[phase].currentness);
|
|
233
|
+
if (backup.at < branch.lastActivityAt ||
|
|
234
|
+
branchCommit !== backup.lastCommitHash)
|
|
235
|
+
return unverified(statusFailureStages[phase].currentness);
|
|
207
236
|
return backup;
|
|
208
237
|
}
|
|
209
238
|
// Export can fall back to an older backup after a refresh error. Only accept
|
|
210
239
|
// the bytes whose digest and commit still match the branch's recorded state.
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
240
|
+
let rawDownload;
|
|
241
|
+
try {
|
|
242
|
+
rawDownload = await request("/projects/branch/bundle-download-url", {
|
|
243
|
+
projectId: input.projectId,
|
|
244
|
+
branchName: input.branchName,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
if (error instanceof BuilderBranchFilesError) {
|
|
249
|
+
throw new BuilderBranchFilesError(error.code, error.message, "download_url_request");
|
|
250
|
+
}
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
const download = downloadSchema.safeParse(rawDownload);
|
|
215
254
|
if (!download.success)
|
|
216
|
-
return unverified();
|
|
217
|
-
const before = await readStatus();
|
|
255
|
+
return unverified("download_url_shape");
|
|
256
|
+
const before = await readStatus("before_read");
|
|
218
257
|
const beforeFingerprint = backupIdentityFingerprint(before);
|
|
219
258
|
const filePath = before.ownerId +
|
|
220
259
|
"/" +
|
|
@@ -229,7 +268,7 @@ export async function readBuilderBranchFiles(input) {
|
|
|
229
268
|
storagePath = decodeURIComponent(url.pathname);
|
|
230
269
|
}
|
|
231
270
|
catch {
|
|
232
|
-
return unverified();
|
|
271
|
+
return unverified("download_url_binding");
|
|
233
272
|
}
|
|
234
273
|
if (download.data.filePath !== filePath ||
|
|
235
274
|
url.origin !== "https://storage.googleapis.com" ||
|
|
@@ -240,7 +279,7 @@ export async function readBuilderBranchFiles(input) {
|
|
|
240
279
|
!Number.isFinite(expiresAt) ||
|
|
241
280
|
expiresAt <= Date.now() ||
|
|
242
281
|
expiresAt > Date.now() + 25 * 60 * 60 * 1000)
|
|
243
|
-
return unverified();
|
|
282
|
+
return unverified("download_url_binding");
|
|
244
283
|
let response;
|
|
245
284
|
try {
|
|
246
285
|
response = await ssrfSafeFetch(url.toString(), { signal: AbortSignal.timeout(API_TIMEOUT_MS) }, {
|
|
@@ -254,16 +293,16 @@ export async function readBuilderBranchFiles(input) {
|
|
|
254
293
|
});
|
|
255
294
|
}
|
|
256
295
|
catch {
|
|
257
|
-
throw new BuilderBranchFilesError("unavailable", "Builder branch bundle could not be downloaded.");
|
|
296
|
+
throw new BuilderBranchFilesError("unavailable", "Builder branch bundle could not be downloaded.", "bundle_download");
|
|
258
297
|
}
|
|
259
298
|
if (!response.ok) {
|
|
260
299
|
await response.body?.cancel().catch(() => { });
|
|
261
|
-
throw new BuilderBranchFilesError("unavailable", "Builder branch bundle is unavailable.");
|
|
300
|
+
throw new BuilderBranchFilesError("unavailable", "Builder branch bundle is unavailable.", "bundle_download");
|
|
262
301
|
}
|
|
263
|
-
const bundle = await readBytes(response, MAX_BUNDLE_BYTES);
|
|
302
|
+
const bundle = await readBytes(response, MAX_BUNDLE_BYTES, "bundle_read");
|
|
264
303
|
if (bundle.byteLength !== before.size ||
|
|
265
304
|
createHash("md5").update(bundle).digest("base64") !== before.contentMd5) {
|
|
266
|
-
throw new BuilderBranchFilesError("integrity_failed", "Builder branch bundle failed its recorded byte integrity check.");
|
|
305
|
+
throw new BuilderBranchFilesError("integrity_failed", "Builder branch bundle failed its recorded byte integrity check.", "bundle_digest");
|
|
267
306
|
}
|
|
268
307
|
let snapshot;
|
|
269
308
|
try {
|
|
@@ -276,11 +315,13 @@ export async function readBuilderBranchFiles(input) {
|
|
|
276
315
|
catch (error) {
|
|
277
316
|
throw new BuilderBranchFilesError(error instanceof BuilderGitBundleError && error.code === "unavailable"
|
|
278
317
|
? "unavailable"
|
|
279
|
-
: "integrity_failed", "Builder branch files could not be verified from the bundle."
|
|
318
|
+
: "integrity_failed", "Builder branch files could not be verified from the bundle.", error instanceof BuilderGitBundleError && error.code === "target_missing"
|
|
319
|
+
? "target_subtree_missing"
|
|
320
|
+
: "bundle_parse");
|
|
280
321
|
}
|
|
281
|
-
const after = await readStatus();
|
|
322
|
+
const after = await readStatus("after_read");
|
|
282
323
|
if (backupIdentityFingerprint(after) !== beforeFingerprint)
|
|
283
|
-
return unverified();
|
|
324
|
+
return unverified("post_read_currentness");
|
|
284
325
|
return {
|
|
285
326
|
projectId: input.projectId,
|
|
286
327
|
branchName: input.branchName,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type BuilderGitBundleErrorCode = "invalid_request" | "integrity_failed" | "unavailable";
|
|
1
|
+
export type BuilderGitBundleErrorCode = "invalid_request" | "integrity_failed" | "target_missing" | "unavailable";
|
|
2
2
|
export declare class BuilderGitBundleError extends Error {
|
|
3
3
|
readonly code: BuilderGitBundleErrorCode;
|
|
4
4
|
constructor(code: BuilderGitBundleErrorCode);
|
|
@@ -100,6 +100,8 @@ async function findTargetTree(gitApi, root, gitdir, rootTreeOid, appPath, cache)
|
|
|
100
100
|
const current = await readTree(gitApi, root, gitdir, treeOid, MAX_TREE_ENTRIES - traversedEntries, cache);
|
|
101
101
|
traversedEntries += current.entryCount;
|
|
102
102
|
const matching = current.tree.tree.filter((entry) => entry.path === segment);
|
|
103
|
+
if (matching.length === 0)
|
|
104
|
+
fail("target_missing");
|
|
103
105
|
if (matching.length !== 1)
|
|
104
106
|
fail("integrity_failed");
|
|
105
107
|
const entry = matching.at(0);
|
|
@@ -45,6 +45,7 @@ import { getUserSetting, putUserSetting, deleteUserSetting, } from "../settings/
|
|
|
45
45
|
import { ANALYTICS_CLIENT_PLATFORM_PROPERTY } from "../shared/analytics-platform.js";
|
|
46
46
|
import { EMPTY_SPECULATION_RULES, resolveSsrCacheHeaders, } from "../shared/cache-control.js";
|
|
47
47
|
import { EMBED_TARGET_HEADER } from "../shared/embed-auth.js";
|
|
48
|
+
import { FIRST_RUN_ONBOARDING_EXPERIMENT_EVENT_NAMES } from "../shared/first-run-builder-credits-experiment.js";
|
|
48
49
|
import { isGoogleProfileImageUrl } from "../shared/google-profile-image.js";
|
|
49
50
|
import { llmConnectionTrackingProperties } from "../shared/llm-connection.js";
|
|
50
51
|
import { EMBED_TRANSPLANT_HEADER, isMcpEmbedCorsOrigin, MCP_EMBED_CORS_ALLOW_HEADERS, shouldAllowMcpEmbedCredentials, } from "../shared/mcp-embed-headers.js";
|
|
@@ -4313,15 +4314,41 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
4313
4314
|
};
|
|
4314
4315
|
if (orgId)
|
|
4315
4316
|
properties.org_id = orgId;
|
|
4317
|
+
// The browser cannot supply experiment attribution. Restore an
|
|
4318
|
+
// exposed first-run arm from server storage before tracking onboarding
|
|
4319
|
+
// events so the registry can replace, rather than trust, client tags.
|
|
4320
|
+
const isFirstRunOnboardingEvent = FIRST_RUN_ONBOARDING_EXPERIMENT_EVENT_NAMES.has(validation.name) && validation.properties?.flow === "first_run";
|
|
4316
4321
|
// Best-effort — server `track()` swallows provider errors. We still
|
|
4317
4322
|
// guard here so an unexpected throw can't surface to the browser.
|
|
4318
4323
|
try {
|
|
4319
|
-
|
|
4324
|
+
const browserSessionId = readBrowserSessionIdHeader(event);
|
|
4325
|
+
const trackingSource = {
|
|
4320
4326
|
userId: userEmail,
|
|
4321
|
-
authUserId: session.authUserId,
|
|
4322
|
-
sessionId:
|
|
4327
|
+
...(session.authUserId ? { authUserId: session.authUserId } : {}),
|
|
4328
|
+
...(browserSessionId ? { sessionId: browserSessionId } : {}),
|
|
4323
4329
|
telemetryOrigin: "client",
|
|
4324
|
-
}
|
|
4330
|
+
};
|
|
4331
|
+
const trackClientEvent = () => track(validation.name, properties, trackingSource);
|
|
4332
|
+
if (isFirstRunOnboardingEvent) {
|
|
4333
|
+
await runWithRequestContext({
|
|
4334
|
+
userEmail,
|
|
4335
|
+
...(session.authUserId
|
|
4336
|
+
? { authUserId: session.authUserId }
|
|
4337
|
+
: {}),
|
|
4338
|
+
}, async () => {
|
|
4339
|
+
try {
|
|
4340
|
+
const { loadFirstRunBuilderCreditsV2Assignment } = await import("../onboarding/first-run-builder-credits-experiment.js");
|
|
4341
|
+
await loadFirstRunBuilderCreditsV2Assignment();
|
|
4342
|
+
}
|
|
4343
|
+
catch {
|
|
4344
|
+
console.error("[tracking] first-run onboarding assignment lookup failed", { eventName: validation.name });
|
|
4345
|
+
}
|
|
4346
|
+
trackClientEvent();
|
|
4347
|
+
});
|
|
4348
|
+
}
|
|
4349
|
+
else {
|
|
4350
|
+
trackClientEvent();
|
|
4351
|
+
}
|
|
4325
4352
|
}
|
|
4326
4353
|
catch {
|
|
4327
4354
|
// best-effort
|
|
@@ -64,6 +64,14 @@ export interface RequestRunContext {
|
|
|
64
64
|
totalTools: number;
|
|
65
65
|
resultNames: string[];
|
|
66
66
|
}>;
|
|
67
|
+
/** Signup-time question-intake assignment loaded for this authenticated run. */
|
|
68
|
+
questionIntakeExperimentAssignment?: {
|
|
69
|
+
authUserId: string;
|
|
70
|
+
experimentId: string;
|
|
71
|
+
variantId: "control" | "no_questions";
|
|
72
|
+
};
|
|
73
|
+
/** Tools unavailable to the current agent run and its tool-search surface. */
|
|
74
|
+
excludedActionNames?: readonly string[];
|
|
67
75
|
}
|
|
68
76
|
export interface RequestContext {
|
|
69
77
|
/**
|
|
@@ -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
|
-
text?: undefined;
|
|
21
20
|
error: string;
|
|
21
|
+
text?: undefined;
|
|
22
22
|
} | {
|
|
23
23
|
error?: undefined;
|
|
24
24
|
text: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare const FIRST_RUN_BUILDER_CREDITS_V2_EXPERIMENT_ID = "first_run_builder_credits_v2";
|
|
2
2
|
export declare const FIRST_RUN_BUILDER_CREDITS_V2_FLAG_KEY = "clips-first-run-credits-v2";
|
|
3
3
|
export declare const FIRST_RUN_BUILDER_CREDITS_V2_VARIANTS: readonly ["control", "credits"];
|
|
4
|
+
export declare const FIRST_RUN_ONBOARDING_EXPERIMENT_EVENT_NAMES: Set<string>;
|
|
4
5
|
export type FirstRunBuilderCreditsV2Variant = (typeof FIRST_RUN_BUILDER_CREDITS_V2_VARIANTS)[number];
|
|
5
6
|
export interface FirstRunBuilderCreditsV2Assignment {
|
|
6
7
|
experiment_id: typeof FIRST_RUN_BUILDER_CREDITS_V2_EXPERIMENT_ID;
|
|
@@ -4,6 +4,25 @@ export const FIRST_RUN_BUILDER_CREDITS_V2_VARIANTS = [
|
|
|
4
4
|
"control",
|
|
5
5
|
"credits",
|
|
6
6
|
];
|
|
7
|
+
export const FIRST_RUN_ONBOARDING_EXPERIMENT_EVENT_NAMES = new Set([
|
|
8
|
+
"onboarding_app_entered",
|
|
9
|
+
"onboarding_started",
|
|
10
|
+
"onboarding_experiment_exposed",
|
|
11
|
+
"onboarding_step_viewed",
|
|
12
|
+
"onboarding_step_completed",
|
|
13
|
+
"onboarding_step_skipped",
|
|
14
|
+
"onboarding_method_clicked",
|
|
15
|
+
"onboarding_method_started",
|
|
16
|
+
"onboarding_method_outcome",
|
|
17
|
+
"onboarding_abandoned",
|
|
18
|
+
"onboarding_correlation_unavailable",
|
|
19
|
+
"onboarding_role_save_started",
|
|
20
|
+
"onboarding_more_info_opened",
|
|
21
|
+
"onboarding_role_option_selected",
|
|
22
|
+
"onboarding_dismissed",
|
|
23
|
+
"onboarding_reopened",
|
|
24
|
+
"onboarding_completed",
|
|
25
|
+
]);
|
|
7
26
|
/** A stable, account-scoped arm that is the same in the browser and on server events. */
|
|
8
27
|
export function assignFirstRunBuilderCreditsV2(authUserId) {
|
|
9
28
|
const userId = authUserId?.trim();
|
package/dist/tracking/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { track, identify, flushTracking, registerTrackingProvider, unregisterTrackingProvider, listTrackingProviders, type TrackingMeta, type TrackingSource, } from "./registry.js";
|
|
1
|
+
export { track, trackQuestionIntakeExposure, identify, flushTracking, registerTrackingProvider, unregisterTrackingProvider, listTrackingProviders, type TrackingMeta, type TrackingSource, } from "./registry.js";
|
|
2
2
|
export { registerBuiltinProviders } from "./providers.js";
|
|
3
3
|
export { readAnalyticsAnonymousId } from "../server/attribution.js";
|
|
4
4
|
export { AGENT_NATIVE_ACTION_EVENTS, AGENT_NATIVE_LIFECYCLE_EVENTS, trackActionEvent, trackLifecycleEvent, type AgentNativeActionEventName, type AgentNativeLifecycleEventName, } from "./lifecycle.js";
|
package/dist/tracking/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { track, identify, flushTracking, registerTrackingProvider, unregisterTrackingProvider, listTrackingProviders, } from "./registry.js";
|
|
1
|
+
export { track, trackQuestionIntakeExposure, identify, flushTracking, registerTrackingProvider, unregisterTrackingProvider, listTrackingProviders, } from "./registry.js";
|
|
2
2
|
export { registerBuiltinProviders } from "./providers.js";
|
|
3
3
|
export { readAnalyticsAnonymousId } from "../server/attribution.js";
|
|
4
4
|
export { AGENT_NATIVE_ACTION_EVENTS, AGENT_NATIVE_LIFECYCLE_EVENTS, trackActionEvent, trackLifecycleEvent, } from "./lifecycle.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ActionRunContext } from "../action.js";
|
|
2
|
+
import { type QuestionIntakeExperimentAssignment } from "../observability/question-intake-experiment.js";
|
|
2
3
|
import { type TrackingEventOrigin } from "../observability/tracing.js";
|
|
3
4
|
import type { TrackingProvider } from "./types.js";
|
|
4
5
|
export { isQaTestEmail } from "../shared/qa-test-email.js";
|
|
@@ -17,5 +18,11 @@ export interface TrackingMeta {
|
|
|
17
18
|
}
|
|
18
19
|
export type TrackingSource = TrackingMeta | (ActionRunContext & Pick<TrackingMeta, "sessionId">);
|
|
19
20
|
export declare function track(name: string, properties?: Record<string, unknown>, source?: TrackingSource): void;
|
|
21
|
+
export declare function trackQuestionIntakeExposure(assignment: QuestionIntakeExperimentAssignment, context: {
|
|
22
|
+
appId: string;
|
|
23
|
+
threadId?: string;
|
|
24
|
+
turnId: string;
|
|
25
|
+
userId?: string;
|
|
26
|
+
}): void;
|
|
20
27
|
export declare function identify(userId: string, traits?: Record<string, unknown>): void;
|
|
21
28
|
export declare function flushTracking(): Promise<void[]>;
|