@agent-native/core 0.209.0-nightly-20261014124140 → 0.209.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 (53) hide show
  1. package/dist/agent/production-agent.d.ts +8 -0
  2. package/dist/agent/production-agent.js +68 -3
  3. package/dist/agent/strict-tool-input.d.ts +4 -0
  4. package/dist/agent/strict-tool-input.js +26 -0
  5. package/dist/agent/types.d.ts +7 -0
  6. package/dist/cli/skills-content/turn-into-app-attachments.d.ts +1 -1
  7. package/dist/cli/skills-content/turn-into-app-attachments.js +1 -3
  8. package/dist/cli/skills-content/turn-into-app-skill.d.ts +2 -2
  9. package/dist/cli/skills-content/turn-into-app-skill.js +15 -10
  10. package/dist/client/chat/agentkit-protocol.js +2 -0
  11. package/dist/client/chat/runtime.d.ts +1 -0
  12. package/dist/client/chat/runtime.js +35 -3
  13. package/dist/collab/routes.d.ts +2 -2
  14. package/dist/collab/struct-routes.d.ts +1 -1
  15. package/dist/mcp/actions/call-mcp-tool.js +1 -1
  16. package/dist/mcp-client/app-api.d.ts +3 -0
  17. package/dist/mcp-client/app-api.js +8 -1
  18. package/dist/mcp-client/index.d.ts +7 -2
  19. package/dist/mcp-client/index.js +10 -4
  20. package/dist/mcp-client/manager.d.ts +16 -1
  21. package/dist/mcp-client/manager.js +35 -7
  22. package/dist/mcp-client/routes.js +9 -4
  23. package/dist/observability/metrics.d.ts +2 -0
  24. package/dist/observability/metrics.js +15 -1
  25. package/dist/observability/traces.js +8 -1
  26. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  27. package/dist/provider-api/actions/provider-api.d.ts +10 -10
  28. package/dist/resources/handlers.d.ts +1 -1
  29. package/dist/server/agent-chat/additional-actions.d.ts +45 -0
  30. package/dist/server/agent-chat/additional-actions.js +707 -0
  31. package/dist/server/agent-chat/mcp-glue.d.ts +3 -1
  32. package/dist/server/agent-chat/mcp-glue.js +12 -1
  33. package/dist/server/agent-chat/plugin-options.d.ts +27 -0
  34. package/dist/server/agent-chat-plugin.js +59 -14
  35. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  36. package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
  37. package/dist/server/better-auth-instance.js +108 -18
  38. package/dist/server/builder-browser.d.ts +4 -2
  39. package/dist/server/builder-browser.js +21 -16
  40. package/dist/server/index.d.ts +2 -1
  41. package/dist/server/index.js +1 -1
  42. package/dist/server/realtime-token.d.ts +1 -1
  43. package/dist/shared/data-url.d.ts +2 -0
  44. package/dist/shared/data-url.js +12 -0
  45. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/attachments.md +1 -3
  46. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -8
  47. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/source-brief.md +2 -2
  48. package/docs/content/agent-surfaces.mdx +61 -0
  49. package/docs/content/observability.mdx +2 -0
  50. package/package.json +5 -4
  51. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/attachments.md +1 -3
  52. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -8
  53. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/source-brief.md +2 -2
@@ -1,5 +1,7 @@
1
- import { McpClientManager } from "../../mcp-client/index.js";
1
+ import { McpClientManager, type McpToolAuthorizationResolver } from "../../mcp-client/index.js";
2
2
  import { type McpPrincipal } from "../../mcp-client/principal.js";
3
+ /** Install the app-owned MCP tool authorization on every principal manager. */
4
+ export declare function setMcpToolAuthorizationResolver(resolver?: McpToolAuthorizationResolver): void;
3
5
  /** Lazily hydrate one bounded manager for one authenticated user/org pair. */
4
6
  export declare function getMcpManagerForPrincipal(rawPrincipal: McpPrincipal, forceRefresh?: boolean): Promise<McpClientManager>;
5
7
  export declare function refreshMcpManagerForPrincipal(rawPrincipal: McpPrincipal): Promise<boolean>;
@@ -6,6 +6,14 @@ const MCP_MANAGER_CACHE_LIMIT = 32;
6
6
  const MCP_MANAGER_IDLE_TTL_MS = 15 * 60_000;
7
7
  const MCP_CONFIG_TTL_MS = 60_000;
8
8
  const managers = new Map();
9
+ let toolAuthorizationResolver;
10
+ /** Install the app-owned MCP tool authorization on every principal manager. */
11
+ export function setMcpToolAuthorizationResolver(resolver) {
12
+ toolAuthorizationResolver = resolver;
13
+ for (const entry of managers.values()) {
14
+ entry.manager.setToolAuthorizationResolver(resolver);
15
+ }
16
+ }
9
17
  function principalKey(principal) {
10
18
  return JSON.stringify([principal.userEmail, principal.orgId]);
11
19
  }
@@ -61,8 +69,10 @@ export async function getMcpManagerForPrincipal(rawPrincipal, forceRefresh = fal
61
69
  throw new Error("MCP manager capacity is busy hydrating");
62
70
  }
63
71
  }
72
+ const manager = new McpClientManager(null);
73
+ manager.setToolAuthorizationResolver(toolAuthorizationResolver);
64
74
  entry = {
65
- manager: new McpClientManager(null),
75
+ manager,
66
76
  principal,
67
77
  lastAccessedAt: now,
68
78
  configuredAt: 0,
@@ -136,6 +146,7 @@ export async function stopAllMcpManagers() {
136
146
  await Promise.all(entries.map(stopEntryAfterReady));
137
147
  }
138
148
  export function _resetMcpManagerRegistryForTests() {
149
+ toolAuthorizationResolver = undefined;
139
150
  return stopAllMcpManagers();
140
151
  }
141
152
  export function resolveBackgroundMcpToolSelection(requested, includeAll) {
@@ -6,6 +6,7 @@ import type { McpActionEntryOptions } from "../../mcp-client/index.js";
6
6
  import type { ExternalAgentPolicy } from "../../mcp/external-agent-policy.js";
7
7
  import type { DatabaseToolsOption } from "../../scripts/db/tool-mode.js";
8
8
  import type { PromptExamples } from "../prompts/index.js";
9
+ import type { AgentChatAdditionalActionsExternalSurface, ResolveAgentChatAdditionalActions } from "./additional-actions.js";
9
10
  import type { AgentChatMcpIcon, AgentChatMcpOptions } from "./mcp-options.js";
10
11
  export type NitroPluginDef = (nitroApp: any) => void | Promise<void>;
11
12
  export interface AgentChatPluginOptions {
@@ -63,6 +64,12 @@ export interface AgentChatPluginOptions {
63
64
  clientCompatibilityVersion?: string;
64
65
  connectApps?: boolean;
65
66
  backgroundMcpTools?: "requested" | "all";
67
+ /**
68
+ * Resolve approval and per-call authorization for connected MCP tools.
69
+ * Authorization runs at the shared MCP invocation boundary for agent and
70
+ * app calls; use `resolveActionSurface` separately to hide unavailable tools
71
+ * from an interactive request.
72
+ */
66
73
  resolveMcpActionEntry?: McpActionEntryOptions["resolveActionEntry"];
67
74
  mcp?: AgentChatMcpOptions;
68
75
  /** @deprecated Use `mcp.title` / `mcp.description` / `mcp.websiteUrl` / `mcp.icons`. */
@@ -96,6 +103,26 @@ export interface AgentChatPluginOptions {
96
103
  finalResponseGuard?: AgentLoopFinalResponseGuard;
97
104
  prepareRequest?: ProductionAgentOptions["prepareRequest"];
98
105
  resolveActionSurface?: ProductionAgentOptions["resolveActionSurface"];
106
+ /**
107
+ * Adds actions for the signed-in caller on each request, such as tools whose
108
+ * names are only known at runtime. Called once per run, before `tool-search`
109
+ * and `resolveActionSurface`, so both see the entries; `surface` says which
110
+ * loop is asking. Interactive chat always calls it; A2A and MCP `ask_app`
111
+ * call it only when listed in `additionalActionSurfaces`.
112
+ *
113
+ * Entries are treated as untrusted. One whose name is malformed, reserved by
114
+ * a framework, template, dev, or MCP action (even one hidden from the
115
+ * agent), or an `mcp__` name this caller cannot see is ignored with a
116
+ * warning. Descriptions are cleaned and capped, and schemas must be bounded
117
+ * object schemas. Every call rechecks that it runs for the caller the entry
118
+ * was resolved for, enforces the entry's `access` policy, and validates its
119
+ * arguments without type coercion. A durable background continuation keeps
120
+ * the turn's original names. Not called for anonymous requests, scheduled
121
+ * jobs, automations, or agent-team sub-agents.
122
+ */
123
+ resolveAdditionalActions?: ResolveAgentChatAdditionalActions;
124
+ /** Surfaces besides interactive chat that receive `resolveAdditionalActions` entries. */
125
+ additionalActionSurfaces?: readonly AgentChatAdditionalActionsExternalSurface[];
99
126
  leanPrompt?: boolean;
100
127
  skipFilesContext?: boolean;
101
128
  initialToolNames?: string[];
@@ -173,6 +173,7 @@ import { buildSystemManifestSections, setContextXraySystemSections, } from "../a
173
173
  // `createAgentChatPlugin` below stays a thinner orchestrator.
174
174
  // ---------------------------------------------------------------------------
175
175
  import { buildSelectedA2AReceiverContext, createA2AEngineToolSurface, DEFAULT_DELEGATED_MAX_ITERATIONS, DEFAULT_DELEGATED_MAX_RUN_INPUT_TOKENS, DEFAULT_DELEGATED_MAX_TOOL_RESULT_CHARS, filterAgentTools, filterMcpOnlyActions, filterPublicAgentActions, filterDirectA2AActions, filterReadOnlyActions, isSelectedA2AReceiver, shouldSelectedA2AReceiverOwnObjective, resolveInitialToolNames, runA2AAgentLoop, runMCPAgentLoop, assembleA2AFinalResponse, buildPublicAgentA2ASkills, buildAuthenticatedAgentA2ASkills, resolveArtifactBaseUrl, unverifiedA2AUserError, } from "./agent-chat/action-filters-a2a.js";
176
+ import { normalizeAdditionalActionSurfaces, resolveAppAdditionalActions, } from "./agent-chat/additional-actions.js";
176
177
  import { createBuilderBrowserTool, createTeamTools, } from "./agent-chat/browser-team-tools.js";
177
178
  import { createDataWidgetActionEntries, createFrameworkContextEntry, createRefreshScreenEntry, createUrlTools, } from "./agent-chat/context-tools.js";
178
179
  import { _agentChatPromptSectionsForTests, buildFrameworkPrompts, buildSchemaBlock, collectFiles, corpusToolNamesTaughtByPrompt, generateActionsPrompt, generateCorpusToolsPrompt, } from "./agent-chat/framework-prompts.js";
@@ -734,6 +735,10 @@ export function createAgentChatPlugin(options) {
734
735
  const mcpActionEntryOptions = options?.resolveMcpActionEntry
735
736
  ? { resolveActionEntry: options.resolveMcpActionEntry }
736
737
  : {};
738
+ if (options?.resolveMcpActionEntry) {
739
+ const resolveMcpActionEntry = options.resolveMcpActionEntry;
740
+ setMcpToolAuthorizationResolver((tool) => resolveMcpActionEntry(tool)?.authorize);
741
+ }
737
742
  // Build the four assembled system prompt strings. These are static for the
738
743
  // lifetime of this plugin instance — examples come from options once at
739
744
  // startup, not per-request.
@@ -758,6 +763,28 @@ export function createAgentChatPlugin(options) {
758
763
  ...(toolNames ? { toolNames } : {}),
759
764
  });
760
765
  };
766
+ // Every registry any surface can dispatch to a built-in implementation,
767
+ // pushed as each one is assembled below. App-supplied request actions
768
+ // may not reuse any of these names, even on a surface (lean chat, A2A,
769
+ // ask_app) that leaves the built-in out, or an allowlist or approval
770
+ // granted to that name could be redirected to app code.
771
+ const reservedActionRegistries = [];
772
+ const additionalActionsConfig = {
773
+ resolver: options?.resolveAdditionalActions,
774
+ externalSurfaces: normalizeAdditionalActionSurfaces(options?.additionalActionSurfaces),
775
+ isReservedName: (name) => reservedActionRegistries.some((registry) => Object.hasOwn(registry, name)),
776
+ };
777
+ // The one place a request's caller-scoped actions are resolved — the
778
+ // caller's MCP tools plus the app's additional actions — for chat, A2A,
779
+ // and ask_app alike. Call it once per run, before tool-search and
780
+ // surface filtering, and reuse the result for the whole run.
781
+ const getRequestScopedActions = async (details, registered) => {
782
+ const mcpEntries = await getMcpActionEntriesForPrincipal(details.ownerEmail, details.orgId);
783
+ return {
784
+ ...mcpEntries,
785
+ ...(await resolveAppAdditionalActions(additionalActionsConfig, details, { ...registered, ...mcpEntries })),
786
+ };
787
+ };
761
788
  const getJobMcpActionEntries = async (job) => {
762
789
  const requested = job?.meta.mcpTools ?? [];
763
790
  const toolNames = resolveBackgroundMcpToolSelection(requested, options?.backgroundMcpTools === "all");
@@ -1574,6 +1601,7 @@ export function createAgentChatPlugin(options) {
1574
1601
  const externalFullActions = mcpFullActions
1575
1602
  ? { ...mcpOnlyActions, ...mcpFullActions }
1576
1603
  : undefined;
1604
+ reservedActionRegistries.push(templateScriptsAll, discoveredActionsAll, devScriptsForA2A, externalActions, ...(externalFullActions ? [externalFullActions] : []));
1577
1605
  const { mountA2A } = await import("../a2a/server.js");
1578
1606
  const localA2AConfig = {
1579
1607
  appId: options?.appId,
@@ -1905,8 +1933,8 @@ export function createAgentChatPlugin(options) {
1905
1933
  // repetition guard kills the run minutes later. A rejected `{}` call
1906
1934
  // returns a schema error the model can fix on the next step, which is
1907
1935
  // strictly better than a shell loop nobody can see.
1908
- const requestMcpActionEntries = await getMcpActionEntriesForPrincipal(userEmail, getRequestOrgId());
1909
- const a2aActions = attachToolSearch(devActive
1936
+ const a2aOrgId = getRequestOrgId();
1937
+ const a2aRegisteredActions = devActive
1910
1938
  ? {
1911
1939
  ...templateScripts,
1912
1940
  ...resourceScripts,
@@ -1926,7 +1954,6 @@ export function createAgentChatPlugin(options) {
1926
1954
  ...coreEmailTools,
1927
1955
  ...coreAttachmentTools,
1928
1956
  ...browserTools,
1929
- ...requestMcpActionEntries,
1930
1957
  ...devScriptsForA2A,
1931
1958
  ...devRunCodeTool,
1932
1959
  }
@@ -1951,10 +1978,18 @@ export function createAgentChatPlugin(options) {
1951
1978
  ...coreEmailTools,
1952
1979
  ...coreAttachmentTools,
1953
1980
  ...browserTools,
1954
- ...requestMcpActionEntries,
1955
1981
  ...(resolvedProdCodeExec !== "off" ? runCodeTool : {}),
1956
1982
  ...prodCodingTools,
1957
- });
1983
+ };
1984
+ const a2aActions = attachToolSearch({
1985
+ ...a2aRegisteredActions,
1986
+ ...(await getRequestScopedActions({
1987
+ surface: "a2a",
1988
+ event: context.event,
1989
+ ownerEmail: userEmail,
1990
+ orgId: a2aOrgId,
1991
+ }, a2aRegisteredActions)),
1992
+ });
1958
1993
  const a2aToolSurface = createA2AEngineToolSurface(actionsToEngineTools(a2aActions), effectiveInitialToolNames, {
1959
1994
  receiverOwnsObjective,
1960
1995
  // Same curated set the MCP mount serves: config names plus the
@@ -2027,6 +2062,9 @@ export function createAgentChatPlugin(options) {
2027
2062
  // scope when a processor hop or alternate runner is involved.
2028
2063
  ownerEmail: userEmail,
2029
2064
  orgId: getRequestOrgId() ?? null,
2065
+ // Without it, every action with an `access` policy is refused
2066
+ // for lack of an application identity.
2067
+ appId: options?.appId,
2030
2068
  executionMode: "act",
2031
2069
  runId: context.taskId,
2032
2070
  networkProtocol: "a2a",
@@ -2402,8 +2440,8 @@ export function createAgentChatPlugin(options) {
2402
2440
  // here for the same reason as the A2A surface above: an external
2403
2441
  // caller cannot nurse a shell command that misfires.
2404
2442
  const devActiveMcp = isDevMode();
2405
- const requestMcpActionEntries = await getMcpActionEntriesForPrincipal(getRequestUserEmail(), getRequestOrgId());
2406
- const mcpActions = attachToolSearch(devActiveMcp
2443
+ const mcpOrgId = getRequestOrgId();
2444
+ const mcpRegisteredActions = devActiveMcp
2407
2445
  ? {
2408
2446
  ...templateScripts,
2409
2447
  ...resourceScripts,
@@ -2416,7 +2454,6 @@ export function createAgentChatPlugin(options) {
2416
2454
  ...workspaceFilesTool,
2417
2455
  ...workspaceFileActions,
2418
2456
  ...toolActions,
2419
- ...requestMcpActionEntries,
2420
2457
  ...devScriptsForA2A,
2421
2458
  ...devRunCodeTool,
2422
2459
  }
@@ -2434,10 +2471,13 @@ export function createAgentChatPlugin(options) {
2434
2471
  ...workspaceFilesTool,
2435
2472
  ...workspaceFileActions,
2436
2473
  ...toolActions,
2437
- ...requestMcpActionEntries,
2438
2474
  ...(resolvedProdCodeExec !== "off" ? runCodeTool : {}),
2439
2475
  ...prodCodingTools,
2440
- });
2476
+ };
2477
+ const mcpActions = attachToolSearch({
2478
+ ...mcpRegisteredActions,
2479
+ ...(await getRequestScopedActions({ surface: "mcp", ownerEmail, orgId: mcpOrgId }, mcpRegisteredActions)),
2480
+ });
2441
2481
  // Same compact initial-tool surface as interactive chat and A2A:
2442
2482
  // template actions + the small framework default set stay visible
2443
2483
  // on the first request; everything else (provider, MCP, extension
@@ -2488,6 +2528,7 @@ export function createAgentChatPlugin(options) {
2488
2528
  actions: mcpActions,
2489
2529
  ownerEmail,
2490
2530
  orgId: getRequestOrgId() ?? null,
2531
+ appId: options?.appId,
2491
2532
  executionMode: "act",
2492
2533
  runId: mcpRunId,
2493
2534
  networkProtocol: "mcp",
@@ -2581,6 +2622,7 @@ export function createAgentChatPlugin(options) {
2581
2622
  catch {
2582
2623
  // Ignore — templates without sharing still work.
2583
2624
  }
2625
+ reservedActionRegistries.push(httpActions);
2584
2626
  const { mountActionRoutes, mountWebMcpActionRoutes } = await import("./action-routes.js");
2585
2627
  const directoryProfile = mcpOptions.enabled
2586
2628
  ? mcpOptions.directoryProfile
@@ -3316,6 +3358,7 @@ export function createAgentChatPlugin(options) {
3316
3358
  : {}),
3317
3359
  });
3318
3360
  leanRunCodeToolActions = leanActions;
3361
+ reservedActionRegistries.push(prodActions, leanActions);
3319
3362
  // Always build the production handler (includes resource tools + call-agent + team tools)
3320
3363
  // In production mode (!canToggle), resolve the owner from the request session.
3321
3364
  const isHostedProd = !canToggle;
@@ -3525,9 +3568,10 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
3525
3568
  ? "\n\n<code-execution-mode>Full shell access is enabled (trusted mode). You have bash, read, edit, write, and run-code tools available. Use bash for file discovery, running tests and builds, and project CLIs. Use run-code for sandboxed JavaScript data processing: provider/API pagination, joins, classification, aggregation, and large-response reduction. Use tool-orchestration for short bounded fan-out or reduction over read-only tools. Use `pnpm action <name>` in bash to invoke registered app actions from the shell.</code-execution-mode>"
3526
3569
  : "\n\n<code-execution-mode>Sandboxed code execution is enabled. Use tool-orchestration for short bounded fan-out, joins, and reduction over read-only tools. Use run-code when you need its broader provider/web helpers, workspace staging, or durable background execution. In either tool, authenticated calls go through the provided host globals and results should be reduced before printing.</code-execution-mode>"
3527
3570
  : "";
3571
+ const prodHandlerActions = leanPrompt ? leanActions : prodActions;
3528
3572
  const prodHandler = createProductionAgentHandler({
3529
3573
  assertAiSetupReady: requireAgentChatAiSetup,
3530
- actions: leanPrompt ? leanActions : prodActions,
3574
+ actions: prodHandlerActions,
3531
3575
  systemPrompt: async (event) => {
3532
3576
  const { owner, extra } = await prepareRun(event);
3533
3577
  const requestActionsPrompt = resolveRequestActionsPrompt("tool");
@@ -3678,7 +3722,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
3678
3722
  _runSendByThread.delete(threadId);
3679
3723
  await onRunComplete(run, threadId, trackingSource);
3680
3724
  },
3681
- resolveAdditionalActions: ({ ownerEmail, orgId }) => getMcpActionEntriesForPrincipal(ownerEmail, orgId),
3725
+ resolveAdditionalActions: (details) => getRequestScopedActions({ surface: "chat", ...details }, prodHandlerActions),
3682
3726
  // Resolve owner from session for usage attribution in hosted prod
3683
3727
  resolveOwnerEmail: isHostedProd ? getOwnerFromEvent : undefined,
3684
3728
  });
@@ -3825,10 +3869,11 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
3825
3869
  // Wire the late-binding supplier for devRunCodeTool so the bridge can
3826
3870
  // call back into the fully-assembled devActions registry.
3827
3871
  devRunCodeToolActions = devActions;
3872
+ reservedActionRegistries.push(devActions);
3828
3873
  devHandler = createProductionAgentHandler({
3829
3874
  assertAiSetupReady: requireAgentChatAiSetup,
3830
3875
  actions: devActions,
3831
- resolveAdditionalActions: ({ ownerEmail, orgId }) => getMcpActionEntriesForPrincipal(ownerEmail, orgId),
3876
+ resolveAdditionalActions: (details) => getRequestScopedActions({ surface: "chat", ...details }, devActions),
3832
3877
  systemPrompt: async (event) => {
3833
3878
  const { owner, extra } = await prepareRun(event);
3834
3879
  const requestActionsPrompt = resolveRequestActionsPrompt(devNative ? "tool" : "cli");
@@ -6787,4 +6832,4 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
6787
6832
  * In production, provides only the default system prompt.
6788
6833
  */
6789
6834
  export const defaultAgentChatPlugin = createAgentChatPlugin();
6790
- import { getMcpManagerForPrincipal, invalidateMcpManagersForScope, resolveBackgroundMcpToolSelection, stopAllMcpManagers, mountMcpHubStatusRoute, mountMcpStatusRoute, } from "./agent-chat/mcp-glue.js";
6835
+ import { getMcpManagerForPrincipal, invalidateMcpManagersForScope, resolveBackgroundMcpToolSelection, setMcpToolAuthorizationResolver, stopAllMcpManagers, mountMcpHubStatusRoute, mountMcpStatusRoute, } from "./agent-chat/mcp-glue.js";
@@ -67,12 +67,12 @@ export declare function createAgentEngineApiKeyHandler(): import("h3").EventHand
67
67
  code: any;
68
68
  } | {
69
69
  errorCode?: undefined;
70
- error?: undefined;
71
70
  code?: undefined;
72
71
  ok: boolean;
73
72
  key: string;
74
73
  baseUrlKey?: string;
75
74
  scope: AgentEngineApiKeyScope;
76
75
  defaultModel?: import("./agent-engine-default-model-route.js").SavedKeyDefaultModelOutcome;
76
+ error?: undefined;
77
77
  }>>;
78
78
  export {};
@@ -1,9 +1,9 @@
1
1
  export declare function createAgentEngineOllamaModelsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
2
2
  error: string;
3
- models?: undefined;
4
3
  ok?: undefined;
4
+ models?: undefined;
5
5
  } | {
6
+ error?: undefined;
6
7
  ok: boolean;
7
8
  models: string[];
8
- error?: undefined;
9
9
  }>>;
@@ -8,7 +8,9 @@ function stringifyValue(value) {
8
8
  import crypto from "node:crypto";
9
9
  import fs from "node:fs";
10
10
  import path from "node:path";
11
+ import { getCurrentAuthEndpointContext } from "@better-auth/core/context";
11
12
  import { betterAuth, } from "better-auth";
13
+ import { createAuthMiddleware, createEmailVerificationToken, } from "better-auth/api";
12
14
  import { bearer } from "better-auth/plugins/bearer";
13
15
  import { jwt } from "better-auth/plugins/jwt";
14
16
  import { magicLink } from "better-auth/plugins/magic-link";
@@ -19,6 +21,7 @@ import { enterpriseAuthAdaptersBuilt, getAppConfig, } from "../app-config/index.
19
21
  import { TEMPLATES } from "../cli/templates-meta.js";
20
22
  import { getDbExec } from "../db/client.js";
21
23
  import { assertHostedRuntimeDatabase, getRuntimeDatabaseUrl, getPgliteClient, isPgliteUrl, loadPgliteDrizzle, pgliteDrizzleClient, pgPoolOptions, neonPoolOptions, guardNeonPool, sharedDbPool, resolveSharedDbPool, onSharedDbPoolsClosed, onSharedDbPoolReplaced, onDbClientsClosing, } from "../db/client.js";
24
+ import { ensureTable as ensureEmailLogTable } from "../email-catalog/log.js";
22
25
  import { CORE_CHANGE_EMAIL_CONFIRMATION_EMAIL_ID, CORE_CHANGE_EMAIL_VERIFICATION_EMAIL_ID, CORE_MAGIC_LINK_EMAIL_ID, CORE_RESET_PASSWORD_EMAIL_ID, CORE_VERIFY_SIGNUP_EMAIL_ID, } from "../email-catalog/system-emails.js";
23
26
  import { renderTransactionalEmail } from "../email-catalog/templates.js";
24
27
  import { executeIdentityRekey, rekeyIdentity, resumePendingIdentityRekeys, verifiedEmailChangeFromToken, } from "../identity/rekey.js";
@@ -80,6 +83,9 @@ async function preflightEmailIdentityRekey(oldEmail, newEmail) {
80
83
  });
81
84
  }
82
85
  export { getAuthLoginMode, resolveAuthLoginMode, resolveAuthLoginModeFromReadiness, } from "./auth-login-mode.js";
86
+ const EXISTING_ACCOUNT_VERIFICATION_COOLDOWN_MS = 5 * 60_000;
87
+ // Signup delivery holds its transaction, connection, and per-address lock.
88
+ const VERIFICATION_EMAIL_TIMEOUT_MS = 10_000;
83
89
  async function flushSignupTracking() {
84
90
  try {
85
91
  await Promise.race([
@@ -1559,6 +1565,25 @@ async function createBetterAuthInstance(config) {
1559
1565
  });
1560
1566
  },
1561
1567
  });
1568
+ const sendVerificationEmail = async ({ user, url, token }) => {
1569
+ const deliveredVerifyUrl = emailAuthLinkLandingUrl(url) ?? url;
1570
+ const emailChange = await verifiedEmailChangeFromToken(token, secret, user.email);
1571
+ if (emailChange)
1572
+ await preflightEmailIdentityRekey(emailChange.oldEmail, emailChange.newEmail);
1573
+ const renderedEmail = await renderTransactionalEmail(emailChange
1574
+ ? CORE_CHANGE_EMAIL_VERIFICATION_EMAIL_ID
1575
+ : CORE_VERIFY_SIGNUP_EMAIL_ID, { email: user.email, verifyUrl: deliveredVerifyUrl });
1576
+ await sendEmail({
1577
+ to: user.email,
1578
+ ...renderedEmail,
1579
+ disableClickTracking: true,
1580
+ templateId: emailChange
1581
+ ? CORE_CHANGE_EMAIL_VERIFICATION_EMAIL_ID
1582
+ : CORE_VERIFY_SIGNUP_EMAIL_ID,
1583
+ authCritical: true,
1584
+ timeoutMs: VERIFICATION_EMAIL_TIMEOUT_MS,
1585
+ });
1586
+ };
1562
1587
  const auth = betterAuth({
1563
1588
  basePath,
1564
1589
  baseURL: appUrl,
@@ -1575,6 +1600,39 @@ async function createBetterAuthInstance(config) {
1575
1600
  // Email verification is enabled only when a provider is ready. Without
1576
1601
  // one, hosted deployments keep password signup available.
1577
1602
  requireEmailVerification,
1603
+ onExistingUserSignUp: requireEmailVerification
1604
+ ? async ({ user }) => {
1605
+ // Duplicate signup is unauthenticated, and direct auth.api calls
1606
+ // such as the register route skip Better Auth's IP rate limit.
1607
+ // The lock makes a concurrent signup for this address wait for
1608
+ // this transaction, so it sees the sent row before checking.
1609
+ const db = getDbExec();
1610
+ await db.execute({
1611
+ sql: "SELECT pg_advisory_xact_lock(hashtextextended(?, 0::bigint))",
1612
+ args: [`existing-account-verification:${user.email}`],
1613
+ });
1614
+ const { rows } = await db.execute({
1615
+ sql: `SELECT 1 FROM email_log
1616
+ WHERE template_id = ? AND recipient = ? AND status = 'sent' AND created_at > ?
1617
+ LIMIT 1`,
1618
+ args: [
1619
+ CORE_VERIFY_SIGNUP_EMAIL_ID,
1620
+ user.email,
1621
+ Date.now() - EXISTING_ACCOUNT_VERIFICATION_COOLDOWN_MS,
1622
+ ],
1623
+ });
1624
+ if (rows.length > 0)
1625
+ return;
1626
+ const endpoint = getCurrentAuthEndpointContext();
1627
+ const callbackURL = typeof endpoint.body?.callbackURL === "string" &&
1628
+ endpoint.body.callbackURL
1629
+ ? endpoint.body.callbackURL
1630
+ : "/";
1631
+ const token = await createEmailVerificationToken(secret, user.email);
1632
+ const url = `${endpoint.context.baseURL}/verify-email?token=${token}&callbackURL=${encodeURIComponent(callbackURL)}`;
1633
+ await sendVerificationEmail({ user, url, token });
1634
+ }
1635
+ : undefined,
1578
1636
  sendResetPassword: async ({ user, token }) => {
1579
1637
  const appBasePath = (process.env.VITE_APP_BASE_PATH ||
1580
1638
  process.env.APP_BASE_PATH ||
@@ -1599,24 +1657,7 @@ async function createBetterAuthInstance(config) {
1599
1657
  emailVerification: {
1600
1658
  sendOnSignUp: requireEmailVerification,
1601
1659
  autoSignInAfterVerification: true,
1602
- sendVerificationEmail: async ({ user, url, token }) => {
1603
- const deliveredVerifyUrl = emailAuthLinkLandingUrl(url) ?? url;
1604
- const emailChange = await verifiedEmailChangeFromToken(token, secret, user.email);
1605
- if (emailChange)
1606
- await preflightEmailIdentityRekey(emailChange.oldEmail, emailChange.newEmail);
1607
- const renderedEmail = await renderTransactionalEmail(emailChange
1608
- ? CORE_CHANGE_EMAIL_VERIFICATION_EMAIL_ID
1609
- : CORE_VERIFY_SIGNUP_EMAIL_ID, { email: user.email, verifyUrl: deliveredVerifyUrl });
1610
- await sendEmail({
1611
- to: user.email,
1612
- ...renderedEmail,
1613
- disableClickTracking: true,
1614
- templateId: emailChange
1615
- ? CORE_CHANGE_EMAIL_VERIFICATION_EMAIL_ID
1616
- : CORE_VERIFY_SIGNUP_EMAIL_ID,
1617
- authCritical: true,
1618
- });
1619
- },
1660
+ sendVerificationEmail,
1620
1661
  afterEmailVerification: async (user, request) => {
1621
1662
  if (!request)
1622
1663
  return;
@@ -1870,6 +1911,55 @@ async function createBetterAuthInstance(config) {
1870
1911
  ]),
1871
1912
  ...enterprisePlugins,
1872
1913
  ...configuredPlugins,
1914
+ {
1915
+ id: "verification-email-delivery",
1916
+ hooks: {
1917
+ before: [
1918
+ {
1919
+ matcher: (ctx) => ctx.path === "/sign-up/email" && requireEmailVerification,
1920
+ handler: createAuthMiddleware(async () => {
1921
+ // A failed delivery rolls signup back; lazy log DDL must not
1922
+ // leave its readiness cache pointing at a rolled-back table.
1923
+ await ensureEmailLogTable();
1924
+ }),
1925
+ },
1926
+ ],
1927
+ },
1928
+ init(ctx) {
1929
+ const emailVerification = ctx.options.emailVerification;
1930
+ if (!emailVerification?.sendVerificationEmail)
1931
+ return;
1932
+ const sendVerificationEmail = emailVerification.sendVerificationEmail;
1933
+ const deliveries = new WeakSet();
1934
+ emailVerification.sendVerificationEmail = (...args) => {
1935
+ const delivery = sendVerificationEmail(...args);
1936
+ if (delivery instanceof Promise)
1937
+ deliveries.add(delivery);
1938
+ return delivery;
1939
+ };
1940
+ const passwordOptions = ctx.options.emailAndPassword;
1941
+ const onExistingUserSignUp = passwordOptions?.onExistingUserSignUp;
1942
+ if (passwordOptions && onExistingUserSignUp) {
1943
+ passwordOptions.onExistingUserSignUp = (...args) => {
1944
+ const delivery = onExistingUserSignUp(...args);
1945
+ deliveries.add(delivery);
1946
+ return delivery;
1947
+ };
1948
+ }
1949
+ const runInBackgroundOrAwait = ctx.runInBackgroundOrAwait.bind(ctx);
1950
+ return {
1951
+ context: {
1952
+ runInBackgroundOrAwait(task) {
1953
+ // Better Auth swallows even awaited task errors; verification
1954
+ // must fail the request before the UI can claim mail was sent.
1955
+ if (task instanceof Promise && deliveries.has(task))
1956
+ return task;
1957
+ return runInBackgroundOrAwait(task);
1958
+ },
1959
+ },
1960
+ };
1961
+ },
1962
+ },
1873
1963
  ],
1874
1964
  });
1875
1965
  return auth;
@@ -261,7 +261,6 @@ export interface BuilderAgentUploadAttachment {
261
261
  name: string;
262
262
  dataUrl: string;
263
263
  text?: string;
264
- size: number;
265
264
  id: string;
266
265
  }
267
266
  export interface BuilderAgentUrlAttachment {
@@ -269,6 +268,9 @@ export interface BuilderAgentUrlAttachment {
269
268
  value: string;
270
269
  }
271
270
  export type BuilderAgentAttachment = BuilderAgentUploadAttachment | BuilderAgentUrlAttachment;
271
+ export type BuilderAgentWireAttachment = (BuilderAgentUploadAttachment & {
272
+ size: number;
273
+ }) | BuilderAgentUrlAttachment;
272
274
  export interface RunBuilderAgentArgs {
273
275
  prompt: string;
274
276
  context?: string;
@@ -279,7 +281,7 @@ export interface RunBuilderAgentArgs {
279
281
  userId?: string;
280
282
  }
281
283
  export declare const BUILDER_AGENT_CONTEXT_MAX_CHARS = 32000;
282
- export declare function normalizeBuilderAgentAttachments(attachments: BuilderAgentAttachment[] | undefined): BuilderAgentAttachment[] | undefined;
284
+ export declare function normalizeBuilderAgentAttachments(attachments: BuilderAgentAttachment[] | undefined): BuilderAgentWireAttachment[] | undefined;
283
285
  export declare function normalizeBuilderAgentContext(value: unknown): string | undefined;
284
286
  export declare function buildBuilderAgentUserPrompt(prompt: string, context?: string): string;
285
287
  export interface RunBuilderAgentResult {
@@ -3,7 +3,7 @@ import { getHeader, getQuery, getRequestIP, setResponseHeader, setResponseStatus
3
3
  import { ActionContractError } from "../action.js";
4
4
  import { getSetting } from "../settings/store.js";
5
5
  import { applyBuilderUtmTrackingParams } from "../shared/builder-link-tracking.js";
6
- import { parseBase64DataUrl } from "../shared/data-url.js";
6
+ import { base64ByteLength, isBase64, parseBase64DataUrl, } from "../shared/data-url.js";
7
7
  import { getAuthSecret, resolveSignupTrackingIdentity, } from "./better-auth-instance.js";
8
8
  import { resolveBuilderRequestAuthorization, } from "./builder-api-auth.js";
9
9
  import { firstPublicBuilderPreviewOriginFromEnv, isLoopbackBuilderRequestHost, isTrustedBuilderRequestHost, } from "./builder-preview-origin.js";
@@ -1561,50 +1561,55 @@ const BUILDER_AGENT_UPLOAD_CONTENT_TYPES = new Set([
1561
1561
  "application/json",
1562
1562
  "text/plain",
1563
1563
  ]);
1564
+ function invalidAttachment(message) {
1565
+ return new ActionContractError(message, {
1566
+ errorCode: "invalid_attachment",
1567
+ statusCode: 400,
1568
+ });
1569
+ }
1564
1570
  export function normalizeBuilderAgentAttachments(attachments) {
1565
1571
  if (!attachments || attachments.length === 0)
1566
1572
  return undefined;
1567
- const normalized = attachments.map((attachment) => {
1573
+ return attachments.map((attachment) => {
1568
1574
  if (attachment.type === "url") {
1569
1575
  let url;
1570
1576
  try {
1571
1577
  url = new URL(attachment.value);
1572
1578
  }
1573
1579
  catch {
1574
- throw new Error("Builder attachment URL is malformed");
1580
+ throw invalidAttachment("Builder attachment URL is malformed");
1575
1581
  }
1576
1582
  if (url.protocol !== "https:" && url.protocol !== "http:") {
1577
- throw new Error("Builder attachment URL must use http or https");
1583
+ throw invalidAttachment("Builder attachment URL must use http or https");
1578
1584
  }
1579
1585
  return attachment;
1580
1586
  }
1581
1587
  if (!BUILDER_AGENT_UPLOAD_CONTENT_TYPES.has(attachment.contentType)) {
1582
- throw new Error(`Unsupported Builder attachment content type: ${attachment.contentType}`);
1588
+ throw invalidAttachment(`Unsupported Builder attachment content type: ${attachment.contentType}`);
1583
1589
  }
1584
1590
  if (!attachment.name.trim() || !attachment.id.trim()) {
1585
- throw new Error("Builder upload attachments require a name and id");
1586
- }
1587
- if (!Number.isInteger(attachment.size) || attachment.size < 0) {
1588
- throw new Error("Builder attachment size must be a non-negative integer");
1591
+ throw invalidAttachment("Builder upload attachments require a name and id");
1589
1592
  }
1593
+ let size;
1590
1594
  if (attachment.contentType === "text/plain" ||
1591
1595
  attachment.contentType === "application/json") {
1592
1596
  if (attachment.text === undefined || attachment.dataUrl !== "") {
1593
- throw new Error("Text and JSON Builder attachments require text and an empty dataUrl");
1594
- }
1595
- if (Buffer.byteLength(attachment.text, "utf8") !== attachment.size) {
1596
- throw new Error("Builder attachment size does not match its text content");
1597
+ throw invalidAttachment("Text and JSON Builder attachments require text and an empty dataUrl");
1597
1598
  }
1599
+ size = Buffer.byteLength(attachment.text, "utf8");
1598
1600
  }
1599
1601
  else {
1600
1602
  const dataUrl = parseBase64DataUrl(attachment.dataUrl);
1601
1603
  if (!dataUrl || dataUrl.mediaType !== attachment.contentType) {
1602
- throw new Error("Image and PDF Builder attachments require a matching base64 dataUrl");
1604
+ throw invalidAttachment("Image and PDF Builder attachments require a matching base64 dataUrl");
1605
+ }
1606
+ if (!isBase64(dataUrl.data)) {
1607
+ throw invalidAttachment("Image and PDF Builder attachments require valid base64 data");
1603
1608
  }
1609
+ size = base64ByteLength(dataUrl.data);
1604
1610
  }
1605
- return attachment;
1611
+ return { ...attachment, size };
1606
1612
  });
1607
- return normalized;
1608
1613
  }
1609
1614
  export function normalizeBuilderAgentContext(value) {
1610
1615
  if (value == null)
@@ -50,6 +50,7 @@ export { createObservationalMemoryPlugin, defaultObservationalMemoryPlugin, } fr
50
50
  export { createGoogleAuthPlugin, type GoogleAuthPluginOptions, } from "./google-auth-plugin.js";
51
51
  export type { GoogleAuthMode } from "./google-auth-mode.js";
52
52
  export { createAgentChatPlugin, defaultAgentChatPlugin, loadResourcesForPrompt, type AgentChatPluginOptions, } from "./agent-chat-plugin.js";
53
+ export type { AgentChatAdditionalActionsDetails, AgentChatAdditionalActionsExternalSurface, AgentChatAdditionalActionsSurface, ResolveAgentChatAdditionalActions, } from "./agent-chat/additional-actions.js";
53
54
  export { buildFrameworkPrompts, generateActionsPrompt, } from "./agent-chat/framework-prompts.js";
54
55
  export { refreshMcpManagerForPrincipal } from "./agent-chat/mcp-glue.js";
55
56
  export { AGENT_CHAT_STREAM_PATH, AGENT_CHAT_STREAM_TOKEN_SUFFIX, AGENT_CHAT_STREAM_TOKEN_TTL_SECONDS, createAgentChatStreamToken, isAgentChatStreamingRuntime, readAgentChatStreamBearerToken, verifyAgentChatStreamToken, type AgentChatStreamPrincipal, } from "./agent-chat-stream.js";
@@ -99,7 +100,7 @@ export { isServerlessRuntime } from "../db/client.js";
99
100
  export { BUILDER_PUBLISH_MCP_RESOURCE, canAuthorizeBuilderApiRequest, hasBuilderApiCredentialCustody, resolveBuilderApiAuthorization, resolveBuilderLegacyRequestAuthorization, resolveBuilderRequestAuthorization, type BuilderLegacyCredentialKey, type BuilderRequestAuthorization, } from "./builder-api-auth.js";
100
101
  export { BUILDER_ASSETS_WRITE_SCOPE, BUILDER_OAUTH_SCOPE, type BuilderOAuthPermissionScope, } from "./builder-oauth.js";
101
102
  export { assertBuilderDesignSystemCodeIndexingAllowed, builderDesignSystemUrl, builderProjectBranchUrl, buildBuilderDesignSystemIndexFiles, collectBuilderDesignSystemGitHubFiles, createBuilderDesignSystemProxyFields, designSystemTierUpgradeUrl, fetchBuilderDesignSystemDecodeJobStatus, fetchBuilderDesignSystemDocs, fetchBuilderDesignSystemDocumentCount, fetchBuilderDesignSystemRecord, fetchBuilderDesignSystemTierLimit, getBuilderDesignSystemsBaseUrl, hydrateBuilderDesignSystemReference, indexBuilderDesignSystem, isBuilderDesignSystemReadyByCount, localBuilderDesignSystemId, mimeTypeForBuilderDesignSystemFilename, parseBuilderDesignSystemProxyReference, startBuilderDesignSystemIndex, startBuilderDesignSystemUpload, type BuildBuilderDesignSystemIndexFilesOptions, type BuilderDesignSystemCodeFileInput, type BuilderDesignSystemDecodeJobStatus, type BuilderDesignSystemDocsOptions, type BuilderDesignSystemDocument, type BuilderDesignSystemDocumentCountResult, type BuilderDesignSystemHydratedReference, type BuilderDesignSystemIndexFile, type BuilderDesignSystemIndexFromSourcesOptions, type BuilderDesignSystemIndexOptions, type BuilderDesignSystemIndexResult, type BuilderDesignSystemRecord, type BuilderDesignSystemStatus, type BuilderDesignSystemTierLimit, type BuilderDesignSystemGitHubFile, type BuilderDesignSystemGitHubFileCollection, type BuilderDesignSystemGitHubSource, type BuilderDesignSystemUploadAttachment, type BuilderDesignSystemUploadSlot, type BuilderDesignSystemProxyFields, type BuilderDesignSystemProxyFieldsOptions, type BuilderDesignSystemProxyReference, type BuilderDesignSystemSourceKind, } from "./builder-design-systems.js";
102
- export { cdnSafeOriginStatus, createBuilderProject, ensureBuilderProject, findBuilderProjectForRepo, getBuilderBranchProjectId, isBuilderBranchingEnabled, requestBuilderBrowserConnection, resolveBuilderBranchProjectId, resolveIsBuilderBranchingEnabled, runBuilderAgent, type BuilderAgentAttachment, type BuilderAgentUploadAttachment, type BuilderAgentUrlAttachment, type BuilderProjectResult, type RunBuilderAgentResult, } from "./builder-browser.js";
103
+ export { cdnSafeOriginStatus, createBuilderProject, ensureBuilderProject, findBuilderProjectForRepo, getBuilderBranchProjectId, isBuilderBranchingEnabled, requestBuilderBrowserConnection, resolveBuilderBranchProjectId, resolveIsBuilderBranchingEnabled, normalizeBuilderAgentAttachments, runBuilderAgent, type BuilderAgentAttachment, type BuilderAgentUploadAttachment, type BuilderAgentUrlAttachment, type BuilderProjectResult, type RunBuilderAgentResult, } from "./builder-browser.js";
103
104
  export { ensureFusionContainer, sendFusionBranchMessage, pushFusionBranch, reserveFusionHostingSlug, deployFusionProject, getFusionDeploys, getFusionBranchEditorUrl, getFusionHostingUrl, type FusionBranchRef, type EnsureFusionContainerResult, type SendFusionMessageResult, } from "./fusion-app.js";
104
105
  export { sendEmail, isEmailConfigured, getEmailReadiness, getEmailProvider, type EmailAttachment, type EmailReadiness, type EmailProvider, type SendEmailArgs, } from "./email.js";
105
106
  export { defineTransactionalEmail, defineTransactionalEmails, replaceTransactionalEmails, listTransactionalEmails, getTransactionalEmail, renderTransactionalEmailPreview, renderTransactionalEmailPreviewAsync, type TransactionalEmailDefinition, type RegisteredTransactionalEmail, } from "../email-catalog/registry.js";
@@ -94,7 +94,7 @@ export { isServerlessRuntime } from "../db/client.js";
94
94
  export { BUILDER_PUBLISH_MCP_RESOURCE, canAuthorizeBuilderApiRequest, hasBuilderApiCredentialCustody, resolveBuilderApiAuthorization, resolveBuilderLegacyRequestAuthorization, resolveBuilderRequestAuthorization, } from "./builder-api-auth.js";
95
95
  export { BUILDER_ASSETS_WRITE_SCOPE, BUILDER_OAUTH_SCOPE, } from "./builder-oauth.js";
96
96
  export { assertBuilderDesignSystemCodeIndexingAllowed, builderDesignSystemUrl, builderProjectBranchUrl, buildBuilderDesignSystemIndexFiles, collectBuilderDesignSystemGitHubFiles, createBuilderDesignSystemProxyFields, designSystemTierUpgradeUrl, fetchBuilderDesignSystemDecodeJobStatus, fetchBuilderDesignSystemDocs, fetchBuilderDesignSystemDocumentCount, fetchBuilderDesignSystemRecord, fetchBuilderDesignSystemTierLimit, getBuilderDesignSystemsBaseUrl, hydrateBuilderDesignSystemReference, indexBuilderDesignSystem, isBuilderDesignSystemReadyByCount, localBuilderDesignSystemId, mimeTypeForBuilderDesignSystemFilename, parseBuilderDesignSystemProxyReference, startBuilderDesignSystemIndex, startBuilderDesignSystemUpload, } from "./builder-design-systems.js";
97
- export { cdnSafeOriginStatus, createBuilderProject, ensureBuilderProject, findBuilderProjectForRepo, getBuilderBranchProjectId, isBuilderBranchingEnabled, requestBuilderBrowserConnection, resolveBuilderBranchProjectId, resolveIsBuilderBranchingEnabled, runBuilderAgent, } from "./builder-browser.js";
97
+ export { cdnSafeOriginStatus, createBuilderProject, ensureBuilderProject, findBuilderProjectForRepo, getBuilderBranchProjectId, isBuilderBranchingEnabled, requestBuilderBrowserConnection, resolveBuilderBranchProjectId, resolveIsBuilderBranchingEnabled, normalizeBuilderAgentAttachments, runBuilderAgent, } from "./builder-browser.js";
98
98
  export { ensureFusionContainer, sendFusionBranchMessage, pushFusionBranch, reserveFusionHostingSlug, deployFusionProject, getFusionDeploys, getFusionBranchEditorUrl, getFusionHostingUrl, } from "./fusion-app.js";
99
99
  export { sendEmail, isEmailConfigured, getEmailReadiness, getEmailProvider, } from "./email.js";
100
100
  export { defineTransactionalEmail, defineTransactionalEmails, replaceTransactionalEmails, listTransactionalEmails, getTransactionalEmail, renderTransactionalEmailPreview, renderTransactionalEmailPreviewAsync, } from "../email-catalog/registry.js";