diff --git a/package.json b/package.json index 49743f4..5de2492 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "g1nation", "displayName": "G1nation", "description": "High-performance autonomous local AI coding agent for VS Code. Features vectorized inference, asynchronous task management, and 100% offline processing.", - "version": "2.38.0", + "version": "2.39.0", "publisher": "connectailab", "license": "MIT", "icon": "assets/icon.png", diff --git a/src/agent.ts b/src/agent.ts index ad73f59..6bf3b32 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -264,6 +264,9 @@ export class AgentExecutor { activeBrain.description ? `Description: ${activeBrain.description}` : '', brainPreview ? `Available file examples:\n${brainPreview}` : 'Files: none found' ].filter(Boolean).join('\n'); + const brainInventoryCtx = prompt && this.isSecondBrainInventoryRequest(prompt) + ? `\n\n${this.buildSecondBrainInventoryContext(activeBrain, brainFiles)}` + : ''; const editor = vscode.window.activeTextEditor; if (editor && editor.document.uri.scheme === 'file') { const text = editor.document.getText(); @@ -332,7 +335,7 @@ export class AgentExecutor { : ''; const memoryCtx = this.buildMemoryContext(prompt || '', activeBrain); - const fullSystemPrompt = `${agentSkillCtx}\n\n${systemPrompt}${internetCtx}${memoryCtx}${designerCtx}${secondBrainTraceCtx}\n\n[CONTEXT]\n${brainContext}\n${contextBlock}${negativeCtx}`; + const fullSystemPrompt = `${agentSkillCtx}\n\n${systemPrompt}${internetCtx}${memoryCtx}${designerCtx}${secondBrainTraceCtx}\n\n[CONTEXT]\n${brainContext}${brainInventoryCtx}\n${contextBlock}${negativeCtx}`; const messagesForRequest: ChatMessage[] = [ { role: 'system', content: fullSystemPrompt, internal: true }, ...reqMessages @@ -428,13 +431,10 @@ export class AgentExecutor { ? renderSecondBrainTraceMarkdown(secondBrainTrace, !!options.secondBrainTraceDebug) : ''; const finalAssistantContent = traceMarkdown ? `${assistantContent}\n${traceMarkdown}` : assistantContent; - const assistantMessage: ChatMessage = { role: 'assistant', content: finalAssistantContent, internal: false, rationale }; - this.chatHistory.push(assistantMessage); - + this.statusBarManager.updateStatus(AgentStatus.Executing); const report = await this.executeActions(aiResponseText, rootPath, activeBrain); if (!assistantContent.trim() && report.length === 0) { - this.chatHistory.pop(); logError('Model returned an empty response without actions.', { model: actualModel, engine, apiUrl, loopDepth }); this.webview.postMessage({ type: 'error', @@ -476,6 +476,8 @@ export class AgentExecutor { return; } + const assistantMessage: ChatMessage = { role: 'assistant', content: finalAssistantContent, internal: false, rationale }; + this.chatHistory.push(assistantMessage); this.emitHistoryChanged(); this.statusBarManager.updateStatus(AgentStatus.Success); this.webview.postMessage({ type: 'streamChunk', value: finalAssistantContent }); @@ -613,6 +615,47 @@ export class AgentExecutor { return /(second brain|2nd brain|제2뇌|브레인|brain|기억|기록|노트|문서|참고해서|사용해서|검색해서|근거|출처)/i.test(prompt); } + private isSecondBrainInventoryRequest(prompt: string): boolean { + const normalized = prompt.toLowerCase(); + const asksBrain = /(second brain|2nd brain|제2뇌|브레인|brain)/i.test(normalized); + const asksOverview = /(평가|분석|강점|약점|부족|무엇을 할 수|활용|전체|연결된|현재|inside|overview|inventory|strength|weakness)/i.test(normalized); + return asksBrain && asksOverview; + } + + private buildSecondBrainInventoryContext(activeBrain: BrainProfile, brainFiles: string[]): string { + const relativeFiles = brainFiles.map((file) => path.relative(activeBrain.localBrainPath, file)); + const directoryCounts = new Map(); + for (const rel of relativeFiles) { + const topDir = rel.includes(path.sep) ? rel.split(path.sep)[0] : '(root)'; + directoryCounts.set(topDir, (directoryCounts.get(topDir) || 0) + 1); + } + + const topDirectories = [...directoryCounts.entries()] + .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])) + .slice(0, 12) + .map(([dir, count]) => `- ${dir}: ${count} markdown files`) + .join('\n'); + + const samples = relativeFiles + .slice(0, 40) + .map((file) => `- ${file}`) + .join('\n'); + + return [ + '[SECOND BRAIN INVENTORY]', + 'The user is asking about the currently selected Second Brain as a knowledge base. Use this inventory as direct evidence.', + `Selected brain name: ${activeBrain.name}`, + `Selected brain path: ${activeBrain.localBrainPath}`, + `Markdown file count: ${brainFiles.length}`, + brainFiles.length > 0 + ? 'Do not say the Second Brain has no data, no files, or cannot be evaluated because files were not provided.' + : 'No Markdown files were found in the selected Second Brain path.', + topDirectories ? `Top-level distribution:\n${topDirectories}` : 'Top-level distribution: none', + samples ? `Sample files:\n${samples}` : 'Sample files: none', + 'For strengths and weaknesses, infer from the inventory and selected note excerpts. Mark broad conclusions as inference when they are not directly proven.' + ].join('\n'); + } + private isStaleRun(runId: number): boolean { return runId !== this.activeRunId; }