import * as path from 'path'; import * as vscode from 'vscode'; import { SidebarChatProvider } from '../sidebarProvider'; import { logError, logInfo } from '../utils'; import { resolveScopeForAgent, openKnowledgeMapEditor, getOrCreateAgentEntry, upsertAgentEntry, } from '../skills/agentKnowledgeMap'; import { getActiveBrainProfile } from '../utils'; /** * Handles agent-skill messages: the per-conversation agent picker, agent CRUD, * persisting the user's last selected agent, and the knowledge-map dropdown. */ export async function handleAgentMessage(provider: SidebarChatProvider, data: any): Promise { switch (data.type) { case 'getAgents': await provider._sendAgentsList(); return true; case 'createAgent': await provider._createAgent(); return true; case 'getAgentContent': await provider._sendAgentContent(data.path); return true; case 'updateAgent': await provider._updateAgent(data.path, data.content, data.negativePrompt); return true; case 'deleteAgent': await provider._deleteAgent(data.path); return true; case 'saveAgentSelection': await provider._context.globalState.update(SidebarChatProvider.lastAgentStateKey, data.path || 'none'); logInfo(`Agent selection saved: ${data.path}`); void provider._sendReadyStatus(); return true; case 'getKnowledgeScope': { const view = (provider as any)._view as vscode.WebviewView | undefined; if (!view) return true; const brain = getActiveBrainProfile(); const brainRoot = brain?.localBrainPath || ''; const scope = resolveScopeForAgent(data.agentPath || '', brainRoot); const folders = scope.folders.map((abs) => ({ absolute: abs, relative: brainRoot ? path.relative(brainRoot, abs) || abs : abs, })); view.webview.postMessage({ type: 'knowledgeScope', value: { agent: scope.agent?.name ?? null, folders, source: scope.source, brainRoot, }, }); void provider._sendReadyStatus(); return true; } case 'editKnowledgeMap': await openKnowledgeMapEditor(); return true; case 'getAgentMap': { const view = (provider as any)._view as vscode.WebviewView | undefined; if (!view) return true; try { const entry = getOrCreateAgentEntry(data.agentPath || ''); const hasWeightOverride = typeof entry.secondBrainWeight === 'number'; const knowledgeMapHasEntry = entry.knowledgeFolders.length > 0 || (entry.skillFolders?.length ?? 0) > 0 || !!(entry.model && entry.model.trim()) || hasWeightOverride; view.webview.postMessage({ type: 'agentMapData', value: { name: entry.name, knowledgeFolders: entry.knowledgeFolders, skillFolders: entry.skillFolders || [], // Per-agent model override — empty string means "use current default model". model: entry.model || '', // null = no override (fall back to global slider); number = pinned 0–100. secondBrainWeight: hasWeightOverride ? entry.secondBrainWeight : null, exists: knowledgeMapHasEntry, }, }); } catch (e: any) { logError('agent-map: load failed.', { error: e?.message ?? String(e) }); view.webview.postMessage({ type: 'agentMapData', value: { name: '', knowledgeFolders: [], skillFolders: [], model: '', secondBrainWeight: null, exists: false }, }); } return true; } case 'saveAgentMap': { const view = (provider as any)._view as vscode.WebviewView | undefined; if (!view) return true; const agentPath = typeof data.agentPath === 'string' ? data.agentPath : ''; const name = path.basename(agentPath).replace(/\.(md|markdown)$/i, '').trim(); const knowledgeFolders = Array.isArray(data.knowledgeFolders) ? data.knowledgeFolders.filter((f: unknown) => typeof f === 'string') : []; const skillFolders = Array.isArray(data.skillFolders) ? data.skillFolders.filter((f: unknown) => typeof f === 'string') : []; // Treat blank / "Use current model" as no override — drop the field entirely // so the JSON stays clean and the resolver falls back to the global default. const modelOverride = typeof data.model === 'string' ? data.model.trim() : ''; // null / undefined / non-finite = "Use global setting" → drop the field. let weightOverride: number | undefined; if (typeof data.secondBrainWeight === 'number' && Number.isFinite(data.secondBrainWeight)) { weightOverride = Math.max(0, Math.min(100, Math.round(data.secondBrainWeight))); } const result = upsertAgentEntry({ name, knowledgeFolders, skillFolders, model: modelOverride || undefined, secondBrainWeight: weightOverride, }); view.webview.postMessage({ type: 'agentMapSaved', value: { ok: result.ok, path: result.path, error: result.error }, }); return true; } case 'pickPath': { const view = (provider as any)._view as vscode.WebviewView | undefined; if (!view) return true; const kind = data.kind === 'skillFile' ? 'skillFile' : data.kind === 'skillFolder' ? 'skillFolder' : 'knowledgeFolder'; const isFile = kind === 'skillFile'; const label = kind === 'knowledgeFolder' ? 'Select Knowledge Folder' : kind === 'skillFolder' ? 'Select Skill Folder' : 'Select Skill File (.md)'; const defaultUri = (() => { if (kind === 'knowledgeFolder') { const brain = getActiveBrainProfile(); if (brain?.localBrainPath) return vscode.Uri.file(brain.localBrainPath); } return undefined; })(); try { const picked = await vscode.window.showOpenDialog({ canSelectFiles: isFile, canSelectFolders: !isFile, canSelectMany: false, openLabel: label, defaultUri, filters: isFile ? { Markdown: ['md', 'markdown'] } : undefined, }); const fsPath = picked?.[0]?.fsPath || ''; if (fsPath) { view.webview.postMessage({ type: 'pickedPath', value: { kind, path: fsPath }, }); } } catch (e: any) { logError('agent-map: pick failed.', { kind, error: e?.message ?? String(e) }); } return true; } default: return false; } }