@agent-native/core 0.201.0-nightly-20261005153421 → 0.201.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.
Files changed (118) hide show
  1. package/README.md +22 -22
  2. package/dist/a2a/server.d.ts +2 -2
  3. package/dist/a2a/server.js +13 -2
  4. package/dist/agent/thread-data-builder.d.ts +4 -0
  5. package/dist/agent/thread-data-builder.js +8 -0
  6. package/dist/agent-sql/analysis.d.ts +31 -0
  7. package/dist/agent-sql/analysis.js +169 -0
  8. package/dist/agent-sql/index.d.ts +4 -0
  9. package/dist/agent-sql/index.js +4 -0
  10. package/dist/agent-sql/lexer.d.ts +37 -0
  11. package/dist/agent-sql/lexer.js +357 -0
  12. package/dist/agent-sql/postgres.d.ts +51 -0
  13. package/dist/agent-sql/postgres.js +197 -0
  14. package/dist/agent-sql/query.d.ts +30 -0
  15. package/dist/agent-sql/query.js +514 -0
  16. package/dist/catalog.json +24 -24
  17. package/dist/client/analytics/index.d.ts +1 -1
  18. package/dist/client/analytics/index.js +1 -1
  19. package/dist/client/analytics.d.ts +8 -0
  20. package/dist/client/analytics.js +17 -2
  21. package/dist/client/session-replay.js +3 -2
  22. package/dist/identity/offboard.js +31 -3
  23. package/dist/identity/rekey.d.ts +12 -1
  24. package/dist/identity/rekey.js +77 -6
  25. package/dist/identity/retired-emails.d.ts +23 -0
  26. package/dist/identity/retired-emails.js +55 -0
  27. package/dist/mcp/actions/create-org-service-token.js +20 -8
  28. package/dist/mcp/actions/service-token-access.d.ts +1 -0
  29. package/dist/mcp/actions/service-token-access.js +41 -24
  30. package/dist/mcp/build-server.d.ts +20 -6
  31. package/dist/mcp/build-server.js +131 -35
  32. package/dist/mcp/connect-route.d.ts +4 -3
  33. package/dist/mcp/connect-route.js +140 -82
  34. package/dist/mcp/connect-store.d.ts +17 -11
  35. package/dist/mcp/connect-store.js +88 -60
  36. package/dist/mcp/credential-issuance.d.ts +23 -0
  37. package/dist/mcp/credential-issuance.js +77 -0
  38. package/dist/mcp/credential-membership.d.ts +26 -0
  39. package/dist/mcp/credential-membership.js +67 -0
  40. package/dist/mcp/oauth-route.js +133 -45
  41. package/dist/mcp/oauth-store.d.ts +15 -5
  42. package/dist/mcp/oauth-store.js +119 -34
  43. package/dist/mcp/oauth-token.d.ts +4 -0
  44. package/dist/mcp/oauth-token.js +5 -0
  45. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  46. package/dist/mcp/server.js +14 -0
  47. package/dist/onboarding/default-steps.js +1 -1
  48. package/dist/org/handlers.d.ts +1 -1
  49. package/dist/org/index.d.ts +2 -2
  50. package/dist/org/membership.d.ts +3 -1
  51. package/dist/org/membership.js +5 -2
  52. package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
  53. package/dist/provider-api/actions/provider-api.d.ts +10 -10
  54. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  55. package/dist/resource-changes/store.d.ts +2 -0
  56. package/dist/resource-changes/store.js +4 -0
  57. package/dist/resources/handlers.d.ts +1 -1
  58. package/dist/scripts/db/exec.js +28 -101
  59. package/dist/scripts/db/patch.js +14 -34
  60. package/dist/scripts/db/postgres-client.d.ts +8 -1
  61. package/dist/scripts/db/postgres-client.js +20 -6
  62. package/dist/scripts/db/query.js +31 -33
  63. package/dist/scripts/db/safety.d.ts +47 -2
  64. package/dist/scripts/db/safety.js +383 -137
  65. package/dist/scripts/db/scoping.d.ts +1 -0
  66. package/dist/scripts/db/scoping.js +3 -2
  67. package/dist/search/index-store.d.ts +21 -0
  68. package/dist/search/index-store.js +47 -0
  69. package/dist/search/indexer.js +35 -32
  70. package/dist/search/registry.d.ts +6 -3
  71. package/dist/search/registry.js +30 -8
  72. package/dist/secrets/routes.d.ts +6 -6
  73. package/dist/server/agent-chat-plugin.js +18 -6
  74. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  75. package/dist/server/agent-engine-default-model-route.d.ts +1 -1
  76. package/dist/server/agent-run-context.js +9 -0
  77. package/dist/server/attribution.d.ts +2 -0
  78. package/dist/server/attribution.js +7 -1
  79. package/dist/server/auth.js +9 -0
  80. package/dist/server/credential-membership-unavailable.d.ts +13 -0
  81. package/dist/server/credential-membership-unavailable.js +22 -0
  82. package/dist/server/index.d.ts +1 -0
  83. package/dist/server/index.js +1 -0
  84. package/dist/server/onboarding-html.js +1 -1
  85. package/dist/server/realtime-token.d.ts +1 -1
  86. package/dist/server/recap-image-route.js +4 -0
  87. package/dist/server/release-schema.js +4 -0
  88. package/dist/shared/docs-url.d.ts +3 -1
  89. package/dist/shared/docs-url.js +6 -3
  90. package/dist/shared/environment-lanes.d.ts +8 -0
  91. package/dist/shared/environment-lanes.js +26 -0
  92. package/dist/templates/chat/.agents/skills/security/SKILL.md +9 -2
  93. package/dist/templates/chat/.agents/skills/storing-data/SKILL.md +12 -7
  94. package/dist/templates/chat/README.md +2 -2
  95. package/dist/templates/default/.agents/skills/security/SKILL.md +9 -2
  96. package/dist/templates/default/.agents/skills/storing-data/SKILL.md +12 -7
  97. package/dist/templates/default/app/routes/home.tsx +5 -1
  98. package/dist/templates/headless/.agents/skills/security/SKILL.md +9 -2
  99. package/dist/templates/headless/.agents/skills/storing-data/SKILL.md +12 -7
  100. package/dist/templates/workspace-core/.agents/skills/security/SKILL.md +9 -2
  101. package/dist/templates/workspace-core/.agents/skills/storing-data/SKILL.md +12 -7
  102. package/dist/tracking/providers.js +13 -1
  103. package/docs/content/authentication.mdx +5 -1
  104. package/docs/content/durable-background-runs.mdx +17 -8
  105. package/docs/content/external-agents.mdx +3 -1
  106. package/docs/content/mcp-protocol.mdx +6 -2
  107. package/docs/content/security.mdx +1 -1
  108. package/package.json +5 -4
  109. package/src/templates/chat/.agents/skills/security/SKILL.md +9 -2
  110. package/src/templates/chat/.agents/skills/storing-data/SKILL.md +12 -7
  111. package/src/templates/chat/README.md +2 -2
  112. package/src/templates/default/.agents/skills/security/SKILL.md +9 -2
  113. package/src/templates/default/.agents/skills/storing-data/SKILL.md +12 -7
  114. package/src/templates/default/app/routes/home.tsx +5 -1
  115. package/src/templates/headless/.agents/skills/security/SKILL.md +9 -2
  116. package/src/templates/headless/.agents/skills/storing-data/SKILL.md +12 -7
  117. package/src/templates/workspace-core/.agents/skills/security/SKILL.md +9 -2
  118. package/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +12 -7
@@ -42,6 +42,53 @@ export function ensureSearchIndexTables(injectedClient) {
42
42
  });
43
43
  return ensured;
44
44
  }
45
+ /**
46
+ * Raises an index's target version, leaving the rebuild for the first drain
47
+ * at that version to claim. A process on a lower version reads the index as
48
+ * outdated from then on and serves its previous search. Returns the target
49
+ * now in force, which is higher than `version` when a newer build raised it.
50
+ */
51
+ export async function raiseSearchIndexTarget(exec, target) {
52
+ await exec.execute({
53
+ sql: `INSERT INTO ${SEARCH_INDEX_STATE_TABLE} (app, resource_type, target_version)
54
+ VALUES (?, ?, ?)
55
+ ON CONFLICT (app, resource_type) DO UPDATE SET
56
+ target_version = EXCLUDED.target_version,
57
+ rebuild_high_seq = NULL,
58
+ rebuild_started_at = NULL,
59
+ rebuild_completed_at = NULL
60
+ WHERE ${SEARCH_INDEX_STATE_TABLE}.target_version < EXCLUDED.target_version`,
61
+ args: [target.app, target.type, target.version],
62
+ });
63
+ const { rows } = await exec.execute({
64
+ sql: `SELECT target_version FROM ${SEARCH_INDEX_STATE_TABLE} WHERE app = ? AND resource_type = ?`,
65
+ args: [target.app, target.type],
66
+ });
67
+ const [row] = rows;
68
+ if (!row)
69
+ throw new Error("Raising the search index target left no row.");
70
+ return Number(row.target_version);
71
+ }
72
+ /**
73
+ * Marks the index as needing a rebuild at its current version, including a
74
+ * rebuild still queueing rows: its queue may predate the writes this
75
+ * invalidation is for. Nothing to do before the search tables exist: there
76
+ * is no index yet.
77
+ */
78
+ export async function invalidateSearchIndex(exec, target) {
79
+ const { rows } = await exec.execute({
80
+ sql: `SELECT 1 AS present WHERE to_regclass(?) IS NOT NULL`,
81
+ args: [SEARCH_INDEX_STATE_TABLE],
82
+ });
83
+ if (!rows.length)
84
+ return;
85
+ await exec.execute({
86
+ sql: `UPDATE ${SEARCH_INDEX_STATE_TABLE}
87
+ SET rebuild_high_seq = NULL, rebuild_started_at = NULL, rebuild_completed_at = NULL
88
+ WHERE app = ? AND resource_type = ?`,
89
+ args: [target.app, target.type],
90
+ });
91
+ }
45
92
  async function ensureAll(injectedClient) {
46
93
  const options = { injectedClient };
47
94
  await ensureTableExists(SEARCH_RESOURCES_TABLE, SEARCH_RESOURCES_CREATE_SQL, options);
@@ -13,7 +13,7 @@ import { getAppConfig } from "../app-config/index.js";
13
13
  import { getDbExec } from "../db/client.js";
14
14
  import { RESOURCE_CHANGE_MAX_ATTEMPTS, claimResourceChanges, completeResourceChanges, enqueueAllResourceChanges, failResourceChanges, hasPendingResourceChanges, resourceChangeBacklogColumns, resourceChangeCaptureInstalled, subscribeResourceChangeConsumer, } from "../resource-changes/store.js";
15
15
  import { getRequestRunContext } from "../server/request-context.js";
16
- import { SEARCH_INDEX_STATE_TABLE, SEARCH_RESOURCES_TABLE, } from "./index-store.js";
16
+ import { SEARCH_INDEX_STATE_TABLE, SEARCH_RESOURCES_TABLE, invalidateSearchIndex, } from "./index-store.js";
17
17
  import { SEARCH_CHANGE_CONSUMER, listSearchableResources, searchableResourceSource, } from "./registry.js";
18
18
  import { buildSearchVector, normalizeSearchText, utf8ByteLength, } from "./tokenize.js";
19
19
  const CLAIM_BATCH = 50;
@@ -247,7 +247,7 @@ async function captureInstalled(exec, registration, runtime) {
247
247
  const source = searchableResourceSource(registration);
248
248
  const installed = await resourceChangeCaptureInstalled(exec, source);
249
249
  if (!installed) {
250
- await invalidateIndex(exec, registration);
250
+ await invalidateSearchIndex(exec, registration);
251
251
  if (runtime.captureInstalled !== false) {
252
252
  console.error(`[search] Change capture for ${registration.app}/${registration.type} is missing or disabled on table "${source.table}". ` +
253
253
  "Add searchIndexMigration() for it to the app's runMigrations list, or re-enable its triggers. " +
@@ -261,27 +261,6 @@ async function captureInstalled(exec, registration, runtime) {
261
261
  runtime.captureVerifiedAt = Date.now();
262
262
  return installed;
263
263
  }
264
- /**
265
- * Marks the index as needing a rebuild at its current version. Nothing to do
266
- * before the search tables exist: there is no index yet.
267
- */
268
- async function invalidateIndex(exec, registration) {
269
- const { rows } = await exec.execute({
270
- sql: `SELECT to_regclass(?) IS NOT NULL AS present`,
271
- args: [SEARCH_INDEX_STATE_TABLE],
272
- });
273
- const [table] = rows;
274
- if (!table)
275
- throw new Error("Looking up the search index table returned no row.");
276
- if (!flag(table.present))
277
- return;
278
- await exec.execute({
279
- sql: `UPDATE ${SEARCH_INDEX_STATE_TABLE}
280
- SET rebuild_high_seq = NULL, rebuild_started_at = NULL, rebuild_completed_at = NULL
281
- WHERE app = ? AND resource_type = ? AND rebuild_high_seq IS NOT NULL`,
282
- args: [registration.app, registration.type],
283
- });
284
- }
285
264
  function flag(value) {
286
265
  return value === true || value === "t" || value === "true";
287
266
  }
@@ -332,7 +311,15 @@ function rebuildNeedsQueueing(registration, state) {
332
311
  * one, then queues every source row once. Only one process wins either, and
333
312
  * from a version bump on, the fence stops older processes. Queueing replaces
334
313
  * changes they already hold, so none of their in-flight work can complete
335
- * what the rebuild queued.
314
+ * what the rebuild queued. The queue counts toward completion only if no
315
+ * other transaction has written the index state since the claim: an
316
+ * invalidation or a later claim meanwhile means it may miss writes.
317
+ *
318
+ * The claim is the row's `xmin`, the transaction that last wrote it, not its
319
+ * timestamp: `now()` is when a transaction began, so a later claim can carry
320
+ * the same one. Writes in the claim's own transaction keep its `xmin`, so
321
+ * nothing may invalidate between this claim and its publish on a shared
322
+ * transaction.
336
323
  */
337
324
  async function startRebuild(exec, registration, state) {
338
325
  const { rows } = !state || state.targetVersion < registration.version
@@ -345,7 +332,7 @@ async function startRebuild(exec, registration, state) {
345
332
  rebuild_high_seq = NULL,
346
333
  rebuild_completed_at = NULL
347
334
  WHERE ${SEARCH_INDEX_STATE_TABLE}.target_version < EXCLUDED.target_version
348
- RETURNING target_version`,
335
+ RETURNING xmin::text AS claim`,
349
336
  args: [registration.app, registration.type, registration.version],
350
337
  })
351
338
  : await exec.execute({
@@ -353,7 +340,7 @@ async function startRebuild(exec, registration, state) {
353
340
  WHERE app = ? AND resource_type = ? AND target_version = ?
354
341
  AND rebuild_high_seq IS NULL AND rebuild_completed_at IS NULL
355
342
  AND (rebuild_started_at IS NULL OR rebuild_started_at < now() - make_interval(secs => ?))
356
- RETURNING target_version`,
343
+ RETURNING xmin::text AS claim`,
357
344
  args: [
358
345
  registration.app,
359
346
  registration.type,
@@ -361,18 +348,28 @@ async function startRebuild(exec, registration, state) {
361
348
  REBUILD_ENQUEUE_RETRY_MS / 1000,
362
349
  ],
363
350
  });
364
- if (!rows.length)
351
+ const [claimed] = rows;
352
+ if (!claimed)
365
353
  return;
366
354
  const highSeq = await enqueueAllResourceChanges(exec, searchableResourceSource(registration), SEARCH_CHANGE_CONSUMER, "rebuild");
367
355
  await exec.execute({
368
356
  sql: `UPDATE ${SEARCH_INDEX_STATE_TABLE} SET rebuild_high_seq = ?::bigint
369
- WHERE app = ? AND resource_type = ? AND target_version = ?`,
370
- args: [highSeq, registration.app, registration.type, registration.version],
357
+ WHERE app = ? AND resource_type = ? AND target_version = ?
358
+ AND xmin = ?::xid`,
359
+ args: [
360
+ highSeq,
361
+ registration.app,
362
+ registration.type,
363
+ registration.version,
364
+ String(claimed.claim),
365
+ ],
371
366
  });
372
367
  }
373
368
  /**
374
- * Marks the rebuild complete once every change it queued is processed.
375
- * Returns whether the state may have changed.
369
+ * Marks the rebuild complete once every change it queued is processed, if
370
+ * it's still the rebuild that was checked: an invalidation meanwhile clears
371
+ * its highest `seq`, which leaves the rows to be queued again. Returns
372
+ * whether the state may have changed.
376
373
  */
377
374
  async function completeRebuildIfDone(exec, registration, state) {
378
375
  if (state.rebuildHighSeq === null)
@@ -386,8 +383,14 @@ async function completeRebuildIfDone(exec, registration, state) {
386
383
  sql: `UPDATE ${SEARCH_INDEX_STATE_TABLE}
387
384
  SET index_version = target_version, rebuild_completed_at = now()
388
385
  WHERE app = ? AND resource_type = ? AND target_version = ? AND rebuild_completed_at IS NULL
386
+ AND rebuild_high_seq = ?::bigint
389
387
  RETURNING target_version`,
390
- args: [registration.app, registration.type, state.targetVersion],
388
+ args: [
389
+ registration.app,
390
+ registration.type,
391
+ state.targetVersion,
392
+ state.rebuildHighSeq,
393
+ ],
391
394
  });
392
395
  if (rows.length) {
393
396
  // Rows whose source row is gone are deletes the feed missed: the source
@@ -45,9 +45,12 @@ export declare function listSearchableResources(): SearchableResourceRegistratio
45
45
  export declare function unregisterSearchableResource(app: string, type: string): void;
46
46
  export declare function searchableResourceSource(registration: SearchableResourceRegistration): ResourceChangeSource;
47
47
  /**
48
- * A named migration that creates the search tables and installs change
49
- * capture on the registration's table. Add it to the app's `runMigrations`
50
- * list with the app's next version number.
48
+ * A named migration that creates the search tables, raises the index's
49
+ * target to the registration's version, and installs change capture on the
50
+ * registration's table. Add it to the app's `runMigrations`
51
+ * list with the app's next version number. It's recorded under `name` plus a
52
+ * fingerprint of the capture SQL, so a release that changes that SQL installs
53
+ * it again.
51
54
  */
52
55
  export declare function searchIndexMigration(registration: SearchableResourceRegistration, entry: {
53
56
  version: number;
@@ -1,8 +1,8 @@
1
1
  import { getTableName } from "drizzle-orm";
2
2
  import { deferMigration } from "../db/migrations.js";
3
3
  import { registerRecurringSweepHandler } from "../jobs/sweep-hooks.js";
4
- import { assertResourceKey, installResourceChangeCapture, registerAfterWriteDrain, resourceChangeTriggerNames, } from "../resource-changes/store.js";
5
- import { ensureSearchIndexTables } from "./index-store.js";
4
+ import { assertResourceKey, installResourceChangeCapture, registerAfterWriteDrain, resourceChangeCaptureFingerprint, resourceChangeCaptureInstalled, resourceChangeTriggerNames, } from "../resource-changes/store.js";
5
+ import { ensureSearchIndexTables, invalidateSearchIndex, raiseSearchIndexTarget, } from "./index-store.js";
6
6
  /** The change-feed consumer name the search index subscribes as. */
7
7
  export const SEARCH_CHANGE_CONSUMER = "search";
8
8
  const registrations = new Map();
@@ -73,21 +73,43 @@ export function searchableResourceSource(registration) {
73
73
  };
74
74
  }
75
75
  /**
76
- * A named migration that creates the search tables and installs change
77
- * capture on the registration's table. Add it to the app's `runMigrations`
78
- * list with the app's next version number.
76
+ * A named migration that creates the search tables, raises the index's
77
+ * target to the registration's version, and installs change capture on the
78
+ * registration's table. Add it to the app's `runMigrations`
79
+ * list with the app's next version number. It's recorded under `name` plus a
80
+ * fingerprint of the capture SQL, so a release that changes that SQL installs
81
+ * it again.
79
82
  */
80
83
  export function searchIndexMigration(registration, entry) {
84
+ const source = searchableResourceSource(registration);
81
85
  return {
82
86
  version: entry.version,
83
- name: entry.name,
87
+ // Under the bare name, a database that recorded an earlier build's
88
+ // capture would never get this one, and search would stay on the
89
+ // fallback for good.
90
+ name: `${entry.name}@${resourceChangeCaptureFingerprint(source)}`,
84
91
  sql: {},
85
92
  run: async (exec) => {
86
93
  await ensureSearchIndexTables(exec);
87
- const installed = await installResourceChangeCapture(exec, searchableResourceSource(registration), SEARCH_CHANGE_CONSUMER);
88
- // A busy table: try again on the next boot rather than block writes.
94
+ // Before capture exists: a build still running a lower version would
95
+ // otherwise see this capture, finish a rebuild at its own version, and
96
+ // answer from rows it didn't write.
97
+ const target = await raiseSearchIndexTarget(exec, registration);
98
+ // A newer build owns the index and its capture, which this build's
99
+ // older SQL would replace.
100
+ if (target > registration.version)
101
+ return;
102
+ const hadCapture = await resourceChangeCaptureInstalled(exec, source);
103
+ const installed = await installResourceChangeCapture(exec, source, SEARCH_CHANGE_CONSUMER);
104
+ // A busy table: try again at the next migration run, which serverless
105
+ // hosts reach only at their next release, rather than block writes.
89
106
  if (!installed)
90
107
  return deferMigration();
108
+ // Writes made while capture was missing were never recorded. A search
109
+ // that saw it missing has already discarded the finished index, but
110
+ // nothing guarantees one ran.
111
+ if (!hadCapture)
112
+ await invalidateSearchIndex(exec, registration);
91
113
  },
92
114
  };
93
115
  }
@@ -73,32 +73,32 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
73
73
  ok?: undefined;
74
74
  status?: undefined;
75
75
  } | {
76
+ error?: undefined;
76
77
  errorCode?: undefined;
77
78
  retryable?: undefined;
78
79
  ok: boolean;
79
80
  status: string;
80
- error?: undefined;
81
81
  } | {
82
82
  ok?: undefined;
83
83
  error: string;
84
84
  removed?: undefined;
85
85
  } | {
86
+ error?: undefined;
86
87
  ok: boolean;
87
88
  removed: boolean;
88
- error?: undefined;
89
89
  }>>;
90
90
  export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
91
91
  ok?: undefined;
92
92
  error: string;
93
93
  note?: undefined;
94
94
  } | {
95
+ error?: undefined;
95
96
  ok: boolean;
96
97
  note?: undefined;
97
- error?: undefined;
98
98
  } | {
99
+ error?: undefined;
99
100
  ok: boolean;
100
101
  note: string;
101
- error?: undefined;
102
102
  } | {
103
103
  note?: undefined;
104
104
  ok: boolean;
@@ -126,11 +126,11 @@ export declare function createAdHocSecretHandler(): import("h3").EventHandlerWit
126
126
  } | {
127
127
  error: string;
128
128
  } | {
129
+ error?: undefined;
129
130
  ok: boolean;
130
131
  key: string;
131
- error?: undefined;
132
132
  } | {
133
+ error?: undefined;
133
134
  ok: boolean;
134
135
  removed: boolean;
135
- error?: undefined;
136
136
  }>>;
@@ -49,6 +49,7 @@ import { normalizeDatabaseToolsMode } from "../scripts/db/tool-mode.js";
49
49
  import { getSetting, putSetting } from "../settings/store.js";
50
50
  import { retryContextFromRequest, } from "../shared/agent-chat-run-not-started.js";
51
51
  import { ANALYTICS_CLIENT_PLATFORM_BODY_FIELD, normalizeAnalyticsClientPlatform, } from "../shared/analytics-platform.js";
52
+ import { backgroundAgentTurnIdForReceipt } from "../shared/background-agent-session.js";
52
53
  import { docsUrl } from "../shared/docs-url.js";
53
54
  import { stripSqlParams } from "../shared/error-noise.js";
54
55
  import { track } from "../tracking/registry.js";
@@ -2901,6 +2902,22 @@ export function createAgentChatPlugin(options) {
2901
2902
  ? { refusedRetry: details.retryContext ?? {} }
2902
2903
  : {}),
2903
2904
  });
2905
+ // Background agent sessions send their operation id as
2906
+ // queuedMessageId (Content binds comment AI turns to it). It never
2907
+ // enters the queue, so it has no promotion claim; the session's
2908
+ // derived turn id is what marks the request as one.
2909
+ const queuedMessage = !details.queuedMessageId
2910
+ ? undefined
2911
+ : details.turnId ===
2912
+ backgroundAgentTurnIdForReceipt(threadId, details.queuedMessageId)
2913
+ ? {
2914
+ kind: "background-operation",
2915
+ id: details.queuedMessageId,
2916
+ }
2917
+ : {
2918
+ id: details.queuedMessageId,
2919
+ claimId: details.queuedMessageClaimId,
2920
+ };
2904
2921
  let submissionFailure;
2905
2922
  await updateThreadData(threadId, "{}", "", thread.preview, thread.messageCount, {
2906
2923
  transformThreadData: (threadData) => {
@@ -2917,12 +2934,7 @@ export function createAgentChatPlugin(options) {
2917
2934
  submissionFailure = "invalid_thread_data";
2918
2935
  return threadData;
2919
2936
  }
2920
- const result = applySubmittedUserMessage(repo, userMessage, details.queuedMessageId
2921
- ? {
2922
- id: details.queuedMessageId,
2923
- claimId: details.queuedMessageClaimId,
2924
- }
2925
- : undefined);
2937
+ const result = applySubmittedUserMessage(repo, userMessage, queuedMessage);
2926
2938
  if (!("repo" in result)) {
2927
2939
  submissionFailure = result.status;
2928
2940
  return threadData;
@@ -66,12 +66,12 @@ export declare function createAgentEngineApiKeyHandler(): import("h3").EventHand
66
66
  code: any;
67
67
  } | {
68
68
  errorCode?: undefined;
69
- error?: undefined;
70
69
  code?: undefined;
71
70
  ok: boolean;
72
71
  key: string;
73
72
  baseUrlKey?: string;
74
73
  scope: AgentEngineApiKeyScope;
75
74
  defaultModel?: import("./agent-engine-default-model-route.js").SavedKeyDefaultModelOutcome;
75
+ error?: undefined;
76
76
  }>>;
77
77
  export {};
@@ -8,8 +8,8 @@ export declare const AGENT_ENGINE_API_KEY_ACTION_NAME = "agent-engine-api-key";
8
8
  * resolveEngine's credential detection.
9
9
  */
10
10
  export declare function createAgentEngineDisconnectHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
11
- ok?: undefined;
12
11
  error: string;
12
+ ok?: undefined;
13
13
  scope?: undefined;
14
14
  } | {
15
15
  ok: boolean;
@@ -1,6 +1,7 @@
1
1
  import { createError, getHeader } from "h3";
2
2
  import { ANALYTICS_CLIENT_PLATFORM_BODY_FIELD, ANALYTICS_CLIENT_PLATFORM_HEADER, normalizeAnalyticsClientPlatform, } from "../shared/analytics-platform.js";
3
3
  import { SYNTHETIC_TRAFFIC_HEADER, isSyntheticTrafficValue, } from "../shared/test-traffic.js";
4
+ import { CREDENTIAL_MEMBERSHIP_UNAVAILABLE_MESSAGE, isCredentialMembershipUnavailable, } from "./credential-membership-unavailable.js";
4
5
  import { getRequestContext, getRequestIdentityAuthenticatedAtMs, getRequestIdentitySessionToken, runWithRequestContext, } from "./request-context.js";
5
6
  const resolveOrgIdForEmail = (...args) => import("../org/context.js").then(({ resolveOrgIdForEmail }) => resolveOrgIdForEmail(...args));
6
7
  const getOrgContext = (...args) => import("../org/context.js").then(({ getOrgContext }) => getOrgContext(...args));
@@ -99,6 +100,14 @@ export async function resolveAgentRunOwnerContext(event, options = {}) {
99
100
  return seeded;
100
101
  const { getSession } = await import("./auth.js");
101
102
  const session = await getSession(event);
103
+ if (!session?.email && isCredentialMembershipUnavailable(event)) {
104
+ throw createError({
105
+ statusCode: 503,
106
+ statusMessage: "Service Unavailable",
107
+ message: CREDENTIAL_MEMBERSHIP_UNAVAILABLE_MESSAGE,
108
+ headers: { "Retry-After": "5" },
109
+ });
110
+ }
102
111
  if (session?.email) {
103
112
  const orgScope = getRequestContext()?.orgScope;
104
113
  const identityAuthenticatedAtMs = getRequestIdentityAuthenticatedAtMs(event, session.email);
@@ -31,6 +31,8 @@ export interface FirstTouchAttribution {
31
31
  vector_source?: string;
32
32
  landing_path?: string;
33
33
  landing_referrer?: string;
34
+ site_referrer?: string;
35
+ site_landing_path?: string;
34
36
  landed_at?: string;
35
37
  capture_truncated?: string;
36
38
  }
@@ -15,6 +15,8 @@ const STRING_FIELDS = [
15
15
  "vector_source",
16
16
  "landing_path",
17
17
  "landing_referrer",
18
+ "site_referrer",
19
+ "site_landing_path",
18
20
  "landed_at",
19
21
  "capture_truncated",
20
22
  ];
@@ -111,8 +113,10 @@ export function deriveReferralSource(ft) {
111
113
  path.includes("/share-plan/")) {
112
114
  return "plan_share";
113
115
  }
114
- if (isExternalReferrerHost(ft?.landing_referrer))
116
+ if (isExternalReferrerHost(ft?.landing_referrer) ||
117
+ isExternalReferrerHost(ft?.site_referrer)) {
115
118
  return "external";
119
+ }
116
120
  return "direct";
117
121
  }
118
122
  export function deriveSignupAttribution(ft) {
@@ -139,6 +143,8 @@ export function deriveSignupAttribution(ft) {
139
143
  setIf("vector_source", ft.vector_source);
140
144
  setIf("first_touch_path", ft.landing_path);
141
145
  setIf("landing_referrer", ft.landing_referrer);
146
+ setIf("site_referrer", ft.site_referrer);
147
+ setIf("site_landing_path", ft.site_landing_path);
142
148
  if (ft.capture_truncated === "1")
143
149
  out.attribution_truncated = "true";
144
150
  return out;
@@ -69,6 +69,7 @@ import { createBetterAuthSessionForEmail, ensureGoogleAuthIdentity, getBetterAut
69
69
  import { BUILDER_CONNECT_PARAM, BUILDER_RELAY_PATH, BUILDER_RELAY_STATE_PARAM, verifyBuilderConnectTokenAndGetOwner, verifyBuilderPreviewRelayStateForCallback, } from "./builder-browser.js";
70
70
  import { frameworkSessionHintCookieName, resolveAuthCookieNamespace, } from "./cookie-namespace.js";
71
71
  import { getAllowedCorsOrigin, readCorsAllowedOrigins, } from "./cors-origins.js";
72
+ import { isCredentialMembershipUnavailable, markCredentialMembershipUnavailable, respondCredentialMembershipUnavailable, } from "./credential-membership-unavailable.js";
72
73
  import { resolveDeployEnvironment } from "./deploy-environment.js";
73
74
  import { getSignInBlockingSettingKeys } from "./deploy-settings.js";
74
75
  import { readDesktopSso, writeDesktopSso, clearDesktopSso, } from "./desktop-sso.js";
@@ -777,7 +778,12 @@ export async function getMcpOAuthBearerSession(event) {
777
778
  const result = await verifyAuth(authHeader, undefined, {
778
779
  resourceUrl: getMcpOAuthAudiences(event),
779
780
  allowDevOpen: false,
781
+ requestOrigin: getOrigin(event),
780
782
  });
783
+ if (!result.authed && result.unavailable) {
784
+ markCredentialMembershipUnavailable(event);
785
+ return null;
786
+ }
781
787
  const identity = result.authed ? result.identity : undefined;
782
788
  if (!identity?.userEmail)
783
789
  return null;
@@ -3008,6 +3014,9 @@ function createAuthGuardFn(app) {
3008
3014
  }
3009
3015
  return;
3010
3016
  }
3017
+ if (isCredentialMembershipUnavailable(event)) {
3018
+ return respondCredentialMembershipUnavailable(event);
3019
+ }
3011
3020
  if (p.startsWith("/api/") || p.startsWith("/_agent-native/")) {
3012
3021
  setResponseStatus(event, 401);
3013
3022
  // Dev-only breadcrumb for the loopback origin-label trap: the session
@@ -0,0 +1,13 @@
1
+ import { type H3Event } from "h3";
2
+ export declare const CREDENTIAL_MEMBERSHIP_UNAVAILABLE_MESSAGE = "Organization membership could not be verified. Retry shortly.";
3
+ /**
4
+ * Records that this request's bearer token verified but its organization
5
+ * membership could not be checked. Such a request has no session, but it is
6
+ * not unauthenticated: answer a retryable 503, never a 401 that tells the
7
+ * client to sign in again.
8
+ */
9
+ export declare function markCredentialMembershipUnavailable(event: H3Event): void;
10
+ export declare function isCredentialMembershipUnavailable(event: H3Event): boolean;
11
+ export declare function respondCredentialMembershipUnavailable(event: H3Event): {
12
+ error: string;
13
+ };
@@ -0,0 +1,22 @@
1
+ import { setResponseHeader, setResponseStatus } from "h3";
2
+ const CONTEXT_KEY = "__anCredentialMembershipUnavailable";
3
+ export const CREDENTIAL_MEMBERSHIP_UNAVAILABLE_MESSAGE = "Organization membership could not be verified. Retry shortly.";
4
+ /**
5
+ * Records that this request's bearer token verified but its organization
6
+ * membership could not be checked. Such a request has no session, but it is
7
+ * not unauthenticated: answer a retryable 503, never a 401 that tells the
8
+ * client to sign in again.
9
+ */
10
+ export function markCredentialMembershipUnavailable(event) {
11
+ const holder = event;
12
+ (holder.context ??= {})[CONTEXT_KEY] = true;
13
+ }
14
+ export function isCredentialMembershipUnavailable(event) {
15
+ return (event.context?.[CONTEXT_KEY] ===
16
+ true);
17
+ }
18
+ export function respondCredentialMembershipUnavailable(event) {
19
+ setResponseStatus(event, 503);
20
+ setResponseHeader(event, "Retry-After", "5");
21
+ return { error: CREDENTIAL_MEMBERSHIP_UNAVAILABLE_MESSAGE };
22
+ }
@@ -13,6 +13,7 @@ export { createOpenRouteHandler, type OpenRouteOptions } from "./open-route.js";
13
13
  export { createEmbedStartRouteHandler, buildEmbedStartPath, type EmbedStartRouteOptions, } from "./embed-route.js";
14
14
  export { createEmbedSessionTicket, consumeEmbedSessionTicket, normalizeEmbedTargetPath, requestHasEmbedAuthMarker, resolveEmbedSessionFromRequest, setEmbedSessionCookie, signEmbedSessionToken, verifyEmbedSessionToken, type ConsumedEmbedSessionTicket, type ConsumeEmbedSessionTicketOptions, type EmbedSessionTicket, type EmbedSessionTicketInput, type EmbedSessionTokenClaims, type ResolvedEmbedSession, type VerifyEmbedSessionTokenResult, } from "./embed-session.js";
15
15
  export { createSSEHandler, type SSEHandlerOptions } from "./sse.js";
16
+ export { isCredentialMembershipUnavailable } from "./credential-membership-unavailable.js";
16
17
  export { mountAuthMiddleware, autoMountAuth, registerAuthPublicPaths, getSession, getMcpOAuthBearerSession, logout, COOKIE_NAME, addSession, removeSession, getSessionEmail, getFrameworkSessionCookieValues, setFrameworkSessionCookie, setFirstRunOnboardingCookie, clearFrameworkSessionCookies, runAuthGuard, registerDesktopExchange, prepareDesktopOAuthBrowserBinding, matchesDesktopOAuthBrowserBinding, DESKTOP_OAUTH_BROWSER_BINDING_COOKIE, setDesktopExchange, setDesktopExchangeError, safeReturnPath, type DesktopExchangeErrorPayload, type AuthSession, type AuthOptions, } from "./auth.js";
17
18
  export { handleIdentitySso, ensureIdentityUser, getIdentityHubUrl, isIdentitySsoEnabled, isIdentitySsoBypassPath, identitySsoLoginButtonHtml, IDENTITY_SSO_BOOTSTRAP_ACTIVATE_PATH, IDENTITY_SSO_BOOTSTRAP_BINDING_COOKIE, clearIdentitySsoBootstrapBindingCookie, getIdentitySsoBootstrapBindingCookie, setIdentitySsoBootstrapBindingCookie, IDENTITY_SSO_PROVIDER_ID, IDENTITY_SSO_SCOPE, IDENTITY_SSO_DESKTOP_COMPLETE_PATH, } from "./identity-sso.js";
18
19
  export { createBetterAuthSessionForEmail, ensureGoogleAuthIdentity, hasGoogleAuthIdentity, setBetterAuthSessionCookie, } from "./better-auth-instance.js";
@@ -13,6 +13,7 @@ export { createOpenRouteHandler } from "./open-route.js";
13
13
  export { createEmbedStartRouteHandler, buildEmbedStartPath, } from "./embed-route.js";
14
14
  export { createEmbedSessionTicket, consumeEmbedSessionTicket, normalizeEmbedTargetPath, requestHasEmbedAuthMarker, resolveEmbedSessionFromRequest, setEmbedSessionCookie, signEmbedSessionToken, verifyEmbedSessionToken, } from "./embed-session.js";
15
15
  export { createSSEHandler } from "./sse.js";
16
+ export { isCredentialMembershipUnavailable } from "./credential-membership-unavailable.js";
16
17
  export { mountAuthMiddleware, autoMountAuth, registerAuthPublicPaths, getSession, getMcpOAuthBearerSession, logout, COOKIE_NAME, addSession, removeSession, getSessionEmail, getFrameworkSessionCookieValues, setFrameworkSessionCookie, setFirstRunOnboardingCookie, clearFrameworkSessionCookies, runAuthGuard, registerDesktopExchange, prepareDesktopOAuthBrowserBinding, matchesDesktopOAuthBrowserBinding, DESKTOP_OAUTH_BROWSER_BINDING_COOKIE, setDesktopExchange, setDesktopExchangeError, safeReturnPath, } from "./auth.js";
17
18
  export { handleIdentitySso, ensureIdentityUser, getIdentityHubUrl, isIdentitySsoEnabled, isIdentitySsoBypassPath, identitySsoLoginButtonHtml, IDENTITY_SSO_BOOTSTRAP_ACTIVATE_PATH, IDENTITY_SSO_BOOTSTRAP_BINDING_COOKIE, clearIdentitySsoBootstrapBindingCookie, getIdentitySsoBootstrapBindingCookie, setIdentitySsoBootstrapBindingCookie, IDENTITY_SSO_PROVIDER_ID, IDENTITY_SSO_SCOPE, IDENTITY_SSO_DESKTOP_COMPLETE_PATH, } from "./identity-sso.js";
18
19
  export { createBetterAuthSessionForEmail, ensureGoogleAuthIdentity, hasGoogleAuthIdentity, setBetterAuthSessionCookie, } from "./better-auth-instance.js";
@@ -1125,7 +1125,7 @@ export function getOnboardingHtml(opts = {}) {
1125
1125
  authDescription: marketing.authDescription ?? marketingPresentation?.description,
1126
1126
  learnMoreUrl: marketing.learnMoreUrl ??
1127
1127
  (marketingSlug
1128
- ? `https://agent-native.com/apps/${marketingSlug}`
1128
+ ? `https://agent-native.com/apps/${marketingSlug}?utm_source=app&utm_medium=product&utm_content=onboarding-learn-more`
1129
1129
  : undefined),
1130
1130
  }
1131
1131
  : undefined,
@@ -54,8 +54,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
54
54
  expiresAt?: undefined;
55
55
  ttlSeconds?: undefined;
56
56
  } | {
57
+ error?: undefined;
57
58
  token: string;
58
59
  expiresAt: string;
59
60
  ttlSeconds: number;
60
- error?: undefined;
61
61
  }>>;
@@ -1,5 +1,6 @@
1
1
  import { defineEventHandler, getHeader, getMethod, readRawBody, setResponseHeader, setResponseStatus, } from "h3";
2
2
  import { getMcpOAuthBearerSession, getSession, } from "./auth.js";
3
+ import { isCredentialMembershipUnavailable, respondCredentialMembershipUnavailable, } from "./credential-membership-unavailable.js";
3
4
  import { getAppUrl } from "./google-oauth.js";
4
5
  import { RECAP_IMAGE_CONTENT_TYPE, RECAP_IMAGE_MAX_BYTES, getRecapImage, isValidRecapImageToken, saveRecapImage, } from "./recap-image-store.js";
5
6
  const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
@@ -50,6 +51,9 @@ async function readPngFromRequest(event) {
50
51
  }
51
52
  async function handleUpload(event) {
52
53
  const session = await resolveUploadSession(event);
54
+ if (!session?.email && isCredentialMembershipUnavailable(event)) {
55
+ return respondCredentialMembershipUnavailable(event);
56
+ }
53
57
  if (!session?.email) {
54
58
  setResponseStatus(event, 401);
55
59
  return { error: "Authentication required" };
@@ -137,6 +137,10 @@ const FRAMEWORK_SCHEMA_ENSURES = [
137
137
  "IdentityLinks",
138
138
  () => import("../integrations/identity-links-store.js").then((m) => m.ensureTable()),
139
139
  ],
140
+ [
141
+ "IdentityRetiredEmails",
142
+ () => import("../identity/retired-emails.js").then((m) => m.ensureTable()),
143
+ ],
140
144
  [
141
145
  "IdentitySso",
142
146
  () => import("./identity-sso-store.js").then((m) => m.ensureTable()),
@@ -1,8 +1,10 @@
1
1
  export declare const AGENT_NATIVE_DOCS_ORIGIN = "https://www.agent-native.com";
2
2
  export type DocsUrlOptions = {
3
3
  hash?: string;
4
+ source?: string;
4
5
  medium?: string;
5
- campaign?: string;
6
+ /** Defaults to "docs" once any UTM option is set; `null` leaves it off. */
7
+ campaign?: string | null;
6
8
  content?: string | null;
7
9
  };
8
10
  export declare function docsUrl(slug: string, options?: DocsUrlOptions): string;
@@ -1,13 +1,16 @@
1
1
  export const AGENT_NATIVE_DOCS_ORIGIN = "https://www.agent-native.com";
2
2
  function applyDocsUtm(params, options) {
3
- const wantsUtm = options.medium != null ||
3
+ const wantsUtm = options.source != null ||
4
+ options.medium != null ||
4
5
  options.campaign != null ||
5
6
  options.content != null;
6
7
  if (!wantsUtm)
7
8
  return;
8
- params.set("utm_source", "agent-native");
9
+ params.set("utm_source", options.source ?? "agent-native");
9
10
  params.set("utm_medium", options.medium ?? "product");
10
- params.set("utm_campaign", options.campaign ?? "docs");
11
+ const campaign = options.campaign === undefined ? "docs" : options.campaign;
12
+ if (campaign)
13
+ params.set("utm_campaign", campaign);
11
14
  if (options.content)
12
15
  params.set("utm_content", options.content);
13
16
  }
@@ -33,6 +33,14 @@ export interface EnvironmentBadgeTargets {
33
33
  productionHost: string;
34
34
  }
35
35
  export declare function resolveEnvironmentTargets(hostname: string | undefined): EnvironmentBadgeTargets | null;
36
+ /**
37
+ * Moves an endpoint on a production lane host to that host's beta lane when
38
+ * the calling app is itself served from beta, so beta apps report to beta
39
+ * Analytics. Both lanes share one database: this picks which build ingests an
40
+ * event, not where the event is stored. Endpoints on any other host are an
41
+ * explicit choice and pass through unchanged.
42
+ */
43
+ export declare function resolveLaneEndpoint(endpoint: string, appHostname: string | undefined): string;
36
44
  export declare function buildEnvironmentUrl(sourceHref: string, targetHost: string): string | null;
37
45
  export declare function buildAutomaticBetaRedirectUrl(sourceHref: string, betaHost: string): string | null;
38
46
  export declare function buildEnvironmentOptOutUrl(sourceHref: string, targetHost: string, now?: number): string | null;