feat(floating-ai): step 6 — pass uiContext through to the AI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Roberto Musso
2026-02-28 09:40:00 +01:00
parent 6c498c5f40
commit d12681b79f
3 changed files with 16 additions and 12 deletions

View File

@@ -740,7 +740,7 @@ currentSender = sender;
## Step 6: Pass `uiContext` Through to the AI ## Step 6: Pass `uiContext` Through to the AI
**Status**: [ ] **Status**: [x] (2026-02-28)
**Prerequisites**: Step 5 completed **Prerequisites**: Step 5 completed
**Modifies**: **Modifies**:
- `src/main/router/index.ts` (line ~550-556) - `src/main/router/index.ts` (line ~550-556)

View File

@@ -35,7 +35,7 @@ let currentSender: Electron.WebContents | undefined;
export interface OrchestrateInput { export interface OrchestrateInput {
message: string; message: string;
context: { type: 'global' | 'project'; projectId?: string }; context: { type: 'global' | 'project'; projectId?: string; uiContext?: string };
sender?: Electron.WebContents; sender?: Electron.WebContents;
} }
@@ -445,7 +445,7 @@ function buildKnowledgeTools(): StructuredTool[] {
// System prompts // System prompts
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function makeProjectAgentPrompt(contextData: string, withTools = true): string { function makeProjectAgentPrompt(contextData: string, withTools = true, uiContext?: string): string {
const toolsSection = withTools ? ` const toolsSection = withTools ? `
You also have access to the following tools — use them proactively when appropriate: You also have access to the following tools — use them proactively when appropriate:
- read_project_notes: Fetch full untruncated note content. Use for detailed note questions. - read_project_notes: Fetch full untruncated note content. Use for detailed note questions.
@@ -464,10 +464,10 @@ ${contextData}
${toolsSection} ${toolsSection}
Answer the user's question based on this project context. Be concise and helpful. Answer the user's question based on this project context. Be concise and helpful.
When referencing tasks, notes, or checkpoints, mention them by name. When referencing tasks, notes, or checkpoints, mention them by name.
If you don't have enough information, say so.`; If you don't have enough information, say so.${uiContext ? `\nThe user is currently viewing the "${uiContext}" section of the UI.\nIf your response relates to a different section (e.g., user asks about checkpoints while viewing Tasks), prefix your response with [SECTION:<section-id>] where section-id matches one of: project-summary, project-timeline, project-tasks, project-notes, tasks-overview, tasks-list, timeline-chart, note-editor.\nOnly use this prefix when the answer clearly belongs in a different section than where the user currently is.` : ''}`;
} }
function makeGeneralAgentPrompt(contextData: string, withTools = true): string { function makeGeneralAgentPrompt(contextData: string, withTools = true, uiContext?: string): string {
const toolsSection = withTools ? ` const toolsSection = withTools ? `
You also have access to the following tools — use them proactively when appropriate: You also have access to the following tools — use them proactively when appropriate:
- add_task: Create a new task. Use whenever the user asks to add, register, or note a to-do item or task. - add_task: Create a new task. Use whenever the user asks to add, register, or note a to-do item or task.
@@ -481,10 +481,10 @@ You have access to the following workspace data:
${contextData} ${contextData}
${toolsSection} ${toolsSection}
Help the user with their question based on this workspace context. Provide concise, actionable answers. Help the user with their question based on this workspace context. Provide concise, actionable answers.
When discussing tasks or projects, reference them by name.`; When discussing tasks or projects, reference them by name.${uiContext ? `\nThe user is currently viewing the "${uiContext}" section of the UI.\nIf your response relates to a different section (e.g., user asks about checkpoints while viewing Tasks), prefix your response with [SECTION:<section-id>] where section-id matches one of: project-summary, project-timeline, project-tasks, project-notes, tasks-overview, tasks-list, timeline-chart, note-editor.\nOnly use this prefix when the answer clearly belongs in a different section than where the user currently is.` : ''}`;
} }
function makeKnowledgeAgentPrompt(contextData: string, withTools = true): string { function makeKnowledgeAgentPrompt(contextData: string, withTools = true, uiContext?: string): string {
const toolsSection = withTools ? ` const toolsSection = withTools ? `
You have access to the following tools — use them proactively: You have access to the following tools — use them proactively:
- vector_search_all: Performs semantic search across ALL project notes. Always use this tool when the user asks a knowledge question. Pass the user's question (or a refined version) as the query. - vector_search_all: Performs semantic search across ALL project notes. Always use this tool when the user asks a knowledge question. Pass the user's question (or a refined version) as the query.
@@ -504,7 +504,7 @@ ${contextData}
${toolsSection} ${toolsSection}
Your primary job is to find and synthesize information from notes across all projects. Your primary job is to find and synthesize information from notes across all projects.
Always use the vector_search_all tool to search for relevant notes before answering. Always use the vector_search_all tool to search for relevant notes before answering.
If no results are found, say so clearly.`; If no results are found, say so clearly.${uiContext ? `\nThe user is currently viewing the "${uiContext}" section of the UI.\nIf your response relates to a different section (e.g., user asks about checkpoints while viewing Tasks), prefix your response with [SECTION:<section-id>] where section-id matches one of: project-summary, project-timeline, project-tasks, project-notes, tasks-overview, tasks-list, timeline-chart, note-editor.\nOnly use this prefix when the answer clearly belongs in a different section than where the user currently is.` : ''}`;
} }
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -515,7 +515,7 @@ const OrchestratorState = Annotation.Root({
/** The user's original message */ /** The user's original message */
userMessage: Annotation<string>(), userMessage: Annotation<string>(),
/** Chat context (global vs project-scoped) */ /** Chat context (global vs project-scoped) */
chatContext: Annotation<{ type: 'global' | 'project'; projectId?: string }>(), chatContext: Annotation<{ type: 'global' | 'project'; projectId?: string; uiContext?: string }>(),
/** The route chosen by the orchestrator */ /** The route chosen by the orchestrator */
route: Annotation<'project' | 'knowledge' | 'general'>(), route: Annotation<'project' | 'knowledge' | 'general'>(),
/** Messages for the specialist agent */ /** Messages for the specialist agent */
@@ -583,7 +583,8 @@ async function projectAgent(state: State): Promise<Partial<State>> {
// Including text tool descriptions in the system prompt causes the model to output // Including text tool descriptions in the system prompt causes the model to output
// XML <tool_call> blocks instead of using the SDK's API-level mechanism. // XML <tool_call> blocks instead of using the SDK's API-level mechanism.
const includeToolsInPrompt = supportsTools && getActiveProviderName() !== 'copilot'; const includeToolsInPrompt = supportsTools && getActiveProviderName() !== 'copilot';
const systemPrompt = makeProjectAgentPrompt(contextData, includeToolsInPrompt); const uiContext = state.chatContext.uiContext;
const systemPrompt = makeProjectAgentPrompt(contextData, includeToolsInPrompt, uiContext);
if (!supportsTools) { if (!supportsTools) {
console.log('[Orchestrator] projectAgent: using context-only fallback (no tool support)'); console.log('[Orchestrator] projectAgent: using context-only fallback (no tool support)');
@@ -671,7 +672,8 @@ async function knowledgeAgent(state: State): Promise<Partial<State>> {
const supportsTools = TOOL_CALLING_PROVIDERS.has(getActiveProviderName()); const supportsTools = TOOL_CALLING_PROVIDERS.has(getActiveProviderName());
const includeToolsInPrompt = supportsTools && getActiveProviderName() !== 'copilot'; const includeToolsInPrompt = supportsTools && getActiveProviderName() !== 'copilot';
const systemPrompt = makeKnowledgeAgentPrompt(contextData, includeToolsInPrompt); const uiContext = state.chatContext.uiContext;
const systemPrompt = makeKnowledgeAgentPrompt(contextData, includeToolsInPrompt, uiContext);
console.log(`[Orchestrator] knowledgeAgent: provider="${getActiveProviderName()}", supportsTools=${supportsTools}`); console.log(`[Orchestrator] knowledgeAgent: provider="${getActiveProviderName()}", supportsTools=${supportsTools}`);
@@ -749,7 +751,8 @@ async function generalAgent(state: State): Promise<Partial<State>> {
const supportsTools = TOOL_CALLING_PROVIDERS.has(getActiveProviderName()); const supportsTools = TOOL_CALLING_PROVIDERS.has(getActiveProviderName());
const includeToolsInPrompt = supportsTools && getActiveProviderName() !== 'copilot'; const includeToolsInPrompt = supportsTools && getActiveProviderName() !== 'copilot';
const systemPrompt = makeGeneralAgentPrompt(contextData, includeToolsInPrompt); const uiContext = state.chatContext.uiContext;
const systemPrompt = makeGeneralAgentPrompt(contextData, includeToolsInPrompt, uiContext);
console.log(`[Orchestrator] generalAgent: provider="${getActiveProviderName()}", supportsTools=${supportsTools}`); console.log(`[Orchestrator] generalAgent: provider="${getActiveProviderName()}", supportsTools=${supportsTools}`);

View File

@@ -552,6 +552,7 @@ const aiRouter = router({
context: z.object({ context: z.object({
type: z.enum(['global', 'project']), type: z.enum(['global', 'project']),
projectId: z.string().optional(), projectId: z.string().optional(),
uiContext: z.string().optional(),
}), }),
})) }))
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {