Skip to content

Commit 55c63b7

Browse files
committed
fix(attio): fix silent update-clobber bugs + complete comment mutual-exclusion
- create_comment: support all 3 of Attio's mutually-exclusive comment targets (thread_id, record, entry), not just thread_id/entry - Fix update_task silently clearing is_completed on unrelated field updates (taskIsCompletedUpdate now defaults to "leave unchanged") - Fix update_list silently resetting workspace_access to full-access on unrelated field updates (listWorkspaceAccessUpdate, same pattern) - create_attribute: restore config:{} as always-required per Attio's schema (previously omitted it entirely, which is invalid on create) - create_record/update_record/assert_record/list_records: throw on invalid values/filter/sorts JSON instead of silently substituting {} (was a silent data-loss risk on malformed input) - get_task: drop stray Content-Type header on a GET request - types.ts: remove two dead unreferenced interfaces, fix workspaceMemberAccess type (was string, is actually an array)
1 parent a87e0fa commit 55c63b7

9 files changed

Lines changed: 111 additions & 53 deletions

File tree

apps/sim/blocks/blocks/attio.ts

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,19 @@ YYYY-MM-DDTHH:mm:ss.SSSZ
434434
{ label: 'Yes', id: 'true' },
435435
],
436436
value: () => 'false',
437-
condition: { field: 'operation', value: ['create_task', 'update_task'] },
437+
condition: { field: 'operation', value: 'create_task' },
438+
},
439+
{
440+
id: 'taskIsCompletedUpdate',
441+
title: 'Completed',
442+
type: 'dropdown',
443+
options: [
444+
{ label: 'Leave unchanged', id: 'unchanged' },
445+
{ label: 'No', id: 'false' },
446+
{ label: 'Yes', id: 'true' },
447+
],
448+
value: () => 'unchanged',
449+
condition: { field: 'operation', value: 'update_task' },
438450
},
439451
{
440452
id: 'taskLinkedRecords',
@@ -666,7 +678,20 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text.
666678
{ label: 'Read Only', id: 'read-only' },
667679
],
668680
value: () => 'full-access',
669-
condition: { field: 'operation', value: ['create_list', 'update_list'] },
681+
condition: { field: 'operation', value: 'create_list' },
682+
},
683+
{
684+
id: 'listWorkspaceAccessUpdate',
685+
title: 'Workspace Access',
686+
type: 'dropdown',
687+
options: [
688+
{ label: 'Leave unchanged', id: 'unchanged' },
689+
{ label: 'Full Access', id: 'full-access' },
690+
{ label: 'Read & Write', id: 'read-and-write' },
691+
{ label: 'Read Only', id: 'read-only' },
692+
],
693+
value: () => 'unchanged',
694+
condition: { field: 'operation', value: 'update_list' },
670695
},
671696

672697
// List entry fields
@@ -831,17 +856,31 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text.
831856
id: 'commentList',
832857
title: 'List',
833858
type: 'short-input',
834-
placeholder: 'List ID or slug',
859+
placeholder: 'List ID or slug (used with Entry ID)',
835860
condition: { field: 'operation', value: 'create_comment' },
836-
required: { field: 'operation', value: 'create_comment' },
837861
},
838862
{
839863
id: 'commentEntryId',
840864
title: 'Entry ID',
841865
type: 'short-input',
842-
placeholder: 'List entry ID to comment on',
866+
placeholder: 'List entry ID to comment on (used with List)',
843867
condition: { field: 'operation', value: 'create_comment' },
844-
required: { field: 'operation', value: 'create_comment' },
868+
},
869+
{
870+
id: 'commentRecordObject',
871+
title: 'Record Object',
872+
type: 'short-input',
873+
placeholder: 'Object ID or slug the record belongs to (used with Record ID)',
874+
condition: { field: 'operation', value: 'create_comment' },
875+
mode: 'advanced',
876+
},
877+
{
878+
id: 'commentRecordId',
879+
title: 'Record ID',
880+
type: 'short-input',
881+
placeholder: 'Record ID to comment on directly (used with Record Object)',
882+
condition: { field: 'operation', value: 'create_comment' },
883+
mode: 'advanced',
845884
},
846885
{
847886
id: 'commentThreadId',
@@ -1349,6 +1388,12 @@ Record reference: {"allowed_objects": ["people", "companies"]}`,
13491388
if (params.taskIsCompleted !== undefined)
13501389
cleanParams.isCompleted =
13511390
params.taskIsCompleted === 'true' || params.taskIsCompleted === true
1391+
if (
1392+
params.taskIsCompletedUpdate !== undefined &&
1393+
params.taskIsCompletedUpdate !== 'unchanged'
1394+
)
1395+
cleanParams.isCompleted =
1396+
params.taskIsCompletedUpdate === 'true' || params.taskIsCompletedUpdate === true
13521397
if (params.taskLinkedRecords) cleanParams.linkedRecords = params.taskLinkedRecords
13531398
if (params.taskAssignees) cleanParams.assignees = params.taskAssignees
13541399
if (params.taskId) cleanParams.taskId = params.taskId
@@ -1371,6 +1416,8 @@ Record reference: {"allowed_objects": ["people", "companies"]}`,
13711416
if (params.listParentObject) cleanParams.parentObject = params.listParentObject
13721417
if (params.listApiSlug) cleanParams.apiSlug = params.listApiSlug
13731418
if (params.listWorkspaceAccess) cleanParams.workspaceAccess = params.listWorkspaceAccess
1419+
if (params.listWorkspaceAccessUpdate && params.listWorkspaceAccessUpdate !== 'unchanged')
1420+
cleanParams.workspaceAccess = params.listWorkspaceAccessUpdate
13741421

13751422
// List entry params
13761423
if (params.entryId) cleanParams.entryId = params.entryId
@@ -1390,6 +1437,8 @@ Record reference: {"allowed_objects": ["people", "companies"]}`,
13901437
if (params.commentAuthorId) cleanParams.authorId = params.commentAuthorId
13911438
if (params.commentList) cleanParams.list = params.commentList
13921439
if (params.commentEntryId) cleanParams.entryId = params.commentEntryId
1440+
if (params.commentRecordObject) cleanParams.recordObject = params.commentRecordObject
1441+
if (params.commentRecordId) cleanParams.recordId = params.commentRecordId
13931442
if (params.commentThreadId) cleanParams.threadId = params.commentThreadId
13941443
if (params.commentCreatedAt) cleanParams.createdAt = params.commentCreatedAt
13951444
if (params.commentId) cleanParams.commentId = params.commentId
@@ -1462,6 +1511,7 @@ Record reference: {"allowed_objects": ["people", "companies"]}`,
14621511
taskContent: { type: 'string', description: 'Task content' },
14631512
taskDeadline: { type: 'string', description: 'Task deadline' },
14641513
taskIsCompleted: { type: 'string', description: 'Task completion status' },
1514+
taskIsCompletedUpdate: { type: 'string', description: 'Task completion status (update)' },
14651515
taskLinkedRecords: { type: 'json', description: 'Linked records JSON array' },
14661516
taskAssignees: { type: 'json', description: 'Assignees JSON array' },
14671517
taskId: { type: 'string', description: 'Task ID' },
@@ -1474,6 +1524,10 @@ Record reference: {"allowed_objects": ["people", "companies"]}`,
14741524
listParentObject: { type: 'string', description: 'List parent object' },
14751525
listApiSlug: { type: 'string', description: 'List API slug' },
14761526
listWorkspaceAccess: { type: 'string', description: 'List workspace-level access' },
1527+
listWorkspaceAccessUpdate: {
1528+
type: 'string',
1529+
description: 'List workspace-level access (update)',
1530+
},
14771531
entryId: { type: 'string', description: 'List entry ID' },
14781532
entryParentRecordId: { type: 'string', description: 'Record ID for list entry' },
14791533
entryParentObject: { type: 'string', description: 'Record object type for list entry' },
@@ -1487,6 +1541,8 @@ Record reference: {"allowed_objects": ["people", "companies"]}`,
14871541
commentAuthorId: { type: 'string', description: 'Comment author ID' },
14881542
commentList: { type: 'string', description: 'List for comment' },
14891543
commentEntryId: { type: 'string', description: 'Entry ID for comment' },
1544+
commentRecordObject: { type: 'string', description: 'Object for record comment' },
1545+
commentRecordId: { type: 'string', description: 'Record ID for record comment' },
14901546
commentThreadId: { type: 'string', description: 'Thread ID to reply to' },
14911547
commentCreatedAt: { type: 'string', description: 'Comment creation timestamp (backdate)' },
14921548
commentId: { type: 'string', description: 'Comment ID' },

apps/sim/tools/attio/assert_record.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ export const attioAssertRecordTool: ToolConfig<AttioAssertRecordParams, AttioAss
5656
'Content-Type': 'application/json',
5757
}),
5858
body: (params) => {
59-
let values: Record<string, unknown> = {}
59+
let values: Record<string, unknown>
6060
try {
6161
values = typeof params.values === 'string' ? JSON.parse(params.values) : params.values
6262
} catch {
63-
values = {}
63+
throw new Error('Invalid JSON provided for record values')
6464
}
6565
return { data: { values } }
6666
},

apps/sim/tools/attio/create_attribute.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ export const attioCreateAttributeTool: ToolConfig<
107107
is_required: params.isRequired ?? false,
108108
is_unique: params.isUnique ?? false,
109109
is_multiselect: params.isMultiselect ?? false,
110+
// `config` is a required key on Attio's create-attribute request body (even though its
111+
// nested fields are only required for type-dependent configs like currency/record-reference).
112+
config: {},
110113
}
111114
if (params.config) {
112115
try {

apps/sim/tools/attio/create_comment.ts

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,37 @@ export const attioCreateCommentTool: ToolConfig<
5252
},
5353
list: {
5454
type: 'string',
55-
required: true,
55+
required: false,
5656
visibility: 'user-or-llm',
57-
description: 'The list ID or slug the entry belongs to',
57+
description:
58+
'The list ID or slug the entry belongs to (used with entryId; omit if threadId or recordId is set)',
5859
},
5960
entryId: {
6061
type: 'string',
61-
required: true,
62+
required: false,
63+
visibility: 'user-or-llm',
64+
description:
65+
'The list entry ID to comment on (used with list; omit if threadId or recordId is set)',
66+
},
67+
recordObject: {
68+
type: 'string',
69+
required: false,
6270
visibility: 'user-or-llm',
63-
description: 'The entry ID to comment on',
71+
description:
72+
'The object ID or slug the record belongs to (used with recordId; omit if threadId or entryId is set)',
73+
},
74+
recordId: {
75+
type: 'string',
76+
required: false,
77+
visibility: 'user-or-llm',
78+
description:
79+
'The record ID to comment on directly (used with recordObject; omit if threadId or entryId is set)',
6480
},
6581
threadId: {
6682
type: 'string',
6783
required: false,
6884
visibility: 'user-or-llm',
69-
description: 'Thread ID to reply to (omit to start a new thread)',
85+
description: 'Thread ID to reply to (omit to start a new thread on a record or list entry)',
7086
},
7187
createdAt: {
7288
type: 'string',
@@ -92,14 +108,23 @@ export const attioCreateCommentTool: ToolConfig<
92108
id: params.authorId,
93109
},
94110
}
95-
// Attio's comment body accepts exactly one of `thread_id` or `entry` — sending both is rejected.
111+
// Attio's comment body accepts exactly one of `thread_id`, `record`, or `entry` — mutually exclusive.
96112
if (params.threadId) {
97113
data.thread_id = params.threadId
98-
} else {
114+
} else if (params.recordObject && params.recordId) {
115+
data.record = {
116+
object: params.recordObject,
117+
record_id: params.recordId,
118+
}
119+
} else if (params.list && params.entryId) {
99120
data.entry = {
100121
list: params.list,
101122
entry_id: params.entryId,
102123
}
124+
} else {
125+
throw new Error(
126+
'Must provide either threadId, both recordObject and recordId, or both list and entryId'
127+
)
103128
}
104129
if (params.createdAt) data.created_at = params.createdAt
105130
return { data }

apps/sim/tools/attio/create_record.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export const attioCreateRecordTool: ToolConfig<AttioCreateRecordParams, AttioCre
5050
try {
5151
values = typeof params.values === 'string' ? JSON.parse(params.values) : params.values
5252
} catch {
53-
values = {}
53+
throw new Error('Invalid JSON provided for record values')
5454
}
5555
return { data: { values } }
5656
},

apps/sim/tools/attio/get_task.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export const attioGetTaskTool: ToolConfig<AttioGetTaskParams, AttioGetTaskRespon
3636
method: 'GET',
3737
headers: (params) => ({
3838
Authorization: `Bearer ${params.accessToken}`,
39-
'Content-Type': 'application/json',
4039
}),
4140
},
4241

apps/sim/tools/attio/list_records.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,14 @@ export const attioListRecordsTool: ToolConfig<AttioListRecordsParams, AttioListR
6868
try {
6969
body.filter = JSON.parse(params.filter)
7070
} catch {
71-
body.filter = params.filter
71+
throw new Error('Invalid JSON provided for filter')
7272
}
7373
}
7474
if (params.sorts) {
7575
try {
7676
body.sorts = JSON.parse(params.sorts)
7777
} catch {
78-
body.sorts = params.sorts
78+
throw new Error('Invalid JSON provided for sorts')
7979
}
8080
}
8181
if (params.limit != null) body.limit = params.limit

apps/sim/tools/attio/types.ts

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -326,33 +326,6 @@ interface AttioRecord {
326326
values: Record<string, unknown>
327327
}
328328

329-
/** Raw Attio note shape from the API */
330-
interface AttioNote {
331-
id: { workspace_id: string; note_id: string }
332-
parent_object: string
333-
parent_record_id: string
334-
title: string
335-
content_plaintext: string
336-
content_markdown: string
337-
meeting_id: string | null
338-
tags: unknown[]
339-
created_by_actor: unknown
340-
created_at: string
341-
}
342-
343-
/** Raw Attio task shape from the API */
344-
interface AttioTask {
345-
id: { workspace_id: string; task_id: string }
346-
content_plaintext: string
347-
deadline_at: string | null
348-
is_completed: boolean
349-
completed_at: string | null
350-
linked_records: Array<{ target_object_id: string; target_record_id: string }>
351-
assignees: Array<{ referenced_actor_type: string; referenced_actor_id: string }>
352-
created_by_actor: unknown
353-
created_at: string
354-
}
355-
356329
/** Params for listing/querying records */
357330
export interface AttioListRecordsParams {
358331
accessToken: string
@@ -767,7 +740,7 @@ export interface AttioListListsResponse extends ToolResponse {
767740
name: string | null
768741
parentObject: string | null
769742
workspaceAccess: string | null
770-
workspaceMemberAccess: string | null
743+
workspaceMemberAccess: unknown
771744
createdByActor: { type: string | null; id: string | null } | null
772745
createdAt: string | null
773746
}>
@@ -789,7 +762,7 @@ export interface AttioGetListResponse extends ToolResponse {
789762
name: string | null
790763
parentObject: string | null
791764
workspaceAccess: string | null
792-
workspaceMemberAccess: string | null
765+
workspaceMemberAccess: unknown
793766
createdByActor: { type: string | null; id: string | null } | null
794767
createdAt: string | null
795768
}
@@ -813,7 +786,7 @@ export interface AttioCreateListResponse extends ToolResponse {
813786
name: string | null
814787
parentObject: string | null
815788
workspaceAccess: string | null
816-
workspaceMemberAccess: string | null
789+
workspaceMemberAccess: unknown
817790
createdByActor: { type: string | null; id: string | null } | null
818791
createdAt: string | null
819792
}
@@ -837,7 +810,7 @@ export interface AttioUpdateListResponse extends ToolResponse {
837810
name: string | null
838811
parentObject: string | null
839812
workspaceAccess: string | null
840-
workspaceMemberAccess: string | null
813+
workspaceMemberAccess: unknown
841814
createdByActor: { type: string | null; id: string | null } | null
842815
createdAt: string | null
843816
}
@@ -989,8 +962,10 @@ export interface AttioCreateCommentParams {
989962
format?: string
990963
authorType: string
991964
authorId: string
992-
list: string
993-
entryId: string
965+
list?: string
966+
entryId?: string
967+
recordObject?: string
968+
recordId?: string
994969
threadId?: string
995970
createdAt?: string
996971
}

apps/sim/tools/attio/update_record.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export const attioUpdateRecordTool: ToolConfig<AttioUpdateRecordParams, AttioUpd
5757
try {
5858
values = typeof params.values === 'string' ? JSON.parse(params.values) : params.values
5959
} catch {
60-
values = {}
60+
throw new Error('Invalid JSON provided for record values')
6161
}
6262
return { data: { values } }
6363
},

0 commit comments

Comments
 (0)