Skip to content

Commit 3af24cd

Browse files
Sg312Vikhyath Mondreti
authored andcommitted
Gate Search Assistant integration lookups with AppConfig (#8301)
* Enable read-only integration lookups for Search Assistant * Preserve inherited tool versions in generated integration docs * Align provider tests with Search read-only operations * Gate Search integration tools with AppConfig * Preserve dev test runner isolation during promotion
1 parent 36da911 commit 3af24cd

20 files changed

Lines changed: 362 additions & 106 deletions

File tree

‎apps/docs/content/docs/integrations/github.mdx‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3414,6 +3414,46 @@ List users who have starred a repository
34143414
| ↳ `repos_url` | string | Repos API URL |
34153415
| `count` | number | Number of stargazers returned |
34163416

3417+
### GitHub List Review Threads
3418+
3419+
List one page of a pull request's review threads with their comments, plus the newest submitted review.
3420+
3421+
#### Input
3422+
3423+
| Parameter | Type | Required | Description |
3424+
| --------- | ---- | -------- | ----------- |
3425+
| `owner` | string | Yes | Repository owner |
3426+
| `repo` | string | Yes | Repository name |
3427+
| `pullNumber` | number | Yes | Pull request number |
3428+
| `threadsPerPage` | number | No | Review threads to fetch in this page \(1-100\) |
3429+
| `commentsPerThread` | number | No | Comments to fetch per thread \(1-100\) |
3430+
| `cursor` | string | No | Cursor from a previous page \(endCursor\) to continue from |
3431+
| `apiKey` | string | Yes | GitHub API token with pull request read access |
3432+
3433+
#### Output
3434+
3435+
| Parameter | Type | Description |
3436+
| --------- | ---- | ----------- |
3437+
| `threads` | array | Review threads in this page |
3438+
| ↳ `id` | string | Review thread node ID |
3439+
| ↳ `isResolved` | boolean | Whether the thread is resolved |
3440+
| ↳ `path` | string | Repository-relative file path |
3441+
| ↳ `line` | number | Line the thread is anchored to |
3442+
| ↳ `commentsTotalCount` | number | Total comments on the thread; exceeds the fetched count when the thread was truncated |
3443+
| ↳ `comments` | array | Fetched comments, oldest first |
3444+
| ↳ `body` | string | Comment body |
3445+
| ↳ `authorAssociation` | string | Author's association with the repository \(OWNER, MEMBER, ...\) |
3446+
| ↳ `authorLogin` | string | Author login |
3447+
| ↳ `authorType` | string | Author GraphQL type \(User, Bot, Organization\) |
3448+
| `totalCount` | number | Total review threads on the pull request |
3449+
| `hasNextPage` | boolean | Whether more thread pages remain |
3450+
| `endCursor` | string | Cursor to pass as `cursor` for the next page |
3451+
| `latestReview` | object | Newest submitted review on the pull request |
3452+
| ↳ `state` | string | Review state |
3453+
| ↳ `submittedAt` | string | Submission timestamp |
3454+
| ↳ `authorLogin` | string | Reviewer login |
3455+
| ↳ `authorType` | string | Reviewer GraphQL type \(User, Bot\) |
3456+
34173457

34183458

34193459
## Triggers

‎apps/docs/content/docs/integrations/gmail.mdx‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,23 @@ Remove label(s) from a Gmail message. Returns API-aligned fields only.
300300
| `threadId` | string | Gmail thread ID |
301301
| `labelIds` | array | Updated email labels |
302302

303+
### Gmail List Labels
304+
305+
List all labels in a Gmail account
306+
307+
#### Input
308+
309+
| Parameter | Type | Required | Description |
310+
| --------- | ---- | -------- | ----------- |
311+
312+
#### Output
313+
314+
| Parameter | Type | Description |
315+
| --------- | ---- | ----------- |
316+
| `labels` | json | Array of label objects with id, name, type, and visibility settings |
317+
318+
319+
303320
## Triggers
304321

305322
A **Trigger** is a block that starts a workflow when an event happens in this service.

‎apps/sim/blocks/blocks/github.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2443,7 +2443,10 @@ export const GitHubV2Block: BlockConfig = {
24432443
integrationType: IntegrationType.DevOps,
24442444
tools: {
24452445
...GitHubBlock.tools,
2446-
access: (GitHubBlock.tools?.access || []).map((toolId) => `${toolId}_v2`),
2446+
access: [
2447+
...(GitHubBlock.tools?.access || []).map((toolId) => `${toolId}_v2`),
2448+
'github_list_review_threads',
2449+
],
24472450
config: {
24482451
...GitHubBlock.tools?.config,
24492452
tool: createVersionedToolSelector({

‎apps/sim/blocks/blocks/gmail.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,7 @@ export const GmailV2Block: BlockConfig = {
678678
'gmail_delete_v2',
679679
'gmail_add_label_v2',
680680
'gmail_remove_label_v2',
681+
'gmail_list_labels_v2',
681682
],
682683
config: {
683684
...GmailBlock.tools?.config,

‎apps/sim/lib/atlassian/assistant.test.ts‎

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
1-
import { beforeEach, describe, expect, it, vi } from 'vitest'
1+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
22
import { clearAtlassianCloudIdCache } from '@/lib/atlassian/discovery'
33
import { createConfluenceClient } from '@/lib/internal/confluence/client'
44
import { createJiraClient } from '@/lib/internal/jira/client'
5-
import {
6-
assertAssistantIntegrationCall,
7-
isAssistantIntegrationTool,
8-
} from '@/lib/mothership/assistant/tool-policy'
5+
import { assertAssistantIntegrationCall } from '@/lib/mothership/assistant/tool-policy'
96
import { getToolMetadata } from '@/tools/metadata'
10-
import { getToolIds } from '@/tools/tool-ids'
117

128
vi.unmock('@/tools/metadata')
13-
vi.unmock('@/tools/tool-ids')
149

1510
const CLOUD_ID = '12345678-1234-1234-1234-123456789012'
1611
const OTHER_CLOUD_ID = '12345678-1234-1234-1234-123456789013'
@@ -26,29 +21,23 @@ describe('Atlassian Assistant resource selection', () => {
2621
vi.stubGlobal('fetch', fetchMock)
2722
})
2823

29-
it.each(['jira', 'confluence'])(
30-
'offers %s operations with a site selector and personal credential',
31-
(service) => {
32-
const tools = getToolIds()
33-
.filter((id) => id.startsWith(`${service}_`))
34-
.map((id) => getToolMetadata(id))
35-
.filter((tool) => tool?.params.domain)
36-
expect(tools.length).toBeGreaterThan(0)
37-
for (const tool of tools) {
38-
expect(tool?.params.domain.visibility, tool?.id).toBe('user-or-llm')
39-
expect(isAssistantIntegrationTool(tool), tool?.id).toBe(true)
24+
afterEach(() => vi.unstubAllGlobals())
25+
26+
it.each(['jira_get_project', 'confluence_list_spaces'])(
27+
'allows site selection for %s without accepting credential overrides',
28+
(toolId) => {
29+
const tool = getToolMetadata(toolId)
30+
expect(() =>
31+
assertAssistantIntegrationCall(tool, { credentialId: 'mine', domain: DOMAIN })
32+
).not.toThrow()
33+
for (const name of ['cloudId', 'accessToken', '_context']) {
4034
expect(() =>
41-
assertAssistantIntegrationCall(tool, { credentialId: 'mine', domain: DOMAIN })
42-
).not.toThrow()
43-
for (const name of ['cloudId', 'accessToken', '_context']) {
44-
expect(() =>
45-
assertAssistantIntegrationCall(tool, {
46-
credentialId: 'mine',
47-
domain: DOMAIN,
48-
[name]: 'override',
49-
})
50-
).toThrow()
51-
}
35+
assertAssistantIntegrationCall(tool, {
36+
credentialId: 'mine',
37+
domain: DOMAIN,
38+
[name]: 'override',
39+
})
40+
).toThrow()
5241
}
5342
}
5443
)

‎apps/sim/lib/core/config/feature-flags.ts‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,17 @@ export type FeatureFlagContext = AppConfigGateContext
3131
/**
3232
* The single definition of a feature flag. Everything about a flag lives in one
3333
* place: its name (the registry key), a human-readable `description`, and the
34-
* `fallback` secret consulted when AppConfig isn't the source of truth (truthy ⇒ on
35-
* globally).
34+
* optional `fallback` secret consulted when AppConfig is not the source of truth.
35+
* A null fallback keeps the flag off outside AppConfig.
3636
*
3737
* Gating by workspace/org/user/admin is deliberately NOT part of a definition — it lives only
3838
* in the hosted AppConfig document, so no environment can grant access from a code
3939
* literal.
4040
*/
4141
interface FeatureFlagDefinition {
4242
description: string
43-
/** Env/secret key consulted when AppConfig isn't the source of truth. Truthy ⇒ on. */
44-
fallback: keyof typeof env
43+
/** Null means AppConfig-only; otherwise a truthy env/secret enables the fallback. */
44+
fallback: keyof typeof env | null
4545
}
4646

4747
/** The single registry of known flags. To add a flag, add one entry here. */
@@ -56,6 +56,12 @@ const FEATURE_FLAGS = {
5656
'Enable native macOS computer use in Mothership. Global on/off only; each device must also opt in.',
5757
fallback: 'MSHIP_COMPUTER_USE',
5858
},
59+
'mothership-search-integration-tools': {
60+
description:
61+
'Give Search Assistant read-only integration discovery, calls, and matching prompt ' +
62+
'instructions. Global AppConfig on/off only; disabled by default with no env fallback.',
63+
fallback: null,
64+
},
5965
'mothership-model-selector': {
6066
description:
6167
'Show the Mothership model selector, model-specific effort levels, and Fast for supported ' +
@@ -145,7 +151,7 @@ const FEATURE_FLAGS = {
145151

146152
/**
147153
* The closed set of known feature flags. Derived from the registry, so a flag
148-
* cannot exist — or be checked — without a definition (and its mandatory fallback).
154+
* cannot exist — or be checked — without a definition (and its explicit fallback policy).
149155
*/
150156
export type FeatureFlagName = keyof typeof FEATURE_FLAGS
151157

@@ -155,7 +161,7 @@ function fallbackFlags(): FeatureFlagsConfig {
155161
for (const [name, def] of Object.entries(FEATURE_FLAGS) as Array<
156162
[string, FeatureFlagDefinition]
157163
>) {
158-
flags[name] = { enabled: isTruthy(env[def.fallback]) }
164+
flags[name] = { enabled: def.fallback !== null && isTruthy(env[def.fallback]) }
159165
}
160166
return flags
161167
}

‎apps/sim/lib/mothership/assistant/tool-policy.test.ts‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
import type { ToolMetadata } from '@/tools/metadata'
77

88
const tool: ToolMetadata = {
9-
id: 'service_write',
9+
id: 'google_drive_get_file',
1010
oauth: { required: true, provider: 'google-drive', authoritativeParams: ['instanceUrl'] },
1111
params: {
1212
credential: { type: 'string', visibility: 'user-only' },
@@ -19,7 +19,7 @@ const tool: ToolMetadata = {
1919

2020
describe('Assistant integration policy', () => {
2121
const tokenTool: ToolMetadata = {
22-
id: 'gitlab_get_project',
22+
id: 'gitlab_list_projects',
2323
personalToken: { provider: 'gitlab', tokenParam: 'accessToken', hostParam: 'host' },
2424
params: {
2525
accessToken: { type: 'string', required: true, visibility: 'user-only' },
@@ -41,12 +41,22 @@ describe('Assistant integration policy', () => {
4141
expect(isAssistantIntegrationTool({ ...tokenTool, params: {} })).toBe(false)
4242
})
4343

44-
it('allows writes with one explicit connected account', () => {
44+
it('allows selected reads with one explicit connected account', () => {
4545
expect(() =>
4646
assertAssistantIntegrationCall(tool, { credential: 'mine', body: 'updated content' })
4747
).not.toThrow()
4848
})
4949

50+
it.each(['gmail_send', 'google_drive_create_file', 'new_provider_operation'])(
51+
'rejects unapproved operation %s even with a personal account',
52+
(id) => {
53+
expect(isAssistantIntegrationTool({ ...tool, id })).toBe(false)
54+
expect(() =>
55+
assertAssistantIntegrationCall({ ...tool, id }, { credential: 'mine' })
56+
).toThrow()
57+
}
58+
)
59+
5060
it.each(['accessToken', 'apiKey', 'headers', '_context', 'impersonateUserEmail', 'instanceUrl'])(
5161
'rejects model-supplied %s before execution',
5262
(name) =>

‎apps/sim/lib/mothership/assistant/tool-policy.ts‎

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,52 @@ export const ASSISTANT_TOOLS = new Set([
1010

1111
const CREDENTIAL_PARAMS = new Set(['credential', 'credentialId', 'oauthCredential'])
1212

13-
/** Assistant uses the regular integration registry, with authentication supplied by the caller's account. */
13+
/** Read-only lookups complement search_workspace without exposing provider writes. */
14+
const ASSISTANT_INTEGRATION_TOOLS = new Set([
15+
'slack_list_users',
16+
'slack_get_user',
17+
'slack_list_channels',
18+
'slack_list_user_conversations',
19+
'slack_get_channel_info',
20+
'slack_list_members',
21+
'gmail_list_labels_v2',
22+
'google_calendar_list_calendars_v2',
23+
'google_calendar_get_v2',
24+
'google_calendar_instances_v2',
25+
'google_calendar_freebusy_v2',
26+
'google_drive_get_file',
27+
'google_drive_list_comments',
28+
'google_sheets_get_spreadsheet_v2',
29+
'google_sheets_read_v2',
30+
'jira_search_users',
31+
'jira_list_projects',
32+
'jira_get_project',
33+
'jira_get_fields',
34+
'jira_get_comments',
35+
'confluence_list_spaces',
36+
'confluence_get_user',
37+
'confluence_get_page_children',
38+
'confluence_get_page_ancestors',
39+
'confluence_list_comments',
40+
'github_search_users_v2',
41+
'github_repo_info_v2',
42+
'github_get_tree_v2',
43+
'github_list_review_threads',
44+
'github_get_pr_files_v2',
45+
'gitlab_search_users',
46+
'gitlab_list_members',
47+
'gitlab_list_projects',
48+
'gitlab_get_merge_request_changes',
49+
'coda_resolve_browser_link',
50+
'coda_list_pages',
51+
'coda_list_tables',
52+
'coda_list_columns',
53+
'coda_list_rows',
54+
])
55+
56+
/** Discovery and execution share the same operations and personal-account requirements. */
1457
export function isAssistantIntegrationTool(tool: ToolMetadata | undefined): boolean {
15-
if (!tool) return false
58+
if (!tool || !ASSISTANT_INTEGRATION_TOOLS.has(tool.id)) return false
1659
tool = projectAssistantConnectedAccountTool(tool)
1760
const tokenBinding = tool.personalToken
1861
const supportsToken =

0 commit comments

Comments
 (0)