@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
@@ -1,3 +1,4 @@
1
+ import { type FrameworkToolGroup } from "../../framework-tools.js";
1
2
  import type { ContextGovernanceTier, ContextManifestSourceRef, ContextSystemProvenance } from "../../shared/context-xray.js";
2
3
  export declare const COMPACT_PROMPT_RESOURCE_MAX_CHARS = 6000;
3
4
  export interface PromptContextProviderContext {
@@ -89,4 +90,6 @@ export declare function resourceScopeForOwner(owner: string, currentOwner?: stri
89
90
  * AGENTS.md and restarting the server is all it takes; Vite HMR invalidates
90
91
  * the bundle in dev so changes land instantly.
91
92
  */
92
- export declare function loadResourcesForPrompt(owner: string, compact?: boolean, selfAppId?: string, orgId?: string | null): Promise<string>;
93
+ export declare function loadResourcesForPrompt(owner: string, compact?: boolean, selfAppId?: string, orgId?: string | null, opts?: {
94
+ disabledFrameworkGroups?: ReadonlySet<FrameworkToolGroup>;
95
+ }): Promise<string>;
@@ -1,3 +1,4 @@
1
+ import { frameworkGroupEnabled, } from "../../framework-tools.js";
1
2
  import { getFrontmatterValue, getSkillNameFromPath, parseFrontmatter, } from "../../resources/metadata.js";
2
3
  import { ensurePersonalDefaults, organizationIdFromResourceOwner, resourceGet, resourceGetByPath, resourceList, resourceListAccessible, SHARED_OWNER, sharedResourceOwner, WORKSPACE_OWNER, } from "../../resources/store.js";
3
4
  import { discoverAgents } from "../agent-discovery.js";
@@ -600,7 +601,7 @@ async function loadResourceIndexForPrompt(owner, scope) {
600
601
  * AGENTS.md and restarting the server is all it takes; Vite HMR invalidates
601
602
  * the bundle in dev so changes land instantly.
602
603
  */
603
- export async function loadResourcesForPrompt(owner, compact = false, selfAppId, orgId = getRequestOrgId() ?? null) {
604
+ export async function loadResourcesForPrompt(owner, compact = false, selfAppId, orgId = getRequestOrgId() ?? null, opts) {
604
605
  await ensurePersonalDefaults(owner);
605
606
  const sections = [];
606
607
  const addSection = (content, governance = "inherited") => {
@@ -774,7 +775,12 @@ export async function loadResourcesForPrompt(owner, compact = false, selfAppId,
774
775
  orgId,
775
776
  }));
776
777
  try {
777
- const agents = (await discoverAgents(selfAppId)).slice(0, 30);
778
+ // Both tools this block names by name come from the `workspaceApps` group.
779
+ // With that group off there is no `call-agent` to delegate through, so the
780
+ // block would only teach a capability the request cannot carry.
781
+ const agents = frameworkGroupEnabled(opts?.disabledFrameworkGroups, "workspaceApps")
782
+ ? (await discoverAgents(selfAppId)).slice(0, 30)
783
+ : [];
778
784
  if (agents.length > 0) {
779
785
  const lines = agents.map((agent) => `- ${agent.name} (${agent.id}) — ${agent.description || "Connected A2A app"}`);
780
786
  // Nothing else in the prompt or the initial tool surface tells the agent
@@ -24,6 +24,7 @@ import { adoptThreadScopeIfUnscoped, createThread, forkThread, getThread, regist
24
24
  import { isCheckpointRestorePath } from "../checkpoints/route-match.js";
25
25
  import { createDbAdminAgentTools } from "../db-admin/agent-tools.js";
26
26
  import { isTransientDatabaseError } from "../db/client.js";
27
+ import { filterFrameworkToolGroups, resolveFrameworkTools, } from "../framework-tools.js";
27
28
  import { verifyInternalToken, extractBearerToken, } from "../integrations/internal-token.js";
28
29
  import { RECURRING_JOBS_SWEEP_PATH, RECURRING_JOBS_SWEEP_TOKEN_SUBJECT, } from "../jobs/scheduler-dispatch.js";
29
30
  import { McpClientManager, loadMcpConfig, autoDetectMcpConfig, mcpToolsToActionEntries, syncMcpActionEntries, mountMcpServersRoutes, mountMcpHubRoutes, buildMergedConfig, startMcpConfigRefresh, getHubStatus, isHubServeEnabled, } from "../mcp-client/index.js";
@@ -41,6 +42,7 @@ import { captureError } from "./capture-error.js";
41
42
  import { getH3App, markDefaultPluginProvided, trackPluginInit, } from "./framework-request-handler.js";
42
43
  import { getOrigin } from "./google-oauth.js";
43
44
  import { readBody } from "./h3-helpers.js";
45
+ import { startIntervalJob } from "./interval-job.js";
44
46
  import { getModelFamilyOverlay } from "./prompts/index.js";
45
47
  import { mountRealtimeVoiceRoutes } from "./realtime-voice.js";
46
48
  import { runWithRequestContext, getRequestContext, getRequestOrgId, getRequestUserEmail, getRequestRunContext, ensureRequestRunContext, } from "./request-context.js";
@@ -276,12 +278,19 @@ export function createAgentChatPlugin(options) {
276
278
  // `canToggle` means "this environment allows toggling" (static); this
277
279
  // function means "the user currently has dev mode ON" (live).
278
280
  const isDevMode = () => currentDevMode;
281
+ // Resolve the framework's own tool surface once, folding the deprecated
282
+ // `databaseTools` / `extensionTools` into `frameworkTools` so every
283
+ // consumer below reads a single shape. Conflicting old/new values throw
284
+ // here rather than booting with a surface nobody chose.
285
+ const frameworkTools = resolveFrameworkTools(options);
286
+ const disabledFrameworkGroups = frameworkTools.disabledGroups;
279
287
  // Build the four assembled system prompt strings. These are static for the
280
288
  // lifetime of this plugin instance — examples come from options once at
281
289
  // startup, not per-request.
282
290
  const { PROD_FRAMEWORK_PROMPT, DEV_FRAMEWORK_PROMPT, PROD_FRAMEWORK_PROMPT_COMPACT, DEV_FRAMEWORK_PROMPT_COMPACT, } = buildFrameworkPrompts(options?.promptExamples, {
283
- databaseTools: options?.databaseTools,
284
- extensionTools: options?.extensionTools,
291
+ databaseTools: frameworkTools.database,
292
+ extensionTools: frameworkTools.extensions,
293
+ disabledFrameworkGroups,
285
294
  });
286
295
  // Route readiness must not wait on settings scans, remote hub fetches, or
287
296
  // third-party MCP handshakes. Build the action surface against an empty
@@ -396,13 +405,23 @@ export function createAgentChatPlugin(options) {
396
405
  catch {
397
406
  // Package action registration is optional.
398
407
  }
399
- // Resource, chat, docs, db, and cross-agent scripts are available in both prod and dev modes
400
- const resourceScripts = await createResourceScriptEntries();
401
- const docsScripts = await createDocsScriptEntries();
402
- const databaseToolsMode = normalizeDatabaseToolsMode(options?.databaseTools);
408
+ // Resource, chat, docs, db, and cross-agent scripts are available in both
409
+ // prod and dev modes, unless the app switched the group off through
410
+ // `frameworkTools`. Gating at construction is deliberate: an empty map
411
+ // flows through all thirteen registry composition sites below, whereas a
412
+ // condition at each spread site is the same omission waiting to happen
413
+ // thirteen times. Matching HTTP routes are mounted from `httpActions`,
414
+ // which is built from the ungated sets so the UI keeps working.
415
+ const resourceScripts = frameworkTools.isEnabled("resources")
416
+ ? await createResourceScriptEntries()
417
+ : {};
418
+ const docsScripts = frameworkTools.isEnabled("docs")
419
+ ? await createDocsScriptEntries()
420
+ : {};
421
+ const databaseToolsMode = normalizeDatabaseToolsMode(frameworkTools.database);
403
422
  const databaseToolsEnabled = databaseToolsMode !== "off";
404
423
  const databaseWriteToolsEnabled = databaseToolsMode === "write";
405
- const extensionToolsEnabled = options?.extensionTools === true;
424
+ const extensionToolsEnabled = frameworkTools.extensions;
406
425
  const dbScripts = databaseToolsEnabled
407
426
  ? await createDbScriptEntries(databaseToolsMode, {
408
427
  extensionTools: extensionToolsEnabled,
@@ -416,12 +435,20 @@ export function createAgentChatPlugin(options) {
416
435
  const urlTools = createUrlTools();
417
436
  const engineScripts = await createAgentEngineScriptEntries(options?.appId);
418
437
  const loopSettingsScripts = await createAgentLoopSettingsScriptEntries();
419
- const chatScripts = {
420
- ...(await createChatScriptEntries()),
421
- ...engineScripts,
422
- ...loopSettingsScripts,
423
- };
424
- const callAgentScript = await createCallAgentScriptEntry(options?.appId);
438
+ // `httpActions` spreads engineScripts/loopSettingsScripts directly, so
439
+ // gating here removes them from the agent's tools without unmounting the
440
+ // routes the settings UI calls.
441
+ const chatScripts = frameworkTools.isEnabled("chat")
442
+ ? {
443
+ ...(await createChatScriptEntries()),
444
+ ...engineScripts,
445
+ ...loopSettingsScripts,
446
+ }
447
+ : {};
448
+ // Both `call-agent` and `describe-workspace-apps` come from this factory.
449
+ const callAgentScript = frameworkTools.isEnabled("workspaceApps")
450
+ ? await createCallAgentScriptEntry(options?.appId)
451
+ : {};
425
452
  let runNowSchedulerDeps = null;
426
453
  const browserTools = createBuilderBrowserTool({
427
454
  getOrigin: () => getRequestRunContext()?.requestOrigin ?? "http://localhost:3000",
@@ -581,7 +608,13 @@ export function createAgentChatPlugin(options) {
581
608
  // surface, prompt, MCP/A2A list, and job/trigger runner below. The full
582
609
  // `*All` sets are reserved for `httpActions`, which keeps agent-hidden
583
610
  // actions reachable from the frontend / HTTP.
584
- const templateScripts = filterAgentTools(templateScriptsAll);
611
+ //
612
+ // The framework's own action kits (sharing, review, history, flags, …)
613
+ // arrive in this same registry through `autoDiscoverActions`, tagged with
614
+ // `frameworkGroup`. Subtracting disabled groups HERE is what removes them
615
+ // from every downstream agent surface at once; `httpActions` re-merges
616
+ // them ungated, so the share dialog and review threads keep their routes.
617
+ const templateScripts = filterFrameworkToolGroups(filterAgentTools(templateScriptsAll), disabledFrameworkGroups);
585
618
  // Compact is the safe default for every app, including generated and
586
619
  // third-party apps that have not curated a starter list yet. Keep the
587
620
  // app's own action surface immediately callable; framework, provider,
@@ -591,7 +624,7 @@ export function createAgentChatPlugin(options) {
591
624
  // (below, after the run-code tools are built) additionally folds in
592
625
  // whatever `generateCorpusToolsPrompt` teaches by name for this request.
593
626
  const templateInitialToolNames = resolveInitialToolNames(templateScripts, options?.initialToolNames);
594
- const discoveredActions = filterAgentTools(discoveredActionsAll);
627
+ const discoveredActions = filterFrameworkToolGroups(filterAgentTools(discoveredActionsAll), disabledFrameworkGroups);
595
628
  // Per-request owner is read from the AsyncLocalStorage run context
596
629
  // (populated by prepareRun). Module-scope `let` would race across
597
630
  // concurrent requests on a long-lived Node process — overlapping
@@ -627,22 +660,29 @@ export function createAgentChatPlugin(options) {
627
660
  // Automation tools + fetch tool — depend on owner via callback.
628
661
  // Each callback short-circuits with a clear error when the run context
629
662
  // has no authenticated owner (see SECURITY note on getCurrentRunOwner).
663
+ const automationGroupEnabled = frameworkTools.isEnabled("automation");
630
664
  let automationTools = {};
631
665
  try {
632
- const { createAutomationToolEntries } = await import("../triggers/actions.js");
633
- automationTools = createAutomationToolEntries(() => requireCurrentRunOwner("manage automations"));
666
+ if (automationGroupEnabled) {
667
+ const { createAutomationToolEntries } = await import("../triggers/actions.js");
668
+ automationTools = createAutomationToolEntries(() => requireCurrentRunOwner("manage automations"));
669
+ }
634
670
  }
635
671
  catch { }
636
672
  let notificationTools = {};
637
673
  try {
638
- const { createNotificationToolEntries } = await import("../notifications/actions.js");
639
- notificationTools = createNotificationToolEntries(() => requireCurrentRunOwner("manage notifications"));
674
+ if (automationGroupEnabled) {
675
+ const { createNotificationToolEntries } = await import("../notifications/actions.js");
676
+ notificationTools = createNotificationToolEntries(() => requireCurrentRunOwner("manage notifications"));
677
+ }
640
678
  }
641
679
  catch { }
642
680
  let progressTools = {};
643
681
  try {
644
- const { createProgressToolEntries } = await import("../progress/actions.js");
645
- progressTools = createProgressToolEntries(() => requireCurrentRunOwner("manage progress"));
682
+ if (automationGroupEnabled) {
683
+ const { createProgressToolEntries } = await import("../progress/actions.js");
684
+ progressTools = createProgressToolEntries(() => requireCurrentRunOwner("manage progress"));
685
+ }
646
686
  }
647
687
  catch { }
648
688
  let githubRepoTools = {};
@@ -660,48 +700,53 @@ export function createAgentChatPlugin(options) {
660
700
  });
661
701
  }
662
702
  catch { }
703
+ const webGroupEnabled = frameworkTools.isEnabled("web");
663
704
  let fetchTool = {};
664
705
  try {
665
- const { createFetchToolEntry } = await import("../extensions/fetch-tool.js");
666
- // Resolve `${keys.NAME}` through the same request-scope cascade
667
- // already used by extension fetches (extensions/routes.ts) and
668
- // automation connector headers (automation/index.ts): user scope
669
- // first (personal overrides win), then the active org scope (the
670
- // Dispatch vault syncs workspace secrets here), then workspace
671
- // scope. Org/workspace vault rows are write-gated (org-admin +
672
- // Dispatch vault UI), so this is safe to read by default — unlike
673
- // the opt-in-only user→workspace fallback in resolveKeyReferences
674
- // (see audit 05 H2 in secrets/substitution.ts), which stays off.
675
- // Previously this tool only looked at scope "user", so a
676
- // ${keys.NAME} reference to a key synced into the org/workspace
677
- // vault could never resolve here even though the same key already
678
- // worked for extension fetches and automations.
679
- const { resolveKeyReferencesWithRequestScopes, validateUrlAllowlist, getKeyAllowlist, getResolvedKeyAllowlist, } = await import("../secrets/substitution.js");
680
- fetchTool = createFetchToolEntry({
681
- resolveKeys: async (text) => resolveKeyReferencesWithRequestScopes(text, requireCurrentRunOwner("resolve key references")),
682
- validateUrl: async (url, usedKeys, resolvedKeys) => {
683
- for (const keyName of usedKeys) {
684
- const allowlist = await resolveFetchToolKeyAllowlist(keyName, resolvedKeys, requireCurrentRunOwner("validate URL allowlist"), { getKeyAllowlist, getResolvedKeyAllowlist });
685
- if (allowlist && !validateUrlAllowlist(url, allowlist)) {
686
- return false;
706
+ if (webGroupEnabled) {
707
+ const { createFetchToolEntry } = await import("../extensions/fetch-tool.js");
708
+ // Resolve `${keys.NAME}` through the same request-scope cascade
709
+ // already used by extension fetches (extensions/routes.ts) and
710
+ // automation connector headers (automation/index.ts): user scope
711
+ // first (personal overrides win), then the active org scope (the
712
+ // Dispatch vault syncs workspace secrets here), then workspace
713
+ // scope. Org/workspace vault rows are write-gated (org-admin +
714
+ // Dispatch vault UI), so this is safe to read by default — unlike
715
+ // the opt-in-only user→workspace fallback in resolveKeyReferences
716
+ // (see audit 05 H2 in secrets/substitution.ts), which stays off.
717
+ // Previously this tool only looked at scope "user", so a
718
+ // ${keys.NAME} reference to a key synced into the org/workspace
719
+ // vault could never resolve here even though the same key already
720
+ // worked for extension fetches and automations.
721
+ const { resolveKeyReferencesWithRequestScopes, validateUrlAllowlist, getKeyAllowlist, getResolvedKeyAllowlist, } = await import("../secrets/substitution.js");
722
+ fetchTool = createFetchToolEntry({
723
+ resolveKeys: async (text) => resolveKeyReferencesWithRequestScopes(text, requireCurrentRunOwner("resolve key references")),
724
+ validateUrl: async (url, usedKeys, resolvedKeys) => {
725
+ for (const keyName of usedKeys) {
726
+ const allowlist = await resolveFetchToolKeyAllowlist(keyName, resolvedKeys, requireCurrentRunOwner("validate URL allowlist"), { getKeyAllowlist, getResolvedKeyAllowlist });
727
+ if (allowlist && !validateUrlAllowlist(url, allowlist)) {
728
+ return false;
729
+ }
687
730
  }
688
- }
689
- return true;
690
- },
691
- });
731
+ return true;
732
+ },
733
+ });
734
+ }
692
735
  }
693
736
  catch { }
694
737
  let webSearchTool = {};
695
738
  try {
696
- const { createWebSearchToolEntry } = await import("../extensions/web-search-tool.js");
697
- const { getBuilderWebSearchBaseUrl, resolveBuilderCredentials, resolveSecret, } = await import("./credential-provider.js");
698
- const { getBuilderGatewayRequestHeaders } = await import("../agent/engine/builder-gateway-headers.js");
699
- webSearchTool = createWebSearchToolEntry({
700
- resolveSecret,
701
- resolveBuilderCredentials,
702
- getBuilderWebSearchBaseUrl,
703
- getBuilderRequestHeaders: getBuilderGatewayRequestHeaders,
704
- });
739
+ if (webGroupEnabled) {
740
+ const { createWebSearchToolEntry } = await import("../extensions/web-search-tool.js");
741
+ const { getBuilderWebSearchBaseUrl, resolveBuilderCredentials, resolveSecret, } = await import("./credential-provider.js");
742
+ const { getBuilderGatewayRequestHeaders } = await import("../agent/engine/builder-gateway-headers.js");
743
+ webSearchTool = createWebSearchToolEntry({
744
+ resolveSecret,
745
+ resolveBuilderCredentials,
746
+ getBuilderWebSearchBaseUrl,
747
+ getBuilderRequestHeaders: getBuilderGatewayRequestHeaders,
748
+ });
749
+ }
705
750
  }
706
751
  catch { }
707
752
  let workspaceFilesTool = {};
@@ -751,11 +796,13 @@ export function createAgentChatPlugin(options) {
751
796
  let coreEmailTools = {};
752
797
  let backgroundCoreEmailTools = {};
753
798
  try {
754
- const { createCoreEmailActionEntries } = await import("./email-actions.js");
755
- coreEmailTools = createCoreEmailActionEntries();
756
- backgroundCoreEmailTools = createCoreEmailActionEntries({
757
- unattended: true,
758
- });
799
+ if (frameworkTools.isEnabled("email")) {
800
+ const { createCoreEmailActionEntries } = await import("./email-actions.js");
801
+ coreEmailTools = createCoreEmailActionEntries();
802
+ backgroundCoreEmailTools = createCoreEmailActionEntries({
803
+ unattended: true,
804
+ });
805
+ }
759
806
  }
760
807
  catch { }
761
808
  // Core read-attachment tool — always registered so the agent can page
@@ -1232,7 +1279,7 @@ export function createAgentChatPlugin(options) {
1232
1279
  const devActive = isDevMode();
1233
1280
  // Build the same system prompt the interactive agent uses
1234
1281
  const owner = userEmail;
1235
- const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId);
1282
+ const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups });
1236
1283
  const schemaBlock = lazyContext
1237
1284
  ? ""
1238
1285
  : await buildSchemaBlock(owner, databaseToolsMode);
@@ -1700,7 +1747,7 @@ export function createAgentChatPlugin(options) {
1700
1747
  // catalog on its very first request, undermining the compact MCP
1701
1748
  // catalog this surface is supposed to keep external callers on.
1702
1749
  const mcpToolSurface = createA2AEngineToolSurface(actionsToEngineTools(mcpActions), effectiveInitialToolNames);
1703
- const resources = await loadResourcesForPrompt(SHARED_OWNER, lazyContext, options?.appId);
1750
+ const resources = await loadResourcesForPrompt(SHARED_OWNER, lazyContext, options?.appId, undefined, { disabledFrameworkGroups });
1704
1751
  const schemaBlock = lazyContext
1705
1752
  ? ""
1706
1753
  : await buildSchemaBlock(SHARED_OWNER, databaseToolsMode);
@@ -2155,8 +2202,10 @@ export function createAgentChatPlugin(options) {
2155
2202
  // Job management tool (manage-jobs)
2156
2203
  let jobTools = {};
2157
2204
  try {
2158
- const { createJobTools } = await import("../jobs/tools.js");
2159
- jobTools = createJobTools();
2205
+ if (automationGroupEnabled) {
2206
+ const { createJobTools } = await import("../jobs/tools.js");
2207
+ jobTools = createJobTools();
2208
+ }
2160
2209
  }
2161
2210
  catch { }
2162
2211
  // Lean mode: only template actions + essential framework tools. Drop
@@ -2486,7 +2535,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
2486
2535
  modelOverlay +
2487
2536
  runtimeContext);
2488
2537
  }
2489
- const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId);
2538
+ const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups });
2490
2539
  // In lazy context mode, skip embedding the full schema. When database
2491
2540
  // tools are enabled the agent can call `db-schema` on demand.
2492
2541
  const schemaBlock = lazyContext
@@ -2719,7 +2768,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
2719
2768
  });
2720
2769
  return setSystemPromptOnContext(leanBasePrompt + extra + modelOverlay + runtimeContext);
2721
2770
  }
2722
- const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId);
2771
+ const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups });
2723
2772
  const schemaBlock = lazyContext || !databaseToolsEnabled
2724
2773
  ? ""
2725
2774
  : await buildSchemaBlock(owner, databaseToolsMode);
@@ -4695,7 +4744,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4695
4744
  const schedulerDeps = {
4696
4745
  getActions: getBackgroundActionEntries,
4697
4746
  getSystemPrompt: async (owner) => {
4698
- const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId);
4747
+ const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups });
4699
4748
  const schemaBlock = lazyContext
4700
4749
  ? ""
4701
4750
  : await buildSchemaBlock(owner, databaseToolsMode);
@@ -4711,8 +4760,11 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4711
4760
  // fetchTool/webSearchTool/toolActions catalog every tick.
4712
4761
  getInitialToolNames: (job) => [
4713
4762
  ...effectiveInitialToolNames,
4714
- "manage-jobs",
4715
- "manage-progress",
4763
+ // Only promotable while the group that supplies them is on; with
4764
+ // `automation` off the prompt no longer names them either.
4765
+ ...(automationGroupEnabled
4766
+ ? ["manage-jobs", "manage-progress"]
4767
+ : []),
4716
4768
  ...(job?.meta.mcpTools ?? []),
4717
4769
  ],
4718
4770
  apiKey: options?.apiKey,
@@ -4966,35 +5018,31 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4966
5018
  setTimeout(() => {
4967
5019
  (async () => {
4968
5020
  const { UNCLAIMED_BACKGROUND_RUN_FAST_SWEEP_MS } = await import("../agent/run-store.js");
4969
- setInterval(() => {
4970
- (async () => {
4971
- const { listUnclaimedBackgroundRunRows, shouldRedispatchUnclaimedBackgroundRun, reapAllStaleRuns, } = await import("../agent/run-store.js");
4972
- // The unclaimed-background sweep below only matches
4973
- // dispatch_mode='background' — handoffs a worker never
4974
- // claimed. Once a worker CLAIMS a row nothing periodic looked
4975
- // at it again, so a dead producer was only reaped when some
4976
- // client request path or an unrelated run's cleanup happened
4977
- // to notice (prod: 24 minutes after the last heartbeat,
4978
- // against a 45s window). `reapAllStaleRuns` is per-row,
4979
- // idempotent, re-checks staleness at UPDATE time and honours
4980
- // the in-flight grace, so it is safe on this cadence.
4981
- await reapAllStaleRuns().catch(() => { });
4982
- let rows;
4983
- try {
4984
- rows = await listUnclaimedBackgroundRunRows();
4985
- }
4986
- catch {
4987
- return; // Table may not exist yet on first boot
4988
- }
4989
- for (const row of rows) {
4990
- if (!shouldRedispatchUnclaimedBackgroundRun(row))
4991
- continue;
4992
- await attemptUnclaimedBackgroundRunRedispatch(row).catch(() => { });
4993
- }
4994
- })().catch(() => {
4995
- // best-effort — never break the server
4996
- });
4997
- }, UNCLAIMED_BACKGROUND_RUN_FAST_SWEEP_MS);
5021
+ startIntervalJob(async () => {
5022
+ const { listUnclaimedBackgroundRunRows, shouldRedispatchUnclaimedBackgroundRun, reapAllStaleRuns, } = await import("../agent/run-store.js");
5023
+ // The unclaimed-background sweep below only matches
5024
+ // dispatch_mode='background' — handoffs a worker never
5025
+ // claimed. Once a worker CLAIMS a row nothing periodic looked
5026
+ // at it again, so a dead producer was only reaped when some
5027
+ // client request path or an unrelated run's cleanup happened
5028
+ // to notice (prod: 24 minutes after the last heartbeat,
5029
+ // against a 45s window). `reapAllStaleRuns` is per-row,
5030
+ // idempotent, re-checks staleness at UPDATE time and honours
5031
+ // the in-flight grace, so it is safe on this cadence.
5032
+ await reapAllStaleRuns().catch(() => { });
5033
+ let rows;
5034
+ try {
5035
+ rows = await listUnclaimedBackgroundRunRows();
5036
+ }
5037
+ catch {
5038
+ return; // Table may not exist yet on first boot
5039
+ }
5040
+ for (const row of rows) {
5041
+ if (!shouldRedispatchUnclaimedBackgroundRun(row))
5042
+ continue;
5043
+ await attemptUnclaimedBackgroundRunRedispatch(row).catch(() => { });
5044
+ }
5045
+ }, { intervalMs: UNCLAIMED_BACKGROUND_RUN_FAST_SWEEP_MS });
4998
5046
  })().catch(() => {
4999
5047
  // best-effort — if run-store fails to load, the slow sweep below
5000
5048
  // still provides eventual (loud) recovery.
@@ -5076,7 +5124,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
5076
5124
  await initTriggerDispatcher({
5077
5125
  getActions: getBackgroundActionEntries,
5078
5126
  getSystemPrompt: async (owner) => {
5079
- const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId);
5127
+ const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups });
5080
5128
  const schemaBlock = lazyContext
5081
5129
  ? ""
5082
5130
  : await buildSchemaBlock(owner, databaseToolsMode);
@@ -1,4 +1,5 @@
1
1
  export { createServer, type CreateServerOptions, type EnvKeyConfig, } from "./create-server.js";
2
+ export { startIntervalJob, type IntervalJobOptions, type IntervalJobHandle, } from "./interval-job.js";
2
3
  export { AGENT_BACKGROUND_PROCESSOR_FIELD, AGENT_BACKGROUND_PROCESSOR_ROUTE, AGENT_BACKGROUND_PROCESSOR_ROUTE_FIELD, dispatchPathTargetsNetlifyBackgroundFunction, isInBackgroundFunctionRuntime, resolveDurableBackgroundDispatchPath, } from "../agent/durable-background.js";
3
4
  export { readBody, readBodyWithSizeLimit, streamFile, DEFAULT_CHAT_MAX_BODY_BYTES, DEFAULT_UPLOAD_MAX_FILE_BYTES, MAX_CHAT_ATTACHMENTS_PER_MESSAGE, isAllowedUploadMimeType, } from "./h3-helpers.js";
4
5
  export { buildDeepLink, toAbsoluteOpenUrl, toDesktopOpenUrl, toVsCodeOpenUrl, OPEN_ROUTE_SUBPATH, DESKTOP_OPEN_URL, VSCODE_OPEN_URL, type DeepLinkInput, } from "./deep-link.js";
@@ -1,4 +1,5 @@
1
1
  export { createServer, } from "./create-server.js";
2
+ export { startIntervalJob, } from "./interval-job.js";
2
3
  export { AGENT_BACKGROUND_PROCESSOR_FIELD, AGENT_BACKGROUND_PROCESSOR_ROUTE, AGENT_BACKGROUND_PROCESSOR_ROUTE_FIELD, dispatchPathTargetsNetlifyBackgroundFunction, isInBackgroundFunctionRuntime, resolveDurableBackgroundDispatchPath, } from "../agent/durable-background.js";
3
4
  export { readBody, readBodyWithSizeLimit, streamFile, DEFAULT_CHAT_MAX_BODY_BYTES, DEFAULT_UPLOAD_MAX_FILE_BYTES, MAX_CHAT_ATTACHMENTS_PER_MESSAGE, isAllowedUploadMimeType, } from "./h3-helpers.js";
4
5
  export { buildDeepLink, toAbsoluteOpenUrl, toDesktopOpenUrl, toVsCodeOpenUrl, OPEN_ROUTE_SUBPATH, DESKTOP_OPEN_URL, VSCODE_OPEN_URL, } from "./deep-link.js";
@@ -0,0 +1,25 @@
1
+ export interface IntervalJobOptions {
2
+ /** Delay in ms between runs. */
3
+ intervalMs: number;
4
+ /** Per-run timeout. Default: `Math.max(timeoutFloorMs, intervalMs * 4)`. */
5
+ timeoutMs?: number;
6
+ /** Floor used by the default `timeoutMs`. Default: 10_000. */
7
+ timeoutFloorMs?: number;
8
+ /** Called when a run throws or times out. Default: swallow (a background job shouldn't crash the host process). */
9
+ onError?: (err: unknown) => void;
10
+ /** Run immediately on start. Default: true. */
11
+ leading?: boolean;
12
+ }
13
+ export interface IntervalJobHandle {
14
+ stop(): void;
15
+ }
16
+ /**
17
+ * Run a background job on a recurring interval without hand-rolling a
18
+ * `running` overlap guard or a request timeout — see `createPollEngine` for
19
+ * the underlying guarantees. `runOnce` keeps its normal exported signature
20
+ * (serverless schedulers can still call it directly, bypassing the loop);
21
+ * this only replaces the `setInterval` + module-level flag wiring around it.
22
+ * The internal timer is `.unref()`'d so it never keeps the process alive on
23
+ * its own.
24
+ */
25
+ export declare function startIntervalJob(runOnce: (signal: AbortSignal) => Promise<void>, options: IntervalJobOptions): IntervalJobHandle;
@@ -0,0 +1,19 @@
1
+ import { createPollEngine } from "../shared/poll-engine.js";
2
+ /**
3
+ * Run a background job on a recurring interval without hand-rolling a
4
+ * `running` overlap guard or a request timeout — see `createPollEngine` for
5
+ * the underlying guarantees. `runOnce` keeps its normal exported signature
6
+ * (serverless schedulers can still call it directly, bypassing the loop);
7
+ * this only replaces the `setInterval` + module-level flag wiring around it.
8
+ * The internal timer is `.unref()`'d so it never keeps the process alive on
9
+ * its own.
10
+ */
11
+ export function startIntervalJob(runOnce, options) {
12
+ const engine = createPollEngine(runOnce, options);
13
+ engine.start();
14
+ return {
15
+ stop() {
16
+ engine.stop();
17
+ },
18
+ };
19
+ }
@@ -8,11 +8,16 @@
8
8
  * - Detailed "Parallel Tool Calls" prose (replaced with one-liner)
9
9
  * - Detailed "Resources" section (agent uses resources tool)
10
10
  */
11
+ import { type FrameworkToolGroup } from "../../framework-tools.js";
11
12
  import { type DatabaseToolsOption } from "../../scripts/db/tool-mode.js";
12
13
  import { type PromptExamples } from "./shared-rules.js";
13
14
  export interface FrameworkCoreCompactPromptOptions {
14
15
  databaseTools?: DatabaseToolsOption;
15
16
  extensionTools?: boolean;
17
+ /** Framework tool groups this app switched off. Every block below that names
18
+ * a group's tool by name is gated on this — a prompt naming an absent tool
19
+ * makes the model call it, fail, and often report the capability as missing. */
20
+ disabledFrameworkGroups?: ReadonlySet<FrameworkToolGroup>;
16
21
  }
17
22
  /**
18
23
  * Build the compact FRAMEWORK_CORE prompt string.
@@ -8,14 +8,37 @@
8
8
  * - Detailed "Parallel Tool Calls" prose (replaced with one-liner)
9
9
  * - Detailed "Resources" section (agent uses resources tool)
10
10
  */
11
+ import { frameworkGroupEnabled, } from "../../framework-tools.js";
11
12
  import { hasDatabaseReadTools, hasDatabaseWriteTools, } from "../../scripts/db/tool-mode.js";
12
- import { sharedRule8, SHARED_RULE_9, SHARED_RULE_14, SHARED_RULE_15, SHARED_RULE_AGENT_WARNINGS, } from "./shared-rules.js";
13
+ import { sharedRule8, SHARED_RULE_9, sharedRule14, SHARED_RULE_15, SHARED_RULE_AGENT_WARNINGS, } from "./shared-rules.js";
13
14
  /**
14
15
  * Build the compact FRAMEWORK_CORE prompt string.
15
16
  *
16
17
  * @param examples Optional injectable provider/action examples for rule 8.
17
18
  */
18
19
  export function buildFrameworkCoreCompact(examples, options) {
20
+ const groupOn = (group) => frameworkGroupEnabled(options?.disabledFrameworkGroups, group);
21
+ const resourcesSection = groupOn("resources")
22
+ ? `### Resources
23
+
24
+ Use the \`resources\` tool for persistent notes and context files: \`action: "list"\`, \`"read"\`, \`"effective"\`, \`"write"\`, \`"promote"\`, or \`"delete"\`.
25
+ Resources have three levels: workspace defaults inherited from Dispatch, shared organization/app overrides, and personal overrides. Use \`resources\` with \`action: "effective"\` before editing when you need to explain or inspect which level is active for a path.
26
+ Workspace resources are user-facing by default. If you need temporary working files, write them as agent scratch (\`visibility: "agent_scratch"\`); scratch is hidden from the Workspace view by default and expires. Use \`visibility: "workspace"\` only when the user explicitly asked to save/manage that file, or for durable AGENTS.md, LEARNINGS.md, memory, skills, jobs, or custom agents.
27
+ `
28
+ : "";
29
+ const extendedCapabilityClauses = [
30
+ "inline embeds",
31
+ groupOn("chat") ? "chat history search (`chat-history`)" : "",
32
+ groupOn("automation") ? "recurring jobs (`manage-jobs`)" : "",
33
+ "structured memory (`save-memory`/`delete-memory`)",
34
+ "browser automation (`activate-browser` in production, `set-browser-control` locally)",
35
+ ].filter(Boolean);
36
+ const extendedCapabilitiesList = `${extendedCapabilityClauses.slice(0, -1).join(", ")}, and ${extendedCapabilityClauses.at(-1)}`;
37
+ const callAgentSection = groupOn("workspaceApps")
38
+ ? `
39
+ For generated media, prefer this app's native generation action; otherwise use \`call-agent\` with agent "assets".
40
+ `
41
+ : "";
19
42
  const hasDatabaseTools = hasDatabaseReadTools(options?.databaseTools);
20
43
  const hasDatabaseWrites = hasDatabaseWriteTools(options?.databaseTools);
21
44
  const dataRule = hasDatabaseWrites
@@ -56,22 +79,15 @@ ${SHARED_RULE_9}
56
79
  **Native widgets** — For table/chart/graph/report requests, prefer actions labeled \`Native chat widget\`; use \`render-data-widget\` for already-summarized data (≤50 rows) instead of markdown tables. Above that, give the total plus the top rows — never retype a full result set as widget arguments. Deliver files in chat, never just a path.
57
80
  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.
58
81
  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.
59
- ${SHARED_RULE_14}
82
+ ${sharedRule14(options)}
60
83
  ${SHARED_RULE_15}
61
84
  ${SHARED_RULE_AGENT_WARNINGS}
62
85
 
63
- ### Resources
64
-
65
- Use the \`resources\` tool for persistent notes and context files: \`action: "list"\`, \`"read"\`, \`"effective"\`, \`"write"\`, \`"promote"\`, or \`"delete"\`.
66
- Resources have three levels: workspace defaults inherited from Dispatch, shared organization/app overrides, and personal overrides. Use \`resources\` with \`action: "effective"\` before editing when you need to explain or inspect which level is active for a path.
67
- Workspace resources are user-facing by default. If you need temporary working files, write them as agent scratch (\`visibility: "agent_scratch"\`); scratch is hidden from the Workspace view by default and expires. Use \`visibility: "workspace"\` only when the user explicitly asked to save/manage that file, or for durable AGENTS.md, LEARNINGS.md, memory, skills, jobs, or custom agents.
68
-
86
+ ${resourcesSection}
69
87
  ### Extended Capabilities
70
88
 
71
- You also have tools for inline embeds, chat history search (\`chat-history\`), recurring jobs (\`manage-jobs\`), structured memory (\`save-memory\`/\`delete-memory\`), and browser automation (\`activate-browser\` in production, \`set-browser-control\` locally). Call \`get-framework-context\` with the matching key — it lists its own topics — for full instructions when needed.
89
+ You also have tools for ${extendedCapabilitiesList}. Call \`get-framework-context\` with the matching key — it lists its own topics — for full instructions when needed.
72
90
 
73
91
  **Agent teams:** default to doing the work yourself. Delegate ONE sub-agent (\`agent-teams\` action "spawn") for self-contained heavy work; fan out to several only for genuinely independent units; never parallelize tightly-coupled work; cap fan-out around 3. 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. After \`spawn\`, say the task started/running, not completed; use \`status\`/\`read-result\` before claiming the delegated work is done. Give each sub-agent a self-contained brief (objective, the specific context/IDs it needs, output format, boundaries) — it can't see this thread — then read all results and synthesize one integrated answer. Full details: \`get-framework-context\` key \`agent-teams\`.
74
-
75
- For generated media, prefer this app's native generation action; otherwise use \`call-agent\` with agent "assets".
76
- `;
92
+ ${callAgentSection}`;
77
93
  }
@@ -5,11 +5,16 @@
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 { type FrameworkToolGroup } from "../../framework-tools.js";
8
9
  import { type DatabaseToolsOption } from "../../scripts/db/tool-mode.js";
9
10
  import { type PromptExamples } from "./shared-rules.js";
10
11
  export interface FrameworkCorePromptOptions {
11
12
  databaseTools?: DatabaseToolsOption;
12
13
  extensionTools?: boolean;
14
+ /** Framework tool groups this app switched off. Every block below that names
15
+ * a group's tool by name is gated on this — a prompt naming an absent tool
16
+ * makes the model call it, fail, and often report the capability as missing. */
17
+ disabledFrameworkGroups?: ReadonlySet<FrameworkToolGroup>;
13
18
  }
14
19
  /**
15
20
  * Build the full FRAMEWORK_CORE prompt string.