@agent-native/core 0.176.1 → 0.177.0-nightly-20260828155142

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 (71) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/actions/set-workspace-feature-flag.ts +2 -2
  3. package/corpus/templates/analytics/app/components/agents/FeatureFlagsFleetPanel.tsx +16 -1
  4. package/corpus/templates/analytics/changelog/2026-08-27-show-analytics-s-own-registered-flags-in-fleet-feature-flag-.md +6 -0
  5. package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +204 -32
  6. package/corpus/templates/assets/changelog/2026-08-26-templates-across-brand-kits.md +1 -0
  7. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +61 -68
  8. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +9 -3
  9. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +3 -3
  10. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +8 -3
  11. package/corpus/templates/calendar/app/lib/calendar-timezone.ts +12 -3
  12. package/corpus/templates/calendar/changelog/2026-08-28-all-day-events-stay-in-the-compact-top-bar-in-day-and-week-v.md +6 -0
  13. package/corpus/templates/calendar/changelog/2026-08-28-event-creators-can-choose-whether-a-meeting-shows-as-free-or.md +6 -0
  14. package/corpus/templates/clips/app/global.css +0 -27
  15. package/corpus/templates/clips/app/i18n/en-US.ts +2 -0
  16. package/corpus/templates/clips/app/routes/download.tsx +131 -16
  17. package/corpus/templates/clips/app/routes/share.$shareId.tsx +35 -20
  18. package/corpus/templates/clips/server/routes/api/clips-latest.json.get.ts +77 -22
  19. package/corpus/templates/forms/actions/response-insights.ts +4 -4
  20. package/corpus/templates/forms/actions/retry-response-deliveries.ts +26 -0
  21. package/corpus/templates/forms/server/db/schema.ts +64 -17
  22. package/corpus/templates/forms/server/handlers/submissions.ts +1022 -94
  23. package/corpus/templates/forms/server/lib/integrations.ts +100 -33
  24. package/corpus/templates/forms/server/middleware/00-public-cors.ts +1 -1
  25. package/corpus/templates/forms/server/plugins/db.ts +40 -0
  26. package/corpus/templates/plan/agent-native.json +1 -4
  27. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +6 -1
  28. package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +2 -2
  29. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +6 -6
  30. package/corpus/templates/slides/app/components/editor/ImageOverlay.tsx +78 -12
  31. package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +4 -4
  32. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +111 -27
  33. package/corpus/templates/slides/app/components/editor/SlidesLayersPanel.tsx +20 -34
  34. package/corpus/templates/slides/app/components/editor/slide-style.ts +2 -0
  35. package/corpus/templates/slides/app/global.css +35 -0
  36. package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +133 -5
  37. package/corpus/templates/slides/app/lib/slide-image-replacement.ts +675 -0
  38. package/corpus/templates/slides/app/lib/validate-native-slide-html.ts +1 -0
  39. package/corpus/templates/slides/app/pages/DeckEditor.tsx +44 -44
  40. package/corpus/templates/slides/app/root.tsx +1 -22
  41. package/corpus/templates/slides/changelog/2026-08-27-image-fit-controls-apply-cover-crops-and-position.md +6 -0
  42. package/corpus/templates/slides/changelog/2026-08-28-shared-slides-with-a-style-block-render-again.md +6 -0
  43. package/dist/action.d.ts +3 -1
  44. package/dist/client/FeedbackButton.d.ts +12 -0
  45. package/dist/client/FeedbackButton.js +62 -28
  46. package/dist/client/app-providers.js +13 -1
  47. package/dist/client/feedback-context.d.ts +1 -0
  48. package/dist/client/feedback-context.js +3 -2
  49. package/dist/client/host-bridge.d.ts +1 -0
  50. package/dist/client/observability/ThumbsFeedback.js +73 -10
  51. package/dist/client/webmcp.d.ts +4 -0
  52. package/dist/client/webmcp.js +51 -4
  53. package/dist/collab/routes.d.ts +1 -1
  54. package/dist/collab/struct-routes.d.ts +1 -1
  55. package/dist/index.browser.d.ts +1 -1
  56. package/dist/index.browser.js +1 -1
  57. package/dist/observability/routes.d.ts +5 -5
  58. package/dist/observability/routes.js +8 -2
  59. package/dist/observability/store.d.ts +1 -1
  60. package/dist/observability/store.js +18 -3
  61. package/dist/observability/types.d.ts +1 -0
  62. package/dist/progress/routes.d.ts +1 -1
  63. package/dist/resources/handlers.d.ts +2 -2
  64. package/dist/secrets/routes.d.ts +9 -9
  65. package/dist/server/action-routes.d.ts +1 -6
  66. package/dist/server/action-routes.js +58 -17
  67. package/dist/server/agent-chat-plugin.js +8 -1
  68. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  69. package/dist/server/transcribe-voice.d.ts +1 -1
  70. package/docs/content/webmcp.mdx +31 -12
  71. package/package.json +1 -1
@@ -2,7 +2,7 @@ export * from "./config.js";
2
2
  export { addContextToAgentChat, appendAgentChatContextToMessage, clearAgentChatContext, formatAgentChatContextItemsForPrompt, listAgentChatContext, refreshAgentChatContext, removeAgentChatContextItem, sendToAgentChat, sendToAgentChatAndConfirm, setAgentChatContextItem, setContextToAgentChat, isEmbedMcpChatBridgeActive, useAgentChatContext, useAgentChatGenerating, useDevMode, useSendToAgentChat, AGENT_NATIVE_MCP_APP_HOST_MESSAGE_TYPES, getMcpAppHostContext, openMcpAppHostLink, requestMcpAppDisplayMode, sendMcpAppHostMessage, updateMcpAppModelContext, useMcpAppHostContext, CodeRequiredDialog, AgentNativeI18nProvider, LanguagePicker, getLocaleInitScript, localeDirection, normalizeLocaleCode, normalizeLocalePreference, normalizeLocalizationPreference, resolveLocaleFromCandidates, resolveLocaleFromPreference, useFormatters, useLocale, useT, DEFAULT_LOCALE, LOCALE_HYDRATION_GLOBAL, LOCALE_METADATA, LOCALE_STORAGE_KEY, SUPPORTED_LOCALES, useDbSync, useFileWatcher, useSession, cn, ApiKeySettings, type AgentChatContextItem, type AgentChatContextMessage, type AgentChatContextMutationOptions, type AgentChatContextRemoveOptions, type AgentChatContextSetOptions, type AgentChatContextState, type AgentChatMessage, type AgentNativeMcpAppHostMessageType, type McpAppDisplayMode, type McpAppHostChatMessage, type McpAppHostCapabilities, type McpAppHostContext, type McpAppHostContextSnapshot, type McpAppModelContextContentPart, type McpAppModelContextUpdate, type CodeRequiredDialogProps, type AuthSession, type UseAgentChatContextResult, type AgentNativeI18nCatalog, type AgentNativeI18nProviderProps, type LocaleCode, type LocaleHydrationPayload, type LocaleMessages, type LocaleMetadata, type LocalePreference, type LocalizationPreference, } from "./client/index.js";
3
3
  export { agentChat } from "./shared/index.js";
4
4
  export { parseArgs, camelCaseArgs } from "./scripts/parse-args.js";
5
- export { AgentNativeWebMcpUnsupportedError, createAgentNativeWebMcpClient, createAgentNativeWebMcpRegistration, isAgentNativeWebMcpSupported, type AgentNativeWebMcpApprovalRequest, type AgentNativeWebMcpClient, type AgentNativeWebMcpClientOptions, type AgentNativeWebMcpRegistration, type AgentNativeWebMcpRegistrationOptions, type AgentNativeWebMcpTool, type AgentNativeWebMcpToolAnnotations, type AgentNativeWebMcpToolExecutionOptions, type AgentNativeWebMcpToolResult, } from "./client/webmcp.js";
5
+ export { AgentNativeWebMcpUnsupportedError, createAgentNativeWebMcpClient, createAgentNativeWebMcpRegistration, createAgentNativeServerActionWebMcpRegistration, isAgentNativeWebMcpSupported, type AgentNativeWebMcpApprovalRequest, type AgentNativeWebMcpClient, type AgentNativeWebMcpClientOptions, type AgentNativeWebMcpRegistration, type AgentNativeWebMcpRegistrationOptions, type AgentNativeWebMcpTool, type AgentNativeWebMcpToolAnnotations, type AgentNativeWebMcpToolExecutionOptions, type AgentNativeWebMcpToolResult, } from "./client/webmcp.js";
6
6
  export { defineAction, fail, type FailOptions, ActionContractError, isActionContractError, type ActionContractErrorOptions, AgentActionStopError, isAgentActionStopError, type ActionHttpConfig, type AgentActionStopOptions, MCP_APP_EXTENSION_ID, MCP_APP_MIME_TYPE, MCP_APP_RESOURCE_URI_META_KEY, type ActionMcpAppConfig, type ActionMcpAppCsp, type ActionMcpAppCspBuilder, type ActionMcpAppHtmlBuilder, type ActionMcpAppPermissions, type ActionMcpAppResourceConfig, type ActionMcpAppResourceMeta, } from "./action.js";
7
7
  export { isEmailDerivedName, normalizeUserProfileName, resolveUserProfileName, USER_PROFILE_SETTING_KEY, type UserProfile, } from "./user-profile/index.js";
8
8
  export { ACTION_CHAT_UI_DATA_CHART_RENDERER, ACTION_CHAT_UI_DATA_INSIGHTS_RENDERER, ACTION_CHAT_UI_DATA_TABLE_RENDERER, ACTION_CHAT_UI_DATA_WIDGET_RENDERER, ACTION_CHAT_UI_INLINE_EXTENSION_RENDERER, ACTION_CHAT_UI_WORKSPACE_FILE_RENDERER, type ActionChatUIConfig, } from "./action-ui.js";
@@ -8,7 +8,7 @@ export { addContextToAgentChat, appendAgentChatContextToMessage, clearAgentChatC
8
8
  export { agentChat } from "./shared/index.js";
9
9
  // Pure utilities (no Node.js deps — safe for browser and SSR)
10
10
  export { parseArgs, camelCaseArgs } from "./scripts/parse-args.js";
11
- export { AgentNativeWebMcpUnsupportedError, createAgentNativeWebMcpClient, createAgentNativeWebMcpRegistration, isAgentNativeWebMcpSupported, } from "./client/webmcp.js";
11
+ export { AgentNativeWebMcpUnsupportedError, createAgentNativeWebMcpClient, createAgentNativeWebMcpRegistration, createAgentNativeServerActionWebMcpRegistration, isAgentNativeWebMcpSupported, } from "./client/webmcp.js";
12
12
  // defineAction — used by template actions, no Node.js deps
13
13
  export { defineAction, fail, ActionContractError, isActionContractError, AgentActionStopError, isAgentActionStopError, MCP_APP_EXTENSION_ID, MCP_APP_MIME_TYPE, MCP_APP_RESOURCE_URI_META_KEY, } from "./action.js";
14
14
  export { isEmailDerivedName, normalizeUserProfileName, resolveUserProfileName, USER_PROFILE_SETTING_KEY, } from "./user-profile/index.js";
@@ -41,27 +41,27 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
- error?: undefined;
45
- ok?: undefined;
46
44
  summary: import("./types.js").TraceSummary;
47
45
  spans: import("./types.js").TraceSpan[];
48
46
  id?: undefined;
49
- } | {
50
47
  error?: undefined;
51
48
  ok?: undefined;
49
+ } | {
52
50
  summary?: undefined;
53
51
  spans?: undefined;
54
52
  id: string;
55
- } | {
53
+ error?: undefined;
56
54
  ok?: undefined;
55
+ } | {
57
56
  summary?: undefined;
58
57
  spans?: undefined;
59
58
  id?: undefined;
60
59
  error: any;
60
+ ok?: undefined;
61
61
  } | {
62
- error?: undefined;
63
62
  summary?: undefined;
64
63
  spans?: undefined;
65
64
  id?: undefined;
66
65
  ok: boolean;
66
+ error?: undefined;
67
67
  }>>;
@@ -19,7 +19,7 @@
19
19
  * POST /experiments/:id/results — compute experiment results
20
20
  * GET /experiments/:id/results — get experiment results
21
21
  */
22
- import { defineEventHandler, getMethod, getQuery, setResponseStatus, } from "h3";
22
+ import { defineEventHandler, getHeader, getMethod, getQuery, setResponseStatus, } from "h3";
23
23
  import { getSession } from "../server/auth.js";
24
24
  import { readBody } from "../server/h3-helpers.js";
25
25
  import { getRequestContext } from "../server/request-context.js";
@@ -164,16 +164,22 @@ export function createObservabilityHandler() {
164
164
  ? JSON.stringify(rawValue)
165
165
  : String(rawValue);
166
166
  const id = nanoid();
167
- await insertFeedback({
167
+ const idempotencyKey = feedbackType === "text"
168
+ ? getHeader(event, "idempotency-key")?.trim() || null
169
+ : null;
170
+ const inserted = await insertFeedback({
168
171
  id,
169
172
  runId: body.runId ? String(body.runId) : null,
170
173
  threadId: body.threadId ? String(body.threadId) : null,
171
174
  messageSeq: typeof body.messageSeq === "number" ? body.messageSeq : null,
172
175
  feedbackType,
173
176
  value,
177
+ idempotencyKey,
174
178
  userId: owner,
175
179
  createdAt: Date.now(),
176
180
  });
181
+ if (!inserted)
182
+ return { id };
177
183
  {
178
184
  const runId = body.runId ? String(body.runId) : null;
179
185
  const threadId = body.threadId ? String(body.threadId) : null;
@@ -34,7 +34,7 @@ export declare function getLatestTraceSummaryForThread(threadId: string, opts: {
34
34
  userId: string;
35
35
  excludeRunId: string;
36
36
  }): Promise<TraceSummary | null>;
37
- export declare function insertFeedback(entry: FeedbackEntry): Promise<void>;
37
+ export declare function insertFeedback(entry: FeedbackEntry): Promise<boolean>;
38
38
  export declare function getFeedback(opts: {
39
39
  threadId?: string;
40
40
  sinceMs?: number;
@@ -95,6 +95,7 @@ export async function ensureObservabilityTables() {
95
95
  message_seq ${intType()},
96
96
  feedback_type TEXT NOT NULL,
97
97
  value TEXT NOT NULL DEFAULT '',
98
+ idempotency_key TEXT,
98
99
  user_id TEXT,
99
100
  created_at ${intType()} NOT NULL
100
101
  )
@@ -187,6 +188,7 @@ export async function ensureObservabilityTables() {
187
188
  for (const table of USER_SCOPED_TABLES) {
188
189
  await ensureColumnExists(table, "user_id", `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS user_id TEXT`);
189
190
  }
191
+ await ensureColumnExists("agent_feedback", "idempotency_key", `ALTER TABLE agent_feedback ADD COLUMN IF NOT EXISTS idempotency_key TEXT`);
190
192
  await ensureIndexExists("idx_trace_spans_run", `CREATE INDEX IF NOT EXISTS idx_trace_spans_run ON agent_trace_spans (run_id)`);
191
193
  await ensureIndexExists("idx_trace_spans_thread", `CREATE INDEX IF NOT EXISTS idx_trace_spans_thread ON agent_trace_spans (thread_id)`);
192
194
  await ensureIndexExists("idx_trace_spans_created", `CREATE INDEX IF NOT EXISTS idx_trace_spans_created ON agent_trace_spans (created_at)`);
@@ -198,6 +200,7 @@ export async function ensureObservabilityTables() {
198
200
  await ensureIndexExists("idx_feedback_created", `CREATE INDEX IF NOT EXISTS idx_feedback_created ON agent_feedback (created_at)`);
199
201
  await ensureIndexExists("idx_feedback_user", `CREATE INDEX IF NOT EXISTS idx_feedback_user ON agent_feedback (user_id, created_at)`);
200
202
  await ensureIndexExists("idx_feedback_type_created", `CREATE INDEX IF NOT EXISTS idx_feedback_type_created ON agent_feedback (feedback_type, created_at)`);
203
+ await ensureIndexExists("idx_feedback_idempotency", `CREATE UNIQUE INDEX IF NOT EXISTS idx_feedback_idempotency ON agent_feedback (user_id, idempotency_key)`);
201
204
  await ensureIndexExists("idx_satisfaction_thread", `CREATE INDEX IF NOT EXISTS idx_satisfaction_thread ON agent_satisfaction_scores (thread_id)`);
202
205
  await ensureIndexExists("idx_satisfaction_user", `CREATE INDEX IF NOT EXISTS idx_satisfaction_user ON agent_satisfaction_scores (user_id, computed_at)`);
203
206
  await ensureIndexExists("idx_evals_run", `CREATE INDEX IF NOT EXISTS idx_evals_run ON agent_evals (run_id)`);
@@ -241,6 +244,13 @@ export async function ensureObservabilityTables() {
241
244
  throw err;
242
245
  }
243
246
  }
247
+ try {
248
+ await client.execute(`ALTER TABLE agent_feedback ADD COLUMN idempotency_key TEXT`);
249
+ }
250
+ catch (err) {
251
+ if (!isDuplicateColumnError(err))
252
+ throw err;
253
+ }
244
254
  // Indexes for common query patterns
245
255
  const indexes = [
246
256
  `CREATE INDEX IF NOT EXISTS idx_trace_spans_run ON agent_trace_spans (run_id)`,
@@ -254,6 +264,7 @@ export async function ensureObservabilityTables() {
254
264
  `CREATE INDEX IF NOT EXISTS idx_feedback_created ON agent_feedback (created_at)`,
255
265
  `CREATE INDEX IF NOT EXISTS idx_feedback_user ON agent_feedback (user_id, created_at)`,
256
266
  `CREATE INDEX IF NOT EXISTS idx_feedback_type_created ON agent_feedback (feedback_type, created_at)`,
267
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_feedback_idempotency ON agent_feedback (user_id, idempotency_key)`,
257
268
  `CREATE INDEX IF NOT EXISTS idx_satisfaction_thread ON agent_satisfaction_scores (thread_id)`,
258
269
  `CREATE INDEX IF NOT EXISTS idx_satisfaction_user ON agent_satisfaction_scores (user_id, computed_at)`,
259
270
  `CREATE INDEX IF NOT EXISTS idx_evals_run ON agent_evals (run_id)`,
@@ -478,10 +489,11 @@ export async function getLatestTraceSummaryForThread(threadId, opts) {
478
489
  export async function insertFeedback(entry) {
479
490
  await ensureObservabilityTables();
480
491
  const client = getDbExec();
481
- await client.execute({
492
+ const result = await client.execute({
482
493
  sql: `INSERT INTO agent_feedback
483
- (id, run_id, thread_id, message_seq, feedback_type, value, user_id, created_at)
484
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
494
+ (id, run_id, thread_id, message_seq, feedback_type, value, idempotency_key, user_id, created_at)
495
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
496
+ ON CONFLICT DO NOTHING`,
485
497
  args: [
486
498
  entry.id,
487
499
  entry.runId,
@@ -489,10 +501,12 @@ export async function insertFeedback(entry) {
489
501
  entry.messageSeq,
490
502
  entry.feedbackType,
491
503
  entry.value,
504
+ entry.idempotencyKey,
492
505
  entry.userId,
493
506
  entry.createdAt,
494
507
  ],
495
508
  });
509
+ return result.rowsAffected > 0;
496
510
  }
497
511
  export async function getFeedback(opts) {
498
512
  await ensureObservabilityTables();
@@ -1019,6 +1033,7 @@ function rowToFeedback(row) {
1019
1033
  messageSeq: row.message_seq != null ? Number(row.message_seq) : null,
1020
1034
  feedbackType: row.feedback_type,
1021
1035
  value: String(row.value ?? ""),
1036
+ idempotencyKey: row.idempotency_key ? String(row.idempotency_key) : null,
1022
1037
  userId: row.user_id ? String(row.user_id) : null,
1023
1038
  createdAt: Number(row.created_at),
1024
1039
  };
@@ -54,6 +54,7 @@ export interface FeedbackEntry {
54
54
  messageSeq: number | null;
55
55
  feedbackType: FeedbackType;
56
56
  value: string;
57
+ idempotencyKey?: string | null;
57
58
  userId: string | null;
58
59
  createdAt: number;
59
60
  }
@@ -15,6 +15,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
15
15
  error: string;
16
16
  ok?: undefined;
17
17
  } | {
18
- error?: undefined;
19
18
  ok: boolean;
19
+ error?: undefined;
20
20
  }>>;
@@ -51,8 +51,8 @@ export declare function handleDeleteResource(event: any): Promise<{
51
51
  error: string;
52
52
  ok?: undefined;
53
53
  } | {
54
- ok: boolean;
55
54
  error?: undefined;
55
+ ok: boolean;
56
56
  }>;
57
57
  /** POST /_agent-native/resources/upload — upload a file as a resource */
58
58
  export declare function handleUploadResource(event: any): Promise<import("./store.js").Resource | {
@@ -73,10 +73,10 @@ export declare function handleUploadResource(event: any): Promise<import("./stor
73
73
  runId: string | null;
74
74
  expiresAt: number | null;
75
75
  metadata: string | null;
76
+ error?: undefined;
76
77
  url: string;
77
78
  provider: string;
78
79
  storageSetupRequired?: undefined;
79
- error?: undefined;
80
80
  } | {
81
81
  error: string;
82
82
  storageSetupRequired: boolean;
@@ -34,37 +34,37 @@ export declare function createListSecretsHandler(): import("h3").EventHandlerWit
34
34
  /** POST /_agent-native/secrets/:key — write a secret. */
35
35
  export declare function createWriteSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
36
36
  error: string;
37
- ok?: undefined;
38
37
  status?: undefined;
38
+ ok?: undefined;
39
39
  } | {
40
- error?: undefined;
41
40
  ok: boolean;
42
41
  status: string;
42
+ error?: undefined;
43
43
  } | {
44
- ok?: undefined;
45
44
  error: string;
46
45
  removed?: undefined;
46
+ ok?: undefined;
47
47
  } | {
48
- error?: undefined;
49
48
  ok: boolean;
50
49
  removed: boolean;
50
+ error?: undefined;
51
51
  }>>;
52
52
  /**
53
53
  * POST /_agent-native/secrets/:key/test — validate an optional candidate value
54
54
  * or the current stored value without changing anything.
55
55
  */
56
56
  export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
57
- ok?: undefined;
58
57
  error: string;
59
58
  note?: undefined;
59
+ ok?: undefined;
60
60
  } | {
61
- error?: undefined;
62
61
  ok: boolean;
63
62
  note?: undefined;
64
- } | {
65
63
  error?: undefined;
64
+ } | {
66
65
  ok: boolean;
67
66
  note: string;
67
+ error?: undefined;
68
68
  } | {
69
69
  note?: undefined;
70
70
  ok: boolean;
@@ -95,11 +95,11 @@ export interface AdHocSecretPayload {
95
95
  export declare function createAdHocSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<AdHocSecretPayload[] | {
96
96
  error: string;
97
97
  } | {
98
- error?: undefined;
99
98
  ok: boolean;
100
99
  key: string;
101
- } | {
102
100
  error?: undefined;
101
+ } | {
103
102
  ok: boolean;
104
103
  removed: boolean;
104
+ error?: undefined;
105
105
  }>>;
@@ -60,10 +60,5 @@ export interface MountActionRoutesOptions {
60
60
  */
61
61
  actionRouteAuth?: ActionRouteAuthAdapter;
62
62
  }
63
- /**
64
- * Mount discovered actions as HTTP endpoints.
65
- *
66
- * Only actions from `autoDiscoverActions` (template actions) are mounted.
67
- * Built-in actions (resource-*, chat-*, shell, etc.) are NOT passed here.
68
- */
69
63
  export declare function mountActionRoutes(nitroApp: any, actions: Record<string, ActionEntry>, options?: MountActionRoutesOptions): void;
64
+ export declare function mountWebMcpActionRoutes(nitroApp: any, actions: Record<string, ActionEntry>, options?: MountActionRoutesOptions): void;
@@ -240,21 +240,23 @@ async function resolveRequestAuthCapability(event) {
240
240
  * Only actions from `autoDiscoverActions` (template actions) are mounted.
241
241
  * Built-in actions (resource-*, chat-*, shell, etc.) are NOT passed here.
242
242
  */
243
- export function mountActionRoutes(nitroApp, actions, options) {
243
+ function mountActionRoutesInternal(nitroApp, actions, options) {
244
244
  const mounted = [];
245
245
  const app = getH3App(nitroApp);
246
246
  for (const [name, entry] of Object.entries(actions)) {
247
247
  // Skip agent-only actions
248
- if (entry.http === false)
248
+ if (entry.http === false && !options?.includeAgentOnly)
249
249
  continue;
250
- const method = entry.http?.method ?? "POST";
251
- const path = entry.http?.path ?? name;
252
- const routePath = `${ROUTE_PREFIX}/${path}`;
250
+ const http = entry.http || undefined;
251
+ const method = options?.forcePost ? "POST" : (http?.method ?? "POST");
252
+ const path = options?.forcePost ? name : (http?.path ?? name);
253
+ const routePath = `${options?.routePrefix ?? ROUTE_PREFIX}/${path}`;
253
254
  // These two actions authenticate with a scoped A2A bearer rather than a
254
255
  // browser session. Let that verifier see the request before the cookie
255
256
  // auth guard rejects it; the action route still fails closed on invalid
256
257
  // or missing credentials.
257
- if (name === "list-feature-flags" || name === "set-feature-flag") {
258
+ if (!options?.caller &&
259
+ (name === "list-feature-flags" || name === "set-feature-flag")) {
258
260
  registerAuthPublicPaths([routePath], app);
259
261
  }
260
262
  app.use(routePath, defineEventHandler(async (event) => {
@@ -329,7 +331,7 @@ export function mountActionRoutes(nitroApp, actions, options) {
329
331
  // through, so a live same-origin session cookie can't silently execute
330
332
  // the request as the logged-in user.
331
333
  let resolvedCaller = null;
332
- {
334
+ if (options?.allowDelegatedCaller !== false) {
333
335
  let caller;
334
336
  try {
335
337
  caller = options?.actionRouteAuth?.resolveCaller
@@ -468,11 +470,12 @@ export function mountActionRoutes(nitroApp, actions, options) {
468
470
  // userEmail / orgId mirror the request context resolved above (do
469
471
  // NOT inject a dev identity — leave undefined when unauthenticated).
470
472
  try {
471
- const caller = resolvedCaller
472
- ? "a2a"
473
- : isFrontendActionRequest(event)
474
- ? "frontend"
475
- : "http";
473
+ const caller = options?.caller ??
474
+ (resolvedCaller
475
+ ? "a2a"
476
+ : isFrontendActionRequest(event)
477
+ ? "frontend"
478
+ : "http");
476
479
  const result = await entry.run(params, {
477
480
  userEmail,
478
481
  orgId: orgId ?? null,
@@ -574,11 +577,12 @@ export function mountActionRoutes(nitroApp, actions, options) {
574
577
  method: reqMethod,
575
578
  tags: {
576
579
  action: name,
577
- caller: resolvedCaller
578
- ? "a2a"
579
- : isFrontendActionRequest(event)
580
- ? "frontend"
581
- : "http",
580
+ caller: options?.caller ??
581
+ (resolvedCaller
582
+ ? "a2a"
583
+ : isFrontendActionRequest(event)
584
+ ? "frontend"
585
+ : "http"),
582
586
  status_code: String(status),
583
587
  },
584
588
  ...(requestId ? { extra: { request_id: requestId } } : {}),
@@ -598,3 +602,40 @@ export function mountActionRoutes(nitroApp, actions, options) {
598
602
  if (mounted.length > 0 && process.env.DEBUG)
599
603
  console.log(`[action-routes] Mounted ${mounted.length} action route(s): ${mounted.join(", ")}`);
600
604
  }
605
+ export function mountActionRoutes(nitroApp, actions, options) {
606
+ mountActionRoutesInternal(nitroApp, actions, options);
607
+ }
608
+ export function mountWebMcpActionRoutes(nitroApp, actions, options) {
609
+ const eligible = Object.fromEntries(Object.entries(actions).filter(([name, entry]) => /^[A-Za-z0-9_.-]{1,128}$/.test(name) &&
610
+ entry.agentTool !== false &&
611
+ entry.needsApproval === undefined));
612
+ if (Object.keys(eligible).length === 0)
613
+ return;
614
+ const app = getH3App(nitroApp);
615
+ app.use("/_agent-native/webmcp/manifest", defineEventHandler(async (event) => {
616
+ if (getMethod(event) !== "GET") {
617
+ setResponseStatus(event, 405);
618
+ return { error: "Method not allowed. Use GET." };
619
+ }
620
+ if (!options?.getOwnerFromEvent) {
621
+ throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
622
+ }
623
+ await options.getOwnerFromEvent(event);
624
+ setResponseHeader(event, "Cache-Control", "no-store");
625
+ return Object.entries(eligible).map(([name, entry]) => ({
626
+ name,
627
+ description: entry.tool.description,
628
+ inputSchema: entry.tool.parameters,
629
+ readOnly: entry.readOnly === true,
630
+ }));
631
+ }));
632
+ mountActionRoutesInternal(nitroApp, eligible, {
633
+ ...options,
634
+ routePrefix: "/_agent-native/webmcp/actions",
635
+ includeAgentOnly: true,
636
+ forcePost: true,
637
+ caller: "webmcp",
638
+ actionRouteAuth: undefined,
639
+ allowDelegatedCaller: false,
640
+ });
641
+ }
@@ -2128,7 +2128,7 @@ export function createAgentChatPlugin(options) {
2128
2128
  // Ignore — templates without sharing still work.
2129
2129
  }
2130
2130
  if (Object.keys(httpActions).length > 0) {
2131
- const { mountActionRoutes } = await import("./action-routes.js");
2131
+ const { mountActionRoutes, mountWebMcpActionRoutes } = await import("./action-routes.js");
2132
2132
  if (options?.actionRoutePublicPaths?.length) {
2133
2133
  registerAuthPublicPaths(options.actionRoutePublicPaths, getH3App(nitroApp));
2134
2134
  }
@@ -2139,6 +2139,13 @@ export function createAgentChatPlugin(options) {
2139
2139
  resolveOrgId: options?.resolveOrgId,
2140
2140
  actionRouteAuth: options?.actionRouteAuth,
2141
2141
  });
2142
+ mountWebMcpActionRoutes(nitroApp, httpActions, {
2143
+ getOwnerFromEvent,
2144
+ getUserNameFromEvent,
2145
+ appId: options?.appId,
2146
+ resolveOrgId: options?.resolveOrgId,
2147
+ actionRouteAuth: options?.actionRouteAuth,
2148
+ });
2142
2149
  }
2143
2150
  const preRunGitStatusByThread = new Map();
2144
2151
  async function recordPreRunGitStatus(threadId) {
@@ -34,10 +34,10 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
34
34
  export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
35
35
  error: any;
36
36
  } | {
37
- error?: undefined;
38
37
  ok: boolean;
39
38
  key: string;
40
39
  baseUrlKey?: string;
41
40
  scope: AgentEngineApiKeyScope;
41
+ error?: undefined;
42
42
  }>>;
43
43
  export {};
@@ -20,6 +20,6 @@ export declare function createTranscribeVoiceHandler(): import("h3").EventHandle
20
20
  error: string;
21
21
  text?: undefined;
22
22
  } | {
23
- error?: undefined;
24
23
  text: string;
24
+ error?: undefined;
25
25
  }>>;
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: "WebMCP"
3
- description: "Expose explicit Agent-Native client actions to browser agents and consume page-local WebMCP tools from Agent-Native chat."
3
+ description: "Automatically expose Agent-Native actions to browser agents, register explicit client actions, and consume page-local WebMCP tools from Agent-Native chat."
4
4
  search: "WebMCP browser tools modelContext registerTool getTools executeTool browser session"
5
5
  ---
6
6
 
@@ -15,24 +15,42 @@ discover them, and the browser mediates execution.
15
15
 
16
16
  The framework supports both directions:
17
17
 
18
- <!-- i18n-copy-ignore: oxfmt-only table alignment; no translated prose changed. -->
19
-
20
18
  | You want to... | Use |
21
19
  | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
22
- | Make selected actions in your app available to a browser agent | `createAgentNativeWebMcpRegistration` |
20
+ | Make backend actions available to a browser agent | Automatic on authenticated pages through `AppProviders` |
21
+ | Make page-local client actions available to a browser agent | `createAgentNativeWebMcpRegistration` |
23
22
  | Let Agent-Native chat discover and call WebMCP tools in the current page | `createAgentNativeWebMcpClient` plus `webmcp` on `AgentNativeEmbedded` or `AgentNativeFrame` |
24
23
  | Connect a headless or remote agent to an app | [MCP Server](/docs/mcp-protocol), [External Agents](/docs/external-agents), or [A2A](/docs/a2a-protocol) |
25
24
 
26
25
  WebMCP is not a network MCP server and does not make a closed app globally
27
26
  discoverable. A consuming agent needs an open, connected browser page and the
28
- page must explicitly opt into the tools it exposes.
27
+ page must expose tools to it.
29
28
 
30
29
  The browser API is experimental and may change. See the [Chrome WebMCP
31
30
  Imperative API](https://developer.chrome.com/docs/ai/webmcp/imperative-api) and
32
31
  the [WebMCP specification](https://webmachinelearning.github.io/webmcp/) for
33
32
  the current browser contract.
34
33
 
35
- ## Produce WebMCP tools
34
+ ## Automatic backend actions
35
+
36
+ On an authenticated app page, `AppProviders` automatically registers eligible
37
+ backend actions with `document.modelContext`. The tools are derived from the
38
+ same action registry used by the in-app agent and execute through the same
39
+ validated, authenticated server dispatcher. This includes agent-only actions
40
+ declared with `http: false`; WebMCP does not turn those actions into public HTTP
41
+ APIs.
42
+
43
+ An action is eligible when `agentTool` is not `false` and `needsApproval` is
44
+ not declared. The latter is a fail-closed boundary: the server action approval
45
+ contract belongs to the agent loop and cannot be delegated to a browser hint.
46
+ Public paths, signed-out pages, and `sessionBypass` surfaces do not register
47
+ backend action tools. Browsers without `document.modelContext` simply keep the
48
+ normal app behavior.
49
+
50
+ No per-app WebMCP list or new `defineAction` metadata is required. Change the
51
+ existing action definition when its exposure should change.
52
+
53
+ ## Explicit page-local actions
36
54
 
37
55
  Register only the actions that should be available to a page agent. The
38
56
  framework removes the action's local `run` function from the descriptor and
@@ -181,10 +199,10 @@ remote or headless tool calls, and use A2A when another app's agent should
181
199
  receive work and reason over it. WebMCP is an opt-in browser bridge, not a
182
200
  replacement for either network protocol.
183
201
 
184
- The framework does not auto-register every backend action as a WebMCP tool.
185
- Choose a small explicit producer list, keep side effects behind approval, and
186
- retain normal MCP or A2A exposure for capabilities that must work without an
187
- open browser page.
202
+ The framework automatically projects eligible backend actions and still lets a
203
+ page register explicit client-only capabilities. Keep server-side approval
204
+ actions on the agent loop, and retain normal MCP or A2A exposure for
205
+ capabilities that must work without an open browser page.
188
206
 
189
207
  ## Compatibility and safety
190
208
 
@@ -195,7 +213,8 @@ open browser page.
195
213
  - Treat descriptions, schemas, annotations, and results as page-provided
196
214
  data. The framework validates and bounds them, but agents should still treat
197
215
  `untrustedContentHint` results as untrusted content.
198
- - Use `stop()` when the owning page or component unmounts so stale tools do not
199
- remain available.
216
+ - Automatic registrations are aborted when the private app shell unmounts. Use
217
+ `stop()` for explicit page-local registrations so stale tools do not remain
218
+ available.
200
219
  - Keep server MCP and A2A endpoints for remote access. WebMCP only reaches the
201
220
  tools registered in a connected browser context.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.176.1",
3
+ "version": "0.177.0-nightly-20260828155142",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {