Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions apps/docs/content/docs/integrations/github.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3414,6 +3414,46 @@ List users who have starred a repository
| ↳ `repos_url` | string | Repos API URL |
| `count` | number | Number of stargazers returned |

### GitHub List Review Threads

List one page of a pull request's review threads with their comments, plus the newest submitted review.

#### Input

| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `owner` | string | Yes | Repository owner |
| `repo` | string | Yes | Repository name |
| `pullNumber` | number | Yes | Pull request number |
| `threadsPerPage` | number | No | Review threads to fetch in this page \(1-100\) |
| `commentsPerThread` | number | No | Comments to fetch per thread \(1-100\) |
| `cursor` | string | No | Cursor from a previous page \(endCursor\) to continue from |
| `apiKey` | string | Yes | GitHub API token with pull request read access |

#### Output

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `threads` | array | Review threads in this page |
| ↳ `id` | string | Review thread node ID |
| ↳ `isResolved` | boolean | Whether the thread is resolved |
| ↳ `path` | string | Repository-relative file path |
| ↳ `line` | number | Line the thread is anchored to |
| ↳ `commentsTotalCount` | number | Total comments on the thread; exceeds the fetched count when the thread was truncated |
| ↳ `comments` | array | Fetched comments, oldest first |
| ↳ `body` | string | Comment body |
| ↳ `authorAssociation` | string | Author's association with the repository \(OWNER, MEMBER, ...\) |
| ↳ `authorLogin` | string | Author login |
| ↳ `authorType` | string | Author GraphQL type \(User, Bot, Organization\) |
| `totalCount` | number | Total review threads on the pull request |
| `hasNextPage` | boolean | Whether more thread pages remain |
| `endCursor` | string | Cursor to pass as `cursor` for the next page |
| `latestReview` | object | Newest submitted review on the pull request |
| ↳ `state` | string | Review state |
| ↳ `submittedAt` | string | Submission timestamp |
| ↳ `authorLogin` | string | Reviewer login |
| ↳ `authorType` | string | Reviewer GraphQL type \(User, Bot\) |



## Triggers
Expand Down
15 changes: 15 additions & 0 deletions apps/docs/content/docs/integrations/gmail.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,21 @@ Remove label(s) from a Gmail message. Returns API-aligned fields only.
| `threadId` | string | Gmail thread ID |
| `labelIds` | array | Updated email labels |

### Gmail List Labels

List all labels in a Gmail account

#### Input

| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |

#### Output

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `labels` | json | Array of label objects with id, name, type, and visibility settings |



## Triggers
Expand Down
29 changes: 0 additions & 29 deletions apps/docs/content/docs/integrations/greptile.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,35 +53,6 @@ Query repositories in natural language and get answers with relevant code refere
| ↳ `summary` | string | Summary of the code section |
| ↳ `distance` | number | Similarity score \(lower = more relevant\) |

### Greptile Search

Search repositories in natural language and get relevant code references without generating an answer. Useful for finding specific code locations.

#### Input

| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `query` | string | Yes | Natural language search query to find relevant code. Example: "authentication middleware" or "database connection handling" |
| `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" |
| `sessionId` | string | No | Session ID for conversation continuity. Use the same sessionId across multiple searches to maintain context. Example: "session-abc123" |
| `genius` | boolean | No | Enable genius mode for more thorough search \(slower but more accurate\) |
| `apiKey` | string | Yes | Greptile API key |
| `githubToken` | string | Yes | GitHub Personal Access Token with repo read access |

#### Output

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `sources` | array | Relevant code references matching the search query |
| ↳ `repository` | string | Repository name \(owner/repo\) |
| ↳ `remote` | string | Git remote \(github/gitlab\) |
| ↳ `branch` | string | Branch name |
| ↳ `filepath` | string | Path to the file |
| ↳ `linestart` | number | Starting line number |
| ↳ `lineend` | number | Ending line number |
| ↳ `summary` | string | Summary of the code section |
| ↳ `distance` | number | Similarity score \(lower = more relevant\) |

### Greptile Index Repository

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.
Expand Down
5 changes: 4 additions & 1 deletion apps/sim/blocks/blocks/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2521,7 +2521,10 @@ export const GitHubV2Block: BlockConfig<GitHubResponse> = {
integrationType: IntegrationType.DevOps,
tools: {
...GitHubBlock.tools,
access: (GitHubBlock.tools?.access || []).map((toolId) => `${toolId}_v2`),
access: [
...(GitHubBlock.tools?.access || []).map((toolId) => `${toolId}_v2`),
'github_list_review_threads',
Comment thread
Sg312 marked this conversation as resolved.
],
config: {
...GitHubBlock.tools?.config,
tool: createVersionedToolSelector({
Expand Down
1 change: 1 addition & 0 deletions apps/sim/blocks/blocks/gmail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@ export const GmailV2Block: BlockConfig<GmailToolResponse> = {
'gmail_delete_v2',
'gmail_add_label_v2',
'gmail_remove_label_v2',
'gmail_list_labels_v2',
],
config: {
...GmailBlock.tools?.config,
Expand Down
43 changes: 15 additions & 28 deletions apps/sim/lib/atlassian/assistant.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { clearAtlassianCloudIdCache } from '@/lib/atlassian/discovery'
import { createConfluenceClient } from '@/lib/internal/confluence/client'
import { createJiraClient } from '@/lib/internal/jira/client'
import {
assertAssistantIntegrationCall,
isAssistantIntegrationTool,
} from '@/lib/mothership/assistant/tool-policy'
import { assertAssistantIntegrationCall } from '@/lib/mothership/assistant/tool-policy'
import { getToolMetadata } from '@/tools/metadata'
import { getToolIds } from '@/tools/tool-ids'

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

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

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

it.each(['jira', 'confluence'])(
'offers %s operations with a site selector and personal credential',
(service) => {
const tools = getToolIds()
.filter((id) => id.startsWith(`${service}_`))
.map((id) => getToolMetadata(id))
.filter((tool) => tool?.params.domain)
expect(tools.length).toBeGreaterThan(0)
for (const tool of tools) {
expect(tool?.params.domain.visibility, tool?.id).toBe('user-or-llm')
expect(isAssistantIntegrationTool(tool), tool?.id).toBe(true)
it.each(['jira_get_project', 'confluence_list_spaces'])(
'allows site selection for %s without accepting credential overrides',
(toolId) => {
const tool = getToolMetadata(toolId)
expect(() =>
assertAssistantIntegrationCall(tool, { credentialId: 'mine', domain: DOMAIN })
).not.toThrow()
for (const name of ['cloudId', 'accessToken', '_context']) {
expect(() =>
assertAssistantIntegrationCall(tool, { credentialId: 'mine', domain: DOMAIN })
).not.toThrow()
for (const name of ['cloudId', 'accessToken', '_context']) {
expect(() =>
assertAssistantIntegrationCall(tool, {
credentialId: 'mine',
domain: DOMAIN,
[name]: 'override',
})
).toThrow()
}
assertAssistantIntegrationCall(tool, {
credentialId: 'mine',
domain: DOMAIN,
[name]: 'override',
})
).toThrow()
}
}
)
Expand Down
18 changes: 12 additions & 6 deletions apps/sim/lib/core/config/feature-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,17 @@ export type FeatureFlagContext = AppConfigGateContext
/**
* The single definition of a feature flag. Everything about a flag lives in one
* place: its name (the registry key), a human-readable `description`, and the
* `fallback` secret consulted when AppConfig isn't the source of truth (truthy ⇒ on
* globally).
* optional `fallback` secret consulted when AppConfig is not the source of truth.
* A null fallback keeps the flag off outside AppConfig.
*
* Gating by workspace/org/user/admin is deliberately NOT part of a definition — it lives only
* in the hosted AppConfig document, so no environment can grant access from a code
* literal.
*/
interface FeatureFlagDefinition {
description: string
/** Env/secret key consulted when AppConfig isn't the source of truth. Truthy ⇒ on. */
fallback: keyof typeof env
/** Null means AppConfig-only; otherwise a truthy env/secret enables the fallback. */
fallback: keyof typeof env | null
}

/** The single registry of known flags. To add a flag, add one entry here. */
Expand All @@ -51,6 +51,12 @@ const FEATURE_FLAGS = {
'Enable native macOS computer use in Mothership. Global on/off only; each device must also opt in.',
fallback: 'MSHIP_COMPUTER_USE',
},
'mothership-search-integration-tools': {
description:
'Give Search Assistant read-only integration discovery, calls, and matching prompt ' +
'instructions. Global AppConfig on/off only; disabled by default with no env fallback.',
fallback: null,
},
'mothership-model-selector': {
description:
'Show the Mothership model selector, model-specific effort levels, and Fast for supported ' +
Expand Down Expand Up @@ -143,7 +149,7 @@ const FEATURE_FLAGS = {

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

Expand All @@ -153,7 +159,7 @@ function fallbackFlags(): FeatureFlagsConfig {
for (const [name, def] of Object.entries(FEATURE_FLAGS) as Array<
[string, FeatureFlagDefinition]
>) {
flags[name] = { enabled: isTruthy(env[def.fallback]) }
flags[name] = { enabled: def.fallback !== null && isTruthy(env[def.fallback]) }
}
return flags
}
Expand Down
16 changes: 13 additions & 3 deletions apps/sim/lib/mothership/assistant/tool-policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import type { ToolMetadata } from '@/tools/metadata'

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

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

it('allows writes with one explicit connected account', () => {
it('allows selected reads with one explicit connected account', () => {
expect(() =>
assertAssistantIntegrationCall(tool, { credential: 'mine', body: 'updated content' })
).not.toThrow()
})

it.each(['gmail_send', 'google_drive_create_file', 'new_provider_operation'])(
'rejects unapproved operation %s even with a personal account',
(id) => {
expect(isAssistantIntegrationTool({ ...tool, id })).toBe(false)
expect(() =>
assertAssistantIntegrationCall({ ...tool, id }, { credential: 'mine' })
).toThrow()
}
)

it.each(['accessToken', 'apiKey', 'headers', '_context', 'impersonateUserEmail', 'instanceUrl'])(
'rejects model-supplied %s before execution',
(name) =>
Expand Down
47 changes: 45 additions & 2 deletions apps/sim/lib/mothership/assistant/tool-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,52 @@ export const ASSISTANT_TOOLS = new Set([

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

/** Assistant uses the regular integration registry, with authentication supplied by the caller's account. */
/** Read-only lookups complement search_workspace without exposing provider writes. */
const ASSISTANT_INTEGRATION_TOOLS = new Set([
'slack_list_users',
'slack_get_user',
'slack_list_channels',
'slack_list_user_conversations',
'slack_get_channel_info',
'slack_list_members',
'gmail_list_labels_v2',
'google_calendar_list_calendars_v2',
'google_calendar_get_v2',
'google_calendar_instances_v2',
'google_calendar_freebusy_v2',
'google_drive_get_file',
'google_drive_list_comments',
'google_sheets_get_spreadsheet_v2',
'google_sheets_read_v2',
'jira_search_users',
'jira_list_projects',
'jira_get_project',
'jira_get_fields',
'jira_get_comments',
'confluence_list_spaces',
'confluence_get_user',
'confluence_get_page_children',
'confluence_get_page_ancestors',
'confluence_list_comments',
'github_search_users_v2',
'github_repo_info_v2',
'github_get_tree_v2',
'github_list_review_threads',
'github_get_pr_files_v2',
'gitlab_search_users',
'gitlab_list_members',
'gitlab_list_projects',
'gitlab_get_merge_request_changes',
'coda_resolve_browser_link',
'coda_list_pages',
'coda_list_tables',
'coda_list_columns',
'coda_list_rows',
])

/** Discovery and execution share the same operations and personal-account requirements. */
export function isAssistantIntegrationTool(tool: ToolMetadata | undefined): boolean {
if (!tool) return false
if (!tool || !ASSISTANT_INTEGRATION_TOOLS.has(tool.id)) return false
tool = projectAssistantConnectedAccountTool(tool, isLiveEnterpriseSearchEnabled)
const tokenBinding = tool.personalToken
const supportsToken =
Expand Down
Loading
Loading