Files
connectai/src/sidebar/agentHandlers.ts
T

170 lines
7.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<boolean> {
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 0100.
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;
}
}