feat(ai): add browsing context and fix tool loading (#16476)

## Summary

- Add `BrowsingContext` type to automatically pass what the user is
currently viewing (recordPage or listView) to the AI chat
- Simplify context architecture: remove toggleable context UI, make it
automatic and invisible to the user
- Fix tool loading: add `unionOf` handling in
`getDatabaseToolsForObject` and fix regex ordering so `find_one_*` tools
are properly registered
- Use plural names for find tools (`find_people` vs `find_one_person`)
for better semantics
- Clean up unused components and states

## Changes

### Frontend
- New `BrowsingContext` type and `useGetBrowsingContext` hook to gather
context from Recoil state
- Simplified `useAgentChat` to use the new browsing context
- Removed toggleable context UI components
(`AgentChatContextRecordPreview`, `SendMessageWithRecordsContextButton`,
etc.)
- Removed `isAgentChatCurrentContextActiveState`

### Backend
- New `BrowsingContextType` for recordPage and listView contexts
- Updated `ChatExecutionService` to build context from browsing context
- Fixed `tool-registry.service.ts`:
  - Added `unionOf` handling in permission config
- Fixed regex ordering (`find_one` before `find`) so tools load
correctly
- Use plural names for search tools (`find_people` instead of
`find_person`)

## Test plan

- [x] Typecheck passes
- [x] Lint passes
- [ ] Test AI chat on record page - should show context in system prompt
- [ ] Test AI chat on list view - should show view name and filters
- [ ] Test `find_one_*` tools now load correctly
- [ ] Test `find_*` tools use plural naming
This commit is contained in:
Félix Malfait
2025-12-11 10:19:27 +01:00
committed by GitHub
parent 5daef28328
commit bc57b8ee4e
35 changed files with 696 additions and 834 deletions
@@ -1,5 +1,5 @@
export enum ToolType {
HTTP_REQUEST = 'HTTP_REQUEST',
SEND_EMAIL = 'SEND_EMAIL',
SEARCH_ARTICLES = 'SEARCH_ARTICLES',
SEARCH_HELP_CENTER = 'SEARCH_HELP_CENTER',
}
@@ -4,10 +4,10 @@ import { PermissionFlagType } from 'twenty-shared/constants';
import { ToolType } from 'src/engine/core-modules/tool/enums/tool-type.enum';
import { HttpTool } from 'src/engine/core-modules/tool/tools/http-tool/http-tool';
import { SendEmailTool } from 'src/engine/core-modules/tool/tools/send-email-tool/send-email-tool';
import { type SendEmailTool } from 'src/engine/core-modules/tool/tools/send-email-tool/send-email-tool';
import { type SendEmailInput } from 'src/engine/core-modules/tool/tools/send-email-tool/types/send-email-input.type';
import { type Tool } from 'src/engine/core-modules/tool/types/tool.type';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { type TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
@Injectable()
export class ToolRegistryService {
@@ -4,7 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
import { FileModule } from 'src/engine/core-modules/file/file.module';
import { HttpTool } from 'src/engine/core-modules/tool/tools/http-tool/http-tool';
import { SearchArticlesTool } from 'src/engine/core-modules/tool/tools/search-articles-tool/search-articles-tool';
import { SearchHelpCenterTool } from 'src/engine/core-modules/tool/tools/search-help-center-tool/search-help-center-tool';
import { SendEmailTool } from 'src/engine/core-modules/tool/tools/send-email-tool/send-email-tool';
import { MessagingImportManagerModule } from 'src/modules/messaging/message-import-manager/messaging-import-manager.module';
@@ -14,7 +14,7 @@ import { MessagingImportManagerModule } from 'src/modules/messaging/message-impo
TypeOrmModule.forFeature([FileEntity]),
FileModule,
],
providers: [HttpTool, SendEmailTool, SearchArticlesTool],
exports: [HttpTool, SendEmailTool, SearchArticlesTool],
providers: [HttpTool, SendEmailTool, SearchHelpCenterTool],
exports: [HttpTool, SendEmailTool, SearchHelpCenterTool],
})
export class ToolModule {}
@@ -1,18 +1,20 @@
import { z } from 'zod';
export const SearchArticlesInputZodSchema = z.object({
export const SearchHelpCenterInputZodSchema = z.object({
query: z
.string()
.describe('The search query to find relevant help articles about Twenty'),
});
export const SearchArticlesToolParametersZodSchema = z.object({
export const SearchHelpCenterToolParametersZodSchema = z.object({
loadingMessage: z
.string()
.describe(
'A clear, human-readable status message describing the search being performed. This will be shown to the user while the tool is being called, so phrase it as a present-tense status update (e.g., "Searching help articles for..."). Explain what you are searching for in natural language.',
'A clear, human-readable status message describing the search being performed. This will be shown to the user while the tool is being called, so phrase it as a present-tense status update (e.g., "Searching help center for..."). Explain what you are searching for in natural language.',
),
input: SearchArticlesInputZodSchema,
input: SearchHelpCenterInputZodSchema,
});
export type SearchArticlesInput = z.infer<typeof SearchArticlesInputZodSchema>;
export type SearchHelpCenterInput = z.infer<
typeof SearchHelpCenterInputZodSchema
>;
@@ -2,17 +2,17 @@ import { Injectable } from '@nestjs/common';
import axios from 'axios';
import { SearchArticlesToolParametersZodSchema } from 'src/engine/core-modules/tool/tools/search-articles-tool/search-articles-tool.schema';
import { SearchHelpCenterToolParametersZodSchema } from 'src/engine/core-modules/tool/tools/search-help-center-tool/search-help-center-tool.schema';
import { type ToolInput } from 'src/engine/core-modules/tool/types/tool-input.type';
import { type ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type';
import { type Tool } from 'src/engine/core-modules/tool/types/tool.type';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
@Injectable()
export class SearchArticlesTool implements Tool {
export class SearchHelpCenterTool implements Tool {
description =
'Search Twenty documentation and help articles to find information about features, setup, usage, and troubleshooting.';
inputSchema = SearchArticlesToolParametersZodSchema;
'Search Twenty documentation and help center to find information about features, setup, usage, and troubleshooting.';
inputSchema = SearchHelpCenterToolParametersZodSchema;
constructor(private readonly twentyConfigService: TwentyConfigService) {}
@@ -46,14 +46,14 @@ export class SearchArticlesTool implements Tool {
if (results.length === 0) {
return {
success: true,
message: `No help articles found for "${query}"`,
message: `No help center articles found for "${query}"`,
result: [],
};
}
return {
success: true,
message: `Found ${results.length} relevant help article${results.length === 1 ? '' : 's'} for "${query}"`,
message: `Found ${results.length} relevant help center article${results.length === 1 ? '' : 's'} for "${query}"`,
result: results,
};
} catch (error) {
@@ -61,11 +61,11 @@ export class SearchArticlesTool implements Tool {
? error.response?.data?.message || error.message
: error instanceof Error
? error.message
: 'Documentation search failed';
: 'Help center search failed';
return {
success: false,
message: `Failed to search help articles for "${query}"`,
message: `Failed to search help center for "${query}"`,
error: errorDetail,
};
}
@@ -5,7 +5,7 @@ import { render, toPlainText } from '@react-email/render';
import DOMPurify from 'dompurify';
import { reactMarkupFromJSON } from 'twenty-emails';
import { isDefined, isValidUuid } from 'twenty-shared/utils';
import { In, Repository } from 'typeorm';
import { In, type Repository } from 'typeorm';
import { z } from 'zod';
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';