From 787c51cb1df839585a9fae88544cbf5a6cf1aca1 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 2 Jul 2026 08:57:33 -0700 Subject: [PATCH 1/7] feat(hex): expand API coverage, fix ID trimming, add cursor pagination - Add hex_update_collection, hex_create_group, hex_update_group, hex_delete_group, hex_deactivate_user tools + block wiring - Add missing run_project fields (viewId, notifications) and get_project_runs runTriggerFilter - Add after/before cursor pagination to list_projects, list_groups, list_data_connections, list_collections - Add .trim() on all interpolated ID path params to guard against copy-paste whitespace --- apps/sim/blocks/blocks/hex.ts | 238 +++++++++++++++++++- apps/sim/tools/hex/cancel_run.ts | 2 +- apps/sim/tools/hex/create_group.ts | 70 ++++++ apps/sim/tools/hex/deactivate_user.ts | 60 +++++ apps/sim/tools/hex/delete_group.ts | 60 +++++ apps/sim/tools/hex/get_collection.ts | 2 +- apps/sim/tools/hex/get_data_connection.ts | 3 +- apps/sim/tools/hex/get_group.ts | 2 +- apps/sim/tools/hex/get_project.ts | 2 +- apps/sim/tools/hex/get_project_runs.ts | 17 +- apps/sim/tools/hex/get_queried_tables.ts | 2 +- apps/sim/tools/hex/get_run_status.ts | 2 +- apps/sim/tools/hex/index.ts | 10 + apps/sim/tools/hex/list_collections.ts | 22 ++ apps/sim/tools/hex/list_data_connections.ts | 22 ++ apps/sim/tools/hex/list_groups.ts | 22 ++ apps/sim/tools/hex/list_projects.ts | 22 ++ apps/sim/tools/hex/run_project.ts | 22 +- apps/sim/tools/hex/types.ts | 96 ++++++++ apps/sim/tools/hex/update_collection.ts | 85 +++++++ apps/sim/tools/hex/update_group.ts | 90 ++++++++ apps/sim/tools/hex/update_project.ts | 2 +- apps/sim/tools/registry.ts | 10 + 23 files changed, 841 insertions(+), 22 deletions(-) create mode 100644 apps/sim/tools/hex/create_group.ts create mode 100644 apps/sim/tools/hex/deactivate_user.ts create mode 100644 apps/sim/tools/hex/delete_group.ts create mode 100644 apps/sim/tools/hex/update_collection.ts create mode 100644 apps/sim/tools/hex/update_group.ts diff --git a/apps/sim/blocks/blocks/hex.ts b/apps/sim/blocks/blocks/hex.ts index 2bb36f75ef6..8bdd4df3300 100644 --- a/apps/sim/blocks/blocks/hex.ts +++ b/apps/sim/blocks/blocks/hex.ts @@ -8,7 +8,7 @@ export const HexBlock: BlockConfig = { name: 'Hex', description: 'Run and manage Hex projects', longDescription: - 'Integrate Hex into your workflow. Run projects, check run status, manage collections and groups, list users, and view data connections. Requires a Hex API token.', + 'Integrate Hex into your workflow. Run projects, check run status, manage collections and groups (including membership and deactivating users), list users, and view data connections. Requires a Hex API token.', docsLink: 'https://docs.sim.ai/integrations/hex', category: 'tools', integrationType: IntegrationType.Analytics, @@ -36,8 +36,13 @@ export const HexBlock: BlockConfig = { { label: 'List Collections', id: 'list_collections' }, { label: 'Get Collection', id: 'get_collection' }, { label: 'Create Collection', id: 'create_collection' }, + { label: 'Update Collection', id: 'update_collection' }, { label: 'List Data Connections', id: 'list_data_connections' }, { label: 'Get Data Connection', id: 'get_data_connection' }, + { label: 'Create Group', id: 'create_group' }, + { label: 'Update Group', id: 'update_group' }, + { label: 'Delete Group', id: 'delete_group' }, + { label: 'Deactivate User', id: 'deactivate_user' }, ], value: () => 'run_project', }, @@ -107,6 +112,39 @@ Example: generationType: 'json-object', }, }, + { + id: 'viewId', + title: 'Saved View ID', + type: 'short-input', + placeholder: 'Enter a SavedView UUID (optional)', + condition: { field: 'operation', value: 'run_project' }, + mode: 'advanced', + }, + { + id: 'notifications', + title: 'Notifications', + type: 'code', + placeholder: '[{"type": "FAILURE", "slackChannelIds": ["C0123456789"]}]', + condition: { field: 'operation', value: 'run_project' }, + mode: 'advanced', + wandConfig: { + enabled: true, + maintainHistory: true, + prompt: `You are an expert at creating Hex run notification configs. +Generate ONLY the raw JSON array based on the user's request. +The output MUST be a single, valid JSON array, starting with [ and ending with ]. + +Current value: {context} + +Do not include any explanations, markdown formatting, or other text outside the JSON array. +Each item's "type" must be one of ALL, SUCCESS, or FAILURE. Optional fields: includeSuccessScreenshot (boolean), slackChannelIds, userIds, groupIds (arrays of strings). + +Example: +[{"type": "FAILURE", "slackChannelIds": ["C0123456789"], "includeSuccessScreenshot": false}]`, + placeholder: 'Describe who should be notified and when...', + generationType: 'json-object', + }, + }, { id: 'projectStatus', title: 'Status', @@ -130,28 +168,116 @@ Example: value: () => '', condition: { field: 'operation', value: 'get_project_runs' }, }, + { + id: 'runTriggerFilter', + title: 'Trigger Filter', + type: 'dropdown', + options: [ + { label: 'All', id: '' }, + { label: 'API', id: 'API' }, + { label: 'Scheduled', id: 'SCHEDULED' }, + { label: 'App Refresh', id: 'APP_REFRESH' }, + ], + value: () => '', + condition: { field: 'operation', value: 'get_project_runs' }, + mode: 'advanced', + }, { id: 'groupIdInput', title: 'Group ID', type: 'short-input', placeholder: 'Enter group UUID', - condition: { field: 'operation', value: 'get_group' }, - required: { field: 'operation', value: 'get_group' }, + condition: { field: 'operation', value: ['get_group', 'update_group', 'delete_group'] }, + required: { field: 'operation', value: ['get_group', 'update_group', 'delete_group'] }, + }, + { + id: 'groupName', + title: 'Group Name', + type: 'short-input', + placeholder: 'Enter group name', + condition: { field: 'operation', value: ['create_group', 'update_group'] }, + required: { field: 'operation', value: 'create_group' }, + }, + { + id: 'groupMemberUserIds', + title: 'Initial Member User IDs', + type: 'code', + placeholder: '["uuid1", "uuid2"]', + condition: { field: 'operation', value: 'create_group' }, + mode: 'advanced', + wandConfig: { + enabled: true, + maintainHistory: true, + prompt: `You are an expert at creating JSON arrays of user UUIDs. +Generate ONLY the raw JSON array of user ID strings based on the user's request. +The output MUST be a single, valid JSON array of strings, starting with [ and ending with ]. + +Current value: {context} + +Do not include any explanations, markdown formatting, or other text outside the JSON array. + +Example: +["a1b2c3d4-0000-0000-0000-000000000000", "e5f6a7b8-0000-0000-0000-000000000000"]`, + placeholder: 'Describe which users to add...', + generationType: 'json-object', + }, + }, + { + id: 'groupAddUserIds', + title: 'Add Member User IDs', + type: 'code', + placeholder: '["uuid1", "uuid2"]', + condition: { field: 'operation', value: 'update_group' }, + mode: 'advanced', + wandConfig: { + enabled: true, + maintainHistory: true, + prompt: `You are an expert at creating JSON arrays of user UUIDs. +Generate ONLY the raw JSON array of user ID strings to add based on the user's request. +The output MUST be a single, valid JSON array of strings, starting with [ and ending with ]. + +Current value: {context} + +Do not include any explanations, markdown formatting, or other text outside the JSON array.`, + placeholder: 'Describe which users to add...', + generationType: 'json-object', + }, + }, + { + id: 'groupRemoveUserIds', + title: 'Remove Member User IDs', + type: 'code', + placeholder: '["uuid1", "uuid2"]', + condition: { field: 'operation', value: 'update_group' }, + mode: 'advanced', + wandConfig: { + enabled: true, + maintainHistory: true, + prompt: `You are an expert at creating JSON arrays of user UUIDs. +Generate ONLY the raw JSON array of user ID strings to remove based on the user's request. +The output MUST be a single, valid JSON array of strings, starting with [ and ending with ]. + +Current value: {context} + +Do not include any explanations, markdown formatting, or other text outside the JSON array.`, + placeholder: 'Describe which users to remove...', + generationType: 'json-object', + }, }, { id: 'collectionId', title: 'Collection ID', type: 'short-input', placeholder: 'Enter collection UUID', - condition: { field: 'operation', value: 'get_collection' }, - required: { field: 'operation', value: 'get_collection' }, + condition: { field: 'operation', value: ['get_collection', 'update_collection'] }, + required: { field: 'operation', value: ['get_collection', 'update_collection'] }, }, { id: 'collectionName', title: 'Collection Name', type: 'short-input', placeholder: 'Enter collection name', - condition: { field: 'operation', value: 'create_collection' }, + condition: { field: 'operation', value: ['create_collection', 'update_collection'] }, required: { field: 'operation', value: 'create_collection' }, }, { @@ -159,7 +285,7 @@ Example: title: 'Description', type: 'long-input', placeholder: 'Optional description for the collection', - condition: { field: 'operation', value: 'create_collection' }, + condition: { field: 'operation', value: ['create_collection', 'update_collection'] }, }, { id: 'dataConnectionId', @@ -169,6 +295,14 @@ Example: condition: { field: 'operation', value: 'get_data_connection' }, required: { field: 'operation', value: 'get_data_connection' }, }, + { + id: 'userId', + title: 'User ID', + type: 'short-input', + placeholder: 'Enter user UUID', + condition: { field: 'operation', value: 'deactivate_user' }, + required: { field: 'operation', value: 'deactivate_user' }, + }, { id: 'apiKey', title: 'API Key', @@ -261,12 +395,37 @@ Example: condition: { field: 'operation', value: 'list_users' }, mode: 'advanced', }, + { + id: 'after', + title: 'After Cursor', + type: 'short-input', + placeholder: 'Cursor for the next page', + condition: { + field: 'operation', + value: ['list_projects', 'list_groups', 'list_collections', 'list_data_connections'], + }, + mode: 'advanced', + }, + { + id: 'before', + title: 'Before Cursor', + type: 'short-input', + placeholder: 'Cursor for the previous page', + condition: { + field: 'operation', + value: ['list_projects', 'list_groups', 'list_collections', 'list_data_connections'], + }, + mode: 'advanced', + }, ], tools: { access: [ 'hex_cancel_run', 'hex_create_collection', + 'hex_create_group', + 'hex_deactivate_user', + 'hex_delete_group', 'hex_get_collection', 'hex_get_data_connection', 'hex_get_group', @@ -280,6 +439,8 @@ Example: 'hex_list_projects', 'hex_list_users', 'hex_run_project', + 'hex_update_collection', + 'hex_update_group', 'hex_update_project', ], config: { @@ -313,10 +474,20 @@ Example: return 'hex_get_collection' case 'create_collection': return 'hex_create_collection' + case 'update_collection': + return 'hex_update_collection' case 'list_data_connections': return 'hex_list_data_connections' case 'get_data_connection': return 'hex_get_data_connection' + case 'create_group': + return 'hex_create_group' + case 'update_group': + return 'hex_update_group' + case 'delete_group': + return 'hex_delete_group' + case 'deactivate_user': + return 'hex_deactivate_user' default: return 'hex_run_project' } @@ -330,11 +501,27 @@ Example: if (op === 'update_project' && params.projectStatus) result.status = params.projectStatus if (op === 'get_project_runs' && params.runStatusFilter) result.statusFilter = params.runStatusFilter - if (op === 'get_group' && params.groupIdInput) result.groupId = params.groupIdInput + if ( + (op === 'get_group' || op === 'update_group' || op === 'delete_group') && + params.groupIdInput + ) + result.groupId = params.groupIdInput if (op === 'list_users' && params.groupId) result.groupId = params.groupId - if (op === 'create_collection' && params.collectionName) result.name = params.collectionName - if (op === 'create_collection' && params.collectionDescription) + if ((op === 'create_collection' || op === 'update_collection') && params.collectionName) + result.name = params.collectionName + if ( + (op === 'create_collection' || op === 'update_collection') && + params.collectionDescription + ) result.description = params.collectionDescription + if ((op === 'create_group' || op === 'update_group') && params.groupName) + result.name = params.groupName + if (op === 'create_group' && params.groupMemberUserIds) + result.memberUserIds = params.groupMemberUserIds + if (op === 'update_group' && params.groupAddUserIds) + result.addUserIds = params.groupAddUserIds + if (op === 'update_group' && params.groupRemoveUserIds) + result.removeUserIds = params.groupRemoveUserIds return result }, @@ -360,21 +547,34 @@ Example: type: 'boolean', description: 'Use cached SQL results instead of re-running queries', }, + viewId: { type: 'string', description: 'SavedView UUID to use for the project run' }, + notifications: { + type: 'json', + description: 'Notification details to deliver once the run completes', + }, projectStatus: { type: 'string', description: 'New project status name (custom workspace status label)', }, limit: { type: 'number', description: 'Max number of results to return' }, offset: { type: 'number', description: 'Offset for paginated results' }, + after: { type: 'string', description: 'Cursor to fetch results after' }, + before: { type: 'string', description: 'Cursor to fetch results before' }, includeArchived: { type: 'boolean', description: 'Include archived projects' }, statusFilter: { type: 'string', description: 'Filter projects by status' }, runStatusFilter: { type: 'string', description: 'Filter runs by status' }, + runTriggerFilter: { type: 'string', description: 'Filter runs by trigger source' }, groupId: { type: 'string', description: 'Filter users by group UUID' }, - groupIdInput: { type: 'string', description: 'Group UUID for get group' }, + groupIdInput: { type: 'string', description: 'Group UUID for get/update/delete group' }, + groupName: { type: 'string', description: 'Group name' }, + groupMemberUserIds: { type: 'json', description: 'Initial member user UUIDs for new group' }, + groupAddUserIds: { type: 'json', description: 'User UUIDs to add to the group' }, + groupRemoveUserIds: { type: 'json', description: 'User UUIDs to remove from the group' }, collectionId: { type: 'string', description: 'Collection UUID' }, collectionName: { type: 'string', description: 'Collection name' }, collectionDescription: { type: 'string', description: 'Collection description' }, dataConnectionId: { type: 'string', description: 'Data connection UUID' }, + userId: { type: 'string', description: 'User UUID' }, }, outputs: { @@ -437,8 +637,15 @@ Example: creator: { type: 'json', description: 'Creator details ({ email, id })' }, owner: { type: 'json', description: 'Owner details ({ email })' }, total: { type: 'number', description: 'Total results returned' }, - // Cancel output + // Cancel / delete / deactivate output success: { type: 'boolean', description: 'Whether the operation succeeded' }, + groupId: { type: 'string', description: 'Group UUID' }, + userId: { type: 'string', description: 'User UUID' }, + // Pagination + nextPage: { type: 'string', description: 'Cursor for the next page of runs' }, + previousPage: { type: 'string', description: 'Cursor for the previous page of runs' }, + after: { type: 'string', description: 'Cursor for the next page of results' }, + before: { type: 'string', description: 'Cursor for the previous page of results' }, // Data connection flags connectViaSsh: { type: 'boolean', description: 'SSH tunneling enabled' }, includeMagic: { type: 'boolean', description: 'Magic AI features enabled' }, @@ -546,5 +753,12 @@ export const HexBlockMeta = { content: '# Inventory Projects\n\nMap what projects and data sources exist in the workspace.\n\n## Steps\n1. List projects and capture IDs, names, and owners.\n2. List collections and get details to see how projects are grouped.\n3. List data connections to map which sources power the projects.\n4. Cross-reference projects to their collections and data connections.\n\n## Output\nReturn an inventory of projects grouped by collection, each annotated with its data connections. Useful for governance and cleanup.', }, + { + name: 'onboard-offboard-teammate', + description: + 'Add a new teammate to the right Hex groups on hire, or deactivate and remove them on departure.', + content: + '# Onboard/Offboard Teammate\n\nManage workspace access as people join or leave the team.\n\n## Steps\n1. List users to resolve the target user by name or email, and list groups to resolve the relevant group by name.\n2. For onboarding: add the user to the appropriate group(s) via group update.\n3. For offboarding: remove the user from their groups via group update, then deactivate the user account.\n4. Confirm the change by getting the group or listing users filtered by group.\n\n## Output\nReturn the user and group IDs affected and the action taken (added, removed, deactivated). Flag if the user or group could not be resolved.', + }, ], } as const satisfies BlockMeta diff --git a/apps/sim/tools/hex/cancel_run.ts b/apps/sim/tools/hex/cancel_run.ts index 17c65944c3a..f7a9646b495 100644 --- a/apps/sim/tools/hex/cancel_run.ts +++ b/apps/sim/tools/hex/cancel_run.ts @@ -30,7 +30,7 @@ export const cancelRunTool: ToolConfig request: { url: (params) => - `https://app.hex.tech/api/v1/projects/${params.projectId}/runs/${params.runId}`, + `https://app.hex.tech/api/v1/projects/${params.projectId.trim()}/runs/${params.runId.trim()}`, method: 'DELETE', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/hex/create_group.ts b/apps/sim/tools/hex/create_group.ts new file mode 100644 index 00000000000..d23d6c16a5c --- /dev/null +++ b/apps/sim/tools/hex/create_group.ts @@ -0,0 +1,70 @@ +import type { HexCreateGroupParams, HexCreateGroupResponse } from '@/tools/hex/types' +import type { ToolConfig } from '@/tools/types' + +export const createGroupTool: ToolConfig = { + id: 'hex_create_group', + name: 'Hex Create Group', + description: 'Create a new group in the Hex workspace, optionally with initial members.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Hex API token (Personal or Workspace)', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name for the new group', + }, + memberUserIds: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: + 'JSON array of user UUIDs to add as initial group members (e.g., ["uuid1", "uuid2"])', + }, + }, + + request: { + url: 'https://app.hex.tech/api/v1/groups', + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = { name: params.name } + if (params.memberUserIds) { + const userIds = + typeof params.memberUserIds === 'string' + ? JSON.parse(params.memberUserIds) + : params.memberUserIds + body.members = { users: (userIds as string[]).map((id) => ({ id })) } + } + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + id: data.id ?? null, + name: data.name ?? null, + createdAt: data.createdAt ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Newly created group UUID' }, + name: { type: 'string', description: 'Group name' }, + createdAt: { type: 'string', description: 'Creation timestamp' }, + }, +} diff --git a/apps/sim/tools/hex/deactivate_user.ts b/apps/sim/tools/hex/deactivate_user.ts new file mode 100644 index 00000000000..1f75120e751 --- /dev/null +++ b/apps/sim/tools/hex/deactivate_user.ts @@ -0,0 +1,60 @@ +import type { HexDeactivateUserParams, HexDeactivateUserResponse } from '@/tools/hex/types' +import type { ToolConfig } from '@/tools/types' + +export const deactivateUserTool: ToolConfig = { + id: 'hex_deactivate_user', + name: 'Hex Deactivate User', + description: 'Deactivate a user in the Hex workspace.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Hex API token (Personal or Workspace)', + }, + userId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The UUID of the user to deactivate', + }, + }, + + request: { + url: (params) => `https://app.hex.tech/api/v1/users/${params.userId.trim()}/deactivate`, + method: 'POST', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response, params) => { + if (response.status === 204 || response.ok) { + return { + success: true, + output: { + success: true, + userId: params?.userId ?? '', + }, + } + } + + const data = await response.json().catch(() => ({})) + return { + success: false, + output: { + success: false, + userId: params?.userId ?? '', + }, + error: (data as Record).message ?? 'Failed to deactivate user', + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the user was successfully deactivated' }, + userId: { type: 'string', description: 'User UUID that was deactivated' }, + }, +} diff --git a/apps/sim/tools/hex/delete_group.ts b/apps/sim/tools/hex/delete_group.ts new file mode 100644 index 00000000000..1135a6c87e2 --- /dev/null +++ b/apps/sim/tools/hex/delete_group.ts @@ -0,0 +1,60 @@ +import type { HexDeleteGroupParams, HexDeleteGroupResponse } from '@/tools/hex/types' +import type { ToolConfig } from '@/tools/types' + +export const deleteGroupTool: ToolConfig = { + id: 'hex_delete_group', + name: 'Hex Delete Group', + description: 'Delete a group from the Hex workspace.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Hex API token (Personal or Workspace)', + }, + groupId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The UUID of the group to delete', + }, + }, + + request: { + url: (params) => `https://app.hex.tech/api/v1/groups/${params.groupId.trim()}`, + method: 'DELETE', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response, params) => { + if (response.status === 204 || response.ok) { + return { + success: true, + output: { + success: true, + groupId: params?.groupId ?? '', + }, + } + } + + const data = await response.json().catch(() => ({})) + return { + success: false, + output: { + success: false, + groupId: params?.groupId ?? '', + }, + error: (data as Record).message ?? 'Failed to delete group', + } + }, + + outputs: { + success: { type: 'boolean', description: 'Whether the group was successfully deleted' }, + groupId: { type: 'string', description: 'Group UUID that was deleted' }, + }, +} diff --git a/apps/sim/tools/hex/get_collection.ts b/apps/sim/tools/hex/get_collection.ts index 8222d88a92c..8b5c0fe888c 100644 --- a/apps/sim/tools/hex/get_collection.ts +++ b/apps/sim/tools/hex/get_collection.ts @@ -23,7 +23,7 @@ export const getCollectionTool: ToolConfig `https://app.hex.tech/api/v1/collections/${params.collectionId}`, + url: (params) => `https://app.hex.tech/api/v1/collections/${params.collectionId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/hex/get_data_connection.ts b/apps/sim/tools/hex/get_data_connection.ts index 3b9e54b94f3..68bd0838389 100644 --- a/apps/sim/tools/hex/get_data_connection.ts +++ b/apps/sim/tools/hex/get_data_connection.ts @@ -27,7 +27,8 @@ export const getDataConnectionTool: ToolConfig< }, request: { - url: (params) => `https://app.hex.tech/api/v1/data-connections/${params.dataConnectionId}`, + url: (params) => + `https://app.hex.tech/api/v1/data-connections/${params.dataConnectionId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/hex/get_group.ts b/apps/sim/tools/hex/get_group.ts index c649e657a86..683bde7e519 100644 --- a/apps/sim/tools/hex/get_group.ts +++ b/apps/sim/tools/hex/get_group.ts @@ -23,7 +23,7 @@ export const getGroupTool: ToolConfig = }, request: { - url: (params) => `https://app.hex.tech/api/v1/groups/${params.groupId}`, + url: (params) => `https://app.hex.tech/api/v1/groups/${params.groupId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/hex/get_project.ts b/apps/sim/tools/hex/get_project.ts index fda718f2f6c..fd5ecb29f34 100644 --- a/apps/sim/tools/hex/get_project.ts +++ b/apps/sim/tools/hex/get_project.ts @@ -24,7 +24,7 @@ export const getProjectTool: ToolConfig `https://app.hex.tech/api/v1/projects/${params.projectId}`, + url: (params) => `https://app.hex.tech/api/v1/projects/${params.projectId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/hex/get_project_runs.ts b/apps/sim/tools/hex/get_project_runs.ts index 9d2897d900e..0495603de27 100644 --- a/apps/sim/tools/hex/get_project_runs.ts +++ b/apps/sim/tools/hex/get_project_runs.ts @@ -40,6 +40,12 @@ export const getProjectRunsTool: ToolConfig ({ @@ -78,6 +85,8 @@ export const getProjectRunsTool: ToolConfig ({ diff --git a/apps/sim/tools/hex/get_run_status.ts b/apps/sim/tools/hex/get_run_status.ts index 90dd26cdb01..0c0de4a870b 100644 --- a/apps/sim/tools/hex/get_run_status.ts +++ b/apps/sim/tools/hex/get_run_status.ts @@ -31,7 +31,7 @@ export const getRunStatusTool: ToolConfig - `https://app.hex.tech/api/v1/projects/${params.projectId}/runs/${params.runId}`, + `https://app.hex.tech/api/v1/projects/${params.projectId.trim()}/runs/${params.runId.trim()}`, method: 'GET', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/hex/index.ts b/apps/sim/tools/hex/index.ts index 9a561587d7d..e96b0bb53fb 100644 --- a/apps/sim/tools/hex/index.ts +++ b/apps/sim/tools/hex/index.ts @@ -1,5 +1,8 @@ import { cancelRunTool } from '@/tools/hex/cancel_run' import { createCollectionTool } from '@/tools/hex/create_collection' +import { createGroupTool } from '@/tools/hex/create_group' +import { deactivateUserTool } from '@/tools/hex/deactivate_user' +import { deleteGroupTool } from '@/tools/hex/delete_group' import { getCollectionTool } from '@/tools/hex/get_collection' import { getDataConnectionTool } from '@/tools/hex/get_data_connection' import { getGroupTool } from '@/tools/hex/get_group' @@ -13,10 +16,15 @@ import { listGroupsTool } from '@/tools/hex/list_groups' import { listProjectsTool } from '@/tools/hex/list_projects' import { listUsersTool } from '@/tools/hex/list_users' import { runProjectTool } from '@/tools/hex/run_project' +import { updateCollectionTool } from '@/tools/hex/update_collection' +import { updateGroupTool } from '@/tools/hex/update_group' import { updateProjectTool } from '@/tools/hex/update_project' export const hexCancelRunTool = cancelRunTool export const hexCreateCollectionTool = createCollectionTool +export const hexCreateGroupTool = createGroupTool +export const hexDeactivateUserTool = deactivateUserTool +export const hexDeleteGroupTool = deleteGroupTool export const hexGetCollectionTool = getCollectionTool export const hexGetDataConnectionTool = getDataConnectionTool export const hexGetGroupTool = getGroupTool @@ -30,4 +38,6 @@ export const hexListGroupsTool = listGroupsTool export const hexListProjectsTool = listProjectsTool export const hexListUsersTool = listUsersTool export const hexRunProjectTool = runProjectTool +export const hexUpdateCollectionTool = updateCollectionTool +export const hexUpdateGroupTool = updateGroupTool export const hexUpdateProjectTool = updateProjectTool diff --git a/apps/sim/tools/hex/list_collections.ts b/apps/sim/tools/hex/list_collections.ts index 9902db0d158..beaf4054f57 100644 --- a/apps/sim/tools/hex/list_collections.ts +++ b/apps/sim/tools/hex/list_collections.ts @@ -27,6 +27,18 @@ export const listCollectionsTool: ToolConfig `https://app.hex.tech/api/v1/projects/${params.projectId}/runs`, + url: (params) => `https://app.hex.tech/api/v1/projects/${params.projectId.trim()}/runs`, method: 'POST', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, @@ -76,6 +89,13 @@ export const runProjectTool: ToolConfig total: number + after: string | null + before: string | null } } @@ -159,6 +163,8 @@ export interface HexRunProjectParams { updateCache?: boolean updatePublishedResults?: boolean useCachedSqlResults?: boolean + viewId?: string + notifications?: string } export interface HexRunProjectResponse extends ToolResponse { @@ -212,6 +218,7 @@ export interface HexGetProjectRunsParams { limit?: number offset?: number statusFilter?: string + runTriggerFilter?: string } export interface HexGetProjectRunsResponse extends ToolResponse { @@ -229,6 +236,8 @@ export interface HexGetProjectRunsResponse extends ToolResponse { }> total: number traceId: string | null + nextPage: string | null + previousPage: string | null } } @@ -280,6 +289,8 @@ export interface HexListCollectionsParams { apiKey: string limit?: number sortBy?: string + after?: string + before?: string } export interface HexListCollectionsResponse extends ToolResponse { @@ -291,6 +302,8 @@ export interface HexListCollectionsResponse extends ToolResponse { creator: { email: string; id: string } | null }> total: number + after: string | null + before: string | null } } @@ -299,6 +312,8 @@ export interface HexListDataConnectionsParams { limit?: number sortBy?: string sortDirection?: string + after?: string + before?: string } export interface HexListDataConnectionsResponse extends ToolResponse { @@ -313,6 +328,8 @@ export interface HexListDataConnectionsResponse extends ToolResponse { allowWritebackCells: boolean | null }> total: number + after: string | null + before: string | null } } @@ -338,6 +355,8 @@ export interface HexListGroupsParams { limit?: number sortBy?: string sortDirection?: string + after?: string + before?: string } export interface HexListGroupsResponse extends ToolResponse { @@ -348,6 +367,8 @@ export interface HexListGroupsResponse extends ToolResponse { createdAt: string | null }> total: number + after: string | null + before: string | null } } @@ -410,6 +431,76 @@ export interface HexCreateCollectionResponse extends ToolResponse { } } +export interface HexUpdateCollectionParams { + apiKey: string + collectionId: string + name?: string + description?: string +} + +export interface HexUpdateCollectionResponse extends ToolResponse { + output: { + id: string + name: string + description: string | null + creator: { email: string; id: string } | null + } +} + +export interface HexCreateGroupParams { + apiKey: string + name: string + memberUserIds?: string +} + +export interface HexCreateGroupResponse extends ToolResponse { + output: { + id: string + name: string + createdAt: string | null + } +} + +export interface HexUpdateGroupParams { + apiKey: string + groupId: string + name?: string + addUserIds?: string + removeUserIds?: string +} + +export interface HexUpdateGroupResponse extends ToolResponse { + output: { + id: string + name: string + createdAt: string | null + } +} + +export interface HexDeleteGroupParams { + apiKey: string + groupId: string +} + +export interface HexDeleteGroupResponse extends ToolResponse { + output: { + success: boolean + groupId: string + } +} + +export interface HexDeactivateUserParams { + apiKey: string + userId: string +} + +export interface HexDeactivateUserResponse extends ToolResponse { + output: { + success: boolean + userId: string + } +} + export type HexResponse = | HexListProjectsResponse | HexGetProjectResponse @@ -427,3 +518,8 @@ export type HexResponse = | HexGetDataConnectionResponse | HexGetCollectionResponse | HexCreateCollectionResponse + | HexUpdateCollectionResponse + | HexCreateGroupResponse + | HexUpdateGroupResponse + | HexDeleteGroupResponse + | HexDeactivateUserResponse diff --git a/apps/sim/tools/hex/update_collection.ts b/apps/sim/tools/hex/update_collection.ts new file mode 100644 index 00000000000..a21c2aee1db --- /dev/null +++ b/apps/sim/tools/hex/update_collection.ts @@ -0,0 +1,85 @@ +import type { HexUpdateCollectionParams, HexUpdateCollectionResponse } from '@/tools/hex/types' +import type { ToolConfig } from '@/tools/types' + +export const updateCollectionTool: ToolConfig< + HexUpdateCollectionParams, + HexUpdateCollectionResponse +> = { + id: 'hex_update_collection', + name: 'Hex Update Collection', + description: 'Update the name or description of an existing Hex collection.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Hex API token (Personal or Workspace)', + }, + collectionId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The UUID of the collection to update', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New name for the collection', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New description for the collection', + }, + }, + + request: { + url: (params) => `https://app.hex.tech/api/v1/collections/${params.collectionId.trim()}`, + method: 'PATCH', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = {} + if (params.name) body.name = params.name + if (params.description !== undefined) body.description = params.description + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + id: data.id ?? null, + name: data.name ?? null, + description: data.description ?? null, + creator: data.creator + ? { email: data.creator.email ?? null, id: data.creator.id ?? null } + : null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Collection UUID' }, + name: { type: 'string', description: 'Collection name' }, + description: { type: 'string', description: 'Collection description', optional: true }, + creator: { + type: 'object', + description: 'Collection creator', + optional: true, + properties: { + email: { type: 'string', description: 'Creator email' }, + id: { type: 'string', description: 'Creator UUID' }, + }, + }, + }, +} diff --git a/apps/sim/tools/hex/update_group.ts b/apps/sim/tools/hex/update_group.ts new file mode 100644 index 00000000000..0fbc97bcb13 --- /dev/null +++ b/apps/sim/tools/hex/update_group.ts @@ -0,0 +1,90 @@ +import type { HexUpdateGroupParams, HexUpdateGroupResponse } from '@/tools/hex/types' +import type { ToolConfig } from '@/tools/types' + +export const updateGroupTool: ToolConfig = { + id: 'hex_update_group', + name: 'Hex Update Group', + description: 'Rename a Hex group or add/remove members from it.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Hex API token (Personal or Workspace)', + }, + groupId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The UUID of the group to update', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New name for the group', + }, + addUserIds: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: 'JSON array of user UUIDs to add to the group (e.g., ["uuid1", "uuid2"])', + }, + removeUserIds: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: 'JSON array of user UUIDs to remove from the group (e.g., ["uuid1", "uuid2"])', + }, + }, + + request: { + url: (params) => `https://app.hex.tech/api/v1/groups/${params.groupId.trim()}`, + method: 'PATCH', + headers: (params) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = {} + if (params.name) body.name = params.name + + const parseIds = (value: unknown) => + (typeof value === 'string' ? JSON.parse(value) : value) as string[] + + if (params.addUserIds || params.removeUserIds) { + const members: Record = {} + if (params.addUserIds) { + members.add = { users: parseIds(params.addUserIds).map((id) => ({ id })) } + } + if (params.removeUserIds) { + members.remove = { users: parseIds(params.removeUserIds).map((id) => ({ id })) } + } + body.members = members + } + + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + id: data.id ?? null, + name: data.name ?? null, + createdAt: data.createdAt ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Group UUID' }, + name: { type: 'string', description: 'Group name' }, + createdAt: { type: 'string', description: 'Creation timestamp' }, + }, +} diff --git a/apps/sim/tools/hex/update_project.ts b/apps/sim/tools/hex/update_project.ts index e8da0a3c278..faf75cfb9a7 100644 --- a/apps/sim/tools/hex/update_project.ts +++ b/apps/sim/tools/hex/update_project.ts @@ -30,7 +30,7 @@ export const updateProjectTool: ToolConfig `https://app.hex.tech/api/v1/projects/${params.projectId}`, + url: (params) => `https://app.hex.tech/api/v1/projects/${params.projectId.trim()}`, method: 'PATCH', headers: (params) => ({ Authorization: `Bearer ${params.apiKey}`, diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index d3826f8c2db..2ba0dbd97be 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1555,6 +1555,9 @@ import { guardrailsValidateTool } from '@/tools/guardrails' import { hexCancelRunTool, hexCreateCollectionTool, + hexCreateGroupTool, + hexDeactivateUserTool, + hexDeleteGroupTool, hexGetCollectionTool, hexGetDataConnectionTool, hexGetGroupTool, @@ -1568,6 +1571,8 @@ import { hexListProjectsTool, hexListUsersTool, hexRunProjectTool, + hexUpdateCollectionTool, + hexUpdateGroupTool, hexUpdateProjectTool, } from '@/tools/hex' import { httpRequestTool, webhookRequestTool } from '@/tools/http' @@ -4637,6 +4642,9 @@ export const tools: Record = { guardrails_validate: guardrailsValidateTool, hex_cancel_run: hexCancelRunTool, hex_create_collection: hexCreateCollectionTool, + hex_create_group: hexCreateGroupTool, + hex_deactivate_user: hexDeactivateUserTool, + hex_delete_group: hexDeleteGroupTool, hex_get_collection: hexGetCollectionTool, hex_get_data_connection: hexGetDataConnectionTool, hex_get_group: hexGetGroupTool, @@ -4650,6 +4658,8 @@ export const tools: Record = { hex_list_projects: hexListProjectsTool, hex_list_users: hexListUsersTool, hex_run_project: hexRunProjectTool, + hex_update_collection: hexUpdateCollectionTool, + hex_update_group: hexUpdateGroupTool, hex_update_project: hexUpdateProjectTool, instantly_activate_campaign: instantlyActivateCampaignTool, instantly_create_campaign: instantlyCreateCampaignTool, From e1adf54bb8f9a9e8476134fba1430bab55054612 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 2 Jul 2026 09:09:22 -0700 Subject: [PATCH 2/7] fix(hex): guard JSON.parse on user-supplied array/object params Wrap JSON.parse calls for memberUserIds, addUserIds, removeUserIds, inputParams, and notifications in try/catch so malformed input throws a clear error instead of an opaque parse exception. --- apps/sim/tools/hex/create_group.ts | 15 ++++++++++----- apps/sim/tools/hex/run_project.ts | 24 ++++++++++++++++-------- apps/sim/tools/hex/update_group.ts | 11 +++++++++-- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/apps/sim/tools/hex/create_group.ts b/apps/sim/tools/hex/create_group.ts index d23d6c16a5c..1dc65f3a8ca 100644 --- a/apps/sim/tools/hex/create_group.ts +++ b/apps/sim/tools/hex/create_group.ts @@ -39,11 +39,16 @@ export const createGroupTool: ToolConfig { const body: Record = { name: params.name } if (params.memberUserIds) { - const userIds = - typeof params.memberUserIds === 'string' - ? JSON.parse(params.memberUserIds) - : params.memberUserIds - body.members = { users: (userIds as string[]).map((id) => ({ id })) } + let userIds: string[] + try { + userIds = + typeof params.memberUserIds === 'string' + ? JSON.parse(params.memberUserIds) + : (params.memberUserIds as string[]) + } catch { + throw new Error('memberUserIds must be a valid JSON array of user UUID strings') + } + body.members = { users: userIds.map((id) => ({ id })) } } return body }, diff --git a/apps/sim/tools/hex/run_project.ts b/apps/sim/tools/hex/run_project.ts index b0046e11c0b..560bf6a7da8 100644 --- a/apps/sim/tools/hex/run_project.ts +++ b/apps/sim/tools/hex/run_project.ts @@ -78,10 +78,14 @@ export const runProjectTool: ToolConfig = {} if (params.inputParams) { - body.inputParams = - typeof params.inputParams === 'string' - ? JSON.parse(params.inputParams) - : params.inputParams + try { + body.inputParams = + typeof params.inputParams === 'string' + ? JSON.parse(params.inputParams) + : params.inputParams + } catch { + throw new Error('inputParams must be valid JSON') + } } if (params.dryRun !== undefined) body.dryRun = params.dryRun if (params.updateCache !== undefined) body.updateCache = params.updateCache @@ -91,10 +95,14 @@ export const runProjectTool: ToolConfig = {} if (params.name) body.name = params.name - const parseIds = (value: unknown) => - (typeof value === 'string' ? JSON.parse(value) : value) as string[] + const parseIds = (value: unknown): string[] => { + try { + return (typeof value === 'string' ? JSON.parse(value) : value) as string[] + } catch { + throw new Error( + 'addUserIds/removeUserIds must be a valid JSON array of user UUID strings' + ) + } + } if (params.addUserIds || params.removeUserIds) { const members: Record = {} From 0b2fbfcd24f9cd91baf9550589a4861fc0172a8d Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 2 Jul 2026 09:36:37 -0700 Subject: [PATCH 3/7] fix(hex): forward empty collection description on update update_collection's params() mapping only copied collectionDescription when truthy, so clearing it to an empty string in the UI never reached the API. Untouched fields resolve to null (not undefined), so use a loose null check to distinguish "cleared" from "never touched" without sending description: null on every unrelated update. --- apps/sim/blocks/blocks/hex.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/sim/blocks/blocks/hex.ts b/apps/sim/blocks/blocks/hex.ts index 8bdd4df3300..92870092e8c 100644 --- a/apps/sim/blocks/blocks/hex.ts +++ b/apps/sim/blocks/blocks/hex.ts @@ -509,10 +509,9 @@ Do not include any explanations, markdown formatting, or other text outside the if (op === 'list_users' && params.groupId) result.groupId = params.groupId if ((op === 'create_collection' || op === 'update_collection') && params.collectionName) result.name = params.collectionName - if ( - (op === 'create_collection' || op === 'update_collection') && - params.collectionDescription - ) + if (op === 'create_collection' && params.collectionDescription) + result.description = params.collectionDescription + if (op === 'update_collection' && params.collectionDescription != null) result.description = params.collectionDescription if ((op === 'create_group' || op === 'update_group') && params.groupName) result.name = params.groupName From 17f59bed9a0ea28b710ed3c866338e21653b9b38 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 2 Jul 2026 09:59:06 -0700 Subject: [PATCH 4/7] fix(hex): close remaining API coverage gaps found via raw OpenAPI spec Pulled Hex's actual OpenAPI spec (static.hex.site/openapi.json) as ground truth for a final verification pass: - list_users was missing after/before cursor pagination and the userIds filter, which the spec confirms it supports (an earlier doc-summary pass had incorrectly flagged this as unverifiable) - list_users response also exposes lastLoginDate per user, not previously surfaced - list_projects was missing includeComponents, includeTrashed, creatorEmail, ownerEmail, collectionId, categories, sortBy, and sortDirection filters that the spec confirms are real query params --- apps/sim/blocks/blocks/hex.ts | 107 ++++++++++++++++++++++++++-- apps/sim/tools/hex/list_projects.ts | 69 ++++++++++++++++++ apps/sim/tools/hex/list_users.ts | 35 +++++++++ apps/sim/tools/hex/types.ts | 14 ++++ 4 files changed, 221 insertions(+), 4 deletions(-) diff --git a/apps/sim/blocks/blocks/hex.ts b/apps/sim/blocks/blocks/hex.ts index 92870092e8c..0d4cc2605f7 100644 --- a/apps/sim/blocks/blocks/hex.ts +++ b/apps/sim/blocks/blocks/hex.ts @@ -269,7 +269,10 @@ Do not include any explanations, markdown formatting, or other text outside the title: 'Collection ID', type: 'short-input', placeholder: 'Enter collection UUID', - condition: { field: 'operation', value: ['get_collection', 'update_collection'] }, + condition: { + field: 'operation', + value: ['get_collection', 'update_collection', 'list_projects'], + }, required: { field: 'operation', value: ['get_collection', 'update_collection'] }, }, { @@ -374,6 +377,20 @@ Do not include any explanations, markdown formatting, or other text outside the condition: { field: 'operation', value: 'list_projects' }, mode: 'advanced', }, + { + id: 'includeComponents', + title: 'Include Components', + type: 'switch', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, + { + id: 'includeTrashed', + title: 'Include Trashed', + type: 'switch', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, { id: 'statusFilter', title: 'Status Filter', @@ -387,6 +404,57 @@ Do not include any explanations, markdown formatting, or other text outside the condition: { field: 'operation', value: 'list_projects' }, mode: 'advanced', }, + { + id: 'creatorEmail', + title: 'Creator Email', + type: 'short-input', + placeholder: 'Filter by creator email (optional)', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, + { + id: 'ownerEmail', + title: 'Owner Email', + type: 'short-input', + placeholder: 'Filter by owner email (optional)', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, + { + id: 'categories', + title: 'Categories', + type: 'code', + placeholder: '["Marketing", "Finance"]', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, + { + id: 'sortBy', + title: 'Sort By', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Created At', id: 'CREATED_AT' }, + { label: 'Last Edited At', id: 'LAST_EDITED_AT' }, + { label: 'Last Published At', id: 'LAST_PUBLISHED_AT' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, + { + id: 'sortDirection', + title: 'Sort Direction', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Ascending', id: 'ASC' }, + { label: 'Descending', id: 'DESC' }, + ], + value: () => '', + condition: { field: 'operation', value: 'list_projects' }, + mode: 'advanced', + }, { id: 'groupId', title: 'Filter by Group', @@ -395,6 +463,14 @@ Do not include any explanations, markdown formatting, or other text outside the condition: { field: 'operation', value: 'list_users' }, mode: 'advanced', }, + { + id: 'userIds', + title: 'Filter by User IDs', + type: 'short-input', + placeholder: 'Comma-separated user UUIDs (optional)', + condition: { field: 'operation', value: 'list_users' }, + mode: 'advanced', + }, { id: 'after', title: 'After Cursor', @@ -402,7 +478,13 @@ Do not include any explanations, markdown formatting, or other text outside the placeholder: 'Cursor for the next page', condition: { field: 'operation', - value: ['list_projects', 'list_groups', 'list_collections', 'list_data_connections'], + value: [ + 'list_projects', + 'list_groups', + 'list_collections', + 'list_data_connections', + 'list_users', + ], }, mode: 'advanced', }, @@ -413,7 +495,13 @@ Do not include any explanations, markdown formatting, or other text outside the placeholder: 'Cursor for the previous page', condition: { field: 'operation', - value: ['list_projects', 'list_groups', 'list_collections', 'list_data_connections'], + value: [ + 'list_projects', + 'list_groups', + 'list_collections', + 'list_data_connections', + 'list_users', + ], }, mode: 'advanced', }, @@ -560,10 +648,18 @@ Do not include any explanations, markdown formatting, or other text outside the after: { type: 'string', description: 'Cursor to fetch results after' }, before: { type: 'string', description: 'Cursor to fetch results before' }, includeArchived: { type: 'boolean', description: 'Include archived projects' }, + includeComponents: { type: 'boolean', description: 'Include components in results' }, + includeTrashed: { type: 'boolean', description: 'Include trashed projects in results' }, statusFilter: { type: 'string', description: 'Filter projects by status' }, + creatorEmail: { type: 'string', description: 'Filter projects by creator email' }, + ownerEmail: { type: 'string', description: 'Filter projects by owner email' }, + categories: { type: 'json', description: 'Filter projects by category names' }, + sortBy: { type: 'string', description: 'Sort field for list results' }, + sortDirection: { type: 'string', description: 'Sort direction for list results' }, runStatusFilter: { type: 'string', description: 'Filter runs by status' }, runTriggerFilter: { type: 'string', description: 'Filter runs by trigger source' }, groupId: { type: 'string', description: 'Filter users by group UUID' }, + userIds: { type: 'string', description: 'Comma-separated user UUIDs to filter by' }, groupIdInput: { type: 'string', description: 'Group UUID for get/update/delete group' }, groupName: { type: 'string', description: 'Group name' }, groupMemberUserIds: { type: 'json', description: 'Initial member user UUIDs for new group' }, @@ -614,7 +710,10 @@ Do not include any explanations, markdown formatting, or other text outside the description: 'List of runs with runId, status, runUrl, startTime, endTime, elapsedTime, projectVersion', }, - users: { type: 'json', description: 'List of users with id, name, email, role' }, + users: { + type: 'json', + description: 'List of users with id, name, email, role, lastLoginDate', + }, groups: { type: 'json', description: 'List of groups with id, name, createdAt' }, collections: { type: 'json', diff --git a/apps/sim/tools/hex/list_projects.ts b/apps/sim/tools/hex/list_projects.ts index 742b1974cab..790efcfeb26 100644 --- a/apps/sim/tools/hex/list_projects.ts +++ b/apps/sim/tools/hex/list_projects.ts @@ -32,6 +32,54 @@ export const listProjectsTool: ToolConfig visibility: 'user-or-llm', description: 'Filter users by group UUID', }, + userIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of user UUIDs to filter by', + }, + after: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Cursor to fetch the page of results after this value', + }, + before: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Cursor to fetch the page of results before this value', + }, }, request: { @@ -47,6 +65,9 @@ export const listUsersTool: ToolConfig if (params.sortBy) searchParams.set('sortBy', params.sortBy) if (params.sortDirection) searchParams.set('sortDirection', params.sortDirection) if (params.groupId) searchParams.set('groupId', params.groupId) + if (params.userIds) searchParams.set('userIds', params.userIds) + if (params.after) searchParams.set('after', params.after) + if (params.before) searchParams.set('before', params.before) const qs = searchParams.toString() return `https://app.hex.tech/api/v1/users${qs ? `?${qs}` : ''}` }, @@ -69,8 +90,11 @@ export const listUsersTool: ToolConfig name: (u.name as string) ?? null, email: (u.email as string) ?? null, role: (u.role as string) ?? null, + lastLoginDate: (u.lastLoginDate as string) ?? null, })), total: users.length, + after: data.pagination?.after ?? null, + before: data.pagination?.before ?? null, }, } }, @@ -90,9 +114,20 @@ export const listUsersTool: ToolConfig description: 'User role (ADMIN, MANAGER, EDITOR, EXPLORER, MEMBER, GUEST, EMBEDDED_USER, ANONYMOUS)', }, + lastLoginDate: { + type: 'string', + description: 'Last login timestamp', + optional: true, + }, }, }, }, total: { type: 'number', description: 'Total number of users returned' }, + after: { type: 'string', description: 'Cursor for the next page of results', optional: true }, + before: { + type: 'string', + description: 'Cursor for the previous page of results', + optional: true, + }, }, } diff --git a/apps/sim/tools/hex/types.ts b/apps/sim/tools/hex/types.ts index 251922bf964..c8fc52b2295 100644 --- a/apps/sim/tools/hex/types.ts +++ b/apps/sim/tools/hex/types.ts @@ -104,7 +104,15 @@ export interface HexListProjectsParams { apiKey: string limit?: number includeArchived?: boolean + includeComponents?: boolean + includeTrashed?: boolean statusFilter?: string + creatorEmail?: string + ownerEmail?: string + collectionId?: string + categories?: string + sortBy?: string + sortDirection?: string after?: string before?: string } @@ -271,6 +279,9 @@ export interface HexListUsersParams { sortBy?: string sortDirection?: string groupId?: string + userIds?: string + after?: string + before?: string } export interface HexListUsersResponse extends ToolResponse { @@ -280,8 +291,11 @@ export interface HexListUsersResponse extends ToolResponse { name: string email: string role: string + lastLoginDate: string | null }> total: number + after: string | null + before: string | null } } From 1d237ee6a09f4e72d999e274a3a1be6a2953b31f Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 2 Jul 2026 10:09:50 -0700 Subject: [PATCH 5/7] fix(hex): trim remaining user-suppliable IDs for consistency Trim collectionId (list_projects filter), viewId (run_project body), and group member UUIDs (create_group/update_group) to match the .trim() convention already applied to path-segment IDs elsewhere. --- apps/sim/tools/hex/create_group.ts | 2 +- apps/sim/tools/hex/list_projects.ts | 2 +- apps/sim/tools/hex/run_project.ts | 2 +- apps/sim/tools/hex/update_group.ts | 6 ++++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/sim/tools/hex/create_group.ts b/apps/sim/tools/hex/create_group.ts index 1dc65f3a8ca..a3fe2fd71d6 100644 --- a/apps/sim/tools/hex/create_group.ts +++ b/apps/sim/tools/hex/create_group.ts @@ -48,7 +48,7 @@ export const createGroupTool: ToolConfig ({ id })) } + body.members = { users: userIds.map((id) => ({ id: id.trim() })) } } return body }, diff --git a/apps/sim/tools/hex/list_projects.ts b/apps/sim/tools/hex/list_projects.ts index 790efcfeb26..ef298159687 100644 --- a/apps/sim/tools/hex/list_projects.ts +++ b/apps/sim/tools/hex/list_projects.ts @@ -104,7 +104,7 @@ export const listProjectsTool: ToolConfig = {} if (params.addUserIds) { - members.add = { users: parseIds(params.addUserIds).map((id) => ({ id })) } + members.add = { users: parseIds(params.addUserIds).map((id) => ({ id: id.trim() })) } } if (params.removeUserIds) { - members.remove = { users: parseIds(params.removeUserIds).map((id) => ({ id })) } + members.remove = { + users: parseIds(params.removeUserIds).map((id) => ({ id: id.trim() })), + } } body.members = members } From 13a682f8f2393d72ffbfba70ccc9f19740081fa3 Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 2 Jul 2026 10:20:21 -0700 Subject: [PATCH 6/7] fix(hex): validate parsed JSON is actually an array before iterating categories, memberUserIds, addUserIds, and removeUserIds could parse as valid JSON that isn't an array (e.g. an object), which would throw an opaque "not iterable"/"map is not a function" error deeper in the call. Validate Array.isArray after parsing and fail with a clear message instead. --- apps/sim/tools/hex/create_group.ts | 9 ++++++--- apps/sim/tools/hex/list_projects.ts | 7 +++++-- apps/sim/tools/hex/update_group.ts | 9 ++++++++- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/apps/sim/tools/hex/create_group.ts b/apps/sim/tools/hex/create_group.ts index a3fe2fd71d6..85c91aaca90 100644 --- a/apps/sim/tools/hex/create_group.ts +++ b/apps/sim/tools/hex/create_group.ts @@ -39,16 +39,19 @@ export const createGroupTool: ToolConfig { const body: Record = { name: params.name } if (params.memberUserIds) { - let userIds: string[] + let userIds: unknown try { userIds = typeof params.memberUserIds === 'string' ? JSON.parse(params.memberUserIds) - : (params.memberUserIds as string[]) + : params.memberUserIds } catch { throw new Error('memberUserIds must be a valid JSON array of user UUID strings') } - body.members = { users: userIds.map((id) => ({ id: id.trim() })) } + if (!Array.isArray(userIds)) { + throw new Error('memberUserIds must be a valid JSON array of user UUID strings') + } + body.members = { users: userIds.map((id: string) => ({ id: id.trim() })) } } return body }, diff --git a/apps/sim/tools/hex/list_projects.ts b/apps/sim/tools/hex/list_projects.ts index ef298159687..280b3efc5ab 100644 --- a/apps/sim/tools/hex/list_projects.ts +++ b/apps/sim/tools/hex/list_projects.ts @@ -106,15 +106,18 @@ export const listProjectsTool: ToolConfig { + let parsed: unknown try { - return (typeof value === 'string' ? JSON.parse(value) : value) as string[] + parsed = typeof value === 'string' ? JSON.parse(value) : value } catch { throw new Error( 'addUserIds/removeUserIds must be a valid JSON array of user UUID strings' ) } + if (!Array.isArray(parsed)) { + throw new Error( + 'addUserIds/removeUserIds must be a valid JSON array of user UUID strings' + ) + } + return parsed } if (params.addUserIds || params.removeUserIds) { From d34d4476d3b16e72f35ae997d6681b5858b0f64e Mon Sep 17 00:00:00 2001 From: waleed Date: Thu, 2 Jul 2026 10:31:00 -0700 Subject: [PATCH 7/7] fix(hex): validate array element types, send explicit ALL trigger filter - notifications now gets the same Array.isArray guard already applied to categories/memberUserIds/addUserIds/removeUserIds - member/category array elements are now validated as strings before .trim()/append, instead of crashing on non-string entries - runTriggerFilter "All" option now sends the documented ALL enum value explicitly instead of relying on omission --- apps/sim/blocks/blocks/hex.ts | 4 ++-- apps/sim/tools/hex/create_group.ts | 2 +- apps/sim/tools/hex/list_projects.ts | 2 +- apps/sim/tools/hex/run_project.ts | 7 ++++++- apps/sim/tools/hex/update_group.ts | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/sim/blocks/blocks/hex.ts b/apps/sim/blocks/blocks/hex.ts index 0d4cc2605f7..76cf338782c 100644 --- a/apps/sim/blocks/blocks/hex.ts +++ b/apps/sim/blocks/blocks/hex.ts @@ -173,12 +173,12 @@ Example: title: 'Trigger Filter', type: 'dropdown', options: [ - { label: 'All', id: '' }, + { label: 'All', id: 'ALL' }, { label: 'API', id: 'API' }, { label: 'Scheduled', id: 'SCHEDULED' }, { label: 'App Refresh', id: 'APP_REFRESH' }, ], - value: () => '', + value: () => 'ALL', condition: { field: 'operation', value: 'get_project_runs' }, mode: 'advanced', }, diff --git a/apps/sim/tools/hex/create_group.ts b/apps/sim/tools/hex/create_group.ts index 85c91aaca90..cac1e503b3e 100644 --- a/apps/sim/tools/hex/create_group.ts +++ b/apps/sim/tools/hex/create_group.ts @@ -48,7 +48,7 @@ export const createGroupTool: ToolConfig typeof id === 'string')) { throw new Error('memberUserIds must be a valid JSON array of user UUID strings') } body.members = { users: userIds.map((id: string) => ({ id: id.trim() })) } diff --git a/apps/sim/tools/hex/list_projects.ts b/apps/sim/tools/hex/list_projects.ts index 280b3efc5ab..889cbd982e9 100644 --- a/apps/sim/tools/hex/list_projects.ts +++ b/apps/sim/tools/hex/list_projects.ts @@ -115,7 +115,7 @@ export const listProjectsTool: ToolConfig typeof c === 'string')) { throw new Error('categories must be a valid JSON array of category name strings') } for (const category of categories) { diff --git a/apps/sim/tools/hex/run_project.ts b/apps/sim/tools/hex/run_project.ts index 9ae2e12b6a0..6d1a9ff6022 100644 --- a/apps/sim/tools/hex/run_project.ts +++ b/apps/sim/tools/hex/run_project.ts @@ -95,14 +95,19 @@ export const runProjectTool: ToolConfig typeof id === 'string')) { throw new Error( 'addUserIds/removeUserIds must be a valid JSON array of user UUID strings' )