Compare commits

..

2 Commits

Author SHA1 Message Date
waleed
f83d9ee5a2 ack comments 2026-02-05 18:01:33 -08:00
waleed
1cfa12538e fix(cmdk): clean up search modal input handling 2026-02-05 14:32:09 -08:00
45 changed files with 219 additions and 1107 deletions

View File

@@ -320,7 +320,6 @@ Search for issues in Linear using full-text search
| `teamId` | string | No | Filter by team ID |
| `includeArchived` | boolean | No | Include archived issues in search results |
| `first` | number | No | Number of results to return \(default: 50\) |
| `after` | string | No | Cursor for pagination |
#### Output
@@ -755,10 +754,6 @@ List all labels in Linear workspace or team
| ↳ `name` | string | Label name |
| ↳ `color` | string | Label color \(hex\) |
| ↳ `description` | string | Label description |
| ↳ `isGroup` | boolean | Whether this label is a group |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last update timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
| ↳ `team` | object | Team object |
| ↳ `id` | string | Team ID |
| ↳ `name` | string | Team name |
@@ -785,10 +780,6 @@ Create a new label in Linear
| ↳ `name` | string | Label name |
| ↳ `color` | string | Label color \(hex\) |
| ↳ `description` | string | Label description |
| ↳ `isGroup` | boolean | Whether this label is a group |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last update timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
| ↳ `team` | object | Team object |
| ↳ `id` | string | Team ID |
| ↳ `name` | string | Team name |
@@ -815,10 +806,6 @@ Update an existing label in Linear
| ↳ `name` | string | Label name |
| ↳ `color` | string | Label color \(hex\) |
| ↳ `description` | string | Label description |
| ↳ `isGroup` | boolean | Whether this label is a group |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last update timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
| ↳ `team` | object | Team object |
| ↳ `id` | string | Team ID |
| ↳ `name` | string | Team name |
@@ -862,13 +849,9 @@ List all workflow states (statuses) in Linear
| `states` | array | Array of workflow states |
| ↳ `id` | string | State ID |
| ↳ `name` | string | State name \(e.g., "Todo", "In Progress"\) |
| ↳ `description` | string | State description |
| ↳ `type` | string | State type \(triage, backlog, unstarted, started, completed, canceled\) |
| ↳ `type` | string | State type \(unstarted, started, completed, canceled\) |
| ↳ `color` | string | State color \(hex\) |
| ↳ `position` | number | State position in workflow |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last update timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
| ↳ `team` | object | Team object |
| ↳ `id` | string | Team ID |
| ↳ `name` | string | Team name |
@@ -894,17 +877,11 @@ Create a new workflow state (status) in Linear
| --------- | ---- | ----------- |
| `state` | object | The created workflow state |
| ↳ `id` | string | State ID |
| ↳ `name` | string | State name \(e.g., "Todo", "In Progress"\) |
| ↳ `description` | string | State description |
| ↳ `type` | string | State type \(triage, backlog, unstarted, started, completed, canceled\) |
| ↳ `color` | string | State color \(hex\) |
| ↳ `position` | number | State position in workflow |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last update timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
| ↳ `team` | object | Team object |
| ↳ `id` | string | Team ID |
| ↳ `name` | string | Team name |
| ↳ `name` | string | State name |
| ↳ `type` | string | State type |
| ↳ `color` | string | State color |
| ↳ `position` | number | State position |
| ↳ `team` | object | Team this state belongs to |
### `linear_update_workflow_state`
@@ -926,17 +903,10 @@ Update an existing workflow state in Linear
| --------- | ---- | ----------- |
| `state` | object | The updated workflow state |
| ↳ `id` | string | State ID |
| ↳ `name` | string | State name \(e.g., "Todo", "In Progress"\) |
| ↳ `description` | string | State description |
| ↳ `type` | string | State type \(triage, backlog, unstarted, started, completed, canceled\) |
| ↳ `color` | string | State color \(hex\) |
| ↳ `position` | number | State position in workflow |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last update timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
| ↳ `team` | object | Team object |
| ↳ `id` | string | Team ID |
| ↳ `name` | string | Team name |
| ↳ `name` | string | State name |
| ↳ `type` | string | State type |
| ↳ `color` | string | State color |
| ↳ `position` | number | State position |
### `linear_list_cycles`
@@ -965,7 +935,6 @@ List cycles (sprints/iterations) in Linear
| ↳ `endsAt` | string | End date \(ISO 8601\) |
| ↳ `completedAt` | string | Completion date \(ISO 8601\) |
| ↳ `progress` | number | Progress percentage \(0-1\) |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `team` | object | Team object |
| ↳ `id` | string | Team ID |
| ↳ `name` | string | Team name |
@@ -992,7 +961,6 @@ Get a single cycle by ID from Linear
| ↳ `endsAt` | string | End date \(ISO 8601\) |
| ↳ `completedAt` | string | Completion date \(ISO 8601\) |
| ↳ `progress` | number | Progress percentage \(0-1\) |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `team` | object | Team object |
| ↳ `id` | string | Team ID |
| ↳ `name` | string | Team name |
@@ -1018,14 +986,9 @@ Create a new cycle (sprint/iteration) in Linear
| ↳ `id` | string | Cycle ID |
| ↳ `number` | number | Cycle number |
| ↳ `name` | string | Cycle name |
| ↳ `startsAt` | string | Start date \(ISO 8601\) |
| ↳ `endsAt` | string | End date \(ISO 8601\) |
| ↳ `completedAt` | string | Completion date \(ISO 8601\) |
| ↳ `progress` | number | Progress percentage \(0-1\) |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `team` | object | Team object |
| ↳ `id` | string | Team ID |
| ↳ `name` | string | Team name |
| ↳ `startsAt` | string | Start date |
| ↳ `endsAt` | string | End date |
| ↳ `team` | object | Team this cycle belongs to |
### `linear_get_active_cycle`
@@ -1045,14 +1008,10 @@ Get the currently active cycle for a team
| ↳ `id` | string | Cycle ID |
| ↳ `number` | number | Cycle number |
| ↳ `name` | string | Cycle name |
| ↳ `startsAt` | string | Start date \(ISO 8601\) |
| ↳ `endsAt` | string | End date \(ISO 8601\) |
| ↳ `completedAt` | string | Completion date \(ISO 8601\) |
| ↳ `progress` | number | Progress percentage \(0-1\) |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `team` | object | Team object |
| ↳ `id` | string | Team ID |
| ↳ `name` | string | Team name |
| ↳ `startsAt` | string | Start date |
| ↳ `endsAt` | string | End date |
| ↳ `progress` | number | Progress percentage |
| ↳ `team` | object | Team this cycle belongs to |
### `linear_create_attachment`
@@ -1375,12 +1334,8 @@ Create a new customer in Linear
| ↳ `domains` | array | Associated domains |
| ↳ `externalIds` | array | External IDs from other systems |
| ↳ `logoUrl` | string | Logo URL |
| ↳ `slugId` | string | Unique URL slug |
| ↳ `approximateNeedCount` | number | Number of customer needs |
| ↳ `revenue` | number | Annual revenue |
| ↳ `size` | number | Organization size |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last update timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
### `linear_list_customers`
@@ -1408,12 +1363,8 @@ List all customers in Linear
| ↳ `domains` | array | Associated domains |
| ↳ `externalIds` | array | External IDs from other systems |
| ↳ `logoUrl` | string | Logo URL |
| ↳ `slugId` | string | Unique URL slug |
| ↳ `approximateNeedCount` | number | Number of customer needs |
| ↳ `revenue` | number | Annual revenue |
| ↳ `size` | number | Organization size |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last update timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
### `linear_create_customer_request`
@@ -1529,12 +1480,8 @@ Get a single customer by ID in Linear
| ↳ `domains` | array | Associated domains |
| ↳ `externalIds` | array | External IDs from other systems |
| ↳ `logoUrl` | string | Logo URL |
| ↳ `slugId` | string | Unique URL slug |
| ↳ `approximateNeedCount` | number | Number of customer needs |
| ↳ `revenue` | number | Annual revenue |
| ↳ `size` | number | Organization size |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last update timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
### `linear_update_customer`
@@ -1566,12 +1513,8 @@ Update a customer in Linear
| ↳ `domains` | array | Associated domains |
| ↳ `externalIds` | array | External IDs from other systems |
| ↳ `logoUrl` | string | Logo URL |
| ↳ `slugId` | string | Unique URL slug |
| ↳ `approximateNeedCount` | number | Number of customer needs |
| ↳ `revenue` | number | Annual revenue |
| ↳ `size` | number | Organization size |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last update timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
### `linear_delete_customer`
@@ -1617,8 +1560,8 @@ Create a new customer status in Linear
| --------- | ---- | -------- | ----------- |
| `name` | string | Yes | Customer status name |
| `color` | string | Yes | Status color \(hex code\) |
| `description` | string | No | Status description |
| `displayName` | string | No | Display name for the status |
| `description` | string | No | Status description |
| `position` | number | No | Position in status list |
#### Output
@@ -1628,12 +1571,11 @@ Create a new customer status in Linear
| `customerStatus` | object | The created customer status |
| ↳ `id` | string | Customer status ID |
| ↳ `name` | string | Status name |
| ↳ `displayName` | string | Display name |
| ↳ `description` | string | Status description |
| ↳ `color` | string | Status color \(hex\) |
| ↳ `position` | number | Position in list |
| ↳ `type` | string | Status type \(active, inactive\) |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last updated timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
### `linear_update_customer_status`
@@ -1647,8 +1589,8 @@ Update a customer status in Linear
| `statusId` | string | Yes | Customer status ID to update |
| `name` | string | No | Updated status name |
| `color` | string | No | Updated status color |
| `description` | string | No | Updated description |
| `displayName` | string | No | Updated display name |
| `description` | string | No | Updated description |
| `position` | number | No | Updated position |
#### Output
@@ -1656,15 +1598,6 @@ Update a customer status in Linear
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `customerStatus` | object | The updated customer status |
| ↳ `id` | string | Customer status ID |
| ↳ `name` | string | Status name |
| ↳ `description` | string | Status description |
| ↳ `color` | string | Status color \(hex\) |
| ↳ `position` | number | Position in list |
| ↳ `type` | string | Status type \(active, inactive\) |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last updated timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
### `linear_delete_customer_status`
@@ -1690,25 +1623,19 @@ List all customer statuses in Linear
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `first` | number | No | Number of statuses to return \(default: 50\) |
| `after` | string | No | Cursor for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `pageInfo` | object | Pagination information |
| ↳ `hasNextPage` | boolean | Whether there are more results |
| ↳ `endCursor` | string | Cursor for the next page |
| `customerStatuses` | array | List of customer statuses |
| ↳ `id` | string | Customer status ID |
| ↳ `name` | string | Status name |
| ↳ `displayName` | string | Display name |
| ↳ `description` | string | Status description |
| ↳ `color` | string | Status color \(hex\) |
| ↳ `position` | number | Position in list |
| ↳ `type` | string | Status type \(active, inactive\) |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last updated timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
### `linear_create_customer_tier`
@@ -1784,16 +1711,11 @@ List all customer tiers in Linear
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `first` | number | No | Number of tiers to return \(default: 50\) |
| `after` | string | No | Cursor for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `pageInfo` | object | Pagination information |
| ↳ `hasNextPage` | boolean | Whether there are more results |
| ↳ `endCursor` | string | Cursor for the next page |
| `customerTiers` | array | List of customer tiers |
| ↳ `id` | string | Customer tier ID |
| ↳ `name` | string | Tier name |
@@ -1839,14 +1761,6 @@ Create a new project label in Linear
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `projectLabel` | object | The created project label |
| ↳ `id` | string | Project label ID |
| ↳ `name` | string | Label name |
| ↳ `description` | string | Label description |
| ↳ `color` | string | Label color \(hex\) |
| ↳ `isGroup` | boolean | Whether this label is a group |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last update timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
### `linear_update_project_label`
@@ -1866,14 +1780,6 @@ Update a project label in Linear
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `projectLabel` | object | The updated project label |
| ↳ `id` | string | Project label ID |
| ↳ `name` | string | Label name |
| ↳ `description` | string | Label description |
| ↳ `color` | string | Label color \(hex\) |
| ↳ `isGroup` | boolean | Whether this label is a group |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last update timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
### `linear_delete_project_label`
@@ -1900,25 +1806,12 @@ List all project labels in Linear
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | No | Optional project ID to filter labels for a specific project |
| `first` | number | No | Number of labels to return \(default: 50\) |
| `after` | string | No | Cursor for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `pageInfo` | object | Pagination information |
| ↳ `hasNextPage` | boolean | Whether there are more results |
| ↳ `endCursor` | string | Cursor for the next page |
| `projectLabels` | array | List of project labels |
| ↳ `id` | string | Project label ID |
| ↳ `name` | string | Label name |
| ↳ `description` | string | Label description |
| ↳ `color` | string | Label color \(hex\) |
| ↳ `isGroup` | boolean | Whether this label is a group |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last update timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
### `linear_add_label_to_project`
@@ -1974,16 +1867,6 @@ Create a new project milestone in Linear
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `projectMilestone` | object | The created project milestone |
| ↳ `id` | string | Project milestone ID |
| ↳ `name` | string | Milestone name |
| ↳ `description` | string | Milestone description |
| ↳ `projectId` | string | Project ID |
| ↳ `targetDate` | string | Target date \(YYYY-MM-DD\) |
| ↳ `progress` | number | Progress percentage \(0-1\) |
| ↳ `sortOrder` | number | Sort order within the project |
| ↳ `status` | string | Milestone status \(done, next, overdue, unstarted\) |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
### `linear_update_project_milestone`
@@ -2003,16 +1886,6 @@ Update a project milestone in Linear
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `projectMilestone` | object | The updated project milestone |
| ↳ `id` | string | Project milestone ID |
| ↳ `name` | string | Milestone name |
| ↳ `description` | string | Milestone description |
| ↳ `projectId` | string | Project ID |
| ↳ `targetDate` | string | Target date \(YYYY-MM-DD\) |
| ↳ `progress` | number | Progress percentage \(0-1\) |
| ↳ `sortOrder` | number | Sort order within the project |
| ↳ `status` | string | Milestone status \(done, next, overdue, unstarted\) |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
### `linear_delete_project_milestone`
@@ -2039,27 +1912,12 @@ List all milestones for a project in Linear
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Project ID to list milestones for |
| `first` | number | No | Number of milestones to return \(default: 50\) |
| `after` | string | No | Cursor for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `pageInfo` | object | Pagination information |
| ↳ `hasNextPage` | boolean | Whether there are more results |
| ↳ `endCursor` | string | Cursor for the next page |
| `projectMilestones` | array | List of project milestones |
| ↳ `id` | string | Project milestone ID |
| ↳ `name` | string | Milestone name |
| ↳ `description` | string | Milestone description |
| ↳ `projectId` | string | Project ID |
| ↳ `targetDate` | string | Target date \(YYYY-MM-DD\) |
| ↳ `progress` | number | Progress percentage \(0-1\) |
| ↳ `sortOrder` | number | Sort order within the project |
| ↳ `status` | string | Milestone status \(done, next, overdue, unstarted\) |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
### `linear_create_project_status`
@@ -2081,16 +1939,6 @@ Create a new project status in Linear
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `projectStatus` | object | The created project status |
| ↳ `id` | string | Project status ID |
| ↳ `name` | string | Status name |
| ↳ `description` | string | Status description |
| ↳ `color` | string | Status color \(hex\) |
| ↳ `indefinite` | boolean | Whether this status is indefinite |
| ↳ `position` | number | Position in list |
| ↳ `type` | string | Status type \(backlog, planned, started, paused, completed, canceled\) |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last updated timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
### `linear_update_project_status`
@@ -2112,16 +1960,6 @@ Update a project status in Linear
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `projectStatus` | object | The updated project status |
| ↳ `id` | string | Project status ID |
| ↳ `name` | string | Status name |
| ↳ `description` | string | Status description |
| ↳ `color` | string | Status color \(hex\) |
| ↳ `indefinite` | boolean | Whether this status is indefinite |
| ↳ `position` | number | Position in list |
| ↳ `type` | string | Status type \(backlog, planned, started, paused, completed, canceled\) |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last updated timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |
### `linear_delete_project_status`
@@ -2147,26 +1985,11 @@ List all project statuses in Linear
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `first` | number | No | Number of statuses to return \(default: 50\) |
| `after` | string | No | Cursor for pagination |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `pageInfo` | object | Pagination information |
| ↳ `hasNextPage` | boolean | Whether there are more results |
| ↳ `endCursor` | string | Cursor for the next page |
| `projectStatuses` | array | List of project statuses |
| ↳ `id` | string | Project status ID |
| ↳ `name` | string | Status name |
| ↳ `description` | string | Status description |
| ↳ `color` | string | Status color \(hex\) |
| ↳ `indefinite` | boolean | Whether this status is indefinite |
| ↳ `position` | number | Position in list |
| ↳ `type` | string | Status type \(backlog, planned, started, paused, completed, canceled\) |
| ↳ `createdAt` | string | Creation timestamp \(ISO 8601\) |
| ↳ `updatedAt` | string | Last updated timestamp \(ISO 8601\) |
| ↳ `archivedAt` | string | Archive timestamp \(ISO 8601\) |

View File

@@ -491,13 +491,6 @@ export function useWorkflowExecution() {
updateActiveBlocks(data.blockId, false)
setBlockRunStatus(data.blockId, 'error')
executedBlockIds.add(data.blockId)
accumulatedBlockStates.set(data.blockId, {
output: { error: data.error },
executed: true,
executionTime: data.durationMs || 0,
})
accumulatedBlockLogs.push(
createBlockLogEntry(data, { success: false, output: {}, error: data.error })
)

View File

@@ -349,15 +349,7 @@ export function PreviewWorkflow({
if (block.type === 'loop' || block.type === 'parallel') {
const isSelected = selectedBlockId === blockId
const dimensions = calculateContainerDimensions(blockId, workflowState.blocks)
// Check for direct error on the subflow block itself (e.g., loop resolution errors)
// before falling back to children-derived status
const directExecution = blockExecutionMap.get(blockId)
const subflowExecutionStatus: ExecutionStatus | undefined =
directExecution?.status === 'error'
? 'error'
: (getSubflowExecutionStatus(blockId) ??
(directExecution ? (directExecution.status as ExecutionStatus) : undefined))
const subflowExecutionStatus = getSubflowExecutionStatus(blockId)
nodeArray.push({
id: blockId,

View File

@@ -79,7 +79,9 @@ export function SearchModal({
const router = useRouter()
const workspaceId = params.workspaceId as string
const inputRef = useRef<HTMLInputElement>(null)
const listRef = useRef<HTMLDivElement>(null)
const [mounted, setMounted] = useState(false)
const [search, setSearch] = useState('')
const openSettingsModal = useSettingsModalStore((state) => state.openModal)
const { config: permissionConfig } = usePermissionConfig()
@@ -142,41 +144,18 @@ export function SearchModal({
)
useEffect(() => {
if (open && inputRef.current) {
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype,
'value'
)?.set
if (nativeInputValueSetter) {
nativeInputValueSetter.call(inputRef.current, '')
inputRef.current.dispatchEvent(new Event('input', { bubbles: true }))
}
inputRef.current.focus()
if (open) {
setSearch('')
inputRef.current?.focus()
}
}, [open])
const handleSearchChange = useCallback(() => {
requestAnimationFrame(() => {
const list = document.querySelector('[cmdk-list]')
if (list) {
list.scrollTop = 0
}
})
}, [])
useEffect(() => {
if (!open) return
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
e.preventDefault()
onOpenChange(false)
}
const handleSearchChange = useCallback((value: string) => {
setSearch(value)
if (listRef.current) {
listRef.current.scrollTop = 0
}
document.addEventListener('keydown', handleKeyDown)
return () => document.removeEventListener('keydown', handleKeyDown)
}, [open, onOpenChange])
}, [])
const handleBlockSelect = useCallback(
(block: SearchBlockItem, type: 'block' | 'trigger' | 'tool') => {
@@ -264,7 +243,7 @@ export function SearchModal({
{/* Overlay */}
<div
className={cn(
'fixed inset-0 z-40 bg-[#E4E4E4]/50 backdrop-blur-[0.75px] transition-opacity duration-100 dark:bg-[#0D0D0D]/50',
'fixed inset-0 z-40 bg-[#E4E4E4]/50 transition-opacity duration-100 dark:bg-[#0D0D0D]/50',
open ? 'opacity-100' : 'pointer-events-none opacity-0'
)}
onClick={() => onOpenChange(false)}
@@ -281,16 +260,31 @@ export function SearchModal({
'-translate-x-1/2 fixed top-[15%] left-1/2 z-50 w-[500px] overflow-hidden rounded-[12px] border border-[var(--border)] bg-[var(--surface-4)] shadow-lg',
open ? 'visible opacity-100' : 'invisible opacity-0'
)}
onKeyDown={(e) => {
if (e.key === 'Escape') {
e.preventDefault()
onOpenChange(false)
}
}}
>
<Command label='Search' filter={customFilter}>
<Command.Input
ref={inputRef}
autoFocus
value={search}
onValueChange={handleSearchChange}
onKeyDown={(e) => {
if (e.key === 'Escape') {
e.preventDefault()
onOpenChange(false)
}
}}
placeholder='Search anything...'
className='w-full border-0 border-[var(--border)] border-b bg-transparent px-[12px] py-[10px] font-base text-[15px] text-[var(--text-primary)] placeholder:text-[var(--text-secondary)] focus:outline-none'
/>
<Command.List className='scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent max-h-[400px] overflow-y-auto p-[8px]'>
<Command.List
ref={listRef}
className='scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent max-h-[400px] overflow-y-auto p-[8px]'
>
<Command.Empty className='flex items-center justify-center px-[16px] py-[24px] text-[15px] text-[var(--text-subtle)]'>
No results found.
</Command.Empty>

View File

@@ -21,7 +21,6 @@ import { executeWorkflowCore } from '@/lib/workflows/executor/execution-core'
import { PauseResumeManager } from '@/lib/workflows/executor/human-in-the-loop-manager'
import { loadDeployedWorkflowState } from '@/lib/workflows/persistence/utils'
import { getWorkflowById } from '@/lib/workflows/utils'
import { getBlock } from '@/blocks'
import { ExecutionSnapshot } from '@/executor/execution/snapshot'
import type { ExecutionMetadata } from '@/executor/execution/types'
import { hasExecutionResult } from '@/executor/utils/errors'
@@ -75,21 +74,8 @@ async function processTriggerFileOutputs(
logger.error(`[${context.requestId}] Error processing ${currentPath}:`, error)
processed[key] = val
}
} else if (
outputDef &&
typeof outputDef === 'object' &&
(outputDef.type === 'object' || outputDef.type === 'json') &&
outputDef.properties
) {
// Explicit object schema with properties - recurse into properties
processed[key] = await processTriggerFileOutputs(
val,
outputDef.properties,
context,
currentPath
)
} else if (outputDef && typeof outputDef === 'object' && !outputDef.type) {
// Nested object in schema (flat pattern) - recurse with the nested schema
// Nested object in schema - recurse with the nested schema
processed[key] = await processTriggerFileOutputs(val, outputDef, context, currentPath)
} else {
// Not a file output - keep as is
@@ -419,23 +405,11 @@ async function executeWebhookJobInternal(
const rawSelectedTriggerId = triggerBlock?.subBlocks?.selectedTriggerId?.value
const rawTriggerId = triggerBlock?.subBlocks?.triggerId?.value
let resolvedTriggerId = [rawSelectedTriggerId, rawTriggerId].find(
const resolvedTriggerId = [rawSelectedTriggerId, rawTriggerId].find(
(candidate): candidate is string =>
typeof candidate === 'string' && isTriggerValid(candidate)
)
if (!resolvedTriggerId) {
const blockConfig = getBlock(triggerBlock.type)
if (blockConfig?.category === 'triggers' && isTriggerValid(triggerBlock.type)) {
resolvedTriggerId = triggerBlock.type
} else if (triggerBlock.triggerMode && blockConfig?.triggers?.enabled) {
const available = blockConfig.triggers?.available?.[0]
if (available && isTriggerValid(available)) {
resolvedTriggerId = available
}
}
}
if (resolvedTriggerId) {
const triggerConfig = getTrigger(resolvedTriggerId)

View File

@@ -810,29 +810,7 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
placeholder: 'Number of items to return (default: 50)',
condition: {
field: 'operation',
value: [
'linear_read_issues',
'linear_search_issues',
'linear_list_comments',
'linear_list_projects',
'linear_list_users',
'linear_list_teams',
'linear_list_labels',
'linear_list_workflow_states',
'linear_list_cycles',
'linear_list_attachments',
'linear_list_issue_relations',
'linear_list_favorites',
'linear_list_project_updates',
'linear_list_notifications',
'linear_list_customer_statuses',
'linear_list_customer_tiers',
'linear_list_customers',
'linear_list_customer_requests',
'linear_list_project_labels',
'linear_list_project_milestones',
'linear_list_project_statuses',
],
value: ['linear_list_favorites'],
},
},
// Pagination - After (for list operations)
@@ -843,29 +821,7 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
placeholder: 'Cursor for pagination',
condition: {
field: 'operation',
value: [
'linear_read_issues',
'linear_search_issues',
'linear_list_comments',
'linear_list_projects',
'linear_list_users',
'linear_list_teams',
'linear_list_labels',
'linear_list_workflow_states',
'linear_list_cycles',
'linear_list_attachments',
'linear_list_issue_relations',
'linear_list_favorites',
'linear_list_project_updates',
'linear_list_notifications',
'linear_list_customers',
'linear_list_customer_requests',
'linear_list_customer_statuses',
'linear_list_customer_tiers',
'linear_list_project_labels',
'linear_list_project_milestones',
'linear_list_project_statuses',
],
value: ['linear_list_favorites'],
},
},
// Project health (for project updates)
@@ -1097,6 +1053,28 @@ Return ONLY the description text - no explanations.`,
value: ['linear_create_customer_request', 'linear_update_customer_request'],
},
},
// Pagination - first
{
id: 'first',
title: 'Limit',
type: 'short-input',
placeholder: 'Number of items (default: 50)',
condition: {
field: 'operation',
value: ['linear_list_customers', 'linear_list_customer_requests'],
},
},
// Pagination - after
{
id: 'after',
title: 'After Cursor',
type: 'short-input',
placeholder: 'Cursor for pagination',
condition: {
field: 'operation',
value: ['linear_list_customers', 'linear_list_customer_requests'],
},
},
// Customer ID for get/update/delete/merge operations
{
id: 'customerIdTarget',
@@ -1515,8 +1493,6 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
teamId: effectiveTeamId || undefined,
projectId: effectiveProjectId || undefined,
includeArchived: params.includeArchived,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_get_issue':
@@ -1582,8 +1558,6 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
query: params.query.trim(),
teamId: effectiveTeamId,
includeArchived: params.includeArchived,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_add_label_to_issue':
@@ -1633,8 +1607,6 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
return {
...baseParams,
issueId: params.issueId.trim(),
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_list_projects':
@@ -1642,8 +1614,6 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
...baseParams,
teamId: effectiveTeamId,
includeArchived: params.includeArchived,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_get_project':
@@ -1695,12 +1665,6 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
case 'linear_list_users':
case 'linear_list_teams':
return {
...baseParams,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_get_viewer':
return baseParams
@@ -1708,8 +1672,6 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
return {
...baseParams,
teamId: effectiveTeamId,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_create_label':
@@ -1747,8 +1709,6 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
return {
...baseParams,
teamId: effectiveTeamId,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_create_workflow_state':
@@ -1778,8 +1738,6 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
return {
...baseParams,
teamId: effectiveTeamId,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_get_cycle':
@@ -1843,8 +1801,6 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
return {
...baseParams,
issueId: params.issueId.trim(),
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_update_attachment':
@@ -1884,8 +1840,6 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
return {
...baseParams,
issueId: params.issueId.trim(),
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_delete_issue_relation':
@@ -1932,16 +1886,10 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
return {
...baseParams,
projectId: effectiveProjectId,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_list_notifications':
return {
...baseParams,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
return baseParams
case 'linear_update_notification':
if (!params.notificationId?.trim()) {
@@ -2070,9 +2018,9 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
return {
...baseParams,
name: params.statusName.trim(),
displayName: params.statusDisplayName?.trim() || params.statusName.trim(),
color: params.statusColor.trim(),
description: params.statusDescription?.trim() || undefined,
displayName: params.statusDisplayName?.trim() || undefined,
}
case 'linear_update_customer_status':
@@ -2083,9 +2031,9 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
...baseParams,
statusId: params.statusId.trim(),
name: params.statusName?.trim() || undefined,
displayName: params.statusDisplayName?.trim() || undefined,
color: params.statusColor?.trim() || undefined,
description: params.statusDescription?.trim() || undefined,
displayName: params.statusDisplayName?.trim() || undefined,
}
case 'linear_delete_customer_status':
@@ -2098,11 +2046,7 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
}
case 'linear_list_customer_statuses':
return {
...baseParams,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
return baseParams
// Customer Tier Operations
case 'linear_create_customer_tier':
@@ -2140,11 +2084,7 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
}
case 'linear_list_customer_tiers':
return {
...baseParams,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
return baseParams
// Project Management Operations
case 'linear_delete_project':
@@ -2195,8 +2135,6 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
return {
...baseParams,
projectId: effectiveProjectId || undefined,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_add_label_to_project':
@@ -2260,8 +2198,6 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
return {
...baseParams,
projectId: params.projectIdForMilestone.trim(),
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
// Project Status Operations
@@ -2309,11 +2245,7 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
}
case 'linear_list_project_statuses':
return {
...baseParams,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
return baseParams
default:
return baseParams
@@ -2389,9 +2321,9 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
// Customer status and tier inputs
statusId: { type: 'string', description: 'Status identifier' },
statusName: { type: 'string', description: 'Status name' },
statusDisplayName: { type: 'string', description: 'Status display name' },
statusColor: { type: 'string', description: 'Status color in hex format' },
statusDescription: { type: 'string', description: 'Status description' },
statusDisplayName: { type: 'string', description: 'Status display name' },
tierId: { type: 'string', description: 'Tier identifier' },
tierName: { type: 'string', description: 'Tier name' },
tierDisplayName: { type: 'string', description: 'Tier display name' },

View File

@@ -42,7 +42,6 @@ export const WorkflowBlock: BlockConfig = {
outputs: {
success: { type: 'boolean', description: 'Execution success status' },
childWorkflowName: { type: 'string', description: 'Child workflow name' },
childWorkflowId: { type: 'string', description: 'Child workflow ID' },
result: { type: 'json', description: 'Workflow execution result' },
error: { type: 'string', description: 'Error message' },
childTraceSpans: {

View File

@@ -41,7 +41,6 @@ export const WorkflowInputBlock: BlockConfig = {
outputs: {
success: { type: 'boolean', description: 'Execution success status' },
childWorkflowName: { type: 'string', description: 'Child workflow name' },
childWorkflowId: { type: 'string', description: 'Child workflow ID' },
result: { type: 'json', description: 'Workflow execution result' },
error: { type: 'string', description: 'Error message' },
childTraceSpans: {

View File

@@ -2478,9 +2478,6 @@ describe('EdgeManager', () => {
expect(readyNodes).toContain(otherBranchId)
expect(readyNodes).not.toContain(sentinelStartId)
// sentinel_end should NOT be ready - it's on a fully deactivated path
expect(readyNodes).not.toContain(sentinelEndId)
// afterLoop should NOT be ready - its incoming edge from sentinel_end should be deactivated
expect(readyNodes).not.toContain(afterLoopId)
@@ -2548,84 +2545,6 @@ describe('EdgeManager', () => {
expect(edgeManager.isNodeReady(afterParallelNode)).toBe(true)
})
it('should not queue loop sentinel-end when upstream condition deactivates entire loop branch', () => {
// Regression test for: upstream condition → (if) → ... many blocks ... → sentinel_start → body → sentinel_end
// → (else) → exit_block
// When condition takes "else", the deep cascade deactivation should NOT queue sentinel_end.
// Previously, sentinel_end was flagged as a cascadeTarget (terminal control node) and
// spuriously queued, causing it to attempt loop scope initialization and fail.
const conditionId = 'condition'
const intermediateId = 'intermediate'
const sentinelStartId = 'sentinel-start'
const loopBodyId = 'loop-body'
const sentinelEndId = 'sentinel-end'
const afterLoopId = 'after-loop'
const exitBlockId = 'exit-block'
const conditionNode = createMockNode(conditionId, [
{ target: intermediateId, sourceHandle: 'condition-if' },
{ target: exitBlockId, sourceHandle: 'condition-else' },
])
const intermediateNode = createMockNode(
intermediateId,
[{ target: sentinelStartId }],
[conditionId]
)
const sentinelStartNode = createMockNode(
sentinelStartId,
[{ target: loopBodyId }],
[intermediateId]
)
const loopBodyNode = createMockNode(
loopBodyId,
[{ target: sentinelEndId }],
[sentinelStartId]
)
const sentinelEndNode = createMockNode(
sentinelEndId,
[
{ target: sentinelStartId, sourceHandle: 'loop_continue' },
{ target: afterLoopId, sourceHandle: 'loop_exit' },
],
[loopBodyId]
)
const afterLoopNode = createMockNode(afterLoopId, [], [sentinelEndId])
const exitBlockNode = createMockNode(exitBlockId, [], [conditionId])
const nodes = new Map<string, DAGNode>([
[conditionId, conditionNode],
[intermediateId, intermediateNode],
[sentinelStartId, sentinelStartNode],
[loopBodyId, loopBodyNode],
[sentinelEndId, sentinelEndNode],
[afterLoopId, afterLoopNode],
[exitBlockId, exitBlockNode],
])
const dag = createMockDAG(nodes)
const edgeManager = new EdgeManager(dag)
const readyNodes = edgeManager.processOutgoingEdges(conditionNode, {
selectedOption: 'else',
})
// Only exitBlock should be ready
expect(readyNodes).toContain(exitBlockId)
// Nothing on the deactivated path should be queued
expect(readyNodes).not.toContain(intermediateId)
expect(readyNodes).not.toContain(sentinelStartId)
expect(readyNodes).not.toContain(loopBodyId)
expect(readyNodes).not.toContain(sentinelEndId)
expect(readyNodes).not.toContain(afterLoopId)
})
it('should still correctly handle normal loop exit (not deactivate when loop runs)', () => {
// When a loop actually executes and exits normally, after_loop should become ready
const sentinelStartId = 'sentinel-start'

View File

@@ -71,13 +71,7 @@ export class EdgeManager {
for (const targetId of cascadeTargets) {
if (!readyNodes.includes(targetId) && !activatedTargets.includes(targetId)) {
// Only queue cascade terminal control nodes when ALL outgoing edges from the
// current node were deactivated (dead-end scenario). When some edges are
// activated, terminal control nodes on deactivated branches should NOT be
// queued - they will be reached through the normal activated path's completion.
// This prevents loop/parallel sentinels on fully deactivated paths (e.g., an
// upstream condition took a different branch) from being spuriously executed.
if (activatedTargets.length === 0 && this.isTargetReady(targetId)) {
if (this.isTargetReady(targetId)) {
readyNodes.push(targetId)
}
}

View File

@@ -1,7 +1,3 @@
import {
extractFieldsFromSchema,
parseResponseFormatSafely,
} from '@/lib/core/utils/response-format'
import { normalizeInputFormatValue } from '@/lib/workflows/input-format'
import { isTriggerBehavior, normalizeName } from '@/executor/constants'
import type { ExecutionContext } from '@/executor/types'
@@ -47,53 +43,23 @@ function getInputFormatFields(block: SerializedBlock): OutputSchema {
const schema: OutputSchema = {}
for (const field of inputFormat) {
if (!field.name) continue
schema[field.name] = { type: field.type || 'any' }
schema[field.name] = {
type: (field.type || 'any') as 'string' | 'number' | 'boolean' | 'object' | 'array' | 'any',
}
}
return schema
}
function getEvaluatorMetricsSchema(block: SerializedBlock): OutputSchema | undefined {
if (block.metadata?.id !== 'evaluator') return undefined
const metrics = block.config?.params?.metrics
if (!Array.isArray(metrics) || metrics.length === 0) return undefined
const validMetrics = metrics.filter(
(m: { name?: string }) => m?.name && typeof m.name === 'string'
)
if (validMetrics.length === 0) return undefined
const schema: OutputSchema = { ...(block.outputs as OutputSchema) }
for (const metric of validMetrics) {
schema[metric.name.toLowerCase()] = { type: 'number' }
}
return schema
}
function getResponseFormatSchema(block: SerializedBlock): OutputSchema | undefined {
const responseFormatValue = block.config?.params?.responseFormat
if (!responseFormatValue) return undefined
const parsed = parseResponseFormatSafely(responseFormatValue, block.id)
if (!parsed) return undefined
const fields = extractFieldsFromSchema(parsed)
if (fields.length === 0) return undefined
const schema: OutputSchema = {}
for (const field of fields) {
schema[field.name] = { type: field.type || 'any' }
}
return schema
}
export function getBlockSchema(
block: SerializedBlock,
toolConfig?: ToolConfig
): OutputSchema | undefined {
const blockType = block.metadata?.id
// For blocks that expose inputFormat as outputs, always merge them
// This includes both triggers (start_trigger, generic_webhook) and
// non-triggers (starter, human_in_the_loop) that have inputFormat
if (
blockType &&
BLOCKS_WITH_INPUT_FORMAT_OUTPUTS.includes(
@@ -108,16 +74,6 @@ export function getBlockSchema(
}
}
const evaluatorSchema = getEvaluatorMetricsSchema(block)
if (evaluatorSchema) {
return evaluatorSchema
}
const responseFormatSchema = getResponseFormatSchema(block)
if (responseFormatSchema) {
return responseFormatSchema
}
const isTrigger = isTriggerBehavior(block)
if (isTrigger && block.outputs && Object.keys(block.outputs).length > 0) {

View File

@@ -527,113 +527,6 @@ export async function validateTwilioSignature(
}
}
const SLACK_FILE_HOSTS = new Set(['files.slack.com', 'files-pri.slack.com'])
const SLACK_MAX_FILE_SIZE = 50 * 1024 * 1024 // 50 MB
const SLACK_MAX_FILES = 10
/**
* Downloads file attachments from Slack using the bot token.
* Returns files in the format expected by WebhookAttachmentProcessor:
* { name, data (base64 string), mimeType, size }
*
* Security:
* - Validates each url_private against allowlisted Slack file hosts
* - Uses validateUrlWithDNS + secureFetchWithPinnedIP to prevent SSRF
* - Enforces per-file size limit and max file count
*/
async function downloadSlackFiles(
rawFiles: any[],
botToken: string
): Promise<Array<{ name: string; data: string; mimeType: string; size: number }>> {
const filesToProcess = rawFiles.slice(0, SLACK_MAX_FILES)
const downloaded: Array<{ name: string; data: string; mimeType: string; size: number }> = []
for (const file of filesToProcess) {
const urlPrivate = file.url_private as string | undefined
if (!urlPrivate) {
continue
}
// Validate the URL points to a known Slack file host
let parsedUrl: URL
try {
parsedUrl = new URL(urlPrivate)
} catch {
logger.warn('Slack file has invalid url_private, skipping', { fileId: file.id })
continue
}
if (!SLACK_FILE_HOSTS.has(parsedUrl.hostname)) {
logger.warn('Slack file url_private points to unexpected host, skipping', {
fileId: file.id,
hostname: sanitizeUrlForLog(urlPrivate),
})
continue
}
// Skip files that exceed the size limit
const reportedSize = Number(file.size) || 0
if (reportedSize > SLACK_MAX_FILE_SIZE) {
logger.warn('Slack file exceeds size limit, skipping', {
fileId: file.id,
size: reportedSize,
limit: SLACK_MAX_FILE_SIZE,
})
continue
}
try {
const urlValidation = await validateUrlWithDNS(urlPrivate, 'url_private')
if (!urlValidation.isValid) {
logger.warn('Slack file url_private failed DNS validation, skipping', {
fileId: file.id,
error: urlValidation.error,
})
continue
}
const response = await secureFetchWithPinnedIP(urlPrivate, urlValidation.resolvedIP!, {
headers: { Authorization: `Bearer ${botToken}` },
})
if (!response.ok) {
logger.warn('Failed to download Slack file, skipping', {
fileId: file.id,
status: response.status,
})
continue
}
const arrayBuffer = await response.arrayBuffer()
const buffer = Buffer.from(arrayBuffer)
// Verify the actual downloaded size doesn't exceed our limit
if (buffer.length > SLACK_MAX_FILE_SIZE) {
logger.warn('Downloaded Slack file exceeds size limit, skipping', {
fileId: file.id,
actualSize: buffer.length,
limit: SLACK_MAX_FILE_SIZE,
})
continue
}
downloaded.push({
name: file.name || 'download',
data: buffer.toString('base64'),
mimeType: file.mimetype || 'application/octet-stream',
size: buffer.length,
})
} catch (error) {
logger.error('Error downloading Slack file, skipping', {
fileId: file.id,
error: error instanceof Error ? error.message : String(error),
})
}
}
return downloaded
}
/**
* Format webhook input based on provider
*/
@@ -894,44 +787,43 @@ export async function formatWebhookInput(
}
if (foundWebhook.provider === 'slack') {
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
const botToken = providerConfig.botToken as string | undefined
const includeFiles = Boolean(providerConfig.includeFiles)
const event = body?.event
const rawEvent = body?.event
if (!rawEvent) {
logger.warn('Unknown Slack event type', {
type: body?.type,
hasEvent: false,
bodyKeys: Object.keys(body || {}),
})
if (event && body?.type === 'event_callback') {
return {
event: {
event_type: event.type || '',
channel: event.channel || '',
channel_name: '',
user: event.user || '',
user_name: '',
text: event.text || '',
timestamp: event.ts || event.event_ts || '',
thread_ts: event.thread_ts || '',
team_id: body.team_id || event.team || '',
event_id: body.event_id || '',
},
}
}
const rawFiles: any[] = rawEvent?.files ?? []
const hasFiles = rawFiles.length > 0
let files: any[] = []
if (hasFiles && includeFiles && botToken) {
files = await downloadSlackFiles(rawFiles, botToken)
} else if (hasFiles && includeFiles && !botToken) {
logger.warn('Slack message has files and includeFiles is enabled, but no bot token provided')
}
logger.warn('Unknown Slack event type', {
type: body?.type,
hasEvent: !!body?.event,
bodyKeys: Object.keys(body || {}),
})
return {
event: {
event_type: rawEvent?.type || body?.type || 'unknown',
channel: rawEvent?.channel || '',
event_type: body?.event?.type || body?.type || 'unknown',
channel: body?.event?.channel || '',
channel_name: '',
user: rawEvent?.user || '',
user: body?.event?.user || '',
user_name: '',
text: rawEvent?.text || '',
timestamp: rawEvent?.ts || rawEvent?.event_ts || '',
thread_ts: rawEvent?.thread_ts || '',
team_id: body?.team_id || rawEvent?.team || '',
text: body?.event?.text || '',
timestamp: body?.event?.ts || '',
thread_ts: body?.event?.thread_ts || '',
team_id: body?.team_id || '',
event_id: body?.event_id || '',
hasFiles,
files,
},
}
}

View File

@@ -131,12 +131,8 @@ export const linearCreateCustomerTool: ToolConfig<
domains
externalIds
logoUrl
slugId
approximateNeedCount
revenue
size
createdAt
updatedAt
archivedAt
}
}

View File

@@ -32,18 +32,18 @@ export const linearCreateCustomerStatusTool: ToolConfig<
visibility: 'user-or-llm',
description: 'Status color (hex code)',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Status description',
},
displayName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Display name for the status',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Status description',
},
position: {
type: 'number',
required: false,
@@ -70,12 +70,12 @@ export const linearCreateCustomerStatusTool: ToolConfig<
color: params.color,
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.displayName != null && params.displayName !== '') {
input.displayName = params.displayName
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.position != null) {
input.position = params.position
}
@@ -88,12 +88,11 @@ export const linearCreateCustomerStatusTool: ToolConfig<
status {
id
name
displayName
description
color
position
type
createdAt
updatedAt
archivedAt
}
}

View File

@@ -1,5 +1,4 @@
import type { LinearCreateCycleParams, LinearCreateCycleResponse } from '@/tools/linear/types'
import { CYCLE_FULL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateCycleTool: ToolConfig<LinearCreateCycleParams, LinearCreateCycleResponse> =
@@ -73,9 +72,7 @@ export const linearCreateCycleTool: ToolConfig<LinearCreateCycleParams, LinearCr
name
startsAt
endsAt
completedAt
progress
createdAt
team {
id
name
@@ -123,7 +120,14 @@ export const linearCreateCycleTool: ToolConfig<LinearCreateCycleParams, LinearCr
cycle: {
type: 'object',
description: 'The created cycle',
properties: CYCLE_FULL_OUTPUT_PROPERTIES,
properties: {
id: { type: 'string', description: 'Cycle ID' },
number: { type: 'number', description: 'Cycle number' },
name: { type: 'string', description: 'Cycle name' },
startsAt: { type: 'string', description: 'Start date' },
endsAt: { type: 'string', description: 'End date' },
team: { type: 'object', description: 'Team this cycle belongs to' },
},
},
},
}

View File

@@ -73,10 +73,6 @@ export const linearCreateLabelTool: ToolConfig<LinearCreateLabelParams, LinearCr
name
color
description
isGroup
createdAt
updatedAt
archivedAt
team {
id
name

View File

@@ -2,7 +2,6 @@ import type {
LinearCreateProjectLabelParams,
LinearCreateProjectLabelResponse,
} from '@/tools/linear/types'
import { PROJECT_LABEL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateProjectLabelTool: ToolConfig<
@@ -94,7 +93,6 @@ export const linearCreateProjectLabelTool: ToolConfig<
color
isGroup
createdAt
updatedAt
archivedAt
}
}
@@ -139,7 +137,6 @@ export const linearCreateProjectLabelTool: ToolConfig<
projectLabel: {
type: 'object',
description: 'The created project label',
properties: PROJECT_LABEL_OUTPUT_PROPERTIES,
},
},
}

View File

@@ -2,7 +2,6 @@ import type {
LinearCreateProjectMilestoneParams,
LinearCreateProjectMilestoneResponse,
} from '@/tools/linear/types'
import { PROJECT_MILESTONE_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateProjectMilestoneTool: ToolConfig<
@@ -80,15 +79,10 @@ export const linearCreateProjectMilestoneTool: ToolConfig<
id
name
description
projectId
targetDate
progress
sortOrder
status
createdAt
archivedAt
project {
id
}
}
}
}
@@ -120,15 +114,10 @@ export const linearCreateProjectMilestoneTool: ToolConfig<
}
}
const milestone = result.projectMilestone
return {
success: true,
output: {
projectMilestone: {
...milestone,
projectId: milestone.project?.id ?? null,
project: undefined,
},
projectMilestone: result.projectMilestone,
},
}
},
@@ -137,7 +126,6 @@ export const linearCreateProjectMilestoneTool: ToolConfig<
projectMilestone: {
type: 'object',
description: 'The created project milestone',
properties: PROJECT_MILESTONE_OUTPUT_PROPERTIES,
},
},
}

View File

@@ -2,7 +2,6 @@ import type {
LinearCreateProjectStatusParams,
LinearCreateProjectStatusResponse,
} from '@/tools/linear/types'
import { PROJECT_STATUS_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateProjectStatusTool: ToolConfig<
@@ -98,9 +97,7 @@ export const linearCreateProjectStatusTool: ToolConfig<
color
indefinite
position
type
createdAt
updatedAt
archivedAt
}
}
@@ -145,7 +142,6 @@ export const linearCreateProjectStatusTool: ToolConfig<
projectStatus: {
type: 'object',
description: 'The created project status',
properties: PROJECT_STATUS_OUTPUT_PROPERTIES,
},
},
}

View File

@@ -2,7 +2,6 @@ import type {
LinearCreateWorkflowStateParams,
LinearCreateWorkflowStateResponse,
} from '@/tools/linear/types'
import { WORKFLOW_STATE_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateWorkflowStateTool: ToolConfig<
@@ -95,13 +94,9 @@ export const linearCreateWorkflowStateTool: ToolConfig<
workflowState {
id
name
description
type
color
position
createdAt
updatedAt
archivedAt
team {
id
name
@@ -149,7 +144,14 @@ export const linearCreateWorkflowStateTool: ToolConfig<
state: {
type: 'object',
description: 'The created workflow state',
properties: WORKFLOW_STATE_OUTPUT_PROPERTIES,
properties: {
id: { type: 'string', description: 'State ID' },
name: { type: 'string', description: 'State name' },
type: { type: 'string', description: 'State type' },
color: { type: 'string', description: 'State color' },
position: { type: 'number', description: 'State position' },
team: { type: 'object', description: 'Team this state belongs to' },
},
},
},
}

View File

@@ -1,5 +1,4 @@
import type { LinearGetActiveCycleParams, LinearGetActiveCycleResponse } from '@/tools/linear/types'
import { CYCLE_FULL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearGetActiveCycleTool: ToolConfig<
@@ -49,7 +48,6 @@ export const linearGetActiveCycleTool: ToolConfig<
endsAt
completedAt
progress
createdAt
team {
id
name
@@ -95,7 +93,15 @@ export const linearGetActiveCycleTool: ToolConfig<
cycle: {
type: 'object',
description: 'The active cycle (null if no active cycle)',
properties: CYCLE_FULL_OUTPUT_PROPERTIES,
properties: {
id: { type: 'string', description: 'Cycle ID' },
number: { type: 'number', description: 'Cycle number' },
name: { type: 'string', description: 'Cycle name' },
startsAt: { type: 'string', description: 'Start date' },
endsAt: { type: 'string', description: 'End date' },
progress: { type: 'number', description: 'Progress percentage' },
team: { type: 'object', description: 'Team this cycle belongs to' },
},
},
},
}

View File

@@ -44,12 +44,8 @@ export const linearGetCustomerTool: ToolConfig<LinearGetCustomerParams, LinearGe
domains
externalIds
logoUrl
slugId
approximateNeedCount
revenue
size
createdAt
updatedAt
archivedAt
}
}

View File

@@ -45,7 +45,6 @@ export const linearGetCycleTool: ToolConfig<LinearGetCycleParams, LinearGetCycle
endsAt
completedAt
progress
createdAt
team {
id
name

View File

@@ -88,7 +88,7 @@ export const linearListCustomerRequestsTool: ToolConfig<
}
`,
variables: {
first: params.first ? Number(params.first) : 50,
first: params.first || 50,
after: params.after,
includeArchived: params.includeArchived || false,
},

View File

@@ -2,7 +2,7 @@ import type {
LinearListCustomerStatusesParams,
LinearListCustomerStatusesResponse,
} from '@/tools/linear/types'
import { CUSTOMER_STATUS_OUTPUT_PROPERTIES, PAGE_INFO_OUTPUT } from '@/tools/linear/types'
import { CUSTOMER_STATUS_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListCustomerStatusesTool: ToolConfig<
@@ -19,20 +19,7 @@ export const linearListCustomerStatusesTool: ToolConfig<
provider: 'linear',
},
params: {
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of statuses to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
params: {},
request: {
url: 'https://api.linear.app/graphql',
@@ -46,32 +33,23 @@ export const linearListCustomerStatusesTool: ToolConfig<
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
body: () => ({
query: `
query CustomerStatuses($first: Int, $after: String) {
customerStatuses(first: $first, after: $after) {
query CustomerStatuses {
customerStatuses {
nodes {
id
name
displayName
description
color
position
type
createdAt
updatedAt
archivedAt
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
first: params.first ? Number(params.first) : 50,
after: params.after,
},
}),
},
@@ -86,15 +64,10 @@ export const linearListCustomerStatusesTool: ToolConfig<
}
}
const result = data.data.customerStatuses
return {
success: true,
output: {
customerStatuses: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
customerStatuses: data.data.customerStatuses.nodes,
},
}
},
@@ -108,6 +81,5 @@ export const linearListCustomerStatusesTool: ToolConfig<
properties: CUSTOMER_STATUS_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}

View File

@@ -2,7 +2,7 @@ import type {
LinearListCustomerTiersParams,
LinearListCustomerTiersResponse,
} from '@/tools/linear/types'
import { CUSTOMER_TIER_OUTPUT_PROPERTIES, PAGE_INFO_OUTPUT } from '@/tools/linear/types'
import { CUSTOMER_TIER_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListCustomerTiersTool: ToolConfig<
@@ -19,20 +19,7 @@ export const linearListCustomerTiersTool: ToolConfig<
provider: 'linear',
},
params: {
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of tiers to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
params: {},
request: {
url: 'https://api.linear.app/graphql',
@@ -46,10 +33,10 @@ export const linearListCustomerTiersTool: ToolConfig<
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
body: () => ({
query: `
query CustomerTiers($first: Int, $after: String) {
customerTiers(first: $first, after: $after) {
query CustomerTiers {
customerTiers {
nodes {
id
name
@@ -60,17 +47,9 @@ export const linearListCustomerTiersTool: ToolConfig<
createdAt
archivedAt
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
first: params.first ? Number(params.first) : 50,
after: params.after,
},
}),
},
@@ -85,15 +64,10 @@ export const linearListCustomerTiersTool: ToolConfig<
}
}
const result = data.data.customerTiers
return {
success: true,
output: {
customerTiers: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
customerTiers: data.data.customerTiers.nodes,
},
}
},
@@ -107,6 +81,5 @@ export const linearListCustomerTiersTool: ToolConfig<
properties: CUSTOMER_TIER_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}

View File

@@ -59,12 +59,8 @@ export const linearListCustomersTool: ToolConfig<
domains
externalIds
logoUrl
slugId
approximateNeedCount
revenue
size
createdAt
updatedAt
archivedAt
}
pageInfo {
@@ -75,7 +71,7 @@ export const linearListCustomersTool: ToolConfig<
}
`,
variables: {
first: params.first ? Number(params.first) : 50,
first: params.first || 50,
after: params.after,
includeArchived: params.includeArchived || false,
},

View File

@@ -64,7 +64,6 @@ export const linearListCyclesTool: ToolConfig<LinearListCyclesParams, LinearList
endsAt
completedAt
progress
createdAt
team {
id
name

View File

@@ -61,10 +61,6 @@ export const linearListLabelsTool: ToolConfig<LinearListLabelsParams, LinearList
name
color
description
isGroup
createdAt
updatedAt
archivedAt
team {
id
name

View File

@@ -2,7 +2,6 @@ import type {
LinearListProjectLabelsParams,
LinearListProjectLabelsResponse,
} from '@/tools/linear/types'
import { PAGE_INFO_OUTPUT, PROJECT_LABEL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListProjectLabelsTool: ToolConfig<
@@ -26,18 +25,6 @@ export const linearListProjectLabelsTool: ToolConfig<
visibility: 'user-or-llm',
description: 'Optional project ID to filter labels for a specific project',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of labels to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
@@ -53,14 +40,15 @@ export const linearListProjectLabelsTool: ToolConfig<
}
},
body: (params) => {
// If projectId is provided, query the specific project's labels
if (params.projectId?.trim()) {
return {
query: `
query ProjectWithLabels($id: String!, $first: Int, $after: String) {
query ProjectWithLabels($id: String!) {
project(id: $id) {
id
name
labels(first: $first, after: $after) {
labels {
nodes {
id
name
@@ -68,29 +56,23 @@ export const linearListProjectLabelsTool: ToolConfig<
color
isGroup
createdAt
updatedAt
archivedAt
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
`,
variables: {
id: params.projectId.trim(),
first: params.first ? Number(params.first) : 50,
after: params.after,
},
}
}
// Otherwise, list all project labels
return {
query: `
query ProjectLabels($first: Int, $after: String) {
projectLabels(first: $first, after: $after) {
query ProjectLabels {
projectLabels {
nodes {
id
name
@@ -98,20 +80,11 @@ export const linearListProjectLabelsTool: ToolConfig<
color
isGroup
createdAt
updatedAt
archivedAt
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
first: params.first ? Number(params.first) : 50,
after: params.after,
},
}
},
},
@@ -127,29 +100,21 @@ export const linearListProjectLabelsTool: ToolConfig<
}
}
// Handle project-specific query response
if (data.data.project) {
const result = data.data.project.labels
return {
success: true,
output: {
projectLabels: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
projectLabels: data.data.project.labels.nodes,
},
}
}
const result = data.data.projectLabels
// Handle global projectLabels query response
return {
success: true,
output: {
projectLabels: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
projectLabels: data.data.projectLabels.nodes,
},
}
},
@@ -158,11 +123,6 @@ export const linearListProjectLabelsTool: ToolConfig<
projectLabels: {
type: 'array',
description: 'List of project labels',
items: {
type: 'object',
properties: PROJECT_LABEL_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}

View File

@@ -2,7 +2,6 @@ import type {
LinearListProjectMilestonesParams,
LinearListProjectMilestonesResponse,
} from '@/tools/linear/types'
import { PAGE_INFO_OUTPUT, PROJECT_MILESTONE_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListProjectMilestonesTool: ToolConfig<
@@ -26,18 +25,6 @@ export const linearListProjectMilestonesTool: ToolConfig<
visibility: 'user-or-llm',
description: 'Project ID to list milestones for',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of milestones to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
@@ -54,26 +41,17 @@ export const linearListProjectMilestonesTool: ToolConfig<
},
body: (params) => ({
query: `
query Project($id: String!, $first: Int, $after: String) {
query Project($id: String!) {
project(id: $id) {
projectMilestones(first: $first, after: $after) {
projectMilestones {
nodes {
id
name
description
projectId
targetDate
progress
sortOrder
status
createdAt
archivedAt
project {
id
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
@@ -81,8 +59,6 @@ export const linearListProjectMilestonesTool: ToolConfig<
`,
variables: {
id: params.projectId,
first: params.first ? Number(params.first) : 50,
after: params.after,
},
}),
},
@@ -98,20 +74,10 @@ export const linearListProjectMilestonesTool: ToolConfig<
}
}
const result = data.data.project?.projectMilestones
const milestones = (result?.nodes || []).map((node: Record<string, unknown>) => ({
...node,
projectId: (node.project as Record<string, string>)?.id ?? null,
project: undefined,
}))
return {
success: true,
output: {
projectMilestones: milestones,
pageInfo: {
hasNextPage: result?.pageInfo?.hasNextPage ?? false,
endCursor: result?.pageInfo?.endCursor,
},
projectMilestones: data.data.project?.projectMilestones?.nodes || [],
},
}
},
@@ -120,11 +86,6 @@ export const linearListProjectMilestonesTool: ToolConfig<
projectMilestones: {
type: 'array',
description: 'List of project milestones',
items: {
type: 'object',
properties: PROJECT_MILESTONE_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}

View File

@@ -2,7 +2,6 @@ import type {
LinearListProjectStatusesParams,
LinearListProjectStatusesResponse,
} from '@/tools/linear/types'
import { PAGE_INFO_OUTPUT, PROJECT_STATUS_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListProjectStatusesTool: ToolConfig<
@@ -19,20 +18,7 @@ export const linearListProjectStatusesTool: ToolConfig<
provider: 'linear',
},
params: {
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of statuses to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
params: {},
request: {
url: 'https://api.linear.app/graphql',
@@ -46,10 +32,10 @@ export const linearListProjectStatusesTool: ToolConfig<
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
body: () => ({
query: `
query ProjectStatuses($first: Int, $after: String) {
projectStatuses(first: $first, after: $after) {
query ProjectStatuses {
projectStatuses {
nodes {
id
name
@@ -57,22 +43,12 @@ export const linearListProjectStatusesTool: ToolConfig<
color
indefinite
position
type
createdAt
updatedAt
archivedAt
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
first: params.first ? Number(params.first) : 50,
after: params.after,
},
}),
},
@@ -87,15 +63,10 @@ export const linearListProjectStatusesTool: ToolConfig<
}
}
const result = data.data.projectStatuses
return {
success: true,
output: {
projectStatuses: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
projectStatuses: data.data.projectStatuses.nodes,
},
}
},
@@ -104,11 +75,6 @@ export const linearListProjectStatusesTool: ToolConfig<
projectStatuses: {
type: 'array',
description: 'List of project statuses',
items: {
type: 'object',
properties: PROJECT_STATUS_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}

View File

@@ -93,7 +93,7 @@ export const linearListProjectsTool: ToolConfig<
}
`,
variables: {
first: params.first ? Number(params.first) : 50,
first: params.first || 50,
after: params.after,
includeArchived: params.includeArchived || false,
},

View File

@@ -65,13 +65,9 @@ export const linearListWorkflowStatesTool: ToolConfig<
nodes {
id
name
description
type
color
position
createdAt
updatedAt
archivedAt
team {
id
name

View File

@@ -41,12 +41,6 @@ export const linearSearchIssuesTool: ToolConfig<
visibility: 'user-or-llm',
description: 'Number of results to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
@@ -69,8 +63,8 @@ export const linearSearchIssuesTool: ToolConfig<
return {
query: `
query SearchIssues($term: String!, $filter: IssueFilter, $first: Int, $after: String, $includeArchived: Boolean) {
searchIssues(term: $term, filter: $filter, first: $first, after: $after, includeArchived: $includeArchived) {
query SearchIssues($term: String!, $filter: IssueFilter, $first: Int, $includeArchived: Boolean) {
searchIssues(term: $term, filter: $filter, first: $first, includeArchived: $includeArchived) {
nodes {
id
title
@@ -117,8 +111,7 @@ export const linearSearchIssuesTool: ToolConfig<
variables: {
term: params.query,
filter: Object.keys(filter).length > 0 ? filter : undefined,
first: params.first ? Number(params.first) : 50,
after: params.after,
first: params.first || 50,
includeArchived: params.includeArchived || false,
},
}

View File

@@ -112,10 +112,6 @@ export const LABEL_FULL_OUTPUT_PROPERTIES = {
name: { type: 'string', description: 'Label name' },
color: { type: 'string', description: 'Label color (hex)' },
description: { type: 'string', description: 'Label description' },
isGroup: { type: 'boolean', description: 'Whether this label is a group' },
createdAt: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
updatedAt: { type: 'string', description: 'Last update timestamp (ISO 8601)' },
archivedAt: { type: 'string', description: 'Archive timestamp (ISO 8601)' },
team: TEAM_OUTPUT,
} as const satisfies Record<string, OutputProperty>
@@ -148,7 +144,6 @@ export const CYCLE_FULL_OUTPUT_PROPERTIES = {
endsAt: { type: 'string', description: 'End date (ISO 8601)' },
completedAt: { type: 'string', description: 'Completion date (ISO 8601)' },
progress: { type: 'number', description: 'Progress percentage (0-1)' },
createdAt: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
team: TEAM_OUTPUT,
} as const satisfies Record<string, OutputProperty>
@@ -282,16 +277,9 @@ export const ATTACHMENT_OUTPUT_PROPERTIES = {
export const WORKFLOW_STATE_OUTPUT_PROPERTIES = {
id: { type: 'string', description: 'State ID' },
name: { type: 'string', description: 'State name (e.g., "Todo", "In Progress")' },
description: { type: 'string', description: 'State description' },
type: {
type: 'string',
description: 'State type (triage, backlog, unstarted, started, completed, canceled)',
},
type: { type: 'string', description: 'State type (unstarted, started, completed, canceled)' },
color: { type: 'string', description: 'State color (hex)' },
position: { type: 'number', description: 'State position in workflow' },
createdAt: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
updatedAt: { type: 'string', description: 'Last update timestamp (ISO 8601)' },
archivedAt: { type: 'string', description: 'Archive timestamp (ISO 8601)' },
team: TEAM_OUTPUT,
} as const satisfies Record<string, OutputProperty>
@@ -355,12 +343,8 @@ export const CUSTOMER_OUTPUT_PROPERTIES = {
items: { type: 'string', description: 'External ID' },
},
logoUrl: { type: 'string', description: 'Logo URL' },
slugId: { type: 'string', description: 'Unique URL slug' },
approximateNeedCount: { type: 'number', description: 'Number of customer needs' },
revenue: { type: 'number', description: 'Annual revenue' },
size: { type: 'number', description: 'Organization size' },
createdAt: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
updatedAt: { type: 'string', description: 'Last update timestamp (ISO 8601)' },
archivedAt: { type: 'string', description: 'Archive timestamp (ISO 8601)' },
} as const satisfies Record<string, OutputProperty>
@@ -394,12 +378,11 @@ export const CUSTOMER_NEED_OUTPUT_PROPERTIES = {
export const CUSTOMER_STATUS_OUTPUT_PROPERTIES = {
id: { type: 'string', description: 'Customer status ID' },
name: { type: 'string', description: 'Status name' },
displayName: { type: 'string', description: 'Display name' },
description: { type: 'string', description: 'Status description' },
color: { type: 'string', description: 'Status color (hex)' },
position: { type: 'number', description: 'Position in list' },
type: { type: 'string', description: 'Status type (active, inactive)' },
createdAt: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
updatedAt: { type: 'string', description: 'Last updated timestamp (ISO 8601)' },
archivedAt: { type: 'string', description: 'Archive timestamp (ISO 8601)' },
} as const satisfies Record<string, OutputProperty>
@@ -427,7 +410,6 @@ export const PROJECT_LABEL_OUTPUT_PROPERTIES = {
color: { type: 'string', description: 'Label color (hex)' },
isGroup: { type: 'boolean', description: 'Whether this label is a group' },
createdAt: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
updatedAt: { type: 'string', description: 'Last update timestamp (ISO 8601)' },
archivedAt: { type: 'string', description: 'Archive timestamp (ISO 8601)' },
} as const satisfies Record<string, OutputProperty>
@@ -440,9 +422,6 @@ export const PROJECT_MILESTONE_OUTPUT_PROPERTIES = {
description: { type: 'string', description: 'Milestone description' },
projectId: { type: 'string', description: 'Project ID' },
targetDate: { type: 'string', description: 'Target date (YYYY-MM-DD)' },
progress: { type: 'number', description: 'Progress percentage (0-1)' },
sortOrder: { type: 'number', description: 'Sort order within the project' },
status: { type: 'string', description: 'Milestone status (done, next, overdue, unstarted)' },
createdAt: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
archivedAt: { type: 'string', description: 'Archive timestamp (ISO 8601)' },
} as const satisfies Record<string, OutputProperty>
@@ -465,12 +444,7 @@ export const PROJECT_STATUS_OUTPUT_PROPERTIES = {
color: { type: 'string', description: 'Status color (hex)' },
indefinite: { type: 'boolean', description: 'Whether this status is indefinite' },
position: { type: 'number', description: 'Position in list' },
type: {
type: 'string',
description: 'Status type (backlog, planned, started, paused, completed, canceled)',
},
createdAt: { type: 'string', description: 'Creation timestamp (ISO 8601)' },
updatedAt: { type: 'string', description: 'Last updated timestamp (ISO 8601)' },
archivedAt: { type: 'string', description: 'Archive timestamp (ISO 8601)' },
} as const satisfies Record<string, OutputProperty>
@@ -613,10 +587,6 @@ export interface LinearLabel {
name: string
color: string
description?: string
isGroup: boolean
createdAt: string
updatedAt: string
archivedAt?: string
team?: {
id: string
name: string
@@ -626,13 +596,9 @@ export interface LinearLabel {
export interface LinearWorkflowState {
id: string
name: string
description?: string
type: string
color: string
position: number
createdAt: string
updatedAt: string
archivedAt?: string
team: {
id: string
name: string
@@ -647,7 +613,6 @@ export interface LinearCycle {
endsAt: string
completedAt?: string
progress: number
createdAt: string
team: {
id: string
name: string
@@ -745,7 +710,6 @@ export interface LinearSearchIssuesParams {
teamId?: string
includeArchived?: boolean
first?: number
after?: string
accessToken?: string
}
@@ -1241,7 +1205,7 @@ export interface LinearAttachment {
subtitle?: string
url: string
createdAt: string
updatedAt: string
updatedAt?: string
}
export interface LinearCreateAttachmentResponse extends ToolResponse {
@@ -1402,12 +1366,8 @@ export interface LinearCustomer {
domains: string[]
externalIds: string[]
logoUrl?: string
slugId: string
approximateNeedCount: number
revenue?: number
size?: number
createdAt: string
updatedAt: string
archivedAt?: string
}
@@ -1582,12 +1542,11 @@ export interface LinearMergeCustomersResponse extends ToolResponse {
export interface LinearCustomerStatus {
id: string
name: string
displayName: string
description?: string
color: string
position: number
type: string
createdAt: string
updatedAt: string
archivedAt?: string
}
@@ -1634,18 +1593,12 @@ export interface LinearDeleteCustomerStatusResponse extends ToolResponse {
}
export interface LinearListCustomerStatusesParams {
first?: number
after?: string
accessToken?: string
}
export interface LinearListCustomerStatusesResponse extends ToolResponse {
output: {
customerStatuses?: LinearCustomerStatus[]
pageInfo?: {
hasNextPage: boolean
endCursor?: string
}
}
}
@@ -1705,18 +1658,12 @@ export interface LinearDeleteCustomerTierResponse extends ToolResponse {
}
export interface LinearListCustomerTiersParams {
first?: number
after?: string
accessToken?: string
}
export interface LinearListCustomerTiersResponse extends ToolResponse {
output: {
customerTiers?: LinearCustomerTier[]
pageInfo?: {
hasNextPage: boolean
endCursor?: string
}
}
}
@@ -1729,7 +1676,6 @@ export interface LinearProjectLabel {
color?: string
isGroup: boolean
createdAt: string
updatedAt: string
archivedAt?: string
}
@@ -1774,19 +1720,13 @@ export interface LinearDeleteProjectLabelResponse extends ToolResponse {
}
export interface LinearListProjectLabelsParams {
projectId?: string
first?: number
after?: string
accessToken?: string
projectId?: string
}
export interface LinearListProjectLabelsResponse extends ToolResponse {
output: {
projectLabels?: LinearProjectLabel[]
pageInfo?: {
hasNextPage: boolean
endCursor?: string
}
}
}
@@ -1824,9 +1764,6 @@ export interface LinearProjectMilestone {
description?: string
projectId: string
targetDate?: string
progress: number
sortOrder: number
status: string
createdAt: string
archivedAt?: string
}
@@ -1872,18 +1809,12 @@ export interface LinearDeleteProjectMilestoneResponse extends ToolResponse {
export interface LinearListProjectMilestonesParams {
projectId: string
first?: number
after?: string
accessToken?: string
}
export interface LinearListProjectMilestonesResponse extends ToolResponse {
output: {
projectMilestones?: LinearProjectMilestone[]
pageInfo?: {
hasNextPage: boolean
endCursor?: string
}
}
}
@@ -1896,9 +1827,7 @@ export interface LinearProjectStatus {
color: string
indefinite: boolean
position: number
type: string
createdAt: string
updatedAt: string
archivedAt?: string
}
@@ -1946,18 +1875,12 @@ export interface LinearDeleteProjectStatusResponse extends ToolResponse {
}
export interface LinearListProjectStatusesParams {
first?: number
after?: string
accessToken?: string
}
export interface LinearListProjectStatusesResponse extends ToolResponse {
output: {
projectStatuses?: LinearProjectStatus[]
pageInfo?: {
hasNextPage: boolean
endCursor?: string
}
}
}

View File

@@ -71,7 +71,6 @@ export const linearUpdateAttachmentTool: ToolConfig<
title
subtitle
url
createdAt
updatedAt
}
}

View File

@@ -137,12 +137,8 @@ export const linearUpdateCustomerTool: ToolConfig<
domains
externalIds
logoUrl
slugId
approximateNeedCount
revenue
size
createdAt
updatedAt
archivedAt
}
}

View File

@@ -2,7 +2,6 @@ import type {
LinearUpdateCustomerStatusParams,
LinearUpdateCustomerStatusResponse,
} from '@/tools/linear/types'
import { CUSTOMER_STATUS_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateCustomerStatusTool: ToolConfig<
@@ -38,18 +37,18 @@ export const linearUpdateCustomerStatusTool: ToolConfig<
visibility: 'user-or-llm',
description: 'Updated status color',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated description',
},
displayName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated display name',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated description',
},
position: {
type: 'number',
required: false,
@@ -79,12 +78,12 @@ export const linearUpdateCustomerStatusTool: ToolConfig<
if (params.color != null && params.color !== '') {
input.color = params.color
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.displayName != null && params.displayName !== '') {
input.displayName = params.displayName
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.position != null) {
input.position = params.position
}
@@ -97,12 +96,11 @@ export const linearUpdateCustomerStatusTool: ToolConfig<
customerStatus {
id
name
displayName
description
color
position
type
createdAt
updatedAt
archivedAt
}
}
@@ -140,7 +138,6 @@ export const linearUpdateCustomerStatusTool: ToolConfig<
customerStatus: {
type: 'object',
description: 'The updated customer status',
properties: CUSTOMER_STATUS_OUTPUT_PROPERTIES,
},
},
}

View File

@@ -71,10 +71,6 @@ export const linearUpdateLabelTool: ToolConfig<LinearUpdateLabelParams, LinearUp
name
color
description
isGroup
createdAt
updatedAt
archivedAt
team {
id
name

View File

@@ -2,7 +2,6 @@ import type {
LinearUpdateProjectLabelParams,
LinearUpdateProjectLabelResponse,
} from '@/tools/linear/types'
import { PROJECT_LABEL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateProjectLabelTool: ToolConfig<
@@ -83,7 +82,6 @@ export const linearUpdateProjectLabelTool: ToolConfig<
color
isGroup
createdAt
updatedAt
archivedAt
}
}
@@ -121,7 +119,6 @@ export const linearUpdateProjectLabelTool: ToolConfig<
projectLabel: {
type: 'object',
description: 'The updated project label',
properties: PROJECT_LABEL_OUTPUT_PROPERTIES,
},
},
}

View File

@@ -2,7 +2,6 @@ import type {
LinearUpdateProjectMilestoneParams,
LinearUpdateProjectMilestoneResponse,
} from '@/tools/linear/types'
import { PROJECT_MILESTONE_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateProjectMilestoneTool: ToolConfig<
@@ -80,15 +79,10 @@ export const linearUpdateProjectMilestoneTool: ToolConfig<
id
name
description
projectId
targetDate
progress
sortOrder
status
createdAt
archivedAt
project {
id
}
}
}
}
@@ -113,23 +107,10 @@ export const linearUpdateProjectMilestoneTool: ToolConfig<
}
const result = data.data.projectMilestoneUpdate
if (!result.success) {
return {
success: false,
error: 'Project milestone update was not successful',
output: {},
}
}
const milestone = result.projectMilestone
return {
success: true,
success: result.success,
output: {
projectMilestone: {
...milestone,
projectId: milestone.project?.id ?? null,
project: undefined,
},
projectMilestone: result.projectMilestone,
},
}
},
@@ -138,7 +119,6 @@ export const linearUpdateProjectMilestoneTool: ToolConfig<
projectMilestone: {
type: 'object',
description: 'The updated project milestone',
properties: PROJECT_MILESTONE_OUTPUT_PROPERTIES,
},
},
}

View File

@@ -2,7 +2,6 @@ import type {
LinearUpdateProjectStatusParams,
LinearUpdateProjectStatusResponse,
} from '@/tools/linear/types'
import { PROJECT_STATUS_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateProjectStatusTool: ToolConfig<
@@ -101,9 +100,7 @@ export const linearUpdateProjectStatusTool: ToolConfig<
color
indefinite
position
type
createdAt
updatedAt
archivedAt
}
}
@@ -141,7 +138,6 @@ export const linearUpdateProjectStatusTool: ToolConfig<
projectStatus: {
type: 'object',
description: 'The updated project status',
properties: PROJECT_STATUS_OUTPUT_PROPERTIES,
},
},
}

View File

@@ -2,7 +2,6 @@ import type {
LinearUpdateWorkflowStateParams,
LinearUpdateWorkflowStateResponse,
} from '@/tools/linear/types'
import { WORKFLOW_STATE_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateWorkflowStateTool: ToolConfig<
@@ -88,13 +87,9 @@ export const linearUpdateWorkflowStateTool: ToolConfig<
workflowState {
id
name
description
type
color
position
createdAt
updatedAt
archivedAt
team {
id
name
@@ -143,7 +138,13 @@ export const linearUpdateWorkflowStateTool: ToolConfig<
state: {
type: 'object',
description: 'The updated workflow state',
properties: WORKFLOW_STATE_OUTPUT_PROPERTIES,
properties: {
id: { type: 'string', description: 'State ID' },
name: { type: 'string', description: 'State name' },
type: { type: 'string', description: 'State type' },
color: { type: 'string', description: 'State color' },
position: { type: 'number', description: 'State position' },
},
},
},
}

View File

@@ -30,27 +30,6 @@ export const slackWebhookTrigger: TriggerConfig = {
required: true,
mode: 'trigger',
},
{
id: 'botToken',
title: 'Bot Token',
type: 'short-input',
placeholder: 'xoxb-...',
description:
'The bot token from your Slack app. Required for downloading files attached to messages.',
password: true,
required: false,
mode: 'trigger',
},
{
id: 'includeFiles',
title: 'Include File Attachments',
type: 'switch',
defaultValue: false,
description:
'Download and include file attachments from messages. Requires a bot token with files:read scope.',
required: false,
mode: 'trigger',
},
{
id: 'triggerSave',
title: '',
@@ -67,10 +46,9 @@ export const slackWebhookTrigger: TriggerConfig = {
'Go to <a href="https://api.slack.com/apps" target="_blank" rel="noopener noreferrer" class="text-muted-foreground underline transition-colors hover:text-muted-foreground/80">Slack Apps page</a>',
'If you don\'t have an app:<br><ul class="mt-1 ml-5 list-disc"><li>Create an app from scratch</li><li>Give it a name and select your workspace</li></ul>',
'Go to "Basic Information", find the "Signing Secret", and paste it in the field above.',
'Go to "OAuth & Permissions" and add bot token scopes:<br><ul class="mt-1 ml-5 list-disc"><li><code>app_mentions:read</code> - For viewing messages that tag your bot with an @</li><li><code>chat:write</code> - To send messages to channels your bot is a part of</li><li><code>files:read</code> - To access files and images shared in messages</li></ul>',
'Go to "OAuth & Permissions" and add bot token scopes:<br><ul class="mt-1 ml-5 list-disc"><li><code>app_mentions:read</code> - For viewing messages that tag your bot with an @</li><li><code>chat:write</code> - To send messages to channels your bot is a part of</li></ul>',
'Go to "Event Subscriptions":<br><ul class="mt-1 ml-5 list-disc"><li>Enable events</li><li>Under "Subscribe to Bot Events", add <code>app_mention</code> to listen to messages that mention your bot</li><li>Paste the Webhook URL above into the "Request URL" field</li></ul>',
'Go to "Install App" in the left sidebar and install the app into your desired Slack workspace and channel.',
'Copy the "Bot User OAuth Token" (starts with <code>xoxb-</code>) and paste it in the Bot Token field above to enable file downloads.',
'Save changes in both Slack and here.',
]
.map(
@@ -128,15 +106,6 @@ export const slackWebhookTrigger: TriggerConfig = {
type: 'string',
description: 'Unique event identifier',
},
hasFiles: {
type: 'boolean',
description: 'Whether the message has file attachments',
},
files: {
type: 'file[]',
description:
'File attachments downloaded from the message (if includeFiles is enabled and bot token is provided)',
},
},
},
},