Skip to content

Commit d4f32d6

Browse files
authored
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 e4225f5 commit d4f32d6

21 files changed

Lines changed: 365 additions & 140 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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,21 @@ 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+
303318

304319

305320
## Triggers

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

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -53,35 +53,6 @@ Query repositories in natural language and get answers with relevant code refere
5353
| ↳ `summary` | string | Summary of the code section |
5454
| ↳ `distance` | number | Similarity score \(lower = more relevant\) |
5555

56-
### Greptile Search
57-
58-
Search repositories in natural language and get relevant code references without generating an answer. Useful for finding specific code locations.
59-
60-
#### Input
61-
62-
| Parameter | Type | Required | Description |
63-
| --------- | ---- | -------- | ----------- |
64-
| `query` | string | Yes | Natural language search query to find relevant code. Example: "authentication middleware" or "database connection handling" |
65-
| `repositories` | string | Yes | Comma-separated list of repositories. Format: "github:branch:owner/repo" or just "owner/repo" \(defaults to github:main\). Example: "facebook/react" or "github:main:facebook/react,github:main:facebook/relay" |
66-
| `sessionId` | string | No | Session ID for conversation continuity. Use the same sessionId across multiple searches to maintain context. Example: "session-abc123" |
67-
| `genius` | boolean | No | Enable genius mode for more thorough search \(slower but more accurate\) |
68-
| `apiKey` | string | Yes | Greptile API key |
69-
| `githubToken` | string | Yes | GitHub Personal Access Token with repo read access |
70-
71-
#### Output
72-
73-
| Parameter | Type | Description |
74-
| --------- | ---- | ----------- |
75-
| `sources` | array | Relevant code references matching the search query |
76-
| ↳ `repository` | string | Repository name \(owner/repo\) |
77-
| ↳ `remote` | string | Git remote \(github/gitlab\) |
78-
| ↳ `branch` | string | Branch name |
79-
| ↳ `filepath` | string | Path to the file |
80-
| ↳ `linestart` | number | Starting line number |
81-
| ↳ `lineend` | number | Ending line number |
82-
| ↳ `summary` | string | Summary of the code section |
83-
| ↳ `distance` | number | Similarity score \(lower = more relevant\) |
84-
8556
### Greptile Index Repository
8657

8758
Submit a repository to be indexed by Greptile. Indexing must complete before the repository can be queried. Small repos take 3-5 minutes, larger ones can take over an hour.

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2521,7 +2521,10 @@ export const GitHubV2Block: BlockConfig<GitHubResponse> = {
25212521
integrationType: IntegrationType.DevOps,
25222522
tools: {
25232523
...GitHubBlock.tools,
2524-
access: (GitHubBlock.tools?.access || []).map((toolId) => `${toolId}_v2`),
2524+
access: [
2525+
...(GitHubBlock.tools?.access || []).map((toolId) => `${toolId}_v2`),
2526+
'github_list_review_threads',
2527+
],
25252528
config: {
25262529
...GitHubBlock.tools?.config,
25272530
tool: createVersionedToolSelector({

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,7 @@ export const GmailV2Block: BlockConfig<GmailToolResponse> = {
679679
'gmail_delete_v2',
680680
'gmail_add_label_v2',
681681
'gmail_remove_label_v2',
682+
'gmail_list_labels_v2',
682683
],
683684
config: {
684685
...GmailBlock.tools?.config,

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

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
55
import { clearAtlassianCloudIdCache } from '@/lib/atlassian/discovery'
66
import { createConfluenceClient } from '@/lib/internal/confluence/client'
77
import { createJiraClient } from '@/lib/internal/jira/client'
8-
import {
9-
assertAssistantIntegrationCall,
10-
isAssistantIntegrationTool,
11-
} from '@/lib/mothership/assistant/tool-policy'
8+
import { assertAssistantIntegrationCall } from '@/lib/mothership/assistant/tool-policy'
129
import { getToolMetadata } from '@/tools/metadata'
13-
import { getToolIds } from '@/tools/tool-ids'
1410

1511
vi.unmock('@/tools/metadata')
16-
vi.unmock('@/tools/tool-ids')
1712

1813
const CLOUD_ID = '12345678-1234-1234-1234-123456789012'
1914
const OTHER_CLOUD_ID = '12345678-1234-1234-1234-123456789013'
@@ -32,29 +27,21 @@ describe('Atlassian Assistant resource selection', () => {
3227

3328
afterEach(() => vi.unstubAllGlobals())
3429

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

‎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. */
@@ -51,6 +51,12 @@ const FEATURE_FLAGS = {
5151
'Enable native macOS computer use in Mothership. Global on/off only; each device must also opt in.',
5252
fallback: 'MSHIP_COMPUTER_USE',
5353
},
54+
'mothership-search-integration-tools': {
55+
description:
56+
'Give Search Assistant read-only integration discovery, calls, and matching prompt ' +
57+
'instructions. Global AppConfig on/off only; disabled by default with no env fallback.',
58+
fallback: null,
59+
},
5460
'mothership-model-selector': {
5561
description:
5662
'Show the Mothership model selector, model-specific effort levels, and Fast for supported ' +
@@ -143,7 +149,7 @@ const FEATURE_FLAGS = {
143149

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

@@ -153,7 +159,7 @@ function fallbackFlags(): FeatureFlagsConfig {
153159
for (const [name, def] of Object.entries(FEATURE_FLAGS) as Array<
154160
[string, FeatureFlagDefinition]
155161
>) {
156-
flags[name] = { enabled: isTruthy(env[def.fallback]) }
162+
flags[name] = { enabled: def.fallback !== null && isTruthy(env[def.fallback]) }
157163
}
158164
return flags
159165
}

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

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

1111
const tool: ToolMetadata = {
12-
id: 'service_write',
12+
id: 'google_drive_get_file',
1313
oauth: { required: true, provider: 'google-drive', authoritativeParams: ['instanceUrl'] },
1414
params: {
1515
credential: { type: 'string', visibility: 'user-only' },
@@ -22,7 +22,7 @@ const tool: ToolMetadata = {
2222

2323
describe('Assistant integration policy', () => {
2424
const tokenTool: ToolMetadata = {
25-
id: 'gitlab_get_project',
25+
id: 'gitlab_list_projects',
2626
personalToken: { provider: 'gitlab', tokenParam: 'accessToken', hostParam: 'host' },
2727
params: {
2828
accessToken: { type: 'string', required: true, visibility: 'user-only' },
@@ -44,12 +44,22 @@ describe('Assistant integration policy', () => {
4444
expect(isAssistantIntegrationTool({ ...tokenTool, params: {} })).toBe(false)
4545
})
4646

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

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

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

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

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

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

0 commit comments

Comments
 (0)