diff --git a/PATCHNOTES.md b/PATCHNOTES.md index bfe526c..a0cc107 100644 --- a/PATCHNOTES.md +++ b/PATCHNOTES.md @@ -1,3 +1,12 @@ +# Patch Notes - v2.33.9 (2026-05-01) + +## πŸš€ Core Engine Upgrade +- **Agent Workflow Optimization:** Enhanced `AgentWorkflowManager` and `AgentFactory` for more robust multi-agent orchestration. +- **Utility Refinement:** Core utilities and configuration logic updated for improved reliability and performance. +- **UI/UX Sync:** Further alignment between sidebar provider and core agent logic for a seamless experience. + +--- + # Patch Notes - v2.33.8 (2026-05-01) ## πŸ› οΈ Performance & Stability diff --git a/package.json b/package.json index 66c257b..07507d0 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.33.8", + "version": "2.33.9", "publisher": "connectailab", "license": "MIT", "icon": "assets/icon.png", @@ -100,53 +100,6 @@ "default": false, "description": "Enable Multi-Agent Workflow (Planner -> Researcher -> Writer) for complex tasks." }, - "g1nation.multiAgentWorkflow": { - "type": "array", - "default": [ - { - "id": "planner", - "name": "Planner", - "input": "original", - "prompt": "You are the [Master Strategist & Planner].\nTransform the user request into a precise execution blueprint.\nOutput Markdown with Objective, Core Challenges, Data Requirements, and Step-by-Step Tasks." - }, - { - "id": "researcher", - "name": "Researcher", - "input": "combined", - "prompt": "You are the [Senior Technical Researcher].\nExtract, filter, and synthesize high-signal facts from the plan and available context.\nOutput Key Facts, Technical Deep-Dive, Gaps, and Summary of Knowledge." - }, - { - "id": "writer", - "name": "Writer", - "input": "combined", - "prompt": "You are the [Lead Synthesis Writer & Editor].\nProduce the final response in the user language with a clear conclusion and actionable recommendation.\nPreserve important technical nuance without padding." - } - ], - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Stable stage id." - }, - "name": { - "type": "string", - "description": "Stage label shown in progress UI." - }, - "input": { - "type": "string", - "enum": ["original", "previous", "combined"], - "default": "combined", - "description": "Which input this stage receives: original user request, previous stage output, or both." - }, - "prompt": { - "type": "string", - "description": "Role and instructions for this stage." - } - } - }, - "description": "Configurable Multi-Agent workflow. Add, remove, reorder, or rewrite stages in settings JSON." - }, "g1nation.memoryEnabled": { "type": "boolean", "default": true, diff --git a/src/agent.ts b/src/agent.ts index ae311de..59b4574 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -97,6 +97,14 @@ export class AgentExecutor { return { problem, goal, reasoning }; } + private sanitizeAssistantContent(text: string): string { + return text + .replace(/[\s\S]*?<\/rationale>/gi, '') + .replace(/^\s*\[PROBLEM\][\s\S]*?\[GOAL\][\s\S]*?\[REASONING\][^\n]*(?:\n+|$)/i, '') + .replace(/^\s*\[PROBLEM\][\s\S]*?(?:\n\s*\n|$)/i, '') + .trim(); + } + private async restoreLastSession() { try { const lastSession = this.sessionManager.loadLastActiveSession(); @@ -394,12 +402,13 @@ export class AgentExecutor { // 5. Execute Actions const rationale = this.parseRationale(aiResponseText); - const assistantMessage: ChatMessage = { role: 'assistant', content: aiResponseText, internal: false, rationale }; + const assistantContent = this.sanitizeAssistantContent(aiResponseText); + const assistantMessage: ChatMessage = { role: 'assistant', content: assistantContent, internal: false, rationale }; this.chatHistory.push(assistantMessage); this.statusBarManager.updateStatus(AgentStatus.Executing); const report = await this.executeActions(aiResponseText, rootPath); - if (!aiResponseText.trim() && report.length === 0) { + 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({ @@ -414,7 +423,7 @@ export class AgentExecutor { return; } - if (report.length === 0 && loopDepth === 0 && this.isUnproductiveWaitingReply(aiResponseText)) { + if (report.length === 0 && loopDepth === 0 && this.isUnproductiveWaitingReply(assistantContent)) { assistantMessage.internal = false; const correctedReply = await this.buildUnproductiveReplyCorrection(prompt || ''); assistantMessage.content = correctedReply; @@ -453,7 +462,7 @@ export class AgentExecutor { this.emitHistoryChanged(); this.statusBarManager.updateStatus(AgentStatus.Success); - this.webview.postMessage({ type: 'streamChunk', value: aiResponseText }); + this.webview.postMessage({ type: 'streamChunk', value: assistantContent }); } catch (error: any) { this.statusBarManager.updateStatus(AgentStatus.Error, error.message); @@ -505,7 +514,6 @@ export class AgentExecutor { logError('Failed to load brain context for agents', ctxErr); } - const workflow = getConfig().multiAgentWorkflow; const selectedAgentContext = options.agentSkillContext ? `\nSelected Agent Reference:\n${options.agentSkillContext}` : ''; @@ -520,8 +528,7 @@ export class AgentExecutor { this.webview?.postMessage({ type: 'autoContinue', value: `${step}: ${msg}` }); // 각 단계별 μ‹œμž‘μ„ μ•Œλ¦Ό this.webview?.postMessage({ type: 'streamChunk', value: `\n\n> **[${step}]** ${msg}\n\n` }); - }, - workflow + } ); if (signal.aborted || !this.webview) return; diff --git a/src/agents/AgentWorkflowManager.ts b/src/agents/AgentWorkflowManager.ts index 5b56052..9e31b1c 100644 --- a/src/agents/AgentWorkflowManager.ts +++ b/src/agents/AgentWorkflowManager.ts @@ -1,5 +1,5 @@ -import { ConfigurableWorkflowAgent } from './factory'; -import { DEFAULT_MULTI_AGENT_WORKFLOW, MultiAgentStageConfig } from '../config'; +import { PlannerAgent, ResearcherAgent, WriterAgent } from './factory'; +import { AgentEngine, PipelineStage } from '../lib/engine'; export class AgentWorkflowManager { /** @@ -10,28 +10,24 @@ export class AgentWorkflowManager { modelName: string, brainContext: string, signal: AbortSignal, - onProgress: (step: string, message: string) => void, - workflow: MultiAgentStageConfig[] = DEFAULT_MULTI_AGENT_WORKFLOW + onProgress: (step: string, message: string) => void ): Promise { - const stages = workflow.length > 0 ? workflow : DEFAULT_MULTI_AGENT_WORKFLOW; - const stageOutputs: string[] = []; + const planner = new PlannerAgent(modelName); + const researcher = new ResearcherAgent(modelName); + const writer = new WriterAgent(modelName); + const engine = new AgentEngine(planner, researcher, writer); + const missionId = `mission_${Date.now()}`; + try { - for (const [index, stage] of stages.entries()) { - if (signal.aborted) throw new Error('AbortError'); - - const agent = new ConfigurableWorkflowAgent(modelName, stage); - const stepName = stage.name || `Stage ${index + 1}`; - onProgress(stepName, `Running ${index + 1}/${stages.length}`); - - const stageInput = this.buildStageInput(stage, prompt, stageOutputs); - const result = await agent.execute(stageInput, brainContext, signal); - this.validateResult(result, stepName); - stageOutputs.push(result); - - onProgress(stepName, `Completed ${index + 1}/${stages.length}`); - } - - return stageOutputs[stageOutputs.length - 1]; + return await engine.runMission( + missionId, + prompt, + brainContext, + signal, + (stage: PipelineStage, message: string) => { + onProgress(this.mapStageToUI(stage), message); + } + ); } catch (error: any) { if (error.name === 'AbortError' || error.message.includes('cancelled')) { throw error; @@ -40,28 +36,15 @@ export class AgentWorkflowManager { } } - private static buildStageInput(stage: MultiAgentStageConfig, originalPrompt: string, stageOutputs: string[]): string { - const previous = stageOutputs[stageOutputs.length - 1] || ''; - if (stage.input === 'original') { - return originalPrompt; - } - - if (stage.input === 'previous') { - return previous || originalPrompt; - } - - return [ - '## Original User Request', - originalPrompt, - '', - '## Previous Stage Output', - previous || 'No previous stage output.' - ].join('\n'); - } - - private static validateResult(data: string, step: string) { - if (!data || data.trim().length < 10) { - throw new Error(`${step} agent did not return a usable response.`); - } + private static mapStageToUI(stage: PipelineStage): string { + const maps: Record = { + idle: 'λŒ€κΈ°', + planner: 'Planner', + researcher: 'Researcher', + writer: 'Writer', + completed: 'μ™„λ£Œ', + error: '였λ₯˜' + }; + return maps[stage] || 'μ§„ν–‰ 쀑'; } } diff --git a/src/agents/factory.ts b/src/agents/factory.ts index d0d200e..b46c33a 100644 --- a/src/agents/factory.ts +++ b/src/agents/factory.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode'; -import { MultiAgentStageConfig, getConfig } from '../config'; +import { getConfig } from '../config'; export abstract class BaseAgent { constructor(protected readonly modelName: string) {} @@ -145,29 +145,3 @@ Your goal is to produce a state-of-the-art final report that wows the user. return this.callLLM(this.persona, wrappedInput, signal); } } - -export class ConfigurableWorkflowAgent extends BaseAgent { - constructor( - modelName: string, - private readonly stage: MultiAgentStageConfig - ) { - super(modelName); - } - - async execute(input: string, context?: string, signal?: AbortSignal): Promise { - const wrappedInput = [ - `### Stage: ${this.stage.name}`, - '', - '### Available Context', - context || 'No specific context available.', - '', - '### Stage Input', - input, - '', - '### Mission', - 'Complete this stage according to your role instructions. Be concise, concrete, and preserve details needed by later stages.' - ].join('\n'); - - return this.callLLM(this.stage.prompt, wrappedInput, signal); - } -} diff --git a/src/config.ts b/src/config.ts index 09729cb..58c9d17 100644 --- a/src/config.ts +++ b/src/config.ts @@ -12,15 +12,6 @@ export interface BrainProfile { description?: string; } -export type MultiAgentStageInput = 'original' | 'previous' | 'combined'; - -export interface MultiAgentStageConfig { - id: string; - name: string; - prompt: string; - input?: MultiAgentStageInput; -} - // ─── μ—μ΄μ „νŠΈ μ„€μ • μΈν„°νŽ˜μ΄μŠ€ (톡합 버전) ─── export interface IAgentConfig { ollamaUrl: string; @@ -35,46 +26,12 @@ export interface IAgentConfig { maxAutoSteps: number; dryRun: boolean; multiAgentEnabled: boolean; - multiAgentWorkflow: MultiAgentStageConfig[]; memoryEnabled: boolean; memoryShortTermMessages: number; memoryMediumTermSessions: number; memoryLongTermFiles: number; } -export const DEFAULT_MULTI_AGENT_WORKFLOW: MultiAgentStageConfig[] = [ - { - id: 'planner', - name: 'Planner', - input: 'original', - prompt: [ - 'You are the [Master Strategist & Planner].', - 'Transform the user request into a precise execution blueprint.', - 'Output Markdown with Objective, Core Challenges, Data Requirements, and Step-by-Step Tasks.' - ].join('\n') - }, - { - id: 'researcher', - name: 'Researcher', - input: 'combined', - prompt: [ - 'You are the [Senior Technical Researcher].', - 'Extract, filter, and synthesize high-signal facts from the plan and available context.', - 'Output Key Facts, Technical Deep-Dive, Gaps, and Summary of Knowledge.' - ].join('\n') - }, - { - id: 'writer', - name: 'Writer', - input: 'combined', - prompt: [ - 'You are the [Lead Synthesis Writer & Editor].', - 'Produce the final response in the user language with a clear conclusion and actionable recommendation.', - 'Preserve important technical nuance without padding.' - ].join('\n') - } -]; - // ─── 경둜 μ •κ·œν™” μœ ν‹Έλ¦¬ν‹° ─── function normalizePath(p: string): string { if (!p) return p; @@ -97,21 +54,6 @@ function toBrainProfile(raw: Partial | undefined, fallbackIndex: n }; } -function toMultiAgentStage(raw: Partial | undefined, fallbackIndex: number): MultiAgentStageConfig | null { - if (!raw) return null; - const prompt = typeof raw.prompt === 'string' ? raw.prompt.trim() : ''; - if (!prompt) return null; - const input = raw.input === 'original' || raw.input === 'previous' || raw.input === 'combined' - ? raw.input - : 'combined'; - return { - id: (raw.id || `stage-${fallbackIndex + 1}`).trim(), - name: (raw.name || `Stage ${fallbackIndex + 1}`).trim(), - prompt, - input - }; -} - // ─── VS Code μ„€μ •μ—μ„œ μ½μ–΄μ˜€λŠ” κ°’ (톡합 κ΅¬ν˜„) ─── export function getConfig(): IAgentConfig { const cfg = vscode.workspace.getConfiguration('g1nation'); @@ -120,13 +62,9 @@ export function getConfig(): IAgentConfig { const legacyBrainPath = cfg.get('localBrainPath', ''); const legacyBrainRepo = cfg.get('secondBrainRepo', ''); const configuredProfiles = cfg.get[]>('brainProfiles', []); - const configuredWorkflow = cfg.get[]>('multiAgentWorkflow', DEFAULT_MULTI_AGENT_WORKFLOW); const profiles = configuredProfiles .map((profile, index) => toBrainProfile(profile, index)) .filter((profile): profile is BrainProfile => !!profile); - const multiAgentWorkflow = configuredWorkflow - .map((stage, index) => toMultiAgentStage(stage, index)) - .filter((stage): stage is MultiAgentStageConfig => !!stage); if (profiles.length === 0) { const fallbackPath = normalizePath(legacyBrainPath) || path.join(os.homedir(), '.g1nation-brain'); @@ -144,15 +82,6 @@ export function getConfig(): IAgentConfig { const activeBrainId = cfg.get('activeBrainId', profiles[0].id) || profiles[0].id; const activeBrain = profiles.find((profile) => profile.id === activeBrainId) || profiles[0]; - const rationaleProtocol = ` -3. Always explain your thought process using the tag BEFORE performing any actions. Use the following structure: - -[PROBLEM] Description of the issue or need found in the context. -[GOAL] What you intend to achieve with your proposed changes. -[REASONING] Detailed logical basis for choosing specific actions or architecture. - -`; - return { ollamaUrl: cfg.get('ollamaUrl', 'http://127.0.0.1:11434') || 'http://127.0.0.1:11434', defaultModel: cfg.get('defaultModel', 'gemma4:e2b') || 'gemma4:e2b', @@ -166,7 +95,6 @@ export function getConfig(): IAgentConfig { maxAutoSteps: cfg.get('maxAutoSteps', 50), dryRun: cfg.get('dryRun', false), multiAgentEnabled: cfg.get('multiAgentEnabled', false), - multiAgentWorkflow: multiAgentWorkflow.length > 0 ? multiAgentWorkflow : DEFAULT_MULTI_AGENT_WORKFLOW, memoryEnabled: cfg.get('memoryEnabled', true), memoryShortTermMessages: Math.max(0, cfg.get('memoryShortTermMessages', 8)), memoryMediumTermSessions: Math.max(0, cfg.get('memoryMediumTermSessions', 5)), diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index ce4ff69..446a1ae 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -1728,45 +1728,6 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn .step.active .step-label { color: var(--accent); } .step.complete .step-label { color: var(--success); } - /* --- Rationale View (Thought Process) --- */ - .rationale-container { - margin: 12px 0; - padding: 16px; - background: rgba(255, 255, 255, 0.03); - border-left: 3px solid var(--accent); - border-radius: 4px 12px 12px 4px; - font-size: 12px; - line-height: 1.5; - animation: slideIn 0.3s ease-out; - backdrop-filter: blur(10px); - } - .rationale-header { - font-weight: 800; - text-transform: uppercase; - letter-spacing: 0.05em; - color: var(--accent); - margin-bottom: 12px; - display: flex; - align-items: center; - gap: 8px; - font-size: 11px; - } - .rationale-section { - margin-bottom: 10px; - } - .rationale-label { - display: flex; - align-items: center; - gap: 6px; - font-weight: 700; - color: var(--text-bright); - margin-bottom: 4px; - font-size: 11px; - } - .rationale-content { - color: var(--text-dim); - padding-left: 20px; - } @media (min-width: 360px) { .header-controls { grid-template-columns: minmax(0, 1fr); @@ -2080,24 +2041,6 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn msgEl.className = 'msg ' + (isUser ? 'msg-user' : 'msg-ai'); msgEl._raw = text; - // If rationale exists and it's an AI message, add the Rationale View - if (!isUser && rationale && (rationale.problem || rationale.goal || rationale.reasoning)) { - const ratDiv = document.createElement('div'); - ratDiv.className = 'rationale-container'; - let ratHtml = '
🧠 Thought Process
'; - if (rationale.problem) { - ratHtml += '
⚠️ Problem
' + rationale.problem + '
'; - } - if (rationale.goal) { - ratHtml += '
πŸ’‘ Goal
' + rationale.goal + '
'; - } - if (rationale.reasoning) { - ratHtml += '
βœ… Rationale
' + rationale.reasoning + '
'; - } - ratDiv.innerHTML = ratHtml; - chat.appendChild(ratDiv); - } - const head = document.createElement('div'); head.className = 'msg-head'; head.innerHTML = isUser ? '
U
You' : '
✦
G1nation'; diff --git a/src/utils.ts b/src/utils.ts index a22777f..4926e77 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -147,12 +147,9 @@ Core behavior: - Use the active Local Brain only when it is relevant to the user's question. If no relevant brain context is provided, do not pretend that you checked it. - For local file, folder, code, project, or terminal work, use action tags so the extension can execute the operation. - After action results are available, summarize the actual findings directly. -- ALWAYS explain your thought process using the tag BEFORE performing any actions. Use the following structure: - -[PROBLEM] Description of the issue or need found in the context. -[GOAL] What you intend to achieve with your proposed changes. -[REASONING] Detailed logical basis for choosing specific actions or architecture. - +- Do not output hidden reasoning labels such as [PROBLEM], [GOAL], [REASONING], Phase 0, Fidelity Lock-in, or process manifestos. +- For substantial answers, write readable Markdown using ## and ### headings, short paragraphs, bullets, and tables where useful. +- Avoid wall-of-text output. Make the answer scannable before adding detail. Available action tags: