@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.
Files changed (38) hide show
  1. package/dist/agent/production-agent.d.ts +2 -0
  2. package/dist/agent/production-agent.js +86 -6
  3. package/dist/agent/run-store.js +3 -2
  4. package/dist/app-config/agent.d.ts +9 -0
  5. package/dist/app-config/agent.js +31 -0
  6. package/dist/app-config/schema.d.ts +9 -0
  7. package/dist/app-config/store.js +48 -5
  8. package/dist/collab/routes.d.ts +1 -1
  9. package/dist/collab/struct-routes.d.ts +1 -1
  10. package/dist/file-upload/actions/upload-image.d.ts +4 -4
  11. package/dist/jobs/run-history.js +3 -2
  12. package/dist/observability/hosted-model-experiment.d.ts +1 -1
  13. package/dist/observability/hosted-model-experiment.js +7 -1
  14. package/dist/observability/question-intake-experiment.d.ts +34 -0
  15. package/dist/observability/question-intake-experiment.js +114 -0
  16. package/dist/observability/traces.d.ts +4 -0
  17. package/dist/observability/traces.js +5 -4
  18. package/dist/observability/tracing.d.ts +1 -0
  19. package/dist/observability/tracing.js +14 -4
  20. package/dist/org/index.d.ts +1 -1
  21. package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
  22. package/dist/provider-api/actions/provider-api.d.ts +7 -7
  23. package/dist/server/agent-engine-api-key-route.d.ts +3 -3
  24. package/dist/server/better-auth-instance.js +43 -0
  25. package/dist/server/builder-branch-files.d.ts +3 -1
  26. package/dist/server/builder-branch-files.js +74 -33
  27. package/dist/server/builder-branch-git.d.ts +1 -1
  28. package/dist/server/builder-branch-git.js +2 -0
  29. package/dist/server/core-routes-plugin.js +31 -4
  30. package/dist/server/request-context.d.ts +8 -0
  31. package/dist/server/transcribe-voice.d.ts +1 -1
  32. package/dist/shared/first-run-builder-credits-experiment.d.ts +1 -0
  33. package/dist/shared/first-run-builder-credits-experiment.js +19 -0
  34. package/dist/tracking/index.d.ts +1 -1
  35. package/dist/tracking/index.js +1 -1
  36. package/dist/tracking/registry.d.ts +7 -0
  37. package/dist/tracking/registry.js +84 -4
  38. 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
- const pendingEvents = scope?.pending ?? pendingTrackingEvents;
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
- await Promise.allSettled([...(scope?.pending ?? pendingTrackingEvents)]);
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();
@@ -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
- provider?: undefined;
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
- provider?: undefined;
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
- provider?: undefined;
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
- provider?: undefined;
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
- provider?: undefined;
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
- provider?: undefined;
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
- constructor(code: "invalid_request" | "not_connected" | "forbidden" | "unavailable" | "unverified" | "integrity_failed", message: string);
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
- constructor(code, message) {
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
- const result = statusSchema.safeParse(await request("/projects/branch/status"));
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
- backup.at < branch.lastActivityAt ||
205
- primaryCommit(branch.lastCommitHash) !== backup.lastCommitHash)
206
- return unverified();
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
- const download = downloadSchema.safeParse(await request("/projects/branch/bundle-download-url", {
212
- projectId: input.projectId,
213
- branchName: input.branchName,
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
- track(validation.name, properties, {
4324
+ const browserSessionId = readBrowserSessionIdHeader(event);
4325
+ const trackingSource = {
4320
4326
  userId: userEmail,
4321
- authUserId: session.authUserId,
4322
- sessionId: readBrowserSessionIdHeader(event),
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();
@@ -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";
@@ -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[]>;