diff --git a/packages/twenty-front/src/modules/ai/components/LazyMarkdownRenderer.tsx b/packages/twenty-front/src/modules/ai/components/LazyMarkdownRenderer.tsx
index c4b87dd8e9..9c9036b0d5 100644
--- a/packages/twenty-front/src/modules/ai/components/LazyMarkdownRenderer.tsx
+++ b/packages/twenty-front/src/modules/ai/components/LazyMarkdownRenderer.tsx
@@ -7,6 +7,7 @@ import {
} from '@/ai/components/LazyMarkdownRendererStyledComponents';
import { MarkdownCodeBlock } from '@/ai/components/MarkdownCodeBlock';
import { TextWithRecordLinks } from '@/ai/components/TextWithRecordLinks';
+import { protectRecordReferencesForMarkdown } from '@/ai/utils/protectRecordReferencesForMarkdown';
import { marked } from 'marked';
import {
cloneElement,
@@ -179,11 +180,16 @@ const MemoizedMarkdownBlock = memo(
);
export const LazyMarkdownRenderer = ({ text }: { text: string }) => {
- const markdownBlocks = useMemo(
- () => marked.lexer(text).map((token) => token.raw),
+ const protectedText = useMemo(
+ () => protectRecordReferencesForMarkdown(text),
[text],
);
+ const markdownBlocks = useMemo(
+ () => marked.lexer(protectedText).map((token) => token.raw),
+ [protectedText],
+ );
+
return (
);
};
-
-export const RECORD_REFERENCE_REGEX =
- /\[\[(?:record:)?([a-zA-Z]+):([a-f0-9-]+):([^\]]+)\]\]/g;
-
-export const parseRecordReference = (match: string) => {
- const regex = /\[\[(?:record:)?([a-zA-Z]+):([a-f0-9-]+):([^\]]+)\]\]/;
- const result = regex.exec(match);
-
- if (!result) {
- return null;
- }
-
- return {
- objectNameSingular: result[1],
- recordId: result[2],
- displayName: result[3],
- };
-};
diff --git a/packages/twenty-front/src/modules/ai/components/TextWithRecordLinks.tsx b/packages/twenty-front/src/modules/ai/components/TextWithRecordLinks.tsx
index 9deb84ee00..bb16558fea 100644
--- a/packages/twenty-front/src/modules/ai/components/TextWithRecordLinks.tsx
+++ b/packages/twenty-front/src/modules/ai/components/TextWithRecordLinks.tsx
@@ -1,42 +1,36 @@
-import {
- parseRecordReference,
- RECORD_REFERENCE_REGEX,
- RecordLink,
-} from '@/ai/components/RecordLink';
+import { RecordLink } from '@/ai/components/RecordLink';
+import { findRecordReferences } from '@/ai/utils/findRecordReferences';
import { type ReactNode } from 'react';
-import { isDefined } from 'twenty-shared/utils';
type TextWithRecordLinksProps = {
text: string;
};
export const TextWithRecordLinks = ({ text }: TextWithRecordLinksProps) => {
+ const references = findRecordReferences(text);
+
+ if (references.length === 0) {
+ return <>{text}>;
+ }
+
const parts: ReactNode[] = [];
let lastIndex = 0;
- RECORD_REFERENCE_REGEX.lastIndex = 0;
-
- let match;
-
- while ((match = RECORD_REFERENCE_REGEX.exec(text)) !== null) {
- if (match.index > lastIndex) {
- parts.push(text.slice(lastIndex, match.index));
+ for (const reference of references) {
+ if (reference.index > lastIndex) {
+ parts.push(text.slice(lastIndex, reference.index));
}
- const parsed = parseRecordReference(match[0]);
+ parts.push(
+ ,
+ );
- if (isDefined(parsed)) {
- parts.push(
- ,
- );
- }
-
- lastIndex = match.index + match[0].length;
+ lastIndex = reference.index + reference.fullMatch.length;
}
if (lastIndex < text.length) {
diff --git a/packages/twenty-front/src/modules/ai/components/__tests__/TextWithRecordLinks.test.tsx b/packages/twenty-front/src/modules/ai/components/__tests__/TextWithRecordLinks.test.tsx
index 3789fbbe72..ca6a9a837c 100644
--- a/packages/twenty-front/src/modules/ai/components/__tests__/TextWithRecordLinks.test.tsx
+++ b/packages/twenty-front/src/modules/ai/components/__tests__/TextWithRecordLinks.test.tsx
@@ -3,22 +3,6 @@ import { render, screen } from '@testing-library/react';
import { TextWithRecordLinks } from '@/ai/components/TextWithRecordLinks';
jest.mock('@/ai/components/RecordLink', () => ({
- RECORD_REFERENCE_REGEX:
- /\[\[(?:record:)?([a-zA-Z]+):([a-f0-9-]+):([^\]]+)\]\]/g,
- parseRecordReference: (match: string) => {
- const regex = /\[\[(?:record:)?([a-zA-Z]+):([a-f0-9-]+):([^\]]+)\]\]/;
- const result = regex.exec(match);
-
- if (!result) {
- return null;
- }
-
- return {
- objectNameSingular: result[1],
- recordId: result[2],
- displayName: result[3],
- };
- },
RecordLink: ({
displayName,
objectNameSingular,
@@ -44,9 +28,9 @@ describe('TextWithRecordLinks', () => {
expect(screen.queryByTestId('record-link')).not.toBeInTheDocument();
});
- it('should replace record references with RecordLink chips', () => {
+ it('should replace tagged record references with RecordLink chips', () => {
render(
- ,
+ ,
);
expect(screen.getByTestId('record-link')).toHaveTextContent('Acme');
@@ -54,9 +38,18 @@ describe('TextWithRecordLinks', () => {
expect(screen.queryByText(/\[\[record:company:/)).not.toBeInTheDocument();
});
+ it('should still replace legacy ]] record references with RecordLink chips', () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByTestId('record-link')).toHaveTextContent('Acme');
+ expect(screen.getByText(/Contact/)).toHaveTextContent('Contact Acme next');
+ });
+
it('should replace multiple record references in option-style labels', () => {
render(
- ,
+ ,
);
const recordLinks = screen.getAllByTestId('record-link');
@@ -66,4 +59,18 @@ describe('TextWithRecordLinks', () => {
expect(recordLinks[1]).toHaveTextContent('Bob');
expect(screen.queryByText(/\[\[/)).not.toBeInTheDocument();
});
+
+ it('should chip tagged labels that contain backticks, brackets, colons, and ]]', () => {
+ render(
+ ,
+ );
+
+ const recordLinks = screen.getAllByTestId('record-link');
+
+ expect(recordLinks).toHaveLength(3);
+ expect(recordLinks[0]).toHaveTextContent('Workflow `UPDATE_RECORD` step');
+ expect(recordLinks[1]).toHaveTextContent('[test] ]] [test] [test] ###');
+ expect(recordLinks[2]).toHaveTextContent('Doe: Jane');
+ expect(screen.queryByText(/\[\[record:/)).not.toBeInTheDocument();
+ });
});
diff --git a/packages/twenty-front/src/modules/ai/constants/RecordReferenceCloseTag.ts b/packages/twenty-front/src/modules/ai/constants/RecordReferenceCloseTag.ts
new file mode 100644
index 0000000000..80c87936f4
--- /dev/null
+++ b/packages/twenty-front/src/modules/ai/constants/RecordReferenceCloseTag.ts
@@ -0,0 +1 @@
+export const RECORD_REFERENCE_CLOSE_TAG = '[[/record]]';
diff --git a/packages/twenty-front/src/modules/ai/types/RecordReferenceMatch.ts b/packages/twenty-front/src/modules/ai/types/RecordReferenceMatch.ts
new file mode 100644
index 0000000000..89ad453af3
--- /dev/null
+++ b/packages/twenty-front/src/modules/ai/types/RecordReferenceMatch.ts
@@ -0,0 +1,7 @@
+export type RecordReferenceMatch = {
+ fullMatch: string;
+ index: number;
+ objectNameSingular: string;
+ recordId: string;
+ displayName: string;
+};
diff --git a/packages/twenty-front/src/modules/ai/utils/__tests__/findRecordReferences.test.ts b/packages/twenty-front/src/modules/ai/utils/__tests__/findRecordReferences.test.ts
new file mode 100644
index 0000000000..f524aa9c2a
--- /dev/null
+++ b/packages/twenty-front/src/modules/ai/utils/__tests__/findRecordReferences.test.ts
@@ -0,0 +1,102 @@
+import { findRecordReferences } from '@/ai/utils/findRecordReferences';
+
+describe('findRecordReferences', () => {
+ it('should leave plain text without matches', () => {
+ expect(findRecordReferences('Which company should we contact?')).toEqual(
+ [],
+ );
+ });
+
+ it('should find a tagged record reference', () => {
+ expect(
+ findRecordReferences(
+ 'Contact [[record:company:a1b2c3d4-e5f6-7890-abcd-ef1234567890:Acme[[/record]] next',
+ ),
+ ).toEqual([
+ {
+ fullMatch:
+ '[[record:company:a1b2c3d4-e5f6-7890-abcd-ef1234567890:Acme[[/record]]',
+ index: 8,
+ objectNameSingular: 'company',
+ recordId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
+ displayName: 'Acme',
+ },
+ ]);
+ });
+
+ it('should include ]] inside a tagged display name', () => {
+ expect(
+ findRecordReferences(
+ 'The company is [[record:company:a1b2c3d4-e5f6-7890-abcd-ef1234567890:[test] ]] [test] [test] ###[[/record]], created on July 21',
+ ),
+ ).toEqual([
+ {
+ fullMatch:
+ '[[record:company:a1b2c3d4-e5f6-7890-abcd-ef1234567890:[test] ]] [test] [test] ###[[/record]]',
+ index: 15,
+ objectNameSingular: 'company',
+ recordId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
+ displayName: '[test] ]] [test] [test] ###',
+ },
+ ]);
+ });
+
+ it('should still support legacy ]] terminators', () => {
+ expect(
+ findRecordReferences(
+ 'Contact [[record:company:a1b2c3d4-e5f6-7890-abcd-ef1234567890:Acme]] next',
+ ),
+ ).toEqual([
+ {
+ fullMatch:
+ '[[record:company:a1b2c3d4-e5f6-7890-abcd-ef1234567890:Acme]]',
+ index: 8,
+ objectNameSingular: 'company',
+ recordId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
+ displayName: 'Acme',
+ },
+ ]);
+ });
+
+ it('should still support legacy ]] inside display names', () => {
+ expect(
+ findRecordReferences(
+ 'The company is [[record:company:a1b2c3d4-e5f6-7890-abcd-ef1234567890:[test] ]] [test] [test] ###]], created on July 21',
+ ),
+ ).toEqual([
+ {
+ fullMatch:
+ '[[record:company:a1b2c3d4-e5f6-7890-abcd-ef1234567890:[test] ]] [test] [test] ###]]',
+ index: 15,
+ objectNameSingular: 'company',
+ recordId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
+ displayName: '[test] ]] [test] [test] ###',
+ },
+ ]);
+ });
+
+ it('should find multiple tagged references without consuming into the next one', () => {
+ expect(
+ findRecordReferences(
+ 'Merge [[person:11111111-1111-1111-1111-111111111111:Alice[[/record]] into [[record:person:22222222-2222-2222-2222-222222222222:Bob[[/record]]',
+ ),
+ ).toEqual([
+ {
+ fullMatch:
+ '[[person:11111111-1111-1111-1111-111111111111:Alice[[/record]]',
+ index: 6,
+ objectNameSingular: 'person',
+ recordId: '11111111-1111-1111-1111-111111111111',
+ displayName: 'Alice',
+ },
+ {
+ fullMatch:
+ '[[record:person:22222222-2222-2222-2222-222222222222:Bob[[/record]]',
+ index: 74,
+ objectNameSingular: 'person',
+ recordId: '22222222-2222-2222-2222-222222222222',
+ displayName: 'Bob',
+ },
+ ]);
+ });
+});
diff --git a/packages/twenty-front/src/modules/ai/utils/__tests__/formatRecordReference.test.ts b/packages/twenty-front/src/modules/ai/utils/__tests__/formatRecordReference.test.ts
new file mode 100644
index 0000000000..1caae8b38d
--- /dev/null
+++ b/packages/twenty-front/src/modules/ai/utils/__tests__/formatRecordReference.test.ts
@@ -0,0 +1,15 @@
+import { formatRecordReference } from '@/ai/utils/formatRecordReference';
+
+describe('formatRecordReference', () => {
+ it('should use the [[/record]] close tag', () => {
+ expect(
+ formatRecordReference({
+ objectNameSingular: 'company',
+ recordId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
+ displayName: '[test] ]] [test]',
+ }),
+ ).toBe(
+ '[[record:company:a1b2c3d4-e5f6-7890-abcd-ef1234567890:[test] ]] [test][[/record]]',
+ );
+ });
+});
diff --git a/packages/twenty-front/src/modules/ai/utils/__tests__/protectRecordReferencesForMarkdown.test.ts b/packages/twenty-front/src/modules/ai/utils/__tests__/protectRecordReferencesForMarkdown.test.ts
new file mode 100644
index 0000000000..f08435f3d1
--- /dev/null
+++ b/packages/twenty-front/src/modules/ai/utils/__tests__/protectRecordReferencesForMarkdown.test.ts
@@ -0,0 +1,49 @@
+import { protectRecordReferencesForMarkdown } from '@/ai/utils/protectRecordReferencesForMarkdown';
+
+describe('protectRecordReferencesForMarkdown', () => {
+ it('should leave plain text unchanged', () => {
+ expect(
+ protectRecordReferencesForMarkdown('Which company should we contact?'),
+ ).toBe('Which company should we contact?');
+ });
+
+ it('should rewrite legacy refs to the tagged format', () => {
+ expect(
+ protectRecordReferencesForMarkdown(
+ 'Contact [[record:company:a1b2c3d4-e5f6-7890-abcd-ef1234567890:Acme]] next',
+ ),
+ ).toBe(
+ 'Contact [[record:company:a1b2c3d4-e5f6-7890-abcd-ef1234567890:Acme[[/record]] next',
+ );
+ });
+
+ it('should escape backticks in labels and close with the tag', () => {
+ expect(
+ protectRecordReferencesForMarkdown(
+ 'See [[record:workflow:a1b2c3d4-e5f6-7890-abcd-ef1234567890:Workflow `UPDATE_RECORD` step[[/record]]',
+ ),
+ ).toBe(
+ 'See [[record:workflow:a1b2c3d4-e5f6-7890-abcd-ef1234567890:Workflow \\`UPDATE\\_RECORD\\` step[[/record]]',
+ );
+ });
+
+ it('should escape square brackets and ]] inside labels', () => {
+ expect(
+ protectRecordReferencesForMarkdown(
+ 'Open [[record:company:a1b2c3d4-e5f6-7890-abcd-ef1234567890:[test] ]] [test] [test] ###[[/record]]',
+ ),
+ ).toBe(
+ 'Open [[record:company:a1b2c3d4-e5f6-7890-abcd-ef1234567890:\\[test\\] \\]\\] \\[test\\] \\[test\\] \\#\\#\\#[[/record]]',
+ );
+ });
+
+ it('should leave colons in labels unchanged', () => {
+ expect(
+ protectRecordReferencesForMarkdown(
+ 'Ping [[record:person:a1b2c3d4-e5f6-7890-abcd-ef1234567890:Doe: Jane[[/record]]',
+ ),
+ ).toBe(
+ 'Ping [[record:person:a1b2c3d4-e5f6-7890-abcd-ef1234567890:Doe: Jane[[/record]]',
+ );
+ });
+});
diff --git a/packages/twenty-front/src/modules/ai/utils/findRecordReferences.ts b/packages/twenty-front/src/modules/ai/utils/findRecordReferences.ts
new file mode 100644
index 0000000000..7465938e38
--- /dev/null
+++ b/packages/twenty-front/src/modules/ai/utils/findRecordReferences.ts
@@ -0,0 +1,62 @@
+import { RECORD_REFERENCE_CLOSE_TAG } from '@/ai/constants/RecordReferenceCloseTag';
+import { type RecordReferenceMatch } from '@/ai/types/RecordReferenceMatch';
+
+const RECORD_REFERENCE_START_REGEX =
+ /\[\[(?:record:)?([a-zA-Z]+):([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}):/g;
+
+export const findRecordReferences = (text: string): RecordReferenceMatch[] => {
+ const starts: Array<{
+ index: number;
+ prefixLength: number;
+ objectNameSingular: string;
+ recordId: string;
+ }> = [];
+
+ RECORD_REFERENCE_START_REGEX.lastIndex = 0;
+
+ let startMatch;
+
+ while ((startMatch = RECORD_REFERENCE_START_REGEX.exec(text)) !== null) {
+ starts.push({
+ index: startMatch.index,
+ prefixLength: startMatch[0].length,
+ objectNameSingular: startMatch[1],
+ recordId: startMatch[2],
+ });
+ }
+
+ return starts.flatMap((start, startIndex) => {
+ const displayNameStart = start.index + start.prefixLength;
+ const windowEnd =
+ startIndex + 1 < starts.length
+ ? starts[startIndex + 1].index
+ : text.length;
+ const window = text.slice(displayNameStart, windowEnd);
+
+ const closeTagIndexInWindow = window.indexOf(RECORD_REFERENCE_CLOSE_TAG);
+
+ const closingIndexInWindow =
+ closeTagIndexInWindow !== -1
+ ? closeTagIndexInWindow
+ : window.lastIndexOf(']]');
+
+ if (closingIndexInWindow === -1) {
+ return [];
+ }
+
+ const closeLength =
+ closeTagIndexInWindow !== -1 ? RECORD_REFERENCE_CLOSE_TAG.length : 2;
+ const displayName = window.slice(0, closingIndexInWindow);
+ const fullMatchEnd = displayNameStart + closingIndexInWindow + closeLength;
+
+ return [
+ {
+ fullMatch: text.slice(start.index, fullMatchEnd),
+ index: start.index,
+ objectNameSingular: start.objectNameSingular,
+ recordId: start.recordId,
+ displayName,
+ },
+ ];
+ });
+};
diff --git a/packages/twenty-front/src/modules/ai/utils/formatRecordReference.ts b/packages/twenty-front/src/modules/ai/utils/formatRecordReference.ts
new file mode 100644
index 0000000000..8ddcb49850
--- /dev/null
+++ b/packages/twenty-front/src/modules/ai/utils/formatRecordReference.ts
@@ -0,0 +1,12 @@
+import { RECORD_REFERENCE_CLOSE_TAG } from '@/ai/constants/RecordReferenceCloseTag';
+
+export const formatRecordReference = ({
+ objectNameSingular,
+ recordId,
+ displayName,
+}: {
+ objectNameSingular: string;
+ recordId: string;
+ displayName: string;
+}): string =>
+ `[[record:${objectNameSingular}:${recordId}:${displayName}${RECORD_REFERENCE_CLOSE_TAG}`;
diff --git a/packages/twenty-front/src/modules/ai/utils/protectRecordReferencesForMarkdown.ts b/packages/twenty-front/src/modules/ai/utils/protectRecordReferencesForMarkdown.ts
new file mode 100644
index 0000000000..d9cbcabfca
--- /dev/null
+++ b/packages/twenty-front/src/modules/ai/utils/protectRecordReferencesForMarkdown.ts
@@ -0,0 +1,26 @@
+import { findRecordReferences } from '@/ai/utils/findRecordReferences';
+import { formatRecordReference } from '@/ai/utils/formatRecordReference';
+
+const escapeMarkdown = (text: string): string =>
+ text.replace(/([\\`*_{}[\]()#+\-.!|~>])/g, '\\$1');
+
+export const protectRecordReferencesForMarkdown = (text: string): string => {
+ const references = findRecordReferences(text);
+ let result = text;
+
+ for (let index = references.length - 1; index >= 0; index--) {
+ const reference = references[index];
+ const replacement = formatRecordReference({
+ objectNameSingular: reference.objectNameSingular,
+ recordId: reference.recordId,
+ displayName: escapeMarkdown(reference.displayName),
+ });
+
+ result =
+ result.slice(0, reference.index) +
+ replacement +
+ result.slice(reference.index + reference.fullMatch.length);
+ }
+
+ return result;
+};
diff --git a/packages/twenty-front/src/modules/mention/extensions/MentionTag.ts b/packages/twenty-front/src/modules/mention/extensions/MentionTag.ts
index 972dba3f7d..640a9ec169 100644
--- a/packages/twenty-front/src/modules/mention/extensions/MentionTag.ts
+++ b/packages/twenty-front/src/modules/mention/extensions/MentionTag.ts
@@ -1,3 +1,4 @@
+import { formatRecordReference } from '@/ai/utils/formatRecordReference';
import { MentionChip } from '@/mention/components/MentionChip';
import { Node } from '@tiptap/core';
import { mergeAttributes, ReactNodeViewRenderer } from '@tiptap/react';
@@ -57,6 +58,10 @@ export const MentionTag = Node.create({
renderText: ({ node }) => {
const { objectNameSingular, recordId, label } = node.attrs;
- return `[[record:${objectNameSingular}:${recordId}:${label}]]`;
+ return formatRecordReference({
+ objectNameSingular,
+ recordId,
+ displayName: label,
+ });
},
});
diff --git a/packages/twenty-front/src/modules/mention/extensions/__tests__/MentionSuggestion.test.ts b/packages/twenty-front/src/modules/mention/extensions/__tests__/MentionSuggestion.test.ts
index 25afe3fc8b..af973af4aa 100644
--- a/packages/twenty-front/src/modules/mention/extensions/__tests__/MentionSuggestion.test.ts
+++ b/packages/twenty-front/src/modules/mention/extensions/__tests__/MentionSuggestion.test.ts
@@ -82,7 +82,7 @@ describe('MentionSuggestion', () => {
const text = editor.getText();
- expect(text).toContain('[[record:company:test-id:Acme]]');
+ expect(text).toContain('[[record:company:test-id:Acme[[/record]]');
});
it('should accept @ character in editor content', () => {
diff --git a/packages/twenty-front/src/modules/mention/extensions/__tests__/MentionTag.test.ts b/packages/twenty-front/src/modules/mention/extensions/__tests__/MentionTag.test.ts
index 6ab95dffbb..5214eb1cce 100644
--- a/packages/twenty-front/src/modules/mention/extensions/__tests__/MentionTag.test.ts
+++ b/packages/twenty-front/src/modules/mention/extensions/__tests__/MentionTag.test.ts
@@ -67,7 +67,9 @@ describe('MentionTag', () => {
const text = editor.getText();
- expect(text).toBe('Hello [[record:company:abc-123:Acme Corp]] world');
+ expect(text).toBe(
+ 'Hello [[record:company:abc-123:Acme Corp[[/record]] world',
+ );
});
it('should handle mentions with empty label', () => {
@@ -93,7 +95,7 @@ describe('MentionTag', () => {
const text = editor.getText();
- expect(text).toBe('[[record:person:id-456:]]');
+ expect(text).toBe('[[record:person:id-456:[[/record]]');
});
it('should serialize multiple mentions in the same paragraph', () => {
@@ -130,7 +132,7 @@ describe('MentionTag', () => {
const text = editor.getText();
expect(text).toBe(
- '[[record:person:r1:Alice]] and [[record:company:r2:Beta Inc]]',
+ '[[record:person:r1:Alice[[/record]] and [[record:company:r2:Beta Inc[[/record]]',
);
});
});
@@ -156,7 +158,9 @@ describe('MentionTag', () => {
const text = editor.getText();
- expect(text).toContain('[[record:opportunity:test-id:Big Deal]]');
+ expect(text).toContain(
+ '[[record:opportunity:test-id:Big Deal[[/record]]',
+ );
});
});
diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/constants/chat-system-prompts.const.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/constants/chat-system-prompts.const.ts
index 97cf026104..7e3d37994c 100644
--- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/constants/chat-system-prompts.const.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/constants/chat-system-prompts.const.ts
@@ -79,7 +79,8 @@ Format responses with markdown for clarity (headings, lists, code blocks, tables
Record References - IMPORTANT:
- Tool responses include a "recordReferences" array with clickable links
- ONLY use record references that are returned by tools - NEVER make up IDs
-- Copy the exact format from the tool response: [[record:objectName:recordId:displayName]]
+- Copy the exact format from the tool response: [[record:objectName:recordId:displayName[[/record]]
+- Example: [[record:company:abc12345-1234-5678-abcd-123456789012:Acme Corp[[/record]]
- Use record references only in paragraphs, lists, or markdown tables (\`| ... |\`); never in headings, code, links, or raw HTML
- The recordId MUST be a real UUID (like "abc12345-1234-5678-abcd-123456789012")
- DO NOT create record references before calling the tool