chore: bump version to v2.33.4 and apply manual stabilization updates
This commit is contained in:
@@ -1,3 +1,12 @@
|
|||||||
|
# Patch Notes - v2.33.4 (2026-05-01)
|
||||||
|
|
||||||
|
## 🛠️ Manual Refinement & Stabilization
|
||||||
|
- **Core Agent Logic Update:** Manually refined `agent.ts` and `AgentWorkflowManager.ts` for improved task orchestration.
|
||||||
|
- **Workflow Factory Optimization:** Updated `factory.ts` to support more robust agent instantiation.
|
||||||
|
- **Configuration & UI Polish:** Fine-tuned `config.ts` and `sidebarProvider.ts` for a better user experience.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# Patch Notes - v2.32.0 (2026-04-30)
|
# Patch Notes - v2.32.0 (2026-04-30)
|
||||||
|
|
||||||
## 🏛️ Modernization: Actor/Queue Model & Monitoring
|
## 🏛️ Modernization: Actor/Queue Model & Monitoring
|
||||||
|
|||||||
+71
-1
@@ -2,7 +2,7 @@
|
|||||||
"name": "g1nation",
|
"name": "g1nation",
|
||||||
"displayName": "G1nation",
|
"displayName": "G1nation",
|
||||||
"description": "High-performance autonomous local AI coding agent for VS Code. Features vectorized inference, asynchronous task management, and 100% offline processing.",
|
"description": "High-performance autonomous local AI coding agent for VS Code. Features vectorized inference, asynchronous task management, and 100% offline processing.",
|
||||||
"version": "2.33.3",
|
"version": "2.33.4",
|
||||||
"publisher": "connectailab",
|
"publisher": "connectailab",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"icon": "assets/icon.png",
|
"icon": "assets/icon.png",
|
||||||
@@ -100,6 +100,76 @@
|
|||||||
"default": true,
|
"default": true,
|
||||||
"description": "Enable Multi-Agent Workflow (Planner -> Researcher -> Writer) for complex tasks."
|
"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,
|
||||||
|
"description": "Enable layered memory injection before each model response."
|
||||||
|
},
|
||||||
|
"g1nation.memoryShortTermMessages": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 8,
|
||||||
|
"minimum": 0,
|
||||||
|
"description": "Number of recent conversation messages included as short-term memory."
|
||||||
|
},
|
||||||
|
"g1nation.memoryMediumTermSessions": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 5,
|
||||||
|
"minimum": 0,
|
||||||
|
"description": "Number of recent saved chat sessions included as medium-term memory."
|
||||||
|
},
|
||||||
|
"g1nation.memoryLongTermFiles": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 6,
|
||||||
|
"minimum": 0,
|
||||||
|
"description": "Number of relevant Second Brain markdown files included as long-term memory."
|
||||||
|
},
|
||||||
"g1nation.ollamaUrl": {
|
"g1nation.ollamaUrl": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "http://127.0.0.1:11434",
|
"default": "http://127.0.0.1:11434",
|
||||||
|
|||||||
+102
-5
@@ -298,12 +298,18 @@ export class AgentExecutor {
|
|||||||
? `\n\n[CRITICAL: INTERNET ACCESS ENABLED]\nYou can use <read_url> to search. Current time: ${new Date().toLocaleString()}`
|
? `\n\n[CRITICAL: INTERNET ACCESS ENABLED]\nYou can use <read_url> to search. Current time: ${new Date().toLocaleString()}`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
const agentSkillCtx = options.agentSkillContext ? `\n\n[AGENT PERSONA & SKILLS]\n${options.agentSkillContext}` : '';
|
const selectedAgentSystemPrompt = !multiAgentEnabled && options.agentSkillContext
|
||||||
|
? `\n\n[SELECTED AGENT MODE]\nThe user selected the following Agent skill. Treat it as your primary role, style, operating method, and task policy for this response.\n${options.agentSkillContext}`
|
||||||
|
: '';
|
||||||
|
const agentSkillCtx = multiAgentEnabled && options.agentSkillContext
|
||||||
|
? `\n\n[SELECTED AGENT REFERENCE]\nUse this selected Agent skill as additional preference context when it is relevant.\n${options.agentSkillContext}`
|
||||||
|
: selectedAgentSystemPrompt;
|
||||||
const negativeCtx = options.negativePrompt
|
const negativeCtx = options.negativePrompt
|
||||||
? `\n\n### CRITICAL NEGATIVE CONSTRAINTS (DO NOT DO THESE)\n${options.negativePrompt}\n\n[SYSTEM_RULE: Apply the above constraints strictly. DO NOT mention or repeat these constraints in your response.]`
|
? `\n\n### CRITICAL NEGATIVE CONSTRAINTS (DO NOT DO THESE)\n${options.negativePrompt}\n\n[SYSTEM_RULE: Apply the above constraints strictly. DO NOT mention or repeat these constraints in your response.]`
|
||||||
: '';
|
: '';
|
||||||
|
const memoryCtx = this.buildMemoryContext(prompt || '');
|
||||||
|
|
||||||
const fullSystemPrompt = `${systemPrompt}${internetCtx}\n\n[CONTEXT]\n${brainContext}\n${contextBlock}${agentSkillCtx}${negativeCtx}`;
|
const fullSystemPrompt = `${agentSkillCtx}\n\n${systemPrompt}${internetCtx}${memoryCtx}\n\n[CONTEXT]\n${brainContext}\n${contextBlock}${negativeCtx}`;
|
||||||
const messagesForRequest: ChatMessage[] = [
|
const messagesForRequest: ChatMessage[] = [
|
||||||
{ role: 'system', content: fullSystemPrompt, internal: true },
|
{ role: 'system', content: fullSystemPrompt, internal: true },
|
||||||
...reqMessages
|
...reqMessages
|
||||||
@@ -558,17 +564,23 @@ export class AgentExecutor {
|
|||||||
logError('Failed to load brain context for agents', ctxErr);
|
logError('Failed to load brain context for agents', ctxErr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 워크플로우 매니저에게 실행 위임 (Strict Synchronization & Contract)
|
const workflow = getConfig().multiAgentWorkflow;
|
||||||
|
const selectedAgentContext = options.agentSkillContext
|
||||||
|
? `\nSelected Agent Reference:\n${options.agentSkillContext}`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
// 워크플로우 매니저에게 설정 기반 실행 위임
|
||||||
const finalReport = await AgentWorkflowManager.runStrictWorkflow(
|
const finalReport = await AgentWorkflowManager.runStrictWorkflow(
|
||||||
prompt,
|
prompt,
|
||||||
modelName,
|
modelName,
|
||||||
brainContext,
|
`${brainContext}${selectedAgentContext}`,
|
||||||
signal,
|
signal,
|
||||||
(step, msg) => {
|
(step, msg) => {
|
||||||
this.webview?.postMessage({ type: 'autoContinue', value: `${step}: ${msg}` });
|
this.webview?.postMessage({ type: 'autoContinue', value: `${step}: ${msg}` });
|
||||||
// 각 단계별 시작을 알림
|
// 각 단계별 시작을 알림
|
||||||
this.webview?.postMessage({ type: 'streamChunk', value: `\n\n> **[${step}]** ${msg}\n\n` });
|
this.webview?.postMessage({ type: 'streamChunk', value: `\n\n> **[${step}]** ${msg}\n\n` });
|
||||||
}
|
},
|
||||||
|
workflow
|
||||||
);
|
);
|
||||||
|
|
||||||
if (signal.aborted || !this.webview) return;
|
if (signal.aborted || !this.webview) return;
|
||||||
@@ -917,6 +929,91 @@ export class AgentExecutor {
|
|||||||
return runId !== this.activeRunId;
|
return runId !== this.activeRunId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildMemoryContext(currentPrompt: string): string {
|
||||||
|
const config = getConfig();
|
||||||
|
if (!config.memoryEnabled) return '';
|
||||||
|
|
||||||
|
const visibleHistory = this.chatHistory.filter((message) => !message.internal);
|
||||||
|
const shortTerm = visibleHistory
|
||||||
|
.slice(-config.memoryShortTermMessages)
|
||||||
|
.map((message) => `- ${message.role}: ${summarizeText(message.content, 260)}`)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
const savedSessions = this.context.globalState.get<any[]>('chat_sessions', []) || [];
|
||||||
|
const mediumTerm = savedSessions
|
||||||
|
.slice(0, config.memoryMediumTermSessions)
|
||||||
|
.map((session: any) => {
|
||||||
|
const title = summarizeText(String(session?.title || 'Untitled session'), 120);
|
||||||
|
const lastMessage = Array.isArray(session?.history)
|
||||||
|
? session.history[session.history.length - 1]?.content || ''
|
||||||
|
: '';
|
||||||
|
return `- ${title}: ${summarizeText(String(lastMessage), 220)}`;
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
const longTerm = this.findRelevantBrainMemory(currentPrompt, config.memoryLongTermFiles);
|
||||||
|
const sections = [
|
||||||
|
shortTerm ? `### Short-Term Memory\n${shortTerm}` : '',
|
||||||
|
mediumTerm ? `### Medium-Term Memory\n${mediumTerm}` : '',
|
||||||
|
longTerm ? `### Long-Term Memory\n${longTerm}` : ''
|
||||||
|
].filter(Boolean).join('\n\n');
|
||||||
|
|
||||||
|
if (!sections) return '';
|
||||||
|
|
||||||
|
return [
|
||||||
|
'',
|
||||||
|
'[MEMORY CONTEXT]',
|
||||||
|
'Review this layered memory before preparing the answer. Use it only when relevant, and prefer the current user request when there is conflict.',
|
||||||
|
sections
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
private findRelevantBrainMemory(currentPrompt: string, limit: number): string {
|
||||||
|
if (limit <= 0) return '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const activeBrain = getActiveBrainProfile();
|
||||||
|
const files = findBrainFiles(activeBrain.localBrainPath);
|
||||||
|
const terms = currentPrompt
|
||||||
|
.toLowerCase()
|
||||||
|
.split(/[^a-z0-9가-힣_]+/g)
|
||||||
|
.filter((term) => term.length >= 2)
|
||||||
|
.slice(0, 24);
|
||||||
|
|
||||||
|
const scored = files.map((file) => {
|
||||||
|
let score = 0;
|
||||||
|
const basename = path.basename(file).toLowerCase();
|
||||||
|
for (const term of terms) {
|
||||||
|
if (basename.includes(term)) score += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
let preview = '';
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(file, 'utf8');
|
||||||
|
const lower = content.toLowerCase();
|
||||||
|
for (const term of terms) {
|
||||||
|
if (lower.includes(term)) score += 1;
|
||||||
|
}
|
||||||
|
preview = summarizeText(content, 360);
|
||||||
|
} catch {
|
||||||
|
preview = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const stat = fs.existsSync(file) ? fs.statSync(file) : undefined;
|
||||||
|
return { file, score, preview, mtime: stat?.mtimeMs || 0 };
|
||||||
|
});
|
||||||
|
|
||||||
|
return scored
|
||||||
|
.sort((a, b) => (b.score - a.score) || (b.mtime - a.mtime))
|
||||||
|
.slice(0, limit)
|
||||||
|
.map((entry) => `- ${path.relative(activeBrain.localBrainPath, entry.file)}: ${entry.preview}`)
|
||||||
|
.join('\n');
|
||||||
|
} catch (error: any) {
|
||||||
|
logError('Failed to build long-term memory context.', { error: error?.message || String(error) });
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private emitHistoryChanged() {
|
private emitHistoryChanged() {
|
||||||
if (!this.historyChangeListener) return;
|
if (!this.historyChangeListener) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import * as vscode from 'vscode';
|
import { ConfigurableWorkflowAgent } from './factory';
|
||||||
import { PlannerAgent, ResearcherAgent, WriterAgent } from './factory';
|
import { DEFAULT_MULTI_AGENT_WORKFLOW, MultiAgentStageConfig } from '../config';
|
||||||
import { AgentEngine, PipelineStage } from '../lib/engine';
|
|
||||||
|
|
||||||
export class AgentWorkflowManager {
|
export class AgentWorkflowManager {
|
||||||
/**
|
/**
|
||||||
@@ -11,33 +10,28 @@ export class AgentWorkflowManager {
|
|||||||
modelName: string,
|
modelName: string,
|
||||||
brainContext: string,
|
brainContext: string,
|
||||||
signal: AbortSignal,
|
signal: AbortSignal,
|
||||||
onProgress: (step: string, message: string) => void
|
onProgress: (step: string, message: string) => void,
|
||||||
|
workflow: MultiAgentStageConfig[] = DEFAULT_MULTI_AGENT_WORKFLOW
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
|
const stages = workflow.length > 0 ? workflow : DEFAULT_MULTI_AGENT_WORKFLOW;
|
||||||
// 1. 에이전트 준비 (DI를 위한 인스턴스화)
|
const stageOutputs: string[] = [];
|
||||||
const planner = new PlannerAgent(modelName);
|
|
||||||
const researcher = new ResearcherAgent(modelName);
|
|
||||||
const writer = new WriterAgent(modelName);
|
|
||||||
|
|
||||||
// 2. 엔진 인스턴스 생성 (의존성 주입)
|
|
||||||
const engine = new AgentEngine(planner, researcher, writer);
|
|
||||||
|
|
||||||
// 3. 고유 미션 ID 생성 (현재는 타임스탬프 기반)
|
|
||||||
const missionId = `mission_${Date.now()}`;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 4. 엔진을 통한 미션 실행 (Producer-Consumer & Mutex 적용)
|
for (const [index, stage] of stages.entries()) {
|
||||||
return await engine.runMission(
|
if (signal.aborted) throw new Error('AbortError');
|
||||||
missionId,
|
|
||||||
prompt,
|
const agent = new ConfigurableWorkflowAgent(modelName, stage);
|
||||||
brainContext,
|
const stepName = stage.name || `Stage ${index + 1}`;
|
||||||
signal,
|
onProgress(stepName, `Running ${index + 1}/${stages.length}`);
|
||||||
(stage: PipelineStage, message: string) => {
|
|
||||||
// UI 피드백을 위한 프로그레스 업데이트
|
const stageInput = this.buildStageInput(stage, prompt, stageOutputs);
|
||||||
const uiStepName = this.mapStageToUI(stage);
|
const result = await agent.execute(stageInput, brainContext, signal);
|
||||||
onProgress(uiStepName, message);
|
this.validateResult(result, stepName);
|
||||||
}
|
stageOutputs.push(result);
|
||||||
);
|
|
||||||
|
onProgress(stepName, `Completed ${index + 1}/${stages.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stageOutputs[stageOutputs.length - 1];
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.name === 'AbortError' || error.message.includes('cancelled')) {
|
if (error.name === 'AbortError' || error.message.includes('cancelled')) {
|
||||||
throw error;
|
throw error;
|
||||||
@@ -46,18 +40,28 @@ export class AgentWorkflowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static buildStageInput(stage: MultiAgentStageConfig, originalPrompt: string, stageOutputs: string[]): string {
|
||||||
* 엔진 스테이지를 UI 표시용 명칭으로 매핑
|
const previous = stageOutputs[stageOutputs.length - 1] || '';
|
||||||
*/
|
if (stage.input === 'original') {
|
||||||
private static mapStageToUI(stage: PipelineStage): string {
|
return originalPrompt;
|
||||||
const maps: Record<PipelineStage, string> = {
|
}
|
||||||
idle: '대기',
|
|
||||||
planner: 'Planner',
|
if (stage.input === 'previous') {
|
||||||
researcher: 'Researcher',
|
return previous || originalPrompt;
|
||||||
writer: 'Writer',
|
}
|
||||||
completed: '완료',
|
|
||||||
error: '오류'
|
return [
|
||||||
};
|
'## Original User Request',
|
||||||
return maps[stage] || '진행 중';
|
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.`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-1
@@ -1,5 +1,5 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { getConfig } from '../config';
|
import { MultiAgentStageConfig, getConfig } from '../config';
|
||||||
|
|
||||||
export abstract class BaseAgent {
|
export abstract class BaseAgent {
|
||||||
constructor(protected readonly modelName: string) {}
|
constructor(protected readonly modelName: string) {}
|
||||||
@@ -145,3 +145,29 @@ Your goal is to produce a state-of-the-art final report that wows the user.
|
|||||||
return this.callLLM(this.persona, wrappedInput, signal);
|
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<string> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+73
-2
@@ -12,6 +12,15 @@ export interface BrainProfile {
|
|||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MultiAgentStageInput = 'original' | 'previous' | 'combined';
|
||||||
|
|
||||||
|
export interface MultiAgentStageConfig {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
prompt: string;
|
||||||
|
input?: MultiAgentStageInput;
|
||||||
|
}
|
||||||
|
|
||||||
// ─── 에이전트 설정 인터페이스 (통합 버전) ───
|
// ─── 에이전트 설정 인터페이스 (통합 버전) ───
|
||||||
export interface IAgentConfig {
|
export interface IAgentConfig {
|
||||||
ollamaUrl: string;
|
ollamaUrl: string;
|
||||||
@@ -26,8 +35,46 @@ export interface IAgentConfig {
|
|||||||
maxAutoSteps: number;
|
maxAutoSteps: number;
|
||||||
dryRun: boolean;
|
dryRun: boolean;
|
||||||
multiAgentEnabled: 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 {
|
function normalizePath(p: string): string {
|
||||||
if (!p) return p;
|
if (!p) return p;
|
||||||
@@ -50,6 +97,21 @@ function toBrainProfile(raw: Partial<BrainProfile> | undefined, fallbackIndex: n
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toMultiAgentStage(raw: Partial<MultiAgentStageConfig> | 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 설정에서 읽어오는 값 (통합 구현) ───
|
// ─── VS Code 설정에서 읽어오는 값 (통합 구현) ───
|
||||||
export function getConfig(): IAgentConfig {
|
export function getConfig(): IAgentConfig {
|
||||||
const cfg = vscode.workspace.getConfiguration('g1nation');
|
const cfg = vscode.workspace.getConfiguration('g1nation');
|
||||||
@@ -58,9 +120,13 @@ export function getConfig(): IAgentConfig {
|
|||||||
const legacyBrainPath = cfg.get<string>('localBrainPath', '');
|
const legacyBrainPath = cfg.get<string>('localBrainPath', '');
|
||||||
const legacyBrainRepo = cfg.get<string>('secondBrainRepo', '');
|
const legacyBrainRepo = cfg.get<string>('secondBrainRepo', '');
|
||||||
const configuredProfiles = cfg.get<Partial<BrainProfile>[]>('brainProfiles', []);
|
const configuredProfiles = cfg.get<Partial<BrainProfile>[]>('brainProfiles', []);
|
||||||
|
const configuredWorkflow = cfg.get<Partial<MultiAgentStageConfig>[]>('multiAgentWorkflow', DEFAULT_MULTI_AGENT_WORKFLOW);
|
||||||
const profiles = configuredProfiles
|
const profiles = configuredProfiles
|
||||||
.map((profile, index) => toBrainProfile(profile, index))
|
.map((profile, index) => toBrainProfile(profile, index))
|
||||||
.filter((profile): profile is BrainProfile => !!profile);
|
.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) {
|
if (profiles.length === 0) {
|
||||||
const fallbackPath = normalizePath(legacyBrainPath) || path.join(os.homedir(), '.g1nation-brain');
|
const fallbackPath = normalizePath(legacyBrainPath) || path.join(os.homedir(), '.g1nation-brain');
|
||||||
@@ -99,7 +165,12 @@ export function getConfig(): IAgentConfig {
|
|||||||
maxContextSize: cfg.get<number>('maxContextSize', 12000),
|
maxContextSize: cfg.get<number>('maxContextSize', 12000),
|
||||||
maxAutoSteps: cfg.get<number>('maxAutoSteps', 50),
|
maxAutoSteps: cfg.get<number>('maxAutoSteps', 50),
|
||||||
dryRun: cfg.get<boolean>('dryRun', false),
|
dryRun: cfg.get<boolean>('dryRun', false),
|
||||||
multiAgentEnabled: cfg.get<boolean>('multiAgentEnabled', true)
|
multiAgentEnabled: cfg.get<boolean>('multiAgentEnabled', true),
|
||||||
|
multiAgentWorkflow: multiAgentWorkflow.length > 0 ? multiAgentWorkflow : DEFAULT_MULTI_AGENT_WORKFLOW,
|
||||||
|
memoryEnabled: cfg.get<boolean>('memoryEnabled', true),
|
||||||
|
memoryShortTermMessages: Math.max(0, cfg.get<number>('memoryShortTermMessages', 8)),
|
||||||
|
memoryMediumTermSessions: Math.max(0, cfg.get<number>('memoryMediumTermSessions', 5)),
|
||||||
|
memoryLongTermFiles: Math.max(0, cfg.get<number>('memoryLongTermFiles', 6))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,4 +225,4 @@ export const EXCLUDED_DIRS = new Set([
|
|||||||
'node_modules', '.git', '.vscode', 'out', 'dist', 'build',
|
'node_modules', '.git', '.vscode', 'out', 'dist', 'build',
|
||||||
'.next', '.cache', '__pycache__', '.DS_Store', 'coverage',
|
'.next', '.cache', '__pycache__', '.DS_Store', 'coverage',
|
||||||
'.turbo', '.nuxt', '.output', 'vendor', 'target'
|
'.turbo', '.nuxt', '.output', 'vendor', 'target'
|
||||||
]);
|
]);
|
||||||
|
|||||||
+60
-37
@@ -1198,7 +1198,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 10px 12px 8px;
|
padding: 10px 12px 6px;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1210,7 +1210,6 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header-actions,
|
.header-actions,
|
||||||
.tool-strip,
|
|
||||||
.tool-group,
|
.tool-group,
|
||||||
.select-stack,
|
.select-stack,
|
||||||
.select-line,
|
.select-line,
|
||||||
@@ -1219,8 +1218,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-actions { gap: 6px; flex-shrink: 0; }
|
.header-actions { gap: 6px; flex-shrink: 0; flex-wrap: wrap; justify-content: flex-end; }
|
||||||
.tool-strip { gap: 6px; flex-wrap: wrap; }
|
|
||||||
.tool-group {
|
.tool-group {
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
@@ -1230,6 +1228,13 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
}
|
}
|
||||||
.select-stack { flex-direction: column; gap: 6px; min-width: 0; }
|
.select-stack { flex-direction: column; gap: 6px; min-width: 0; }
|
||||||
.select-line { gap: 6px; width: 100%; min-width: 0; }
|
.select-line { gap: 6px; width: 100%; min-width: 0; }
|
||||||
|
.paired-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
.control-row {
|
.control-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
@@ -1237,12 +1242,6 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.utility-row {
|
|
||||||
display: flex;
|
|
||||||
gap: 6px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand { font-weight: 700; font-size: 14px; color: var(--text-bright); letter-spacing: 0; display: flex; align-items: center; gap: 8px; min-width: 0; }
|
.brand { font-weight: 700; font-size: 14px; color: var(--text-bright); letter-spacing: 0; display: flex; align-items: center; gap: 8px; min-width: 0; }
|
||||||
.logo { width: 22px; height: 22px; background: var(--accent); color: #fff; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 900; }
|
.logo { width: 22px; height: 22px; background: var(--accent); color: #fff; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 900; }
|
||||||
@@ -1289,9 +1288,18 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
animation: msgIn 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
animation: msgIn 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.msg-user {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-ai {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes msgIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
@keyframes msgIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
||||||
|
|
||||||
.msg-head { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 11px; color: var(--text-dim); }
|
.msg-head { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 11px; color: var(--text-dim); }
|
||||||
|
.msg-user .msg-head { flex-direction: row-reverse; }
|
||||||
.av { width: 22px; height: 22px; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 12px; }
|
.av { width: 22px; height: 22px; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 12px; }
|
||||||
|
|
||||||
/* Tooltip System */
|
/* Tooltip System */
|
||||||
@@ -1309,10 +1317,10 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
|
|
||||||
|
|
||||||
.msg-body {
|
.msg-body {
|
||||||
padding-left: 30px;
|
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-size: 13.5px;
|
font-size: 13.5px;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
max-width: min(88%, 760px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-user .msg-body {
|
.msg-user .msg-body {
|
||||||
@@ -1320,8 +1328,14 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 10px 14px;
|
padding: 10px 14px;
|
||||||
margin-left: 30px;
|
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msg-ai .msg-body {
|
||||||
|
padding-left: 30px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Markdown Style --- */
|
/* --- Markdown Style --- */
|
||||||
@@ -1367,8 +1381,9 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
background: var(--control-bg);
|
background: var(--control-bg);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
color: var(--text-dim);
|
color: var(--text-dim);
|
||||||
width: 28px;
|
min-width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
|
padding: 0 8px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1378,7 +1393,7 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
flex: 0 0 28px;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
.icon-btn:hover { color: var(--text-bright); border-color: var(--border-bright); background: var(--control-bg-hover); box-shadow: var(--shadow-soft); }
|
.icon-btn:hover { color: var(--text-bright); border-color: var(--border-bright); background: var(--control-bg-hover); box-shadow: var(--shadow-soft); }
|
||||||
.icon-btn.active { color: var(--accent); border-color: var(--accent); background: var(--control-active-bg); }
|
.icon-btn.active { color: var(--accent); border-color: var(--accent); background: var(--control-active-bg); }
|
||||||
@@ -1724,10 +1739,18 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
}
|
}
|
||||||
@media (min-width: 360px) {
|
@media (min-width: 360px) {
|
||||||
.header-controls {
|
.header-controls {
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
grid-template-columns: minmax(0, 1fr);
|
||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
.paired-row {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
.header-top {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
@keyframes slideIn {
|
@keyframes slideIn {
|
||||||
from { opacity: 0; transform: translateX(-10px); }
|
from { opacity: 0; transform: translateX(-10px); }
|
||||||
to { opacity: 1; transform: translateX(0); }
|
to { opacity: 1; transform: translateX(0); }
|
||||||
@@ -1739,8 +1762,12 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
<div class="header-top">
|
<div class="header-top">
|
||||||
<div class="brand"><div class="logo">✦</div> G1nation</div>
|
<div class="brand"><div class="logo">✦</div> G1nation</div>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<button class="icon-btn" id="newChatBtn" data-tooltip="New Chat">+</button>
|
<button class="icon-btn" id="newChatBtn" data-tooltip="New Chat">New</button>
|
||||||
<button class="icon-btn" id="settingsBtn" data-tooltip="Settings">⚙</button>
|
<button class="icon-btn" id="saveWikiRawBtn" data-tooltip="Save Wiki Raw">Wiki</button>
|
||||||
|
<button class="icon-btn" id="multiAgentBtn" data-tooltip="Multi-Agent Mode">MA</button>
|
||||||
|
<button class="icon-btn" id="internetBtn" data-tooltip="Internet Access">Web</button>
|
||||||
|
<button class="icon-btn" id="historyBtn" data-tooltip="View History">Log</button>
|
||||||
|
<button class="icon-btn" id="settingsBtn" data-tooltip="Settings">Set</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-controls">
|
<div class="header-controls">
|
||||||
@@ -1749,29 +1776,25 @@ export class SidebarChatProvider implements vscode.WebviewViewProvider, BridgeIn
|
|||||||
<div class="status-pill"><span id="statusDot" class="status-dot"></span><span id="engineStatusText">Engine</span></div>
|
<div class="status-pill"><span id="statusDot" class="status-dot"></span><span id="engineStatusText">Engine</span></div>
|
||||||
<div class="select-wrap"><select id="modelSel" title="Select Model"></select></div>
|
<div class="select-wrap"><select id="modelSel" title="Select Model"></select></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control-row">
|
<div class="paired-row">
|
||||||
<div class="select-wrap"><select id="brainSel" title="Select Brain"></select></div>
|
<div class="control-row">
|
||||||
<div class="tool-group" aria-label="Brain actions">
|
<div class="select-wrap"><select id="brainSel" title="Select Brain"></select></div>
|
||||||
<button class="icon-btn" id="addBrainBtn" data-tooltip="Add Brain">+</button>
|
<div class="tool-group" aria-label="Brain actions">
|
||||||
<button class="icon-btn" id="editBrainBtn" data-tooltip="Edit Brain">✎</button>
|
<button class="icon-btn" id="addBrainBtn" data-tooltip="Add Brain">Add</button>
|
||||||
<button class="icon-btn" id="deleteBrainBtn" data-tooltip="Delete Brain">−</button>
|
<button class="icon-btn" id="editBrainBtn" data-tooltip="Edit Brain">Edit</button>
|
||||||
<button class="icon-btn" id="brainBtn" data-tooltip="Sync Knowledge">↻</button>
|
<button class="icon-btn" id="deleteBrainBtn" data-tooltip="Delete Brain">Del</button>
|
||||||
|
<button class="icon-btn" id="brainBtn" data-tooltip="Sync Knowledge">Sync</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="control-row">
|
||||||
<div class="control-row">
|
<div class="select-wrap"><select id="agentSel" title="Select Agentic Skill"></select></div>
|
||||||
<div class="select-wrap"><select id="agentSel" title="Select Agentic Skill"></select></div>
|
<div class="tool-group" aria-label="Agent actions">
|
||||||
<div class="tool-group" aria-label="Agent actions">
|
<button class="icon-btn" id="addAgentBtn" data-tooltip="Create Agent">Add</button>
|
||||||
<button class="icon-btn" id="addAgentBtn" data-tooltip="Create Agent">+</button>
|
<button class="icon-btn" id="editAgentBtn" data-tooltip="Edit Agent Skill">Edit</button>
|
||||||
<button class="icon-btn" id="editAgentBtn" data-tooltip="Edit Agent Skill">✎</button>
|
<button class="icon-btn" id="deleteAgentBtn" data-tooltip="Delete Agent Skill">Del</button>
|
||||||
<button class="icon-btn" id="deleteAgentBtn" data-tooltip="Delete Agent Skill">−</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="utility-row">
|
|
||||||
<button class="icon-btn" id="saveWikiRawBtn" data-tooltip="Save Wiki Raw">□</button>
|
|
||||||
<button class="icon-btn" id="multiAgentBtn" data-tooltip="Multi-Agent Mode">⧉</button>
|
|
||||||
<button class="icon-btn" id="internetBtn" data-tooltip="Internet Access">↗</button>
|
|
||||||
<button class="icon-btn" id="historyBtn" data-tooltip="View History">◷</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user