fix: exclude system objects and workflow/dashboard from AI/MCP write tool descriptors (#20973)
## Summary fix: exclude system join objects from AI/MCP create/update/delete tool descriptors Closes #20403 --- AI was used for assistance. --------- Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Co-authored-by: Félix Malfait <felix@twenty.com> Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
// Objects whose records must not be created, updated, or deleted by
|
||||
// automation callers (workflows and AI tools). Either they back the
|
||||
// automation runtime itself (recursion risk), gate access/permissions,
|
||||
// or are owned by background sync (writing to them corrupts state).
|
||||
export const OBJECTS_BLOCKED_FROM_AUTOMATION = [
|
||||
'workflow',
|
||||
'workflowVersion',
|
||||
'workflowRun',
|
||||
'workflowAutomatedTrigger',
|
||||
'workspaceMember',
|
||||
'dashboard',
|
||||
'message',
|
||||
'messageThread',
|
||||
'messageChannelMessageAssociation',
|
||||
'messageParticipant',
|
||||
'calendarEvent',
|
||||
'calendarEventParticipant',
|
||||
'calendarChannelEventAssociation',
|
||||
] as const;
|
||||
@@ -10,6 +10,7 @@
|
||||
export { CAPTURE_ALL_VARIABLE_TAG_INNER_REGEX } from './constants/CaptureAllVariableTagInnerRegex';
|
||||
export { CONTENT_TYPE_VALUES_HTTP_REQUEST } from './constants/ContentTypeValuesHttpRequest';
|
||||
export { IF_ELSE_BRANCH_POSITION_OFFSETS } from './constants/IfElseBranchPositionOffsets';
|
||||
export { OBJECTS_BLOCKED_FROM_AUTOMATION } from './constants/ObjectsBlockedFromAutomation';
|
||||
export { TRIGGER_STEP_ID } from './constants/TriggerStepId';
|
||||
export { workflowAiAgentActionSchema } from './schemas/ai-agent-action-schema';
|
||||
export { workflowAiAgentActionSettingsSchema } from './schemas/ai-agent-action-settings-schema';
|
||||
@@ -81,7 +82,7 @@ export type {
|
||||
WorkflowRunStepInfos,
|
||||
} from './types/WorkflowRunStateStepInfos';
|
||||
export { StepStatus } from './types/WorkflowRunStateStepInfos';
|
||||
export { canObjectBeManagedByWorkflow } from './utils/canObjectBeManagedByWorkflow';
|
||||
export { canObjectBeManagedByAutomation } from './utils/canObjectBeManagedByAutomation';
|
||||
export { extractRawVariableNamePart } from './utils/extractRawVariableNameParts';
|
||||
export { getFunctionInputFromInputSchema } from './utils/getFunctionInputFromInputSchema';
|
||||
export { getWorkflowRunContext } from './utils/getWorkflowRunContext';
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import { canObjectBeManagedByAutomation } from '@/workflow/utils/canObjectBeManagedByAutomation';
|
||||
|
||||
describe('canObjectBeManagedByAutomation', () => {
|
||||
it('should return true for a standard non-blocked object', () => {
|
||||
expect(canObjectBeManagedByAutomation({ nameSingular: 'company' })).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return true for noteTarget and taskTarget', () => {
|
||||
expect(canObjectBeManagedByAutomation({ nameSingular: 'noteTarget' })).toBe(
|
||||
true,
|
||||
);
|
||||
expect(canObjectBeManagedByAutomation({ nameSingular: 'taskTarget' })).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return true for attachment and timelineActivity', () => {
|
||||
expect(canObjectBeManagedByAutomation({ nameSingular: 'attachment' })).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
canObjectBeManagedByAutomation({ nameSingular: 'timelineActivity' }),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
'workflow',
|
||||
'workflowVersion',
|
||||
'workflowRun',
|
||||
'workflowAutomatedTrigger',
|
||||
'workspaceMember',
|
||||
'dashboard',
|
||||
'message',
|
||||
'messageThread',
|
||||
'messageChannelMessageAssociation',
|
||||
'messageParticipant',
|
||||
'calendarEvent',
|
||||
'calendarEventParticipant',
|
||||
'calendarChannelEventAssociation',
|
||||
])('should return false for %s', (nameSingular) => {
|
||||
expect(canObjectBeManagedByAutomation({ nameSingular })).toBe(false);
|
||||
});
|
||||
});
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
import { canObjectBeManagedByWorkflow } from '@/workflow/utils/canObjectBeManagedByWorkflow';
|
||||
|
||||
describe('canObjectBeManagedByWorkflow', () => {
|
||||
it('should return true for non-system, non-excluded objects', () => {
|
||||
expect(
|
||||
canObjectBeManagedByWorkflow({
|
||||
nameSingular: 'company',
|
||||
isSystem: false,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for system objects', () => {
|
||||
expect(
|
||||
canObjectBeManagedByWorkflow({
|
||||
nameSingular: 'company',
|
||||
isSystem: true,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for workflow object', () => {
|
||||
expect(
|
||||
canObjectBeManagedByWorkflow({
|
||||
nameSingular: 'workflow',
|
||||
isSystem: false,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for workflowVersion object', () => {
|
||||
expect(
|
||||
canObjectBeManagedByWorkflow({
|
||||
nameSingular: 'workflowVersion',
|
||||
isSystem: false,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for workflowRun object', () => {
|
||||
expect(
|
||||
canObjectBeManagedByWorkflow({
|
||||
nameSingular: 'workflowRun',
|
||||
isSystem: false,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for dashboard object', () => {
|
||||
expect(
|
||||
canObjectBeManagedByWorkflow({
|
||||
nameSingular: 'dashboard',
|
||||
isSystem: false,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { OBJECTS_BLOCKED_FROM_AUTOMATION } from '../constants/ObjectsBlockedFromAutomation';
|
||||
|
||||
export const canObjectBeManagedByAutomation = ({
|
||||
nameSingular,
|
||||
}: {
|
||||
nameSingular: string;
|
||||
}): boolean => {
|
||||
return !OBJECTS_BLOCKED_FROM_AUTOMATION.includes(
|
||||
nameSingular as (typeof OBJECTS_BLOCKED_FROM_AUTOMATION)[number],
|
||||
);
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
export const canObjectBeManagedByWorkflow = ({
|
||||
nameSingular,
|
||||
isSystem,
|
||||
}: {
|
||||
nameSingular: string;
|
||||
isSystem: boolean;
|
||||
}) => {
|
||||
const excludedNonSystemObjectMetadataItemNames = [
|
||||
'workflow',
|
||||
'workflowVersion',
|
||||
'workflowRun',
|
||||
'dashboard',
|
||||
];
|
||||
|
||||
return (
|
||||
!excludedNonSystemObjectMetadataItemNames.includes(nameSingular) &&
|
||||
!isSystem
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user