Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
cc6dd5b
feat(copilot): identify current workspace in workspace list
j15z Aug 13, 2026
ea295e2
feat(copilot): replace search_documentation with path-scoped search_d…
j15z Jul 24, 2026
010f0a6
improvement(copilot): label docs corpus reads as Section/filename in …
j15z Jul 24, 2026
c5f355e
improvement(copilot): show the query in search_docs tool chips
j15z Jul 24, 2026
9aa29aa
feat(copilot): build the docs vfs from a generated manifest, rescope …
j15z Jul 24, 2026
c6b8eec
fix(review): act on docs-vfs review findings
j15z Jul 24, 2026
da102c7
fix(copilot): explain a short or empty search_docs result set
j15z Jul 25, 2026
32e07e5
fix(copilot): include a section overview in either layout when scopin…
j15z Jul 25, 2026
7fdce71
fix(copilot): make the search_docs topK clamp type-safe and test it
j15z Jul 25, 2026
b175d0a
fix(copilot): restore the query in search_docs tool chips
j15z Jul 25, 2026
bb65387
improvement(copilot): share the unmounted-docs list, shrink the searc…
j15z Jul 28, 2026
8dd7bf6
chore(copilot): regenerate the tool catalog for the retired quick-ref…
j15z Jul 28, 2026
2bb974c
fix(review): attach the scope-error TSDoc to the class it documents
j15z Jul 29, 2026
ae25efd
fix(review): harden docs corpus edges — trailing-slash glob, root-ind…
j15z Jul 29, 2026
5e66306
chore(copilot): regenerate the tool catalog for the lean search agent
j15z Jul 29, 2026
d28b956
improvement(copilot): retire search_documentation and get_platform_ac…
j15z Jul 29, 2026
df72959
changed search_docs tool title to Searching Sim docs
j15z Jul 29, 2026
d651d6f
fix(copilot): apply the Searching Sim docs rename to the dynamic titl…
j15z Jul 29, 2026
ae89151
improvement(copilot): retry docs fetches and grep docs directories in…
j15z Aug 6, 2026
b272cf1
chore(copilot): drop the retired search_documentation test
j15z Aug 6, 2026
407c7c7
test(copilot): cover directory-scoped docs grep at the handler level
j15z Aug 6, 2026
a807e2e
chore(copilot): regenerate the docs manifest for staging docs content
j15z Aug 7, 2026
8b5a8d8
chore(copilot): resync the grep tool description from mothership cont…
j15z Aug 7, 2026
60cccd2
improvement(copilot): stamp docs grep fan-out size on the grep span
j15z Aug 7, 2026
4107177
chore(copilot): resync docs and mothership contracts after restack
j15z Aug 12, 2026
be702bc
improvement(copilot): narrow the docs search rollout
j15z Aug 13, 2026
4805cf3
improvement(copilot): preserve docs path casing in read labels
j15z Aug 13, 2026
a16a593
improvement(copilot): keep @Docs on search_docs
j15z Aug 13, 2026
2d02043
fix(copilot): preserve docs search guidance (#6389)
j15z Aug 13, 2026
935edc4
docs(copilot): document VFS grep routing
j15z Aug 13, 2026
a82622f
fix(copilot): harden docs context retrieval
j15z Aug 13, 2026
eb2867f
chore(copilot): sync search context contract
j15z Aug 13, 2026
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
3 changes: 3 additions & 0 deletions .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ jobs:
- name: Repo audits
run: bun run check:audits

- name: Verify docs manifest is in sync
run: bun run docs-manifest:check

- name: Migration safety (zero-downtime) audit
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
Expand Down
10 changes: 8 additions & 2 deletions apps/sim/app/api/mothership/execute/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,13 @@ describe('mothership private trace provenance transport', () => {
'user-1',
'secret-value __var_FOREIGN',
'workspace-1',
'chat-1'
'chat-1',
expect.any(Object)
)

const contextRegistry = mockProcessContextsServer.mock.calls.at(-1)?.[5]
const lifecycleOptions = mockRunHeadlessCopilotLifecycle.mock.calls.at(-1)?.[1]
expect(contextRegistry).toBe(lifecycleOptions.environmentContext?.resolvedSecretTraceRegistry)
})

it('keeps context routing and display inputs raw until the lifecycle boundary', async () => {
Expand Down Expand Up @@ -290,7 +295,8 @@ describe('mothership private trace provenance transport', () => {
'user-1',
'hello',
'workspace-1',
'chat-1'
'chat-1',
expect.any(Object)
)
})

Expand Down
3 changes: 2 additions & 1 deletion apps/sim/app/api/mothership/execute/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,8 @@ export const POST = withRouteHandler(async (req: NextRequest) => {
userId,
lastUserMessage,
workspaceId,
effectiveChatId
effectiveChatId,
activeResolvedSecretTraceRegistry
).catch((error) => {
reqLogger.warn('Failed to resolve agent contexts for execution', {
error: toError(error).message,
Expand Down
6 changes: 4 additions & 2 deletions apps/sim/lib/copilot/chat/post.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,8 @@ describe('handleUnifiedChatPost', () => {
'user-1',
'Hello',
'ws-1',
expect.anything()
expect.anything(),
expect.any(ResolvedSecretTraceRegistry)
)
})

Expand Down Expand Up @@ -448,7 +449,8 @@ describe('handleUnifiedChatPost', () => {
'user-1',
'Explain these selections',
'ws-1',
'chat-1'
'chat-1',
expect.any(ResolvedSecretTraceRegistry)
)
})

Expand Down
18 changes: 15 additions & 3 deletions apps/sim/lib/copilot/chat/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,9 +461,19 @@ async function resolveAgentContexts(params: {
message: string
workspaceId?: string
chatId?: string
resolvedSecretTraceRegistry?: ExecutionContext['resolvedSecretTraceRegistry']
requestId: string
}): Promise<Array<{ type: string; content: string; tag?: string; path?: string }>> {
const { contexts, resourceAttachments, userId, message, workspaceId, chatId, requestId } = params
const {
contexts,
resourceAttachments,
userId,
message,
workspaceId,
chatId,
resolvedSecretTraceRegistry,
requestId,
} = params

let agentContexts: Array<{ type: string; content: string; tag?: string; path?: string }> = []

Expand All @@ -474,7 +484,8 @@ async function resolveAgentContexts(params: {
userId,
message,
workspaceId,
chatId
chatId,
resolvedSecretTraceRegistry
)
} catch (error) {
logger.error(`[${requestId}] Failed to process contexts`, error)
Expand Down Expand Up @@ -1267,7 +1278,7 @@ export async function handleUnifiedChatPost(req: NextRequest) {
}),
activeOtelRoot.context
)
const agentContextsPromise = executionContextPromise.then(() => {
const agentContextsPromise = executionContextPromise.then((executionContext) => {
return withCopilotSpan(
TraceSpan.CopilotChatResolveAgentContexts,
{
Expand All @@ -1282,6 +1293,7 @@ export async function handleUnifiedChatPost(req: NextRequest) {
message: body.message,
workspaceId,
chatId: actualChatId,
resolvedSecretTraceRegistry: executionContext.resolvedSecretTraceRegistry,
requestId,
}),
activeOtelRoot.context
Expand Down
121 changes: 121 additions & 0 deletions apps/sim/lib/copilot/chat/process-contents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
MAX_TABLE_SELECTION_ROWS,
} from '@/lib/copilot/chat/selection-context'
import { DelegatedWorkspaceAuthorizationError } from '@/lib/core/application'
import { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry'
import type { ChatContext } from '@/stores/panel'

const {
Expand All @@ -25,6 +26,7 @@ const {
readKnowledgeBase,
getBlockVisibilityForCopilot,
isIntegrationDeploymentAvailable,
searchDocsExecute,
} = vi.hoisted(() => ({
discoverServerTools: vi.fn(),
getBlock: vi.fn(),
Expand All @@ -38,6 +40,7 @@ const {
readKnowledgeBase: vi.fn(),
getBlockVisibilityForCopilot: vi.fn(async () => null),
isIntegrationDeploymentAvailable: vi.fn(() => true),
searchDocsExecute: vi.fn(),
}))

vi.mock('@/blocks/registry', () => ({ getBlock, getBlockRegistry }))
Expand All @@ -57,6 +60,9 @@ vi.mock('@/lib/table/rows/service', () => ({ getRowsByIds }))
vi.mock('@/lib/knowledge/application/knowledge-bases', () => ({
readKnowledgeBase: { execute: readKnowledgeBase },
}))
vi.mock('@/lib/copilot/tools/server/docs/search-docs', () => ({
searchDocsServerTool: { execute: searchDocsExecute },
}))

/**
* Overrides the global `@sim/db` mock: the logs-context tests below need
Expand Down Expand Up @@ -282,6 +288,121 @@ describe('processContextsServer - skill contexts', () => {
})
})

describe('processContextsServer - docs contexts', () => {
beforeEach(() => {
vi.clearAllMocks()
})

it('routes @Docs to an unscoped search_docs query', async () => {
const resolvedSecretTraceRegistry = new ResolvedSecretTraceRegistry()
const results = [
{
path: 'docs/workflows/loops.mdx',
url: 'https://docs.sim.ai/workflows/loops',
title: 'Loops',
content: 'Use a loop block to iterate.',
similarity: 0.9,
},
]
searchDocsExecute.mockResolvedValue({ results, query: 'how do loops work?', totalResults: 1 })

const result = await processContextsServer(
[{ kind: 'docs', label: 'Docs' }],
'user-1',
'@Docs how do loops work?',
'ws-1',
undefined,
resolvedSecretTraceRegistry
)

expect(searchDocsExecute).toHaveBeenCalledWith(
{ query: 'how do loops work?' },
{
userId: 'user-1',
workspaceId: 'ws-1',
chatId: undefined,
resolvedSecretTraceRegistry,
}
)
expect(result).toEqual([
{
type: 'docs',
tag: '@Docs',
content: JSON.stringify({ results }),
},
])
})

it('preserves the search note when @Docs has no relevant matches', async () => {
const note =
'No relevant matches. This does NOT mean the docs lack this topic. Rephrase the query.'
searchDocsExecute.mockResolvedValue({ results: [], query: 'new topic', totalResults: 0, note })

const result = await processContextsServer(
[{ kind: 'docs', label: 'Docs' }],
'user-1',
'@Docs new topic',
'ws-1',
undefined,
new ResolvedSecretTraceRegistry()
)

expect(result).toEqual([
{
type: 'docs',
tag: '@Docs',
content: JSON.stringify({ results: [], note }),
},
])
})

it('uses the Docs label when the message only contains the mention', async () => {
searchDocsExecute.mockResolvedValue({ results: [], query: 'Docs', totalResults: 0 })

await processContextsServer(
[{ kind: 'docs', label: 'Docs' }],
'user-1',
'@Docs',
'ws-1',
'chat-1',
new ResolvedSecretTraceRegistry()
)

expect(searchDocsExecute).toHaveBeenCalledWith(
{ query: 'Docs' },
expect.objectContaining({ workspaceId: 'ws-1', chatId: 'chat-1' })
)
})

it('preserves an explicit unavailable note when docs search fails', async () => {
searchDocsExecute.mockRejectedValue(new Error('embedding service unavailable'))

const result = await processContextsServer(
[{ kind: 'docs', label: 'Docs' }],
'user-1',
'@Docs explain schedules',
'ws-1',
'chat-1',
new ResolvedSecretTraceRegistry()
)

expect(result).toEqual([
{
type: 'docs',
tag: '@Docs',
content: JSON.stringify({
results: [],
note: 'Documentation search is temporarily unavailable. Do not infer that the docs lack this topic; retry search_docs or browse docs/** later.',
}),
},
])
expect(mockProcessContentsLogger.error).toHaveBeenCalledWith(
'Failed to process docs context',
expect.any(Error)
)
})
})

describe('processContextsServer - MCP contexts', () => {
beforeEach(() => {
vi.clearAllMocks()
Expand Down
35 changes: 28 additions & 7 deletions apps/sim/lib/copilot/chat/process-contents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { readWorkspaceFileMetadata } from '@/lib/workspace-files/application/rea
import { parseWorkspaceFileFolderDisplayPath } from '@/lib/workspace-files/folder-display-path'
import { getUserPermissionConfig } from '@/ee/access-control/utils/permission-check'
import { escapeRegExp } from '@/executor/constants'
import type { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry'
import type { BrowserTextSelection, ChatContext, TerminalTextSelection } from '@/stores/panel'

type AgentContextType =
Expand Down Expand Up @@ -124,7 +125,8 @@ export async function processContextsServer(
userId: string,
userMessage?: string,
currentWorkspaceId?: string,
chatId?: string
chatId?: string,
resolvedSecretTraceRegistry?: ResolvedSecretTraceRegistry
): Promise<AgentContext[]> {
if (!Array.isArray(contexts) || contexts.length === 0) return []
const tasks = contexts.map(async (ctx) => {
Expand Down Expand Up @@ -314,17 +316,36 @@ export async function processContextsServer(
}
if (ctx.kind === 'docs') {
try {
const { searchDocumentationServerTool } = await import(
'@/lib/copilot/tools/server/docs/search-documentation'
const { searchDocsServerTool } = await import(
'@/lib/copilot/tools/server/docs/search-docs'
)
const rawQuery = (userMessage || '').trim() || ctx.label || 'Sim documentation'
const query = sanitizeMessageForDocs(rawQuery, contexts)
const res = await searchDocumentationServerTool.execute({ query, topK: 10 })
const content = JSON.stringify(res?.results || [])
const query =
sanitizeMessageForDocs(rawQuery, contexts) || ctx.label || 'Sim documentation'
const res = await searchDocsServerTool.execute(
{ query },
{
userId,
workspaceId: currentWorkspaceId,
chatId,
resolvedSecretTraceRegistry,
}
)
const content = JSON.stringify({
results: res?.results || [],
...(res?.note ? { note: res.note } : {}),
})
return { type: 'docs', tag: ctx.label ? `@${ctx.label}` : '@', content }
Comment thread
j15z marked this conversation as resolved.
} catch (e) {
logger.error('Failed to process docs context', e)
return null
return {
type: 'docs',
tag: ctx.label ? `@${ctx.label}` : '@',
content: JSON.stringify({
results: [],
note: 'Documentation search is temporarily unavailable. Do not infer that the docs lack this topic; retry search_docs or browse docs/** later.',
}),
}
}
}
return null
Expand Down
Loading