@agent-native/core 0.144.3 → 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 (40) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/clips/changelog/2026-08-06-desktop-full-screen-recordings-now-normalize-microphone-leve.md +6 -0
  3. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +185 -69
  4. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +47 -4
  5. package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -1
  6. package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
  7. package/dist/agent/production-agent.d.ts +6 -0
  8. package/dist/collab/routes.d.ts +1 -1
  9. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  10. package/dist/framework-tools.d.ts +126 -0
  11. package/dist/framework-tools.js +114 -0
  12. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  13. package/dist/notifications/routes.d.ts +4 -4
  14. package/dist/observability/routes.d.ts +3 -3
  15. package/dist/progress/routes.d.ts +1 -1
  16. package/dist/provider-api/actions/custom-provider-registration.d.ts +2 -2
  17. package/dist/resources/handlers.d.ts +1 -1
  18. package/dist/secrets/routes.d.ts +3 -3
  19. package/dist/server/action-discovery.d.ts +27 -0
  20. package/dist/server/action-discovery.js +89 -0
  21. package/dist/server/agent-chat/action-filters-a2a.d.ts +11 -0
  22. package/dist/server/agent-chat/action-filters-a2a.js +17 -1
  23. package/dist/server/agent-chat/framework-prompts.d.ts +2 -0
  24. package/dist/server/agent-chat/plugin-options.d.ts +30 -0
  25. package/dist/server/agent-chat/prompt-resources.d.ts +4 -1
  26. package/dist/server/agent-chat/prompt-resources.js +8 -2
  27. package/dist/server/agent-chat-plugin.js +122 -71
  28. package/dist/server/prompts/framework-core-compact.d.ts +5 -0
  29. package/dist/server/prompts/framework-core-compact.js +28 -12
  30. package/dist/server/prompts/framework-core.d.ts +5 -0
  31. package/dist/server/prompts/framework-core.js +33 -16
  32. package/dist/server/prompts/index.d.ts +1 -1
  33. package/dist/server/prompts/index.js +1 -1
  34. package/dist/server/prompts/shared-rules.d.ts +12 -4
  35. package/dist/server/prompts/shared-rules.js +14 -4
  36. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -3
  37. package/docs/content/deployment.mdx +47 -13
  38. package/docs/content/key-concepts.mdx +13 -6
  39. package/package.json +3 -3
  40. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -3
@@ -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";
@@ -277,12 +278,19 @@ export function createAgentChatPlugin(options) {
277
278
  // `canToggle` means "this environment allows toggling" (static); this
278
279
  // function means "the user currently has dev mode ON" (live).
279
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;
280
287
  // Build the four assembled system prompt strings. These are static for the
281
288
  // lifetime of this plugin instance — examples come from options once at
282
289
  // startup, not per-request.
283
290
  const { PROD_FRAMEWORK_PROMPT, DEV_FRAMEWORK_PROMPT, PROD_FRAMEWORK_PROMPT_COMPACT, DEV_FRAMEWORK_PROMPT_COMPACT, } = buildFrameworkPrompts(options?.promptExamples, {
284
- databaseTools: options?.databaseTools,
285
- extensionTools: options?.extensionTools,
291
+ databaseTools: frameworkTools.database,
292
+ extensionTools: frameworkTools.extensions,
293
+ disabledFrameworkGroups,
286
294
  });
287
295
  // Route readiness must not wait on settings scans, remote hub fetches, or
288
296
  // third-party MCP handshakes. Build the action surface against an empty
@@ -397,13 +405,23 @@ export function createAgentChatPlugin(options) {
397
405
  catch {
398
406
  // Package action registration is optional.
399
407
  }
400
- // Resource, chat, docs, db, and cross-agent scripts are available in both prod and dev modes
401
- const resourceScripts = await createResourceScriptEntries();
402
- const docsScripts = await createDocsScriptEntries();
403
- 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);
404
422
  const databaseToolsEnabled = databaseToolsMode !== "off";
405
423
  const databaseWriteToolsEnabled = databaseToolsMode === "write";
406
- const extensionToolsEnabled = options?.extensionTools === true;
424
+ const extensionToolsEnabled = frameworkTools.extensions;
407
425
  const dbScripts = databaseToolsEnabled
408
426
  ? await createDbScriptEntries(databaseToolsMode, {
409
427
  extensionTools: extensionToolsEnabled,
@@ -417,12 +435,20 @@ export function createAgentChatPlugin(options) {
417
435
  const urlTools = createUrlTools();
418
436
  const engineScripts = await createAgentEngineScriptEntries(options?.appId);
419
437
  const loopSettingsScripts = await createAgentLoopSettingsScriptEntries();
420
- const chatScripts = {
421
- ...(await createChatScriptEntries()),
422
- ...engineScripts,
423
- ...loopSettingsScripts,
424
- };
425
- 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
+ : {};
426
452
  let runNowSchedulerDeps = null;
427
453
  const browserTools = createBuilderBrowserTool({
428
454
  getOrigin: () => getRequestRunContext()?.requestOrigin ?? "http://localhost:3000",
@@ -582,7 +608,13 @@ export function createAgentChatPlugin(options) {
582
608
  // surface, prompt, MCP/A2A list, and job/trigger runner below. The full
583
609
  // `*All` sets are reserved for `httpActions`, which keeps agent-hidden
584
610
  // actions reachable from the frontend / HTTP.
585
- 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);
586
618
  // Compact is the safe default for every app, including generated and
587
619
  // third-party apps that have not curated a starter list yet. Keep the
588
620
  // app's own action surface immediately callable; framework, provider,
@@ -592,7 +624,7 @@ export function createAgentChatPlugin(options) {
592
624
  // (below, after the run-code tools are built) additionally folds in
593
625
  // whatever `generateCorpusToolsPrompt` teaches by name for this request.
594
626
  const templateInitialToolNames = resolveInitialToolNames(templateScripts, options?.initialToolNames);
595
- const discoveredActions = filterAgentTools(discoveredActionsAll);
627
+ const discoveredActions = filterFrameworkToolGroups(filterAgentTools(discoveredActionsAll), disabledFrameworkGroups);
596
628
  // Per-request owner is read from the AsyncLocalStorage run context
597
629
  // (populated by prepareRun). Module-scope `let` would race across
598
630
  // concurrent requests on a long-lived Node process — overlapping
@@ -628,22 +660,29 @@ export function createAgentChatPlugin(options) {
628
660
  // Automation tools + fetch tool — depend on owner via callback.
629
661
  // Each callback short-circuits with a clear error when the run context
630
662
  // has no authenticated owner (see SECURITY note on getCurrentRunOwner).
663
+ const automationGroupEnabled = frameworkTools.isEnabled("automation");
631
664
  let automationTools = {};
632
665
  try {
633
- const { createAutomationToolEntries } = await import("../triggers/actions.js");
634
- automationTools = createAutomationToolEntries(() => requireCurrentRunOwner("manage automations"));
666
+ if (automationGroupEnabled) {
667
+ const { createAutomationToolEntries } = await import("../triggers/actions.js");
668
+ automationTools = createAutomationToolEntries(() => requireCurrentRunOwner("manage automations"));
669
+ }
635
670
  }
636
671
  catch { }
637
672
  let notificationTools = {};
638
673
  try {
639
- const { createNotificationToolEntries } = await import("../notifications/actions.js");
640
- notificationTools = createNotificationToolEntries(() => requireCurrentRunOwner("manage notifications"));
674
+ if (automationGroupEnabled) {
675
+ const { createNotificationToolEntries } = await import("../notifications/actions.js");
676
+ notificationTools = createNotificationToolEntries(() => requireCurrentRunOwner("manage notifications"));
677
+ }
641
678
  }
642
679
  catch { }
643
680
  let progressTools = {};
644
681
  try {
645
- const { createProgressToolEntries } = await import("../progress/actions.js");
646
- progressTools = createProgressToolEntries(() => requireCurrentRunOwner("manage progress"));
682
+ if (automationGroupEnabled) {
683
+ const { createProgressToolEntries } = await import("../progress/actions.js");
684
+ progressTools = createProgressToolEntries(() => requireCurrentRunOwner("manage progress"));
685
+ }
647
686
  }
648
687
  catch { }
649
688
  let githubRepoTools = {};
@@ -661,48 +700,53 @@ export function createAgentChatPlugin(options) {
661
700
  });
662
701
  }
663
702
  catch { }
703
+ const webGroupEnabled = frameworkTools.isEnabled("web");
664
704
  let fetchTool = {};
665
705
  try {
666
- const { createFetchToolEntry } = await import("../extensions/fetch-tool.js");
667
- // Resolve `${keys.NAME}` through the same request-scope cascade
668
- // already used by extension fetches (extensions/routes.ts) and
669
- // automation connector headers (automation/index.ts): user scope
670
- // first (personal overrides win), then the active org scope (the
671
- // Dispatch vault syncs workspace secrets here), then workspace
672
- // scope. Org/workspace vault rows are write-gated (org-admin +
673
- // Dispatch vault UI), so this is safe to read by default — unlike
674
- // the opt-in-only user→workspace fallback in resolveKeyReferences
675
- // (see audit 05 H2 in secrets/substitution.ts), which stays off.
676
- // Previously this tool only looked at scope "user", so a
677
- // ${keys.NAME} reference to a key synced into the org/workspace
678
- // vault could never resolve here even though the same key already
679
- // worked for extension fetches and automations.
680
- const { resolveKeyReferencesWithRequestScopes, validateUrlAllowlist, getKeyAllowlist, getResolvedKeyAllowlist, } = await import("../secrets/substitution.js");
681
- fetchTool = createFetchToolEntry({
682
- resolveKeys: async (text) => resolveKeyReferencesWithRequestScopes(text, requireCurrentRunOwner("resolve key references")),
683
- validateUrl: async (url, usedKeys, resolvedKeys) => {
684
- for (const keyName of usedKeys) {
685
- const allowlist = await resolveFetchToolKeyAllowlist(keyName, resolvedKeys, requireCurrentRunOwner("validate URL allowlist"), { getKeyAllowlist, getResolvedKeyAllowlist });
686
- if (allowlist && !validateUrlAllowlist(url, allowlist)) {
687
- 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
+ }
688
730
  }
689
- }
690
- return true;
691
- },
692
- });
731
+ return true;
732
+ },
733
+ });
734
+ }
693
735
  }
694
736
  catch { }
695
737
  let webSearchTool = {};
696
738
  try {
697
- const { createWebSearchToolEntry } = await import("../extensions/web-search-tool.js");
698
- const { getBuilderWebSearchBaseUrl, resolveBuilderCredentials, resolveSecret, } = await import("./credential-provider.js");
699
- const { getBuilderGatewayRequestHeaders } = await import("../agent/engine/builder-gateway-headers.js");
700
- webSearchTool = createWebSearchToolEntry({
701
- resolveSecret,
702
- resolveBuilderCredentials,
703
- getBuilderWebSearchBaseUrl,
704
- getBuilderRequestHeaders: getBuilderGatewayRequestHeaders,
705
- });
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
+ }
706
750
  }
707
751
  catch { }
708
752
  let workspaceFilesTool = {};
@@ -752,11 +796,13 @@ export function createAgentChatPlugin(options) {
752
796
  let coreEmailTools = {};
753
797
  let backgroundCoreEmailTools = {};
754
798
  try {
755
- const { createCoreEmailActionEntries } = await import("./email-actions.js");
756
- coreEmailTools = createCoreEmailActionEntries();
757
- backgroundCoreEmailTools = createCoreEmailActionEntries({
758
- unattended: true,
759
- });
799
+ if (frameworkTools.isEnabled("email")) {
800
+ const { createCoreEmailActionEntries } = await import("./email-actions.js");
801
+ coreEmailTools = createCoreEmailActionEntries();
802
+ backgroundCoreEmailTools = createCoreEmailActionEntries({
803
+ unattended: true,
804
+ });
805
+ }
760
806
  }
761
807
  catch { }
762
808
  // Core read-attachment tool — always registered so the agent can page
@@ -1233,7 +1279,7 @@ export function createAgentChatPlugin(options) {
1233
1279
  const devActive = isDevMode();
1234
1280
  // Build the same system prompt the interactive agent uses
1235
1281
  const owner = userEmail;
1236
- const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId);
1282
+ const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups });
1237
1283
  const schemaBlock = lazyContext
1238
1284
  ? ""
1239
1285
  : await buildSchemaBlock(owner, databaseToolsMode);
@@ -1701,7 +1747,7 @@ export function createAgentChatPlugin(options) {
1701
1747
  // catalog on its very first request, undermining the compact MCP
1702
1748
  // catalog this surface is supposed to keep external callers on.
1703
1749
  const mcpToolSurface = createA2AEngineToolSurface(actionsToEngineTools(mcpActions), effectiveInitialToolNames);
1704
- const resources = await loadResourcesForPrompt(SHARED_OWNER, lazyContext, options?.appId);
1750
+ const resources = await loadResourcesForPrompt(SHARED_OWNER, lazyContext, options?.appId, undefined, { disabledFrameworkGroups });
1705
1751
  const schemaBlock = lazyContext
1706
1752
  ? ""
1707
1753
  : await buildSchemaBlock(SHARED_OWNER, databaseToolsMode);
@@ -2156,8 +2202,10 @@ export function createAgentChatPlugin(options) {
2156
2202
  // Job management tool (manage-jobs)
2157
2203
  let jobTools = {};
2158
2204
  try {
2159
- const { createJobTools } = await import("../jobs/tools.js");
2160
- jobTools = createJobTools();
2205
+ if (automationGroupEnabled) {
2206
+ const { createJobTools } = await import("../jobs/tools.js");
2207
+ jobTools = createJobTools();
2208
+ }
2161
2209
  }
2162
2210
  catch { }
2163
2211
  // Lean mode: only template actions + essential framework tools. Drop
@@ -2487,7 +2535,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
2487
2535
  modelOverlay +
2488
2536
  runtimeContext);
2489
2537
  }
2490
- const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId);
2538
+ const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups });
2491
2539
  // In lazy context mode, skip embedding the full schema. When database
2492
2540
  // tools are enabled the agent can call `db-schema` on demand.
2493
2541
  const schemaBlock = lazyContext
@@ -2720,7 +2768,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
2720
2768
  });
2721
2769
  return setSystemPromptOnContext(leanBasePrompt + extra + modelOverlay + runtimeContext);
2722
2770
  }
2723
- const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId);
2771
+ const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups });
2724
2772
  const schemaBlock = lazyContext || !databaseToolsEnabled
2725
2773
  ? ""
2726
2774
  : await buildSchemaBlock(owner, databaseToolsMode);
@@ -4696,7 +4744,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4696
4744
  const schedulerDeps = {
4697
4745
  getActions: getBackgroundActionEntries,
4698
4746
  getSystemPrompt: async (owner) => {
4699
- const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId);
4747
+ const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups });
4700
4748
  const schemaBlock = lazyContext
4701
4749
  ? ""
4702
4750
  : await buildSchemaBlock(owner, databaseToolsMode);
@@ -4712,8 +4760,11 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
4712
4760
  // fetchTool/webSearchTool/toolActions catalog every tick.
4713
4761
  getInitialToolNames: (job) => [
4714
4762
  ...effectiveInitialToolNames,
4715
- "manage-jobs",
4716
- "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
+ : []),
4717
4768
  ...(job?.meta.mcpTools ?? []),
4718
4769
  ],
4719
4770
  apiKey: options?.apiKey,
@@ -5073,7 +5124,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
5073
5124
  await initTriggerDispatcher({
5074
5125
  getActions: getBackgroundActionEntries,
5075
5126
  getSystemPrompt: async (owner) => {
5076
- const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId);
5127
+ const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups });
5077
5128
  const schemaBlock = lazyContext
5078
5129
  ? ""
5079
5130
  : await buildSchemaBlock(owner, databaseToolsMode);
@@ -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.
@@ -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.