@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.
- package/dist/agent/actions/manage-builder-connection.js +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +8 -1
- package/dist/agent/engine/builder-engine.js +1 -1
- package/dist/agent/engine/credential-errors.d.ts +1 -1
- package/dist/agent/engine/credential-errors.js +2 -2
- package/dist/agent/engine/translate-ai-sdk.d.ts +1 -1
- package/dist/agent/engine/translate-ai-sdk.js +2 -1
- package/dist/agent/production-agent.js +1 -1
- package/dist/agent/run-manager.d.ts +7 -0
- package/dist/agent/run-manager.js +4 -0
- package/dist/agent/thread-data-builder.js +63 -14
- package/dist/brand-kit/fig/index.js +1 -1
- package/dist/cli/skills-content/turn-into-app-skill.d.ts +1 -1
- package/dist/cli/skills-content/turn-into-app-skill.js +2 -2
- package/dist/client/agent-engine-key.js +1 -1
- package/dist/client/chat/agentkit-agent-native.js +26 -14
- package/dist/client/error-format.js +5 -5
- package/dist/client/uploads/upload-editor-image.js +1 -1
- package/dist/client/use-chat-threads.js +48 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/deploy/build.js +7 -1
- package/dist/extensions/web-search-tool.js +2 -2
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.js +1 -1
- package/dist/file-upload/builder.js +2 -2
- package/dist/file-upload/registry.js +1 -1
- package/dist/ingestion/pptx.d.ts +3 -1
- package/dist/ingestion/pptx.js +3 -2
- package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
- package/dist/localization/default-messages.js +11 -11
- package/dist/mcp/build-server.js +11 -5
- package/dist/mcp-client/manager.js +14 -2
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/metrics.js +10 -1
- package/dist/onboarding/app-profile.js +1 -1
- package/dist/onboarding/default-steps.js +4 -4
- package/dist/private-blob/attachment-errors.js +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.js +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/action-routes.js +14 -3
- package/dist/server/agent-chat/browser-team-tools.js +4 -4
- package/dist/server/agent-chat/context-tools.js +2 -2
- package/dist/server/agent-chat-ai-setup.js +1 -1
- package/dist/server/agent-chat-plugin.d.ts +2 -2
- package/dist/server/agent-chat-plugin.js +9 -5
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-default-model-route.d.ts +1 -1
- package/dist/server/builder-api-auth.js +5 -5
- package/dist/server/builder-browser.js +1 -1
- package/dist/server/builder-design-systems.js +1 -1
- package/dist/server/core-routes-plugin.d.ts +5 -11
- package/dist/server/core-routes-plugin.js +36 -68
- package/dist/server/credential-provider.js +3 -3
- package/dist/server/deep-link.d.ts +2 -0
- package/dist/server/deep-link.js +30 -3
- package/dist/server/fusion-app.js +1 -1
- package/dist/server/prompts/framework-core.js +1 -1
- package/dist/server/realtime-voice.js +1 -1
- package/dist/server/transcribe-voice.js +3 -3
- package/dist/shared/connect-required.js +1 -1
- package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +2 -2
- package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +2 -2
- package/dist/templates/default/app/i18n/ar-SA.ts +3 -3
- package/dist/templates/default/app/i18n/de-DE.ts +3 -3
- package/dist/templates/default/app/i18n/en-US.ts +3 -3
- package/dist/templates/default/app/i18n/es-ES.ts +3 -3
- package/dist/templates/default/app/i18n/fr-FR.ts +3 -3
- package/dist/templates/default/app/i18n/hi-IN.ts +3 -3
- package/dist/templates/default/app/i18n/ja-JP.ts +3 -3
- package/dist/templates/default/app/i18n/ko-KR.ts +3 -3
- package/dist/templates/default/app/i18n/pt-BR.ts +3 -3
- package/dist/templates/default/app/i18n/zh-CN.ts +3 -3
- package/dist/templates/default/app/i18n/zh-TW.ts +3 -3
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +2 -2
- package/dist/templates/workspace-root/.env.example +1 -1
- package/dist/transcription/builder-transcription.js +1 -1
- package/dist/triggers/actions/manage-automation.d.ts +2 -2
- package/docs/content/getting-started.mdx +1 -1
- package/docs/content/multi-app-workspace.mdx +1 -1
- package/docs/content/onboarding.mdx +7 -8
- package/docs/content/real-time-collaboration.mdx +1 -1
- package/docs/content/template-chat-developers.mdx +1 -1
- package/docs/content/template-clips-developers.mdx +1 -1
- package/docs/content/template-content-developers.mdx +1 -1
- package/docs/content/template-slides-features.mdx +2 -2
- package/docs/content/toolkit-settings.mdx +10 -10
- package/docs/content/toolkit-setup-connections.mdx +1 -1
- package/docs/content/tracking.mdx +1 -1
- package/docs/content/updating-ui-in-production.mdx +3 -3
- package/docs/content/voice-input.mdx +1 -1
- package/package.json +4 -4
- package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +2 -2
- package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +2 -2
- package/src/templates/default/app/i18n/ar-SA.ts +3 -3
- package/src/templates/default/app/i18n/de-DE.ts +3 -3
- package/src/templates/default/app/i18n/en-US.ts +3 -3
- package/src/templates/default/app/i18n/es-ES.ts +3 -3
- package/src/templates/default/app/i18n/fr-FR.ts +3 -3
- package/src/templates/default/app/i18n/hi-IN.ts +3 -3
- package/src/templates/default/app/i18n/ja-JP.ts +3 -3
- package/src/templates/default/app/i18n/ko-KR.ts +3 -3
- package/src/templates/default/app/i18n/pt-BR.ts +3 -3
- package/src/templates/default/app/i18n/zh-CN.ts +3 -3
- package/src/templates/default/app/i18n/zh-TW.ts +3 -3
- package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +2 -2
- 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:
|
|
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
|
}>>;
|
package/dist/deploy/build.js
CHANGED
|
@@ -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
|
|
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
|
-
"
|
|
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/",
|
|
@@ -417,7 +417,7 @@ async function fetchRemote(url) {
|
|
|
417
417
|
function uploadNotConfiguredError() {
|
|
418
418
|
return [
|
|
419
419
|
"No object storage is connected.",
|
|
420
|
-
"
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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;
|
package/dist/ingestion/pptx.d.ts
CHANGED
|
@@ -155,5 +155,7 @@ export interface ParsedPptxPresentation {
|
|
|
155
155
|
};
|
|
156
156
|
hiddenSlideCount?: number;
|
|
157
157
|
}
|
|
158
|
-
export declare function parsePptxPresentation(fileBuffer: Uint8Array
|
|
158
|
+
export declare function parsePptxPresentation(fileBuffer: Uint8Array, options?: {
|
|
159
|
+
includeHiddenSlides?: boolean;
|
|
160
|
+
}): Promise<ParsedPptxPresentation>;
|
|
159
161
|
export declare function parsePptxSlideMetadata(value: unknown): ParsedPptxSlideMetadata;
|
package/dist/ingestion/pptx.js
CHANGED
|
@@ -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: "
|
|
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: "
|
|
87
|
-
reconnectBuilder: "
|
|
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: "
|
|
662
|
-
connectBuilderIo: "
|
|
663
|
-
connectingBuilder: "
|
|
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: "
|
|
724
|
-
connectBuilder: "
|
|
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: "
|
|
1049
|
-
connectBuilderDescription: "
|
|
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: "
|
|
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",
|
package/dist/mcp/build-server.js
CHANGED
|
@@ -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 {
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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(() =>
|
|
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: "
|
|
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
|
|
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: "
|
|
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: "
|
|
360
|
-
description: "Builder includes managed file storage alongside its AI
|
|
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.
|
|
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":
|
|
@@ -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.
|
|
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
|
}
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -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
|
|
100
|
-
const
|
|
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");
|