@agent-native/core 0.159.3 → 0.160.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 (90) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/assets/server/lib/generation.ts +10 -6
  3. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +18 -4
  4. package/corpus/templates/calendar/actions/create-event.ts +4 -2
  5. package/corpus/templates/calendar/actions/event-action-helpers.ts +56 -9
  6. package/corpus/templates/calendar/actions/update-event.ts +1 -0
  7. package/corpus/templates/calendar/agent-native.config.ts +1 -0
  8. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +8 -2
  9. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +102 -68
  10. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +150 -49
  11. package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +57 -23
  12. package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +4 -1
  13. package/corpus/templates/calendar/app/components/calendar/TimezoneSwitchDialog.tsx +70 -0
  14. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +127 -88
  15. package/corpus/templates/calendar/app/components/calendar/WorkingLocationEditor.tsx +59 -25
  16. package/corpus/templates/calendar/app/hooks/use-event-drag.ts +55 -18
  17. package/corpus/templates/calendar/app/i18n-data.ts +60 -0
  18. package/corpus/templates/calendar/app/lib/all-day-layout.ts +16 -8
  19. package/corpus/templates/calendar/app/lib/calendar-drafts.ts +56 -0
  20. package/corpus/templates/calendar/app/lib/calendar-timezone.ts +351 -0
  21. package/corpus/templates/calendar/app/lib/event-layout.ts +14 -8
  22. package/corpus/templates/calendar/app/lib/out-of-office.ts +29 -61
  23. package/corpus/templates/calendar/app/lib/working-location.ts +34 -2
  24. package/corpus/templates/calendar/app/pages/CalendarView.tsx +317 -139
  25. package/corpus/templates/calendar/changelog/2026-08-13-calendar-stays-pinned-to-the-saved-timezone-and-asks-before-.md +6 -0
  26. package/corpus/templates/calendar/changelog/2026-08-13-working-locations-can-be-added-from-calendar-days-with-a-ful.md +6 -0
  27. package/corpus/templates/calendar/changelog/2026-08-14-choosing-office-or-other-when-adding-a-working-location-creates.md +6 -0
  28. package/corpus/templates/calendar/changelog/2026-08-14-creating-an-other-working-location-now-keeps-the-custom-name.md +6 -0
  29. package/corpus/templates/calendar/changelog/2026-08-14-timed-working-locations-keep-home-office-or-custom-titles.md +6 -0
  30. package/corpus/templates/calendar/changelog/2026-08-14-timed-working-locations-now-keep-a-home-office-or-custom-title.md +6 -0
  31. package/corpus/templates/calendar/changelog/2026-08-14-turning-a-midnight-ending-working-location-back-to-all-day.md +6 -0
  32. package/corpus/templates/calendar/server/lib/google-calendar.ts +22 -1
  33. package/dist/agent/engine/builder-engine.d.ts +16 -2
  34. package/dist/agent/engine/builder-engine.js +178 -115
  35. package/dist/agent/engine/builtin.js +13 -4
  36. package/dist/agent/engine/credential-errors.d.ts +23 -0
  37. package/dist/agent/engine/credential-errors.js +19 -0
  38. package/dist/agent/engine/error-detail.d.ts +15 -0
  39. package/dist/agent/engine/error-detail.js +29 -0
  40. package/dist/agent/engine/registry.d.ts +14 -3
  41. package/dist/agent/engine/registry.js +106 -61
  42. package/dist/agent/engine/types.d.ts +16 -0
  43. package/dist/agent/engine/types.js +9 -0
  44. package/dist/agent/production-agent.d.ts +7 -0
  45. package/dist/agent/production-agent.js +58 -20
  46. package/dist/agent/run-manager.js +30 -0
  47. package/dist/agent/thread-data-builder.js +4 -0
  48. package/dist/agent/types.d.ts +9 -0
  49. package/dist/client/agent-chat-adapter.js +46 -15
  50. package/dist/client/chat-first/app-pane.js +1 -1
  51. package/dist/client/chat-first/copy.js +1 -0
  52. package/dist/client/error-format.js +25 -2
  53. package/dist/client/sse-event-processor.d.ts +2 -0
  54. package/dist/client/sse-event-processor.js +12 -1
  55. package/dist/feature-flags/index.d.ts +0 -2
  56. package/dist/feature-flags/index.js +3 -2
  57. package/dist/feature-flags/server.d.ts +2 -0
  58. package/dist/feature-flags/server.js +2 -0
  59. package/dist/index.d.ts +2 -1
  60. package/dist/index.js +2 -1
  61. package/dist/jobs/background-automation-runner.js +9 -15
  62. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  63. package/dist/notifications/routes.d.ts +3 -3
  64. package/dist/observability/routes.d.ts +2 -2
  65. package/dist/progress/routes.d.ts +1 -1
  66. package/dist/provider-api/actions/custom-provider-registration.d.ts +2 -2
  67. package/dist/resources/handlers.d.ts +1 -1
  68. package/dist/secrets/register-framework-secrets.js +25 -0
  69. package/dist/secrets/routes.d.ts +6 -6
  70. package/dist/server/agent-chat-plugin.js +9 -2
  71. package/dist/server/auth.js +10 -0
  72. package/dist/server/credential-provider.d.ts +80 -0
  73. package/dist/server/credential-provider.js +168 -1
  74. package/dist/server/google-realtime-session.d.ts +1 -1
  75. package/dist/server/google-realtime-session.js +6 -6
  76. package/dist/server/index.d.ts +2 -2
  77. package/dist/server/index.js +7 -2
  78. package/dist/server/onboarding-html.js +16 -257
  79. package/dist/server/realtime-token.d.ts +1 -1
  80. package/dist/server/realtime-voice.js +12 -6
  81. package/dist/server/transcribe-voice.d.ts +1 -1
  82. package/dist/server/transcribe-voice.js +18 -16
  83. package/dist/shared/auth-copy.d.ts +34 -0
  84. package/dist/shared/auth-copy.js +293 -0
  85. package/dist/shared/index.d.ts +1 -0
  86. package/dist/shared/index.js +1 -0
  87. package/dist/transcription/builder-transcription.js +8 -4
  88. package/docs/content/actions-advanced.mdx +1 -1
  89. package/docs/content/portal.mdx +21 -8
  90. package/package.json +3 -1
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
26
26
  expiresAt?: undefined;
27
27
  ttlSeconds?: undefined;
28
28
  } | {
29
- error?: undefined;
30
29
  token: string;
31
30
  expiresAt: string;
32
31
  ttlSeconds: number;
32
+ error?: undefined;
33
33
  }>>;
@@ -5,7 +5,7 @@ import { redactSensitiveFields, sanitizeToolErrorText, sanitizeToolErrorValue, }
5
5
  import { TOOL_SEARCH_ACTION_NAME } from "../agent/tool-search.js";
6
6
  import { parseAcceptLanguage } from "../localization/server.js";
7
7
  import { getSession } from "./auth.js";
8
- import { getBuilderGatewayBaseUrl, resolveBuilderCredentials, resolveSecret, } from "./credential-provider.js";
8
+ import { gatewayLaneUnavailableMessage, getBuilderGatewayBaseUrl, resolveBuilderGatewayCredentials, resolveSecret, } from "./credential-provider.js";
9
9
  import { getH3App } from "./framework-request-handler.js";
10
10
  import { runWithRequestContext } from "./request-context.js";
11
11
  import { isSameOriginRequest } from "./request-origin.js";
@@ -379,7 +379,7 @@ function createSessionHandler(tools, options) {
379
379
  ? { browserTabId: auth.browserTabId }
380
380
  : undefined,
381
381
  }, async () => {
382
- const builderCredentials = await resolveBuilderCredentials();
382
+ const builderCredentials = await resolveBuilderGatewayCredentials();
383
383
  const builderConfigured = Boolean(builderCredentials.privateKey?.trim() &&
384
384
  builderCredentials.publicKey?.trim());
385
385
  const apiKey = builderConfigured
@@ -388,7 +388,7 @@ function createSessionHandler(tools, options) {
388
388
  if (!builderConfigured && !apiKey) {
389
389
  setResponseStatus(event, 409);
390
390
  return {
391
- error: "Connect Builder (free tier available) or configure an OpenAI API key to use realtime voice.",
391
+ error: gatewayLaneUnavailableMessage("Connect Builder (free tier available) or configure an OpenAI API key to use realtime voice."),
392
392
  code: "realtime_voice_setup_required",
393
393
  };
394
394
  }
@@ -466,22 +466,28 @@ function createSessionHandler(tools, options) {
466
466
  setResponseStatus(event, 502);
467
467
  return {
468
468
  error: builderConfigured
469
- ? "Could not reach the Builder realtime voice gateway"
469
+ ? gatewayLaneUnavailableMessage("Could not reach the Builder realtime voice gateway")
470
470
  : "Could not reach the OpenAI Realtime API",
471
471
  };
472
472
  }
473
473
  if (!upstream.ok) {
474
474
  const detail = await safeOpenAiErrorDetail(upstream, builderConfigured ? builderCredentials.privateKey : apiKey);
475
475
  setResponseStatus(event, builderConfigured ? upstream.status : 502);
476
+ const rejection = `${builderConfigured ? "Builder" : "OpenAI"} rejected the realtime session (${upstream.status})${detail ? `: ${detail}` : ""}`;
476
477
  return {
477
- error: `${builderConfigured ? "Builder" : "OpenAI"} rejected the realtime session (${upstream.status})${detail ? `: ${detail}` : ""}`,
478
+ error: builderConfigured
479
+ ? gatewayLaneUnavailableMessage(rejection)
480
+ : rejection,
478
481
  };
479
482
  }
480
483
  const answerSdp = await upstream.text().catch(() => "");
481
484
  if (!answerSdp.trim()) {
482
485
  setResponseStatus(event, 502);
486
+ const emptyAnswer = `${builderConfigured ? "Builder" : "OpenAI"} returned an empty realtime session answer`;
483
487
  return {
484
- error: `${builderConfigured ? "Builder" : "OpenAI"} returned an empty realtime session answer`,
488
+ error: builderConfigured
489
+ ? gatewayLaneUnavailableMessage(emptyAnswer)
490
+ : emptyAnswer,
485
491
  };
486
492
  }
487
493
  setResponseStatus(event, upstream.status);
@@ -20,6 +20,6 @@ export declare function createTranscribeVoiceHandler(): import("h3").EventHandle
20
20
  error: string;
21
21
  text?: undefined;
22
22
  } | {
23
- error?: undefined;
24
23
  text: string;
24
+ error?: undefined;
25
25
  }>>;
@@ -23,7 +23,7 @@ import { getOrgContext } from "../org/context.js";
23
23
  import { transcribeWithBuilder } from "../transcription/builder-transcription.js";
24
24
  import { applyVoiceContextReplacements, buildVoiceGuidanceBlock, parseVoiceContextPack, voiceContextTermsOnly, } from "../voice/index.js";
25
25
  import { getSession } from "./auth.js";
26
- import { resolveHasBuilderPrivateKey, resolveSecret, } from "./credential-provider.js";
26
+ import { gatewayLaneUnavailableMessage, resolveHasBuilderGatewayCredential, resolveSecret, } from "./credential-provider.js";
27
27
  import { runWithRequestContext } from "./request-context.js";
28
28
  import { isSameOriginRequest } from "./request-origin.js";
29
29
  const WHISPER_URL = "https://api.openai.com/v1/audio/transcriptions";
@@ -149,7 +149,7 @@ export function createTranscribeVoiceHandler() {
149
149
  const withRequestContext = async (fn) => requestContext.userEmail
150
150
  ? runWithRequestContext(requestContext, fn)
151
151
  : fn();
152
- const hasBuilderPrivateKey = async () => withRequestContext(() => resolveHasBuilderPrivateKey());
152
+ const hasBuilderCredential = async () => withRequestContext(() => resolveHasBuilderGatewayCredential());
153
153
  const transcribeWithBuilderForRequest = (opts) => withRequestContext(() => transcribeWithBuilder(opts));
154
154
  const sessionId = session?.email ?? "local";
155
155
  let providerPref;
@@ -212,7 +212,7 @@ export function createTranscribeVoiceHandler() {
212
212
  instructions: voiceGuidance,
213
213
  contextPack: voiceContext,
214
214
  providerPref,
215
- hasBuilderPrivateKey,
215
+ hasBuilderCredential,
216
216
  withRequestContext,
217
217
  resolveApiKey,
218
218
  clientAbortSignal: clientAbort,
@@ -266,10 +266,10 @@ export function createTranscribeVoiceHandler() {
266
266
  const label = providerPref === "builder-gemini"
267
267
  ? "Builder Gemini Flash-Lite"
268
268
  : "Builder";
269
- if (!(await hasBuilderPrivateKey())) {
269
+ if (!(await hasBuilderCredential())) {
270
270
  setResponseStatus(event, 400);
271
271
  return {
272
- error: `${label} is selected but Builder.io is not connected. Connect Builder.io (free tier available) in Settings, or change the provider preference.`,
272
+ error: gatewayLaneUnavailableMessage(`${label} is selected but Builder.io is not connected. Connect Builder.io (free tier available) in Settings, or change the provider preference.`),
273
273
  };
274
274
  }
275
275
  try {
@@ -288,10 +288,12 @@ export function createTranscribeVoiceHandler() {
288
288
  const message = err?.message ?? String(err);
289
289
  if (message.includes("credits exhausted")) {
290
290
  setResponseStatus(event, 402);
291
- return { error: message };
291
+ return { error: gatewayLaneUnavailableMessage(message) };
292
292
  }
293
293
  setResponseStatus(event, 502);
294
- return { error: `${label} transcription failed: ${message}` };
294
+ return {
295
+ error: gatewayLaneUnavailableMessage(`${label} transcription failed: ${message}`),
296
+ };
295
297
  }
296
298
  }
297
299
  if (providerPref === "groq") {
@@ -322,7 +324,7 @@ export function createTranscribeVoiceHandler() {
322
324
  // ── Builder Gemini Flash-Lite path ─────────────────────────────────
323
325
  // First-priority in auto mode when Builder is connected. This lets users
324
326
  // try Gemini 3.1 Flash-Lite without bringing their own Google key.
325
- if (providerPref !== "openai" && (await hasBuilderPrivateKey())) {
327
+ if (providerPref !== "openai" && (await hasBuilderCredential())) {
326
328
  try {
327
329
  const result = await transcribeWithBuilderForRequest({
328
330
  audioBytes,
@@ -339,7 +341,7 @@ export function createTranscribeVoiceHandler() {
339
341
  // a specific upgrade prompt.
340
342
  if (message.includes("credits exhausted")) {
341
343
  setResponseStatus(event, 402);
342
- return { error: message };
344
+ return { error: gatewayLaneUnavailableMessage(message) };
343
345
  }
344
346
  builderError = message;
345
347
  }
@@ -401,9 +403,9 @@ export function createTranscribeVoiceHandler() {
401
403
  if (!provider) {
402
404
  setResponseStatus(event, builderError ? 502 : 400);
403
405
  return {
404
- error: builderError
406
+ error: gatewayLaneUnavailableMessage(builderError
405
407
  ? `Builder transcription failed: ${builderError}. Add GEMINI_API_KEY, GROQ_API_KEY, or OPENAI_API_KEY in Settings → API Keys to enable a fallback provider.`
406
- : "No voice transcription provider configured. Connect Builder.io (free tier available) or add GEMINI_API_KEY / GROQ_API_KEY / OPENAI_API_KEY in Settings → API Keys.",
408
+ : "No voice transcription provider configured. Connect Builder.io (free tier available) or add GEMINI_API_KEY / GROQ_API_KEY / OPENAI_API_KEY in Settings → API Keys."),
407
409
  };
408
410
  }
409
411
  return await callWhisperCompat({
@@ -477,7 +479,7 @@ async function callWhisperCompat({ event, provider, audioBytes, mime, language,
477
479
  clearTimeout(timeout);
478
480
  }
479
481
  }
480
- async function cleanupTranscriptText({ event, text, instructions, contextPack, providerPref, hasBuilderPrivateKey, withRequestContext, resolveApiKey, clientAbortSignal, }) {
482
+ async function cleanupTranscriptText({ event, text, instructions, contextPack, providerPref, hasBuilderCredential, withRequestContext, resolveApiKey, clientAbortSignal, }) {
481
483
  const original = text.trim();
482
484
  if (!original)
483
485
  return { text: "" };
@@ -486,10 +488,10 @@ async function cleanupTranscriptText({ event, text, instructions, contextPack, p
486
488
  return { text: finalizeText(original) };
487
489
  }
488
490
  if (providerPref === "builder" || providerPref === "builder-gemini") {
489
- if (!(await hasBuilderPrivateKey())) {
491
+ if (!(await hasBuilderCredential())) {
490
492
  setResponseStatus(event, 400);
491
493
  return {
492
- error: "Builder.io cleanup is selected but Builder.io is not connected. Connect Builder.io (free tier available) in Settings, or change the provider preference.",
494
+ error: gatewayLaneUnavailableMessage("Builder.io cleanup is selected but Builder.io is not connected. Connect Builder.io (free tier available) in Settings, or change the provider preference."),
493
495
  };
494
496
  }
495
497
  try {
@@ -506,7 +508,7 @@ async function cleanupTranscriptText({ event, text, instructions, contextPack, p
506
508
  catch (err) {
507
509
  setResponseStatus(event, 502);
508
510
  return {
509
- error: `Builder.io cleanup failed: ${err?.message ?? String(err)}`,
511
+ error: gatewayLaneUnavailableMessage(`Builder.io cleanup failed: ${err?.message ?? String(err)}`),
510
512
  };
511
513
  }
512
514
  }
@@ -560,7 +562,7 @@ async function cleanupTranscriptText({ event, text, instructions, contextPack, p
560
562
  };
561
563
  }
562
564
  }
563
- if (await hasBuilderPrivateKey()) {
565
+ if (await hasBuilderCredential()) {
564
566
  try {
565
567
  const cleaned = await withRequestContext(() => cleanupWithBuilder({ text: original, instructions, clientAbortSignal }));
566
568
  if (cleaned)
@@ -0,0 +1,34 @@
1
+ import { type LocaleCode } from "../localization/shared.js";
2
+ /**
3
+ * The native sign-in surfaces use the same copy keys as the hosted onboarding
4
+ * form. Keep this browser-safe subset here so Electron and mobile do not load
5
+ * the server HTML renderer or grow a second auth vocabulary.
6
+ */
7
+ export interface NativeAuthCopy {
8
+ googleButton: string;
9
+ dividerOr: string;
10
+ email: string;
11
+ emailPlaceholder: string;
12
+ password: string;
13
+ confirmPassword: string;
14
+ enterPasswordPlaceholder: string;
15
+ forgotPassword: string;
16
+ sendMagicLink: string;
17
+ magicLinkSent: string;
18
+ magicLinkSentCopy: string;
19
+ magicLinkFailed: string;
20
+ usePasswordInstead: string;
21
+ backToMagicLink: string;
22
+ signIn: string;
23
+ back: string;
24
+ close: string;
25
+ sending: string;
26
+ signingIn: string;
27
+ invalidEmail: string;
28
+ invalidLogin: string;
29
+ failedToConnect: string;
30
+ googleNeverFinished: string;
31
+ checking: string;
32
+ }
33
+ export declare const NATIVE_AUTH_COPY: Record<LocaleCode, NativeAuthCopy>;
34
+ export declare function resolveNativeAuthCopy(locale: unknown): NativeAuthCopy;
@@ -0,0 +1,293 @@
1
+ import { DEFAULT_LOCALE, resolveLocaleFromCandidates, } from "../localization/shared.js";
2
+ export const NATIVE_AUTH_COPY = {
3
+ "en-US": {
4
+ googleButton: "Sign in with Google",
5
+ dividerOr: "or",
6
+ email: "Email",
7
+ emailPlaceholder: "you@example.com",
8
+ password: "Password",
9
+ confirmPassword: "Confirm password",
10
+ enterPasswordPlaceholder: "Enter password",
11
+ forgotPassword: "Forgot password?",
12
+ sendMagicLink: "Continue",
13
+ magicLinkSent: "Check your email",
14
+ magicLinkSentCopy: "We sent a secure sign-in link to",
15
+ magicLinkFailed: "We couldn't send a sign-in link. Check your email and try again.",
16
+ usePasswordInstead: "Use a password instead",
17
+ backToMagicLink: "Use a sign-in link instead",
18
+ signIn: "Sign in",
19
+ back: "Back",
20
+ close: "Close",
21
+ sending: "Sending...",
22
+ signingIn: "Signing in…",
23
+ invalidEmail: "Enter a valid email address, like you@example.com.",
24
+ invalidLogin: "The email or password is incorrect.",
25
+ failedToConnect: "We couldn't connect. Please try again.",
26
+ googleNeverFinished: "Google sign-in did not finish. Check the Google OAuth redirect URI and server logs for [agent-native][google-oauth].",
27
+ checking: "Checking...",
28
+ },
29
+ "zh-CN": {
30
+ googleButton: "使用 Google 登录",
31
+ dividerOr: "或",
32
+ email: "电子邮箱",
33
+ emailPlaceholder: "you@example.com",
34
+ password: "密码",
35
+ confirmPassword: "确认密码",
36
+ enterPasswordPlaceholder: "输入密码",
37
+ forgotPassword: "忘记密码?",
38
+ sendMagicLink: "继续",
39
+ magicLinkSent: "检查你的邮箱",
40
+ magicLinkSentCopy: "我们已向以下邮箱发送安全登录链接:",
41
+ magicLinkFailed: "无法发送登录链接。",
42
+ usePasswordInstead: "改用密码",
43
+ backToMagicLink: "改用登录链接",
44
+ signIn: "登录",
45
+ back: "返回",
46
+ close: "关闭",
47
+ sending: "正在发送...",
48
+ signingIn: "正在登录…",
49
+ invalidEmail: "请输入有效邮箱地址,例如 you@example.com。",
50
+ invalidLogin: "邮箱或密码无效",
51
+ failedToConnect: "连接失败。请重试。",
52
+ googleNeverFinished: "Google 登录未完成。请检查 Google OAuth 重定向 URI 和服务器日志中的 [agent-native][google-oauth]。",
53
+ checking: "正在检查...",
54
+ },
55
+ "zh-TW": {
56
+ googleButton: "使用 Google 登入",
57
+ dividerOr: "或",
58
+ email: "電子郵件",
59
+ emailPlaceholder: "you@example.com",
60
+ password: "密碼",
61
+ confirmPassword: "確認密碼",
62
+ enterPasswordPlaceholder: "輸入密碼",
63
+ forgotPassword: "忘記密碼?",
64
+ sendMagicLink: "繼續",
65
+ magicLinkSent: "檢查你的電子郵件",
66
+ magicLinkSentCopy: "我們已向以下電子郵件寄送安全登入連結:",
67
+ magicLinkFailed: "無法寄送登入連結。",
68
+ usePasswordInstead: "改用密碼",
69
+ backToMagicLink: "改用登入連結",
70
+ signIn: "登入",
71
+ back: "返回",
72
+ close: "關閉",
73
+ sending: "正在寄送...",
74
+ signingIn: "正在登入...",
75
+ invalidEmail: "請輸入有效的電子郵件地址,例如 you@example.com。",
76
+ invalidLogin: "電子郵件或密碼無效",
77
+ failedToConnect: "連線失敗。請重試。",
78
+ googleNeverFinished: "Google 登入未完成。請檢查 Google OAuth 重新導向 URI,以及伺服器記錄中的 [agent-native][google-oauth]。",
79
+ checking: "正在檢查...",
80
+ },
81
+ "es-ES": {
82
+ googleButton: "Iniciar sesión con Google",
83
+ dividerOr: "o",
84
+ email: "Email",
85
+ emailPlaceholder: "you@example.com",
86
+ password: "Contraseña",
87
+ confirmPassword: "Confirmar contraseña",
88
+ enterPasswordPlaceholder: "Introduce la contraseña",
89
+ forgotPassword: "¿Olvidaste tu contraseña?",
90
+ sendMagicLink: "Continuar",
91
+ magicLinkSent: "Revisa tu email",
92
+ magicLinkSentCopy: "Enviamos un enlace seguro a",
93
+ magicLinkFailed: "No se pudo enviar el enlace de inicio de sesión.",
94
+ usePasswordInstead: "Usar una contraseña",
95
+ backToMagicLink: "Usar un enlace de inicio de sesión",
96
+ signIn: "Iniciar sesión",
97
+ back: "Volver",
98
+ close: "Cerrar",
99
+ sending: "Enviando...",
100
+ signingIn: "Iniciando sesión…",
101
+ invalidEmail: "Introduce un email válido, como you@example.com.",
102
+ invalidLogin: "Email o contraseña no válidos",
103
+ failedToConnect: "No se pudo conectar. Inténtalo de nuevo.",
104
+ googleNeverFinished: "El inicio de sesión con Google no terminó. Comprueba el URI de redirección de Google OAuth y los logs del servidor para [agent-native][google-oauth].",
105
+ checking: "Comprobando...",
106
+ },
107
+ "fr-FR": {
108
+ googleButton: "Se connecter avec Google",
109
+ dividerOr: "ou",
110
+ email: "E-mail",
111
+ emailPlaceholder: "you@example.com",
112
+ password: "Mot de passe",
113
+ confirmPassword: "Confirmer le mot de passe",
114
+ enterPasswordPlaceholder: "Saisir le mot de passe",
115
+ forgotPassword: "Mot de passe oublié ?",
116
+ sendMagicLink: "Continuer",
117
+ magicLinkSent: "Vérifiez votre e-mail",
118
+ magicLinkSentCopy: "Nous avons envoyé un lien sécurisé à",
119
+ magicLinkFailed: "Impossible d'envoyer le lien de connexion.",
120
+ usePasswordInstead: "Utiliser un mot de passe",
121
+ backToMagicLink: "Utiliser un lien de connexion",
122
+ signIn: "Connexion",
123
+ back: "Retour",
124
+ close: "Fermer",
125
+ sending: "Envoi...",
126
+ signingIn: "Connexion…",
127
+ invalidEmail: "Saisissez une adresse e-mail valide, comme you@example.com.",
128
+ invalidLogin: "E-mail ou mot de passe invalide",
129
+ failedToConnect: "Connexion impossible. Veuillez réessayer.",
130
+ googleNeverFinished: "La connexion Google n'a pas abouti. Vérifiez l'URI de redirection Google OAuth et les logs serveur pour [agent-native][google-oauth].",
131
+ checking: "Vérification...",
132
+ },
133
+ "de-DE": {
134
+ googleButton: "Mit Google anmelden",
135
+ dividerOr: "oder",
136
+ email: "E-Mail",
137
+ emailPlaceholder: "you@example.com",
138
+ password: "Passwort",
139
+ confirmPassword: "Passwort bestätigen",
140
+ enterPasswordPlaceholder: "Passwort eingeben",
141
+ forgotPassword: "Passwort vergessen?",
142
+ sendMagicLink: "Weiter",
143
+ magicLinkSent: "Prüfe deine E-Mail",
144
+ magicLinkSentCopy: "Wir haben einen sicheren Anmeldelink gesendet an",
145
+ magicLinkFailed: "Anmeldelink konnte nicht gesendet werden.",
146
+ usePasswordInstead: "Stattdessen Passwort verwenden",
147
+ backToMagicLink: "Stattdessen Anmeldelink verwenden",
148
+ signIn: "Anmelden",
149
+ back: "Zurück",
150
+ close: "Schließen",
151
+ sending: "Senden...",
152
+ signingIn: "Anmeldung…",
153
+ invalidEmail: "Gib eine gültige E-Mail-Adresse ein, z. B. you@example.com.",
154
+ invalidLogin: "E-Mail oder Passwort ungültig",
155
+ failedToConnect: "Verbindung fehlgeschlagen. Bitte erneut versuchen.",
156
+ googleNeverFinished: "Die Google-Anmeldung wurde nicht abgeschlossen. Prüfe die Google-OAuth-Redirect-URI und Serverlogs für [agent-native][google-oauth].",
157
+ checking: "Prüfen...",
158
+ },
159
+ "ja-JP": {
160
+ googleButton: "Google でサインイン",
161
+ dividerOr: "または",
162
+ email: "メール",
163
+ emailPlaceholder: "you@example.com",
164
+ password: "パスワード",
165
+ confirmPassword: "パスワードを確認",
166
+ enterPasswordPlaceholder: "パスワードを入力",
167
+ forgotPassword: "パスワードをお忘れですか?",
168
+ sendMagicLink: "続行",
169
+ magicLinkSent: "メールを確認してください",
170
+ magicLinkSentCopy: "安全なサインインリンクを送信しました:",
171
+ magicLinkFailed: "サインインリンクを送信できませんでした。",
172
+ usePasswordInstead: "パスワードを使用する",
173
+ backToMagicLink: "サインインリンクを使用する",
174
+ signIn: "サインイン",
175
+ back: "戻る",
176
+ close: "閉じる",
177
+ sending: "送信中...",
178
+ signingIn: "サインイン中…",
179
+ invalidEmail: "you@example.com のような有効なメールアドレスを入力してください。",
180
+ invalidLogin: "メールまたはパスワードが正しくありません",
181
+ failedToConnect: "接続できませんでした。もう一度お試しください。",
182
+ googleNeverFinished: "Google サインインが完了しませんでした。Google OAuth リダイレクト URI と [agent-native][google-oauth] のサーバーログを確認してください。",
183
+ checking: "確認中...",
184
+ },
185
+ "ko-KR": {
186
+ googleButton: "Google로 로그인",
187
+ dividerOr: "또는",
188
+ email: "이메일",
189
+ emailPlaceholder: "you@example.com",
190
+ password: "비밀번호",
191
+ confirmPassword: "비밀번호 확인",
192
+ enterPasswordPlaceholder: "비밀번호 입력",
193
+ forgotPassword: "비밀번호를 잊으셨나요?",
194
+ sendMagicLink: "계속",
195
+ magicLinkSent: "이메일을 확인하세요",
196
+ magicLinkSentCopy: "안전한 로그인 링크를 보냈습니다:",
197
+ magicLinkFailed: "로그인 링크를 보낼 수 없습니다.",
198
+ usePasswordInstead: "비밀번호 사용",
199
+ backToMagicLink: "로그인 링크 사용",
200
+ signIn: "로그인",
201
+ back: "뒤로",
202
+ close: "닫기",
203
+ sending: "보내는 중...",
204
+ signingIn: "로그인 중…",
205
+ invalidEmail: "you@example.com 같은 올바른 이메일 주소를 입력하세요.",
206
+ invalidLogin: "이메일 또는 비밀번호가 올바르지 않습니다",
207
+ failedToConnect: "연결하지 못했습니다. 다시 시도하세요.",
208
+ googleNeverFinished: "Google 로그인이 완료되지 않았습니다. Google OAuth 리디렉션 URI와 [agent-native][google-oauth] 서버 로그를 확인하세요.",
209
+ checking: "확인 중...",
210
+ },
211
+ "pt-BR": {
212
+ googleButton: "Entrar com Google",
213
+ dividerOr: "ou",
214
+ email: "Email",
215
+ emailPlaceholder: "you@example.com",
216
+ password: "Senha",
217
+ confirmPassword: "Confirmar senha",
218
+ enterPasswordPlaceholder: "Digite a senha",
219
+ forgotPassword: "Esqueceu a senha?",
220
+ sendMagicLink: "Continuar",
221
+ magicLinkSent: "Confira seu email",
222
+ magicLinkSentCopy: "Enviamos um link seguro para",
223
+ magicLinkFailed: "Não foi possível enviar o link de acesso.",
224
+ usePasswordInstead: "Usar uma senha",
225
+ backToMagicLink: "Usar um link de acesso",
226
+ signIn: "Entrar",
227
+ back: "Voltar",
228
+ close: "Fechar",
229
+ sending: "Enviando...",
230
+ signingIn: "Entrando…",
231
+ invalidEmail: "Digite um email válido, como you@example.com.",
232
+ invalidLogin: "Email ou senha inválidos",
233
+ failedToConnect: "Não foi possível conectar. Tente novamente.",
234
+ googleNeverFinished: "O login com Google não terminou. Confira o URI de redirecionamento do Google OAuth e os logs do servidor para [agent-native][google-oauth].",
235
+ checking: "Verificando...",
236
+ },
237
+ "hi-IN": {
238
+ googleButton: "Google से साइन इन करें",
239
+ dividerOr: "या",
240
+ email: "ईमेल",
241
+ emailPlaceholder: "you@example.com",
242
+ password: "पासवर्ड",
243
+ confirmPassword: "पासवर्ड की पुष्टि करें",
244
+ enterPasswordPlaceholder: "पासवर्ड दर्ज करें",
245
+ forgotPassword: "पासवर्ड भूल गए?",
246
+ sendMagicLink: "जारी रखें",
247
+ magicLinkSent: "अपना ईमेल देखें",
248
+ magicLinkSentCopy: "हमने सुरक्षित साइन-इन लिंक यहां भेजा है:",
249
+ magicLinkFailed: "साइन-इन लिंक नहीं भेजा जा सका।",
250
+ usePasswordInstead: "पासवर्ड का उपयोग करें",
251
+ backToMagicLink: "साइन-इन लिंक का उपयोग करें",
252
+ signIn: "साइन इन",
253
+ back: "वापस",
254
+ close: "बंद करें",
255
+ sending: "भेजा जा रहा है...",
256
+ signingIn: "साइन इन हो रहा है…",
257
+ invalidEmail: "एक मान्य ईमेल पता दर्ज करें, जैसे you@example.com.",
258
+ invalidLogin: "ईमेल या पासवर्ड अमान्य है",
259
+ failedToConnect: "कनेक्ट नहीं हो सका। कृपया फिर कोशिश करें।",
260
+ googleNeverFinished: "Google साइन इन पूरा नहीं हुआ। Google OAuth redirect URI और [agent-native][google-oauth] के server logs देखें।",
261
+ checking: "जांच हो रही है...",
262
+ },
263
+ "ar-SA": {
264
+ googleButton: "تسجيل الدخول باستخدام Google",
265
+ dividerOr: "أو",
266
+ email: "البريد الإلكتروني",
267
+ emailPlaceholder: "you@example.com",
268
+ password: "كلمة المرور",
269
+ confirmPassword: "تأكيد كلمة المرور",
270
+ enterPasswordPlaceholder: "أدخل كلمة المرور",
271
+ forgotPassword: "هل نسيت كلمة المرور؟",
272
+ sendMagicLink: "متابعة",
273
+ magicLinkSent: "تحقق من بريدك الإلكتروني",
274
+ magicLinkSentCopy: "أرسلنا رابط تسجيل دخول آمنًا إلى",
275
+ magicLinkFailed: "تعذر إرسال رابط تسجيل الدخول.",
276
+ usePasswordInstead: "استخدام كلمة مرور بدلًا من ذلك",
277
+ backToMagicLink: "استخدام رابط تسجيل الدخول بدلًا من ذلك",
278
+ signIn: "تسجيل الدخول",
279
+ back: "رجوع",
280
+ close: "إغلاق",
281
+ sending: "جارٍ الإرسال...",
282
+ signingIn: "جارٍ تسجيل الدخول…",
283
+ invalidEmail: "أدخل بريدًا إلكترونيًا صالحًا، مثل you@example.com.",
284
+ invalidLogin: "البريد الإلكتروني أو كلمة المرور غير صحيحة",
285
+ failedToConnect: "تعذر الاتصال. حاول مرة أخرى.",
286
+ googleNeverFinished: "لم يكتمل تسجيل الدخول عبر Google. تحقق من URI إعادة التوجيه في Google OAuth وسجلات الخادم لـ [agent-native][google-oauth].",
287
+ checking: "جارٍ التحقق...",
288
+ },
289
+ };
290
+ export function resolveNativeAuthCopy(locale) {
291
+ const resolvedLocale = resolveLocaleFromCandidates([locale]);
292
+ return NATIVE_AUTH_COPY[resolvedLocale] ?? NATIVE_AUTH_COPY[DEFAULT_LOCALE];
293
+ }
@@ -13,6 +13,7 @@ export { DISPATCH_WORKSPACE_ROOT_REDIRECTS, RESERVED_WORKSPACE_APP_IDS, assertVa
13
13
  export { DEFAULT_WORKSPACE_APP_AUDIENCE, WORKSPACE_APP_AUDIENCES, normalizeWorkspaceAppAudience, normalizeWorkspaceAppPathList, workspaceAppAudienceFromEnv, workspaceAppAudienceFromPackageJson, workspaceAppRouteAccessFromEnv, workspaceAppRouteAccessFromPackageJson, type WorkspaceAppRouteAccess, type WorkspaceAppRouteAccessFromConfig, type WorkspaceAppAudience, } from "./workspace-app-audience.js";
14
14
  export { AGENT_NATIVE_OPEN_PATH, AGENT_SIDEBAR_QUERY_PARAM, AGENT_SIDEBAR_QUERY_VALUE_CLOSED, isAgentNativeOpenDeepLink, withCollapsedAgentSidebarParam, } from "./agent-sidebar-url.js";
15
15
  export { buildChatFirstAppCreationPrompt, titleFromChatFirstAppPrompt, type ChatFirstAppCreationPromptInput, type ChatFirstAppCreationResource, type ChatFirstAppCreationVaultAccessMode, } from "./chat-first-app-creation.js";
16
+ export { NATIVE_AUTH_COPY, resolveNativeAuthCopy, type NativeAuthCopy, } from "./auth-copy.js";
16
17
  export { createPollEngine, type PollEngineOptions, type PollEngineHandle, } from "./poll-engine.js";
17
18
  export { AGENT_NATIVE_DEFAULT_SOCIAL_IMAGE, AGENT_NATIVE_SOCIAL_IMAGE_CACHE_BUSTER, AGENT_NATIVE_SOCIAL_IMAGE_ALT, AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT, AGENT_NATIVE_SOCIAL_IMAGE_PATH, AGENT_NATIVE_SOCIAL_IMAGE_TYPE, AGENT_NATIVE_SOCIAL_IMAGE_WIDTH, defaultSocialImageMeta, withAgentNativeSocialImageCacheBuster, withDefaultSocialImage, type SocialMetaDescriptor, } from "./social-meta.js";
18
19
  export { EMBED_MODE_QUERY_PARAM, EMBED_SESSION_COOKIE, EMBED_START_PATH, EMBED_TOKEN_QUERY_PARAM, } from "./embed-auth.js";
@@ -13,6 +13,7 @@ export { DISPATCH_WORKSPACE_ROOT_REDIRECTS, RESERVED_WORKSPACE_APP_IDS, assertVa
13
13
  export { DEFAULT_WORKSPACE_APP_AUDIENCE, WORKSPACE_APP_AUDIENCES, normalizeWorkspaceAppAudience, normalizeWorkspaceAppPathList, workspaceAppAudienceFromEnv, workspaceAppAudienceFromPackageJson, workspaceAppRouteAccessFromEnv, workspaceAppRouteAccessFromPackageJson, } from "./workspace-app-audience.js";
14
14
  export { AGENT_NATIVE_OPEN_PATH, AGENT_SIDEBAR_QUERY_PARAM, AGENT_SIDEBAR_QUERY_VALUE_CLOSED, isAgentNativeOpenDeepLink, withCollapsedAgentSidebarParam, } from "./agent-sidebar-url.js";
15
15
  export { buildChatFirstAppCreationPrompt, titleFromChatFirstAppPrompt, } from "./chat-first-app-creation.js";
16
+ export { NATIVE_AUTH_COPY, resolveNativeAuthCopy, } from "./auth-copy.js";
16
17
  export { createPollEngine, } from "./poll-engine.js";
17
18
  export { AGENT_NATIVE_DEFAULT_SOCIAL_IMAGE, AGENT_NATIVE_SOCIAL_IMAGE_CACHE_BUSTER, AGENT_NATIVE_SOCIAL_IMAGE_ALT, AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT, AGENT_NATIVE_SOCIAL_IMAGE_PATH, AGENT_NATIVE_SOCIAL_IMAGE_TYPE, AGENT_NATIVE_SOCIAL_IMAGE_WIDTH, defaultSocialImageMeta, withAgentNativeSocialImageCacheBuster, withDefaultSocialImage, } from "./social-meta.js";
18
19
  export { EMBED_MODE_QUERY_PARAM, EMBED_SESSION_COOKIE, EMBED_START_PATH, EMBED_TOKEN_QUERY_PARAM, } from "./embed-auth.js";
@@ -1,4 +1,4 @@
1
- import { resolveBuilderAuthHeader, getBuilderProxyOrigin, } from "../server/credential-provider.js";
1
+ import { resolveBuilderGatewayAuth, getBuilderProxyOrigin, } from "../server/credential-provider.js";
2
2
  function describeError(err) {
3
3
  if (!(err instanceof Error))
4
4
  return String(err);
@@ -7,8 +7,8 @@ function describeError(err) {
7
7
  return `${err.name}: ${err.message}${causeText}`;
8
8
  }
9
9
  export async function transcribeWithBuilder(opts) {
10
- const authHeader = await resolveBuilderAuthHeader();
11
- if (!authHeader) {
10
+ const auth = await resolveBuilderGatewayAuth();
11
+ if (!auth) {
12
12
  throw new Error("Builder private key not configured. Connect your Builder.io account (free tier available) in Settings.");
13
13
  }
14
14
  const params = new URLSearchParams();
@@ -39,8 +39,12 @@ export async function transcribeWithBuilder(opts) {
39
39
  res = await fetch(url, {
40
40
  method: "POST",
41
41
  headers: {
42
- Authorization: authHeader,
42
+ Authorization: auth.authorization,
43
43
  "Content-Type": "application/octet-stream",
44
+ // A gateway token without a space id is rejected before any route
45
+ // policy is consulted, so the space id travels with every call.
46
+ ...(auth.spaceId ? { "x-builder-api-key": auth.spaceId } : {}),
47
+ ...(auth.userId ? { "x-builder-user-id": auth.userId } : {}),
44
48
  },
45
49
  body,
46
50
  signal: controller.signal,
@@ -17,7 +17,7 @@ Declare the flag in a module both the server and the client can import, since th
17
17
 
18
18
  ```ts
19
19
  // shared/feature-flags.ts
20
- import { defineFeatureFlag } from "@agent-native/core/feature-flags";
20
+ import { defineFeatureFlag } from "@agent-native/core/feature-flags/registry";
21
21
 
22
22
  export const FULL_APP_BUILDING = defineFeatureFlag({
23
23
  key: "full-app-building",
@@ -47,15 +47,28 @@ the device token was rotated or the workspace moved. If Cloud is unavailable,
47
47
  the run has not started - use Local or Computer instead of retrying a queued
48
48
  cloud request.
49
49
 
50
+ If the connector runs and polls but every request returns `401`, and the queued
51
+ run is never claimed, repairing the pairing will not help. A deployment only
52
+ accepts device tokens minted against its own database, so a device paired
53
+ against one origin polls a second origin forever without ever being recognized.
54
+ Read `deviceId` and `relayUrl` from `~/.agent-native/remote-device.json`, then
55
+ request the authenticated host list from the origin `relayUrl` names. An
56
+ authenticated `200` whose `hosts` array does not contain that `deviceId` is the
57
+ mismatch - pair again from the origin the connector actually polls. Restart the
58
+ connector afterwards: it reads the token and relay URL once at startup and
59
+ caches them for the life of the process, so a running connector keeps using the
60
+ previous pairing.
61
+
50
62
  ## Environment variables
51
63
 
52
64
  Self-hosted deployments can customize the Portal connection with:
53
65
 
54
- | Variable | Purpose |
55
- | ------------------------------------ | -------------------------------------------------------------------- |
56
- | `WORKSPACE_GATEWAY_URL` | Public workspace gateway used for device handoff. |
57
- | `AGENT_NATIVE_REMOTE_DEVICE_PATH` | Override the local device registration file path. |
58
- | `AGENT_NATIVE_COMPUTER_BRIDGE_URL` | Computer bridge URL used by remote execution. |
59
- | `AGENT_NATIVE_COMPUTER_BRIDGE_TOKEN` | Token used to authenticate with that bridge. |
60
- | `AGENT_NATIVE_REMOTE_ENGINES` | Comma-separated remote execution engines available to the workspace. |
61
- | `AGENT_NATIVE_IDENTITY_HUB_URL` | Optional Dispatch identity hub for self-hosted cross-app sign-in. |
66
+ | Variable | Purpose |
67
+ | ------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
68
+ | `WORKSPACE_GATEWAY_URL` | Public workspace gateway used for device handoff. |
69
+ | `AGENT_NATIVE_REMOTE_DEVICE_PATH` | Override the local device registration file path. |
70
+ | `AGENT_NATIVE_COMPUTER_BRIDGE_URL` | Computer bridge URL used by remote execution. |
71
+ | `AGENT_NATIVE_COMPUTER_BRIDGE_TOKEN` | Token used to authenticate with that bridge. |
72
+ | `AGENT_NATIVE_COMPUTER_CAPABILITIES` | Capabilities that bridge exposes. Required with the two above - the bridge stays off unless all three are set. |
73
+ | `AGENT_NATIVE_REMOTE_ENGINES` | Comma-separated remote execution engines available to the workspace. |
74
+ | `AGENT_NATIVE_IDENTITY_HUB_URL` | Optional Dispatch identity hub for self-hosted cross-app sign-in. |