@agent-native/core 0.161.2 → 0.161.4

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.
@@ -227,16 +227,16 @@ export declare class AppSyncState {
227
227
  * transaction, and a long transaction killed mid-flight by a serverless
228
228
  * worker shutdown is what leaves connections `idle in transaction`
229
229
  * holding locks — the shape of the 2026-08-06 outage.
230
- * - `ORDER BY version` inside the subquery. Without it the planner
230
+ * - `ORDER BY created_at, id` inside the subquery. Without it the planner
231
231
  * prefers a sequential scan even with a LIMIT; with it, the existing
232
- * `sync_events_version_idx` drives the batch and the delete is a
232
+ * `sync_events_created_at_id_idx` drives the batch and the delete is a
233
233
  * bounded index range scan.
234
234
  *
235
235
  * The `lastDurablePrune` throttle below is per-process, so every serverless
236
236
  * worker prunes on its first poll. Postgres workers also take a
237
- * transaction-scoped advisory lease for each batch: only one worker deletes
238
- * at a time, but each batch still commits independently so a killed worker
239
- * cannot hold a long transaction open.
237
+ * transaction-scoped advisory lease for each batch. The lease and delete
238
+ * are one bounded statement, so a serverless worker killed after the
239
+ * statement starts cannot leave an open transaction.
240
240
  */
241
241
  private pruneDurableEvents;
242
242
  persistSyncEvent(event: ChangeEvent, dedupeKey?: string, presetId?: string): Promise<void>;
@@ -455,16 +455,16 @@ export class AppSyncState {
455
455
  * transaction, and a long transaction killed mid-flight by a serverless
456
456
  * worker shutdown is what leaves connections `idle in transaction`
457
457
  * holding locks — the shape of the 2026-08-06 outage.
458
- * - `ORDER BY version` inside the subquery. Without it the planner
458
+ * - `ORDER BY created_at, id` inside the subquery. Without it the planner
459
459
  * prefers a sequential scan even with a LIMIT; with it, the existing
460
- * `sync_events_version_idx` drives the batch and the delete is a
460
+ * `sync_events_created_at_id_idx` drives the batch and the delete is a
461
461
  * bounded index range scan.
462
462
  *
463
463
  * The `lastDurablePrune` throttle below is per-process, so every serverless
464
464
  * worker prunes on its first poll. Postgres workers also take a
465
- * transaction-scoped advisory lease for each batch: only one worker deletes
466
- * at a time, but each batch still commits independently so a killed worker
467
- * cannot hold a long transaction open.
465
+ * transaction-scoped advisory lease for each batch. The lease and delete
466
+ * are one bounded statement, so a serverless worker killed after the
467
+ * statement starts cannot leave an open transaction.
468
468
  */
469
469
  async pruneDurableEvents(client) {
470
470
  const now = Date.now();
@@ -476,15 +476,6 @@ export class AppSyncState {
476
476
  try {
477
477
  for (let batch = 0; batch < DURABLE_PRUNE_MAX_BATCHES; batch++) {
478
478
  const runBatch = async (tx) => {
479
- if (this.isPg()) {
480
- const lockResult = await tx.execute({
481
- sql: "SELECT pg_try_advisory_xact_lock(hashtextextended(?, 0::bigint)) AS acquired",
482
- args: [DURABLE_PRUNE_LOCK_KEY],
483
- });
484
- const acquired = lockResult.rows[0]?.acquired;
485
- if (acquired !== true && acquired !== "t")
486
- return null;
487
- }
488
479
  // `id IN (...)` rather than ctid/rowid: both are dialect-specific,
489
480
  // and the primary key is indexed on every dialect we ship.
490
481
  const result = await tx.execute({
@@ -496,20 +487,22 @@ export class AppSyncState {
496
487
  });
497
488
  return result.rowsAffected;
498
489
  };
499
- // A Postgres advisory transaction lock is only useful when the lock
500
- // and delete share a connection and transaction. Never fall back to
501
- // an uncoordinated delete on a transactionless client.
502
490
  const rowsAffected = this.isPg()
503
- ? client.transaction
504
- ? await client.transaction(runBatch)
505
- : null
491
+ ? (await client.execute({
492
+ sql: `WITH prune_lease AS MATERIALIZED (
493
+ SELECT pg_try_advisory_xact_lock(hashtextextended(?, 0::bigint)) AS acquired
494
+ ), prune_batch AS MATERIALIZED (
495
+ SELECT sync_events.id
496
+ FROM sync_events CROSS JOIN prune_lease
497
+ WHERE prune_lease.acquired AND sync_events.created_at < ?
498
+ ORDER BY sync_events.created_at, sync_events.id LIMIT ?
499
+ )
500
+ DELETE FROM sync_events WHERE id IN (
501
+ SELECT id FROM prune_batch
502
+ )`,
503
+ args: [DURABLE_PRUNE_LOCK_KEY, cutoff, DURABLE_PRUNE_BATCH],
504
+ })).rowsAffected
506
505
  : await runBatch(client);
507
- if (rowsAffected == null) {
508
- if (this.isPg() && !client.transaction) {
509
- throw new Error("Postgres prune requires a database transaction");
510
- }
511
- break;
512
- }
513
506
  deleted += rowsAffected;
514
507
  if (rowsAffected < DURABLE_PRUNE_BATCH)
515
508
  break;
@@ -7,8 +7,15 @@ import { type LocaleCode } from "../localization/shared.js";
7
7
  export interface NativeAuthCopy {
8
8
  googleButton: string;
9
9
  dividerOr: string;
10
+ welcomeTitle: string;
11
+ welcomeSubtitle: string;
10
12
  email: string;
11
13
  emailPlaceholder: string;
14
+ legalPrefix: string;
15
+ legalTerms: string;
16
+ legalConnector: string;
17
+ legalPrivacy: string;
18
+ legalSuffix: string;
12
19
  password: string;
13
20
  confirmPassword: string;
14
21
  enterPasswordPlaceholder: string;
@@ -3,8 +3,15 @@ export const NATIVE_AUTH_COPY = {
3
3
  "en-US": {
4
4
  googleButton: "Sign in with Google",
5
5
  dividerOr: "or",
6
+ welcomeTitle: "Welcome",
7
+ welcomeSubtitle: "Create an account or sign in",
6
8
  email: "Email",
7
9
  emailPlaceholder: "you@example.com",
10
+ legalPrefix: "By signing up, you accept our",
11
+ legalTerms: "Terms",
12
+ legalConnector: "and",
13
+ legalPrivacy: "Privacy Policy",
14
+ legalSuffix: ".",
8
15
  password: "Password",
9
16
  confirmPassword: "Confirm password",
10
17
  enterPasswordPlaceholder: "Enter password",
@@ -29,8 +36,15 @@ export const NATIVE_AUTH_COPY = {
29
36
  "zh-CN": {
30
37
  googleButton: "使用 Google 登录",
31
38
  dividerOr: "或",
39
+ welcomeTitle: "欢迎",
40
+ welcomeSubtitle: "创建账户或登录",
32
41
  email: "电子邮箱",
33
42
  emailPlaceholder: "you@example.com",
43
+ legalPrefix: "注册即表示你接受我们的",
44
+ legalTerms: "条款",
45
+ legalConnector: "和",
46
+ legalPrivacy: "隐私政策",
47
+ legalSuffix: "。",
34
48
  password: "密码",
35
49
  confirmPassword: "确认密码",
36
50
  enterPasswordPlaceholder: "输入密码",
@@ -55,8 +69,15 @@ export const NATIVE_AUTH_COPY = {
55
69
  "zh-TW": {
56
70
  googleButton: "使用 Google 登入",
57
71
  dividerOr: "或",
72
+ welcomeTitle: "歡迎",
73
+ welcomeSubtitle: "建立帳戶或登入",
58
74
  email: "電子郵件",
59
75
  emailPlaceholder: "you@example.com",
76
+ legalPrefix: "註冊即表示你接受我們的",
77
+ legalTerms: "條款",
78
+ legalConnector: "和",
79
+ legalPrivacy: "隱私權政策",
80
+ legalSuffix: "。",
60
81
  password: "密碼",
61
82
  confirmPassword: "確認密碼",
62
83
  enterPasswordPlaceholder: "輸入密碼",
@@ -81,8 +102,15 @@ export const NATIVE_AUTH_COPY = {
81
102
  "es-ES": {
82
103
  googleButton: "Iniciar sesión con Google",
83
104
  dividerOr: "o",
105
+ welcomeTitle: "Bienvenido",
106
+ welcomeSubtitle: "Crea una cuenta o inicia sesión",
84
107
  email: "Email",
85
108
  emailPlaceholder: "you@example.com",
109
+ legalPrefix: "Al registrarte, aceptas nuestros",
110
+ legalTerms: "Términos",
111
+ legalConnector: "y",
112
+ legalPrivacy: "Política de privacidad",
113
+ legalSuffix: ".",
86
114
  password: "Contraseña",
87
115
  confirmPassword: "Confirmar contraseña",
88
116
  enterPasswordPlaceholder: "Introduce la contraseña",
@@ -107,8 +135,15 @@ export const NATIVE_AUTH_COPY = {
107
135
  "fr-FR": {
108
136
  googleButton: "Se connecter avec Google",
109
137
  dividerOr: "ou",
138
+ welcomeTitle: "Bienvenue",
139
+ welcomeSubtitle: "Créez un compte ou connectez-vous",
110
140
  email: "E-mail",
111
141
  emailPlaceholder: "you@example.com",
142
+ legalPrefix: "En vous inscrivant, vous acceptez nos",
143
+ legalTerms: "Conditions",
144
+ legalConnector: "et",
145
+ legalPrivacy: "Politique de confidentialité",
146
+ legalSuffix: ".",
112
147
  password: "Mot de passe",
113
148
  confirmPassword: "Confirmer le mot de passe",
114
149
  enterPasswordPlaceholder: "Saisir le mot de passe",
@@ -133,8 +168,15 @@ export const NATIVE_AUTH_COPY = {
133
168
  "de-DE": {
134
169
  googleButton: "Mit Google anmelden",
135
170
  dividerOr: "oder",
171
+ welcomeTitle: "Willkommen",
172
+ welcomeSubtitle: "Konto erstellen oder anmelden",
136
173
  email: "E-Mail",
137
174
  emailPlaceholder: "you@example.com",
175
+ legalPrefix: "Mit der Registrierung akzeptierst du unsere",
176
+ legalTerms: "Bedingungen",
177
+ legalConnector: "und",
178
+ legalPrivacy: "Datenschutzrichtlinie",
179
+ legalSuffix: ".",
138
180
  password: "Passwort",
139
181
  confirmPassword: "Passwort bestätigen",
140
182
  enterPasswordPlaceholder: "Passwort eingeben",
@@ -159,8 +201,15 @@ export const NATIVE_AUTH_COPY = {
159
201
  "ja-JP": {
160
202
  googleButton: "Google でサインイン",
161
203
  dividerOr: "または",
204
+ welcomeTitle: "ようこそ",
205
+ welcomeSubtitle: "アカウントを作成するかサインインしてください",
162
206
  email: "メール",
163
207
  emailPlaceholder: "you@example.com",
208
+ legalPrefix: "登録すると、以下に同意したものとみなされます:",
209
+ legalTerms: "利用規約",
210
+ legalConnector: "および",
211
+ legalPrivacy: "プライバシーポリシー",
212
+ legalSuffix: "。",
164
213
  password: "パスワード",
165
214
  confirmPassword: "パスワードを確認",
166
215
  enterPasswordPlaceholder: "パスワードを入力",
@@ -185,8 +234,15 @@ export const NATIVE_AUTH_COPY = {
185
234
  "ko-KR": {
186
235
  googleButton: "Google로 로그인",
187
236
  dividerOr: "또는",
237
+ welcomeTitle: "환영합니다",
238
+ welcomeSubtitle: "계정을 만들거나 로그인하세요",
188
239
  email: "이메일",
189
240
  emailPlaceholder: "you@example.com",
241
+ legalPrefix: "가입하면 다음에 동의하게 됩니다:",
242
+ legalTerms: "약관",
243
+ legalConnector: "및",
244
+ legalPrivacy: "개인정보 처리방침",
245
+ legalSuffix: ".",
190
246
  password: "비밀번호",
191
247
  confirmPassword: "비밀번호 확인",
192
248
  enterPasswordPlaceholder: "비밀번호 입력",
@@ -211,8 +267,15 @@ export const NATIVE_AUTH_COPY = {
211
267
  "pt-BR": {
212
268
  googleButton: "Entrar com Google",
213
269
  dividerOr: "ou",
270
+ welcomeTitle: "Bem-vindo",
271
+ welcomeSubtitle: "Crie uma conta ou entre",
214
272
  email: "Email",
215
273
  emailPlaceholder: "you@example.com",
274
+ legalPrefix: "Ao se cadastrar, você aceita nossos",
275
+ legalTerms: "Termos",
276
+ legalConnector: "e",
277
+ legalPrivacy: "Política de Privacidade",
278
+ legalSuffix: ".",
216
279
  password: "Senha",
217
280
  confirmPassword: "Confirmar senha",
218
281
  enterPasswordPlaceholder: "Digite a senha",
@@ -237,8 +300,15 @@ export const NATIVE_AUTH_COPY = {
237
300
  "hi-IN": {
238
301
  googleButton: "Google से साइन इन करें",
239
302
  dividerOr: "या",
303
+ welcomeTitle: "स्वागत है",
304
+ welcomeSubtitle: "खाता बनाएं या साइन इन करें",
240
305
  email: "ईमेल",
241
306
  emailPlaceholder: "you@example.com",
307
+ legalPrefix: "साइन अप करके, आप हमारी",
308
+ legalTerms: "शर्तें",
309
+ legalConnector: "और",
310
+ legalPrivacy: "गोपनीयता नीति",
311
+ legalSuffix: "स्वीकार करते हैं।",
242
312
  password: "पासवर्ड",
243
313
  confirmPassword: "पासवर्ड की पुष्टि करें",
244
314
  enterPasswordPlaceholder: "पासवर्ड दर्ज करें",
@@ -263,8 +333,15 @@ export const NATIVE_AUTH_COPY = {
263
333
  "ar-SA": {
264
334
  googleButton: "تسجيل الدخول باستخدام Google",
265
335
  dividerOr: "أو",
336
+ welcomeTitle: "مرحبًا",
337
+ welcomeSubtitle: "أنشئ حسابًا أو سجّل الدخول",
266
338
  email: "البريد الإلكتروني",
267
339
  emailPlaceholder: "you@example.com",
340
+ legalPrefix: "بالتسجيل، فإنك توافق على",
341
+ legalTerms: "الشروط",
342
+ legalConnector: "و",
343
+ legalPrivacy: "سياسة الخصوصية",
344
+ legalSuffix: ".",
268
345
  password: "كلمة المرور",
269
346
  confirmPassword: "تأكيد كلمة المرور",
270
347
  enterPasswordPlaceholder: "أدخل كلمة المرور",
@@ -3,6 +3,13 @@ export const EMBED_TRANSPLANT_HEADER = "x-agent-native-embed-transplant";
3
3
  const CLAUDE_MCP_CONTENT_HOST_RE = /^[a-f0-9]{32}\.claudemcpcontent\.com$/i;
4
4
  const CHATGPT_MCP_SANDBOX_HOST_RE = /^(?:[^.]+\.)?web-sandbox\.oaiusercontent\.com$/i;
5
5
  const AGENT_NATIVE_FIRST_PARTY_APP_HOST_SUFFIX = ".agent-native.com";
6
+ const BUILDER_EMBED_HOST_SUFFIXES = [
7
+ ".builder.io",
8
+ ".builder.my",
9
+ ".builderio.xyz",
10
+ ".builderio.dev",
11
+ ".builder.codes",
12
+ ];
6
13
  const MCP_PRODUCT_HOST_ORIGINS = new Set([
7
14
  "https://chat.openai.com",
8
15
  "https://chatgpt.com",
@@ -87,10 +94,7 @@ export function isBuilderIoEmbedOrigin(origin) {
87
94
  return (url.protocol === "https:" &&
88
95
  !url.username &&
89
96
  !url.password &&
90
- (hostname === "builder.io" ||
91
- hostname.endsWith(".builder.io") ||
92
- hostname === "builder.my" ||
93
- hostname.endsWith(".builder.my")));
97
+ BUILDER_EMBED_HOST_SUFFIXES.some((suffix) => hostname === suffix.slice(1) || hostname.endsWith(suffix)));
94
98
  }
95
99
  catch {
96
100
  return false;
@@ -1451,6 +1451,66 @@ const EMBED_DEV_STATIC_ASSET_PATHS = new Set([
1451
1451
  "/favicon.svg",
1452
1452
  "/manifest.json",
1453
1453
  ]);
1454
+ const VITE_RUNTIME_MODULE_QUERY_KEYS = new Set([
1455
+ "commonjs-proxy",
1456
+ "direct",
1457
+ "html-proxy",
1458
+ "import",
1459
+ "inline",
1460
+ "inline-css",
1461
+ "no-inline",
1462
+ "raw",
1463
+ "sharedworker",
1464
+ "style-attr",
1465
+ "transform-only",
1466
+ "url",
1467
+ "worker",
1468
+ ]);
1469
+ const VITE_STATIC_ASSET_EXTENSIONS = new Set([
1470
+ ".aac",
1471
+ ".apng",
1472
+ ".avif",
1473
+ ".bmp",
1474
+ ".css",
1475
+ ".cur",
1476
+ ".eot",
1477
+ ".flac",
1478
+ ".gif",
1479
+ ".ico",
1480
+ ".jfif",
1481
+ ".jpeg",
1482
+ ".jpg",
1483
+ ".jxl",
1484
+ ".less",
1485
+ ".m4a",
1486
+ ".mp3",
1487
+ ".mp4",
1488
+ ".mov",
1489
+ ".ogg",
1490
+ ".otf",
1491
+ ".pjp",
1492
+ ".pjpeg",
1493
+ ".pcss",
1494
+ ".pdf",
1495
+ ".png",
1496
+ ".postcss",
1497
+ ".opus",
1498
+ ".sass",
1499
+ ".scss",
1500
+ ".svg",
1501
+ ".styl",
1502
+ ".stylus",
1503
+ ".ttf",
1504
+ ".txt",
1505
+ ".vtt",
1506
+ ".wasm",
1507
+ ".wav",
1508
+ ".webm",
1509
+ ".webp",
1510
+ ".webmanifest",
1511
+ ".woff",
1512
+ ".woff2",
1513
+ ]);
1454
1514
  function mountedPathCandidates(reqUrl, base) {
1455
1515
  if (!reqUrl)
1456
1516
  return [];
@@ -1528,9 +1588,38 @@ function mountedEmbedRuntimeModuleUrl(reqUrl, base) {
1528
1588
  if (!VITE_RUNTIME_PATH_PREFIXES.some((prefix) => url.pathname.startsWith(prefix))) {
1529
1589
  return null;
1530
1590
  }
1531
- url.searchParams.delete(EMBED_TOKEN_QUERY_PARAM);
1532
- url.searchParams.delete(MCP_APP_CHAT_BRIDGE_QUERY_PARAM);
1533
- return `${url.pathname}${url.search}${url.hash}`;
1591
+ // Vite distinguishes valueless module flags such as `?url` from `?url=`.
1592
+ // Strip only the embed controls so those flags reach Vite byte-for-byte.
1593
+ const hashStart = runtimeUrl.indexOf("#");
1594
+ const searchStart = runtimeUrl.indexOf("?");
1595
+ const search = searchStart >= 0 && (hashStart < 0 || searchStart < hashStart)
1596
+ ? runtimeUrl.slice(searchStart, hashStart < 0 ? undefined : hashStart)
1597
+ : "";
1598
+ return `${url.pathname}${stripMountedEmbedRuntimeQueryParams(search)}${url.hash}`;
1599
+ }
1600
+ function stripMountedEmbedRuntimeQueryParams(search) {
1601
+ if (!search)
1602
+ return "";
1603
+ const kept = search
1604
+ .slice(1)
1605
+ .split("&")
1606
+ .filter((pair) => {
1607
+ const key = pair.split("=", 1)[0];
1608
+ return (key !== EMBED_TOKEN_QUERY_PARAM &&
1609
+ key !== MCP_APP_CHAT_BRIDGE_QUERY_PARAM);
1610
+ });
1611
+ return kept.length > 0 ? `?${kept.join("&")}` : "";
1612
+ }
1613
+ function isMountedEmbedStaticAssetRequest(req, runtimeUrl) {
1614
+ const url = new URL(runtimeUrl, "http://agent-native.local");
1615
+ const isViteModuleQuery = [...url.searchParams.keys()].some((key) => VITE_RUNTIME_MODULE_QUERY_KEYS.has(key));
1616
+ if (isViteModuleQuery)
1617
+ return false;
1618
+ const fetchDestination = String(req.headers["sec-fetch-dest"] ?? "").toLowerCase();
1619
+ if (["audio", "font", "image", "style", "track", "video"].includes(fetchDestination)) {
1620
+ return true;
1621
+ }
1622
+ return VITE_STATIC_ASSET_EXTENSIONS.has(path.extname(url.pathname).toLowerCase());
1534
1623
  }
1535
1624
  function virtualModuleIdFromRuntimeUrl(runtimeUrl) {
1536
1625
  try {
@@ -1564,6 +1653,8 @@ function serveMountedEmbedRuntimeModule(server, req, res, base) {
1564
1653
  const runtimeUrl = mountedEmbedRuntimeModuleUrl(req.url, base);
1565
1654
  if (!runtimeUrl)
1566
1655
  return false;
1656
+ if (isMountedEmbedStaticAssetRequest(req, runtimeUrl))
1657
+ return false;
1567
1658
  void loadMountedEmbedRuntimeModule(server, runtimeUrl)
1568
1659
  .then((code) => {
1569
1660
  if (!code) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.161.2",
3
+ "version": "0.161.4",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -427,8 +427,8 @@
427
427
  "y-protocols": "^1.0.7",
428
428
  "yjs": "^13.6.31",
429
429
  "zod": "^4.3.6",
430
- "@agent-native/toolkit": "^0.16.4",
431
- "@agent-native/recap-cli": "0.5.4"
430
+ "@agent-native/recap-cli": "0.5.4",
431
+ "@agent-native/toolkit": "^0.16.4"
432
432
  },
433
433
  "devDependencies": {
434
434
  "@ai-sdk/anthropic": "^3.0.71",