Skip to content

Commit a8f7f80

Browse files
committed
improvement(copilot): keep @docs on search_docs
1 parent fbf8ab2 commit a8f7f80

19 files changed

Lines changed: 313 additions & 43 deletions

File tree

apps/sim/app/api/mothership/execute/route.test.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ describe('mothership private trace provenance transport', () => {
224224
{
225225
...requestBody,
226226
messages: [{ role: 'user', content: 'secret-value __var_FOREIGN' }],
227-
contexts: [{ kind: 'knowledge', knowledgeId: 'knowledge-1', label: 'Docs' }],
227+
contexts: [{ kind: 'docs', label: 'Docs' }],
228228
},
229229
{ Authorization: 'Bearer internal', 'x-sim-billing-attribution': 'billing' },
230230
'http://localhost:3000/api/mothership/execute'
@@ -235,9 +235,15 @@ describe('mothership private trace provenance transport', () => {
235235
expect(mockProcessContextsServer).toHaveBeenCalledWith(
236236
expect.any(Array),
237237
'user-1',
238+
'secret-value __var_FOREIGN',
238239
'workspace-1',
239-
'chat-1'
240+
'chat-1',
241+
expect.any(Object)
240242
)
243+
244+
const contextRegistry = mockProcessContextsServer.mock.calls.at(-1)?.[5]
245+
const lifecycleOptions = mockRunHeadlessCopilotLifecycle.mock.calls.at(-1)?.[1]
246+
expect(contextRegistry).toBe(lifecycleOptions.environmentContext?.resolvedSecretTraceRegistry)
241247
})
242248

243249
it('keeps context routing and display inputs raw until the lifecycle boundary', async () => {
@@ -287,8 +293,10 @@ describe('mothership private trace provenance transport', () => {
287293
},
288294
],
289295
'user-1',
296+
'hello',
290297
'workspace-1',
291-
'chat-1'
298+
'chat-1',
299+
expect.any(Object)
292300
)
293301
})
294302

apps/sim/app/api/mothership/execute/route.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => {
211211
workflowId,
212212
executionId,
213213
})
214+
const lastUserMessage = messages.filter((message) => message.role === 'user').at(-1)?.content
214215
// double-cast-allowed: the contract validates contexts as open kind/label objects; processContextsServer narrows on `kind` at runtime
215216
const agentMentions = contexts as unknown as ChatContext[] | undefined
216217
const taggedMcpServerIds = (agentMentions ?? []).flatMap((context) =>
@@ -238,14 +239,19 @@ export const POST = withRouteHandler(async (req: NextRequest) => {
238239
buildIntegrationToolSchemas(userId, messageId, undefined, workspaceId),
239240
mothershipToolsPromise,
240241
computeWorkspaceEntitlements(workspaceId, userId),
241-
processContextsServer(nonMcpAgentMentions, userId, workspaceId, effectiveChatId).catch(
242-
(error) => {
243-
reqLogger.warn('Failed to resolve agent contexts for execution', {
244-
error: toError(error).message,
245-
})
246-
return []
247-
}
248-
),
242+
processContextsServer(
243+
nonMcpAgentMentions,
244+
userId,
245+
lastUserMessage,
246+
workspaceId,
247+
effectiveChatId,
248+
activeResolvedSecretTraceRegistry
249+
).catch((error) => {
250+
reqLogger.warn('Failed to resolve agent contexts for execution', {
251+
error: toError(error).message,
252+
})
253+
return []
254+
}),
249255
])
250256
const requestPayload: Record<string, unknown> = {
251257
messages,

apps/sim/app/workspace/[workspaceId]/home/components/chat-context-kind-registry/chat-context-kind-registry.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ export const CHAT_CONTEXT_KIND_REGISTRY: Record<ChatContextKind, ChatContextKind
110110
label: 'Logs',
111111
renderIcon: ({ className }) => <Library className={className} />,
112112
},
113+
docs: { label: 'Docs', renderIcon: () => null },
113114
slash_command: { label: 'Command', renderIcon: () => null },
114115
integration: { label: 'Integration', renderIcon: renderIntegrationTile },
115116
skill: {

apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/chip-clipboard-codec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ const CHIP_LINK_SCHEME = 'sim'
1919
* string>>` keeps it union-synced: rename a kind's id field and this stops
2020
* type-checking.
2121
*
22-
* Kinds absent from this map have no portable single-id representation and
23-
* degrade to plain text.
22+
* Excluded kinds (`current_workflow`, `blocks`, `workflow_block`, `docs`) carry
23+
* no single portable id (an array / two ids / none) and degrade to plain text.
2424
*/
2525
const PORTABLE_KIND_TO_ID_FIELD = {
2626
table: 'tableId',

apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/constants.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export const SPEECH_RECOGNITION_LANG = 'en-US'
113113
// inner tab. The singleton ids ask the agent to inspect the whole resource;
114114
// every other id is a precise live-tab pointer.
115115
const RESOURCE_TO_CONTEXT: Record<
116-
Exclude<MothershipResourceType, 'generic'>,
116+
MothershipResourceType,
117117
(resource: MothershipResource) => ChatContext
118118
> = {
119119
browser: (r) => ({ kind: 'browser_tab', tabId: r.id, label: r.title }),
@@ -127,9 +127,9 @@ const RESOURCE_TO_CONTEXT: Record<
127127
task: (r) => ({ kind: 'past_chat', chatId: r.id, label: r.title }),
128128
log: (r) => ({ kind: 'logs', executionId: r.id, label: r.title }),
129129
integration: (r) => ({ kind: 'integration', blockType: r.id, label: r.title }),
130+
generic: (r) => ({ kind: 'docs', label: r.title }),
130131
}
131132

132-
export function mapResourceToContext(resource: MothershipResource): ChatContext | null {
133-
if (resource.type === 'generic') return null
133+
export function mapResourceToContext(resource: MothershipResource): ChatContext {
134134
return RESOURCE_TO_CONTEXT[resource.type](resource)
135135
}

apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/prompt-editor/use-prompt-editor.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -407,9 +407,6 @@ export function usePromptEditor({
407407

408408
const insertResource = useCallback(
409409
(resource: MothershipResource) => {
410-
const context = mapResourceToContext(resource)
411-
if (!context) return
412-
413410
const textarea = textareaRef.current
414411
if (textarea) {
415412
const currentValue = valueRef.current
@@ -445,6 +442,7 @@ export function usePromptEditor({
445442
setValueState(newValue)
446443
}
447444

445+
const context = mapResourceToContext(resource)
448446
addContextNotified(context)
449447
},
450448
[textareaRef, addContextNotified]

apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/resource-context.test.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,4 @@ describe('mapResourceToContext', () => {
4747
label: 'Leads',
4848
})
4949
})
50-
51-
it('does not turn a synthetic panel into a chat context', () => {
52-
expect(mapResourceToContext(resource({ type: 'generic', title: 'Results' }))).toBeNull()
53-
})
5450
})

apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,8 @@ function isChatContext(value: unknown): value is ChatContext {
458458
return typeof value.folderId === 'string'
459459
case 'filefolder':
460460
return typeof value.fileFolderId === 'string'
461+
case 'docs':
462+
return true
461463
case 'slash_command':
462464
return typeof value.command === 'string'
463465
case 'integration':

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/constants.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ export type MentionFolderId =
1212
| 'logs'
1313
| 'integrations'
1414

15+
/**
16+
* Menu item category types for mention menu (includes folders + docs item)
17+
*/
18+
export type MentionCategory = MentionFolderId | 'docs'
19+
1520
/**
1621
* Configuration interface for folder types
1722
*/
@@ -179,9 +184,17 @@ export const FOLDER_ORDER: MentionFolderId[] = [
179184
]
180185

181186
/**
182-
* Total number of items in the root menu.
187+
* Docs item configuration (special case - not a folder)
188+
*/
189+
export const DOCS_CONFIG = {
190+
getLabel: () => 'Docs',
191+
buildContext: (): ChatContext => ({ kind: 'docs', label: 'Docs' }),
192+
} as const
193+
194+
/**
195+
* Total number of items in root menu (folders + docs)
183196
*/
184-
export const ROOT_MENU_ITEM_COUNT = FOLDER_ORDER.length
197+
export const ROOT_MENU_ITEM_COUNT = FOLDER_ORDER.length + 1
185198

186199
/**
187200
* Slash command configuration

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/hooks/use-mention-insert-handlers.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useCallback, useMemo } from 'react'
22
import {
3+
DOCS_CONFIG,
34
FOLDER_CONFIGS,
45
type FolderConfig,
56
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/constants'
@@ -88,6 +89,36 @@ export function useMentionInsertHandlers({
8889
]
8990
)
9091

92+
/**
93+
* Special handler for Docs (no item parameter, uses DOCS_CONFIG)
94+
*/
95+
const insertDocsMention = useCallback(() => {
96+
const label = DOCS_CONFIG.getLabel()
97+
const context = DOCS_CONFIG.buildContext()
98+
99+
// Prevent duplicate insertion
100+
if (isContextAlreadySelected(context, selectedContexts)) {
101+
resetActiveMentionQuery()
102+
closeMenus()
103+
return
104+
}
105+
106+
// Docs uses fallback insertion
107+
if (!replaceActiveMentionWith(label)) {
108+
insertAtCursor(` @${label} `)
109+
}
110+
111+
onContextAdd(context)
112+
closeMenus()
113+
}, [
114+
selectedContexts,
115+
replaceActiveMentionWith,
116+
insertAtCursor,
117+
onContextAdd,
118+
resetActiveMentionQuery,
119+
closeMenus,
120+
])
121+
91122
const handlers = useMemo(
92123
() => ({
93124
insertPastChatMention: createInsertHandler(FOLDER_CONFIGS.chats),
@@ -97,8 +128,9 @@ export function useMentionInsertHandlers({
97128
insertWorkflowBlockMention: createInsertHandler(FOLDER_CONFIGS['workflow-blocks']),
98129
insertLogMention: createInsertHandler(FOLDER_CONFIGS.logs),
99130
insertIntegrationMention: createInsertHandler(FOLDER_CONFIGS.integrations),
131+
insertDocsMention,
100132
}),
101-
[createInsertHandler]
133+
[createInsertHandler, insertDocsMention]
102134
)
103135

104136
return handlers

0 commit comments

Comments
 (0)