@agent-native/core 0.200.0 → 0.200.1-nightly-20261002213029

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 (107) hide show
  1. package/dist/agent/actions/manage-builder-connection.js +1 -1
  2. package/dist/agent/engine/ai-sdk-engine.js +8 -1
  3. package/dist/agent/engine/builder-engine.js +1 -1
  4. package/dist/agent/engine/credential-errors.d.ts +1 -1
  5. package/dist/agent/engine/credential-errors.js +2 -2
  6. package/dist/agent/engine/translate-ai-sdk.d.ts +1 -1
  7. package/dist/agent/engine/translate-ai-sdk.js +2 -1
  8. package/dist/agent/production-agent.js +1 -1
  9. package/dist/agent/run-manager.d.ts +7 -0
  10. package/dist/agent/run-manager.js +4 -0
  11. package/dist/agent/thread-data-builder.js +63 -14
  12. package/dist/brand-kit/fig/index.js +1 -1
  13. package/dist/cli/skills-content/turn-into-app-skill.d.ts +1 -1
  14. package/dist/cli/skills-content/turn-into-app-skill.js +2 -2
  15. package/dist/client/agent-engine-key.js +1 -1
  16. package/dist/client/chat/agentkit-agent-native.js +26 -14
  17. package/dist/client/error-format.js +5 -5
  18. package/dist/client/uploads/upload-editor-image.js +1 -1
  19. package/dist/client/use-chat-threads.js +48 -1
  20. package/dist/collab/awareness.d.ts +2 -2
  21. package/dist/deploy/build.js +7 -1
  22. package/dist/extensions/web-search-tool.js +2 -2
  23. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  24. package/dist/file-upload/actions/upload-image.js +1 -1
  25. package/dist/file-upload/builder.js +2 -2
  26. package/dist/file-upload/registry.js +1 -1
  27. package/dist/ingestion/pptx.d.ts +3 -1
  28. package/dist/ingestion/pptx.js +3 -2
  29. package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
  30. package/dist/localization/default-messages.js +11 -11
  31. package/dist/mcp/build-server.js +11 -5
  32. package/dist/mcp-client/manager.js +14 -2
  33. package/dist/notifications/routes.d.ts +3 -3
  34. package/dist/observability/metrics.js +10 -1
  35. package/dist/onboarding/app-profile.js +1 -1
  36. package/dist/onboarding/default-steps.js +4 -4
  37. package/dist/private-blob/attachment-errors.js +1 -1
  38. package/dist/progress/routes.d.ts +1 -1
  39. package/dist/resources/handlers.js +1 -1
  40. package/dist/secrets/routes.d.ts +6 -6
  41. package/dist/server/action-routes.js +14 -3
  42. package/dist/server/agent-chat/browser-team-tools.js +4 -4
  43. package/dist/server/agent-chat/context-tools.js +2 -2
  44. package/dist/server/agent-chat-ai-setup.js +1 -1
  45. package/dist/server/agent-chat-plugin.d.ts +2 -2
  46. package/dist/server/agent-chat-plugin.js +9 -5
  47. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  48. package/dist/server/agent-engine-default-model-route.d.ts +1 -1
  49. package/dist/server/builder-api-auth.js +5 -5
  50. package/dist/server/builder-browser.js +1 -1
  51. package/dist/server/builder-design-systems.js +1 -1
  52. package/dist/server/core-routes-plugin.d.ts +5 -11
  53. package/dist/server/core-routes-plugin.js +36 -68
  54. package/dist/server/credential-provider.js +3 -3
  55. package/dist/server/deep-link.d.ts +2 -0
  56. package/dist/server/deep-link.js +30 -3
  57. package/dist/server/fusion-app.js +1 -1
  58. package/dist/server/prompts/framework-core.js +1 -1
  59. package/dist/server/realtime-voice.js +1 -1
  60. package/dist/server/transcribe-voice.js +3 -3
  61. package/dist/shared/connect-required.js +1 -1
  62. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +2 -2
  63. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +2 -2
  64. package/dist/templates/default/app/i18n/ar-SA.ts +3 -3
  65. package/dist/templates/default/app/i18n/de-DE.ts +3 -3
  66. package/dist/templates/default/app/i18n/en-US.ts +3 -3
  67. package/dist/templates/default/app/i18n/es-ES.ts +3 -3
  68. package/dist/templates/default/app/i18n/fr-FR.ts +3 -3
  69. package/dist/templates/default/app/i18n/hi-IN.ts +3 -3
  70. package/dist/templates/default/app/i18n/ja-JP.ts +3 -3
  71. package/dist/templates/default/app/i18n/ko-KR.ts +3 -3
  72. package/dist/templates/default/app/i18n/pt-BR.ts +3 -3
  73. package/dist/templates/default/app/i18n/zh-CN.ts +3 -3
  74. package/dist/templates/default/app/i18n/zh-TW.ts +3 -3
  75. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +2 -2
  76. package/dist/templates/workspace-root/.env.example +1 -1
  77. package/dist/transcription/builder-transcription.js +1 -1
  78. package/dist/triggers/actions/manage-automation.d.ts +2 -2
  79. package/docs/content/getting-started.mdx +1 -1
  80. package/docs/content/multi-app-workspace.mdx +1 -1
  81. package/docs/content/onboarding.mdx +7 -8
  82. package/docs/content/real-time-collaboration.mdx +1 -1
  83. package/docs/content/template-chat-developers.mdx +1 -1
  84. package/docs/content/template-clips-developers.mdx +1 -1
  85. package/docs/content/template-content-developers.mdx +1 -1
  86. package/docs/content/template-slides-features.mdx +2 -2
  87. package/docs/content/toolkit-settings.mdx +10 -10
  88. package/docs/content/toolkit-setup-connections.mdx +1 -1
  89. package/docs/content/tracking.mdx +1 -1
  90. package/docs/content/updating-ui-in-production.mdx +3 -3
  91. package/docs/content/voice-input.mdx +1 -1
  92. package/package.json +4 -4
  93. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +2 -2
  94. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +2 -2
  95. package/src/templates/default/app/i18n/ar-SA.ts +3 -3
  96. package/src/templates/default/app/i18n/de-DE.ts +3 -3
  97. package/src/templates/default/app/i18n/en-US.ts +3 -3
  98. package/src/templates/default/app/i18n/es-ES.ts +3 -3
  99. package/src/templates/default/app/i18n/fr-FR.ts +3 -3
  100. package/src/templates/default/app/i18n/hi-IN.ts +3 -3
  101. package/src/templates/default/app/i18n/ja-JP.ts +3 -3
  102. package/src/templates/default/app/i18n/ko-KR.ts +3 -3
  103. package/src/templates/default/app/i18n/pt-BR.ts +3 -3
  104. package/src/templates/default/app/i18n/zh-CN.ts +3 -3
  105. package/src/templates/default/app/i18n/zh-TW.ts +3 -3
  106. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +2 -2
  107. package/src/templates/workspace-root/.env.example +1 -1
@@ -81,6 +81,26 @@ export function appendChatThreadScopeParams(params, scope) {
81
81
  params.set("scopeType", scope.type);
82
82
  params.set("scopeId", scope.id);
83
83
  }
84
+ /**
85
+ * The scope a thread save reports for the thread: a scope, `null` when the
86
+ * server holds the thread unscoped, `undefined` when the response says nothing
87
+ * (an older server or an unreadable body), so the local scope is left alone.
88
+ */
89
+ function savedThreadScope(body) {
90
+ const record = body;
91
+ if (!record || typeof record !== "object" || !("scope" in record)) {
92
+ return undefined;
93
+ }
94
+ const scope = record.scope;
95
+ if (scope === null)
96
+ return null;
97
+ if (!scope || typeof scope !== "object")
98
+ return undefined;
99
+ const { type, id } = scope;
100
+ return typeof type === "string" && typeof id === "string"
101
+ ? scope
102
+ : undefined;
103
+ }
84
104
  function withChatThreadScope(url, scope) {
85
105
  const params = new URLSearchParams();
86
106
  appendChatThreadScopeParams(params, scope);
@@ -239,6 +259,9 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
239
259
  ? new Set([initialActiveThreadRef.current.id])
240
260
  : new Set());
241
261
  const explicitlyOpenedThreadIdsRef = useRef(new Set());
262
+ // Bumped when a thread's scope is changed on purpose (detach), so a save
263
+ // response that was in flight across the change cannot restore the old scope.
264
+ const scopeMutationsRef = useRef(new Map());
242
265
  const optimisticThreadScopesRef = useRef(new Map());
243
266
  const knownThreadScopesRef = useRef(new Map());
244
267
  const serverConfirmedThreadIdsRef = useRef(new Set());
@@ -728,6 +751,8 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
728
751
  routeThreadId,
729
752
  ]);
730
753
  const detachThread = useCallback(async (threadId) => {
754
+ const bumpScopeMutation = () => scopeMutationsRef.current.set(threadId, (scopeMutationsRef.current.get(threadId) ?? 0) + 1);
755
+ bumpScopeMutation();
731
756
  try {
732
757
  const res = await fetch(withChatThreadScope(`${apiUrl}/threads/${encodeURIComponent(threadId)}`, historyScope), {
733
758
  method: "PUT",
@@ -738,6 +763,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
738
763
  await fetchThreads();
739
764
  return;
740
765
  }
766
+ bumpScopeMutation();
741
767
  knownThreadScopesRef.current.set(threadId, null);
742
768
  optimisticThreadScopesRef.current.set(threadId, null);
743
769
  const wasActive = activeThreadIdRef.current === threadId;
@@ -969,6 +995,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
969
995
  // rides only on creation: a periodic save must never move an existing thread
970
996
  // between resources, however stale this client's guess is.
971
997
  const saveThreadData = useCallback(async (id, data) => {
998
+ const scopeEpoch = scopeMutationsRef.current.get(id) ?? 0;
972
999
  try {
973
1000
  const { titleSource, ...threadDataPayload } = data;
974
1001
  const localThread = threadsRef.current.find((t) => t.id === id);
@@ -1004,6 +1031,13 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
1004
1031
  }
1005
1032
  if (!response.ok)
1006
1033
  return;
1034
+ const reportedScope = savedThreadScope(
1035
+ // coercion-ok: a save response without a readable body carries no scope, and the local scope stays as it was.
1036
+ await response.json().catch(() => null));
1037
+ // A response that was in flight across a detach describes the thread
1038
+ // as it was before it, so it must not put the old scope back.
1039
+ const scopeChangedDuringSave = (scopeMutationsRef.current.get(id) ?? 0) !== scopeEpoch;
1040
+ const savedScope = scopeChangedDuringSave ? undefined : reportedScope;
1007
1041
  serverConfirmedThreadIdsRef.current.add(id);
1008
1042
  clearClientDraftThreadMarker(id);
1009
1043
  newlyCreatedRef.current.delete(id);
@@ -1021,10 +1055,21 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
1021
1055
  ...(data.messageCount != null && {
1022
1056
  messageCount: data.messageCount,
1023
1057
  }),
1058
+ // The run adopts the scope it was sent with, which the
1059
+ // visible page at save time may no longer be; take the
1060
+ // server's, or the active pointer stays under the
1061
+ // unscoped key and a reload here starts fresh.
1062
+ ...(savedScope !== undefined
1063
+ ? { scope: savedScope }
1064
+ : {}),
1024
1065
  updatedAt: Date.now(),
1025
1066
  }
1026
1067
  : t));
1027
1068
  }
1069
+ // A thread the list no longer holds because it was detached while
1070
+ // this save ran must not come back under the page's current scope.
1071
+ if (scopeChangedDuringSave)
1072
+ return prev;
1028
1073
  const now = Date.now();
1029
1074
  return sortThreadSummaries([
1030
1075
  {
@@ -1034,7 +1079,9 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
1034
1079
  messageCount: data.messageCount ?? 0,
1035
1080
  createdAt: now,
1036
1081
  updatedAt: now,
1037
- scope: scopeRef.current ?? null,
1082
+ scope: savedScope !== undefined
1083
+ ? savedScope
1084
+ : (scopeRef.current ?? null),
1038
1085
  },
1039
1086
  ...prev,
1040
1087
  ]);
@@ -38,19 +38,19 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
38
38
  error: string;
39
39
  states?: undefined;
40
40
  } | {
41
- error?: undefined;
42
41
  states: {
43
42
  clientId: number;
44
43
  state: string;
45
44
  }[];
45
+ error?: undefined;
46
46
  }>>;
47
47
  export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
48
48
  error: string;
49
49
  users?: undefined;
50
50
  } | {
51
- error?: undefined;
52
51
  users: {
53
52
  clientId: number;
54
53
  lastSeen: number;
55
54
  }[];
55
+ error?: undefined;
56
56
  }>>;
@@ -18,6 +18,7 @@ import { normalizeAppBasePath } from "../server/app-base-path.js";
18
18
  import { frameworkSessionHintCookieName, resolveAuthCookieNamespace, } from "../server/cookie-namespace.js";
19
19
  import { resolveAgentNativeBuildId } from "../shared/build-id.js";
20
20
  import { DEFAULT_SPECULATION_RULES_PATH, resolveSsrCacheHeaders, resolveSsrCacheKeyHeaders, SSR_QUERY_CACHE_KEY_HEADER, } from "../shared/cache-control.js";
21
+ import { EMBED_TARGET_QUERY_PARAM, EMBED_TOKEN_QUERY_PARAM, } from "../shared/embed-auth.js";
21
22
  import { normalizeFrameworkRoutePrefix } from "../shared/framework-route-prefix.js";
22
23
  import { mcpEmbedStaticAssetRouteRules } from "../shared/mcp-embed-headers.js";
23
24
  import { isTruthyRuntimeValue } from "../shared/runtime-config.js";
@@ -1100,6 +1101,10 @@ function normalizeAppBasePath(value) {
1100
1101
  // CLASSIFICATION happens here; the h3 boundary inside the handler is what
1101
1102
  // translates the public prefix to the internal one, exactly once.
1102
1103
  const builtFrameworkRoutePrefix = ${JSON.stringify(builtFrameworkRoutePrefix)};
1104
+ const embedActionQueryParams = new Set(${JSON.stringify([
1105
+ EMBED_TARGET_QUERY_PARAM,
1106
+ EMBED_TOKEN_QUERY_PARAM,
1107
+ ])});
1103
1108
 
1104
1109
  function getAppBasePath() {
1105
1110
  const builtAppBasePath = ${JSON.stringify(builtAppBasePath)};
@@ -1126,9 +1131,10 @@ function parseActionSearchParams(searchParams) {
1126
1131
  const params = {};
1127
1132
  for (const [rawKey, value] of searchParams.entries()) {
1128
1133
  const isArrayKey = rawKey.endsWith("[]");
1134
+ const key = isArrayKey ? rawKey.slice(0, -2) : rawKey;
1135
+ if (embedActionQueryParams.has(key)) continue;
1129
1136
  // The core client serializes arrays as key[]=value so one-item arrays
1130
1137
  // survive GET action parsing in generated worker deployments.
1131
- const key = isArrayKey ? rawKey.slice(0, -2) : rawKey;
1132
1138
  const current = params[key];
1133
1139
  if (current === undefined) {
1134
1140
  params[key] = isArrayKey ? [value] : value;
@@ -178,7 +178,7 @@ export function createWebSearchToolEntry(opts = {}) {
178
178
  return {
179
179
  "web-search": {
180
180
  tool: {
181
- description: "Search the public web. Use to find API documentation, endpoints, current information, or any topic. Returns ranked results from BYOK providers or a grounded Builder-managed summary. Follow up with web-request or provider-api-docs using responseMode:'markdown' or responseMode:'matches' to fetch clean text, links, or compact snippets from promising URLs. Requires either Connect Builder.io (free tier available) or one of: BRAVE_SEARCH_API_KEY, TAVILY_API_KEY, EXA_API_KEY, or FIRECRAWL_API_KEY.",
181
+ description: "Search the public web. Use to find API documentation, endpoints, current information, or any topic. Returns ranked results from BYOK providers or a grounded Builder-managed summary. Follow up with web-request or provider-api-docs using responseMode:'markdown' or responseMode:'matches' to fetch clean text, links, or compact snippets from promising URLs. Requires either Builder.io (free tier available) or one of: BRAVE_SEARCH_API_KEY, TAVILY_API_KEY, EXA_API_KEY, or FIRECRAWL_API_KEY.",
182
182
  parameters: {
183
183
  type: "object",
184
184
  properties: {
@@ -235,7 +235,7 @@ export function createWebSearchToolEntry(opts = {}) {
235
235
  else {
236
236
  return [
237
237
  "No web-search backend configured.",
238
- "Connect Builder.io (free tier available) in Settings, or add one of the following keys via app settings or environment variables:",
238
+ "Use Builder.io (free tier available) in Settings, or add one of the following keys via app settings or environment variables:",
239
239
  " • BRAVE_SEARCH_API_KEY — https://brave.com/search/api/",
240
240
  " • TAVILY_API_KEY — https://tavily.com/",
241
241
  " • EXA_API_KEY — https://exa.ai/",
@@ -42,8 +42,8 @@ declare const _default: import("../../action.js").ActionDefinition<{
42
42
  url: string;
43
43
  id: string;
44
44
  provider: string;
45
- error?: undefined;
46
45
  configured?: undefined;
46
+ error?: undefined;
47
47
  } | {
48
48
  url?: undefined;
49
49
  id?: undefined;
@@ -417,7 +417,7 @@ async function fetchRemote(url) {
417
417
  function uploadNotConfiguredError() {
418
418
  return [
419
419
  "No object storage is connected.",
420
- "Connect Builder.io (free) or configure your own S3-compatible storage keys in Settings → File uploads.",
420
+ "Use Builder.io's managed storage (free) or configure your own S3-compatible storage keys in Settings → File uploads.",
421
421
  ].join(" ");
422
422
  }
423
423
  export default defineAction({
@@ -197,11 +197,11 @@ async function assetAuthorization(options = {}) {
197
197
  const privateKey = credentials.privateKey?.trim();
198
198
  const publicKey = credentials.publicKey?.trim();
199
199
  if (!privateKey || !publicKey) {
200
- throw new Error("Builder personal access token connection is missing its space id. Reconnect Builder.io to continue.");
200
+ throw new Error("Builder personal access token connection is missing its space id. Sign in to Builder.io again to continue.");
201
201
  }
202
202
  const authorized = authorization.replace(/^Bearer\s+/i, "").trim();
203
203
  if (privateKey !== authorized) {
204
- throw new Error("Builder credential scope mismatch: the connection holding the upload space is not the one authorized for this request. Reconnect Builder.io to continue.");
204
+ throw new Error("Builder credential scope mismatch: the connection holding the upload space is not the one authorized for this request. Sign in to Builder.io again to continue.");
205
205
  }
206
206
  return { authorization, apiKey: publicKey };
207
207
  }
@@ -90,7 +90,7 @@ export async function uploadFile(input) {
90
90
  if (!warnedFallbackRef.value) {
91
91
  warnedFallbackRef.value = true;
92
92
  console.warn("[agent-native] No file upload provider configured. " +
93
- "Connect or reconnect Builder.io (free tier available) in Settings → File uploads, " +
93
+ "Use Builder.io (free tier available) in Settings → File uploads, " +
94
94
  "or register a custom provider (S3, R2, GCS, …) via registerFileUploadProvider().");
95
95
  }
96
96
  return null;
@@ -155,5 +155,7 @@ export interface ParsedPptxPresentation {
155
155
  };
156
156
  hiddenSlideCount?: number;
157
157
  }
158
- export declare function parsePptxPresentation(fileBuffer: Uint8Array): Promise<ParsedPptxPresentation>;
158
+ export declare function parsePptxPresentation(fileBuffer: Uint8Array, options?: {
159
+ includeHiddenSlides?: boolean;
160
+ }): Promise<ParsedPptxPresentation>;
159
161
  export declare function parsePptxSlideMetadata(value: unknown): ParsedPptxSlideMetadata;
@@ -1,5 +1,5 @@
1
1
  import { loadOptionalPeer } from "../shared/optional-peer.js";
2
- export async function parsePptxPresentation(fileBuffer) {
2
+ export async function parsePptxPresentation(fileBuffer, options = {}) {
3
3
  const dependencies = await loadPptxDependencies();
4
4
  const loadZip = dependencies.loadZip.bind(dependencies);
5
5
  const parseXml = dependencies.parseXml.bind(dependencies);
@@ -102,7 +102,8 @@ export async function parsePptxPresentation(fileBuffer) {
102
102
  droppedSlides.push(`${slidePath} could not be parsed: ${error instanceof Error ? error.message : String(error)}`);
103
103
  continue;
104
104
  }
105
- if (stringValue(record(record(slide)?.["p:sld"])?.["@_show"]) === "0") {
105
+ if (stringValue(record(record(slide)?.["p:sld"])?.["@_show"]) === "0" &&
106
+ !options.includeHiddenSlides) {
106
107
  hiddenSlideCount += 1;
107
108
  continue;
108
109
  }
@@ -11,9 +11,9 @@ declare const _default: import("../../action.js").ActionDefinition<{
11
11
  enabled?: undefined;
12
12
  nextRun?: undefined;
13
13
  } | {
14
- deleted?: undefined;
15
14
  name: string;
16
15
  enabled: boolean;
17
16
  nextRun: string;
17
+ deleted?: undefined;
18
18
  }>;
19
19
  export default _default;
@@ -38,7 +38,7 @@ const messages = {
38
38
  composer: {
39
39
  sendMessage: "Send message",
40
40
  queueMessage: "Queue message",
41
- connectBuilder: "Connect Builder.io",
41
+ connectBuilder: "Use Builder.io",
42
42
  },
43
43
  home: {
44
44
  settingsTitle: "Settings",
@@ -83,8 +83,8 @@ const messages = {
83
83
  openBackgroundAgentSettings: "Open Background Agent settings",
84
84
  fileStorage: {
85
85
  title: "Connect storage to upload files",
86
- description: "Connect Builder.io (free) or configure your own S3-compatible object storage.",
87
- reconnectBuilder: "Reconnect Builder.io",
86
+ description: "Use Builder.io's managed storage (free) or configure your own S3-compatible object storage.",
87
+ reconnectBuilder: "Sign in to Builder.io again",
88
88
  custom: "Use custom keys",
89
89
  customDescription: "Configure an S3-compatible bucket with a stable public URL.",
90
90
  },
@@ -658,9 +658,9 @@ const messages = {
658
658
  askAgentTitle: "Ask the agent",
659
659
  askAgentPlaceholder: "Tell the agent what you want to do…",
660
660
  connectAi: "Connect AI",
661
- builderOrOwnKeys: "Choose Builder.io or custom keys.",
662
- connectBuilderIo: "Connect Builder.io",
663
- connectingBuilder: "Connecting Builder.io…",
661
+ builderOrOwnKeys: "Use Builder.io or custom keys.",
662
+ connectBuilderIo: "Use Builder.io",
663
+ connectingBuilder: "Setting up Builder.io…",
664
664
  builderModelCredits: "Free credits for Claude, OpenAI & Gemini",
665
665
  addOwnKeys: "Custom keys",
666
666
  configureProviderKeys: "Choose a provider.",
@@ -720,8 +720,8 @@ const messages = {
720
720
  promptTitle: "Use your voice",
721
721
  promptDescription: "Voice mode keeps listening while the agent navigates and takes actions.",
722
722
  setupTitle: "Set up voice mode",
723
- setupDescription: "Connect Builder.io to use managed voice with free credits, or add your own keys.",
724
- connectBuilder: "Connect Builder.io",
723
+ setupDescription: "Use Builder.io for managed voice with free credits, or add your own keys.",
724
+ connectBuilder: "Use Builder.io",
725
725
  useOpenAiKey: "Custom keys",
726
726
  startWithOpenAiKey: "Start with OpenAI key",
727
727
  start: "Start voice chat",
@@ -1045,8 +1045,8 @@ const messages = {
1045
1045
  builderAgentDescription: "Let our cloud agent make the changes for you. You'll get a link to preview and deploy.",
1046
1046
  codeChangeTitle: "This requires a code change",
1047
1047
  codeChangeBadge: "Code change",
1048
- connectBuilderTitle: "Connect Builder.io",
1049
- connectBuilderDescription: "Connect Builder (free tier available) to enable cloud-based code changes from this app.",
1048
+ connectBuilderTitle: "Use Builder.io",
1049
+ connectBuilderDescription: "Use Builder.io (free tier available) to enable cloud-based code changes from this app.",
1050
1050
  setupRequired: "Setup required",
1051
1051
  branchCreated: "Branch created",
1052
1052
  close: "Close",
@@ -1313,7 +1313,7 @@ const messages = {
1313
1313
  networkError: "Network error reaching the server",
1314
1314
  back: "Back",
1315
1315
  agentEngineRequired: "Agent engine required",
1316
- agentEngineDescription: "Connect Builder.io (free tier available) or an LLM key before {{platform}} can answer.",
1316
+ agentEngineDescription: "Use Builder.io (free tier available) or an LLM key before {{platform}} can answer.",
1317
1317
  openLlm: "Open LLM",
1318
1318
  setup: "Setup",
1319
1319
  shareDocumentsWith: "Share documents with",
@@ -7,10 +7,10 @@ import { getAppConfig } from "../app-config/store.js";
7
7
  import { isMcpActionResult } from "../mcp-client/app-result.js";
8
8
  import { writeActionChangeMarker } from "../server/action-change-marker-write.js";
9
9
  import { getConfiguredAppBasePath } from "../server/app-base-path.js";
10
- import { buildDeepLink, toAbsoluteOpenUrl, toDesktopOpenUrl, toVsCodeOpenUrl, } from "../server/deep-link.js";
10
+ import { buildDeepLink, isAgentNativeOpenUrl, isSameOriginUrl, toAbsoluteOpenUrl, toDesktopOpenUrl, toVsCodeOpenUrl, } from "../server/deep-link.js";
11
11
  import { getRequestContext, getRequestOrgId, getRequestUserEmail, runWithRequestContext, } from "../server/request-context.js";
12
12
  import { agentNativeMcpInstructions, agentNativeToolTitle, } from "../shared/agent-mcp-metadata.js";
13
- import { isAgentNativeOpenDeepLink, withCollapsedAgentSidebarParam, } from "../shared/agent-sidebar-url.js";
13
+ import { withCollapsedAgentSidebarParam } from "../shared/agent-sidebar-url.js";
14
14
  import { MCP_APP_CHAT_BRIDGE_QUERY_PARAM } from "../shared/embed-auth.js";
15
15
  import { describeMcpError, readClientInfoFromRequest, trackMcpResourceRead, trackMcpResourcesList, trackMcpToolCall, trackMcpToolsList, } from "./analytics.js";
16
16
  import { consumeMcpApprovalGrant, createMcpApprovalGrant, } from "./approval-store.js";
@@ -550,6 +550,9 @@ function mcpAppEmbedOpenLinkMeta(result, resource, meta) {
550
550
  const safeOpenUrl = explicitOpenUrl
551
551
  ? toAbsoluteOpenUrl(explicitOpenUrl, meta?.origin)
552
552
  : null;
553
+ const isNativeOpenUrl = safeOpenUrl
554
+ ? isAgentNativeOpenUrl(safeOpenUrl, meta?.origin, meta?.basePath)
555
+ : false;
553
556
  const desktopDeepLinkUrl = (() => {
554
557
  if (!safeOpenUrl)
555
558
  return null;
@@ -558,9 +561,11 @@ function mcpAppEmbedOpenLinkMeta(result, resource, meta) {
558
561
  : undefined;
559
562
  if (!app)
560
563
  return safeOpenUrl;
561
- if (isAgentNativeOpenDeepLink(safeOpenUrl)) {
564
+ if (isNativeOpenUrl) {
562
565
  return toDesktopOpenUrl(safeOpenUrl);
563
566
  }
567
+ if (!isSameOriginUrl(safeOpenUrl, meta?.origin))
568
+ return safeOpenUrl;
564
569
  const targetRoute = routePathFromOpenUrl(safeOpenUrl);
565
570
  if (!targetRoute)
566
571
  return safeOpenUrl;
@@ -657,11 +662,12 @@ export function buildLinkArtifacts(entry, args, result, meta) {
657
662
  const lk = entry.link({ args: args ?? {}, result });
658
663
  if (!lk?.url)
659
664
  return {};
660
- const linkUrl = isAgentNativeOpenDeepLink(lk.url)
665
+ const isNativeOpenUrl = isAgentNativeOpenUrl(lk.url, meta?.origin, meta?.basePath);
666
+ const linkUrl = isNativeOpenUrl
661
667
  ? withCollapsedAgentSidebarParam(lk.url)
662
668
  : lk.url;
663
669
  const webUrl = toAbsoluteOpenUrl(linkUrl, meta?.origin);
664
- const desktopUrl = toDesktopOpenUrl(linkUrl);
670
+ const desktopUrl = isNativeOpenUrl ? toDesktopOpenUrl(linkUrl) : webUrl;
665
671
  const vscodeUrl = toVsCodeOpenUrl(webUrl);
666
672
  const markdownUrl = meta?.target === "desktop" ? desktopUrl : webUrl;
667
673
  return {
@@ -313,10 +313,12 @@ export class McpClientManager {
313
313
  });
314
314
  let handshakeComplete = false;
315
315
  let callbackError;
316
+ let callbackErrorsAreHttp400 = true;
316
317
  const recordConnectionError = (error) => {
317
318
  const message = formatMcpConnectError(error);
318
319
  entry.error = message;
319
320
  callbackError ??= error;
321
+ callbackErrorsAreHttp400 &&= httpStatusFromError(error) === 400;
320
322
  if (handshakeComplete)
321
323
  this.emitChange();
322
324
  if (this.debug) {
@@ -330,8 +332,18 @@ export class McpClientManager {
330
332
  try {
331
333
  const timeoutMs = mcpConnectTimeoutMs(this.connectTimeoutMs);
332
334
  await withConnectTimeout(Promise.resolve(client.connect(transport)), `MCP server ${entry.id} connect`, timeoutMs);
333
- if (callbackError)
334
- throw callbackError;
335
+ if (callbackError) {
336
+ // Auto-negotiation can report its rejected probe before fallback succeeds.
337
+ const negotiatedAfterProbe = cfg.type === "http" &&
338
+ httpStatusFromError(callbackError) === 400 &&
339
+ callbackErrorsAreHttp400 &&
340
+ typeof client.getNegotiatedProtocolVersion === "function" &&
341
+ Boolean(client.getNegotiatedProtocolVersion());
342
+ if (!negotiatedAfterProbe)
343
+ throw callbackError;
344
+ callbackError = undefined;
345
+ entry.error = undefined;
346
+ }
335
347
  const listed = await withConnectTimeout(Promise.resolve(client.listTools()), `MCP server ${entry.id} tools/list`, timeoutMs);
336
348
  if (callbackError)
337
349
  throw callbackError;
@@ -1,12 +1,12 @@
1
1
  export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
2
- error?: undefined;
3
2
  count: number;
4
3
  updated?: undefined;
4
+ error?: undefined;
5
5
  ok?: undefined;
6
6
  } | {
7
- error?: undefined;
8
7
  count?: undefined;
9
8
  updated: number;
9
+ error?: undefined;
10
10
  ok?: undefined;
11
11
  } | {
12
12
  count?: undefined;
@@ -14,8 +14,8 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
14
14
  error: string;
15
15
  ok?: undefined;
16
16
  } | {
17
- error?: undefined;
18
17
  count?: undefined;
19
18
  updated?: undefined;
20
19
  ok: boolean;
20
+ error?: undefined;
21
21
  }>>;
@@ -11,6 +11,10 @@ const METER_NAME = "@agent-native/core";
11
11
  // The SDK default is dense below 100ms, which serverless requests don't need.
12
12
  const HTTP_SERVER_DURATION_BUCKETS_S = [0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];
13
13
  export const OBSERVABILITY_FLUSH_TIMEOUT_MS = 2_000;
14
+ // A timer that fires this much past its deadline means the process was frozen
15
+ // mid-flush (the runtime suspended it after the response), not that the export
16
+ // was slow.
17
+ const FLUSH_SUSPENDED_SLACK_MS = 1_000;
14
18
  const KNOWN_HTTP_METHODS = new Set([
15
19
  "CONNECT",
16
20
  "DELETE",
@@ -81,8 +85,13 @@ export async function flushObservability() {
81
85
  if (!provider)
82
86
  return;
83
87
  let timer;
88
+ const startedAt = Date.now();
84
89
  const timeout = new Promise((resolve) => {
85
- timer = setTimeout(() => resolve("timeout"), OBSERVABILITY_FLUSH_TIMEOUT_MS);
90
+ timer = setTimeout(() => {
91
+ const late = Date.now() - startedAt - OBSERVABILITY_FLUSH_TIMEOUT_MS >
92
+ FLUSH_SUSPENDED_SLACK_MS;
93
+ resolve(late ? "suspended" : "timeout");
94
+ }, OBSERVABILITY_FLUSH_TIMEOUT_MS);
86
95
  timer.unref?.();
87
96
  });
88
97
  try {
@@ -250,7 +250,7 @@ const PROFILES = {
250
250
  label: "Builder executor",
251
251
  required: true,
252
252
  builderIncluded: true,
253
- keySummary: "Connect Builder for managed agent runs",
253
+ keySummary: "Use Builder.io for managed agent runs",
254
254
  why: "The executor runs approved code and review workflows for Factory items.",
255
255
  },
256
256
  {
@@ -54,7 +54,7 @@ const JEV_KEY_METHOD = {
54
54
  id: "jev-key",
55
55
  kind: "form",
56
56
  label: "Decision model (Jev)",
57
- description: "Optional direct Jev API key for smarter tool and skill selection. Builder-managed Jev may be available through Connect Builder, so both are not required.",
57
+ description: "Optional direct Jev API key for smarter tool and skill selection. Builder-managed Jev may be available through Builder.io, so both are not required.",
58
58
  badge: "recommended",
59
59
  payload: {
60
60
  writeScope: "user",
@@ -77,7 +77,7 @@ const llmStep = {
77
77
  {
78
78
  id: "builder",
79
79
  kind: "builder-cli-auth",
80
- label: "Connect Builder",
80
+ label: "Use Builder.io",
81
81
  description: "Builder.io's free tier includes AI credits.",
82
82
  primary: true,
83
83
  badge: "free",
@@ -356,8 +356,8 @@ const fileStorageStep = {
356
356
  {
357
357
  id: "builder",
358
358
  kind: "builder-cli-auth",
359
- label: "Connect Builder",
360
- description: "Builder includes managed file storage alongside its AI connection.",
359
+ label: "Use Builder.io",
360
+ description: "Builder.io includes managed file storage alongside its AI service.",
361
361
  primary: true,
362
362
  badge: "free",
363
363
  payload: { scope: "llm" },
@@ -39,7 +39,7 @@ function describeStorageUnavailable(failure, operation) {
39
39
  const cause = (() => {
40
40
  switch (failure.reason) {
41
41
  case "not_configured":
42
- return "No object storage is connected. Connect Builder.io (free) or configure your own S3-compatible storage keys in Settings → File uploads.";
42
+ return "No object storage is connected. Use Builder.io's managed storage (free) or configure your own S3-compatible storage keys in Settings → File uploads.";
43
43
  case "encryption_key_unavailable":
44
44
  return "File storage is unavailable because this deployment has no encryption key configured.";
45
45
  case "misconfigured":
@@ -2,6 +2,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
2
2
  error: string;
3
3
  ok?: undefined;
4
4
  } | {
5
- ok: boolean;
6
5
  error?: undefined;
6
+ ok: boolean;
7
7
  }>>;
@@ -534,7 +534,7 @@ export async function handleUploadResource(event) {
534
534
  }
535
535
  setResponseStatus(event, 503);
536
536
  return {
537
- error: "File storage is not configured. Connect Builder.io (free tier available) or register an S3/R2/GCS file upload provider before uploading binary resources.",
537
+ error: "File storage is not configured. Use Builder.io (free tier available) or register an S3/R2/GCS file upload provider before uploading binary resources.",
538
538
  storageSetupRequired: true,
539
539
  };
540
540
  }
@@ -73,32 +73,32 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
73
73
  ok?: undefined;
74
74
  status?: undefined;
75
75
  } | {
76
- error?: undefined;
77
76
  errorCode?: undefined;
78
77
  retryable?: undefined;
79
78
  ok: boolean;
80
79
  status: string;
80
+ error?: undefined;
81
81
  } | {
82
82
  ok?: undefined;
83
83
  error: string;
84
84
  removed?: undefined;
85
85
  } | {
86
- error?: undefined;
87
86
  ok: boolean;
88
87
  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;
96
95
  ok: boolean;
97
96
  note?: undefined;
98
- } | {
99
97
  error?: undefined;
98
+ } | {
100
99
  ok: boolean;
101
100
  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;
130
129
  ok: boolean;
131
130
  key: string;
132
- } | {
133
131
  error?: undefined;
132
+ } | {
134
133
  ok: boolean;
135
134
  removed: boolean;
135
+ error?: undefined;
136
136
  }>>;
@@ -10,7 +10,7 @@ import { isFeatureFlagAdminEmail } from "../feature-flags/permissions.js";
10
10
  import { isFederationMembershipValidatedForEvent, resolveOrgByDomain, resolveOrgIdForEmail, } from "../org/context.js";
11
11
  import { LLM_PROVIDER_MISSING_ERROR_CODE, LLM_PROVIDER_MISSING_STATUS, } from "../shared/action-error-codes.js";
12
12
  import { agentNativeMcpInstructions, agentNativeToolTitle, } from "../shared/agent-mcp-metadata.js";
13
- import { EMBED_TARGET_HEADER } from "../shared/embed-auth.js";
13
+ import { EMBED_TARGET_HEADER, EMBED_TARGET_QUERY_PARAM, EMBED_TOKEN_QUERY_PARAM, } from "../shared/embed-auth.js";
14
14
  import { isMcpEmbedCorsOrigin, MCP_EMBED_CORS_ALLOW_HEADERS, shouldAllowMcpEmbedCredentials, } from "../shared/mcp-embed-headers.js";
15
15
  import { countActionFailure, countCredentialState, } from "../tracking/failure-counters.js";
16
16
  import { redact, redactErrorStack } from "../tracking/redaction.js";
@@ -45,6 +45,10 @@ import { getH3App } from "./framework-request-handler.js";
45
45
  import { getRequestIdentityAuthenticatedAtMs, getRequestIdentitySessionToken, hasExplicitPersonalOrgScope, markExplicitPersonalOrgScope, runWithRequestContext, } from "./request-context.js";
46
46
  const ROUTE_PREFIX = "/_agent-native/actions";
47
47
  const FRONTEND_MUTATION_METHODS = new Set(["POST", "PUT", "DELETE"]);
48
+ const EMBED_ACTION_QUERY_PARAMS = new Set([
49
+ EMBED_TARGET_QUERY_PARAM,
50
+ EMBED_TOKEN_QUERY_PARAM,
51
+ ]);
48
52
  async function resolveFeatureFlagA2ACaller(event, actionName) {
49
53
  const required = actionName === "list-feature-flags"
50
54
  ? "flags:read"
@@ -80,6 +84,8 @@ async function resolveFeatureFlagA2ACaller(event, actionName) {
80
84
  export function parseActionSearchParams(searchParams) {
81
85
  const params = {};
82
86
  for (const [rawKey, value] of searchParams.entries()) {
87
+ if (EMBED_ACTION_QUERY_PARAMS.has(actionParamKey(rawKey)))
88
+ continue;
83
89
  appendActionParam(params, rawKey, value);
84
90
  }
85
91
  return params;
@@ -87,6 +93,8 @@ export function parseActionSearchParams(searchParams) {
87
93
  function parseActionQueryObject(query) {
88
94
  const params = {};
89
95
  for (const [rawKey, rawValue] of Object.entries(query)) {
96
+ if (EMBED_ACTION_QUERY_PARAMS.has(actionParamKey(rawKey)))
97
+ continue;
90
98
  const values = Array.isArray(rawValue) ? rawValue : [rawValue];
91
99
  for (const value of values) {
92
100
  if (value != null)
@@ -96,8 +104,8 @@ function parseActionQueryObject(query) {
96
104
  return params;
97
105
  }
98
106
  function appendActionParam(params, rawKey, value) {
99
- const isArrayKey = rawKey.endsWith("[]");
100
- const key = isArrayKey ? rawKey.slice(0, -2) : rawKey;
107
+ const key = actionParamKey(rawKey);
108
+ const isArrayKey = key !== rawKey;
101
109
  const current = params[key];
102
110
  if (current === undefined) {
103
111
  params[key] = isArrayKey ? [value] : value;
@@ -109,6 +117,9 @@ function appendActionParam(params, rawKey, value) {
109
117
  params[key] = [current, value];
110
118
  }
111
119
  }
120
+ function actionParamKey(rawKey) {
121
+ return rawKey.endsWith("[]") ? rawKey.slice(0, -2) : rawKey;
122
+ }
112
123
  function readTimezoneHeader(event) {
113
124
  try {
114
125
  const raw = getHeader(event, "x-user-timezone");