Skip to content

fix(ai): move tool discovery to global assistant + enforce page agent enabledTools - #1306

Merged
2witstudios merged 3 commits into
masterfrom
pu/tool-disc-fix
May 12, 2026
Merged

2witstudios merged 3 commits into
masterfrom
pu/tool-disc-fix

Conversation

@2witstudios

@2witstudios 2witstudios commented May 12, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Moves stub schemas + `tool_search` from the page AI chat route to the global assistant route — the correct target (receives all 37 tools on every request, ~7–16k tokens/turn saved)
  • Reverts the stub/tool_search pattern from `/api/ai/chat/route.ts` (page agents are selectively configured per-agent, not appropriate for global discovery)
  • Closes a security gap: adds server-side enforcement of `page.enabledTools` in the page AI chat route. Previously, the allowlist was only used to seed client-side toggles; the server never restricted execution. Agents could reach tools outside their configured allowlist. Now, when `enabledTools` is a non-empty array, only those tools are made available to the model (still intersected with `isReadOnly`/`webSearchEnabled` filters so read-only mode always wins)
  • Scopes `TOOL_DISCOVERY_PROMPT` to the global assistant only. The shared `buildSystemPrompt` no longer includes tool-discovery instructions; the global assistant appends them explicitly after `baseSystemPrompt`. This prevents page agents from being instructed to call `tool_search`, which is not registered in their tool set

What changed

File Change
`apps/web/src/app/api/ai/global/[id]/messages/route.ts` Use `pageSpaceToolsStubbed`, inject `tool_search`, append `TOOL_DISCOVERY_PROMPT`
`apps/web/src/app/api/ai/chat/route.ts` Revert to `pageSpaceTools`; add `page.enabledTools` allowlist enforcement
`apps/web/src/lib/ai/core/system-prompt.ts` Export `TOOL_DISCOVERY_PROMPT`; remove it from shared `buildSystemPrompt` sections
`chat/tests/mcp-scope.test.ts` Remove stubs/tool_search mocks
`chat/tests/stream-socket-events.test.ts` Remove stubs/tool_search mocks
`global/[id]/messages/tests/stream-socket-events.test.ts` Add `pageSpaceToolsStubbed` + `createToolSearchTool` mocks

Infrastructure

All underlying infrastructure (`stub-tools.ts`, `tool-search-tool.ts`, `pageSpaceToolsStubbed`) landed in #1301 and is unchanged.

Test plan

  • `src/app/api/ai/chat/tests` — 20 tests pass
  • `src/app/api/ai/global/**/tests` — 91 tests pass
  • `src/lib/ai/**/tests` — 733 tests pass (includes system-prompt.test.ts)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Restructured AI tool management system for improved filtering and per-page access controls.
    • Updated tool discovery mechanism in AI chat and messaging routes.
    • Refactored system prompt composition for better tool discovery handling.

Review Change Stack

… enabledTools

- Apply stub schemas + tool_search to the global assistant route
  (the correct target — handles all 37 tools every request)
- Revert stubs + tool_search from page AI chat route (wrong surface)
- Enforce page.enabledTools server-side in the chat route: when a
  non-empty allowlist is configured, only those tools reach the model.
  This closes a security gap where agents could execute tools outside
  their configured allowlist via the stub/tool_search mechanism.
- Update test mocks accordingly (add to global, remove from chat)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 12, 2026 •

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 201b557d-34c9-436d-afb7-ec6d875b25e9

📥 Commits

Reviewing files that changed from the base of the PR and between ac44d94 and 908be1d.

📒 Files selected for processing (2)
  • apps/web/src/app/api/ai/chat/route.ts
  • apps/web/src/app/api/ai/global/[id]/messages/__tests__/stream-socket-events.test.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/web/src/app/api/ai/global/[id]/messages/tests/stream-socket-events.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/src/app/api/ai/chat/route.ts

📝 Walkthrough

Walkthrough

Global messages route adds explicit tool-discovery (TOOL_DISCOVERY_PROMPT), uses stubbed tools for read-only filtering, and injects a request-scoped tool_search. Chat route builds from full pageSpaceTools and enforces a server-side per-agent allowlist; system prompt export and test mocks updated accordingly.

Changes

AI Tool Control and Discovery

Layer / File(s) Summary
System Prompt Infrastructure
apps/web/src/lib/ai/core/system-prompt.ts
TOOL_DISCOVERY_PROMPT is exported and removed from automatic insertion in buildSystemPrompt().
Global Messages Tool Discovery Flow
apps/web/src/app/api/ai/global/[id]/messages/route.ts
Route imports pageSpaceToolsStubbed, TOOL_DISCOVERY_PROMPT, and createToolSearchTool; prepends TOOL_DISCOVERY_PROMPT to the system prompt; filters tools with stubs for read-only; reconstructs enabled full-schema subset; injects a request-scoped tool_search.
Global Messages Test Mocks
apps/web/src/app/api/ai/global/[id]/messages/__tests__/stream-socket-events.test.ts
Adds mocks for pageSpaceToolsStubbed, sets TOOL_DISCOVERY_PROMPT in the mock, and stubs createToolSearchTool to return {}.
Chat Route Per-Agent Allowlist
apps/web/src/app/api/ai/chat/route.ts
Removes pageSpaceToolsStubbed and createToolSearchTool imports; builds filteredTools from full pageSpaceTools using runtime toggles and optionally enforces page.enabledTools as a server-side allowlist; removes route-level tool_search injection.
Chat Route Test Mocks
apps/web/src/app/api/ai/chat/__tests__/mcp-scope.test.ts, apps/web/src/app/api/ai/chat/__tests__/stream-socket-events.test.ts
Mocks updated to remove pageSpaceToolsStubbed, add pageSpaceTools and provider setting stubs (getUserLMStudioSettings, getUserGLMSettings), and drop the @/lib/ai/tools/tool-search-tool mock so it's unmocked in these tests.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through prompts and tool-laden glades,

Exported a whisper that routes now parade.
One path stubs tools to show only a few,
The other keeps full lists for the guardian crew.
Cheers — a rabbit approves both branches anew!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: moving tool discovery to the global assistant and enforcing page agent enabledTools server-side.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pu/tool-disc-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f61676b59d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +511 to 514
let filteredTools: ToolSet = buildPageAITools(pageSpaceTools, {
isReadOnly: readOnlyMode,
webSearchEnabled: webSearchMode,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep tool_search available for default page prompts

This route now builds filteredTools from pageSpaceTools only, but for pages without a custom system prompt it still uses buildSystemPrompt(...) (later in this file), and that shared prompt includes guidance to call tool_search for non-core tools (apps/web/src/lib/ai/core/system-prompt.ts, TOOL_DISCOVERY_PROMPT). In that common path, the model can be instructed to invoke a tool that is no longer registered, causing invalid tool-call failures and degraded completions (often retries/loops) whenever it tries to discover calendar/agent/channel/task schemas.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ac44d94. TOOL_DISCOVERY_PROMPT was removed from buildSystemPrompt (which is shared by both routes) and is now injected explicitly only in the global assistant route, immediately after baseSystemPrompt is constructed. The page AI chat no longer includes the instruction to call tool_search, so there is no risk of the model attempting an unregistered tool call.

buildSystemPrompt is shared by both the page AI chat and global
assistant routes. Having TOOL_DISCOVERY_PROMPT in the shared prompt
instructed page agents to call tool_search, which is not registered
in the page AI chat tool set — causing invalid tool-call failures.

Move TOOL_DISCOVERY_PROMPT out of buildSystemPrompt (export it),
and inject it explicitly only in the global assistant route where
tool_search is actually available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/app/api/ai/chat/route.ts (1)

277-281: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Stale comment contradicts new behavior.

This comment states page.enabledTools is "intentionally NOT consulted here," but the new code at lines 516-526 now explicitly enforces page.enabledTools as a server-side allowlist. The comment should be updated to reflect the new security enforcement behavior.

📝 Suggested comment update
-    // Extract custom agent configuration from page.
-    // Note: page.enabledTools is intentionally NOT consulted here. It seeds the
-    // composer's tool toggles on the client; the toggles in the request body
-    // are the source of truth at runtime so the UI doesn't lie about what the
-    // model can actually do.
+    // Extract custom agent configuration from page.
+    // Note: page.enabledTools is enforced server-side as an allowlist (lines 516-526).
+    // When non-empty, only explicitly listed tools are available to the agent.
+    // Runtime toggles (isReadOnly, webSearchEnabled) are applied first, then the
+    // allowlist further restricts the available tool set.
     const customSystemPrompt = page.systemPrompt;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/app/api/ai/chat/route.ts` around lines 277 - 281, Update the
stale comment in apps/web/src/app/api/ai/chat/route.ts to reflect that
page.enabledTools is now enforced server-side as an allowlist; replace the line
stating "page.enabledTools is intentionally NOT consulted here" with a concise
note that the server now intersects/validates requested tool toggles against
page.enabledTools as an allowlist (so client toggles are still seeded from the
page but the server enforces allowed tools), and reference the relevant logic
that performs this enforcement (the code handling the request body/tool toggles
and the page.enabledTools check).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@apps/web/src/app/api/ai/chat/route.ts`:
- Around line 277-281: Update the stale comment in
apps/web/src/app/api/ai/chat/route.ts to reflect that page.enabledTools is now
enforced server-side as an allowlist; replace the line stating
"page.enabledTools is intentionally NOT consulted here" with a concise note that
the server now intersects/validates requested tool toggles against
page.enabledTools as an allowlist (so client toggles are still seeded from the
page but the server enforces allowed tools), and reference the relevant logic
that performs this enforcement (the code handling the request body/tool toggles
and the page.enabledTools check).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dd032a29-fe02-409b-822f-e7e44c06f625

📥 Commits

Reviewing files that changed from the base of the PR and between 9dd288b and ac44d94.

📒 Files selected for processing (6)
  • apps/web/src/app/api/ai/chat/__tests__/mcp-scope.test.ts
  • apps/web/src/app/api/ai/chat/__tests__/stream-socket-events.test.ts
  • apps/web/src/app/api/ai/chat/route.ts
  • apps/web/src/app/api/ai/global/[id]/messages/__tests__/stream-socket-events.test.ts
  • apps/web/src/app/api/ai/global/[id]/messages/route.ts
  • apps/web/src/lib/ai/core/system-prompt.ts
💤 Files with no reviewable changes (2)
  • apps/web/src/app/api/ai/chat/tests/stream-socket-events.test.ts
  • apps/web/src/app/api/ai/chat/tests/mcp-scope.test.ts

…date stale comment

Add missing `TOOL_DISCOVERY_PROMPT` export to the `@/lib/ai/core` mock in the
global assistant stream-socket-events test. The route now imports this constant
and concatenates it into the system prompt string; without the mock entry it
resolved as `undefined`, causing the system prompt to contain the literal string
"undefined" and triggering an early exit in the route before `createStreamLifecycle`
was called — causing all 16 lifecycle tests to fail with "called 0 times".

Also update the stale comment in the page AI chat route that previously stated
`page.enabledTools` is "intentionally NOT consulted here". The server now enforces
it as an allowlist (added in this PR), so the comment is updated to reflect the
actual enforcement order: `buildPageAITools` applies runtime toggles first, then
the `agentEnabledTools` filter restricts to the per-agent allowlist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@2witstudios

Copy link
Copy Markdown
Owner Author

Stale comment at lines 277–281 (flagged by CodeRabbit) has been updated in 908be1d to accurately describe the server-side enforcement order: buildPageAITools applies runtime toggles first, then the agentEnabledTools filter restricts to the per-agent allowlist.

@2witstudios
2witstudios merged commit fdfe99b into master May 12, 2026
10 checks passed
2witstudios added a commit that referenced this pull request May 15, 2026
… enabledTools (#1306)

* fix(ai): move tool discovery to global assistant + enforce page agent enabledTools

- Apply stub schemas + tool_search to the global assistant route
  (the correct target — handles all 37 tools every request)
- Revert stubs + tool_search from page AI chat route (wrong surface)
- Enforce page.enabledTools server-side in the chat route: when a
  non-empty allowlist is configured, only those tools reach the model.
  This closes a security gap where agents could execute tools outside
  their configured allowlist via the stub/tool_search mechanism.
- Update test mocks accordingly (add to global, remove from chat)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ai): scope TOOL_DISCOVERY_PROMPT to global assistant only

buildSystemPrompt is shared by both the page AI chat and global
assistant routes. Having TOOL_DISCOVERY_PROMPT in the shared prompt
instructed page agents to call tool_search, which is not registered
in the page AI chat tool set — causing invalid tool-call failures.

Move TOOL_DISCOVERY_PROMPT out of buildSystemPrompt (export it),
and inject it explicitly only in the global assistant route where
tool_search is actually available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ai): add TOOL_DISCOVERY_PROMPT to global assistant test mock + update stale comment

Add missing `TOOL_DISCOVERY_PROMPT` export to the `@/lib/ai/core` mock in the
global assistant stream-socket-events test. The route now imports this constant
and concatenates it into the system prompt string; without the mock entry it
resolved as `undefined`, causing the system prompt to contain the literal string
"undefined" and triggering an early exit in the route before `createStreamLifecycle`
was called — causing all 16 lifecycle tests to fail with "called 0 times".

Also update the stale comment in the page AI chat route that previously stated
`page.enabledTools` is "intentionally NOT consulted here". The server now enforces
it as an allowlist (added in this PR), so the comment is updated to reflect the
actual enforcement order: `buildPageAITools` applies runtime toggles first, then
the `agentEnabledTools` filter restricts to the per-agent allowlist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@2witstudios
2witstudios deleted the pu/tool-disc-fix branch May 27, 2026 02:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant