@agent-native/core 0.84.59 → 0.84.61

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 (43) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/AssistantChat.tsx +49 -14
  5. package/corpus/core/src/db/client.ts +13 -1
  6. package/corpus/core/src/server/ssr-handler.ts +14 -16
  7. package/corpus/core/src/sharing/access.ts +79 -5
  8. package/corpus/templates/clips/app/i18n/ar-SA.ts +7 -0
  9. package/corpus/templates/clips/app/i18n/de-DE.ts +8 -0
  10. package/corpus/templates/clips/app/i18n/en-US.ts +7 -0
  11. package/corpus/templates/clips/app/i18n/es-ES.ts +7 -0
  12. package/corpus/templates/clips/app/i18n/fr-FR.ts +7 -0
  13. package/corpus/templates/clips/app/i18n/hi-IN.ts +6 -0
  14. package/corpus/templates/clips/app/i18n/ja-JP.ts +8 -0
  15. package/corpus/templates/clips/app/i18n/ko-KR.ts +7 -0
  16. package/corpus/templates/clips/app/i18n/pt-BR.ts +7 -0
  17. package/corpus/templates/clips/app/i18n/zh-CN.ts +5 -0
  18. package/corpus/templates/clips/app/i18n/zh-TW.ts +5 -0
  19. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +174 -28
  20. package/corpus/templates/clips/app/routes/record.tsx +74 -6
  21. package/corpus/templates/plan/changelog/2026-07-02-fixed-hosted-visual-plans-getting-stuck-loading-when-a-datab.md +6 -0
  22. package/corpus/templates/plan/server/plugins/db.ts +25 -0
  23. package/dist/client/AssistantChat.d.ts.map +1 -1
  24. package/dist/client/AssistantChat.js +42 -15
  25. package/dist/client/AssistantChat.js.map +1 -1
  26. package/dist/collab/awareness.d.ts +2 -2
  27. package/dist/collab/awareness.d.ts.map +1 -1
  28. package/dist/collab/routes.d.ts +1 -1
  29. package/dist/db/client.d.ts.map +1 -1
  30. package/dist/db/client.js +11 -1
  31. package/dist/db/client.js.map +1 -1
  32. package/dist/notifications/routes.d.ts +3 -3
  33. package/dist/observability/routes.d.ts +3 -3
  34. package/dist/progress/routes.d.ts +1 -1
  35. package/dist/resources/handlers.d.ts +1 -1
  36. package/dist/server/ssr-handler.d.ts.map +1 -1
  37. package/dist/server/ssr-handler.js +14 -16
  38. package/dist/server/ssr-handler.js.map +1 -1
  39. package/dist/server/transcribe-voice.d.ts +1 -1
  40. package/dist/sharing/access.d.ts.map +1 -1
  41. package/dist/sharing/access.js +54 -5
  42. package/dist/sharing/access.js.map +1 -1
  43. package/package.json +1 -1
package/corpus/README.md CHANGED
@@ -28,4 +28,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
28
28
  ## Generated Counts
29
29
 
30
30
  - core files: 2044
31
- - template files: 5018
31
+ - template files: 5019
@@ -1,5 +1,17 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.84.61
4
+
5
+ ### Patch Changes
6
+
7
+ - 3a66817: Recover share access lookups when a hosted database is missing newer additive resource columns, and prefer Netlify unpooled database URLs for migrations.
8
+
9
+ ## 0.84.60
10
+
11
+ ### Patch Changes
12
+
13
+ - 4b6ca6c: Loosen the document script CSP allowances so hosted Google Tag Manager scripts and framework inline bootstrap scripts do not trigger CSP violations.
14
+
3
15
  ## 0.84.59
4
16
 
5
17
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.84.59",
3
+ "version": "0.84.61",
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": {
@@ -256,9 +256,21 @@ type ActiveRunLookup = {
256
256
  status?: string;
257
257
  heartbeatAt?: number | null;
258
258
  lastProgressAt?: number | null;
259
+ dispatchMode?: string | null;
260
+ terminalReason?: string | null;
259
261
  serverNow?: number;
260
262
  };
261
263
 
264
+ function isReplayableTerminalRun(runInfo: ActiveRunLookup): boolean {
265
+ const dispatchMode =
266
+ typeof runInfo.dispatchMode === "string" ? runInfo.dispatchMode : "";
267
+ return (
268
+ runInfo.status !== "running" &&
269
+ dispatchMode.startsWith("background") &&
270
+ runInfo.terminalReason === "run_timeout"
271
+ );
272
+ }
273
+
262
274
  function activeRunLooksStale(runInfo: ActiveRunLookup): boolean {
263
275
  const lastProgressAt =
264
276
  typeof runInfo.lastProgressAt === "number" ? runInfo.lastProgressAt : null;
@@ -1846,7 +1858,11 @@ const AssistantChatInner = forwardRef<
1846
1858
 
1847
1859
  const startReconnectToRun = useCallback(
1848
1860
  (runInfo: ActiveRunLookup): boolean => {
1849
- if (!threadId || !runInfo.runId || runInfo.status !== "running") {
1861
+ if (
1862
+ !threadId ||
1863
+ !runInfo.runId ||
1864
+ (runInfo.status !== "running" && !isReplayableTerminalRun(runInfo))
1865
+ ) {
1850
1866
  return false;
1851
1867
  }
1852
1868
  const runId = String(runInfo.runId);
@@ -1876,6 +1892,8 @@ const AssistantChatInner = forwardRef<
1876
1892
 
1877
1893
  const abortCtrl = new AbortController();
1878
1894
  reconnectAbortRef.current = abortCtrl;
1895
+ let reconnectTerminalReason: AgentAutoContinueSignal["reason"] | null =
1896
+ null;
1879
1897
 
1880
1898
  const watchdog = setInterval(async () => {
1881
1899
  try {
@@ -1888,6 +1906,9 @@ const AssistantChatInner = forwardRef<
1888
1906
  return;
1889
1907
  }
1890
1908
  const info = (await res.json()) as ActiveRunLookup;
1909
+ if (isReplayableTerminalRun(info)) {
1910
+ return;
1911
+ }
1891
1912
  if (info.status !== "running" || activeRunLooksStale(info)) {
1892
1913
  abortCtrl.abort();
1893
1914
  clearInterval(watchdog);
@@ -1911,6 +1932,7 @@ const AssistantChatInner = forwardRef<
1911
1932
  lastReconnectProgressAt = Date.now();
1912
1933
  };
1913
1934
  const idleCheck = setInterval(() => {
1935
+ if (reconnectTerminalReason !== null) return;
1914
1936
  if (
1915
1937
  !reconnectProgressTimedOut({
1916
1938
  lastProgressAt: lastReconnectProgressAt,
@@ -2038,6 +2060,10 @@ const AssistantChatInner = forwardRef<
2038
2060
  err.reason === "no_progress"
2039
2061
  ) {
2040
2062
  noProgressDuringReconnect = true;
2063
+ reconnectTerminalReason = err.reason;
2064
+ } else if (err instanceof AgentAutoContinueSignal) {
2065
+ noProgressDuringReconnect = true;
2066
+ reconnectTerminalReason = err.reason;
2041
2067
  } else if (
2042
2068
  reconnectTimedOut &&
2043
2069
  err instanceof Error &&
@@ -2054,11 +2080,16 @@ const AssistantChatInner = forwardRef<
2054
2080
  }
2055
2081
 
2056
2082
  if (noProgressDuringReconnect && reconnectRunIdRef.current === runId) {
2057
- captureError(new Error("agent-chat:reconnect_no_progress"), {
2083
+ const reconnectErrorCode =
2084
+ reconnectTerminalReason === "run_timeout"
2085
+ ? "run_timeout"
2086
+ : "reconnect_no_progress";
2087
+ captureError(new Error(`agent-chat:${reconnectErrorCode}`), {
2058
2088
  tags: {
2059
2089
  context: "agent-native-chat",
2060
- errorCode: "reconnect_no_progress",
2090
+ errorCode: reconnectErrorCode,
2061
2091
  reconnectTimedOut: String(reconnectTimedOut),
2092
+ reconnectTerminalReason: reconnectTerminalReason ?? undefined,
2062
2093
  },
2063
2094
  extra: {
2064
2095
  runId,
@@ -2067,14 +2098,16 @@ const AssistantChatInner = forwardRef<
2067
2098
  contentLength: latestContent.length,
2068
2099
  },
2069
2100
  });
2070
- try {
2071
- await fetch(`${apiUrl}/runs/${encodeURIComponent(runId)}/abort`, {
2072
- method: "POST",
2073
- headers: { "Content-Type": "application/json" },
2074
- body: JSON.stringify({ reason: "no_progress" }),
2075
- });
2076
- } catch {
2077
- // Best effort — the important part is unwinding the UI.
2101
+ if (reconnectTerminalReason !== "run_timeout") {
2102
+ try {
2103
+ await fetch(`${apiUrl}/runs/${encodeURIComponent(runId)}/abort`, {
2104
+ method: "POST",
2105
+ headers: { "Content-Type": "application/json" },
2106
+ body: JSON.stringify({ reason: "no_progress" }),
2107
+ });
2108
+ } catch {
2109
+ // Best effort — the important part is unwinding the UI.
2110
+ }
2078
2111
  }
2079
2112
  if (afterSeq > 0) {
2080
2113
  // Tail-resume only replays new events; never freeze that slice as a
@@ -2091,8 +2124,10 @@ const AssistantChatInner = forwardRef<
2091
2124
  }
2092
2125
  setRunErrorInfo({
2093
2126
  message:
2094
- "The previous agent run stopped producing visible progress during recovery, so it was stopped before it could keep looping.",
2095
- errorCode: "reconnect_no_progress",
2127
+ reconnectTerminalReason === "run_timeout"
2128
+ ? "The previous background agent run reached its time limit before finishing. The partial work was preserved; continue or retry to pick up from here."
2129
+ : "The previous agent run stopped producing visible progress during recovery, so it was stopped before it could keep looping.",
2130
+ errorCode: reconnectErrorCode,
2096
2131
  recoverable: true,
2097
2132
  runId,
2098
2133
  });
@@ -2175,7 +2210,7 @@ const AssistantChatInner = forwardRef<
2175
2210
  const runInfo = (await runRes.json()) as ActiveRunLookup;
2176
2211
  if (
2177
2212
  !runInfo.active ||
2178
- runInfo.status !== "running" ||
2213
+ (runInfo.status !== "running" && !isReplayableTerminalRun(runInfo)) ||
2179
2214
  activeRunLooksStale(runInfo)
2180
2215
  ) {
2181
2216
  if (storedActiveRun?.threadId === threadId) {
@@ -78,6 +78,10 @@ export function getDatabaseAuthToken(): string | undefined {
78
78
  );
79
79
  }
80
80
 
81
+ function getAppEnvPrefix(): string | undefined {
82
+ return process.env.APP_NAME?.toUpperCase().replace(/-/g, "_") || undefined;
83
+ }
84
+
81
85
  /**
82
86
  * Database URL to use for migrations — identical to DATABASE_URL but with the
83
87
  * Neon connection-pooler suffix stripped. Neon's PgBouncer runs in transaction
@@ -89,7 +93,15 @@ export function getDatabaseAuthToken(): string | undefined {
89
93
  * Non-Neon URLs and already-direct Neon URLs are returned unchanged.
90
94
  */
91
95
  export function getMigrationDatabaseUrl(): string {
92
- const url = getDatabaseUrl();
96
+ const appName = getAppEnvPrefix();
97
+ const appUnpooled = appName
98
+ ? process.env[`${appName}_DATABASE_URL_UNPOOLED`]
99
+ : undefined;
100
+ const url =
101
+ appUnpooled ||
102
+ process.env.NETLIFY_DATABASE_URL_UNPOOLED ||
103
+ process.env.DATABASE_URL_UNPOOLED ||
104
+ getDatabaseUrl();
93
105
  // Neon pooler hostname: ep-<id>-pooler.<region>.<cloud>.neon.tech
94
106
  // Direct hostname: ep-<id>.<region>.<cloud>.neon.tech
95
107
  // The region between `-pooler.` and `.neon.tech` can contain multiple
@@ -261,8 +261,7 @@ function applyDefaultSpeculationRulesHeader(
261
261
  * Extract the plain JS body from a `<script ...>body</script>` string.
262
262
  * Returns `null` if the input is falsy or has no recognisable `</script>` end.
263
263
  * Used to compute the sha256 hash of framework-injected inline scripts so the
264
- * hash can be listed in the `script-src` CSP directive without relying on
265
- * `'unsafe-inline'`.
264
+ * hash can be listed in app-owned `script-src` CSP directives.
266
265
  */
267
266
  function extractScriptBody(scriptTag: string | null): string | null {
268
267
  if (!scriptTag) return null;
@@ -547,13 +546,10 @@ function augmentExistingReportOnlyCspForFrameworkScripts(
547
546
  *
548
547
  * A third directive, `script-src`, is emitted via `Content-Security-Policy-
549
548
  * Report-Only` rather than enforced when the app has no existing document CSP.
550
- * The framework injects deterministic inline scripts (the Sentry config block,
551
- * whose hash is computed once at process startup from the resolved env vars,
552
- * and when `GA_MEASUREMENT_ID` is set the gtag config block, whose hash is
553
- * derived from the same string `wrapWithAnalytics` embeds). It also loads
554
- * Google Tag Manager / GA4 from `GA_CSP_SCRIPT_HOSTS`. All of those are listed
555
- * here so the report-only policy reflects the code the framework itself injects
556
- * instead of reporting a violation on every page load.
549
+ * The framework injects inline scripts for analytics, Sentry, and template
550
+ * setup, and hosted apps need Google Tag Manager to load without noisy CSP
551
+ * diagnostics. The report-only policy is intentionally permissive for scripts:
552
+ * it includes `'unsafe-inline'` plus the known GA/GTM loader hosts.
557
553
  *
558
554
  * If an app or host already sends an enforced CSP with `script-src`,
559
555
  * `script-src-elem`, `connect-src`, `img-src`, or `default-src`, we merge only
@@ -577,20 +573,22 @@ function applyDocumentCsp(headers: Headers, sentryScript: string | null): void {
577
573
  if (process.env.NODE_ENV !== "production") return;
578
574
  if (process.env.AGENT_NATIVE_DISABLE_DOC_CSP === "1") return;
579
575
 
580
- // script-src as Report-Only: list 'self', the framework-injected inline
581
- // script hashes (Sentry config + gtag config), and the Google Analytics /
582
- // Tag Manager loader hosts. These are exactly the scripts the framework
583
- // itself injects, so listing them keeps the report-only policy from flagging
584
- // GA on every page load (and keeps it safe to graduate to enforcement).
585
- // Template theme-init hashes are NOT included here — see function comment.
576
+ // script-src as Report-Only: keep this deliberately loose so the framework's
577
+ // injected analytics and template bootstrap scripts do not look blocked in
578
+ // browser diagnostics.
586
579
  const sentryBody = extractScriptBody(sentryScript);
587
580
  const sentryHash = sentryBody ? computeInlineScriptHash(sentryBody) : null;
588
581
  const gaInlineBody = getGaInlineConfigScriptBody();
589
582
  const gaHash = gaInlineBody ? computeInlineScriptHash(gaInlineBody) : null;
590
583
  const gaHosts = gaInlineBody ? [...GA_CSP_SCRIPT_HOSTS] : [];
591
- const gaScriptSrcTokens = [...(gaHash ? [gaHash] : []), ...gaHosts];
584
+ const gaScriptSrcTokens = [
585
+ "'unsafe-inline'",
586
+ ...(gaHash ? [gaHash] : []),
587
+ ...gaHosts,
588
+ ];
592
589
  const scriptSrcTokens = [
593
590
  "'self'",
591
+ "'unsafe-inline'",
594
592
  ...(sentryHash ? [sentryHash] : []),
595
593
  ...(gaHash ? [gaHash] : []),
596
594
  ...gaHosts,
@@ -250,6 +250,84 @@ function higherShareRole(a: ShareRole, b: ShareRole | null): ShareRole {
250
250
  return ROLE_RANK[b] > ROLE_RANK[a] ? b : a;
251
251
  }
252
252
 
253
+ function columnName(column: unknown): string | null {
254
+ const candidate = column as
255
+ | {
256
+ name?: unknown;
257
+ config?: { name?: unknown };
258
+ _: { name?: unknown };
259
+ }
260
+ | undefined;
261
+ const name = candidate?.name ?? candidate?.config?.name ?? candidate?._?.name;
262
+ return typeof name === "string" && name ? name : null;
263
+ }
264
+
265
+ function missingColumnName(err: unknown): string | null {
266
+ const error = err as { code?: string; message?: string } | undefined;
267
+ const message = error?.message ?? "";
268
+ if (
269
+ error?.code !== "42703" &&
270
+ !/no such column|does not exist/i.test(message)
271
+ ) {
272
+ return null;
273
+ }
274
+ const quoted = message.match(/column\s+"([^"]+)"\s+does not exist/i)?.[1];
275
+ if (quoted) return quoted;
276
+ const sqlite = message.match(/no such column:\s+["`]?([\w.]+)["`]?/i)?.[1];
277
+ if (sqlite) return sqlite.split(".").pop() ?? sqlite;
278
+ return null;
279
+ }
280
+
281
+ function selectAllExistingResourceColumns(
282
+ resourceTable: any,
283
+ omittedColumnNames: Set<string>,
284
+ ): Record<string, unknown> {
285
+ const selection: Record<string, unknown> = {};
286
+ for (const [key, column] of Object.entries(resourceTable)) {
287
+ const name = columnName(column);
288
+ if (!name || omittedColumnNames.has(name)) continue;
289
+ selection[key] = column;
290
+ }
291
+ return selection;
292
+ }
293
+
294
+ async function loadResourceForAccess(
295
+ reg: ShareableResourceRegistration,
296
+ resourceId: string,
297
+ ): Promise<any | null> {
298
+ const db = reg.getDb() as any;
299
+ const omittedColumnNames = new Set<string>();
300
+
301
+ for (let attempt = 0; attempt < 12; attempt++) {
302
+ try {
303
+ const query =
304
+ omittedColumnNames.size === 0
305
+ ? db.select()
306
+ : db.select(
307
+ selectAllExistingResourceColumns(
308
+ reg.resourceTable,
309
+ omittedColumnNames,
310
+ ),
311
+ );
312
+ const [resource] = await query
313
+ .from(reg.resourceTable)
314
+ .where(eq(reg.resourceTable.id, resourceId));
315
+ return resource ?? null;
316
+ } catch (err) {
317
+ const missing = missingColumnName(err);
318
+ if (!missing || omittedColumnNames.has(missing)) throw err;
319
+ omittedColumnNames.add(missing);
320
+ console.warn(
321
+ `[sharing] ${reg.type} access lookup omitted missing column ${missing}`,
322
+ );
323
+ }
324
+ }
325
+
326
+ throw new Error(
327
+ `Could not load ${reg.type} ${resourceId}: too many missing resource columns`,
328
+ );
329
+ }
330
+
253
331
  /**
254
332
  * Return the effective role the current user has on a specific resource, or
255
333
  * null if they have no access. Loads the resource and relevant share rows.
@@ -261,12 +339,8 @@ export async function resolveAccess(
261
339
  ): Promise<ResolvedAccess | null> {
262
340
  const reg = requireShareableResource(resourceType);
263
341
  const ctx = resolveRegisteredAccessContext(reg, rawCtx);
264
- const db = reg.getDb() as any;
265
342
 
266
- const [resource] = await db
267
- .select()
268
- .from(reg.resourceTable)
269
- .where(eq(reg.resourceTable.id, resourceId));
343
+ const resource = await loadResourceForAccess(reg, resourceId);
270
344
  if (!resource) return null;
271
345
 
272
346
  const { userEmail, orgId } = ctx;
@@ -637,6 +637,11 @@ const messages = {
637
637
  s3SecretAccessKeyLabel: "مفتاح الوصول السري",
638
638
  s3RegionLabel: "المنطقة",
639
639
  s3PublicBaseUrlLabel: "عنوان URL الأساسي العام",
640
+ s3UrlInvalid:
641
+ "يجب أن يكون عنوان URL صالحًا (مثال: https://s3.us-east-1.amazonaws.com)",
642
+ s3BucketInvalid:
643
+ "يجب أن يتكون اسم الحاوية من 3 إلى 63 حرفًا صغيرًا أو رقمًا أو شرطة",
644
+ s3RegionInvalid: 'يجب أن تكون منطقة صالحة (مثال: us-east-1) أو "auto"',
640
645
  apiSetup: "إعداد الذكاء الاصطناعي",
641
646
  apiSetupDescription:
642
647
  "صِل الذكاء الاصطناعي باستخدام أرصدة Builder.io المجانية أو مفاتيح LLM الخاصة بك.",
@@ -651,6 +656,8 @@ const messages = {
651
656
  providerKeysSet: "تم تعيين {{count}}",
652
657
  checkingProviderKeys: "جار فحص مفاتيح المزود…",
653
658
  keySet: "تم التعيين",
659
+ keyCleared: "تم مسح بيانات اعتماد التخزين",
660
+ clearAllS3: "مسح بيانات الاعتماد",
654
661
  replaceKey: "استبدال المفتاح…",
655
662
  pasteProviderKey: "الصق مفتاح مزود أولًا.",
656
663
  apiKeySaved: "تم حفظ مفتاح API",
@@ -662,6 +662,12 @@ Alle sichtbaren Änderungen für Clips-Nutzer werden hier dokumentiert. Du kanns
662
662
  s3SecretAccessKeyLabel: "Geheimer Zugriffsschlüssel",
663
663
  s3RegionLabel: "Übersetzt: Region",
664
664
  s3PublicBaseUrlLabel: "Öffentliche Basis-URL",
665
+ s3UrlInvalid:
666
+ "Muss eine gültige URL sein (z. B. https://s3.us-east-1.amazonaws.com)",
667
+ s3BucketInvalid:
668
+ "Bucket-Name muss 3–63 Kleinbuchstaben, Zahlen oder Bindestriche enthalten",
669
+ s3RegionInvalid:
670
+ 'Muss eine gültige Region sein (z. B. us-east-1) oder "auto"',
665
671
  apiSetup: "KI-Einrichtung",
666
672
  apiSetupDescription:
667
673
  "Verbinde KI mit kostenlosen Builder.io-Credits oder deinen eigenen LLM-Schlüsseln.",
@@ -676,6 +682,8 @@ Alle sichtbaren Änderungen für Clips-Nutzer werden hier dokumentiert. Du kanns
676
682
  providerKeysSet: "{{count}} gesetzt",
677
683
  checkingProviderKeys: "Anbieter-Schlüssel werden geprüft…",
678
684
  keySet: "Gesetzt",
685
+ keyCleared: "Speicher-Anmeldedaten gelöscht",
686
+ clearAllS3: "Anmeldedaten löschen",
679
687
  replaceKey: "Schlüssel ersetzen…",
680
688
  pasteProviderKey: "Füge zuerst einen Anbieter-Schlüssel ein.",
681
689
  apiKeySaved: "API-Schlüssel gespeichert",
@@ -637,6 +637,11 @@ All notable user-facing changes to Clips are documented here. Open it any time f
637
637
  s3SecretAccessKeyLabel: "Secret access key",
638
638
  s3RegionLabel: "Region",
639
639
  s3PublicBaseUrlLabel: "Public base URL",
640
+ s3UrlInvalid:
641
+ "Must be a valid URL (e.g. https://s3.us-east-1.amazonaws.com)",
642
+ s3BucketInvalid:
643
+ "Bucket name must be 3–63 lowercase letters, numbers, or hyphens",
644
+ s3RegionInvalid: 'Must be a valid region (e.g. us-east-1) or "auto"',
640
645
  apiSetup: "AI setup",
641
646
  apiSetupDescription:
642
647
  "Connect AI with Builder.io free credits or your own LLM keys.",
@@ -651,6 +656,8 @@ All notable user-facing changes to Clips are documented here. Open it any time f
651
656
  providerKeysSet: "{{count}} set",
652
657
  checkingProviderKeys: "Checking provider keys…",
653
658
  keySet: "Set",
659
+ keyCleared: "Storage credentials cleared",
660
+ clearAllS3: "Clear credentials",
654
661
  replaceKey: "Replace key…",
655
662
  pasteProviderKey: "Paste a provider key first.",
656
663
  apiKeySaved: "API key saved",
@@ -654,6 +654,11 @@ Todos los cambios visibles para los usuarios de Clips se documentan aquí. Puede
654
654
  s3SecretAccessKeyLabel: "Clave de acceso secreta",
655
655
  s3RegionLabel: "Región",
656
656
  s3PublicBaseUrlLabel: "URL base pública",
657
+ s3UrlInvalid:
658
+ "Debe ser una URL válida (p. ej. https://s3.us-east-1.amazonaws.com)",
659
+ s3BucketInvalid:
660
+ "El nombre del bucket debe tener 3–63 letras minúsculas, números o guiones",
661
+ s3RegionInvalid: 'Debe ser una región válida (p. ej. us-east-1) o "auto"',
657
662
  apiSetup: "Configuración de IA",
658
663
  apiSetupDescription:
659
664
  "Conecta IA con créditos gratis de Builder.io o tus propias claves LLM.",
@@ -668,6 +673,8 @@ Todos los cambios visibles para los usuarios de Clips se documentan aquí. Puede
668
673
  providerKeysSet: "{{count}} configuradas",
669
674
  checkingProviderKeys: "Comprobando claves de proveedor…",
670
675
  keySet: "Configurada",
676
+ keyCleared: "Credenciales de almacenamiento borradas",
677
+ clearAllS3: "Borrar credenciales",
671
678
  replaceKey: "Reemplazar clave…",
672
679
  pasteProviderKey: "Pega primero una clave de proveedor.",
673
680
  apiKeySaved: "Clave de API guardada",
@@ -655,6 +655,11 @@ Tous les changements visibles par les utilisateurs de Clips sont documentés ici
655
655
  s3SecretAccessKeyLabel: "Clé d’accès secrète",
656
656
  s3RegionLabel: "Région",
657
657
  s3PublicBaseUrlLabel: "URL de base publique",
658
+ s3UrlInvalid:
659
+ "Doit être une URL valide (ex. https://s3.us-east-1.amazonaws.com)",
660
+ s3BucketInvalid:
661
+ "Le nom du bucket doit contenir 3–63 lettres minuscules, chiffres ou tirets",
662
+ s3RegionInvalid: 'Doit être une région valide (ex. us-east-1) ou "auto"',
658
663
  apiSetup: "Configuration IA",
659
664
  apiSetupDescription:
660
665
  "Connectez l’IA avec les crédits gratuits Builder.io ou vos propres clés LLM.",
@@ -669,6 +674,8 @@ Tous les changements visibles par les utilisateurs de Clips sont documentés ici
669
674
  providerKeysSet: "{{count}} définies",
670
675
  checkingProviderKeys: "Vérification des clés fournisseur…",
671
676
  keySet: "Définie",
677
+ keyCleared: "Identifiants de stockage effacés",
678
+ clearAllS3: "Effacer les identifiants",
672
679
  replaceKey: "Remplacer la clé…",
673
680
  pasteProviderKey: "Collez d’abord une clé fournisseur.",
674
681
  apiKeySaved: "Clé API enregistrée",
@@ -635,6 +635,10 @@ Clips में उपयोगकर्ताओं को दिखने व
635
635
  s3SecretAccessKeyLabel: "गुप्त एक्सेस कुंजी",
636
636
  s3RegionLabel: "क्षेत्र",
637
637
  s3PublicBaseUrlLabel: "सार्वजनिक बेस URL",
638
+ s3UrlInvalid:
639
+ "एक मान्य URL होना चाहिए (उदा. https://s3.us-east-1.amazonaws.com)",
640
+ s3BucketInvalid: "बकेट नाम 3–63 लोअरकेस अक्षर, अंक या हाइफ़न होने चाहिए",
641
+ s3RegionInvalid: 'एक मान्य क्षेत्र (उदा. us-east-1) या "auto" होना चाहिए',
638
642
  apiSetup: "AI सेटअप",
639
643
  apiSetupDescription:
640
644
  "Builder.io मुफ्त क्रेडिट या अपनी LLM keys के साथ AI कनेक्ट करें.",
@@ -649,6 +653,8 @@ Clips में उपयोगकर्ताओं को दिखने व
649
653
  providerKeysSet: "{{count}} सेट",
650
654
  checkingProviderKeys: "प्रोवाइडर कीज़ जाँची जा रही हैं…",
651
655
  keySet: "सेट",
656
+ keyCleared: "स्टोरेज क्रेडेंशियल साफ़ किए गए",
657
+ clearAllS3: "क्रेडेंशियल साफ़ करें",
652
658
  replaceKey: "की बदलें…",
653
659
  pasteProviderKey: "पहले प्रोवाइडर की पेस्ट करें।",
654
660
  apiKeySaved: "API की सहेजी गई",
@@ -649,6 +649,12 @@ Clips のユーザー向けの主な変更はここに記録されます。コ
649
649
  s3SecretAccessKeyLabel: "シークレットアクセスキー",
650
650
  s3RegionLabel: "リージョン",
651
651
  s3PublicBaseUrlLabel: "公開ベース URL",
652
+ s3UrlInvalid:
653
+ "有効な URL を入力してください(例: https://s3.us-east-1.amazonaws.com)",
654
+ s3BucketInvalid:
655
+ "バケット名は 3〜63 文字の小文字、数字、またはハイフンで指定してください",
656
+ s3RegionInvalid:
657
+ '有効なリージョン(例: us-east-1)または "auto" を入力してください',
652
658
  apiSetup: "AI 設定",
653
659
  apiSetupDescription:
654
660
  "Builder.io の無料クレジット、または自分の LLM キーで AI を接続します。",
@@ -663,6 +669,8 @@ Clips のユーザー向けの主な変更はここに記録されます。コ
663
669
  providerKeysSet: "{{count}} 件設定済み",
664
670
  checkingProviderKeys: "プロバイダーキーを確認中…",
665
671
  keySet: "設定済み",
672
+ keyCleared: "ストレージ認証情報をクリアしました",
673
+ clearAllS3: "認証情報をクリア",
666
674
  replaceKey: "キーを置換…",
667
675
  pasteProviderKey: "先にプロバイダーキーを貼り付けてください。",
668
676
  apiKeySaved: "API キーを保存しました",
@@ -641,6 +641,11 @@ Clips의 모든 사용자 대상 변경 사항은 여기에 기록됩니다. 명
641
641
  s3SecretAccessKeyLabel: "비밀 액세스 키",
642
642
  s3RegionLabel: "리전",
643
643
  s3PublicBaseUrlLabel: "공개 기본 URL",
644
+ s3UrlInvalid:
645
+ "유효한 URL이어야 합니다 (예: https://s3.us-east-1.amazonaws.com)",
646
+ s3BucketInvalid:
647
+ "버킷 이름은 3–63자의 소문자, 숫자 또는 하이픈이어야 합니다",
648
+ s3RegionInvalid: '유효한 리전(예: us-east-1) 또는 "auto"이어야 합니다',
644
649
  apiSetup: "AI 설정",
645
650
  apiSetupDescription:
646
651
  "Builder.io 무료 크레딧 또는 직접 보유한 LLM 키로 AI를 연결하세요.",
@@ -655,6 +660,8 @@ Clips의 모든 사용자 대상 변경 사항은 여기에 기록됩니다. 명
655
660
  providerKeysSet: "{{count}}개 설정됨",
656
661
  checkingProviderKeys: "제공자 키 확인 중…",
657
662
  keySet: "설정됨",
663
+ keyCleared: "스토리지 자격 증명이 삭제되었습니다",
664
+ clearAllS3: "자격 증명 삭제",
658
665
  replaceKey: "키 바꾸기…",
659
666
  pasteProviderKey: "먼저 제공자 키를 붙여넣으세요.",
660
667
  apiKeySaved: "API 키가 저장됨",
@@ -652,6 +652,11 @@ Todas as mudanças visíveis para usuários do Clips são documentadas aqui. Voc
652
652
  s3SecretAccessKeyLabel: "Chave de acesso secreta",
653
653
  s3RegionLabel: "Região",
654
654
  s3PublicBaseUrlLabel: "URL base pública",
655
+ s3UrlInvalid:
656
+ "Deve ser uma URL válida (ex.: https://s3.us-east-1.amazonaws.com)",
657
+ s3BucketInvalid:
658
+ "O nome do bucket deve ter 3–63 letras minúsculas, números ou hifens",
659
+ s3RegionInvalid: 'Deve ser uma região válida (ex.: us-east-1) ou "auto"',
655
660
  apiSetup: "Configuração de IA",
656
661
  apiSetupDescription:
657
662
  "Conecte IA com créditos grátis da Builder.io ou suas próprias chaves LLM.",
@@ -666,6 +671,8 @@ Todas as mudanças visíveis para usuários do Clips são documentadas aqui. Voc
666
671
  providerKeysSet: "{{count}} configuradas",
667
672
  checkingProviderKeys: "Verificando chaves de provedor…",
668
673
  keySet: "Configurada",
674
+ keyCleared: "Credenciais de armazenamento limpas",
675
+ clearAllS3: "Limpar credenciais",
669
676
  replaceKey: "Substituir chave…",
670
677
  pasteProviderKey: "Cole primeiro uma chave de provedor.",
671
678
  apiKeySaved: "Chave de API salva",
@@ -614,6 +614,9 @@ Clips 中所有面向用户的重要更改都会记录在这里。你可以随
614
614
  s3SecretAccessKeyLabel: "秘密访问密钥",
615
615
  s3RegionLabel: "区域",
616
616
  s3PublicBaseUrlLabel: "公共基础 URL",
617
+ s3UrlInvalid: "必须是有效的 URL(例如 https://s3.us-east-1.amazonaws.com)",
618
+ s3BucketInvalid: "存储桶名称必须为 3–63 个小写字母、数字或连字符",
619
+ s3RegionInvalid: '必须是有效的区域(例如 us-east-1)或 "auto"',
617
620
  apiSetup: "AI 设置",
618
621
  apiSetupDescription: "使用 Builder.io 免费额度或你自己的 LLM 密钥连接 AI。",
619
622
  builderEasySetup: "Builder.io 免费额度",
@@ -626,6 +629,8 @@ Clips 中所有面向用户的重要更改都会记录在这里。你可以随
626
629
  providerKeysSet: "已设置 {{count}} 个",
627
630
  checkingProviderKeys: "正在检查提供方密钥…",
628
631
  keySet: "已设置",
632
+ keyCleared: "存储凭证已清除",
633
+ clearAllS3: "清除凭证",
629
634
  replaceKey: "替换密钥…",
630
635
  pasteProviderKey: "请先粘贴提供方密钥。",
631
636
  apiKeySaved: "API 密钥已保存",
@@ -607,6 +607,9 @@ const messages = {
607
607
  s3SecretAccessKeyLabel: "秘密存取金鑰",
608
608
  s3RegionLabel: "區域",
609
609
  s3PublicBaseUrlLabel: "公開基礎 URL",
610
+ s3UrlInvalid: "必須是有效的 URL(例如 https://s3.us-east-1.amazonaws.com)",
611
+ s3BucketInvalid: "儲存貯體名稱必須為 3–63 個小寫字母、數字或連字號",
612
+ s3RegionInvalid: '必須是有效的區域(例如 us-east-1)或 "auto"',
610
613
  apiSetup: "AI 設定",
611
614
  apiSetupDescription: "使用 Builder.io 免費額度或您自己的 LLM 金鑰連線 AI。",
612
615
  builderEasySetup: "Builder.io 免費額度",
@@ -619,6 +622,8 @@ const messages = {
619
622
  providerKeysSet: "已設定 {{count}} 個",
620
623
  checkingProviderKeys: "正在檢查提供方金鑰…",
621
624
  keySet: "已設定",
625
+ keyCleared: "儲存憑證已清除",
626
+ clearAllS3: "清除憑證",
622
627
  replaceKey: "替換金鑰…",
623
628
  pasteProviderKey: "請先貼上提供方金鑰。",
624
629
  apiKeySaved: "API 金鑰已儲存",