@agent-native/core 0.144.2 → 0.145.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 (104) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/app/pages/DataSources.tsx +28 -4
  3. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +7 -3
  4. package/corpus/templates/analytics/server/plugins/dashboard-report-jobs.ts +13 -3
  5. package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +64 -23
  6. package/corpus/templates/calendar/app/hooks/use-google-auth.ts +69 -46
  7. package/corpus/templates/calendar/app/hooks/use-view-preferences.ts +35 -6
  8. package/corpus/templates/calendar/app/hooks/use-zoom-auth.ts +24 -3
  9. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +45 -6
  10. package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +38 -5
  11. package/corpus/templates/clips/changelog/2026-08-06-desktop-full-screen-recordings-now-normalize-microphone-leve.md +6 -0
  12. package/corpus/templates/clips/desktop/src/app.tsx +146 -45
  13. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +185 -69
  14. package/corpus/templates/clips/server/jobs/brain-export.ts +13 -6
  15. package/corpus/templates/clips/server/jobs/builder-media-compression.ts +10 -3
  16. package/corpus/templates/clips/server/jobs/meeting-reminders.ts +10 -3
  17. package/corpus/templates/clips/server/jobs/poll-calendars.ts +23 -21
  18. package/corpus/templates/clips/server/jobs/stale-meeting-sweeper.ts +10 -3
  19. package/corpus/templates/clips/server/jobs/transactional-emails.ts +10 -3
  20. package/corpus/templates/content/app/components/sidebar/NotionButton.tsx +1 -0
  21. package/corpus/templates/design/app/components/design/FusionAppBanner.tsx +15 -2
  22. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +47 -4
  23. package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -1
  24. package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +82 -39
  25. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +31 -9
  26. package/corpus/templates/mail/server/plugins/mail-jobs.ts +36 -17
  27. package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
  28. package/dist/agent/production-agent.d.ts +6 -0
  29. package/dist/client/AssistantChat.js +73 -43
  30. package/dist/client/MultiTabAssistantChat.d.ts +3 -1
  31. package/dist/client/MultiTabAssistantChat.js +79 -92
  32. package/dist/client/browser-session-bridge.js +97 -54
  33. package/dist/client/db-admin/useAgentSync.js +34 -43
  34. package/dist/client/dynamic-suggestions.d.ts +3 -0
  35. package/dist/client/dynamic-suggestions.js +54 -52
  36. package/dist/client/hooks/index.d.ts +1 -0
  37. package/dist/client/hooks/index.js +1 -0
  38. package/dist/client/i18n.d.ts +7 -0
  39. package/dist/client/i18n.js +12 -3
  40. package/dist/client/integrations/useIntegrationStatus.d.ts +1 -1
  41. package/dist/client/integrations/useIntegrationStatus.js +9 -22
  42. package/dist/client/notifications/NotificationsBell.js +14 -9
  43. package/dist/client/progress/RunsTray.js +6 -5
  44. package/dist/client/settings/useBuilderStatus.js +95 -73
  45. package/dist/client/transcription/BuilderTranscriptionCta.js +27 -31
  46. package/dist/client/use-agent-engine-configured.js +21 -3
  47. package/dist/client/use-pausing-interval.d.ts +6 -0
  48. package/dist/client/use-pausing-interval.js +6 -0
  49. package/dist/client/use-poll-loop.d.ts +39 -0
  50. package/dist/client/use-poll-loop.js +73 -0
  51. package/dist/client/use-run-stuck-detection.js +41 -13
  52. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  53. package/dist/framework-tools.d.ts +126 -0
  54. package/dist/framework-tools.js +114 -0
  55. package/dist/integrations/google-docs-poller.js +26 -7
  56. package/dist/integrations/pending-tasks-retry-job.js +17 -23
  57. package/dist/integrations/plugin.js +16 -15
  58. package/dist/integrations/remote-push-delivery-job.js +16 -25
  59. package/dist/integrations/remote-retry-job.js +19 -23
  60. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  61. package/dist/notifications/routes.d.ts +3 -3
  62. package/dist/oauth-tokens/google-refresh.js +12 -19
  63. package/dist/observability/routes.d.ts +3 -3
  64. package/dist/progress/routes.d.ts +1 -1
  65. package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
  66. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  67. package/dist/resources/handlers.d.ts +1 -1
  68. package/dist/secrets/routes.d.ts +3 -3
  69. package/dist/server/action-discovery.d.ts +27 -0
  70. package/dist/server/action-discovery.js +89 -0
  71. package/dist/server/agent-chat/action-filters-a2a.d.ts +11 -0
  72. package/dist/server/agent-chat/action-filters-a2a.js +17 -1
  73. package/dist/server/agent-chat/framework-prompts.d.ts +2 -0
  74. package/dist/server/agent-chat/plugin-options.d.ts +30 -0
  75. package/dist/server/agent-chat/prompt-resources.d.ts +4 -1
  76. package/dist/server/agent-chat/prompt-resources.js +8 -2
  77. package/dist/server/agent-chat-plugin.js +148 -100
  78. package/dist/server/index.d.ts +1 -0
  79. package/dist/server/index.js +1 -0
  80. package/dist/server/interval-job.d.ts +25 -0
  81. package/dist/server/interval-job.js +19 -0
  82. package/dist/server/prompts/framework-core-compact.d.ts +5 -0
  83. package/dist/server/prompts/framework-core-compact.js +28 -12
  84. package/dist/server/prompts/framework-core.d.ts +5 -0
  85. package/dist/server/prompts/framework-core.js +33 -16
  86. package/dist/server/prompts/index.d.ts +1 -1
  87. package/dist/server/prompts/index.js +1 -1
  88. package/dist/server/prompts/shared-rules.d.ts +12 -4
  89. package/dist/server/prompts/shared-rules.js +14 -4
  90. package/dist/server/realtime-token.d.ts +1 -1
  91. package/dist/shared/index.d.ts +1 -0
  92. package/dist/shared/index.js +1 -0
  93. package/dist/shared/poll-engine.d.ts +49 -0
  94. package/dist/shared/poll-engine.js +148 -0
  95. package/dist/templates/default/.agents/skills/internationalization/SKILL.md +5 -0
  96. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -3
  97. package/dist/templates/workspace-core/.agents/skills/internationalization/SKILL.md +5 -0
  98. package/dist/tracking/providers.js +10 -1
  99. package/docs/content/deployment.mdx +47 -13
  100. package/docs/content/key-concepts.mdx +13 -6
  101. package/package.json +2 -1
  102. package/src/templates/default/.agents/skills/internationalization/SKILL.md +5 -0
  103. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -3
  104. package/src/templates/workspace-core/.agents/skills/internationalization/SKILL.md +5 -0
@@ -5,8 +5,9 @@
5
5
  * Shared rules (8-9, 12-13) are imported from shared-rules.ts so the
6
6
  * compact variant uses the same text and the two can never drift.
7
7
  */
8
+ import { frameworkGroupEnabled, } from "../../framework-tools.js";
8
9
  import { hasDatabaseReadTools, hasDatabaseWriteTools, } from "../../scripts/db/tool-mode.js";
9
- import { sharedRule8, SHARED_RULE_9, SHARED_RULE_14, SHARED_RULE_15, SHARED_RULE_AGENT_WARNINGS, } from "./shared-rules.js";
10
+ import { sharedRule8, SHARED_RULE_9, sharedRule14, SHARED_RULE_15, SHARED_RULE_AGENT_WARNINGS, } from "./shared-rules.js";
10
11
  /**
11
12
  * Build the full FRAMEWORK_CORE prompt string.
12
13
  *
@@ -40,6 +41,33 @@ export function buildFrameworkCore(examples, options) {
40
41
  const actionSurface = options?.extensionTools === true
41
42
  ? "this app's registered actions, extensions, and connected MCP tools"
42
43
  : "this app's registered actions and connected MCP tools";
44
+ const groupOn = (group) => frameworkGroupEnabled(options?.disabledFrameworkGroups, group);
45
+ const resourcesSection = groupOn("resources")
46
+ ? `### Resources
47
+
48
+ You have access to a Resources system for persistent notes and context files.
49
+ Use the \`resources\` tool to manage resources: \`action: "list"\`, \`"read"\`, \`"effective"\`, \`"write"\`, \`"promote"\`, or \`"delete"\`.
50
+ Resources can be workspace defaults inherited from Dispatch, shared organization/app overrides, or personal overrides. By default, resources are personal. Workspace-scope resources are read-only from app agents; create shared or personal resources to override or narrow them.
51
+
52
+ When the user gives instructions that should apply to all users/sessions, update the shared "AGENTS.md" resource.
53
+
54
+ Workspace resources are user-facing by default. If you need temporary working files, use the \`resources\` tool with \`visibility: "agent_scratch"\`; scratch resources are hidden from the Workspace view by default and expire automatically. Use \`visibility: "workspace"\` only when the user explicitly asked to save/create/manage that file, or for durable control files such as \`AGENTS.md\`, \`LEARNINGS.md\`, \`memory/\`, \`skills/\`, \`jobs/\`, or \`agents/\`. If a scratch result becomes useful to the user, call \`resources\` with \`action: "promote"\` or rewrite it with \`visibility: "workspace"\`.
55
+ `
56
+ : "";
57
+ const extendedCapabilityClauses = [
58
+ "inline embeds (`embed` fenced code block)",
59
+ groupOn("chat") ? "chat history search (`chat-history`)" : "",
60
+ groupOn("automation") ? "recurring jobs (`manage-jobs`)" : "",
61
+ "connecting Builder.io (`connect-builder`)",
62
+ "browser automation (`set-browser-control`/`activate-browser`)",
63
+ "structured memory (`save-memory`/`delete-memory`)",
64
+ ].filter(Boolean);
65
+ const extendedCapabilitiesList = `${extendedCapabilityClauses.slice(0, -1).join(", ")}, and ${extendedCapabilityClauses.at(-1)}`;
66
+ const callAgentSection = groupOn("workspaceApps")
67
+ ? `
68
+ **call-agent** messages a DIFFERENT deployed app's agent over A2A — never use it for your own actions or to call yourself. For brand-consistent generated media when this app has no native generation action, call agent "assets".
69
+ `
70
+ : "";
43
71
  return `
44
72
  ### How You Work
45
73
 
@@ -78,7 +106,7 @@ ${SHARED_RULE_9}
78
106
  **Downloadable files** — When the user asks you to create or export a file, deliver it in the same chat turn. For compact tabular results, prefer \`render-data-widget\` so the user gets the native Download CSV action without a second stored copy. For a complete or durable file written through \`workspaceWrite\`, use a normal non-\`scratch/\` path and then call \`show-workspace-file\` with that path so chat renders a direct download card. Never finish by giving filesystem paths or telling the user to navigate elsewhere to find the file.
79
107
  10. **Your tool list is not the whole surface** — Most app actions and connected MCP tools load on demand, so search the live registry with \`tool-search\` before concluding a capability doesn't exist.
80
108
  11. **Relative dates use runtime context** — The \`<runtime-context>\` block gives the authoritative current date/time. Resolve "today", "yesterday", "last week", and similar phrases to explicit calendar dates before querying data or creating artifacts. When answering factual questions, include the exact date or date range you used.
81
- ${SHARED_RULE_14}
109
+ ${sharedRule14(options)}
82
110
  ${SHARED_RULE_15}
83
111
  ${SHARED_RULE_AGENT_WARNINGS}
84
112
 
@@ -86,22 +114,11 @@ ${SHARED_RULE_AGENT_WARNINGS}
86
114
 
87
115
  Gather context efficiently: when you need several independent read-only lookups (reading state, querying different tables, searching, fetching unrelated records), issue those tool calls together in one batch rather than one at a time. Keep mutating actions ordered and sequential — anything that creates, updates, deletes, sends, or publishes runs one at a time so each can be confirmed before the next, and so writes that depend on each other stay consistent.
88
116
 
89
- ### Resources
90
-
91
- You have access to a Resources system for persistent notes and context files.
92
- Use the \`resources\` tool to manage resources: \`action: "list"\`, \`"read"\`, \`"effective"\`, \`"write"\`, \`"promote"\`, or \`"delete"\`.
93
- Resources can be workspace defaults inherited from Dispatch, shared organization/app overrides, or personal overrides. By default, resources are personal. Workspace-scope resources are read-only from app agents; create shared or personal resources to override or narrow them.
94
-
95
- When the user gives instructions that should apply to all users/sessions, update the shared "AGENTS.md" resource.
96
-
97
- Workspace resources are user-facing by default. If you need temporary working files, use the \`resources\` tool with \`visibility: "agent_scratch"\`; scratch resources are hidden from the Workspace view by default and expire automatically. Use \`visibility: "workspace"\` only when the user explicitly asked to save/create/manage that file, or for durable control files such as \`AGENTS.md\`, \`LEARNINGS.md\`, \`memory/\`, \`skills/\`, \`jobs/\`, or \`agents/\`. If a scratch result becomes useful to the user, call \`resources\` with \`action: "promote"\` or rewrite it with \`visibility: "workspace"\`.
98
-
117
+ ${resourcesSection}
99
118
  ### Extended Capabilities
100
119
 
101
- You also have tools for inline embeds (\`embed\` fenced code block), chat history search (\`chat-history\`), recurring jobs (\`manage-jobs\`), connecting Builder.io (\`connect-builder\`), browser automation (\`set-browser-control\`/\`activate-browser\`), and structured memory (\`save-memory\`/\`delete-memory\`). Call \`get-framework-context\` with the matching key — it lists its own topics — for full instructions before non-trivial work in any of these areas.
120
+ You also have tools for ${extendedCapabilitiesList}. Call \`get-framework-context\` with the matching key — it lists its own topics — for full instructions before non-trivial work in any of these areas.
102
121
 
103
122
  **Agent teams:** default to doing the work yourself in this thread, but treat "background agent", "sub-agent", "parallel", "batch", "kick off", "run the rest", and "queued items" as delegation intent when the user is asking you to start or continue independent work items. Delegate ONE sub-agent for self-contained heavy work (deep research, long multi-step generation, noisy scans); fan out to MULTIPLE only for genuinely independent units; never parallelize tightly-coupled work; cap fan-out around 3. After \`spawn\`, say the task started/running, not completed; use \`status\`/\`read-result\` before claiming delegated work is done. Give every sub-agent a self-contained brief (objective, the specific context/IDs it needs, output format, boundaries), then read all results and synthesize one integrated answer. Full details: \`get-framework-context\` key \`agent-teams\`.
104
-
105
- **call-agent** messages a DIFFERENT deployed app's agent over A2A — never use it for your own actions or to call yourself. For brand-consistent generated media when this app has no native generation action, call agent "assets".
106
- `;
123
+ ${callAgentSection}`;
107
124
  }
@@ -4,5 +4,5 @@
4
4
  */
5
5
  export { buildFrameworkCore } from "./framework-core.js";
6
6
  export { buildFrameworkCoreCompact } from "./framework-core-compact.js";
7
- export { sharedRule8, SHARED_RULE_9, SHARED_RULE_14, SHARED_RULE_15, type PromptExamples, } from "./shared-rules.js";
7
+ export { sharedRule8, SHARED_RULE_9, sharedRule14, SHARED_RULE_15, type PromptExamples, type SharedRuleOptions, } from "./shared-rules.js";
8
8
  export { getModelFamilyOverlay } from "./model-overlays.js";
@@ -4,5 +4,5 @@
4
4
  */
5
5
  export { buildFrameworkCore } from "./framework-core.js";
6
6
  export { buildFrameworkCoreCompact } from "./framework-core-compact.js";
7
- export { sharedRule8, SHARED_RULE_9, SHARED_RULE_14, SHARED_RULE_15, } from "./shared-rules.js";
7
+ export { sharedRule8, SHARED_RULE_9, sharedRule14, SHARED_RULE_15, } from "./shared-rules.js";
8
8
  export { getModelFamilyOverlay } from "./model-overlays.js";
@@ -7,6 +7,7 @@
7
7
  * rule) and 12–13 (planning discipline; collaborate through uncertainty) are
8
8
  * reproduced verbatim in both prompts — keep them here.
9
9
  */
10
+ import { type FrameworkToolGroup } from "../../framework-tools.js";
10
11
  import { type DatabaseToolsOption } from "../../scripts/db/tool-mode.js";
11
12
  /**
12
13
  * Injectable provider/action examples. Defaults are generic; templates that
@@ -21,6 +22,9 @@ export interface PromptExamples {
21
22
  export interface SharedRuleOptions {
22
23
  databaseTools?: DatabaseToolsOption;
23
24
  extensionTools?: boolean;
25
+ /** Framework tool groups this app switched off. Any rule naming a group's
26
+ * tool must drop that reference — see `frameworkGroupEnabled`. */
27
+ disabledFrameworkGroups?: ReadonlySet<FrameworkToolGroup>;
24
28
  }
25
29
  /** Rule 8 — db-* tools are internal only (shared between full and compact). */
26
30
  export declare function sharedRule8(examples?: PromptExamples, options?: SharedRuleOptions): string;
@@ -34,14 +38,18 @@ export declare function sharedRule8(examples?: PromptExamples, options?: SharedR
34
38
  */
35
39
  export declare const SHARED_RULE_9 = "9. **Never fabricate \u2014 verify results, report failures honestly, and recover instead of giving up.**\n - **Never fabricate factual claims or records** \u2014 do not invent numbers, metrics, records, query results, URLs, citations, source attributions, customer names, dates, or success rates. This applies inside generated artifacts too: decks, documents, reports, dashboards, Slack/email replies, and charts. Only state factual numbers/claims the user provided or you retrieved with an action/tool. If a data source is unavailable, returns no rows, is missing credentials, or has a connection error, say so clearly; do not create placeholder rows or fetch unrelated providers to look complete unless the user explicitly asked you to import/sync/backfill. Prefer qualitative wording, placeholders like `[metric TBD]`, or clearly labeled draft assumptions over plausible-looking facts \u2014 presenting made-up data as real is worse than admitting the limitation.\n - **Never fabricate success from tool errors** \u2014 when a tool call returns an error (marked `isError: true`, contains \"Command failed\", \"Error:\", or non-zero exit output), the operation FAILED; do not synthesize a success narrative or describe what it \"would have\" produced. Report the failure verbatim (this applies especially to `bash(command=\"pnpm action ...\")` calls). Before telling the user a mutating action (create/update/delete/send/publish) is done, confirm it actually landed \u2014 check the tool result, or read the refreshed `<current-screen>` / re-query the data; having *called* an action is not proof it worked. If a result is ambiguous, check rather than assume.\n - **Recover instead of giving up** \u2014 treat a failure or ambiguous result as a signal to retry the obvious fix, try an alternate tool or approach, or clearly hand the blocker back with what you tried; never silently give up, and never paper over a failure by claiming success.";
36
40
  /**
37
- * Rule 12 (const name retained as SHARED_RULE_14 for import stability) —
38
- * Planning and progress. Deliberately states only when to open a progress run;
39
- * the start/update/complete discipline lives in `manage-progress`'s own tool
41
+ * Rule 12 (name retained as `sharedRule14` for import stability) — Planning and
42
+ * progress. Deliberately states only when to open a progress run; the
43
+ * start/update/complete discipline lives in `manage-progress`'s own tool
40
44
  * description (packages/core/src/progress/actions.ts). Re-adding the mechanics
41
45
  * here means every turn pays for guidance the model only needs once it has the
42
46
  * tool loaded.
47
+ *
48
+ * Drops the `manage-progress` reference when the `automation` group is off, so
49
+ * the rule never names a tool the request does not carry. The planning
50
+ * discipline itself still applies — only the tool call goes away.
43
51
  */
44
- export declare const SHARED_RULE_14 = "12. **Plan and track multi-step work** \u2014 When a task spans several real steps the user would want to watch, open a `manage-progress` run so the work is visible while it's still in flight, and keep it updated as you go rather than after the fact; the tool's own description carries the full discipline. Skip it for single-action lookups, simple reads, and anything that finishes in one tool call \u2014 never create single-step plans.";
52
+ export declare function sharedRule14(options?: SharedRuleOptions): string;
45
53
  /**
46
54
  * Rule 14 — Relay agent warnings. Named for what it is rather than its number,
47
55
  * since the `SHARED_RULE_14`/`15` names above no longer match theirs.
@@ -7,6 +7,7 @@
7
7
  * rule) and 12–13 (planning discipline; collaborate through uncertainty) are
8
8
  * reproduced verbatim in both prompts — keep them here.
9
9
  */
10
+ import { frameworkGroupEnabled, } from "../../framework-tools.js";
10
11
  import { normalizeDatabaseToolsMode, } from "../../scripts/db/tool-mode.js";
11
12
  const DEFAULT_PROVIDER_ACTIONS = [
12
13
  "provider-search",
@@ -64,14 +65,23 @@ export const SHARED_RULE_9 = `9. **Never fabricate — verify results, report fa
64
65
  - **Never fabricate success from tool errors** — when a tool call returns an error (marked \`isError: true\`, contains "Command failed", "Error:", or non-zero exit output), the operation FAILED; do not synthesize a success narrative or describe what it "would have" produced. Report the failure verbatim (this applies especially to \`bash(command="pnpm action ...")\` calls). Before telling the user a mutating action (create/update/delete/send/publish) is done, confirm it actually landed — check the tool result, or read the refreshed \`<current-screen>\` / re-query the data; having *called* an action is not proof it worked. If a result is ambiguous, check rather than assume.
65
66
  - **Recover instead of giving up** — treat a failure or ambiguous result as a signal to retry the obvious fix, try an alternate tool or approach, or clearly hand the blocker back with what you tried; never silently give up, and never paper over a failure by claiming success.`;
66
67
  /**
67
- * Rule 12 (const name retained as SHARED_RULE_14 for import stability) —
68
- * Planning and progress. Deliberately states only when to open a progress run;
69
- * the start/update/complete discipline lives in `manage-progress`'s own tool
68
+ * Rule 12 (name retained as `sharedRule14` for import stability) — Planning and
69
+ * progress. Deliberately states only when to open a progress run; the
70
+ * start/update/complete discipline lives in `manage-progress`'s own tool
70
71
  * description (packages/core/src/progress/actions.ts). Re-adding the mechanics
71
72
  * here means every turn pays for guidance the model only needs once it has the
72
73
  * tool loaded.
74
+ *
75
+ * Drops the `manage-progress` reference when the `automation` group is off, so
76
+ * the rule never names a tool the request does not carry. The planning
77
+ * discipline itself still applies — only the tool call goes away.
73
78
  */
74
- export const SHARED_RULE_14 = `12. **Plan and track multi-step work** — When a task spans several real steps the user would want to watch, open a \`manage-progress\` run so the work is visible while it's still in flight, and keep it updated as you go rather than after the fact; the tool's own description carries the full discipline. Skip it for single-action lookups, simple reads, and anything that finishes in one tool call — never create single-step plans.`;
79
+ export function sharedRule14(options) {
80
+ if (!frameworkGroupEnabled(options?.disabledFrameworkGroups, "automation")) {
81
+ return `12. **Plan and track multi-step work** — When a task spans several real steps, work through them in order and tell the user where you are as you go rather than only at the end. Skip ceremony for single-action lookups, simple reads, and anything that finishes in one tool call.`;
82
+ }
83
+ return `12. **Plan and track multi-step work** — When a task spans several real steps the user would want to watch, open a \`manage-progress\` run so the work is visible while it's still in flight, and keep it updated as you go rather than after the fact; the tool's own description carries the full discipline. Skip it for single-action lookups, simple reads, and anything that finishes in one tool call — never create single-step plans.`;
84
+ }
75
85
  /**
76
86
  * Rule 14 — Relay agent warnings. Named for what it is rather than its number,
77
87
  * since the `SHARED_RULE_14`/`15` names above no longer match theirs.
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
26
26
  expiresAt?: undefined;
27
27
  ttlSeconds?: undefined;
28
28
  } | {
29
+ error?: undefined;
29
30
  token: string;
30
31
  expiresAt: string;
31
32
  ttlSeconds: number;
32
- error?: undefined;
33
33
  }>>;
@@ -9,6 +9,7 @@ export { llmConnectionTrackingProperties, normalizeLlmConnection, type LlmConnec
9
9
  export { DISPATCH_WORKSPACE_ROOT_REDIRECTS, RESERVED_WORKSPACE_APP_IDS, assertValidWorkspaceAppId, getWorkspaceAppIdValidationError, isValidWorkspaceAppIdFormat, } from "./workspace-app-id.js";
10
10
  export { DEFAULT_WORKSPACE_APP_AUDIENCE, WORKSPACE_APP_AUDIENCES, normalizeWorkspaceAppAudience, normalizeWorkspaceAppPathList, workspaceAppAudienceFromEnv, workspaceAppAudienceFromPackageJson, workspaceAppRouteAccessFromEnv, workspaceAppRouteAccessFromPackageJson, type WorkspaceAppRouteAccess, type WorkspaceAppRouteAccessFromConfig, type WorkspaceAppAudience, } from "./workspace-app-audience.js";
11
11
  export { AGENT_NATIVE_OPEN_PATH, AGENT_SIDEBAR_QUERY_PARAM, AGENT_SIDEBAR_QUERY_VALUE_CLOSED, isAgentNativeOpenDeepLink, withCollapsedAgentSidebarParam, } from "./agent-sidebar-url.js";
12
+ export { createPollEngine, type PollEngineOptions, type PollEngineHandle, } from "./poll-engine.js";
12
13
  export { AGENT_NATIVE_DEFAULT_SOCIAL_IMAGE, AGENT_NATIVE_SOCIAL_IMAGE_CACHE_BUSTER, AGENT_NATIVE_SOCIAL_IMAGE_ALT, AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT, AGENT_NATIVE_SOCIAL_IMAGE_PATH, AGENT_NATIVE_SOCIAL_IMAGE_TYPE, AGENT_NATIVE_SOCIAL_IMAGE_WIDTH, defaultSocialImageMeta, withAgentNativeSocialImageCacheBuster, withDefaultSocialImage, type SocialMetaDescriptor, } from "./social-meta.js";
13
14
  export { EMBED_MODE_QUERY_PARAM, EMBED_SESSION_COOKIE, EMBED_START_PATH, EMBED_TOKEN_QUERY_PARAM, } from "./embed-auth.js";
14
15
  export { AGENT_ACCESS_PARAM, DEFAULT_AGENT_ACCESS_TTL_SECONDS, appendAgentAccessParam, buildAgentAccessApiUrl, buildAgentAccessUrl, normalizeAgentAccessBasePath, normalizeAgentAccessOrigin, scopedAgentAccessResourceId, toAgentAccessUrl, type AgentAccessApiUrlOptions, type AgentAccessResourceScope, type AgentAccessUrlOptions, } from "./agent-access.js";
@@ -9,6 +9,7 @@ export { llmConnectionTrackingProperties, normalizeLlmConnection, } from "./llm-
9
9
  export { DISPATCH_WORKSPACE_ROOT_REDIRECTS, RESERVED_WORKSPACE_APP_IDS, assertValidWorkspaceAppId, getWorkspaceAppIdValidationError, isValidWorkspaceAppIdFormat, } from "./workspace-app-id.js";
10
10
  export { DEFAULT_WORKSPACE_APP_AUDIENCE, WORKSPACE_APP_AUDIENCES, normalizeWorkspaceAppAudience, normalizeWorkspaceAppPathList, workspaceAppAudienceFromEnv, workspaceAppAudienceFromPackageJson, workspaceAppRouteAccessFromEnv, workspaceAppRouteAccessFromPackageJson, } from "./workspace-app-audience.js";
11
11
  export { AGENT_NATIVE_OPEN_PATH, AGENT_SIDEBAR_QUERY_PARAM, AGENT_SIDEBAR_QUERY_VALUE_CLOSED, isAgentNativeOpenDeepLink, withCollapsedAgentSidebarParam, } from "./agent-sidebar-url.js";
12
+ export { createPollEngine, } from "./poll-engine.js";
12
13
  export { AGENT_NATIVE_DEFAULT_SOCIAL_IMAGE, AGENT_NATIVE_SOCIAL_IMAGE_CACHE_BUSTER, AGENT_NATIVE_SOCIAL_IMAGE_ALT, AGENT_NATIVE_SOCIAL_IMAGE_HEIGHT, AGENT_NATIVE_SOCIAL_IMAGE_PATH, AGENT_NATIVE_SOCIAL_IMAGE_TYPE, AGENT_NATIVE_SOCIAL_IMAGE_WIDTH, defaultSocialImageMeta, withAgentNativeSocialImageCacheBuster, withDefaultSocialImage, } from "./social-meta.js";
13
14
  export { EMBED_MODE_QUERY_PARAM, EMBED_SESSION_COOKIE, EMBED_START_PATH, EMBED_TOKEN_QUERY_PARAM, } from "./embed-auth.js";
14
15
  export { AGENT_ACCESS_PARAM, DEFAULT_AGENT_ACCESS_TTL_SECONDS, appendAgentAccessParam, buildAgentAccessApiUrl, buildAgentAccessUrl, normalizeAgentAccessBasePath, normalizeAgentAccessOrigin, scopedAgentAccessResourceId, toAgentAccessUrl, } from "./agent-access.js";
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Shared recurring-poll engine used by both `usePollLoop` (client) and
3
+ * `startIntervalJob` (server) so every polling loop in the codebase gets the
4
+ * same three properties for free instead of hand-rolling them per call site:
5
+ *
6
+ * 1. Never overlaps: the next attempt is scheduled via `setTimeout` only
7
+ * after the current one settles, not on a fixed `setInterval` cadence.
8
+ * This also avoids the latency `setInterval` + skip-if-busy introduces —
9
+ * a skipped tick there means waiting a full extra cadence before the next
10
+ * retry; here the gap after completion is always exactly `intervalMs`.
11
+ * 2. Never stalls silently: every attempt is bounded by a timeout (paired
12
+ * with an `AbortController` so a `fetch` can actually cancel, not just be
13
+ * abandoned), and that timeout reports through `onError` the moment it
14
+ * fires. An attempt that ignores its `signal` keeps holding the loop —
15
+ * reported, not hidden — because releasing the slot to the next attempt
16
+ * would trade a visible stall for a silent overlap.
17
+ * 3. Never double-fires: a single internal in-flight flag, owned by this
18
+ * engine rather than duplicated in every caller.
19
+ */
20
+ export interface PollEngineOptions {
21
+ /** Delay in ms before the next attempt. Evaluated fresh before each schedule. */
22
+ intervalMs: number | (() => number);
23
+ /** Per-attempt timeout. Default: `Math.max(timeoutFloorMs, intervalMs * 4)`. */
24
+ timeoutMs?: number | (() => number);
25
+ /** Floor used by the default `timeoutMs`. Default: 10_000. */
26
+ timeoutFloorMs?: number;
27
+ /** Called when an attempt throws or times out. Default: swallow. */
28
+ onError?: (err: unknown) => void;
29
+ /** Run the first attempt immediately on `start()`. Default: true. */
30
+ leading?: boolean;
31
+ }
32
+ export interface PollEngineHandle {
33
+ /** Arm the loop. No-op if already running. */
34
+ start(): void;
35
+ /** Halt the loop and abort any in-flight attempt; its eventual settlement is a no-op. */
36
+ stop(): void;
37
+ /** Cancel the pending wait and run now. No-op if not running or an attempt is already in flight. */
38
+ pollNow(): void;
39
+ /**
40
+ * Re-arm the pending wait using a freshly resolved `intervalMs`, without
41
+ * running an attempt. Use this when `intervalMs` is a function whose
42
+ * result can change externally (e.g. tab visibility) — otherwise a
43
+ * change only takes effect the next time an attempt completes. No-op if
44
+ * there's no pending wait (not running, or an attempt is in flight).
45
+ */
46
+ reschedule(): void;
47
+ readonly isRunning: boolean;
48
+ }
49
+ export declare function createPollEngine(attempt: (signal: AbortSignal) => Promise<void>, options: PollEngineOptions): PollEngineHandle;
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Shared recurring-poll engine used by both `usePollLoop` (client) and
3
+ * `startIntervalJob` (server) so every polling loop in the codebase gets the
4
+ * same three properties for free instead of hand-rolling them per call site:
5
+ *
6
+ * 1. Never overlaps: the next attempt is scheduled via `setTimeout` only
7
+ * after the current one settles, not on a fixed `setInterval` cadence.
8
+ * This also avoids the latency `setInterval` + skip-if-busy introduces —
9
+ * a skipped tick there means waiting a full extra cadence before the next
10
+ * retry; here the gap after completion is always exactly `intervalMs`.
11
+ * 2. Never stalls silently: every attempt is bounded by a timeout (paired
12
+ * with an `AbortController` so a `fetch` can actually cancel, not just be
13
+ * abandoned), and that timeout reports through `onError` the moment it
14
+ * fires. An attempt that ignores its `signal` keeps holding the loop —
15
+ * reported, not hidden — because releasing the slot to the next attempt
16
+ * would trade a visible stall for a silent overlap.
17
+ * 3. Never double-fires: a single internal in-flight flag, owned by this
18
+ * engine rather than duplicated in every caller.
19
+ */
20
+ const DEFAULT_TIMEOUT_FLOOR_MS = 10_000;
21
+ function resolve(value) {
22
+ return typeof value === "function" ? value() : value;
23
+ }
24
+ function maybeUnref(timer) {
25
+ if (timer &&
26
+ typeof timer === "object" &&
27
+ "unref" in timer &&
28
+ typeof timer.unref === "function") {
29
+ timer.unref();
30
+ }
31
+ }
32
+ export function createPollEngine(attempt, options) {
33
+ const timeoutFloorMs = options.timeoutFloorMs ?? DEFAULT_TIMEOUT_FLOOR_MS;
34
+ const getTimeoutMs = () => options.timeoutMs != null
35
+ ? resolve(options.timeoutMs)
36
+ : Math.max(timeoutFloorMs, resolve(options.intervalMs) * 4);
37
+ const onError = options.onError ?? (() => { });
38
+ const leading = options.leading ?? true;
39
+ let generation = 0;
40
+ let running = false;
41
+ let inFlight = false;
42
+ let timer = null;
43
+ let activeController = null;
44
+ function clearTimer() {
45
+ if (timer != null) {
46
+ clearTimeout(timer);
47
+ timer = null;
48
+ }
49
+ }
50
+ function schedule(gen) {
51
+ clearTimer();
52
+ timer = setTimeout(() => {
53
+ timer = null;
54
+ void tick(gen);
55
+ }, resolve(options.intervalMs));
56
+ maybeUnref(timer);
57
+ }
58
+ async function tick(gen) {
59
+ if (gen !== generation || !running)
60
+ return;
61
+ if (inFlight) {
62
+ // An attempt from a superseded generation is still settling — stop()
63
+ // aborted it but the caller has not returned yet. Its own `finally`
64
+ // sees a stale generation and will not reschedule, so re-arming here
65
+ // is what keeps a stop()/start() cycle (tab hidden then visible) from
66
+ // leaving the engine alive-but-timerless, i.e. permanently dead.
67
+ schedule(gen);
68
+ return;
69
+ }
70
+ inFlight = true;
71
+ const controller = new AbortController();
72
+ activeController = controller;
73
+ const timeoutMs = getTimeoutMs();
74
+ const abortTimer = setTimeout(() => controller.abort(), timeoutMs);
75
+ maybeUnref(abortTimer);
76
+ let reported = false;
77
+ const report = (err) => {
78
+ if (reported)
79
+ return;
80
+ reported = true;
81
+ onError(err);
82
+ };
83
+ // The attempt is retained separately from the timeout race below. The
84
+ // timeout reports a slow attempt immediately, but the in-flight slot is
85
+ // only released once the attempt itself settles: releasing on the
86
+ // timeout would let the next tick start while an attempt that ignores
87
+ // `signal` is still doing work, which is the overlap (duplicate external
88
+ // requests, racing writes) this engine exists to prevent. An attempt that
89
+ // never settles blocks further attempts by design — `onError` has already
90
+ // fired, so it is loud rather than silent.
91
+ const settled = Promise.resolve()
92
+ .then(() => attempt(controller.signal))
93
+ .then(() => { }, (err) => report(err));
94
+ try {
95
+ await Promise.race([
96
+ settled,
97
+ new Promise((_, reject) => {
98
+ controller.signal.addEventListener("abort", () => reject(new Error(`poll attempt timed out after ${timeoutMs}ms`)), { once: true });
99
+ }),
100
+ ]);
101
+ }
102
+ catch (err) {
103
+ report(err);
104
+ }
105
+ finally {
106
+ await settled;
107
+ clearTimeout(abortTimer);
108
+ if (activeController === controller)
109
+ activeController = null;
110
+ inFlight = false;
111
+ if (gen === generation && running)
112
+ schedule(gen);
113
+ }
114
+ }
115
+ return {
116
+ start() {
117
+ if (running)
118
+ return;
119
+ running = true;
120
+ generation++;
121
+ const gen = generation;
122
+ if (leading)
123
+ void tick(gen);
124
+ else
125
+ schedule(gen);
126
+ },
127
+ stop() {
128
+ running = false;
129
+ generation++;
130
+ clearTimer();
131
+ activeController?.abort();
132
+ },
133
+ pollNow() {
134
+ if (!running || inFlight)
135
+ return;
136
+ clearTimer();
137
+ void tick(generation);
138
+ },
139
+ reschedule() {
140
+ if (!running || inFlight || timer == null)
141
+ return;
142
+ schedule(generation);
143
+ },
144
+ get isRunning() {
145
+ return running;
146
+ },
147
+ };
148
+ }
@@ -29,6 +29,11 @@ Templates use `app/i18n/`:
29
29
  Use BCP-47 filenames from the supported set: `en-US`, `zh-CN`, `es-ES`,
30
30
  `fr-FR`, `de-DE`, `ja-JP`, `ko-KR`, `pt-BR`, `hi-IN`, `ar-SA`.
31
31
 
32
+ If an app only ships translations for a subset of that set, set
33
+ `supportedLocales` on the `AgentNativeI18nCatalog` to that subset. Without it,
34
+ `<LanguagePicker />` lists every framework-supported locale regardless of
35
+ whether `loadMessages` can actually resolve it.
36
+
32
37
  ## UI Pattern
33
38
 
34
39
  - Wrap apps with `AppProviders i18n={{ catalog: i18nCatalog }}`.
@@ -217,9 +217,12 @@ POST /_agent-native/extensions
217
217
 
218
218
  HTTP creation is disabled by default. The host app must set
219
219
  `extensionTools: true` on `createCoreRoutesPlugin()` as well as
220
- `createAgentChatPlugin()`; otherwise authenticated collection `POST` requests
221
- return `403` while existing extension runtime, read, edit, and deep-link routes
222
- remain available for compatibility.
220
+ `frameworkTools: { extensions: true }` on `createAgentChatPlugin()`; otherwise
221
+ authenticated collection `POST` requests return `403` while existing extension
222
+ runtime, read, edit, and deep-link routes remain available for compatibility.
223
+ (`extensionTools` on the agent-chat plugin is the deprecated spelling of
224
+ `frameworkTools.extensions`; the core-routes option keeps its own name because
225
+ it gates route mounting rather than agent tools.)
223
226
 
224
227
  The action accepts:
225
228
 
@@ -29,6 +29,11 @@ Templates use `app/i18n/`:
29
29
  Use BCP-47 filenames from the supported set: `en-US`, `zh-CN`, `es-ES`,
30
30
  `fr-FR`, `de-DE`, `ja-JP`, `ko-KR`, `pt-BR`, `hi-IN`, `ar-SA`.
31
31
 
32
+ If an app only ships translations for a subset of that set, set
33
+ `supportedLocales` on the `AgentNativeI18nCatalog` to that subset. Without it,
34
+ `<LanguagePicker />` lists every framework-supported locale regardless of
35
+ whether `loadMessages` can actually resolve it.
36
+
32
37
  ## UI Pattern
33
38
 
34
39
  - Wrap apps with `AppProviders i18n={{ catalog: i18nCatalog }}`.
@@ -38,7 +38,16 @@ function setTimer(t) {
38
38
  function enqueue(url, body, headers, options) {
39
39
  const queue = getQueue();
40
40
  queue.push({ url, body, headers });
41
- if (options?.flushImmediately || queue.length >= MAX_BATCH_SIZE) {
41
+ // The batch timer is unref'd so it never keeps a long-lived server alive —
42
+ // but on Netlify/Vercel/Lambda the execution environment can freeze the
43
+ // moment the event loop looks empty, which is exactly when an unref'd timer
44
+ // doesn't count. Without a signal, a warm container freezing between
45
+ // invocations never fires that timer, silently dropping every provider's
46
+ // queued events for a request that ends in a crash. Default to flushing
47
+ // synchronously with the response in that environment; a caller (e.g.
48
+ // Agent Native Analytics' flush-mode override) can still force either way.
49
+ const flushImmediately = options?.flushImmediately ?? isServerlessRuntime();
50
+ if (flushImmediately || queue.length >= MAX_BATCH_SIZE) {
42
51
  void drainQueue();
43
52
  }
44
53
  else if (!getTimer()) {
@@ -580,16 +580,19 @@ Rotate signing/session keys by replacing the env var on every instance and redep
580
580
 
581
581
  ## Production Agent Tools {#production-agent-tools}
582
582
 
583
- Production agents get the app's registered actions plus framework tools from
584
- the agent chat plugin. SQL inspection is enabled by default, while database
585
- writes go through typed app actions unless the app explicitly opts into raw
586
- write tools:
583
+ Production agents get the app's registered actions plus the framework's own
584
+ tools. `frameworkTools` selects which of those framework tools this app exposes.
585
+ Every group defaults to today's behavior, so omitting the option changes
586
+ nothing:
587
587
 
588
588
  ```ts filename="server/plugins/agent-chat.ts"
589
589
  export default createAgentChatPlugin({
590
- // Default: "read"
591
- databaseTools: "read", // "write" | "read" | "off"
592
- extensionTools: true,
590
+ frameworkTools: {
591
+ database: "read", // "write" | "read" | "off" — default "read"
592
+ extensions: true, // default false
593
+ sharing: false, // this app has no share surface
594
+ review: false,
595
+ },
593
596
  });
594
597
  ```
595
598
 
@@ -599,17 +602,48 @@ export default createCoreRoutesPlugin({
599
602
  });
600
603
  ```
601
604
 
602
- - `databaseTools: "read"` — default. Registers only `db-schema` and `db-query`;
605
+ `database` and `extensions` accept the values the old top-level flags did:
606
+
607
+ - `database: "read"` — default. Registers only `db-schema` and `db-query`;
603
608
  agents inspect data with SQL but must use typed app actions for writes.
604
- - `databaseTools: "write"` or `true` — additionally registers `db-exec` and
609
+ - `database: "write"` or `true` — additionally registers `db-exec` and
605
610
  `db-patch` for deliberate raw SQL maintenance. Writes are scoped to the
606
611
  current user/org and schema changes are blocked.
607
- - `databaseTools: "off"` or `false` — removes raw database tools from the
612
+ - `database: "off"` or `false` — removes raw database tools from the
608
613
  agent surface so the app's actions are the only data access path.
609
- - `extensionTools: true` — opts the app into framework extension-management
614
+ - `extensions: true` — opts the app into framework extension-management
610
615
  actions and prompt guidance (`create-extension`, `update-extension`, etc.).
611
- Set it on both plugins to also allow authenticated REST creation. Extensions
612
- are disabled by default.
616
+ Set `extensionTools: true` on the core-routes plugin as well to allow
617
+ authenticated REST creation. Extensions are disabled by default.
618
+
619
+ The remaining groups are booleans, all on by default: `sharing`, `review`,
620
+ `history`, `featureFlags`, `localization`, `audit`, `contextXray`,
621
+ `userProfile`, `automation`, `docs`, `resources`, `web`, `workspaceApps`,
622
+ `chat`, and `email`. Use `frameworkTools: "minimal"` to turn every group off at
623
+ once for a voice-first or single-purpose app, or
624
+ `frameworkTools: { preset: "minimal", resources: true }` to keep just one — an
625
+ explicit group key always wins over the preset.
626
+
627
+ Switching a group off removes it from the **agent** surfaces only: interactive
628
+ chat, MCP, A2A, and background runs. The matching HTTP action routes stay
629
+ mounted, because the UI reaches them through client hooks — disabling `sharing`
630
+ must not 404 a share dialog that is still on screen. Weigh that parity cost
631
+ before using it: the framework's contract is that anything the UI can do the
632
+ agent can do, so `sharing: false` alongside a visible share button is a
633
+ deliberate break, and only makes sense when the app has no such surface at all.
634
+
635
+ Framework tools are no longer promoted into the first model request by default,
636
+ independent of this option. They stay in the searchable registry and load
637
+ through `tool-search`, or immediately if the app names them in
638
+ `initialToolNames`.
639
+
640
+ <Callout tone="warning">
641
+ The top-level `databaseTools` and `extensionTools` options are deprecated in
642
+ favor of `frameworkTools.database` and `frameworkTools.extensions`. They are
643
+ still honored, but setting a top-level flag and its `frameworkTools`
644
+ equivalent to conflicting values throws at plugin startup rather than booting
645
+ with a tool surface nobody chose.
646
+ </Callout>
613
647
 
614
648
  ## Production Code Execution {#production-code-execution}
615
649
 
@@ -254,12 +254,19 @@ pnpm action db-query --sql "SELECT * FROM forms"
254
254
  ```
255
255
 
256
256
  The production agent chat plugin defaults raw SQL tools to read-only
257
- (`databaseTools: "read"`), so agents inspect app-owned data with `db-schema` /
258
- `db-query` and perform writes through typed app actions. Set
259
- `databaseTools: "write"` (or `true`) only for deliberate maintenance surfaces
260
- that should also expose scoped `db-exec` / `db-patch`; set
261
- `databaseTools: "off"` / `false` to require typed app actions for all data
262
- access.
257
+ (`frameworkTools: { database: "read" }`), so agents inspect app-owned data with
258
+ `db-schema` / `db-query` and perform writes through typed app actions. Set
259
+ `database: "write"` (or `true`) only for deliberate maintenance surfaces that
260
+ should also expose scoped `db-exec` / `db-patch`; set `database: "off"` /
261
+ `false` to require typed app actions for all data access.
262
+
263
+ `frameworkTools` governs the rest of the framework's own tools the same way —
264
+ sharing, review comments, version history, feature flags, localization, audit,
265
+ context X-Ray, profile, automations, docs, resources, web, cross-app delegation,
266
+ chat, and email. See
267
+ [Production Agent Tools](/docs/deployment#production-agent-tools) for the full
268
+ list, the `"minimal"` preset, and why disabling a group leaves its HTTP routes
269
+ mounted.
263
270
 
264
271
  ## Agent chat bridge {#agent-chat-bridge}
265
272
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.144.2",
3
+ "version": "0.145.0",
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": {
@@ -72,6 +72,7 @@
72
72
  "./server/request-context": "./dist/server/request-context.js",
73
73
  "./server/design-token-utils": "./dist/server/design-token-utils.js",
74
74
  "./server/poll": "./dist/server/poll.js",
75
+ "./server/interval-job": "./dist/server/interval-job.js",
75
76
  "./server/sse": "./dist/server/sse.js",
76
77
  "./server/short-lived-token": "./dist/server/short-lived-token.js",
77
78
  "./integrations": "./dist/integrations/index.js",
@@ -29,6 +29,11 @@ Templates use `app/i18n/`:
29
29
  Use BCP-47 filenames from the supported set: `en-US`, `zh-CN`, `es-ES`,
30
30
  `fr-FR`, `de-DE`, `ja-JP`, `ko-KR`, `pt-BR`, `hi-IN`, `ar-SA`.
31
31
 
32
+ If an app only ships translations for a subset of that set, set
33
+ `supportedLocales` on the `AgentNativeI18nCatalog` to that subset. Without it,
34
+ `<LanguagePicker />` lists every framework-supported locale regardless of
35
+ whether `loadMessages` can actually resolve it.
36
+
32
37
  ## UI Pattern
33
38
 
34
39
  - Wrap apps with `AppProviders i18n={{ catalog: i18nCatalog }}`.
@@ -217,9 +217,12 @@ POST /_agent-native/extensions
217
217
 
218
218
  HTTP creation is disabled by default. The host app must set
219
219
  `extensionTools: true` on `createCoreRoutesPlugin()` as well as
220
- `createAgentChatPlugin()`; otherwise authenticated collection `POST` requests
221
- return `403` while existing extension runtime, read, edit, and deep-link routes
222
- remain available for compatibility.
220
+ `frameworkTools: { extensions: true }` on `createAgentChatPlugin()`; otherwise
221
+ authenticated collection `POST` requests return `403` while existing extension
222
+ runtime, read, edit, and deep-link routes remain available for compatibility.
223
+ (`extensionTools` on the agent-chat plugin is the deprecated spelling of
224
+ `frameworkTools.extensions`; the core-routes option keeps its own name because
225
+ it gates route mounting rather than agent tools.)
223
226
 
224
227
  The action accepts:
225
228